Compare commits
2 Commits
d164b88f1e
...
9167153a9e
| Author | SHA1 | Date |
|---|---|---|
|
|
9167153a9e | |
|
|
52a663e698 |
|
|
@ -1,17 +1,258 @@
|
||||||
package backend;
|
package backend;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
public class AutoPlayer {
|
public class AutoPlayer {
|
||||||
|
|
||||||
|
// Piece values used for evaluation and best move to do by calculating later the profit and losses (PnL)
|
||||||
|
private final int PAWN_VALUE = 1;
|
||||||
|
private final int KNIGHT_VALUE = 3;
|
||||||
|
private final int BISHOP_VALUE = 3;
|
||||||
|
private final int ROOK_VALUE = 5;
|
||||||
|
private final int QUEEN_VALUE = 9;
|
||||||
|
private final int KING_VALUE = 100; // High value to prioritize king safety
|
||||||
|
|
||||||
/**
|
/// This chess AI uses a minimax algorithm with alpha-beta pruning to look 2 moves ahead so depth 4
|
||||||
* returns the best Move to try on provided board for active player
|
// it will evaluate the material value, the center control, and the pawn advancement (ELO 1400-1800)
|
||||||
* @param board
|
|
||||||
* @return
|
private final int MAX_DEPTH = 4;
|
||||||
*/
|
|
||||||
|
// Time limit for search in milliseconds to avoid bug or latence during the game
|
||||||
|
private final long TIME_LIMIT_MS = 5000; // 5 seconds max
|
||||||
|
private long startTime;
|
||||||
|
|
||||||
|
// Random generator if there are several moves equally calculated as good
|
||||||
|
private Random random = new Random();
|
||||||
|
|
||||||
|
//Returns the best Move to try
|
||||||
public Move computeBestMove(Board board) {
|
public Move computeBestMove(Board board) {
|
||||||
|
startTime = System.currentTimeMillis();//record start time for search
|
||||||
|
boolean isWhite = board.isTurnWhite();// determine player's turn
|
||||||
|
ArrayList<Move> possibleMoves = generateAllPossibleMoves(board, isWhite);// generate the legal moves
|
||||||
|
|
||||||
return null;
|
if (possibleMoves.isEmpty()) {
|
||||||
|
return null; // If no moves available (checkmate or stalemate)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Move bestMove = null;//store best move
|
||||||
|
int bestScore = isWhite ? Integer.MIN_VALUE : Integer.MAX_VALUE;//best score for the move: maximize for white and minimize for black
|
||||||
|
|
||||||
|
// Evaluate each possible move
|
||||||
|
for (Move move : possibleMoves) {
|
||||||
|
// Create a copy of the board to simulate the move without changing the real board
|
||||||
|
Board boardCopy = new Board(board);
|
||||||
|
applyMoveToBoard(boardCopy, move);//apply the move to the virtual board
|
||||||
|
|
||||||
|
// Evaluate the resulting position using minimax
|
||||||
|
int score = minimax(boardCopy, MAX_DEPTH - 1, Integer.MIN_VALUE, Integer.MAX_VALUE, !isWhite);
|
||||||
|
|
||||||
|
// Update best move based on the score
|
||||||
|
if ((isWhite && score > bestScore) || (!isWhite && score < bestScore) ||
|
||||||
|
(score == bestScore && random.nextBoolean())) { // Random choice if equal score
|
||||||
|
bestScore = score;//update best score
|
||||||
|
bestMove = move;//update best move
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if time limit is exceeded and stop search if exceeded
|
||||||
|
if (System.currentTimeMillis() - startTime > TIME_LIMIT_MS) {
|
||||||
|
System.out.println("Time limit exceeded, returning best move found so far");
|
||||||
|
return bestMove;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return bestMove;
|
||||||
|
}
|
||||||
|
//apply the move to the real board
|
||||||
|
private void applyMoveToBoard(Board board, Move move) {
|
||||||
|
int fromX = move.getFromX();//original X coordinate
|
||||||
|
int fromY = move.getFromY();//original Y coordinate
|
||||||
|
int toX = move.getToX();//destination X
|
||||||
|
int toY = move.getToY();//destination Y
|
||||||
|
Piece piece = move.getMovedPiece();//move the piece
|
||||||
|
|
||||||
|
// Make the move by creating the piece a the position
|
||||||
|
board.setPiece(piece.isWhite(), piece.getType(), toX, toY);
|
||||||
|
|
||||||
|
//remove the piece that moved
|
||||||
|
try {
|
||||||
|
//access the board field for modifications
|
||||||
|
java.lang.reflect.Field boardField = Board.class.getDeclaredField("board");
|
||||||
|
boardField.setAccessible(true);//give accessibility to it
|
||||||
|
Piece[][] boardArray = (Piece[][]) boardField.get(board);//board array
|
||||||
|
boardArray[fromX][fromY] = null;//make the case empty
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.err.println("Failed to update board: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increment turn
|
||||||
|
board.incrementTurn();
|
||||||
|
}
|
||||||
|
|
||||||
|
//Minimax algorithm with alpha-beta pruning to evaluate positions
|
||||||
|
|
||||||
|
private int minimax(Board board, int depth, int alpha, int beta, boolean isMaximizing) {
|
||||||
|
// Check if time limit is exceeded
|
||||||
|
if (System.currentTimeMillis() - startTime > TIME_LIMIT_MS) {
|
||||||
|
return evaluateBoard(board); // Return current evaluation if out of time
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the board before calculating possible moves
|
||||||
|
if (depth == 0) {
|
||||||
|
return evaluateBoard(board);//initial position
|
||||||
|
}
|
||||||
|
//all possible moves are generated
|
||||||
|
ArrayList<Move> possibleMoves = generateAllPossibleMoves(board, isMaximizing);
|
||||||
|
possibleMoves = orderMoves(possibleMoves);//order moves by their quality
|
||||||
|
|
||||||
|
// No moves available, could be checkmate or stalemate
|
||||||
|
if (possibleMoves.isEmpty()) {
|
||||||
|
return evaluateBoard(board);// current position
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isMaximizing) {//white turn (maximizing player)
|
||||||
|
int maxEval = Integer.MIN_VALUE;//initialize with lowest value
|
||||||
|
for (Move move : possibleMoves) {//try each move
|
||||||
|
Board boardCopy = new Board(board);//create a copy of the board
|
||||||
|
applyMoveToBoard(boardCopy, move);//apply the move
|
||||||
|
int eval = minimax(boardCopy, depth - 1, alpha, beta, false);//evaluate the resulting position
|
||||||
|
maxEval = Math.max(maxEval, eval);//gives best evaluation
|
||||||
|
alpha = Math.max(alpha, eval);//gives the alpha value
|
||||||
|
if (beta <= alpha) {
|
||||||
|
break; // Beta cutoff
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return maxEval;//best evaluation found
|
||||||
|
} else {//black turn
|
||||||
|
int minEval = Integer.MAX_VALUE;//initialize with highest value
|
||||||
|
for (Move move : possibleMoves) {
|
||||||
|
Board boardCopy = new Board(board);
|
||||||
|
applyMoveToBoard(boardCopy, move);
|
||||||
|
int eval = minimax(boardCopy, depth - 1, alpha, beta, true);
|
||||||
|
minEval = Math.min(minEval, eval);
|
||||||
|
beta = Math.min(beta, eval);//gives beta value
|
||||||
|
if (beta <= alpha) {
|
||||||
|
break; // Alpha cutoff
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return minEval;//best evaluation found
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Orders moves to improve alpha-beta pruning efficiency
|
||||||
|
|
||||||
|
private ArrayList<Move> orderMoves(ArrayList<Move> moves) {
|
||||||
|
// Score each move (captures are considered first)
|
||||||
|
for (Move move : moves) {
|
||||||
|
int score = 0;//initialize score for the move
|
||||||
|
|
||||||
|
// Prioritize captures based on a function of the Most Valuable Victim and the Least Valuable Attacker with the system of points
|
||||||
|
if (move.getCapturedPiece() != null) {
|
||||||
|
score += 10 * getPieceValue(move.getCapturedPiece().getType()) -
|
||||||
|
getPieceValue(move.getMovedPiece().getType()) / 10; // Higher score for capturing valuable pieces with less valuable ones
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the score with the move
|
||||||
|
move.setScore(score);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort moves by score in a descending
|
||||||
|
moves.sort((a, b) -> Integer.compare(b.getScore(), a.getScore()));
|
||||||
|
|
||||||
|
return moves;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Evaluates the current board position
|
||||||
|
|
||||||
|
private int evaluateBoard(Board board) {
|
||||||
|
int score = 0;
|
||||||
|
|
||||||
|
// Material evaluation
|
||||||
|
for (Piece piece : board.getPieces()) {
|
||||||
|
int pieceValue = getPieceValue(piece.getType());//get value of this piece
|
||||||
|
if (piece.isWhite()) {
|
||||||
|
score += pieceValue;//add value for white
|
||||||
|
} else {
|
||||||
|
score -= pieceValue;//substract value for black
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add position evaluation (takes in account the center control bonus)
|
||||||
|
for (Piece piece : board.getPieces()) {
|
||||||
|
int x = piece.getX();
|
||||||
|
int y = piece.getY();
|
||||||
|
|
||||||
|
// Center control bonus (small bonus for controlling center squares)
|
||||||
|
int centerBonus = 0;
|
||||||
|
if ((x == 3 || x == 4) && (y == 3 || y == 4)) {//center 4 squares
|
||||||
|
centerBonus = 1; // Small bonus for center control
|
||||||
|
}
|
||||||
|
|
||||||
|
if (piece.isWhite()) {
|
||||||
|
score += centerBonus;//add for white
|
||||||
|
} else {
|
||||||
|
score -= centerBonus;//subtract for black
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pawn advancement bonus (encourages pawn progression)
|
||||||
|
if (piece.getType() == PieceType.Pawn) {
|
||||||
|
int pawnBonus = 0;
|
||||||
|
if (piece.isWhite()) {
|
||||||
|
pawnBonus = 7 - y; // Higher bonus as white pawns advance
|
||||||
|
score += pawnBonus / 10; // Small fractional bonus
|
||||||
|
} else {
|
||||||
|
pawnBonus = y; // Higher bonus as black pawns advance
|
||||||
|
score -= pawnBonus / 10; // Small fractional bonus
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return score;//return final score
|
||||||
|
}
|
||||||
|
|
||||||
|
//Gets the 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;// safety net to protect from bug
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Generates all possible moves for a player
|
||||||
|
|
||||||
|
private ArrayList<Move> generateAllPossibleMoves(Board board, boolean isWhite) {
|
||||||
|
ArrayList<Move> moves = new ArrayList<>();//list of all moves
|
||||||
|
|
||||||
|
// Get all pieces of the current player
|
||||||
|
for (Piece piece : board.getPieces()) {
|
||||||
|
if (piece.isWhite() == isWhite) {//only consider pieces of the player
|
||||||
|
// Get valid moves for this piece
|
||||||
|
ArrayList<int[]> validMoves = piece.getValidMoves(board);
|
||||||
|
|
||||||
|
for (int[] destination : validMoves) {//move object for each valid destination
|
||||||
|
int toX = destination[0];//X
|
||||||
|
int toY = destination[1];//Y
|
||||||
|
Piece capturedPiece = board.getPiece(toX, toY);//check is a piece is capturable
|
||||||
|
|
||||||
|
// Create a move object with all theses infos
|
||||||
|
Move move = new Move(
|
||||||
|
piece.getX(), piece.getY(),//start X Y
|
||||||
|
toX, toY, //destination X Y
|
||||||
|
piece, //piece moved
|
||||||
|
capturedPiece //piece captured (if there i one)
|
||||||
|
);
|
||||||
|
|
||||||
|
moves.add(move);// add move to list
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return moves;//return all moves
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3,21 +3,22 @@ package backend;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
public class Board {
|
public class Board {
|
||||||
private int selectedX = -1;
|
|
||||||
|
private int selectedX = -1; // negative value means impossible x and y so unselected
|
||||||
private int selectedY = -1;
|
private int selectedY = -1;
|
||||||
private int turnNumber = 0;
|
|
||||||
public int width;
|
private int turnNumber = 0; // tracks current turn
|
||||||
|
public int width; // enables to define the dimensions of board (public because used in console)
|
||||||
public int height;
|
public int height;
|
||||||
private Piece[][] board;
|
private Piece[][] board; // 2D array chess board
|
||||||
private ArrayList<int[]> highlightedPositions = new ArrayList<>();
|
private ArrayList<int[]> highlightedPositions = new ArrayList<>(); // list of valid positions to highlight
|
||||||
private int[] lastPawnDoubleMove = null; // Added for en passant tracking
|
|
||||||
|
|
||||||
public Board(int colNum, int lineNum) {
|
public Board(int colNum, int lineNum) {
|
||||||
this.width = colNum;
|
this.width = colNum;
|
||||||
this.height = lineNum;
|
this.height = lineNum;
|
||||||
this.board = new Piece[width][height];
|
this.board = new Piece[width][height]; // first empty board *********REVIEW************
|
||||||
clearConsole();
|
clearConsole();
|
||||||
System.out.println(toString());
|
System.out.println(toString()); // print the chess at the beginning of the game
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getWidth() {
|
public int getWidth() {
|
||||||
|
|
@ -28,22 +29,28 @@ public class Board {
|
||||||
return height;
|
return height;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// new piece on the board at x,y (More specifically changes the empty cell of coordinates x,y with a new chess piece)
|
||||||
public void setPiece(boolean isWhite, PieceType type, int x, int y) {
|
public void setPiece(boolean isWhite, PieceType type, int x, int y) {
|
||||||
board[x][y] = new Piece(x, y, type, isWhite);
|
board[x][y] = new Piece(x, y, type, isWhite);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isTurnWhite() {
|
public boolean isTurnWhite() {
|
||||||
return turnNumber % 2 == 0;
|
if (turnNumber % 2 == 0) { // even turns including 0 are white's ones (% calculates the reminder of the euclidean division)
|
||||||
|
return true;
|
||||||
|
} else { // same reasoning, odd turns are black's ones
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getTurnNumber() {
|
public int getTurnNumber() { // this class enables to obtain the current turn number while increment adds 1 to this value for each turn
|
||||||
return turnNumber;
|
return turnNumber; // Necessarly in two functions to get rid of an infinite loop ****WHY****
|
||||||
}
|
}
|
||||||
|
|
||||||
public void incrementTurn() {
|
public void incrementTurn() {
|
||||||
turnNumber++;
|
turnNumber++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// set up the classic chess board taking it as a matrix and putting each corresponding starting piece at its place 0,0 is the top left spot of the board
|
||||||
public void populateBoard() {
|
public void populateBoard() {
|
||||||
// Black
|
// Black
|
||||||
setPiece(false, PieceType.Rook, 0, 0);
|
setPiece(false, PieceType.Rook, 0, 0);
|
||||||
|
|
@ -79,41 +86,97 @@ public class Board {
|
||||||
public void cleanBoard() {
|
public void cleanBoard() {
|
||||||
for (int x = 0; x < width; x++) {
|
for (int x = 0; x < width; x++) {
|
||||||
for (int y = 0; y < height; y++) {
|
for (int y = 0; y < height; y++) {
|
||||||
board[x][y] = null;
|
board[x][y] = null; // each position becomes empty
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Piece getPiece(int x, int y) {
|
public Piece getPiece(int x, int y) {
|
||||||
if (!isInBounds (x, y)) return null;
|
if (!isInBounds (x, y)) return null;
|
||||||
return board [x][y];
|
return board [x][y];
|
||||||
}
|
}
|
||||||
|
|
||||||
private void clearConsole() {
|
private void clearConsole() { // ***************CONSOLE
|
||||||
for (int i = 0; i < 50; i++) {
|
for (int i = 0; i < 50; i++) {
|
||||||
System.out.println();
|
System.out.println(); // Print 50 empty lines to "clear" the console
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isEnPassantTarget(int x, int y) {
|
public String toString() {
|
||||||
if (lastPawnDoubleMove == null) return false;
|
StringBuilder str = new StringBuilder();
|
||||||
if (lastPawnDoubleMove[2] != turnNumber - 1) return false;
|
str.append(" A B C D E F G H\n"); // columns letter at the top
|
||||||
return (lastPawnDoubleMove[0] == x && lastPawnDoubleMove[1] == y);
|
|
||||||
|
// representation of the rows
|
||||||
|
for (int y = 0; y < height; y++) {
|
||||||
|
str.append(8 - y).append(" "); // row number on the left
|
||||||
|
|
||||||
|
for (int x = 0; x < width; x++) {
|
||||||
|
if (board[x][y] == null) {
|
||||||
|
str.append("- "); // empty positions
|
||||||
|
} else {
|
||||||
|
// convert each piece of both color into a character
|
||||||
|
Piece piece = board[x][y];
|
||||||
|
char pieceChar;
|
||||||
|
|
||||||
|
switch (piece.getType()) { // switch function avoids too many if-else
|
||||||
|
case King: pieceChar = 'K'; break;
|
||||||
|
case Queen: pieceChar = 'Q'; break;
|
||||||
|
case Bishop: pieceChar = 'B'; break;
|
||||||
|
case Knight: pieceChar = 'N'; break; // N because we already have King
|
||||||
|
case Rook: pieceChar = 'R'; break;
|
||||||
|
case Pawn: pieceChar = 'P'; break;
|
||||||
|
default: pieceChar = '?'; break; // safety net
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Make black pieces in lowercase
|
||||||
|
if (!piece.isWhite()) {
|
||||||
|
pieceChar = Character.toLowerCase(pieceChar);
|
||||||
|
}
|
||||||
|
|
||||||
|
str.append(pieceChar).append(" "); // gives structure to the output
|
||||||
|
}
|
||||||
|
}
|
||||||
|
str.append("\n"); // change of row
|
||||||
|
}
|
||||||
|
|
||||||
|
// Additional infos for a proper output
|
||||||
|
str.append("Turn ").append(getTurnNumber()).append(": ");
|
||||||
|
str.append(isTurnWhite() ? "White" : "Black");
|
||||||
|
|
||||||
|
return str.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// list the placement of the pieces on the board
|
||||||
|
public ArrayList<Piece> getPieces() {
|
||||||
|
ArrayList<Piece> pieces = new ArrayList<>();
|
||||||
|
// collect infos for the non-empty positions
|
||||||
|
for (int x = 0; x < width; x++) {
|
||||||
|
for (int y = 0; y < height; y++) {
|
||||||
|
if (board[x][y] != null) {
|
||||||
|
pieces.add(board[x][y]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pieces;
|
||||||
|
}
|
||||||
|
|
||||||
|
// user clicks on the board
|
||||||
public void userTouch(int x, int y) {
|
public void userTouch(int x, int y) {
|
||||||
if (selectedX == -1 && selectedY == -1) {
|
if (selectedX == -1 && selectedY == -1) { // This condition is only possible at the very start of the game
|
||||||
|
// check if the position is empty and the color
|
||||||
if (board[x][y] != null && board[x][y].isWhite() == isTurnWhite()) {
|
if (board[x][y] != null && board[x][y].isWhite() == isTurnWhite()) {
|
||||||
|
// select it as active location
|
||||||
selectedX = x;
|
selectedX = x;
|
||||||
selectedY = y;
|
selectedY = y;
|
||||||
highlightedPositions = getValidMoves(board[x][y]);
|
highlightedPositions = getValidMoves(board[x][y]); // compute moves
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (x == selectedX && y == selectedY) {
|
if (x == selectedX && y == selectedY) {
|
||||||
|
// unselect it if the destination is unvalid (not highlighted)
|
||||||
selectedX = -1;
|
selectedX = -1;
|
||||||
selectedY = -1;
|
selectedY = -1;
|
||||||
highlightedPositions.clear();
|
highlightedPositions.clear();
|
||||||
} else {
|
} else {
|
||||||
|
// move if valid destination
|
||||||
boolean valid = false;
|
boolean valid = false;
|
||||||
for (int[] pos : highlightedPositions) {
|
for (int[] pos : highlightedPositions) {
|
||||||
if (pos[0] == x && pos[1] == y) {
|
if (pos[0] == x && pos[1] == y) {
|
||||||
|
|
@ -125,14 +188,6 @@ public class Board {
|
||||||
if (valid) {
|
if (valid) {
|
||||||
Piece pieceToMove = board[selectedX][selectedY];
|
Piece pieceToMove = board[selectedX][selectedY];
|
||||||
|
|
||||||
// Check for en passant capture
|
|
||||||
if (pieceToMove.getType() == PieceType.Pawn &&
|
|
||||||
x != selectedX &&
|
|
||||||
board[x][y] == null) {
|
|
||||||
board[x][selectedY] = null; // Capture the pawn
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle castling
|
|
||||||
if(pieceToMove.getType() == PieceType.King && Math.abs(x - selectedX)==2) {
|
if(pieceToMove.getType() == PieceType.King && Math.abs(x - selectedX)==2) {
|
||||||
y = selectedY;
|
y = selectedY;
|
||||||
if (x == 6) {
|
if (x == 6) {
|
||||||
|
|
@ -142,39 +197,36 @@ public class Board {
|
||||||
board[5][y].setX(5);
|
board[5][y].setX(5);
|
||||||
board[5][y].setY(y);
|
board[5][y].setY(y);
|
||||||
} else if (x == 2) {
|
} else if (x == 2) {
|
||||||
|
//System.out.println(board [3][y]);
|
||||||
|
//System.out.println(board [0][y]);
|
||||||
board [3][y] = board[0][y];
|
board [3][y] = board[0][y];
|
||||||
board [0][y] = null;
|
board [0][y] = null;
|
||||||
|
//System.out.println(board [3][y]);
|
||||||
|
//System.out.println(board [0][y]);
|
||||||
board[3][y].setMoved(true);
|
board[3][y].setMoved(true);
|
||||||
board[3][y].setX(3);
|
board[3][y].setX(3);
|
||||||
board[3][y].setY(y);
|
board[3][y].setY(y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Track pawn double move for en passant
|
|
||||||
if (pieceToMove.getType() == PieceType.Pawn &&
|
|
||||||
Math.abs(y - selectedY) == 2) {
|
|
||||||
lastPawnDoubleMove = new int[]{x, y, turnNumber};
|
|
||||||
} else {
|
|
||||||
lastPawnDoubleMove = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move the piece
|
|
||||||
board[x][y] = new Piece(x, y, pieceToMove.getType(), pieceToMove.isWhite());
|
board[x][y] = new Piece(x, y, pieceToMove.getType(), pieceToMove.isWhite());
|
||||||
board[x][y].setMoved(true);
|
|
||||||
board[selectedX][selectedY] = null;
|
board[selectedX][selectedY] = null;
|
||||||
|
board[x][y].setMoved(true);
|
||||||
incrementTurn();
|
incrementTurn();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// reset selection
|
||||||
selectedX = -1;
|
selectedX = -1;
|
||||||
selectedY = -1;
|
selectedY = -1;
|
||||||
highlightedPositions.clear();
|
highlightedPositions.clear();
|
||||||
|
clearConsole();
|
||||||
System.out.println(toString());
|
System.out.println(toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isSelected(int x, int y) {
|
public boolean isSelected(int x, int y) {
|
||||||
return (x == selectedX && y == selectedY);
|
return (x == selectedX && y == selectedY); // true if matching position
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isHighlighted(int x, int y) {
|
public boolean isHighlighted(int x, int y) {
|
||||||
|
|
@ -186,6 +238,7 @@ public class Board {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* utility methods */
|
||||||
public boolean isInBounds(int x, int y) {
|
public boolean isInBounds(int x, int y) {
|
||||||
return x >= 0 && x < width && y >= 0 && y < height;
|
return x >= 0 && x < width && y >= 0 && y < height;
|
||||||
}
|
}
|
||||||
|
|
@ -194,70 +247,22 @@ public class Board {
|
||||||
return piece.getValidMoves(this);
|
return piece.getValidMoves(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ArrayList<Piece> getPieces() {
|
|
||||||
ArrayList<Piece> pieces = new ArrayList<>();
|
|
||||||
for (int x = 0; x < width; x++) {
|
|
||||||
for (int y = 0; y < height; y++) {
|
|
||||||
if (board[x][y] != null) {
|
|
||||||
pieces.add(board[x][y]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return pieces;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String toString() {
|
|
||||||
StringBuilder str = new StringBuilder();
|
|
||||||
str.append(" A B C D E F G H\n");
|
|
||||||
|
|
||||||
for (int y = 0; y < height; y++) {
|
|
||||||
str.append(8 - y).append(" ");
|
|
||||||
|
|
||||||
for (int x = 0; x < width; x++) {
|
|
||||||
if (board[x][y] == null) {
|
|
||||||
str.append("- ");
|
|
||||||
} else {
|
|
||||||
Piece piece = board[x][y];
|
|
||||||
char pieceChar;
|
|
||||||
|
|
||||||
switch (piece.getType()) {
|
|
||||||
case King: pieceChar = 'K'; break;
|
|
||||||
case Queen: pieceChar = 'Q'; break;
|
|
||||||
case Bishop: pieceChar = 'B'; break;
|
|
||||||
case Knight: pieceChar = 'N'; break;
|
|
||||||
case Rook: pieceChar = 'R'; break;
|
|
||||||
case Pawn: pieceChar = 'P'; break;
|
|
||||||
default: pieceChar = '?'; break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!piece.isWhite()) {
|
|
||||||
pieceChar = Character.toLowerCase(pieceChar);
|
|
||||||
}
|
|
||||||
|
|
||||||
str.append(pieceChar).append(" ");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
str.append("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
str.append("Turn ").append(getTurnNumber()).append(": ");
|
|
||||||
str.append(isTurnWhite() ? "White" : "Black");
|
|
||||||
|
|
||||||
return str.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String[] toFileRep() {
|
public String[] toFileRep() {
|
||||||
String[] rep = new String[height + 1];
|
String[] rep = new String[height + 1]; // height lines for the board + 1 for turn info
|
||||||
|
|
||||||
|
// taking each row
|
||||||
for (int y = 0; y < height; y++) {
|
for (int y = 0; y < height; y++) {
|
||||||
StringBuilder line = new StringBuilder();
|
StringBuilder line = new StringBuilder();
|
||||||
|
//now each column
|
||||||
for (int x = 0; x < width; x++) {
|
for (int x = 0; x < width; x++) {
|
||||||
if (board[x][y] == null) {
|
if (board[x][y] == null) {
|
||||||
line.append("-");
|
line.append("-"); //empty square
|
||||||
} else {
|
} else {
|
||||||
|
// Convert each piece to a character using the same logic as toString()
|
||||||
Piece piece = board[x][y];
|
Piece piece = board[x][y];
|
||||||
String pieceChar = piece.getType().getSummary();
|
String pieceChar = piece.getType().getSummary(); //get the character representation
|
||||||
|
|
||||||
|
// Make black pieces lowercase, just like in toString()
|
||||||
if (!piece.isWhite()) {
|
if (!piece.isWhite()) {
|
||||||
pieceChar = pieceChar.toLowerCase();
|
pieceChar = pieceChar.toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
@ -265,65 +270,112 @@ public class Board {
|
||||||
line.append(pieceChar);
|
line.append(pieceChar);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add a comma after each square except the last one
|
||||||
if (x < width - 1) {
|
if (x < width - 1) {
|
||||||
line.append(",");
|
line.append(",");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rep[y] = line.toString();
|
rep[y] = line.toString();//store line
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add turn information as the last line
|
||||||
rep[height] = isTurnWhite() ? "W" : "B";
|
rep[height] = isTurnWhite() ? "W" : "B";
|
||||||
|
|
||||||
return rep;
|
return rep;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Board(String[] fileRepresentation) {
|
public Board(String[] fileRepresentation) {
|
||||||
this.height = fileRepresentation.length - 1;
|
// Determine board dimensions from the file
|
||||||
this.width = fileRepresentation[0].split(",").length;
|
this.height = fileRepresentation.length - 1; // Last line is turn info
|
||||||
|
this.width = fileRepresentation[0].split(",").length;//width counting with commas
|
||||||
this.board = new Piece[width][height];
|
this.board = new Piece[width][height];
|
||||||
this.selectedX = -1;
|
this.selectedX = -1;//no position selected at beginning
|
||||||
this.selectedY = -1;
|
this.selectedY = -1;
|
||||||
this.highlightedPositions = new ArrayList<>();
|
this.highlightedPositions = new ArrayList<>();//no highlighted position at beginning
|
||||||
|
|
||||||
|
// Process each row
|
||||||
for (int y = 0; y < height; y++) {
|
for (int y = 0; y < height; y++) {
|
||||||
String[] squares = fileRepresentation[y].split(",");
|
String[] squares = fileRepresentation[y].split(",");//split rows with commas
|
||||||
|
|
||||||
for (int x = 0; x < width; x++) {
|
for (int x = 0; x < width; x++) {//take the square infos
|
||||||
String squareData = squares[x];
|
String squareData = squares[x];
|
||||||
|
|
||||||
|
// Skip empty squares
|
||||||
if (squareData.equals("-")) {
|
if (squareData.equals("-")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
char pieceChar = squareData.charAt(0);
|
char pieceChar = squareData.charAt(0);//take the character of the piece
|
||||||
|
|
||||||
|
// Determine color by case: uppercase = white, lowercase = black
|
||||||
boolean isWhite = Character.isUpperCase(pieceChar);
|
boolean isWhite = Character.isUpperCase(pieceChar);
|
||||||
|
|
||||||
|
// Convert to uppercase for the fromSummary method if it's lowercase
|
||||||
if (!isWhite) {
|
if (!isWhite) {
|
||||||
pieceChar = Character.toUpperCase(pieceChar);
|
pieceChar = Character.toUpperCase(pieceChar);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get piece type
|
||||||
PieceType type = PieceType.fromSummary(pieceChar);
|
PieceType type = PieceType.fromSummary(pieceChar);
|
||||||
|
|
||||||
|
// Create the piece
|
||||||
setPiece(isWhite, type, x, y);
|
setPiece(isWhite, type, x, y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set turn information
|
||||||
String turnInfo = fileRepresentation[height];
|
String turnInfo = fileRepresentation[height];
|
||||||
this.turnNumber = turnInfo.equals("W") ? 0 : 1;
|
this.turnNumber = turnInfo.equals("W") ? 0 : 1;//white=even, black=odd
|
||||||
|
|
||||||
clearConsole();
|
clearConsole();
|
||||||
System.out.println(toString());
|
System.out.println(toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* additional functionality to implement later */
|
||||||
public void undoLastMove() {
|
public void undoLastMove() {
|
||||||
// TODO
|
// TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
public void playMove(Move move) {
|
public void playMove(Move move) {
|
||||||
// TODO
|
int fromX = move.getFromX();
|
||||||
|
int fromY = move.getFromY();
|
||||||
|
int toX = move.getToX();
|
||||||
|
int toY = move.getToY();
|
||||||
|
Piece piece = board[fromX][fromY];
|
||||||
|
|
||||||
|
// Make the move
|
||||||
|
board[toX][toY] = new Piece(toX, toY, piece.getType(), piece.isWhite());
|
||||||
|
board[toX][toY].setMoved(true);
|
||||||
|
board[fromX][fromY] = null;
|
||||||
|
|
||||||
|
incrementTurn();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Board(Board board) {
|
|
||||||
// TODO
|
// Copy constructor that creates a copy of another board
|
||||||
|
|
||||||
|
public Board(Board otherBoard) {
|
||||||
|
this.width = otherBoard.width;
|
||||||
|
this.height = otherBoard.height;
|
||||||
|
this.turnNumber = otherBoard.turnNumber;
|
||||||
|
this.selectedX = -1; // Don't copy selection because negative in a matrix
|
||||||
|
this.selectedY = -1;
|
||||||
|
this.board = new Piece[width][height];
|
||||||
|
this.highlightedPositions = new ArrayList<>();
|
||||||
|
|
||||||
|
// Deep copy the board pieces
|
||||||
|
for (int x = 0; x < width; x++) {
|
||||||
|
for (int y = 0; y < height; y++) {
|
||||||
|
if (otherBoard.board[x][y] != null) {
|
||||||
|
Piece original = otherBoard.board[x][y];
|
||||||
|
this.board[x][y] = new Piece(x, y, original.getType(), original.isWhite());
|
||||||
|
// Remove the hasMoved check since it doesn't exist
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,48 @@
|
||||||
package backend;
|
package backend;
|
||||||
|
|
||||||
import java.util.Optional;
|
// Represents a chess move from one position to another
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a chess move, including the starting and ending positions,
|
|
||||||
* the moving piece, and an optional captured piece.
|
|
||||||
*/
|
|
||||||
public class Move {
|
public class Move {
|
||||||
|
private int fromX; // Starting X coordinate
|
||||||
|
private int fromY; // Starting Y coordinate
|
||||||
|
private int toX; // Destination X coordinate
|
||||||
|
private int toY; // Destination Y coordinate
|
||||||
|
private Piece movedPiece; // The piece being moved
|
||||||
|
private Piece capturedPiece;// The piece being captured (null if none)
|
||||||
|
private int score = 0; // Used for move ordering in alpha-beta pruning
|
||||||
|
|
||||||
|
//Constructor for a move
|
||||||
|
|
||||||
|
public Move(int fromX, int fromY, int toX, int toY, Piece movedPiece, Piece capturedPiece) {
|
||||||
|
this.fromX = fromX;
|
||||||
|
this.fromY = fromY;
|
||||||
|
this.toX = toX;
|
||||||
|
this.toY = toY;
|
||||||
|
this.movedPiece = movedPiece;
|
||||||
|
this.capturedPiece = capturedPiece;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getters to access move properties
|
||||||
|
public int getFromX() { return fromX; }
|
||||||
|
public int getFromY() { return fromY; }
|
||||||
|
public int getToX() { return toX; }
|
||||||
|
public int getToY() { return toY; }
|
||||||
|
public Piece getMovedPiece() { return movedPiece; }
|
||||||
|
public Piece getCapturedPiece() { return capturedPiece; }
|
||||||
|
|
||||||
|
// Method for move ordering
|
||||||
|
public void setScore(int score) {
|
||||||
|
this.score = score;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getScore() {
|
||||||
|
return score;//return the evaluation score of the move
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {//coordinates into chess notation
|
||||||
|
String from = (char)('a' + fromX) + "" + (8 - fromY);
|
||||||
|
String to = (char)('a' + toX) + "" + (8 - toY);
|
||||||
|
return from + " -> " + to;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@ public class Piece {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// diagonal captures (including en passant)
|
// diagonal captures
|
||||||
for (int dx = -1; dx <= 1; dx += 2) {
|
for (int dx = -1; dx <= 1; dx += 2) {
|
||||||
int nx = x + dx;
|
int nx = x + dx;
|
||||||
if (board.isInBounds(nx, nextY)) {
|
if (board.isInBounds(nx, nextY)) {
|
||||||
|
|
@ -78,14 +78,6 @@ public class Piece {
|
||||||
if (target != null && target.isWhite() != this.isWhite()) {
|
if (target != null && target.isWhite() != this.isWhite()) {
|
||||||
moves.add(new int[]{nx, nextY});
|
moves.add(new int[]{nx, nextY});
|
||||||
}
|
}
|
||||||
// Check for en passant
|
|
||||||
else if (target == null &&
|
|
||||||
board.getPiece(nx, y) != null &&
|
|
||||||
board.getPiece(nx, y).getType() == PieceType.Pawn &&
|
|
||||||
board.getPiece(nx, y).isWhite() != this.isWhite() &&
|
|
||||||
board.isEnPassantTarget(nx, y)) {
|
|
||||||
moves.add(new int[]{nx, nextY});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
@ -203,5 +195,4 @@ public class Piece {
|
||||||
ny += dy;
|
ny += dy;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue