package dev.kske.chess.board; import java.util.*; import dev.kske.chess.board.Piece.Color; import dev.kske.chess.event.EventBus; import dev.kske.chess.event.MoveEvent; /** * Project: Chess
* File: Board.java
* Created: 01.07.2019
* * @since Chess v0.1-alpha * @author Kai S. K. Engelbart */ public class Board { private Piece[][] boardArr = new Piece[8][8]; private Map kingPos = new EnumMap<>(Color.class); private Log log = new Log(); /** * Initializes the board with the default chess starting position. */ public Board() { initDefaultPositions(); } /** * Creates a copy of another {@link Board} instance.
* The created object is a deep copy, and can optionally contain the move * history of the Board to copy. * * @param other The Board instance to copy * @param copyVariations if set to {@code true}, the {@link Log} object of * the * other Board instance is copied with its entire move * history */ public Board(Board other, boolean copyVariations) { for (int i = 0; i < 8; i++) for (int j = 0; j < 8; j++) { if (other.boardArr[i][j] == null) continue; boardArr[i][j] = (Piece) other.boardArr[i][j].clone(); boardArr[i][j].board = this; } kingPos.putAll(other.kingPos); log = new Log(other.log, copyVariations); // Synchronize the current move node with the board while (log.getLast().hasVariations()) log.selectNextNode(0); } /** * Moves a piece across the board if the move is legal. * * @param move The move to execute * @return {@code true}, if the attempted move was legal and thus executed */ public boolean attemptMove(Move move) { Piece piece = getPos(move); if (piece == null || !piece.isValidMove(move)) return false; // Move piece move(move); // Revert move if it caused a check for its team if (checkCheck(piece.getColor())) { revert(); return false; } return true; } /** * Moves a piece across the board without checking if the move is legal. * * @param move The move to execute */ public void move(Move move) { Piece piece = getPos(move); Piece capturePiece = getDest(move); // Execute the move move.execute(this); // Update the king's position if the moved piece is the king if (piece instanceof King) kingPos.put(piece.getColor(), move.getDest()); // Update log log.add(move, piece, capturePiece); } /** * Moves a piece across the board without checking if the move is legal. * * @param sanMove The move to execute in SAN (Standard Algebraic Notation) */ public void move(String sanMove) { move(Move.fromSAN(sanMove, this)); } /** * Reverts the last move and removes it from the log. */ public void revert() { MoveNode moveNode = log.getLast(); Move move = moveNode.move; // Revert the move move.revert(this, moveNode.capturedPiece); // Update the king's position if the moved piece is the king if (getPos(move) instanceof King) kingPos.put(getPos(move).getColor(), move.getPos()); // Update log log.removeLast(); } /** * Reverts the last move without removing it from the log. After that, a * {@link MoveEvent} is dispatched containing the inverse of the reverted * move. */ public void selectPreviousNode() { MoveNode moveNode = log.getLast(); Move move = moveNode.move; // Revert the move move.revert(this, moveNode.capturedPiece); // Select previous move node log.selectPreviousNode(); // Dispatch move event EventBus.getInstance() .dispatch( new MoveEvent( move.invert(), getState(log.getActiveColor().opposite()) ) ); } /** * Applies the next move stored in the log. After that, a {@link MoveEvent} * is * dispatched. * * @param index the variation index of the move to select */ public void selectNextNode(int index) { log.selectNextNode(index); MoveNode moveNode = log.getLast(); Move move = moveNode.move; // Execute the next move move.execute(this); // Dispatch move event EventBus.getInstance() .dispatch( new MoveEvent(move, getState(log.getActiveColor().opposite())) ); } /** * Generated every legal move for one color * * @param color The color to generate the moves for * @return A list of all legal moves */ public List getMoves(Color color) { List moves = new ArrayList<>(); for (int i = 0; i < 8; i++) for (int j = 0; j < 8; j++) if ( boardArr[i][j] != null && boardArr[i][j].getColor() == color ) moves.addAll(boardArr[i][j].getMoves(new Position(i, j))); return moves; } /** * Delegate method for {@link Piece#getMoves(Position)}. * * @param pos the position of the piece to invoke the method on * @return a list of legal moves generated for the piece */ public List getMoves(Position pos) { return get(pos).getMoves(pos); } /** * Checks, if the king is in check. * * @param color The color of the king to check * @return {@code true}, if the king is in check */ public boolean checkCheck(Color color) { return isAttacked(kingPos.get(color), color.opposite()); } /** * Checks, if a field can be attacked by pieces of a certain color. * * @param dest the field to check * @param color the color of a potential attacker piece * @return {@code true} if a move with the destination {@code dest} */ public boolean isAttacked(Position dest, Color color) { for (int i = 0; i < 8; i++) for (int j = 0; j < 8; j++) { Position pos = new Position(i, j); if ( get(pos) != null && get(pos).getColor() == color && get(pos).isValidMove(new Move(pos, dest)) ) return true; } return false; } /** * Checks, if the king is in checkmate. * This requires the king to already be in check! * * @param color The color of the king to check * @return {@code true}, if the king is in checkmate */ public boolean checkCheckmate(Color color) { // Return false immediately if the king can move if (!getMoves(kingPos.get(color)).isEmpty()) return false; for (Move move : getMoves(color)) { move(move); boolean check = checkCheck(color); revert(); if (!check) return false; } return true; } /** * Checks whether the a check, checkmate, stalemate of none of the above is * currently present. * * @param color the color to evaluate the board for * @return the current {@link BoardState} */ public BoardState getState(Color color) { return checkCheck(color) ? checkCheckmate(color) ? BoardState.CHECKMATE : BoardState.CHECK : getMoves(color).isEmpty() || log.getLast().halfmoveClock >= 50 ? BoardState.STALEMATE : BoardState.NORMAL; } /** * Initialized the board array with the default chess pieces and positions. */ public void initDefaultPositions() { // Initialize pawns for (int i = 0; i < 8; i++) { boardArr[i][1] = new Pawn(Color.BLACK, this); boardArr[i][6] = new Pawn(Color.WHITE, this); } // Initialize kings boardArr[4][0] = new King(Color.BLACK, this); boardArr[4][7] = new King(Color.WHITE, this); // Initialize king position objects kingPos.put(Color.BLACK, new Position(4, 0)); kingPos.put(Color.WHITE, new Position(4, 7)); // Initialize queens boardArr[3][0] = new Queen(Color.BLACK, this); boardArr[3][7] = new Queen(Color.WHITE, this); // Initialize rooks boardArr[0][0] = new Rook(Color.BLACK, this); boardArr[0][7] = new Rook(Color.WHITE, this); boardArr[7][0] = new Rook(Color.BLACK, this); boardArr[7][7] = new Rook(Color.WHITE, this); // Initialize knights boardArr[1][0] = new Knight(Color.BLACK, this); boardArr[1][7] = new Knight(Color.WHITE, this); boardArr[6][0] = new Knight(Color.BLACK, this); boardArr[6][7] = new Knight(Color.WHITE, this); // Initialize bishops boardArr[2][0] = new Bishop(Color.BLACK, this); boardArr[2][7] = new Bishop(Color.WHITE, this); boardArr[5][0] = new Bishop(Color.BLACK, this); boardArr[5][7] = new Bishop(Color.WHITE, this); // Clear all other tiles for (int i = 0; i < 8; i++) for (int j = 2; j < 6; j++) boardArr[i][j] = null; log.reset(); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + Arrays.deepHashCode(boardArr); result = prime * result + Objects.hash(kingPos, log); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Board other = (Board) obj; return Arrays.deepEquals(boardArr, other.boardArr) && Objects .equals(kingPos, other.kingPos) && Objects.equals(log, other.log); } /** * @param pos The position from which to return a piece * @return The piece at the position */ public Piece get(Position pos) { return boardArr[pos.x][pos.y]; } /** * Searches for a {@link Piece} inside a file (A - H). * * @param pieceClass The class of the piece to search for * @param file The file in which to search for the piece * @return The rank (1 - 8) of the first piece with the specified type and * current color in the file, or {@code -1} if there isn't any */ public int get(Class pieceClass, char file) { int x = file - 97; for (int i = 0; i < 8; i++) if ( boardArr[x][i] != null && boardArr[x][i].getClass() == pieceClass && boardArr[x][i].getColor() == log.getActiveColor() ) return 8 - i; return -1; } /** * Searches for a {@link Piece} inside a rank (1 - 8). * * @param pieceClass The class of the piece to search for * @param rank The rank in which to search for the piece * @return The file (A - H) of the first piece with the specified type and * current color in the file, or {@code -} if there isn't any */ public char get(Class pieceClass, int rank) { int y = rank - 1; for (int i = 0; i < 8; i++) if ( boardArr[i][y] != null && boardArr[i][y].getClass() == pieceClass && boardArr[i][y].getColor() == log.getActiveColor() ) return (char) (i + 97); return '-'; } /** * Searches for a {@link Piece} that can move to a {@link Position}. * * @param pieceClass The class of the piece to search for * @param dest The destination that the piece is required to reach * @return The position of a piece that can move to the specified * destination */ public Position get(Class pieceClass, Position dest) { for (int i = 0; i < 8; i++) for (int j = 0; j < 8; j++) if ( boardArr[i][j] != null && boardArr[i][j].getClass() == pieceClass && boardArr[i][j].getColor() == log.getActiveColor() ) { Position pos = new Position(i, j); if (boardArr[i][j].isValidMove(new Move(pos, dest))) return pos; } return null; } /** * Places a piece at a position. * * @param pos The position to place the piece at * @param piece The piece to place */ public void set(Position pos, Piece piece) { boardArr[pos.x][pos.y] = piece; } /** * @param move The move from which position to return a piece * @return The piece at the position of the move */ public Piece getPos(Move move) { return get(move.getPos()); } /** * @param move The move from which destination to return a piece * @return The piece at the destination of the move */ public Piece getDest(Move move) { return get(move.getDest()); } /** * Places a piece at the position of a move. * * @param move The move at which position to place the piece * @param piece The piece to place */ public void setPos(Move move, Piece piece) { set(move.getPos(), piece); } /** * Places a piece at the destination of a move. * * @param move The move at which destination to place the piece * @param piece The piece to place */ public void setDest(Move move, Piece piece) { set(move.getDest(), piece); } /** * @return The board array */ public Piece[][] getBoardArr() { return boardArr; } /** * @return The move log */ public Log getLog() { return log; } }