diff --git a/default.board b/default.board index 7a05912..a7b4828 100644 --- a/default.board +++ b/default.board @@ -2,9 +2,9 @@ BR,BN,BB,BQ,BK,BB,BN,BR BP,BP,BP,BP,BP,BP,BP,BP --,--,--,--,--,--,--,-- --,--,--,--,--,--,--,-- +--,--,--,--,--,--,--,WP --,--,--,--,--,--,--,-- ---,--,--,--,--,--,--,-- -WP,WP,WP,WP,WP,WP,WP,WP +WP,WP,WP,WP,WP,WP,WP,-- WR,WN,WB,WQ,WK,WB,WN,WR -W -000000,-1,-1 +B +000000,7,5 diff --git a/src/Main.java b/src/Main.java index 0664dad..0199fa9 100644 --- a/src/Main.java +++ b/src/Main.java @@ -9,6 +9,7 @@ import java.io.FileNotFoundException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Scanner; +import backend.AutoPlayer; public class Main { public static void main(String[] args) { @@ -16,7 +17,16 @@ public class Main { Board testBoard = new Board(8, 8); testBoard.populateBoard(); System.out.println(testBoard.toString()); - + AutoPlayer cpu = new AutoPlayer(); + Move cpuMove = cpu.computeBestMove(testBoard); + if (cpuMove != null) { + testBoard.playMove(cpuMove); + System.out.println("AutoPlayer plays: " + + cpuMove.getFromX() + "," + cpuMove.getFromY() + + " -> " + + cpuMove.getToX() + "," + cpuMove.getToY()); + System.out.println(testBoard.toString()); + } // Test saving board to file testSavingBoard(testBoard, "default.board"); diff --git a/src/backend/AutoPlayer.java b/src/backend/AutoPlayer.java index dd4758f..ed53a38 100644 --- a/src/backend/AutoPlayer.java +++ b/src/backend/AutoPlayer.java @@ -4,373 +4,37 @@ import java.util.ArrayList; import java.util.Random; public class AutoPlayer { - private static final int DEFAULT_DEPTH = 3; - private int searchDepth; - private Random random = new Random(); // For choosing between equally valued moves - - // Piece values for the evaluation function - private static final int PAWN_VALUE = 100; - private static final int KNIGHT_VALUE = 320; - private static final int BISHOP_VALUE = 330; - private static final int ROOK_VALUE = 500; - private static final int QUEEN_VALUE = 900; - private static final int KING_VALUE = 20000; - - // Piece-Square tables for positional evaluation - private static final int[][] PAWN_TABLE = { - {0, 0, 0, 0, 0, 0, 0, 0}, - {50, 50, 50, 50, 50, 50, 50, 50}, - {10, 10, 20, 30, 30, 20, 10, 10}, - {5, 5, 10, 25, 25, 10, 5, 5}, - {0, 0, 0, 20, 20, 0, 0, 0}, - {5, -5,-10, 0, 0,-10, -5, 5}, - {5, 10, 10,-20,-20, 10, 10, 5}, - {0, 0, 0, 0, 0, 0, 0, 0} - }; - - private static final int[][] KNIGHT_TABLE = { - {-50,-40,-30,-30,-30,-30,-40,-50}, - {-40,-20, 0, 0, 0, 0,-20,-40}, - {-30, 0, 10, 15, 15, 10, 0,-30}, - {-30, 5, 15, 20, 20, 15, 5,-30}, - {-30, 0, 15, 20, 20, 15, 0,-30}, - {-30, 5, 10, 15, 15, 10, 5,-30}, - {-40,-20, 0, 5, 5, 0,-20,-40}, - {-50,-40,-30,-30,-30,-30,-40,-50} - }; - - private static final int[][] BISHOP_TABLE = { - {-20,-10,-10,-10,-10,-10,-10,-20}, - {-10, 0, 0, 0, 0, 0, 0,-10}, - {-10, 0, 10, 10, 10, 10, 0,-10}, - {-10, 5, 5, 10, 10, 5, 5,-10}, - {-10, 0, 5, 10, 10, 5, 0,-10}, - {-10, 5, 5, 5, 5, 5, 5,-10}, - {-10, 0, 5, 0, 0, 5, 0,-10}, - {-20,-10,-10,-10,-10,-10,-10,-20} - }; - - private static final int[][] ROOK_TABLE = { - {0, 0, 0, 0, 0, 0, 0, 0}, - {5, 10, 10, 10, 10, 10, 10, 5}, - {-5, 0, 0, 0, 0, 0, 0, -5}, - {-5, 0, 0, 0, 0, 0, 0, -5}, - {-5, 0, 0, 0, 0, 0, 0, -5}, - {-5, 0, 0, 0, 0, 0, 0, -5}, - {-5, 0, 0, 0, 0, 0, 0, -5}, - {0, 0, 0, 5, 5, 0, 0, 0} - }; - - private static final int[][] QUEEN_TABLE = { - {-20,-10,-10, -5, -5,-10,-10,-20}, - {-10, 0, 0, 0, 0, 0, 0,-10}, - {-10, 0, 5, 5, 5, 5, 0,-10}, - {-5, 0, 5, 5, 5, 5, 0, -5}, - {0, 0, 5, 5, 5, 5, 0, -5}, - {-10, 5, 5, 5, 5, 5, 0,-10}, - {-10, 0, 5, 0, 0, 0, 0,-10}, - {-20,-10,-10, -5, -5,-10,-10,-20} - }; - - private static final int[][] KING_TABLE_MIDDLEGAME = { - {-30,-40,-40,-50,-50,-40,-40,-30}, - {-30,-40,-40,-50,-50,-40,-40,-30}, - {-30,-40,-40,-50,-50,-40,-40,-30}, - {-30,-40,-40,-50,-50,-40,-40,-30}, - {-20,-30,-30,-40,-40,-30,-30,-20}, - {-10,-20,-20,-20,-20,-20,-20,-10}, - {20, 20, 0, 0, 0, 0, 20, 20}, - {20, 30, 10, 0, 0, 10, 30, 20} - }; - - private static final int[][] KING_TABLE_ENDGAME = { - {-50,-40,-30,-20,-20,-30,-40,-50}, - {-30,-20,-10, 0, 0,-10,-20,-30}, - {-30,-10, 20, 30, 30, 20,-10,-30}, - {-30,-10, 30, 40, 40, 30,-10,-30}, - {-30,-10, 30, 40, 40, 30,-10,-30}, - {-30,-10, 20, 30, 30, 20,-10,-30}, - {-30,-30, 0, 0, 0, 0,-30,-30}, - {-50,-30,-30,-30,-30,-30,-30,-50} - }; - - public AutoPlayer() { - this(DEFAULT_DEPTH); - } - - public AutoPlayer(int searchDepth) { - this.searchDepth = searchDepth; - } - - /** - * Computes the best move for the current player based on the board state - * @param board The current board state - * @return The best move according to the minimax algorithm with alpha-beta pruning - */ + private Random rand = new Random(); + public Move computeBestMove(Board board) { - boolean isWhite = board.isTurnWhite(); - ArrayList possibleMoves = generateAllMoves(board, isWhite); - - if (possibleMoves.isEmpty()) { - return null; // No moves available, likely checkmate or stalemate - } - - Move bestMove = null; - int bestScore = Integer.MIN_VALUE; - - for (Move move : possibleMoves) { - Board tempBoard = new Board(board); // Create a copy of the board - tempBoard.playMove(move); // Apply the move to the copy - - // Negamax with alpha-beta pruning - int score = -negamax(tempBoard, searchDepth - 1, Integer.MIN_VALUE + 1, Integer.MAX_VALUE - 1, !isWhite); - - if (score > bestScore) { - bestScore = score; - bestMove = move; - } else if (score == bestScore && random.nextBoolean()) { - // Add some randomness between equal-valued moves - bestMove = move; - } - } - - return bestMove; - } - - /** - * Implements the negamax algorithm with alpha-beta pruning - */ - private int negamax(Board board, int depth, int alpha, int beta, boolean isWhite) { - if (depth == 0 || isGameOver(board)) { - return evaluateBoard(board, isWhite); - } - - ArrayList possibleMoves = generateAllMoves(board, isWhite); - - if (possibleMoves.isEmpty()) { - if (board.isInCheck(isWhite)) { - return -20000; // Checkmate (worst possible outcome) - } else { - return 0; // Stalemate (neutral) - } - } - - int bestScore = Integer.MIN_VALUE; - - for (Move move : possibleMoves) { - Board tempBoard = new Board(board); - tempBoard.playMove(move); - - int score = -negamax(tempBoard, depth - 1, -beta, -alpha, !isWhite); - - bestScore = Math.max(bestScore, score); - alpha = Math.max(alpha, bestScore); - - if (alpha >= beta) { - break; // Alpha-beta pruning - } - } - - return bestScore; - } - - /** - * Checks if the game is over (checkmate or stalemate) - */ - private boolean isGameOver(Board board) { - return board.isCheckmate() || board.isStalemate(); - } - - /** - * Generates all possible moves for the given player - */ - private ArrayList generateAllMoves(Board board, boolean isWhite) { - ArrayList allMoves = new ArrayList<>(); - ArrayList pieces = board.getPieces(); - - for (Piece piece : pieces) { - if (piece.isWhite() == isWhite) { - ArrayList validPositions = getValidMoves(board, piece); - - for (int[] position : validPositions) { - int toX = position[0]; - int toY = position[1]; - - boolean isEnPassant = false; - boolean isCastling = false; - - // Check if move is castling - if (piece.getType() == PieceType.King && Math.abs(toX - piece.getX()) == 2) { - isCastling = true; - } - - // Check if move is en passant - if (piece.getType() == PieceType.Pawn && toX != piece.getX() && getPieceAt(board, toX, toY) == null) { - isEnPassant = true; - } - - Piece capturedPiece = null; - if (isEnPassant) { - capturedPiece = getPieceAt(board, toX, piece.getY()); - } else { - capturedPiece = getPieceAt(board, toX, toY); - } - - Move move = new Move(piece.getX(), piece.getY(), toX, toY, - new Piece(piece.getX(), piece.getY(), piece.getType(), piece.isWhite()), - capturedPiece, isEnPassant, isCastling); - - allMoves.add(move); + ArrayList possibleMoves = new ArrayList<>(); + boolean whiteTurn = board.isTurnWhite(); + + for (Piece p : board.getPieces()) { + if (p.isWhite() == whiteTurn) { + ArrayList moves = board.getValidMovesForPiece(p.getX(), p.getY()); + for (int[] pos : moves) { + int toX = pos[0]; + int toY = pos[1]; + Piece movingPiece = new Piece(p.getX(), p.getY(), p.getType(), p.isWhite()); + Piece captured = board.getPieceAt(toX, toY); + Move move = new Move(p.getX(), p.getY(), toX, toY, movingPiece, captured, false, false); + possibleMoves.add(move); } } } - - return allMoves; - } - - /** - * Evaluates the board state from the perspective of the current player - */ - private int evaluateBoard(Board board, boolean isWhite) { - int score = 0; - boolean isEndgame = isEndGame(board); - - // Material evaluation - ArrayList pieces = board.getPieces(); - for (Piece piece : pieces) { - int pieceValue = getPieceValue(piece.getType()); - int positionValue = getPositionValue(piece, isEndgame); - - if (piece.isWhite() == isWhite) { - score += pieceValue + positionValue; - } else { - score -= pieceValue + positionValue; + + // Si aucune possibilit� => null + if (possibleMoves.isEmpty()) return null; + + // Cherche un coup qui capture une pi�ce + for (Move m : possibleMoves) { + if (m.getCapturedPiece() != null) { + return m; } } - - // Bonus for checks and checkmate - if (board.isInCheck(!isWhite)) { - score += 50; // Bonus for putting opponent in check - } - - if (board.isCheckmate()) { - if (board.isTurnWhite() != isWhite) { - score += 10000; // Bonus for checkmate - } else { - score -= 10000; // Penalty for being checkmated - } - } - - return score; + + // Sinon, retourne un coup al�atoire + return possibleMoves.get(rand.nextInt(possibleMoves.size())); } - - /** - * Determines if the game is in an endgame state - */ - private boolean isEndGame(Board board) { - int queens = 0; - int minors = 0; - - for (Piece piece : board.getPieces()) { - if (piece.getType() == PieceType.Queen) { - queens++; - } else if (piece.getType() == PieceType.Rook || - piece.getType() == PieceType.Bishop || - piece.getType() == PieceType.Knight) { - minors++; - } - } - - // Consider it endgame if no queens or few minor pieces - return queens == 0 || (queens == 1 && minors <= 2); - } - - /** - * Returns the basic value of a piece - */ - private int getPieceValue(PieceType type) { - switch (type) { - case Pawn: return PAWN_VALUE; - case Knight: return KNIGHT_VALUE; - case Bishop: return BISHOP_VALUE; - case Rook: return ROOK_VALUE; - case Queen: return QUEEN_VALUE; - case King: return KING_VALUE; - default: return 0; - } - } - - /** - * Returns the positional value of a piece based on its location - */ - private int getPositionValue(Piece piece, boolean isEndgame) { - int x = piece.getX(); - int y = piece.getY(); - boolean isWhite = piece.isWhite(); - - // Flip coordinates for black pieces to use the same tables - if (!isWhite) { - y = 7 - y; - } - - switch (piece.getType()) { - case Pawn: return PAWN_TABLE[y][x]; - case Knight: return KNIGHT_TABLE[y][x]; - case Bishop: return BISHOP_TABLE[y][x]; - case Rook: return ROOK_TABLE[y][x]; - case Queen: return QUEEN_TABLE[y][x]; - case King: - if (isEndgame) { - return KING_TABLE_ENDGAME[y][x]; - } else { - return KING_TABLE_MIDDLEGAME[y][x]; - } - default: return 0; - } - } - - /** - * Gets valid moves for a piece (had to reimplement as board methods are private) - */ - private ArrayList getValidMoves(Board board, Piece piece) { - // This method would need to mimic board.getValidMoves logic - // Since we don't have direct access to Board's private methods, - // we'll simulate by analyzing the board setup - - // For this implementation, I'll use a simplified approach - // that leverages board's existing functionality by simulating user touches - - ArrayList validMoves = new ArrayList<>(); - int x = piece.getX(); - int y = piece.getY(); - - // Make a copy of the board to avoid side effects - Board tempBoard = new Board(board); - - // Select the piece - tempBoard.userTouch(x, y); - - // Check which positions are highlighted (valid moves) - for (int i = 0; i < 8; i++) { - for (int j = 0; j < 8; j++) { - if (tempBoard.isHighlighted(i, j)) { - validMoves.add(new int[]{i, j}); - } - } - } - - return validMoves; - } - - /** - * Helper method to get a piece at a specific position - */ - private Piece getPieceAt(Board board, int x, int y) { - for (Piece piece : board.getPieces()) { - if (piece.getX() == x && piece.getY() == y) { - return piece; - } - } - return null; - } -} \ No newline at end of file +} diff --git a/src/backend/Board.java b/src/backend/Board.java index 270678e..beb1544 100644 --- a/src/backend/Board.java +++ b/src/backend/Board.java @@ -23,7 +23,7 @@ public class Board { private boolean blackRookQueensideMoved = false; private ArrayList moveHistory = new ArrayList<>(); - private Piece getPieceAt(int x, int y) { + public Piece getPieceAt(int x, int y) { for (Piece piece : pieces) { if (piece.getX() == x && piece.getY() == y) { return piece;