AutoPlayer
This commit is contained in:
parent
f81c49d27c
commit
043fdb36be
|
|
@ -1,17 +1,336 @@
|
||||||
package backend;
|
package backend;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
public class AutoPlayer {
|
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
|
/**
|
||||||
* @param board
|
* Returns the best Move to try on provided board for active player
|
||||||
* @return
|
* @param board
|
||||||
*/
|
* @return
|
||||||
public Move computeBestMove(Board board) {
|
*/
|
||||||
|
public Move computeBestMove(Board board) {
|
||||||
return null;
|
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) {
|
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) {
|
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 ==========
|
// ========== Helper Methods ==========
|
||||||
|
|
@ -312,4 +351,5 @@ public class Board {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue