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

com.kelseyde.calvin.movegen.MoveGenerator Maven / Gradle / Ivy

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

import com.kelseyde.calvin.board.*;

import java.util.ArrayList;
import java.util.List;

/**
 * Generates all the legal moves in a given position.
 * Using a hybrid of pseudo-legal and legal move generation: first we calculate the bitboards for checking pieces and
 * pinned pieces. If there is a check, we filter out all moves that do not resolve the check. Finally, we filter out all
 * moves that leave the king in (a new) check.
 */
public class MoveGenerator {

    private int checkersCount;
    private long checkersMask;
    private long pinMask;
    private final long[] pinRayMasks = new long[64];
    private long captureMask;
    private long pushMask;
    private MoveFilter filter;
    private boolean white;

    private long pawns;
    private long knights;
    private long bishops;
    private long rooks;
    private long queens;
    private long king;

    private List legalMoves;

    public List generateMoves(Board board) {
        return generateMoves(board, MoveFilter.ALL);
    }

    public List generateMoves(Board board, MoveFilter filter) {

        white = board.isWhite();

        // Initialise piece fields
        initPieces(board, white);

        final int kingSquare = Bits.next(king);
        this.filter = filter;

        // Initialize capture and push masks
        captureMask = Square.ALL;
        pushMask = Square.ALL;

        // Calculate pins and checks
        calculatePins(board, white);
        checkersMask = calculateCheckers(board, kingSquare);
        checkersCount = Bits.count(checkersMask);

        final int estimatedLegalMoves = estimateLegalMoves();
        legalMoves = new ArrayList<>(estimatedLegalMoves);

        if (checkersCount > 0 && filter == MoveFilter.QUIET) {
            return legalMoves;
        }

        // Generate king moves first
        generateKingMoves(board);

        if (checkersCount == 2) {
            // If we are in double-check, the only legal moves are king moves
            return legalMoves;
        }

        if (checkersCount == 1) {
            // If only one checker, we can evade check by capturing it
            captureMask = checkersMask;

            final int checkerSquare = Bits.next(checkersMask);
            if (board.pieceAt(checkerSquare).isSlider()) {
                // If the piece giving check is a slider, we can evade check by blocking it
                pushMask = Ray.between(checkerSquare, kingSquare);
            } else {
                // If the piece is not a slider, we can only evade check by capturing it
                // Therefore all non-capture 'push' moves are illegal.
                pushMask = Square.NONE;
            }
        }

        // Generate all the other legal moves using the capture and push masks
        generatePawnMoves(board);
        generateKnightMoves(board);
        generateAllSlidingMoves(board);
        generateCastlingMoves(board);

        return legalMoves;

    }

    /**
     * Checks if the specified side is in check.
     *
     * @param board   The current board state.
     * @param white Indicates whether the side in question is white.
     * @return True if the specified side is in check, otherwise false.
     */
    public boolean isCheck(Board board, boolean white) {
        final long king = board.getKing(white);
        return isAttacked(board, white, king);
    }

    public boolean isCheck(Board board) {
        return isCheck(board, board.isWhite());
    }

    private void generatePawnMoves(Board board) {
        if (pawns == 0) return;

        final long opponents = board.getPieces(!white);
        final long occupied = board.getOccupied();
        final int opponentKing = Bits.next(board.getKing(!white));

        // Precompute attack and filter masks
        final long opponentAttackMask = Attacks.pawnAttacks(Bits.of(opponentKing), !white);
        final long filterMask = getFilterMask(opponents, opponentAttackMask);

        if (filterMask == Square.NONE) return;

        // Single and double pawn pushes
        if (filter != MoveFilter.CAPTURES_ONLY) {
            generatePawnPushes(occupied, filterMask);
        }

        // Pawn captures, en passant, and promotions
        if (filter != MoveFilter.QUIET) {
            generatePawnCaptures(opponents, filterMask);
            generatePromotions(opponents, occupied);
            generateEnPassant(board);
        }
    }

    private void generatePawnPushes(long occupied, long filterMask) {
        // Single and double pawn pushes combined
        long singleMoves = Attacks.pawnSingleMoves(pawns, occupied, white) & pushMask & filterMask;
        long doubleMoves = Attacks.pawnDoubleMoves(pawns, occupied, white) & pushMask & filterMask;

        while (singleMoves != 0) {
            final int to = Bits.next(singleMoves);
            final int from = white ? to - 8 : to + 8;
            if (!isPinned(from) || isMovingAlongPinRay(from, to)) {
                legalMoves.add(new Move(from, to));
            }
            singleMoves = Bits.pop(singleMoves);
        }

        while (doubleMoves != 0) {
            final int to = Bits.next(doubleMoves);
            final int from = white ? to - 16 : to + 16;
            if (!isPinned(from) || isMovingAlongPinRay(from, to)) {
                legalMoves.add(new Move(from, to, Move.PAWN_DOUBLE_MOVE_FLAG));
            }
            doubleMoves = Bits.pop(doubleMoves);
        }
    }

    private void generatePawnCaptures(long opponents, long filterMask) {
        long leftCaptures = Attacks.pawnLeftCaptures(pawns, opponents, white) & captureMask & filterMask;
        long rightCaptures = Attacks.pawnRightCaptures(pawns, opponents, white) & captureMask & filterMask;

        while (leftCaptures != 0) {
            final int to = Bits.next(leftCaptures);
            final int from = white ? to - 7 : to + 9;
            if (!isPinned(from) || isMovingAlongPinRay(from, to)) {
                legalMoves.add(new Move(from, to));
            }
            leftCaptures = Bits.pop(leftCaptures);
        }

        while (rightCaptures != 0) {
            final int to = Bits.next(rightCaptures);
            final int from = white ? to - 9 : to + 7;
            if (!isPinned(from) || isMovingAlongPinRay(from, to)) {
                legalMoves.add(new Move(from, to));
            }
            rightCaptures = Bits.pop(rightCaptures);
        }
    }

    private void generatePromotions(long opponents, long occupied) {
        final long pushPromotions = Attacks.pawnPushPromotions(pawns, occupied, white) & pushMask;
        final long leftCapturePromotions = Attacks.pawnLeftCapturePromotions(pawns, opponents, white) & (captureMask | pushMask);
        final long rightCapturePromotions = Attacks.pawnRightCapturePromotions(pawns, opponents, white) & (captureMask | pushMask);

        generatePromotionMoves(pushPromotions, 8, 8);
        generatePromotionMoves(leftCapturePromotions, 7, 9);
        generatePromotionMoves(rightCapturePromotions, 9, 7);
    }

    private void generatePromotionMoves(long promotionMask, int offsetWhite, int offsetBlack) {
        while (promotionMask != 0) {
            final int to = Bits.next(promotionMask);
            final int from = white ? to - offsetWhite : to + offsetBlack;
            if (!isPinned(from) || isMovingAlongPinRay(from, to)) {
                legalMoves.addAll(getPromotionMoves(from, to));
            }
            promotionMask = Bits.pop(promotionMask);
        }
    }

    private void generateEnPassant(Board board) {
        if (board.getState().getEnPassantFile() < 0) return;

        final long enPassantFile = File.toBitboard(board.getState().getEnPassantFile());
        final long leftEnPassants = Attacks.pawnLeftEnPassants(pawns, enPassantFile, white);
        final long rightEnPassants = Attacks.pawnRightEnPassants(pawns, enPassantFile, white);

        generateEnPassantMoves(board, leftEnPassants, 7, 9);
        generateEnPassantMoves(board, rightEnPassants, 9, 7);
    }

    private void generateEnPassantMoves(Board board, long enPassantMask, int offsetWhite, int offsetBlack) {
        while (enPassantMask != 0) {
            final int to = Bits.next(enPassantMask);
            final int from = white ? to - offsetWhite : to + offsetBlack;
            final Move move = new Move(from, to, Move.EN_PASSANT_FLAG);
            if (!leavesKingInCheck(board, move, white)) {
                legalMoves.add(move);
            }
            enPassantMask = Bits.pop(enPassantMask);
        }
    }

    private long getFilterMask(long opponents, long opponentAttackMask) {
        return checkersCount > 0 ? captureMask | pushMask : switch (filter) {
            case ALL -> Square.ALL;
            case CAPTURES_ONLY -> opponents;
            case NOISY -> opponents | opponentAttackMask;
            case QUIET -> ~opponents & ~opponentAttackMask;
        };
    }

    private void generateKnightMoves(Board board) {
        if (knights == 0) return;
        final long opponents = board.getPieces(!white);
        final int opponentKing = Bits.next(board.getKing(!white));

        // Initialize filter mask based on move filter type
        final long filterMask = checkersCount > 0 ? captureMask | pushMask : switch (filter) {
            case ALL -> Square.ALL;
            case CAPTURES_ONLY -> opponents;
            case NOISY -> opponents | Attacks.knightAttacks(opponentKing);
            case QUIET -> ~opponents & ~Attacks.knightAttacks(opponentKing);
        };
        if (filterMask == Square.NONE) {
            return;
        }

        // Exclude pinned knights from generating moves
        long unpinnedKnights = knights & ~pinMask;

        // Generate legal knight moves
        while (unpinnedKnights != 0) {
            final int from = Bits.next(unpinnedKnights);
            long possibleMoves = getKnightAttacks(board, from, white) & (pushMask | captureMask) & filterMask;
            while (possibleMoves != 0) {
                final int to = Bits.next(possibleMoves);
                legalMoves.add(new Move(from, to));
                possibleMoves = Bits.pop(possibleMoves);
            }
            unpinnedKnights = Bits.pop(unpinnedKnights);
        }
    }

    private void generateKingMoves(Board board) {
        final int from = Bits.next(king);
        final long friendlies = board.getPieces(white);
        final long opponents = board.getPieces(!white);

        final long filterMask = checkersCount > 0 ? captureMask | pushMask : switch (filter) {
            case ALL -> Square.ALL;
            case CAPTURES_ONLY, NOISY -> opponents;
            case QUIET -> ~opponents;
        };
        if (filterMask == Square.NONE) {
            return;
        }

        long kingMoves = Attacks.kingAttacks(from) & ~friendlies & filterMask;

        // Temporarily remove the king from the board
        board.removeKing(white);

        // Generate legal king moves
        while (kingMoves != 0) {
            final int to = Bits.next(kingMoves);
            // Check if the end square is not attacked by the opponent
            if (!isAttacked(board, white, Bits.of(to))) {
                legalMoves.add(new Move(from, to));
            }
            kingMoves = Bits.pop(kingMoves);
        }

        // Restore the king to its original position on the board
        board.addKing(from, white);
    }

    private void generateCastlingMoves(Board board) {
        if ((filter != MoveFilter.ALL && filter != MoveFilter.QUIET)
                || checkersMask != 0) {
            return;
        }
        final int from = Bits.next(king);
        final long occupied = board.getOccupied();

        final boolean isKingsideAllowed = Castling.kingsideAllowed(board.getState().rights, white);
        if (isKingsideAllowed) {
            generateCastlingMove(board, white, true, from, occupied);
        }

        final boolean isQueensideAllowed = Castling.queensideAllowed(board.getState().rights, white);
        if (isQueensideAllowed) {
            generateCastlingMove(board, white, false, from, occupied);
        }

    }

    private void generateCastlingMove(Board board, boolean white, boolean kingside, int kingSquare, long occupied) {
        switch (board.variant()) {
            case STANDARD -> generateStandardCastlingMove(board, white, kingside, kingSquare, occupied);
            case CHESS960 -> generateChess960CastlingMove(board, white, kingside, kingSquare, occupied);
        }
    }

    private void generateStandardCastlingMove(Board board, boolean white, boolean kingside, int kingSquare, long occupied) {
        final long travelSquares = Castling.Standard.travelSquares(white, kingside);
        final long blockedSquares = travelSquares & occupied;
        final long safeSquares = Castling.Standard.safeSquares(white, kingside);
        if (blockedSquares == 0 && !isAttacked(board, white, safeSquares)) {
            int to = getCastleEndSquare(board, white, kingside);
            legalMoves.add(new Move(kingSquare, to, Move.CASTLE_FLAG));
        }
    }

    private void generateChess960CastlingMove(Board board, boolean white, boolean kingside, int kingSquare, long occupied) {
        final int rookSquare = Castling.getRook(board.getState().rights, kingside, white);
        final long rookSquareBit = Bits.of(rookSquare); 
        if ((pinMask & rookSquareBit) != 0) {
            // can't castle if rook is pinned 
        	return;
        }
        final int kingDst = Castling.kingTo(kingside, white);
        final int rookDst = Castling.rookTo(kingside, white);

        final long kingTravelSquares = (Ray.between(kingSquare, kingDst) | Bits.of(kingDst));
        final long rookTravelSquares = (Ray.between(rookSquare, rookDst) | Bits.of(rookDst));
        // Warning : King and rook initial positions should be ignored when verifying if cells are free
        final long travelSquares = (kingTravelSquares | rookTravelSquares) & ~ (rookSquareBit | Bits.of(kingSquare));

        final long blockedSquares = travelSquares & occupied;
        final long safeSquares = Bits.of(kingSquare) | Ray.between(kingSquare, kingDst) | Bits.of(kingDst);
        if (blockedSquares == 0 && !isAttacked(board, white, safeSquares)) {
            int to = getCastleEndSquare(board, white, kingside);
            legalMoves.add(new Move(kingSquare, to, Move.CASTLE_FLAG));
        }
    }

    private void generateAllSlidingMoves(Board board) {
        if (filter == MoveFilter.ALL) {
            final long diagonalSliders = bishops | queens;
            final long orthogonalSliders = rooks | queens;
            generateSlidingMoves(board, diagonalSliders, false, true);
            generateSlidingMoves(board, orthogonalSliders, true, false);
        } else {
            generateSlidingMoves(board, bishops, false, true);
            generateSlidingMoves(board, rooks, true, false);
            generateSlidingMoves(board, queens, true, true);
        }
    }

    private void generateSlidingMoves(Board board, long sliders, boolean isOrthogonal, boolean isDiagonal) {
        final long opponents = board.getPieces(!white);
        final long occupied = board.getOccupied();
        final long friendlies = board.getPieces(white);

        // Apply move filters
        final long filterMask = checkersCount > 0 ? captureMask | pushMask : switch (filter) {
            case ALL -> Square.ALL;
            case CAPTURES_ONLY -> opponents;
            case NOISY -> getCaptureAndCheckMask(board, white, opponents, occupied, isDiagonal, isOrthogonal);
            case QUIET -> ~getCaptureAndCheckMask(board, white, opponents, occupied, isDiagonal, isOrthogonal);
        };
        if (filterMask == Square.NONE) {
            return;
        }

        while (sliders != 0) {
            final int from = Bits.next(sliders);
            long attackMask = getSlidingAttacks(from, friendlies, occupied, isDiagonal, isOrthogonal);

            attackMask &= pushMask | captureMask;
            attackMask &= filterMask;

            // Handle pinned pieces
            if (isPinned(from)) {
                attackMask &= pinRayMasks[from];
            }

            sliders = Bits.pop(sliders);
            while (attackMask != 0) {
                final int to = Bits.next(attackMask);
                legalMoves.add(new Move(from, to));
                attackMask = Bits.pop(attackMask);
            }
        }
    }

    private long getCaptureAndCheckMask(Board board, boolean white, long opponents, long occupied, boolean isDiagonal, boolean isOrthogonal) {
        final int opponentKing = Bits.next(board.getKing(!white));
        long filterMask = opponents;
        if (isDiagonal) {
            filterMask |= Attacks.bishopAttacks(opponentKing, occupied);
        }
        if (isOrthogonal) {
            filterMask |= Attacks.rookAttacks(opponentKing, occupied);
        }
        return filterMask;
    }

    public long getPawnAttacks(Board board, int square, boolean white) {
        long attackMask = 0L;
        final long squareBB = Bits.of(square);
        final long friendlies = board.getPieces(white);

        long leftCapture = white ?
                Bits.northWest(squareBB) &~ friendlies :
                Bits.southWest(squareBB) &~ friendlies;
        attackMask |= leftCapture;

        long rightCapture = white ?
                Bits.northEast(squareBB) &~ friendlies :
                Bits.southEast(squareBB) &~ friendlies;
        attackMask |= rightCapture;

        return attackMask;
    }

    public long getKnightAttacks(Board board, int square, boolean white) {
        final long friendlies = board.getPieces(white);
        return Attacks.knightAttacks(square) &~ friendlies;
    }

    public long getBishopAttacks(Board board, int square, boolean white) {
        final long occupied = board.getOccupied();
        final long friendlies = board.getPieces(white);
        return getSlidingAttacks(square, friendlies, occupied, true, false);
    }

    public long getRookAttacks(Board board, int square, boolean white) {
        final long occupied = board.getOccupied();
        final long friendlies = board.getPieces(white);
        return getSlidingAttacks(square, friendlies, occupied, false, true);
    }

    public long getQueenAttacks(Board board, int square, boolean white) {
        final long occupied = board.getOccupied();
        final long friendlies = board.getPieces(white);
        return getSlidingAttacks(square, friendlies, occupied, true, true);
    }

    public long getKingAttacks(Board board, int square, boolean white) {
        final long friendlies = board.getPieces(white);
        return Attacks.kingAttacks(square) &~ friendlies;
    }

    private long getSlidingAttacks(int square, long friendlies, long occ, boolean isDiagonal, boolean isOrthogonal) {
        long attackMask = 0L;
        if (isOrthogonal) {
            attackMask |= Attacks.rookAttacks(square, occ);
        }
        if (isDiagonal) {
            attackMask |= Attacks.bishopAttacks(square, occ);
        }
        return attackMask &~ friendlies;
    }

    private long calculateCheckers(Board board, int square) {
        final long occupied = board.getOccupied();
        final long friendlies = board.getPieces(white);
        long attackerMask = 0L;

        final long opponentPawns = board.getPawns(!white);
        if (opponentPawns != 0) {
            final long pawnAttackMask = getPawnAttacks(board, square, white);
            attackerMask |= pawnAttackMask & opponentPawns;
        }

        final long opponentKnights = board.getKnights(!white);
        if (opponentKnights != 0) {
            final long knightAttackMask = getKnightAttacks(board, square, white);
            attackerMask |= knightAttackMask & opponentKnights;
        }

        final long opponentBishops = board.getBishops(!white);
        final long opponentQueens = board.getQueens(!white);
        final long diagonalSliders = opponentBishops | opponentQueens;
        if (diagonalSliders != 0) {
            attackerMask |= getSlidingAttacks(square, friendlies, occupied, true, false) & diagonalSliders;
        }

        final long opponentRooks = board.getRooks(!white);
        final long orthogonalSliders = opponentRooks | opponentQueens;
        if (orthogonalSliders != 0) {
            attackerMask |= getSlidingAttacks(square, friendlies, occupied, false, true) & orthogonalSliders;
        }

        // King can never give check

        return attackerMask;
    }

    private boolean isAttacked(Board board, boolean white, long squareMask) {

        final long opponentPawns = board.getPawns(!white);
        if (opponentPawns != 0) {
            long pawnAttackMask = Attacks.pawnAttacks(squareMask, white);
            if ((pawnAttackMask & opponentPawns) != 0) {
                return true;
            }
        }

        final long occupied = board.getOccupied();

        while (squareMask != 0) {
            final int square = Bits.next(squareMask);

            final long opponentKnights = board.getKnights(!white);
            if (opponentKnights != 0) {
                if ((Attacks.knightAttacks(square) & opponentKnights) != 0) {
                    return true;
                }
            }

            final long opponentBishops = board.getBishops(!white);
            final long opponentQueens = board.getQueens(!white);
            final long diagonalSliders = opponentBishops | opponentQueens;
            if (diagonalSliders != 0) {
                if ((Attacks.bishopAttacks(square, occupied) & diagonalSliders) != 0) {
                    return true;
                }
            }

            final long opponentRooks = board.getRooks(!white);
            final long orthogonalSliders = opponentRooks | opponentQueens;
            if (orthogonalSliders != 0) {
                if ((Attacks.rookAttacks(square, occupied) & orthogonalSliders) != 0) {
                    return true;
                }
            }

            final long opponentKing = board.getKing(!white);
            if ((Attacks.kingAttacks(square) & opponentKing) != 0) {
                return true;
            }

            squareMask = Bits.pop(squareMask);
        }
        return false;
    }

    public void calculatePins(Board board, boolean white) {
        this.pinMask = 0L;

        final int kingSquare = Bits.next(board.getKing(white));
        final long friendlies = board.getPieces(white);
        final long opponents = board.getPieces(!white);

        long possiblePinners = 0L;

        // Calculate possible orthogonal pins
        final long orthogonalSliders = board.getRooks(!white) | board.getQueens(!white);
        if (orthogonalSliders != 0) {
            possiblePinners |= Attacks.rookAttacks(kingSquare, 0) & orthogonalSliders;
        }

        // Calculate possible diagonal pins
        final long diagonalSliders = board.getBishops(!white) | board.getQueens(!white);
        if (diagonalSliders != 0) {
            possiblePinners |= Attacks.bishopAttacks(kingSquare, 0) & diagonalSliders;
        }

        while (possiblePinners != 0) {
            final int possiblePinner = Bits.next(possiblePinners);
            final long ray = Ray.between(kingSquare, possiblePinner);

            // Skip if there are opponents between the king and the possible pinner
            if ((ray & opponents) != 0) {
                possiblePinners = Bits.pop(possiblePinners);
                continue;
            }

            final long friendliesBetween = ray & friendlies;
            // If there is exactly one friendly piece between the king and the pinner, it's pinned
            if (Bits.count(friendliesBetween) == 1) {
                int friendlySquare = Bits.next(friendliesBetween);
                this.pinMask |= friendliesBetween;
                this.pinRayMasks[friendlySquare] = ray | (Bits.of(possiblePinner));
            }

            possiblePinners = Bits.pop(possiblePinners);
        }

    }

    public long calculateThreats(Board board, boolean white) {

        long threats = 0L;
        long occ = board.getOccupied();

        long knights = board.getKnights(white);
        while (knights != 0) {
            final int square = Bits.next(knights);
            threats |= Attacks.knightAttacks(square);
            knights = Bits.pop(knights);
        }

        long bishops = board.getBishops(white) | board.getQueens(white);
        while (bishops != 0) {
            final int square = Bits.next(bishops);
            threats |= Attacks.bishopAttacks(square, occ);
            bishops = Bits.pop(bishops);
        }

        long rooks = board.getRooks(white) | board.getQueens(white);
        while (rooks != 0) {
            final int square = Bits.next(rooks);
            threats |= Attacks.rookAttacks(square, occ);
            rooks = Bits.pop(rooks);
        }

        long pawns = board.getPawns(white);
        threats |= Attacks.pawnAttacks(pawns, white);

        long king = board.getKing(white);
        final int square = Bits.next(king);
        threats |= Attacks.kingAttacks(square);

        return threats;

    }

    public boolean isPseudoLegal(Board board, Move move) {

        if (move == null)
            return false;

        final boolean white = board.isWhite();
        final int from = move.from();
        final int to = move.to();
        final Piece piece = board.pieceAt(from);
        final long occupied = board.getOccupied();

        // Can't move without a piece
        if (piece == null)
            return false;

        // Can't move from an empty square
        if (!Bits.contains(board.getPieces(white), from))
            return false;

        Piece captured = board.pieceAt(to);
        if (captured != null) {

            // Can't capture our own piece except in chess 960 castling
            if (Bits.contains(board.getPieces(white), to) && !(move.isCastling() && ChessVariant.CHESS960==board.variant()))
                return false;

            // Can't capture a king
            if (Bits.contains(board.getKings(), to))
                return false;

        }

        if (move.isCastling()) {

            // Can only castle with a king
            if (piece != Piece.KING)
                return false;

            // Must be castling on the home rank
            long rank = white ? Rank.FIRST : Rank.EIGHTH;
            if (!Bits.contains(rank, from) || !Bits.contains(rank, to))
                return false;

            int kingsideCastleSquare;
            int queensideCastleSquare;
            if (ChessVariant.CHESS960==board.variant()) {
            	kingsideCastleSquare = Castling.getRook(board.getState().rights, true, white);
            	queensideCastleSquare = Castling.getRook(board.getState().rights, false, white);
            } else {
	            kingsideCastleSquare = white ? 6 : 62;
	            queensideCastleSquare = white ? 2 : 58;
            }

            // Must be valid castling squares
            if (to != kingsideCastleSquare && to != queensideCastleSquare)
                return false;

            // Must have kingside rights
            if (to == kingsideCastleSquare && !Castling.kingsideAllowed(board.getState().rights, white))
                return false;

            // Must have queenside rights
            if (to == queensideCastleSquare && !Castling.queensideAllowed(board.getState().rights, white))
                return false;

            boolean kingside = to == kingsideCastleSquare;

            final long travelSquares;
            final long safeSquares;
            if (ChessVariant.CHESS960==board.variant()) {
	            int kingDst = Castling.kingTo(kingside, white);
	            final int rookDst = Castling.rookTo(kingside, white);
	            final long kingTravelSquares = (Ray.between(from, kingDst) | Bits.of(kingDst));
	            final long rookTravelSquares = (Ray.between(to, rookDst) | Bits.of(rookDst));
	            // Warning : King and rook initial positions should be ignored when verifying if cells are free
	            travelSquares = (kingTravelSquares | rookTravelSquares) & ~ (Bits.of(to) | Bits.of(from));
	            safeSquares = Bits.of(from) | Ray.between(from, kingDst) | Bits.of(kingDst);
           } else {
            final int rookSquare = Castling.getRook(board.getState().rights, kingside, white);
	            travelSquares = Ray.between(from, rookSquare);
	            safeSquares = Castling.Standard.safeSquares(white, kingside);
            }

            final long blockedSquares = travelSquares & occupied;
            // Can't castle through check or occupied cell
            return blockedSquares == 0 && !isAttacked(board, white, safeSquares);

        }

        if (piece == Piece.PAWN) {

            if (move.isEnPassant()) {

                // Can't en passant if there's no en passant square
                if (board.getState().getEnPassantFile() < 0)
                    return false;

                final int epSquare = white ? to - 8 : to + 8;

                // Can't en passant if there's no enemy pawn to capture
                if (!Bits.contains(board.getPawns(!white), epSquare))
                    return false;

            }

            int fromRank = Rank.of(from);
            int toRank = Rank.of(to);

            // Can't move backwards
            if ((white && fromRank >= toRank) || (!white && fromRank <= toRank))
                return false;

            // Must promote on the promo rank, and can't promote on any other rank
            long promoRank = white ? Rank.EIGHTH : Rank.FIRST;
            if (move.isPromotion() != Bits.contains(promoRank, to))
                return false;

            int fromFile = File.of(from);
            int toFile = File.of(to);

            // Pawn captures
            if (fromFile != toFile) {

                // Must capture on an adjacent file
                if (toFile != fromFile + 1 && toFile != fromFile - 1)
                    return false;

                // Must be capturing a piece
                return captured != null || move.isEnPassant();

            } else {
                // Can't capture a piece with a pawn push
                if (captured != null)
                    return false;

                if (move.isPawnDoubleMove()) {

                    // Can't double push from the wrong rank
                    long startRank = white ? Rank.SECOND : Rank.SEVENTH;
                    if (!Bits.contains(startRank, from))
                        return false;

                    // Can't double push if there's a piece in the way
                    int betweenSquare = white ? from + 8 : from - 8;
                    return !Bits.contains(occupied, betweenSquare);

                }
            }

        } else {

            // Can't make pawn-specific moves with a non-pawn
            if (move.isPawnDoubleMove() || move.isEnPassant() || move.isPromotion())
                return false;

            long attacks = switch (piece) {
                case KNIGHT ->  getKnightAttacks(board, from, white);
                case BISHOP ->  getBishopAttacks(board, from, white);
                case ROOK ->    getRookAttacks(board, from, white);
                case QUEEN ->   getQueenAttacks(board, from, white);
                case KING ->    getKingAttacks(board, from, white);
                default -> 0L;
            };

            // Must be a valid move for the piece
            return Bits.contains(attacks, to);

        }

        return true;

    }

    public boolean isLegal(Board board, Move move) {
        if (!isPseudoLegal(board, move))
            return false;

        board.makeMove(move);
        boolean legal = !isCheck(board, !board.isWhite());
        board.unmakeMove();

        return legal;
    }

    private List getPromotionMoves(int from, int to) {
        return List.of(new Move(from, to, Move.PROMOTE_TO_QUEEN_FLAG),
                        new Move(from, to, Move.PROMOTE_TO_ROOK_FLAG),
                        new Move(from, to, Move.PROMOTE_TO_BISHOP_FLAG),
                        new Move(from, to, Move.PROMOTE_TO_KNIGHT_FLAG));
    }


    private boolean leavesKingInCheck(Board board, Move move, boolean white) {
        board.makeMove(move);
        final int kingSquare = white ? Bits.next(board.getKing(true)) : Bits.next(board.getKing(false));
        final boolean isAttacked = isAttacked(board, white, Bits.of(kingSquare));
        board.unmakeMove();
        return isAttacked;
    }

    private boolean isPinned(int from) {
        return (Bits.of(from) & pinMask) != 0;
    }

    private boolean isMovingAlongPinRay(int from, int to) {
        final long pinRay = pinRayMasks[from];
        return (Bits.of(to) & pinRay) != 0;
    }

    private int getCastleEndSquare(Board board, boolean white, boolean kingside) {
        // In standard chess, the king 'to' square is the actual destination square
        // In Chess960 UCI notation, castle moves are encoded as king-captures-rook
        return switch (board.variant()) {
            case STANDARD -> kingside ? (white ? 6 : 62) : (white ? 2 : 58);
            case CHESS960 -> Castling.getRook(board.getState().getRights(), kingside, white);
        };
    }

    public long getPinMask() {
        return pinMask;
    }

    /**
     * Estimate the number of legal moves in the current position, based on the piece count and
     * the average number of legal moves per piece. Used to initialise the legal moves ArrayList
     * with a 'best guess', to reduce the number of times the ArrayList has to grow during move
     * generation, yielding a small increase in performance.
     */
    private int estimateLegalMoves() {
        return (Bits.count(pawns) * 2) +
                (Bits.count(knights) * 3) +
                (Bits.count(bishops) * 3) +
                (Bits.count(rooks) * 6) +
                (Bits.count(queens) * 9) +
                (Bits.count(king) * 3);
    }

    private void initPieces(Board board, boolean white) {
        this.pawns = board.getPawns(white);
        this.knights = board.getKnights(white);
        this.bishops = board.getBishops(white);
        this.rooks = board.getRooks(white);
        this.queens = board.getQueens(white);
        this.king = board.getKing(white);
    }

    public enum MoveFilter {
        ALL,
        NOISY,
        QUIET,
        CAPTURES_ONLY,
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy