Compare commits

...

2 Commits

Author SHA1 Message Date
mathy 9167153a9e test 2025-05-20 16:48:02 +02:00
mathy 52a663e698 autoplayer 2025-05-16 11:52:17 +02:00
4 changed files with 702 additions and 382 deletions

View File

@ -1,17 +1,258 @@
package backend;
import java.util.ArrayList;
import java.util.Random;
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
/**
* returns the best Move to try on provided board for active player
* @param board
* @return
*/
public Move computeBestMove(Board board) {
/// This chess AI uses a minimax algorithm with alpha-beta pruning to look 2 moves ahead so depth 4
// it will evaluate the material value, the center control, and the pawn advancement (ELO 1400-1800)
return null;
}
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) {
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
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
}
}

View File

@ -3,21 +3,22 @@ package backend;
import java.util.ArrayList;
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 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;
private Piece[][] board;
private ArrayList<int[]> highlightedPositions = new ArrayList<>();
private int[] lastPawnDoubleMove = null; // Added for en passant tracking
private Piece[][] board; // 2D array chess board
private ArrayList<int[]> highlightedPositions = new ArrayList<>(); // list of valid positions to highlight
public Board(int colNum, int lineNum) {
this.width = colNum;
this.height = lineNum;
this.board = new Piece[width][height];
this.board = new Piece[width][height]; // first empty board *********REVIEW************
clearConsole();
System.out.println(toString());
System.out.println(toString()); // print the chess at the beginning of the game
}
public int getWidth() {
@ -28,22 +29,28 @@ public class Board {
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) {
board[x][y] = new Piece(x, y, type, isWhite);
}
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() {
return turnNumber;
public int getTurnNumber() { // this class enables to obtain the current turn number while increment adds 1 to this value for each turn
return turnNumber; // Necessarly in two functions to get rid of an infinite loop ****WHY****
}
public void incrementTurn() {
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() {
// Black
setPiece(false, PieceType.Rook, 0, 0);
@ -79,41 +86,97 @@ public class Board {
public void cleanBoard() {
for (int x = 0; x < width; x++) {
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) {
if (!isInBounds(x, y)) return null;
return board[x][y];
}
if (!isInBounds (x, y)) return null;
return board [x][y];
}
private void clearConsole() {
private void clearConsole() { // ***************CONSOLE
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) {
if (lastPawnDoubleMove == null) return false;
if (lastPawnDoubleMove[2] != turnNumber - 1) return false;
return (lastPawnDoubleMove[0] == x && lastPawnDoubleMove[1] == y);
public String toString() {
StringBuilder str = new StringBuilder();
str.append(" A B C D E F G H\n"); // columns letter at the top
// 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) {
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()) {
// select it as active location
selectedX = x;
selectedY = y;
highlightedPositions = getValidMoves(board[x][y]);
highlightedPositions = getValidMoves(board[x][y]); // compute moves
}
} else {
if (x == selectedX && y == selectedY) {
// unselect it if the destination is unvalid (not highlighted)
selectedX = -1;
selectedY = -1;
highlightedPositions.clear();
} else {
// move if valid destination
boolean valid = false;
for (int[] pos : highlightedPositions) {
if (pos[0] == x && pos[1] == y) {
@ -125,56 +188,45 @@ public class Board {
if (valid) {
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) {
y = selectedY;
if (x == 6) {
board[5][y] = board[7][y];
board[7][y] = null;
board[5][y].setMoved(true);
board[5][y].setX(5);
board[5][y].setY(y);
} else if (x == 2) {
board[3][y] = board[0][y];
board[0][y] = null;
board[3][y].setMoved(true);
board[3][y].setX(3);
board[3][y].setY(y);
}
y = selectedY;
if (x == 6) {
board[5][y] = board[7][y];
board[7][y] = null;
board[5][y].setMoved(true);
board[5][y].setX(5);
board[5][y].setY(y);
} else if (x == 2) {
//System.out.println(board [3][y]);
//System.out.println(board [0][y]);
board [3][y] = board[0][y];
board [0][y] = null;
//System.out.println(board [3][y]);
//System.out.println(board [0][y]);
board[3][y].setMoved(true);
board[3][y].setX(3);
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].setMoved(true);
board[selectedX][selectedY] = null;
board[x][y].setMoved(true);
incrementTurn();
}
// reset selection
selectedX = -1;
selectedY = -1;
highlightedPositions.clear();
clearConsole();
System.out.println(toString());
}
}
}
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) {
@ -186,6 +238,7 @@ public class Board {
return false;
}
/* utility methods */
public boolean isInBounds(int x, int y) {
return x >= 0 && x < width && y >= 0 && y < height;
}
@ -194,70 +247,22 @@ public class Board {
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() {
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++) {
StringBuilder line = new StringBuilder();
//now each column
for (int x = 0; x < width; x++) {
if (board[x][y] == null) {
line.append("-");
line.append("-"); //empty square
} else {
// Convert each piece to a character using the same logic as toString()
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()) {
pieceChar = pieceChar.toLowerCase();
}
@ -265,65 +270,112 @@ public class Board {
line.append(pieceChar);
}
// Add a comma after each square except the last one
if (x < width - 1) {
line.append(",");
}
}
rep[y] = line.toString();
rep[y] = line.toString();//store line
}
// Add turn information as the last line
rep[height] = isTurnWhite() ? "W" : "B";
return rep;
}
public Board(String[] fileRepresentation) {
this.height = fileRepresentation.length - 1;
this.width = fileRepresentation[0].split(",").length;
// Determine board dimensions from the file
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.selectedX = -1;
this.selectedX = -1;//no position selected at beginning
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++) {
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];
// Skip empty squares
if (squareData.equals("-")) {
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);
// Convert to uppercase for the fromSummary method if it's lowercase
if (!isWhite) {
pieceChar = Character.toUpperCase(pieceChar);
}
// Get piece type
PieceType type = PieceType.fromSummary(pieceChar);
// Create the piece
setPiece(isWhite, type, x, y);
}
}
// Set turn information
String turnInfo = fileRepresentation[height];
this.turnNumber = turnInfo.equals("W") ? 0 : 1;
this.turnNumber = turnInfo.equals("W") ? 0 : 1;//white=even, black=odd
clearConsole();
System.out.println(toString());
}
/* additional functionality to implement later */
public void undoLastMove() {
// TODO
}
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
}
}
}
}
}

View File

@ -1,12 +1,48 @@
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 {
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;
}
}

View File

@ -54,41 +54,33 @@ public class Piece {
int y = this.getY();
switch (type) {
case Pawn:
int direction = isWhite() ? -1 : 1;
int nextY = y + direction;
case Pawn:
int direction = isWhite() ? -1 : 1;
int nextY = y + direction;
// forward move
if (board.isInBounds(x, nextY) && board.getPiece(x, nextY) == null) {
moves.add(new int[]{x, nextY});
// forward move
if (board.isInBounds(x, nextY) && board.getPiece(x, nextY) == null) {
moves.add(new int[]{x, nextY});
// double move from starting position
int startRow = isWhite() ? 6 : 1;
int doubleStepY = y + 2 * direction;
if (y == startRow && board.isInBounds(x, doubleStepY) && board.getPiece(x, doubleStepY) == null) {
moves.add(new int[]{x, doubleStepY});
}
}
// diagonal captures (including en passant)
for (int dx = -1; dx <= 1; dx += 2) {
int nx = x + dx;
if (board.isInBounds(nx, nextY)) {
Piece target = board.getPiece(nx, nextY);
if (target != null && target.isWhite() != this.isWhite()) {
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});
// double move from starting position
int startRow = isWhite() ? 6 : 1;
int doubleStepY = y + 2 * direction;
if (y == startRow && board.isInBounds(x, doubleStepY) && board.getPiece(x, doubleStepY) == null) {
moves.add(new int[]{x, doubleStepY});
}
}
}
break;
// diagonal captures
for (int dx = -1; dx <= 1; dx += 2) {
int nx = x + dx;
if (board.isInBounds(nx, nextY)) {
Piece target = board.getPiece(nx, nextY);
if (target != null && target.isWhite() != this.isWhite()) {
moves.add(new int[]{nx, nextY});
}
}
}
break;
case Rook:
addLinearMoves(board, moves, x, y, 1, 0);
@ -203,5 +195,4 @@ public class Piece {
ny += dy;
}
}
}