diff --git a/src/main/java/com/boredxgames/tictactoeclient/domain/managers/navigation/NavigationManager.java b/src/main/java/com/boredxgames/tictactoeclient/domain/managers/navigation/NavigationManager.java index c447254..53b9c92 100644 --- a/src/main/java/com/boredxgames/tictactoeclient/domain/managers/navigation/NavigationManager.java +++ b/src/main/java/com/boredxgames/tictactoeclient/domain/managers/navigation/NavigationManager.java @@ -20,13 +20,13 @@ private NavigationManager() { private static final Stack screenStack = new Stack<>(); public static Scene init() throws IOException { -// شاشة البداية + current = new ScreenNavigationEntry(Screens.PRIMARY, null, null); Parent root = initRoot(current.screen().getName()); scene = new Scene(root, 640, 480); - // طبق الثيم على الـ Scene الرئيسي وفعل listener لأي تغيير مستقبلي + ThemeManager.init(scene); return scene; diff --git a/src/main/java/com/boredxgames/tictactoeclient/domain/model/Move.java b/src/main/java/com/boredxgames/tictactoeclient/domain/model/Move.java index 3b84e6e..eafb058 100644 --- a/src/main/java/com/boredxgames/tictactoeclient/domain/model/Move.java +++ b/src/main/java/com/boredxgames/tictactoeclient/domain/model/Move.java @@ -1,9 +1,13 @@ package com.boredxgames.tictactoeclient.domain.model; + +import com.boredxgames.tictactoeclient.domain.services.game.GameBoard; + /** * @author Tasneem */ public class Move { - private final int row; + + private final int row; private final int col; public Move(int row, int col) { @@ -11,11 +15,6 @@ public Move(int row, int col) { this.col = col; } - public int getRow() { - return row; - } - - public int getCol() { - return col; - } + public int getRow() { return row; } + public int getCol() { return col; } } diff --git a/src/main/java/com/boredxgames/tictactoeclient/domain/services/AIDifficulty.java b/src/main/java/com/boredxgames/tictactoeclient/domain/services/AIDifficulty.java new file mode 100644 index 0000000..cc17852 --- /dev/null +++ b/src/main/java/com/boredxgames/tictactoeclient/domain/services/AIDifficulty.java @@ -0,0 +1,15 @@ +/* + * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license + * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Enum.java to edit this template + */ +package com.boredxgames.tictactoeclient.domain.services; + +/** + * + * @author sheri + */ +public enum AIDifficulty { + EASY, + MEDIUM, + HARD +} diff --git a/src/main/java/com/boredxgames/tictactoeclient/domain/services/AIService.java b/src/main/java/com/boredxgames/tictactoeclient/domain/services/AIService.java new file mode 100644 index 0000000..3c8c49a --- /dev/null +++ b/src/main/java/com/boredxgames/tictactoeclient/domain/services/AIService.java @@ -0,0 +1,53 @@ +/* + * 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.services; + +import com.boredxgames.tictactoeclient.domain.services.game.GameBoard; + +import java.util.List; +import java.util.Random; + +/** + * + * @author sheri + */ +public class AIService { + private static final Random random = new Random(); + private static AIDifficulty diffiulty = AIDifficulty.EASY; + + public static int[] nextMove(GameBoard board, AIDifficulty difficulty) { + if (board.getAvailableMoves().isEmpty()) return null; + + return switch (difficulty) { + case EASY -> randomMove(board); + case MEDIUM -> mediumMove(board); + case HARD -> hardMove(board); + }; + } + + public static AIDifficulty getDiffiulty() { + return diffiulty; + } + + public static void setDiffiulty(AIDifficulty diffiulty) { + AIService.diffiulty = diffiulty; + } + + + + private static int[] randomMove(GameBoard board) { + List moves = board.getAvailableMoves(); + return moves.get(random.nextInt(moves.size())); + } + + private static int[] mediumMove(GameBoard board) { + if (random.nextBoolean()) return hardMove(board); + return randomMove(board); + } + + private static int[] hardMove(GameBoard board) { + return MinimaxEngine.bestMove(board, board.getCurrentPlayer()); + } +} diff --git a/src/main/java/com/boredxgames/tictactoeclient/domain/services/BoardUtils.java b/src/main/java/com/boredxgames/tictactoeclient/domain/services/BoardUtils.java new file mode 100644 index 0000000..ecb3f9c --- /dev/null +++ b/src/main/java/com/boredxgames/tictactoeclient/domain/services/BoardUtils.java @@ -0,0 +1,24 @@ +/* + * 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.services; + + +import com.boredxgames.tictactoeclient.domain.services.game.GameBoard; + +/** + * + * @author sheri + */ +public class BoardUtils { + public static GameBoard copy(GameBoard original) { + GameBoard copy = new GameBoard(original.getCurrentPlayer()); + for (int i = 0; i < 3; i++) + for (int j = 0; j < 3; j++) { + char cell = original.getCellValue(i, j); + if (cell != GameBoard.EMPTY) copy.forceMove(i, j, cell); + } + return copy; + } +} diff --git a/src/main/java/com/boredxgames/tictactoeclient/domain/services/MinimaxEngine.java b/src/main/java/com/boredxgames/tictactoeclient/domain/services/MinimaxEngine.java new file mode 100644 index 0000000..ffbca61 --- /dev/null +++ b/src/main/java/com/boredxgames/tictactoeclient/domain/services/MinimaxEngine.java @@ -0,0 +1,56 @@ +/* + * 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.services; + +import com.boredxgames.tictactoeclient.domain.services.game.GameBoard; + +/** + * + * @author sheri + */ +public class MinimaxEngine { + + public static int[] bestMove(GameBoard board, char player) { + int bestScore = Integer.MIN_VALUE; + int[] bestMove = null; + + char opponent = (player == GameBoard.PLAYER_X) ? GameBoard.PLAYER_O : GameBoard.PLAYER_X; + + for (int[] move : board.getAvailableMoves()) { + GameBoard copy = BoardUtils.copy(board); + copy.forceMove(move[0], move[1], player); + + int score = minimax(copy, false, 0, player, opponent); + if (score > bestScore) { + bestScore = score; + bestMove = move; + } + } + return bestMove; + } + + private static int minimax(GameBoard board, boolean isMax, int depth, char player, char opponent) { + if (board.checkWin(player)) { + return 10 - depth; + } + if (board.checkWin(opponent)) { + return depth - 10; + } + if (board.getAvailableMoves().isEmpty()) { + return 0; + } + + int best = isMax ? Integer.MIN_VALUE : Integer.MAX_VALUE; + + for (int[] move : board.getAvailableMoves()) { + GameBoard copy = BoardUtils.copy(board); + copy.forceMove(move[0], move[1], isMax ? player : opponent); + int score = minimax(copy, !isMax, depth + 1, player, opponent); + best = isMax ? Math.max(best, score) : Math.min(best, score); + } + + return best; + } +} 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 d583126..900730b 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 @@ -10,21 +10,22 @@ * @author Tasneem */ public class GameBoard { + private final char[][] board; private final int BOARD_SIZE = 3; public static final char PLAYER_X = 'X'; public static final char PLAYER_O = 'O'; public static final char EMPTY = '-'; - + private char currentPlayer; private GameState gameState; private int movesCount; - + public GameBoard() { this(PLAYER_X); } - + public GameBoard(char startingPlayer) { board = new char[BOARD_SIZE][BOARD_SIZE]; initializeBoard(); @@ -32,7 +33,7 @@ public GameBoard(char startingPlayer) { gameState = GameState.IN_PROGRESS; movesCount = 0; } - + private void initializeBoard() { for (int i = 0; i < BOARD_SIZE; i++) { for (int j = 0; j < BOARD_SIZE; j++) { @@ -40,52 +41,59 @@ private void initializeBoard() { } } } - + public void resetGame() { resetGame(PLAYER_X); } - + public void resetGame(char startingPlayer) { initializeBoard(); currentPlayer = startingPlayer; gameState = GameState.IN_PROGRESS; movesCount = 0; } - + public boolean makeMove(int row, int col) { return makeMove(row, col, currentPlayer); } - + public boolean makeMove(int row, int col, char player) { if (!isValidMove(row, col)) { return false; - } - + } + board[row][col] = player; movesCount++; - - currentPlayer = player; updateGameState(); + + if (gameState == GameState.IN_PROGRESS) { + currentPlayer = (player == PLAYER_X) ? PLAYER_O : PLAYER_X; + } + return true; } - + + public void forceMove(int row, int col, char player) { + board[row][col] = player; + movesCount++; + } + public boolean isValidMove(int row, int col) { - if(gameState != GameState.IN_PROGRESS) { + if (gameState != GameState.IN_PROGRESS) { return false; } - - if(row < 0 || row >= BOARD_SIZE || col < 0 || col >= BOARD_SIZE) { + + if (row < 0 || row >= BOARD_SIZE || col < 0 || col >= BOARD_SIZE) { return false; } - + return board[row][col] == EMPTY; } - - + public void switchPlayer() { currentPlayer = (currentPlayer == PLAYER_X) ? PLAYER_O : PLAYER_X; } - + private void updateGameState() { if (checkWin(PLAYER_X)) { gameState = GameState.X_WINS; @@ -95,11 +103,11 @@ private void updateGameState() { gameState = GameState.DRAW; } } - + public boolean isBoardFull() { return movesCount == BOARD_SIZE * BOARD_SIZE; } - + public boolean checkWin(char player) { // rows for (int i = 0; i < BOARD_SIZE; i++) { @@ -107,61 +115,61 @@ public boolean checkWin(char player) { return true; } } - + // col for (int j = 0; j < BOARD_SIZE; j++) { if (board[0][j] == player && board[1][j] == player && board[2][j] == player) { return true; } } - + // diagonal left -> right if (board[0][0] == player && board[1][1] == player && board[2][2] == player) { return true; } - + // diagonal right -> left if (board[0][2] == player && board[1][1] == player && board[2][0] == player) { return true; } - + return false; } - + public int[] getWinningLine() { if (gameState == GameState.IN_PROGRESS || gameState == GameState.DRAW) { return null; } - + char winner = (gameState == GameState.X_WINS) ? PLAYER_X : PLAYER_O; - + // row for (int i = 0; i < BOARD_SIZE; i++) { if (board[i][0] == winner && board[i][1] == winner && board[i][2] == winner) { return new int[]{i, 0, i, 1, i, 2}; } } - + // col for (int j = 0; j < BOARD_SIZE; j++) { if (board[0][j] == winner && board[1][j] == winner && board[2][j] == winner) { return new int[]{0, j, 1, j, 2, j}; } } - + // diagonal left -> right if (board[0][0] == winner && board[1][1] == winner && board[2][2] == winner) { return new int[]{0, 0, 1, 1, 2, 2}; } - + // diagonal right -> left if (board[0][2] == winner && board[1][1] == winner && board[2][0] == winner) { return new int[]{0, 2, 1, 1, 2, 0}; } - + return null; } - + public char getWinner() { if (gameState == GameState.X_WINS) { return PLAYER_X; @@ -170,22 +178,22 @@ public char getWinner() { } return EMPTY; } - + public char getCurrentPlayer() { return currentPlayer; } - + public GameState getGameState() { return gameState; } - + public char getCellValue(int row, int col) { if (row < 0 || row >= BOARD_SIZE || col < 0 || col >= BOARD_SIZE) { return EMPTY; } return board[row][col]; } - + public List getAvailableMoves() { List availableMoves = new ArrayList<>(); for (int i = 0; i < BOARD_SIZE; i++) { diff --git a/src/main/java/com/boredxgames/tictactoeclient/domain/services/game/GameService.java b/src/main/java/com/boredxgames/tictactoeclient/domain/services/game/GameService.java index 115c3de..455d07a 100644 --- a/src/main/java/com/boredxgames/tictactoeclient/domain/services/game/GameService.java +++ b/src/main/java/com/boredxgames/tictactoeclient/domain/services/game/GameService.java @@ -8,6 +8,7 @@ */ public interface GameService { void makeMove(Move move, char currentPlayer); + void makeMove(Move move, char currentPlayer, GameBoard board); Move getNextMove(GameBoard board, char currentPlayer); GameState getOutcome(GameBoard board); } 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 new file mode 100644 index 0000000..16c299c --- /dev/null +++ b/src/main/java/com/boredxgames/tictactoeclient/domain/services/game/OfflinePVEAIService.java @@ -0,0 +1,45 @@ +/* + * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license + * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Interface.java to edit this template + */ +package com.boredxgames.tictactoeclient.domain.services.game; + +import com.boredxgames.tictactoeclient.domain.model.GameState; +import com.boredxgames.tictactoeclient.domain.model.Move; +import com.boredxgames.tictactoeclient.domain.services.AIService; + +/** + * + * @author sheri + */ +public class OfflinePVEAIService implements GameService { + + @Override + public void makeMove(Move move, char currentPlayer) { + } + + @Override + public void makeMove(Move move, char currentPlayer, GameBoard board) { + if (move != null) { + board.makeMove(move.getRow(), move.getCol(), currentPlayer); + } + + } + + @Override + public Move getNextMove(GameBoard board, char currentPlayer) { + if (currentPlayer == GameBoard.PLAYER_O) { + int[] aiMove = AIService.nextMove(board, AIService.getDiffiulty()); + if (aiMove != null) { + return new Move(aiMove[0], aiMove[1]); + } + } + return null; + } + + @Override + public GameState getOutcome(GameBoard board) { + return board.getGameState(); + } + +} 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 new file mode 100644 index 0000000..18c3697 --- /dev/null +++ b/src/main/java/com/boredxgames/tictactoeclient/domain/services/game/OfflinePVPService.java @@ -0,0 +1,36 @@ +/* + * 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.services.game; + +import com.boredxgames.tictactoeclient.domain.model.GameState; +import com.boredxgames.tictactoeclient.domain.model.Move; + +/** + * + * @author sheri + */ +public class OfflinePVPService implements GameService{ + + @Override + public void makeMove(Move move, char currentPlayer) { + } + + @Override + public void makeMove(Move move, char currentPlayer, GameBoard board) { + board.makeMove(move.getRow(), move.getCol(), currentPlayer); + } + + @Override + public Move getNextMove(GameBoard board, char currentPlayer) { + + return null; + } + + @Override + public GameState getOutcome(GameBoard board) { + + return board.getGameState(); + } +} diff --git a/src/main/java/com/boredxgames/tictactoeclient/presentation/Difficulty_selectionController.java b/src/main/java/com/boredxgames/tictactoeclient/presentation/Difficulty_selectionController.java index 9bb918f..5346a6e 100644 --- a/src/main/java/com/boredxgames/tictactoeclient/presentation/Difficulty_selectionController.java +++ b/src/main/java/com/boredxgames/tictactoeclient/presentation/Difficulty_selectionController.java @@ -4,13 +4,23 @@ */ package com.boredxgames.tictactoeclient.presentation; +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.GameMode; +import com.boredxgames.tictactoeclient.domain.model.GameNavigationParams; +import com.boredxgames.tictactoeclient.domain.services.AIDifficulty; +import com.boredxgames.tictactoeclient.domain.services.AIService; import java.net.URL; import java.util.ResourceBundle; import javafx.application.Platform; import javafx.fxml.FXML; import javafx.fxml.Initializable; +import javafx.scene.control.Button; +import javafx.scene.control.Label; import javafx.scene.layout.Pane; + /** * FXML Controller class * @@ -18,9 +28,17 @@ */ public class Difficulty_selectionController implements Initializable { - @FXML private Pane backgroundPane; + @FXML + private Button easyBtn; + @FXML + private Button mediumBtn; + @FXML + private Button hardBtn; + @FXML + private Label backLabel; + /** * Initializes the controller class. */ @@ -28,14 +46,45 @@ public class Difficulty_selectionController implements Initializable { public void initialize(URL url, ResourceBundle rb) { backgroundPane.getChildren().clear(); - Platform.runLater(() -> { BackgroundAnimation.startWarpAnimation( - backgroundPane, - backgroundPane.getWidth(), - backgroundPane.getHeight() + backgroundPane, + backgroundPane.getWidth(), + backgroundPane.getHeight() ); }); - } - + + easyBtn.setOnAction(e -> startGameWithDifficulty("EASY")); + mediumBtn.setOnAction(e -> startGameWithDifficulty("MEDIUM")); + hardBtn.setOnAction(e -> startGameWithDifficulty("HARD")); + + backLabel.setOnMouseClicked(e -> goBack()); + } + + private void startGameWithDifficulty(String difficulty) { + System.out.println("Starting game with difficulty: " + difficulty); + + switch (difficulty) { + case "MEDIUM": + AIService.setDiffiulty(AIDifficulty.MEDIUM); + break; + case "HARD": + AIService.setDiffiulty(AIDifficulty.HARD); + break; + default: + AIService.setDiffiulty(AIDifficulty.EASY); + } + GameNavigationParams params = new GameNavigationParams( + "Player", + "CPU", + GameMode.OFFLINE_PVE + ); + NavigationManager.navigate(Screens.GAME, NavigationAction.REPLACE, params); + + } + + private void goBack() { + System.out.println("Back to previous screen"); + NavigationManager.navigate(Screens.GAME, NavigationAction.REPLACE); + } } diff --git a/src/main/java/com/boredxgames/tictactoeclient/presentation/GameController.java b/src/main/java/com/boredxgames/tictactoeclient/presentation/GameController.java index 457b824..fe84999 100644 --- a/src/main/java/com/boredxgames/tictactoeclient/presentation/GameController.java +++ b/src/main/java/com/boredxgames/tictactoeclient/presentation/GameController.java @@ -9,7 +9,10 @@ import com.boredxgames.tictactoeclient.domain.model.Move; import com.boredxgames.tictactoeclient.domain.services.game.GameBoard; import com.boredxgames.tictactoeclient.domain.model.GameState; +import com.boredxgames.tictactoeclient.domain.services.AIService; import com.boredxgames.tictactoeclient.domain.services.game.GameService; +import com.boredxgames.tictactoeclient.domain.services.game.OfflinePVEAIService; +import com.boredxgames.tictactoeclient.domain.services.game.OfflinePVPService; import javafx.animation.PauseTransition; import javafx.application.Platform; import javafx.fxml.FXML; @@ -100,9 +103,9 @@ 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(); @@ -111,12 +114,15 @@ public void initialize(URL url, ResourceBundle rb) { @Override public void setNavigationParameter(Object parameter) { - if (parameter instanceof GameNavigationParams(String player1, String player2, GameMode mode)) { - this.player1Name = player1; - this.player2Name = player2; - this.gameMode = mode; - } else if (parameter instanceof GameMode mode) { - this.gameMode = mode; + if (parameter instanceof GameNavigationParams params) { + this.player1Name = params.player1(); + this.player2Name = params.player2(); + this.gameMode = params.mode(); + } else { + // Default + this.gameMode = GameMode.OFFLINE_PVP; + this.player1Name = "Player 1"; + this.player2Name = "Player 2"; } applyPlayerInfo(); @@ -138,7 +144,7 @@ private void applyGameModeSettings() { difficultyBadge.setManaged(false); changeDifficultyButton.setVisible(false); changeDifficultyButton.setManaged(false); - // gameService = new OfflinePVPService(); // TODO: implement offline pvp service + gameService = new OfflinePVPService(); } case OFFLINE_PVE -> { opponentTypeLabel.setText("CPU (O)"); @@ -146,7 +152,7 @@ private void applyGameModeSettings() { difficultyBadge.setManaged(true); changeDifficultyButton.setVisible(true); changeDifficultyButton.setManaged(true); - // gameService = new OfflinePVEAIService(); // TODO: implement offline pve ai service + gameService = new OfflinePVEAIService(); } case ONLINE_PVP -> { opponentTypeLabel.setText("ONLINE PLAYER"); @@ -192,34 +198,68 @@ private void setupButtonHandlers() { } private void handleCellClick(int row, int col) { - if (!gameBoard.isValidMove(row, col)) return; - - if (gameMode == GameMode.ONLINE_PVP && !isPlayerTurn) return; + if (!gameBoard.isValidMove(row, col)) { + return; + } char currentPlayer = gameBoard.getCurrentPlayer(); - Move move = new Move(row, col); - gameService.makeMove(move, currentPlayer); - - Move nextMove = gameService.getNextMove(gameBoard, currentPlayer); - 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 { + switch (gameMode) { + case OFFLINE_PVP -> { + // Player vs Player + + gameBoard.makeMove(row, col, currentPlayer); + updateCell(row, col, currentPlayer); + + if (!checkGameEnd()) { + + updateTurnIndicator(); + } + } + + case OFFLINE_PVE -> { + // Player X moves + gameBoard.makeMove(row, col, GameBoard.PLAYER_X); + updateCell(row, col, GameBoard.PLAYER_X); + if (checkGameEnd()) { + return; + } + + // 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(); + } + + case ONLINE_PVP -> { + // Player sends move to server instead of executing locally + + // sendMoveToServer(row, col); + } } + } - GameState state = gameService.getOutcome(gameBoard); + private boolean checkGameEnd() { + GameState state = gameBoard.getGameState(); if (state != GameState.IN_PROGRESS) { handleGameEnd(); + return true; } + return false; } private void updateCell(int row, int col, char player) { @@ -277,27 +317,29 @@ private void setActiveCard(VBox activeCard, VBox inactiveCard) { } private void makeCPUMove() { - var availableMoves = gameBoard.getAvailableMoves(); - - if (!availableMoves.isEmpty()) { - // TODO: change difficulty - int randomIndex = (int) (Math.random() * availableMoves.size()); - int[] move = availableMoves.get(randomIndex); + if (gameMode != GameMode.OFFLINE_PVE) { + return; + } - Platform.runLater(() -> { - if (gameBoard.makeMove(move[0], move[1], GameBoard.PLAYER_O)) { - updateCell(move[0], move[1], GameBoard.PLAYER_O); + int[] aiMove = AIService.nextMove(gameBoard, AIService.getDiffiulty()); + if (aiMove == null) { + return; + } - if (gameBoard.getGameState() != GameState.IN_PROGRESS) { - handleGameEnd(); - } else { - gameBoard.switchPlayer(); - updateTurnIndicator(); - enableBoard(); - } + Platform.runLater(() -> { + if (gameBoard.makeMove(aiMove[0], aiMove[1], GameBoard.PLAYER_O)) { + updateCell(aiMove[0], aiMove[1], GameBoard.PLAYER_O); + + GameState state = gameBoard.getGameState(); + if (state != GameState.IN_PROGRESS) { + handleGameEnd(); + } else { + gameBoard.switchPlayer(); + updateTurnIndicator(); + enableBoard(); } - }); - } + } + }); } private void handleGameEnd() { @@ -331,17 +373,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: @@ -411,4 +453,4 @@ private void enableBoard() { } } } -} \ No newline at end of file +} diff --git a/src/main/java/com/boredxgames/tictactoeclient/presentation/GameModeScreenController.java b/src/main/java/com/boredxgames/tictactoeclient/presentation/GameModeScreenController.java index b0e30e9..8521247 100644 --- a/src/main/java/com/boredxgames/tictactoeclient/presentation/GameModeScreenController.java +++ b/src/main/java/com/boredxgames/tictactoeclient/presentation/GameModeScreenController.java @@ -50,7 +50,7 @@ public void initialize() { private void setupActions() { - // عند الضغط على الكارد الخاص بالـ Offline + offlineCard.setOnMouseClicked(e -> { System.out.println("Offline Mode Selected"); NavigationManager.navigate(Screens.PVP_SETUP, NavigationAction.REPLACE); diff --git a/src/main/java/com/boredxgames/tictactoeclient/presentation/Pvp_setupController.java b/src/main/java/com/boredxgames/tictactoeclient/presentation/Pvp_setupController.java index 0249349..0f73caf 100644 --- a/src/main/java/com/boredxgames/tictactoeclient/presentation/Pvp_setupController.java +++ b/src/main/java/com/boredxgames/tictactoeclient/presentation/Pvp_setupController.java @@ -7,6 +7,8 @@ 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.GameMode; +import com.boredxgames.tictactoeclient.domain.model.GameNavigationParams; import java.net.URL; import java.util.ResourceBundle; import javafx.application.Platform; @@ -69,16 +71,17 @@ private void onStartGameClicked(ActionEvent event) { if (playerOneName.isEmpty()) { playerOneName = "Player 1"; } + if (playerTwoName.isEmpty()) { playerTwoName = "Player 2"; } - boolean isPlayerOneStarter = true; - ToggleButton selected = (ToggleButton) starterGroup.getSelectedToggle(); + GameNavigationParams params = new GameNavigationParams( + playerOneName, + playerTwoName, + GameMode.OFFLINE_PVP + ); - if (selected != null) { - isPlayerOneStarter = selected.getText().contains("Player 1"); - } - //TODO : Navigate to game board + NavigationManager.navigate(Screens.GAME, NavigationAction.REPLACE, params); } } diff --git a/src/main/resources/fxml/difficulty_selection.fxml b/src/main/resources/fxml/difficulty_selection.fxml index 510cea6..8e657ec 100644 --- a/src/main/resources/fxml/difficulty_selection.fxml +++ b/src/main/resources/fxml/difficulty_selection.fxml @@ -54,7 +54,7 @@