Programming Basics SQL HTML CSS JavaScript Python C++ Java JavaFX Swing Problem Solving English English Conversations Computer Fundamentals Learn Typing

JavaFXطريقة إنشاء لعبة Tic Tac Toe

في هذا الدرس ستتعلم طريقة إنشاء لعبة ( Tic Tac Toe ) إحترافية باستخدام إطار الـ JavaFX.

javafx tic tac toe source code تحميل كود لعبة tic tac toe في جافا



مميزات اللعبة

  • يمكن لعب هذه اللعبة مع صديق أو ضد الكمبيوتر نفسه.
  • يمكن تعديل تصميمها بكل سهولة من داخل اللعبة.


بناء اللعبة

  • ملفات الجافا وضعناها مباشرةً في المشروع.
  • الصور وضعناها بداخل مجلد إسمه images.


خيارات التحميل

⇓ تحميل اللعبة ⇓ تحميل المشروع كاملاً ⇓ تحميل مجلد الصور فقط



كود اللعبة

StartPane.java
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.Button;
import javafx.scene.layout.Pane;
// يمثل الحاوية التي سنظهرها عند تشغيل البرنامج StartPane الكلاس
public class StartPane extends Pane {
// هنا قمنا بإنشاء جميع الأشياء التي سنضعها في الحاوية
Button singlePlayer = new Button("Single Player");
Button multiPlayer = new Button("Multi Player");
Button settings = new Button("Settings");
Button about = new Button("About");
Button exit = new Button("Exit");
// about لأننا سنستخدمه لعرض نافذة منبثقة عندما يقوم المستخدم بالنقر على الزر Alert هنا قمنا بإنشاء كائن من الكلاس
Alert alert = new Alert(AlertType.INFORMATION);
// هذا كونستركور الكلاس
public StartPane() {
// StartPane هنا قمنا بتحديد حجم كل شيء سنضيفه في الحاوية التي يمثلها الكائن الذي ننشئه من الكلاس
singlePlayer.setPrefSize(240, 40);
multiPlayer.setPrefSize(240, 40);
settings.setPrefSize(240, 40);
about.setPrefSize(240, 40);
exit.setPrefSize(240, 40);
// StartPane هنا قمنا بتحديد موقع كل شيء سنضيفه في الحاوية التي يمثلها الكائن الذي ننشئه من الكلاس
singlePlayer.setTranslateX(80);
singlePlayer.setTranslateY(110);
multiPlayer.setTranslateX(80);
multiPlayer.setTranslateY(170);
settings.setTranslateX(80);
settings.setTranslateY(230);
about.setTranslateX(80);
about.setTranslateY(290);
exit.setTranslateX(80);
exit.setTranslateY(350);
// StartPane هنا قمنا بإضافة كل شيء قمنا بإنشائه في الحاوية التي يمثلها الكائن الذي ننشئه من الكلاس
getChildren().add(singlePlayer);
getChildren().add(multiPlayer);
getChildren().add(settings);
getChildren().add(about);
getChildren().add(exit);
// singlePlayer هنا قمنا بتحديد ما سيحدث عند النقر على الزر الذي يمثله الكائن
// مكان الحاوية الحالية singlePlayerPane لعرض الحاوية التي يمثلها الكائن viewPane() سيتم إستدعاء الدالة الثابتة
singlePlayer.setOnAction((Action) -> {
AppManager.viewPane(AppManager.singlePlayerPane);
});
// multiPlayer هنا قمنا بتحديد ما سيحدث عند النقر على الزر الذي يمثله الكائن
// مكان الحاوية الحالية multiPlayerPane لعرض الحاوية التي يمثلها الكائن viewPane() سيتم إستدعاء الدالة الثابتة
multiPlayer.setOnAction((Action) -> {
AppManager.viewPane(AppManager.multiPlayerPane);
});
// settings هنا قمنا بتحديد ما سيحدث عند النقر على الزر الذي يمثله الكائن
// مكان الحاوية الحالية settings لعرض الحاوية التي يمثلها الكائن viewPane() سيتم إستدعاء الدالة الثابتة
settings.setOnAction((Action) -> {
AppManager.viewPane(AppManager.settingsPane);
});
// about هنا قمنا بتحديد ما سيحدث عند النقر على الزر الذي يمثله الكائن
// alert سيتم تجهيز نص يمثل معلومات عامة عن اللعبة و الذي سنعرضه بداخل النافذة المنبثقة التي يمثلها الكائن
about.setOnAction((Action) -> {
String str
= "Prepared by Mhamad Harmush\n\n"
+ "If you have any comments, ideas.. just let me know\n\n"
+ "Email: mhamad.harmush@gmail.com\n"
+ "Twitter & Facebook: @MhamadHarmush\n\n"
+ "Note\n"
+ "I used JDK 1.8 to compile the source code.\n\n"
+ "© Copyright 2019 harmash.com - All Rights Reserved";
alert.setTitle("About Tic Tac Toe");
alert.setHeaderText("About Tic Tac Toe");
alert.setContentText(str);
alert.showAndWait();
});
// exit هنا قمنا بتحديد ما سيحدث عند النقر على الزر الذي يمثله الكائن
exit.setOnAction((Action) -> {
System.exit(0);
});
}
}
import javafx.scene.control.Alert; import javafx.scene.control.Alert.AlertType; import javafx.scene.control.Button; import javafx.scene.layout.Pane; // يمثل الحاوية التي سنظهرها عند تشغيل البرنامج StartPane الكلاس public class StartPane extends Pane { // هنا قمنا بإنشاء جميع الأشياء التي سنضعها في الحاوية Button singlePlayer = new Button("Single Player"); Button multiPlayer = new Button("Multi Player"); Button settings = new Button("Settings"); Button about = new Button("About"); Button exit = new Button("Exit"); // about لأننا سنستخدمه لعرض نافذة منبثقة عندما يقوم المستخدم بالنقر على الزر Alert هنا قمنا بإنشاء كائن من الكلاس Alert alert = new Alert(AlertType.INFORMATION); // هذا كونستركور الكلاس public StartPane() { // StartPane هنا قمنا بتحديد حجم كل شيء سنضيفه في الحاوية التي يمثلها الكائن الذي ننشئه من الكلاس singlePlayer.setPrefSize(240, 40); multiPlayer.setPrefSize(240, 40); settings.setPrefSize(240, 40); about.setPrefSize(240, 40); exit.setPrefSize(240, 40); // StartPane هنا قمنا بتحديد موقع كل شيء سنضيفه في الحاوية التي يمثلها الكائن الذي ننشئه من الكلاس singlePlayer.setTranslateX(80); singlePlayer.setTranslateY(110); multiPlayer.setTranslateX(80); multiPlayer.setTranslateY(170); settings.setTranslateX(80); settings.setTranslateY(230); about.setTranslateX(80); about.setTranslateY(290); exit.setTranslateX(80); exit.setTranslateY(350); // StartPane هنا قمنا بإضافة كل شيء قمنا بإنشائه في الحاوية التي يمثلها الكائن الذي ننشئه من الكلاس getChildren().add(singlePlayer); getChildren().add(multiPlayer); getChildren().add(settings); getChildren().add(about); getChildren().add(exit); // singlePlayer هنا قمنا بتحديد ما سيحدث عند النقر على الزر الذي يمثله الكائن // مكان الحاوية الحالية singlePlayerPane لعرض الحاوية التي يمثلها الكائن viewPane() سيتم إستدعاء الدالة الثابتة singlePlayer.setOnAction((Action) -> { AppManager.viewPane(AppManager.singlePlayerPane); }); // multiPlayer هنا قمنا بتحديد ما سيحدث عند النقر على الزر الذي يمثله الكائن // مكان الحاوية الحالية multiPlayerPane لعرض الحاوية التي يمثلها الكائن viewPane() سيتم إستدعاء الدالة الثابتة multiPlayer.setOnAction((Action) -> { AppManager.viewPane(AppManager.multiPlayerPane); }); // settings هنا قمنا بتحديد ما سيحدث عند النقر على الزر الذي يمثله الكائن // مكان الحاوية الحالية settings لعرض الحاوية التي يمثلها الكائن viewPane() سيتم إستدعاء الدالة الثابتة settings.setOnAction((Action) -> { AppManager.viewPane(AppManager.settingsPane); }); // about هنا قمنا بتحديد ما سيحدث عند النقر على الزر الذي يمثله الكائن // alert سيتم تجهيز نص يمثل معلومات عامة عن اللعبة و الذي سنعرضه بداخل النافذة المنبثقة التي يمثلها الكائن about.setOnAction((Action) -> { String str = "Prepared by Mhamad Harmush\n\n" + "If you have any comments, ideas.. just let me know\n\n" + "Email: mhamad.harmush@gmail.com\n" + "Twitter & Facebook: @MhamadHarmush\n\n" + "Note\n" + "I used JDK 1.8 to compile the source code.\n\n" + "© Copyright 2019 harmash.com - All Rights Reserved"; alert.setTitle("About Tic Tac Toe"); alert.setHeaderText("About Tic Tac Toe"); alert.setContentText(str); alert.showAndWait(); }); // exit هنا قمنا بتحديد ما سيحدث عند النقر على الزر الذي يمثله الكائن exit.setOnAction((Action) -> { System.exit(0); }); } }

SettingsPane.java
import javafx.beans.value.ObservableValue;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.layout.Pane;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
// ( Settings ) يمثل الحاوية التي سنظهرها عند النقر على زر ضبط اللعبة SettingsPane الكلاس
public class SettingsPane extends Pane {
// هنا قمنا بإنشاء جميع الأشياء التي سنضعها في الحاوية
Label labelForBoards = new Label("Game Board");
Label labelForFontSizes = new Label("Font Size");
ComboBox boardsComboBox = new ComboBox();
ComboBox fontSizesComboBox = new ComboBox();
Button reset = new Button("Reset Default Settings");
Button back = new Button("Back");
// هذا كونستركور الكلاس
public SettingsPane() {
// fontSizesComboBox و boardsComboBox هنا قمنا بوضع الخيارات التي يمكن للمستخدم اختيارها في الكائنين
boardsComboBox.getItems().addAll("Board 1", "Board 2", "Board 3", "Board 4");
fontSizesComboBox.getItems().addAll("Small", "Medium", "Large");
// SettingsPane هنا قمنا بتحديد حجم كل شيء سنضيفه في الحاوية التي يمثلها الكائن الذي ننشئه من الكلاس
labelForBoards.setPrefSize(100, 30);
boardsComboBox.setPrefSize(120, 30);
labelForFontSizes.setPrefSize(100, 30);
fontSizesComboBox.setPrefSize(120, 30);
reset.setPrefSize(240, 40);
back.setPrefSize(240, 40);
// SettingsPane هنا قمنا بتحديد موقع كل شيء سنضيفه في الحاوية التي يمثلها الكائن الذي ننشئه من الكلاس
labelForBoards.setTranslateX(80);
labelForBoards.setTranslateY(130);
boardsComboBox.setTranslateX(200);
boardsComboBox.setTranslateY(130);
labelForFontSizes.setTranslateX(80);
labelForFontSizes.setTranslateY(190);
fontSizesComboBox.setTranslateX(200);
fontSizesComboBox.setTranslateY(190);
reset.setTranslateX(80);
reset.setTranslateY(250);
back.setTranslateX(80);
back.setTranslateY(310);
// SettingsPane هنا قمنا بإضافة كل شيء قمنا بإنشائه في الحاوية التي يمثلها الكائن الذي ننشئه من الكلاس
getChildren().add(labelForBoards);
getChildren().add(boardsComboBox);
getChildren().add(labelForFontSizes);
getChildren().add(fontSizesComboBox);
getChildren().add(reset);
getChildren().add(back);
// boardsComboBox هنا قمنا بتحديد ما سيحدث عندما يقوم المستخدم بتغيير القيمة الظاهرة في الكائن
// AppManager الموضوع في الكلاس preferredBoard بناءاً على القيمة التي يختارها سيتم تمرير إسم الصورة للمتغير الثابت
boardsComboBox.getSelectionModel().selectedIndexProperty().addListener(
(ObservableValue<? extends Number> ov, Number oldVal, Number newVal) -> {
switch((int)newVal) {
case 0:
AppManager.preferredBoard = "board_1.png";
break;
case 1:
AppManager.preferredBoard = "board_2.png";
break;
case 2:
AppManager.preferredBoard = "board_3.png";
break;
case 3:
AppManager.preferredBoard = "board_4.png";
break;
}
});
// fontSizesComboBox هنا قمنا بتحديد ما سيحدث عندما يقوم المستخدم بتغيير القيمة الظاهرة في الكائن
// AppManager الموضوع في الكلاس preferredFont بناءاً على القيمة التي يختارها سيتم تمرير حجم الخط للكائن الثابت
// لتغيير حجم خط كل الأزرار, النصوص و مربعات النصوص الموضوعة في اللعبة setFont() كما أنه سيتم استدعاء الدالة
fontSizesComboBox.getSelectionModel().selectedIndexProperty().addListener(
(ObservableValue<? extends Number> ov, Number oldVal, Number newVal) -> {
String selectedFont = fontSizesComboBox.getSelectionModel().getSelectedItem().toString();
int fontSize = 0;
switch(selectedFont) {
case "Small":
fontSize = 15;
break;
case "Medium":
fontSize = 16;
break;
case "Large":
fontSize = 17;
break;
}
AppManager.preferredFont = Font.font("Arial", FontWeight.BOLD, fontSize);
AppManager.setFont();
});
// reset هنا قمنا بتحديد ما سيحدث عند النقر على الزر الذي يمثله الكائن
// لإرجاع القيم الإفتراضية التي كانت موضوعة في الحاوية AppManager الموجودة في الكلاس setDefaultSettings() سيتم استدعاء الدالة
reset.setOnAction((Action) -> {
AppManager.setDefaultSettings();
boardsComboBox.getSelectionModel().selectFirst();
fontSizesComboBox.getSelectionModel().select(1);
});
// back هنا قمنا بتحديد ما سيحدث عند النقر على الزر الذي يمثله الكائن
// مكان الحاوية الحالية startPane لعرض الحاوية التي يمثلها الكائن viewPane() سيتم إستدعاء الدالة الثابتة
back.setOnAction((Action) -> {
AppManager.viewPane(AppManager.startPane);
});
}
}
import javafx.beans.value.ObservableValue; import javafx.scene.control.Button; import javafx.scene.control.ComboBox; import javafx.scene.control.Label; import javafx.scene.layout.Pane; import javafx.scene.text.Font; import javafx.scene.text.FontWeight; // ( Settings ) يمثل الحاوية التي سنظهرها عند النقر على زر ضبط اللعبة SettingsPane الكلاس public class SettingsPane extends Pane { // هنا قمنا بإنشاء جميع الأشياء التي سنضعها في الحاوية Label labelForBoards = new Label("Game Board"); Label labelForFontSizes = new Label("Font Size"); ComboBox boardsComboBox = new ComboBox(); ComboBox fontSizesComboBox = new ComboBox(); Button reset = new Button("Reset Default Settings"); Button back = new Button("Back"); // هذا كونستركور الكلاس public SettingsPane() { // fontSizesComboBox و boardsComboBox هنا قمنا بوضع الخيارات التي يمكن للمستخدم اختيارها في الكائنين boardsComboBox.getItems().addAll("Board 1", "Board 2", "Board 3", "Board 4"); fontSizesComboBox.getItems().addAll("Small", "Medium", "Large"); // SettingsPane هنا قمنا بتحديد حجم كل شيء سنضيفه في الحاوية التي يمثلها الكائن الذي ننشئه من الكلاس labelForBoards.setPrefSize(100, 30); boardsComboBox.setPrefSize(120, 30); labelForFontSizes.setPrefSize(100, 30); fontSizesComboBox.setPrefSize(120, 30); reset.setPrefSize(240, 40); back.setPrefSize(240, 40); // SettingsPane هنا قمنا بتحديد موقع كل شيء سنضيفه في الحاوية التي يمثلها الكائن الذي ننشئه من الكلاس labelForBoards.setTranslateX(80); labelForBoards.setTranslateY(130); boardsComboBox.setTranslateX(200); boardsComboBox.setTranslateY(130); labelForFontSizes.setTranslateX(80); labelForFontSizes.setTranslateY(190); fontSizesComboBox.setTranslateX(200); fontSizesComboBox.setTranslateY(190); reset.setTranslateX(80); reset.setTranslateY(250); back.setTranslateX(80); back.setTranslateY(310); // SettingsPane هنا قمنا بإضافة كل شيء قمنا بإنشائه في الحاوية التي يمثلها الكائن الذي ننشئه من الكلاس getChildren().add(labelForBoards); getChildren().add(boardsComboBox); getChildren().add(labelForFontSizes); getChildren().add(fontSizesComboBox); getChildren().add(reset); getChildren().add(back); // boardsComboBox هنا قمنا بتحديد ما سيحدث عندما يقوم المستخدم بتغيير القيمة الظاهرة في الكائن // AppManager الموضوع في الكلاس preferredBoard بناءاً على القيمة التي يختارها سيتم تمرير إسم الصورة للمتغير الثابت boardsComboBox.getSelectionModel().selectedIndexProperty().addListener( (ObservableValue<? extends Number> ov, Number oldVal, Number newVal) -> { switch((int)newVal) { case 0: AppManager.preferredBoard = "board_1.png"; break; case 1: AppManager.preferredBoard = "board_2.png"; break; case 2: AppManager.preferredBoard = "board_3.png"; break; case 3: AppManager.preferredBoard = "board_4.png"; break; } }); // fontSizesComboBox هنا قمنا بتحديد ما سيحدث عندما يقوم المستخدم بتغيير القيمة الظاهرة في الكائن // AppManager الموضوع في الكلاس preferredFont بناءاً على القيمة التي يختارها سيتم تمرير حجم الخط للكائن الثابت // لتغيير حجم خط كل الأزرار, النصوص و مربعات النصوص الموضوعة في اللعبة setFont() كما أنه سيتم استدعاء الدالة fontSizesComboBox.getSelectionModel().selectedIndexProperty().addListener( (ObservableValue<? extends Number> ov, Number oldVal, Number newVal) -> { String selectedFont = fontSizesComboBox.getSelectionModel().getSelectedItem().toString(); int fontSize = 0; switch(selectedFont) { case "Small": fontSize = 15; break; case "Medium": fontSize = 16; break; case "Large": fontSize = 17; break; } AppManager.preferredFont = Font.font("Arial", FontWeight.BOLD, fontSize); AppManager.setFont(); }); // reset هنا قمنا بتحديد ما سيحدث عند النقر على الزر الذي يمثله الكائن // لإرجاع القيم الإفتراضية التي كانت موضوعة في الحاوية AppManager الموجودة في الكلاس setDefaultSettings() سيتم استدعاء الدالة reset.setOnAction((Action) -> { AppManager.setDefaultSettings(); boardsComboBox.getSelectionModel().selectFirst(); fontSizesComboBox.getSelectionModel().select(1); }); // back هنا قمنا بتحديد ما سيحدث عند النقر على الزر الذي يمثله الكائن // مكان الحاوية الحالية startPane لعرض الحاوية التي يمثلها الكائن viewPane() سيتم إستدعاء الدالة الثابتة back.setOnAction((Action) -> { AppManager.viewPane(AppManager.startPane); }); } }

SinglePlayerPane.java
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.image.Image;
import javafx.scene.layout.Pane;
// ( Single Player ) يمثل الحاوية التي سنظهرها عند النقر على زر اللعب ضد الكمبيوتر SinglePlayerPane الكلاس
public class SinglePlayerPane extends Pane {
// هنا قمنا بإنشاء جميع الأشياء التي سنضعها في الحاوية
Label playerNameLabel = new Label("Player Name");
TextField playerName = new TextField("player");
Button start = new Button("Start");
Button back = new Button("Back");
// هذا كونستركور الكلاس
public SinglePlayerPane() {
// SinglePlayerPane هنا قمنا بتحديد حجم كل شيء سنضيفه في الحاوية التي يمثلها الكائن الذي ننشئه من الكلاس
playerNameLabel.setPrefSize(100, 30);
playerName.setPrefSize(130, 30);
start.setPrefSize(240, 40);
back.setPrefSize(240, 40);
// SinglePlayerPane هنا قمنا بتحديد موقع كل شيء سنضيفه في الحاوية التي يمثلها الكائن الذي ننشئه من الكلاس
playerNameLabel.setTranslateX(80);
playerNameLabel.setTranslateY(170);
playerName.setTranslateX(190);
playerName.setTranslateY(170);
start.setTranslateX(80);
start.setTranslateY(220);
back.setTranslateX(80);
back.setTranslateY(280);
// SinglePlayerPane هنا قمنا بإضافة كل شيء قمنا بإنشائه في الحاوية التي يمثلها الكائن الذي ننشئه من الكلاس
getChildren().add(playerNameLabel);
getChildren().add(playerName);
getChildren().add(start);
getChildren().add(back);
// start هنا قمنا بتحديد ما سيحدث عند النقر على الزر الذي يمثله الكائن
// مع وضع القيمة 0 كنتيجة أولية له و للكمبيوتر gamePane سيتم تمرير الإسم الذي يدخله المستخدم, كإسم اللاعب الذي سيظهر في الحاوية
// gamePane بعدها سيتم وضع صورة الخلفية التي اختارها المستخدم أو الصورة المختارة إفتراضياً كخلفية للعبة في الحاوية
// مكان الحاوية الحالية gamePane لعرض الحاوية التي يمثلها الكائن viewPane() في الأخير سيتم إستدعاء الدالة الثابتة
start.setOnAction((Action) -> {
AppManager.gamePane.firstPlayerName.setText(playerName.getText());
AppManager.gamePane.secondPlayerName.setText("Computer");
AppManager.gamePane.firstPlayerScore.setText("0");
AppManager.gamePane.secondPlayerScore.setText("0");
// للإشارة إلى أنه سيتم اللعب ضد الكمبيوتر AppManager الموضوع في الكلاس challengeComputer للمتغير الثابت true مررنا القيمة
AppManager.challengeComputer = true;
AppManager.gamePane.boardBackground
.setImage(new Image(getClass().getResourceAsStream("/images/"+AppManager.preferredBoard)));
AppManager.viewPane(AppManager.gamePane);
});
// back هنا قمنا بتحديد ما سيحدث عند النقر على الزر الذي يمثله الكائن
// مكان الحاوية الحالية startPane لعرض الحاوية التي يمثلها الكائن viewPane() سيتم إستدعاء الدالة الثابتة
back.setOnAction((Action) -> {
AppManager.viewPane(AppManager.startPane);
});
}
}
import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.control.TextField; import javafx.scene.image.Image; import javafx.scene.layout.Pane; // ( Single Player ) يمثل الحاوية التي سنظهرها عند النقر على زر اللعب ضد الكمبيوتر SinglePlayerPane الكلاس public class SinglePlayerPane extends Pane { // هنا قمنا بإنشاء جميع الأشياء التي سنضعها في الحاوية Label playerNameLabel = new Label("Player Name"); TextField playerName = new TextField("player"); Button start = new Button("Start"); Button back = new Button("Back"); // هذا كونستركور الكلاس public SinglePlayerPane() { // SinglePlayerPane هنا قمنا بتحديد حجم كل شيء سنضيفه في الحاوية التي يمثلها الكائن الذي ننشئه من الكلاس playerNameLabel.setPrefSize(100, 30); playerName.setPrefSize(130, 30); start.setPrefSize(240, 40); back.setPrefSize(240, 40); // SinglePlayerPane هنا قمنا بتحديد موقع كل شيء سنضيفه في الحاوية التي يمثلها الكائن الذي ننشئه من الكلاس playerNameLabel.setTranslateX(80); playerNameLabel.setTranslateY(170); playerName.setTranslateX(190); playerName.setTranslateY(170); start.setTranslateX(80); start.setTranslateY(220); back.setTranslateX(80); back.setTranslateY(280); // SinglePlayerPane هنا قمنا بإضافة كل شيء قمنا بإنشائه في الحاوية التي يمثلها الكائن الذي ننشئه من الكلاس getChildren().add(playerNameLabel); getChildren().add(playerName); getChildren().add(start); getChildren().add(back); // start هنا قمنا بتحديد ما سيحدث عند النقر على الزر الذي يمثله الكائن // مع وضع القيمة 0 كنتيجة أولية له و للكمبيوتر gamePane سيتم تمرير الإسم الذي يدخله المستخدم, كإسم اللاعب الذي سيظهر في الحاوية // gamePane بعدها سيتم وضع صورة الخلفية التي اختارها المستخدم أو الصورة المختارة إفتراضياً كخلفية للعبة في الحاوية // مكان الحاوية الحالية gamePane لعرض الحاوية التي يمثلها الكائن viewPane() في الأخير سيتم إستدعاء الدالة الثابتة start.setOnAction((Action) -> { AppManager.gamePane.firstPlayerName.setText(playerName.getText()); AppManager.gamePane.secondPlayerName.setText("Computer"); AppManager.gamePane.firstPlayerScore.setText("0"); AppManager.gamePane.secondPlayerScore.setText("0"); // للإشارة إلى أنه سيتم اللعب ضد الكمبيوتر AppManager الموضوع في الكلاس challengeComputer للمتغير الثابت true مررنا القيمة AppManager.challengeComputer = true; AppManager.gamePane.boardBackground .setImage(new Image(getClass().getResourceAsStream("/images/"+AppManager.preferredBoard))); AppManager.viewPane(AppManager.gamePane); }); // back هنا قمنا بتحديد ما سيحدث عند النقر على الزر الذي يمثله الكائن // مكان الحاوية الحالية startPane لعرض الحاوية التي يمثلها الكائن viewPane() سيتم إستدعاء الدالة الثابتة back.setOnAction((Action) -> { AppManager.viewPane(AppManager.startPane); }); } }

MultiPlayerPane.java
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.image.Image;
import javafx.scene.layout.Pane;
// ( Multi Player ) يمثل الحاوية التي سنظهرها عند النقر على زر اللعبة بين شخصين موجودين على نفس الجهاز MultiPlayerPane الكلاس
public class MultiPlayerPane extends Pane {
// هنا قمنا بإنشاء جميع الأشياء التي سنضعها في الحاوية
Label playerXLabel = new Label("Player X");
Label playerOLabel = new Label("Player O");
TextField firstPlayerName = new TextField("player 1");
TextField secondPlayerName = new TextField("player 2");
Button start = new Button("Start");
Button back = new Button("Back");
// هذا كونستركور الكلاس
public MultiPlayerPane() {
// MultiPlayerPane هنا قمنا بتحديد حجم كل شيء سنضيفه في الحاوية التي يمثلها الكائن الذي ننشئه من الكلاس
playerXLabel.setPrefSize(70, 30);
firstPlayerName.setPrefSize(160, 30);
playerOLabel.setPrefSize(70, 30);
secondPlayerName.setPrefSize(160, 30);
start.setPrefSize(240, 40);
back.setPrefSize(240, 40);
// MultiPlayerPane هنا قمنا بتحديد موقع كل شيء سنضيفه في الحاوية التي يمثلها الكائن الذي ننشئه من الكلاس
playerXLabel.setTranslateX(80);
playerXLabel.setTranslateY(130);
firstPlayerName.setTranslateX(160);
firstPlayerName.setTranslateY(130);
playerOLabel.setTranslateX(80);
playerOLabel.setTranslateY(190);
secondPlayerName.setTranslateX(160);
secondPlayerName.setTranslateY(190);
start.setTranslateX(80);
start.setTranslateY(250);
back.setTranslateX(80);
back.setTranslateY(310);
// MultiPlayerPane هنا قمنا بإضافة كل شيء قمنا بإنشائه في الحاوية التي يمثلها الكائن الذي ننشئه من الكلاس
getChildren().add(playerXLabel);
getChildren().add(playerOLabel);
getChildren().add(firstPlayerName);
getChildren().add(secondPlayerName);
getChildren().add(start);
getChildren().add(back);
// start هنا قمنا بتحديد ما سيحدث عند النقر على الزر الذي يمثله الكائن
// مع وضع القيمة 0 كنتيجة أولية لكلا اللاعبين gamePane سيتم تمرير أسماء اللاعبين اللذين سيدخلوهما للحاوية
// gamePane بعدها سيتم وضع صورة الخلفية التي اختاروها أو الصورة المختارة إفتراضياً كخلفية للعبة في الحاوية
// مكان الحاوية الحالية gamePane لعرض الحاوية التي يمثلها الكائن viewPane() في الأخير سيتم إستدعاء الدالة الثابتة
start.setOnAction((Action) -> {
AppManager.gamePane.firstPlayerName.setText(firstPlayerName.getText());
AppManager.gamePane.secondPlayerName.setText(secondPlayerName.getText());
AppManager.gamePane.firstPlayerScore.setText("0");
AppManager.gamePane.secondPlayerScore.setText("0");
// للإشارة إلى أنه لن يتم اللعب ضد الكمبيوتر AppManager الموضوع في الكلاس challengeComputer للمتغير الثابت false مررنا القيمة
AppManager.challengeComputer = false;
AppManager.gamePane.boardBackground
.setImage(new Image(getClass().getResourceAsStream("/images/"+AppManager.preferredBoard)));
AppManager.viewPane(AppManager.gamePane);
});
// back هنا قمنا بتحديد ما سيحدث عند النقر على الزر الذي يمثله الكائن
// مكان الحاوية الحالية startPane لعرض الحاوية التي يمثلها الكائن viewPane() سيتم إستدعاء الدالة الثابتة
back.setOnAction((Action) -> {
AppManager.viewPane(AppManager.startPane);
});
}
}
import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.control.TextField; import javafx.scene.image.Image; import javafx.scene.layout.Pane; // ( Multi Player ) يمثل الحاوية التي سنظهرها عند النقر على زر اللعبة بين شخصين موجودين على نفس الجهاز MultiPlayerPane الكلاس public class MultiPlayerPane extends Pane { // هنا قمنا بإنشاء جميع الأشياء التي سنضعها في الحاوية Label playerXLabel = new Label("Player X"); Label playerOLabel = new Label("Player O"); TextField firstPlayerName = new TextField("player 1"); TextField secondPlayerName = new TextField("player 2"); Button start = new Button("Start"); Button back = new Button("Back"); // هذا كونستركور الكلاس public MultiPlayerPane() { // MultiPlayerPane هنا قمنا بتحديد حجم كل شيء سنضيفه في الحاوية التي يمثلها الكائن الذي ننشئه من الكلاس playerXLabel.setPrefSize(70, 30); firstPlayerName.setPrefSize(160, 30); playerOLabel.setPrefSize(70, 30); secondPlayerName.setPrefSize(160, 30); start.setPrefSize(240, 40); back.setPrefSize(240, 40); // MultiPlayerPane هنا قمنا بتحديد موقع كل شيء سنضيفه في الحاوية التي يمثلها الكائن الذي ننشئه من الكلاس playerXLabel.setTranslateX(80); playerXLabel.setTranslateY(130); firstPlayerName.setTranslateX(160); firstPlayerName.setTranslateY(130); playerOLabel.setTranslateX(80); playerOLabel.setTranslateY(190); secondPlayerName.setTranslateX(160); secondPlayerName.setTranslateY(190); start.setTranslateX(80); start.setTranslateY(250); back.setTranslateX(80); back.setTranslateY(310); // MultiPlayerPane هنا قمنا بإضافة كل شيء قمنا بإنشائه في الحاوية التي يمثلها الكائن الذي ننشئه من الكلاس getChildren().add(playerXLabel); getChildren().add(playerOLabel); getChildren().add(firstPlayerName); getChildren().add(secondPlayerName); getChildren().add(start); getChildren().add(back); // start هنا قمنا بتحديد ما سيحدث عند النقر على الزر الذي يمثله الكائن // مع وضع القيمة 0 كنتيجة أولية لكلا اللاعبين gamePane سيتم تمرير أسماء اللاعبين اللذين سيدخلوهما للحاوية // gamePane بعدها سيتم وضع صورة الخلفية التي اختاروها أو الصورة المختارة إفتراضياً كخلفية للعبة في الحاوية // مكان الحاوية الحالية gamePane لعرض الحاوية التي يمثلها الكائن viewPane() في الأخير سيتم إستدعاء الدالة الثابتة start.setOnAction((Action) -> { AppManager.gamePane.firstPlayerName.setText(firstPlayerName.getText()); AppManager.gamePane.secondPlayerName.setText(secondPlayerName.getText()); AppManager.gamePane.firstPlayerScore.setText("0"); AppManager.gamePane.secondPlayerScore.setText("0"); // للإشارة إلى أنه لن يتم اللعب ضد الكمبيوتر AppManager الموضوع في الكلاس challengeComputer للمتغير الثابت false مررنا القيمة AppManager.challengeComputer = false; AppManager.gamePane.boardBackground .setImage(new Image(getClass().getResourceAsStream("/images/"+AppManager.preferredBoard))); AppManager.viewPane(AppManager.gamePane); }); // back هنا قمنا بتحديد ما سيحدث عند النقر على الزر الذي يمثله الكائن // مكان الحاوية الحالية startPane لعرض الحاوية التي يمثلها الكائن viewPane() سيتم إستدعاء الدالة الثابتة back.setOnAction((Action) -> { AppManager.viewPane(AppManager.startPane); }); } }

AppManager.java
import javafx.scene.layout.Pane;
import javafx.scene.text.Font;
// static قمنا بإنشائه لتمرير القيم المشتركة بين حاويات اللعبة بسهولة لهذا قمنا بتعريف كل شيء فيه كـ AppManager الكلاس
public class AppManager {
// هنا قمنا بإنشاء كائن من كل كلاس يمثل حاوية قمنا بتجهيزه سابقاً
static StartPane startPane = new StartPane();
static SinglePlayerPane singlePlayerPane = new SinglePlayerPane();
static MultiPlayerPane multiPlayerPane = new MultiPlayerPane();
static SettingsPane settingsPane = new SettingsPane();
static GamePane gamePane = new GamePane();
// SettingsPane سنخزن فيه إسم صورة خلفية اللعبة التي يستطيع المستخدم تغييرها من الحاوية preferredBoard المتغير
static String preferredBoard;
// SettingsPane سنخزن فيه حجم خط كل زر, نص و مربع نص أضفناه في اللعبة و الذي يستطيع المستخدم تغييره من الحاوية preferredFont الكائن
static Font preferredFont;
// للإشارة إلى أنه سيتم اللعب ضد الكمبيوتر SinglePlayerPane الموضوع في الحاوية start عند النقر على الزر true سنخزن فيه القيمة challengeComputer المتغير
static boolean challengeComputer;
// pane الدالة التالية نستخدمها لإخفاء أي نافذة معروضة حالياً في النافذة و عرض الحاوية التي نمررها لها فقط مكان الباراميتر
public static void viewPane(Pane pane)
{
startPane.setVisible(false);
singlePlayerPane.setVisible(false);
multiPlayerPane.setVisible(false);
settingsPane.setVisible(false);
gamePane.setVisible(false);
pane.setVisible(true);
}
// settingsPane الدالة التالية نستخدمها لوضع الخيارات الإفتراضية التي يمكن تغييرها في الحاوية
public static void setDefaultSettings()
{
// fontSizesComboBox و ثاني خيار في الكائن boardsComboBox هنا قلنا أنه سيتم إختيار أول خيار في الكائن
settingsPane.boardsComboBox.getSelectionModel().selectFirst();
settingsPane.fontSizesComboBox.getSelectionModel().select(1);
// preferredFont لتغيير حجم خط كل زر, نص و مربع نص موضوع في اللعبة نسبةً لقيمة الكائن setFont() هنا قمنا باستدعاء الدالة
setFont();
}
// preferredFont الدالة التالية نستخدمها لتحديد حجم خط كل زر, نص و مربع نص موضوع في اللعبة نسبةً لقيمة الكائن
public static void setFont()
{
startPane.singlePlayer.setFont(preferredFont);
startPane.multiPlayer.setFont(preferredFont);
startPane.settings.setFont(preferredFont);
startPane.about.setFont(preferredFont);
startPane.exit.setFont(preferredFont);
singlePlayerPane.playerNameLabel.setFont(preferredFont);
singlePlayerPane.playerName.setFont(preferredFont);
singlePlayerPane.start.setFont(preferredFont);
singlePlayerPane.back.setFont(preferredFont);
multiPlayerPane.playerXLabel.setFont(preferredFont);
multiPlayerPane.playerOLabel.setFont(preferredFont);
multiPlayerPane.firstPlayerName.setFont(preferredFont);
multiPlayerPane.secondPlayerName.setFont(preferredFont);
multiPlayerPane.start.setFont(preferredFont);
multiPlayerPane.back.setFont(preferredFont);
gamePane.firstPlayerName.setFont(preferredFont);
gamePane.secondPlayerName.setFont(preferredFont);
gamePane.firstPlayerScore.setFont(preferredFont);
gamePane.secondPlayerScore.setFont(preferredFont);
gamePane.currentPlayerSymbol.setFont(preferredFont);
gamePane.restart.setFont(preferredFont);
gamePane.back.setFont(preferredFont);
settingsPane.labelForBoards.setFont(preferredFont);
settingsPane.labelForFontSizes.setFont(preferredFont);
settingsPane.reset.setFont(preferredFont);
settingsPane.back.setFont(preferredFont);
// لتحديد لهما setStyle() لا يملكان دالة خاصة لتحديد حجم الخط, لذلك قمنا باستخدام الدالة fontSizesComboBox و boardsComboBox الكائنين
settingsPane.boardsComboBox.setStyle(
"-fx-font-family:" + preferredFont.getName() + ";"
+"-fx-font-size: " + preferredFont.getSize() +"px;"
+"-fx-font-weight: bold;"
);
settingsPane.fontSizesComboBox.setStyle(
"-fx-font-family:" + preferredFont.getName() + ";"
+"-fx-font-size: " + preferredFont.getSize() +"px;"
+"-fx-font-weight: bold;"
);
}
}
import javafx.scene.layout.Pane; import javafx.scene.text.Font; // static قمنا بإنشائه لتمرير القيم المشتركة بين حاويات اللعبة بسهولة لهذا قمنا بتعريف كل شيء فيه كـ AppManager الكلاس public class AppManager { // هنا قمنا بإنشاء كائن من كل كلاس يمثل حاوية قمنا بتجهيزه سابقاً static StartPane startPane = new StartPane(); static SinglePlayerPane singlePlayerPane = new SinglePlayerPane(); static MultiPlayerPane multiPlayerPane = new MultiPlayerPane(); static SettingsPane settingsPane = new SettingsPane(); static GamePane gamePane = new GamePane(); // SettingsPane سنخزن فيه إسم صورة خلفية اللعبة التي يستطيع المستخدم تغييرها من الحاوية preferredBoard المتغير static String preferredBoard; // SettingsPane سنخزن فيه حجم خط كل زر, نص و مربع نص أضفناه في اللعبة و الذي يستطيع المستخدم تغييره من الحاوية preferredFont الكائن static Font preferredFont; // للإشارة إلى أنه سيتم اللعب ضد الكمبيوتر SinglePlayerPane الموضوع في الحاوية start عند النقر على الزر true سنخزن فيه القيمة challengeComputer المتغير static boolean challengeComputer; // pane الدالة التالية نستخدمها لإخفاء أي نافذة معروضة حالياً في النافذة و عرض الحاوية التي نمررها لها فقط مكان الباراميتر public static void viewPane(Pane pane) { startPane.setVisible(false); singlePlayerPane.setVisible(false); multiPlayerPane.setVisible(false); settingsPane.setVisible(false); gamePane.setVisible(false); pane.setVisible(true); } // settingsPane الدالة التالية نستخدمها لوضع الخيارات الإفتراضية التي يمكن تغييرها في الحاوية public static void setDefaultSettings() { // fontSizesComboBox و ثاني خيار في الكائن boardsComboBox هنا قلنا أنه سيتم إختيار أول خيار في الكائن settingsPane.boardsComboBox.getSelectionModel().selectFirst(); settingsPane.fontSizesComboBox.getSelectionModel().select(1); // preferredFont لتغيير حجم خط كل زر, نص و مربع نص موضوع في اللعبة نسبةً لقيمة الكائن setFont() هنا قمنا باستدعاء الدالة setFont(); } // preferredFont الدالة التالية نستخدمها لتحديد حجم خط كل زر, نص و مربع نص موضوع في اللعبة نسبةً لقيمة الكائن public static void setFont() { startPane.singlePlayer.setFont(preferredFont); startPane.multiPlayer.setFont(preferredFont); startPane.settings.setFont(preferredFont); startPane.about.setFont(preferredFont); startPane.exit.setFont(preferredFont); singlePlayerPane.playerNameLabel.setFont(preferredFont); singlePlayerPane.playerName.setFont(preferredFont); singlePlayerPane.start.setFont(preferredFont); singlePlayerPane.back.setFont(preferredFont); multiPlayerPane.playerXLabel.setFont(preferredFont); multiPlayerPane.playerOLabel.setFont(preferredFont); multiPlayerPane.firstPlayerName.setFont(preferredFont); multiPlayerPane.secondPlayerName.setFont(preferredFont); multiPlayerPane.start.setFont(preferredFont); multiPlayerPane.back.setFont(preferredFont); gamePane.firstPlayerName.setFont(preferredFont); gamePane.secondPlayerName.setFont(preferredFont); gamePane.firstPlayerScore.setFont(preferredFont); gamePane.secondPlayerScore.setFont(preferredFont); gamePane.currentPlayerSymbol.setFont(preferredFont); gamePane.restart.setFont(preferredFont); gamePane.back.setFont(preferredFont); settingsPane.labelForBoards.setFont(preferredFont); settingsPane.labelForFontSizes.setFont(preferredFont); settingsPane.reset.setFont(preferredFont); settingsPane.back.setFont(preferredFont); // لتحديد لهما setStyle() لا يملكان دالة خاصة لتحديد حجم الخط, لذلك قمنا باستخدام الدالة fontSizesComboBox و boardsComboBox الكائنين settingsPane.boardsComboBox.setStyle( "-fx-font-family:" + preferredFont.getName() + ";" +"-fx-font-size: " + preferredFont.getSize() +"px;" +"-fx-font-weight: bold;" ); settingsPane.fontSizesComboBox.setStyle( "-fx-font-family:" + preferredFont.getName() + ";" +"-fx-font-size: " + preferredFont.getSize() +"px;" +"-fx-font-weight: bold;" ); } }

GamePane.java
import java.util.Random;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.image.ImageView;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
// ( Multi Player ) و ( Single Player ) يمثل حاوية اللعب التي سنظهرها عند النقر على زر بدء اللعبة الموجود في كل من الحاويتين GamePane الكلاس
public class GamePane extends Pane {
// هنا قمنا بإنشاء جميع الأشياء التي سنضعها في الحاوية
Label firstPlayerName = new Label();
Label secondPlayerName = new Label();
Label firstPlayerScore = new Label("0");
Label secondPlayerScore = new Label("0");
Label currentPlayerSymbol = new Label();
GridPane boardPane = new GridPane();
Button[] boardButtons = new Button[3*3];
Button back = new Button("Back");
Button newGame = new Button("New Game");
ImageView boardBackground = new ImageView();
// سنستخدم هذا المتغير أيضاً لتحديد ما إذا كان سيتم إيقاف اللعبة بسبب فوز أحد اللاعبين
boolean isGameEnds;
// سنستخدم هذا المتغير لتحديد دور من في اللعب
boolean isFirstPlayerTurn = true;
// سنستخدم هذا المتغير لحساب عدد النقرات و بالتالي لتحديد ما إذا كان سيتم إيقاف اللعبة أم لا
int XOCounter = 0;
// randomNumber لتوليد أرقام عشوائية عند اللعب ضد الكمبيوتر. و سنخزن الرقم في المتغير random سنستخدم الكائن
Random random = new Random();
int randomNumber;
// O و X يمثلان الألوان الإفتراضية التي سنضعها للرموز Color هنا قمنا بإنشاء كائنين من الكلاس
Color xForeground = Color.BLUE;
Color oForeground = Color.RED;
// boardPane لأننا سنستخدمه لتحديد ما سيحدث عند النقر على أي زر موضوع في الحاوية EventHandler هنا قمنا بإنشاء كائن من الإنترفيس
// e و تمرير الكائن الذي يمثل الزر الذي تم النقر عليه مكان الباراميتر actionPerformed() بشكل عام, سيتم استدعاء الدالة
EventHandler<ActionEvent> eventHandler = (ActionEvent e) -> {
actionPerformed(e);
};
// سنستخدم هذه الدالة لتلوين خلفية المربعات التي بسببها فاز اللاعب باللون الأصفر
private void colorBackgroundWinnerButtons(Button b1, Button b2, Button b3)
{
b1.setStyle("-fx-background-color: yellow;");
b2.setStyle("-fx-background-color: yellow;");
b3.setStyle("-fx-background-color: yellow;");
}
// O و X سنستخدم هذه الدالة لإنشاء الأزرار التي يمكن النقر عليها لإظهار الرموز
// أيضاَ boardPane و سنضيفها في الحاوية boardButtons كما أننا سنخزن هذه الأزرار في المصفوفة
private void createGameBoard() {
int row = 0;
int column = 0;
for (int i = 0; i < boardButtons.length; i++) {
boardButtons[i] = new Button();
boardButtons[i].setPrefSize(90, 90);
boardButtons[i].setFocusTraversable(false);
GridPane.setMargin(boardButtons[i], new Insets(5));
boardButtons[i].setFont(Font.font("Arial", FontWeight.BOLD, 40));
boardPane.add(boardButtons[i], column, row);
boardButtons[i].addEventHandler(ActionEvent.ACTION, e -> {
actionPerformed(e);
});
column++;
if(column == 3)
{
row++;
column = 0;
}
}
}
// سنستخدم هذه الدالة في كل مرة يلعب فيها اللاعبون للتأكد ما إذا كان هناك فائز أم لا
// لتلوين خلفية خلفية المربعات التي كانت سبب فوز الاعب colorBackgroundWinnerButtons و في حال كان يوجد فائز سيتم مناداة الدالة
// لإيقاف اللعبة. و سيتم إضافة واحد في نتيجة اللاعب الفائز true إلى isGameEnds كما أننا سنقوم بتغيير قيمة المتغير
private void checkIfGameEnds() {
String t00 = boardButtons[0].getText();
String t01 = boardButtons[1].getText();
String t02 = boardButtons[2].getText();
String t10 = boardButtons[3].getText();
String t11 = boardButtons[4].getText();
String t12 = boardButtons[5].getText();
String t20= boardButtons[6].getText();
String t21 = boardButtons[7].getText();
String t22 = boardButtons[8].getText();
if (t00.equals(t01) && t00.equals(t02) && !t00.equals("")) {
isGameEnds = true;
colorBackgroundWinnerButtons(boardButtons[0], boardButtons[1], boardButtons[2]);
}
if (t10.equals(t11) && t10.equals(t12) && !t10.equals("")) {
isGameEnds = true;
colorBackgroundWinnerButtons(boardButtons[3], boardButtons[4], boardButtons[5]);
}
if (t20.equals(t21) && t20.equals(t22) && !t20.equals("")) {
isGameEnds = true;
colorBackgroundWinnerButtons(boardButtons[6], boardButtons[7], boardButtons[8]);
}
if (t00.equals(t10) && t00.equals(t20) && !t00.equals("")) {
isGameEnds = true;
colorBackgroundWinnerButtons(boardButtons[0], boardButtons[3], boardButtons[6]);
}
if (t01.equals(t11) && t01.equals(t21) && !t01.equals("")) {
isGameEnds = true;
colorBackgroundWinnerButtons(boardButtons[1], boardButtons[4], boardButtons[7]);
}
if (t02.equals(t12) && t02.equals(t22) && !t02.equals("")) {
isGameEnds = true;
colorBackgroundWinnerButtons(boardButtons[2], boardButtons[5], boardButtons[8]);
}
if (t00.equals(t11) && t00.equals(t22) && !t00.equals("")) {
isGameEnds = true;
colorBackgroundWinnerButtons(boardButtons[0], boardButtons[4], boardButtons[8]);
}
if (t02.equals(t11) && t02.equals(t20) && !t02.equals("")) {
isGameEnds = true;
colorBackgroundWinnerButtons(boardButtons[2], boardButtons[4], boardButtons[6]);
}
if( XOCounter >= 9)
{
isGameEnds = true;
isFirstPlayerTurn = true;
XOCounter = 0;
}
if(isGameEnds == true)
{
if(isFirstPlayerTurn)
firstPlayerScore.setText(Integer.valueOf(firstPlayerScore.getText()) + 1 + "");
else
secondPlayerScore.setText(Integer.valueOf(secondPlayerScore.getText()) + 1 + "");
XOCounter = 0;
newGame.requestFocus();
}
}
// موضوع في الحاوية و لإزالة أي O و X نستخدم هذه الدالة في كل مرة عند بدء اللعب من جديد لإزالة أي رمز
// و لتحديد دور اللاعب الذي سيبدأ colorBackgroundWinnerButtons() ألوان موضوعة بسبب الدالة
private void startNewGame() {
isGameEnds = false;
setCurrentPlayerSymbol();
for (Button boardButton : boardButtons) {
boardButton.setText("");
boardButton.setStyle("-fx-background-color: none; -fx-cursor: hand;");
}
}
// مما يجعلنا نعرف دور من الآن في اللعب currentPlayerSymbol كنص للكائن O أو X نستخدم هذه الدالة في كل مرة لإظهار الرمز
private void setCurrentPlayerSymbol() {
if (isFirstPlayerTurn == true) {
currentPlayerSymbol.setText("X");
currentPlayerSymbol.setTextFill(xForeground);
} else {
currentPlayerSymbol.setText("O");
currentPlayerSymbol.setTextFill(oForeground);
}
}
// boardPane في هذه الدالة قمنا بتحديد ما سيحدث عندما يقوم اللاعبون بالنقر على أي زر موضوع في الحاوية
private void actionPerformed(ActionEvent e)
{
// clickedButton سيتم تخزين الزر الذي تم النقر عليه بشكل مؤقت في الكائن
Button clickedButton = (Button) e.getSource();
// سيحدث التالي O أو X إذا لم تكن اللعبة قد انتهت و كان المستخدم قد قام بالنقر على زر لا يوجد عليه رمز
if( isGameEnds == false && clickedButton.getText().equals("") )
{
// إذا كان يوجد لاعبين يلعبان ضد بعضهما سيتم وضع رمز اللاعب الحالي على الزر الذي تم النقر عليه
if(AppManager.challengeComputer == false)
{
if(isFirstPlayerTurn) {
clickedButton.setTextFill(xForeground);
clickedButton.setText("X");
}
else {
clickedButton.setTextFill(oForeground);
clickedButton.setText("O");
}
// بعدها سيتم التأكد ما إن فاز أم لا و سيتم تبديل الأدوار إن لم يكن قد فاز
checkIfGameEnds();
setCurrentPlayerSymbol();
isFirstPlayerTurn = !isFirstPlayerTurn;
setCurrentPlayerSymbol();
}
// إذا كان اللاعب يلعب ضد الكمبيوتر
if (AppManager.challengeComputer == true)
{
// على الزر الذي نقر عليه و من ثم التأكد ما إن فاز أم لا X سيتم وضع الرمز
XOCounter++;
isFirstPlayerTurn = true;
clickedButton.setTextFill(xForeground);
clickedButton.setText("X");
checkIfGameEnds();
// إذا لم يكن المستخدم قد فاز, أي إذا لم يتم إيقاف اللعبة, سيحد التالي
if(isGameEnds == false)
{
// O هنا قمنا بجعل جميع الأزرار غير قابلة للنقر, لأننا نريد جعل الكمبيوتر الآن يقوم بالنقر و وضع الرمز
for (Button boardButton : boardButtons) {
boardButton.removeEventHandler(ActionEvent.ACTION, eventHandler);
}
// في مكان عشوائي و من ثم تأكدنا ما إن كان قد فاز أم لا O هنا جعلنا الكمبيوتر يضع الرمز
XOCounter++;
isFirstPlayerTurn = false;
for (;;) {
randomNumber = random.nextInt(9);
if (boardButtons[randomNumber].getText().equals(""))
{
boardButtons[randomNumber].setTextFill(oForeground);
boardButtons[randomNumber].setText("O");
break;
}
}
checkIfGameEnds();
// X هنا قمنا بجعل جميع الأزرار قابلة للنقر من جديد, لأننا نريد جعل المستخدم قادر على النقر و وضع الرمز
for (Button boardButton : boardButtons) {
boardButton.addEventHandler(ActionEvent.ACTION, eventHandler);
}
}
}
}
}
// هذا كونستركور الكلاس
public GamePane() {
// GamePane هنا قمنا بتحديد حجم كل شيء سنضيفه في الحاوية التي يمثلها الكائن الذي ننشئه من الكلاس
firstPlayerName.setPrefSize(150, 30);
secondPlayerName.setPrefSize(150, 30);
firstPlayerScore.setPrefSize(150, 30);
secondPlayerScore.setPrefSize(150, 30);
currentPlayerSymbol.setPrefSize(150, 30);
boardPane.setPrefSize(300, 300);
newGame.setPrefSize(140, 30);
// GamePane هنا قمنا بتحديد موقع كل شيء سنضيفه في الحاوية التي يمثلها الكائن الذي ننشئه من الكلاس
firstPlayerName.setTranslateY(10);
secondPlayerName.setTranslateX(250);
secondPlayerName.setTranslateY(10);
firstPlayerScore.setTranslateY(40);
secondPlayerScore.setTranslateX(250);
secondPlayerScore.setTranslateY(40);
currentPlayerSymbol.setTranslateX(120);
currentPlayerSymbol.setTranslateY(25);
boardBackground.setFitWidth(300);
boardBackground.setFitHeight(300);
boardBackground.setTranslateX(45);
boardBackground.setTranslateY(105);
boardPane.setTranslateX(45);
boardPane.setTranslateY(105);
back.setPrefSize(140, 30);
back.setTranslateX(20);
back.setTranslateY(455);
newGame.setTranslateX(230);
newGame.setTranslateY(455);
// هنا قمنا بجعل نصوص أسماء اللاعبين, و نتيجتهم تظهر في وسط المكان المخصص لظهورهم
firstPlayerName.setAlignment(Pos.CENTER);
secondPlayerName.setAlignment(Pos.CENTER);
firstPlayerScore.setAlignment(Pos.CENTER);
secondPlayerScore.setAlignment(Pos.CENTER);
currentPlayerSymbol.setAlignment(Pos.CENTER);
// boardPane و التي سيتم عرضها في الحاوية boardButtons حتى تنشئ الأزرار التي سيتم وضعها في المصفوفة createGameBoard() هنا قمنا باستدعاء الدالة
createGameBoard();
// GamePane هنا قمنا بإضافة كل شيء قمنا بإنشائه في الحاوية التي يمثلها الكائن الذي ننشئه من الكلاس
getChildren().add(firstPlayerName);
getChildren().add(secondPlayerName);
getChildren().add(firstPlayerScore);
getChildren().add(secondPlayerScore);
getChildren().add(currentPlayerSymbol);
getChildren().add(boardPane);
getChildren().add(boardBackground);
getChildren().add(back);
getChildren().add(newGame);
// لبدء لعبة جديدة startNewGame() هنا قمنا باستدعاء الدالة
startNewGame();
// back هنا قمنا بتحديد ما سيحدث عند النقر على الزر الذي يمثله الكائن
// لعرض الحاوية التي كانت معروضة قبل عرض الحاوية الحالية viewPane() سيتم إستدعاء الدالة الثابتة
back.setOnAction((Action) -> {
startNewGame();
if (AppManager.challengeComputer)
AppManager.viewPane(AppManager.singlePlayerPane);
else
AppManager.viewPane(AppManager.multiPlayerPane);
});
// newGame هنا قمنا بتحديد ما سيحدث عند النقر على الزر الذي يمثله الكائن
// لبدء اللعبة من جديد startNewGame() سيتم استدعاء الدالة
newGame.setOnAction((Action) -> {
startNewGame();
});
}
}
import java.util.Random; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.image.ImageView; import javafx.scene.layout.GridPane; import javafx.scene.layout.Pane; import javafx.scene.paint.Color; import javafx.scene.text.Font; import javafx.scene.text.FontWeight; // ( Multi Player ) و ( Single Player ) يمثل حاوية اللعب التي سنظهرها عند النقر على زر بدء اللعبة الموجود في كل من الحاويتين GamePane الكلاس public class GamePane extends Pane { // هنا قمنا بإنشاء جميع الأشياء التي سنضعها في الحاوية Label firstPlayerName = new Label(); Label secondPlayerName = new Label(); Label firstPlayerScore = new Label("0"); Label secondPlayerScore = new Label("0"); Label currentPlayerSymbol = new Label(); GridPane boardPane = new GridPane(); Button[] boardButtons = new Button[3*3]; Button back = new Button("Back"); Button newGame = new Button("New Game"); ImageView boardBackground = new ImageView(); // سنستخدم هذا المتغير أيضاً لتحديد ما إذا كان سيتم إيقاف اللعبة بسبب فوز أحد اللاعبين boolean isGameEnds; // سنستخدم هذا المتغير لتحديد دور من في اللعب boolean isFirstPlayerTurn = true; // سنستخدم هذا المتغير لحساب عدد النقرات و بالتالي لتحديد ما إذا كان سيتم إيقاف اللعبة أم لا int XOCounter = 0; // randomNumber لتوليد أرقام عشوائية عند اللعب ضد الكمبيوتر. و سنخزن الرقم في المتغير random سنستخدم الكائن Random random = new Random(); int randomNumber; // O و X يمثلان الألوان الإفتراضية التي سنضعها للرموز Color هنا قمنا بإنشاء كائنين من الكلاس Color xForeground = Color.BLUE; Color oForeground = Color.RED; // boardPane لأننا سنستخدمه لتحديد ما سيحدث عند النقر على أي زر موضوع في الحاوية EventHandler هنا قمنا بإنشاء كائن من الإنترفيس // e و تمرير الكائن الذي يمثل الزر الذي تم النقر عليه مكان الباراميتر actionPerformed() بشكل عام, سيتم استدعاء الدالة EventHandler<ActionEvent> eventHandler = (ActionEvent e) -> { actionPerformed(e); }; // سنستخدم هذه الدالة لتلوين خلفية المربعات التي بسببها فاز اللاعب باللون الأصفر private void colorBackgroundWinnerButtons(Button b1, Button b2, Button b3) { b1.setStyle("-fx-background-color: yellow;"); b2.setStyle("-fx-background-color: yellow;"); b3.setStyle("-fx-background-color: yellow;"); } // O و X سنستخدم هذه الدالة لإنشاء الأزرار التي يمكن النقر عليها لإظهار الرموز // أيضاَ boardPane و سنضيفها في الحاوية boardButtons كما أننا سنخزن هذه الأزرار في المصفوفة private void createGameBoard() { int row = 0; int column = 0; for (int i = 0; i < boardButtons.length; i++) { boardButtons[i] = new Button(); boardButtons[i].setPrefSize(90, 90); boardButtons[i].setFocusTraversable(false); GridPane.setMargin(boardButtons[i], new Insets(5)); boardButtons[i].setFont(Font.font("Arial", FontWeight.BOLD, 40)); boardPane.add(boardButtons[i], column, row); boardButtons[i].addEventHandler(ActionEvent.ACTION, e -> { actionPerformed(e); }); column++; if(column == 3) { row++; column = 0; } } } // سنستخدم هذه الدالة في كل مرة يلعب فيها اللاعبون للتأكد ما إذا كان هناك فائز أم لا // لتلوين خلفية خلفية المربعات التي كانت سبب فوز الاعب colorBackgroundWinnerButtons و في حال كان يوجد فائز سيتم مناداة الدالة // لإيقاف اللعبة. و سيتم إضافة واحد في نتيجة اللاعب الفائز true إلى isGameEnds كما أننا سنقوم بتغيير قيمة المتغير private void checkIfGameEnds() { String t00 = boardButtons[0].getText(); String t01 = boardButtons[1].getText(); String t02 = boardButtons[2].getText(); String t10 = boardButtons[3].getText(); String t11 = boardButtons[4].getText(); String t12 = boardButtons[5].getText(); String t20= boardButtons[6].getText(); String t21 = boardButtons[7].getText(); String t22 = boardButtons[8].getText(); if (t00.equals(t01) && t00.equals(t02) && !t00.equals("")) { isGameEnds = true; colorBackgroundWinnerButtons(boardButtons[0], boardButtons[1], boardButtons[2]); } if (t10.equals(t11) && t10.equals(t12) && !t10.equals("")) { isGameEnds = true; colorBackgroundWinnerButtons(boardButtons[3], boardButtons[4], boardButtons[5]); } if (t20.equals(t21) && t20.equals(t22) && !t20.equals("")) { isGameEnds = true; colorBackgroundWinnerButtons(boardButtons[6], boardButtons[7], boardButtons[8]); } if (t00.equals(t10) && t00.equals(t20) && !t00.equals("")) { isGameEnds = true; colorBackgroundWinnerButtons(boardButtons[0], boardButtons[3], boardButtons[6]); } if (t01.equals(t11) && t01.equals(t21) && !t01.equals("")) { isGameEnds = true; colorBackgroundWinnerButtons(boardButtons[1], boardButtons[4], boardButtons[7]); } if (t02.equals(t12) && t02.equals(t22) && !t02.equals("")) { isGameEnds = true; colorBackgroundWinnerButtons(boardButtons[2], boardButtons[5], boardButtons[8]); } if (t00.equals(t11) && t00.equals(t22) && !t00.equals("")) { isGameEnds = true; colorBackgroundWinnerButtons(boardButtons[0], boardButtons[4], boardButtons[8]); } if (t02.equals(t11) && t02.equals(t20) && !t02.equals("")) { isGameEnds = true; colorBackgroundWinnerButtons(boardButtons[2], boardButtons[4], boardButtons[6]); } if( XOCounter >= 9) { isGameEnds = true; isFirstPlayerTurn = true; XOCounter = 0; } if(isGameEnds == true) { if(isFirstPlayerTurn) firstPlayerScore.setText(Integer.valueOf(firstPlayerScore.getText()) + 1 + ""); else secondPlayerScore.setText(Integer.valueOf(secondPlayerScore.getText()) + 1 + ""); XOCounter = 0; newGame.requestFocus(); } } // موضوع في الحاوية و لإزالة أي O و X نستخدم هذه الدالة في كل مرة عند بدء اللعب من جديد لإزالة أي رمز // و لتحديد دور اللاعب الذي سيبدأ colorBackgroundWinnerButtons() ألوان موضوعة بسبب الدالة private void startNewGame() { isGameEnds = false; setCurrentPlayerSymbol(); for (Button boardButton : boardButtons) { boardButton.setText(""); boardButton.setStyle("-fx-background-color: none; -fx-cursor: hand;"); } } // مما يجعلنا نعرف دور من الآن في اللعب currentPlayerSymbol كنص للكائن O أو X نستخدم هذه الدالة في كل مرة لإظهار الرمز private void setCurrentPlayerSymbol() { if (isFirstPlayerTurn == true) { currentPlayerSymbol.setText("X"); currentPlayerSymbol.setTextFill(xForeground); } else { currentPlayerSymbol.setText("O"); currentPlayerSymbol.setTextFill(oForeground); } } // boardPane في هذه الدالة قمنا بتحديد ما سيحدث عندما يقوم اللاعبون بالنقر على أي زر موضوع في الحاوية private void actionPerformed(ActionEvent e) { // clickedButton سيتم تخزين الزر الذي تم النقر عليه بشكل مؤقت في الكائن Button clickedButton = (Button) e.getSource(); // سيحدث التالي O أو X إذا لم تكن اللعبة قد انتهت و كان المستخدم قد قام بالنقر على زر لا يوجد عليه رمز if( isGameEnds == false && clickedButton.getText().equals("") ) { // إذا كان يوجد لاعبين يلعبان ضد بعضهما سيتم وضع رمز اللاعب الحالي على الزر الذي تم النقر عليه if(AppManager.challengeComputer == false) { if(isFirstPlayerTurn) { clickedButton.setTextFill(xForeground); clickedButton.setText("X"); } else { clickedButton.setTextFill(oForeground); clickedButton.setText("O"); } // بعدها سيتم التأكد ما إن فاز أم لا و سيتم تبديل الأدوار إن لم يكن قد فاز checkIfGameEnds(); setCurrentPlayerSymbol(); isFirstPlayerTurn = !isFirstPlayerTurn; setCurrentPlayerSymbol(); } // إذا كان اللاعب يلعب ضد الكمبيوتر if (AppManager.challengeComputer == true) { // على الزر الذي نقر عليه و من ثم التأكد ما إن فاز أم لا X سيتم وضع الرمز XOCounter++; isFirstPlayerTurn = true; clickedButton.setTextFill(xForeground); clickedButton.setText("X"); checkIfGameEnds(); // إذا لم يكن المستخدم قد فاز, أي إذا لم يتم إيقاف اللعبة, سيحد التالي if(isGameEnds == false) { // O هنا قمنا بجعل جميع الأزرار غير قابلة للنقر, لأننا نريد جعل الكمبيوتر الآن يقوم بالنقر و وضع الرمز for (Button boardButton : boardButtons) { boardButton.removeEventHandler(ActionEvent.ACTION, eventHandler); } // في مكان عشوائي و من ثم تأكدنا ما إن كان قد فاز أم لا O هنا جعلنا الكمبيوتر يضع الرمز XOCounter++; isFirstPlayerTurn = false; for (;;) { randomNumber = random.nextInt(9); if (boardButtons[randomNumber].getText().equals("")) { boardButtons[randomNumber].setTextFill(oForeground); boardButtons[randomNumber].setText("O"); break; } } checkIfGameEnds(); // X هنا قمنا بجعل جميع الأزرار قابلة للنقر من جديد, لأننا نريد جعل المستخدم قادر على النقر و وضع الرمز for (Button boardButton : boardButtons) { boardButton.addEventHandler(ActionEvent.ACTION, eventHandler); } } } } } // هذا كونستركور الكلاس public GamePane() { // GamePane هنا قمنا بتحديد حجم كل شيء سنضيفه في الحاوية التي يمثلها الكائن الذي ننشئه من الكلاس firstPlayerName.setPrefSize(150, 30); secondPlayerName.setPrefSize(150, 30); firstPlayerScore.setPrefSize(150, 30); secondPlayerScore.setPrefSize(150, 30); currentPlayerSymbol.setPrefSize(150, 30); boardPane.setPrefSize(300, 300); newGame.setPrefSize(140, 30); // GamePane هنا قمنا بتحديد موقع كل شيء سنضيفه في الحاوية التي يمثلها الكائن الذي ننشئه من الكلاس firstPlayerName.setTranslateY(10); secondPlayerName.setTranslateX(250); secondPlayerName.setTranslateY(10); firstPlayerScore.setTranslateY(40); secondPlayerScore.setTranslateX(250); secondPlayerScore.setTranslateY(40); currentPlayerSymbol.setTranslateX(120); currentPlayerSymbol.setTranslateY(25); boardBackground.setFitWidth(300); boardBackground.setFitHeight(300); boardBackground.setTranslateX(45); boardBackground.setTranslateY(105); boardPane.setTranslateX(45); boardPane.setTranslateY(105); back.setPrefSize(140, 30); back.setTranslateX(20); back.setTranslateY(455); newGame.setTranslateX(230); newGame.setTranslateY(455); // هنا قمنا بجعل نصوص أسماء اللاعبين, و نتيجتهم تظهر في وسط المكان المخصص لظهورهم firstPlayerName.setAlignment(Pos.CENTER); secondPlayerName.setAlignment(Pos.CENTER); firstPlayerScore.setAlignment(Pos.CENTER); secondPlayerScore.setAlignment(Pos.CENTER); currentPlayerSymbol.setAlignment(Pos.CENTER); // boardPane و التي سيتم عرضها في الحاوية boardButtons حتى تنشئ الأزرار التي سيتم وضعها في المصفوفة createGameBoard() هنا قمنا باستدعاء الدالة createGameBoard(); // GamePane هنا قمنا بإضافة كل شيء قمنا بإنشائه في الحاوية التي يمثلها الكائن الذي ننشئه من الكلاس getChildren().add(firstPlayerName); getChildren().add(secondPlayerName); getChildren().add(firstPlayerScore); getChildren().add(secondPlayerScore); getChildren().add(currentPlayerSymbol); getChildren().add(boardPane); getChildren().add(boardBackground); getChildren().add(back); getChildren().add(newGame); // لبدء لعبة جديدة startNewGame() هنا قمنا باستدعاء الدالة startNewGame(); // back هنا قمنا بتحديد ما سيحدث عند النقر على الزر الذي يمثله الكائن // لعرض الحاوية التي كانت معروضة قبل عرض الحاوية الحالية viewPane() سيتم إستدعاء الدالة الثابتة back.setOnAction((Action) -> { startNewGame(); if (AppManager.challengeComputer) AppManager.viewPane(AppManager.singlePlayerPane); else AppManager.viewPane(AppManager.multiPlayerPane); }); // newGame هنا قمنا بتحديد ما سيحدث عند النقر على الزر الذي يمثله الكائن // لبدء اللعبة من جديد startNewGame() سيتم استدعاء الدالة newGame.setOnAction((Action) -> { startNewGame(); }); } }

Main.java
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
public class Main extends Application {
@Override
public void start(Stage stage) {
// لتحديد خصائص الخط الإفتراضي الذي سيتم وضعه لكل setDefaultSettings() هنا قمنا باستدعاء الدالة الثابتة
// زر, نص و مربع نص تم وضعه في اللعبة. بالإضافة إلى الصورة التي سيتم وضعها كخلفية في حاوية اللعب
AppManager.setDefaultSettings();
// لكل الحاويات التي سنضعها في اللعبة Root Node و الذي سنضعه كـ Pane هنا قمنا بإنشاء كائن من الكلاس
Pane root = new Pane();
// حتى نكون قادرين على عرضها في النافذة root في الكائن AppManager هنا قمنا بإضافة جميع الحاويات التي أنشأناها في الكلاس
root.getChildren().add(AppManager.startPane);
root.getChildren().add(AppManager.singlePlayerPane);
root.getChildren().add(AppManager.multiPlayerPane);
root.getChildren().add(AppManager.settingsPane);
root.getChildren().add(AppManager.gamePane);
// لها لأننا نريد عرض هذه الحاوية في النافذة عند تشغيل اللعبة startPane و تمرير الحاوية AppManager من الكلاس viewPane() هنا قمنا باستدعاء الدالة الثابتة
AppManager.viewPane(AppManager.startPane);
// فيها و تحديد حجمها Node كأول root هنا قمنا بإنشاء محتوى النافذة مع تعيين الكائن
Scene scene = new Scene(root, 380, 500);
// هنا قمنا بإنشاء و إظهار نافذة اللعبة مع جعل حجمها غير قابل للتكبير أو التصغي
stage.setTitle("Tic Tac Toe");
stage.setScene(scene);
stage.setResizable(false);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
import javafx.application.Application; import javafx.scene.Scene; import javafx.scene.layout.Pane; import javafx.stage.Stage; public class Main extends Application { @Override public void start(Stage stage) { // لتحديد خصائص الخط الإفتراضي الذي سيتم وضعه لكل setDefaultSettings() هنا قمنا باستدعاء الدالة الثابتة // زر, نص و مربع نص تم وضعه في اللعبة. بالإضافة إلى الصورة التي سيتم وضعها كخلفية في حاوية اللعب AppManager.setDefaultSettings(); // لكل الحاويات التي سنضعها في اللعبة Root Node و الذي سنضعه كـ Pane هنا قمنا بإنشاء كائن من الكلاس Pane root = new Pane(); // حتى نكون قادرين على عرضها في النافذة root في الكائن AppManager هنا قمنا بإضافة جميع الحاويات التي أنشأناها في الكلاس root.getChildren().add(AppManager.startPane); root.getChildren().add(AppManager.singlePlayerPane); root.getChildren().add(AppManager.multiPlayerPane); root.getChildren().add(AppManager.settingsPane); root.getChildren().add(AppManager.gamePane); // لها لأننا نريد عرض هذه الحاوية في النافذة عند تشغيل اللعبة startPane و تمرير الحاوية AppManager من الكلاس viewPane() هنا قمنا باستدعاء الدالة الثابتة AppManager.viewPane(AppManager.startPane); // فيها و تحديد حجمها Node كأول root هنا قمنا بإنشاء محتوى النافذة مع تعيين الكائن Scene scene = new Scene(root, 380, 500); // هنا قمنا بإنشاء و إظهار نافذة اللعبة مع جعل حجمها غير قابل للتكبير أو التصغي stage.setTitle("Tic Tac Toe"); stage.setScene(scene); stage.setResizable(false); stage.show(); } public static void main(String[] args) { launch(args); } }

هذه الصور جميعها من اللعبة.

javafx tic tac toe source code تحميل كود لعبة tic tac toe في جافا

الدورات

أدوات مساعدة

أقسام الموقع

دورات
مقالات كتب مشاريع أسئلة