1 /* san.cpp
2 
3    GNU Chess protocol adapter
4 
5    Copyright (C) 2001-2011 Free Software Foundation, Inc.
6 
7    This program is free software: you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation, either version 3 of the License, or
10    (at your option) any later version.
11 
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16 
17    You should have received a copy of the GNU General Public License
18    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 */
20 
21 
22 // san.cpp
23 
24 // includes
25 
26 #include <cctype>
27 #include <cstdio>
28 #include <cstdlib>
29 #include <cstring>
30 
31 #include "attack.h"
32 #include "board.h"
33 #include "list.h"
34 #include "move.h"
35 #include "move_gen.h"
36 #include "move_legal.h"
37 #include "piece.h"
38 #include "san.h"
39 #include "square.h"
40 #include "util.h"
41 
42 namespace adapter {
43 
44 // constants
45 
46 static const bool UseSlowDebug = false;
47 
48 enum ambiguity_t {
49    AMBIGUITY_NONE,
50    AMBIGUITY_FILE,
51    AMBIGUITY_RANK,
52    AMBIGUITY_SQUARE
53 };
54 
55 // functions
56 
57 static bool san_to_lan    (const char san[], const board_t * board, char string[], int size);
58 static int  move_from_lan (const char string[], const board_t * board);
59 
60 static int  ambiguity     (int move, const board_t * board);
61 
62 // move_to_san()
63 
move_to_san(int move,const board_t * board,char string[],int size)64 bool move_to_san(int move, const board_t * board, char string[], int size) {
65 
66    int from, to, piece;
67    char tmp_string[256];
68 
69    ASSERT(move_is_ok(move));
70    ASSERT(board_is_ok(board));
71    ASSERT(string!=NULL);
72    ASSERT(size>=8);
73 
74    ASSERT(move_is_legal(move,board));
75 
76    if (size < 8) return false;
77 
78    // init
79 
80    from = move_from(move);
81    to = move_to(move);
82 
83    string[0] = '\0';
84 
85    // castle
86 
87    if (move_is_castle(move,board)) {
88 
89       if (to > from) {
90          strcat(string,"O-O");
91       } else {
92          strcat(string,"O-O-O");
93       }
94 
95       goto check;
96    }
97 
98    // from
99 
100    piece = board->square[from];
101 
102    if (piece_is_pawn(piece)) {
103 
104       // pawn
105 
106       if (move_is_capture(move,board)) {
107          sprintf(tmp_string,"%c",file_to_char(square_file(from)));
108          strcat(string,tmp_string);
109       }
110 
111    } else {
112 
113       // piece
114 
115       sprintf(tmp_string,"%c",toupper(piece_to_char(piece)));
116       strcat(string,tmp_string);
117 
118       // ambiguity
119 
120       switch (ambiguity(move,board)) {
121       case AMBIGUITY_NONE:
122          break;
123       case AMBIGUITY_FILE:
124          sprintf(tmp_string,"%c",file_to_char(square_file(from)));
125          strcat(string,tmp_string);
126          break;
127       case AMBIGUITY_RANK:
128          sprintf(tmp_string,"%c",rank_to_char(square_rank(from)));
129          strcat(string,tmp_string);
130          break;
131       case AMBIGUITY_SQUARE:
132          if (!square_to_string(from,tmp_string,256)) return false;
133          strcat(string,tmp_string);
134          break;
135       default:
136          ASSERT(false);
137          break;
138       }
139    }
140 
141    // capture
142 
143    if (move_is_capture(move,board)) strcat(string,"x");
144 
145    // to
146 
147    if (!square_to_string(to,tmp_string,256)) return false;
148    strcat(string,tmp_string);
149 
150    // promote
151 
152    if (move_is_promote(move)) {
153       sprintf(tmp_string,"=%c",toupper(piece_to_char(move_promote(move,board))));
154       strcat(string,tmp_string);
155    }
156 
157    // check
158 
159 check:
160 
161    if (move_is_mate(move,board)) {
162       strcat(string,"#");
163    } else if (move_is_check(move,board)) {
164       strcat(string,"+");
165    }
166 
167    return true;
168 }
169 
170 // move_from_san()
171 
move_from_san(const char string[],const board_t * board)172 int move_from_san(const char string[], const board_t * board) {
173 
174    char s[256];
175    int move;
176 
177    ASSERT(string!=NULL);
178    ASSERT(board_is_ok(board));
179 
180    san_to_lan(string,board,s,256);
181    move = move_from_lan(s,board);
182 
183    ASSERT(!UseSlowDebug||move==move_from_san_debug(string,board));
184 
185    return move;
186 }
187 
188 // move_from_san_debug()
189 
move_from_san_debug(const char string[],const board_t * board)190 int move_from_san_debug(const char string[], const board_t * board) {
191 
192    list_t list[1];
193    int i, move;
194    char move_string[256];
195 
196    ASSERT(string!=NULL);
197    ASSERT(board_is_ok(board));
198 
199    gen_legal_moves(list,board);
200 
201    for (i = 0; i < list_size(list); i++) {
202       move = list_move(list,i);
203       if (!move_to_san(move,board,move_string,256)) ASSERT(false);
204       if (my_string_equal(move_string,string)) return move;
205    }
206 
207    return MoveNone;
208 }
209 
210 // san_to_lan()
211 
san_to_lan(const char san[],const board_t * board,char string[],int size)212 static bool san_to_lan(const char san[], const board_t * board, char string[], int size) {
213 
214    int len;
215    int left, right;
216    int c;
217    int king, rook;
218    char king_string[3], rook_string[3];
219 
220    ASSERT(san!=NULL);
221    ASSERT(board_is_ok(board));
222    ASSERT(string!=NULL);
223    ASSERT(size>=8);
224 
225    // init
226 
227    if (size < 8) return false;
228    strcpy(string,"???????");
229 
230    len = strlen(san);
231 
232    left = 0;
233    right = len;
234 
235    // skip trailing '+' or '#'
236 
237    if (left < right) {
238       c = san[right-1];
239       if (c == '+' || c == '#') right--;
240    }
241 
242    // castling
243 
244    ASSERT(left==0);
245 
246    if (false) {
247 
248    } else if (right == 3 && strncmp(san,"O-O",3) == 0) {
249 
250       if (board->castle[board->turn][SideH] == SquareNone) return false;
251 
252       king = king_pos(board,board->turn);
253       rook = board->castle[board->turn][SideH];
254 
255       square_to_string(king,king_string,3);
256       square_to_string(rook,rook_string,3);
257 
258       sprintf(string,"K%s?%s?",king_string,rook_string);
259 
260    } else if (right == 5 && strncmp(san,"O-O-O",5) == 0) {
261 
262       if (board->castle[board->turn][SideA] == SquareNone) return false;
263 
264       king = king_pos(board,board->turn);
265       rook = board->castle[board->turn][SideA];
266 
267       square_to_string(king,king_string,3);
268       square_to_string(rook,rook_string,3);
269 
270       sprintf(string,"K%s?%s?",king_string,rook_string);
271 
272    } else {
273 
274       // moved piece
275 
276       if (left < right) {
277 
278          c = san[left];
279 
280          if (char_is_piece(c)) {
281             string[0] = c;
282             left++;
283          }
284       }
285 
286       // promotion
287 
288       if (left < right) {
289 
290          c = toupper(san[right-1]);
291 
292          if (char_is_piece(c)) {
293 
294             string[6] = c;
295             right--;
296 
297             // skip '='
298 
299             if (left < right && san[right-1] == '=') right--;
300          }
301       }
302 
303       // to-square rank
304 
305       if (left < right) {
306 
307          c = san[right-1];
308 
309          if (char_is_rank(c)) {
310             string[5] = c;
311             right--;
312          }
313       }
314 
315       // to-square file
316 
317       if (left < right) {
318 
319          c = san[right-1];
320 
321          if (char_is_file(c)) {
322             string[4] = c;
323             right--;
324          }
325       }
326 
327       // captured piece
328 
329       if (left < right) {
330 
331          c = san[right-1];
332 
333          if (char_is_piece(c)) {
334             string[3] = c;
335             right--;
336          }
337       }
338 
339       // skip middle '-' or 'x'
340 
341       if (left < right) {
342          c = san[right-1];
343          if (c == '-' || c == 'x') right--;
344       }
345 
346       // from-square file
347 
348       if (left < right) {
349 
350          c = san[left];
351 
352          if (char_is_file(c)) {
353             string[1] = c;
354             left++;
355          }
356       }
357 
358       // from-square rank
359 
360       if (left < right) {
361 
362          c = san[left];
363 
364          if (char_is_rank(c)) {
365             string[2] = c;
366             left++;
367          }
368       }
369 
370       if (left != right) return false;
371    }
372 
373    // end
374 
375    return true;
376 }
377 
378 // move_from_lan()
379 
move_from_lan(const char string[],const board_t * board)380 static int move_from_lan(const char string[], const board_t * board) {
381 
382    int len;
383    int move;
384    int promote;
385    char s[256];
386    int from, to;
387    int colour;
388    int inc;
389    int piece_char;
390    int n;
391    const uint8 * ptr;
392    int piece;
393    int side;
394 
395    ASSERT(string!=NULL);
396    ASSERT(board_is_ok(board));
397 
398    // init
399 
400    len = strlen(string);
401    if (len != 7) return MoveNone;
402 
403    move = MoveNone;
404    colour = board->turn;
405 
406    // promote
407 
408    promote = 0;
409 
410    switch (string[6]) {
411    case '?': // not a promotion
412       break;
413    case 'N':
414       promote = MovePromoteKnight;
415       break;
416    case 'B':
417       promote = MovePromoteBishop;
418       break;
419    case 'R':
420       promote = MovePromoteRook;
421       break;
422    case 'Q':
423       promote = MovePromoteQueen;
424       break;
425    default:
426       return MoveNone;
427       break;
428    }
429 
430    // to square
431 
432    s[0] = string[4];
433    s[1] = string[5];
434    s[2] = '\0';
435 
436    to = square_from_string(s);
437    if (to == SquareNone) return MoveNone;
438 
439    // known from square?
440 
441    if (string[1] != '?' && string[2] != '?') {
442 
443       // from square
444 
445       s[0] = string[1];
446       s[1] = string[2];
447       s[2] = '\0';
448 
449       from = square_from_string(s);
450       if (from == SquareNone) return MoveNone;
451 
452       // convert "king slide" castling to KxR
453 
454       if (piece_is_king(board->square[from])
455        && square_rank(to) == square_rank(from)
456        && abs(to-from) > 1) {
457          side = (to > from) ? SideH : SideA;
458          to = board->castle[colour][side];
459          if (to == SquareNone) return MoveNone;
460       }
461 
462       // move
463 
464       move = move_make(from,to) | promote;
465 
466       return move;
467    }
468 
469    // pawn non-capture?
470 
471    if (string[0] == '?' && string[1] == '?') {
472 
473       if (board->square[to] != Empty) return MoveNone; // useful?
474 
475       inc = (colour_is_white(colour)) ? +16 : -16;
476 
477       from = to - inc;
478       if (board->square[from] == Empty && square_side_rank(to,colour) == Rank4) {
479          from -= inc;
480       }
481 
482       if (board->square[from] != piece_make_pawn(colour)) { // useful?
483          return MoveNone;
484       }
485 
486       // move
487 
488       move = move_make(from,to) | promote;
489 
490       return move;
491    }
492 
493    // pawn capture?
494 
495    piece_char = string[0];
496 
497    if (piece_char == '?' && string[1] != '?') {
498       piece_char = 'P';
499    }
500 
501    // attack loop
502 
503    n = 0;
504 
505    for (ptr = board->list[colour]; (from=*ptr) != SquareNone; ptr++) {
506 
507       piece = board->square[from];
508 
509       if (toupper(piece_to_char(piece)) == piece_char) {
510          if (piece_attack(board,piece,from,to)) {
511             if (true
512              && (string[1] == '?' || file_to_char(square_file(from)) == string[1])
513              && (string[2] == '?' || rank_to_char(square_rank(from)) == string[2])) {
514                if (!is_pinned(board,from,to,colour)) {
515                   move = move_make(from,to) | promote;
516                   n++;
517                }
518             }
519          }
520       }
521    }
522 
523    if (n != 1) move = MoveNone;
524 
525    return move;
526 }
527 
528 // ambiguity()
529 
ambiguity(int move,const board_t * board)530 static int ambiguity(int move, const board_t * board) {
531 
532    int from, to, piece;
533    list_t list[1];
534    int i, n, m;
535 
536    // init
537 
538    from = move_from(move);
539    to = move_to(move);
540    piece = move_piece(move,board);
541 
542    gen_legal_moves(list,board);
543 
544    // no ambiguity?
545 
546    n = 0;
547 
548    for (i = 0; i < list_size(list); i++) {
549       m = list_move(list,i);
550       if (move_piece(m,board) == piece && move_to(m) == to) {
551          n++;
552       }
553    }
554 
555    if (n == 1) return AMBIGUITY_NONE;
556 
557    // file ambiguity?
558 
559    n = 0;
560 
561    for (i = 0; i < list_size(list); i++) {
562       m = list_move(list,i);
563       if (move_piece(m,board) == piece && move_to(m) == to) {
564          if (square_file(move_from(m)) == square_file(from)) n++;
565       }
566    }
567 
568    if (n == 1) return AMBIGUITY_FILE;
569 
570    // rank ambiguity?
571 
572    n = 0;
573 
574    for (i = 0; i < list_size(list); i++) {
575       m = list_move(list,i);
576       if (move_piece(m,board) == piece && move_to(m) == to) {
577          if (square_rank(move_from(m)) == square_rank(from)) n++;
578       }
579    }
580 
581    if (n == 1) return AMBIGUITY_RANK;
582 
583    // square ambiguity
584 
585    return AMBIGUITY_SQUARE;
586 }
587 
588 }  // namespace adapter
589 
590 // end of san.cpp
591 
592