AutoPlayer

This commit is contained in:
gitea 2025-04-15 11:35:03 +02:00
parent f81c49d27c
commit 043fdb36be
2 changed files with 374 additions and 15 deletions

View File

@ -1,17 +1,336 @@
package backend;
import java.util.ArrayList;
import java.util.Random;
public class AutoPlayer {
/**
* returns the best Move to try on provided board for active player
* @param board
* @return
*/
public Move computeBestMove(Board board) {
return null;
}
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
* @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;
}
}

View File

@ -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 ==========
@ -312,4 +351,5 @@ public class Board {
}
}