Added the common work on the reworked Ai using the correct negaMax
function and 3 of depth.
This commit is contained in:
parent
2c30f157b0
commit
1227a67ee3
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue