Update codebase to remove clang warnings (and a couple of legit errors
[typhoon.git] / src / san.c
1 /**
2
3 Copyright (c) Scott Gasch
4
5 Module Name:
6
7     san.c 
8
9 Abstract:
10
11     Code to support Standard Algebraic Notation parsing.  SAN is a
12     format for expressing moves: e.g. "Nd4".
13
14     See also ics.c.  
15
16 Author:
17
18     Scott Gasch ([email protected]) 13 May 2004
19
20 Revision History:
21
22     $Id: san.c 345 2007-12-02 22:56:42Z scott $
23
24 **/
25
26 #include "chess.h"
27
28 CHAR g_cPieces[] = { 'P', 'N', 'B', 'R', 'Q', 'K', '\0' };
29
30 static MOVE 
31 _ParseCastleSan(CHAR *szCapturedMove, POSITION *pos)
32 /**
33
34 Routine description:
35
36     Parse a SAN-format castle move string.
37
38 Parameters:
39
40     CHAR *szCapturedMove,
41     POSITION *pos
42
43 Return value:
44
45     static MOVE
46
47 **/
48 {
49     MOVE mv = {0};
50     
51     ASSERT(IS_VALID_COLOR(pos->uToMove));
52     if ((!STRCMPI(szCapturedMove, "OO")) ||
53         (!STRCMPI(szCapturedMove, "00")))
54     {
55         switch(pos->uToMove)
56         {
57             case WHITE:
58                 if ((pos->rgSquare[E1].pPiece != WHITE_KING) ||
59                     (InCheck(pos, WHITE)))
60                 {
61                     goto end;
62                 }
63
64                 if (pos->bvCastleInfo & CASTLE_WHITE_SHORT) 
65                 {
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))
71                     {
72                         goto end;
73                     }
74                     mv.uMove = 
75                         MAKE_MOVE(E1, G1, WHITE_KING, 0, 0, MOVE_FLAG_SPECIAL);
76                     goto end;
77                 }
78                 break;
79                 
80             case BLACK:
81                 if ((pos->rgSquare[E8].pPiece != BLACK_KING) ||
82                     (InCheck(pos, BLACK)))
83                 {
84                     goto end;
85                 }
86
87                 if (pos->bvCastleInfo & CASTLE_BLACK_SHORT)
88                 {
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))
94                     {
95                         goto end;
96                     }
97                     mv.uMove = 
98                         MAKE_MOVE(E8, G8, BLACK_KING, 0, 0, MOVE_FLAG_SPECIAL);
99                     goto end;
100                 }
101                 break;
102 #ifdef DEBUG
103             default:
104                 UtilPanic(SHOULD_NOT_GET_HERE,
105                           NULL, NULL, NULL, NULL, 
106                           __FILE__, __LINE__);
107                 goto end;
108 #endif
109         }
110     }
111     else if ((!STRCMPI(szCapturedMove, "OOO")) ||
112              (!STRCMPI(szCapturedMove, "000")))
113     {
114         switch(pos->uToMove)
115         {
116             case WHITE:
117                 if ((pos->rgSquare[E1].pPiece != WHITE_KING) ||
118                     (InCheck(pos, WHITE)))
119                 {
120                     goto end;
121                 }
122
123                 if (pos->bvCastleInfo & CASTLE_WHITE_LONG) 
124                 {
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))
131                     {
132                         goto end;
133                     }
134                     mv.uMove = 
135                         MAKE_MOVE(E1, C1, WHITE_KING, 0, 0, MOVE_FLAG_SPECIAL);
136                     goto end;
137                 }
138                 break;
139                 
140             case BLACK:
141                 if ((pos->rgSquare[E8].pPiece != BLACK_KING) ||
142                     (InCheck(pos, BLACK)))
143                 {
144                     goto end;
145                 }
146
147                 if (pos->bvCastleInfo & CASTLE_BLACK_LONG) 
148                 {
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))
155                     {
156                         goto end;
157                     }
158                     mv.uMove = 
159                         MAKE_MOVE(E8, C8, BLACK_KING, 0, 0, MOVE_FLAG_SPECIAL);
160                     goto end;
161                 }
162                 break;
163                 
164 #ifdef DEBUG
165             default:
166                 UtilPanic(SHOULD_NOT_GET_HERE,
167                           NULL, NULL, NULL, NULL, 
168                           __FILE__, __LINE__);
169                 goto end;
170 #endif
171         }
172     }
173     
174  end:
175     return(mv);
176 }
177
178 static MOVE 
179 _ParseNormalSan(CHAR *szCapturedMove, POSITION *pos)
180 /**
181
182 Routine description:
183
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.
188
189 Parameters:
190
191     CHAR *szCapturedMove,
192     POSITION *pos
193
194 Return value:
195
196     static MOVE
197
198 **/
199 {
200     static LIGHTWEIGHT_SEARCHER_CONTEXT ctx;
201     CHAR *p, *q;
202     PIECE pPieceType = PAWN;
203     ULONG u;
204     COOR cFileRestr = 99;
205     COOR cRankRestr = 99;
206     COOR cFrom = ILLEGAL_COOR;
207     COOR cTo = ILLEGAL_COOR;
208     PIECE pMoved;
209     PIECE pCaptured;
210     PIECE pPromoted = 0;
211     FLAG fSpecial = FALSE;
212     MOVE mv;
213     ULONG uNumMatches = 0;
214     
215     p = szCapturedMove;
216    
217     //
218     // Is the first thing a piece code?  If not PAWN is implied.
219     //
220     if (isupper(*p))
221     {
222         if (NULL != (q = strchr(g_cPieces, *p)))
223         {
224             pPieceType = (PIECE)((BYTE *)q - (BYTE *)g_cPieces);
225             pPieceType++;
226             ASSERT(IS_VALID_PIECE(pPieceType));
227         }
228         p++;
229     }
230     
231     //
232     // Is the next thing a board coor?  If not it should be a rank or
233     // file restrictor.
234     // 
235     while ((*p != '\0') && 
236            (FALSE == LooksLikeCoor(p)))
237     {
238         if (LooksLikeFile(*p))
239         {
240             cFileRestr = toupper(*p) - 'A';
241         }
242         else if (LooksLikeRank(*p))
243         {
244             cRankRestr = *p - '0';
245         }
246         else
247         {
248             goto illegal;
249         }
250         p++;
251     }
252     if (*p == '\0') goto illegal;
253     
254     //
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.
257     // 
258     if ((strlen(p) > 3) &&
259         LooksLikeCoor(p) && 
260         LooksLikeCoor(p + 2))
261     {
262         cFrom = FILE_RANK_TO_COOR((toupper(*p) - 'A'),
263                                   *(p+1) - '0');
264         cTo = FILE_RANK_TO_COOR((toupper(*(p+2)) - 'A'),
265                                 *(p+3) - '0');
266         if (!IS_ON_BOARD(cFrom) || !IS_ON_BOARD(cTo))
267         {
268             goto illegal;
269         }
270         p += 4;
271         goto found_coors;
272     }
273     else if ((strlen(p) > 1) &&
274              LooksLikeCoor(p))
275     {
276         cTo = FILE_RANK_TO_COOR(toupper(*p) - 'A',
277                                 *(p+1) - '0');
278         if (!IS_ON_BOARD(cTo))
279         {
280             goto illegal;
281         }
282         p += 2;
283     }
284     
285     //
286     // We need to find out what piece can move to cTo
287     //
288     if (!IS_ON_BOARD(cFrom))
289     {
290         if (!IS_ON_BOARD(cTo))
291         {
292             goto illegal;
293         }
294
295         //
296         // Handle promotion
297         //
298         switch (*(p))
299         {
300             case 'N':
301                 pPromoted = BLACK_KNIGHT | pos->uToMove;
302                 fSpecial = MOVE_FLAG_SPECIAL;
303                 break;
304             case 'B':
305                 pPromoted = BLACK_BISHOP | pos->uToMove;
306                 fSpecial = MOVE_FLAG_SPECIAL;
307                 break;
308             case 'R':
309                 pPromoted = BLACK_ROOK | pos->uToMove;
310                 fSpecial = MOVE_FLAG_SPECIAL;
311                 break;
312             case 'Q':
313                 pPromoted = BLACK_QUEEN | pos->uToMove;
314                 fSpecial = MOVE_FLAG_SPECIAL;
315                 break;
316             default:
317                 break;
318         }
319         
320         InitializeLightweightSearcherContext(pos, &ctx);
321         mv.uMove = 0;
322         GenerateMoves((SEARCHER_THREAD_CONTEXT *)&ctx,
323                       mv,
324                       (InCheck(pos, pos->uToMove) ? GENERATE_ESCAPES :
325                                                     GENERATE_ALL_MOVES));
326         for (u = ctx.sMoveStack.uBegin[0];
327              u < ctx.sMoveStack.uEnd[0];
328              u++)
329         {
330             mv = ctx.sMoveStack.mvf[u].mv;
331             pMoved = mv.pMoved;
332             if (PIECE_TYPE(pMoved) == pPieceType)
333             {
334                 if ((99 != cFileRestr) && 
335                     (FILE(mv.cFrom) != cFileRestr)) continue;
336                 if ((99 != cRankRestr) && 
337                     (RANK(mv.cFrom) != cRankRestr)) continue;
338
339                 if ((mv.cTo == cTo) &&
340                     (mv.pPromoted == pPromoted))
341                 {
342                     if (TRUE == MakeMove((SEARCHER_THREAD_CONTEXT *)&ctx, mv))
343                     {
344                         UnmakeMove((SEARCHER_THREAD_CONTEXT *)&ctx, mv);
345                         cFrom = mv.cFrom;
346                         cTo = mv.cTo;
347                         uNumMatches++;
348                     }
349                 }
350             }
351         }
352         
353         //
354         // Note: 4 moves match for promotions
355         //
356         if (uNumMatches == 1)
357         {
358             goto found_coors;
359         }
360     }
361     goto illegal;
362
363  found_coors:
364     pMoved = pos->rgSquare[cFrom].pPiece;
365     if (IS_EMPTY(pMoved))
366     {
367         goto illegal;
368     }
369     pCaptured = pos->rgSquare[cTo].pPiece;
370     if (!IS_EMPTY(pCaptured) &&
371         !OPPOSITE_COLORS(pMoved, pCaptured))
372     {
373         goto illegal;
374     }
375
376     //
377     // Handle enpassant
378     // 
379     if ((cTo == pos->cEpSquare) && (IS_PAWN(pMoved)))
380     {
381         if (WHITE == pos->uToMove)
382         {
383             pCaptured = pos->rgSquare[cTo + 16].pPiece;
384             if (pCaptured != BLACK_PAWN) goto illegal;
385         }
386         else
387         {
388             pCaptured = pos->rgSquare[cTo - 16].pPiece;
389             if (pCaptured != WHITE_PAWN) goto illegal;
390         }
391         fSpecial = MOVE_FLAG_SPECIAL;
392     }
393     
394     //
395     // Handle double jump
396     // 
397     if (IS_PAWN(pMoved) && DISTANCE(cFrom, cTo) > 1)
398     {
399         fSpecial = MOVE_FLAG_SPECIAL;
400     }
401     
402     mv.uMove = MAKE_MOVE(cFrom, cTo, pMoved, pCaptured, pPromoted, fSpecial);
403     if (FALSE == SanityCheckMove(pos, mv)) 
404     {
405         goto illegal;
406     }
407     return(mv);
408     
409  illegal:
410     mv.uMove = 0;
411     return(mv);
412 }
413
414
415 MOVE 
416 ParseMoveSan(CHAR *szInput, 
417              POSITION *pos)
418 /**
419
420 Routine description:
421
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.
424
425 Parameters:
426
427     CHAR *szInput,
428     POSITION *pos
429
430 Return value:
431
432     MOVE
433
434 **/
435 {
436     CHAR szCapturedMove[SMALL_STRING_LEN_CHAR];
437     MOVE mv;
438
439     mv.uMove = 0;
440     memset(szCapturedMove, 0, sizeof(szCapturedMove));
441     strncpy(szCapturedMove, StripMove(szInput), SMALL_STRING_LEN_CHAR - 1);
442
443     if (MOVE_SAN != LooksLikeMove(szCapturedMove)) 
444     {
445         return(mv);                           // fail
446     }
447
448     mv = _ParseCastleSan(szCapturedMove, pos);
449     if (mv.uMove != 0)
450     {
451         goto end;
452     }
453     
454     mv = _ParseNormalSan(szCapturedMove, pos);
455     if (mv.uMove != 0)
456     {
457         goto end;
458     }
459     
460     return(mv);                               // fail
461
462  end:
463     if (InCheck(pos, pos->uToMove))
464     {
465         mv.bvFlags |= MOVE_FLAG_ESCAPING_CHECK;
466     }
467     return(mv);
468 }
469
470
471 CHAR *
472 MoveToSan(MOVE mv, POSITION *pos)
473 /**
474
475 Routine description:
476
477     Given a move and a current board position, convert the MOVE
478     structure into a SAN text string.
479
480 Parameters:
481
482     MOVE mv,
483     POSITION *pos
484
485 Return value:
486
487     CHAR
488
489 **/
490 {
491     static char szTextMove[10];
492     char *p;
493     ULONG u = 0;
494     MOVE x;
495     
496     ASSERT(IS_ON_BOARD(mv.cFrom));
497     ASSERT(IS_ON_BOARD(mv.cTo));
498     if (0 == mv.uMove)
499     {
500         return(NULL);
501     }
502     
503     do
504     {
505         p = szTextMove;
506         memset(szTextMove, 0, sizeof(szTextMove));
507
508         //
509         // Handle castles.
510         //
511         if (IS_KING(mv.pMoved) && (IS_SPECIAL_MOVE(mv)))
512         {
513             if ((mv.cTo == G1) || (mv.cTo == G8))
514             {
515                 strcpy(szTextMove, "O-O");
516                 if (IS_CHECKING_MOVE(mv))
517                 {
518                     *p++ = '+';
519                 }
520                 goto end;
521             }
522             else if ((mv.cTo == C1) || (mv.cTo == C8))
523             {
524                 strcpy(szTextMove, "O-O-O");
525                 if (IS_CHECKING_MOVE(mv))
526                 {
527                     *p++ = '+';
528                 }
529                 goto end;
530             }
531             ASSERT(FALSE);
532             strcpy(szTextMove, "ILLEGAL");
533             goto end;
534         }
535     
536         if (!IS_PAWN(mv.pMoved))
537         {
538             *p++ = g_cPieces[(PIECE_TYPE(mv.pMoved)) - 1];
539             if (u == 1)
540             {
541                 *p++ = FILE(mv.cFrom) + 'a';
542             }
543             else if (u == 2)
544             {
545                 *p++ = RANK(mv.cFrom) + '0';
546             }
547             else if (u == 3)
548             {
549                 *p++ = FILE(mv.cFrom) + 'a';
550                 *p++ = RANK(mv.cFrom) + '0';
551             }
552         }                
553         else
554         {
555             if (u > 0)
556             {
557                 *p++ = FILE(mv.cFrom) + 'a';
558             }
559         }
560         
561         if (mv.pCaptured)
562         {
563             if (IS_PAWN(mv.pMoved))
564             {
565                 *p++ = FILE(mv.cFrom) + 'a';
566             }
567             *p++ = 'x';
568         }
569         *p++ = FILE(mv.cTo) + 'a';
570         *p++ = RANK(mv.cTo) + '0';
571
572         if (mv.pPromoted)
573         {
574             *p++ = '=';
575             *p++ = g_cPieces[(PIECE_TYPE(mv.pPromoted)) - 1];
576         }
577         
578         u++;
579         if (u >= 4)
580         {
581             strcpy(szTextMove, "ILLEGAL");
582             goto end;
583         }
584
585         if (IS_CHECKING_MOVE(mv))
586         {
587             *p++ = '+';
588         }
589         x = ParseMoveSan(szTextMove, pos);
590     }
591     while ((mv.cTo != x.cTo) ||
592            (mv.cFrom != x.cFrom));
593
594 end:
595     return(szTextMove);    
596 }