/** Copyright (c) Scott Gasch Module Name: board.c Abstract: Chess-board related functions. Author: Scott Gasch (scott.gasch@gmail.com) 15 Apr 2004 Revision History: $Id: board.c 345 2007-12-02 22:56:42Z scott $ **/ #include "chess.h" // See eval.c's identical comment: routes the debug-dump/consistency- // check bitboard-scan loops (added retiring cNonPawns/cPawns/ // uPawnCount/uNonPawnCount) to the inlined FastCoorFromBitBoardRank8ToRank1 // (chess.h) instead of the real out-of-line extern call. #define CoorFromBitBoardRank8ToRank1 FastCoorFromBitBoardRank8ToRank1 void SetRootToInitialPosition(void) /** Routine description: Sets the root position to the initial position. Parameters: none Return value: void **/ { #ifdef DEBUG POSITION *pos; #endif (void)SetRootPosition(STARTING_POSITION_IN_FEN); #ifdef DEBUG pos = GetRootPosition(); // // Sanity check the sig system. Note: if this changes then any // opening books need to be reconstructed. // ASSERT(pos->u64NonPawnSig == 0xd46f3e84d897a443ULL); ASSERT(pos->u64PawnSig == 0xa9341a54d7352d3cULL); ASSERT_ASM_ASSUMPTIONS; #endif } CHAR * DrawTextBoardFromPosition(POSITION *pos) /** Routine description: Draw a text-based representation of the board configuration in POSITION pos and returned it as a string. Note: this function is _NOT_ thread safe. Parameters: POSITION *pos : position to draw Return value: static char * : my drawing **/ { static char buf[MEDIUM_STRING_LEN_CHAR]; char *pch = buf; size_t cbLeft; COOR f, r; memset(buf, 0, sizeof(buf)); strcpy(buf, " 0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07\n"); strcat(buf, " +---------------------------------------+\n"); pch = buf + strlen(buf); for (r = 8; r >= 1; r--) { cbLeft = sizeof(buf) - (size_t)(pch - buf); pch += snprintf(pch, cbLeft, " %u |", r); for (f = A; f <= H; f++) { cbLeft = sizeof(buf) - (size_t)(pch - buf); if (IS_WHITE_SQUARE_FR(f, r)) { if (IS_EMPTY(pos->rgSquare[FILE_RANK_TO_COOR(f,r)].pPiece)) { pch += snprintf(pch, cbLeft, "%s", " |"); } else { pch += snprintf(pch, cbLeft, " %2s |", PieceAbbrev(pos->rgSquare[FILE_RANK_TO_COOR(f,r)].pPiece)); } } else { if (IS_EMPTY(pos->rgSquare[FILE_RANK_TO_COOR(f,r)].pPiece)) { pch += snprintf(pch, cbLeft, "%s", "::::|"); } else { pch += snprintf(pch, cbLeft, ":%2s:|", PieceAbbrev(pos->rgSquare[FILE_RANK_TO_COOR(f,r)].pPiece)); } } } cbLeft = sizeof(buf) - (size_t)(pch - buf); pch += snprintf(pch, cbLeft, " 0x%x0\n", (8 - r)); } strcat(buf, " +---------------------------------------+\n"); strcat(buf, " A B C D E F G H\n\n"); return(buf); } FLAG IsBoardLegal(POSITION *pos) /** Routine description: True if *pos describes a position reachable through legal chess play: no pawns on rank 1/8, and the side NOT on move isn't in check (that can only happen if whoever moved last left their own king in check -- illegal, and never reachable through real play). Moved here (2026-10-15, was testsup.c) from test-only status: gamelist.c's SetRootPosition needs this on every build profile, since testsup.c only linked under TEST/EVAL_DUMP. Parameters: POSITION *pos Return value: FLAG **/ { COOR c; for (c = A1; c <= H1; c++) { if (!IS_EMPTY(pos->rgSquare[c].pPiece)) { if (IS_PAWN(pos->rgSquare[c].pPiece)) { return(FALSE); } } } for (c = A8; c <= H8; c++) { if (!IS_EMPTY(pos->rgSquare[c].pPiece)) { if (IS_PAWN(pos->rgSquare[c].pPiece)) { return(FALSE); } } } if (InCheck(pos, WHITE) && InCheck(pos, BLACK)) { return(FALSE); } if (InCheck(pos, FLIP(pos->uToMove))) { return(FALSE); } // En passant square must be consistent with whose move it is, not // just "a plausible ep square in isolation" (chess.h's // VALID_EP_SQUARE already checks that -- rank 3 or 6 -- but not // against uToMove). An ep square records the square a just-moved // pawn passed over: if it's White to move, BLACK must have just // double-pushed (passing over rank 6); if Black to move, WHITE // must have (passing over rank 3). Also verify the double-moved // pawn is actually there and its origin square is now empty -- // not reachable through real play otherwise. 2026-10-15: a // malformed hand-built FEN (here, testgenerate.c's // TestCapturesAndPromotionsGenerator fixture table) fell through // this exact gap -- ep square on rank 6 with BLACK to move, // self-contradictory, but VALID_EP_SQUARE alone can't catch it. if (IS_ON_BOARD(pos->cEpSquare)) { COOR cEp = pos->cEpSquare; ULONG uJustMoved = FLIP(pos->uToMove); COOR cDoubleMovedPawn, cOriginSquare; if (WHITE == pos->uToMove) { if (!RANK6(cEp)) { return(FALSE); } cDoubleMovedPawn = FILE_RANK_TO_COOR(FILE(cEp), 5); cOriginSquare = FILE_RANK_TO_COOR(FILE(cEp), 7); } else { if (!RANK3(cEp)) { return(FALSE); } cDoubleMovedPawn = FILE_RANK_TO_COOR(FILE(cEp), 4); cOriginSquare = FILE_RANK_TO_COOR(FILE(cEp), 2); } if (!IS_PAWN(pos->rgSquare[cDoubleMovedPawn].pPiece) || (GET_COLOR(pos->rgSquare[cDoubleMovedPawn].pPiece) != uJustMoved) || !IS_EMPTY(pos->rgSquare[cOriginSquare].pPiece)) { return(FALSE); } } return(TRUE); } FLAG VerifyPositionConsistency(POSITION *pos, FLAG fContinue) /** Routine description: Verifies the internal consistency of a POSITION structure. Looks for defects like bad piece lists, bad piece counts, bad material counts, etc... Parameters: POSITION *pos : the position to verify Return value: FLAG : TRUE if consistent, FALSE otherwise **/ { static char *szExplainations[] = { "Pawn signature mismatch", "Main signature mismatch", "Invalid enpassant square", "Material balance for white != -Material balance for black", "White castled already and can still castle", "Black castled already and can still castle", "Pawn list points to non-pawn square on board", "Square pointed to by pawn list doesn't point back", "Piece list points to non-piece square on board", "Square pointed to by the non-piece list doesn't point back", "Non-king piece in 0th position of list", "Invalid piece sitting in non-pawn list entry", "Counted pawn material != expected material sum", "Counted piece material != expected material sum", "Total army material != expected material sum", "More white square bishops than total bishop count", "Strange bits flipped in board square", "More pawns on board than accounted for in pawn material", "More pieces on board than accounted for in piece material", "Extra pieces on board that are not accounted for", "Fifty move counter is too high", "bbPieces bitboard doesn't match piece list", "bbPawns bitboard doesn't match pawn list", "bbOccupied bitboard doesn't match piece list", "bbOccupiedSide bitboard doesn't match piece list", }; ULONG u, v; COOR c; ULONG uPawnMaterial[2] = {0, 0}; ULONG uPawnCount[2] = {0, 0}; ULONG uNonPawnMaterial[2] = {0, 0}; ULONG uNonPawnCount[2][7]; BITBOARD bbPieces[2][8]; BITBOARD bbPawns[2]; BITBOARD bbOccupied = 0; BITBOARD bbOccupiedSide[2] = {0, 0}; ULONG uSigmaNonPawnCount[2] = {0, 0}; ULONG uWhiteSqBishopCount[2] = {0, 0}; UINT64 u64Computed; PIECE p; FLAG fRet = FALSE; ULONG uReason = (ULONG)-1; memset(uNonPawnCount, 0, sizeof(uNonPawnCount)); memset(bbPieces, 0, sizeof(bbPieces)); memset(bbPawns, 0, sizeof(bbPawns)); u64Computed = ComputeSig(pos); if (pos->u64NonPawnSig != u64Computed) { uReason = 0; goto end; } u64Computed = ComputePawnSig(pos); if (pos->u64PawnSig != u64Computed) { uReason = 1; goto end; } if (!VALID_EP_SQUARE(pos->cEpSquare)) { uReason = 2; goto end; } if (pos->iMaterialBalance[WHITE] != -pos->iMaterialBalance[BLACK]) { uReason = 3; goto end; } if (pos->iMaterialBalance[WHITE] != ((SCORE)(PawnMaterial(pos, WHITE) + pos->uNonPawnMaterial[WHITE]) - (SCORE)(PawnMaterial(pos, BLACK) + pos->uNonPawnMaterial[BLACK]))) { uReason = 3; goto end; } FOREACH_COLOR(u) { // // If a side has already castled it shouldn't have legal castling // options. // if (TRUE == pos->fCastled[u]) { if (u == WHITE) { if (pos->bvCastleInfo & WHITE_CAN_CASTLE) { uReason = 4; goto end; } } else { ASSERT(u == BLACK); if (pos->bvCastleInfo & BLACK_CAN_CASTLE) { uReason = 5; goto end; } } } // // Make sure the pawns are where they should be -- rebuilt by // scanning the mailbox directly (there is no cPawns[] piece- // location list anymore, see chess.h's bbPawns comment). // FOREACH_SQUARE(c) { if (!IS_ON_BOARD(c)) { continue; } if (pos->rgSquare[c].pPiece != ((PAWN << 1) | u)) { continue; } uPawnMaterial[u] += VALUE_PAWN; uPawnCount[u]++; bbPawns[u] |= COOR_TO_BB(c); bbOccupied |= COOR_TO_BB(c); bbOccupiedSide[u] |= COOR_TO_BB(c); } // // Make sure the non-pawns are where they should be -- rebuilt // by scanning the mailbox directly (there is no cNonPawns[] // piece-location list anymore, see chess.h's cKingSq/bbPieces // comments) rather than trusting any incrementally-maintained // state. // FOREACH_SQUARE(c) { if (!IS_ON_BOARD(c)) { continue; } p = pos->rgSquare[c].pPiece; if ((EMPTY == p) || IS_PAWN(p) || (PIECE_COLOR(p) != u)) { continue; } if (!IS_VALID_PIECE(p)) { uReason = 11; goto end; } if (IS_KING(p)) { if (pos->cKingSq[u] != c) { uReason = 10; goto end; } } else { bbPieces[u][PIECE_TYPE(p)] |= COOR_TO_BB(c); } uNonPawnCount[u][PIECE_TYPE(p)]++; bbOccupied |= COOR_TO_BB(c); bbOccupiedSide[u] |= COOR_TO_BB(c); uNonPawnMaterial[u] += PIECE_VALUE(p); if ((IS_BISHOP(p)) && (IS_WHITE_SQUARE_COOR(c))) { uWhiteSqBishopCount[u]++; } } } FOREACH_COLOR(u) { // // Make sure the materials and counts match up with what we // got when walking the piece lists // if (PawnMaterial(pos, u) != uPawnMaterial[u]) { uReason = 12; goto end; } // // Per-type counts no longer stored on POSITION (see chess.h's // bbPieces comment) -- bbPieces itself is verified exactly // against the independently-rebuilt bbPieces[u][v] just below // (uReason 21), a stronger check than a population count would // be, so uSigmaNonPawnCount just accumulates the local, // freshly-rebuilt-from-the-mailbox-scan counts for the total // check further down. // for (v = KNIGHT; v <= KING; v++) { uSigmaNonPawnCount[u] += uNonPawnCount[u][v]; } for (v = KNIGHT; v < KING; v++) { if (pos->bbPieces[u][v] != bbPieces[u][v]) { uReason = 21; goto end; } } if (pos->bbPawns[u] != bbPawns[u]) { uReason = 22; goto end; } // // uSigmaNonPawnCount includes the king (see the KNIGHT..KING // loop above), matching TotalNonPawnCount(pos, u). // if (uSigmaNonPawnCount[u] != TotalNonPawnCount(pos, u)) { uReason = 14; goto end; } // // uWhiteSqBishopCount[u] here is the from-scratch mailbox // tally (see the FOREACH_SQUARE walk above) -- verify it // matches WhiteSqBishopCount's bbPieces-derived value (chess.h), // same mailbox/bitboard cross-check as bbPieces itself gets. // if (uWhiteSqBishopCount[u] != WhiteSqBishopCount(pos, u)) { uReason = 15; goto end; } } if (pos->bbOccupied != bbOccupied) { uReason = 23; goto end; } if ((pos->bbOccupiedSide[WHITE] != bbOccupiedSide[WHITE]) || (pos->bbOccupiedSide[BLACK] != bbOccupiedSide[BLACK])) { uReason = 24; goto end; } // // Now walk the actual board and reduce the material counts we got // by walking the piece lists. If everything is ok then the // material counts will end up at 0. If there are pieces on the // board that are not on a list or pieces in a list that are not // on the board this will catch it. // FOREACH_SQUARE(c) { if (IS_ON_BOARD(c)) { p = pos->rgSquare[c].pPiece; if (EMPTY != p) { // // Make sure none of these have strange bits flipped // if (p & 0xFFFFFFF0) { uReason = 16; goto end; } u = PIECE_COLOR(p); if (IS_PAWN(p)) { // // No per-square index to sanity-check here anymore // (pawns don't carry a list index either -- see // chess.h's bbPawns comment). // uPawnMaterial[u] -= VALUE_PAWN; } else { // // No per-square index to sanity-check here anymore // (non-pawn pieces don't carry a list index -- // see chess.h's cKingSq/bbPieces comments); the // per-color/per-type census taken above already // catches a piece present on the board but not // accounted for. // uNonPawnMaterial[u] -= PIECE_VALUE(p); } } } } if ((uPawnMaterial[WHITE] + uPawnMaterial[BLACK] + uNonPawnMaterial[WHITE] + uNonPawnMaterial[BLACK]) != 0) { uReason = 19; goto end; } // if (pos->uFifty > 101) // { // uReason = 20; // goto end; // } // // Looks Ok // fRet = TRUE; end: if (FALSE == fRet) { if (FALSE == fContinue) { UtilPanic(INCONSISTENT_POSITION, pos, szExplainations[uReason], NULL, NULL, __FILE__, __LINE__); ASSERT(FALSE); } } return(fRet); } FLAG PositionsAreEquivalent(POSITION *p1, POSITION *p2) /** Routine description: Compare two positions and decide whether they are the same position. Note: this code is meant to be accessed from test code or from non-speed critical codepaths. Comparing their signatures should be sufficient to determine whether they are the same in production code. Parameters: POSITION *p1 : first position to compare POSITION *p2 : second position to compare Return value: FLAG : TRUE if they are the same position, FALSE otherwise **/ { COOR c; ULONG u; if ((p1->u64NonPawnSig != p2->u64NonPawnSig) || (p1->u64PawnSig != p2->u64PawnSig) || (p1->uToMove != p2->uToMove) || (p1->uFifty != p2->uFifty) || (p1->bvCastleInfo != p2->bvCastleInfo) || (p1->cEpSquare != p2->cEpSquare)) { return(FALSE); } FOREACH_COLOR(u) { if ((p1->fCastled[u] != p2->fCastled[u]) || (WhiteSqBishopCount(p1, u) != WhiteSqBishopCount(p2, u))) { return(FALSE); } } FOREACH_SQUARE(c) { if (!IS_ON_BOARD(c)) continue; if (p1->rgSquare[c].pPiece != p2->rgSquare[c].pPiece) { return(FALSE); } } VerifyPositionConsistency(p1, FALSE); VerifyPositionConsistency(p2, FALSE); return(TRUE); } char * CastleInfoString(BITV bv) /** Routine description: Generate and returns a small string describing the possible castling choices in the bitvector passed in for use in a friandly display or in a FEN string representing a position. This function is _NOT_ thread safe. Parameters: BITV bv : the castling bitvector Return value: char * : the string, either '-' signifying none possible or some combination of: K = white kingside possible Q = white queenside possibe k = black kingside possible q = black queenside possible **/ { static char buf[5]; char *p = buf; memset(buf, 0, sizeof(buf)); ASSERT((bv & ~CASTLE_ALL_POSSIBLE) == 0); if (bv == 0) { *p++ = '-'; } else { if (bv & CASTLE_WHITE_SHORT) { *p++ = 'K'; } if (bv & CASTLE_WHITE_LONG) { *p++ = 'Q'; } if (bv & CASTLE_BLACK_SHORT) { *p++ = 'k'; } if (bv & CASTLE_BLACK_LONG) { *p++ = 'q'; } } return(buf); } void DumpPosition(POSITION *pos) /** Routine description: Dump an ASCII representation of POSITION pos on stdout. Parameters: POSITION *pos Return value: void **/ { char *p; #ifdef DEBUG ULONG u; if (!VerifyPositionConsistency(pos, TRUE)) { Bug("===========================================================================\n" " WARNING: The following position seems to be inconsistent. I'll do my best\n" " to render it but this may not be such a good idea...\n" "===========================================================================\n\n"); } #endif p = PositionToFen(pos); if (g_Options.fRunningUnderXboard) { Trace("; PositionToFen(pos): %s\n", p); goto end; } else { Trace("FEN: %s\n\n", p); } #ifdef DEBUG Trace("--------------------------------------------------------------------------------\n" "PositionToFen(pos): %s\n\n" "POSITION (@%p):\n" " +0x%03x u64NonPawnSig : %" COMPILER_LONGLONG_HEX_FORMAT "\n" " +0x%03x u64PawnSig : %" COMPILER_LONGLONG_HEX_FORMAT "\n" " u64NonPawnSig ^ u64PawnSig: %" COMPILER_LONGLONG_HEX_FORMAT "\n" " +0x%03x pSquare[128] : \n\n" "%s" " +0x%03x uToMove : 0x%02x (%s)\n" " +0x%03x uFifty : 0x%02x (%u moves)\n" " +0x%03x fCastled[2] : W:0x%02x B:0x%02x\n" " +0x%03x bvCastleInfo : 0x%02x (%s)\n" " +0x%03x cEpSquare : 0x%02x (%s)\n" " +0x%03x bbPawns[2] (pawn locations): \n", p, pos, OFFSET_OF(u64PawnSig, POSITION), pos->u64PawnSig, OFFSET_OF(u64NonPawnSig, POSITION), pos->u64NonPawnSig, pos->u64NonPawnSig ^ pos->u64PawnSig, OFFSET_OF(rgSquare, POSITION), DrawTextBoardFromPosition(pos), OFFSET_OF(uToMove, POSITION), pos->uToMove, ColorToString(pos->uToMove), OFFSET_OF(uFifty, POSITION), pos->uFifty, pos->uFifty, OFFSET_OF(fCastled, POSITION), pos->fCastled[WHITE], pos->fCastled[BLACK], OFFSET_OF(bvCastleInfo, POSITION), pos->bvCastleInfo, CastleInfoString(pos->bvCastleInfo), OFFSET_OF(cEpSquare, POSITION), pos->cEpSquare, (IS_ON_BOARD(pos->cEpSquare) ? CoorToString(pos->cEpSquare) : "none"), OFFSET_OF(bbPawns, POSITION)); Trace(" W: "); { BITBOARD bbDump = pos->bbPawns[WHITE]; COOR cDump; while (IS_ON_BOARD(cDump = CoorFromBitBoardRank8ToRank1(&bbDump))) { Trace("0x%02x[%s] ", cDump, CoorToString(cDump)); } } Trace("\n" " B: "); { BITBOARD bbDump = pos->bbPawns[BLACK]; COOR cDump; while (IS_ON_BOARD(cDump = CoorFromBitBoardRank8ToRank1(&bbDump))) { Trace("0x%02x[%s] ", cDump, CoorToString(cDump)); } } Trace("\n" " PawnMaterial() : W:0x%02x (%u, pawn=%u)\n" " : B:0x%02x (%u, pawn=%u)\n" " bbPawns popcount : W:%1u pawns B:%1u pawns\n", PawnMaterial(pos, WHITE), PawnMaterial(pos, WHITE), VALUE_PAWN, PawnMaterial(pos, BLACK), PawnMaterial(pos, BLACK), VALUE_PAWN, FastCountBits(pos->bbPawns[WHITE]), FastCountBits(pos->bbPawns[BLACK])); Trace(" +0x%03x cKingSq[2] + bbPieces[2][8] (non-pawn locations): \n" " W: ", OFFSET_OF(cKingSq, POSITION)); { BITBOARD bbDump; COOR cDump; Trace("0x%02x[%s](K) ", pos->cKingSq[WHITE], CoorToString(pos->cKingSq[WHITE])); ASSERT(IS_ON_BOARD(pos->cKingSq[WHITE])); bbDump = NON_KING_PIECES_BB(pos, WHITE); while (IS_ON_BOARD(cDump = CoorFromBitBoardRank8ToRank1(&bbDump))) { Trace("0x%02x[%s] ", cDump, CoorToString(cDump)); } } Trace("\n" " B: "); { BITBOARD bbDump; COOR cDump; Trace("0x%02x[%s](K) ", pos->cKingSq[BLACK], CoorToString(pos->cKingSq[BLACK])); ASSERT(IS_ON_BOARD(pos->cKingSq[BLACK])); bbDump = NON_KING_PIECES_BB(pos, BLACK); while (IS_ON_BOARD(cDump = CoorFromBitBoardRank8ToRank1(&bbDump))) { Trace("0x%02x[%s] ", cDump, CoorToString(cDump)); } } Trace("\n" " +0x%03x uNonPawnMaterial[2] : W: 0x%02x (%u, pawn=%u)\n" " : B: 0x%02x (%u, pawn=%u)\n" " bbPieces popcounts : WHITE BLACK\n" " KNIGHT : %u %u\n" " BISHOP : %u %u\n" " WHITE SQUARE BISHOP : %u %u\n" " ROOK : %u %u\n" " QUEEN : %u %u\n" " SUM : %u %u\n", OFFSET_OF(uNonPawnMaterial, POSITION), pos->uNonPawnMaterial[WHITE], pos->uNonPawnMaterial[WHITE], VALUE_PAWN, pos->uNonPawnMaterial[BLACK], pos->uNonPawnMaterial[BLACK], VALUE_PAWN, FastCountBits(pos->bbPieces[WHITE][KNIGHT]), FastCountBits(pos->bbPieces[BLACK][KNIGHT]), FastCountBits(pos->bbPieces[WHITE][BISHOP]), FastCountBits(pos->bbPieces[BLACK][BISHOP]), WhiteSqBishopCount(pos, WHITE), WhiteSqBishopCount(pos, BLACK), FastCountBits(pos->bbPieces[WHITE][ROOK]), FastCountBits(pos->bbPieces[BLACK][ROOK]), FastCountBits(pos->bbPieces[WHITE][QUEEN]), FastCountBits(pos->bbPieces[BLACK][QUEEN]), TotalNonPawnCount(pos, WHITE), TotalNonPawnCount(pos, BLACK)); #else Trace("\n%s\n", DrawTextBoardFromPosition(pos)); #endif end: if (NULL != p) { SystemFreeMemory(p); } } // // TODO: DumpBoard //