This commit is contained in:
Utilisateur 2025-05-12 11:11:33 +02:00
parent 820183e90b
commit 9e86e6ee48
5 changed files with 433 additions and 49 deletions

10
custom.board Normal file
View File

@ -0,0 +1,10 @@
BR,--,--,BQ,BK,--,--,BR
--,--,--,--,--,--,--,--
--,--,--,--,--,--,--,--
--,--,--,--,--,--,--,--
--,--,--,--,--,--,--,--
--,--,--,--,--,--,--,--
--,--,--,--,--,--,--,--
WR,--,--,WQ,WK,--,--,WR
W
000000,-1,-1

View File

@ -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

View File

@ -4,18 +4,373 @@ 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}
};
/**
* returns the best Move to try on provided board for active player
* @param board
* @return
*/
public Move computeBestMove(Board board) {
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}
};
return null;
}
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<Move> 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<Move> 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<Move> generateAllMoves(Board board, boolean isWhite) {
ArrayList<Move> allMoves = new ArrayList<>();
ArrayList<Piece> pieces = board.getPieces();
for (Piece piece : pieces) {
if (piece.isWhite() == isWhite) {
ArrayList<int[]> 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<Piece> 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<int[]> 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<int[]> 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;
}
}

View File

@ -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<int[]> 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<int[]> 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;
}
}