OOP_1A6_Project/src/backend/Board.java

628 lines
20 KiB
Java

package backend;
import java.util.ArrayList;
import java.util.Stack;
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<String[]> 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<Piece> getPieces() {
ArrayList<Piece> 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<Move> 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<Move> 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
}
}
private ArrayList<Move> getLegalMoves(Piece piece) {
ArrayList<Move> 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<Move> 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<Move> 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<Move> 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<Move> 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<Move> 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;
}
private 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 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 void playMove(Move move) {
// Save current state before move for undo
undoStack.push(toFileRep());
// existing move logic
Piece piece = board[move.getFromX()][move.getFromY()];
board[move.getToX()][move.getToY()] = piece;
board[move.getFromX()][move.getFromY()] = null;
piece.x = move.getToX();
piece.y = move.getToY();
// Reset en passant tracking at the start of each move
enPassantCol = -1;
enPassantRow = -1;
// Check if this is a pawn making a double move
if (piece.getType() == PieceType.Pawn &&
Math.abs(move.getFromY() - move.getToY()) == 2) {
enPassantCol = move.getToX();
enPassantRow = (move.getFromY() + move.getToY()) / 2; // Middle square
}
if (piece.getType() == PieceType.Pawn &&
(piece.getY() == 0 || piece.getY() == height - 1)) {
board[piece.getX()][piece.getY()] = promotePawn(piece.isWhite(), piece.getX(), piece.getY());
}
// Handle en passant capture
if (piece.getType() == PieceType.Pawn &&
move.getToX() != move.getFromX() &&
board[move.getToX()][move.getToY()] == null) {
// This is a diagonal pawn move to an empty square - must be en passant
// Capture the pawn
Piece capturedPawn = board[move.getToX()][move.getFromY()];
move.setCapturedPiece(capturedPawn);
// Remove the captured pawn
board[move.getToX()][move.getFromY()] = null; // not working
}
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);
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<Move> moves = getLegalMoves(piece);
// Check if any move can capture the king
for (Move move : moves) {
if (move.getToX() == kingX && move.getToY() == kingY) {
return true; // King is in check
}
}
}
}
return false; // King is not in check
}
public static Piece promotePawn(boolean isWhite, int x, int y) {
return new Piece(isWhite, PieceType.Queen, x, y);
}
private void highlightKingInCheck() {
// Check if white king is in check
Piece whiteKing = findKing(true);
if (whiteKing != null && isKingInCheck(true)) {
highlightedSquares[whiteKing.getX()][whiteKing.getY()] = true;
}
// 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<Move> 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<Move> 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());
}
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;
}
}