AutoPlayer
This commit is contained in:
parent
f81c49d27c
commit
043fdb36be
|
|
@ -1,17 +1,336 @@
|
|||
package backend;
|
||||
|
||||
public class AutoPlayer {
|
||||
import java.util.ArrayList;
|
||||
import java.util.Random;
|
||||
|
||||
public class AutoPlayer {
|
||||
private final int MAX_DEPTH = 3; // Depth of search
|
||||
private final Random random = new Random();
|
||||
|
||||
/**
|
||||
* returns the best Move to try on provided board for active player
|
||||
* Returns the best Move to try on provided board for active player
|
||||
* @param board
|
||||
* @return
|
||||
*/
|
||||
public Move computeBestMove(Board board) {
|
||||
ArrayList<Move> possibleMoves = generateAllPossibleMoves(board);
|
||||
|
||||
if (possibleMoves.isEmpty()) {
|
||||
return null; // No moves available
|
||||
}
|
||||
|
||||
Move bestMove = null;
|
||||
int bestScore = Integer.MIN_VALUE;
|
||||
|
||||
for (Move move : possibleMoves) {
|
||||
// Create a copy of the board and apply the move
|
||||
Board tempBoard = new Board(board);
|
||||
tempBoard.playMove(move);
|
||||
|
||||
// Evaluate the move using negamax
|
||||
int score = -negamax(tempBoard, MAX_DEPTH - 1, Integer.MIN_VALUE + 1, Integer.MAX_VALUE - 1);
|
||||
|
||||
// If this move is better than our current best, update bestMove
|
||||
if (score > bestScore || (score == bestScore && random.nextBoolean())) {
|
||||
bestScore = score;
|
||||
bestMove = move;
|
||||
}
|
||||
}
|
||||
|
||||
return bestMove;
|
||||
}
|
||||
|
||||
private int negamax(Board board, int depth, int alpha, int beta) {
|
||||
// If we've reached the maximum depth or the game is over, evaluate the board
|
||||
if (depth == 0) {
|
||||
return evaluateBoard(board);
|
||||
}
|
||||
|
||||
ArrayList<Move> possibleMoves = generateAllPossibleMoves(board);
|
||||
|
||||
// If no moves are available, this might be checkmate or stalemate
|
||||
if (possibleMoves.isEmpty()) {
|
||||
return -1000; // Heavily penalize positions with no moves
|
||||
}
|
||||
|
||||
int bestScore = Integer.MIN_VALUE;
|
||||
|
||||
for (Move move : possibleMoves) {
|
||||
// Create a copy of the board and apply the move
|
||||
Board tempBoard = new Board(board);
|
||||
tempBoard.playMove(move);
|
||||
|
||||
// Recursively evaluate the position
|
||||
int score = -negamax(tempBoard, depth - 1, -beta, -alpha);
|
||||
|
||||
bestScore = Math.max(bestScore, score);
|
||||
alpha = Math.max(alpha, score);
|
||||
|
||||
// Alpha-beta pruning
|
||||
if (alpha >= beta) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return bestScore;
|
||||
}
|
||||
|
||||
private ArrayList<Move> generateAllPossibleMoves(Board board) {
|
||||
ArrayList<Move> moves = new ArrayList<>();
|
||||
boolean isWhiteTurn = board.isTurnWhite();
|
||||
|
||||
for (Piece piece : board.getPieces()) {
|
||||
// Only consider pieces of the current player
|
||||
if (piece.isWhite() != isWhiteTurn) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get all legal moves for this piece
|
||||
ArrayList<int[]> legalMoves = computeLegalMoves(board, piece);
|
||||
|
||||
for (int[] destination : legalMoves) {
|
||||
int toX = destination[0];
|
||||
int toY = destination[1];
|
||||
|
||||
// Check if there's a piece to capture
|
||||
Piece capturedPiece = null;
|
||||
for (Piece p : board.getPieces()) {
|
||||
if (p.getX() == toX && p.getY() == toY) {
|
||||
capturedPiece = p;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Create a move
|
||||
Move move = new Move(
|
||||
piece.getX(), piece.getY(),
|
||||
toX, toY,
|
||||
piece.isWhite(),
|
||||
piece.getType(),
|
||||
capturedPiece
|
||||
);
|
||||
|
||||
moves.add(move);
|
||||
}
|
||||
}
|
||||
|
||||
return moves;
|
||||
}
|
||||
|
||||
private ArrayList<int[]> computeLegalMoves(Board board, Piece piece) {
|
||||
ArrayList<int[]> moves = new ArrayList<>();
|
||||
int x = piece.getX();
|
||||
int y = piece.getY();
|
||||
boolean isWhite = piece.isWhite();
|
||||
int width = board.getWidth();
|
||||
int height = board.getHeight();
|
||||
|
||||
switch (piece.getType()) {
|
||||
case Pawn:
|
||||
int dir = isWhite ? -1 : 1;
|
||||
int startRow = isWhite ? 6 : 1;
|
||||
|
||||
// Forward move
|
||||
if (getPieceAt(board, x, y + dir) == null) {
|
||||
moves.add(new int[]{x, y + dir});
|
||||
// Double move from starting position
|
||||
if (y == startRow && getPieceAt(board, x, y + 2 * dir) == null) {
|
||||
moves.add(new int[]{x, y + 2 * dir});
|
||||
}
|
||||
}
|
||||
|
||||
// Captures
|
||||
for (int dx = -1; dx <= 1; dx += 2) {
|
||||
int nx = x + dx;
|
||||
int ny = y + dir;
|
||||
if (nx >= 0 && nx < width && ny >= 0 && ny < height) {
|
||||
Piece target = getPieceAt(board, nx, ny);
|
||||
if (target != null && target.isWhite() != isWhite) {
|
||||
moves.add(new int[]{nx, ny});
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case Rook:
|
||||
addSlidingMoves(board, moves, x, y, isWhite, new int[][]{{1,0},{-1,0},{0,1},{0,-1}});
|
||||
break;
|
||||
|
||||
case Bishop:
|
||||
addSlidingMoves(board, moves, x, y, isWhite, new int[][]{{1,1},{1,-1},{-1,1},{-1,-1}});
|
||||
break;
|
||||
|
||||
case Queen:
|
||||
addSlidingMoves(board, moves, x, y, isWhite, new int[][]{
|
||||
{1,0},{-1,0},{0,1},{0,-1},{1,1},{1,-1},{-1,1},{-1,-1}
|
||||
});
|
||||
break;
|
||||
|
||||
case Knight:
|
||||
int[][] knightMoves = {
|
||||
{1,2}, {1,-2}, {-1,2}, {-1,-2},
|
||||
{2,1}, {2,-1}, {-2,1}, {-2,-1}
|
||||
};
|
||||
for (int[] move : knightMoves) {
|
||||
int nx = x + move[0];
|
||||
int ny = y + move[1];
|
||||
if (nx >= 0 && nx < width && ny >= 0 && ny < height) {
|
||||
Piece target = getPieceAt(board, nx, ny);
|
||||
if (target == null || target.isWhite() != isWhite) {
|
||||
moves.add(new int[]{nx, ny});
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case King:
|
||||
for (int dx = -1; dx <= 1; dx++) {
|
||||
for (int dy = -1; dy <= 1; dy++) {
|
||||
if (dx == 0 && dy == 0) continue;
|
||||
int nx = x + dx;
|
||||
int ny = y + dy;
|
||||
if (nx >= 0 && nx < width && ny >= 0 && ny < height) {
|
||||
Piece target = getPieceAt(board, nx, ny);
|
||||
if (target == null || target.isWhite() != isWhite) {
|
||||
moves.add(new int[]{nx, ny});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return moves;
|
||||
}
|
||||
|
||||
private void addSlidingMoves(Board board, ArrayList<int[]> moves, int x, int y, boolean isWhite, int[][] directions) {
|
||||
int width = board.getWidth();
|
||||
int height = board.getHeight();
|
||||
|
||||
for (int[] dir : directions) {
|
||||
for (int i = 1; i < 8; i++) {
|
||||
int nx = x + dir[0] * i;
|
||||
int ny = y + dir[1] * i;
|
||||
if (nx < 0 || nx >= width || ny < 0 || ny >= height) break;
|
||||
|
||||
Piece target = getPieceAt(board, nx, ny);
|
||||
if (target == null) {
|
||||
moves.add(new int[]{nx, ny});
|
||||
} else {
|
||||
if (target.isWhite() != isWhite) {
|
||||
moves.add(new int[]{nx, ny});
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Piece getPieceAt(Board board, int x, int y) {
|
||||
for (Piece p : board.getPieces()) {
|
||||
if (p.getX() == x && p.getY() == y) {
|
||||
return p;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private int evaluateBoard(Board board) {
|
||||
int score = 0;
|
||||
boolean isWhiteTurn = board.isTurnWhite();
|
||||
|
||||
// Material value
|
||||
for (Piece piece : board.getPieces()) {
|
||||
int pieceValue = getPieceValue(piece.getType());
|
||||
|
||||
// Add value if it's our piece, subtract if it's opponent's
|
||||
if (piece.isWhite() == isWhiteTurn) {
|
||||
score += pieceValue;
|
||||
} else {
|
||||
score -= pieceValue;
|
||||
}
|
||||
|
||||
// Position bonuses
|
||||
score += getPositionBonus(piece, isWhiteTurn);
|
||||
}
|
||||
|
||||
// Mobility (number of legal moves)
|
||||
ArrayList<Move> possibleMoves = generateAllPossibleMoves(board);
|
||||
score += possibleMoves.size() / 10; // Small bonus for mobility
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
private int getPieceValue(PieceType type) {
|
||||
switch (type) {
|
||||
case Pawn: return 100;
|
||||
case Knight: return 320;
|
||||
case Bishop: return 330;
|
||||
case Rook: return 500;
|
||||
case Queen: return 900;
|
||||
case King: return 20000; // Very high value to prioritize king safety
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private int getPositionBonus(Piece piece, boolean isWhiteTurn) {
|
||||
int bonus = 0;
|
||||
int x = piece.getX();
|
||||
int y = piece.getY();
|
||||
|
||||
// Adjust y coordinate for black pieces to use the same tables
|
||||
if (!piece.isWhite()) {
|
||||
y = 7 - y;
|
||||
}
|
||||
|
||||
switch (piece.getType()) {
|
||||
case Pawn:
|
||||
// Bonus for advancing pawns
|
||||
bonus += y * 10;
|
||||
|
||||
// Bonus for central pawns
|
||||
if (x >= 2 && x <= 5) {
|
||||
bonus += 10;
|
||||
}
|
||||
break;
|
||||
|
||||
case Knight:
|
||||
// Knights are better in the center
|
||||
if (x >= 2 && x <= 5 && y >= 2 && y <= 5) {
|
||||
bonus += 20;
|
||||
}
|
||||
break;
|
||||
|
||||
case Bishop:
|
||||
// Bishops control more squares from the center
|
||||
if (x >= 2 && x <= 5 && y >= 2 && y <= 5) {
|
||||
bonus += 15;
|
||||
}
|
||||
break;
|
||||
|
||||
case Rook:
|
||||
// Rooks are good on open files and 7th rank
|
||||
if (y == 6) {
|
||||
bonus += 20;
|
||||
}
|
||||
break;
|
||||
|
||||
case Queen:
|
||||
// Queens are slightly better in the center
|
||||
if (x >= 2 && x <= 5 && y >= 2 && y <= 5) {
|
||||
bonus += 10;
|
||||
}
|
||||
break;
|
||||
|
||||
case King:
|
||||
// Kings are safer at the edge in the middlegame
|
||||
if ((x <= 1 || x >= 6) && y <= 1) {
|
||||
bonus += 30;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Adjust sign based on whose piece it is
|
||||
return piece.isWhite() == isWhiteTurn ? bonus : -bonus;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -190,11 +190,50 @@ public class Board {
|
|||
}
|
||||
|
||||
public Board(Board board) {
|
||||
// TODO: Part 4 - Clone
|
||||
this.width = board.width;
|
||||
this.height = board.height;
|
||||
this.turnNumber = board.turnNumber;
|
||||
this.isWhiteTurn = board.isWhiteTurn;
|
||||
this.selectedX = board.selectedX;
|
||||
this.selectedY = board.selectedY;
|
||||
|
||||
// Deep copy of pieces
|
||||
this.pieces = new ArrayList<>();
|
||||
for (Piece p : board.pieces) {
|
||||
this.pieces.add(new Piece(p.getX(), p.getY(), p.isWhite(), p.getType()));
|
||||
}
|
||||
|
||||
// Copy highlighted squares
|
||||
this.highlightedSquares = new ArrayList<>();
|
||||
for (int[] square : board.highlightedSquares) {
|
||||
this.highlightedSquares.add(new int[]{square[0], square[1]});
|
||||
}
|
||||
|
||||
// We don't copy the move history as it's not needed for evaluation
|
||||
this.moveHistory = new Stack<>();
|
||||
}
|
||||
|
||||
public void playMove(Move move) {
|
||||
// TODO: Part 4 - AutoPlayer
|
||||
if (move == null) return;
|
||||
|
||||
// Store move for undo
|
||||
moveHistory.push(move);
|
||||
|
||||
// Remove captured piece if any
|
||||
if (move.getCapturedPiece() != null) {
|
||||
removePieceAt(move.getToX(), move.getToY());
|
||||
}
|
||||
|
||||
// Remove piece from original position
|
||||
removePieceAt(move.getFromX(), move.getFromY());
|
||||
|
||||
// Add piece to new position
|
||||
pieces.add(new Piece(move.getToX(), move.getToY(), move.isWhite(), move.getType()));
|
||||
|
||||
// Update turn
|
||||
turnNumber++;
|
||||
isWhiteTurn = !isWhiteTurn;
|
||||
|
||||
}
|
||||
|
||||
// ========== Helper Methods ==========
|
||||
|
|
@ -313,3 +352,4 @@ public class Board {
|
|||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue