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

io.github.wolfraam.chessgame.board.Board Maven / Gradle / Ivy

The newest version!
package io.github.wolfraam.chessgame.board;

import io.github.wolfraam.chessgame.move.castle.CastleMoveType;
import java.io.Serializable;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;

/**
 * The chess board.
 */
public class Board implements Serializable, Cloneable {

    public static Board fromFen(final String fen) {
        final Board board = new Board(new BoardData());
        final String[] fenParts = fen.split(" ");
        if (fenParts.length != 6) {
            throw new IllegalArgumentException("Invalid fen");
        }

        int squareIndex = 0;
        for (final char c : fenParts[0].toCharArray()) {
            if (c != '/') {
                if (Character.isDigit(c)) {
                    squareIndex += Integer.parseInt(String.valueOf(c));
                } else {
                    final Square square = Square.FEN_SQUARE_LIST.get(squareIndex);
                    board.boardData.putPieceOnSquare(square, Piece.fromFenNotation(c));
                    squareIndex++;
                }
            }
        }

        board.sideToMove = "w".equals(fenParts[1]) ? Side.WHITE : Side.BLACK;

        if (fenParts[2].contains("k")
                && board.boardData.getPiece(Square.E8) == Piece.BLACK_KING
                && board.boardData.getPiece(Square.H8) == Piece.BLACK_ROOK) {
            board.allowedCastleMoveTypes.add(CastleMoveType.BLACK_KING_SIDE);
        }
        if (fenParts[2].contains("q")
                && board.boardData.getPiece(Square.E8) == Piece.BLACK_KING
                && board.boardData.getPiece(Square.A8) == Piece.BLACK_ROOK) {
            board.allowedCastleMoveTypes.add(CastleMoveType.BLACK_QUEEN_SIDE);
        }
        if (fenParts[2].contains("K")
                && board.boardData.getPiece(Square.E1) == Piece.WHITE_KING
                && board.boardData.getPiece(Square.H1) == Piece.WHITE_ROOK) {
            board.allowedCastleMoveTypes.add(CastleMoveType.WHITE_KING_SIDE);
        }
        if (fenParts[2].contains("Q")
                && board.boardData.getPiece(Square.E1) == Piece.WHITE_KING
                && board.boardData.getPiece(Square.A1) == Piece.WHITE_ROOK) {
            board.allowedCastleMoveTypes.add(CastleMoveType.WHITE_QUEEN_SIDE);
        }

        if (!fenParts[3].equals("-")) {
            board.enPassantTarget = Square.fromName(fenParts[3]);
        }

        board.halfMoveCount = Integer.parseInt(fenParts[4]);
        board.fullMoveCount = Integer.parseInt(fenParts[5]);
        board.initialFen = board.getFen();

        board.updateHistory();
        return board;
    }

    public static Board fromInitialPosition() {
        final Board board = new Board(new BoardData());

        board.boardData.putPieceOnSquare(Square.A1, Piece.WHITE_ROOK);
        board.boardData.putPieceOnSquare(Square.B1, Piece.WHITE_KNIGHT);
        board.boardData.putPieceOnSquare(Square.C1, Piece.WHITE_BISHOP);
        board.boardData.putPieceOnSquare(Square.D1, Piece.WHITE_QUEEN);
        board.boardData.putPieceOnSquare(Square.E1, Piece.WHITE_KING);
        board.boardData.putPieceOnSquare(Square.F1, Piece.WHITE_BISHOP);
        board.boardData.putPieceOnSquare(Square.G1, Piece.WHITE_KNIGHT);
        board.boardData.putPieceOnSquare(Square.H1, Piece.WHITE_ROOK);

        board.boardData.putPieceOnSquare(Square.A2, Piece.WHITE_PAWN);
        board.boardData.putPieceOnSquare(Square.B2, Piece.WHITE_PAWN);
        board.boardData.putPieceOnSquare(Square.C2, Piece.WHITE_PAWN);
        board.boardData.putPieceOnSquare(Square.D2, Piece.WHITE_PAWN);
        board.boardData.putPieceOnSquare(Square.E2, Piece.WHITE_PAWN);
        board.boardData.putPieceOnSquare(Square.F2, Piece.WHITE_PAWN);
        board.boardData.putPieceOnSquare(Square.G2, Piece.WHITE_PAWN);
        board.boardData.putPieceOnSquare(Square.H2, Piece.WHITE_PAWN);

        board.boardData.putPieceOnSquare(Square.A8, Piece.BLACK_ROOK);
        board.boardData.putPieceOnSquare(Square.B8, Piece.BLACK_KNIGHT);
        board.boardData.putPieceOnSquare(Square.C8, Piece.BLACK_BISHOP);
        board.boardData.putPieceOnSquare(Square.D8, Piece.BLACK_QUEEN);
        board.boardData.putPieceOnSquare(Square.E8, Piece.BLACK_KING);
        board.boardData.putPieceOnSquare(Square.F8, Piece.BLACK_BISHOP);
        board.boardData.putPieceOnSquare(Square.G8, Piece.BLACK_KNIGHT);
        board.boardData.putPieceOnSquare(Square.H8, Piece.BLACK_ROOK);

        board.boardData.putPieceOnSquare(Square.A7, Piece.BLACK_PAWN);
        board.boardData.putPieceOnSquare(Square.B7, Piece.BLACK_PAWN);
        board.boardData.putPieceOnSquare(Square.C7, Piece.BLACK_PAWN);
        board.boardData.putPieceOnSquare(Square.D7, Piece.BLACK_PAWN);
        board.boardData.putPieceOnSquare(Square.E7, Piece.BLACK_PAWN);
        board.boardData.putPieceOnSquare(Square.F7, Piece.BLACK_PAWN);
        board.boardData.putPieceOnSquare(Square.G7, Piece.BLACK_PAWN);
        board.boardData.putPieceOnSquare(Square.H7, Piece.BLACK_PAWN);

        board.sideToMove = Side.WHITE;
        board.allowedCastleMoveTypes.addAll(EnumSet.allOf(CastleMoveType.class));

        board.halfMoveCount = 0;
        board.fullMoveCount = 1;
        board.initialFen = board.getFen();

        board.updateHistory();

        return board;
    }

    private final EnumSet allowedCastleMoveTypes = EnumSet.noneOf(CastleMoveType.class);
    private final BoardData boardData;
    private Square enPassantTarget;
    private final Map fen2Repetitions = new HashMap<>();
    private int fullMoveCount;
    private int halfMoveCount;
    private String initialFen;
    private Side sideToMove;

    private Board(final BoardData boardData) {
        this.boardData = boardData;
    }

    public boolean canCastle(final CastleMoveType castleMoveType) {
        return allowedCastleMoveTypes.contains(castleMoveType);
    }

    @Override
    @SuppressWarnings("all")
    public Board clone() {
        final Board board = new Board(boardData.clone());
        board.allowedCastleMoveTypes.addAll(allowedCastleMoveTypes);
        board.enPassantTarget = enPassantTarget;
        board.fullMoveCount = fullMoveCount;
        board.halfMoveCount = halfMoveCount;
        board.initialFen = initialFen;
        board.fen2Repetitions.putAll(fen2Repetitions);
        board.sideToMove = sideToMove;

        return board;
    }

    public String getASCII() {
        final StringBuilder stringBuilder = new StringBuilder();
        for (final Square square : Square.FEN_SQUARE_LIST) {
            final Piece piece = boardData.getPiece(square);
            if (piece != null) {
                stringBuilder.append(piece.fenCharacter);
            } else {
                stringBuilder.append('_');
            }
            if (square.x == 7) {
                stringBuilder.append('\n');
            }
        }
        return stringBuilder.toString();
    }

    public String getFen() {
        return getFen(true);
    }

    public String getFenSmall() {
        final StringBuilder stringBuilder = new StringBuilder();
        int blankCount = 0;
        for (final Square square : Square.FEN_SQUARE_LIST) {
            final Piece piece = boardData.getPiece(square);
            if (piece != null) {
                if (blankCount != 0) {
                    stringBuilder.append(blankCount);
                    blankCount = 0;
                }
                stringBuilder.append(piece.fenCharacter);
            } else {
                blankCount++;
            }
        }
        if (sideToMove == Side.WHITE) {
            stringBuilder.append('w');
        }
        return stringBuilder.toString();
    }

    public int getFullMoveCount() {
        return fullMoveCount;
    }

    public String getInitialFen() {
        return initialFen;
    }

    public Set getOccupiedSquares() {
        return boardData.getOccupiedSquares();
    }

    public Piece getPiece(final Square square) {
        return boardData.getPiece(square);
    }

    public Side getSideToMove() {
        return sideToMove;
    }

    public Set getSquares(final Piece piece) {
        return boardData.getSquares(piece);
    }

    public boolean isDrawFiftyMoveRule() {
        return 100 <= halfMoveCount;
    }

    public boolean isDrawInsufficientMaterial() {
        if (getSquares(Piece.BLACK_PAWN).isEmpty() &&
                getSquares(Piece.WHITE_PAWN).isEmpty() &&
                getSquares(Piece.BLACK_ROOK).isEmpty() &&
                getSquares(Piece.WHITE_ROOK).isEmpty() &&
                getSquares(Piece.BLACK_QUEEN).isEmpty() &&
                getSquares(Piece.WHITE_QUEEN).isEmpty()) {
            final Set blackBishopSquares = getSquares(Piece.BLACK_BISHOP);
            final Set whiteBishopSquares = getSquares(Piece.WHITE_BISHOP);

            final int blackBishopsAndKnights = blackBishopSquares.size() + getSquares(Piece.BLACK_KNIGHT).size();
            final int whiteBishopsAndKnights = whiteBishopSquares.size() + getSquares(Piece.WHITE_KNIGHT).size();

            if (blackBishopsAndKnights < 2 && whiteBishopsAndKnights < 2) {
                if (blackBishopsAndKnights == 0 || whiteBishopsAndKnights == 0) {
                    // king against king,
                    // king against king and bishop,
                    // king against king and knight
                    return true;
                } else {
                    final int blackBishops = blackBishopSquares.size();
                    final int whiteBishops = whiteBishopSquares.size();

                    // king and bishop against king and bishop, with both bishops on squares of the same color
                    if (blackBishops == 1 && whiteBishops == 1) {
                        return blackBishopSquares.iterator().next().squareColor ==
                                whiteBishopSquares.iterator().next().squareColor;
                    }
                }
            }
        }
        return false;
    }

    public boolean isDrawThreefoldRepetition() {
        return 2 < fen2Repetitions.getOrDefault(getFen(false), 0);
    }

    public boolean isEnPassant(final Square from, final Square to) {
        if (to == enPassantTarget) {
            final Piece fromPiece = getPiece(from);
            if (fromPiece.pieceType == PieceType.PAWN) {
                if (from.x != to.x) {
                    return squareIsEmpty(to);
                }
            }
        }
        return false;
    }

    public Piece playMove(final Square from, final Square to, final PieceType promotion, final boolean updateHistory) {
        final boolean isEnPassant = isEnPassant(from, to);

        final Piece piece = boardData.getPiece(from);
        boardData.removePieceFromSquare(from);
        Piece capturedPiece = boardData.getPiece(to);

        if (isEnPassant) {
            final Square capturedPawnSquare = Square.fromCoordinates(to.x, from.y);
            capturedPiece = boardData.getPiece(capturedPawnSquare);
            boardData.removePieceFromSquare(capturedPawnSquare);
        } else if (capturedPiece != null) {
            boardData.removePieceFromSquare(to);
        }

        if (promotion != null) {
            boardData.putPieceOnSquare(to, Piece.fromPieceTypeAndSide(promotion, piece.side));
        } else {
            boardData.putPieceOnSquare(to, piece);
        }

        final CastleMoveType castleMoveType = CastleMoveType.determine(from, to, piece);
        if (castleMoveType != null) {
            final Piece rookPiece = boardData.getPiece(castleMoveType.rookFrom);
            boardData.removePieceFromSquare(castleMoveType.rookFrom);
            boardData.putPieceOnSquare(castleMoveType.rookTo, rookPiece);
        }

        if (piece.pieceType == PieceType.PAWN && Math.abs(from.y - to.y) == 2) {
            enPassantTarget = Square.fromCoordinates(to.x, to.y + (sideToMove == Side.WHITE ? -1 : 1));
        } else {
            enPassantTarget = null;
        }

        if ((piece == Piece.WHITE_ROOK && from == Square.A1) || (capturedPiece == Piece.WHITE_ROOK && to == Square.A1) || piece == Piece.WHITE_KING) {
            allowedCastleMoveTypes.remove(CastleMoveType.WHITE_QUEEN_SIDE);
        }
        if ((piece == Piece.WHITE_ROOK && from == Square.H1) || (capturedPiece == Piece.WHITE_ROOK && to == Square.H1) || piece == Piece.WHITE_KING) {
            allowedCastleMoveTypes.remove(CastleMoveType.WHITE_KING_SIDE);
        }
        if ((piece == Piece.BLACK_ROOK && from == Square.A8) || (capturedPiece == Piece.BLACK_ROOK && to == Square.A8) || piece == Piece.BLACK_KING) {
            allowedCastleMoveTypes.remove(CastleMoveType.BLACK_QUEEN_SIDE);
        }
        if ((piece == Piece.BLACK_ROOK && from == Square.H8) || (capturedPiece == Piece.BLACK_ROOK && to == Square.H8) || piece == Piece.BLACK_KING) {
            allowedCastleMoveTypes.remove(CastleMoveType.BLACK_KING_SIDE);
        }

        sideToMove = sideToMove.flip();
        if (sideToMove == Side.WHITE) {
            fullMoveCount++;
        }
        if (piece.pieceType == PieceType.PAWN || capturedPiece != null) {
            halfMoveCount = 0;
        } else {
            halfMoveCount++;
        }

        if (updateHistory) {
            if (piece.pieceType == PieceType.PAWN) {
                fen2Repetitions.clear();
            }
            updateHistory();
        }

        return capturedPiece;
    }

    public  T playMoveAndRollBack(final Square from, final Square to, final PieceType promotion, final Supplier supplier) {

        // Store data
        final EnumSet currentAllowedCastleMoveTypes = allowedCastleMoveTypes.clone();

        final Square currentEnPassantTarget = enPassantTarget;
        final int currentFullMoveCount = fullMoveCount;
        final int currenHalfMoveCount = halfMoveCount;

        // Play Move
        final boolean isEnPassant = isEnPassant(from, to);
        final Piece piece = boardData.getPiece(from);
        final Piece capturedPiece = playMove(from, to, promotion, false);

        // Call supplier
        final T returnValue = supplier.get();

        // Roll Back
        boardData.removePieceFromSquare(to);
        boardData.putPieceOnSquare(from, piece);

        if (isEnPassant) {
            final Square capturedPawnSquare = Square.fromCoordinates(to.x, from.y);
            boardData.putPieceOnSquare(capturedPawnSquare, capturedPiece);
        } else {
            if (capturedPiece != null) {
                boardData.putPieceOnSquare(to, capturedPiece);
            }
        }

        final CastleMoveType castleMoveType = CastleMoveType.determine(from, to, piece);
        if (castleMoveType != null) {
            final Piece rookPiece = boardData.getPiece(castleMoveType.rookTo);
            boardData.removePieceFromSquare(castleMoveType.rookTo);
            boardData.putPieceOnSquare(castleMoveType.rookFrom, rookPiece);
        }

        sideToMove = sideToMove.flip();

        allowedCastleMoveTypes.clear();
        allowedCastleMoveTypes.addAll(currentAllowedCastleMoveTypes);
        enPassantTarget = currentEnPassantTarget;
        fullMoveCount = currentFullMoveCount;
        halfMoveCount = currenHalfMoveCount;

        return returnValue;
    }

    public boolean squareIsEmpty(final Square square) {
        return boardData.getPiece(square) == null;
    }

    private String getFen(final boolean includeCounters) {
        final StringBuilder stringBuilder = new StringBuilder();
        int blankCount = 0;
        for (final Square square : Square.FEN_SQUARE_LIST) {
            final Piece piece = boardData.getPiece(square);
            if (piece != null) {
                if (blankCount != 0) {
                    stringBuilder.append(blankCount);
                    blankCount = 0;
                }
                stringBuilder.append(piece.fenCharacter);
            } else {
                blankCount++;
            }
            if (square.x == 7) {
                if (blankCount != 0) {
                    stringBuilder.append(blankCount);
                    blankCount = 0;
                }
                if (square.y != 0) {
                    stringBuilder.append('/');
                }
            }
        }
        stringBuilder.append(' ');
        if (sideToMove == Side.BLACK) {
            stringBuilder.append('b');
        } else if (sideToMove == Side.WHITE) {
            stringBuilder.append('w');
        }

        stringBuilder.append(' ');
        if (allowedCastleMoveTypes.contains(CastleMoveType.WHITE_KING_SIDE)) {
            stringBuilder.append('K');
        }
        if (allowedCastleMoveTypes.contains(CastleMoveType.WHITE_QUEEN_SIDE)) {
            stringBuilder.append('Q');
        }
        if (allowedCastleMoveTypes.contains(CastleMoveType.BLACK_KING_SIDE)) {
            stringBuilder.append('k');
        }
        if (allowedCastleMoveTypes.contains(CastleMoveType.BLACK_QUEEN_SIDE)) {
            stringBuilder.append('q');
        }
        if (allowedCastleMoveTypes.isEmpty()) {
            stringBuilder.append('-');
        }

        stringBuilder.append(' ');

        if (enPassantTarget == null) {
            stringBuilder.append('-');
        } else {
            stringBuilder.append(enPassantTarget.name);
        }

        if (includeCounters) {
            stringBuilder.append(' ');
            stringBuilder.append(halfMoveCount);

            stringBuilder.append(' ');
            stringBuilder.append(fullMoveCount);
        }

        return stringBuilder.toString();
    }

    private void updateHistory() {
        final String fen = getFen(false);
        fen2Repetitions.compute(fen, (s, integer) -> integer == null ? 1 : integer + 1);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy