From 1227a67ee36a5d224cd92b4b105da38f233aedae Mon Sep 17 00:00:00 2001 From: yohanmontagne Date: Wed, 21 May 2025 20:02:16 +0200 Subject: [PATCH] Added the common work on the reworked Ai using the correct negaMax function and 3 of depth. --- OOP_2B1_Project/.DS_Store | Bin 6148 -> 6148 bytes OOP_2B1_Project/src/.DS_Store | Bin 6148 -> 6148 bytes OOP_2B1_Project/src/backend/.DS_Store | Bin 0 -> 6148 bytes OOP_2B1_Project/src/backend/AutoPlayer.java | 310 +++++++++++++++++--- 4 files changed, 263 insertions(+), 47 deletions(-) create mode 100644 OOP_2B1_Project/src/backend/.DS_Store diff --git a/OOP_2B1_Project/.DS_Store b/OOP_2B1_Project/.DS_Store index 4e0186954fb4ef4bfc8f63d232c3cda15c9610a0..9a322a7c0b2cc07ab3a916aaa737686ae32137fe 100644 GIT binary patch delta 19 acmZoMXffC@mzB*xN5R<8V)J6wL?Hk?hXtnq delta 19 acmZoMXffC@mzB*>N5R<8eDh+~L?Hk?j0L9v diff --git a/OOP_2B1_Project/src/.DS_Store b/OOP_2B1_Project/src/.DS_Store index 93eb05e6ceb6539bccc52c2327af35a28b4715d0..015fd29a10e271c67f2a928a5ff96776392ea587 100644 GIT binary patch delta 19 acmZoMXffDumW9ngN5R<8V)JDdLty|vcm>t~ delta 19 acmZoMXffDumW9nwN5R<8eDh@%Lty|veFfG4 diff --git a/OOP_2B1_Project/src/backend/.DS_Store b/OOP_2B1_Project/src/backend/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..06559130e373bb3b39098981dbd4647a77c42b44 GIT binary patch literal 6148 zcmeH~O-{ow5QU#Z7j2555*wB|K_ymfLsf)Og~ZMWwQnAJBS=cQC4@;&QRW3O?H8Txp{7>mki z?N94Yq(LI+)@P6Z+=} zxXf@61wrE>LTbbsrDR706+U6&*1hQ)U>3vlt;(^lTJ7=bV@4VXtLZ^u{>hzhcZ^ z@E#+fSM)S+N)8#WLr)b6xuS!I8oxw4YuVYK|G1q)NAgPXBfs=sm^Nr7Q;9@`x8Gd^_n|%b{N0;Fg~;KI}~HH^ZJP$ z4wE|7S_vqDuLRcIVN2Hk{qX+(t4WWPfD-su1Wc`a*6r|-{MuT1I9Y2GmJ1dM8J9b? k7FK>awhdW|w^?lPTp|r>>NR)D9)^AhSPWVzfgdIC0h;%=aR2}S literal 0 HcmV?d00001 diff --git a/OOP_2B1_Project/src/backend/AutoPlayer.java b/OOP_2B1_Project/src/backend/AutoPlayer.java index 8c6587b..72e5070 100644 --- a/OOP_2B1_Project/src/backend/AutoPlayer.java +++ b/OOP_2B1_Project/src/backend/AutoPlayer.java @@ -1,48 +1,264 @@ -package backend; - -import java.util.List; - -public class AutoPlayer { - - public Move computeBestMove(Board board) { - boolean aiIsWhite = board.isTurnWhite(); // Important! - List moves = board.getAllLegalMoves(aiIsWhite); - - Move bestMove = null; - int bestScore = Integer.MIN_VALUE; - - for (Move move : moves) { - Board simulated = board.clone(); - simulated.playMove(move); - - int score = evaluate(simulated, aiIsWhite); // Pass correct side - if (score > bestScore) { - bestScore = score; - bestMove = move; - } - } - - return bestMove; - } - - private int evaluate(Board board, boolean forWhite) { - int score = 0; - for (Piece piece : board.getPieces()) { - int value = getPieceValue(piece); - score += piece.isWhite() == forWhite ? value : -value; - } - return score; - } - - private int getPieceValue(Piece piece) { - switch (piece.getType()) { - case Pawn: return 1; - case Knight: - case Bishop: return 3; - case Rook: return 5; - case Queen: return 9; - case King: return 100; - default: return 0; - } - } +package backend; + +import java.util.List; // Changed from ArrayList to List for moves +import java.util.Random; + +public class AutoPlayer { + private final int MAX_DEPTH = 3; // Depth of search (Adjust as needed; 4 can be slow without further optimizations or king safety) + private final Random random = new Random(); + + /** + * Computes and returns an estimation of the best move for the active player + * depending on the board state. + * @param board The current board state. + * @return The best Move found, or null if no legal moves exist. + */ + public Move computeBestMove(Board board) { + // Determine whose turn it is on this specific board instance + boolean aiIsWhite = board.isTurnWhite(); + List possibleMoves = board.getAllLegalMoves(aiIsWhite); + + if (possibleMoves.isEmpty()) { + return null; // No legal moves available (could be checkmate or stalemate) + } + + Move bestMove = possibleMoves.get(0); // Default to first move + int bestScore = Integer.MIN_VALUE; + + // Iterate through all possible moves for the current player + for (Move move : possibleMoves) { + Board tempBoard = board.clone(); // Use the board's clone method + tempBoard.playMove(move); // Apply the move to the temporary board + + // Evaluate the resulting position using negamax + // The score is from the perspective of the player who just moved (the AI) + // so we negate the result of negamax called for the opponent. + int score = -negamax(tempBoard, MAX_DEPTH - 1, Integer.MIN_VALUE + 1, Integer.MAX_VALUE); + // Note: Negamax traditionally works where the current player maximizes. + // The score returned by negamax(tempBoard, ...) will be from the perspective of the *next* player. + // So, if tempBoard is now opponent's turn, negamax returns score for opponent. We negate it for AI's score. + + if (score > bestScore) { + bestScore = score; + bestMove = move; + } else if (score == bestScore && random.nextBoolean()) { + // If scores are equal, sometimes pick the new move to vary play + bestMove = move; + } + } + return bestMove; + } + + /** + * Implements the Negamax algorithm with alpha-beta pruning. + * @param board The current board state. + * @param depth The remaining depth to search. + * @param alpha The alpha value for pruning. + * @param beta The beta value for pruning. + * @return The score of the board position from the perspective of the current player to move. + */ + private int negamax(Board board, int depth, int alpha, int beta) { + boolean currentPlayerIsWhite = board.isTurnWhite(); // Player whose turn it is on THIS board state + + if (depth == 0 || isTerminal(board)) { // isTerminal checks for checkmate/stalemate + return evaluateBoard(board, currentPlayerIsWhite); // Evaluate from current player's perspective + } + + List possibleMoves = board.getAllLegalMoves(currentPlayerIsWhite); + + if (possibleMoves.isEmpty()) { + // If no moves, it might be checkmate or stalemate + // isKingInCheck will determine this relative to evaluateBoard + return evaluateBoard(board, currentPlayerIsWhite); + } + + int maxScore = Integer.MIN_VALUE; + for (Move move : possibleMoves) { + Board tempBoard = board.clone(); + tempBoard.playMove(move); + + int score = -negamax(tempBoard, depth - 1, -beta, -alpha); + + maxScore = Math.max(maxScore, score); + alpha = Math.max(alpha, score); + + if (alpha >= beta) { + break; // Beta cut-off + } + } + return maxScore; + } + + /** + * Checks if the king of the specified color is currently in check. + * @param board The board state to check. + * @param kingIsWhite True if checking White's king, false for Black's king. + * @return True if the king is in check, false otherwise. + */ + private boolean isKingInCheck(Board board, boolean kingIsWhite) { + Piece king = null; + for (Piece p : board.getPieces()) { + if (p.getType() == PieceType.King && p.isWhite() == kingIsWhite) { + king = p; + break; + } + } + if (king == null) { + // This should ideally not happen in a valid game + return false; + } + + // Get all moves for the opponent + List opponentMoves = board.getAllLegalMoves(!kingIsWhite); + for (Move move : opponentMoves) { + if (move.getToCol() == king.getX() && move.getToRow() == king.getY()) { + return true; // King's square is targeted by an opponent's move + } + } + return false; + } + + /** + * Checks if the current board state is terminal (checkmate or stalemate). + * Assumes board.getAllLegalMoves() returns moves that do not leave the king in check. + * If not, this detection will be inaccurate. + * @param board The board state. + * @return True if the game is over due to checkmate or stalemate. + */ + private boolean isTerminal(Board board) { + boolean currentPlayerIsWhite = board.isTurnWhite(); + List legalMoves = board.getAllLegalMoves(currentPlayerIsWhite); + return legalMoves.isEmpty(); // No legal moves means checkmate (if in check) or stalemate (if not in check) + } + + + /** + * Evaluates the board from the perspective of the specified player. + * A positive score is good for 'playerToEvaluateForIsWhite'. + * @param board The board to evaluate. + * @param playerToEvaluateForIsWhite The color of the player for whom the evaluation is being done. + * @return The score of the board. + */ + private int evaluateBoard(Board board, boolean playerToEvaluateForIsWhite) { + int score = 0; + + // Check for checkmate or stalemate + boolean currentPlayerIsWhiteOnThisBoard = board.isTurnWhite(); // Turn on the board being evaluated + List legalMovesForCurrentPlayer = board.getAllLegalMoves(currentPlayerIsWhiteOnThisBoard); + + if (legalMovesForCurrentPlayer.isEmpty()) { + if (isKingInCheck(board, currentPlayerIsWhiteOnThisBoard)) { + // Current player is checkmated. This is very bad for them. + // If we are evaluating FOR this current player, return a very low score. + // If we are evaluating FOR their opponent, return a very high score. + return (currentPlayerIsWhiteOnThisBoard == playerToEvaluateForIsWhite) ? -getPieceValue(PieceType.King) : getPieceValue(PieceType.King); + } else { + // Stalemate - usually a draw + return 0; + } + } + + // Material value and positional bonuses + for (Piece piece : board.getPieces()) { + int pieceValue = getPieceValue(piece.getType()); + int positionalBonus = getPositionBonus(piece); + + if (piece.isWhite() == playerToEvaluateForIsWhite) { // Piece belongs to the player we are evaluating for + score += pieceValue; + score += positionalBonus; + } else { // Piece belongs to the opponent + score -= pieceValue; + score -= positionalBonus; // Subtract opponent's positional bonus too (or add negated if getPositionBonus returns signed) + } + } + return score; + } + + + /** + * Gets the basic material value of a piece. + * @param type The type of the piece. + * @return The material value. + */ + private int getPieceValue(PieceType type) { + switch (type) { + case Pawn: return 100; + case Knight: return 320; // Slightly higher than bishop often + case Bishop: return 330; + case Rook: return 500; + case Queen: return 900; + case King: return 20000; // Represents checkmate value in evaluation + default: return 0; + } + } + + /** + * Gets a positional bonus for a piece based on its type and location. + * Bonuses are always positive from the piece's own perspective. + * @param piece The piece to evaluate. + * @return The positional bonus. + */ + private int getPositionBonus(Piece piece) { + // Simplified piece-square tables (values are for the piece itself, sign applied in evaluateBoard) + // For a more accurate representation, these tables would be 8x8 + // These are just conceptual bonuses + int x = piece.getX(); + int y = piece.getY(); + int bonus = 0; + + // Make y relative for black pieces if tables are designed from white's perspective + // int effectiveY = piece.isWhite() ? y : 7 - y; // Assuming 0-7 board, white starts rows 0,1 or 6,7 + + // Your setup: White pawns Y=6, Black Y=1 (initially) + // Board: Y=0 is top, Y=7 is bottom. + // White wants to move from Y=6 to Y=0. Black from Y=1 to Y=7. + // Let's make tables always from White's perspective (Y=0 is opponent's back rank) + int evalY = piece.isWhite() ? (7 - y) : y; // evalY: 0=own back rank, 7=opponent's back rank + + switch (piece.getType()) { + case Pawn: + // Bonus for advancing (evalY increases as pawn advances for both colors) + bonus += evalY * 5; + // Bonus for central pawns (d, e files -> columns 3, 4 on 0-7 board) + if (x == 3 || x == 4) { + bonus += 10; + if (evalY >= 2 && evalY <= 4) bonus += 5; // Central and advanced + } + break; + case Knight: + // Knights are stronger in the center + if ((x >= 2 && x <= 5) && (y >= 2 && y <= 5)) { // Absolute center + bonus += 20; + } else if ((x >= 1 && x <= 6) && (y >= 1 && y <= 6)) { // Wider center + bonus += 10; + } + break; + case Bishop: + // Bishops like open diagonals, somewhat central + if ((x >= 2 && x <= 5) && (y >= 2 && y <= 5)) { + bonus += 10; + } + break; + case Rook: + // Rooks on 7th rank (from their perspective, i.e., evalY = 6) + if (evalY == 6) bonus += 15; + // Simplified open file (less useful without checking other pawns) + break; + case Queen: + // Slight preference for central but mobile + if ((x >= 2 && x <= 5) && (y >= 2 && y <= 5)) { + bonus += 5; + } + break; + case King: + // Early/Mid game: prefer king safety (e.g., castled, back rank) + // End game: king activity becomes important + // This is a simple placeholder, King safety is complex + if (evalY <= 1) bonus += 10; // Safer on back ranks initially + break; + default: + break; + } + return bonus; + } } \ No newline at end of file