diff options
Diffstat (limited to 'src/san.c')
| -rwxr-xr-x | src/san.c | 596 |
1 files changed, 596 insertions, 0 deletions
diff --git a/src/san.c b/src/san.c new file mode 100755 index 0000000..725e555 --- /dev/null +++ b/src/san.c @@ -0,0 +1,596 @@ +/** + +Copyright (c) Scott Gasch + +Module Name: + + san.c + +Abstract: + + Code to support Standard Algebraic Notation parsing. SAN is a + format for expressing moves: e.g. "Nd4". + + See also ics.c. + +Author: + + Scott Gasch ([email protected]) 13 May 2004 + +Revision History: + + $Id: san.c 345 2007-12-02 22:56:42Z scott $ + +**/ + +#include "chess.h" + +CHAR g_cPieces[] = { 'P', 'N', 'B', 'R', 'Q', 'K', '\0' }; + +static MOVE +_ParseCastleSan(CHAR *szCapturedMove, POSITION *pos) +/** + +Routine description: + + Parse a SAN-format castle move string. + +Parameters: + + CHAR *szCapturedMove, + POSITION *pos + +Return value: + + static MOVE + +**/ +{ + MOVE mv = {0}; + + ASSERT(IS_VALID_COLOR(pos->uToMove)); + if ((!STRCMPI(szCapturedMove, "OO")) || + (!STRCMPI(szCapturedMove, "00"))) + { + switch(pos->uToMove) + { + case WHITE: + if ((pos->rgSquare[E1].pPiece != WHITE_KING) || + (InCheck(pos, WHITE))) + { + goto end; + } + + if (pos->bvCastleInfo & CASTLE_WHITE_SHORT) + { + ASSERT(pos->rgSquare[H1].pPiece == WHITE_ROOK); + if (!IS_EMPTY(pos->rgSquare[F1].pPiece) || + !IS_EMPTY(pos->rgSquare[G1].pPiece) || + IsAttacked(F1, pos, BLACK) || + IsAttacked(G1, pos, BLACK)) + { + goto end; + } + mv.uMove = + MAKE_MOVE(E1, G1, WHITE_KING, 0, 0, MOVE_FLAG_SPECIAL); + goto end; + } + break; + + case BLACK: + if ((pos->rgSquare[E8].pPiece != BLACK_KING) || + (InCheck(pos, BLACK))) + { + goto end; + } + + if (pos->bvCastleInfo & CASTLE_BLACK_SHORT) + { + ASSERT(pos->rgSquare[H8].pPiece == BLACK_ROOK); + if (!IS_EMPTY(pos->rgSquare[F8].pPiece) || + !IS_EMPTY(pos->rgSquare[G8].pPiece) || + IsAttacked(F8, pos, WHITE) || + IsAttacked(G8, pos, WHITE)) + { + goto end; + } + mv.uMove = + MAKE_MOVE(E8, G8, BLACK_KING, 0, 0, MOVE_FLAG_SPECIAL); + goto end; + } + break; +#ifdef DEBUG + default: + UtilPanic(SHOULD_NOT_GET_HERE, + NULL, NULL, NULL, NULL, + __FILE__, __LINE__); + goto end; +#endif + } + } + else if ((!STRCMPI(szCapturedMove, "OOO")) || + (!STRCMPI(szCapturedMove, "000"))) + { + switch(pos->uToMove) + { + case WHITE: + if ((pos->rgSquare[E1].pPiece != WHITE_KING) || + (InCheck(pos, WHITE))) + { + goto end; + } + + if (pos->bvCastleInfo & CASTLE_WHITE_LONG) + { + ASSERT(pos->rgSquare[A1].pPiece == WHITE_ROOK); + if (!IS_EMPTY(pos->rgSquare[D1].pPiece) || + !IS_EMPTY(pos->rgSquare[C1].pPiece) || + !IS_EMPTY(pos->rgSquare[B1].pPiece) || + IsAttacked(D1, pos, BLACK) || + IsAttacked(C1, pos, BLACK)) + { + goto end; + } + mv.uMove = + MAKE_MOVE(E1, C1, WHITE_KING, 0, 0, MOVE_FLAG_SPECIAL); + goto end; + } + break; + + case BLACK: + if ((pos->rgSquare[E8].pPiece != BLACK_KING) || + (InCheck(pos, BLACK))) + { + goto end; + } + + if (pos->bvCastleInfo & CASTLE_BLACK_LONG) + { + ASSERT(pos->rgSquare[A8].pPiece == BLACK_ROOK); + if (!IS_EMPTY(pos->rgSquare[D8].pPiece) || + !IS_EMPTY(pos->rgSquare[C8].pPiece) || + !IS_EMPTY(pos->rgSquare[B8].pPiece) || + IsAttacked(D8, pos, WHITE) || + IsAttacked(C8, pos, WHITE)) + { + goto end; + } + mv.uMove = + MAKE_MOVE(E8, C8, BLACK_KING, 0, 0, MOVE_FLAG_SPECIAL); + goto end; + } + break; + +#ifdef DEBUG + default: + UtilPanic(SHOULD_NOT_GET_HERE, + NULL, NULL, NULL, NULL, + __FILE__, __LINE__); + goto end; +#endif + } + } + + end: + return(mv); +} + +static MOVE +_ParseNormalSan(CHAR *szCapturedMove, POSITION *pos) +/** + +Routine description: + + Parse a "normal" SAN move. Note: these SAN string should begin + with a piece code (or have pawn implied). This parser relies on + the fact that the piece code is uppercase. This is in order to + distinguish between file b and Bishop. + +Parameters: + + CHAR *szCapturedMove, + POSITION *pos + +Return value: + + static MOVE + +**/ +{ + static LIGHTWEIGHT_SEARCHER_CONTEXT ctx; + CHAR *p, *q; + PIECE pPieceType = PAWN; + ULONG u; + COOR cFileRestr = 99; + COOR cRankRestr = 99; + COOR cFrom = ILLEGAL_COOR; + COOR cTo = ILLEGAL_COOR; + PIECE pMoved; + PIECE pCaptured; + PIECE pPromoted = 0; + FLAG fSpecial = FALSE; + MOVE mv; + ULONG uNumMatches = 0; + + p = szCapturedMove; + + // + // Is the first thing a piece code? If not PAWN is implied. + // + if (isupper(*p)) + { + if (NULL != (q = strchr(g_cPieces, *p))) + { + pPieceType = (PIECE)((BYTE *)q - (BYTE *)g_cPieces); + pPieceType++; + ASSERT(IS_VALID_PIECE(pPieceType)); + } + p++; + } + + // + // Is the next thing a board coor? If not it should be a rank or + // file restrictor. + // + while ((*p != '\0') && + (FALSE == LooksLikeCoor(p))) + { + if (LooksLikeFile(*p)) + { + cFileRestr = toupper(*p) - 'A'; + } + else if (LooksLikeRank(*p)) + { + cRankRestr = *p - '0'; + } + else + { + goto illegal; + } + p++; + } + if (*p == '\0') goto illegal; + + // + // We got the piece code taken care of and any rank/file + // restrictions done with. See if we just have two coors left. + // + if ((strlen(p) > 3) && + LooksLikeCoor(p) && + LooksLikeCoor(p + 2)) + { + cFrom = FILE_RANK_TO_COOR((toupper(*p) - 'A'), + *(p+1) - '0'); + cTo = FILE_RANK_TO_COOR((toupper(*(p+2)) - 'A'), + *(p+3) - '0'); + if (!IS_ON_BOARD(cFrom) || !IS_ON_BOARD(cTo)) + { + goto illegal; + } + p += 4; + goto found_coors; + } + else if ((strlen(p) > 1) && + LooksLikeCoor(p)) + { + cTo = FILE_RANK_TO_COOR(toupper(*p) - 'A', + *(p+1) - '0'); + if (!IS_ON_BOARD(cTo)) + { + goto illegal; + } + p += 2; + } + + // + // We need to find out what piece can move to cTo + // + if (!IS_ON_BOARD(cFrom)) + { + if (!IS_ON_BOARD(cTo)) + { + goto illegal; + } + + // + // Handle promotion + // + switch (*(p)) + { + case 'N': + pPromoted = BLACK_KNIGHT | pos->uToMove; + fSpecial = MOVE_FLAG_SPECIAL; + break; + case 'B': + pPromoted = BLACK_BISHOP | pos->uToMove; + fSpecial = MOVE_FLAG_SPECIAL; + break; + case 'R': + pPromoted = BLACK_ROOK | pos->uToMove; + fSpecial = MOVE_FLAG_SPECIAL; + break; + case 'Q': + pPromoted = BLACK_QUEEN | pos->uToMove; + fSpecial = MOVE_FLAG_SPECIAL; + break; + default: + break; + } + + InitializeLightweightSearcherContext(pos, &ctx); + mv.uMove = 0; + GenerateMoves((SEARCHER_THREAD_CONTEXT *)&ctx, + mv, + (InCheck(pos, pos->uToMove) ? GENERATE_ESCAPES : + GENERATE_ALL_MOVES)); + for (u = ctx.sMoveStack.uBegin[0]; + u < ctx.sMoveStack.uEnd[0]; + u++) + { + mv = ctx.sMoveStack.mvf[u].mv; + pMoved = mv.pMoved; + if (PIECE_TYPE(pMoved) == pPieceType) + { + if ((99 != cFileRestr) && + (FILE(mv.cFrom) != cFileRestr)) continue; + if ((99 != cRankRestr) && + (RANK(mv.cFrom) != cRankRestr)) continue; + + if ((mv.cTo == cTo) && + (mv.pPromoted == pPromoted)) + { + if (TRUE == MakeMove((SEARCHER_THREAD_CONTEXT *)&ctx, mv)) + { + UnmakeMove((SEARCHER_THREAD_CONTEXT *)&ctx, mv); + cFrom = mv.cFrom; + cTo = mv.cTo; + uNumMatches++; + } + } + } + } + + // + // Note: 4 moves match for promotions + // + if (uNumMatches == 1) + { + goto found_coors; + } + } + goto illegal; + + found_coors: + pMoved = pos->rgSquare[cFrom].pPiece; + if (IS_EMPTY(pMoved)) + { + goto illegal; + } + pCaptured = pos->rgSquare[cTo].pPiece; + if (!IS_EMPTY(pCaptured) && + !OPPOSITE_COLORS(pMoved, pCaptured)) + { + goto illegal; + } + + // + // Handle enpassant + // + if ((cTo == pos->cEpSquare) && (IS_PAWN(pMoved))) + { + if (WHITE == pos->uToMove) + { + pCaptured = pos->rgSquare[cTo + 16].pPiece; + if (pCaptured != BLACK_PAWN) goto illegal; + } + else + { + pCaptured = pos->rgSquare[cTo - 16].pPiece; + if (pCaptured != WHITE_PAWN) goto illegal; + } + fSpecial = MOVE_FLAG_SPECIAL; + } + + // + // Handle double jump + // + if (IS_PAWN(pMoved) && DISTANCE(cFrom, cTo) > 1) + { + fSpecial = MOVE_FLAG_SPECIAL; + } + + mv.uMove = MAKE_MOVE(cFrom, cTo, pMoved, pCaptured, pPromoted, fSpecial); + if (FALSE == SanityCheckMove(pos, mv)) + { + goto illegal; + } + return(mv); + + illegal: + mv.uMove = 0; + return(mv); +} + + +MOVE +ParseMoveSan(CHAR *szInput, + POSITION *pos) +/** + +Routine description: + + Given a move string in SAN format and a current board position, + parse the move and make it into a valid MOVE structure. + +Parameters: + + CHAR *szInput, + POSITION *pos + +Return value: + + MOVE + +**/ +{ + CHAR szCapturedMove[SMALL_STRING_LEN_CHAR]; + MOVE mv; + + mv.uMove = 0; + memset(szCapturedMove, 0, sizeof(szCapturedMove)); + strncpy(szCapturedMove, StripMove(szInput), SMALL_STRING_LEN_CHAR - 1); + + if (MOVE_SAN != LooksLikeMove(szCapturedMove)) + { + return(mv); // fail + } + + mv = _ParseCastleSan(szCapturedMove, pos); + if (mv.uMove != 0) + { + goto end; + } + + mv = _ParseNormalSan(szCapturedMove, pos); + if (mv.uMove != 0) + { + goto end; + } + + return(mv); // fail + + end: + if (InCheck(pos, pos->uToMove)) + { + mv.bvFlags |= MOVE_FLAG_ESCAPING_CHECK; + } + return(mv); +} + + +CHAR * +MoveToSan(MOVE mv, POSITION *pos) +/** + +Routine description: + + Given a move and a current board position, convert the MOVE + structure into a SAN text string. + +Parameters: + + MOVE mv, + POSITION *pos + +Return value: + + CHAR + +**/ +{ + static char szTextMove[10]; + char *p; + ULONG u = 0; + MOVE x; + + ASSERT(IS_ON_BOARD(mv.cFrom)); + ASSERT(IS_ON_BOARD(mv.cTo)); + if (0 == mv.uMove) + { + return(NULL); + } + + do + { + p = szTextMove; + memset(szTextMove, 0, sizeof(szTextMove)); + + // + // Handle castles. + // + if (IS_KING(mv.pMoved) && (IS_SPECIAL_MOVE(mv))) + { + if ((mv.cTo == G1) || (mv.cTo == G8)) + { + strcpy(szTextMove, "O-O"); + if (IS_CHECKING_MOVE(mv)) + { + *p++ = '+'; + } + goto end; + } + else if ((mv.cTo == C1) || (mv.cTo == C8)) + { + strcpy(szTextMove, "O-O-O"); + if (IS_CHECKING_MOVE(mv)) + { + *p++ = '+'; + } + goto end; + } + ASSERT(FALSE); + strcpy(szTextMove, "ILLEGAL"); + goto end; + } + + if (!IS_PAWN(mv.pMoved)) + { + *p++ = g_cPieces[(PIECE_TYPE(mv.pMoved)) - 1]; + if (u == 1) + { + *p++ = FILE(mv.cFrom) + 'a'; + } + else if (u == 2) + { + *p++ = RANK(mv.cFrom) + '0'; + } + else if (u == 3) + { + *p++ = FILE(mv.cFrom) + 'a'; + *p++ = RANK(mv.cFrom) + '0'; + } + } + else + { + if (u > 0) + { + *p++ = FILE(mv.cFrom) + 'a'; + } + } + + if (mv.pCaptured) + { + if (IS_PAWN(mv.pMoved)) + { + *p++ = FILE(mv.cFrom) + 'a'; + } + *p++ = 'x'; + } + *p++ = FILE(mv.cTo) + 'a'; + *p++ = RANK(mv.cTo) + '0'; + + if (mv.pPromoted) + { + *p++ = '='; + *p++ = g_cPieces[(PIECE_TYPE(mv.pPromoted)) - 1]; + } + + u++; + if (u >= 4) + { + strcpy(szTextMove, "ILLEGAL"); + goto end; + } + + if (IS_CHECKING_MOVE(mv)) + { + *p++ = '+'; + } + x = ParseMoveSan(szTextMove, pos); + } + while ((mv.cTo != x.cTo) || + (mv.cFrom != x.cFrom)); + +end: + return(szTextMove); +} |
