diff --git a/src/main/java/com/boredxgames/tictactoeclient/domain/model/GameEndInfo.java b/src/main/java/com/boredxgames/tictactoeclient/domain/model/GameEndInfo.java new file mode 100644 index 0000000..33e9195 --- /dev/null +++ b/src/main/java/com/boredxgames/tictactoeclient/domain/model/GameEndInfo.java @@ -0,0 +1,32 @@ +/* + * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license + * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template + */ +package com.boredxgames.tictactoeclient.domain.model; + +/** + * + * @author moham + */ +public class GameEndInfo { + + private String roomId; + private String winnerId; // If this is null, it means DRAW + + public GameEndInfo() { + } + + public GameEndInfo(String roomId, String winnerId) { + this.roomId = roomId; + this.winnerId = winnerId; + } + + public String getRoomId() { + return roomId; + } + + public String getWinnerId() { + return winnerId; + } +} + 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 b523275..ab0659d 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 @@ -16,6 +16,7 @@ import com.boredxgames.tictactoeclient.domain.network.ServerConnectionManager; import com.boredxgames.tictactoeclient.domain.services.game.OnlinePVPService; import com.boredxgames.tictactoeclient.presentation.AuthenticationController; +import com.boredxgames.tictactoeclient.presentation.GameController; import com.boredxgames.tictactoeclient.presentation.HomeController; import com.google.gson.Gson; import javafx.application.Platform; @@ -26,6 +27,8 @@ public class MessageRouter { private static ServerConnectionManager connection; private Gson gson = new Gson(); private static HomeController homeController; + private static GameController gameController; + private MessageRouter() { connection = ServerConnectionManager.getInstance(); @@ -56,6 +59,9 @@ public static void setHomeController(HomeController controller) { homeController = controller; } + public static void setGameController(GameController controller) { + gameController = controller; + } private Message handleRequest(Message msg) { Action action = msg.getHeader().getAction(); @@ -73,27 +79,20 @@ private Message handleRequest(Message msg) { private void handleResponse(Message msg) { Action action = msg.getHeader().getAction(); - System.out.println(action); + System.out.println("Response Action: " + action); switch (action) { case LOGIN_SUCCESS -> { System.out.println("Login Success"); 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 -> { - System.out.println("Registration success"); - AuthResponseEntity responseData = gson.fromJson(msg.getData(), AuthResponseEntity.class); AuthenticationController.showUserAlert("Registration success"); - } 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."); @@ -103,6 +102,7 @@ private void handleResponse(Message msg) { if (homeController != null) { Platform.runLater(() -> homeController.updatePlayersList(info)); } + } case GAME_RESPONSE -> { GameResponseInfo info = gson.fromJson(msg.getData(), GameResponseInfo.class); @@ -116,22 +116,20 @@ private void handleResponse(Message msg) { Platform.runLater(() -> homeController.updateLeaderboardUI(info)); } } + case GAME_START -> { + handleGameStart(msg); + } case SEND_GAME_UPDATE -> { MoveInfo moveInfo = gson.fromJson(msg.getData(), MoveInfo.class); OnlinePVPService.onIncomingMove(moveInfo); } - - default -> { - System.out.println("Unknown Action: " + action); - - } + default -> System.out.println("Unknown Action: " + action); } - ; } - private void handleEvent(Message msg) { +private void handleEvent(Message msg) { Action action = msg.getHeader().getAction(); - System.out.println(action); + System.out.println("Event Action: " + action); switch (action) { case REQUEST_GAME -> { @@ -139,24 +137,25 @@ private void handleEvent(Message msg) { if (homeController != null) { Platform.runLater(() -> homeController.showIncomingGameRequest(info)); } - } case GAME_START -> { - OnlineGameState.info = gson.fromJson(msg.getData(), GameStartInfo.class); - String player1Name = "You"; - String player2Name = "Opponent"; - NavigationManager.navigate(Screens.GAME, NavigationAction.REPLACE, new GameNavigationParams( - player1Name,player2Name, GameMode.ONLINE_PVP - )); - - } - - default -> { - System.out.println("Unknown Action: " + action); - + handleGameStart(msg); } + default -> System.out.println("Unknown Action: " + action); } - ; + } + + // *** NEW HELPER METHOD *** + private void handleGameStart(Message msg) { + OnlineGameState.info = gson.fromJson(msg.getData(), GameStartInfo.class); + String player1Name = "You"; + String player2Name = "Opponent"; + + Platform.runLater(() -> { + NavigationManager.navigate(Screens.GAME, NavigationAction.REPLACE, new GameNavigationParams( + player1Name, player2Name, GameMode.ONLINE_PVP + )); + }); } private void handleError(Message msg) { @@ -179,6 +178,10 @@ private void handleError(Message msg) { if (homeController != null) { Platform.runLater(() -> homeController.showErrorAlert(errorMessage)); } + if (gameController != null) { + Platform.runLater(() -> gameController.showErrorAlert(errorMessage)); + } + } case PLAYER_BUSY -> { @@ -187,6 +190,7 @@ private void handleError(Message msg) { if (homeController != null) { Platform.runLater(() -> homeController.showErrorAlert(errorMsg)); } + } case PENDING_REQUEST_EXISTS -> { String errorMsg = "You already have a request pending."; @@ -206,10 +210,12 @@ private void handleError(Message msg) { case ROOM_NOT_FOUND -> { String errorMsg = "The game session is no longer available."; if (homeController != null) { - // Close the "Accept/Decline" popup if open homeController.dismissIncomingRequest(); Platform.runLater(() -> homeController.showErrorAlert(errorMsg)); } + if (gameController != null) { + Platform.runLater(() -> gameController.showErrorAlert(errorMsg)); + } } case INVALID_OPPONENT -> { @@ -218,6 +224,9 @@ private void handleError(Message msg) { homeController.dismissIncomingRequest(); Platform.runLater(() -> homeController.showErrorAlert(errorMsg)); } + if (gameController != null) { + Platform.runLater(() -> gameController.showErrorAlert(errorMsg)); + } } case INVALID_CREDENTIAL -> { System.out.println("INVALID_CREDENTIAL"); 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 b6eedb7..5f163c5 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 @@ -1,5 +1,8 @@ package com.boredxgames.tictactoeclient.domain.services.game; +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.*; import com.boredxgames.tictactoeclient.domain.network.ServerConnectionManager; import com.boredxgames.tictactoeclient.domain.services.GameService; @@ -42,9 +45,18 @@ public OnlinePVPService setMoveListener(Consumer listener) { return this; } - public static void onIncomingMove(MoveInfo moveInfo) { + public static void onIncomingMove(MoveInfo moveInfo) { Gson gson = new Gson(); - Move move = gson.fromJson(moveInfo.getMove(), Move.class); + Object moveData = moveInfo.getMove(); + Move move; + + if (moveData instanceof String) { + move = gson.fromJson((String) moveData, Move.class); + } else { + String json = gson.toJson(moveData); + move = gson.fromJson(json, Move.class); + } + if (moveListener != null) { moveListener.accept(move); } @@ -56,10 +68,8 @@ 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, @@ -67,7 +77,24 @@ public void makeMove(Move move, char currentPlayer) { ); connectionManager.sendMessage(msg); } + + public void sendGameEnd(String winnerId) { + ServerConnectionManager connectionManager = ServerConnectionManager.getInstance(); + + if (OnlineGameState.info == null) return; + String roomId = OnlineGameState.info.getRoomId(); + GameEndInfo endInfo = new GameEndInfo(roomId, winnerId); + + Message msg = Message.createMessage( + MessageType.RESPONSE, + Action.GAME_END, + endInfo + ); + + connectionManager.sendMessage(msg); + NavigationManager.navigate(Screens.Home, NavigationAction.REPLACE); + } @Override public void makeMove(Move move, char currentPlayer, GameBoard board) { diff --git a/src/main/java/com/boredxgames/tictactoeclient/presentation/GameController.java b/src/main/java/com/boredxgames/tictactoeclient/presentation/GameController.java index 7b4e3df..8065a03 100644 --- a/src/main/java/com/boredxgames/tictactoeclient/presentation/GameController.java +++ b/src/main/java/com/boredxgames/tictactoeclient/presentation/GameController.java @@ -16,6 +16,8 @@ import com.boredxgames.tictactoeclient.domain.services.game.OfflinePVPService; import com.boredxgames.tictactoeclient.domain.services.game.OnlinePVPService; import com.boredxgames.tictactoeclient.domain.model.GameRecord; +import com.boredxgames.tictactoeclient.domain.model.OnlineGameState; +import com.boredxgames.tictactoeclient.domain.services.communication.MessageRouter; import com.boredxgames.tictactoeclient.domain.services.storage.GameRecordingService; import java.net.URL; import java.util.Objects; @@ -97,6 +99,27 @@ public void initialize(URL url, ResourceBundle rb) { gameBoard = new GameBoard(); setupCellHandlers(); setupButtonHandlers(); + MessageRouter.setGameController(this); + } + public void showErrorAlert(String message) { + Platform.runLater(() -> { + modalIcon.setText("error"); + modalTitle.setText("Connection Error"); + modalMessage.setText(message); + + playAgainButton.setVisible(false); + playAgainButton.setManaged(false); + + if (saveGameButton != null) { + saveGameButton.setVisible(false); + saveGameButton.setManaged(false); + } + + mainMenuButton.setVisible(true); + mainMenuButton.setManaged(true); + modalOverlay.setVisible(true); + disableBoard(); + }); } @Override @@ -270,7 +293,6 @@ private void handleOfflinePvp(int row, int col) { performMove(row, col, currentPlayer); if (!checkGameEnd()) { - gameBoard.switchPlayer(); updateTurnIndicator(); } } @@ -313,20 +335,19 @@ private void scheduleAiTurn() { pause.play(); } - private void executeAiMove() { - Move aiMove = gameService.getNextMove(gameBoard, GameBoard.PLAYER_O); + 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 (aiMove != null) { + + performMove(aiMove.getRow(), aiMove.getCol(), GameBoard.PLAYER_O); + } - if (!checkGameEnd()) { - enableBoard(); - gameBoard.switchPlayer(); - updateTurnIndicator(); - } + if (!checkGameEnd()) { + enableBoard(); + updateTurnIndicator(); } +} @@ -378,6 +399,8 @@ private void setActiveCard(VBox activeCard, VBox inactiveCard) { inactiveCard.getStyleClass().remove("active-card"); } + // In GameController.java, update handleGameEnd: + private void handleGameEnd() { GameState state = gameBoard.getGameState(); @@ -389,57 +412,102 @@ private void handleGameEnd() { cells[row][col].getStyleClass().add("cell-winning"); } } + char winnerChar = GameBoard.EMPTY; + if (state == GameState.X_WINS) winnerChar = GameBoard.PLAYER_X; + else if (state == GameState.O_WINS) winnerChar = GameBoard.PLAYER_O; - if (state == GameState.X_WINS) { + if (winnerChar == GameBoard.PLAYER_X) { if (gameMode != GameMode.REPLAY) { playerScore++; playerScoreLabel.setText(String.valueOf(playerScore)); } - playVictoryVideo(); - } else if (state == GameState.O_WINS) { + } else if (winnerChar == GameBoard.PLAYER_O) { if (gameMode != GameMode.REPLAY) { opponentScore++; opponentScoreLabel.setText(String.valueOf(opponentScore)); } } + if (gameMode == GameMode.ONLINE_PVP) { + + + String winnerId = "DRAW"; + if (state == GameState.X_WINS) { + // Player 1 is always X + winnerId = OnlineGameState.info.getPlayer1(); + } else if (state == GameState.O_WINS) { + winnerId = OnlineGameState.info.getPlayer2(); + } + + OnlinePVPService.getInstance().sendGameEnd(winnerId); + + // Play video check (Keep existing logic) + boolean amIWinner = (winnerChar == localPlayerId); + if (amIWinner) playVictoryVideo(); + + } else { + // Offline Video Logic (Keep existing logic) + if (winnerChar == GameBoard.PLAYER_X) playVictoryVideo(); + } + + // 4. Show Modal (Keep existing code) PauseTransition pause = new PauseTransition(Duration.millis(800)); pause.setOnFinished(e -> showGameOverModal(state)); pause.play(); } - private void showGameOverModal(GameState state) { - switch (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!"); - 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!"); - break; - case DRAW: - modalIcon.setText("handshake"); - modalTitle.setText("Draw!"); - modalMessage.setText("It's a tie! Both players played well."); - break; + private void showGameOverModal(GameState state) { + String title = ""; + String message = ""; + String icon = ""; + + if (state == GameState.DRAW) { + icon = "handshake"; + title = "Draw!"; + message = "It's a tie! Both players played well."; + } else { + char winner = (state == GameState.X_WINS) ? GameBoard.PLAYER_X : GameBoard.PLAYER_O; + + if (gameMode == GameMode.ONLINE_PVP) { + if (winner == localPlayerId) { + icon = "emoji_events"; + title = "Victory!"; + message = "You won the match!"; + } else { + icon = "sentiment_dissatisfied"; + title = "Defeat!"; + message = "Better luck next time."; + } + } + else { + if (winner == GameBoard.PLAYER_X) { + icon = "emoji_events"; + title = "Victory!"; + message = (gameMode == GameMode.OFFLINE_PVE) + ? "The CPU didn't stand a chance." + : "Player 1 wins!"; + } else { + icon = "sentiment_dissatisfied"; + title = (gameMode == GameMode.OFFLINE_PVE) ? "Defeat!" : "Player 2 Wins!"; + message = (gameMode == GameMode.OFFLINE_PVE) + ? "The CPU outsmarted you." + : "Player 2 takes the round."; + } + } } + modalIcon.setText(icon); + modalTitle.setText(title); + modalMessage.setText(message); + if (saveGameButton != null) { saveGameButton.setText("Save Replay"); saveGameButton.setDisable(false); - boolean canSave = (gameMode == GameMode.OFFLINE_PVE || gameMode == GameMode.OFFLINE_PVP); saveGameButton.setVisible(canSave); saveGameButton.setManaged(canSave); } - + if (gameMode == GameMode.REPLAY) { playAgainButton.setVisible(false); playAgainButton.setManaged(false);