Perft engine - king moves

Discussion of chess software programming and technical issues.

Moderator: Ras

chessbit
Posts: 9
Joined: Fri Dec 29, 2023 4:47 pm
Location: Belgium
Full name: thomas albert

Perft engine - king moves

Post by chessbit »

I'm building a perft engine (bitboards, single threaded, no hash table, just raw move generation) and in optimization phase.
Currently perft at depth 7 of initial position is ~3.8s (intel i7 8700, on current CPUs this would be lower but that's in my upcoming shopping basket :D)

The two main bottlenecks of my engine currently are king moves, specifically verify whether the king can castle, and whether the squares it can go to are legal. If I ignore these 2 steps, my perft time drops to ~3.2 so huge improvement.
I feel like there's a genius idea waiting to be found to improve this...
I was curious if you guys have some tips & tricks to make these 2 scenarios as fast as possible, and how you usually implement this in your engine(s).

If you're curious, this is what my functions look like, I think it's mostly understandable without too much context:

This function clears bits of the king attacks if they are attacked by opposing pieces

Code: Select all

void filterKingAttacks(bool side, U64 occupancy, int kingSquare, U64& kingAttacks) {
        if (!kingAttacks) {
            return;
        }

        //kingAttacks &= pawnAttackZones[!side][kingSquare][_pext_u64(pieces[!side][p], pawnZones[!side][kingSquare])];
        kingAttacks &= getPawnKingAttacks(!side, kingSquare, pieces[!side][p]);

        kingAttacks &= ~getKingAttacks(king[!side]);

        //remove king to avoid collisions, as it should not be considered when checking for threats
        PopBit(occupancy, kingSquare);

        U64 bitboard;
        bitboard = pieces[!side][n] & knightAttackZones[kingSquare];
        Bitloop(bitboard) {
            kingAttacks &= ~getKnightAttacks(SquareOf(bitboard));
        }

        bitboard = (pieces[!side][b] | pieces[!side][q]) & bishopAttackZones[kingSquare];
        Bitloop(bitboard) {
            kingAttacks &= ~getBishopAttacks(SquareOf(bitboard), occupancy);
        }

        bitboard = (pieces[!side][r] | pieces[!side][q]) & rookAttackZones[kingSquare];
        Bitloop(bitboard) {
            kingAttacks &= ~getRookAttacks(SquareOf(bitboard), occupancy);
        }
    }
A lot of ugly ifs here... basically filtering as much as possible to avoid having to check if the 2 squares between king and rook are not attacked by bishops & rooks (only need to check for bishops, rooks and queens after filtering)

Code: Select all

bool castle(bool side, int castlingSide) {
        if (!(castlingPermissions[moveCount] & CASTLING[castlingSide])) {
            return false;
        }

        if (CASTLING_OCCUPIED_SQUARES[castlingSide] & occupancies[moveCount][both]) {
            return false;
        }

        if (occupancies[moveCount][!side] & CASTLING_FORBIDDEN_SQUARES[castlingSide]) {
            return false;
        }

        if ((occupancies[moveCount][!side] ^ pieces[!side][n]) & CASTLING_FORBIDDEN_SQUARE_EXCEPT_KNIGHT[castlingSide]) {
            return false;
        }

        if ((occupancies[moveCount][!side] ^ pieces[!side][r]) & CASTLING_FORBIDDEN_SQUARE_EXCEPT_ROOK[castlingSide]) {
            return false;
        }

        if (pieces[!side][n] & CASTLING_FORBIDDEN_KNIGHT_SQUARES[castlingSide]) {
            return false;
        }

        U64 bishopAttacks = getBishopCastleAttacks(castlingSide, occupancies[moveCount][both]) & (pieces[!side][b] | pieces[!side][q]);
        U64 rookAttacks = getRookCastleAttacks(castlingSide, occupancies[moveCount][both]) & (pieces[!side][r] | pieces[!side][q]);

        return !(bishopAttacks | rookAttacks);
    }
imnoumni
Posts: 10
Joined: Sat Dec 02, 2023 4:14 am
Full name: Imno Umni

Re: Perft engine - king moves

Post by imnoumni »

To check if king can castle, do you not simply check for the required empty squares and check that squares the king travels through are not attacked? Basically,

Code: Select all

if castle_kingside && ((occupancy & castling_empties) == 0) && ((attacked_squares & castling_safeties) == 0) {
 // castle
}
chessbit
Posts: 9
Joined: Fri Dec 29, 2023 4:47 pm
Location: Belgium
Full name: thomas albert

Re: Perft engine - king moves

Post by chessbit »

imnoumni wrote: Fri Mar 14, 2025 8:17 pm To check if king can castle, do you not simply check for the required empty squares and check that squares the king travels through are not attacked? Basically,

Code: Select all

if castle_kingside && ((occupancy & castling_empties) == 0) && ((attacked_squares & castling_safeties) == 0) {
 // castle
}
Yes if you look at my code this is pretty much what I do except I filter proximity squares to prune pawns, knights and king, then I only have to do the costly rook and bishop lookups if they don't occupy these proximity squares.
Unless you have a very efficient way to know if squares are attacked, I don't see how to improve my way?

Also since I posted I improved my engine a lot. Mostly optimizing the code and some logic as well.
I am now faster in the initial position than Gigantua, however it's still a little bit faster in more advanced positions. It seems my engine is good at the starting position when the king is not too active, but that is logical considering the bottlenecks are the king moves.

I do a little under 2700ms for perft 7, while Gigantua is a little above 3000ms on my CPU.

My goal was to beat it, so I will not rest until I'm also faster in late game positions :)
imnoumni
Posts: 10
Joined: Sat Dec 02, 2023 4:14 am
Full name: Imno Umni

Re: Perft engine - king moves

Post by imnoumni »

chessbit wrote: Mon Mar 17, 2025 12:05 am I do a little under 2700ms for perft 7, while Gigantua is a little above 3000ms on my CPU.
That's impressive. Congratulations. My code is substantially slower, taking 1.5x Gigantua's time.