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

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

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




مميزات لعبة Tic Tac Toe

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

  • يمكن تعديل تصميمها بكل سهولة من داخل اللعبة.



بناء لعبة Tic Tac Toe

  • ملفات الجافا وضعناها مباشرةً في المشروع.

  • الصور وضعناها بداخل مجلد إسمه images.

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


خيارات تحميل لعبة Tic Tac Toe

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



كود اللعبة

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);
	  });
	  }

	  }
	

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);
	  });

	  }

	  }
	

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);
	  });
	  }

	  }
	

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);
	  });
	  }

	  }
	

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;"
	  );
	  }

	  }
	

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&lt;ActionEvent&gt; 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);
	  }

	  }
	

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