All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.kelseyde.calvin.board.Board Maven / Gradle / Ivy

The newest version!
package com.kelseyde.calvin.board;

import com.kelseyde.calvin.utils.notation.FEN;

import java.util.Arrays;

/**
 * Represents the current state of the chess board, including the positions of the pieces, the side to move, en passant
 * rights, fifty-move counter, and the move counter. Includes functions to 'make' and 'unmake' moves on the board, which
 * are fundamental to both the search and evaluation algorithms. Uses bitboards to represent the pieces and 'toggling'
 * functions to set and unset pieces.
 *
 * @see Chess Programming Wiki
 */
public class Board {

    private ChessVariant variant;
    private long[] bitboards;
    private Piece[] pieces;
    private BoardState state;
    private BoardState[] states;
    private Move[] moves;
    private boolean white;
    private int ply;

    public Board() {
        this.variant     = ChessVariant.STANDARD;
        this.bitboards   = new long[Piece.COUNT + 2];
        this.pieces      = new Piece[Square.COUNT];
        this.moves       = new Move[256];
        this.states      = new BoardState[256];
        this.state       = new BoardState();
        this.white       = true;
        this.ply         = 0;
    }

    /**
     * Updates the internal board representation with the {@link Move} just made. Toggles the piece bitboards to move the
     * piece + remove the captured piece, plus special rules for pawn double-moves, castling, promotion and en passant.
     */
    public boolean makeMove(Move move) {

        final int from = move.from();
        final int to = move.to();
        final Piece piece = pieces[from];
        if (piece == null) return false;
        final Piece captured = move.isEnPassant() ? Piece.PAWN : pieces[to];
        states[ply] = state.copy();

        if (move.isPawnDoubleMove())  makePawnDoubleMove(from, to);
        else if (move.isCastling())   makeCastleMove(from, to);
        else if (move.isPromotion())  makePromotionMove(from, to, move.promoPiece(), captured);
        else if (move.isEnPassant())  makeEnPassantMove(from, to);
        else                          makeStandardMove(from, to, piece, captured);

        updateState(from, to, piece, captured, move);
        moves[ply++] = move;
        checkMaxPly();
        white = !white;

        return true;

    }

    /**
     * Reverts the internal board representation to the state before the last move. Toggles the piece bitboards to move the
     * piece + reinstate the captured piece, plus special rules for pawn double-moves, castling, promotion and en passant.
     */
    public void unmakeMove() {

        white = !white;
        final Move move = moves[--ply];

        final int from = move.from();
        final int to = move.to();
        final Piece piece = pieceAt(to);

        if (move.isCastling())        unmakeCastlingMove(from, to);
        else if (move.isPromotion())  unmakePromotionMove(from, to, move.promoPiece());
        else if (move.isEnPassant())  unmakeEnPassantMove(from, to);
        else                          unmakeStandardMove(from, to, piece);

        state = states[ply];

    }

    private void makePawnDoubleMove(int from, int to) {
        // Handle moving pawn
        updateBitboards(from, to, Piece.PAWN, white);
        updateMailbox(from, to, Piece.PAWN);
        updateKeys(from, to, Piece.PAWN, white);
    }

    private void makeCastleMove(int from, int to) {
        switch (variant) {
            case STANDARD -> makeStandardCastleMove(from, to);
            case CHESS960 -> makeChess960CastleMove(from, to);
        }
    }

    private void makeStandardCastleMove(int from, int to) {
        final boolean kingside = Castling.isKingside(from, to);

        // Handle moving king
        updateBitboards(from, to, Piece.KING, white);
        updateMailbox(from, to, Piece.KING);

        // Handle moving rook
        final int rookFrom = Castling.rookFrom(kingside, white);
        final int rookTo = Castling.rookTo(kingside, white);
        updateBitboards(rookFrom, rookTo, Piece.ROOK, white);
        updateMailbox(rookFrom, rookTo, Piece.ROOK);

        updateKeys(from, to, Piece.KING, white);
        updateKeys(rookFrom, rookTo, Piece.ROOK, white);
    }

    private void makeChess960CastleMove(int from, int to) {
        final boolean kingside = Castling.isKingside(from, to);

        // Unset king
        updateBitboard(from, Piece.KING, white);
        updateMailbox(from, null);

        // Unset rook
        // (in Chess960 the 'to' square of a castling move is the rook square)
        final int rookTo = Castling.rookTo(kingside, white);
        updateBitboard(to, Piece.ROOK, white);
        updateMailbox(to, null);

        final int kingTo = Castling.kingTo(kingside, white);

        // Set king
        updateBitboard(kingTo, Piece.KING, white);
        updateMailbox(kingTo, Piece.KING);

        // Set rook
        updateBitboard(rookTo, Piece.ROOK, white);
        updateMailbox(rookTo, Piece.ROOK);

        updateKeys(from, kingTo, Piece.KING, white);
        updateKeys(to, rookTo, Piece.ROOK, white);
    }

    private void makeEnPassantMove(int from, int to) {
        // Handle capturing pawn
        updateBitboards(from, to, Piece.PAWN, white);
        updateMailbox(from, to, Piece.PAWN);
        updateKeys(from, to, Piece.PAWN, white);
        // Handle captured pawn
        final int pawnSquare = white ? to - 8 : to + 8;
        updateBitboard(pawnSquare, Piece.PAWN, !white);
        updateMailbox(pawnSquare, null);
        updateKeys(pawnSquare, Piece.PAWN, !white);
    }

    private void makePromotionMove(int from, int to, Piece promoted, Piece captured) {
        // Remove promoting pawn
        updateBitboard(from, Piece.PAWN, white);
        updateKeys(from, Piece.PAWN, white);
        // Add promoted piece
        updateBitboard(to, promoted, white);
        updateMailbox(from, to, promoted);
        updateKeys(to, promoted, white);
        if (captured != null) {
            // Handle captured piece
            updateBitboard(to, captured, !white);
            updateKeys(to, captured, !white);
        }
    }

    private void makeStandardMove(int from, int to, Piece piece, Piece captured) {
        // Handle moving piece
        updateBitboards(from, to, piece, white);
        updateKeys(from, to, piece, white);
        updateMailbox(from, to, piece);
        if (captured != null) {
            // Remove captured piece
            updateBitboard(to, captured, !white);
            updateKeys(to, captured, !white);
        }
    }

    private void updateState(int from, int to, Piece piece, Piece captured, Move move) {
        state.captured = captured;
        final boolean resetClock = captured != null || Piece.PAWN.equals(piece);
        state.halfMoveClock = resetClock ? 0 : ++state.halfMoveClock;

        final int castleRights = updateCastleRights(from, to, piece);
        state.key ^= Key.rights(state.rights, castleRights);
        state.rights = castleRights;

        final int enPassantFile = move.isPawnDoubleMove() ? File.of(to) : -1;
        state.key ^= Key.enPassant(state.enPassantFile, enPassantFile);
        state.enPassantFile = enPassantFile;

        state.key ^= Key.sideToMove();
    }

    private void unmakeCastlingMove(int from, int to) {
        switch (variant) {
            case STANDARD -> unmakeStandardCastleMove(from, to);
            case CHESS960 -> unmakeChess960CastleMove(from, to);
        }
    }

    private void unmakeStandardCastleMove(int from, int to) {
        // Put back king
        updateBitboards(to, from, Piece.KING, white);
        updateMailbox(to, from, Piece.KING);
        // Put back rook
        final boolean kingside = Castling.isKingside(from, to);
        final int rookFrom = Castling.rookFrom(kingside, white);
        final int rookTo = Castling.rookTo(kingside, white);
        updateBitboards(rookTo, rookFrom, Piece.ROOK, white);
        updateMailbox(rookTo, rookFrom, Piece.ROOK);
    }

    private void unmakeChess960CastleMove(int from, int to) {
        final boolean kingside = Castling.isKingside(from, to);
        final int kingTo = Castling.kingTo(kingside, white);
        // Unset king
        updateBitboard(kingTo, Piece.KING, white);
        updateMailbox(kingTo, null);
        // Unset rook
        final int rookTo = Castling.rookTo(kingside, white);
        updateBitboard(rookTo, Piece.ROOK, white);
        updateMailbox(rookTo, null);
        // Set king
        updateBitboard(from, Piece.KING, white);
        updateMailbox(from, Piece.KING);
        // Set rook
        updateBitboard(to, Piece.ROOK, white);
        updateMailbox(to, Piece.ROOK);
    }

    private void unmakePromotionMove(int from, int to, Piece promotionPiece) {
        // Remove promoted piece
        updateBitboard(to, promotionPiece, white);
        // Put back promoting pawn
        updateMailbox(from, Piece.PAWN);
        updateBitboard(from, Piece.PAWN, white);
        // Put back captured piece
        if (state.getCaptured() != null) {
            updateBitboard(to, state.getCaptured(), !white);
        }
        // If no piece was captured, this correctly nullifies the promo square
        updateMailbox(to, state.getCaptured());
    }

    private void unmakeEnPassantMove(int from, int to) {
        // Put back capturing pawn
        updateBitboards(to, from, Piece.PAWN, white);
        updateMailbox(to, from, Piece.PAWN);
        // Add back captured pawn
        final int captureSquare = white ? to - 8 : to + 8;
        updateBitboard(captureSquare, Piece.PAWN, !white);
        updateMailbox(captureSquare, Piece.PAWN);
    }

    private void unmakeStandardMove(int from, int to, Piece piece) {
        // Put back moving piece
        updateBitboards(to, from, piece, white);
        updateMailbox(to, from, piece);
        if (state.getCaptured() != null) {
            // Add back captured piece
            updateBitboard(to, state.getCaptured(), !white);
            updateMailbox(to, state.getCaptured());
        }
    }

    /**
     * Make a 'null' move, meaning the side to move passes their turn and gives the opponent a double-move. Used exclusively
     * during null-move pruning during search.
     */
    public void makeNullMove() {
        white = !white;
        final long key = state.key ^ Key.nullMove(state.enPassantFile);
        final long[] nonPawnKeys = new long[] {state.nonPawnKeys[0], state.nonPawnKeys[1]};
        final BoardState newState = new BoardState(key, state.pawnKey, nonPawnKeys, null, -1, state.getRights(), 0);
        states[ply++] = state;
        state = newState;
    }

    /**
     * Unmake the 'null' move used during null-move pruning to try and prove a beta cut-off.
     */
    public void unmakeNullMove() {
        white = !white;
        state = states[--ply];
    }

    public void updateBitboards(int from, int to, Piece piece, boolean white) {
        final long toggleMask = Bits.of(from) | Bits.of(to);
        toggle(toggleMask, piece, white);
    }

    public void updateBitboard(int square, Piece piece, boolean white) {
        final long toggleMask = Bits.of(square);
        toggle(toggleMask, piece, white);
    }

    private void toggle(long mask, Piece type, boolean white) {
        final int pieceIndex = type.index();
        bitboards[pieceIndex] ^= mask;
        final int colourIndex = white ? Piece.WHITE_PIECES : Piece.BLACK_PIECES;
        bitboards[colourIndex] ^= mask;
    }

    private void updateKeys(int from, int to, Piece piece, boolean white) {
        final long hash = Key.piece(from, to, piece, white);
        state.key ^= hash;
        if (piece == Piece.PAWN) {
            state.pawnKey ^= hash;
        } else {
            final int colourIndex = Colour.index(white);
            state.nonPawnKeys[colourIndex] ^= hash;
        }
    }

    private void updateKeys(int square, Piece piece, boolean white) {
        final long hash = Key.piece(square, piece, white);
        state.key ^= hash;
        if (piece == Piece.PAWN) {
            state.pawnKey ^= hash;
        } else {
            final int colourIndex = Colour.index(white);
            state.nonPawnKeys[colourIndex] ^= hash;
        }
    }

    private void updateMailbox(int from, int to, Piece piece) {
        pieces[from] = null;
        pieces[to] = piece;
    }

    private void updateMailbox(int square, Piece piece) {
        pieces[square] = piece;
    }

    public void removeKing(boolean white) {
        final int pieceIndex = Piece.KING.index;
        final int colourIndex = white ? Piece.WHITE_PIECES : Piece.BLACK_PIECES;
        final long toggleMask = bitboards[pieceIndex] & bitboards[colourIndex];
        bitboards[pieceIndex] ^= toggleMask;
        bitboards[colourIndex] ^= toggleMask;
    }

    public void addKing(int kingSquare, boolean white) {
        final long toggleMask = Bits.of(kingSquare);
        final int pieceIndex = Piece.KING.index;
        final int colourIndex = white ? Piece.WHITE_PIECES : Piece.BLACK_PIECES;
        bitboards[pieceIndex] |= toggleMask;
        bitboards[colourIndex] |= toggleMask;
    }

    private int updateCastleRights(int from, int to, Piece pieceType) {
        int newRights = state.getRights();
        if (newRights == Castling.empty()) {
            // Both sides already lost castling rights, so nothing to calculate.
            return newRights;
        }
        // Any move by the king removes castling rights.
        if (Piece.KING.equals(pieceType)) {
            newRights = Castling.clearSide(newRights, white);
        }
        // Any move starting from/ending at a rook square removes castling rights for that corner.
        // Note: all of these cases need to be checked, to cover the scenario where a rook in starting position captures
        // another rook in starting position; in that case, both sides lose castling rights!
        int wk = Castling.getRook(newRights, true, true);
        if (from == wk || to == wk) {
            newRights = Castling.clearRook(newRights, true, true);
        }
        int wq = Castling.getRook(newRights, false, true);
        if (from == wq || to == wq) {
            newRights = Castling.clearRook(newRights, false, true);
        }
        int bk = Castling.getRook(newRights, true, false);
        if (from == bk || to == bk) {
            newRights = Castling.clearRook(newRights, true, false);
        }
        int bq = Castling.getRook(newRights, false, false);
        if (from == bq || to == bq) {
            newRights = Castling.clearRook(newRights, false, false);
        }
        return newRights;
    }

    public Piece pieceAt(int square) {
        return pieces[square];
    }

    public boolean isCapture(Move move) {
        return move.isEnPassant() || pieceAt(move.to()) != null;
    }

    public boolean isQuiet(Move move) {
        return !move.isPromotion() && !isCapture(move);
    }

    public long getPawns(boolean white) {
        final int colourIndex = white ? Piece.WHITE_PIECES : Piece.BLACK_PIECES;
        return bitboards[Piece.PAWN.index] & bitboards[colourIndex];
    }

    public long getKnights(boolean white) {
        final int colourIndex = white ? Piece.WHITE_PIECES : Piece.BLACK_PIECES;
        return bitboards[Piece.KNIGHT.index] & bitboards[colourIndex];
    }

    public long getBishops(boolean white) {
        final int colourIndex = white ? Piece.WHITE_PIECES : Piece.BLACK_PIECES;
        return bitboards[Piece.BISHOP.index] & bitboards[colourIndex];
    }

    public long getRooks(boolean white) {
        final int colourIndex = white ? Piece.WHITE_PIECES : Piece.BLACK_PIECES;
        return bitboards[Piece.ROOK.index] & bitboards[colourIndex];
    }

    public long getQueens(boolean white) {
        final int colourIndex = white ? Piece.WHITE_PIECES : Piece.BLACK_PIECES;
        return bitboards[Piece.QUEEN.index] & bitboards[colourIndex];
    }

    public long getKing(boolean white) {
        final int colourIndex = white ? Piece.WHITE_PIECES : Piece.BLACK_PIECES;
        return bitboards[Piece.KING.index] & bitboards[colourIndex];
    }

    public long getPieces(boolean white) {
        return bitboards[white ? Piece.WHITE_PIECES : Piece.BLACK_PIECES];
    }

    public void setPawns(long pawns) {
        this.bitboards[Piece.PAWN.index] = pawns;
    }

    public void setKnights(long knights) {
        this.bitboards[Piece.KNIGHT.index] = knights;
    }

    public void setBishops(long bishops) {
        this.bitboards[Piece.BISHOP.index] = bishops;
    }

    public void setRooks(long rooks) {
        this.bitboards[Piece.ROOK.index] = rooks;
    }

    public void setQueens(long queens) {
        this.bitboards[Piece.QUEEN.index] = queens;
    }

    public void setKings(long kings) {
        this.bitboards[Piece.KING.index] = kings;
    }

    public void setWhitePieces(long whitePieces) {
        this.bitboards[Piece.WHITE_PIECES] = whitePieces;
    }

    public void setBlackPieces(long blackPieces) {
        this.bitboards[Piece.BLACK_PIECES] = blackPieces;
    }

    public void setBitboards(long[] bitboards) {
        this.bitboards = bitboards;
    }

    public void setPieces(Piece[] pieces) {
        this.pieces = pieces;
    }

    public void setWhite(boolean white) {
        this.white = white;
    }

    public void setState(BoardState state) {
        this.state = state;
    }

    public void setStates(BoardState[] states) {
        this.states = states;
    }

    public void setMoves(Move[] moves) {
        this.moves = moves;
    }

    public long getPawns() {
        return bitboards[Piece.PAWN.index];
    }

    public long getKnights() {
        return bitboards[Piece.KNIGHT.index];
    }

    public long getBishops() {
        return bitboards[Piece.BISHOP.index];
    }

    public long getRooks() {
        return bitboards[Piece.ROOK.index];
    }

    public long getQueens() {
        return bitboards[Piece.QUEEN.index];
    }

    public long getKings() {
        return bitboards[Piece.KING.index];
    }

    public long getWhitePieces() {
        return bitboards[Piece.WHITE_PIECES];
    }

    public long getBlackPieces() {
        return bitboards[Piece.BLACK_PIECES];
    }

    public long getOccupied() {
        return bitboards[Piece.WHITE_PIECES] | bitboards[Piece.BLACK_PIECES];
    }

    public Piece[] getPieces() {
        return pieces;
    }

    public boolean isWhite() {
        return white;
    }

    public BoardState getState() {
        return state;
    }

    public BoardState[] getStates() {
        return states;
    }

    public Move[] getMoves() {
        return moves;
    }

    public int getPly() {
        return ply;
    }

    public long key() {
        return state.getKey();
    }

    public long pawnKey() {
        return state.getPawnKey();
    }

    public long[] nonPawnKeys() {
        return state.nonPawnKeys;
    }

    public void setVariant(ChessVariant variant) {
        this.variant = variant;
    }

    public ChessVariant variant() {
        return variant;
    }

    public int kingSquare(boolean white) {
        final long kings = getKing(white);
        final long pieces = getPieces(white);
        return Bits.next(kings & pieces);
    }

    public long getPieces(Piece piece, boolean white) {
        return switch (piece) {
            case PAWN -> getPawns(white);
            case KNIGHT -> getKnights(white);
            case BISHOP -> getBishops(white);
            case ROOK -> getRooks(white);
            case QUEEN -> getQueens(white);
            case KING -> getKing(white);
        };
    }


    public boolean hasPiecesRemaining(boolean white) {
        return white ?
                (getKnights(true) != 0 || getBishops(true) != 0 || getRooks(true) != 0 || getQueens(true) != 0) :
                (getKnights(false) != 0 || getBishops(false) != 0 || getRooks(false) != 0 || getQueens(false) != 0);
    }

    /** Tests whether a specified player has a bishop pair (two bishops on different color squares).
     * @param white the colour of the player to check
     * @return true if the board has a bishop pair of the specified player, false otherwise
     */
    public boolean hasBishopPair(boolean white) {
        final long bishops = getBishops(white);
        final long bishopsOnWhiteSquares = bishops & Square.WHITE;
        return bishops!=bishopsOnWhiteSquares && bishopsOnWhiteSquares!=0;
    }


    public static Board from(String fen) {
        return FEN.toBoard(fen);
    }

    private void checkMaxPly() {
        if (ply >= states.length) {
            BoardState[] newStates = new BoardState[states.length + 64];
            System.arraycopy(states, 0, newStates, 0, states.length);

            Move[] newMoves = new Move[moves.length + 64];
            System.arraycopy(moves, 0, newMoves, 0, moves.length);

            states = newStates;
            moves = newMoves;
        }
    }

    public Board copy() {
        final Board newBoard = new Board();
        newBoard.setPawns(this.getPawns());
        newBoard.setKnights(this.getKnights());
        newBoard.setBishops(this.getBishops());
        newBoard.setRooks(this.getRooks());
        newBoard.setQueens(this.getQueens());
        newBoard.setKings(this.getKings());
        newBoard.setWhitePieces(this.getWhitePieces());
        newBoard.setBlackPieces(this.getBlackPieces());
        newBoard.setWhite(this.isWhite());
        newBoard.setState(this.getState().copy());
        BoardState[] newStates = new BoardState[this.getStates().length];
        for (int i = 0; i < this.getStates().length; i++) {
            if (this.getStates()[i] == null) {
                break;
            }
            newStates[i] = this.getStates()[i].copy();
        }
        newBoard.setStates(newStates);
        Move[] newMoves = new Move[this.getMoves().length];
        for (int i = 0; i < this.getMoves().length; i++) {
            if (this.getMoves()[i] == null) {
                break;
            }
            newMoves[i] = new Move(this.getMoves()[i].value());
        }
        newBoard.setMoves(newMoves);
        newBoard.setPieces(Arrays.copyOf(this.getPieces(), this.getPieces().length));
        newBoard.ply = this.ply;
        newBoard.variant = this.variant;
        return newBoard;
    }

    @Deprecated(forRemoval = true)
    /** @deprecated
     * Prints the board to the standard output.
     *  Use {@link BoardPrinter#print(Board))} or {@link #toString()} instead.
     */
    public void print() {
    	System.out.println(this);
    }

	@Override
	public String toString() {
		final StringBuilder builder = new StringBuilder();
        new BoardPrinter(BoardPrinter.getConsumer(builder)).print(this);
		builder.append('\n');
		builder.append(white ? "White" : "Black");
        builder.append(" to move");
		return builder.toString();
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy