diff options
Diffstat (limited to 'src/generate.c')
| -rwxr-xr-x | src/generate.c | 1562 |
1 files changed, 1556 insertions, 6 deletions
diff --git a/src/generate.c b/src/generate.c index 09e3e0a..69e431f 100755 --- a/src/generate.c +++ b/src/generate.c @@ -728,6 +728,211 @@ GenerateWhiteKnight(IN MOVE_STACK *pStack, } // +// board_representation/MOVEGEN_MIGRATION.md section 3 step 1: bitboard +// knight generator, the pilot function for the whole migration -- +// lowest risk, most precedented (reuses g_KnightAttacksBB, already +// built and verified for GetAttacks, no new tables needed). Unlike the +// mailbox pair above, one function serves both JumpTable slots +// (BLACK_KNIGHT and WHITE_KNIGHT) -- GenerateWhiteKnight's +// GET_COLOR(p)==BLACK bit trick was purely a mailbox micro- +// optimization exploiting how BLACK happens to be encoded; a bitboard +// lookup needs no such color-specific shortcut, it just ANDs off +// whichever side's occupancy pos->uToMove identifies. +// +// Full-board occupancy, both sides -- same formula as see.c's static +// _BuildOccupiedBB (a separate copy, not shared, since that one is +// file-local to see.c and this module's own convention keeps its +// bitboard helpers together). Needed by the slider magic-bitboard +// generators (_GenerateRookBB/_GenerateBishopBB) to index into +// g_RookAttackTable/g_BishopAttackTable -- see MOVE_STACK's +// bbOccupied field comment in chess.h. Non-static so testgenerate.c's +// harness can call it directly. +BITBOARD +_BuildFullOccupiedBB(IN POSITION *pos) +/** + +Routine description: + + Full-board occupancy bitboard (both colors, every piece including + pawns and kings). + +Parameters: + + POSITION *pos + +Return value: + + BITBOARD + +**/ +{ + return (pos->bbPieces[WHITE][KNIGHT] | pos->bbPieces[WHITE][BISHOP] | + pos->bbPieces[WHITE][ROOK] | pos->bbPieces[WHITE][QUEEN] | + pos->bbPieces[BLACK][KNIGHT] | pos->bbPieces[BLACK][BISHOP] | + pos->bbPieces[BLACK][ROOK] | pos->bbPieces[BLACK][QUEEN] | + pos->bbPawns[WHITE] | pos->bbPawns[BLACK] | + COOR_TO_BB(pos->cNonPawns[WHITE][0]) | + COOR_TO_BB(pos->cNonPawns[BLACK][0])); +} + +// board_representation/MOVEGEN_MIGRATION.md section 6a Phase 2: raw +// magic-table lookups, factored out of _GenerateRookBB/_GenerateBishopBB +// so _ComputeCheckTargetMaskBB (below) and the Part B SaveMe*BB +// functions can reuse them without duplicating the index arithmetic a +// third/fourth time. Non-static (unlike their original file-local +// status) so movesup.c's section 6b ExposesCheckBB work can call them +// too -- same convention as _BuildFriendlySideBB/_WhoAttacksSquareBB. +BITBOARD FORCEINLINE +_RookAttacksBB(IN COOR c, IN BITBOARD bbOccupied) +{ + ULONG uMagicIndex = (ULONG) + (((bbOccupied & g_RookOccupancyMask[c]) * + g_RookMagic[c]) >> g_RookMagicShift[c]); + return g_RookAttackTable[c][uMagicIndex]; +} + +BITBOARD FORCEINLINE +_BishopAttacksBB(IN COOR c, IN BITBOARD bbOccupied) +{ + ULONG uMagicIndex = (ULONG) + (((bbOccupied & g_BishopOccupancyMask[c]) * + g_BishopMagic[c]) >> g_BishopMagicShift[c]); + return g_BishopAttackTable[c][uMagicIndex]; +} + +// board_representation/MOVEGEN_MIGRATION.md section 6a Phase 2: +// bbTargetMask = every square a non-king move could land on to resolve +// a lone check -- the checker's own square (a capture always resolves +// check) OR, if the checker is a slider, every square strictly between +// it and the king (a block also resolves check). The "squares between +// two aligned pieces" trick costs nothing new: each square's magic +// attack bitboard already reaches exactly to its nearest blocker in +// every direction, so ANDing both sides' attack sets together gives +// precisely the empty segment between them, excluding both endpoints +// (neither piece's own attack set includes its own square). A +// diagonally-adjacent pawn checker naturally falls out of the same +// bishop-table branch with zero extra bits, since there is nothing +// between two adjacent squares -- no separate pawn case needed. A +// non-aligned (knight) checker is the only case requiring a branch: +// DIRECTION_BETWEEN_SQUARES returns 0 for a knight offset, and there +// is no way to block a knight's check regardless. +static BITBOARD +_ComputeCheckTargetMaskBB(IN COOR cKing, IN COOR cAttacker, + IN BITBOARD bbOccupied) +{ + int iDelta = DIRECTION_BETWEEN_SQUARES(cAttacker, cKing); + BITBOARD bbBetween = 0; + + if (0 != iDelta) + { + if ((16 == iDelta) || (-16 == iDelta) || + (1 == iDelta) || (-1 == iDelta)) + { + bbBetween = _RookAttacksBB(cKing, bbOccupied) & + _RookAttacksBB(cAttacker, bbOccupied); + } + else + { + bbBetween = _BishopAttacksBB(cKing, bbOccupied) & + _BishopAttacksBB(cAttacker, bbOccupied); + } + } + return COOR_TO_BB(cAttacker) | bbBetween; +} + +// Non-static so testgenerate.c's harness can call it directly to set +// up MOVE_STACK.bbFriendlyOccupied when calling a _Generate*BB +// function outside of _GenerateAllMoves. +BITBOARD +_BuildFriendlySideBB(IN POSITION *pos, IN ULONG uSide) +/** + +Routine description: + + Full occupancy bitboard for one side only (all piece types + including pawns and king) -- see.c's _BuildOccupiedBB ORs both + sides together for a different purpose (SEE's "is this square + occupied at all" query); move generation needs just one side's + squares, to AND off as illegal (self-occupied) destinations. + +Parameters: + + POSITION *pos + ULONG uSide + +Return value: + + BITBOARD + +**/ +{ + return (pos->bbPieces[uSide][KNIGHT] | pos->bbPieces[uSide][BISHOP] | + pos->bbPieces[uSide][ROOK] | pos->bbPieces[uSide][QUEEN] | + pos->bbPawns[uSide] | + COOR_TO_BB(pos->cNonPawns[uSide][0])); +} + +// Non-static (unlike a purely-internal helper would be) so +// testgenerate.c's speed/correctness harness can call it directly, +// same convention _GetAttacksBB (see.c) already uses. +void +_GenerateKnightBB(IN MOVE_STACK *pStack, + IN POSITION *pos, + IN COOR cKnight) +/** + +Routine description: + + Bitboard equivalent of GenerateKnight/GenerateWhiteKnight -- called + by GenerateMoves' JumpTable in place of both when + GENERATE_KNIGHT_BITBOARD is defined. Produces the exact same + pseudo-legal move set (same over-generation behavior, no + legal-awareness added) -- see MOVEGEN_MIGRATION.md section 1's + explicit non-goal. + + Relies on pStack->bbFriendlyOccupied already being set by the + caller (_GenerateAllMoves computes it once per node, before + dispatching to any piece type, precisely so every bitboard-backed + generator for that node shares one build instead of each paying + for its own -- see MOVE_STACK's field comment in chess.h). A + direct caller outside of _GenerateAllMoves (e.g. testgenerate.c's + harness) must set it first; the DEBUG-build ASSERT below catches + a stale/unset value, but only in a DEBUG build. + +Parameters: + + MOVE_STACK *pStack : the move stack (bbFriendlyOccupied must + already be set for pos->uToMove) + POSITION *pos : the board position + COOR cKnight : the knight's location + +Return value: + + static void + +**/ +{ + BITBOARD bbDest = g_KnightAttacksBB[cKnight] & ~pStack->bbFriendlyOccupied; + ULONG uBitIndex; + COOR c; + PIECE p; + + ASSERT(IS_KNIGHT(pos->rgSquare[cKnight].pPiece)); + ASSERT(GET_COLOR(pos->rgSquare[cKnight].pPiece) == pos->uToMove); + ASSERT(pStack->bbFriendlyOccupied == + _BuildFriendlySideBB(pos, pos->uToMove)); + + while (bbDest) + { + uBitIndex = FastFirstBit(bbDest) - 1; + bbDest &= (bbDest - 1); + c = BIT_NUMBER_TO_COOR(uBitIndex); + p = pos->rgSquare[c].pPiece; + _AddNormalMove(pStack, pos, cKnight, c, p); + } +} + +// // These logical AND/ORs replaced with bitwise AND/OR; the effect is // the same the the bitwise is marginally faster. // @@ -803,6 +1008,68 @@ Return value: while(0 != g_iNDeltas[u]); } +// +// board_representation/MOVEGEN_MIGRATION.md section 6a Phase 2: +// bitboard equivalent of SaveMeKnight -- one AND against a +// precomputed bbTargetMask replaces the per-square (c == cAttacker) || +// BLOCKS_THE_CHECK(c) test entirely. ExposesCheck (pin detection) +// stays exactly as-is, unchanged, called per surviving candidate -- +// not in scope to alter, see section 6a's "what does not change." +// +void +_SaveMeKnightBB(IN MOVE_STACK *pStack, + IN POSITION *pos, + IN COOR cKnight, + IN COOR cKing, + IN BITBOARD bbTargetMask) +/** + +Routine description: + + Bitboard equivalent of SaveMeKnight -- called in place of it (via + a direct, statically-known call, not JumpTable) when + GENERATE_ESCAPES_BLOCK_BITBOARD is defined. + +Parameters: + + MOVE_STACK *pStack : the move stack (bbFriendlyOccupied must + already be set for pos->uToMove) + POSITION *pos : the board position + COOR cKnight : the knight's location + COOR cKing : the friendly king's location + BITBOARD bbTargetMask : squares that resolve the lone check -- + see _ComputeCheckTargetMaskBB + +Return value: + + void + +**/ +{ + BITBOARD bbDest = g_KnightAttacksBB[cKnight] & + ~pStack->bbFriendlyOccupied & bbTargetMask; + ULONG uBitIndex; + COOR c, cExposed; + PIECE p; + + ASSERT(InCheck(pos, pos->uToMove)); + ASSERT(IS_KNIGHT(pos->rgSquare[cKnight].pPiece)); + ASSERT(GET_COLOR(pos->rgSquare[cKnight].pPiece) == pos->uToMove); + + while (bbDest) + { + uBitIndex = FastFirstBit(bbDest) - 1; + bbDest &= (bbDest - 1); + c = BIT_NUMBER_TO_COOR(uBitIndex); + cExposed = ExposesCheck(pos, cKnight, cKing); + if (!IS_ON_BOARD(cExposed) || (cExposed == c)) + { + p = pos->rgSquare[c].pPiece; + _AddNormalMove(pStack, pos, cKnight, c, p); + } + } +} + const INT g_iBDeltas[] = { -17, -15, +15, +17, 0 }; @@ -916,6 +1183,78 @@ Return value: } } +// +// board_representation/MOVEGEN_MIGRATION.md section 3 step 3: +// magic-bitboard bishop generator -- mechanically identical to +// _GenerateRookBB, swapping in the bishop's magic tables. Measured +// speed result for rook was parity (not a win) against mailbox at +// these small destination counts -- see that finding written up in +// MOVEGEN_MIGRATION.md's section 3 entry; expect the same here rather +// than a different outcome, since the underlying reason (a mailbox ray +// walk's cost is already ~O(destination count), so magic's O(1) +// lookup pipeline doesn't out-race it at these distances) applies +// identically to diagonals. +// +void +_GenerateBishopBB(IN MOVE_STACK *pStack, + IN POSITION *pos, + IN COOR cBishop) +/** + +Routine description: + + Bitboard equivalent of GenerateBishop -- called by GenerateMoves' + JumpTable in place of it when GENERATE_BISHOP_BITBOARD is defined. + Produces the exact same pseudo-legal move set (same over-generation + behavior, no legal-awareness added) -- see MOVEGEN_MIGRATION.md + section 1's explicit non-goal. + + Relies on pStack->bbFriendlyOccupied and pStack->bbOccupied already + being set by the caller, same as _GenerateRookBB -- see that + function's header comment and MOVE_STACK's field comments in + chess.h. + +Parameters: + + MOVE_STACK *pStack : the move stack (bbFriendlyOccupied and + bbOccupied must already be set) + POSITION *pos : the board position + COOR cBishop : the bishop's location + +Return value: + + void + +**/ +{ + ULONG uMagicIndex; + BITBOARD bbDest; + ULONG uBitIndex; + COOR c; + PIECE p; + + ASSERT(IS_BISHOP(pos->rgSquare[cBishop].pPiece)); + ASSERT(GET_COLOR(pos->rgSquare[cBishop].pPiece) == pos->uToMove); + ASSERT(pStack->bbFriendlyOccupied == + _BuildFriendlySideBB(pos, pos->uToMove)); + ASSERT(pStack->bbOccupied == _BuildFullOccupiedBB(pos)); + + uMagicIndex = (ULONG) + (((pStack->bbOccupied & g_BishopOccupancyMask[cBishop]) * + g_BishopMagic[cBishop]) >> g_BishopMagicShift[cBishop]); + bbDest = g_BishopAttackTable[cBishop][uMagicIndex] & + ~pStack->bbFriendlyOccupied; + + while (bbDest) + { + uBitIndex = FastFirstBit(bbDest) - 1; + bbDest &= (bbDest - 1); + c = BIT_NUMBER_TO_COOR(uBitIndex); + p = pos->rgSquare[c].pPiece; + _AddNormalMove(pStack, pos, cBishop, c, p); + } +} + void SaveMeBishop(IN MOVE_STACK *pStack, @@ -990,6 +1329,65 @@ Return value: while(0 != g_iBDeltas[u]); } +// +// board_representation/MOVEGEN_MIGRATION.md section 6a Phase 2: +// bitboard equivalent of SaveMeBishop -- same pattern as +// _SaveMeKnightBB, using the magic lookup instead of a ray walk. +// +void +_SaveMeBishopBB(IN MOVE_STACK *pStack, + IN POSITION *pos, + IN COOR cBishop, + IN COOR cKing, + IN BITBOARD bbTargetMask) +/** + +Routine description: + + Bitboard equivalent of SaveMeBishop -- called in place of it (via + a direct, statically-known call, not JumpTable) when + GENERATE_ESCAPES_BLOCK_BITBOARD is defined. + +Parameters: + + MOVE_STACK *pStack : the move stack (bbFriendlyOccupied and + bbOccupied must already be set) + POSITION *pos : the board position + COOR cBishop : the bishop's location + COOR cKing : the friendly king's location + BITBOARD bbTargetMask : squares that resolve the lone check -- + see _ComputeCheckTargetMaskBB + +Return value: + + void + +**/ +{ + BITBOARD bbDest = _BishopAttacksBB(cBishop, pStack->bbOccupied) & + ~pStack->bbFriendlyOccupied & bbTargetMask; + ULONG uBitIndex; + COOR c, cExposed; + PIECE p; + + ASSERT(InCheck(pos, pos->uToMove)); + ASSERT(IS_BISHOP(pos->rgSquare[cBishop].pPiece)); + ASSERT(GET_COLOR(pos->rgSquare[cBishop].pPiece) == pos->uToMove); + + while (bbDest) + { + uBitIndex = FastFirstBit(bbDest) - 1; + bbDest &= (bbDest - 1); + c = BIT_NUMBER_TO_COOR(uBitIndex); + cExposed = ExposesCheck(pos, cBishop, cKing); + if (!IS_ON_BOARD(cExposed) || (cExposed == c)) + { + p = pos->rgSquare[c].pPiece; + _AddNormalMove(pStack, pos, cBishop, c, p); + } + } +} + const INT g_iRDeltas[] = { -1, +1, +16, -16, 0 }; @@ -1104,6 +1502,77 @@ Return value: } } +// +// board_representation/MOVEGEN_MIGRATION.md section 3 step 3: magic- +// bitboard rook generator -- the first consumer of the InitMagic() +// infrastructure (data.c) built ahead of time for exactly this. Unlike +// knight/king, this piece type is expected to actually win on speed: +// the magic lookup replaces a 4-direction ray walk (up to 7 squares +// per direction) with one multiply+shift+table lookup, independent of +// how far the rook can see. +// +void +_GenerateRookBB(IN MOVE_STACK *pStack, + IN POSITION *pos, + IN COOR cRook) +/** + +Routine description: + + Bitboard equivalent of GenerateRook -- called by GenerateMoves' + JumpTable in place of it when GENERATE_ROOK_BITBOARD is defined. + Produces the exact same pseudo-legal move set (same over-generation + behavior, no legal-awareness added) -- see MOVEGEN_MIGRATION.md + section 1's explicit non-goal. + + Relies on pStack->bbFriendlyOccupied and pStack->bbOccupied already + being set by the caller (_GenerateAllMoves computes both once per + node, before dispatching to any piece type) -- see MOVE_STACK's + field comments in chess.h. A direct caller outside of + _GenerateAllMoves (e.g. testgenerate.c's harness) must set both + first. + +Parameters: + + MOVE_STACK *pStack : the move stack (bbFriendlyOccupied and + bbOccupied must already be set) + POSITION *pos : the board position + COOR cRook : the rook's location + +Return value: + + void + +**/ +{ + ULONG uMagicIndex; + BITBOARD bbDest; + ULONG uBitIndex; + COOR c; + PIECE p; + + ASSERT(IS_ROOK(pos->rgSquare[cRook].pPiece)); + ASSERT(GET_COLOR(pos->rgSquare[cRook].pPiece) == pos->uToMove); + ASSERT(pStack->bbFriendlyOccupied == + _BuildFriendlySideBB(pos, pos->uToMove)); + ASSERT(pStack->bbOccupied == _BuildFullOccupiedBB(pos)); + + uMagicIndex = (ULONG) + (((pStack->bbOccupied & g_RookOccupancyMask[cRook]) * + g_RookMagic[cRook]) >> g_RookMagicShift[cRook]); + bbDest = g_RookAttackTable[cRook][uMagicIndex] & + ~pStack->bbFriendlyOccupied; + + while (bbDest) + { + uBitIndex = FastFirstBit(bbDest) - 1; + bbDest &= (bbDest - 1); + c = BIT_NUMBER_TO_COOR(uBitIndex); + p = pos->rgSquare[c].pPiece; + _AddNormalMove(pStack, pos, cRook, c, p); + } +} + void SaveMeRook(IN MOVE_STACK *pStack, IN POSITION *pos, @@ -1177,6 +1646,65 @@ Return value: while(0 != g_iRDeltas[u]); } +// +// board_representation/MOVEGEN_MIGRATION.md section 6a Phase 2: +// bitboard equivalent of SaveMeRook -- same pattern as +// _SaveMeBishopBB, using the rook magic lookup instead of a ray walk. +// +void +_SaveMeRookBB(IN MOVE_STACK *pStack, + IN POSITION *pos, + IN COOR cRook, + IN COOR cKing, + IN BITBOARD bbTargetMask) +/** + +Routine description: + + Bitboard equivalent of SaveMeRook -- called in place of it (via a + direct, statically-known call, not JumpTable) when + GENERATE_ESCAPES_BLOCK_BITBOARD is defined. + +Parameters: + + MOVE_STACK *pStack : the move stack (bbFriendlyOccupied and + bbOccupied must already be set) + POSITION *pos : the board position + COOR cRook : the rook's location + COOR cKing : the friendly king's location + BITBOARD bbTargetMask : squares that resolve the lone check -- + see _ComputeCheckTargetMaskBB + +Return value: + + void + +**/ +{ + BITBOARD bbDest = _RookAttacksBB(cRook, pStack->bbOccupied) & + ~pStack->bbFriendlyOccupied & bbTargetMask; + ULONG uBitIndex; + COOR c, cExposed; + PIECE p; + + ASSERT(InCheck(pos, pos->uToMove)); + ASSERT(IS_ROOK(pos->rgSquare[cRook].pPiece)); + ASSERT(GET_COLOR(pos->rgSquare[cRook].pPiece) == pos->uToMove); + + while (bbDest) + { + uBitIndex = FastFirstBit(bbDest) - 1; + bbDest &= (bbDest - 1); + c = BIT_NUMBER_TO_COOR(uBitIndex); + cExposed = ExposesCheck(pos, cRook, cKing); + if (!IS_ON_BOARD(cExposed) || (cExposed == c)) + { + p = pos->rgSquare[c].pPiece; + _AddNormalMove(pStack, pos, cRook, c, p); + } + } +} + void GenerateQueen(IN MOVE_STACK *pStack, @@ -1234,6 +1762,82 @@ Return value: while(0 != g_iQKDeltas[u]); } +// +// board_representation/MOVEGEN_MIGRATION.md section 3 step 4: queen is +// just rook-directions OR bishop-directions combined, once step 3 is +// solved -- no new design or new tables needed. Two magic lookups (one +// rook-table, one bishop-table) ORed together, same +// `_EvalQueenOccupancyBB`-flagged caution as the rest of this plan: +// a combined single 8-ray table was tried elsewhere (the PoC in data.c +// this migration's tables were built alongside) and measured *slower* +// than reusing the two-pass rook/bishop structure -- don't rediscover +// that, this deliberately does not attempt to unify the two lookups +// into one table. +// +void +_GenerateQueenBB(IN MOVE_STACK *pStack, + IN POSITION *pos, + IN COOR cQueen) +/** + +Routine description: + + Bitboard equivalent of GenerateQueen -- called by GenerateMoves' + JumpTable in place of it when GENERATE_QUEEN_BITBOARD is defined. + Produces the exact same pseudo-legal move set (same over-generation + behavior, no legal-awareness added) -- see MOVEGEN_MIGRATION.md + section 1's explicit non-goal. + + Relies on pStack->bbFriendlyOccupied and pStack->bbOccupied already + being set by the caller, same as _GenerateRookBB/_GenerateBishopBB + -- see those functions' header comments and MOVE_STACK's field + comments in chess.h. + +Parameters: + + MOVE_STACK *pStack : the move stack (bbFriendlyOccupied and + bbOccupied must already be set) + POSITION *pos : the board position + COOR cQueen : the queen's location + +Return value: + + void + +**/ +{ + ULONG uRookMagicIndex, uBishopMagicIndex; + BITBOARD bbDest; + ULONG uBitIndex; + COOR c; + PIECE p; + + ASSERT(IS_QUEEN(pos->rgSquare[cQueen].pPiece)); + ASSERT(GET_COLOR(pos->rgSquare[cQueen].pPiece) == pos->uToMove); + ASSERT(pStack->bbFriendlyOccupied == + _BuildFriendlySideBB(pos, pos->uToMove)); + ASSERT(pStack->bbOccupied == _BuildFullOccupiedBB(pos)); + + uRookMagicIndex = (ULONG) + (((pStack->bbOccupied & g_RookOccupancyMask[cQueen]) * + g_RookMagic[cQueen]) >> g_RookMagicShift[cQueen]); + uBishopMagicIndex = (ULONG) + (((pStack->bbOccupied & g_BishopOccupancyMask[cQueen]) * + g_BishopMagic[cQueen]) >> g_BishopMagicShift[cQueen]); + bbDest = (g_RookAttackTable[cQueen][uRookMagicIndex] | + g_BishopAttackTable[cQueen][uBishopMagicIndex]) & + ~pStack->bbFriendlyOccupied; + + while (bbDest) + { + uBitIndex = FastFirstBit(bbDest) - 1; + bbDest &= (bbDest - 1); + c = BIT_NUMBER_TO_COOR(uBitIndex); + p = pos->rgSquare[c].pPiece; + _AddNormalMove(pStack, pos, cQueen, c, p); + } +} + void SaveMeQueen(IN MOVE_STACK *pStack, @@ -1309,6 +1913,66 @@ Return value: while(0 != g_iQKDeltas[u]); } +// +// board_representation/MOVEGEN_MIGRATION.md section 6a Phase 2: +// bitboard equivalent of SaveMeQueen -- two magic lookups ORed +// together, same as _GenerateQueenBB, ANDed with bbTargetMask. +// +void +_SaveMeQueenBB(IN MOVE_STACK *pStack, + IN POSITION *pos, + IN COOR cQueen, + IN COOR cKing, + IN BITBOARD bbTargetMask) +/** + +Routine description: + + Bitboard equivalent of SaveMeQueen -- called in place of it (via a + direct, statically-known call, not JumpTable) when + GENERATE_ESCAPES_BLOCK_BITBOARD is defined. + +Parameters: + + MOVE_STACK *pStack : the move stack (bbFriendlyOccupied and + bbOccupied must already be set) + POSITION *pos : the board position + COOR cQueen : the queen's location + COOR cKing : the friendly king's location + BITBOARD bbTargetMask : squares that resolve the lone check -- + see _ComputeCheckTargetMaskBB + +Return value: + + void + +**/ +{ + BITBOARD bbDest = (_RookAttacksBB(cQueen, pStack->bbOccupied) | + _BishopAttacksBB(cQueen, pStack->bbOccupied)) & + ~pStack->bbFriendlyOccupied & bbTargetMask; + ULONG uBitIndex; + COOR c, cExposed; + PIECE p; + + ASSERT(InCheck(pos, pos->uToMove)); + ASSERT(IS_QUEEN(pos->rgSquare[cQueen].pPiece)); + ASSERT(GET_COLOR(pos->rgSquare[cQueen].pPiece) == pos->uToMove); + + while (bbDest) + { + uBitIndex = FastFirstBit(bbDest) - 1; + bbDest &= (bbDest - 1); + c = BIT_NUMBER_TO_COOR(uBitIndex); + cExposed = ExposesCheck(pos, cQueen, cKing); + if (!IS_ON_BOARD(cExposed) || (cExposed == c)) + { + p = pos->rgSquare[c].pPiece; + _AddNormalMove(pStack, pos, cQueen, c, p); + } + } +} + void GenerateBlackKing(IN MOVE_STACK *pStack, IN POSITION *pos, @@ -1482,6 +2146,123 @@ Return value: } } +// +// board_representation/MOVEGEN_MIGRATION.md section 3 step 2: bitboard +// king generator, normal (non-castling) moves only -- castling stays +// mailbox per section 1's explicit non-goal (at most 2 candidate +// moves, checked via simple square-emptiness tests, not +// ray-walk-shaped, nothing for a bitboard to speed up). Serves both +// JumpTable slots the same way _GenerateKnightBB does, for the same +// reason (GenerateWhiteKing's GET_COLOR(p)==BLACK bit trick was a +// mailbox-only micro-optimization); the castling tail below still +// branches on color since CASTLE_BLACK_*/CASTLE_WHITE_* and their +// associated squares genuinely differ per side. +// +void +_GenerateKingBB(IN MOVE_STACK *pStack, + IN POSITION *pos, + IN COOR cKing) +/** + +Routine description: + + Bitboard equivalent of GenerateBlackKing/GenerateWhiteKing's normal + (non-castling) move enumeration -- called by GenerateMoves' + JumpTable in place of both when GENERATE_KING_BITBOARD is defined. + Produces the exact same pseudo-legal move set as the mailbox pair, + castling included (via the same mailbox logic those functions use, + verbatim) -- see MOVEGEN_MIGRATION.md section 1's explicit non-goal + against changing over-generation behavior. + + Relies on pStack->bbFriendlyOccupied already being set by the + caller, same as _GenerateKnightBB -- see that function's header + comment and MOVE_STACK's field comment in chess.h. + +Parameters: + + MOVE_STACK *pStack : the move stack (bbFriendlyOccupied must + already be set for pos->uToMove) + POSITION *pos : the board position + COOR cKing : the king's location + +Return value: + + void + +**/ +{ + BITBOARD bbDest = g_KingAttacksBB[cKing] & ~pStack->bbFriendlyOccupied; + ULONG uBitIndex; + COOR c; + PIECE p; + + ASSERT(IS_KING(pos->rgSquare[cKing].pPiece)); + ASSERT(GET_COLOR(pos->rgSquare[cKing].pPiece) == pos->uToMove); + ASSERT(pStack->bbFriendlyOccupied == + _BuildFriendlySideBB(pos, pos->uToMove)); + + while (bbDest) + { + uBitIndex = FastFirstBit(bbDest) - 1; + bbDest &= (bbDest - 1); + c = BIT_NUMBER_TO_COOR(uBitIndex); + p = pos->rgSquare[c].pPiece; + _AddNormalMove(pStack, pos, cKing, c, p); + } + + // + // Castling: unchanged mailbox logic, copied verbatim from + // GenerateBlackKing/GenerateWhiteKing -- see those functions' + // comments. Not in scope for a bitboard rewrite (section 1). + // + if (pos->uToMove == BLACK) + { + if ((pos->bvCastleInfo & BLACK_CAN_CASTLE) == 0) return; +#ifdef DEBUG + ASSERT(IS_KING(pos->rgSquare[E8].pPiece)); + ASSERT(cKing == E8); +#endif + if ((pos->bvCastleInfo & CASTLE_BLACK_SHORT) && + (IS_EMPTY(pos->rgSquare[G8].pPiece)) && + (IS_EMPTY(pos->rgSquare[F8].pPiece))) + { + ASSERT(pos->rgSquare[H8].pPiece == BLACK_ROOK); + _AddCastle(pStack, pos, E8, G8); + } + if ((pos->bvCastleInfo & CASTLE_BLACK_LONG) && + (IS_EMPTY(pos->rgSquare[C8].pPiece)) && + (IS_EMPTY(pos->rgSquare[D8].pPiece)) && + (IS_EMPTY(pos->rgSquare[B8].pPiece))) + { + ASSERT(pos->rgSquare[A8].pPiece == BLACK_ROOK); + _AddCastle(pStack, pos, E8, C8); + } + } + else + { + if ((pos->bvCastleInfo & WHITE_CAN_CASTLE) == 0) return; +#ifdef DEBUG + ASSERT(IS_KING(pos->rgSquare[E1].pPiece)); + ASSERT(cKing == E1); +#endif + if ((pos->bvCastleInfo & CASTLE_WHITE_SHORT) && + (IS_EMPTY(pos->rgSquare[G1].pPiece)) && + (IS_EMPTY(pos->rgSquare[F1].pPiece))) + { + ASSERT(pos->rgSquare[H1].pPiece == WHITE_ROOK); + _AddCastle(pStack, pos, E1, G1); + } + if ((pos->bvCastleInfo & CASTLE_WHITE_LONG) && + (IS_EMPTY(pos->rgSquare[C1].pPiece)) && + (IS_EMPTY(pos->rgSquare[B1].pPiece)) && + (IS_EMPTY(pos->rgSquare[D1].pPiece))) + { + ASSERT(pos->rgSquare[A1].pPiece == WHITE_ROOK); + _AddCastle(pStack, pos, E1, C1); + } + } +} + void GenerateWhitePawn(IN MOVE_STACK *pStack, @@ -1882,6 +2663,213 @@ Return value: } } +// +// board_representation/MOVEGEN_MIGRATION.md section 3 step 5: bulk, +// whole-side pawn generator using the classic shift-and-mask technique +// (confirmed via ~/crafty/movgen.c to be the standard approach, not a +// per-starting-square precomputed mask) rather than a per-square +// lookup like the other five migrated piece types. Structurally +// different for a real reason: pos->bbPawns[uSide]'s bits already +// live in dense rank*8+file space (COOR_TO_BIT_NUMBER), so shifting +// the *entire* bitboard by 8 moves every pawn of that side forward one +// rank simultaneously -- no per-pawn loop needed to find destinations, +// only to emit the resulting moves. +// +// This engine's square numbering has A8 = bit 0 (COOR_TO_BIT_NUMBER of +// 0x88's A8 == 0x00), so rank number increases as the *row* (bits/8) +// *decreases* -- opposite of Crafty's convention (confirmed via +// GenerateWhitePawn's existing 0x88 deltas: -16 forward, -15/-17 +// captures). Concretely, in bit-number space: +// WHITE forward = row decreases = bb >> 8 +// BLACK forward = row increases = bb << 8 +// and the two diagonals per side are +-7/+-9 (one rank plus one file), +// each requiring the *opposite* file's edge excluded first so a +// same-row wraparound (e.g. an h-file pawn's ">>7" would otherwise +// silently land back on the same row's a-file -- a real, silent-wrong- +// answer trap, not just an out-of-range index) never happens -- see +// each shift's comment below for which file it excludes and why. +// +// Double-push eligibility (rank 2 for White, rank 7 for Black) is +// checked by masking the *already-computed single-push destination* +// bitboard against BBRANK[3]/BBRANK[6] (did this pawn's single push +// land on rank 3/6, which is only possible starting from rank 2/7) +// rather than a per-square starting-rank table -- same technique +// Crafty uses (movgen.c's padvances2, masking padvances1_all against +// its own rank-3/rank-6 constant before the second shift). +// +// En passant is deliberately NOT folded into the bulk capture +// bitboards -- it is exactly one specific square (pos->cEpSquare) at +// most once per node, cheaper and less error-prone to check directly +// (does either of the two diagonal-behind squares hold one of this +// side's pawns) than to derive and mask a whole extra bitboard for an +// event this rare. +// +void +_GenerateAllPawnMovesBB(IN MOVE_STACK *pStack, + IN POSITION *pos, + IN ULONG uSide) +/** + +Routine description: + + Bitboard equivalent of the GenerateWhitePawn/GenerateBlackPawn pair + -- called in place of the per-pawn mailbox loop when + GENERATE_PAWN_BITBOARD is defined, for the entire side's pawns in + one call rather than once per pawn. Produces the exact same + pseudo-legal move set (same over-generation behavior, no + legal-awareness added) -- see MOVEGEN_MIGRATION.md section 1's + explicit non-goal. + +Parameters: + + MOVE_STACK *pStack : the move stack + POSITION *pos : the board position + ULONG uSide : which side's pawns to generate for (pos->uToMove) + +Return value: + + void + +**/ +{ + BITBOARD bbPawns = pos->bbPawns[uSide]; + BITBOARD bbOccupied = _BuildFullOccupiedBB(pos); + BITBOARD bbEmpty = ~bbOccupied; + BITBOARD bbEnemy = bbOccupied & ~_BuildFriendlySideBB(pos, uSide); + BITBOARD bbSinglePush, bbDoublePush, bbCapLeft, bbCapRight, bb; + ULONG uBitIndex; + COOR cTo, cFrom, cEp; + PIECE p; + + if (uSide == WHITE) + { + bbSinglePush = (bbPawns >> 8) & bbEmpty; + bbDoublePush = ((bbSinglePush & BBRANK[3]) >> 8) & bbEmpty; + // "Left" diagonal (file-1, i.e. 0x88's -17): exclude file A + // (file-1 invalid/wraps for an a-file pawn). + bbCapLeft = ((bbPawns & ~BBFILE[0]) >> 9) & bbEnemy; + // "Right" diagonal (file+1, i.e. 0x88's -15): exclude file H. + bbCapRight = ((bbPawns & ~BBFILE[7]) >> 7) & bbEnemy; + } + else + { + ASSERT(uSide == BLACK); + bbSinglePush = (bbPawns << 8) & bbEmpty; + bbDoublePush = ((bbSinglePush & BBRANK[6]) << 8) & bbEmpty; + // "Left" diagonal (file-1, 0x88's +15): exclude file A. + bbCapLeft = ((bbPawns & ~BBFILE[0]) << 7) & bbEnemy; + // "Right" diagonal (file+1, 0x88's +17): exclude file H. + bbCapRight = ((bbPawns & ~BBFILE[7]) << 9) & bbEnemy; + } + + // Single push (+ promotion if landing on the far rank). + bb = bbSinglePush; + while (bb) + { + uBitIndex = FastFirstBit(bb) - 1; + bb &= (bb - 1); + cTo = BIT_NUMBER_TO_COOR(uBitIndex); + cFrom = (uSide == WHITE) ? + BIT_NUMBER_TO_COOR(uBitIndex + 8) : + BIT_NUMBER_TO_COOR(uBitIndex - 8); + ASSERT(IS_PAWN(pos->rgSquare[cFrom].pPiece)); + if (RANK8(cTo) || RANK1(cTo)) + { + _AddPromote(pStack, pos, cFrom, cTo); + } + else + { + _AddNormalMove(pStack, pos, cFrom, cTo, 0); + } + } + + // Double push -- never a promotion (rank 4/5 destination only). + bb = bbDoublePush; + while (bb) + { + uBitIndex = FastFirstBit(bb) - 1; + bb &= (bb - 1); + cTo = BIT_NUMBER_TO_COOR(uBitIndex); + cFrom = (uSide == WHITE) ? + BIT_NUMBER_TO_COOR(uBitIndex + 16) : + BIT_NUMBER_TO_COOR(uBitIndex - 16); + ASSERT(IS_PAWN(pos->rgSquare[cFrom].pPiece)); + _AddDoubleJump(pStack, pos, cFrom, cTo); + } + + // Capture left (+ promotion if landing on the far rank). + bb = bbCapLeft; + while (bb) + { + uBitIndex = FastFirstBit(bb) - 1; + bb &= (bb - 1); + cTo = BIT_NUMBER_TO_COOR(uBitIndex); + cFrom = (uSide == WHITE) ? + BIT_NUMBER_TO_COOR(uBitIndex + 9) : + BIT_NUMBER_TO_COOR(uBitIndex - 7); + ASSERT(IS_PAWN(pos->rgSquare[cFrom].pPiece)); + p = pos->rgSquare[cTo].pPiece; + ASSERT(!IS_EMPTY(p) && OPPOSITE_COLORS(p, pos->rgSquare[cFrom].pPiece)); + if (RANK8(cTo) || RANK1(cTo)) + { + _AddPromote(pStack, pos, cFrom, cTo); + } + else + { + _AddNormalMove(pStack, pos, cFrom, cTo, p); + } + } + + // Capture right (+ promotion if landing on the far rank). + bb = bbCapRight; + while (bb) + { + uBitIndex = FastFirstBit(bb) - 1; + bb &= (bb - 1); + cTo = BIT_NUMBER_TO_COOR(uBitIndex); + cFrom = (uSide == WHITE) ? + BIT_NUMBER_TO_COOR(uBitIndex + 7) : + BIT_NUMBER_TO_COOR(uBitIndex - 9); + ASSERT(IS_PAWN(pos->rgSquare[cFrom].pPiece)); + p = pos->rgSquare[cTo].pPiece; + ASSERT(!IS_EMPTY(p) && OPPOSITE_COLORS(p, pos->rgSquare[cFrom].pPiece)); + if (RANK8(cTo) || RANK1(cTo)) + { + _AddPromote(pStack, pos, cFrom, cTo); + } + else + { + _AddNormalMove(pStack, pos, cFrom, cTo, p); + } + } + + // En passant -- deliberately not bulk (see block comment above): + // check the (at most 2) squares diagonally behind pos->cEpSquare + // for one of this side's pawns, exactly like the mailbox + // functions' cTo == pos->cEpSquare check, just run once per side + // per node instead of once per pawn. + cEp = pos->cEpSquare; + if (IS_ON_BOARD(cEp)) + { + int iBehindDelta = (uSide == WHITE) ? 16 : -16; + + cFrom = cEp + iBehindDelta - 1; + if (IS_ON_BOARD(cFrom) && + IS_PAWN(pos->rgSquare[cFrom].pPiece) && + (GET_COLOR(pos->rgSquare[cFrom].pPiece) == uSide)) + { + _AddEnPassant(pStack, pos, cFrom, cEp); + } + cFrom = cEp + iBehindDelta + 1; + if (IS_ON_BOARD(cFrom) && + IS_PAWN(pos->rgSquare[cFrom].pPiece) && + (GET_COLOR(pos->rgSquare[cFrom].pPiece) == uSide)) + { + _AddEnPassant(pStack, pos, cFrom, cEp); + } + } +} + void SaveMeBlackPawn(IN MOVE_STACK *pStack, @@ -2038,6 +3026,209 @@ Return value: } } +// +// board_representation/MOVEGEN_MIGRATION.md section 6a Phase 2: bulk +// whole-side pawn escape generator, same shift-and-mask technique as +// _GenerateAllPawnMovesBB, with each move-category bitboard ANDed +// against bbTargetMask before extraction and an ExposesCheck filter +// added per surviving candidate (the mailbox SaveMeWhitePawn/ +// SaveMeBlackPawn pair calls ExposesCheck per move too -- see section +// 6a's "what does not change"). En passant is NOT covered by +// bbTargetMask (a between-squares/capture-square mask has no way to +// express "the checking pawn happens to be capturable en passant") -- +// kept as the same narrow direct special case the mailbox functions +// use: only relevant when the double-jumping pawn *is* the checker. +// +void +_SaveMeAllPawnMovesBB(IN MOVE_STACK *pStack, + IN POSITION *pos, + IN ULONG uSide, + IN COOR cKing, + IN COOR cAttacker, + IN BITBOARD bbTargetMask) +/** + +Routine description: + + Bitboard equivalent of the SaveMeWhitePawn/SaveMeBlackPawn pair -- + called in place of the per-pawn mailbox loop when + GENERATE_ESCAPES_BLOCK_BITBOARD is defined, for the entire side's + pawns in one call. + +Parameters: + + MOVE_STACK *pStack : the move stack + POSITION *pos : the board position + ULONG uSide : which side's pawns to generate for (pos->uToMove) + COOR cKing : the friendly king's location + COOR cAttacker : the lone checker's location + BITBOARD bbTargetMask : squares that resolve the lone check -- + see _ComputeCheckTargetMaskBB + +Return value: + + void + +**/ +{ + BITBOARD bbPawns = pos->bbPawns[uSide]; + BITBOARD bbOccupied = _BuildFullOccupiedBB(pos); + BITBOARD bbEmpty = ~bbOccupied; + BITBOARD bbEnemy = bbOccupied & ~_BuildFriendlySideBB(pos, uSide); + BITBOARD bbSinglePush, bbDoublePush, bbCapLeft, bbCapRight, bb; + ULONG uBitIndex; + COOR cTo, cFrom, cExposed; + PIECE p; + + if (uSide == WHITE) + { + bbSinglePush = (bbPawns >> 8) & bbEmpty; + bbDoublePush = ((bbSinglePush & BBRANK[3]) >> 8) & bbEmpty; + bbCapLeft = ((bbPawns & ~BBFILE[0]) >> 9) & bbEnemy; + bbCapRight = ((bbPawns & ~BBFILE[7]) >> 7) & bbEnemy; + } + else + { + ASSERT(uSide == BLACK); + bbSinglePush = (bbPawns << 8) & bbEmpty; + bbDoublePush = ((bbSinglePush & BBRANK[6]) << 8) & bbEmpty; + bbCapLeft = ((bbPawns & ~BBFILE[0]) << 7) & bbEnemy; + bbCapRight = ((bbPawns & ~BBFILE[7]) << 9) & bbEnemy; + } + + // Every move category is ANDed against bbTargetMask -- see this + // function's header comment for why that's sufficient (a push + // destination is only ever in bbTargetMask if it's a genuine block + // square, since bbTargetMask's non-capture bits are, by + // construction, empty squares; a capture destination is only ever + // in bbTargetMask if it's the checker's own square, since + // between-squares are empty and captures already require bbEnemy). + bbSinglePush &= bbTargetMask; + bbDoublePush &= bbTargetMask; + bbCapLeft &= bbTargetMask; + bbCapRight &= bbTargetMask; + + bb = bbSinglePush; + while (bb) + { + uBitIndex = FastFirstBit(bb) - 1; + bb &= (bb - 1); + cTo = BIT_NUMBER_TO_COOR(uBitIndex); + cFrom = (uSide == WHITE) ? + BIT_NUMBER_TO_COOR(uBitIndex + 8) : + BIT_NUMBER_TO_COOR(uBitIndex - 8); + cExposed = ExposesCheck(pos, cFrom, cKing); + if (!IS_ON_BOARD(cExposed) || (cExposed == cTo)) + { + if (RANK8(cTo) || RANK1(cTo)) + { + _AddPromote(pStack, pos, cFrom, cTo); + } + else + { + _AddNormalMove(pStack, pos, cFrom, cTo, 0); + } + } + } + + bb = bbDoublePush; + while (bb) + { + uBitIndex = FastFirstBit(bb) - 1; + bb &= (bb - 1); + cTo = BIT_NUMBER_TO_COOR(uBitIndex); + cFrom = (uSide == WHITE) ? + BIT_NUMBER_TO_COOR(uBitIndex + 16) : + BIT_NUMBER_TO_COOR(uBitIndex - 16); + cExposed = ExposesCheck(pos, cFrom, cKing); + if (!IS_ON_BOARD(cExposed) || (cExposed == cTo)) + { + _AddDoubleJump(pStack, pos, cFrom, cTo); + } + } + + bb = bbCapLeft; + while (bb) + { + uBitIndex = FastFirstBit(bb) - 1; + bb &= (bb - 1); + cTo = BIT_NUMBER_TO_COOR(uBitIndex); + cFrom = (uSide == WHITE) ? + BIT_NUMBER_TO_COOR(uBitIndex + 9) : + BIT_NUMBER_TO_COOR(uBitIndex - 7); + cExposed = ExposesCheck(pos, cFrom, cKing); + if (!IS_ON_BOARD(cExposed) || (cExposed == cAttacker)) + { + p = pos->rgSquare[cTo].pPiece; + if (RANK8(cTo) || RANK1(cTo)) + { + _AddPromote(pStack, pos, cFrom, cTo); + } + else + { + _AddNormalMove(pStack, pos, cFrom, cTo, p); + } + } + } + + bb = bbCapRight; + while (bb) + { + uBitIndex = FastFirstBit(bb) - 1; + bb &= (bb - 1); + cTo = BIT_NUMBER_TO_COOR(uBitIndex); + cFrom = (uSide == WHITE) ? + BIT_NUMBER_TO_COOR(uBitIndex + 7) : + BIT_NUMBER_TO_COOR(uBitIndex - 9); + cExposed = ExposesCheck(pos, cFrom, cKing); + if (!IS_ON_BOARD(cExposed) || (cExposed == cAttacker)) + { + p = pos->rgSquare[cTo].pPiece; + if (RANK8(cTo) || RANK1(cTo)) + { + _AddPromote(pStack, pos, cFrom, cTo); + } + else + { + _AddNormalMove(pStack, pos, cFrom, cTo, p); + } + } + } + + // + // En passant: only relevant when the double-jumping enemy pawn + // *is* the checker -- there is no way to block check with an en + // passant capture (see SaveMeWhitePawn/SaveMeBlackPawn's identical + // comment). White defender: cAttacker == cEpSquare + 16. Black + // defender: cAttacker == cEpSquare - 16. + // + if (IS_ON_BOARD(pos->cEpSquare)) + { + COOR cEp = pos->cEpSquare; + int iBehindDelta = (uSide == WHITE) ? 16 : -16; + FLAG fEpResolvesCheck = (uSide == WHITE) ? + (cAttacker == cEp + 16) : (cAttacker == cEp - 16); + + if (fEpResolvesCheck) + { + cFrom = cEp + iBehindDelta - 1; + if (IS_ON_BOARD(cFrom) && + IS_PAWN(pos->rgSquare[cFrom].pPiece) && + (GET_COLOR(pos->rgSquare[cFrom].pPiece) == uSide)) + { + _AddEnPassant(pStack, pos, cFrom, cEp); + } + cFrom = cEp + iBehindDelta + 1; + if (IS_ON_BOARD(cFrom) && + IS_PAWN(pos->rgSquare[cFrom].pPiece) && + (GET_COLOR(pos->rgSquare[cFrom].pPiece) == uSide)) + { + _AddEnPassant(pStack, pos, cFrom, cEp); + } + } + } +} + void InvalidGenerator(IN UNUSED MOVE_STACK *pStack, @@ -2100,7 +3291,12 @@ Return value: } -static void +// Non-static (unlike its historical internal-only status) so +// testgenerate.c's whole-node dispatch benchmark can call it directly +// by name -- see _GenerateAllMovesBB's block comment for why that +// comparison needs both functions callable under their real names in +// a toggle-free build. +void _GenerateAllMoves(IN MOVE_STACK *pStack, IN POSITION *pos) /** @@ -2128,22 +3324,67 @@ Return value: InvalidGenerator, // EMPTY | WHITE InvalidGenerator, // 2 (BLACK_PAWN) InvalidGenerator, // 3 (WHITE_PAWN) + // MOVEGEN_MIGRATION.md section 6 toggle -- one #define per + // piece type, independent of the others. _GenerateKnightBB + // serves both slots; see its header comment for why the + // mailbox pair's color split doesn't carry over to bitboards. +#if defined(GENERATE_KNIGHT_BITBOARD) + _GenerateKnightBB, // 4 (BLACK_KNIGHT) + _GenerateKnightBB, // 5 (WHITE_KNIGHT) +#else GenerateKnight, // 4 (BLACK_KNIGHT) GenerateWhiteKnight, // 5 (WHITE_KNIGHT) +#endif +#if defined(GENERATE_BISHOP_BITBOARD) + _GenerateBishopBB, // 6 (BLACK_BISHOP) + _GenerateBishopBB, // 7 (WHITE_BISHOP) +#else GenerateBishop, // 6 (BLACK_BISHOP) GenerateBishop, // 7 (WHITE_BISHOP) +#endif +#if defined(GENERATE_ROOK_BITBOARD) + _GenerateRookBB, // 8 (BLACK_ROOK) + _GenerateRookBB, // 9 (WHITE_ROOK) +#else GenerateRook, // 8 (BLACK_ROOK) GenerateRook, // 9 (WHITE_ROOK) +#endif +#if defined(GENERATE_QUEEN_BITBOARD) + _GenerateQueenBB, // 10 (BLACK_QUEEN) + _GenerateQueenBB, // 11 (WHITE_QUEEN) +#else GenerateQueen, // 10 (BLACK_QUEEN) - GenerateQueen, // 11 (WHITE_QUEEN) + GenerateQueen, // 11 (WHITE_QUEEN) +#endif +#if defined(GENERATE_KING_BITBOARD) + _GenerateKingBB, // 12 (BLACK_KING) + _GenerateKingBB // 13 (WHITE_KING) +#else GenerateBlackKing, // 12 (BLACK_KING) GenerateWhiteKing // 13 (WHITE_KING) +#endif }; ULONG u; #ifdef DEBUG PIECE p; #endif + // See MOVE_STACK's bbFriendlyOccupied field comment (chess.h) and + // _GenerateKnightBB's header comment: computed once per node here, + // before any piece-type dispatch, so every bitboard-backed + // generator invoked below shares this build instead of each + // recomputing it -- extend this #if with each new + // GENERATE_*_BITBOARD toggle as piece types migrate. +#if defined(GENERATE_KNIGHT_BITBOARD) || defined(GENERATE_KING_BITBOARD) || \ + defined(GENERATE_ROOK_BITBOARD) || defined(GENERATE_BISHOP_BITBOARD) || \ + defined(GENERATE_QUEEN_BITBOARD) + pStack->bbFriendlyOccupied = _BuildFriendlySideBB(pos, pos->uToMove); +#endif +#if defined(GENERATE_ROOK_BITBOARD) || defined(GENERATE_BISHOP_BITBOARD) || \ + defined(GENERATE_QUEEN_BITBOARD) + pStack->bbOccupied = _BuildFullOccupiedBB(pos); +#endif + for(u = pos->uNonPawnCount[pos->uToMove][0] - 1; u != (ULONG)-1; u--) @@ -2159,7 +3400,10 @@ Return value: (JumpTable[pos->rgSquare[c].pPiece])(pStack, pos, c); } - if (pos->uToMove == BLACK) +#if defined(GENERATE_PAWN_BITBOARD) + _GenerateAllPawnMovesBB(pStack, pos, pos->uToMove); +#else + if (pos->uToMove == BLACK) { for(u = 0; u < pos->uPawnCount[BLACK]; u++) { @@ -2175,7 +3419,7 @@ Return value: } } else { ASSERT(pos->uToMove == WHITE); - for(u = 0; u < pos->uPawnCount[WHITE]; u++) + for(u = 0; u < pos->uPawnCount[WHITE]; u++) { c = pos->cPawns[WHITE][u]; #ifdef DEBUG @@ -2188,8 +3432,188 @@ Return value: GenerateWhitePawn(pStack, pos, c); } } +#endif +} + +// +// board_representation/MOVEGEN_MIGRATION.md section 3: fully +// bitboard-driven alternative to _GenerateAllMoves, forked at this +// level (not folded into _GenerateAllMoves's own JumpTable-based body +// via an #if) specifically to eliminate _GenerateAllMoves's own +// indirect-call dispatch, not just to swap which per-piece-type +// function gets called. +// +// _GenerateAllMoves's cNonPawns[side][] loop is a flat list mixing all +// non-pawn piece types together (pieces are added/removed via +// swap-with-last, so there is no contiguous per-type range to slice) +// -- that mixed ordering is *why* it needs +// JumpTable[pos->rgSquare[c].pPiece], an indirect call whose target +// changes almost every iteration as the loop walks across different +// piece types, close to the worst case for a CPU's indirect-branch +// predictor. Every per-piece-type speed benchmark in this migration +// (testgenerate.c's TestGenerateKnightSpeed and friends) called its +// _Generate*BB function directly, bypassing JumpTable entirely -- so +// none of those numbers ever measured, or could benefit from +// removing, this dispatch cost. This function is the piece that +// actually exercises that question: pos->bbPieces[side][KNIGHT/ +// BISHOP/ROOK/QUEEN] already partitions squares by type (unlike +// cNonPawns), so each piece type gets its own bit-extraction loop +// calling its specific _Generate*BB function BY NAME -- a +// statically-known, likely-inlinable direct call, no function pointer +// anywhere in this function. +// +// Only exists (and is only substituted in for _GenerateAllMoves, see +// the #define below) when every non-pawn, non-castling piece type's +// bitboard toggle is defined -- a partial-rollout mix (e.g. knight and +// king migrated, rook/bishop/queen not yet) still needs +// _GenerateAllMoves's cNonPawns/JumpTable path, since that path is the +// only one that knows how to fall back to a still-mailbox piece type +// while also correctly finding already-migrated ones by iterating the +// same mixed list. This function does not attempt to support that +// mixed case -- see MOVEGEN_MIGRATION.md for why an all-or-nothing +// fork was chosen over threading partial-rollout support through this +// function too. +// +void +_GenerateAllMovesBB(IN MOVE_STACK *pStack, + IN POSITION *pos) +/** + +Routine description: + + Fully bitboard-driven equivalent of _GenerateAllMoves -- see the + block comment above. Produces the exact same pseudo-legal move set + (same over-generation behavior, no legal-awareness added, and no + change to which moves are generated, only how the dispatch to each + piece type's generator happens) -- see MOVEGEN_MIGRATION.md + section 1's explicit non-goal. + +Parameters: + + MOVE_STACK *pStack : the move stack + POSITION *pos : the board position + +Return value: + + static void + +**/ +{ + ULONG uSide = pos->uToMove; + BITBOARD bb; + ULONG uBitIndex; + COOR c; +#if !defined(GENERATE_PAWN_BITBOARD) + ULONG u; +#endif +#if defined(DEBUG) && !defined(GENERATE_PAWN_BITBOARD) + PIECE p; +#endif + + pStack->bbFriendlyOccupied = _BuildFriendlySideBB(pos, uSide); + pStack->bbOccupied = _BuildFullOccupiedBB(pos); + + bb = pos->bbPieces[uSide][KNIGHT]; + while (bb) + { + uBitIndex = FastFirstBit(bb) - 1; + bb &= (bb - 1); + c = BIT_NUMBER_TO_COOR(uBitIndex); + ASSERT(IS_KNIGHT(pos->rgSquare[c].pPiece)); + _GenerateKnightBB(pStack, pos, c); + } + + bb = pos->bbPieces[uSide][BISHOP]; + while (bb) + { + uBitIndex = FastFirstBit(bb) - 1; + bb &= (bb - 1); + c = BIT_NUMBER_TO_COOR(uBitIndex); + ASSERT(IS_BISHOP(pos->rgSquare[c].pPiece)); + _GenerateBishopBB(pStack, pos, c); + } + + bb = pos->bbPieces[uSide][ROOK]; + while (bb) + { + uBitIndex = FastFirstBit(bb) - 1; + bb &= (bb - 1); + c = BIT_NUMBER_TO_COOR(uBitIndex); + ASSERT(IS_ROOK(pos->rgSquare[c].pPiece)); + _GenerateRookBB(pStack, pos, c); + } + + bb = pos->bbPieces[uSide][QUEEN]; + while (bb) + { + uBitIndex = FastFirstBit(bb) - 1; + bb &= (bb - 1); + c = BIT_NUMBER_TO_COOR(uBitIndex); + ASSERT(IS_QUEEN(pos->rgSquare[c].pPiece)); + _GenerateQueenBB(pStack, pos, c); + } + + // King has no bitboard of its own (a single square, cNonPawns[ + // side][0] -- see POSITION's bbPieces field comment in chess.h for + // why a bitboard would add nothing here); still a direct, + // statically-known call, same as the four loops above. + c = pos->cNonPawns[uSide][0]; + ASSERT(IS_KING(pos->rgSquare[c].pPiece)); + _GenerateKingBB(pStack, pos, c); + + // Pawns: unchanged from _GenerateAllMoves -- not in scope for this + // migration (section 3 step 5's pawn note) unless + // GENERATE_PAWN_BITBOARD is also defined, in which case pawns get + // the same bulk treatment via _GenerateAllPawnMovesBB -- pawns' + // own toggle is independent of the five above (section 6). +#if defined(GENERATE_PAWN_BITBOARD) + _GenerateAllPawnMovesBB(pStack, pos, uSide); +#else + if (uSide == BLACK) + { + for(u = 0; u < pos->uPawnCount[BLACK]; u++) + { + c = pos->cPawns[BLACK][u]; +#ifdef DEBUG + ASSERT(IS_ON_BOARD(c)); + p = pos->rgSquare[c].pPiece; + ASSERT(!IS_EMPTY(p)); + ASSERT(IS_PAWN(p)); + ASSERT(GET_COLOR(p) == uSide); +#endif + GenerateBlackPawn(pStack, pos, c); + } + } + else + { + ASSERT(uSide == WHITE); + for(u = 0; u < pos->uPawnCount[WHITE]; u++) + { + c = pos->cPawns[WHITE][u]; +#ifdef DEBUG + ASSERT(IS_ON_BOARD(c)); + p = pos->rgSquare[c].pPiece; + ASSERT(!IS_EMPTY(p)); + ASSERT(IS_PAWN(p)); + ASSERT(GET_COLOR(p) == uSide); +#endif + GenerateWhitePawn(pStack, pos, c); + } + } +#endif } +// Whole-dispatch fork -- see _GenerateAllMovesBB's block comment for +// why this is a #define swap of the entire function (matching +// chess.h's GetAttacks precedent) rather than a branch nested inside +// _GenerateAllMoves. Only fires when every non-pawn, non-castling +// piece type is bitboard-backed; any partial mix still uses +// _GenerateAllMoves's cNonPawns/JumpTable path unchanged. +#if defined(GENERATE_KNIGHT_BITBOARD) && defined(GENERATE_KING_BITBOARD) && \ + defined(GENERATE_ROOK_BITBOARD) && defined(GENERATE_BISHOP_BITBOARD) && \ + defined(GENERATE_QUEEN_BITBOARD) +#define _GenerateAllMoves _GenerateAllMovesBB +#endif static ULONG @@ -2219,17 +3643,28 @@ Return value: **/ { +#if !defined(GENERATE_ESCAPES_KING_BITBOARD) || !defined(GENERATE_ESCAPES_BLOCK_BITBOARD) ULONG u; +#endif +#if !defined(GENERATE_ESCAPES_KING_BITBOARD) ULONG v; +#endif COOR c; +#if !defined(GENERATE_ESCAPES_BLOCK_BITBOARD) COOR cDefender; +#endif PIECE p; PIECE pKing; COOR cKing = pos->cNonPawns[pos->uToMove][0]; +#if !defined(GENERATE_ESCAPES_KING_BITBOARD) int iIndex; +#endif SEE_LIST rgCheckers; +#if !defined(GENERATE_ESCAPES_KING_BITBOARD) int iDelta; +#endif ULONG uReturn = 0; +#if !defined(GENERATE_ESCAPES_BLOCK_BITBOARD) static void (*JumpTable[]) (MOVE_STACK *, POSITION *, COOR, COOR, COOR) = { @@ -2248,6 +3683,7 @@ Return value: InvalidSaveMe, // kings already considered InvalidSaveMe // kings already considered }; +#endif ASSERT(IS_KING(pos->rgSquare[cKing].pPiece)); ASSERT(TRUE == InCheck(pos, pos->uToMove)); @@ -2271,6 +3707,50 @@ Return value: ASSERT(GET_COLOR(pKing) == pos->uToMove); ASSERT(IS_KING(pKing)); ASSERT(OPPOSITE_COLORS(pKing, rgCheckers.data[0].pPiece)); +#if defined(GENERATE_ESCAPES_KING_BITBOARD) + // + // board_representation/MOVEGEN_MIGRATION.md section 6a Phase 1: + // g_KingAttacksBB gives every candidate flight square in one + // lookup (already excludes friendly-occupied squares); the + // mailbox version's manual per-checker x-ray loop below is + // replaced entirely by testing _WhoAttacksSquareBB against + // occupancy with the king itself removed -- a slider whose ray + // was only blocked by the king's own (pre-move) body now correctly + // shows up as attacking a candidate square still on that ray, + // exactly the case the manual loop existed to catch by hand. + // Pawns are checked separately since _WhoAttacksSquareBB + // deliberately excludes them (see its header comment). + { + ULONG uEnemy = FLIP(pos->uToMove); + // pKing is only used inside ASSERTs below, which vanish in a + // non-DEBUG build -- silence the resulting "set but not used" + // warning explicitly rather than leave it looking accidental. + (void)pKing; + BITBOARD bbFriendly = _BuildFriendlySideBB(pos, pos->uToMove); + BITBOARD bbOccupiedWithoutKing = + _BuildFullOccupiedBB(pos) & ~COOR_TO_BB(cKing); + BITBOARD bbDest = g_KingAttacksBB[cKing] & ~bbFriendly; + ULONG uBitIndex; + + while (bbDest) + { + uBitIndex = FastFirstBit(bbDest) - 1; + bbDest &= (bbDest - 1); + c = BIT_NUMBER_TO_COOR(uBitIndex); + p = pos->rgSquare[c].pPiece; + ASSERT(IS_EMPTY(p) || OPPOSITE_COLORS(p, pKing)); + + if ((0 == _WhoAttacksSquareBB(pos, c, uEnemy, + bbOccupiedWithoutKing)) && + (0 == (g_PawnAttackOriginBB[uEnemy][c] & + pos->bbPawns[uEnemy]))) + { + _AddNormalMove(pStack, pos, cKing, c, p); + uReturn += 0x00010000; + } + } + } +#else u = 0; while(0 != g_iQKDeltas[u]) { @@ -2332,6 +3812,7 @@ Return value: loop: ; } +#endif // // N.B. If there is more than one piece checking the king then @@ -2370,6 +3851,74 @@ Return value: // checking piece. // c = rgCheckers.data[0].cLoc; +#if defined(GENERATE_ESCAPES_BLOCK_BITBOARD) + // + // board_representation/MOVEGEN_MIGRATION.md section 6a Phase 2: + // pos->cNonPawns[side][] mixes every non-pawn piece type together + // (same reason as _GenerateAllMoves's own loop -- no contiguous + // per-type range to slice), which is why the mailbox path above + // needs JumpTable[p], an indirect call whose target changes almost + // every iteration -- close to the worst case for a CPU's + // indirect-branch predictor. pos->bbPieces[side][KNIGHT/BISHOP/ + // ROOK/QUEEN] sidesteps this exactly like _GenerateAllMovesBB did: + // each piece type gets its own bit-extraction loop calling its + // specific _SaveMe*BB function BY NAME, no function pointer + // anywhere in this block. + { + BITBOARD bbTargetMask; + BITBOARD bb; + ULONG uBitIndex; + COOR cDef; + + // Unlike _GenerateAllMoves, GENERATE_ESCAPES's call site never + // precomputes these -- set them here, once, for every + // _SaveMe*BB call below to share (same amortization reasoning + // as _GenerateAllMoves's own precompute block). + pStack->bbFriendlyOccupied = _BuildFriendlySideBB(pos, pos->uToMove); + pStack->bbOccupied = _BuildFullOccupiedBB(pos); + bbTargetMask = + _ComputeCheckTargetMaskBB(cKing, c, pStack->bbOccupied); + + bb = pos->bbPieces[pos->uToMove][KNIGHT]; + while (bb) + { + uBitIndex = FastFirstBit(bb) - 1; + bb &= (bb - 1); + cDef = BIT_NUMBER_TO_COOR(uBitIndex); + _SaveMeKnightBB(pStack, pos, cDef, cKing, bbTargetMask); + } + + bb = pos->bbPieces[pos->uToMove][BISHOP]; + while (bb) + { + uBitIndex = FastFirstBit(bb) - 1; + bb &= (bb - 1); + cDef = BIT_NUMBER_TO_COOR(uBitIndex); + _SaveMeBishopBB(pStack, pos, cDef, cKing, bbTargetMask); + } + + bb = pos->bbPieces[pos->uToMove][ROOK]; + while (bb) + { + uBitIndex = FastFirstBit(bb) - 1; + bb &= (bb - 1); + cDef = BIT_NUMBER_TO_COOR(uBitIndex); + _SaveMeRookBB(pStack, pos, cDef, cKing, bbTargetMask); + } + + bb = pos->bbPieces[pos->uToMove][QUEEN]; + while (bb) + { + uBitIndex = FastFirstBit(bb) - 1; + bb &= (bb - 1); + cDef = BIT_NUMBER_TO_COOR(uBitIndex); + _SaveMeQueenBB(pStack, pos, cDef, cKing, bbTargetMask); + } + + _SaveMeAllPawnMovesBB(pStack, pos, pos->uToMove, cKing, c, + bbTargetMask); + } +#else for (u = 1; // don't consider the king u < pos->uNonPawnCount[pos->uToMove][0]; u++) @@ -2384,7 +3933,7 @@ Return value: } // Consider all pawns too - if (pos->uToMove == BLACK) + if (pos->uToMove == BLACK) { for (u = 0; u < pos->uPawnCount[BLACK]; u++) { @@ -2399,7 +3948,7 @@ Return value: } } else { ASSERT(pos->uToMove == WHITE); - for (u = 0; u < pos->uPawnCount[WHITE]; u++) + for (u = 0; u < pos->uPawnCount[WHITE]; u++) { cDefender = pos->cPawns[WHITE][u]; #ifdef DEBUG @@ -2411,6 +3960,7 @@ Return value: SaveMeWhitePawn(pStack, pos, cDefender, cKing, c); } } +#endif return(uReturn); } |
