3 Copyright (c) Scott Gasch
11 Code to support Standard Algebraic Notation parsing. SAN is a
12 format for expressing moves: e.g. "Nd4".
22 $Id: san.c 345 2007-12-02 22:56:42Z scott $
28 CHAR g_cPieces[] = { 'P', 'N', 'B', 'R', 'Q', 'K', '\0' };
31 _ParseCastleSan(CHAR *szCapturedMove, POSITION *pos)
36 Parse a SAN-format castle move string.
51 ASSERT(IS_VALID_COLOR(pos->uToMove));
52 if ((!STRCMPI(szCapturedMove, "OO")) ||
53 (!STRCMPI(szCapturedMove, "00")))
58 if ((pos->rgSquare[E1].pPiece != WHITE_KING) ||
59 (InCheck(pos, WHITE)))
64 if (pos->bvCastleInfo & CASTLE_WHITE_SHORT)
66 ASSERT(pos->rgSquare[H1].pPiece == WHITE_ROOK);
67 if (!IS_EMPTY(pos->rgSquare[F1].pPiece) ||
68 !IS_EMPTY(pos->rgSquare[G1].pPiece) ||
69 IsAttacked(F1, pos, BLACK) ||
70 IsAttacked(G1, pos, BLACK))
75 MAKE_MOVE(E1, G1, WHITE_KING, 0, 0, MOVE_FLAG_SPECIAL);
81 if ((pos->rgSquare[E8].pPiece != BLACK_KING) ||
82 (InCheck(pos, BLACK)))
87 if (pos->bvCastleInfo & CASTLE_BLACK_SHORT)
89 ASSERT(pos->rgSquare[H8].pPiece == BLACK_ROOK);
90 if (!IS_EMPTY(pos->rgSquare[F8].pPiece) ||
91 !IS_EMPTY(pos->rgSquare[G8].pPiece) ||
92 IsAttacked(F8, pos, WHITE) ||
93 IsAttacked(G8, pos, WHITE))
98 MAKE_MOVE(E8, G8, BLACK_KING, 0, 0, MOVE_FLAG_SPECIAL);
104 UtilPanic(SHOULD_NOT_GET_HERE,
105 NULL, NULL, NULL, NULL,
111 else if ((!STRCMPI(szCapturedMove, "OOO")) ||
112 (!STRCMPI(szCapturedMove, "000")))
117 if ((pos->rgSquare[E1].pPiece != WHITE_KING) ||
118 (InCheck(pos, WHITE)))
123 if (pos->bvCastleInfo & CASTLE_WHITE_LONG)
125 ASSERT(pos->rgSquare[A1].pPiece == WHITE_ROOK);
126 if (!IS_EMPTY(pos->rgSquare[D1].pPiece) ||
127 !IS_EMPTY(pos->rgSquare[C1].pPiece) ||
128 !IS_EMPTY(pos->rgSquare[B1].pPiece) ||
129 IsAttacked(D1, pos, BLACK) ||
130 IsAttacked(C1, pos, BLACK))
135 MAKE_MOVE(E1, C1, WHITE_KING, 0, 0, MOVE_FLAG_SPECIAL);
141 if ((pos->rgSquare[E8].pPiece != BLACK_KING) ||
142 (InCheck(pos, BLACK)))
147 if (pos->bvCastleInfo & CASTLE_BLACK_LONG)
149 ASSERT(pos->rgSquare[A8].pPiece == BLACK_ROOK);
150 if (!IS_EMPTY(pos->rgSquare[D8].pPiece) ||
151 !IS_EMPTY(pos->rgSquare[C8].pPiece) ||
152 !IS_EMPTY(pos->rgSquare[B8].pPiece) ||
153 IsAttacked(D8, pos, WHITE) ||
154 IsAttacked(C8, pos, WHITE))
159 MAKE_MOVE(E8, C8, BLACK_KING, 0, 0, MOVE_FLAG_SPECIAL);
166 UtilPanic(SHOULD_NOT_GET_HERE,
167 NULL, NULL, NULL, NULL,
179 _ParseNormalSan(CHAR *szCapturedMove, POSITION *pos)
184 Parse a "normal" SAN move. Note: these SAN string should begin
185 with a piece code (or have pawn implied). This parser relies on
186 the fact that the piece code is uppercase. This is in order to
187 distinguish between file b and Bishop.
191 CHAR *szCapturedMove,
200 static LIGHTWEIGHT_SEARCHER_CONTEXT ctx;
202 PIECE pPieceType = PAWN;
204 COOR cFileRestr = 99;
205 COOR cRankRestr = 99;
206 COOR cFrom = ILLEGAL_COOR;
207 COOR cTo = ILLEGAL_COOR;
211 FLAG fSpecial = FALSE;
213 ULONG uNumMatches = 0;
218 // Is the first thing a piece code? If not PAWN is implied.
222 if (NULL != (q = strchr(g_cPieces, *p)))
224 pPieceType = (PIECE)((BYTE *)q - (BYTE *)g_cPieces);
226 ASSERT(IS_VALID_PIECE(pPieceType));
232 // Is the next thing a board coor? If not it should be a rank or
235 while ((*p != '\0') &&
236 (FALSE == LooksLikeCoor(p)))
238 if (LooksLikeFile(*p))
240 cFileRestr = toupper(*p) - 'A';
242 else if (LooksLikeRank(*p))
244 cRankRestr = *p - '0';
252 if (*p == '\0') goto illegal;
255 // We got the piece code taken care of and any rank/file
256 // restrictions done with. See if we just have two coors left.
258 if ((strlen(p) > 3) &&
260 LooksLikeCoor(p + 2))
262 cFrom = FILE_RANK_TO_COOR((toupper(*p) - 'A'),
264 cTo = FILE_RANK_TO_COOR((toupper(*(p+2)) - 'A'),
266 if (!IS_ON_BOARD(cFrom) || !IS_ON_BOARD(cTo))
273 else if ((strlen(p) > 1) &&
276 cTo = FILE_RANK_TO_COOR(toupper(*p) - 'A',
278 if (!IS_ON_BOARD(cTo))
286 // We need to find out what piece can move to cTo
288 if (!IS_ON_BOARD(cFrom))
290 if (!IS_ON_BOARD(cTo))
301 pPromoted = BLACK_KNIGHT | pos->uToMove;
302 fSpecial = MOVE_FLAG_SPECIAL;
305 pPromoted = BLACK_BISHOP | pos->uToMove;
306 fSpecial = MOVE_FLAG_SPECIAL;
309 pPromoted = BLACK_ROOK | pos->uToMove;
310 fSpecial = MOVE_FLAG_SPECIAL;
313 pPromoted = BLACK_QUEEN | pos->uToMove;
314 fSpecial = MOVE_FLAG_SPECIAL;
320 InitializeLightweightSearcherContext(pos, &ctx);
322 GenerateMoves((SEARCHER_THREAD_CONTEXT *)&ctx,
324 (InCheck(pos, pos->uToMove) ? GENERATE_ESCAPES :
325 GENERATE_ALL_MOVES));
326 for (u = ctx.sMoveStack.uBegin[0];
327 u < ctx.sMoveStack.uEnd[0];
330 mv = ctx.sMoveStack.mvf[u].mv;
332 if (PIECE_TYPE(pMoved) == pPieceType)
334 if ((99 != cFileRestr) &&
335 (FILE(mv.cFrom) != cFileRestr)) continue;
336 if ((99 != cRankRestr) &&
337 (RANK(mv.cFrom) != cRankRestr)) continue;
339 if ((mv.cTo == cTo) &&
340 (mv.pPromoted == pPromoted))
342 if (TRUE == MakeMove((SEARCHER_THREAD_CONTEXT *)&ctx, mv))
344 UnmakeMove((SEARCHER_THREAD_CONTEXT *)&ctx, mv);
354 // Note: 4 moves match for promotions
356 if (uNumMatches == 1)
364 pMoved = pos->rgSquare[cFrom].pPiece;
365 if (IS_EMPTY(pMoved))
369 pCaptured = pos->rgSquare[cTo].pPiece;
370 if (!IS_EMPTY(pCaptured) &&
371 !OPPOSITE_COLORS(pMoved, pCaptured))
379 if ((cTo == pos->cEpSquare) && (IS_PAWN(pMoved)))
381 if (WHITE == pos->uToMove)
383 pCaptured = pos->rgSquare[cTo + 16].pPiece;
384 if (pCaptured != BLACK_PAWN) goto illegal;
388 pCaptured = pos->rgSquare[cTo - 16].pPiece;
389 if (pCaptured != WHITE_PAWN) goto illegal;
391 fSpecial = MOVE_FLAG_SPECIAL;
395 // Handle double jump
397 if (IS_PAWN(pMoved) && DISTANCE(cFrom, cTo) > 1)
399 fSpecial = MOVE_FLAG_SPECIAL;
402 mv.uMove = MAKE_MOVE(cFrom, cTo, pMoved, pCaptured, pPromoted, fSpecial);
403 if (FALSE == SanityCheckMove(pos, mv))
416 ParseMoveSan(CHAR *szInput,
422 Given a move string in SAN format and a current board position,
423 parse the move and make it into a valid MOVE structure.
436 CHAR szCapturedMove[SMALL_STRING_LEN_CHAR];
440 memset(szCapturedMove, 0, sizeof(szCapturedMove));
441 strncpy(szCapturedMove, StripMove(szInput), SMALL_STRING_LEN_CHAR - 1);
443 if (MOVE_SAN != LooksLikeMove(szCapturedMove))
448 mv = _ParseCastleSan(szCapturedMove, pos);
454 mv = _ParseNormalSan(szCapturedMove, pos);
463 if (InCheck(pos, pos->uToMove))
465 mv.bvFlags |= MOVE_FLAG_ESCAPING_CHECK;
472 MoveToSan(MOVE mv, POSITION *pos)
477 Given a move and a current board position, convert the MOVE
478 structure into a SAN text string.
491 static char szTextMove[10];
496 ASSERT(IS_ON_BOARD(mv.cFrom));
497 ASSERT(IS_ON_BOARD(mv.cTo));
506 memset(szTextMove, 0, sizeof(szTextMove));
511 if (IS_KING(mv.pMoved) && (IS_SPECIAL_MOVE(mv)))
513 if ((mv.cTo == G1) || (mv.cTo == G8))
515 strcpy(szTextMove, "O-O");
516 if (IS_CHECKING_MOVE(mv))
522 else if ((mv.cTo == C1) || (mv.cTo == C8))
524 strcpy(szTextMove, "O-O-O");
525 if (IS_CHECKING_MOVE(mv))
532 strcpy(szTextMove, "ILLEGAL");
536 if (!IS_PAWN(mv.pMoved))
538 *p++ = g_cPieces[(PIECE_TYPE(mv.pMoved)) - 1];
541 *p++ = FILE(mv.cFrom) + 'a';
545 *p++ = RANK(mv.cFrom) + '0';
549 *p++ = FILE(mv.cFrom) + 'a';
550 *p++ = RANK(mv.cFrom) + '0';
557 *p++ = FILE(mv.cFrom) + 'a';
563 if (IS_PAWN(mv.pMoved))
565 *p++ = FILE(mv.cFrom) + 'a';
569 *p++ = FILE(mv.cTo) + 'a';
570 *p++ = RANK(mv.cTo) + '0';
575 *p++ = g_cPieces[(PIECE_TYPE(mv.pPromoted)) - 1];
581 strcpy(szTextMove, "ILLEGAL");
585 if (IS_CHECKING_MOVE(mv))
589 x = ParseMoveSan(szTextMove, pos);
591 while ((mv.cTo != x.cTo) ||
592 (mv.cFrom != x.cFrom));