1 /* vim:tw=78:ts=8:sw=4:set ft=c: */
3 Copyright (C) 2002-2011 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
46 KINGSIDE
= 1, QUEENSIDE
49 static int finalize_move(GAME g
, BOARD b
, int promo
, int sfile
, int srank
,
51 static int find_source_square(GAME
, BOARD
, int, int *, int *, int, int);
52 static int check_self(GAME g
, BOARD b
, int file
, int rank
);
53 static int validate_pawn(GAME g
, BOARD b
, int sfile
, int srank
, int file
,
55 static int find_source_square(GAME
, BOARD
, int, int *, int *, int, int);
57 static int val_piece_side(register char turn
, register int c
)
59 if ((isupper(c
) && turn
== WHITE
) ||
60 (islower(c
) && turn
== BLACK
))
66 static int count_piece(GAME g
, BOARD b
, register int piece
, register int sfile
,
67 register int srank
, register int file
, register int rank
,
72 if (!VALIDRANK(rank
) || !VALIDFILE(file
))
75 p
= b
[RANKTOBOARD(rank
)][FILETOBOARD(file
)].icon
;
76 pi
= pgn_piece_to_int(p
);
78 if (pi
!= OPEN_SQUARE
) {
79 if (pi
== piece
&& val_piece_side(g
->turn
, p
)) {
80 if (sfile
&& file
== sfile
) {
81 if (!srank
|| (srank
&& rank
== srank
))
84 else if (srank
&& rank
== srank
) {
88 else if (!sfile
&& !srank
)
99 * Get the source row and column for a given piece.
101 * The following two functions find 'piece' from the given square 'col' and
102 * 'row' and store the resulting column or row in 'c' and 'r'. The return
103 * value is the number of 'piece' found (on the current g->side) or zero.
104 * Search for 'piece' stops when a non-empty square is found.
106 static int count_by_diag(GAME g
, BOARD b
, register int piece
,
107 register int sfile
, register int srank
, register int file
,
111 register int ul
= 0, ur
= 0, dl
= 0, dr
= 0;
115 for (i
= 1; VALIDFILE(i
); i
++) {
119 if (!ul
&& VALIDRANK(r
) && VALIDFILE(f
))
120 ul
= count_piece(g
, b
, piece
, sfile
, srank
, f
, r
, &count
);
125 if (!ur
&& VALIDRANK(r
) && VALIDFILE(f
))
126 ur
= count_piece(g
, b
, piece
, sfile
, srank
, f
, r
, &count
);
131 if (!dl
&& VALIDRANK(r
) && VALIDFILE(f
))
132 dl
= count_piece(g
, b
, piece
, sfile
, srank
, f
, r
, &count
);
137 if (!dr
&& VALIDRANK(r
) && VALIDFILE(f
))
138 dr
= count_piece(g
, b
, piece
, sfile
, srank
, f
, r
, &count
);
144 static int count_knight(GAME g
, BOARD b
, int piece
, int sfile
, int srank
,
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
);
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
);
160 static int count_by_rank(GAME g
, BOARD b
, register int piece
,
161 register int sfile
, register int srank
, register int file
,
166 register int u
= 0, d
= 0;
168 for (i
= 1; VALIDRANK(i
); i
++) {
169 if (!u
&& VALIDRANK((rank
+ i
)))
170 u
= count_piece(g
, b
, piece
, sfile
, srank
, file
, rank
+ i
, &count
);
172 if (!d
&& VALIDRANK((rank
- i
)))
173 d
= count_piece(g
, b
, piece
, sfile
, srank
, file
, rank
- i
, &count
);
182 static int count_by_file(GAME g
, BOARD b
, register int piece
,
183 register int sfile
, register int srank
, register int file
,
188 register int l
= 0, r
= 0;
190 for (i
= 1; VALIDFILE(i
); i
++) {
191 if (!r
&& VALIDFILE((file
+ i
)))
192 r
= count_piece(g
, b
, piece
, sfile
, srank
, file
+ i
, rank
, &count
);
194 if (!l
&& VALIDFILE((file
- i
)))
195 l
= count_piece(g
, b
, piece
, sfile
, srank
, file
- i
, rank
, &count
);
204 static int count_by_rank_file(GAME g
, BOARD b
, register int piece
,
205 register int sfile
, register int srank
, register int file
,
210 count
= count_by_rank(g
, b
, piece
, sfile
, srank
, file
, rank
);
211 return count
+ count_by_file(g
, b
, piece
, sfile
, srank
, file
, rank
);
214 static int opponent_can_attack(GAME g
, BOARD b
, register int file
,
219 int kf
= g
->kfile
, kr
= g
->krank
;
222 g
->kfile
= g
->okfile
, g
->krank
= g
->okrank
;
224 for (r
= 1; VALIDRANK(r
); r
++) {
225 for (f
= 1; VALIDFILE(f
); f
++) {
226 p
= b
[RANKTOBOARD(r
)][FILETOBOARD(f
)].icon
;
227 pi
= pgn_piece_to_int(p
);
229 if (pi
== OPEN_SQUARE
|| !val_piece_side(g
->turn
, p
))
232 if (find_source_square(g
, b
, pi
, &f
, &r
, file
, rank
) != 0) {
233 g
->kfile
= kf
, g
->krank
= kr
;
240 g
->kfile
= kf
, g
->krank
= kr
;
245 static int validate_castle_move(GAME g
, BOARD b
, int side
, int sfile
,
246 int srank
, int file
, int rank
)
250 if (side
== KINGSIDE
) {
251 if ((g
->turn
== WHITE
&& !TEST_FLAG(g
->flags
, GF_WK_CASTLE
)) ||
252 (g
->turn
== BLACK
&& !TEST_FLAG(g
->flags
, GF_BK_CASTLE
)))
253 return E_PGN_INVALID
;
256 if ((g
->turn
== WHITE
&& !TEST_FLAG(g
->flags
, GF_WQ_CASTLE
)) ||
257 (g
->turn
== BLACK
&& !TEST_FLAG(g
->flags
, GF_BQ_CASTLE
)))
258 return E_PGN_INVALID
;
261 if (file
> FILETOINT('e')) {
262 if (b
[RANKTOBOARD(srank
)][FILETOBOARD((sfile
+ 1))].icon
263 != pgn_int_to_piece(g
->turn
, OPEN_SQUARE
) ||
264 b
[RANKTOBOARD(srank
)][FILETOBOARD((sfile
+ 2))].icon
265 != pgn_int_to_piece(g
->turn
, OPEN_SQUARE
))
266 return E_PGN_INVALID
;
269 if (pgn_config
.strict_castling
> 0) {
270 if (opponent_can_attack(g
, b
, sfile
+ 1, srank
))
271 return E_PGN_INVALID
;
273 if (opponent_can_attack(g
, b
, sfile
+ 2, srank
))
274 return E_PGN_INVALID
;
278 if (b
[RANKTOBOARD(srank
)][FILETOBOARD((sfile
- 1))].icon
!=
279 pgn_int_to_piece(g
->turn
, OPEN_SQUARE
) ||
280 b
[RANKTOBOARD(srank
)][FILETOBOARD((sfile
- 2))].icon
!=
281 pgn_int_to_piece(g
->turn
, OPEN_SQUARE
) ||
282 b
[RANKTOBOARD(srank
)][FILETOBOARD((sfile
- 3))].icon
!=
283 pgn_int_to_piece(g
->turn
, OPEN_SQUARE
))
284 return E_PGN_INVALID
;
286 if (pgn_config
.strict_castling
> 0) {
287 if (opponent_can_attack(g
, b
, sfile
- 1, srank
))
288 return E_PGN_INVALID
;
290 if (opponent_can_attack(g
, b
, sfile
- 2, srank
))
291 return E_PGN_INVALID
;
293 if (opponent_can_attack(g
, b
, sfile
- 3, srank
))
294 return E_PGN_INVALID
;
298 n
= g
->check_testing
;
299 g
->check_testing
= 1;
301 if (check_self(g
, b
, g
->kfile
, g
->krank
) == CHECK_SELF
) {
302 g
->check_testing
= n
;
303 return E_PGN_INVALID
;
306 g
->check_testing
= n
;
311 static int validate_piece(GAME g
, BOARD b
, register int p
, register int sfile
,
312 register int srank
, register int file
, register int rank
)
315 register int i
, dist
;
320 /* Find the first pawn in the current column. */
321 i
= (g
->turn
== WHITE
) ? -1 : 1;
324 for (r
= rank
+ i
, dist
= 0; VALIDFILE(r
); r
+= i
, dist
++) {
325 p
= b
[RANKTOBOARD(r
)][FILETOBOARD(file
)].icon
;
327 if (pgn_piece_to_int(p
) != OPEN_SQUARE
)
331 if (pgn_piece_to_int(p
) != PAWN
|| !val_piece_side(g
->turn
, p
) || dist
> 2)
332 return E_PGN_INVALID
;
337 dist
= abs(srank
- rank
);
338 p
= b
[RANKTOBOARD(srank
)][FILETOBOARD(sfile
)].icon
;
340 if (pgn_piece_to_int(p
) != PAWN
|| !val_piece_side(g
->turn
, p
) || dist
> 2)
341 return E_PGN_INVALID
;
344 if (g
->turn
== WHITE
) {
345 if ((srank
== 2 && dist
> 2) || (srank
> 2 && dist
> 1))
346 return E_PGN_INVALID
;
349 if ((srank
== 7 && dist
> 2) || (srank
< 7 && dist
> 1))
350 return E_PGN_INVALID
;
353 p
= b
[RANKTOBOARD(rank
)][FILETOBOARD(file
)].icon
;
355 if (pgn_piece_to_int(p
) != OPEN_SQUARE
)
356 return E_PGN_INVALID
;
358 else if (sfile
!= file
) {
359 if (abs(sfile
- file
) != 1)
360 return E_PGN_INVALID
;
362 srank
= (g
->turn
== WHITE
) ? rank
- 1 : rank
+ 1;
363 p
= b
[RANKTOBOARD(srank
)][FILETOBOARD(sfile
)].icon
;
365 if (!val_piece_side(g
->turn
, p
))
366 return E_PGN_INVALID
;
368 if (pgn_piece_to_int(p
) != PAWN
|| abs(srank
- rank
) != 1)
369 return E_PGN_INVALID
;
371 p
= b
[RANKTOBOARD(rank
)][FILETOBOARD(file
)].icon
;
374 if (pgn_piece_to_int(p
) == OPEN_SQUARE
) {
375 /* Previous move was not 2 squares and a pawn. */
376 if (!TEST_FLAG(g
->flags
, GF_ENPASSANT
))
377 return E_PGN_INVALID
;
379 if (!b
[RANKTOBOARD(rank
)][FILETOBOARD(file
)].enpassant
)
380 return E_PGN_INVALID
;
382 r
= (g
->turn
== WHITE
) ? 6 : 3;
385 return E_PGN_INVALID
;
387 r
= (g
->turn
== WHITE
) ? rank
- 1 : rank
+ 1;
388 p
= b
[RANKTOBOARD(r
)][FILETOBOARD(file
)].icon
;
390 if (pgn_piece_to_int(p
) != PAWN
)
391 return E_PGN_INVALID
;
394 if (val_piece_side(g
->turn
, p
))
395 return E_PGN_INVALID
;
398 p
= b
[RANKTOBOARD(srank
)][FILETOBOARD(sfile
)].icon
;
400 if (!val_piece_side(g
->turn
, p
))
401 return E_PGN_INVALID
;
405 f
= abs(sfile
- file
);
406 r
= abs(srank
- rank
);
409 return E_PGN_INVALID
;
412 if (sfile
!= FILETOINT('e'))
413 return E_PGN_INVALID
;
415 if (validate_castle_move(g
, b
, (file
> FILETOINT('e')) ?
416 KINGSIDE
: QUEENSIDE
, sfile
, srank
, file
, rank
)
418 return E_PGN_INVALID
;
430 * Returns the number of pieces of type 'p' that can move to the destination
431 * square located at 'file' and 'rank'. The source square 'sfile' and 'srank'
432 * (if available in the move text) should be determined before calling this
433 * function or set to 0 if unknown. Returns 0 if the move is impossible for
436 static int find_ambiguous(GAME g
, BOARD b
, register int p
, register int sfile
,
437 register int srank
, register int file
, register int rank
)
443 count
= validate_pawn(g
, b
, sfile
, srank
, file
, rank
);
446 count
= count_by_rank_file(g
, b
, p
, sfile
, srank
, file
, rank
);
449 count
= count_knight(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
);
459 count
= count_by_rank_file(g
, b
, p
, sfile
, srank
, file
, rank
);
460 count
+= count_by_diag(g
, b
, p
, sfile
, srank
, file
, rank
);
469 static void find_king_squares(GAME g
, BOARD b
, register int *file
,
470 register int *rank
, register int *ofile
, register int *orank
)
474 for (r
= 1; VALIDRANK(r
); r
++) {
475 for (f
= 1; VALIDFILE(f
); f
++) {
476 register int p
= b
[RANKTOBOARD(r
)][FILETOBOARD(f
)].icon
;
477 register int pi
= pgn_piece_to_int(p
);
479 if (pi
== OPEN_SQUARE
|| pi
!= KING
)
482 if (val_piece_side(g
->turn
, p
))
483 *file
= f
, *rank
= r
;
485 *ofile
= f
, *orank
= r
;
490 PGN_DUMP("%s:%d: king location: %c%c %c%c(opponent)\n", __FILE__
,
491 __LINE__
, INTTOFILE(*file
), INTTORANK(*rank
),
492 INTTOFILE(*ofile
), INTTORANK(*orank
));
496 static int parse_castle_move(GAME g
, BOARD b
, int side
, int *sfile
,
497 int *srank
, int *file
, int *rank
)
499 *srank
= *rank
= (g
->turn
== WHITE
) ? 1 : 8;
500 *sfile
= FILETOINT('e');
502 if (side
== KINGSIDE
)
503 *file
= FILETOINT('g');
505 *file
= FILETOINT('c');
507 return validate_castle_move(g
, b
, side
, *sfile
, *srank
, *file
, *rank
);
511 * Almost exactly like pgn_find_valid_moves() but returns immediately after a
512 * valid move is found.
514 static int check_mate_thingy(GAME g
, BOARD b
, int file
, int rank
)
517 register int p
= pgn_piece_to_int(b
[RANKTOBOARD(rank
)][FILETOBOARD(file
)].icon
);
519 for (r
= 1; VALIDRANK(r
); r
++) {
520 for (f
= 1; VALIDFILE(f
); f
++) {
521 if (val_piece_side(g
->turn
, b
[RANKTOBOARD(r
)][FILETOBOARD(f
)].icon
))
524 if (find_source_square(g
, b
, p
, &file
, &rank
, f
, r
) != 0)
532 static int checkmate_test(GAME g
, BOARD b
)
536 register int kf
= g
->kfile
, kr
= g
->krank
, okf
= g
->okfile
, okr
= g
->okrank
;
539 PGN_DUMP("%s:%d: BEGIN checkmate test\n", __FILE__
, __LINE__
);
542 * The king squares need to be switched also for find_source_squares()
543 * which calls check_self().
546 g
->kfile
= g
->okfile
, g
->krank
= g
->okrank
;
548 for (r
= 1; VALIDRANK(r
); r
++) {
549 for (f
= 1; VALIDFILE(f
); f
++) {
551 int sfile
= f
, srank
= r
;
553 p
= b
[RANKTOBOARD(r
)][FILETOBOARD(f
)].icon
;
555 if (p
== OPEN_SQUARE
|| !val_piece_side(g
->turn
, p
))
558 if (check_mate_thingy(g
, b
, sfile
, srank
)) {
567 g
->kfile
= kf
, g
->krank
= kr
, g
->okfile
= okf
, g
->okrank
= okr
;
571 static int check_opponent(GAME g
, BOARD b
, register int file
, register int rank
)
577 PGN_DUMP("%s:%d: BEGIN opponent check test\n");
580 for (r
= 1; VALIDRANK(r
); r
++) {
581 for (f
= 1; VALIDFILE(f
); f
++) {
582 p
= b
[RANKTOBOARD(r
)][FILETOBOARD(f
)].icon
;
583 pi
= pgn_piece_to_int(p
);
585 if (pi
== OPEN_SQUARE
|| !val_piece_side(g
->turn
, p
))
588 if (find_source_square(g
, b
, pi
, &f
, &r
, file
, rank
) != 0)
596 static int check_self(GAME g
, BOARD b
, int file
, int rank
)
602 PGN_DUMP("%s:%d: BEGIN self check test\n", __FILE__
, __LINE__
);
606 for (r
= 1; VALIDRANK(r
); r
++) {
607 for (f
= 1; VALIDFILE(f
); f
++) {
608 p
= b
[RANKTOBOARD(r
)][FILETOBOARD(f
)].icon
;
609 pi
= pgn_piece_to_int(p
);
611 if (pi
== OPEN_SQUARE
|| !val_piece_side(g
->turn
, p
))
614 if (find_source_square(g
, b
, pi
, &f
, &r
, file
, rank
) != 0) {
625 static int check_test(GAME g
, BOARD b
)
628 PGN_DUMP("%s:%d: BEGIN check test\n", __FILE__
, __LINE__
);
630 g
->check
= check_opponent(g
, b
, g
->okfile
, g
->okrank
);
633 return checkmate_test(g
, b
);
635 g
->check_testing
= 0;
639 static int validate_pawn(GAME g
, BOARD b
, register int sfile
,
640 register int srank
, register int file
, register int rank
)
642 register int n
= abs(srank
- rank
);
645 if (abs(sfile
- file
) > 1)
648 if (g
->turn
== WHITE
) {
649 if ((srank
== 2 && n
> 2) || (srank
> 2 && n
> 1))
653 if ((srank
== 7 && n
> 2) || (srank
< 7 && n
> 1))
657 if (n
> 1 && abs(sfile
- file
) != 0)
660 p
= b
[RANKTOBOARD(srank
)][FILETOBOARD(sfile
)].icon
;
662 if (!val_piece_side(g
->turn
, p
) || pgn_piece_to_int(p
) != PAWN
)
665 if (srank
== rank
|| (g
->turn
== WHITE
&& rank
< srank
) ||
666 (g
->turn
== BLACK
&& rank
> srank
))
670 p
= b
[RANKTOBOARD(rank
)][FILETOBOARD(file
)].icon
;
672 if (pgn_piece_to_int(p
) != OPEN_SQUARE
)
675 p
= (g
->turn
== WHITE
) ? rank
- 1 : rank
+ 1;
676 p
= b
[RANKTOBOARD(p
)][FILETOBOARD(file
)].icon
;
678 if (n
> 1 && pgn_piece_to_int(p
) != OPEN_SQUARE
)
684 p
= b
[RANKTOBOARD(rank
)][FILETOBOARD(file
)].icon
;
686 if (pgn_piece_to_int(p
) != OPEN_SQUARE
) {
687 if (val_piece_side(g
->turn
, p
))
694 p
= (g
->turn
== WHITE
) ? rank
- 1 : rank
+ 1;
695 p
= b
[RANKTOBOARD(p
)][FILETOBOARD(file
)].icon
;
697 if (pgn_piece_to_int(p
) == OPEN_SQUARE
|| val_piece_side(g
->turn
, p
))
700 /* Previous move was not 2 squares and a pawn. */
701 if (!TEST_FLAG(g
->flags
, GF_ENPASSANT
) ||
702 b
[RANKTOBOARD(rank
)][FILETOBOARD(file
)].enpassant
== 0)
706 // GF_ENPASSANT should take care of this
707 if (!b[RANKTOBOARD(rank)][FILETOBOARD(file)].enpassant)
711 if ((g
->turn
== WHITE
&& rank
!= 6) || (g
->turn
== BLACK
&& rank
!= 3))
715 // GF_ENPASSANT should take care of this
716 n
= (g
->turn
== WHITE
) ? rank
- 1 : rank
+ 1;
717 p
= b
[RANKTOBOARD(n
)][FILETOBOARD(file
)].icon
;
719 if (pgn_piece_to_int(p
) != PAWN
)
722 if (val_piece_side(g
->turn
, p
))
729 static int check_self_test(GAME g
, BOARD b
, int p
, int sfile
, int srank
,
734 int oldv
= g
->validate
;
735 int nkfile
, nkrank
, nokfile
, nokrank
;
739 g
->check_testing
= 1;
740 memcpy(tmpb
, b
, sizeof(BOARD
));
741 memcpy(&newg
, g
, sizeof(struct game_s
));
743 if (finalize_move(&newg
, tmpb
, 0, sfile
, srank
, file
, rank
)
745 g
->check_testing
= 0;
751 find_king_squares(&newg
, tmpb
, &nkfile
, &nkrank
, &nokfile
, &nokrank
);
753 nkfile
= g
->kfile
, nkrank
= g
->krank
;
755 go
= TEST_FLAG(newg
.flags
, GF_GAMEOVER
);
756 memcpy(&newg
, g
, sizeof(struct game_s
));
758 if (check_self(&newg
, tmpb
, nkfile
, nkrank
) == CHECK_SELF
) {
759 g
->check_testing
= 0;
764 if (!g
->validate
&& !g
->validate_find
) {
765 g
->flags
= newg
.flags
;
768 SET_FLAG(g
->flags
, GF_GAMEOVER
);
772 g
->check_testing
= 0;
776 static int find_source_square(GAME g
, BOARD b
, int piece
, int *sfile
,
777 int *srank
, register int file
, register int rank
)
780 register int r
, f
, i
;
781 register int dist
= 0;
785 PGN_DUMP("%s:%d: finding source square: piece=%c source=%c%c dest=%c%c\n",
786 __FILE__
, __LINE__
, pgn_int_to_piece(g
->turn
, piece
),
787 (*sfile
) ? INTTOFILE(*sfile
) : '0',
788 (*srank
) ? INTTORANK(*srank
) : '0', INTTOFILE(file
),
793 if (!*srank
&& *sfile
== file
) {
794 /* Find the first pawn in 'file'. */
795 i
= (g
->turn
== WHITE
) ? -1 : 1;
797 for (r
= rank
+ i
, dist
= 0; VALIDFILE(r
); r
+= i
, dist
++) {
798 p
= pgn_piece_to_int(b
[RANKTOBOARD(r
)][FILETOBOARD(file
)].icon
);
800 if (p
!= OPEN_SQUARE
)
808 *srank
= (g
->turn
== WHITE
) ? rank
- 1 : rank
+ 1;
810 if (!validate_pawn(g
, b
, *sfile
, *srank
, file
, rank
))
815 if (!g
->check_testing
) {
816 if (check_self_test(g
, b
, piece
, *sfile
, *srank
, file
, rank
) == 0)
821 if (*sfile
&& *srank
) {
822 count
= find_ambiguous(g
, b
, piece
, *sfile
, *srank
, file
, rank
);
827 if (!g
->check_testing
) {
828 if (check_self_test(g
, b
, piece
, *sfile
, *srank
, file
, rank
)
834 int ff
= *sfile
, rr
= *srank
;
836 for (r
= 1; VALIDRANK(r
); r
++) {
837 for (f
= 1; VALIDFILE(f
); f
++) {
840 if ((*sfile
&& f
!= ff
) || (*srank
&& r
!= rr
))
843 n
= find_ambiguous(g
, b
, piece
, f
, r
, file
, rank
);
846 if (!g
->check_testing
&&
847 check_self_test(g
, b
, piece
, f
, r
, file
, rank
) == 0)
863 if (validate_piece(g
, b
, piece
, *sfile
, *srank
, file
, rank
) != E_PGN_OK
)
870 static int finalize_move(GAME g
, BOARD b
, int promo
, int sfile
, int srank
,
876 if (!g
->validate
&& !g
->check_testing
)
877 PGN_DUMP("%s:%d: BEGIN finalizing\n", __FILE__
, __LINE__
);
880 p
= b
[RANKTOBOARD(rank
)][FILETOBOARD(file
)].icon
;
881 pi
= pgn_piece_to_int(p
);
883 if (pi
!= OPEN_SQUARE
&& val_piece_side(g
->turn
, p
))
884 return E_PGN_INVALID
;
888 if (!g
->check_testing
)
889 PGN_DUMP("%s:%d: updating board and game flags\n", __FILE__
,
892 pgn_reset_enpassant(b
);
893 p
= b
[RANKTOBOARD(srank
)][FILETOBOARD(sfile
)].icon
;
894 pi
= pgn_piece_to_int(p
);
897 p
= b
[RANKTOBOARD(rank
)][FILETOBOARD(file
)].icon
;
899 if (sfile
!= file
&& pgn_piece_to_int(p
) == OPEN_SQUARE
&&
900 TEST_FLAG(g
->flags
, GF_ENPASSANT
)) {
901 p
= (g
->turn
== WHITE
) ? rank
- 1 : rank
+ 1;
902 b
[RANKTOBOARD(p
)][FILETOBOARD(file
)].icon
=
903 pgn_int_to_piece(g
->turn
, OPEN_SQUARE
);
906 if (abs(srank
- rank
) > 1) {
907 SET_FLAG(g
->flags
, GF_ENPASSANT
);
908 b
[RANKTOBOARD(((g
->turn
== WHITE
) ? rank
- 1 : rank
+ 1))][FILETOBOARD(file
)].enpassant
= 1;
911 else if (pi
== ROOK
) {
912 if (g
->turn
== WHITE
) {
913 if (sfile
== FILETOINT('h') && srank
== 1)
914 CLEAR_FLAG(g
->flags
, GF_WK_CASTLE
);
915 else if (sfile
== FILETOINT('a') && srank
== 1)
916 CLEAR_FLAG(g
->flags
, GF_WQ_CASTLE
);
919 if (sfile
== FILETOINT('h') && srank
== 8)
920 CLEAR_FLAG(g
->flags
, GF_BK_CASTLE
);
921 else if (sfile
== FILETOINT('a') && srank
== 8)
922 CLEAR_FLAG(g
->flags
, GF_BQ_CASTLE
);
927 CLEAR_FLAG(g
->flags
, GF_ENPASSANT
);
929 if (pi
== KING
&& !g
->castle
) {
930 if (g
->turn
== WHITE
)
931 CLEAR_FLAG(g
->flags
, GF_WK_CASTLE
|GF_WQ_CASTLE
);
933 CLEAR_FLAG(g
->flags
, GF_BK_CASTLE
|GF_BQ_CASTLE
);
937 p
= b
[RANKTOBOARD(srank
)][FILETOBOARD(sfile
)].icon
;
938 b
[RANKTOBOARD(srank
)][FILETOBOARD(sfile
)].icon
=
939 pgn_int_to_piece(g
->turn
, OPEN_SQUARE
);
940 b
[RANKTOBOARD(srank
)][FILETOBOARD(
941 (file
> FILETOINT('e') ? 8 : 1))].icon
=
942 pgn_int_to_piece(g
->turn
, OPEN_SQUARE
);
943 b
[RANKTOBOARD(rank
)][FILETOBOARD(file
)].icon
= p
;
945 if (file
> FILETOINT('e'))
946 b
[RANKTOBOARD(rank
)][FILETOBOARD((file
- 1))].icon
=
947 pgn_int_to_piece(g
->turn
, ROOK
);
949 b
[RANKTOBOARD(rank
)][FILETOBOARD((file
+ 1))].icon
=
950 pgn_int_to_piece(g
->turn
, ROOK
);
952 if (g
->turn
== WHITE
)
953 CLEAR_FLAG(g
->flags
, (file
> FILETOINT('e')) ? GF_WK_CASTLE
:
956 CLEAR_FLAG(g
->flags
, (file
> FILETOINT('e')) ? GF_BK_CASTLE
:
961 p
= pgn_int_to_piece(g
->turn
, promo
);
963 p
= b
[RANKTOBOARD(srank
)][FILETOBOARD(sfile
)].icon
;
965 b
[RANKTOBOARD(srank
)][FILETOBOARD(sfile
)].icon
=
966 pgn_int_to_piece(g
->turn
, OPEN_SQUARE
);
967 b
[RANKTOBOARD(rank
)][FILETOBOARD(file
)].icon
= p
;
973 if (!g
->check_testing
) {
974 if (pgn_piece_to_int(p
) == KING
)
975 find_king_squares(g
, b
, &g
->kfile
, &g
->krank
, &g
->okfile
, &g
->okrank
);
977 switch (check_test(g
, b
)) {
982 g
->check
= CHECK_MATE
;
985 pgn_tag_add(&g
->tag
, "Result",
986 (g
->turn
== WHITE
) ? "1-0" : "0-1");
987 SET_FLAG(g
->flags
, GF_GAMEOVER
);
995 if (!g
->validate
&& (g
->pgn_fen_tag
> 0 && !g
->done_fen_tag
) &&
996 !pgn_history_total(g
->hp
) && srank
>= 7)
997 SET_FLAG(g
->flags
, GF_BLACK_OPENING
);
1001 if (!g
->validate
&& !g
->check_testing
)
1002 PGN_DUMP("%s:%d: END finalizing\n", __FILE__
, __LINE__
);
1006 p
= pgn_piece_to_int(p
);
1008 if (p
== PAWN
|| promo
|| g
->capture
)
1014 if (g
->tag
[6]->value
[0] == '*') {
1015 pgn_tag_add(&g
->tag
, "Result", "1/2-1/2");
1016 SET_FLAG(g
->flags
, GF_GAMEOVER
);
1024 static void black_opening(GAME g
, BOARD b
, int rank
)
1026 if (!g
->ravlevel
&& !g
->hindex
&& pgn_tag_find(g
->tag
, "FEN") == -1) {
1029 find_king_squares(g
, b
, &g
->kfile
, &g
->krank
, &g
->okfile
, &g
->okrank
);
1032 SET_FLAG(g
->flags
, GF_BLACK_OPENING
);
1038 CLEAR_FLAG(g
->flags
, GF_BLACK_OPENING
);
1043 if (TEST_FLAG(g
->flags
, GF_BLACK_OPENING
))
1044 PGN_DUMP("%s:%d: black opening\n", __FILE__
, __LINE__
);
1048 static char *format_santofrfr(int promo
, int sfile
, int srank
, int file
,
1051 static char frfr
[6] = {0};
1053 snprintf(frfr
, sizeof(frfr
), "%c%c%c%c", INTTOFILE(sfile
),
1054 INTTORANK(srank
), INTTOFILE(file
), INTTORANK(rank
));
1057 frfr
[4] = pgn_int_to_piece(BLACK
, promo
);
1063 * Converts a2a3 formatted moves to SAN format. The promotion piece should be
1066 static int frfrtosan(GAME g
, BOARD b
, char **m
, char **dst
)
1069 int icon
, p
, dp
, promo
= 0;
1070 int sfile
, srank
, file
, rank
;
1076 PGN_DUMP("%s:%d: converting to SAN format\n", __FILE__
, __LINE__
);
1079 sfile
= FILETOINT(bp
[0]);
1080 srank
= RANKTOINT(bp
[1]);
1081 file
= FILETOINT(bp
[2]);
1082 rank
= RANKTOINT(bp
[3]);
1084 black_opening(g
, b
, rank
);
1087 if ((promo
= pgn_piece_to_int(bp
[4])) == -1 || promo
== OPEN_SQUARE
)
1090 PGN_DUMP("%s:%d: promotion to %c\n", __FILE__
, __LINE__
,
1091 pgn_int_to_piece(g
->turn
, promo
));
1095 icon
= b
[RANKTOBOARD(srank
)][FILETOBOARD(sfile
)].icon
;
1097 if ((p
= pgn_piece_to_int(icon
)) == -1 || p
== OPEN_SQUARE
)
1100 if (p
!= PAWN
&& promo
)
1101 return E_PGN_INVALID
;
1104 if (find_source_square(g
, b
, p
, &sfile
, &srank
, file
, rank
) != 1)
1105 return E_PGN_INVALID
;
1110 if (validate_piece(g
, b
, p
, sfile
, srank
, file
, rank
) != E_PGN_OK
)
1111 return E_PGN_INVALID
;
1113 if (p
== KING
&& abs(sfile
- file
) > 1) {
1114 strcpy(bp
, (file
> FILETOINT('e')) ? "O-O" : "O-O-O");
1116 if (finalize_move(g
, b
, promo
, sfile
, srank
, file
, rank
) != E_PGN_OK
)
1117 return E_PGN_INVALID
;
1120 strcat(bp
, (g
->check
== CHECK
) ? "+" : "#");
1122 *dst
= format_santofrfr(promo
, sfile
, srank
, file
, rank
);
1126 *bp
++ = toupper(icon
);
1128 n
= find_source_square(g
, b
, p
, &fc
, &rc
, file
, rank
);
1131 return E_PGN_INVALID
;
1133 fc
= find_source_square(g
, b
, p
, &sfile
, &rr
, file
, rank
);
1134 rc
= find_source_square(g
, b
, p
, &ff
, &srank
, file
, rank
);
1137 *bp
++ = INTTOFILE(sfile
);
1139 *bp
++ = INTTORANK(srank
);
1140 else if (fc
&& rc
) {
1142 *bp
++ = INTTORANK(srank
);
1144 *bp
++ = INTTOFILE(sfile
);
1145 *bp
++ = INTTORANK(srank
);
1149 return E_PGN_PARSE
; // not reached.
1153 icon
= b
[RANKTOBOARD(rank
)][FILETOBOARD(file
)].icon
;
1155 if ((dp
= pgn_piece_to_int(icon
)) == -1)
1161 if (dp
!= OPEN_SQUARE
|| (dp
== OPEN_SQUARE
&& p
== PAWN
&& sfile
!= file
)) {
1163 *bp
++ = INTTOFILE(sfile
);
1172 *bp
++ = INTTOFILE(file
);
1173 *bp
++ = INTTORANK(rank
);
1175 if (p
== PAWN
&& !promo
&& (rank
== 8 || rank
== 1))
1176 promo
= pgn_piece_to_int('q');
1182 if (p
!= PAWN
|| (g
->turn
== WHITE
&& (srank
!= 7 || rank
!= 8)) ||
1183 (g
->turn
== BLACK
&& (srank
!= 2 || rank
!= 1)))
1184 return E_PGN_INVALID
;
1187 *bp
++ = pgn_int_to_piece(WHITE
, promo
);
1192 if (find_source_square(g
, b
, p
, &sfile
, &srank
, file
, rank
) != 1)
1193 return E_PGN_INVALID
;
1195 if (finalize_move(g
, b
, promo
, sfile
, srank
, file
, rank
) != E_PGN_OK
)
1196 return E_PGN_INVALID
;
1199 *bp
++ = (g
->check
== CHECK
) ? '+' : '#';
1202 *dst
= format_santofrfr(promo
, sfile
, srank
, file
, rank
);
1204 PGN_DUMP("%s:%d: END validating %s\n", __FILE__
, __LINE__
, *m
);
1209 static int do_santofrfr(GAME g
, BOARD b
, char **san
, int *promo
, int *sfile
,
1210 int *srank
, int *file
, int *rank
)
1212 static char frfr
[6];
1224 p
= (m
) + strlen(m
);
1226 while (!isdigit(*--p
) && *p
!= 'O') {
1228 *promo
= pgn_piece_to_int(i
);
1237 /* Alternate promotion text (e8Q). Convert to SAN. */
1238 if (i
&& pgn_piece_to_int(i
) != E_PGN_ERR
) {
1239 p
= (m
) + strlen(m
);
1248 /* Skip 'P' (pawn). */
1249 if (pgn_piece_to_int(*p
) == PAWN
)
1254 for (i
= 0; *p
; i
++) {
1257 *file
= FILETOINT(*p
++);
1259 *file
= *sfile
= FILETOINT(*p
++);
1261 else if (VALIDROW(*p
)) {
1263 *rank
= RANKTOINT(*p
++);
1265 *rank
= RANKTOINT(*p
++);
1267 else if (*p
== 'x') {
1268 *file
= FILETOINT(*++p
);
1269 *rank
= RANKTOINT(*++p
);
1272 else if (*p
== '=') {
1273 if (*promo
== -1 || *promo
== KING
|| *promo
== PAWN
)
1277 *p
++ = toupper(pgn_int_to_piece(g
->turn
, *promo
));
1283 PGN_DUMP("Pawn (move: '%s'): %c\n", m
, *p
++);
1290 black_opening(g
, b
, *rank
);
1292 if (find_source_square(g
, b
, PAWN
, sfile
, srank
, *file
, *rank
) != 1)
1293 return E_PGN_INVALID
;
1295 if (!*promo
&& (*rank
== 8 || *rank
== 1)) {
1296 *promo
= pgn_piece_to_int('q');
1298 *p
++ = pgn_int_to_piece(WHITE
, *promo
);
1309 * The first character is the piece but only if not a pawn.
1311 if ((piece
= pgn_piece_to_int(*p
++)) == -1)
1317 if (strlen(m
) > 3) {
1322 *srank
= RANKTOINT(*p
++);
1326 else if (VALIDCOL(*p
)) {
1327 *sfile
= FILETOINT(*p
++);
1333 *srank
= RANKTOINT(*p
++);
1348 * The destination square.
1350 *file
= FILETOINT(*p
++);
1351 *rank
= RANKTOINT(*p
++);
1356 black_opening(g
, b
, *rank
);
1358 if ((i
= find_source_square(g
, b
, piece
, sfile
, srank
, *file
, *rank
))
1360 return (i
== 0) ? E_PGN_INVALID
: E_PGN_AMBIGUOUS
;
1364 * The move is a valid one. Find the source file and rank so we
1365 * can later update the board positions.
1367 if (find_source_square(*g
, b
, piece
, sfile
, srank
, *file
, *rank
)
1369 return E_PGN_INVALID
;
1374 snprintf(frfr
, sizeof(frfr
), "%c%c%c%c", INTTOFILE(*sfile
),
1375 INTTORANK(*srank
), INTTOFILE(*file
), INTTORANK(*rank
));
1382 * Valididate move 'mp' against the game state 'g' and game board 'b' and
1383 * update board 'b'. 'mp' is updated to SAN format for moves which aren't
1384 * (frfr or e8Q for example). Returns E_PGN_PARSE if there was a move text
1385 * parsing error, E_PGN_INVALID if the move is invalid or E_PGN_OK if
1388 pgn_error_t
pgn_parse_move(GAME g
, BOARD b
, char **mp
, char **dst
)
1390 int srank
= 0, sfile
= 0, rank
, file
;
1396 * This may be an empty move with only an annotation. Kinda strange.
1402 PGN_DUMP("%s:%d: BEGIN validating '%s' (%s)...\n", __FILE__
, __LINE__
, m
,
1403 (g
->turn
== WHITE
) ? "white" : "black");
1406 g
->check_testing
= 0;
1407 srank
= rank
= file
= sfile
= promo
= 0;
1408 find_king_squares(g
, b
, &g
->kfile
, &g
->krank
, &g
->okfile
, &g
->okrank
);
1410 if (m
[strlen(m
) - 1] == '+')
1411 m
[strlen(m
) - 1] = 0;
1413 if (VALIDCOL(*m
) && VALIDROW(*(m
+ 1)) && VALIDCOL(*(m
+ 2)) &&
1415 return frfrtosan(g
, b
, mp
, dst
);
1416 else if (*m
== 'O') {
1417 if (strcmp(m
, "O-O") == 0)
1419 else if (strcmp(m
, "O-O-O") == 0)
1424 if (parse_castle_move(g
, b
, i
, &sfile
, &srank
, &file
, &rank
) !=
1426 return E_PGN_INVALID
;
1428 *m
++ = INTTOFILE(sfile
);
1429 *m
++ = INTTORANK(srank
);
1430 *m
++ = INTTOFILE(file
);
1431 *m
++ = INTTORANK(rank
);
1433 return frfrtosan(g
, b
, mp
, dst
);
1436 if ((i
= do_santofrfr(g
, b
, &m
, &promo
, &sfile
, &srank
, &file
, &rank
))
1442 if (finalize_move(g
, b
, promo
, sfile
, srank
, file
, rank
) != E_PGN_OK
)
1443 return E_PGN_INVALID
;
1446 *p
++ = (g
->check
== CHECK
) ? '+' : '#';
1449 *dst
= format_santofrfr(promo
, sfile
, srank
, file
, rank
);
1452 PGN_DUMP("%s:%d: END validating %s\n", __FILE__
, __LINE__
, m
);
1458 * Like pgn_parse_move() but don't modify game flags in 'g' or board 'b'.
1460 pgn_error_t
pgn_validate_move(GAME g
, BOARD b
, char **m
, char **dst
)
1465 PGN_DUMP("%s:%d: BEGIN validate only\n", __FILE__
, __LINE__
);
1468 ret
= pgn_parse_move(g
, b
, m
, dst
);
1471 PGN_DUMP("%s:%d: END validate only\n", __FILE__
, __LINE__
);
1477 * Sets valid moves from game 'g' using board 'b'. The valid moves are for the
1478 * piece on the board 'b' at 'rank' and 'file'. Returns nothing.
1480 void pgn_find_valid_moves(GAME g
, BOARD b
, int file
, int rank
)
1482 register int p
= pgn_piece_to_int(b
[RANKTOBOARD(rank
)][FILETOBOARD(file
)].icon
);
1486 PGN_DUMP("%s:%d: BEGIN valid destination squares for %c%c\n", __FILE__
,
1487 __LINE__
, INTTOFILE(file
), INTTORANK(rank
));
1490 g
->validate_find
= 1;
1491 find_king_squares(g
, b
, &g
->kfile
, &g
->krank
, &g
->okfile
, &g
->okrank
);
1493 for (r
= 1; VALIDRANK(r
); r
++) {
1494 for (f
= 1; VALIDFILE(f
); f
++) {
1495 if (val_piece_side(g
->turn
, b
[RANKTOBOARD(r
)][FILETOBOARD(f
)].icon
))
1498 if (find_source_square(g
, b
, p
, &file
, &rank
, f
, r
) != 0) {
1499 b
[RANKTOBOARD(r
)][FILETOBOARD(f
)].valid
= 1;
1501 PGN_DUMP("%s:%d: %c%c is valid\n", __FILE__
, __LINE__
,
1502 INTTOFILE(f
), INTTORANK(r
));
1509 PGN_DUMP("%s:%d: END valid destination squares for %c%c\n", __FILE__
,
1510 __LINE__
, INTTOFILE(file
), INTTORANK(rank
));
1512 g
->check_testing
= 0;
1513 g
->validate_find
= 0;