1 /* vim:tw=78:ts=8:sw=4:set ft=c: */
3 Copyright (C) 2002-2019 Ben Kibbey <bjk@luxsci.net>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
44 KINGSIDE
= 1, QUEENSIDE
47 static int finalize_move (GAME g
, BOARD b
, int promo
, int sfile
, int srank
,
49 static int find_source_square (GAME
, BOARD
, int, int *, int *, int, int);
50 static int check_self (GAME g
, BOARD b
, int file
, int rank
);
51 static int validate_pawn (GAME g
, BOARD b
, int sfile
, int srank
, int file
,
53 static int find_source_square (GAME
, BOARD
, int, int *, int *, int, int);
56 val_piece_side (char turn
, int c
)
58 if ((isupper (c
) && turn
== WHITE
) || (islower (c
) && turn
== BLACK
))
65 count_piece (GAME g
, BOARD b
, int piece
, int sfile
,
66 int srank
, int file
, int rank
, int *count
)
70 if (!VALIDRANK (rank
) || !VALIDFILE (file
))
73 p
= b
[RANKTOBOARD (rank
)][FILETOBOARD (file
)].icon
;
74 pi
= pgn_piece_to_int (p
);
76 if (pi
!= OPEN_SQUARE
)
78 if (pi
== piece
&& val_piece_side (g
->turn
, p
))
80 if (sfile
&& file
== sfile
)
82 if (!srank
|| (srank
&& rank
== srank
))
85 else if (srank
&& rank
== srank
)
90 else if (!sfile
&& !srank
)
101 * Get the source row and column for a given piece.
103 * The following two functions find 'piece' from the given square 'col' and
104 * 'row' and store the resulting column or row in 'c' and 'r'. The return
105 * value is the number of 'piece' found (on the current g->side) or zero.
106 * Search for 'piece' stops when a non-empty square is found.
109 count_by_diag (GAME g
, BOARD b
, int piece
,
110 int sfile
, int srank
, int file
, int rank
)
113 int ul
= 0, ur
= 0, dl
= 0, dr
= 0;
117 for (i
= 1; VALIDFILE (i
); i
++)
122 if (!ul
&& VALIDRANK (r
) && VALIDFILE (f
))
123 ul
= count_piece (g
, b
, piece
, sfile
, srank
, f
, r
, &count
);
128 if (!ur
&& VALIDRANK (r
) && VALIDFILE (f
))
129 ur
= count_piece (g
, b
, piece
, sfile
, srank
, f
, r
, &count
);
134 if (!dl
&& VALIDRANK (r
) && VALIDFILE (f
))
135 dl
= count_piece (g
, b
, piece
, sfile
, srank
, f
, r
, &count
);
140 if (!dr
&& VALIDRANK (r
) && VALIDFILE (f
))
141 dr
= count_piece (g
, b
, piece
, sfile
, srank
, f
, r
, &count
);
148 count_knight (GAME g
, BOARD b
, int piece
, int sfile
, int srank
,
153 count_piece (g
, b
, piece
, sfile
, srank
, file
- 1, rank
+ 2, &count
);
154 count_piece (g
, b
, piece
, sfile
, srank
, file
+ 1, rank
+ 2, &count
);
155 count_piece (g
, b
, piece
, sfile
, srank
, file
+ 2, rank
+ 1, &count
);
156 count_piece (g
, b
, piece
, sfile
, srank
, file
- 2, rank
+ 1, &count
);
157 count_piece (g
, b
, piece
, sfile
, srank
, file
+ 1, rank
- 2, &count
);
158 count_piece (g
, b
, piece
, sfile
, srank
, file
- 1, rank
- 2, &count
);
159 count_piece (g
, b
, piece
, sfile
, srank
, file
+ 2, rank
- 1, &count
);
160 count_piece (g
, b
, piece
, sfile
, srank
, file
- 2, rank
- 1, &count
);
165 count_by_rank (GAME g
, BOARD b
, int piece
,
166 int sfile
, int srank
, int file
, int rank
)
172 for (i
= 1; VALIDRANK (i
); i
++)
174 if (!u
&& VALIDRANK ((rank
+ i
)))
175 u
= count_piece (g
, b
, piece
, sfile
, srank
, file
, rank
+ i
, &count
);
177 if (!d
&& VALIDRANK ((rank
- i
)))
178 d
= count_piece (g
, b
, piece
, sfile
, srank
, file
, rank
- i
, &count
);
188 count_by_file (GAME g
, BOARD b
, int piece
,
189 int sfile
, int srank
, int file
, int rank
)
195 for (i
= 1; VALIDFILE (i
); i
++)
197 if (!r
&& VALIDFILE ((file
+ i
)))
198 r
= count_piece (g
, b
, piece
, sfile
, srank
, file
+ i
, rank
, &count
);
200 if (!l
&& VALIDFILE ((file
- i
)))
201 l
= count_piece (g
, b
, piece
, sfile
, srank
, file
- i
, rank
, &count
);
211 count_by_rank_file (GAME g
, BOARD b
, int piece
,
212 int sfile
, int srank
, int file
, int rank
)
216 count
= count_by_rank (g
, b
, piece
, sfile
, srank
, file
, rank
);
217 return count
+ count_by_file (g
, b
, piece
, sfile
, srank
, file
, rank
);
221 opponent_can_attack (GAME g
, BOARD b
, int file
, int rank
)
225 int kf
= g
->kfile
, kr
= g
->krank
;
228 g
->kfile
= g
->okfile
, g
->krank
= g
->okrank
;
230 for (r
= 1; VALIDRANK (r
); r
++)
232 for (f
= 1; VALIDFILE (f
); f
++)
234 p
= b
[RANKTOBOARD (r
)][FILETOBOARD (f
)].icon
;
235 pi
= pgn_piece_to_int (p
);
237 if (pi
== OPEN_SQUARE
|| !val_piece_side (g
->turn
, p
))
240 if (find_source_square (g
, b
, pi
, &f
, &r
, file
, rank
) != 0)
242 g
->kfile
= kf
, g
->krank
= kr
;
249 g
->kfile
= kf
, g
->krank
= kr
;
255 validate_castle_move (GAME g
, BOARD b
, int side
, int sfile
,
256 int srank
, int file
, int rank
)
260 if (side
== KINGSIDE
)
262 if ((g
->turn
== WHITE
&& !TEST_FLAG (g
->flags
, GF_WK_CASTLE
)) ||
263 (g
->turn
== BLACK
&& !TEST_FLAG (g
->flags
, GF_BK_CASTLE
)))
264 return E_PGN_INVALID
;
268 if ((g
->turn
== WHITE
&& !TEST_FLAG (g
->flags
, GF_WQ_CASTLE
)) ||
269 (g
->turn
== BLACK
&& !TEST_FLAG (g
->flags
, GF_BQ_CASTLE
)))
270 return E_PGN_INVALID
;
273 if (file
> FILETOINT ('e'))
275 if (b
[RANKTOBOARD (srank
)][FILETOBOARD ((sfile
+ 1))].icon
276 != pgn_int_to_piece (g
->turn
, OPEN_SQUARE
) ||
277 b
[RANKTOBOARD (srank
)][FILETOBOARD ((sfile
+ 2))].icon
278 != pgn_int_to_piece (g
->turn
, OPEN_SQUARE
))
279 return E_PGN_INVALID
;
282 if (pgn_config
.strict_castling
> 0)
284 if (opponent_can_attack (g
, b
, sfile
+ 1, srank
))
285 return E_PGN_INVALID
;
287 if (opponent_can_attack (g
, b
, sfile
+ 2, srank
))
288 return E_PGN_INVALID
;
293 if (b
[RANKTOBOARD (srank
)][FILETOBOARD ((sfile
- 1))].icon
!=
294 pgn_int_to_piece (g
->turn
, OPEN_SQUARE
) ||
295 b
[RANKTOBOARD (srank
)][FILETOBOARD ((sfile
- 2))].icon
!=
296 pgn_int_to_piece (g
->turn
, OPEN_SQUARE
) ||
297 b
[RANKTOBOARD (srank
)][FILETOBOARD ((sfile
- 3))].icon
!=
298 pgn_int_to_piece (g
->turn
, OPEN_SQUARE
))
299 return E_PGN_INVALID
;
301 if (pgn_config
.strict_castling
> 0)
303 if (opponent_can_attack (g
, b
, sfile
- 1, srank
))
304 return E_PGN_INVALID
;
306 if (opponent_can_attack (g
, b
, sfile
- 2, srank
))
307 return E_PGN_INVALID
;
309 if (opponent_can_attack (g
, b
, sfile
- 3, srank
))
310 return E_PGN_INVALID
;
314 n
= g
->check_testing
;
315 g
->check_testing
= 1;
317 if (check_self (g
, b
, g
->kfile
, g
->krank
) == CHECK_SELF
)
319 g
->check_testing
= n
;
320 return E_PGN_INVALID
;
323 g
->check_testing
= n
;
329 validate_piece (GAME g
, BOARD b
, int p
, int sfile
,
330 int srank
, int file
, int rank
)
340 /* Find the first pawn in the current column. */
341 i
= (g
->turn
== WHITE
) ? -1 : 1;
345 for (r
= rank
+ i
, dist
= 0; VALIDFILE (r
); r
+= i
, dist
++)
347 p
= b
[RANKTOBOARD (r
)][FILETOBOARD (file
)].icon
;
349 if (pgn_piece_to_int (p
) != OPEN_SQUARE
)
353 if (pgn_piece_to_int (p
) != PAWN
|| !val_piece_side (g
->turn
, p
)
355 return E_PGN_INVALID
;
361 dist
= abs (srank
- rank
);
362 p
= b
[RANKTOBOARD (srank
)][FILETOBOARD (sfile
)].icon
;
364 if (pgn_piece_to_int (p
) != PAWN
|| !val_piece_side (g
->turn
, p
)
366 return E_PGN_INVALID
;
369 if (g
->turn
== WHITE
)
371 if ((srank
== 2 && dist
> 2) || (srank
> 2 && dist
> 1))
372 return E_PGN_INVALID
;
376 if ((srank
== 7 && dist
> 2) || (srank
< 7 && dist
> 1))
377 return E_PGN_INVALID
;
380 p
= b
[RANKTOBOARD (rank
)][FILETOBOARD (file
)].icon
;
382 if (pgn_piece_to_int (p
) != OPEN_SQUARE
)
383 return E_PGN_INVALID
;
385 else if (sfile
!= file
)
387 if (abs (sfile
- file
) != 1)
388 return E_PGN_INVALID
;
390 srank
= (g
->turn
== WHITE
) ? rank
- 1 : rank
+ 1;
391 p
= b
[RANKTOBOARD (srank
)][FILETOBOARD (sfile
)].icon
;
393 if (!val_piece_side (g
->turn
, p
))
394 return E_PGN_INVALID
;
396 if (pgn_piece_to_int (p
) != PAWN
|| abs (srank
- rank
) != 1)
397 return E_PGN_INVALID
;
399 p
= b
[RANKTOBOARD (rank
)][FILETOBOARD (file
)].icon
;
402 if (pgn_piece_to_int (p
) == OPEN_SQUARE
)
404 /* Previous move was not 2 squares and a pawn. */
405 if (!TEST_FLAG (g
->flags
, GF_ENPASSANT
))
406 return E_PGN_INVALID
;
408 if (!b
[RANKTOBOARD (rank
)][FILETOBOARD (file
)].enpassant
)
409 return E_PGN_INVALID
;
411 r
= (g
->turn
== WHITE
) ? 6 : 3;
414 return E_PGN_INVALID
;
416 r
= (g
->turn
== WHITE
) ? rank
- 1 : rank
+ 1;
417 p
= b
[RANKTOBOARD (r
)][FILETOBOARD (file
)].icon
;
419 if (pgn_piece_to_int (p
) != PAWN
)
420 return E_PGN_INVALID
;
423 if (val_piece_side (g
->turn
, p
))
424 return E_PGN_INVALID
;
427 p
= b
[RANKTOBOARD (srank
)][FILETOBOARD (sfile
)].icon
;
429 if (!val_piece_side (g
->turn
, p
))
430 return E_PGN_INVALID
;
434 f
= abs (sfile
- file
);
435 r
= abs (srank
- rank
);
438 return E_PGN_INVALID
;
442 if (sfile
!= FILETOINT ('e'))
443 return E_PGN_INVALID
;
446 if (validate_castle_move (g
, b
, (file
> FILETOINT ('e')) ?
447 KINGSIDE
: QUEENSIDE
, sfile
, srank
,
448 file
, rank
) != E_PGN_OK
)
449 return E_PGN_INVALID
;
461 * Returns the number of pieces of type 'p' that can move to the destination
462 * square located at 'file' and 'rank'. The source square 'sfile' and 'srank'
463 * (if available in the move text) should be determined before calling this
464 * function or set to 0 if unknown. Returns 0 if the move is impossible for
468 find_ambiguous (GAME g
, BOARD b
, int p
, int sfile
,
469 int srank
, int file
, int rank
)
476 count
= validate_pawn (g
, b
, sfile
, srank
, file
, rank
);
479 count
= count_by_rank_file (g
, b
, p
, sfile
, srank
, file
, rank
);
482 count
= count_knight (g
, b
, p
, sfile
, srank
, file
, rank
);
485 count
= count_by_diag (g
, b
, p
, sfile
, srank
, file
, rank
);
488 count
= count_by_rank_file (g
, b
, p
, sfile
, srank
, file
, rank
);
489 count
+= count_by_diag (g
, b
, p
, sfile
, srank
, file
, rank
);
492 count
= count_by_rank_file (g
, b
, p
, sfile
, srank
, file
, rank
);
493 count
+= count_by_diag (g
, b
, p
, sfile
, srank
, file
, rank
);
503 find_king_squares (GAME g
, BOARD b
, int *file
,
504 int *rank
, int *ofile
, int *orank
)
508 for (r
= 1; VALIDRANK (r
); r
++)
510 for (f
= 1; VALIDFILE (f
); f
++)
512 int p
= b
[RANKTOBOARD (r
)][FILETOBOARD (f
)].icon
;
513 int pi
= pgn_piece_to_int (p
);
515 if (pi
== OPEN_SQUARE
|| pi
!= KING
)
518 if (val_piece_side (g
->turn
, p
))
519 *file
= f
, *rank
= r
;
521 *ofile
= f
, *orank
= r
;
526 PGN_DUMP ("%s:%d: king location: %c%c %c%c(opponent)\n", __FILE__
,
527 __LINE__
, INTTOFILE (*file
), INTTORANK (*rank
),
528 INTTOFILE (*ofile
), INTTORANK (*orank
));
533 parse_castle_move (GAME g
, BOARD b
, int side
, int *sfile
,
534 int *srank
, int *file
, int *rank
)
536 *srank
= *rank
= (g
->turn
== WHITE
) ? 1 : 8;
537 *sfile
= FILETOINT ('e');
539 if (side
== KINGSIDE
)
540 *file
= FILETOINT ('g');
542 *file
= FILETOINT ('c');
544 return validate_castle_move (g
, b
, side
, *sfile
, *srank
, *file
, *rank
);
548 * Almost exactly like pgn_find_valid_moves() but returns immediately after a
549 * valid move is found.
552 check_mate_thingy (GAME g
, BOARD b
, int file
, int rank
)
555 int p
= pgn_piece_to_int (b
[RANKTOBOARD (rank
)][FILETOBOARD (file
)].icon
);
557 for (r
= 1; VALIDRANK (r
); r
++)
559 for (f
= 1; VALIDFILE (f
); f
++)
562 (g
->turn
, b
[RANKTOBOARD (r
)][FILETOBOARD (f
)].icon
))
565 if (find_source_square (g
, b
, p
, &file
, &rank
, f
, r
) != 0)
574 checkmate_test (GAME g
, BOARD b
)
578 int kf
= g
->kfile
, kr
= g
->krank
, okf
= g
->okfile
, okr
= g
->okrank
;
581 PGN_DUMP ("%s:%d: BEGIN checkmate test\n", __FILE__
, __LINE__
);
584 * The king squares need to be switched also for find_source_squares()
585 * which calls check_self().
588 g
->kfile
= g
->okfile
, g
->krank
= g
->okrank
;
590 for (r
= 1; VALIDRANK (r
); r
++)
592 for (f
= 1; VALIDFILE (f
); f
++)
595 int sfile
= f
, srank
= r
;
597 p
= b
[RANKTOBOARD (r
)][FILETOBOARD (f
)].icon
;
599 if (p
== OPEN_SQUARE
|| !val_piece_side (g
->turn
, p
))
602 if (check_mate_thingy (g
, b
, sfile
, srank
))
612 g
->kfile
= kf
, g
->krank
= kr
, g
->okfile
= okf
, g
->okrank
= okr
;
617 check_opponent (GAME g
, BOARD b
, int file
, int rank
)
623 PGN_DUMP ("%s:%d: BEGIN opponent check test\n", __FILE__
, __LINE__
);
626 for (r
= 1; VALIDRANK (r
); r
++)
628 for (f
= 1; VALIDFILE (f
); f
++)
630 p
= b
[RANKTOBOARD (r
)][FILETOBOARD (f
)].icon
;
631 pi
= pgn_piece_to_int (p
);
633 if (pi
== OPEN_SQUARE
|| !val_piece_side (g
->turn
, p
))
636 if (find_source_square (g
, b
, pi
, &f
, &r
, file
, rank
) != 0)
645 check_self (GAME g
, BOARD b
, int file
, int rank
)
651 PGN_DUMP ("%s:%d: BEGIN self check test\n", __FILE__
, __LINE__
);
655 for (r
= 1; VALIDRANK (r
); r
++)
657 for (f
= 1; VALIDFILE (f
); f
++)
659 p
= b
[RANKTOBOARD (r
)][FILETOBOARD (f
)].icon
;
660 pi
= pgn_piece_to_int (p
);
662 if (pi
== OPEN_SQUARE
|| !val_piece_side (g
->turn
, p
))
665 if (find_source_square (g
, b
, pi
, &f
, &r
, file
, rank
) != 0)
678 check_test (GAME g
, BOARD b
)
681 PGN_DUMP ("%s:%d: BEGIN check test\n", __FILE__
, __LINE__
);
683 g
->check
= check_opponent (g
, b
, g
->okfile
, g
->okrank
);
686 return checkmate_test (g
, b
);
688 g
->check_testing
= 0;
693 validate_pawn (GAME g
, BOARD b
, int sfile
, int srank
, int file
, int rank
)
695 int n
= abs (srank
- rank
);
698 if (abs (sfile
- file
) > 1)
701 if (g
->turn
== WHITE
)
703 if ((srank
== 2 && n
> 2) || (srank
> 2 && n
> 1))
708 if ((srank
== 7 && n
> 2) || (srank
< 7 && n
> 1))
712 if (n
> 1 && abs (sfile
- file
) != 0)
715 p
= b
[RANKTOBOARD (srank
)][FILETOBOARD (sfile
)].icon
;
717 if (!val_piece_side (g
->turn
, p
) || pgn_piece_to_int (p
) != PAWN
)
720 if (srank
== rank
|| (g
->turn
== WHITE
&& rank
< srank
) ||
721 (g
->turn
== BLACK
&& rank
> srank
))
726 p
= b
[RANKTOBOARD (rank
)][FILETOBOARD (file
)].icon
;
728 if (pgn_piece_to_int (p
) != OPEN_SQUARE
)
731 p
= (g
->turn
== WHITE
) ? rank
- 1 : rank
+ 1;
732 p
= b
[RANKTOBOARD (p
)][FILETOBOARD (file
)].icon
;
734 if (n
> 1 && pgn_piece_to_int (p
) != OPEN_SQUARE
)
740 p
= b
[RANKTOBOARD (rank
)][FILETOBOARD (file
)].icon
;
742 if (pgn_piece_to_int (p
) != OPEN_SQUARE
)
744 if (val_piece_side (g
->turn
, p
))
751 p
= (g
->turn
== WHITE
) ? rank
- 1 : rank
+ 1;
752 p
= b
[RANKTOBOARD (p
)][FILETOBOARD (file
)].icon
;
754 if (pgn_piece_to_int (p
) == OPEN_SQUARE
|| val_piece_side (g
->turn
, p
))
757 /* Previous move was not 2 squares and a pawn. */
758 if (!TEST_FLAG (g
->flags
, GF_ENPASSANT
) ||
759 b
[RANKTOBOARD (rank
)][FILETOBOARD (file
)].enpassant
== 0)
763 // GF_ENPASSANT should take care of this
764 if (!b[RANKTOBOARD(rank)][FILETOBOARD(file)].enpassant)
768 if ((g
->turn
== WHITE
&& rank
!= 6) || (g
->turn
== BLACK
&& rank
!= 3))
772 // GF_ENPASSANT should take care of this
773 n
= (g
->turn
== WHITE
) ? rank
- 1 : rank
+ 1;
774 p
= b
[RANKTOBOARD (n
)][FILETOBOARD (file
)].icon
;
776 if (pgn_piece_to_int (p
) != PAWN
)
779 if (val_piece_side (g
->turn
, p
))
787 check_self_test (GAME g
, BOARD b
, int p
, int sfile
, int srank
,
792 int oldv
= g
->validate
;
793 int nkfile
, nkrank
, nokfile
, nokrank
;
798 g
->check_testing
= 1;
799 memcpy (tmpb
, b
, sizeof (BOARD
));
800 memcpy (&newg
, g
, sizeof (struct game_s
));
802 if (finalize_move (&newg
, tmpb
, 0, sfile
, srank
, file
, rank
) != E_PGN_OK
)
804 g
->check_testing
= 0;
810 find_king_squares (&newg
, tmpb
, &nkfile
, &nkrank
, &nokfile
, &nokrank
);
812 nkfile
= g
->kfile
, nkrank
= g
->krank
;
814 go
= TEST_FLAG (newg
.flags
, GF_GAMEOVER
);
815 memcpy (&newg
, g
, sizeof (struct game_s
));
817 if (check_self (&newg
, tmpb
, nkfile
, nkrank
) == CHECK_SELF
)
819 g
->check_testing
= 0;
824 if (!g
->validate
&& !g
->validate_find
)
826 g
->flags
= newg
.flags
;
829 SET_FLAG (g
->flags
, GF_GAMEOVER
);
833 g
->check_testing
= 0;
838 find_source_square (GAME g
, BOARD b
, int piece
, int *sfile
,
839 int *srank
, int file
, int rank
)
847 PGN_DUMP ("%s:%d: finding source square: piece=%c source=%c%c dest=%c%c\n",
848 __FILE__
, __LINE__
, pgn_int_to_piece (g
->turn
, piece
),
849 (*sfile
) ? INTTOFILE (*sfile
) : '0',
850 (*srank
) ? INTTORANK (*srank
) : '0', INTTOFILE (file
),
856 if (!*srank
&& *sfile
== file
)
858 /* Find the first pawn in 'file'. */
859 i
= (g
->turn
== WHITE
) ? -1 : 1;
861 for (r
= rank
+ i
, dist
= 0; VALIDFILE (r
); r
+= i
, dist
++)
864 pgn_piece_to_int (b
[RANKTOBOARD (r
)][FILETOBOARD (file
)].
867 if (p
!= OPEN_SQUARE
)
875 *srank
= (g
->turn
== WHITE
) ? rank
- 1 : rank
+ 1;
877 if (!validate_pawn (g
, b
, *sfile
, *srank
, file
, rank
))
882 if (!g
->check_testing
)
884 if (check_self_test (g
, b
, piece
, *sfile
, *srank
, file
, rank
) == 0)
890 if (*sfile
&& *srank
)
892 count
= find_ambiguous (g
, b
, piece
, *sfile
, *srank
, file
, rank
);
897 if (!g
->check_testing
)
899 if (check_self_test (g
, b
, piece
, *sfile
, *srank
, file
, rank
)
906 int ff
= *sfile
, rr
= *srank
;
908 for (r
= 1; VALIDRANK (r
); r
++)
910 for (f
= 1; VALIDFILE (f
); f
++)
914 if ((*sfile
&& f
!= ff
) || (*srank
&& r
!= rr
))
917 n
= find_ambiguous (g
, b
, piece
, f
, r
, file
, rank
);
921 if (!g
->check_testing
&&
922 check_self_test (g
, b
, piece
, f
, r
, file
,
940 if (validate_piece (g
, b
, piece
, *sfile
, *srank
, file
, rank
) !=
949 finalize_move (GAME g
, BOARD b
, int promo
, int sfile
, int srank
,
955 if (!g
->validate
&& !g
->check_testing
)
956 PGN_DUMP ("%s:%d: BEGIN finalizing\n", __FILE__
, __LINE__
);
959 p
= b
[RANKTOBOARD (rank
)][FILETOBOARD (file
)].icon
;
960 pi
= pgn_piece_to_int (p
);
962 if (pi
!= OPEN_SQUARE
&& val_piece_side (g
->turn
, p
))
963 return E_PGN_INVALID
;
968 if (!g
->check_testing
)
969 PGN_DUMP ("%s:%d: updating board and game flags\n", __FILE__
,
972 pgn_reset_enpassant (b
);
973 p
= b
[RANKTOBOARD (srank
)][FILETOBOARD (sfile
)].icon
;
974 pi
= pgn_piece_to_int (p
);
978 p
= b
[RANKTOBOARD (rank
)][FILETOBOARD (file
)].icon
;
980 if (sfile
!= file
&& pgn_piece_to_int (p
) == OPEN_SQUARE
&&
981 TEST_FLAG (g
->flags
, GF_ENPASSANT
))
983 p
= (g
->turn
== WHITE
) ? rank
- 1 : rank
+ 1;
984 b
[RANKTOBOARD (p
)][FILETOBOARD (file
)].icon
=
985 pgn_int_to_piece (g
->turn
, OPEN_SQUARE
);
988 if (abs (srank
- rank
) > 1)
990 SET_FLAG (g
->flags
, GF_ENPASSANT
);
991 b
[RANKTOBOARD (((g
->turn
== WHITE
) ? rank
- 1 : rank
+ 1))]
992 [FILETOBOARD (file
)].enpassant
= 1;
997 if (g
->turn
== WHITE
)
999 if (sfile
== FILETOINT ('h') && srank
== 1)
1000 CLEAR_FLAG (g
->flags
, GF_WK_CASTLE
);
1001 else if (sfile
== FILETOINT ('a') && srank
== 1)
1002 CLEAR_FLAG (g
->flags
, GF_WQ_CASTLE
);
1006 if (sfile
== FILETOINT ('h') && srank
== 8)
1007 CLEAR_FLAG (g
->flags
, GF_BK_CASTLE
);
1008 else if (sfile
== FILETOINT ('a') && srank
== 8)
1009 CLEAR_FLAG (g
->flags
, GF_BQ_CASTLE
);
1014 CLEAR_FLAG (g
->flags
, GF_ENPASSANT
);
1016 if (pi
== KING
&& !g
->castle
)
1018 if (g
->turn
== WHITE
)
1019 CLEAR_FLAG (g
->flags
, GF_WK_CASTLE
| GF_WQ_CASTLE
);
1021 CLEAR_FLAG (g
->flags
, GF_BK_CASTLE
| GF_BQ_CASTLE
);
1026 p
= b
[RANKTOBOARD (srank
)][FILETOBOARD (sfile
)].icon
;
1027 b
[RANKTOBOARD (srank
)][FILETOBOARD (sfile
)].icon
=
1028 pgn_int_to_piece (g
->turn
, OPEN_SQUARE
);
1029 b
[RANKTOBOARD (srank
)][FILETOBOARD ((file
>
1030 FILETOINT ('e') ? 8 : 1))].
1031 icon
= pgn_int_to_piece (g
->turn
, OPEN_SQUARE
);
1032 b
[RANKTOBOARD (rank
)][FILETOBOARD (file
)].icon
= p
;
1034 if (file
> FILETOINT ('e'))
1035 b
[RANKTOBOARD (rank
)][FILETOBOARD ((file
- 1))].icon
=
1036 pgn_int_to_piece (g
->turn
, ROOK
);
1038 b
[RANKTOBOARD (rank
)][FILETOBOARD ((file
+ 1))].icon
=
1039 pgn_int_to_piece (g
->turn
, ROOK
);
1041 if (g
->turn
== WHITE
)
1042 CLEAR_FLAG (g
->flags
, (file
> FILETOINT ('e')) ? GF_WK_CASTLE
:
1045 CLEAR_FLAG (g
->flags
, (file
> FILETOINT ('e')) ? GF_BK_CASTLE
:
1051 p
= pgn_int_to_piece (g
->turn
, promo
);
1053 p
= b
[RANKTOBOARD (srank
)][FILETOBOARD (sfile
)].icon
;
1055 b
[RANKTOBOARD (srank
)][FILETOBOARD (sfile
)].icon
=
1056 pgn_int_to_piece (g
->turn
, OPEN_SQUARE
);
1057 b
[RANKTOBOARD (rank
)][FILETOBOARD (file
)].icon
= p
;
1063 if (!g
->check_testing
)
1065 if (pgn_piece_to_int (p
) == KING
)
1066 find_king_squares (g
, b
, &g
->kfile
, &g
->krank
, &g
->okfile
,
1069 switch (check_test (g
, b
))
1075 g
->check
= CHECK_MATE
;
1079 pgn_tag_add (&g
->tag
, (char *) "Result",
1081 WHITE
) ? (char *) "1-0" : (char *) "0-1");
1082 SET_FLAG (g
->flags
, GF_GAMEOVER
);
1090 if (!g
->validate
&& (g
->pgn_fen_tag
> 0 && !g
->done_fen_tag
) &&
1091 !pgn_history_total (g
->hp
) && srank
>= 7)
1092 SET_FLAG (g
->flags
, GF_BLACK_OPENING
);
1096 if (!g
->validate
&& !g
->check_testing
)
1097 PGN_DUMP ("%s:%d: END finalizing\n", __FILE__
, __LINE__
);
1102 p
= pgn_piece_to_int (p
);
1104 if (p
== PAWN
|| promo
|| g
->capture
)
1111 if (g
->tag
[6]->value
[0] == '*')
1113 pgn_tag_add (&g
->tag
, (char *) "Result", (char *) "1/2-1/2");
1114 SET_FLAG (g
->flags
, GF_GAMEOVER
);
1123 black_opening (GAME g
, BOARD b
, int rank
)
1125 if (!g
->ravlevel
&& !g
->hindex
&& pgn_tag_find (g
->tag
, "FEN") == -1)
1130 find_king_squares (g
, b
, &g
->kfile
, &g
->krank
, &g
->okfile
,
1134 SET_FLAG (g
->flags
, GF_BLACK_OPENING
);
1141 CLEAR_FLAG (g
->flags
, GF_BLACK_OPENING
);
1146 if (TEST_FLAG (g
->flags
, GF_BLACK_OPENING
))
1147 PGN_DUMP ("%s:%d: black opening\n", __FILE__
, __LINE__
);
1152 format_santofrfr (int promo
, int sfile
, int srank
, int file
, int rank
)
1154 char *frfr
= malloc (6);
1156 memset (frfr
, 0, 6);
1157 snprintf (frfr
, 6, "%c%c%c%c", INTTOFILE (sfile
),
1158 INTTORANK (srank
), INTTOFILE (file
), INTTORANK (rank
));
1161 frfr
[4] = pgn_int_to_piece (BLACK
, promo
);
1167 * Converts a2a3 formatted moves to SAN format. The promotion piece should be
1171 frfrtosan (GAME g
, BOARD b
, char **m
, char **dst
)
1173 char buf
[MAX_SAN_MOVE_LEN
+ 1] = { 0 }, *bp
= buf
;
1174 int icon
, p
, dp
, promo
= 0;
1175 int sfile
, srank
, file
, rank
;
1181 PGN_DUMP ("%s:%d: converting to SAN format\n", __FILE__
, __LINE__
);
1185 sfile
= FILETOINT (bp
[0]);
1186 srank
= RANKTOINT (bp
[1]);
1187 file
= FILETOINT (bp
[2]);
1188 rank
= RANKTOINT (bp
[3]);
1190 black_opening (g
, b
, rank
);
1194 if ((promo
= pgn_piece_to_int (bp
[4])) == -1 || promo
== OPEN_SQUARE
)
1197 PGN_DUMP ("%s:%d: promotion to %c\n", __FILE__
, __LINE__
,
1198 pgn_int_to_piece (g
->turn
, promo
));
1202 icon
= b
[RANKTOBOARD (srank
)][FILETOBOARD (sfile
)].icon
;
1204 if ((p
= pgn_piece_to_int (icon
)) == -1 || p
== OPEN_SQUARE
)
1207 if (p
!= PAWN
&& promo
)
1208 return E_PGN_INVALID
;
1212 if (find_source_square (g
, b
, p
, &sfile
, &srank
, file
, rank
) != 1)
1213 return E_PGN_INVALID
;
1218 if (validate_piece (g
, b
, p
, sfile
, srank
, file
, rank
) != E_PGN_OK
)
1219 return E_PGN_INVALID
;
1221 if (p
== KING
&& abs (sfile
- file
) > 1)
1223 strcpy (buf
, (file
> FILETOINT ('e')) ? "O-O" : "O-O-O");
1225 if (finalize_move (g
, b
, promo
, sfile
, srank
, file
, rank
) != E_PGN_OK
)
1226 return E_PGN_INVALID
;
1229 strcat (buf
, (g
->check
== CHECK
) ? "+" : "#");
1231 /* The move buffer size may be shorter than frfr format. Since
1232 * pgn_parse() uses a buffer allocated on the stack, this is not
1233 * needed because its' size is always MAX_SAN_MOVE_LEN+1.
1235 if (!parsing_file
&& strlen (*m
) < strlen (buf
))
1238 *m
= malloc (strlen (buf
) + 1);
1244 *dst
= format_santofrfr (promo
, sfile
, srank
, file
, rank
);
1249 *bp
++ = toupper (icon
);
1251 n
= find_source_square (g
, b
, p
, &fc
, &rc
, file
, rank
);
1254 return E_PGN_INVALID
;
1257 fc
= find_source_square (g
, b
, p
, &sfile
, &rr
, file
, rank
);
1258 rc
= find_source_square (g
, b
, p
, &ff
, &srank
, file
, rank
);
1261 *bp
++ = INTTOFILE (sfile
);
1263 *bp
++ = INTTORANK (srank
);
1267 *bp
++ = INTTORANK (srank
);
1270 *bp
++ = INTTOFILE (sfile
);
1271 *bp
++ = INTTORANK (srank
);
1275 return E_PGN_PARSE
; // not reached.
1279 icon
= b
[RANKTOBOARD (rank
)][FILETOBOARD (file
)].icon
;
1281 if ((dp
= pgn_piece_to_int (icon
)) == -1)
1287 if (dp
!= OPEN_SQUARE
|| (dp
== OPEN_SQUARE
&& p
== PAWN
&& sfile
!= file
))
1290 *bp
++ = INTTOFILE (sfile
);
1299 *bp
++ = INTTOFILE (file
);
1300 *bp
++ = INTTORANK (rank
);
1302 if (p
== PAWN
&& !promo
&& (rank
== 8 || rank
== 1))
1303 promo
= pgn_piece_to_int ('q');
1310 if (p
!= PAWN
|| (g
->turn
== WHITE
&& (srank
!= 7 || rank
!= 8)) ||
1311 (g
->turn
== BLACK
&& (srank
!= 2 || rank
!= 1)))
1312 return E_PGN_INVALID
;
1315 *bp
++ = pgn_int_to_piece (WHITE
, promo
);
1320 if (find_source_square (g
, b
, p
, &sfile
, &srank
, file
, rank
) != 1)
1321 return E_PGN_INVALID
;
1323 if (finalize_move (g
, b
, promo
, sfile
, srank
, file
, rank
) != E_PGN_OK
)
1324 return E_PGN_INVALID
;
1327 *bp
++ = (g
->check
== CHECK
) ? '+' : '#';
1331 /* The move buffer size may be shorter than frfr format. Since
1332 * pgn_parse() uses a buffer allocated on the stack, this is not
1333 * needed because its' size is always MAX_SAN_MOVE_LEN+1.
1335 if (strlen (*m
) < strlen (buf
))
1338 *m
= malloc (strlen (buf
) + 1);
1345 *dst
= format_santofrfr (promo
, sfile
, srank
, file
, rank
);
1347 PGN_DUMP ("%s:%d: END validating %s\n", __FILE__
, __LINE__
, *m
);
1353 do_santofrfr (GAME g
, BOARD b
, char **san
, int *promo
, int *sfile
,
1354 int *srank
, int *file
, int *rank
)
1367 p
= (m
) + strlen (m
);
1369 while (!isdigit (*--p
) && *p
!= 'O')
1373 *promo
= pgn_piece_to_int (i
);
1382 /* Alternate promotion text (e8Q). Convert to SAN. */
1383 if (i
&& pgn_piece_to_int (i
) != E_PGN_ERR
)
1385 p
= (m
) + strlen (m
);
1394 /* Skip 'P' (pawn). */
1395 if (pgn_piece_to_int (*p
) == PAWN
)
1401 for (i
= 0; *p
; i
++)
1406 *file
= FILETOINT (*p
++);
1408 *file
= *sfile
= FILETOINT (*p
++);
1410 else if (VALIDROW (*p
))
1412 *rank
= RANKTOINT (*p
++);
1416 *file
= FILETOINT (*++p
);
1417 *rank
= RANKTOINT (*++p
);
1422 if (*promo
== -1 || *promo
== KING
|| *promo
== PAWN
)
1426 *p
++ = toupper (pgn_int_to_piece (g
->turn
, *promo
));
1433 PGN_DUMP ("Pawn (move: '%s'): %c\n", m
, *p
++);
1440 black_opening (g
, b
, *rank
);
1442 if (find_source_square (g
, b
, PAWN
, sfile
, srank
, *file
, *rank
) != 1)
1443 return E_PGN_INVALID
;
1445 if (!*promo
&& (*rank
== 8 || *rank
== 1))
1447 *promo
= pgn_piece_to_int ('q');
1449 *p
++ = pgn_int_to_piece (WHITE
, *promo
);
1461 * The first character is the piece but only if not a pawn.
1463 if ((piece
= pgn_piece_to_int (*p
++)) == -1)
1475 *srank
= RANKTOINT (*p
++);
1479 else if (VALIDCOL (*p
))
1481 *sfile
= FILETOINT (*p
++);
1487 *srank
= RANKTOINT (*p
++);
1503 * The destination square.
1505 *file
= FILETOINT (*p
++);
1506 *rank
= RANKTOINT (*p
++);
1511 black_opening (g
, b
, *rank
);
1513 if ((i
= find_source_square (g
, b
, piece
, sfile
, srank
, *file
, *rank
))
1515 return (i
== 0) ? E_PGN_INVALID
: E_PGN_AMBIGUOUS
;
1519 * The move is a valid one. Find the source file and rank so we
1520 * can later update the board positions.
1522 if (find_source_square (*g
, b
, piece
, sfile
, srank
, *file
, *rank
)
1524 return E_PGN_INVALID
;
1534 * Valididate move 'mp' against the game state 'g' and game board 'b' and
1535 * update board 'b'. 'mp' is updated to SAN format for moves which aren't
1536 * (frfr or e8Q for example). Returns E_PGN_PARSE if there was a move text
1537 * parsing error, E_PGN_INVALID if the move is invalid or E_PGN_OK if
1541 pgn_parse_move (GAME g
, BOARD b
, char **mp
, char **dst
)
1543 int srank
= 0, sfile
= 0, rank
, file
;
1547 size_t len
= m
? strlen (m
) : 0;
1550 * This may be an empty move with only an annotation. Kinda strange.
1556 PGN_DUMP ("%s:%d: BEGIN validating '%s' (%s)...\n", __FILE__
, __LINE__
, m
,
1557 (g
->turn
== WHITE
) ? "white" : "black");
1560 g
->check_testing
= g
->castle
= 0;
1561 srank
= rank
= file
= sfile
= promo
= 0;
1562 find_king_squares (g
, b
, &g
->kfile
, &g
->krank
, &g
->okfile
, &g
->okrank
);
1564 if (m
[len
- 1] == '+')
1567 if (VALIDCOL (*m
) && VALIDROW (*(m
+ 1)) && VALIDCOL (*(m
+ 2))
1568 && VALIDROW (*(m
+ 3)))
1569 return frfrtosan (g
, b
, mp
, dst
);
1572 if (strcmp (m
, "O-O") == 0)
1574 else if (strcmp (m
, "O-O-O") == 0)
1579 if (parse_castle_move (g
, b
, i
, &sfile
, &srank
, &file
, &rank
) !=
1581 return E_PGN_INVALID
;
1583 /* The move buffer size may be shorter than frfr format. Since
1584 * pgn_parse() uses a buffer allocated on the stack, this is not
1585 * needed because its' size is always MAX_SAN_MOVE_LEN+1.
1587 if (len
< 4 && !parsing_file
)
1589 m
= malloc (5 * sizeof (char));
1595 *m
++ = INTTOFILE (sfile
);
1596 *m
++ = INTTORANK (srank
);
1597 *m
++ = INTTOFILE (file
);
1598 *m
++ = INTTORANK (rank
);
1600 return frfrtosan (g
, b
, mp
, dst
);
1603 if ((i
= do_santofrfr (g
, b
, &m
, &promo
, &sfile
, &srank
, &file
, &rank
))
1609 if (finalize_move (g
, b
, promo
, sfile
, srank
, file
, rank
) != E_PGN_OK
)
1610 return E_PGN_INVALID
;
1613 *p
++ = (g
->check
== CHECK
) ? '+' : '#';
1616 *dst
= format_santofrfr (promo
, sfile
, srank
, file
, rank
);
1619 PGN_DUMP ("%s:%d: END validating %s\n", __FILE__
, __LINE__
, m
);
1625 * Like pgn_parse_move() but don't modify game flags in 'g' or board 'b'.
1628 pgn_validate_move (GAME g
, BOARD b
, char **m
, char **dst
)
1631 int side
= g
->side
, turn
= g
->turn
;
1632 unsigned short flags
= g
->flags
;
1635 PGN_DUMP ("%s:%d: BEGIN validate only\n", __FILE__
, __LINE__
);
1638 ret
= pgn_parse_move (g
, b
, m
, dst
);
1641 PGN_DUMP ("%s:%d: END validate only\n", __FILE__
, __LINE__
);
1650 * Sets valid moves from game 'g' using board 'b'. The valid moves are for the
1651 * piece on the board 'b' at 'rank' and 'file'. Returns nothing.
1654 pgn_find_valid_moves (GAME g
, BOARD b
, int file
, int rank
)
1656 int p
= pgn_piece_to_int (b
[RANKTOBOARD (rank
)][FILETOBOARD (file
)].icon
);
1660 PGN_DUMP ("%s:%d: BEGIN valid destination squares for %c%c\n", __FILE__
,
1661 __LINE__
, INTTOFILE (file
), INTTORANK (rank
));
1664 g
->validate_find
= 1;
1665 find_king_squares (g
, b
, &g
->kfile
, &g
->krank
, &g
->okfile
, &g
->okrank
);
1667 for (r
= 1; VALIDRANK (r
); r
++)
1669 for (f
= 1; VALIDFILE (f
); f
++)
1672 (g
->turn
, b
[RANKTOBOARD (r
)][FILETOBOARD (f
)].icon
))
1675 if (find_source_square (g
, b
, p
, &file
, &rank
, f
, r
) != 0)
1677 b
[RANKTOBOARD (r
)][FILETOBOARD (f
)].valid
= 1;
1679 PGN_DUMP ("%s:%d: %c%c is valid\n", __FILE__
, __LINE__
,
1680 INTTOFILE (f
), INTTORANK (r
));
1687 PGN_DUMP ("%s:%d: END valid destination squares for %c%c\n", __FILE__
,
1688 __LINE__
, INTTOFILE (file
), INTTORANK (rank
));
1690 g
->check_testing
= 0;
1691 g
->validate_find
= 0;