Reviewed-on: #5 Co-authored-by: Persson-dev <sim16.prib@gmail.com> Co-committed-by: Persson-dev <sim16.prib@gmail.com>
332 lines
8.2 KiB
Java
332 lines
8.2 KiB
Java
package chess.model;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
import java.util.Objects;
|
|
|
|
import chess.model.pieces.King;
|
|
import chess.model.visitor.PawnIdentifier;
|
|
import chess.model.visitor.PiecePathChecker;
|
|
|
|
public class ChessBoard {
|
|
public static class Cell {
|
|
private Piece piece;
|
|
|
|
public Cell() {
|
|
this.piece = null;
|
|
}
|
|
|
|
public Piece getPiece() {
|
|
return piece;
|
|
}
|
|
|
|
public void setPiece(Piece piece) {
|
|
this.piece = piece;
|
|
}
|
|
|
|
}
|
|
|
|
private final Cell[][] cells;
|
|
private Move lastVirtualMove;
|
|
private Move lastMove;
|
|
private Piece lastEjectedPiece;
|
|
|
|
private List<Move> cachedAllowedMoves = null;
|
|
|
|
private final Coordinate kingPos[];
|
|
|
|
public ChessBoard() {
|
|
this.cells = new Cell[Coordinate.VALUE_MAX][Coordinate.VALUE_MAX];
|
|
for (int i = 0; i < Coordinate.VALUE_MAX; i++) {
|
|
for (int j = 0; j < Coordinate.VALUE_MAX; j++) {
|
|
this.cells[i][j] = new Cell();
|
|
}
|
|
}
|
|
this.lastVirtualMove = null;
|
|
this.lastMove = null;
|
|
this.lastEjectedPiece = null;
|
|
this.kingPos = new Coordinate[Color.values().length];
|
|
}
|
|
|
|
public void applyMove(Move move) {
|
|
assert move.isValid() : "Invalid move !";
|
|
Piece deadPiece = pieceAt(move.getDeadPieceCoords());
|
|
if (deadPiece != null) {
|
|
this.lastEjectedPiece = deadPiece;
|
|
} else {
|
|
this.lastEjectedPiece = null;
|
|
}
|
|
Piece movingPiece = pieceAt(move.getStart());
|
|
pieceLeaves(move.getDeadPieceCoords());
|
|
pieceLeaves(move.getStart());
|
|
pieceComes(movingPiece, move.getFinish());
|
|
movingPiece.move();
|
|
this.lastVirtualMove = move;
|
|
}
|
|
|
|
public void undoLastMove() {
|
|
assert this.lastVirtualMove != null : "Can't undo at the beginning!";
|
|
|
|
undoMove(this.lastVirtualMove, this.lastEjectedPiece);
|
|
}
|
|
|
|
public void undoMove(Move move, Piece deadPiece) {
|
|
Piece movingPiece = pieceAt(move.getFinish());
|
|
pieceComes(movingPiece, move.getStart());
|
|
pieceLeaves(move.getFinish());
|
|
pieceComes(deadPiece, move.getDeadPieceCoords());
|
|
assert movingPiece != null;
|
|
movingPiece.unMove();
|
|
}
|
|
|
|
public boolean isCellEmpty(Coordinate coordinate) {
|
|
return pieceAt(coordinate) == null;
|
|
}
|
|
|
|
public Piece pieceAt(Coordinate coordinate) {
|
|
if (!coordinate.isValid())
|
|
return null;
|
|
return cellAt(coordinate).getPiece();
|
|
}
|
|
|
|
public void clearBoard() {
|
|
for (int i = 0; i < Coordinate.VALUE_MAX; i++) {
|
|
for (int j = 0; j < Coordinate.VALUE_MAX; j++) {
|
|
pieceLeaves(new Coordinate(i, j));
|
|
}
|
|
}
|
|
}
|
|
|
|
private Cell cellAt(Coordinate coordinate) {
|
|
return this.cells[coordinate.getX()][coordinate.getY()];
|
|
}
|
|
|
|
public void pieceComes(Piece piece, Coordinate coordinate) {
|
|
cellAt(coordinate).setPiece(piece);
|
|
if (piece instanceof King)
|
|
this.kingPos[piece.getColor().ordinal()] = coordinate;
|
|
}
|
|
|
|
public void pieceLeaves(Coordinate coordinate) {
|
|
cellAt(coordinate).setPiece(null);
|
|
}
|
|
|
|
public Coordinate findKing(Color color) {
|
|
// KingIdentifier kingIdentifier = new KingIdentifier(color);
|
|
// for (int i = 0; i < Coordinate.VALUE_MAX; i++) {
|
|
// for (int j = 0; j < Coordinate.VALUE_MAX; j++) {
|
|
// Coordinate coordinate = new Coordinate(i, j);
|
|
// Piece piece = pieceAt(coordinate);
|
|
// if (kingIdentifier.isKing(piece)) {
|
|
// return coordinate;
|
|
// }
|
|
// }
|
|
// }
|
|
// assert false : "No king found ?!";
|
|
// return null;
|
|
return kingPos[color.ordinal()];
|
|
}
|
|
|
|
public boolean isKingInCheck(Color color) {
|
|
Coordinate kingPos = findKing(color);
|
|
assert kingPos.isValid() : "King position is invalid!";
|
|
|
|
for (int i = 0; i < Coordinate.VALUE_MAX; i++) {
|
|
for (int j = 0; j < Coordinate.VALUE_MAX; j++) {
|
|
Coordinate attackCoords = new Coordinate(i, j);
|
|
Piece attackPiece = pieceAt(attackCoords);
|
|
if (attackPiece == null || attackPiece.getColor() == color)
|
|
continue;
|
|
|
|
PiecePathChecker checker = new PiecePathChecker(this, new Move(attackCoords, kingPos));
|
|
if (checker.isValid())
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public boolean hasAllowedMoves(Color player) {
|
|
return !getAllowedMoves(player).isEmpty();
|
|
}
|
|
|
|
public List<Move> getAllowedMoves(Color player) {
|
|
if (this.cachedAllowedMoves != null) {
|
|
return this.cachedAllowedMoves;
|
|
}
|
|
|
|
List<Move> result = new ArrayList<>();
|
|
|
|
for (int x = 0; x < Coordinate.VALUE_MAX; x++) {
|
|
for (int y = 0; y < Coordinate.VALUE_MAX; y++) {
|
|
|
|
Coordinate start = new Coordinate(x, y);
|
|
|
|
Piece piece = pieceAt(start);
|
|
if (piece == null || piece.getColor() != player)
|
|
continue;
|
|
|
|
for (int i = 0; i < Coordinate.VALUE_MAX; i++) {
|
|
for (int j = 0; j < Coordinate.VALUE_MAX; j++) {
|
|
Coordinate destination = new Coordinate(i, j);
|
|
Move move = new Move(start, destination);
|
|
|
|
Piece destPiece = pieceAt(destination);
|
|
|
|
if (destPiece != null && destPiece.getColor() == player)
|
|
continue;
|
|
|
|
PiecePathChecker piecePathChecker = new PiecePathChecker(this,
|
|
move);
|
|
if (!piecePathChecker.isValid())
|
|
continue;
|
|
|
|
applyMove(move);
|
|
if (!isKingInCheck(player))
|
|
result.add(move);
|
|
undoLastMove();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
this.cachedAllowedMoves = result;
|
|
return result;
|
|
}
|
|
|
|
public List<Coordinate> getAllowedMoves(Coordinate pieceCoords) {
|
|
Piece piece = pieceAt(pieceCoords);
|
|
if (piece == null)
|
|
return null;
|
|
|
|
Color player = piece.getColor();
|
|
|
|
List<Coordinate> result = new ArrayList<>();
|
|
|
|
for (int i = 0; i < Coordinate.VALUE_MAX; i++) {
|
|
for (int j = 0; j < Coordinate.VALUE_MAX; j++) {
|
|
Coordinate destination = new Coordinate(i, j);
|
|
Move move = new Move(pieceCoords, destination);
|
|
|
|
PiecePathChecker piecePathChecker = new PiecePathChecker(this,
|
|
move);
|
|
if (!piecePathChecker.isValid())
|
|
continue;
|
|
|
|
applyMove(move);
|
|
if (!isKingInCheck(player))
|
|
result.add(destination);
|
|
undoLastMove();
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
private boolean canCastle(Color color, int rookX, Direction kingDirection) {
|
|
if (isKingInCheck(color))
|
|
return false;
|
|
|
|
int colorLine = color == Color.White ? 7 : 0;
|
|
|
|
Coordinate kingCoords = new Coordinate(4, colorLine);
|
|
Coordinate rookCoords = new Coordinate(rookX, colorLine);
|
|
Piece king = pieceAt(kingCoords);
|
|
Piece rook = pieceAt(rookCoords);
|
|
|
|
if (king == null || rook == null || king.hasMoved() || rook.hasMoved())
|
|
return false;
|
|
|
|
for (int step = 1; step <= 2; step++) {
|
|
Coordinate dest = Coordinate.fromIndex(kingCoords.toIndex() + step * kingDirection.getIndexOffset());
|
|
Piece obstacle = pieceAt(dest);
|
|
if (obstacle != null)
|
|
return false;
|
|
|
|
applyMove(new Move(kingCoords, dest));
|
|
if (isKingInCheck(color)) {
|
|
undoLastMove();
|
|
return false;
|
|
}
|
|
undoLastMove();
|
|
}
|
|
|
|
Coordinate rookObstacleCoords = Coordinate.fromIndex(rookCoords.toIndex() - kingDirection.getIndexOffset());
|
|
Piece obstacle = pieceAt(rookObstacleCoords);
|
|
|
|
return obstacle == null;
|
|
}
|
|
|
|
public boolean canSmallCastle(Color color) {
|
|
return canCastle(color, 7, Direction.Right);
|
|
}
|
|
|
|
public boolean canBigCastle(Color color) {
|
|
return canCastle(color, 0, Direction.Left);
|
|
}
|
|
|
|
public boolean pawnShouldBePromoted() {
|
|
return pawnPromotePosition() != null;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @return Null if there is no pawn to promote
|
|
*/
|
|
public Coordinate pawnPromotePosition() {
|
|
Coordinate piecePos = pawnPromotePosition(Color.White);
|
|
if (piecePos != null)
|
|
return piecePos;
|
|
return pawnPromotePosition(Color.Black);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @return Null if there is no pawn to promote
|
|
*/
|
|
private Coordinate pawnPromotePosition(Color color) {
|
|
int enemyLineY = color == Color.White ? 0 : 7;
|
|
PawnIdentifier identifier = new PawnIdentifier(color);
|
|
|
|
for (int x = 0; x < Coordinate.VALUE_MAX; x++) {
|
|
Coordinate pieceCoords = new Coordinate(x, enemyLineY);
|
|
Piece piece = pieceAt(pieceCoords);
|
|
|
|
if (identifier.isPawn(piece))
|
|
return pieceCoords;
|
|
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
int result = 0;
|
|
for (int i = 0; i < Coordinate.VALUE_MAX; i++) {
|
|
for (int j = 0; j < Coordinate.VALUE_MAX; j++) {
|
|
Piece piece = pieceAt(new Coordinate(i, j));
|
|
if (piece == null)
|
|
continue;
|
|
result = Objects.hash(result, piece.getColor(), new Coordinate(i, j), piece);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public Move getLastMove() {
|
|
return this.lastMove;
|
|
}
|
|
|
|
public void setLastMove(Move lastMove) {
|
|
this.lastMove = lastMove;
|
|
this.cachedAllowedMoves = null;
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object obj) {
|
|
if (obj instanceof ChessBoard board)
|
|
return board.hashCode() == this.hashCode();
|
|
return false;
|
|
}
|
|
|
|
}
|