check and checkmate

This commit is contained in:
gitea 2025-05-14 15:44:26 +02:00
parent 56806bed50
commit 065b410ef3
3 changed files with 968 additions and 431 deletions

View File

@ -1,4 +1,3 @@
package backend;
import java.util.ArrayList;
@ -14,9 +13,22 @@ public class Board {
private ArrayList<int[]> highlightedSquares = new ArrayList<>();
private Stack<Move> moveHistory = new Stack<>();
public Board(int colNum, int lineNum) {
this.width = colNum;
this.height = lineNum;
// 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 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;
}
@ -40,6 +92,11 @@ public class Board {
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);
pieces.add(new Piece(x, y, isWhite, type));
@ -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++) {
@ -121,6 +200,16 @@ public class Board {
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(
selectedX, selectedY, x, y,
@ -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());
@ -234,14 +485,53 @@ public class Board {
turnNumber++;
isWhiteTurn = !isWhiteTurn;
// Check for check or checkmate after move
updateCheckStatus();
}
// ========== Helper Methods ==========
// Method to check if the current player is in check or checkmate
private void updateCheckStatus() {
gameMessage = ""; // Reset message
private void removePieceAt(int x, int y) {
pieces.removeIf(p -> p.getX() == x && p.getY() == y);
// 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<int[]> 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<int[]> moves = computeLegalMoves(piece);
if (!moves.isEmpty()) {
hasLegalMoves = true;
break;
}
}
}
}
if (hasLegalMoves) {
gameMessage = "Check!";
} else {
gameMessage = "Checkmate!";
}
}
}
// 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<int[]> computeLegalMoves(Piece piece) {
ArrayList<int[]> moves = new ArrayList<>();
int x = piece.getX();
int y = piece.getY();
boolean isWhite = piece.isWhite();
ArrayList<int[]> 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<int[]> legalMoves = new ArrayList<>();
for (int[] move : candidateMoves) {
if (!moveWouldLeaveInCheck(piece, move[0], move[1])) {
legalMoves.add(move);
}
}
return legalMoves;
}
private void addSlidingMoves(ArrayList<int[]> 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<int[]> attackMoves = getAttackMoves(piece);
for (int[] move : attackMoves) {
if (move[0] == x && move[1] == y) {
return true;
}
}
}
}
return false;
}
private ArrayList<int[]> getAttackMoves(Piece piece) {
ArrayList<int[]> 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<int[]> 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);
}
}

View File

@ -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();
}
}
}
//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
}
private boolean isAITurn() {
return activationAIFlags[board.isTurnWhite()?1:0];
}
// getter for the board width
public int getWidth() {
return board.getWidth();
}
private void aiPlayerTurn() {
if(isAITurn()) {
board.playMove(aiPlayer.computeBestMove(new Board(board)));
}
}
//getter for the board height
public int getHeight() {
return board.getHeight();
}
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);
}
// 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();
}
public void setPiece(boolean isWhite, PieceType type, int x, int y) {
board.setPiece(isWhite, type, x, y);
}
private boolean isAITurn() {
return activationAIFlags[board.isTurnWhite()?1:0];
}
public String[] getFileRepresentation() {
return board.toFileRep();
}
private void aiPlayerTurn() {
if(isAITurn()) {
board.playMove(aiPlayer.computeBestMove(new Board(board)));
}
}
public void setLoopDelay(int delay) {
this.loopDelay = delay;
}
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 setDefaultSetup() {
board.cleanBoard();
board.populateBoard();
}
public void setPiece(boolean isWhite, PieceType type, int x, int y) {
board.setPiece(isWhite, type, x, y);
}
public void setBoard(String[] array) {
board = new Board(array);
}
public String[] getFileRepresentation() {
return board.toFileRep();
}
public Iterable<Piece> getPieces() {
return board.getPieces();
}
public void setLoopDelay(int delay) {
this.loopDelay = delay;
}
public boolean isSelected(int x, int y) {
return board.isSelected(x, y);
}
public void setDefaultSetup() {
board.cleanBoard();
board.populateBoard();
// Reset any game messages when starting a new game
mjf.update(board.getTurnNumber(), board.isTurnWhite(), "");
}
public boolean isHighlighted(int x, int y) {
return board.isHighlighted(x, y);
}
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 void undoLastMove() {
board.undoLastMove();
}
public Iterable<Piece> getPieces() {
return board.getPieces();
}
public void toggleAI(boolean isWhite) {
this.activationAIFlags[isWhite?1:0] = !this.activationAIFlags[isWhite?1:0];
}
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 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];
}
}

View File

@ -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);
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 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 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 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);
});
panelTop.add(btnUndo);
chckbxWhiteAI = new JCheckBox("WhiteAI");
chckbxWhiteAI.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
clicAIToggle(true);
}
chckbxWhiteAI = new JCheckBox("WhiteAI");
chckbxWhiteAI.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
clicAIToggle(true);
}
});
panelTop.add(chckbxWhiteAI);
});
panelTop.add(chckbxWhiteAI);
chckbxBlackAI = new JCheckBox("BlackAI");
chckbxBlackAI.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
clicAIToggle(false);
}
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 JPanelChessBoard getPanelDessin() {
return panelDraw;
}
public void instantiateSimu() {
if(game==null) {
game = new Game(this);
panelDraw.setGame(game);
game.start();
}
}
public void instantiateSimu() {
if(game==null) {
game = new Game(this);
panelDraw.setGame(game);
game.start();
}
}
public void clicButtonStart() {
this.instantiateSimu();
game.setDefaultSetup();
}
public void clicButtonStart() {
this.instantiateSimu();
game.setDefaultSetup();
}
public void clickButtonAdder() {
panelDraw.toggleAdderMode();
}
public void clickButtonSelector() {
panelDraw.togglePieceSelector();
}
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();
}
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 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<String> lines = new LinkedList<String>();
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 clicLoadFileButton() {
Game loadedSim = new Game(this);
String fileName=SelectFile();
LinkedList<String> lines = new LinkedList<String>();
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 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 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 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();
}
/**
* 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"));
public void eraseLabels() {
this.setStepBanner("Turn : X");
}
// 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");
}
}