Part3.4
This commit is contained in:
parent
820183e90b
commit
9e86e6ee48
|
|
@ -0,0 +1,10 @@
|
|||
BR,--,--,BQ,BK,--,--,BR
|
||||
--,--,--,--,--,--,--,--
|
||||
--,--,--,--,--,--,--,--
|
||||
--,--,--,--,--,--,--,--
|
||||
--,--,--,--,--,--,--,--
|
||||
--,--,--,--,--,--,--,--
|
||||
--,--,--,--,--,--,--,--
|
||||
WR,--,--,WQ,WK,--,--,WR
|
||||
W
|
||||
000000,-1,-1
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue