506 lines
19 KiB
Java
506 lines
19 KiB
Java
package backend;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Stack;
|
|
|
|
public class Board {
|
|
|
|
private int width;
|
|
private int height;
|
|
private ArrayList<Piece> pieces;
|
|
private int turnNumber = 0;
|
|
private boolean turnIsWhite = true;
|
|
private boolean gameOver = false;
|
|
|
|
private Integer selectedX = null;
|
|
private Integer selectedY = null;
|
|
private ArrayList<int[]> highlightedPositions = new ArrayList<>();
|
|
|
|
private Stack<Move> moveHistory = new Stack<>();
|
|
|
|
// EN PASSANT
|
|
private Integer enPassantX = null;
|
|
private Integer enPassantY = null;
|
|
|
|
public Board(int colNum, int lineNum) {
|
|
this.width = colNum;
|
|
this.height = lineNum;
|
|
this.pieces = new ArrayList<>();
|
|
this.enPassantX = null;
|
|
this.enPassantY = null;
|
|
}
|
|
|
|
public Board(Board other) {
|
|
this.width = other.width;
|
|
this.height = other.height;
|
|
this.pieces = new ArrayList<>();
|
|
for (Piece p : other.pieces) {
|
|
this.pieces.add(new Piece(p.isWhite(), p.getType(), p.getX(), p.getY()));
|
|
}
|
|
this.turnNumber = other.turnNumber;
|
|
this.turnIsWhite = other.turnIsWhite;
|
|
this.gameOver = other.gameOver;
|
|
this.selectedX = other.selectedX;
|
|
this.selectedY = other.selectedY;
|
|
this.highlightedPositions = new ArrayList<>(other.highlightedPositions);
|
|
this.moveHistory = new Stack<>();
|
|
this.moveHistory.addAll(other.moveHistory);
|
|
this.enPassantX = other.enPassantX;
|
|
this.enPassantY = other.enPassantY;
|
|
}
|
|
|
|
public Board(String[] array) {
|
|
this.width = 8;
|
|
this.height = 8;
|
|
this.pieces = new ArrayList<>();
|
|
for (int y = 0; y < 8; y++) {
|
|
String[] cells = array[y].split(",");
|
|
for (int x = 0; x < 8; x++) {
|
|
if (cells[x].length() == 2) {
|
|
boolean isWhite = cells[x].charAt(0) == 'w';
|
|
PieceType type = PieceType.fromSummary(cells[x].charAt(1));
|
|
setPiece(isWhite, type, x, y);
|
|
}
|
|
}
|
|
}
|
|
turnIsWhite = array[8].equalsIgnoreCase("W");
|
|
this.enPassantX = null;
|
|
this.enPassantY = null;
|
|
}
|
|
|
|
public int getWidth() { return width; }
|
|
public int getHeight() { return height; }
|
|
public int getTurnNumber() { return turnNumber; }
|
|
public boolean isTurnWhite() { return turnIsWhite; }
|
|
public boolean isGameOver() { return gameOver; }
|
|
public Integer getEnPassantX() { return enPassantX; }
|
|
public Integer getEnPassantY() { return enPassantY; }
|
|
|
|
public void setPiece(boolean isWhite, PieceType type, int x, int y) {
|
|
pieces.removeIf(p -> p.getX() == x && p.getY() == y);
|
|
pieces.add(new Piece(isWhite, type, x, y));
|
|
}
|
|
|
|
public void populateBoard() {
|
|
pieces.clear();
|
|
setPiece(true, PieceType.Rook, 0, 7);
|
|
setPiece(true, PieceType.Knight, 1, 7);
|
|
setPiece(true, PieceType.Bishop, 2, 7);
|
|
setPiece(true, PieceType.Queen, 3, 7);
|
|
setPiece(true, PieceType.King, 4, 7);
|
|
setPiece(true, PieceType.Bishop, 5, 7);
|
|
setPiece(true, PieceType.Knight, 6, 7);
|
|
setPiece(true, PieceType.Rook, 7, 7);
|
|
for (int x = 0; x < 8; x++) setPiece(true, PieceType.Pawn, x, 6);
|
|
|
|
setPiece(false, PieceType.Rook, 0, 0);
|
|
setPiece(false, PieceType.Knight, 1, 0);
|
|
setPiece(false, PieceType.Bishop, 2, 0);
|
|
setPiece(false, PieceType.Queen, 3, 0);
|
|
setPiece(false, PieceType.King, 4, 0);
|
|
setPiece(false, PieceType.Bishop, 5, 0);
|
|
setPiece(false, PieceType.Knight, 6, 0);
|
|
setPiece(false, PieceType.Rook, 7, 0);
|
|
for (int x = 0; x < 8; x++) setPiece(false, PieceType.Pawn, x, 1);
|
|
this.enPassantX = null;
|
|
this.enPassantY = null;
|
|
}
|
|
|
|
public void cleanBoard() {
|
|
pieces.clear();
|
|
selectedX = null;
|
|
selectedY = null;
|
|
highlightedPositions.clear();
|
|
moveHistory.clear();
|
|
gameOver = false;
|
|
enPassantX = null;
|
|
enPassantY = null;
|
|
}
|
|
|
|
public ArrayList<Piece> getPieces() { return pieces; }
|
|
|
|
/**
|
|
* Gère la sélection et le déplacement d'une pièce par l'utilisateur.
|
|
* Empêche la capture du roi et termine la partie sur échec et mat.
|
|
*/
|
|
public void userTouch(int x, int y) {
|
|
if (gameOver) return;
|
|
|
|
Piece clickedPiece = getPieceAt(x, y);
|
|
|
|
// Sélection initiale
|
|
if (selectedX == null || selectedY == null) {
|
|
if (clickedPiece != null && clickedPiece.isWhite() == turnIsWhite) {
|
|
selectedX = x;
|
|
selectedY = y;
|
|
computeHighlightedPositions(clickedPiece);
|
|
}
|
|
} else {
|
|
Piece selectedPiece = getPieceAt(selectedX, selectedY);
|
|
if (selectedPiece != null && selectedPiece.isWhite() == turnIsWhite) {
|
|
// Simuler le mouvement pour vérifier l'absence d'échec
|
|
Board simulation = new Board(this);
|
|
simulation.pieces.removeIf(p -> p.getX() == selectedX && p.getY() == selectedY);
|
|
simulation.pieces.removeIf(p -> p.getX() == x && p.getY() == y);
|
|
simulation.pieces.add(new Piece(
|
|
selectedPiece.isWhite(),
|
|
selectedPiece.getType(),
|
|
x, y
|
|
));
|
|
|
|
if (!simulation.isKingInCheck(turnIsWhite)) {
|
|
Piece target = getPieceAt(x, y);
|
|
|
|
// EN PASSANT (avant la capture normale)
|
|
boolean enPassantMove = false;
|
|
if (selectedPiece.getType() == PieceType.Pawn
|
|
&& getEnPassantX() != null && getEnPassantY() != null
|
|
&& x == getEnPassantX() && y == getEnPassantY()
|
|
&& target == null
|
|
&& selectedX != x) {
|
|
// On va prendre en passant
|
|
enPassantMove = true;
|
|
int capturedY = turnIsWhite ? y + 1 : y - 1;
|
|
pieces.removeIf(p -> p.getX() == x && p.getY() == capturedY);
|
|
target = getPieceAt(x, capturedY); // Pour le move history
|
|
}
|
|
|
|
// Si on cible le roi adverse, c'est un cas de mat -> terminer la partie
|
|
if (target != null && target.getType() == PieceType.King) {
|
|
gameOver = true;
|
|
return;
|
|
}
|
|
|
|
// Capture normale
|
|
if (!enPassantMove && target != null) {
|
|
pieces.removeIf(p -> p.getX() == x && p.getY() == y);
|
|
}
|
|
|
|
// Déplacer la pièce sélectionnée
|
|
pieces.removeIf(p -> p.getX() == selectedX && p.getY() == selectedY);
|
|
pieces.add(new Piece(
|
|
selectedPiece.isWhite(),
|
|
selectedPiece.getType(),
|
|
x, y
|
|
));
|
|
|
|
// EN PASSANT : Maj de la case en passant
|
|
if (selectedPiece.getType() == PieceType.Pawn && Math.abs(y - selectedY) == 2) {
|
|
enPassantX = x;
|
|
enPassantY = (y + selectedY) / 2; // Case traversée
|
|
} else {
|
|
enPassantX = null;
|
|
enPassantY = null;
|
|
}
|
|
|
|
// Move history (on précise enPassant ou non)
|
|
Move move = new Move(selectedPiece, selectedX, selectedY, x, y, target,
|
|
enPassantMove);
|
|
moveHistory.push(move);
|
|
|
|
// Changer de tour
|
|
turnNumber++;
|
|
turnIsWhite = !turnIsWhite;
|
|
|
|
// Vérifier échec et mat sur le prochain joueur
|
|
if (isCheckmate(turnIsWhite)) {
|
|
gameOver = true;
|
|
}
|
|
}
|
|
}
|
|
// Réinitialiser la sélection
|
|
selectedX = null;
|
|
selectedY = null;
|
|
highlightedPositions.clear();
|
|
}
|
|
printBoard();
|
|
}
|
|
|
|
public void undoLastMove() {
|
|
if (!moveHistory.isEmpty()) {
|
|
Move lastMove = moveHistory.pop();
|
|
pieces.removeIf(p -> p.getX() == lastMove.getToX() && p.getY() == lastMove.getToY());
|
|
pieces.add(new Piece(
|
|
lastMove.getMovedPiece().isWhite(),
|
|
lastMove.getMovedPiece().getType(),
|
|
lastMove.getFromX(), lastMove.getFromY()
|
|
));
|
|
if (lastMove.getCapturedPiece() != null) {
|
|
pieces.add(lastMove.getCapturedPiece());
|
|
}
|
|
turnNumber--;
|
|
turnIsWhite = !turnIsWhite;
|
|
selectedX = null;
|
|
selectedY = null;
|
|
highlightedPositions.clear();
|
|
gameOver = false;
|
|
enPassantX = null;
|
|
enPassantY = null;
|
|
}
|
|
}
|
|
|
|
public boolean isCheckmate(boolean forWhite) {
|
|
if (!isKingInCheck(forWhite)) return false;
|
|
for (Piece piece : pieces) {
|
|
if (piece.isWhite() == forWhite) {
|
|
ArrayList<int[]> moves = getLegalMovesFor(piece);
|
|
for (int[] move : moves) {
|
|
Board copy = new Board(this);
|
|
Piece captured = copy.getPieceAt(move[0], move[1]);
|
|
copy.pieces.removeIf(p -> p.getX() == piece.getX() && p.getY() == piece.getY());
|
|
copy.pieces.removeIf(p -> p.getX() == move[0] && p.getY() == move[1]);
|
|
copy.pieces.add(new Piece(piece.isWhite(), piece.getType(), move[0], move[1]));
|
|
if (!copy.isKingInCheck(forWhite)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public boolean isKingInCheck(boolean isWhite) {
|
|
Piece king = null;
|
|
for (Piece p : pieces) {
|
|
if (p.getType() == PieceType.King && p.isWhite() == isWhite) {
|
|
king = p;
|
|
break;
|
|
}
|
|
}
|
|
if (king == null) return true; // pas de roi => considérer comme échec
|
|
for (Piece enemy : pieces) {
|
|
if (enemy.isWhite() != isWhite) {
|
|
ArrayList<int[]> moves = getLegalMovesFor(enemy);
|
|
for (int[] pos : moves) {
|
|
if (pos[0] == king.getX() && pos[1] == king.getY()) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public boolean isSelected(int x, int y) {
|
|
return selectedX != null && selectedY != null && selectedX == x && selectedY == y;
|
|
}
|
|
|
|
public boolean isHighlighted(int x, int y) {
|
|
for (int[] pos : highlightedPositions) {
|
|
if (pos[0] == x && pos[1] == y) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private void computeHighlightedPositions(Piece piece) {
|
|
highlightedPositions.clear();
|
|
highlightedPositions.addAll(getLegalMovesFor(piece));
|
|
}
|
|
|
|
private ArrayList<int[]> getLegalMovesFor(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;
|
|
int startRow = isWhite ? 6 : 1;
|
|
int oneStepY = y + dir;
|
|
int twoStepY = y + 2 * dir;
|
|
if (inBounds(x, oneStepY) && getPieceAt(x, oneStepY) == null) {
|
|
moves.add(new int[]{x, oneStepY});
|
|
if (y == startRow && getPieceAt(x, twoStepY) == null) {
|
|
moves.add(new int[]{x, twoStepY});
|
|
}
|
|
}
|
|
// Capture à gauche
|
|
if (inBounds(x - 1, oneStepY)) {
|
|
Piece left = getPieceAt(x - 1, oneStepY);
|
|
if (left != null && left.isWhite() != isWhite)
|
|
moves.add(new int[]{x - 1, oneStepY});
|
|
// EN PASSANT GAUCHE
|
|
if (getEnPassantX() != null && getEnPassantY() != null &&
|
|
x - 1 == getEnPassantX() && oneStepY == getEnPassantY() &&
|
|
getPieceAt(x - 1, y) != null &&
|
|
getPieceAt(x - 1, y).getType() == PieceType.Pawn &&
|
|
getPieceAt(x - 1, y).isWhite() != isWhite) {
|
|
moves.add(new int[]{x - 1, oneStepY});
|
|
}
|
|
}
|
|
// Capture à droite
|
|
if (inBounds(x + 1, oneStepY)) {
|
|
Piece right = getPieceAt(x + 1, oneStepY);
|
|
if (right != null && right.isWhite() != isWhite)
|
|
moves.add(new int[]{x + 1, oneStepY});
|
|
// EN PASSANT DROITE
|
|
if (getEnPassantX() != null && getEnPassantY() != null &&
|
|
x + 1 == getEnPassantX() && oneStepY == getEnPassantY() &&
|
|
getPieceAt(x + 1, y) != null &&
|
|
getPieceAt(x + 1, y).getType() == PieceType.Pawn &&
|
|
getPieceAt(x + 1, y).isWhite() != isWhite) {
|
|
moves.add(new int[]{x + 1, oneStepY});
|
|
}
|
|
}
|
|
break;
|
|
case Rook:
|
|
addSlideMoves(moves, piece, 1, 0);
|
|
addSlideMoves(moves, piece, -1, 0);
|
|
addSlideMoves(moves, piece, 0, 1);
|
|
addSlideMoves(moves, piece, 0, -1);
|
|
break;
|
|
case Bishop:
|
|
addSlideMoves(moves, piece, 1, 1);
|
|
addSlideMoves(moves, piece, 1, -1);
|
|
addSlideMoves(moves, piece, -1, 1);
|
|
addSlideMoves(moves, piece, -1, -1);
|
|
break;
|
|
case Queen:
|
|
for (int dx = -1; dx <= 1; dx++) {
|
|
for (int dy = -1; dy <= 1; dy++) {
|
|
if (dx != 0 || dy != 0) {
|
|
addSlideMoves(moves, piece, dx, dy);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case Knight:
|
|
int[][] deltas = {{2,1},{1,2},{-1,2},{-2,1},{-2,-1},{-1,-2},{1,-2},{2,-1}};
|
|
for (int[] d : deltas) {
|
|
int nx = x + d[0];
|
|
int ny = y + d[1];
|
|
if (inBounds(nx, ny)) {
|
|
Piece p = getPieceAt(nx, ny);
|
|
if (p == null || p.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;
|
|
int nx = x + dx;
|
|
int ny = y + dy;
|
|
if (inBounds(nx, ny)) {
|
|
Piece p = getPieceAt(nx, ny);
|
|
if (p == null || p.isWhite() != isWhite)
|
|
moves.add(new int[]{nx, ny});
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
return moves;
|
|
}
|
|
|
|
private void addSlideMoves(ArrayList<int[]> moves, Piece piece, int dx, int dy) {
|
|
int x = piece.getX(); int y = piece.getY(); boolean isWhite = piece.isWhite();
|
|
int nx = x + dx; int ny = y + dy;
|
|
while (inBounds(nx, ny)) {
|
|
Piece p = getPieceAt(nx, ny);
|
|
if (p == null) {
|
|
moves.add(new int[]{nx, ny});
|
|
} else {
|
|
if (p.isWhite() != isWhite) moves.add(new int[]{nx, ny});
|
|
break;
|
|
}
|
|
nx += dx; ny += dy;
|
|
}
|
|
}
|
|
|
|
private boolean inBounds(int x, int y) {
|
|
return x >= 0 && y >= 0 && x < width && y < height;
|
|
}
|
|
|
|
public Piece getPieceAt(int x, int y) {
|
|
for (Piece p : pieces) {
|
|
if (p.getX() == x && p.getY() == y) return p;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public String[] toFileRep() {
|
|
String[] output = new String[height + 2];
|
|
StringBuilder top = new StringBuilder(" ");
|
|
for (int x = 0; x < width; x++) {
|
|
if (x > 0) top.append(" ");
|
|
top.append((char)('A' + x));
|
|
}
|
|
output[0] = top.toString();
|
|
for (int y = 0; y < height; y++) {
|
|
int rank = height - y;
|
|
StringBuilder row = new StringBuilder();
|
|
row.append(rank).append(" ");
|
|
for (int x = 0; x < width; x++) {
|
|
if (x > 0) row.append(" ");
|
|
Piece p = getPieceAt(x, y);
|
|
if (p == null) {
|
|
row.append(".");
|
|
} else {
|
|
char c = p.getType().getSummary();
|
|
row.append(p.isWhite() ? Character.toUpperCase(c) : Character.toLowerCase(c));
|
|
}
|
|
}
|
|
output[y + 1] = row.toString();
|
|
}
|
|
output[height + 1] = "Turn: " + (turnIsWhite ? "White" : "Black");
|
|
return output;
|
|
}
|
|
|
|
public void printBoard() {
|
|
for (String line : toFileRep()) System.out.println(line);
|
|
}
|
|
|
|
/**
|
|
* Appliquer un coup déjà construit.
|
|
* Bloque la capture du roi et termine la partie sur échec et mat.
|
|
*/
|
|
public void playMove(Move move) {
|
|
if (move == null || gameOver) return;
|
|
Piece captured = getPieceAt(move.getToX(), move.getToY());
|
|
// EN PASSANT (avant la capture normale)
|
|
if (move.getMovedPiece().getType() == PieceType.Pawn
|
|
&& move.isEnPassant()) {
|
|
int capturedY = move.getMovedPiece().isWhite() ? move.getToY() + 1 : move.getToY() - 1;
|
|
pieces.removeIf(p -> p.getX() == move.getToX() && p.getY() == capturedY);
|
|
}
|
|
// Si tentative de capturer le roi, fin de partie
|
|
if (captured != null && captured.getType() == PieceType.King) {
|
|
gameOver = true;
|
|
return;
|
|
}
|
|
// Capture normale
|
|
if (!(move.getMovedPiece().getType() == PieceType.Pawn && move.isEnPassant()) && captured != null) {
|
|
pieces.removeIf(p -> p.getX() == move.getToX() && p.getY() == move.getToY());
|
|
}
|
|
// Déplacement
|
|
pieces.removeIf(p -> p.getX() == move.getFromX() && p.getY() == move.getFromY());
|
|
pieces.add(new Piece(
|
|
move.getMovedPiece().isWhite(),
|
|
move.getMovedPiece().getType(),
|
|
move.getToX(), move.getToY()
|
|
));
|
|
|
|
// EN PASSANT : Maj de la case en passant
|
|
if (move.getMovedPiece().getType() == PieceType.Pawn && Math.abs(move.getToY() - move.getFromY()) == 2) {
|
|
enPassantX = move.getToX();
|
|
enPassantY = (move.getToY() + move.getFromY()) / 2;
|
|
} else {
|
|
enPassantX = null;
|
|
enPassantY = null;
|
|
}
|
|
|
|
moveHistory.push(move);
|
|
turnNumber++;
|
|
turnIsWhite = !turnIsWhite;
|
|
selectedX = null;
|
|
selectedY = null;
|
|
highlightedPositions.clear();
|
|
|
|
if (isCheckmate(turnIsWhite)) {
|
|
gameOver = true;
|
|
}
|
|
}
|
|
}
|