package backend; import java.util.ArrayList; import java.util.Stack; import javax.swing.JLabel; import javax.swing.JPanel; /* Task: Chess Game Board Implementation Class name: Board Methods name: - getWidth() - Returns the width of the board as an integer - getHeight() - Returns the height of the board as an integer - getTurnNumber() - Returns the current turn number as an integer - isTurnWhite() - Returns true if it's white's turn, false if it's black's turn - setPiece() - Places a piece on the board at specified coordinates, returns nothing - populateBoard() - Sets up the initial chess position with all pieces, returns nothing - cleanBoard() - Resets the board and turn counter to initial state, returns nothing - toString() - Creates a string representation of the board with piece positions and turn info - getPieces() - Returns ArrayList containing all pieces currently on the board - userTouch() - Handles user interaction with the board (selecting/moving pieces), returns nothing - isSelected() - Returns true if the specified square is currently selected, false otherwise - toFileRep() - Returns a String array representation of the board for saving - Board(String[] fileRepresentation) - Constructor that loads a board from string array, returns nothing - calculateValidMoves() - Calculates and highlights valid moves for selected piece, returns nothing - isHighlighted() - Returns true if the specified square is highlighted as a legal move - clearHighlights() - Clears all highlighted squares, returns nothing - highlightLegalMoves() - Highlights all legal moves for a given piece, returns nothing - getLegalMoves() - Returns ArrayList of all legal moves for a given piece - handlePawnMoves() - Adds all legal pawn moves to the moves list, returns nothing - checkPawnCapture() - Checks if a pawn can capture diagonally, returns nothing - handleKnightMoves() - Adds all legal knight moves to the moves list, returns nothing - handleSlidingMoves() - Adds all legal sliding moves (bishop/rook/queen) to the moves list, returns nothing - handleKingMoves() - Adds all legal king moves to the moves list, returns nothing - isValidPosition() - Returns true if the coordinates are within the board boundaries - isEmpty() - Returns true if the specified square is empty - isEnemy() - Returns true if the specified square contains an enemy piece - isAlly() - Returns true if the specified square contains a friendly piece - undoLastMove() - Reverts to the previous board state, returns nothing - Board(Board board) - Copy constructor that creates a duplicate board, returns nothing - playMove() - Executes a chess move and updates the board state, returns nothing - findKing() - Returns the Piece object of the king of specified color - isKingInCheck() - Returns true if the king of specified color is in check - promotePawn() - Returns a new Queen piece to replace a pawn at the end rank - highlightKingInCheck() - Highlights kings that are in check, returns nothing - canSelectPieceWhenInCheck() - Returns true if the piece can be selected when king is in check - isSquareAttacked() - Returns true if the specified square is under attack by specified color - filterMovesForCheck() - Removes moves that would leave king in check, returns nothing Methods output: - Board dimensions (width, height) - Current turn number and active player - String representation of the chess board - Collection of all pieces on the board - Selected piece status - Highlighted squares showing legal moves - File representation for saving/loading - Move validation results - King check status - Game state information Methods works: - Board initialization and management - Chess piece movement and validation - Game state tracking (turns, check conditions) - Special chess rules (promotion, en passant) - Check and checkmate detection - Move history and undo functionality - Board state saving and loading - Legal move calculation for all piece types - User interaction handling - Visual board representation Authors: Bédier Jérôme jerome.bedier@ecam.fr, Gardern Florian florian.gardner@ecam.fr, Leng Sidden sidden.leng@ecam.fr Date: 05/21/2025 */ public class Board { private Piece[][] board; private int width; private int height; private int turn; private int selectedX = -1; private int selectedY = -1; private boolean[][] highlightedSquares; private Stack undoStack = new Stack<>(); private int enPassantCol = -1; private int enPassantRow = -1; public Board(int colNum, int lineNum) { this.width = colNum; // col move x this.height = lineNum; // line mov in y this.board = new Piece[width][height]; // 8x8 chess board this.highlightedSquares = new boolean[width][height]; } public int getWidth() { //width = 8; // setting the width at 8 for the moment can be changed return width; } public int getHeight() { //height = 8; // setting the height at 8 for the moment can be changed return height; } public int getTurnNumber() { return turn; } public boolean isTurnWhite() { return turn % 2 == 0; // White starts on turn 0 and only even turns } public void setPiece(boolean isWhite, PieceType type, int x, int y) { /* Ensure coordinates are inside the board boundaries if (x < 0 || x >= width || y < 0 || y >= height) { System.out.println("Invalid coordinates: (" + x + ", " + y + ")"); return; }*/ // Create and place the new piece board[x][y] = new Piece(isWhite, type, x, y); System.out.println(toString()); // Work but not sure ? } public void populateBoard() { // Place Rooks board[0][0] = new Piece(false, PieceType.Rook, 0, 0); //color, piece , x -> , y | board[0][7] = new Piece(true, PieceType.Rook, 0, 7); board[7][0] = new Piece(false, PieceType.Rook, 7, 0); board[7][7] = new Piece(true, PieceType.Rook, 7, 7); // Place Knights board[1][0] = new Piece(false, PieceType.Knight, 1, 0); board[1][7] = new Piece(true, PieceType.Knight, 1, 7); board[6][0] = new Piece(false, PieceType.Knight, 6, 0); board[6][7] = new Piece(true, PieceType.Knight, 6, 7); // Place Bishops board[2][0] = new Piece(false, PieceType.Bishop, 2, 0); board[2][7] = new Piece(true, PieceType.Bishop, 2, 7); board[5][0] = new Piece(false, PieceType.Bishop, 5, 0); board[5][7] = new Piece(true, PieceType.Bishop, 5, 7); // Place Queens board[4][0] = new Piece(false, PieceType.Queen, 4, 0); board[4][7] = new Piece(true, PieceType.Queen, 4, 7); // Place Kings board[3][0] = new Piece(false, PieceType.King, 3, 0); board[3][7] = new Piece(true, PieceType.King, 3, 7); // Place Pawns for (int i = 0; i < 8; i++) { board[i][1] = new Piece(false, PieceType.Pawn, i, 1); //color, piece , x -> , y | board[i][6] = new Piece(true, PieceType.Pawn, i, 6); //color, piece , x -> , y | } } public void cleanBoard() { turn = 0; this.board = new Piece[width][height]; // should work ? } public String toString() { StringBuilder sb = new StringBuilder(); for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { Piece piece = board[x][y]; if (piece == null) { sb.append(" "); } else { // Using Teacher method no breaks sb.append(piece.isWhite() ? "W" : "B") .append(piece.getType().getSummary()); } if (x < width - 1) sb.append(","); } sb.append("\n"); } sb.append("Turn: ").append(isTurnWhite() ? "White" : "Black").append("\n"); return sb.toString(); } public ArrayList getPieces() { ArrayList pieces = new ArrayList<>(); for (int i = 0; i < width; i++) { for (int j = 0; j < height; j++) { if (board[i][j] != null) { pieces.add(board[i][j]); } } } return pieces; } public void userTouch(int x, int y) { if (x < 0 || x >= width || y < 0 || y >= height) return; Piece clickedPiece = board[x][y]; // No piece selected yet if (selectedX == -1 && selectedY == -1) { if (clickedPiece != null && clickedPiece.isWhite() == isTurnWhite() && canSelectPieceWhenInCheck(x, y)){ selectedX = x; selectedY = y; calculateValidMoves(clickedPiece); filterMovesForCheck(); } } else { // Clicked the same piece again : unselect if (x == selectedX && y == selectedY) { selectedX = -1; selectedY = -1; clearHighlights(); } else if (highlightedSquares[x][y]) { // Move the piece Move move = new Move(selectedX, selectedY, x, y); playMove(move); // This handles moving the piece AND saving the undo state. selectedX = -1; selectedY = -1; // Unselect clearHighlights(); highlightKingInCheck(); System.out.println(toString()); } } } public boolean isSelected(int x, int y) { return x == selectedX && y == selectedY; } /* saving-loading feature :*/ public String[] toFileRep() { String[] lines = new String[height + 1]; // one line per row + 1 for turn for (int y = 0; y < height; y++) { StringBuilder sb = new StringBuilder(); for (int x = 0; x < width; x++) { Piece piece = board[x][y]; if (piece == null) { sb.append("--"); } else { sb.append(piece.isWhite() ? "W" : "B"); sb.append(piece.getType().getSummary()); } if (x < width - 1) sb.append(","); } lines[y] = sb.toString(); } // Last line stores the turn number lines[height] = Integer.toString(turn); return lines; } public Board(String[] fileRepresentation) { this.height = fileRepresentation.length - 1; this.width = fileRepresentation[0].split(",").length; this.board = new Piece[width][height]; this.highlightedSquares = new boolean[width][height]; for (int y = 0; y < height; y++) { String[] tokens = fileRepresentation[y].split(","); for (int x = 0; x < width; x++) { String token = tokens[x]; if (!token.equals("--")) { boolean isWhite = token.charAt(0) == 'W'; char pieceChar = token.charAt(1); PieceType type = PieceType.fromSummary(pieceChar); board[x][y] = new Piece(isWhite, type, x, y); } else { board[x][y] = null; } } } this.turn = Integer.parseInt(fileRepresentation[height]); } private void calculateValidMoves(Piece piece) { clearHighlights(); if (piece == null) return; ArrayList legalMoves = getLegalMoves(piece); // Make sure getLegalMoves is implemented for (Move move : legalMoves) { highlightedSquares[move.getToX()][move.getToY()] = true; } } /* The following methods require more work ! */ public boolean isHighlighted(int x, int y) { return highlightedSquares[x][y]; } private void clearHighlights() { highlightedSquares = new boolean[width][height]; } private void highlightLegalMoves(Piece piece) { // Clear previous highlights for (int i = 0; i < width; i++) for (int j = 0; j < height; j++) highlightedSquares[i][j] = false; if (piece == null) return; ArrayList legalMoves = getLegalMoves(piece); // your existing method for (int i = 0; i < legalMoves.size(); i++) { Move move = legalMoves.get(i); highlightedSquares[move.getToX()][move.getToY()] = true; // assuming Move has toX and toY fields } } public ArrayList getLegalMoves(Piece piece) { ArrayList moves = new ArrayList<>(); int x = piece.getX(); int y = piece.getY(); boolean isWhite = piece.isWhite(); switch(piece.getType()) { case Pawn: handlePawnMoves(moves, piece, x, y); break; case Knight: handleKnightMoves(moves, piece, x, y); break; case Bishop: handleSlidingMoves(moves, piece, x, y, new int[][]{{1,1}, {1,-1}, {-1,1}, {-1,-1}}); break; case Rook: handleSlidingMoves(moves, piece, x, y, new int[][]{{1,0}, {-1,0}, {0,1}, {0,-1}}); break; case Queen: handleSlidingMoves(moves, piece, x, y, new int[][]{{1,0}, {-1,0}, {0,1}, {0,-1}, {1,1}, {1,-1}, {-1,1}, {-1,-1}}); break; case King: handleKingMoves(moves, piece, x, y); break; } return moves; } // Helper methods for movement calculations private void handlePawnMoves(ArrayList moves, Piece pawn, int x, int y) { int direction = pawn.isWhite() ? -1 : 1; int startRow = pawn.isWhite() ? 6 : 1; // Forward moves if (isEmpty(x, y + direction)) { moves.add(new Move(x, y, x, y + direction)); if (y == startRow && isEmpty(x, y + 2*direction)) { moves.add(new Move(x, y, x, y + 2*direction)); } } // Captures checkPawnCapture(moves, pawn, x, y, direction, -1); checkPawnCapture(moves, pawn, x, y, direction, 1); // En passant captures if ((pawn.isWhite() && y == 3) || (!pawn.isWhite() && y == 4)) { // Check both sides for (int dx : new int[]{-1, 1}) { int captureX = x + dx; // If there's a valid en passant opportunity if (captureX == enPassantCol && y + direction == enPassantRow) { Move enPassantMove = new Move(x, y, captureX, y + direction); // Set the captured piece (the pawn that just moved) enPassantMove.setCapturedPiece(board[captureX][y]); moves.add(enPassantMove); } } } } private void checkPawnCapture(ArrayList moves, Piece pawn, int x, int y, int dir, int dx) { int nx = x + dx; int ny = y + dir; if (isEnemy(nx, ny, pawn.isWhite())) { Move move = new Move(x, y, nx, ny); move.setCapturedPiece(board[nx][ny]); moves.add(move); } } private void handleKnightMoves(ArrayList moves, Piece knight, int x, int y) { int[][] offsets = {{2,1}, {2,-1}, {-2,1}, {-2,-1}, {1,2}, {1,-2}, {-1,2}, {-1,-2}}; for (int[] offset : offsets) { int nx = x + offset[0]; int ny = y + offset[1]; if (isValidPosition(nx, ny) && !isAlly(nx, ny, knight.isWhite())) { Move move = new Move(x, y, nx, ny); if (isEnemy(nx, ny, knight.isWhite())) { move.setCapturedPiece(board[nx][ny]); } moves.add(move); } } } private void handleSlidingMoves(ArrayList moves, Piece piece, int x, int y, int[][] directions) { for (int[] dir : directions) { int nx = x; int ny = y; while (true) { nx += dir[0]; ny += dir[1]; if (!isValidPosition(nx, ny)) break; if (isAlly(nx, ny, piece.isWhite())) break; Move move = new Move(x, y, nx, ny); if (isEnemy(nx, ny, piece.isWhite())) { move.setCapturedPiece(board[nx][ny]); moves.add(move); break; } moves.add(move); } } } private void handleKingMoves(ArrayList moves, Piece king, int x, int y) { for (int dx = -1; dx <= 1; dx++) { for (int dy = -1; dy <= 1; dy++) { if (dx == 0 && dy == 0) continue; int nx = x + dx; int ny = y + dy; if (isValidPosition(nx, ny) && !isAlly(nx, ny, king.isWhite())) { Move move = new Move(x, y, nx, ny); if (isEnemy(nx, ny, king.isWhite())) { move.setCapturedPiece(board[nx][ny]); } moves.add(move); } } } } // Utility methods private boolean isValidPosition(int x, int y) { return x >= 0 && x < width && y >= 0 && y < height; } public boolean isEmpty(int x, int y) { return isValidPosition(x, y) && board[x][y] == null; } private boolean isEnemy(int x, int y, boolean isWhite) { return isValidPosition(x, y) && board[x][y] != null && board[x][y].isWhite() != isWhite; } private boolean isAlly(int x, int y, boolean isWhite) { return isValidPosition(x, y) && board[x][y] != null && board[x][y].isWhite() == isWhite; } public Piece getPieceAt(int x, int y) { if (!isValidPosition(x, y)) return null; return board[x][y]; } public void removePieceAt(int x, int y) { if (isValidPosition(x, y)) board[x][y] = null; } public void setEnPassantCol(int col) { this.enPassantCol = col; } public void setEnPassantRow(int row) { this.enPassantRow = row; } public int getEnPassantCol() { return this.enPassantCol; } public int getEnPassantRow() { return this.enPassantRow; } public void undoLastMove() { if (!undoStack.isEmpty()) { String[] lastState = undoStack.pop(); // Load the board from the saved state Board previousBoard = new Board(lastState); // Copy the previousBoard's fields into current board instance this.board = previousBoard.board; this.turn = previousBoard.turn; this.width = previousBoard.width; this.height = previousBoard.height; this.highlightedSquares = previousBoard.highlightedSquares; // Clear selection and highlights as well (optional) this.selectedX = -1; this.selectedY = -1; clearHighlights(); System.out.println("Undo performed."); System.out.println(this.toString()); } else { System.out.println("No moves to undo."); } } public Board(Board original) { this.width = original.width; this.height = original.height; this.turn = original.turn; this.selectedX = original.selectedX; this.selectedY = original.selectedY; this.enPassantCol = original.enPassantCol; this.enPassantRow = original.enPassantRow; // Deep copy the board array and pieces this.board = new Piece[width][height]; for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { Piece p = original.board[x][y]; if (p != null) { this.board[x][y] = new Piece(p.isWhite(), p.getType(), x, y); } else { this.board[x][y] = null; } } } // Deep copy highlightedSquares this.highlightedSquares = new boolean[width][height]; for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { this.highlightedSquares[x][y] = original.highlightedSquares[x][y]; } } // Undo stack is not copied (empty) this.undoStack = new Stack<>(); }//test public void playMove(Move move) { // Save current state before move for undo undoStack.push(toFileRep()); Piece piece = board[move.getFromX()][move.getFromY()]; SpecialMoves specialMoves = new SpecialMoves(); // Handle en passant capture if (specialMoves.isEnPassant(this, move)) { Piece capturedPawn = board[move.getToX()][move.getFromY()]; move.setCapturedPiece(capturedPawn); specialMoves.handleEnPassant(this, move); } // Move the piece board[move.getToX()][move.getToY()] = piece; board[move.getFromX()][move.getFromY()] = null; piece.x = move.getToX(); piece.y = move.getToY(); // Update en passant tracking specialMoves.updateEnPassantTracking(this, move, piece); // Handle pawn promotion if (piece.getType() == PieceType.Pawn && (piece.getY() == 0 || piece.getY() == height - 1)) { board[piece.getX()][piece.getY()] = specialMoves.promotePawn(piece.isWhite(), piece.getX(), piece.getY()); } turn++; } //Finds the position of the king of a given color. public Piece findKing(boolean isWhite) { for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { Piece piece = board[x][y]; if (piece != null && piece.getType() == PieceType.King && piece.isWhite() == isWhite) { return piece; } } } return null; // King not found (should not happen in normal chess) } // check king check based on a given color public boolean isKingInCheck(boolean isWhite) { // First, find the king's position Piece king = findKing(isWhite); soudEffect soundPlayer = new soudEffect(); if (king == null) { // If king not found (shouldn't happen in a valid chess game) return false; } int kingX = king.getX(); int kingY = king.getY(); // Check if any enemy piece can attack the king for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { Piece piece = board[x][y]; // Skip empty squares and pieces of the same color as the king if (piece == null || piece.isWhite() == isWhite) { continue; } // Get all legal moves for this enemy piece ArrayList moves = getLegalMoves(piece); // Check if any move can capture the king for (Move move : moves) { if (move.getToX() == kingX && move.getToY() == kingY) { soundPlayer.playCheckSound(); return true; // King is in check } } } } return false; // King is not in check } public Piece promotePawn(boolean isWhite, int x, int y) { return new Piece(isWhite, PieceType.Queen, x, y); } private void highlightKingInCheck() { int highlightedCount = 0; // Check if white king is in check Piece whiteKing = findKing(true); if (whiteKing != null && isKingInCheck(true)) { highlightedSquares[whiteKing.getX()][whiteKing.getY()] = true; } //check if white king checkmate // Check if black king is in check Piece blackKing = findKing(false); if (blackKing != null && isKingInCheck(false)) { highlightedSquares[blackKing.getX()][blackKing.getY()] = true; } } // check public boolean canSelectPieceWhenInCheck(int x, int y) { Piece piece = board[x][y]; // If no piece at this position or wrong color, can't select if (piece == null || piece.isWhite() != isTurnWhite()) { return false; } // If king is not in check, any piece can be selected if (!isKingInCheck(isTurnWhite())) { return true; } // When king is in check, check if this piece can make any legal move // that would get the king out of check ArrayList legalMoves = getLegalMoves(piece); for (Move move : legalMoves) { // Temporarily make the move Piece capturedPiece = board[move.getToX()][move.getToY()]; board[move.getToX()][move.getToY()] = piece; board[move.getFromX()][move.getFromY()] = null; // Check if king is still in check after this move boolean stillInCheck = isKingInCheck(isTurnWhite()); // Undo the move board[move.getFromX()][move.getFromY()] = piece; board[move.getToX()][move.getToY()] = capturedPiece; // If this move gets king out of check, piece can be selected if (!stillInCheck) { return true; } } // No legal moves that get king out of check return false; } public boolean isSquareAttacked(int x, int y, boolean byWhite) { for (int i = 0; i < width; i++) { for (int j = 0; j < height; j++) { Piece attacker = board[i][j]; if (attacker != null && attacker.isWhite() == byWhite) { ArrayList moves = getLegalMoves(attacker); for (Move move : moves) { if (move.getToX() == x && move.getToY() == y) { return true; } } } } } return false; } private void filterMovesForCheck() { Piece selectedPiece = board[selectedX][selectedY]; boolean[][] newHighlights = new boolean[width][height]; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { if (highlightedSquares[x][y]) { // Temporarily make the move Piece capturedPiece = board[x][y]; board[x][y] = selectedPiece; board[selectedX][selectedY] = null; // Check if king is in check after the move boolean leavesKingInCheck; // If moving the king, check if the destination square is attacked if (selectedPiece.getType() == PieceType.King) { leavesKingInCheck = isSquareAttacked(x, y, !selectedPiece.isWhite()); System.out.println("Game Over"); // only work if king selected } else { leavesKingInCheck = isKingInCheck(isTurnWhite()); } // Undo the move board[selectedX][selectedY] = selectedPiece; board[x][y] = capturedPiece; // Keep only safe moves if (!leavesKingInCheck) { newHighlights[x][y] = true; } } } } // Replace highlights with filtered version highlightedSquares = newHighlights; } public boolean isCheckmate(boolean isWhite) { if (!isKingInCheck(isWhite)) { return false; // Not in check, so not checkmate } // Check if ANY legal move can get the king out of check for (Piece piece : getPieces()) { if (piece.isWhite() == isWhite) { ArrayList legalMoves = getLegalMoves(piece); for (Move move : legalMoves) { // Simulate move Board simulatedBoard = new Board(this.toFileRep()); simulatedBoard.playMove(move); // If king is not in check after this move, it's not checkmate if (!simulatedBoard.isKingInCheck(isWhite)) { return false; } } } } return true; // No legal move prevents check => checkmate } }