/** Copyright (c) Scott Gasch Module Name: move.c Abstract: Functions to make and unmake moves. Author: Scott Gasch (scott.gasch@gmail.com) 09 May 2004 Revision History: $Id: move.c 345 2007-12-02 22:56:42Z scott $ **/ #include "chess.h" void SlidePiece(POSITION *pos, COOR cFrom, COOR cTo, NNUE_ACCUMULATOR sNnueAccum[2]) /** Routine description: Slide a piece (i.e. not a pawn) from cFrom to cTo. This is much faster than LiftPiece / PlacePiece. Parameters: POSITION *pos : the board COOR cFrom : square moving from COOR cTo : square moving to NNUE_ACCUMULATOR sNnueAccum[2] : mover's live accumulator pair, kept incrementally up to date for both perspectives -- even when p is a king, in which case the caller (MakeMove) is expected to treat that one perspective's result here as transient/wrong and immediately overwrite it via NnueAccumRecompute; see chess.h's prototype comment. Return value: void **/ { register PIECE p; register ULONG c; #ifdef DEBUG VerifyPositionConsistency(pos, FALSE); ASSERT(IS_ON_BOARD(cFrom)); ASSERT(IS_ON_BOARD(cTo)); ASSERT(IS_EMPTY(pos->rgSquare[cTo].pPiece)); #endif p = pos->rgSquare[cFrom].pPiece; pos->rgSquare[cFrom].pPiece = EMPTY; ASSERT(!IS_PAWN(p)); ASSERT(IS_VALID_PIECE(p)); c = GET_COLOR(p); ASSERT(IS_VALID_COLOR(c)); if (IS_KING(p)) { pos->cKingSq[c] = cTo; } pos->bbPieces[c][PIECE_TYPE(p)] &= ~COOR_TO_BB(cFrom); pos->bbPieces[c][PIECE_TYPE(p)] |= COOR_TO_BB(cTo); pos->bbOccupied &= ~COOR_TO_BB(cFrom); pos->bbOccupied |= COOR_TO_BB(cTo); pos->bbOccupiedSide[c] &= ~COOR_TO_BB(cFrom); pos->bbOccupiedSide[c] |= COOR_TO_BB(cTo); pos->u64NonPawnSig ^= g_u64SigSeeds[cFrom][PIECE_TYPE(p)][c]; pos->u64NonPawnSig ^= g_u64SigSeeds[cTo][PIECE_TYPE(p)][c]; #ifdef DEBUG if (IS_BISHOP(p) && (IS_SQUARE_WHITE(cFrom))) { ASSERT(IS_SQUARE_WHITE(cTo)); } #endif pos->rgSquare[cTo].pPiece = p; pos->rgSquare[cTo].uIndex = INVALID_PIECE_INDEX; #ifdef DEBUG pos->rgSquare[cFrom].uIndex = INVALID_PIECE_INDEX; #endif // A king move is about to have ITS OWN perspective fully // overwritten by a recompute at the MakeMove/UnmakeMove call // site right after this returns (every HalfKAv2_hm feature in // that perspective depends on its own king square -- see // chess.h's NNUE_ACCUMULATOR comment) -- skip that perspective's // update here entirely rather than computing it only to discard // it unread. The opponent's perspective is unaffected by whose // king this is and still needs the ordinary incremental update. // Single branch, predicts near-perfectly (king moves are a small // minority of all SlidePiece calls). if (IS_KING(p)) { ULONG xC = FLIP(c); NnueAccumRemoveFeature(&sNnueAccum[xC], xC, pos->cKingSq[xC], cFrom, p); NnueAccumAddFeature(&sNnueAccum[xC], xC, pos->cKingSq[xC], cTo, p); } else { NnueAccumRemoveFeature(&sNnueAccum[WHITE], WHITE, pos->cKingSq[WHITE], cFrom, p); NnueAccumAddFeature(&sNnueAccum[WHITE], WHITE, pos->cKingSq[WHITE], cTo, p); NnueAccumRemoveFeature(&sNnueAccum[BLACK], BLACK, pos->cKingSq[BLACK], cFrom, p); NnueAccumAddFeature(&sNnueAccum[BLACK], BLACK, pos->cKingSq[BLACK], cTo, p); } #ifdef DEBUG VerifyPositionConsistency(pos, FALSE); #endif } void SlidePawn(POSITION *pos, COOR cFrom, COOR cTo, NNUE_ACCUMULATOR sNnueAccum[2]) /** Routine description: Slide a pawn (i.e. not a piece) from cFrom to cTo. This is much faster than LiftPiece / PlacePiece. Parameters: POSITION *pos : the board COOR cFrom : square moving from COOR cTo : square moving to NNUE_ACCUMULATOR sNnueAccum[2] : mover's live accumulator pair -- see SlidePiece's comment (a pawn is never a king, so there's no own-perspective-recompute exception to worry about here). Return value: void **/ { register PIECE p; register ULONG c; #ifdef DEBUG VerifyPositionConsistency(pos, FALSE); ASSERT(IS_ON_BOARD(cFrom)); ASSERT(IS_ON_BOARD(cTo)); ASSERT(IS_EMPTY(pos->rgSquare[cTo].pPiece)); #endif p = pos->rgSquare[cFrom].pPiece; pos->rgSquare[cFrom].pPiece = EMPTY; ASSERT(IS_PAWN(p)); ASSERT(IS_VALID_PIECE(p)); c = GET_COLOR(p); ASSERT(IS_VALID_COLOR(c)); pos->bbPawns[c] &= ~COOR_TO_BB(cFrom); pos->bbPawns[c] |= COOR_TO_BB(cTo); pos->bbOccupied &= ~COOR_TO_BB(cFrom); pos->bbOccupied |= COOR_TO_BB(cTo); pos->bbOccupiedSide[c] &= ~COOR_TO_BB(cFrom); pos->bbOccupiedSide[c] |= COOR_TO_BB(cTo); pos->u64PawnSig ^= g_u64PawnSigSeeds[cFrom][c]; pos->u64PawnSig ^= g_u64PawnSigSeeds[cTo][c]; pos->rgSquare[cTo].pPiece = p; pos->rgSquare[cTo].uIndex = INVALID_PIECE_INDEX; #ifdef DEBUG pos->rgSquare[cFrom].uIndex = INVALID_PIECE_INDEX; #endif NnueAccumRemoveFeature(&sNnueAccum[WHITE], WHITE, pos->cKingSq[WHITE], cFrom, p); NnueAccumAddFeature(&sNnueAccum[WHITE], WHITE, pos->cKingSq[WHITE], cTo, p); NnueAccumRemoveFeature(&sNnueAccum[BLACK], BLACK, pos->cKingSq[BLACK], cFrom, p); NnueAccumAddFeature(&sNnueAccum[BLACK], BLACK, pos->cKingSq[BLACK], cTo, p); #ifdef DEBUG VerifyPositionConsistency(pos, FALSE); #endif } void SlidePieceWithoutSigs(POSITION *pos, COOR cFrom, COOR cTo, NNUE_ACCUMULATOR sNnueAccum[2]) /** Routine description: Slide a piece (i.e. not a pawn) from cFrom to cTo. This is way faster than LiftPiece / PlacePiece. Also note that this version of the routine doesn't maintain the consistency of the position signatures and should therefore only be called from UnmakeMove. Note: unlike signatures, the NNUE accumulator has no equivalent cheap "restore wholesale from a saved scalar" trick for a non-king-move's opponent-perspective update, so sNnueAccum is still kept incrementally correct here even though sigs aren't -- see SlidePiece's comment for the king-move exception, handled the same way at the UnmakeMove call site (restore-from-backup immediately after, for the castling color's own perspective). Parameters: POSITION *pos : the board COOR cFrom : square moving from COOR cTo : square moving to NNUE_ACCUMULATOR sNnueAccum[2] : mover's live accumulator pair Return value: void **/ { register PIECE p; register ULONG c; #ifdef DEBUG ASSERT(IS_ON_BOARD(cFrom)); ASSERT(IS_ON_BOARD(cTo)); ASSERT(IS_EMPTY(pos->rgSquare[cTo].pPiece)); #endif p = pos->rgSquare[cFrom].pPiece; pos->rgSquare[cFrom].pPiece = EMPTY; ASSERT(IS_VALID_PIECE(p)); ASSERT(!IS_PAWN(p)); c = GET_COLOR(p); ASSERT(IS_VALID_COLOR(c)); if (IS_KING(p)) { pos->cKingSq[c] = cTo; } pos->bbPieces[c][PIECE_TYPE(p)] &= ~COOR_TO_BB(cFrom); pos->bbPieces[c][PIECE_TYPE(p)] |= COOR_TO_BB(cTo); pos->bbOccupied &= ~COOR_TO_BB(cFrom); pos->bbOccupied |= COOR_TO_BB(cTo); pos->bbOccupiedSide[c] &= ~COOR_TO_BB(cFrom); pos->bbOccupiedSide[c] |= COOR_TO_BB(cTo); pos->rgSquare[cTo].pPiece = p; pos->rgSquare[cTo].uIndex = INVALID_PIECE_INDEX; #ifdef DEBUG pos->rgSquare[cFrom].uIndex = INVALID_PIECE_INDEX; #endif // See SlidePiece's comment on the same check. if (IS_KING(p)) { ULONG xC = FLIP(c); NnueAccumRemoveFeature(&sNnueAccum[xC], xC, pos->cKingSq[xC], cFrom, p); NnueAccumAddFeature(&sNnueAccum[xC], xC, pos->cKingSq[xC], cTo, p); } else { NnueAccumRemoveFeature(&sNnueAccum[WHITE], WHITE, pos->cKingSq[WHITE], cFrom, p); NnueAccumAddFeature(&sNnueAccum[WHITE], WHITE, pos->cKingSq[WHITE], cTo, p); NnueAccumRemoveFeature(&sNnueAccum[BLACK], BLACK, pos->cKingSq[BLACK], cFrom, p); NnueAccumAddFeature(&sNnueAccum[BLACK], BLACK, pos->cKingSq[BLACK], cTo, p); } } void SlidePawnWithoutSigs(POSITION *pos, COOR cFrom, COOR cTo, NNUE_ACCUMULATOR sNnueAccum[2]) /** Routine description: Slide a pawn (i.e. not a piece) from cFrom to cTo. This is way faster than LiftPiece / PlacePiece. Also note that this version of the routine doesn't maintain the consistency of the position signatures and should therefore only be called from UnmakeMove. See SlidePieceWithoutSigs's comment on why sNnueAccum is still kept correct here regardless. Parameters: POSITION *pos : the board COOR cFrom : square moving from COOR cTo : square moving to NNUE_ACCUMULATOR sNnueAccum[2] : mover's live accumulator pair Return value: void **/ { register PIECE p; register ULONG c; #ifdef DEBUG ASSERT(IS_ON_BOARD(cFrom)); ASSERT(IS_ON_BOARD(cTo)); ASSERT(IS_EMPTY(pos->rgSquare[cTo].pPiece)); #endif p = pos->rgSquare[cFrom].pPiece; pos->rgSquare[cFrom].pPiece = EMPTY; ASSERT(IS_VALID_PIECE(p)); ASSERT(IS_PAWN(p)); c = GET_COLOR(p); ASSERT(IS_VALID_COLOR(c)); pos->bbPawns[c] &= ~COOR_TO_BB(cFrom); pos->bbPawns[c] |= COOR_TO_BB(cTo); pos->bbOccupied &= ~COOR_TO_BB(cFrom); pos->bbOccupied |= COOR_TO_BB(cTo); pos->bbOccupiedSide[c] &= ~COOR_TO_BB(cFrom); pos->bbOccupiedSide[c] |= COOR_TO_BB(cTo); pos->rgSquare[cTo].pPiece = p; pos->rgSquare[cTo].uIndex = INVALID_PIECE_INDEX; #ifdef DEBUG pos->rgSquare[cFrom].uIndex = INVALID_PIECE_INDEX; #endif NnueAccumRemoveFeature(&sNnueAccum[WHITE], WHITE, pos->cKingSq[WHITE], cFrom, p); NnueAccumAddFeature(&sNnueAccum[WHITE], WHITE, pos->cKingSq[WHITE], cTo, p); NnueAccumRemoveFeature(&sNnueAccum[BLACK], BLACK, pos->cKingSq[BLACK], cFrom, p); NnueAccumAddFeature(&sNnueAccum[BLACK], BLACK, pos->cKingSq[BLACK], cTo, p); } PIECE LiftPiece(POSITION *pos, COOR cSquare, NNUE_ACCUMULATOR sNnueAccum[2]) /** Routine description: Remove the piece at square cSquare from the board. Update POSITION (piece lists, counts, material, etc...) accordingly. Parameters: POSITION *pos, COOR cSquare NNUE_ACCUMULATOR sNnueAccum[2] : mover's live accumulator pair -- a king is never lifted (see ASSERT below, kings are never captured), so there's no own-perspective exception to worry about here, unlike SlidePiece. Return value: PIECE **/ { register PIECE pLifted; ULONG color; register SCORE pv; ULONG u; #ifdef DEBUG VerifyPositionConsistency(pos, FALSE); ASSERT(IS_ON_BOARD(cSquare)); #endif pLifted = pos->rgSquare[cSquare].pPiece; if (IS_EMPTY(pLifted)) { return(pLifted); // ok to return an empty square } pos->rgSquare[cSquare].pPiece = EMPTY; ASSERT(IS_VALID_PIECE(pLifted)); ASSERT(!IS_KING(pLifted)); pos->bbOccupied &= ~COOR_TO_BB(cSquare); pos->bbOccupiedSide[GET_COLOR(pLifted)] &= ~COOR_TO_BB(cSquare); color = GET_COLOR(pLifted); ASSERT(IS_VALID_COLOR(color)); pv = PIECE_VALUE(pLifted); ASSERT(pv > 0); pos->iMaterialBalance[color] -= pv; pos->iMaterialBalance[FLIP(color)] += pv; ASSERT(pos->iMaterialBalance[WHITE] * -1 == pos->iMaterialBalance[BLACK]); if (IS_PAWN(pLifted)) { #ifdef DEBUG pos->rgSquare[cSquare].uIndex = INVALID_PIECE_INDEX; #endif ASSERT(pv == VALUE_PAWN); pos->u64PawnSig ^= g_u64PawnSigSeeds[cSquare][color]; pos->bbPawns[color] &= ~COOR_TO_BB(cSquare); } else { // // Piece is not a pawn -- no piece-location list to maintain // (see cKingSq/bbPieces in chess.h for how king/knight/bishop/ // rook/queen locations are tracked instead), just the // material/count/bitboard bookkeeping below. // ASSERT(pos->uNonPawnMaterial[color] >= VALUE_KING); ASSERT(pv > VALUE_PAWN); pos->uNonPawnMaterial[color] -= pv; ASSERT(pos->uNonPawnMaterial[color] <= VALUE_MAX_ARMY); // // Change per-piece counters. // u = PIECE_TYPE(pLifted); ASSERT((u >= KNIGHT) && (u < KING)); pos->u64NonPawnSig ^= g_u64SigSeeds[cSquare][u][color]; pos->bbPieces[color][u] &= ~COOR_TO_BB(cSquare); } NnueAccumRemoveFeature(&sNnueAccum[WHITE], WHITE, pos->cKingSq[WHITE], cSquare, pLifted); NnueAccumRemoveFeature(&sNnueAccum[BLACK], BLACK, pos->cKingSq[BLACK], cSquare, pLifted); #ifdef DEBUG VerifyPositionConsistency(pos, FALSE); #endif return(pLifted); } PIECE LiftPieceWithoutSigs(POSITION *pos, COOR cSquare, NNUE_ACCUMULATOR sNnueAccum[2]) /** Routine description: Remove the piece at square cSquare from the board. Update POSITION (piece lists, counts, material, etc...) accordingly. Note: LiftPieceWithoutSigs can _only_ be called from UnmakeMove. It is used to unmake a previously made move and therefore does not worry about updating position signatures. UnmakeMove has the pre-move signature in a history list and will klobber the position signature soon anyway. Parameters: POSITION *pos, COOR cSquare Return value: PIECE **/ { register PIECE pLifted; ULONG color; register SCORE pv; ULONG u; ASSERT(IS_ON_BOARD(cSquare)); pLifted = pos->rgSquare[cSquare].pPiece; if (IS_EMPTY(pLifted)) { return(pLifted); // ok to return an empty square } pos->rgSquare[cSquare].pPiece = EMPTY; ASSERT(IS_VALID_PIECE(pLifted)); ASSERT(!IS_KING(pLifted)); pos->bbOccupied &= ~COOR_TO_BB(cSquare); pos->bbOccupiedSide[GET_COLOR(pLifted)] &= ~COOR_TO_BB(cSquare); color = GET_COLOR(pLifted); ASSERT(IS_VALID_COLOR(color)); pv = PIECE_VALUE(pLifted); ASSERT(pv > 0); pos->iMaterialBalance[color] -= pv; pos->iMaterialBalance[FLIP(color)] += pv; ASSERT(pos->iMaterialBalance[WHITE] * -1 == pos->iMaterialBalance[BLACK]); if (IS_PAWN(pLifted)) { #ifdef DEBUG pos->rgSquare[cSquare].uIndex = INVALID_PIECE_INDEX; #endif ASSERT(pv == VALUE_PAWN); pos->bbPawns[color] &= ~COOR_TO_BB(cSquare); } else { // // Piece is not a pawn -- no piece-location list to maintain, // just the material/count/bitboard bookkeeping below. // ASSERT(pos->uNonPawnMaterial[color] >= VALUE_KING); ASSERT(pv > VALUE_PAWN); pos->uNonPawnMaterial[color] -= pv; ASSERT(pos->uNonPawnMaterial[color] <= VALUE_MAX_ARMY); // // Change per-piece counters. // u = PIECE_TYPE(pLifted); ASSERT((u >= KNIGHT) && (u < KING)); pos->bbPieces[color][u] &= ~COOR_TO_BB(cSquare); } NnueAccumRemoveFeature(&sNnueAccum[WHITE], WHITE, pos->cKingSq[WHITE], cSquare, pLifted); NnueAccumRemoveFeature(&sNnueAccum[BLACK], BLACK, pos->cKingSq[BLACK], cSquare, pLifted); return(pLifted); } void PlacePiece(POSITION *pos, COOR cSquare, PIECE pPiece, NNUE_ACCUMULATOR sNnueAccum[2]) /** Routine description: Place a piece back on the board. Link it into the right piece list. Parameters: POSITION *pos, COOR cSquare, PIECE pPiece Return value: void **/ { register ULONG color = GET_COLOR(pPiece); register SCORE pv; ULONG u; #ifdef DEBUG VerifyPositionConsistency(pos, FALSE); ASSERT(IS_EMPTY(pos->rgSquare[cSquare].pPiece)); ASSERT(!IS_KING(pPiece)); #endif pv = PIECE_VALUE(pPiece); pos->iMaterialBalance[color] += pv; pos->iMaterialBalance[FLIP(color)] -= pv; ASSERT(pos->iMaterialBalance[WHITE] * -1 == pos->iMaterialBalance[BLACK]); pos->bbOccupied |= COOR_TO_BB(cSquare); pos->bbOccupiedSide[color] |= COOR_TO_BB(cSquare); if (IS_PAWN(pPiece)) { // // Piece is a pawn -- no piece-location list to maintain (see // bbPawns in chess.h), just the material/count/bitboard // bookkeeping below. // ASSERT(pv == VALUE_PAWN); pos->bbPawns[color] |= COOR_TO_BB(cSquare); pos->u64PawnSig ^= g_u64PawnSigSeeds[cSquare][color]; pos->rgSquare[cSquare].uIndex = INVALID_PIECE_INDEX; } else { // // Piece is not a pawn -- no piece-location list to maintain, // just the material/count/bitboard bookkeeping below. // ASSERT(pv > VALUE_PAWN); pos->uNonPawnMaterial[color] += pv; ASSERT(pos->uNonPawnMaterial[color] <= VALUE_MAX_ARMY); u = PIECE_TYPE(pPiece); ASSERT((u >= KNIGHT) && (u < KING)); pos->u64NonPawnSig ^= g_u64SigSeeds[cSquare][u][color]; pos->bbPieces[color][u] |= COOR_TO_BB(cSquare); pos->rgSquare[cSquare].uIndex = INVALID_PIECE_INDEX; } // // Place the piece on the board // pos->rgSquare[cSquare].pPiece = pPiece; NnueAccumAddFeature(&sNnueAccum[WHITE], WHITE, pos->cKingSq[WHITE], cSquare, pPiece); NnueAccumAddFeature(&sNnueAccum[BLACK], BLACK, pos->cKingSq[BLACK], cSquare, pPiece); #ifdef DEBUG VerifyPositionConsistency(pos, FALSE); #endif } void PlacePieceWithoutSigs(POSITION *pos, COOR cSquare, PIECE pPiece, NNUE_ACCUMULATOR sNnueAccum[2]) /** Routine description: Place a piece back on the board. Link it into the right piece list. Note: PlacePieceWithoutSigs can _only_ be called from UnmakeMove. It is used to unmake a previously made move and therefore does not worry about updating position signatures. UnmakeMove has the pre-move signature in a history list and will klobber the position signature soon anyway. Parameters: POSITION *pos, COOR cSquare, PIECE pPiece Return value: void **/ { register ULONG color = GET_COLOR(pPiece); register SCORE pv; ULONG u; ASSERT(IS_EMPTY(pos->rgSquare[cSquare].pPiece)); ASSERT(!IS_KING(pPiece)); pv = PIECE_VALUE(pPiece); pos->iMaterialBalance[color] += pv; pos->iMaterialBalance[FLIP(color)] -= pv; ASSERT(pos->iMaterialBalance[WHITE] * -1 == pos->iMaterialBalance[BLACK]); pos->bbOccupied |= COOR_TO_BB(cSquare); pos->bbOccupiedSide[color] |= COOR_TO_BB(cSquare); if (IS_PAWN(pPiece)) { // // Piece is a pawn -- no piece-location list to maintain, just // the material/count/bitboard bookkeeping below. // ASSERT(pv == VALUE_PAWN); pos->bbPawns[color] |= COOR_TO_BB(cSquare); pos->rgSquare[cSquare].uIndex = INVALID_PIECE_INDEX; } else { // // Piece is not a pawn -- no piece-location list to maintain, // just the material/count/bitboard bookkeeping below. // ASSERT(pv > VALUE_PAWN); pos->uNonPawnMaterial[color] += pv; ASSERT(pos->uNonPawnMaterial[color] <= VALUE_MAX_ARMY); u = PIECE_TYPE(pPiece); ASSERT((u >= KNIGHT) && (u < KING)); pos->bbPieces[color][u] |= COOR_TO_BB(cSquare); pos->rgSquare[cSquare].uIndex = INVALID_PIECE_INDEX; } // // Place the piece on the board // pos->rgSquare[cSquare].pPiece = pPiece; NnueAccumAddFeature(&sNnueAccum[WHITE], WHITE, pos->cKingSq[WHITE], cSquare, pPiece); NnueAccumAddFeature(&sNnueAccum[BLACK], BLACK, pos->cKingSq[BLACK], cSquare, pPiece); } void SaveCurrentPositionHistoryInContext(SEARCHER_THREAD_CONTEXT *ctx, MOVE mv) /** Routine description: This routine is called just before MakeMove to record some information about the current board position to help unmake the move later. It's also used to detect draws in the search. Note: when this function is called, ply has not been incremented. Parameters: SEARCHER_THREAD_CONTEXT *ctx Return value: void **/ { ULONG uPly = ctx->uPly; POSITION *pos = &(ctx->sPosition); PLY_INFO *pi = &ctx->sPlyInfo[uPly]; // // Save data about the position we are leaving. // ASSERT(ctx->uPly < MAX_PLY_PER_SEARCH); ASSERT((pos->u64NonPawnSig & 1) == pos->uToMove); pi->u64NonPawnSig = pos->u64NonPawnSig; pi->u64PawnSig = pos->u64PawnSig; pi->u64Sig = (pos->u64NonPawnSig ^ pos->u64PawnSig); pi->uFifty = pos->uFifty; pi->fCastled[BLACK] = pos->fCastled[BLACK]; pi->fCastled[WHITE] = pos->fCastled[WHITE]; pi->uTotalNonPawns = (FastCountBits(NON_KING_PIECES_BB(pos, BLACK)) + FastCountBits(NON_KING_PIECES_BB(pos, WHITE)) + 2); ASSERT(IS_VALID_FLAG(pos->fCastled[WHITE]) && IS_VALID_FLAG(pos->fCastled[BLACK])); ASSERT((pos->bvCastleInfo & ~CASTLE_ALL_POSSIBLE) == 0); pi->bvCastleInfo = pos->bvCastleInfo; ASSERT(VALID_EP_SQUARE(pos->cEpSquare)); pi->cEpSquare = pos->cEpSquare; (pi+1)->fInQsearch = pi->fInQsearch; } static ULONG g_uCastleMask[128] = { 0x7, 0xF, 0xF, 0xF, 0x3, 0xF, 0xF, 0xB, 0,0,0,0,0,0,0,0, 0xF, 0xF, 0xF, 0xF, 0xF, 0xF, 0xF, 0xF, 0,0,0,0,0,0,0,0, 0xF, 0xF, 0xF, 0xF, 0xF, 0xF, 0xF, 0xF, 0,0,0,0,0,0,0,0, 0xF, 0xF, 0xF, 0xF, 0xF, 0xF, 0xF, 0xF, 0,0,0,0,0,0,0,0, 0xF, 0xF, 0xF, 0xF, 0xF, 0xF, 0xF, 0xF, 0,0,0,0,0,0,0,0, 0xF, 0xF, 0xF, 0xF, 0xF, 0xF, 0xF, 0xF, 0,0,0,0,0,0,0,0, 0xF, 0xF, 0xF, 0xF, 0xF, 0xF, 0xF, 0xF, 0,0,0,0,0,0,0,0, 0xD, 0xF, 0xF, 0xF, 0xC, 0xF, 0xF, 0xE, 0,0,0,0,0,0,0,0 }; FLAG MakeMove(SEARCHER_THREAD_CONTEXT *ctx, MOVE mv) /** Routine description: Attempts to make MOVE mv on the POSITION in ctx. Returns TRUE if the move was successful, FALSE if it was illegal. If the move was successful, this function affects the POSITION (piece locations, piece lists, castling perms, en-passant square, signatures etc...) and also increments the ply in ctx. These changes can be reverted at a later point by using UnmakeMove. Note: This function only does a very limited amount of legality checking in order to save time because it is designed to take moves from the move generator (which will only make illegal moves in certain, known ways). If the move is coming from the user (and is therefore of dubious legality)) it should be passed to MakeUserMove, not MakeMove. MakeUserMove verifies that it is a legal chess move in every way. Parameters: SEARCHER_THREAD_CONTEXT *ctx MOVE mv Return value: FLAG **/ { POSITION *pos = &ctx->sPosition; PIECE pPiece; PIECE pCaptured; PIECE pPromoted; COOR c; COOR cRookFrom; COOR cRookTo; COOR cKing; PIECE xPawn; FLAG fMoveIsLegal = FALSE; ULONG uColor, xColor; static int iSign[2] = { -1, +1 }; #ifdef DEBUG cRookFrom = cRookTo = ILLEGAL_COOR; // Satisfy overzealous compiler VerifyPositionConsistency(pos, FALSE); SanityCheckMove(pos, mv); ASSERT(!InCheck(pos, FLIP(pos->uToMove))); if (InCheck(pos, pos->uToMove)) { ASSERT(IS_ESCAPING_CHECK(mv)); } #endif SaveCurrentPositionHistoryInContext(ctx, mv); if (mv.uMove == 0) { pos->uFifty++; goto clear_ep; } pPiece = mv.pMoved; uColor = GET_COLOR(pPiece); xColor = FLIP(uColor); pCaptured = mv.pCaptured; ASSERT(!IS_EMPTY(pPiece)); ASSERT(pPiece == pos->rgSquare[mv.cFrom].pPiece); ASSERT(uColor == pos->uToMove); // // Is this a special move? Special moves are: promotions, enpassant, // castling and double pawn moves. // if (IS_SPECIAL_MOVE(mv)) { if (IS_PAWN(pPiece)) { pPromoted = mv.pPromoted; if (pPromoted) { ASSERT(!IS_PAWN(pPromoted)); ASSERT(GET_COLOR(pPromoted) == uColor); pPiece = LiftPiece(pos, mv.cFrom, ctx->sNnueAccum); pCaptured = LiftPiece(pos, mv.cTo, ctx->sNnueAccum); ASSERT(!pCaptured || (OPPOSITE_COLORS(pCaptured, pPiece))); PlacePiece(pos, mv.cTo, pPromoted, ctx->sNnueAccum); pos->uFifty = 0; goto update_castle_and_clear_ep; } // // Is this an enpassant capture? // else if (pCaptured) { ASSERT(mv.cTo == pos->cEpSquare); pCaptured = LiftPiece(pos, mv.cTo + iSign[uColor] * 16, ctx->sNnueAccum); ASSERT(IS_PAWN(pCaptured)); ASSERT(OPPOSITE_COLORS(pPiece, pCaptured)); ASSERT(IS_EMPTY(pos->rgSquare[mv.cTo].pPiece)); ASSERT(IS_PAWN(pPiece)); SlidePawn(pos, mv.cFrom, mv.cTo, ctx->sNnueAccum); pos->uFifty = 0; goto clear_ep; } // // A special pawn move other than promotion and enpassant // capture must be a double initial jump. // else { c = mv.cFrom; c += (iSign[uColor] * -16); ASSERT(RANK3(c) || RANK6(c)); ASSERT(c + (iSign[uColor] * -16) == mv.cTo); ASSERT(IS_EMPTY(pos->rgSquare[c].pPiece)); ASSERT(IS_EMPTY(pos->rgSquare[mv.cTo].pPiece)); ASSERT(IS_PAWN(pPiece)); SlidePawn(pos, mv.cFrom, mv.cTo, ctx->sNnueAccum); pos->uFifty = 0; // // Only change the position signature to reflect the // possible en-passant if there's an enemy who can // take advantage of it. // xPawn = BLACK_PAWN | xColor; if (((!IS_ON_BOARD(mv.cTo - 1)) || (pos->rgSquare[mv.cTo - 1].pPiece != xPawn)) && ((!IS_ON_BOARD(mv.cTo + 1)) || (pos->rgSquare[mv.cTo + 1].pPiece != xPawn))) { goto clear_ep; } pos->u64NonPawnSig ^= g_u64EpSigSeeds[FILE(pos->cEpSquare)]; ASSERT(VALID_EP_SQUARE(c)); pos->cEpSquare = c; pos->u64NonPawnSig ^= g_u64EpSigSeeds[FILE(pos->cEpSquare)]; goto update_board; } } else if (IS_KING(pPiece)) { ASSERT(!IS_ESCAPING_CHECK(mv)); // // The only special king move is a castle. // switch (mv.cTo) { case C1: ASSERT(pos->bvCastleInfo & CASTLE_WHITE_LONG); ASSERT(IS_EMPTY(pos->rgSquare[B1].pPiece) && IS_EMPTY(pos->rgSquare[C1].pPiece) && IS_EMPTY(pos->rgSquare[D1].pPiece)); if ((IsAttacked(D1, pos, BLACK)) || (IsAttacked(C1, pos, BLACK))) { goto illegal; } cRookFrom = A1; cRookTo = D1; pos->u64NonPawnSig ^= g_u64CastleSigSeeds[pos->bvCastleInfo]; pos->bvCastleInfo &= ~(WHITE_CAN_CASTLE); pos->u64NonPawnSig ^= g_u64CastleSigSeeds[pos->bvCastleInfo]; pos->fCastled[WHITE] = TRUE; break; case G1: ASSERT(pos->bvCastleInfo & CASTLE_WHITE_SHORT); ASSERT(IS_EMPTY(pos->rgSquare[F1].pPiece) && IS_EMPTY(pos->rgSquare[G1].pPiece)); if ((IsAttacked(F1, pos, BLACK)) || (IsAttacked(G1, pos, BLACK))) { goto illegal; } cRookFrom = H1; cRookTo = F1; pos->u64NonPawnSig ^= g_u64CastleSigSeeds[pos->bvCastleInfo]; pos->bvCastleInfo &= ~(WHITE_CAN_CASTLE); pos->u64NonPawnSig ^= g_u64CastleSigSeeds[pos->bvCastleInfo]; pos->fCastled[WHITE] = TRUE; break; case C8: ASSERT(pos->bvCastleInfo & CASTLE_BLACK_LONG); ASSERT(IS_EMPTY(pos->rgSquare[B8].pPiece) && IS_EMPTY(pos->rgSquare[C8].pPiece) && IS_EMPTY(pos->rgSquare[D8].pPiece)); if ((IsAttacked(D8, pos, WHITE)) || (IsAttacked(C8, pos, WHITE))) { goto illegal; } cRookFrom = A8; cRookTo = D8; pos->u64NonPawnSig ^= g_u64CastleSigSeeds[pos->bvCastleInfo]; pos->bvCastleInfo &= ~(BLACK_CAN_CASTLE); pos->u64NonPawnSig ^= g_u64CastleSigSeeds[pos->bvCastleInfo]; pos->fCastled[BLACK] = TRUE; break; case G8: ASSERT(pos->bvCastleInfo & CASTLE_BLACK_SHORT); ASSERT(IS_EMPTY(pos->rgSquare[F8].pPiece) && IS_EMPTY(pos->rgSquare[G8].pPiece)); if ((IsAttacked(F8, pos, WHITE)) || (IsAttacked(G8, pos, WHITE))) { goto illegal; } cRookFrom = H8; cRookTo = F8; pos->u64NonPawnSig ^= g_u64CastleSigSeeds[pos->bvCastleInfo]; pos->bvCastleInfo &= ~(BLACK_CAN_CASTLE); pos->u64NonPawnSig ^= g_u64CastleSigSeeds[pos->bvCastleInfo]; pos->fCastled[BLACK] = TRUE; break; #ifdef DEBUG default: cRookFrom = cRookTo = ILLEGAL_COOR; ASSERT(FALSE); goto illegal; #endif } ASSERT(IS_EMPTY(pos->rgSquare[mv.cTo].pPiece)); ASSERT(IS_ROOK(pos->rgSquare[cRookFrom].pPiece)); ASSERT(IS_EMPTY(pos->rgSquare[cRookTo].pPiece)); // Back up uColor's pre-move accumulator before touching // the board: every HalfKAv2_hm feature in THAT // perspective depends on its own king square, so the // king's move below invalidates the whole thing, not // just a delta -- see chess.h's NNUE_ACCUMULATOR comment. // Indexed by ply (not a single shared slot) so nested // own-king moves at different plies never collide, same // reasoning as cTrapped/cEnprise's existing per-ply // indexing. ctx->sPlyInfo[ctx->uPly].sNnueAccumBackup = ctx->sNnueAccum[uColor]; #ifdef DEBUG ctx->sPlyInfo[ctx->uPly].uNnueAccumBackupColor = uColor; #endif SlidePiece(pos, mv.cFrom, mv.cTo, ctx->sNnueAccum); // king SlidePiece(pos, cRookFrom, cRookTo, ctx->sNnueAccum); // rook // Both slides above already skipped uColor's own // perspective (SlidePiece's own IS_KING check) and kept // xColor's perspective incrementally correct. Recompute // uColor's perspective from scratch against the // now-fully-updated board (king AND rook already in // their final squares) -- the only correct way to update // it, not just the cheapest. NnueAccumRecompute(&ctx->sNnueAccum[uColor], pos, uColor); pos->uFifty = 0; goto clear_ep; } #ifdef DEBUG else { ASSERT(FALSE); goto illegal; } #endif } // // Normal move or normal capture // pos->uFifty++; if (IS_KING(pPiece)) { // Back up BEFORE *anything* about this move touches the // board/accumulator -- including the capture lift just // below, which (for a king capture) would otherwise already // have removed the captured piece's feature from uColor's // own perspective before this snapshot is taken, leaving the // backup missing it. Found exactly this way: testnnue.c's // TestNnueAccumulator caught a king-captures-knight sequence // where the restored accumulator came back short by one // feature row because of this ordering. ctx->sPlyInfo[ctx->uPly].sNnueAccumBackup = ctx->sNnueAccum[uColor]; #ifdef DEBUG ctx->sPlyInfo[ctx->uPly].uNnueAccumBackupColor = uColor; #endif } if (pCaptured) { pos->uFifty = 0; pCaptured = LiftPiece(pos, mv.cTo, ctx->sNnueAccum); ASSERT(!IS_KING(pCaptured)); ASSERT(!IS_EMPTY(pCaptured)); ASSERT(OPPOSITE_COLORS(pCaptured, pPiece)); } if (IS_PAWN(pPiece)) { pos->uFifty = 0; SlidePawn(pos, mv.cFrom, mv.cTo, ctx->sNnueAccum); } else if (IS_KING(pPiece)) { SlidePiece(pos, mv.cFrom, mv.cTo, ctx->sNnueAccum); NnueAccumRecompute(&ctx->sNnueAccum[uColor], pos, uColor); } else { SlidePiece(pos, mv.cFrom, mv.cTo, ctx->sNnueAccum); } update_castle_and_clear_ep: pos->u64NonPawnSig ^= g_u64CastleSigSeeds[pos->bvCastleInfo]; pos->bvCastleInfo &= g_uCastleMask[mv.cFrom]; pos->bvCastleInfo &= g_uCastleMask[mv.cTo]; pos->u64NonPawnSig ^= g_u64CastleSigSeeds[pos->bvCastleInfo]; clear_ep: if (pos->cEpSquare != ILLEGAL_COOR) { pos->u64NonPawnSig ^= g_u64EpSigSeeds[FILE(pos->cEpSquare)]; pos->cEpSquare = ILLEGAL_COOR; pos->u64NonPawnSig ^= g_u64EpSigSeeds[FILE(pos->cEpSquare)]; } update_board: // // At this point we have made the move on the board (moved pieces, // updated castling permissions, updated 50 move count, updated // signature with respect to piece location etc...) // // Now we switch the side on move and flip a bit in the signature // to indicate that it's the other side's turn now. We also // record this position in the movelist (for later detection of // repetition of position draws) and increment either the ply or // move num. // pos->u64NonPawnSig ^= 1; pos->uToMove = FLIP(pos->uToMove); ASSERT((pos->u64NonPawnSig & 1) == pos->uToMove); ASSERT(ctx->uPly < MAX_PLY_PER_SEARCH - 1); ctx->sPlyInfo[ctx->uPly].mv = mv; ctx->uPly++; ASSERT(ctx->uPly < MAX_PLY_PER_SEARCH); // // Make sure the move doesn't leave his own king in check. // if (mv.uMove != 0) { cKing = KingPosition(pos, uColor); ASSERT(IS_KING(pos->rgSquare[cKing].pPiece)); if (IS_KING(mv.pMoved) || IS_ESCAPING_CHECK(mv)) { // // If this guy just moved his king OR he was in check // before making this move we need to do a full check // check now... this involves using the attack vector // table to test ALL pieces in the enemy army and see if // they are attacking the just-moved king. // if (InCheck(pos, uColor)) { goto takeback; } } // // else he did not move the king and he was not in check // before he made this move. To determine if he is in check // now should be cheaper because all we have to consider is // whether the move he just made exposed his king to check. // else { // // This call will also catch positions like: // // 3K4/8/8/8/1k1Pp1R1/8/8/8 w - d3 0 1 // // ... where the black pawn capturing on d3 exposes his // own king to the white rook's horizontal attack. // Because the pawn captured en passant has already been // lifted. // if (IS_ON_BOARD(ExposesCheck(pos, mv.cFrom, cKing))) { ASSERT(InCheck(pos, uColor)); goto takeback; } // // This is a special case -- if the move was enpassant the // guy might have exposed his king to check by removing // the enemy pawn (i.e. exposes a diagonal attack on his // own king) // if (IS_ENPASSANT(mv)) { ASSERT(IS_SPECIAL_MOVE(mv)); ASSERT(!IS_PROMOTION(mv)); ASSERT(mv.pCaptured); ASSERT(IS_PAWN(mv.pMoved)); ASSERT(IS_PAWN(mv.pCaptured)); if (IS_ON_BOARD(ExposesCheck(pos, ((xColor == WHITE) ? (mv.cTo + 16) : (mv.cTo - 16)), cKing))) { ASSERT(InCheck(pos, uColor)); goto takeback; } } } ASSERT(IS_EMPTY(pos->rgSquare[mv.cFrom].pPiece)); ASSERT(!IS_EMPTY(pos->rgSquare[mv.cTo].pPiece)); ASSERT((pos->rgSquare[mv.cTo].pPiece == mv.pMoved) || (pos->rgSquare[mv.cTo].pPiece == mv.pPromoted)); } // NOT uColor here -- this line is reachable via the mv.uMove==0 // (null move) early path, which skips the uColor/xColor // assignment entirely (goto clear_ep at the top, before pPiece = // mv.pMoved runs). GET_COLOR(mv.pMoved) stays well-defined either // way: 0 for a null move (mv.pMoved == 0 too, since MOVE is a // union and mv.uMove == 0 zeroes the whole thing), uColor's value // for a real move. A first version of this substitution used // uColor unconditionally here and read uninitialized stack memory // on the null-move path -- caught via gdb after TryNullmovePruning // crashed inside InCheck/KingPosition with a garbage color index. ASSERT(!InCheck(pos, GET_COLOR(mv.pMoved))); fMoveIsLegal = TRUE; #ifdef DEBUG VerifyPositionConsistency(pos, FALSE); #endif // // Fall into illegal... but don't worry: we're legal! // illegal: return(fMoveIsLegal); takeback: // // If we goto here it means that the guy is trying to move into // check. We didn't determine this until after we already made // the whole move so we can't just return false (illegal move) // from there. First we have to unmake the move we just made. // ASSERT(InCheck(pos, uColor)); UnmakeMove(ctx, mv); #ifdef DEBUG VerifyPositionConsistency(pos, FALSE); #endif return(FALSE); } FLAG MakeUserMove(SEARCHER_THREAD_CONTEXT *ctx, MOVE mvUser) /** Routine description: This function should be called to make a move of dubious quality. The engine calls MakeMove directly for moves it generated itself because these are _mostly_ legal. The only exceptions being: 1. moves that expose the friendly king to check 2. castles through check This function is intended for moves from a user or ICS. It screens these moves by first generating available moves at this ply and making sure it looks like one of them. It returns TRUE if the move is legal and was played, FALSE otherwise. Both moves made with MakeMove and MakeUserMove can be unmade with UnmakeMove. Parameters: SEARCHER_THREAD_CONTEXT *ctx, MOVE mvUser Return value: FLAG **/ { POSITION *pos = &ctx->sPosition; FLAG fInCheck = InCheck(pos, pos->uToMove); FLAG fRet = FALSE; MOVE mv; ULONG x; // // Ask the generator to give us a list of moves here // mv.uMove = 0; GenerateMoves(ctx, mv, (fInCheck ? GENERATE_ESCAPES : GENERATE_ALL_MOVES)); for (x = ctx->sMoveStack.uBegin[ctx->uPly]; x < ctx->sMoveStack.uEnd[ctx->uPly]; x++) { mv = ctx->sMoveStack.mvf[x].mv; if (IS_SAME_MOVE(mvUser, mv)) { if (TRUE == MakeMove(ctx, mv)) { fRet = TRUE; goto end; } } } end: return(fRet); } void UnmakeMove(SEARCHER_THREAD_CONTEXT *ctx, MOVE mv) /** Routine description: Reverses a move made by MakeMove. Parameters: SEARCHER_THREAD_CONTEXT *ctx, MOVE mv Return value: void **/ { PIECE pPiece; PIECE pCaptured; PLY_INFO *pi; POSITION *pos = &(ctx->sPosition); static int iSign[2] = { -1, +1 }; #ifdef DEBUG VerifyPositionConsistency(pos, FALSE); ASSERT(IS_ON_BOARD(mv.cTo)); ASSERT(IS_ON_BOARD(mv.cFrom)); #endif if (mv.uMove == 0) { goto restore_saved_position_data; } pPiece = mv.pMoved; pCaptured = mv.pCaptured; if (IS_CASTLE(mv)) { ASSERT(IS_KING(pPiece)); ASSERT(IS_SPECIAL_MOVE(mv)); switch (mv.cTo) { case C1: // // No need to call SlidePiece here, the signatures will // get reset to their pre-move state below // SlidePieceWithoutSigs(pos, D1, A1, ctx->sNnueAccum); // rook SlidePieceWithoutSigs(pos, C1, E1, ctx->sNnueAccum); // king ASSERT(IS_KING(pos->rgSquare[E1].pPiece)); ASSERT(IS_ROOK(pos->rgSquare[A1].pPiece)); break; case G1: SlidePieceWithoutSigs(pos, F1, H1, ctx->sNnueAccum); // rook SlidePieceWithoutSigs(pos, G1, E1, ctx->sNnueAccum); // king ASSERT(IS_KING(pos->rgSquare[E1].pPiece)); ASSERT(IS_ROOK(pos->rgSquare[H1].pPiece)); break; case C8: SlidePieceWithoutSigs(pos, D8, A8, ctx->sNnueAccum); // rook SlidePieceWithoutSigs(pos, C8, E8, ctx->sNnueAccum); // king ASSERT(IS_KING(pos->rgSquare[E8].pPiece)); ASSERT(IS_ROOK(pos->rgSquare[A8].pPiece)); break; case G8: SlidePieceWithoutSigs(pos, F8, H8, ctx->sNnueAccum); // rook SlidePieceWithoutSigs(pos, G8, E8, ctx->sNnueAccum); // king ASSERT(IS_KING(pos->rgSquare[E8].pPiece)); ASSERT(IS_ROOK(pos->rgSquare[H8].pPiece)); break; #ifdef DEBUG default: ASSERT(FALSE); break; #endif } } else { if (IS_PROMOTION(mv)) { ASSERT(IS_SPECIAL_MOVE(mv)); ASSERT(mv.pPromoted); // // LiftPiece/PlacePiece must be done here to maintain // piece list integrity. // (void)LiftPieceWithoutSigs(pos, mv.cTo, ctx->sNnueAccum); pPiece |= 2; pPiece &= 3; ASSERT(pPiece == mv.pMoved); PlacePieceWithoutSigs(pos, mv.cFrom, pPiece, ctx->sNnueAccum); } else { if (IS_PAWN(pPiece)) { SlidePawnWithoutSigs(pos, mv.cTo, mv.cFrom, ctx->sNnueAccum); } else { // IS_KING(pPiece) here means an ordinary (non-castling) // king step undo -- SlidePieceWithoutSigs's own // IS_KING check already skips uColor's own // perspective; the restore-from-backup for that // perspective happens once, generically, below // (restore_saved_position_data), after ctx->uPly's // decrement lines up with the same ply MakeMove wrote // the backup to. SlidePieceWithoutSigs(pos, mv.cTo, mv.cFrom, ctx->sNnueAccum); } } if (pCaptured) { // // If this was an enpassant capture, put the pawn back to the // right square. // if (IS_ENPASSANT(mv)) { ASSERT(IS_SPECIAL_MOVE(mv)); ASSERT(IS_PAWN(mv.pMoved)); ASSERT(IS_PAWN(pCaptured)); ASSERT(IS_ON_BOARD(mv.cTo) + 16); ASSERT(IS_ON_BOARD(mv.cTo) - 16); // // Resurrect the dead piece in the enpassant location // where it died (not the same as where the killer moved). // ASSERT(IS_EMPTY(pos->rgSquare[mv.cTo + 16 * iSign[GET_COLOR(mv.pMoved)]].pPiece)); PlacePieceWithoutSigs(pos, mv.cTo + 16 * iSign[GET_COLOR(mv.pMoved)], pCaptured, ctx->sNnueAccum); } else { // // Normal capture... resurrect the dead piece where the // killer moved. // ASSERT(IS_EMPTY(pos->rgSquare[mv.cTo].pPiece)); PlacePieceWithoutSigs(pos, mv.cTo, pCaptured, ctx->sNnueAccum); } } } restore_saved_position_data: ctx->uPly--; ASSERT(ctx->uPly < MAX_PLY_PER_SEARCH); pi = &ctx->sPlyInfo[ctx->uPly]; // If mv moved a king (castling or plain step), that color's own // NNUE accumulator was never incrementally maintained above -- // every SlidePiece/SlidePieceWithoutSigs call for a king move // deliberately skips its own perspective (see SlidePiece's // comment) since MakeMove always overwrote it via // NnueAccumRecompute instead. Restore it here from the backup // MakeMove saved at this exact ply before making the move, the // same way pos->u64NonPawnSig etc. are restored from pi just // below -- not via any incremental undo, there isn't one. if ((mv.uMove != 0) && IS_KING(mv.pMoved)) { ULONG uMoverColor = GET_COLOR(mv.pMoved); #ifdef DEBUG ASSERT(pi->uNnueAccumBackupColor == uMoverColor); #endif ctx->sNnueAccum[uMoverColor] = pi->sNnueAccumBackup; #ifdef DEBUG // Poison, not a real "clear" anything downstream depends on // -- see PLY_INFO's sNnueAccumBackup comment (chess.h). memset(&pi->sNnueAccumBackup, 0xCC, sizeof(pi->sNnueAccumBackup)); #endif } pos->u64NonPawnSig = pi->u64NonPawnSig; pos->u64PawnSig = pi->u64PawnSig; ASSERT((pos->u64NonPawnSig & WHITE) == FLIP(pos->uToMove)); pos->uFifty = pi->uFifty; pos->fCastled[WHITE] = pi->fCastled[WHITE]; pos->fCastled[BLACK] = pi->fCastled[BLACK]; ASSERT((pi->bvCastleInfo & ~CASTLE_ALL_POSSIBLE) == 0); pos->bvCastleInfo = pi->bvCastleInfo; ASSERT(VALID_EP_SQUARE(pi->cEpSquare)); ASSERT(VALID_EP_SQUARE(pos->cEpSquare)); pos->cEpSquare = pi->cEpSquare; pos->uToMove = FLIP(pos->uToMove); #ifdef DEBUG VerifyPositionConsistency(pos, FALSE); #endif } static char * _MoveFlagsToString(BITV bv) /** Routine description: Support routing for DumpMove. Translates bvFlags component of a MOVE into a nice string. Not thread safe. Parameters: BITV bv Return value: char **/ { static char buf[5]; char *p = buf; if (bv & MOVE_FLAG_SPECIAL) { *p++ = 'S'; } if (bv & MOVE_FLAG_ESCAPING_CHECK) { *p++ = 'E'; } if (bv & MOVE_FLAG_CHECKING) { *p++ = '+'; } if (bv & MOVE_FLAG_KILLERMATE) { *p++ = '!'; } *p++ = '\0'; return(buf); } void DumpMove(ULONG u) /** Routine description: Dump a move on stdout. This function takes a ULONG instead of a MOVE becasue both gdb and msdev do not like to pass unions to functions invoked at debugtime. Parameters: ULONG u Return value: void **/ { MOVE mv; mv.uMove = u; Trace("--------------------------------------------------------------------------------\n" "MOVE:\n" " +0x000 uMove : 0x%x\n" " +0x000 cFrom : 0x%x (%s)\n", mv.uMove, mv.cFrom, CoorToString(mv.cFrom)); Trace(" +0x001 cTo : 0x%x (%s)\n" " +0x002 pMoved : 0x%x (%s)\n", mv.cTo, CoorToString(mv.cTo), mv.pMoved, PieceAbbrev(mv.pMoved)); Trace(" +0x002 pCaptured : 0x%x (%s)\n", mv.pCaptured, PieceAbbrev(mv.pCaptured)); Trace(" +0x003 pPromoted : 0x%x (%s)\n", mv.pPromoted, PieceAbbrev(mv.pPromoted)); Trace(" +0x003 bvFlags : 0x%x (%s)\n", mv.bvFlags, _MoveFlagsToString(mv.bvFlags)); }