1 /* vim:tw=78:ts=8:sw=4:set ft=c: */
3 Copyright (C) 2002-2015 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
42 KINGSIDE
= 1, QUEENSIDE
45 static int finalize_move(GAME g
, BOARD b
, int promo
, int sfile
, int srank
,
47 static int find_source_square(GAME
, BOARD
, int, int *, int *, int, int);
48 static int check_self(GAME g
, BOARD b
, int file
, int rank
);
49 static int validate_pawn(GAME g
, BOARD b
, int sfile
, int srank
, int file
,
51 static int find_source_square(GAME
, BOARD
, int, int *, int *, int, int);
53 static int val_piece_side(char turn
, int c
)
55 if ((isupper(c
) && turn
== WHITE
) ||
56 (islower(c
) && turn
== BLACK
))
62 static int count_piece(GAME g
, BOARD b
, int piece
, int sfile
,
63 int srank
, int file
, int rank
,
68 if (!VALIDRANK(rank
) || !VALIDFILE(file
))
71 p
= b
[RANKTOBOARD(rank
)][FILETOBOARD(file
)].icon
;
72 pi
= pgn_piece_to_int(p
);
74 if (pi
!= OPEN_SQUARE
) {
75 if (pi
== piece
&& val_piece_side(g
->turn
, p
)) {
76 if (sfile
&& file
== sfile
) {
77 if (!srank
|| (srank
&& rank
== srank
))
80 else if (srank
&& rank
== srank
) {
84 else if (!sfile
&& !srank
)
95 * Get the source row and column for a given piece.
97 * The following two functions find 'piece' from the given square 'col' and
98 * 'row' and store the resulting column or row in 'c' and 'r'. The return
99 * value is the number of 'piece' found (on the current g->side) or zero.
100 * Search for 'piece' stops when a non-empty square is found.
102 static int count_by_diag(GAME g
, BOARD b
, int piece
,
103 int sfile
, int srank
, int file
,
107 int ul
= 0, ur
= 0, dl
= 0, dr
= 0;
111 for (i
= 1; VALIDFILE(i
); i
++) {
115 if (!ul
&& VALIDRANK(r
) && VALIDFILE(f
))
116 ul
= count_piece(g
, b
, piece
, sfile
, srank
, f
, r
, &count
);
121 if (!ur
&& VALIDRANK(r
) && VALIDFILE(f
))
122 ur
= count_piece(g
, b
, piece
, sfile
, srank
, f
, r
, &count
);
127 if (!dl
&& VALIDRANK(r
) && VALIDFILE(f
))
128 dl
= count_piece(g
, b
, piece
, sfile
, srank
, f
, r
, &count
);
133 if (!dr
&& VALIDRANK(r
) && VALIDFILE(f
))
134 dr
= count_piece(g
, b
, piece
, sfile
, srank
, f
, r
, &count
);
140 static int count_knight(GAME g
, BOARD b
, int piece
, int sfile
, int srank
,
145 count_piece(g
, b
, piece
, sfile
, srank
, file
- 1, rank
+ 2, &count
);
146 count_piece(g
, b
, piece
, sfile
, srank
, file
+ 1, rank
+ 2, &count
);
147 count_piece(g
, b
, piece
, sfile
, srank
, file
+ 2, rank
+ 1, &count
);
148 count_piece(g
, b
, piece
, sfile
, srank
, file
- 2, rank
+ 1, &count
);
149 count_piece(g
, b
, piece
, sfile
, srank
, file
+ 1, rank
- 2, &count
);
150 count_piece(g
, b
, piece
, sfile
, srank
, file
- 1, rank
- 2, &count
);
151 count_piece(g
, b
, piece
, sfile
, srank
, file
+ 2, rank
- 1, &count
);
152 count_piece(g
, b
, piece
, sfile
, srank
, file
- 2, rank
- 1, &count
);
156 static int count_by_rank(GAME g
, BOARD b
, int piece
,
157 int sfile
, int srank
, int file
,
164 for (i
= 1; VALIDRANK(i
); i
++) {
165 if (!u
&& VALIDRANK((rank
+ i
)))
166 u
= count_piece(g
, b
, piece
, sfile
, srank
, file
, rank
+ i
, &count
);
168 if (!d
&& VALIDRANK((rank
- i
)))
169 d
= count_piece(g
, b
, piece
, sfile
, srank
, file
, rank
- i
, &count
);
178 static int count_by_file(GAME g
, BOARD b
, int piece
,
179 int sfile
, int srank
, int file
,
186 for (i
= 1; VALIDFILE(i
); i
++) {
187 if (!r
&& VALIDFILE((file
+ i
)))
188 r
= count_piece(g
, b
, piece
, sfile
, srank
, file
+ i
, rank
, &count
);
190 if (!l
&& VALIDFILE((file
- i
)))
191 l
= count_piece(g
, b
, piece
, sfile
, srank
, file
- i
, rank
, &count
);
200 static int count_by_rank_file(GAME g
, BOARD b
, int piece
,
201 int sfile
, int srank
, int file
,
206 count
= count_by_rank(g
, b
, piece
, sfile
, srank
, file
, rank
);
207 return count
+ count_by_file(g
, b
, piece
, sfile
, srank
, file
, rank
);
210 static int opponent_can_attack(GAME g
, BOARD b
, int file
,
215 int kf
= g
->kfile
, kr
= g
->krank
;
218 g
->kfile
= g
->okfile
, g
->krank
= g
->okrank
;
220 for (r
= 1; VALIDRANK(r
); r
++) {
221 for (f
= 1; VALIDFILE(f
); f
++) {
222 p
= b
[RANKTOBOARD(r
)][FILETOBOARD(f
)].icon
;
223 pi
= pgn_piece_to_int(p
);
225 if (pi
== OPEN_SQUARE
|| !val_piece_side(g
->turn
, p
))
228 if (find_source_square(g
, b
, pi
, &f
, &r
, file
, rank
) != 0) {
229 g
->kfile
= kf
, g
->krank
= kr
;
236 g
->kfile
= kf
, g
->krank
= kr
;
241 static int validate_castle_move(GAME g
, BOARD b
, int side
, int sfile
,
242 int srank
, int file
, int rank
)
246 if (side
== KINGSIDE
) {
247 if ((g
->turn
== WHITE
&& !TEST_FLAG(g
->flags
, GF_WK_CASTLE
)) ||
248 (g
->turn
== BLACK
&& !TEST_FLAG(g
->flags
, GF_BK_CASTLE
)))
249 return E_PGN_INVALID
;
252 if ((g
->turn
== WHITE
&& !TEST_FLAG(g
->flags
, GF_WQ_CASTLE
)) ||
253 (g
->turn
== BLACK
&& !TEST_FLAG(g
->flags
, GF_BQ_CASTLE
)))
254 return E_PGN_INVALID
;
257 if (file
> FILETOINT('e')) {
258 if (b
[RANKTOBOARD(srank
)][FILETOBOARD((sfile
+ 1))].icon
259 != pgn_int_to_piece(g
->turn
, OPEN_SQUARE
) ||
260 b
[RANKTOBOARD(srank
)][FILETOBOARD((sfile
+ 2))].icon
261 != pgn_int_to_piece(g
->turn
, OPEN_SQUARE
))
262 return E_PGN_INVALID
;
265 if (pgn_config
.strict_castling
> 0) {
266 if (opponent_can_attack(g
, b
, sfile
+ 1, srank
))
267 return E_PGN_INVALID
;
269 if (opponent_can_attack(g
, b
, sfile
+ 2, srank
))
270 return E_PGN_INVALID
;
274 if (b
[RANKTOBOARD(srank
)][FILETOBOARD((sfile
- 1))].icon
!=
275 pgn_int_to_piece(g
->turn
, OPEN_SQUARE
) ||
276 b
[RANKTOBOARD(srank
)][FILETOBOARD((sfile
- 2))].icon
!=
277 pgn_int_to_piece(g
->turn
, OPEN_SQUARE
) ||
278 b
[RANKTOBOARD(srank
)][FILETOBOARD((sfile
- 3))].icon
!=
279 pgn_int_to_piece(g
->turn
, OPEN_SQUARE
))
280 return E_PGN_INVALID
;
282 if (pgn_config
.strict_castling
> 0) {
283 if (opponent_can_attack(g
, b
, sfile
- 1, srank
))
284 return E_PGN_INVALID
;
286 if (opponent_can_attack(g
, b
, sfile
- 2, srank
))
287 return E_PGN_INVALID
;
289 if (opponent_can_attack(g
, b
, sfile
- 3, srank
))
290 return E_PGN_INVALID
;
294 n
= g
->check_testing
;
295 g
->check_testing
= 1;
297 if (check_self(g
, b
, g
->kfile
, g
->krank
) == CHECK_SELF
) {
298 g
->check_testing
= n
;
299 return E_PGN_INVALID
;
302 g
->check_testing
= n
;
307 static int validate_piece(GAME g
, BOARD b
, int p
, int sfile
,
308 int srank
, int file
, int rank
)
316 /* Find the first pawn in the current column. */
317 i
= (g
->turn
== WHITE
) ? -1 : 1;
320 for (r
= rank
+ i
, dist
= 0; VALIDFILE(r
); r
+= i
, dist
++) {
321 p
= b
[RANKTOBOARD(r
)][FILETOBOARD(file
)].icon
;
323 if (pgn_piece_to_int(p
) != OPEN_SQUARE
)
327 if (pgn_piece_to_int(p
) != PAWN
|| !val_piece_side(g
->turn
, p
) || dist
> 2)
328 return E_PGN_INVALID
;
333 dist
= abs(srank
- rank
);
334 p
= b
[RANKTOBOARD(srank
)][FILETOBOARD(sfile
)].icon
;
336 if (pgn_piece_to_int(p
) != PAWN
|| !val_piece_side(g
->turn
, p
) || dist
> 2)
337 return E_PGN_INVALID
;
340 if (g
->turn
== WHITE
) {
341 if ((srank
== 2 && dist
> 2) || (srank
> 2 && dist
> 1))
342 return E_PGN_INVALID
;
345 if ((srank
== 7 && dist
> 2) || (srank
< 7 && dist
> 1))
346 return E_PGN_INVALID
;
349 p
= b
[RANKTOBOARD(rank
)][FILETOBOARD(file
)].icon
;
351 if (pgn_piece_to_int(p
) != OPEN_SQUARE
)
352 return E_PGN_INVALID
;
354 else if (sfile
!= file
) {
355 if (abs(sfile
- file
) != 1)
356 return E_PGN_INVALID
;
358 srank
= (g
->turn
== WHITE
) ? rank
- 1 : rank
+ 1;
359 p
= b
[RANKTOBOARD(srank
)][FILETOBOARD(sfile
)].icon
;
361 if (!val_piece_side(g
->turn
, p
))
362 return E_PGN_INVALID
;
364 if (pgn_piece_to_int(p
) != PAWN
|| abs(srank
- rank
) != 1)
365 return E_PGN_INVALID
;
367 p
= b
[RANKTOBOARD(rank
)][FILETOBOARD(file
)].icon
;
370 if (pgn_piece_to_int(p
) == OPEN_SQUARE
) {
371 /* Previous move was not 2 squares and a pawn. */
372 if (!TEST_FLAG(g
->flags
, GF_ENPASSANT
))
373 return E_PGN_INVALID
;
375 if (!b
[RANKTOBOARD(rank
)][FILETOBOARD(file
)].enpassant
)
376 return E_PGN_INVALID
;
378 r
= (g
->turn
== WHITE
) ? 6 : 3;
381 return E_PGN_INVALID
;
383 r
= (g
->turn
== WHITE
) ? rank
- 1 : rank
+ 1;
384 p
= b
[RANKTOBOARD(r
)][FILETOBOARD(file
)].icon
;
386 if (pgn_piece_to_int(p
) != PAWN
)
387 return E_PGN_INVALID
;
390 if (val_piece_side(g
->turn
, p
))
391 return E_PGN_INVALID
;
394 p
= b
[RANKTOBOARD(srank
)][FILETOBOARD(sfile
)].icon
;
396 if (!val_piece_side(g
->turn
, p
))
397 return E_PGN_INVALID
;
401 f
= abs(sfile
- file
);
402 r
= abs(srank
- rank
);
405 return E_PGN_INVALID
;
408 if (sfile
!= FILETOINT('e'))
409 return E_PGN_INVALID
;
411 if (validate_castle_move(g
, b
, (file
> FILETOINT('e')) ?
412 KINGSIDE
: QUEENSIDE
, sfile
, srank
, file
, rank
)
414 return E_PGN_INVALID
;
426 * Returns the number of pieces of type 'p' that can move to the destination
427 * square located at 'file' and 'rank'. The source square 'sfile' and 'srank'
428 * (if available in the move text) should be determined before calling this
429 * function or set to 0 if unknown. Returns 0 if the move is impossible for
432 static int find_ambiguous(GAME g
, BOARD b
, int p
, int sfile
,
433 int srank
, int file
, int rank
)
439 count
= validate_pawn(g
, b
, sfile
, srank
, file
, rank
);
442 count
= count_by_rank_file(g
, b
, p
, sfile
, srank
, file
, rank
);
445 count
= count_knight(g
, b
, p
, sfile
, srank
, file
, rank
);
448 count
= count_by_diag(g
, b
, p
, sfile
, srank
, file
, rank
);
451 count
= count_by_rank_file(g
, b
, p
, sfile
, srank
, file
, rank
);
452 count
+= count_by_diag(g
, b
, p
, sfile
, srank
, file
, rank
);
455 count
= count_by_rank_file(g
, b
, p
, sfile
, srank
, file
, rank
);
456 count
+= count_by_diag(g
, b
, p
, sfile
, srank
, file
, rank
);
465 static void find_king_squares(GAME g
, BOARD b
, int *file
,
466 int *rank
, int *ofile
, int *orank
)
470 for (r
= 1; VALIDRANK(r
); r
++) {
471 for (f
= 1; VALIDFILE(f
); f
++) {
472 int p
= b
[RANKTOBOARD(r
)][FILETOBOARD(f
)].icon
;
473 int pi
= pgn_piece_to_int(p
);
475 if (pi
== OPEN_SQUARE
|| pi
!= KING
)
478 if (val_piece_side(g
->turn
, p
))
479 *file
= f
, *rank
= r
;
481 *ofile
= f
, *orank
= r
;
486 PGN_DUMP("%s:%d: king location: %c%c %c%c(opponent)\n", __FILE__
,
487 __LINE__
, INTTOFILE(*file
), INTTORANK(*rank
),
488 INTTOFILE(*ofile
), INTTORANK(*orank
));
492 static int parse_castle_move(GAME g
, BOARD b
, int side
, int *sfile
,
493 int *srank
, int *file
, int *rank
)
495 *srank
= *rank
= (g
->turn
== WHITE
) ? 1 : 8;
496 *sfile
= FILETOINT('e');
498 if (side
== KINGSIDE
)
499 *file
= FILETOINT('g');
501 *file
= FILETOINT('c');
503 return validate_castle_move(g
, b
, side
, *sfile
, *srank
, *file
, *rank
);
507 * Almost exactly like pgn_find_valid_moves() but returns immediately after a
508 * valid move is found.
510 static int check_mate_thingy(GAME g
, BOARD b
, int file
, int rank
)
513 int p
= pgn_piece_to_int(b
[RANKTOBOARD(rank
)][FILETOBOARD(file
)].icon
);
515 for (r
= 1; VALIDRANK(r
); r
++) {
516 for (f
= 1; VALIDFILE(f
); f
++) {
517 if (val_piece_side(g
->turn
, b
[RANKTOBOARD(r
)][FILETOBOARD(f
)].icon
))
520 if (find_source_square(g
, b
, p
, &file
, &rank
, f
, r
) != 0)
528 static int checkmate_test(GAME g
, BOARD b
)
532 int kf
= g
->kfile
, kr
= g
->krank
, okf
= g
->okfile
, okr
= g
->okrank
;
535 PGN_DUMP("%s:%d: BEGIN checkmate test\n", __FILE__
, __LINE__
);
538 * The king squares need to be switched also for find_source_squares()
539 * which calls check_self().
542 g
->kfile
= g
->okfile
, g
->krank
= g
->okrank
;
544 for (r
= 1; VALIDRANK(r
); r
++) {
545 for (f
= 1; VALIDFILE(f
); f
++) {
547 int sfile
= f
, srank
= r
;
549 p
= b
[RANKTOBOARD(r
)][FILETOBOARD(f
)].icon
;
551 if (p
== OPEN_SQUARE
|| !val_piece_side(g
->turn
, p
))
554 if (check_mate_thingy(g
, b
, sfile
, srank
)) {
563 g
->kfile
= kf
, g
->krank
= kr
, g
->okfile
= okf
, g
->okrank
= okr
;
567 static int check_opponent(GAME g
, BOARD b
, int file
, int rank
)
573 PGN_DUMP("%s:%d: BEGIN opponent check test\n", __FILE__
, __LINE__
);
576 for (r
= 1; VALIDRANK(r
); r
++) {
577 for (f
= 1; VALIDFILE(f
); f
++) {
578 p
= b
[RANKTOBOARD(r
)][FILETOBOARD(f
)].icon
;
579 pi
= pgn_piece_to_int(p
);
581 if (pi
== OPEN_SQUARE
|| !val_piece_side(g
->turn
, p
))
584 if (find_source_square(g
, b
, pi
, &f
, &r
, file
, rank
) != 0)
592 static int check_self(GAME g
, BOARD b
, int file
, int rank
)
598 PGN_DUMP("%s:%d: BEGIN self check test\n", __FILE__
, __LINE__
);
602 for (r
= 1; VALIDRANK(r
); r
++) {
603 for (f
= 1; VALIDFILE(f
); f
++) {
604 p
= b
[RANKTOBOARD(r
)][FILETOBOARD(f
)].icon
;
605 pi
= pgn_piece_to_int(p
);
607 if (pi
== OPEN_SQUARE
|| !val_piece_side(g
->turn
, p
))
610 if (find_source_square(g
, b
, pi
, &f
, &r
, file
, rank
) != 0) {
621 static int check_test(GAME g
, BOARD b
)
624 PGN_DUMP("%s:%d: BEGIN check test\n", __FILE__
, __LINE__
);
626 g
->check
= check_opponent(g
, b
, g
->okfile
, g
->okrank
);
629 return checkmate_test(g
, b
);
631 g
->check_testing
= 0;
635 static int validate_pawn(GAME g
, BOARD b
, int sfile
,
636 int srank
, int file
, int rank
)
638 int n
= abs(srank
- rank
);
641 if (abs(sfile
- file
) > 1)
644 if (g
->turn
== WHITE
) {
645 if ((srank
== 2 && n
> 2) || (srank
> 2 && n
> 1))
649 if ((srank
== 7 && n
> 2) || (srank
< 7 && n
> 1))
653 if (n
> 1 && abs(sfile
- file
) != 0)
656 p
= b
[RANKTOBOARD(srank
)][FILETOBOARD(sfile
)].icon
;
658 if (!val_piece_side(g
->turn
, p
) || pgn_piece_to_int(p
) != PAWN
)
661 if (srank
== rank
|| (g
->turn
== WHITE
&& rank
< srank
) ||
662 (g
->turn
== BLACK
&& rank
> srank
))
666 p
= b
[RANKTOBOARD(rank
)][FILETOBOARD(file
)].icon
;
668 if (pgn_piece_to_int(p
) != OPEN_SQUARE
)
671 p
= (g
->turn
== WHITE
) ? rank
- 1 : rank
+ 1;
672 p
= b
[RANKTOBOARD(p
)][FILETOBOARD(file
)].icon
;
674 if (n
> 1 && pgn_piece_to_int(p
) != OPEN_SQUARE
)
680 p
= b
[RANKTOBOARD(rank
)][FILETOBOARD(file
)].icon
;
682 if (pgn_piece_to_int(p
) != OPEN_SQUARE
) {
683 if (val_piece_side(g
->turn
, p
))
690 p
= (g
->turn
== WHITE
) ? rank
- 1 : rank
+ 1;
691 p
= b
[RANKTOBOARD(p
)][FILETOBOARD(file
)].icon
;
693 if (pgn_piece_to_int(p
) == OPEN_SQUARE
|| val_piece_side(g
->turn
, p
))
696 /* Previous move was not 2 squares and a pawn. */
697 if (!TEST_FLAG(g
->flags
, GF_ENPASSANT
) ||
698 b
[RANKTOBOARD(rank
)][FILETOBOARD(file
)].enpassant
== 0)
702 // GF_ENPASSANT should take care of this
703 if (!b[RANKTOBOARD(rank)][FILETOBOARD(file)].enpassant)
707 if ((g
->turn
== WHITE
&& rank
!= 6) || (g
->turn
== BLACK
&& rank
!= 3))
711 // GF_ENPASSANT should take care of this
712 n
= (g
->turn
== WHITE
) ? rank
- 1 : rank
+ 1;
713 p
= b
[RANKTOBOARD(n
)][FILETOBOARD(file
)].icon
;
715 if (pgn_piece_to_int(p
) != PAWN
)
718 if (val_piece_side(g
->turn
, p
))
725 static int check_self_test(GAME g
, BOARD b
, int p
, int sfile
, int srank
,
730 int oldv
= g
->validate
;
731 int nkfile
, nkrank
, nokfile
, nokrank
;
735 g
->check_testing
= 1;
736 memcpy(tmpb
, b
, sizeof(BOARD
));
737 memcpy(&newg
, g
, sizeof(struct game_s
));
739 if (finalize_move(&newg
, tmpb
, 0, sfile
, srank
, file
, rank
)
741 g
->check_testing
= 0;
747 find_king_squares(&newg
, tmpb
, &nkfile
, &nkrank
, &nokfile
, &nokrank
);
749 nkfile
= g
->kfile
, nkrank
= g
->krank
;
751 go
= TEST_FLAG(newg
.flags
, GF_GAMEOVER
);
752 memcpy(&newg
, g
, sizeof(struct game_s
));
754 if (check_self(&newg
, tmpb
, nkfile
, nkrank
) == CHECK_SELF
) {
755 g
->check_testing
= 0;
760 if (!g
->validate
&& !g
->validate_find
) {
761 g
->flags
= newg
.flags
;
764 SET_FLAG(g
->flags
, GF_GAMEOVER
);
768 g
->check_testing
= 0;
772 static int find_source_square(GAME g
, BOARD b
, int piece
, int *sfile
,
773 int *srank
, int file
, int rank
)
781 PGN_DUMP("%s:%d: finding source square: piece=%c source=%c%c dest=%c%c\n",
782 __FILE__
, __LINE__
, pgn_int_to_piece(g
->turn
, piece
),
783 (*sfile
) ? INTTOFILE(*sfile
) : '0',
784 (*srank
) ? INTTORANK(*srank
) : '0', INTTOFILE(file
),
789 if (!*srank
&& *sfile
== file
) {
790 /* Find the first pawn in 'file'. */
791 i
= (g
->turn
== WHITE
) ? -1 : 1;
793 for (r
= rank
+ i
, dist
= 0; VALIDFILE(r
); r
+= i
, dist
++) {
794 p
= pgn_piece_to_int(b
[RANKTOBOARD(r
)][FILETOBOARD(file
)].icon
);
796 if (p
!= OPEN_SQUARE
)
804 *srank
= (g
->turn
== WHITE
) ? rank
- 1 : rank
+ 1;
806 if (!validate_pawn(g
, b
, *sfile
, *srank
, file
, rank
))
811 if (!g
->check_testing
) {
812 if (check_self_test(g
, b
, piece
, *sfile
, *srank
, file
, rank
) == 0)
817 if (*sfile
&& *srank
) {
818 count
= find_ambiguous(g
, b
, piece
, *sfile
, *srank
, file
, rank
);
823 if (!g
->check_testing
) {
824 if (check_self_test(g
, b
, piece
, *sfile
, *srank
, file
, rank
)
830 int ff
= *sfile
, rr
= *srank
;
832 for (r
= 1; VALIDRANK(r
); r
++) {
833 for (f
= 1; VALIDFILE(f
); f
++) {
836 if ((*sfile
&& f
!= ff
) || (*srank
&& r
!= rr
))
839 n
= find_ambiguous(g
, b
, piece
, f
, r
, file
, rank
);
842 if (!g
->check_testing
&&
843 check_self_test(g
, b
, piece
, f
, r
, file
, rank
) == 0)
859 if (validate_piece(g
, b
, piece
, *sfile
, *srank
, file
, rank
) != E_PGN_OK
)
866 static int finalize_move(GAME g
, BOARD b
, int promo
, int sfile
, int srank
,
872 if (!g
->validate
&& !g
->check_testing
)
873 PGN_DUMP("%s:%d: BEGIN finalizing\n", __FILE__
, __LINE__
);
876 p
= b
[RANKTOBOARD(rank
)][FILETOBOARD(file
)].icon
;
877 pi
= pgn_piece_to_int(p
);
879 if (pi
!= OPEN_SQUARE
&& val_piece_side(g
->turn
, p
))
880 return E_PGN_INVALID
;
884 if (!g
->check_testing
)
885 PGN_DUMP("%s:%d: updating board and game flags\n", __FILE__
,
888 pgn_reset_enpassant(b
);
889 p
= b
[RANKTOBOARD(srank
)][FILETOBOARD(sfile
)].icon
;
890 pi
= pgn_piece_to_int(p
);
893 p
= b
[RANKTOBOARD(rank
)][FILETOBOARD(file
)].icon
;
895 if (sfile
!= file
&& pgn_piece_to_int(p
) == OPEN_SQUARE
&&
896 TEST_FLAG(g
->flags
, GF_ENPASSANT
)) {
897 p
= (g
->turn
== WHITE
) ? rank
- 1 : rank
+ 1;
898 b
[RANKTOBOARD(p
)][FILETOBOARD(file
)].icon
=
899 pgn_int_to_piece(g
->turn
, OPEN_SQUARE
);
902 if (abs(srank
- rank
) > 1) {
903 SET_FLAG(g
->flags
, GF_ENPASSANT
);
904 b
[RANKTOBOARD(((g
->turn
== WHITE
) ? rank
- 1 : rank
+ 1))][FILETOBOARD(file
)].enpassant
= 1;
907 else if (pi
== ROOK
) {
908 if (g
->turn
== WHITE
) {
909 if (sfile
== FILETOINT('h') && srank
== 1)
910 CLEAR_FLAG(g
->flags
, GF_WK_CASTLE
);
911 else if (sfile
== FILETOINT('a') && srank
== 1)
912 CLEAR_FLAG(g
->flags
, GF_WQ_CASTLE
);
915 if (sfile
== FILETOINT('h') && srank
== 8)
916 CLEAR_FLAG(g
->flags
, GF_BK_CASTLE
);
917 else if (sfile
== FILETOINT('a') && srank
== 8)
918 CLEAR_FLAG(g
->flags
, GF_BQ_CASTLE
);
923 CLEAR_FLAG(g
->flags
, GF_ENPASSANT
);
925 if (pi
== KING
&& !g
->castle
) {
926 if (g
->turn
== WHITE
)
927 CLEAR_FLAG(g
->flags
, GF_WK_CASTLE
|GF_WQ_CASTLE
);
929 CLEAR_FLAG(g
->flags
, GF_BK_CASTLE
|GF_BQ_CASTLE
);
933 p
= b
[RANKTOBOARD(srank
)][FILETOBOARD(sfile
)].icon
;
934 b
[RANKTOBOARD(srank
)][FILETOBOARD(sfile
)].icon
=
935 pgn_int_to_piece(g
->turn
, OPEN_SQUARE
);
936 b
[RANKTOBOARD(srank
)][FILETOBOARD(
937 (file
> FILETOINT('e') ? 8 : 1))].icon
=
938 pgn_int_to_piece(g
->turn
, OPEN_SQUARE
);
939 b
[RANKTOBOARD(rank
)][FILETOBOARD(file
)].icon
= p
;
941 if (file
> FILETOINT('e'))
942 b
[RANKTOBOARD(rank
)][FILETOBOARD((file
- 1))].icon
=
943 pgn_int_to_piece(g
->turn
, ROOK
);
945 b
[RANKTOBOARD(rank
)][FILETOBOARD((file
+ 1))].icon
=
946 pgn_int_to_piece(g
->turn
, ROOK
);
948 if (g
->turn
== WHITE
)
949 CLEAR_FLAG(g
->flags
, (file
> FILETOINT('e')) ? GF_WK_CASTLE
:
952 CLEAR_FLAG(g
->flags
, (file
> FILETOINT('e')) ? GF_BK_CASTLE
:
957 p
= pgn_int_to_piece(g
->turn
, promo
);
959 p
= b
[RANKTOBOARD(srank
)][FILETOBOARD(sfile
)].icon
;
961 b
[RANKTOBOARD(srank
)][FILETOBOARD(sfile
)].icon
=
962 pgn_int_to_piece(g
->turn
, OPEN_SQUARE
);
963 b
[RANKTOBOARD(rank
)][FILETOBOARD(file
)].icon
= p
;
969 if (!g
->check_testing
) {
970 if (pgn_piece_to_int(p
) == KING
)
971 find_king_squares(g
, b
, &g
->kfile
, &g
->krank
, &g
->okfile
, &g
->okrank
);
973 switch (check_test(g
, b
)) {
978 g
->check
= CHECK_MATE
;
981 pgn_tag_add(&g
->tag
, "Result",
982 (g
->turn
== WHITE
) ? "1-0" : "0-1");
983 SET_FLAG(g
->flags
, GF_GAMEOVER
);
991 if (!g
->validate
&& (g
->pgn_fen_tag
> 0 && !g
->done_fen_tag
) &&
992 !pgn_history_total(g
->hp
) && srank
>= 7)
993 SET_FLAG(g
->flags
, GF_BLACK_OPENING
);
997 if (!g
->validate
&& !g
->check_testing
)
998 PGN_DUMP("%s:%d: END finalizing\n", __FILE__
, __LINE__
);
1002 p
= pgn_piece_to_int(p
);
1004 if (p
== PAWN
|| promo
|| g
->capture
)
1010 if (g
->tag
[6]->value
[0] == '*') {
1011 pgn_tag_add(&g
->tag
, "Result", "1/2-1/2");
1012 SET_FLAG(g
->flags
, GF_GAMEOVER
);
1020 static void black_opening(GAME g
, BOARD b
, int rank
)
1022 if (!g
->ravlevel
&& !g
->hindex
&& pgn_tag_find(g
->tag
, "FEN") == -1) {
1025 find_king_squares(g
, b
, &g
->kfile
, &g
->krank
, &g
->okfile
, &g
->okrank
);
1028 SET_FLAG(g
->flags
, GF_BLACK_OPENING
);
1034 CLEAR_FLAG(g
->flags
, GF_BLACK_OPENING
);
1039 if (TEST_FLAG(g
->flags
, GF_BLACK_OPENING
))
1040 PGN_DUMP("%s:%d: black opening\n", __FILE__
, __LINE__
);
1044 static char *format_santofrfr(int promo
, int sfile
, int srank
, int file
,
1047 char *frfr
= malloc(6);
1050 snprintf(frfr
, 6, "%c%c%c%c", INTTOFILE(sfile
),
1051 INTTORANK(srank
), INTTOFILE(file
), INTTORANK(rank
));
1054 frfr
[4] = pgn_int_to_piece(BLACK
, promo
);
1060 * Converts a2a3 formatted moves to SAN format. The promotion piece should be
1063 static int frfrtosan(GAME g
, BOARD b
, char **m
, char **dst
)
1065 char buf
[MAX_SAN_MOVE_LEN
+1] = {0}, *bp
= buf
;
1066 int icon
, p
, dp
, promo
= 0;
1067 int sfile
, srank
, file
, rank
;
1073 PGN_DUMP("%s:%d: converting to SAN format\n", __FILE__
, __LINE__
);
1077 sfile
= FILETOINT(bp
[0]);
1078 srank
= RANKTOINT(bp
[1]);
1079 file
= FILETOINT(bp
[2]);
1080 rank
= RANKTOINT(bp
[3]);
1082 black_opening(g
, b
, rank
);
1085 if ((promo
= pgn_piece_to_int(bp
[4])) == -1 || promo
== OPEN_SQUARE
)
1088 PGN_DUMP("%s:%d: promotion to %c\n", __FILE__
, __LINE__
,
1089 pgn_int_to_piece(g
->turn
, promo
));
1093 icon
= b
[RANKTOBOARD(srank
)][FILETOBOARD(sfile
)].icon
;
1095 if ((p
= pgn_piece_to_int(icon
)) == -1 || p
== OPEN_SQUARE
)
1098 if (p
!= PAWN
&& promo
)
1099 return E_PGN_INVALID
;
1102 if (find_source_square(g
, b
, p
, &sfile
, &srank
, file
, rank
) != 1)
1103 return E_PGN_INVALID
;
1108 if (validate_piece(g
, b
, p
, sfile
, srank
, file
, rank
) != E_PGN_OK
)
1109 return E_PGN_INVALID
;
1111 if (p
== KING
&& abs(sfile
- file
) > 1) {
1112 strcpy(buf
, (file
> FILETOINT('e')) ? "O-O" : "O-O-O");
1114 if (finalize_move(g
, b
, promo
, sfile
, srank
, file
, rank
) != E_PGN_OK
)
1115 return E_PGN_INVALID
;
1118 strcat(buf
, (g
->check
== CHECK
) ? "+" : "#");
1120 /* The move buffer size may be shorter than frfr format. Since
1121 * pgn_parse() uses a buffer allocated on the stack, this is not
1122 * needed because its' size is always MAX_SAN_MOVE_LEN+1.
1124 if (!parsing_file
&& strlen (*m
) < strlen (buf
)) {
1126 *m
= malloc (strlen (buf
)+1);
1132 *dst
= format_santofrfr(promo
, sfile
, srank
, file
, rank
);
1137 *bp
++ = toupper(icon
);
1139 n
= find_source_square(g
, b
, p
, &fc
, &rc
, file
, rank
);
1142 return E_PGN_INVALID
;
1144 fc
= find_source_square(g
, b
, p
, &sfile
, &rr
, file
, rank
);
1145 rc
= find_source_square(g
, b
, p
, &ff
, &srank
, file
, rank
);
1148 *bp
++ = INTTOFILE(sfile
);
1150 *bp
++ = INTTORANK(srank
);
1151 else if (fc
&& rc
) {
1153 *bp
++ = INTTORANK(srank
);
1155 *bp
++ = INTTOFILE(sfile
);
1156 *bp
++ = INTTORANK(srank
);
1160 return E_PGN_PARSE
; // not reached.
1164 icon
= b
[RANKTOBOARD(rank
)][FILETOBOARD(file
)].icon
;
1166 if ((dp
= pgn_piece_to_int(icon
)) == -1)
1172 if (dp
!= OPEN_SQUARE
|| (dp
== OPEN_SQUARE
&& p
== PAWN
&& sfile
!= file
)) {
1174 *bp
++ = INTTOFILE(sfile
);
1183 *bp
++ = INTTOFILE(file
);
1184 *bp
++ = INTTORANK(rank
);
1186 if (p
== PAWN
&& !promo
&& (rank
== 8 || rank
== 1))
1187 promo
= pgn_piece_to_int('q');
1193 if (p
!= PAWN
|| (g
->turn
== WHITE
&& (srank
!= 7 || rank
!= 8)) ||
1194 (g
->turn
== BLACK
&& (srank
!= 2 || rank
!= 1)))
1195 return E_PGN_INVALID
;
1198 *bp
++ = pgn_int_to_piece(WHITE
, promo
);
1203 if (find_source_square(g
, b
, p
, &sfile
, &srank
, file
, rank
) != 1)
1204 return E_PGN_INVALID
;
1206 if (finalize_move(g
, b
, promo
, sfile
, srank
, file
, rank
) != E_PGN_OK
)
1207 return E_PGN_INVALID
;
1210 *bp
++ = (g
->check
== CHECK
) ? '+' : '#';
1214 /* The move buffer size may be shorter than frfr format. Since
1215 * pgn_parse() uses a buffer allocated on the stack, this is not
1216 * needed because its' size is always MAX_SAN_MOVE_LEN+1.
1218 if (strlen (*m
) < strlen (buf
)) {
1220 *m
= malloc (strlen (buf
)+1);
1227 *dst
= format_santofrfr(promo
, sfile
, srank
, file
, rank
);
1229 PGN_DUMP("%s:%d: END validating %s\n", __FILE__
, __LINE__
, *m
);
1234 static int do_santofrfr(GAME g
, BOARD b
, char **san
, int *promo
, int *sfile
,
1235 int *srank
, int *file
, int *rank
)
1248 p
= (m
) + strlen(m
);
1250 while (!isdigit(*--p
) && *p
!= 'O') {
1252 *promo
= pgn_piece_to_int(i
);
1261 /* Alternate promotion text (e8Q). Convert to SAN. */
1262 if (i
&& pgn_piece_to_int(i
) != E_PGN_ERR
) {
1263 p
= (m
) + strlen(m
);
1272 /* Skip 'P' (pawn). */
1273 if (pgn_piece_to_int(*p
) == PAWN
)
1278 for (i
= 0; *p
; i
++) {
1281 *file
= FILETOINT(*p
++);
1283 *file
= *sfile
= FILETOINT(*p
++);
1285 else if (VALIDROW(*p
)) {
1287 *rank
= RANKTOINT(*p
++);
1289 *rank
= RANKTOINT(*p
++);
1291 else if (*p
== 'x') {
1292 *file
= FILETOINT(*++p
);
1293 *rank
= RANKTOINT(*++p
);
1296 else if (*p
== '=') {
1297 if (*promo
== -1 || *promo
== KING
|| *promo
== PAWN
)
1301 *p
++ = toupper(pgn_int_to_piece(g
->turn
, *promo
));
1307 PGN_DUMP("Pawn (move: '%s'): %c\n", m
, *p
++);
1314 black_opening(g
, b
, *rank
);
1316 if (find_source_square(g
, b
, PAWN
, sfile
, srank
, *file
, *rank
) != 1)
1317 return E_PGN_INVALID
;
1319 if (!*promo
&& (*rank
== 8 || *rank
== 1)) {
1320 *promo
= pgn_piece_to_int('q');
1322 *p
++ = pgn_int_to_piece(WHITE
, *promo
);
1333 * The first character is the piece but only if not a pawn.
1335 if ((piece
= pgn_piece_to_int(*p
++)) == -1)
1341 if (strlen(m
) > 3) {
1346 *srank
= RANKTOINT(*p
++);
1350 else if (VALIDCOL(*p
)) {
1351 *sfile
= FILETOINT(*p
++);
1357 *srank
= RANKTOINT(*p
++);
1372 * The destination square.
1374 *file
= FILETOINT(*p
++);
1375 *rank
= RANKTOINT(*p
++);
1380 black_opening(g
, b
, *rank
);
1382 if ((i
= find_source_square(g
, b
, piece
, sfile
, srank
, *file
, *rank
))
1384 return (i
== 0) ? E_PGN_INVALID
: E_PGN_AMBIGUOUS
;
1388 * The move is a valid one. Find the source file and rank so we
1389 * can later update the board positions.
1391 if (find_source_square(*g
, b
, piece
, sfile
, srank
, *file
, *rank
)
1393 return E_PGN_INVALID
;
1403 * Valididate move 'mp' against the game state 'g' and game board 'b' and
1404 * update board 'b'. 'mp' is updated to SAN format for moves which aren't
1405 * (frfr or e8Q for example). Returns E_PGN_PARSE if there was a move text
1406 * parsing error, E_PGN_INVALID if the move is invalid or E_PGN_OK if
1409 pgn_error_t
pgn_parse_move(GAME g
, BOARD b
, char **mp
, char **dst
)
1411 int srank
= 0, sfile
= 0, rank
, file
;
1415 size_t len
= m
? strlen (m
) : 0;
1418 * This may be an empty move with only an annotation. Kinda strange.
1424 PGN_DUMP("%s:%d: BEGIN validating '%s' (%s)...\n", __FILE__
, __LINE__
, m
,
1425 (g
->turn
== WHITE
) ? "white" : "black");
1428 g
->check_testing
= g
->castle
= 0;
1429 srank
= rank
= file
= sfile
= promo
= 0;
1430 find_king_squares(g
, b
, &g
->kfile
, &g
->krank
, &g
->okfile
, &g
->okrank
);
1432 if (m
[len
-1] == '+')
1435 if (VALIDCOL(*m
) && VALIDROW(*(m
+ 1)) && VALIDCOL(*(m
+ 2))
1436 && VALIDROW(*(m
+ 3)))
1437 return frfrtosan(g
, b
, mp
, dst
);
1438 else if (*m
== 'O') {
1439 if (strcmp(m
, "O-O") == 0)
1441 else if (strcmp(m
, "O-O-O") == 0)
1446 if (parse_castle_move(g
, b
, i
, &sfile
, &srank
, &file
, &rank
) !=
1448 return E_PGN_INVALID
;
1450 /* The move buffer size may be shorter than frfr format. Since
1451 * pgn_parse() uses a buffer allocated on the stack, this is not
1452 * needed because its' size is always MAX_SAN_MOVE_LEN+1.
1454 if (len
< 4 && !parsing_file
) {
1455 m
= malloc (5*sizeof(char));
1461 *m
++ = INTTOFILE(sfile
);
1462 *m
++ = INTTORANK(srank
);
1463 *m
++ = INTTOFILE(file
);
1464 *m
++ = INTTORANK(rank
);
1466 return frfrtosan(g
, b
, mp
, dst
);
1469 if ((i
= do_santofrfr(g
, b
, &m
, &promo
, &sfile
, &srank
, &file
, &rank
))
1475 if (finalize_move(g
, b
, promo
, sfile
, srank
, file
, rank
) != E_PGN_OK
)
1476 return E_PGN_INVALID
;
1479 *p
++ = (g
->check
== CHECK
) ? '+' : '#';
1482 *dst
= format_santofrfr(promo
, sfile
, srank
, file
, rank
);
1485 PGN_DUMP("%s:%d: END validating %s\n", __FILE__
, __LINE__
, m
);
1491 * Like pgn_parse_move() but don't modify game flags in 'g' or board 'b'.
1493 pgn_error_t
pgn_validate_move(GAME g
, BOARD b
, char **m
, char **dst
)
1496 int side
= g
->side
, turn
= g
->turn
;
1497 unsigned short flags
= g
->flags
;
1500 PGN_DUMP("%s:%d: BEGIN validate only\n", __FILE__
, __LINE__
);
1503 ret
= pgn_parse_move(g
, b
, m
, dst
);
1506 PGN_DUMP("%s:%d: END validate only\n", __FILE__
, __LINE__
);
1515 * Sets valid moves from game 'g' using board 'b'. The valid moves are for the
1516 * piece on the board 'b' at 'rank' and 'file'. Returns nothing.
1518 void pgn_find_valid_moves(GAME g
, BOARD b
, int file
, int rank
)
1520 int p
= pgn_piece_to_int(b
[RANKTOBOARD(rank
)][FILETOBOARD(file
)].icon
);
1524 PGN_DUMP("%s:%d: BEGIN valid destination squares for %c%c\n", __FILE__
,
1525 __LINE__
, INTTOFILE(file
), INTTORANK(rank
));
1528 g
->validate_find
= 1;
1529 find_king_squares(g
, b
, &g
->kfile
, &g
->krank
, &g
->okfile
, &g
->okrank
);
1531 for (r
= 1; VALIDRANK(r
); r
++) {
1532 for (f
= 1; VALIDFILE(f
); f
++) {
1533 if (val_piece_side(g
->turn
, b
[RANKTOBOARD(r
)][FILETOBOARD(f
)].icon
))
1536 if (find_source_square(g
, b
, p
, &file
, &rank
, f
, r
) != 0) {
1537 b
[RANKTOBOARD(r
)][FILETOBOARD(f
)].valid
= 1;
1539 PGN_DUMP("%s:%d: %c%c is valid\n", __FILE__
, __LINE__
,
1540 INTTOFILE(f
), INTTORANK(r
));
1547 PGN_DUMP("%s:%d: END valid destination squares for %c%c\n", __FILE__
,
1548 __LINE__
, INTTOFILE(file
), INTTORANK(rank
));
1550 g
->check_testing
= 0;
1551 g
->validate_find
= 0;