Added the common work on the reworked Ai using the correct negaMax

function and 3 of depth.
This commit is contained in:
yohanmontagne 2025-05-21 20:02:16 +02:00
parent 2c30f157b0
commit 1227a67ee3
4 changed files with 263 additions and 47 deletions

Binary file not shown.

Binary file not shown.

BIN
OOP_2B1_Project/src/backend/.DS_Store vendored Normal file

Binary file not shown.

View File

@ -1,48 +1,264 @@
package backend;
import java.util.List;
public class AutoPlayer {
public Move computeBestMove(Board board) {
boolean aiIsWhite = board.isTurnWhite(); // Important!
List<Move> 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<Move> 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<Move> 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<Move> 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<Move> 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<Move> 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;
}
}