diff options
Diffstat (limited to 'src/move.c')
| -rwxr-xr-x | src/move.c | 1441 |
1 files changed, 1441 insertions, 0 deletions
diff --git a/src/move.c b/src/move.c new file mode 100755 index 0000000..5c1a0db --- /dev/null +++ b/src/move.c @@ -0,0 +1,1441 @@ +/** + +Copyright (c) Scott Gasch + +Module Name: + + move.c + +Abstract: + + Functions to make and unmake moves. + +Author: + + Scott Gasch ([email protected]) 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) +/** + +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 + +Return value: + + void + +**/ +{ + register PIECE p; + register ULONG c; + ULONG uIndex; + +#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)); + uIndex = pos->rgSquare[cFrom].uIndex; + c = GET_COLOR(p); + ASSERT(IS_VALID_COLOR(c)); + ASSERT(pos->cNonPawns[c][uIndex] == cFrom); + pos->cNonPawns[c][uIndex] = 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 = uIndex; +#ifdef DEBUG + pos->rgSquare[cFrom].uIndex = INVALID_PIECE_INDEX; + VerifyPositionConsistency(pos, FALSE); +#endif +} + + +void +SlidePawn(POSITION *pos, COOR cFrom, COOR cTo) +/** + +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 + +Return value: + + void + +**/ +{ + register PIECE p; + register ULONG c; + ULONG uIndex; + +#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)); + uIndex = pos->rgSquare[cFrom].uIndex; + c = GET_COLOR(p); + ASSERT(IS_VALID_COLOR(c)); + ASSERT(pos->cPawns[c][uIndex] == cFrom); + pos->cPawns[c][uIndex] = cTo; + pos->u64PawnSig ^= g_u64PawnSigSeeds[cFrom][c]; + pos->u64PawnSig ^= g_u64PawnSigSeeds[cTo][c]; + pos->rgSquare[cTo].pPiece = p; + pos->rgSquare[cTo].uIndex = uIndex; +#ifdef DEBUG + pos->rgSquare[cFrom].uIndex = INVALID_PIECE_INDEX; + VerifyPositionConsistency(pos, FALSE); +#endif +} + + +void +SlidePieceWithoutSigs(POSITION *pos, COOR cFrom, COOR cTo) +/** + +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. + +Parameters: + + POSITION *pos : the board + COOR cFrom : square moving from + COOR cTo : square moving to + +Return value: + + void + +**/ +{ + register PIECE p; + register ULONG c; + ULONG uIndex; + +#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)); + uIndex = pos->rgSquare[cFrom].uIndex; + c = GET_COLOR(p); + ASSERT(IS_VALID_COLOR(c)); + ASSERT(pos->cNonPawns[c][uIndex] == cFrom); + pos->cNonPawns[c][uIndex] = cTo; + pos->rgSquare[cTo].pPiece = p; + pos->rgSquare[cTo].uIndex = uIndex; +#ifdef DEBUG + pos->rgSquare[cFrom].uIndex = INVALID_PIECE_INDEX; +#endif +} + + +void +SlidePawnWithoutSigs(POSITION *pos, COOR cFrom, COOR cTo) +/** + +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. + +Parameters: + + POSITION *pos : the board + COOR cFrom : square moving from + COOR cTo : square moving to + +Return value: + + void + +**/ +{ + register PIECE p; + register ULONG c; + ULONG uIndex; + +#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)); + uIndex = pos->rgSquare[cFrom].uIndex; + c = GET_COLOR(p); + ASSERT(IS_VALID_COLOR(c)); + ASSERT(pos->cPawns[c][uIndex] == cFrom); + pos->cPawns[c][uIndex] = cTo; + pos->rgSquare[cTo].pPiece = p; + pos->rgSquare[cTo].uIndex = uIndex; +#ifdef DEBUG + pos->rgSquare[cFrom].uIndex = INVALID_PIECE_INDEX; +#endif +} + + +PIECE +LiftPiece(POSITION *pos, COOR cSquare) +/** + +Routine description: + + Remove the piece at square cSquare from the board. Update POSITION + (piece lists, counts, material, etc...) accordingly. + +Parameters: + + POSITION *pos, + COOR cSquare + +Return value: + + PIECE + +**/ +{ + register PIECE pLifted; + ULONG color; + register SCORE pv; + ULONG uLastIndex; + ULONG uIndex; + COOR c; + 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)); + uIndex = pos->rgSquare[cSquare].uIndex; +#ifdef DEBUG + ASSERT(IS_VALID_PIECE_INDEX(uIndex)); + pos->rgSquare[cSquare].uIndex = INVALID_PIECE_INDEX; +#endif + 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)) + { + ASSERT(pos->cPawns[color][uIndex] == cSquare); + ASSERT(pv == VALUE_PAWN); + + pos->uPawnMaterial[color] -= pv; + ASSERT(pos->uPawnMaterial[color] <= (7 * VALUE_PAWN)); + + pos->u64PawnSig ^= g_u64PawnSigSeeds[cSquare][color]; + + // + // Remove this pawn from the pawn list. + // + pos->uPawnCount[color]--; + ASSERT(pos->uPawnCount[color] < 8); + uLastIndex = pos->uPawnCount[color]; + + // + // Assume that the pawn being lifted is not the last pawn on + // the list. We can't have a hole in the list so swap the + // last pawn on the list into this guy's spot. + // + c = pos->cPawns[color][uLastIndex]; + pos->rgSquare[c].uIndex = uIndex; + pos->cPawns[color][uIndex] = c; +#ifdef DEBUG + pos->cPawns[color][uLastIndex] = ILLEGAL_COOR; +#endif + } + else + { + // + // Piece is not a pawn + // + ASSERT(pos->cNonPawns[color][uIndex] == cSquare); + ASSERT(pos->uNonPawnMaterial[color] >= VALUE_KING); + ASSERT(pv > VALUE_PAWN); + pos->uNonPawnMaterial[color] -= pv; + ASSERT(pos->uNonPawnMaterial[color] <= VALUE_MAX_ARMY); + + // + // Remove this piece from the list. + // + pos->uNonPawnCount[color][0]--; + ASSERT(pos->uNonPawnCount[color] > 0); + uLastIndex = pos->uNonPawnCount[color][0]; // optimized... + // + // Assume that it is NOT the last piece in the list ... swap + // the last piece into this one's spot. + // + c = pos->cNonPawns[color][uLastIndex]; + pos->rgSquare[c].uIndex = uIndex; + pos->cNonPawns[color][uIndex] = c; +#ifdef DEBUG + pos->cNonPawns[color][uLastIndex] = ILLEGAL_COOR; +#endif + + // + // Change per-piece counters. + // + u = PIECE_TYPE(pLifted); + ASSERT((u >= KNIGHT) && (u < KING)); + pos->u64NonPawnSig ^= g_u64SigSeeds[cSquare][u][color]; + pos->uNonPawnCount[color][u]--; + ASSERT(pos->uNonPawnCount[color][u] <= 9); + pos->uWhiteSqBishopCount[color] -= (IS_BISHOP(pLifted) & + IS_SQUARE_WHITE(cSquare)); + ASSERT(pos->uWhiteSqBishopCount[color] <= 9); + } + +#ifdef DEBUG + VerifyPositionConsistency(pos, FALSE); +#endif + return(pLifted); +} + + +PIECE +LiftPieceWithoutSigs(POSITION *pos, COOR cSquare) +/** + +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 uLastIndex; + ULONG uIndex; + COOR c; + 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)); + + uIndex = pos->rgSquare[cSquare].uIndex; +#ifdef DEBUG + ASSERT(IS_VALID_PIECE_INDEX(uIndex)); + pos->rgSquare[cSquare].uIndex = INVALID_PIECE_INDEX; +#endif + 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)) + { + ASSERT(pos->cPawns[color][uIndex] == cSquare); + ASSERT(pv == VALUE_PAWN); + + pos->uPawnMaterial[color] -= pv; + ASSERT(pos->uPawnMaterial[color] <= (7 * VALUE_PAWN)); + + // + // Remove this pawn from the pawn list. + // + pos->uPawnCount[color]--; + ASSERT(pos->uPawnCount[color] < 8); + uLastIndex = pos->uPawnCount[color]; // optimized... + + // + // Assume that the pawn being lifted is not the last pawn on + // the list. We can't have a hole in the list so swap the + // last pawn on the list into this guy's spot. + // + c = pos->cPawns[color][uLastIndex]; + pos->rgSquare[c].uIndex = uIndex; + pos->cPawns[color][uIndex] = c; +#ifdef DEBUG + pos->cPawns[color][uLastIndex] = ILLEGAL_COOR; +#endif + } + else + { + // + // Piece is not a pawn + // + ASSERT(pos->cNonPawns[color][uIndex] == cSquare); + ASSERT(pos->uNonPawnMaterial[color] >= VALUE_KING); + ASSERT(pv > VALUE_PAWN); + pos->uNonPawnMaterial[color] -= pv; + ASSERT(pos->uNonPawnMaterial[color] <= VALUE_MAX_ARMY); + + // + // Remove this piece from the list. + // + pos->uNonPawnCount[color][0]--; + ASSERT(pos->uNonPawnCount[color] > 0); + uLastIndex = pos->uNonPawnCount[color][0]; // optimized... + + // + // Assume that it is NOT the last piece in the list ... swap + // the last piece into this one's spot. + // + c = pos->cNonPawns[color][uLastIndex]; + pos->rgSquare[c].uIndex = uIndex; + pos->cNonPawns[color][uIndex] = c; +#ifdef DEBUG + pos->cNonPawns[color][uLastIndex] = ILLEGAL_COOR; +#endif + + // + // Change per-piece counters. + // + u = PIECE_TYPE(pLifted); + ASSERT((u >= KNIGHT) && (u < KING)); + pos->uNonPawnCount[color][u]--; + ASSERT(pos->uNonPawnCount[color][u] <= 9); + pos->uWhiteSqBishopCount[color] -= (IS_BISHOP(pLifted) & + IS_SQUARE_WHITE(cSquare)); + ASSERT(pos->uWhiteSqBishopCount[color] <= 9); + } + + return(pLifted); +} + + +void +PlacePiece(POSITION *pos, COOR cSquare, PIECE pPiece) +/** + +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 ULONG uIndex; + 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]); + + if (IS_PAWN(pPiece)) + { + // + // Piece is a pawn + // + ASSERT(pv == VALUE_PAWN); + pos->uPawnMaterial[color] += pv; + ASSERT(pos->uPawnMaterial[color] <= (8 * VALUE_PAWN)); + + uIndex = pos->uPawnCount[color]; + ASSERT((uIndex >= 0) && (uIndex <= 7)); + pos->uPawnCount[color]++; + ASSERT(pos->uPawnCount[color] <= 8); + pos->cPawns[color][uIndex] = cSquare; + pos->u64PawnSig ^= g_u64PawnSigSeeds[cSquare][color]; + } + else + { + // + // Piece is not a pawn + // + ASSERT(pv > VALUE_PAWN); + pos->uNonPawnMaterial[color] += pv; + ASSERT(pos->uNonPawnMaterial[color] <= VALUE_MAX_ARMY); + uIndex = pos->uNonPawnCount[color][0]; + pos->uNonPawnCount[color][0]++; + ASSERT(pos->uNonPawnCount[color][0] <= 16); + pos->cNonPawns[color][uIndex] = cSquare; + + u = PIECE_TYPE(pPiece); + ASSERT((u >= KNIGHT) && (u < KING)); + pos->u64NonPawnSig ^= g_u64SigSeeds[cSquare][u][color]; + pos->uNonPawnCount[color][u]++; + ASSERT(pos->uNonPawnCount[color][u] <= 10); + + pos->uWhiteSqBishopCount[color] += (IS_BISHOP(pPiece) & + IS_SQUARE_WHITE(cSquare)); + ASSERT(pos->uWhiteSqBishopCount[color] <= 10); + } + + // + // Place the piece on the board + // + pos->rgSquare[cSquare].pPiece = pPiece; + pos->rgSquare[cSquare].uIndex = uIndex; + +#ifdef DEBUG + VerifyPositionConsistency(pos, FALSE); +#endif +} + + +void +PlacePieceWithoutSigs(POSITION *pos, COOR cSquare, PIECE pPiece) +/** + +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 ULONG uIndex; + 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]); + + if (IS_PAWN(pPiece)) + { + // + // Piece is a pawn + // + ASSERT(pv == VALUE_PAWN); + pos->uPawnMaterial[color] += pv; + ASSERT(pos->uPawnMaterial[color] <= (8 * VALUE_PAWN)); + + uIndex = pos->uPawnCount[color]; + ASSERT((uIndex >= 0) && (uIndex <= 7)); + pos->uPawnCount[color]++; + ASSERT(pos->uPawnCount[color] <= 8); + pos->cPawns[color][uIndex] = cSquare; + } + else + { + // + // Piece is not a pawn + // + ASSERT(pv > VALUE_PAWN); + pos->uNonPawnMaterial[color] += pv; + ASSERT(pos->uNonPawnMaterial[color] <= VALUE_MAX_ARMY); + uIndex = pos->uNonPawnCount[color][0]; + pos->uNonPawnCount[color][0]++; + ASSERT(pos->uNonPawnCount[color][0] <= 16); + pos->cNonPawns[color][uIndex] = cSquare; + + u = PIECE_TYPE(pPiece); + ASSERT((u >= KNIGHT) && (u < KING)); + pos->uNonPawnCount[color][u]++; + ASSERT(pos->uNonPawnCount[color][u] <= 10); + + pos->uWhiteSqBishopCount[color] += (IS_BISHOP(pPiece) & + IS_SQUARE_WHITE(cSquare)); + ASSERT(pos->uWhiteSqBishopCount[color] <= 10); + } + + // + // Place the piece on the board + // + pos->rgSquare[cSquare].pPiece = pPiece; + pos->rgSquare[cSquare].uIndex = uIndex; +} + + +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 = (pos->uNonPawnCount[BLACK][0] + + pos->uNonPawnCount[WHITE][0]); + 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; + 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; + pCaptured = mv.pCaptured; + ASSERT(!IS_EMPTY(pPiece)); + ASSERT(pPiece == pos->rgSquare[mv.cFrom].pPiece); + ASSERT(GET_COLOR(pPiece) == 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) == GET_COLOR(pPiece)); + pPiece = LiftPiece(pos, mv.cFrom); + pCaptured = LiftPiece(pos, mv.cTo); + ASSERT(!pCaptured || (OPPOSITE_COLORS(pCaptured, pPiece))); + PlacePiece(pos, mv.cTo, pPromoted); + 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[GET_COLOR(pPiece)] * 16); + 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); + 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[GET_COLOR(pPiece)] * -16); + ASSERT(RANK3(c) || RANK6(c)); + ASSERT(c + (iSign[GET_COLOR(pPiece)] * -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); + 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 | FLIP(GET_COLOR(pPiece)); + 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)); + SlidePiece(pos, mv.cFrom, mv.cTo); // king + SlidePiece(pos, cRookFrom, cRookTo); // rook + pos->uFifty = 0; + goto clear_ep; + } +#ifdef DEBUG + else + { + ASSERT(FALSE); + goto illegal; + } +#endif + } + + // + // Normal move or normal capture + // + pos->uFifty++; + if (pCaptured) + { + pos->uFifty = 0; + pCaptured = LiftPiece(pos, mv.cTo); + 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); + } + else + { + SlidePiece(pos, mv.cFrom, mv.cTo); + } + + 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 = pos->cNonPawns[FLIP(pos->uToMove)][0]; + 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. + // + ASSERT(GET_COLOR(mv.pMoved) == FLIP(pos->uToMove)); + if (InCheck(pos, GET_COLOR(mv.pMoved))) + { + 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, GET_COLOR(mv.pMoved))); + 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, + ((pos->uToMove == WHITE) ? + (mv.cTo + 16) : + (mv.cTo - 16)), + cKing))) + { + ASSERT(InCheck(pos, GET_COLOR(mv.pMoved))); + 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)); + } + 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, FLIP(pos->uToMove))); + 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); // rook + SlidePieceWithoutSigs(pos, C1, E1); // king + ASSERT(IS_KING(pos->rgSquare[E1].pPiece)); + ASSERT(IS_ROOK(pos->rgSquare[A1].pPiece)); + break; + case G1: + SlidePieceWithoutSigs(pos, F1, H1); // rook + SlidePieceWithoutSigs(pos, G1, E1); // king + ASSERT(IS_KING(pos->rgSquare[E1].pPiece)); + ASSERT(IS_ROOK(pos->rgSquare[H1].pPiece)); + break; + case C8: + SlidePieceWithoutSigs(pos, D8, A8); // rook + SlidePieceWithoutSigs(pos, C8, E8); // king + ASSERT(IS_KING(pos->rgSquare[E8].pPiece)); + ASSERT(IS_ROOK(pos->rgSquare[A8].pPiece)); + break; + case G8: + SlidePieceWithoutSigs(pos, F8, H8); // rook + SlidePieceWithoutSigs(pos, G8, E8); // 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); + pPiece |= 2; + pPiece &= 3; + ASSERT(pPiece == mv.pMoved); + PlacePieceWithoutSigs(pos, mv.cFrom, pPiece); + } + else + { + if (IS_PAWN(pPiece)) + { + SlidePawnWithoutSigs(pos, mv.cTo, mv.cFrom); + } + else + { + SlidePieceWithoutSigs(pos, mv.cTo, mv.cFrom); + } + } + + 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); + } + else + { + // + // Normal capture... resurrect the dead piece where the + // killer moved. + // + ASSERT(IS_EMPTY(pos->rgSquare[mv.cTo].pPiece)); + PlacePieceWithoutSigs(pos, mv.cTo, pCaptured); + } + } + } + + restore_saved_position_data: + ctx->uPly--; + ASSERT(ctx->uPly < MAX_PLY_PER_SEARCH); + + pi = &ctx->sPlyInfo[ctx->uPly]; + 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 <pos 0, 8 bits>: 0x%x (%s)\n", + mv.uMove, + mv.cFrom, CoorToString(mv.cFrom)); + Trace(" +0x001 cTo <pos 0, 8 bits>: 0x%x (%s)\n" + " +0x002 pMoved <pos 0, 4 Bits>: 0x%x (%s)\n", + mv.cTo, CoorToString(mv.cTo), + mv.pMoved, PieceAbbrev(mv.pMoved)); + Trace(" +0x002 pCaptured <pos 4, 4 Bits>: 0x%x (%s)\n", + mv.pCaptured, PieceAbbrev(mv.pCaptured)); + Trace(" +0x003 pPromoted <pos 0, 4 Bits>: 0x%x (%s)\n", + mv.pPromoted, PieceAbbrev(mv.pPromoted)); + Trace(" +0x003 bvFlags <pos 4, 4 Bits>: 0x%x (%s)\n", + mv.bvFlags, _MoveFlagsToString(mv.bvFlags)); +} |
