diff --git a/OOP_3B6_Project/src/backend/Board.java b/OOP_3B6_Project/src/backend/Board.java index 51f5839..943eb58 100644 --- a/OOP_3B6_Project/src/backend/Board.java +++ b/OOP_3B6_Project/src/backend/Board.java @@ -1,4 +1,3 @@ - package backend; import java.util.ArrayList; @@ -13,10 +12,23 @@ public class Board { private Integer selectedX, selectedY; private ArrayList highlightedSquares = new ArrayList<>(); private Stack moveHistory = new Stack<>(); + + // Game message attribute + private String gameMessage = ""; + + // Advanced movement attributes + private int enPassantColumn = -1; + private int enPassantRow = -1; + private boolean whiteKingMoved = false; + private boolean blackKingMoved = false; + private boolean whiteRookKingSideMoved = false; + private boolean whiteRookQueenSideMoved = false; + private boolean blackRookKingSideMoved = false; + private boolean blackRookQueenSideMoved = false; - public Board(int colNum, int lineNum) { - this.width = colNum; - this.height = lineNum; + public Board(int width, int height) { + this.width = width; + this.height = height; this.turnNumber = 0; this.isWhiteTurn = true; this.pieces = new ArrayList<>(); @@ -24,6 +36,46 @@ public class Board { this.selectedY = null; } + public Board(Board board) { + this.width = board.width; + this.height = board.height; + this.turnNumber = board.turnNumber; + this.isWhiteTurn = board.isWhiteTurn; + this.selectedX = board.selectedX; + this.selectedY = board.selectedY; + this.gameMessage = board.gameMessage; + + // Deep copy of pieces + this.pieces = new ArrayList<>(); + for (Piece p : board.pieces) { + this.pieces.add(new Piece(p.getX(), p.getY(), p.isWhite(), p.getType())); + } + + // Copy highlighted squares + this.highlightedSquares = new ArrayList<>(); + for (int[] square : board.highlightedSquares) { + this.highlightedSquares.add(new int[]{square[0], square[1]}); + } + + // Copy castling and en-passant state + this.whiteKingMoved = board.whiteKingMoved; + this.blackKingMoved = board.blackKingMoved; + this.whiteRookKingSideMoved = board.whiteRookKingSideMoved; + this.whiteRookQueenSideMoved = board.whiteRookQueenSideMoved; + this.blackRookKingSideMoved = board.blackRookKingSideMoved; + this.blackRookQueenSideMoved = board.blackRookQueenSideMoved; + this.enPassantColumn = board.enPassantColumn; + this.enPassantRow = board.enPassantRow; + + // We don't copy the move history as it's not needed for evaluation + this.moveHistory = new Stack<>(); + } + + public Board(String[] array) { + // TODO: Part 3 - Load + this(8, 8); // Default initialization + } + public int getWidth() { return width; } @@ -39,6 +91,11 @@ public class Board { public boolean isTurnWhite() { return isWhiteTurn; } + + // Getter for game message + public String getGameMessage() { + return gameMessage; + } public void setPiece(boolean isWhite, PieceType type, int x, int y) { removePieceAt(x, y); @@ -60,6 +117,17 @@ public class Board { pieces.add(new Piece(x, 0, false, backRow[x])); // Black back row pieces.add(new Piece(x, 7, true, backRow[x])); // White back row } + + // Reset movement tracking + whiteKingMoved = false; + blackKingMoved = false; + whiteRookKingSideMoved = false; + whiteRookQueenSideMoved = false; + blackRookKingSideMoved = false; + blackRookQueenSideMoved = false; + enPassantColumn = -1; + enPassantRow = -1; + gameMessage = ""; } public void cleanBoard() { @@ -70,8 +138,19 @@ public class Board { selectedY = null; highlightedSquares.clear(); moveHistory.clear(); + + // Reset movement tracking + whiteKingMoved = false; + blackKingMoved = false; + whiteRookKingSideMoved = false; + whiteRookQueenSideMoved = false; + blackRookKingSideMoved = false; + blackRookQueenSideMoved = false; + enPassantColumn = -1; + enPassantRow = -1; + gameMessage = ""; } - //create the board + public String toString() { StringBuilder sb = new StringBuilder(); for (int y = 0; y < height; y++) { @@ -120,6 +199,16 @@ public class Board { if (move[0] == x && move[1] == y) { Piece selectedPiece = getPieceAt(selectedX, selectedY); Piece captured = getPieceAt(x, y); + + // Check for en-passant capture + if (selectedPiece.getType() == PieceType.Pawn && + x == enPassantColumn && + ((selectedPiece.isWhite() && y == enPassantRow - 1) || + (!selectedPiece.isWhite() && y == enPassantRow + 1))) { + // Remove the pawn that was captured en-passant + captured = getPieceAt(enPassantColumn, enPassantRow); + removePieceAt(enPassantColumn, enPassantRow); + } // Store move for undo Move m = new Move( @@ -128,12 +217,76 @@ public class Board { ); moveHistory.push(m); - removePieceAt(x, y); // capture + // Track king and rook movements for castling + if (selectedPiece.getType() == PieceType.King) { + if (selectedPiece.isWhite()) { + whiteKingMoved = true; + + // Check for castling + if (selectedX == 4 && selectedY == 7) { + if (x == 6 && y == 7) { // King-side castling + // Move the rook too + removePieceAt(7, 7); + pieces.add(new Piece(5, 7, true, PieceType.Rook)); + } else if (x == 2 && y == 7) { // Queen-side castling + // Move the rook too + removePieceAt(0, 7); + pieces.add(new Piece(3, 7, true, PieceType.Rook)); + } + } + } else { + blackKingMoved = true; + + // Check for castling + if (selectedX == 4 && selectedY == 0) { + if (x == 6 && y == 0) { // King-side castling + // Move the rook too + removePieceAt(7, 0); + pieces.add(new Piece(5, 0, false, PieceType.Rook)); + } else if (x == 2 && y == 0) { // Queen-side castling + // Move the rook too + removePieceAt(0, 0); + pieces.add(new Piece(3, 0, false, PieceType.Rook)); + } + } + } + } else if (selectedPiece.getType() == PieceType.Rook) { + if (selectedPiece.isWhite()) { + if (selectedX == 0 && selectedY == 7) { + whiteRookQueenSideMoved = true; + } else if (selectedX == 7 && selectedY == 7) { + whiteRookKingSideMoved = true; + } + } else { + if (selectedX == 0 && selectedY == 0) { + blackRookQueenSideMoved = true; + } else if (selectedX == 7 && selectedY == 0) { + blackRookKingSideMoved = true; + } + } + } + + // Reset en-passant + enPassantColumn = -1; + enPassantRow = -1; + + // Check if this is a double pawn move for en-passant + if (selectedPiece.getType() == PieceType.Pawn && + Math.abs(y - selectedY) == 2) { + enPassantColumn = x; + enPassantRow = y; + } + + // Move the piece + removePieceAt(x, y); // capture if any removePieceAt(selectedX, selectedY); pieces.add(new Piece(x, y, selectedPiece.isWhite(), selectedPiece.getType())); turnNumber++; isWhiteTurn = !isWhiteTurn; + + // Check for check or checkmate after move + updateCheckStatus(); break; } } @@ -152,10 +305,6 @@ public class Board { return null; } - public Board(String[] array) { - // TODO: Part 3 - Load - } - public boolean isHighlighted(int x, int y) { for (int[] move : highlightedSquares) { if (move[0] == x && move[1] == y) return true; @@ -185,32 +334,65 @@ public class Board { pieces.add(new Piece(p.getX(), p.getY(), p.isWhite(), p.getType())); } + // Undo castling if it was a king move + if (lastMove.getType() == PieceType.King) { + if (lastMove.isWhite()) { + // Undo white king-side castling + if (lastMove.getFromX() == 4 && lastMove.getFromY() == 7 && + lastMove.getToX() == 6 && lastMove.getToY() == 7) { + removePieceAt(5, 7); // Remove rook from new position + pieces.add(new Piece(7, 7, true, PieceType.Rook)); // Restore rook + } + // Undo white queen-side castling + else if (lastMove.getFromX() == 4 && lastMove.getFromY() == 7 && + lastMove.getToX() == 2 && lastMove.getToY() == 7) { + removePieceAt(3, 7); // Remove rook from new position + pieces.add(new Piece(0, 7, true, PieceType.Rook)); // Restore rook + } + whiteKingMoved = false; + } else { + // Undo black king-side castling + if (lastMove.getFromX() == 4 && lastMove.getFromY() == 0 && + lastMove.getToX() == 6 && lastMove.getToY() == 0) { + removePieceAt(5, 0); // Remove rook from new position + pieces.add(new Piece(7, 0, false, PieceType.Rook)); // Restore rook + } + // Undo black queen-side castling + else if (lastMove.getFromX() == 4 && lastMove.getFromY() == 0 && + lastMove.getToX() == 2 && lastMove.getToY() == 0) { + removePieceAt(3, 0); // Remove rook from new position + pieces.add(new Piece(0, 0, false, PieceType.Rook)); // Restore rook + } + blackKingMoved = false; + } + } + + // Undo rook movement tracking + if (lastMove.getType() == PieceType.Rook) { + if (lastMove.isWhite()) { + if (lastMove.getFromX() == 0 && lastMove.getFromY() == 7) { + whiteRookQueenSideMoved = false; + } else if (lastMove.getFromX() == 7 && lastMove.getFromY() == 7) { + whiteRookKingSideMoved = false; + } + } else { + if (lastMove.getFromX() == 0 && lastMove.getFromY() == 0) { + blackRookQueenSideMoved = false; + } else if (lastMove.getFromX() == 7 && lastMove.getFromY() == 0) { + blackRookKingSideMoved = false; + } + } + } + + // Reset en-passant for simplicity (could be more accurate with more state tracking) + enPassantColumn = -1; + enPassantRow = -1; + turnNumber--; isWhiteTurn = !isWhiteTurn; - } - - public Board(Board board) { - this.width = board.width; - this.height = board.height; - this.turnNumber = board.turnNumber; - this.isWhiteTurn = board.isWhiteTurn; - this.selectedX = board.selectedX; - this.selectedY = board.selectedY; - // Deep copy of pieces - this.pieces = new ArrayList<>(); - for (Piece p : board.pieces) { - this.pieces.add(new Piece(p.getX(), p.getY(), p.isWhite(), p.getType())); - } - - // Copy highlighted squares - this.highlightedSquares = new ArrayList<>(); - for (int[] square : board.highlightedSquares) { - this.highlightedSquares.add(new int[]{square[0], square[1]}); - } - - // We don't copy the move history as it's not needed for evaluation - this.moveHistory = new Stack<>(); + // Update check status after undoing move + updateCheckStatus(); } public void playMove(Move move) { @@ -219,11 +401,80 @@ public class Board { // Store move for undo moveHistory.push(move); - // Remove captured piece if any - if (move.getCapturedPiece() != null) { - removePieceAt(move.getToX(), move.getToY()); + Piece movingPiece = getPieceAt(move.getFromX(), move.getFromY()); + + // Check for en-passant capture + if (movingPiece != null && movingPiece.getType() == PieceType.Pawn && + move.getToX() == enPassantColumn && + ((movingPiece.isWhite() && move.getToY() == enPassantRow - 1) || + (!movingPiece.isWhite() && move.getToY() == enPassantRow + 1))) { + // Remove the pawn that was captured en-passant + removePieceAt(enPassantColumn, enPassantRow); } + // Track king and rook movements for castling + if (movingPiece != null && movingPiece.getType() == PieceType.King) { + if (movingPiece.isWhite()) { + whiteKingMoved = true; + + // Check for castling + if (move.getFromX() == 4 && move.getFromY() == 7) { + if (move.getToX() == 6 && move.getToY() == 7) { // King-side castling + // Move the rook too + removePieceAt(7, 7); + pieces.add(new Piece(5, 7, true, PieceType.Rook)); + } else if (move.getToX() == 2 && move.getToY() == 7) { // Queen-side castling + // Move the rook too + removePieceAt(0, 7); + pieces.add(new Piece(3, 7, true, PieceType.Rook)); + } + } + } else { + blackKingMoved = true; + + // Check for castling + if (move.getFromX() == 4 && move.getFromY() == 0) { + if (move.getToX() == 6 && move.getToY() == 0) { // King-side castling + // Move the rook too + removePieceAt(7, 0); + pieces.add(new Piece(5, 0, false, PieceType.Rook)); + } else if (move.getToX() == 2 && move.getToY() == 0) { // Queen-side castling + // Move the rook too + removePieceAt(0, 0); + pieces.add(new Piece(3, 0, false, PieceType.Rook)); + } + } + } + } else if (movingPiece != null && movingPiece.getType() == PieceType.Rook) { + if (movingPiece.isWhite()) { + if (move.getFromX() == 0 && move.getFromY() == 7) { + whiteRookQueenSideMoved = true; + } else if (move.getFromX() == 7 && move.getFromY() == 7) { + whiteRookKingSideMoved = true; + } + } else { + if (move.getFromX() == 0 && move.getFromY() == 0) { + blackRookQueenSideMoved = true; + } else if (move.getFromX() == 7 && move.getFromY() == 0) { + blackRookKingSideMoved = true; + } + } + } + + // Reset en-passant + enPassantColumn = -1; + enPassantRow = -1; + + // Check if this is a double pawn move for en-passant + if (movingPiece != null && movingPiece.getType() == PieceType.Pawn && + Math.abs(move.getToY() - move.getFromY()) == 2) { + enPassantColumn = move.getToX(); + enPassantRow = move.getToY(); + } + + // Remove captured piece if any + removePieceAt(move.getToX(), move.getToY()); + // Remove piece from original position removePieceAt(move.getFromX(), move.getFromY()); @@ -233,15 +484,54 @@ public class Board { // Update turn turnNumber++; isWhiteTurn = !isWhiteTurn; + + // Check for check or checkmate after move + updateCheckStatus(); + } + // Method to check if the current player is in check or checkmate + private void updateCheckStatus() { + gameMessage = ""; // Reset message + + // Find the king of the current player + Piece king = findKing(isWhiteTurn); + if (king == null) return; // Should never happen in a valid game + + // Check if the king is under attack + boolean inCheck = isSquareAttacked(king.getX(), king.getY(), !isWhiteTurn); + + if (inCheck) { + // Check if there are any legal moves for the current player + boolean hasLegalMoves = false; + + // Check king's moves + ArrayList kingMoves = computeLegalMoves(king); + if (!kingMoves.isEmpty()) { + hasLegalMoves = true; + } + + // Check other pieces' moves + if (!hasLegalMoves) { + for (Piece piece : pieces) { + if (piece.isWhite() == isWhiteTurn && piece.getType() != PieceType.King) { + ArrayList moves = computeLegalMoves(piece); + if (!moves.isEmpty()) { + hasLegalMoves = true; + break; + } + } + } + } + + if (hasLegalMoves) { + gameMessage = "Check!"; + } else { + gameMessage = "Checkmate!"; + } + } } - // ========== Helper Methods ========== - - private void removePieceAt(int x, int y) { - pieces.removeIf(p -> p.getX() == x && p.getY() == y); - } - + // Helper methods private Piece getPieceAt(int x, int y) { for (Piece p : pieces) { if (p.getX() == x && p.getY() == y) return p; @@ -249,106 +539,312 @@ public class Board { return null; } + private void removePieceAt(int x, int y) { + pieces.removeIf(p -> p.getX() == x && p.getY() == y); + } + private ArrayList computeLegalMoves(Piece piece) { - ArrayList moves = new ArrayList<>(); - int x = piece.getX(); - int y = piece.getY(); - boolean isWhite = piece.isWhite(); + ArrayList candidateMoves = new ArrayList<>(); + int x = piece.getX(); + int y = piece.getY(); + boolean isWhite = piece.isWhite(); - switch (piece.getType()) { - case Pawn: - int dir = isWhite ? -1 : 1; - int startRow = isWhite ? 6 : 1; + switch (piece.getType()) { + case Pawn: + int dir = isWhite ? -1 : 1; + int startRow = isWhite ? 6 : 1; - if (getPieceAt(x, y + dir) == null) { - moves.add(new int[]{x, y + dir}); - if (y == startRow && getPieceAt(x, y + 2 * dir) == null) { - moves.add(new int[]{x, y + 2 * dir}); - } - } - for (int dx = -1; dx <= 1; dx += 2) { - int nx = x + dx; - int ny = y + dir; - if (nx >= 0 && nx < width && ny >= 0 && ny < height) { - Piece target = getPieceAt(nx, ny); - if (target != null && target.isWhite() != isWhite) { - moves.add(new int[]{nx, ny}); + // Forward move + if (getPieceAt(x, y + dir) == null) { + candidateMoves.add(new int[]{x, y + dir}); + // Double move from starting position + if (y == startRow && getPieceAt(x, y + 2 * dir) == null) { + candidateMoves.add(new int[]{x, y + 2 * dir}); } } - } - break; - - case Rook: - addSlidingMoves(moves, x, y, isWhite, new int[][]{{1,0},{-1,0},{0,1},{0,-1}}); - break; - - case Bishop: - addSlidingMoves(moves, x, y, isWhite, new int[][]{{1,1},{1,-1},{-1,1},{-1,-1}}); - break; - - case Queen: - addSlidingMoves(moves, x, y, isWhite, new int[][]{ - {1,0},{-1,0},{0,1},{0,-1},{1,1},{1,-1},{-1,1},{-1,-1} - }); - break; - - case Knight: - int[][] knightMoves = { - {x+1, y+2}, {x+1, y-2}, {x-1, y+2}, {x-1, y-2}, - {x+2, y+1}, {x+2, y-1}, {x-2, y+1}, {x-2, y-1} - }; - for (int[] move : knightMoves) { - int nx = move[0]; - int ny = move[1]; - if (nx >= 0 && nx < width && ny >= 0 && ny < height) { - Piece target = getPieceAt(nx, ny); - if (target == null || target.isWhite() != isWhite) { - moves.add(new int[]{nx, ny}); - } - } - } - break; - - case King: - for (int dx = -1; dx <= 1; dx++) { - for (int dy = -1; dy <= 1; dy++) { - if (dx == 0 && dy == 0) continue; + + // Regular captures + for (int dx = -1; dx <= 1; dx += 2) { int nx = x + dx; - int ny = y + dy; + int ny = y + dir; + if (nx >= 0 && nx < width && ny >= 0 && ny < height) { + Piece target = getPieceAt(nx, ny); + if (target != null && target.isWhite() != isWhite) { + candidateMoves.add(new int[]{nx, ny}); + } + } + } + + // En-passant capture + if (enPassantColumn != -1 && + Math.abs(x - enPassantColumn) == 1 && + y == enPassantRow) { + candidateMoves.add(new int[]{enPassantColumn, enPassantRow + (isWhite ? -1 : 1)}); + } + break; + + case Rook: + addSlidingMoves(candidateMoves, x, y, isWhite, new int[][]{{1,0},{-1,0},{0,1},{0,-1}}, false); + break; + + case Bishop: + addSlidingMoves(candidateMoves, x, y, isWhite, new int[][]{{1,1},{1,-1},{-1,1},{-1,-1}}, false); + break; + + case Queen: + addSlidingMoves(candidateMoves, x, y, isWhite, new int[][]{ + {1,0},{-1,0},{0,1},{0,-1},{1,1},{1,-1},{-1,1},{-1,-1} + }, false); + break; + + case Knight: + int[][] knightMoves = { + {1,2}, {1,-2}, {-1,2}, {-1,-2}, + {2,1}, {2,-1}, {-2,1}, {-2,-1} + }; + for (int[] move : knightMoves) { + int nx = x + move[0]; + int ny = y + move[1]; if (nx >= 0 && nx < width && ny >= 0 && ny < height) { Piece target = getPieceAt(nx, ny); if (target == null || target.isWhite() != isWhite) { + candidateMoves.add(new int[]{nx, ny}); + } + } + } + break; + + case King: + // Normal king moves + 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 (nx >= 0 && nx < width && ny >= 0 && ny < height) { + Piece target = getPieceAt(nx, ny); + if (target == null || target.isWhite() != isWhite) { + candidateMoves.add(new int[]{nx, ny}); + } + } + } + } + + // Castling + if (isWhite && !whiteKingMoved && x == 4 && y == 7) { + // King-side castling + if (!whiteRookKingSideMoved && + getPieceAt(5, 7) == null && + getPieceAt(6, 7) == null && + getPieceAt(7, 7) != null && + getPieceAt(7, 7).getType() == PieceType.Rook) { + + // Check if king would pass through check + if (!isSquareAttacked(4, 7, false) && + !isSquareAttacked(5, 7, false) && + !isSquareAttacked(6, 7, false)) { + candidateMoves.add(new int[]{6, 7}); + } + } + + // Queen-side castling + if (!whiteRookQueenSideMoved && + getPieceAt(1, 7) == null && + getPieceAt(2, 7) == null && + getPieceAt(3, 7) == null && + getPieceAt(0, 7) != null && + getPieceAt(0, 7).getType() == PieceType.Rook) { + + // Check if king would pass through check + if (!isSquareAttacked(4, 7, false) && + !isSquareAttacked(3, 7, false) && + !isSquareAttacked(2, 7, false)) { + candidateMoves.add(new int[]{2, 7}); + } + } + } else if (!isWhite && !blackKingMoved && x == 4 && y == 0) { + // King-side castling + if (!blackRookKingSideMoved && + getPieceAt(5, 0) == null && + getPieceAt(6, 0) == null && + getPieceAt(7, 0) != null && + getPieceAt(7, 0).getType() == PieceType.Rook) { + + // Check if king would pass through check + if (!isSquareAttacked(4, 0, true) && + !isSquareAttacked(5, 0, true) && + !isSquareAttacked(6, 0, true)) { + candidateMoves.add(new int[]{6, 0}); + } + } + + // Queen-side castling + if (!blackRookQueenSideMoved && + getPieceAt(1, 0) == null && + getPieceAt(2, 0) == null && + getPieceAt(3, 0) == null && + getPieceAt(0, 0) != null && + getPieceAt(0, 0).getType() == PieceType.Rook) { + + // Check if king would pass through check + if (!isSquareAttacked(4, 0, true) && + !isSquareAttacked(3, 0, true) && + !isSquareAttacked(2, 0, true)) { + candidateMoves.add(new int[]{2, 0}); + } + } + } + break; + } + + // Filter out moves that would leave the king in check + ArrayList legalMoves = new ArrayList<>(); + for (int[] move : candidateMoves) { + if (!moveWouldLeaveInCheck(piece, move[0], move[1])) { + legalMoves.add(move); + } + } + + return legalMoves; + } + + private void addSlidingMoves(ArrayList moves, int x, int y, boolean isWhite, int[][] directions, boolean forAttack) { + for (int[] dir : directions) { + for (int i = 1; i < 8; i++) { + int nx = x + dir[0] * i; + int ny = y + dir[1] * i; + if (nx < 0 || nx >= width || ny < 0 || ny >= height) break; + + Piece target = getPieceAt(nx, ny); + if (target == null) { + moves.add(new int[]{nx, ny}); + } else { + if (target.isWhite() != isWhite) { + moves.add(new int[]{nx, ny}); + } + break; + } + } + } + } + + private boolean isSquareAttacked(int x, int y, boolean byWhite) { + for (Piece piece : pieces) { + if (piece.isWhite() == byWhite) { + ArrayList attackMoves = getAttackMoves(piece); + for (int[] move : attackMoves) { + if (move[0] == x && move[1] == y) { + return true; + } + } + } + } + return false; + } + + private ArrayList getAttackMoves(Piece piece) { + ArrayList moves = new ArrayList<>(); + int x = piece.getX(); + int y = piece.getY(); + boolean isWhite = piece.isWhite(); + + switch (piece.getType()) { + case Pawn: + int dir = isWhite ? -1 : 1; + // Pawns attack diagonally + for (int dx = -1; dx <= 1; dx += 2) { + int nx = x + dx; + int ny = y + dir; + if (nx >= 0 && nx < width && ny >= 0 && ny < height) { + moves.add(new int[]{nx, ny}); + } + } + break; + + // Other pieces attack the same way they move + case Rook: + addSlidingMoves(moves, x, y, isWhite, new int[][]{{1,0},{-1,0},{0,1},{0,-1}}, true); + break; + + case Bishop: + addSlidingMoves(moves, x, y, isWhite, new int[][]{{1,1},{1,-1},{-1,1},{-1,-1}}, true); + break; + + case Queen: + addSlidingMoves(moves, x, y, isWhite, new int[][]{ + {1,0},{-1,0},{0,1},{0,-1},{1,1},{1,-1},{-1,1},{-1,-1} + }, true); + break; + + case Knight: + int[][] knightMoves = { + {1,2}, {1,-2}, {-1,2}, {-1,-2}, + {2,1}, {2,-1}, {-2,1}, {-2,-1} + }; + for (int[] move : knightMoves) { + int nx = x + move[0]; + int ny = y + move[1]; + if (nx >= 0 && nx < width && ny >= 0 && ny < height) { + moves.add(new int[]{nx, ny}); + } + } + break; + + case King: + 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 (nx >= 0 && nx < width && ny >= 0 && ny < height) { moves.add(new int[]{nx, ny}); } } } - } - break; - - default: - break; - } - return moves; - } - - private void addSlidingMoves(ArrayList moves, int x, int y, boolean isWhite, int[][] directions) { - for (int[] dir : directions) { - for (int i = 1; i < 8; i++) { - int nx = x + dir[0] * i; - int ny = y + dir[1] * i; - if (nx < 0 || nx >= width || ny < 0 || ny >= height) break; - Piece target = getPieceAt(nx, ny); - if (target == null) { - moves.add(new int[]{nx, ny}); - } else { - if (target.isWhite() != isWhite) { - moves.add(new int[]{nx, ny}); - } break; + } + + return moves; + } + + private Piece findKing(boolean isWhite) { + for (Piece piece : pieces) { + if (piece.getType() == PieceType.King && piece.isWhite() == isWhite) { + return piece; } } + return null; // Should never happen in a valid chess game } - } - -} + private boolean moveWouldLeaveInCheck(Piece piece, int toX, int toY) { + boolean isWhite = piece.isWhite(); + int fromX = piece.getX(); + int fromY = piece.getY(); + + // Create a temporary board to test the move + Board tempBoard = new Board(width, height); + + // Copy all pieces except the one being moved + for (Piece p : pieces) { + if (p.getX() == fromX && p.getY() == fromY) continue; + if (p.getX() == toX && p.getY() == toY) continue; // Skip captured piece + tempBoard.pieces.add(new Piece(p.getX(), p.getY(), p.isWhite(), p.getType())); + } + + // Add the moved piece + tempBoard.pieces.add(new Piece(toX, toY, isWhite, piece.getType())); + + // Find the king on the temporary board + Piece king; + if (piece.getType() == PieceType.King) { + king = tempBoard.getPieceAt(toX, toY); + } else { + king = tempBoard.findKing(isWhite); + } + + if (king == null) return true; // Shouldn't happen, but prevent the move if it does + + // Check if the king is in check + return tempBoard.isSquareAttacked(king.getX(), king.getY(), !isWhite); + } +} \ No newline at end of file diff --git a/OOP_3B6_Project/src/backend/Game.java b/OOP_3B6_Project/src/backend/Game.java index bb253c0..2cbc6e3 100644 --- a/OOP_3B6_Project/src/backend/Game.java +++ b/OOP_3B6_Project/src/backend/Game.java @@ -4,106 +4,125 @@ import windowInterface.MyInterface; public class Game extends Thread { - private AutoPlayer aiPlayer; - private Board board; + private AutoPlayer aiPlayer; + private Board board; - private MyInterface mjf; //Reference to GUI interface - private int COL_NUM = 8; // number of columns of a chess board - private int LINE_NUM = 8; // number of rows of a chess board - private int loopDelay = 250; // Delay in milisec between turns (AI speed) - boolean[] activationAIFlags; //Flags to enable or disable AI for white or black + private MyInterface mjf; //Reference to GUI interface + private int COL_NUM = 8; // number of columns of a chess board + private int LINE_NUM = 8; // number of rows of a chess board + private int loopDelay = 250; // Delay in milisec between turns (AI speed) + boolean[] activationAIFlags; //Flags to enable or disable AI for white or black - //Constructor to initialize the game with a GUI interface - public Game(MyInterface mjfParam) { - mjf = mjfParam; - board = new Board(COL_NUM, LINE_NUM); // create a new chess board - loopDelay = 250; - activationAIFlags = new boolean[2]; // initialize AI flags: false by default - aiPlayer = new AutoPlayer(); // create the AI player - } -// getter for the board width - public int getWidth() { - return board.getWidth(); - } -//getter for the board height - public int getHeight() { - return board.getHeight(); - } -// Main loop for the game thread (run continuously) - public void run() { - while(true) { - aiPlayerTurn(); - mjf.update(board.getTurnNumber(), board.isTurnWhite()); - try { - Thread.sleep(loopDelay); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - } - - private boolean isAITurn() { - return activationAIFlags[board.isTurnWhite()?1:0]; - } + //Constructor to initialize the game with a GUI interface + public Game(MyInterface mjfParam) { + mjf = mjfParam; + board = new Board(COL_NUM, LINE_NUM); // create a new chess board + loopDelay = 250; + activationAIFlags = new boolean[2]; // initialize AI flags: false by default + aiPlayer = new AutoPlayer(); // create the AI player + } + + // getter for the board width + public int getWidth() { + return board.getWidth(); + } + + //getter for the board height + public int getHeight() { + return board.getHeight(); + } + + // Main loop for the game thread (run continuously) + public void run() { + while(true) { + aiPlayerTurn(); + // Pass the game message to the interface + mjf.update(board.getTurnNumber(), board.isTurnWhite(), board.getGameMessage()); + try { + Thread.sleep(loopDelay); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + + // Get the game message (check/checkmate) from the board + public String getGameMessage() { + return board.getGameMessage(); + } + + private boolean isAITurn() { + return activationAIFlags[board.isTurnWhite()?1:0]; + } - private void aiPlayerTurn() { - if(isAITurn()) { - board.playMove(aiPlayer.computeBestMove(new Board(board))); - } - } + private void aiPlayerTurn() { + if(isAITurn()) { + board.playMove(aiPlayer.computeBestMove(new Board(board))); + } + } - public void clickCoords(int x, int y) { - int width = this.getWidth(); - int height = this.getHeight(); - if(0>x || 0>y || x>width || y>height) { - System.out.println("Click out of bounds"); - return; - } - if(!isAITurn()) { - board.userTouch(x, y); - } - - } + public void clickCoords(int x, int y) { + int width = this.getWidth(); + int height = this.getHeight(); + if(0>x || 0>y || x>width || y>height) { + System.out.println("Click out of bounds"); + return; + } + if(!isAITurn()) { + board.userTouch(x, y); + // After a move, update the interface with any game message + mjf.update(board.getTurnNumber(), board.isTurnWhite(), board.getGameMessage()); + } + } - public void setPiece(boolean isWhite, PieceType type, int x, int y) { - board.setPiece(isWhite, type, x, y); - } + public void setPiece(boolean isWhite, PieceType type, int x, int y) { + board.setPiece(isWhite, type, x, y); + } - public String[] getFileRepresentation() { - return board.toFileRep(); - } + public String[] getFileRepresentation() { + return board.toFileRep(); + } - public void setLoopDelay(int delay) { - this.loopDelay = delay; - } + public void setLoopDelay(int delay) { + this.loopDelay = delay; + } - public void setDefaultSetup() { - board.cleanBoard(); - board.populateBoard(); - } + public void setDefaultSetup() { + board.cleanBoard(); + board.populateBoard(); + // Reset any game messages when starting a new game + mjf.update(board.getTurnNumber(), board.isTurnWhite(), ""); + } - public void setBoard(String[] array) { - board = new Board(array); - } + public void setBoard(String[] array) { + board = new Board(array); + // Check for check/checkmate when loading a board + mjf.update(board.getTurnNumber(), board.isTurnWhite(), board.getGameMessage()); + } - public Iterable getPieces() { - return board.getPieces(); - } + public Iterable getPieces() { + return board.getPieces(); + } - public boolean isSelected(int x, int y) { - return board.isSelected(x, y); - } + public boolean isSelected(int x, int y) { + return board.isSelected(x, y); + } - public boolean isHighlighted(int x, int y) { - return board.isHighlighted(x, y); - } + public boolean isHighlighted(int x, int y) { + return board.isHighlighted(x, y); + } - public void undoLastMove() { - board.undoLastMove(); - } - - public void toggleAI(boolean isWhite) { - this.activationAIFlags[isWhite?1:0] = !this.activationAIFlags[isWhite?1:0]; - } + public void undoLastMove() { + if(board == null) { + System.out.println("error: can't undo while no game present"); + } else { + board.undoLastMove(); + // After undoing a move, update the interface with any game message + mjf.update(board.getTurnNumber(), board.isTurnWhite(), board.getGameMessage()); + } + } + public void toggleAI(boolean isWhite) { + this.activationAIFlags[isWhite?1:0] = !this.activationAIFlags[isWhite?1:0]; + } } diff --git a/OOP_3B6_Project/src/windowInterface/MyInterface.java b/OOP_3B6_Project/src/windowInterface/MyInterface.java index b7e2549..8fb00df 100644 --- a/OOP_3B6_Project/src/windowInterface/MyInterface.java +++ b/OOP_3B6_Project/src/windowInterface/MyInterface.java @@ -32,238 +32,260 @@ import javax.swing.JCheckBox; public class MyInterface extends JFrame { - private static final long serialVersionUID = -6840815447618468846L; - private JPanel contentPane; - private JLabel turnLabel; - private JLabel borderLabel; - private JLabel speedLabel; - private JPanelChessBoard panelDraw; - private Game game; - private JLabel actionLabel; - private JCheckBox chckbxBlackAI; - private JCheckBox chckbxWhiteAI; + private static final long serialVersionUID = -6840815447618468846L; + private JPanel contentPane; + private JLabel turnLabel; + private JLabel borderLabel; + private JLabel speedLabel; + private JPanelChessBoard panelDraw; + private Game game; + private JLabel actionLabel; + private JCheckBox chckbxBlackAI; + private JCheckBox chckbxWhiteAI; - /** - * Create the frame. - */ - public MyInterface() { - setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - setBounds(10, 10, 650, 650); - contentPane = new JPanel(); - contentPane.setBorder(new EmptyBorder(5, 5, 5, 5)); - contentPane.setLayout(new BorderLayout(0, 0)); - setContentPane(contentPane); + /** + * Create the frame. + */ + public MyInterface() { + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + setBounds(10, 10, 650, 650); + contentPane = new JPanel(); + contentPane.setBorder(new EmptyBorder(5, 5, 5, 5)); + contentPane.setLayout(new BorderLayout(0, 0)); + setContentPane(contentPane); - JPanel panelTop = new JPanel(); - contentPane.add(panelTop, BorderLayout.NORTH); - JPanel panelRight = new JPanel(); - contentPane.add(panelRight, BorderLayout.EAST); - panelRight.setLayout(new GridLayout(4,1)); - + JPanel panelTop = new JPanel(); + contentPane.add(panelTop, BorderLayout.NORTH); + JPanel panelRight = new JPanel(); + contentPane.add(panelRight, BorderLayout.EAST); + panelRight.setLayout(new GridLayout(4,1)); + - actionLabel = new JLabel("Waiting For Start"); - panelTop.add(actionLabel); + actionLabel = new JLabel("Waiting For Start"); + panelTop.add(actionLabel); - JButton btnGo = new JButton("Start/Restart"); - btnGo.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent arg0) { - clicButtonStart(); - } - }); - panelTop.add(btnGo); + JButton btnGo = new JButton("Start/Restart"); + btnGo.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent arg0) { + clicButtonStart(); + } + }); + panelTop.add(btnGo); - turnLabel = new JLabel("Turn : X"); - panelTop.add(turnLabel); - - JButton btnLoad = new JButton("Load File"); - btnLoad.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent arg0) { - clicLoadFileButton(); - } - }); - panelRight.add(btnLoad); + turnLabel = new JLabel("Turn : X"); + panelTop.add(turnLabel); + + JButton btnLoad = new JButton("Load File"); + btnLoad.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent arg0) { + clicLoadFileButton(); + } + }); + panelRight.add(btnLoad); - JButton btnSave = new JButton("Save To File"); - btnSave.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent arg0) { - clicSaveToFileButton(); - } - }); - panelRight.add(btnSave); + JButton btnSave = new JButton("Save To File"); + btnSave.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent arg0) { + clicSaveToFileButton(); + } + }); + panelRight.add(btnSave); - JButton btnAdder = new JButton("Add Piece"); - btnAdder.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent arg0) { - clickButtonAdder(); - } - }); - panelRight.add(btnAdder); - - JButton btnPieceSelector = new JButton("Piece Select"); - btnPieceSelector.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent arg0) { - clickButtonSelector(); - } - }); - panelRight.add(btnPieceSelector); + JButton btnAdder = new JButton("Add Piece"); + btnAdder.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent arg0) { + clickButtonAdder(); + } + }); + panelRight.add(btnAdder); + + JButton btnPieceSelector = new JButton("Piece Select"); + btnPieceSelector.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent arg0) { + clickButtonSelector(); + } + }); + panelRight.add(btnPieceSelector); - JButton btnUndo = new JButton("Undo"); - btnUndo.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent arg0) { - clicUndoButton(); - } + JButton btnUndo = new JButton("Undo"); + btnUndo.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent arg0) { + clicUndoButton(); + } - }); - panelTop.add(btnUndo); - - chckbxWhiteAI = new JCheckBox("WhiteAI"); - chckbxWhiteAI.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent arg0) { - clicAIToggle(true); - } + }); + panelTop.add(btnUndo); + + chckbxWhiteAI = new JCheckBox("WhiteAI"); + chckbxWhiteAI.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent arg0) { + clicAIToggle(true); + } - }); - panelTop.add(chckbxWhiteAI); - - chckbxBlackAI = new JCheckBox("BlackAI"); - chckbxBlackAI.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent arg0) { - clicAIToggle(false); - } + }); + panelTop.add(chckbxWhiteAI); + + chckbxBlackAI = new JCheckBox("BlackAI"); + chckbxBlackAI.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent arg0) { + clicAIToggle(false); + } - }); - panelTop.add(chckbxBlackAI); + }); + panelTop.add(chckbxBlackAI); - panelDraw = new JPanelChessBoard(this); - contentPane.add(panelDraw, BorderLayout.CENTER); - } + panelDraw = new JPanelChessBoard(this); + contentPane.add(panelDraw, BorderLayout.CENTER); + } - public void setStepBanner(String s) { - turnLabel.setText(s); - } + public void setStepBanner(String s) { + turnLabel.setText(s); + } - public void setBorderBanner(String s) { - borderLabel.setText(s); - } + public void setBorderBanner(String s) { + borderLabel.setText(s); + } - public JPanelChessBoard getPanelDessin() { - return panelDraw; - } - - public void instantiateSimu() { - if(game==null) { - game = new Game(this); - panelDraw.setGame(game); - game.start(); - } - } + public JPanelChessBoard getPanelDessin() { + return panelDraw; + } + + public void instantiateSimu() { + if(game==null) { + game = new Game(this); + panelDraw.setGame(game); + game.start(); + } + } - public void clicButtonStart() { - this.instantiateSimu(); - game.setDefaultSetup(); - } - - public void clickButtonAdder() { - panelDraw.toggleAdderMode(); - } - public void clickButtonSelector() { - panelDraw.togglePieceSelector(); - } + public void clicButtonStart() { + this.instantiateSimu(); + game.setDefaultSetup(); + } + + public void clickButtonAdder() { + panelDraw.toggleAdderMode(); + } + public void clickButtonSelector() { + panelDraw.togglePieceSelector(); + } - private void clicUndoButton() { - if(game == null) { - System.out.println("error : can't undo while no game present"); - } else { - game.undoLastMove(); - } - - } - public void clicAIToggle(boolean isWhite) { - if(game == null) { - System.out.println("error : can't activate AI while no game present"); - if(isWhite) { - chckbxWhiteAI.setSelected(false); - }else { - chckbxBlackAI.setSelected(false); - } - } else { - game.toggleAI(isWhite); - } - } - - public void clicLoadFileButton() { - Game loadedSim = new Game(this); - String fileName=SelectFile(); - LinkedList lines = new LinkedList(); - if (fileName.length()>0) { - try { - BufferedReader fileContent = new BufferedReader(new FileReader(fileName)); - String line = fileContent.readLine(); - int colorID = 0; - while (line != null) { - lines.add(line); - line = fileContent.readLine(); - } - loadedSim.setBoard(Arrays.stream(lines.toArray()).map(Object::toString).toArray(String[]::new)); - fileContent.close(); - } catch (Exception e) { - e.printStackTrace(); - } - game = loadedSim; - panelDraw.setGame(game); - this.repaint(); - } - } - - public void clicSaveToFileButton() { - String fileName=SelectFile(); - if (fileName.length()>0) { - String[] content = game.getFileRepresentation(); - writeFile(fileName, content); - } - } - - public String SelectFile() { - String s; - JFileChooser chooser = new JFileChooser(); - chooser.setCurrentDirectory(new java.io.File(".")); - chooser.setDialogTitle("Choose a file"); - chooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); - chooser.setAcceptAllFileFilterUsed(true); - if (chooser.showOpenDialog(null) == JFileChooser.APPROVE_OPTION) { - s=chooser.getSelectedFile().toString(); - } else { - System.out.println("No Selection "); - s=""; - } - return s; - } - - public void writeFile(String fileName, String[] content) { - FileWriter csvWriter; - try { - csvWriter = new FileWriter(fileName); - for (String row : content) { - csvWriter.append(row); - csvWriter.append("\n"); - } - csvWriter.flush(); - csvWriter.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - - public void update(int turnCount, boolean turnIsWhite) { - turnLabel.setText("Turn : "+turnCount+", "+ (turnIsWhite?"White":"Black")); - actionLabel.setText(panelDraw.isPieceAdderMode()?"Adding Piece": - (panelDraw.isPieceSelectorMode()?"Selecting Piece to Add": - "Playing")); - this.repaint(); - } - - public void eraseLabels() { - this.setStepBanner("Turn : X"); - } + private void clicUndoButton() { + if(game == null) { + System.out.println("error : can't undo while no game present"); + } else { + game.undoLastMove(); + } + } + + public void clicAIToggle(boolean isWhite) { + if(game == null) { + System.out.println("error : can't activate AI while no game present"); + if(isWhite) { + chckbxWhiteAI.setSelected(false); + }else { + chckbxBlackAI.setSelected(false); + } + } else { + game.toggleAI(isWhite); + } + } + + public void clicLoadFileButton() { + Game loadedSim = new Game(this); + String fileName=SelectFile(); + LinkedList lines = new LinkedList(); + if (fileName.length()>0) { + try { + BufferedReader fileContent = new BufferedReader(new FileReader(fileName)); + String line = fileContent.readLine(); + int colorID = 0; + while (line != null) { + lines.add(line); + line = fileContent.readLine(); + } + loadedSim.setBoard(Arrays.stream(lines.toArray()).map(Object::toString).toArray(String[]::new)); + fileContent.close(); + } catch (Exception e) { + e.printStackTrace(); + } + game = loadedSim; + panelDraw.setGame(game); + this.repaint(); + } + } + public void clicSaveToFileButton() { + String fileName=SelectFile(); + if (fileName.length()>0) { + String[] content = game.getFileRepresentation(); + writeFile(fileName, content); + } + } + + public String SelectFile() { + String s; + JFileChooser chooser = new JFileChooser(); + chooser.setCurrentDirectory(new java.io.File(".")); + chooser.setDialogTitle("Choose a file"); + chooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); + chooser.setAcceptAllFileFilterUsed(true); + if (chooser.showOpenDialog(null) == JFileChooser.APPROVE_OPTION) { + s=chooser.getSelectedFile().toString(); + } else { + System.out.println("No Selection "); + s=""; + } + return s; + } + + public void writeFile(String fileName, String[] content) { + FileWriter csvWriter; + try { + csvWriter = new FileWriter(fileName); + for (String row : content) { + csvWriter.append(row); + csvWriter.append("\n"); + } + csvWriter.flush(); + csvWriter.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * Updates the interface with the game's current state and displays any game message + * @param turnCount The current turn number + * @param turnIsWhite True if it's white's turn, false if it's black's + * @param gameMessage Game status message (check/checkmate) + */ + public void update(int turnCount, boolean turnIsWhite, String gameMessage) { + turnLabel.setText("Turn : " + turnCount + ", " + (turnIsWhite ? "White" : "Black")); + + // If we have a game message (check/checkmate), display it + if (gameMessage != null && !gameMessage.isEmpty()) { + actionLabel.setText(gameMessage); + } else { + // Otherwise display the normal interface state + actionLabel.setText(panelDraw.isPieceAdderMode() ? "Adding Piece" : + (panelDraw.isPieceSelectorMode() ? "Selecting Piece to Add" : "Playing")); + } + + this.repaint(); + } + + /** + * Legacy update method for compatibility + * @param turnCount The current turn number + * @param turnIsWhite True if it's white's turn, false if it's black's + */ + public void update(int turnCount, boolean turnIsWhite) { + // Call the new update method with an empty game message + update(turnCount, turnIsWhite, ""); + } + + public void eraseLabels() { + this.setStepBanner("Turn : X"); + } }