diff --git a/custom.board b/custom.board new file mode 100644 index 0000000..b934005 --- /dev/null +++ b/custom.board @@ -0,0 +1,10 @@ +BR,--,--,BQ,BK,--,--,BR +--,--,--,--,--,--,--,-- +--,--,--,--,--,--,--,-- +--,--,--,--,--,--,--,-- +--,--,--,--,--,--,--,-- +--,--,--,--,--,--,--,-- +--,--,--,--,--,--,--,-- +WR,--,--,WQ,WK,--,--,WR +W +000000,-1,-1 diff --git a/default.board b/default.board index 45f802b..7a05912 100644 --- a/default.board +++ b/default.board @@ -7,3 +7,4 @@ BP,BP,BP,BP,BP,BP,BP,BP WP,WP,WP,WP,WP,WP,WP,WP WR,WN,WB,WQ,WK,WB,WN,WR W +000000,-1,-1 diff --git a/src/Main.java b/src/Main.java index b439986..0664dad 100644 --- a/src/Main.java +++ b/src/Main.java @@ -23,7 +23,7 @@ public class Main { // Test loading board from file Board loadedBoard = testLoadingBoard("default.board"); if (loadedBoard != null) { - System.out.println("Loaded board successfully:"); + System.out.println("Loaded board successfully:"); System.out.println(loadedBoard.toString()); } diff --git a/src/backend/AutoPlayer.java b/src/backend/AutoPlayer.java index af19914..dd4758f 100644 --- a/src/backend/AutoPlayer.java +++ b/src/backend/AutoPlayer.java @@ -4,18 +4,373 @@ 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 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 + */ + 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); + } + } + } + + 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; + } + } + + // 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; + } + + /** + * 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 3846d07..270678e 100644 --- a/src/backend/Board.java +++ b/src/backend/Board.java @@ -759,14 +759,15 @@ public class Board { } public void undoLastMove() { - if (moveHistory.isEmpty()) { - return; // Nothing to undo + if (moveHistory.isEmpty()) { + return; // Rien à annuler } - // Get the last move + // Récupérer le dernier mouvement Move lastMove = moveHistory.get(moveHistory.size() - 1); moveHistory.remove(moveHistory.size() - 1); + // Restaurer les pièces à leurs positions d'origine int fromX = lastMove.getFromX(); int fromY = lastMove.getFromY(); int toX = lastMove.getToX(); @@ -776,63 +777,49 @@ public class Board { boolean isEnPassantCapture = lastMove.isEnPassantCapture(); boolean isCastling = lastMove.isCastling(); - // Remove the moved piece from its current position - for (int i = 0; i < pieces.size(); i++) { - Piece piece = pieces.get(i); - if (piece.getX() == toX && piece.getY() == toY) { - pieces.remove(i); - break; - } - } + // Retirer la pièce déplacée de sa position actuelle + removePieceAt(toX, toY); - // Put the moving piece back to its original position + // Remettre la pièce déplacée à sa position d'origine setPiece(movingPiece.isWhite(), movingPiece.getType(), fromX, fromY); - // Restore the captured piece if there was one + // Restaurer la pièce capturée s'il y en avait une if (capturedPiece != null) { if (isEnPassantCapture) { - // For en passant, restore the pawn to its original position + // Pour l'en passant, restaurer le pion à sa position originale setPiece(capturedPiece.isWhite(), capturedPiece.getType(), toX, fromY); } else { - // For regular capture, restore the piece at the capture location + // Pour une capture normale, restaurer la pièce à l'emplacement de capture setPiece(capturedPiece.isWhite(), capturedPiece.getType(), toX, toY); } } - // Handle castling undo + // Gérer l'annulation du roque if (isCastling) { - // Determine rook's original and moved positions + // Déterminer les positions originales et déplacées de la tour int rookOriginalX = (toX > fromX) ? 7 : 0; int rookMovedX = (toX > fromX) ? toX - 1 : toX + 1; - // Remove the rook from its current position - for (int i = 0; i < pieces.size(); i++) { - Piece piece = pieces.get(i); - if (piece.getX() == rookMovedX && piece.getY() == toY && - piece.getType() == PieceType.Rook && piece.isWhite() == movingPiece.isWhite()) { - pieces.remove(i); - break; - } - } + // Retirer la tour de sa position actuelle + removePieceAt(rookMovedX, toY); - // Put the rook back to its original position + // Remettre la tour à sa position d'origine setPiece(movingPiece.isWhite(), PieceType.Rook, rookOriginalX, toY); } - // Reset special move flags based on move history - // We need to recompute these from scratch + // Restaurer l'état précédent d'en passant et des drapeaux de roque + // Ceci nécessite soit de stocker l'état précédent dans l'objet Move, + // soit de recalculer à partir de l'historique restant resetMoveFlags(); - // Decrement turn number and flip turn + // Décrémenter le numéro de tour et changer le joueur actif turnNumber--; isTurnWhite = !isTurnWhite; - // Reset selection and highlighting + // Réinitialiser la sélection et les cases surlignées selectedX = -1; selectedY = -1; highlightedPositions.clear(); - //TODO - } public Board(Board board) { @@ -1072,5 +1059,36 @@ public class Board { return pieceToCapture != null && pieceToCapture.isWhite() != isWhite; } + public ArrayList getValidMovesForPiece(int x, int y) { + Piece piece = getPieceAt(x, y); + if (piece != null && piece.isWhite() == isTurnWhite) { + return getValidMoves(piece); + } + return new ArrayList<>(); + } + + public boolean makeMove(int fromX, int fromY, int toX, int toY) { + Piece piece = getPieceAt(fromX, fromY); + if (piece == null || piece.isWhite() != isTurnWhite) { + return false; + } + + // Check if move is valid + boolean moveIsValid = false; + ArrayList validMoves = getValidMoves(piece); + for (int[] pos : validMoves) { + if (pos[0] == toX && pos[1] == toY) { + moveIsValid = true; + break; + } + } + + if (moveIsValid) { + // Execute the move logic similar to userTouch + // ... + return true; + } + return false; + } }