From dddca6ed525deb61d27847f9babbf63f283bccbe Mon Sep 17 00:00:00 2001 From: Mahmoud Raafat <100778020+MahmoudRafaat@users.noreply.github.com> Date: Sat, 10 Jan 2026 16:50:55 +0200 Subject: [PATCH 01/26] Add FXML views for home, players, leaderboard, and games Introduces new FXML files for the main home screen, players list, leaderboard, and recorded games views. These files define the UI structure and controllers for each section, enabling navigation and display of relevant content in the application. --- src/main/resources/fxml/home.fxml | 62 ++++++++++++++++++++++ src/main/resources/fxml/leaderboard.fxml | 12 +++++ src/main/resources/fxml/players.fxml | 24 +++++++++ src/main/resources/fxml/recordedgames.fxml | 12 +++++ 4 files changed, 110 insertions(+) create mode 100644 src/main/resources/fxml/home.fxml create mode 100644 src/main/resources/fxml/leaderboard.fxml create mode 100644 src/main/resources/fxml/players.fxml create mode 100644 src/main/resources/fxml/recordedgames.fxml diff --git a/src/main/resources/fxml/home.fxml b/src/main/resources/fxml/home.fxml new file mode 100644 index 0000000..c4b5ce9 --- /dev/null +++ b/src/main/resources/fxml/home.fxml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + \ No newline at end of file From c4c0f0a88fad6cf9dc160ee740bfb10552aef953 Mon Sep 17 00:00:00 2001 From: Mahmoud Raafat <100778020+MahmoudRafaat@users.noreply.github.com> Date: Tue, 13 Jan 2026 15:06:22 +0200 Subject: [PATCH 20/26] Remove Player model class Deleted the Player.java file from the domain model. This may indicate a refactor or removal of unused code related to player representation. --- .../tictactoeclient/domain/model/Player.java | 32 ------------------- 1 file changed, 32 deletions(-) delete mode 100644 src/main/java/com/boredxgames/tictactoeclient/domain/model/Player.java diff --git a/src/main/java/com/boredxgames/tictactoeclient/domain/model/Player.java b/src/main/java/com/boredxgames/tictactoeclient/domain/model/Player.java deleted file mode 100644 index 6a634ad..0000000 --- a/src/main/java/com/boredxgames/tictactoeclient/domain/model/Player.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.boredxgames.tictactoeclient.domain.model; - -/** - * @author Tasneem - */ -public class Player { - private String id; - private String username; - private int score; - - public Player(String id, String username, int score) { - this.id = id; - this.username = username; - this.score = score; - } - - public String getUsername() { - return username; - } - - public void setUsername(String username) { - this.username = username; - } - - public int getScore() { - return score; - } - - public void setScore(int score) { - this.score = score; - } -} From 2eaf86a61dfbf2a139c5db515506fff08929e5a7 Mon Sep 17 00:00:00 2001 From: "ZEIAD-LAPTOP\\zeiad" Date: Tue, 13 Jan 2026 17:36:59 +0200 Subject: [PATCH 21/26] feat: implement offline mode selection screen and navigation --- .../domain/model/MoveInfo.java | 25 +++- .../domain/model/OnlineGameState.java | 5 + .../network/ServerConnectionManager.java | 1 + .../domain/services/GameService.java | 3 +- .../services/communication/MessageRouter.java | 110 +++++++++--------- .../services/game/OnlinePVPService.java | 68 +++++++++++ .../presentation/GameController.java | 26 +++-- .../presentation/HomeController.java | 5 +- .../css/features/offline_mode_selection.css | 23 ---- 9 files changed, 170 insertions(+), 96 deletions(-) create mode 100644 src/main/java/com/boredxgames/tictactoeclient/domain/model/OnlineGameState.java create mode 100644 src/main/java/com/boredxgames/tictactoeclient/domain/services/game/OnlinePVPService.java diff --git a/src/main/java/com/boredxgames/tictactoeclient/domain/model/MoveInfo.java b/src/main/java/com/boredxgames/tictactoeclient/domain/model/MoveInfo.java index 9003782..88cc6c3 100644 --- a/src/main/java/com/boredxgames/tictactoeclient/domain/model/MoveInfo.java +++ b/src/main/java/com/boredxgames/tictactoeclient/domain/model/MoveInfo.java @@ -4,6 +4,12 @@ */ package com.boredxgames.tictactoeclient.domain.model; +import com.boredxgames.tictactoeclient.domain.services.communication.Action; +import com.boredxgames.tictactoeclient.domain.services.communication.Header; +import com.boredxgames.tictactoeclient.domain.services.communication.Message; +import com.boredxgames.tictactoeclient.domain.services.communication.MessageType; +import com.google.gson.Gson; + /** * * @author mahmoud @@ -11,9 +17,9 @@ public class MoveInfo { private final String roomId; private final String playerId; - private final Object move; + private final String move; - public MoveInfo(String playerId, Object move, String roomId) { + public MoveInfo(String playerId, String move, String roomId) { this.playerId = playerId; this.move = move; this.roomId = roomId; @@ -27,8 +33,19 @@ public String getPlayerId() { return playerId; } - public Object getMove() { + public String getMove() { return move; } - + + public static MoveInfo createMoveInfo(String roomId, String playerId , Object data) + { + return new MoveInfo(playerId , toJson(data), roomId); + } + + static private String toJson(Object data) + { + Gson gson = new Gson(); + return gson.toJson(data); + + } } diff --git a/src/main/java/com/boredxgames/tictactoeclient/domain/model/OnlineGameState.java b/src/main/java/com/boredxgames/tictactoeclient/domain/model/OnlineGameState.java new file mode 100644 index 0000000..92229e9 --- /dev/null +++ b/src/main/java/com/boredxgames/tictactoeclient/domain/model/OnlineGameState.java @@ -0,0 +1,5 @@ +package com.boredxgames.tictactoeclient.domain.model; + +public class OnlineGameState { + public static GameStartInfo info = null; +} diff --git a/src/main/java/com/boredxgames/tictactoeclient/domain/network/ServerConnectionManager.java b/src/main/java/com/boredxgames/tictactoeclient/domain/network/ServerConnectionManager.java index 786480d..8f06f41 100644 --- a/src/main/java/com/boredxgames/tictactoeclient/domain/network/ServerConnectionManager.java +++ b/src/main/java/com/boredxgames/tictactoeclient/domain/network/ServerConnectionManager.java @@ -98,6 +98,7 @@ public void disconnect() { public synchronized void sendMessage(Message msg) { try { String jsonMessage = gson.toJson(msg); + System.out.println(jsonMessage + "SENTTTTT"); dos.writeUTF(jsonMessage); dos.flush(); diff --git a/src/main/java/com/boredxgames/tictactoeclient/domain/services/GameService.java b/src/main/java/com/boredxgames/tictactoeclient/domain/services/GameService.java index 115c3de..4028f18 100644 --- a/src/main/java/com/boredxgames/tictactoeclient/domain/services/GameService.java +++ b/src/main/java/com/boredxgames/tictactoeclient/domain/services/GameService.java @@ -1,7 +1,8 @@ -package com.boredxgames.tictactoeclient.domain.services.game; +package com.boredxgames.tictactoeclient.domain.services; import com.boredxgames.tictactoeclient.domain.model.GameState; import com.boredxgames.tictactoeclient.domain.model.Move; +import com.boredxgames.tictactoeclient.domain.services.game.GameBoard; /** * @author Tasneem diff --git a/src/main/java/com/boredxgames/tictactoeclient/domain/services/communication/MessageRouter.java b/src/main/java/com/boredxgames/tictactoeclient/domain/services/communication/MessageRouter.java index eef8071..827bbc0 100644 --- a/src/main/java/com/boredxgames/tictactoeclient/domain/services/communication/MessageRouter.java +++ b/src/main/java/com/boredxgames/tictactoeclient/domain/services/communication/MessageRouter.java @@ -8,22 +8,13 @@ * * @author Hazem */ + import com.boredxgames.tictactoeclient.domain.managers.navigation.NavigationAction; import com.boredxgames.tictactoeclient.domain.managers.navigation.NavigationManager; import com.boredxgames.tictactoeclient.domain.managers.navigation.Screens; -import com.boredxgames.tictactoeclient.domain.model.AuthResponseEntity; -import com.boredxgames.tictactoeclient.domain.model.AvailablePlayersInfo; -import com.boredxgames.tictactoeclient.domain.model.GameRequestInfo; -import com.boredxgames.tictactoeclient.domain.model.GameResponseInfo; -import com.boredxgames.tictactoeclient.domain.model.GameStartInfo; +import com.boredxgames.tictactoeclient.domain.model.*; import com.boredxgames.tictactoeclient.domain.network.ServerConnectionManager; -import static com.boredxgames.tictactoeclient.domain.services.communication.Action.INTERNAL_SERVER_ERROR; -import static com.boredxgames.tictactoeclient.domain.services.communication.Action.INVALID_CREDENTIAL; -import static com.boredxgames.tictactoeclient.domain.services.communication.Action.LOGIN_SUCCESS; -import static com.boredxgames.tictactoeclient.domain.services.communication.Action.REGISTERATION_SUCCESS; -import static com.boredxgames.tictactoeclient.domain.services.communication.Action.USERNAME_NOT_FOUND; -import static com.boredxgames.tictactoeclient.domain.services.communication.Action.USER_IS_ONLINE; - +import com.boredxgames.tictactoeclient.domain.services.game.OnlinePVPService; import com.boredxgames.tictactoeclient.presentation.AuthenticationController; import com.boredxgames.tictactoeclient.presentation.HomeController; import com.google.gson.Gson; @@ -35,6 +26,7 @@ public class MessageRouter { private static ServerConnectionManager connection; private Gson gson = new Gson(); private static HomeController homeController; + private MessageRouter() { connection = ServerConnectionManager.getInstance(); } @@ -58,7 +50,8 @@ public void navigateMessage(String response) { case ERROR -> handleError(message); default -> System.out.println("Unknown MessageType: " + messageType); } - } + } + public static void setHomeController(HomeController controller) { homeController = controller; } @@ -77,41 +70,40 @@ private Message handleRequest(Message msg) { }; } - + private void handleResponse(Message msg) { Action action = msg.getHeader().getAction(); - System.out.println(action); - + System.out.println(action); + switch (action) { - case LOGIN_SUCCESS -> - { + case LOGIN_SUCCESS -> { System.out.println("Login Success"); - AuthResponseEntity responseData = gson.fromJson(msg.getData(),AuthResponseEntity.class); - ServerConnectionManager.getInstance().setPlayer(responseData); + AuthResponseEntity responseData = gson.fromJson(msg.getData(), AuthResponseEntity.class); + ServerConnectionManager.getInstance().setPlayer(responseData); System.out.println(responseData); NavigationManager.navigate(Screens.Home, NavigationAction.REPLACE); } - case REGISTERATION_SUCCESS->{ + case REGISTERATION_SUCCESS -> { System.out.println("Registration success"); - AuthResponseEntity responseData = gson.fromJson(msg.getData(),AuthResponseEntity.class); - AuthenticationController.showUserAlert("Registration success"); + AuthResponseEntity responseData = gson.fromJson(msg.getData(), AuthResponseEntity.class); + AuthenticationController.showUserAlert("Registration success"); } - case USERNAME_NOT_FOUND->{ + case USERNAME_NOT_FOUND -> { System.out.println("Username not found"); AuthenticationController.showUserAlert("Username not found"); - + } case REQUEST_GAME -> { - System.out.println("Server acknowledged Game Request."); - } + System.out.println("Server acknowledged Game Request."); + } case GET_AVAILABLE_PLAYERS -> { AvailablePlayersInfo info = gson.fromJson(msg.getData(), AvailablePlayersInfo.class); - if (homeController != null) { - Platform.runLater(() -> homeController.updatePlayersList(info)); + if (homeController != null) { + Platform.runLater(() -> homeController.updatePlayersList(info)); + } } - } case GAME_RESPONSE -> { GameResponseInfo info = gson.fromJson(msg.getData(), GameResponseInfo.class); if (homeController != null) { @@ -119,25 +111,30 @@ private void handleResponse(Message msg) { } } case GET_LEADERBOARD -> { - AvailablePlayersInfo info = gson.fromJson(msg.getData(), AvailablePlayersInfo.class); - if (homeController != null) { - Platform.runLater(() -> homeController.updateLeaderboardUI(info)); - } -} + AvailablePlayersInfo info = gson.fromJson(msg.getData(), AvailablePlayersInfo.class); + if (homeController != null) { + Platform.runLater(() -> homeController.updateLeaderboardUI(info)); + } + } + case SEND_GAME_UPDATE -> { + MoveInfo moveInfo = gson.fromJson(msg.getData(), MoveInfo.class); + OnlinePVPService.onIncomingMove(moveInfo); + } default -> { System.out.println("Unknown Action: " + action); } - }; + } + ; } private void handleEvent(Message msg) { Action action = msg.getHeader().getAction(); - System.out.println(action); + System.out.println(action); switch (action) { - case REQUEST_GAME -> { + case REQUEST_GAME -> { GameRequestInfo info = gson.fromJson(msg.getData(), GameRequestInfo.class); if (homeController != null) { Platform.runLater(() -> homeController.showIncomingGameRequest(info)); @@ -145,8 +142,8 @@ private void handleEvent(Message msg) { } case GAME_START -> { - GameStartInfo info = gson.fromJson(msg.getData(), GameStartInfo.class); - NavigationManager.navigate(Screens.PRIMARY, NavigationAction.REPLACE); + OnlineGameState.info = gson.fromJson(msg.getData(), GameStartInfo.class); + NavigationManager.navigate(Screens.GAME, NavigationAction.REPLACE, GameMode.ONLINE_PVP); } @@ -154,25 +151,26 @@ private void handleEvent(Message msg) { System.out.println("Unknown Action: " + action); } - }; + } + ; } - + private void handleError(Message msg) { System.out.println("my time has come"); Action action = msg.getHeader().getAction(); - System.out.println(action); - + System.out.println(action); + switch (action) { - - case USERNAME_NOT_FOUND->{ + + case USERNAME_NOT_FOUND -> { System.out.println("Username not found"); AuthenticationController.showUserAlert("Username not found"); - + } - case INTERNAL_SERVER_ERROR->{ - String errorMessage = "Server error. Please try again."; - System.out.println("Internal Server Error"); + case INTERNAL_SERVER_ERROR -> { + String errorMessage = "Server error. Please try again."; + System.out.println("Internal Server Error"); AuthenticationController.showUserAlert("Internal Server Error"); if (homeController != null) { Platform.runLater(() -> homeController.showErrorAlert(errorMessage)); @@ -217,18 +215,18 @@ private void handleError(Message msg) { Platform.runLater(() -> homeController.showErrorAlert(errorMsg)); } } - case INVALID_CREDENTIAL->{ - System.out.println("INVALID_CREDENTIAL"); + case INVALID_CREDENTIAL -> { + System.out.println("INVALID_CREDENTIAL"); AuthenticationController.showUserAlert("INVALID CREDENTIAL"); } - case USER_IS_ONLINE->{ - System.out.println("User alread logged in"); + case USER_IS_ONLINE -> { + System.out.println("User alread logged in"); AuthenticationController.showUserAlert("User alread logged in"); } - case USERNAME_ALREADY_EXIST->{ - System.out.println("USERNAME_ALREADY_EXIST"); + case USERNAME_ALREADY_EXIST -> { + System.out.println("USERNAME_ALREADY_EXIST"); AuthenticationController.showUserAlert("USERNAME ALREADY EXIST"); } diff --git a/src/main/java/com/boredxgames/tictactoeclient/domain/services/game/OnlinePVPService.java b/src/main/java/com/boredxgames/tictactoeclient/domain/services/game/OnlinePVPService.java new file mode 100644 index 0000000..e1b3b75 --- /dev/null +++ b/src/main/java/com/boredxgames/tictactoeclient/domain/services/game/OnlinePVPService.java @@ -0,0 +1,68 @@ +package com.boredxgames.tictactoeclient.domain.services.game; + +import com.boredxgames.tictactoeclient.domain.model.*; +import com.boredxgames.tictactoeclient.domain.network.ServerConnectionManager; +import com.boredxgames.tictactoeclient.domain.services.GameService; +import com.boredxgames.tictactoeclient.domain.services.communication.Action; +import com.boredxgames.tictactoeclient.domain.services.communication.Message; +import com.boredxgames.tictactoeclient.domain.services.communication.MessageType; +import com.google.gson.Gson; + +import java.util.function.Consumer; + +public class OnlinePVPService implements GameService { + private static OnlinePVPService instance; + public static OnlinePVPService getInstance() { + if (instance == null) { + instance = new OnlinePVPService(); + } + return instance; + } + private OnlinePVPService(){} + + // specific listener to bridge Network -> UI + private static Consumer moveListener; + + public OnlinePVPService setMoveListener(Consumer listener) { + moveListener = listener; + return this; + } + + public static void onIncomingMove(MoveInfo moveInfo) { + Gson gson = new Gson(); + Move move = gson.fromJson(moveInfo.getMove(), Move.class); + if (moveListener != null) { + moveListener.accept(move); + } + } + + @Override + public void makeMove(Move move, char currentPlayer) { + + ServerConnectionManager connectionManager = ServerConnectionManager.getInstance(); + AuthResponseEntity player = connectionManager.getPlayer(); + GameStartInfo sessionInfo = OnlineGameState.info; + // Prepare the data + MoveInfo info = MoveInfo.createMoveInfo(sessionInfo.getRoomId() , player.getId(), move); + + // Send SEND_GAME_UPDATE action to server + Message msg = Message.createMessage( + MessageType.RESPONSE, + Action.SEND_GAME_UPDATE, + info + ); + connectionManager.sendMessage(msg); + } + + @Override + public Move getNextMove(GameBoard board, char currentPlayer) { + // In online play, we don't calculate the next move locally. + // We wait for the server event instead. + return null; + } + + @Override + public GameState getOutcome(GameBoard board) { + return board.getGameState(); + } +} \ No newline at end of file diff --git a/src/main/java/com/boredxgames/tictactoeclient/presentation/GameController.java b/src/main/java/com/boredxgames/tictactoeclient/presentation/GameController.java index 8c8c136..159c9a5 100644 --- a/src/main/java/com/boredxgames/tictactoeclient/presentation/GameController.java +++ b/src/main/java/com/boredxgames/tictactoeclient/presentation/GameController.java @@ -1,15 +1,16 @@ package com.boredxgames.tictactoeclient.presentation; -import com.boredxgames.tictactoeclient.domain.managers.navigation.NavigationParameterAware; -import com.boredxgames.tictactoeclient.domain.model.GameMode; import com.boredxgames.tictactoeclient.domain.managers.navigation.NavigationAction; import com.boredxgames.tictactoeclient.domain.managers.navigation.NavigationManager; +import com.boredxgames.tictactoeclient.domain.managers.navigation.NavigationParameterAware; import com.boredxgames.tictactoeclient.domain.managers.navigation.Screens; +import com.boredxgames.tictactoeclient.domain.model.GameMode; import com.boredxgames.tictactoeclient.domain.model.GameNavigationParams; +import com.boredxgames.tictactoeclient.domain.model.GameState; import com.boredxgames.tictactoeclient.domain.model.Move; +import com.boredxgames.tictactoeclient.domain.services.GameService; import com.boredxgames.tictactoeclient.domain.services.game.GameBoard; -import com.boredxgames.tictactoeclient.domain.model.GameState; -import com.boredxgames.tictactoeclient.domain.services.game.GameService; +import com.boredxgames.tictactoeclient.domain.services.game.OnlinePVPService; import javafx.animation.PauseTransition; import javafx.application.Platform; import javafx.fxml.FXML; @@ -27,6 +28,7 @@ import javafx.scene.media.MediaView; import javafx.scene.text.Text; import javafx.util.Duration; + import java.net.URL; import java.util.Objects; import java.util.ResourceBundle; @@ -154,7 +156,14 @@ private void applyGameModeSettings() { difficultyBadge.setManaged(false); changeDifficultyButton.setVisible(false); changeDifficultyButton.setManaged(false); - + gameService = OnlinePVPService.getInstance().setMoveListener((move) -> + { + Platform.runLater(() -> { + updateCell(move.getCol(), move.getRow(), gameBoard.getCurrentPlayer()); + gameBoard.switchPlayer(); + enableBoard(); + }); + }); } } } @@ -194,15 +203,16 @@ private void setupButtonHandlers() { private void handleCellClick(int row, int col) { if (!gameBoard.isValidMove(row, col)) return; - if (gameMode == GameMode.ONLINE_PVP && !isPlayerTurn) return; - char currentPlayer = gameBoard.getCurrentPlayer(); Move move = new Move(row, col); gameService.makeMove(move, currentPlayer); + updateCell(move.getCol(), move.getRow(), currentPlayer); + disableBoard(); + gameBoard.switchPlayer(); Move nextMove = gameService.getNextMove(gameBoard, currentPlayer); - if(nextMove != null) { + if (nextMove != null) { Platform.runLater(() -> { if (gameBoard.makeMove(nextMove.getRow(), nextMove.getCol(), gameBoard.getCurrentPlayer())) { updateCell(nextMove.getRow(), nextMove.getCol(), gameBoard.getCurrentPlayer()); diff --git a/src/main/java/com/boredxgames/tictactoeclient/presentation/HomeController.java b/src/main/java/com/boredxgames/tictactoeclient/presentation/HomeController.java index 5a83104..669a6b2 100644 --- a/src/main/java/com/boredxgames/tictactoeclient/presentation/HomeController.java +++ b/src/main/java/com/boredxgames/tictactoeclient/presentation/HomeController.java @@ -3,10 +3,7 @@ import com.boredxgames.tictactoeclient.domain.managers.navigation.NavigationAction; import com.boredxgames.tictactoeclient.domain.managers.navigation.NavigationManager; import com.boredxgames.tictactoeclient.domain.managers.navigation.Screens; -import com.boredxgames.tictactoeclient.domain.model.AuthResponseEntity; -import com.boredxgames.tictactoeclient.domain.model.AvailablePlayersInfo; -import com.boredxgames.tictactoeclient.domain.model.GameRequestInfo; -import com.boredxgames.tictactoeclient.domain.model.GameResponseInfo; +import com.boredxgames.tictactoeclient.domain.model.*; import com.boredxgames.tictactoeclient.domain.network.ServerConnectionManager; import com.boredxgames.tictactoeclient.domain.services.communication.MessageRouter; import com.boredxgames.tictactoeclient.domain.services.game.OnlinGameSession; diff --git a/src/main/resources/css/features/offline_mode_selection.css b/src/main/resources/css/features/offline_mode_selection.css index 3c601d3..870c3ba 100644 --- a/src/main/resources/css/features/offline_mode_selection.css +++ b/src/main/resources/css/features/offline_mode_selection.css @@ -1,10 +1,4 @@ -/* css/features/offline_mode_selection.css */ - .root { - /* Variables inherited from global theme */ - /* -bg-main, -bg-card, -primary, -primary-soft, -text-main */ - - /* UPDATED: Unified radius variable used for cropping */ -offline-card-radius: 24; -icon-size: 48; @@ -14,7 +8,6 @@ -fx-font-family: 'System'; } -/* Background Effects */ .background-effects { -fx-opacity: 0.15; } @@ -25,7 +18,6 @@ -fx-effect: dropshadow(gaussian, -primary, 120, 0.6, 0, 0); } -/* ================== HEADER ================== */ .header { -fx-border-color: derive(-bg-card, 20%); -fx-border-width: 0 0 1 0; @@ -53,7 +45,6 @@ -fx-text-fill: -text-main; } -/* Settings Button */ .icon-button { -fx-background-color: -bg-card; -fx-background-radius: 12; @@ -70,7 +61,6 @@ -fx-fill: -text-main; } -/* Typography */ .title { -fx-font-size: 42px; -fx-font-weight: 800; @@ -83,10 +73,7 @@ -fx-font-weight: 500; } -/* ================== CARDS ================== */ - .game-card { - /* 1. Use the unified variable so container matches content */ -fx-background-radius: -offline-card-radius; -fx-border-radius: -offline-card-radius; -fx-background-color: -bg-card; @@ -100,20 +87,16 @@ .game-card:hover { -fx-border-color: -primary; - /* Maintain the same radius on hover to prevent shape shifting */ -fx-background-radius: -offline-card-radius; -fx-border-radius: -offline-card-radius; -fx-effect: dropshadow(three-pass-box, -primary-soft, 40, 0.2, 0, 0); } -/* BACKGROUND IMAGES (Cropped via radius) */ .card-bg-image-pvp { -fx-background-image: url("https://lh3.googleusercontent.com/aida-public/AB6AXuBuw3YJ7m8TLhz3GNAjD8Scf5cQLafn2bp8Qn2o_Ll0YnRvc5Ab_GqZNGey-746vql56GF4b6Sb0UKlABmsHyFQGa2mOfoV1nhTqEWHwqIn3ZPTFy1766m5UtDZBWmPT7H5a4_W3sL8oBT6ZJ74opM1_UsXjR4LR-Hs8VSTtUWD1axZbvQOF6UgXhNW53aKKwV-BU0E91jRE_LkaM9uukR2NIMq35DC3CE2ggLEMvTft7WS62_B5fMAyJ1oTzUDC7Vk-CL4LrZv30EV"); -fx-background-size: cover; -fx-background-position: center; - /* 2. Radius matches parent to create crop effect */ -fx-background-radius: -offline-card-radius; - /* 3. Insets 0 ensures image fills the region exactly to the edge */ -fx-background-insets: 0; } @@ -125,7 +108,6 @@ -fx-background-insets: 0; } -/* GRADIENT OVERLAY */ .card-gradient { -fx-background-radius: -offline-card-radius; -fx-background-insets: 0; @@ -138,8 +120,6 @@ -fx-opacity: 0.9; } -/* ================== CARD ICONS ================== */ - .card-icon-bg { -fx-background-color: -primary-soft; -fx-background-radius: 100; @@ -159,8 +139,6 @@ -fx-effect: dropshadow(gaussian, -primary, 10, 0.4, 0, 0); } -/* ================== CARD TEXT & FOOTER ================== */ - .card-title { -fx-font-size: 26px; -fx-font-weight: bold; @@ -197,7 +175,6 @@ -fx-fill: -primary; } -/* Back Button */ .back-button { -fx-background-color: transparent; -fx-padding: 10 20 10 20; From cbfc526cc02d0dcf5507aaede498e1a98b450553 Mon Sep 17 00:00:00 2001 From: "hazemkora660@gmail.com" Date: Tue, 13 Jan 2026 19:19:39 +0200 Subject: [PATCH 22/26] add which player turn --- .../services/game/OnlinePVPService.java | 34 ++++++- .../presentation/BackgroundHomeAnimation.java | 99 +++++++++---------- .../presentation/GameController.java | 70 +++++++++---- 3 files changed, 127 insertions(+), 76 deletions(-) diff --git a/src/main/java/com/boredxgames/tictactoeclient/domain/services/game/OnlinePVPService.java b/src/main/java/com/boredxgames/tictactoeclient/domain/services/game/OnlinePVPService.java index e1b3b75..30e143a 100644 --- a/src/main/java/com/boredxgames/tictactoeclient/domain/services/game/OnlinePVPService.java +++ b/src/main/java/com/boredxgames/tictactoeclient/domain/services/game/OnlinePVPService.java @@ -7,18 +7,44 @@ import com.boredxgames.tictactoeclient.domain.services.communication.Message; import com.boredxgames.tictactoeclient.domain.services.communication.MessageType; import com.google.gson.Gson; - import java.util.function.Consumer; public class OnlinePVPService implements GameService { + private static OnlinePVPService instance; + private boolean isMyTurn = false; + + public void setupOnlineGame() { + + System.out.println(" Check equality"+OnlineGameState.info.getPlayer1().equalsIgnoreCase(ServerConnectionManager.getInstance().getPlayer().getId())); + + + if (OnlineGameState.info.getPlayer1().equalsIgnoreCase(ServerConnectionManager.getInstance().getPlayer().getId())) { + isMyTurn = true; + } + } + + public void setIsMyTurn(boolean isMyTurn) { + this.isMyTurn = isMyTurn; + } + + public boolean isIsMyTurn() { + return isMyTurn; + } + + public boolean checkTurn() { + return isMyTurn; + } + public static OnlinePVPService getInstance() { if (instance == null) { instance = new OnlinePVPService(); } return instance; } - private OnlinePVPService(){} + + private OnlinePVPService() { + } // specific listener to bridge Network -> UI private static Consumer moveListener; @@ -43,7 +69,7 @@ public void makeMove(Move move, char currentPlayer) { AuthResponseEntity player = connectionManager.getPlayer(); GameStartInfo sessionInfo = OnlineGameState.info; // Prepare the data - MoveInfo info = MoveInfo.createMoveInfo(sessionInfo.getRoomId() , player.getId(), move); + MoveInfo info = MoveInfo.createMoveInfo(sessionInfo.getRoomId(), player.getId(), move); // Send SEND_GAME_UPDATE action to server Message msg = Message.createMessage( @@ -65,4 +91,4 @@ public Move getNextMove(GameBoard board, char currentPlayer) { public GameState getOutcome(GameBoard board) { return board.getGameState(); } -} \ No newline at end of file +} diff --git a/src/main/java/com/boredxgames/tictactoeclient/presentation/BackgroundHomeAnimation.java b/src/main/java/com/boredxgames/tictactoeclient/presentation/BackgroundHomeAnimation.java index 37d5e2c..1b86222 100644 --- a/src/main/java/com/boredxgames/tictactoeclient/presentation/BackgroundHomeAnimation.java +++ b/src/main/java/com/boredxgames/tictactoeclient/presentation/BackgroundHomeAnimation.java @@ -13,14 +13,9 @@ import java.util.Random; -import javafx.animation.FadeTransition; -import javafx.animation.ParallelTransition; -import javafx.animation.RotateTransition; -import javafx.animation.TranslateTransition; import javafx.scene.Node; import javafx.scene.control.Label; import javafx.scene.layout.Pane; -import javafx.util.Duration; public abstract class BackgroundHomeAnimation{ @@ -32,59 +27,59 @@ public abstract class BackgroundHomeAnimation{ * @param node The container (e.g., VBox) to animate. */ public static void animateCardEntry(Node node) { - node.setOpacity(0); - node.setTranslateY(500); - - TranslateTransition tt = new TranslateTransition(Duration.millis(1500), node); - tt.setToY(0); - - FadeTransition ft = new FadeTransition(Duration.millis(1000), node); - ft.setToValue(1); - - ParallelTransition pt = new ParallelTransition(tt, ft); - pt.play(); +// node.setOpacity(0); +// node.setTranslateY(500); +// +// TranslateTransition tt = new TranslateTransition(Duration.millis(1500), node); +// tt.setToY(0); +// +// FadeTransition ft = new FadeTransition(Duration.millis(1000), node); +// ft.setToValue(1); +// +// ParallelTransition pt = new ParallelTransition(tt, ft); +// pt.play(); } public static void startBackgroundAnimation(Pane backgroundPane, double screenWidth, double screenHeight) { - int particleCount = 50; - - for (int i = 0; i < particleCount; i++) { - Label particle = new Label(random.nextBoolean() ? "X" : "O"); - particle.getStyleClass().add("background-particle"); - - double size = 15 + random.nextInt(50); - particle.setStyle("-fx-font-size: " + size + "px; -fx-text-fill: rgba(79, 94, 247, " + (0.05 + random.nextDouble() * 0.2) + ");"); - - particle.setTranslateX(random.nextInt((int) screenWidth)); - particle.setTranslateY(random.nextInt((int) screenHeight)); - - backgroundPane.getChildren().add(particle); - animateParticle(particle, screenWidth, screenHeight); - } +// int particleCount = 50; +// +// for (int i = 0; i < particleCount; i++) { +// Label particle = new Label(random.nextBoolean() ? "X" : "O"); +// particle.getStyleClass().add("background-particle"); +// +// double size = 15 + random.nextInt(50); +// particle.setStyle("-fx-font-size: " + size + "px; -fx-text-fill: rgba(79, 94, 247, " + (0.05 + random.nextDouble() * 0.2) + ");"); +// +// particle.setTranslateX(random.nextInt((int) screenWidth)); +// particle.setTranslateY(random.nextInt((int) screenHeight)); +// +// backgroundPane.getChildren().add(particle); +// animateParticle(particle, screenWidth, screenHeight); +// } } private static void animateParticle(Label particle, double width, double height) { - TranslateTransition move = new TranslateTransition(Duration.seconds(15 + random.nextInt(15)), particle); - double endX = particle.getTranslateX() + (random.nextInt(200) - 100); // Drift sideways - double endY = -100; - - move.setToX(endX); - move.setToY(endY); - move.setCycleCount(1); - - RotateTransition rotate = new RotateTransition(Duration.seconds(5 + random.nextInt(10)), particle); - rotate.setByAngle(360); - rotate.setCycleCount(RotateTransition.INDEFINITE); - - move.setOnFinished(e -> { - - particle.setTranslateY(height + random.nextInt(100)); - particle.setTranslateX(random.nextInt((int) width)); - animateParticle(particle, width, height); - }); - - move.play(); - rotate.play(); +// TranslateTransition move = new TranslateTransition(Duration.seconds(15 + random.nextInt(15)), particle); +// double endX = particle.getTranslateX() + (random.nextInt(200) - 100); // Drift sideways +// double endY = -100; +// +// move.setToX(endX); +// move.setToY(endY); +// move.setCycleCount(1); +// +// RotateTransition rotate = new RotateTransition(Duration.seconds(5 + random.nextInt(10)), particle); +// rotate.setByAngle(360); +// rotate.setCycleCount(RotateTransition.INDEFINITE); +// +// move.setOnFinished(e -> { +// +// particle.setTranslateY(height + random.nextInt(100)); +// particle.setTranslateX(random.nextInt((int) width)); +// animateParticle(particle, width, height); +// }); +// +// move.play(); +// rotate.play(); } } \ No newline at end of file diff --git a/src/main/java/com/boredxgames/tictactoeclient/presentation/GameController.java b/src/main/java/com/boredxgames/tictactoeclient/presentation/GameController.java index 159c9a5..4802061 100644 --- a/src/main/java/com/boredxgames/tictactoeclient/presentation/GameController.java +++ b/src/main/java/com/boredxgames/tictactoeclient/presentation/GameController.java @@ -11,6 +11,9 @@ import com.boredxgames.tictactoeclient.domain.services.GameService; import com.boredxgames.tictactoeclient.domain.services.game.GameBoard; import com.boredxgames.tictactoeclient.domain.services.game.OnlinePVPService; +import java.net.URL; +import java.util.Objects; +import java.util.ResourceBundle; import javafx.animation.PauseTransition; import javafx.application.Platform; import javafx.fxml.FXML; @@ -29,10 +32,6 @@ import javafx.scene.text.Text; import javafx.util.Duration; -import java.net.URL; -import java.util.Objects; -import java.util.ResourceBundle; - /** * @author Tasneem */ @@ -101,10 +100,11 @@ public class GameController implements Initializable, NavigationParameterAware { @Override public void initialize(URL url, ResourceBundle rb) { + cells = new Button[][]{ - {cell00, cell01, cell02}, - {cell10, cell11, cell12}, - {cell20, cell21, cell22} + {cell00, cell01, cell02}, + {cell10, cell11, cell12}, + {cell20, cell21, cell22} }; gameBoard = new GameBoard(); setupCellHandlers(); @@ -151,19 +151,31 @@ private void applyGameModeSettings() { // gameService = new OfflinePVEAIService(); // TODO: implement offline pve ai service } case ONLINE_PVP -> { + opponentTypeLabel.setText("ONLINE PLAYER"); difficultyBadge.setVisible(false); difficultyBadge.setManaged(false); changeDifficultyButton.setVisible(false); - changeDifficultyButton.setManaged(false); - gameService = OnlinePVPService.getInstance().setMoveListener((move) -> - { + changeDifficultyButton.setManaged(false); + System.out.println("before run later in switch"); + + gameService = OnlinePVPService.getInstance().setMoveListener((move) + -> { + Platform.runLater(() -> { updateCell(move.getCol(), move.getRow(), gameBoard.getCurrentPlayer()); gameBoard.switchPlayer(); enableBoard(); + + if (gameService instanceof OnlinePVPService) { + + ((OnlinePVPService) gameService).setIsMyTurn(true); + } }); }); + System.out.println("before check turn in switch"); + OnlinePVPService.getInstance().setupOnlineGame(); + } } } @@ -201,14 +213,32 @@ private void setupButtonHandlers() { } private void handleCellClick(int row, int col) { - if (!gameBoard.isValidMove(row, col)) return; + if (!gameBoard.isValidMove(row, col)) { + return; + } - char currentPlayer = gameBoard.getCurrentPlayer(); + if (gameService instanceof OnlinePVPService) { + boolean isMyTurn; + isMyTurn = ((OnlinePVPService) gameService).checkTurn(); + if (!isMyTurn) { + return; + } + } + + //TODO switch col and row + Move move = new Move(col, row); - Move move = new Move(row, col); + char currentPlayer = gameBoard.getCurrentPlayer(); gameService.makeMove(move, currentPlayer); updateCell(move.getCol(), move.getRow(), currentPlayer); + disableBoard(); + + if (gameService instanceof OnlinePVPService) { + + ((OnlinePVPService) gameService).setIsMyTurn(false); + } + gameBoard.switchPlayer(); Move nextMove = gameService.getNextMove(gameBoard, currentPlayer); @@ -341,17 +371,17 @@ private void showGameOverModal(GameState state) { case X_WINS: modalIcon.setText("emoji_events"); modalTitle.setText("Victory!"); - modalMessage.setText(gameMode == GameMode.OFFLINE_PVE ? - "The CPU didn't stand a chance against your moves." : - "Player 1 wins the game!"); + modalMessage.setText(gameMode == GameMode.OFFLINE_PVE + ? "The CPU didn't stand a chance against your moves." + : "Player 1 wins the game!"); break; case O_WINS: modalIcon.setText("sentiment_dissatisfied"); modalTitle.setText("Defeat!"); - modalMessage.setText(gameMode == GameMode.OFFLINE_PVE ? - "The CPU outsmarted you this time." : - "Player 2 wins the game!"); + modalMessage.setText(gameMode == GameMode.OFFLINE_PVE + ? "The CPU outsmarted you this time." + : "Player 2 wins the game!"); break; case DRAW: @@ -421,4 +451,4 @@ private void enableBoard() { } } } -} \ No newline at end of file +} From 297c46857b527b865e22dcd72e7ae91685eb613b Mon Sep 17 00:00:00 2001 From: Mahmoud Raafat <100778020+MahmoudRafaat@users.noreply.github.com> Date: Tue, 13 Jan 2026 19:45:36 +0200 Subject: [PATCH 23/26] Improve move handling and game state checks Refactored move handling logic for both online and offline modes to ensure moves are validated and applied consistently. Added game state checks after each move to handle game end scenarios immediately. Updated turn indicator and board enabling/disabling logic for better user experience. --- .../presentation/GameController.java | 93 +++++++++++++------ 1 file changed, 65 insertions(+), 28 deletions(-) diff --git a/src/main/java/com/boredxgames/tictactoeclient/presentation/GameController.java b/src/main/java/com/boredxgames/tictactoeclient/presentation/GameController.java index 159c9a5..9023b38 100644 --- a/src/main/java/com/boredxgames/tictactoeclient/presentation/GameController.java +++ b/src/main/java/com/boredxgames/tictactoeclient/presentation/GameController.java @@ -150,21 +150,28 @@ private void applyGameModeSettings() { changeDifficultyButton.setManaged(true); // gameService = new OfflinePVEAIService(); // TODO: implement offline pve ai service } - case ONLINE_PVP -> { - opponentTypeLabel.setText("ONLINE PLAYER"); - difficultyBadge.setVisible(false); - difficultyBadge.setManaged(false); - changeDifficultyButton.setVisible(false); - changeDifficultyButton.setManaged(false); - gameService = OnlinePVPService.getInstance().setMoveListener((move) -> - { - Platform.runLater(() -> { - updateCell(move.getCol(), move.getRow(), gameBoard.getCurrentPlayer()); - gameBoard.switchPlayer(); - enableBoard(); - }); - }); + case ONLINE_PVP -> { + opponentTypeLabel.setText("ONLINE PLAYER"); + difficultyBadge.setVisible(false); + difficultyBadge.setManaged(false); + changeDifficultyButton.setVisible(false); + changeDifficultyButton.setManaged(false); + gameService = OnlinePVPService.getInstance().setMoveListener((move) -> { + Platform.runLater(() -> { + if (gameBoard.makeMove(move.getRow(), move.getCol(), gameBoard.getCurrentPlayer())) { + updateCell(move.getRow(), move.getCol(), gameBoard.getCurrentPlayer()); + + if (gameBoard.getGameState() != GameState.IN_PROGRESS) { + handleGameEnd(); + } else { + gameBoard.switchPlayer(); + updateTurnIndicator(); + enableBoard(); + } } + }); + }); +} } } @@ -200,18 +207,51 @@ private void setupButtonHandlers() { }); } - private void handleCellClick(int row, int col) { - if (!gameBoard.isValidMove(row, col)) return; - - char currentPlayer = gameBoard.getCurrentPlayer(); + private void handleCellClick(int row, int col) { + if (!gameBoard.isValidMove(row, col)) return; - Move move = new Move(row, col); + char currentPlayer = gameBoard.getCurrentPlayer(); + + // Make the move on the local board first + if (!gameBoard.makeMove(row, col, currentPlayer)) { + return; + } + + Move move = new Move(row, col); + + // For online mode, send the move to server + if (gameMode == GameMode.ONLINE_PVP) { gameService.makeMove(move, currentPlayer); - updateCell(move.getCol(), move.getRow(), currentPlayer); + updateCell(row, col, currentPlayer); disableBoard(); - gameBoard.switchPlayer(); - - Move nextMove = gameService.getNextMove(gameBoard, currentPlayer); + + // Check for game end after our move + if (gameBoard.getGameState() != GameState.IN_PROGRESS) { + handleGameEnd(); + } else { + gameBoard.switchPlayer(); + updateTurnIndicator(); + } + return; + } + + // For offline modes (PVP/PVE) + gameService.makeMove(move, currentPlayer); + updateCell(row, col, currentPlayer); + gameBoard.switchPlayer(); + + // Check game state after player move + if (gameBoard.getGameState() != GameState.IN_PROGRESS) { + handleGameEnd(); + return; + } + + updateTurnIndicator(); + + // Handle CPU move for PVE + if (gameMode == GameMode.OFFLINE_PVE) { + disableBoard(); + Move nextMove = gameService.getNextMove(gameBoard, gameBoard.getCurrentPlayer()); if (nextMove != null) { Platform.runLater(() -> { if (gameBoard.makeMove(nextMove.getRow(), nextMove.getCol(), gameBoard.getCurrentPlayer())) { @@ -221,16 +261,13 @@ private void handleCellClick(int row, int col) { } else { gameBoard.switchPlayer(); updateTurnIndicator(); + enableBoard(); } } }); } - - GameState state = gameService.getOutcome(gameBoard); - if (state != GameState.IN_PROGRESS) { - handleGameEnd(); - } } +} private void updateCell(int row, int col, char player) { Button cell = cells[row][col]; From 955d198ffbcb9c6d0b900ca3fdca60cc73d9a9c3 Mon Sep 17 00:00:00 2001 From: Mahmoud Raafat <100778020+MahmoudRafaat@users.noreply.github.com> Date: Tue, 13 Jan 2026 20:12:34 +0200 Subject: [PATCH 24/26] Update HomeController.java --- .../tictactoeclient/presentation/HomeController.java | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/main/java/com/boredxgames/tictactoeclient/presentation/HomeController.java b/src/main/java/com/boredxgames/tictactoeclient/presentation/HomeController.java index 669a6b2..d7d4851 100644 --- a/src/main/java/com/boredxgames/tictactoeclient/presentation/HomeController.java +++ b/src/main/java/com/boredxgames/tictactoeclient/presentation/HomeController.java @@ -43,15 +43,7 @@ public void initialize(URL url, ResourceBundle rb) { if(scoreLabel != null) scoreLabel.setText("Score: " + currentUser.getScore()); } - Platform.runLater(() -> { - if (backgroundPane != null) { - double w = rootStack.getWidth() > 0 ? rootStack.getWidth() : 1280; - double h = rootStack.getHeight() > 0 ? rootStack.getHeight() : 800; - BackgroundHomeAnimation.startBackgroundAnimation(backgroundPane, w, h); - } - javafx.scene.Node viewport = rootStack.lookup(".viewport"); - if (viewport != null) viewport.setStyle("-fx-background-color: rgba(0,0,0,0);"); - }); + OnlinGameSession.getInstance().requestLeaderboard(); OnlinGameSession.getInstance().requestAvailablePlayers(); From 1e40e5dd3104d3b335f1b3e81daf4b51fa82e2db Mon Sep 17 00:00:00 2001 From: "ZEIAD-LAPTOP\\zeiad" Date: Tue, 13 Jan 2026 23:45:09 +0200 Subject: [PATCH 25/26] feat: enhance online multiplayer functionality and improve game state management --- .../services/communication/MessageRouter.java | 6 +- .../domain/services/game/GameBoard.java | 7 +- .../services/game/OfflinePVEAIService.java | 1 + .../services/game/OfflinePVPService.java | 3 +- .../services/game/OnlinePVPService.java | 23 +- .../presentation/GameController.java | 249 +++++++++--------- .../OfflineModeSelectionController.java | 6 +- 7 files changed, 143 insertions(+), 152 deletions(-) diff --git a/src/main/java/com/boredxgames/tictactoeclient/domain/services/communication/MessageRouter.java b/src/main/java/com/boredxgames/tictactoeclient/domain/services/communication/MessageRouter.java index 827bbc0..b523275 100644 --- a/src/main/java/com/boredxgames/tictactoeclient/domain/services/communication/MessageRouter.java +++ b/src/main/java/com/boredxgames/tictactoeclient/domain/services/communication/MessageRouter.java @@ -143,7 +143,11 @@ private void handleEvent(Message msg) { } case GAME_START -> { OnlineGameState.info = gson.fromJson(msg.getData(), GameStartInfo.class); - NavigationManager.navigate(Screens.GAME, NavigationAction.REPLACE, GameMode.ONLINE_PVP); + String player1Name = "You"; + String player2Name = "Opponent"; + NavigationManager.navigate(Screens.GAME, NavigationAction.REPLACE, new GameNavigationParams( + player1Name,player2Name, GameMode.ONLINE_PVP + )); } diff --git a/src/main/java/com/boredxgames/tictactoeclient/domain/services/game/GameBoard.java b/src/main/java/com/boredxgames/tictactoeclient/domain/services/game/GameBoard.java index 900730b..210766f 100644 --- a/src/main/java/com/boredxgames/tictactoeclient/domain/services/game/GameBoard.java +++ b/src/main/java/com/boredxgames/tictactoeclient/domain/services/game/GameBoard.java @@ -22,6 +22,7 @@ public class GameBoard { private GameState gameState; private int movesCount; + public GameBoard() { this(PLAYER_X); } @@ -58,6 +59,10 @@ public boolean makeMove(int row, int col) { } public boolean makeMove(int row, int col, char player) { + if (player != currentPlayer) { + return false; + } + if (!isValidMove(row, col)) { return false; } @@ -67,7 +72,7 @@ public boolean makeMove(int row, int col, char player) { updateGameState(); if (gameState == GameState.IN_PROGRESS) { - currentPlayer = (player == PLAYER_X) ? PLAYER_O : PLAYER_X; + currentPlayer = (player == PLAYER_X) ? PLAYER_O : PLAYER_X; } return true; diff --git a/src/main/java/com/boredxgames/tictactoeclient/domain/services/game/OfflinePVEAIService.java b/src/main/java/com/boredxgames/tictactoeclient/domain/services/game/OfflinePVEAIService.java index 16c299c..2f93b4b 100644 --- a/src/main/java/com/boredxgames/tictactoeclient/domain/services/game/OfflinePVEAIService.java +++ b/src/main/java/com/boredxgames/tictactoeclient/domain/services/game/OfflinePVEAIService.java @@ -7,6 +7,7 @@ import com.boredxgames.tictactoeclient.domain.model.GameState; import com.boredxgames.tictactoeclient.domain.model.Move; import com.boredxgames.tictactoeclient.domain.services.AIService; +import com.boredxgames.tictactoeclient.domain.services.GameService; /** * diff --git a/src/main/java/com/boredxgames/tictactoeclient/domain/services/game/OfflinePVPService.java b/src/main/java/com/boredxgames/tictactoeclient/domain/services/game/OfflinePVPService.java index 18c3697..c667efc 100644 --- a/src/main/java/com/boredxgames/tictactoeclient/domain/services/game/OfflinePVPService.java +++ b/src/main/java/com/boredxgames/tictactoeclient/domain/services/game/OfflinePVPService.java @@ -6,12 +6,13 @@ import com.boredxgames.tictactoeclient.domain.model.GameState; import com.boredxgames.tictactoeclient.domain.model.Move; +import com.boredxgames.tictactoeclient.domain.services.GameService; /** * * @author sheri */ -public class OfflinePVPService implements GameService{ +public class OfflinePVPService implements GameService { @Override public void makeMove(Move move, char currentPlayer) { diff --git a/src/main/java/com/boredxgames/tictactoeclient/domain/services/game/OnlinePVPService.java b/src/main/java/com/boredxgames/tictactoeclient/domain/services/game/OnlinePVPService.java index 30e143a..b6eedb7 100644 --- a/src/main/java/com/boredxgames/tictactoeclient/domain/services/game/OnlinePVPService.java +++ b/src/main/java/com/boredxgames/tictactoeclient/domain/services/game/OnlinePVPService.java @@ -12,28 +12,16 @@ public class OnlinePVPService implements GameService { private static OnlinePVPService instance; - private boolean isMyTurn = false; - public void setupOnlineGame() { + public boolean shouldPlayFirst() { System.out.println(" Check equality"+OnlineGameState.info.getPlayer1().equalsIgnoreCase(ServerConnectionManager.getInstance().getPlayer().getId())); if (OnlineGameState.info.getPlayer1().equalsIgnoreCase(ServerConnectionManager.getInstance().getPlayer().getId())) { - isMyTurn = true; + return true; } - } - - public void setIsMyTurn(boolean isMyTurn) { - this.isMyTurn = isMyTurn; - } - - public boolean isIsMyTurn() { - return isMyTurn; - } - - public boolean checkTurn() { - return isMyTurn; + return false; } public static OnlinePVPService getInstance() { @@ -80,6 +68,11 @@ public void makeMove(Move move, char currentPlayer) { connectionManager.sendMessage(msg); } + @Override + public void makeMove(Move move, char currentPlayer, GameBoard board) { + + } + @Override public Move getNextMove(GameBoard board, char currentPlayer) { // In online play, we don't calculate the next move locally. diff --git a/src/main/java/com/boredxgames/tictactoeclient/presentation/GameController.java b/src/main/java/com/boredxgames/tictactoeclient/presentation/GameController.java index 20f453a..891ebde 100644 --- a/src/main/java/com/boredxgames/tictactoeclient/presentation/GameController.java +++ b/src/main/java/com/boredxgames/tictactoeclient/presentation/GameController.java @@ -8,15 +8,12 @@ import com.boredxgames.tictactoeclient.domain.model.GameNavigationParams; import com.boredxgames.tictactoeclient.domain.model.GameState; import com.boredxgames.tictactoeclient.domain.model.Move; +import com.boredxgames.tictactoeclient.domain.services.AIService; import com.boredxgames.tictactoeclient.domain.services.GameService; import com.boredxgames.tictactoeclient.domain.services.game.GameBoard; -import com.boredxgames.tictactoeclient.domain.services.AIService; -import com.boredxgames.tictactoeclient.domain.services.game.OnlinePVPService; -import java.net.URL; -import java.util.Objects; -import java.util.ResourceBundle; import com.boredxgames.tictactoeclient.domain.services.game.OfflinePVEAIService; import com.boredxgames.tictactoeclient.domain.services.game.OfflinePVPService; +import com.boredxgames.tictactoeclient.domain.services.game.OnlinePVPService; import javafx.animation.PauseTransition; import javafx.application.Platform; import javafx.fxml.FXML; @@ -35,9 +32,10 @@ import javafx.scene.text.Text; import javafx.util.Duration; -/** - * @author Tasneem - */ +import java.net.URL; +import java.util.Objects; +import java.util.ResourceBundle; + public class GameController implements Initializable, NavigationParameterAware { public GridPane gameGrid; @@ -100,14 +98,15 @@ public class GameController implements Initializable, NavigationParameterAware { private String player2Name = "Player 2"; private GameService gameService; + private char localPlayerId; @Override public void initialize(URL url, ResourceBundle rb) { cells = new Button[][]{ - {cell00, cell01, cell02}, - {cell10, cell11, cell12}, - {cell20, cell21, cell22} + {cell00, cell01, cell02}, + {cell10, cell11, cell12}, + {cell20, cell21, cell22} }; gameBoard = new GameBoard(); setupCellHandlers(); @@ -121,7 +120,6 @@ public void setNavigationParameter(Object parameter) { this.player2Name = params.player2(); this.gameMode = params.mode(); } else { - // Default this.gameMode = GameMode.OFFLINE_PVP; this.player1Name = "Player 1"; this.player2Name = "Player 2"; @@ -146,6 +144,7 @@ private void applyGameModeSettings() { difficultyBadge.setManaged(false); changeDifficultyButton.setVisible(false); changeDifficultyButton.setManaged(false); + gameService = new OfflinePVPService(); } case OFFLINE_PVE -> { @@ -154,30 +153,45 @@ private void applyGameModeSettings() { difficultyBadge.setManaged(true); changeDifficultyButton.setVisible(true); changeDifficultyButton.setManaged(true); + gameService = new OfflinePVEAIService(); } - case ONLINE_PVP -> { - opponentTypeLabel.setText("ONLINE PLAYER"); - difficultyBadge.setVisible(false); - difficultyBadge.setManaged(false); - changeDifficultyButton.setVisible(false); - changeDifficultyButton.setManaged(false); - gameService = OnlinePVPService.getInstance().setMoveListener((move) -> { - Platform.runLater(() -> { - if (gameBoard.makeMove(move.getRow(), move.getCol(), gameBoard.getCurrentPlayer())) { - updateCell(move.getRow(), move.getCol(), gameBoard.getCurrentPlayer()); + case ONLINE_PVP -> { + opponentTypeLabel.setText("ONLINE PLAYER"); + difficultyBadge.setVisible(false); + difficultyBadge.setManaged(false); + changeDifficultyButton.setVisible(false); + changeDifficultyButton.setManaged(false); - if (gameBoard.getGameState() != GameState.IN_PROGRESS) { - handleGameEnd(); - } else { - gameBoard.switchPlayer(); - updateTurnIndicator(); + boolean amIPlayer1 = OnlinePVPService.getInstance().shouldPlayFirst(); + localPlayerId = amIPlayer1 ? GameBoard.PLAYER_X : GameBoard.PLAYER_O; + + System.out.println("I am Player: " + localPlayerId); + + gameService = OnlinePVPService.getInstance().setMoveListener((move) -> { + Platform.runLater(() -> { + char remotePlayer = (localPlayerId == GameBoard.PLAYER_X) ? GameBoard.PLAYER_O : GameBoard.PLAYER_X; + + if (gameBoard.makeMove(move.getRow(), move.getCol(), remotePlayer)) { + updateCell(move.getRow(), move.getCol(), remotePlayer); + + if (gameBoard.getGameState() != GameState.IN_PROGRESS) { + handleGameEnd(); + } else { + updateTurnIndicator(); + enableBoard(); + } + } + }); + }); + + if (amIPlayer1) { enableBoard(); + } else { + disableBoard(); } + updateTurnIndicator(); } - }); - }); -} } } @@ -196,7 +210,7 @@ private void setupButtonHandlers() { playAgainButton.setOnAction(e -> resetGame()); mainMenuButton.setOnAction(e -> { - NavigationManager.navigate(Screens.PRIMARY, NavigationAction.REPLACE_ALL); // TODO: change to mode selection screen + NavigationManager.navigate(Screens.PRIMARY, NavigationAction.REPLACE_ALL); }); backButton.setOnAction(e -> { @@ -209,111 +223,90 @@ private void setupButtonHandlers() { }); changeDifficultyButton.setOnAction(e -> { - // TODO change difficulty }); } private void handleCellClick(int row, int col) { - if (!gameBoard.isValidMove(row, col)) return; + if (!gameBoard.isValidMove(row, col)) { + return; + } - char currentPlayer = gameBoard.getCurrentPlayer(); + if (gameMode == GameMode.ONLINE_PVP && gameBoard.getCurrentPlayer() != localPlayerId) { + return; + } switch (gameMode) { - case OFFLINE_PVP -> { - // Player vs Player - - gameBoard.makeMove(row, col, currentPlayer); - updateCell(row, col, currentPlayer); - - if (!checkGameEnd()) { + case OFFLINE_PVP -> handleOfflinePvp(row, col); + case OFFLINE_PVE -> handleOfflinePve(row, col); + case ONLINE_PVP -> handleOnlinePvp(row, col); + } + } - updateTurnIndicator(); - } - return; - } + private void handleOfflinePvp(int row, int col) { + char currentPlayer = gameBoard.getCurrentPlayer(); - case OFFLINE_PVE -> { - // Player X moves - gameBoard.makeMove(row, col, GameBoard.PLAYER_X); - updateCell(row, col, GameBoard.PLAYER_X); - if (checkGameEnd()) { - return; - } + performMove(row, col, currentPlayer); - // AI O moves - disableBoard(); - PauseTransition pause = new PauseTransition(Duration.millis(500)); // AI "thinking" - pause.setOnFinished(e -> { - Move aiMove = gameService.getNextMove(gameBoard, GameBoard.PLAYER_O); - gameService.makeMove(aiMove, GameBoard.PLAYER_O, gameBoard); - - if (aiMove != null) { - updateCell(aiMove.getRow(), aiMove.getCol(), GameBoard.PLAYER_O); - } - - if (!checkGameEnd()) { - enableBoard(); - gameBoard.switchPlayer(); - updateTurnIndicator(); - } - }); - pause.play(); - return; - } + if (!checkGameEnd()) { + gameBoard.switchPlayer(); + updateTurnIndicator(); } + } + + private void handleOfflinePve(int row, int col) { + performMove(row, col, GameBoard.PLAYER_X); - if (!gameBoard.makeMove(row, col, currentPlayer)) { + if (checkGameEnd()) { return; } - Move move = new Move(row, col); + scheduleAiTurn(); + } - if (gameMode == GameMode.ONLINE_PVP) { - gameService.makeMove(move, currentPlayer); - updateCell(row, col, currentPlayer); - disableBoard(); + private void handleOnlinePvp(int row, int col) { + performMove(row, col, localPlayerId); - if (gameBoard.getGameState() != GameState.IN_PROGRESS) { - handleGameEnd(); - } else { - gameBoard.switchPlayer(); - updateTurnIndicator(); - } - return; - } + Move move = new Move(row, col); + gameService.makeMove(move, localPlayerId); - gameService.makeMove(move, currentPlayer); - updateCell(row, col, currentPlayer); - gameBoard.switchPlayer(); + disableBoard(); if (gameBoard.getGameState() != GameState.IN_PROGRESS) { handleGameEnd(); - return; + } else { + updateTurnIndicator(); } + } - updateTurnIndicator(); + private void performMove(int row, int col, char player) { + gameBoard.makeMove(row, col, player); + updateCell(row, col, player); + } - if (gameMode == GameMode.OFFLINE_PVE) { - disableBoard(); - Move nextMove = gameService.getNextMove(gameBoard, gameBoard.getCurrentPlayer()); - if (nextMove != null) { - Platform.runLater(() -> { - if (gameBoard.makeMove(nextMove.getRow(), nextMove.getCol(), gameBoard.getCurrentPlayer())) { - updateCell(nextMove.getRow(), nextMove.getCol(), gameBoard.getCurrentPlayer()); - if (gameBoard.getGameState() != GameState.IN_PROGRESS) { - handleGameEnd(); - } else { - gameBoard.switchPlayer(); - updateTurnIndicator(); - enableBoard(); - } - } - }); - } - } + private void scheduleAiTurn() { + disableBoard(); + + PauseTransition pause = new PauseTransition(Duration.millis(500)); + pause.setOnFinished(e -> executeAiMove()); + pause.play(); } + + private void executeAiMove() { + Move aiMove = gameService.getNextMove(gameBoard, GameBoard.PLAYER_O); + + if (aiMove != null) { + gameService.makeMove(aiMove, GameBoard.PLAYER_O, gameBoard); + performMove(aiMove.getRow(), aiMove.getCol(), GameBoard.PLAYER_O); + } + + if (!checkGameEnd()) { + enableBoard(); + gameBoard.switchPlayer(); + updateTurnIndicator(); + } } + private boolean checkGameEnd() { GameState state = gameBoard.getGameState(); if (state != GameState.IN_PROGRESS) { @@ -322,6 +315,7 @@ private boolean checkGameEnd() { } return false; } + private void updateCell(int row, int col, char player) { Button cell = cells[row][col]; @@ -347,27 +341,12 @@ private void updateCell(int row, int col, char player) { } private void updateTurnIndicator() { - char current = gameBoard.getCurrentPlayer(); - boolean isPlayerX = (current == GameBoard.PLAYER_X); + char currentPlayer = gameBoard.getCurrentPlayer(); - if (gameMode == GameMode.OFFLINE_PVP) { - if (isPlayerX) { - setActiveCard(playerCard, opponentCard); - } else { - setActiveCard(opponentCard, playerCard); - } - } else if (gameMode == GameMode.OFFLINE_PVE) { - if (isPlayerX) { - setActiveCard(playerCard, opponentCard); - } else { - setActiveCard(opponentCard, playerCard); - } + if (currentPlayer == GameBoard.PLAYER_X) { + setActiveCard(playerCard, opponentCard); } else { - if (isPlayerTurn) { - setActiveCard(playerCard, opponentCard); - } else { - setActiveCard(opponentCard, playerCard); - } + setActiveCard(opponentCard, playerCard); } } @@ -490,8 +469,18 @@ public void resetGame() { } modalOverlay.setVisible(false); + updateTurnIndicator(); - enableBoard(); + + if (gameMode == GameMode.ONLINE_PVP) { + if (localPlayerId == GameBoard.PLAYER_X) { + enableBoard(); + } else { + disableBoard(); + } + } else { + enableBoard(); + } } private void disableBoard() { @@ -513,4 +502,4 @@ private void enableBoard() { } } } -} +} \ No newline at end of file diff --git a/src/main/java/com/boredxgames/tictactoeclient/presentation/OfflineModeSelectionController.java b/src/main/java/com/boredxgames/tictactoeclient/presentation/OfflineModeSelectionController.java index 6a2c593..99f8601 100644 --- a/src/main/java/com/boredxgames/tictactoeclient/presentation/OfflineModeSelectionController.java +++ b/src/main/java/com/boredxgames/tictactoeclient/presentation/OfflineModeSelectionController.java @@ -29,14 +29,12 @@ private void handleBackToMainMenu(ActionEvent event) { @FXML private void handlePlayerVsPlayer(MouseEvent event) { - // TODO: Navigate to PvP game screen - System.out.println("Player vs Player mode selected"); + NavigationManager.navigate(Screens.PVP_SETUP, NavigationAction.PUSH); } @FXML private void handlePlayerVsCPU(MouseEvent event) { - // TODO: Navigate to PvE game screen - System.out.println("Player vs CPU mode selected"); + NavigationManager.navigate(Screens.DifficultySelection, NavigationAction.PUSH); } } \ No newline at end of file From 46e9a747ddc4f1cc95f36a8ceffafa024aa62562 Mon Sep 17 00:00:00 2001 From: "ZEIAD-LAPTOP\\zeiad" Date: Wed, 14 Jan 2026 00:11:53 +0200 Subject: [PATCH 26/26] feat: enhance authentication dialog styling and implement replay functionality --- .../domain/managers/navigation/Screens.java | 1 - .../AuthenticationController.java | 31 ++++++++++++++++++- .../presentation/GameController.java | 30 +++++++++++++----- .../css/features/authentication_screen.css | 21 +++++++++++++ 4 files changed, 73 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/boredxgames/tictactoeclient/domain/managers/navigation/Screens.java b/src/main/java/com/boredxgames/tictactoeclient/domain/managers/navigation/Screens.java index cc73466..2d39688 100644 --- a/src/main/java/com/boredxgames/tictactoeclient/domain/managers/navigation/Screens.java +++ b/src/main/java/com/boredxgames/tictactoeclient/domain/managers/navigation/Screens.java @@ -11,7 +11,6 @@ public enum Screens { GAME("game_screen"), Home("home"), PVP_SETUP("pvp_setup"), - GAME("game_screen"), DifficultySelection("difficulty_selection"), RECORDINGS("RecordingsListScreen"); diff --git a/src/main/java/com/boredxgames/tictactoeclient/presentation/AuthenticationController.java b/src/main/java/com/boredxgames/tictactoeclient/presentation/AuthenticationController.java index 2e095b4..bebe657 100644 --- a/src/main/java/com/boredxgames/tictactoeclient/presentation/AuthenticationController.java +++ b/src/main/java/com/boredxgames/tictactoeclient/presentation/AuthenticationController.java @@ -121,10 +121,39 @@ public static void showUserAlert(String message) { alert.setHeaderText(null); alert.setContentText(message); + DialogPane dialogPane = alert.getDialogPane(); + + // 1. Style the main pane + dialogPane.setStyle( + "-fx-background-color: #1e1e1e; " + + "-fx-border-color: #333333; " + + "-fx-border-width: 2px;" + ); + + // 2. Style all labels (Content text) + dialogPane.lookupAll(".label").forEach(node -> + node.setStyle("-fx-text-fill: #e0e0e0; -fx-font-family: 'Segoe UI';") + ); + + // 3. Style the buttons + dialogPane.lookupAll(".button").forEach(node -> { + node.setStyle( + "-fx-background-color: #3c3f41; " + + "-fx-text-fill: white; " + + "-fx-background-radius: 4; " + + "-fx-cursor: hand;" + ); + + // Add simple hover effect via code + node.setOnMouseEntered(e -> node.setStyle("-fx-background-color: #4b4d4d; -fx-text-fill: white; -fx-background-radius: 4;")); + node.setOnMouseExited(e -> node.setStyle("-fx-background-color: #3c3f41; -fx-text-fill: white; -fx-background-radius: 4;")); + }); + + // Set the owner window javafx.stage.Window.getWindows().stream() .filter(javafx.stage.Window::isShowing) .findFirst() - .ifPresent(window -> alert.initOwner(window)); + .ifPresent(alert::initOwner); alert.showAndWait().ifPresent(type -> { if ("Server is out of service".equals(message) || "Internal Server Error".equals(message)) { diff --git a/src/main/java/com/boredxgames/tictactoeclient/presentation/GameController.java b/src/main/java/com/boredxgames/tictactoeclient/presentation/GameController.java index 97c474d..7b4e3df 100644 --- a/src/main/java/com/boredxgames/tictactoeclient/presentation/GameController.java +++ b/src/main/java/com/boredxgames/tictactoeclient/presentation/GameController.java @@ -24,6 +24,7 @@ import javafx.animation.PauseTransition; import javafx.animation.Timeline; +import javafx.application.Platform; import javafx.fxml.FXML; import javafx.fxml.Initializable; import javafx.scene.control.Button; @@ -324,13 +325,6 @@ private void executeAiMove() { enableBoard(); gameBoard.switchPlayer(); updateTurnIndicator(); - case OFFLINE_PVP -> { - gameBoard.makeMove(row, col, currentPlayer); - updateCell(row, col, currentPlayer); - if (!checkGameEnd()) { - updateTurnIndicator(); - } - } } } @@ -371,7 +365,7 @@ private void updateTurnIndicator() { setActiveCard(opponentCard, playerCard); } if (gameMode == GameMode.OFFLINE_PVP || gameMode == GameMode.OFFLINE_PVE || gameMode == GameMode.REPLAY) { - if (isPlayerX) { + if (localPlayerId == GameBoard.PLAYER_X) { setActiveCard(playerCard, opponentCard); } else { setActiveCard(opponentCard, playerCard); @@ -510,4 +504,24 @@ private void enableBoard() { } } } + + private void startReplay() { + disableBoard(); + Timeline timeline = new Timeline(); + int delay = 0; + + for (var move : replayData.moves()) { + delay += 1000; + KeyFrame frame = new KeyFrame(Duration.millis(delay), e -> { + gameBoard.forceMove(move.row(), move.col(), move.player()); + updateCell(move.row(), move.col(), move.player()); + + gameBoard.switchPlayer(); + updateTurnIndicator(); + checkGameEnd(); + }); + timeline.getKeyFrames().add(frame); + } + timeline.play(); + } } \ No newline at end of file diff --git a/src/main/resources/css/features/authentication_screen.css b/src/main/resources/css/features/authentication_screen.css index adca663..8d82b96 100644 --- a/src/main/resources/css/features/authentication_screen.css +++ b/src/main/resources/css/features/authentication_screen.css @@ -122,3 +122,24 @@ -fx-text-fill: #4f5ef7; -fx-underline: true; } + +.dialog-pane { + -fx-background-color: #2b2b2b; +} + +.dialog-pane .label { + -fx-text-fill: white; +} + +.dialog-pane:header .header-panel { + -fx-background-color: #3c3f41; +} + +.dialog-pane .button { + -fx-background-color: #4a4a4a; + -fx-text-fill: white; +} + +.dialog-pane .button:hover { + -fx-background-color: #5a5a5a; +} \ No newline at end of file