JavaFXطريقة إنشاء لعبة Tic Tac Toe
في هذا الدرس ستتعلم طريقة إنشاء لعبة ( Tic Tac Toe ) إحترافية باستخدام إطار الـ JavaFX.
مميزات اللعبة
- يمكن لعب هذه اللعبة مع صديق أو ضد الكمبيوتر نفسه.
- يمكن تعديل تصميمها بكل سهولة من داخل اللعبة.
بناء اللعبة
- ملفات الجافا وضعناها مباشرةً في المشروع.
- الصور وضعناها بداخل مجلد إسمه
images
.
خيارات التحميل
⇓ تحميل اللعبة ⇓ تحميل المشروع كاملاً ⇓ تحميل مجلد الصور فقط
كود اللعبة
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.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.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; // ( 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.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 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 javafx.application.Application; import javafx.scene.Scene; import javafx.scene.layout.Pane; import javafx.stage.Stage; public class Main extends Application { 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); } }
هذه الصور جميعها من اللعبة.