Programming Basics SQL HTML CSS JavaScript React Python C++ Java JavaFX Swing Problem Solving English English Conversations Computer Fundamentals Linux 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);
        });
    }

}

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

}

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

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