Manual page update.
[cboard.git] / libchess / move.c
blobf26e026f220601277627abe2f21f9fdb2d652d62
1 /* vim:tw=78:ts=8:sw=4:set ft=c: */
2 /*
3 Copyright (C) 2002-2006 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
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <ctype.h>
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
28 #include "chess.h"
29 #include "pgn.h"
30 #include "move.h"
32 #ifdef DEBUG
33 #include "debug.h"
34 #endif
36 #ifdef WITH_DMALLOC
37 #include <dmalloc.h>
38 #endif
40 static int val_piece_side(register char turn, register int c)
42 if ((isupper(c) && turn == WHITE) ||
43 (islower(c) && turn == BLACK))
44 return 1;
46 return 0;
49 static int count_piece(GAME g, BOARD b, register int piece, register int sfile,
50 register int srank, register int file, register int rank,
51 register int *count)
53 register int p = b[RANKTOBOARD(rank)][FILETOBOARD(file)].icon;
54 register int pi = pgn_piece_to_int(p);
56 if (!VALIDRANK(rank) || !VALIDFILE(file))
57 return 0;
59 if (pi != OPEN_SQUARE) {
60 if (pi == piece && val_piece_side(g.turn, p)) {
61 if (sfile && file == sfile) {
62 if (!srank || (srank && rank == srank))
63 (*count)++;
65 else if (srank && rank == srank) {
66 if (!sfile)
67 (*count)++;
69 else if (!sfile && !srank)
70 (*count)++;
73 return 1;
76 return 0;
80 * Get the source row and column for a given piece.
82 * The following two functions find 'piece' from the given square 'col' and
83 * 'row' and store the resulting column or row in 'c' and 'r'. The return
84 * value is the number of 'piece' found (on the current g.side) or zero.
85 * Search for 'piece' stops when a non-empty square is found.
87 static int count_by_diag(GAME g, BOARD b, register int piece,
88 register int sfile, register int srank, register int file,
89 register int rank)
91 int count = 0;
92 register int ul = 0, ur = 0, dl = 0, dr = 0;
93 register int i;
94 register int f, r;
96 for (i = 1; VALIDFILE(i); i++) {
97 r = rank + i;
98 f = file - i;
100 if (!ul && VALIDRANK(r) && VALIDFILE(f))
101 ul = count_piece(g, b, piece, sfile, srank, f, r, &count);
103 r = rank + i;
104 f = file + i;
106 if (!ur && VALIDRANK(r) && VALIDFILE(f))
107 ur = count_piece(g, b, piece, sfile, srank, f, r, &count);
109 r = rank - i;
110 f = file - i;
112 if (!dl && VALIDRANK(r) && VALIDFILE(f))
113 dl = count_piece(g, b, piece, sfile, srank, f, r, &count);
115 r = rank - i;
116 f = file + i;
118 if (!dr && VALIDRANK(r) && VALIDFILE(f))
119 dr = count_piece(g, b, piece, sfile, srank, f, r, &count);
122 return count;
125 static int count_knight(GAME g, BOARD b, int piece, int sfile, int srank,
126 int file, int rank)
128 int count = 0;
130 count_piece(g, b, piece, sfile, srank, file - 1, rank + 2, &count);
131 count_piece(g, b, piece, sfile, srank, file + 1, rank + 2, &count);
132 count_piece(g, b, piece, sfile, srank, file + 2, rank + 1, &count);
133 count_piece(g, b, piece, sfile, srank, file - 2, rank + 1, &count);
134 count_piece(g, b, piece, sfile, srank, file + 1, rank - 2, &count);
135 count_piece(g, b, piece, sfile, srank, file - 1, rank - 2, &count);
136 count_piece(g, b, piece, sfile, srank, file + 2, rank - 1, &count);
137 count_piece(g, b, piece, sfile, srank, file - 2, rank - 1, &count);
138 return count;
141 static int count_by_rank(GAME g, BOARD b, register int piece,
142 register int sfile, register int srank, register int file,
143 register int rank)
145 register int i;
146 int count = 0;
147 register int u = 0, d = 0;
149 for (i = 1; VALIDRANK(i); i++) {
150 if (!u && VALIDRANK((rank + i)))
151 u = count_piece(g, b, piece, sfile, srank, file, rank + i, &count);
153 if (!d && VALIDRANK((rank - i)))
154 d = count_piece(g, b, piece, sfile, srank, file, rank - i, &count);
156 if (d && u)
157 break;
160 return count;
163 static int count_by_file(GAME g, BOARD b, register int piece,
164 register int sfile, register int srank, register int file,
165 register int rank)
167 register int i;
168 int count = 0;
169 register int l = 0, r = 0;
171 for (i = 1; VALIDFILE(i); i++) {
172 if (!r && VALIDFILE((file + i)))
173 r = count_piece(g, b, piece, sfile, srank, file + i, rank, &count);
175 if (!l && VALIDFILE((file - i)))
176 l = count_piece(g, b, piece, sfile, srank, file - i, rank, &count);
178 if (r && l)
179 break;
182 return count;
185 static int count_by_rank_file(GAME g, BOARD b, register int piece,
186 register int sfile, register int srank, register int file,
187 register int rank)
189 register int count;
191 count = count_by_rank(g, b, piece, sfile, srank, file, rank);
192 return count + count_by_file(g, b, piece, sfile, srank, file, rank);
195 static int find_source_square(GAME, BOARD, int, int *, int *, int, int);
196 static int opponent_can_attack(GAME g, BOARD b, register int file,
197 register int rank)
199 int f, r;
200 register int p, pi;
201 int kf = kfile, kr = krank;
203 pgn_switch_turn(&g);
204 kfile = okfile, krank = okrank;
206 for (r = 1; VALIDRANK(r); r++) {
207 for (f = 1; VALIDFILE(f); f++) {
208 p = b[RANKTOBOARD(r)][FILETOBOARD(f)].icon;
209 pi = pgn_piece_to_int(p);
211 if (pi == OPEN_SQUARE || !val_piece_side(g.turn, p))
212 continue;
214 if (find_source_square(g, b, pi, &f, &r, file, rank) != 0) {
215 kfile = kf, krank = kr;
216 pgn_switch_turn(&g);
217 return 1;
222 kfile = kf, krank = kr;
223 pgn_switch_turn(&g);
224 return 0;
227 static int check_self(GAME g, BOARD b, int file, int rank);
228 static int validate_castle_move(GAME g, BOARD b, int side, int sfile,
229 int srank, int file, int rank)
231 int n;
233 if (side == KINGSIDE) {
234 if ((g.turn == WHITE && !TEST_FLAG(g.flags, GF_WK_CASTLE)) ||
235 (g.turn == BLACK && !TEST_FLAG(g.flags, GF_BK_CASTLE)))
236 return E_PGN_INVALID;
238 else {
239 if ((g.turn == WHITE && !TEST_FLAG(g.flags, GF_WQ_CASTLE)) ||
240 (g.turn == BLACK && !TEST_FLAG(g.flags, GF_BQ_CASTLE)))
241 return E_PGN_INVALID;
244 if (file > FILETOINT('e')) {
245 if (b[RANKTOBOARD(srank)][FILETOBOARD((sfile + 1))].icon
246 != pgn_int_to_piece(g.turn, OPEN_SQUARE) ||
247 b[RANKTOBOARD(srank)][FILETOBOARD((sfile + 2))].icon
248 != pgn_int_to_piece(g.turn, OPEN_SQUARE))
249 return E_PGN_INVALID;
251 if (opponent_can_attack(g, b, sfile + 1, srank))
252 return E_PGN_INVALID;
254 if (opponent_can_attack(g, b, sfile + 2, srank))
255 return E_PGN_INVALID;
257 else {
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 b[RANKTOBOARD(srank)][FILETOBOARD((sfile - 3))].icon !=
263 pgn_int_to_piece(g.turn, OPEN_SQUARE))
264 return E_PGN_INVALID;
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;
272 if (opponent_can_attack(g, b, sfile - 3, srank))
273 return E_PGN_INVALID;
276 n = check_testing;
277 check_testing = 1;
279 if (check_self(g, b, kfile, krank) == CHECK_SELF) {
280 check_testing = n;
281 return E_PGN_INVALID;
284 check_testing = n;
285 castle = side;
286 return E_PGN_OK;
289 static int validate_piece(GAME g, BOARD b, register int p, register int sfile,
290 register int srank, register int file, register int rank)
292 register int f, r;
293 register int i, dist;
295 switch (p) {
296 case PAWN:
297 if (sfile == file) {
298 /* Find the first pawn in the current column. */
299 i = (g.turn == WHITE) ? -1 : 1;
301 if (!srank) {
302 for (r = rank + i, dist = 0; VALIDFILE(r); r += i, dist++) {
303 p = b[RANKTOBOARD(r)][FILETOBOARD(file)].icon;
305 if (pgn_piece_to_int(p) != OPEN_SQUARE)
306 break;
309 if (pgn_piece_to_int(p) != PAWN || !val_piece_side(g.turn, p) || dist > 2)
310 return E_PGN_INVALID;
312 srank = r;
314 else {
315 dist = abs(srank - rank);
316 p = b[RANKTOBOARD(srank)][FILETOBOARD(sfile)].icon;
318 if (pgn_piece_to_int(p) != PAWN || !val_piece_side(g.turn, p) || dist > 2)
319 return E_PGN_INVALID;
322 if (g.turn == WHITE) {
323 if ((srank == 2 && dist > 2) || (srank > 2 && dist > 1))
324 return E_PGN_INVALID;
326 else {
327 if ((srank == 7 && dist > 2) || (srank < 7 && dist > 1))
328 return E_PGN_INVALID;
331 p = b[RANKTOBOARD(rank)][FILETOBOARD(file)].icon;
333 if (pgn_piece_to_int(p) != OPEN_SQUARE)
334 return E_PGN_INVALID;
336 else if (sfile != file) {
337 if (abs(sfile - file) != 1)
338 return E_PGN_INVALID;
340 srank = (g.turn == WHITE) ? rank - 1 : rank + 1;
341 p = b[RANKTOBOARD(srank)][FILETOBOARD(sfile)].icon;
343 if (!val_piece_side(g.turn, p))
344 return E_PGN_INVALID;
346 if (pgn_piece_to_int(p) != PAWN || abs(srank - rank) != 1)
347 return E_PGN_INVALID;
349 p = b[RANKTOBOARD(rank)][FILETOBOARD(file)].icon;
351 /* En Passant. */
352 if (pgn_piece_to_int(p) == OPEN_SQUARE) {
353 /* Previous move was not 2 squares and a pawn. */
354 if (!TEST_FLAG(g.flags, GF_ENPASSANT))
355 return E_PGN_INVALID;
357 if (!b[RANKTOBOARD(rank)][FILETOBOARD(file)].enpassant)
358 return E_PGN_INVALID;
360 r = (g.turn == WHITE) ? 6 : 3;
362 if (rank != r)
363 return E_PGN_INVALID;
365 r = (g.turn == WHITE) ? rank - 1 : rank + 1;
366 p = b[RANKTOBOARD(r)][FILETOBOARD(file)].icon;
368 if (pgn_piece_to_int(p) != PAWN)
369 return E_PGN_INVALID;
372 if (val_piece_side(g.turn, p))
373 return E_PGN_INVALID;
376 p = b[RANKTOBOARD(srank)][FILETOBOARD(sfile)].icon;
378 if (!val_piece_side(g.turn, p))
379 return E_PGN_INVALID;
381 break;
382 case KING:
383 f = abs(sfile - file);
384 r = abs(srank - rank);
386 if (r > 1 || f > 2)
387 return E_PGN_INVALID;
389 if (f == 2) {
390 if (sfile != FILETOINT('e'))
391 return E_PGN_INVALID;
392 else {
393 if (validate_castle_move(g, b, (file > FILETOINT('e')) ?
394 KINGSIDE : QUEENSIDE, sfile, srank, file, rank)
395 != E_PGN_OK)
396 return E_PGN_INVALID;
399 break;
400 default:
401 break;
404 return E_PGN_OK;
408 * Returns the number of pieces of type 'p' that can move to the destination
409 * square located at 'file' and 'rank'. The source square 'sfile' and 'srank'
410 * (if available in the move text) should be determined before calling this
411 * function or set to 0 if unknown. Returns 0 if the move is impossible for
412 * the piece 'p'.
414 static int validate_pawn(GAME g, BOARD b, int sfile, int srank, int file,
415 int rank);
416 static int find_ambiguous(GAME g, BOARD b, register int p, register int sfile,
417 register int srank, register int file, register int rank)
419 int count = 0;
421 switch (p) {
422 case PAWN:
423 count = validate_pawn(g, b, sfile, srank, file, rank);
424 break;
425 case ROOK:
426 count = count_by_rank_file(g, b, p, sfile, srank, file, rank);
427 break;
428 case KNIGHT:
429 count = count_knight(g, b, p, sfile, srank, file, rank);
430 break;
431 case BISHOP:
432 count = count_by_diag(g, b, p, sfile, srank, file, rank);
433 break;
434 case QUEEN:
435 count = count_by_rank_file(g, b, p, sfile, srank, file, rank);
436 count += count_by_diag(g, b, p, sfile, srank, file, rank);
437 break;
438 case KING:
439 count = count_by_rank_file(g, b, p, sfile, srank, file, rank);
440 count += count_by_diag(g, b, p, sfile, srank, file, rank);
441 break;
442 default:
443 break;
446 return count;
449 static void find_king_squares(GAME g, BOARD b, register int *file,
450 register int *rank, register int *ofile, register int *orank)
452 register int f, r;
454 for (r = 1; VALIDRANK(r); r++) {
455 for (f = 1; VALIDFILE(f); f++) {
456 register int p = b[RANKTOBOARD(r)][FILETOBOARD(f)].icon;
457 register int pi = pgn_piece_to_int(p);
459 if (pi == OPEN_SQUARE || pi != KING)
460 continue;
462 if (val_piece_side(g.turn, p))
463 *file = f, *rank = r;
464 else
465 *ofile = f, *orank = r;
470 static int parse_castle_move(GAME g, BOARD b, int side, int *sfile,
471 int *srank, int *file, int *rank)
473 *srank = *rank = (g.turn == WHITE) ? 1 : 8;
474 *sfile = FILETOINT('e');
476 if (side == KINGSIDE)
477 *file = FILETOINT('g');
478 else
479 *file = FILETOINT('c');
481 return validate_castle_move(g, b, side, *sfile, *srank, *file, *rank);
485 * Almost exactly like pgn_find_valid_moves() but returns immediately after a
486 * valid move is found.
488 static int check_mate_thingy(GAME g, BOARD b, int file, int rank)
490 register int r, f;
491 register int p = pgn_piece_to_int(b[RANKTOBOARD(rank)][FILETOBOARD(file)].icon);
493 for (r = 1; VALIDRANK(r); r++) {
494 for (f = 1; VALIDFILE(f); f++) {
495 if (val_piece_side(g.turn, b[RANKTOBOARD(r)][FILETOBOARD(f)].icon))
496 continue;
498 if (find_source_square(g, b, p, &file, &rank, f, r) != 0)
499 return 1;
503 return 0;
506 static int checkmate_test(GAME g, BOARD b)
508 register int f, r;
509 int n = CHECK_MATE;
510 register int kf = kfile, kr = krank, okf = okfile, okr = okrank;
513 * The king squares need to be switched also for find_source_squares()
514 * which calls check_self().
516 pgn_switch_turn(&g);
517 kfile = okfile, krank = okrank;
519 for (r = 1; VALIDRANK(r); r++) {
520 for (f = 1; VALIDFILE(f); f++) {
521 int p, pi;
522 int sfile = f, srank = r;
524 p = b[RANKTOBOARD(r)][FILETOBOARD(f)].icon;
525 pi = pgn_piece_to_int(p);
527 if (p == OPEN_SQUARE || !val_piece_side(g.turn, p))
528 continue;
530 if (check_mate_thingy(g, b, sfile, srank)) {
531 n = CHECK;
532 goto done;
537 done:
538 pgn_switch_turn(&g);
539 kfile = kf, krank = kr, okfile = okf, okrank = okr;
540 return n;
543 static int find_source_square(GAME, BOARD, int, int *, int *, int, int);
544 static int check_opponent(GAME g, BOARD b, register int file, register int rank)
546 int f, r;
547 register int p, pi;
549 for (r = 1; VALIDRANK(r); r++) {
550 for (f = 1; VALIDFILE(f); f++) {
551 p = b[RANKTOBOARD(r)][FILETOBOARD(f)].icon;
552 pi = pgn_piece_to_int(p);
554 if (pi == OPEN_SQUARE || !val_piece_side(g.turn, p))
555 continue;
557 if (find_source_square(g, b, pi, &f, &r, file, rank) != 0)
558 return CHECK;
562 return 0;
565 static int check_self(GAME g, BOARD b, int file, int rank)
567 int f, r;
568 register int p, pi;
570 pgn_switch_turn(&g);
572 for (r = 1; VALIDRANK(r); r++) {
573 for (f = 1; VALIDFILE(f); f++) {
574 p = b[RANKTOBOARD(r)][FILETOBOARD(f)].icon;
575 pi = pgn_piece_to_int(p);
577 if (pi == OPEN_SQUARE || !val_piece_side(g.turn, p))
578 continue;
580 if (find_source_square(g, b, pi, &f, &r, file, rank) != 0) {
581 pgn_switch_turn(&g);
582 return CHECK_SELF;
587 pgn_switch_turn(&g);
588 return check;
591 static int check_test(GAME g, BOARD b)
593 check = check_opponent(g, b, okfile, okrank);
595 if (check)
596 return checkmate_test(g, b);
598 check_testing = 0;
599 return check;
602 static int validate_pawn(GAME g, BOARD b, register int sfile,
603 register int srank, register int file, register int rank)
605 register int n = abs(srank - rank);
606 register int p;
608 if (abs(sfile - file) > 1)
609 return 0;
611 if (g.turn == WHITE) {
612 if ((srank == 2 && n > 2) || (srank > 2 && n > 1))
613 return 0;
615 else {
616 if ((srank == 7 && n > 2) || (srank < 7 && n > 1))
617 return 0;
620 if (n > 1 && abs(sfile - file) != 0)
621 return 0;
623 p = b[RANKTOBOARD(srank)][FILETOBOARD(sfile)].icon;
625 if (!val_piece_side(g.turn, p) || pgn_piece_to_int(p) != PAWN)
626 return 0;
628 if (srank == rank || (g.turn == WHITE && rank < srank) ||
629 (g.turn == BLACK && rank > srank))
630 return 0;
632 if (sfile == file) {
633 p = b[RANKTOBOARD(rank)][FILETOBOARD(file)].icon;
635 if (pgn_piece_to_int(p) != OPEN_SQUARE)
636 return 0;
638 p = (g.turn == WHITE) ? rank - 1 : rank + 1;
639 p = b[RANKTOBOARD(p)][FILETOBOARD(file)].icon;
641 if (n > 1 && pgn_piece_to_int(p) != OPEN_SQUARE)
642 return 0;
644 return 1;
647 p = b[RANKTOBOARD(rank)][FILETOBOARD(file)].icon;
649 if (pgn_piece_to_int(p) != OPEN_SQUARE) {
650 if (val_piece_side(g.turn, p))
651 return 0;
653 return 1;
656 /* En Passant. */
657 p = (g.turn == WHITE) ? rank - 1 : rank + 1;
658 p = b[RANKTOBOARD(p)][FILETOBOARD(file)].icon;
660 if (pgn_piece_to_int(p) == OPEN_SQUARE || val_piece_side(g.turn, p))
661 return 0;
663 /* Previous move was not 2 squares and a pawn. */
664 if (!TEST_FLAG(g.flags, GF_ENPASSANT) ||
665 b[RANKTOBOARD(rank)][FILETOBOARD(file)].enpassant == 0)
666 return 0;
669 // GF_ENPASSANT should take care of this
670 if (!b[RANKTOBOARD(rank)][FILETOBOARD(file)].enpassant)
671 return 0;
674 if ((g.turn == WHITE && rank != 6) || (g.turn == BLACK && rank != 3))
675 return 0;
677 #if 0
678 // GF_ENPASSANT should take care of this
679 n = (g.turn == WHITE) ? rank - 1 : rank + 1;
680 p = b[RANKTOBOARD(n)][FILETOBOARD(file)].icon;
682 if (pgn_piece_to_int(p) != PAWN)
683 return 0;
685 if (val_piece_side(g.turn, p))
686 return 0;
687 #endif
689 return 1;
692 static int finalize_move(GAME *g, BOARD b, int promo, int sfile, int srank,
693 int file, int rank);
694 static int find_source_square(GAME g, BOARD b, int piece, int *sfile,
695 int *srank, register int file, register int rank)
697 int p = 0;
698 register int r, f, i;
699 register int dist = 0;
700 int count = 0;
702 if (piece == PAWN) {
703 if (!*srank && *sfile == file) {
704 /* Find the first pawn in 'file'. */
705 i = (g.turn == WHITE) ? -1 : 1;
707 for (r = rank + i, dist = 0; VALIDFILE(r); r += i, dist++) {
708 p = pgn_piece_to_int(b[RANKTOBOARD(r)][FILETOBOARD(file)].icon);
710 if (p != OPEN_SQUARE)
711 break;
714 *srank = r;
717 if (!*srank)
718 *srank = (g.turn == WHITE) ? rank - 1 : rank + 1;
720 if (!validate_pawn(g, b, *sfile, *srank, file, rank))
721 return 0;
722 else
723 count = 1;
725 else {
726 if (*sfile && *srank)
727 count = find_ambiguous(g, b, piece, *sfile, *srank, file, rank);
728 else {
729 for (r = 1; VALIDRANK(r); r++) {
730 for (f = 1; VALIDFILE(f); f++) {
731 int n;
733 if ((*sfile && f != *sfile) || (*srank && r != *srank))
734 continue;
736 n = find_ambiguous(g, b, piece, f, r, file, rank);
738 if (n) {
739 count += n;
740 *sfile = f;
741 *srank = r;
747 if (validate_piece(g, b, piece, *sfile, *srank, file, rank) != E_PGN_OK)
748 return 0;
751 if (count != 1)
752 return count;
754 if (!check_testing) {
755 BOARD tmpb;
756 GAME newg;
757 int oldv = validate;
758 int nkfile, nkrank, nokfile, nokrank;
760 validate = 0;
761 check_testing = 1;
762 memcpy(tmpb, b, sizeof(BOARD));
763 memcpy(&newg, &g, sizeof(GAME));
765 if (finalize_move(&newg, tmpb, 0, *sfile, *srank, file, rank)
766 != E_PGN_OK) {
767 check_testing = 0;
768 validate = oldv;
769 return 0;
772 if (piece == KING)
773 find_king_squares(newg, tmpb, &nkfile, &nkrank, &nokfile, &nokrank);
774 else
775 nkfile = kfile, nkrank = krank;
777 if (check_self(newg, tmpb, nkfile, nkrank) == CHECK_SELF) {
778 check_testing = 0;
779 validate = oldv;
780 return 0;
783 validate = oldv;
784 check_testing = 0;
787 return count;
790 static int finalize_move(GAME *g, BOARD b, int promo, int sfile, int srank,
791 int file, int rank)
793 int p, pi;
795 p = b[RANKTOBOARD(rank)][FILETOBOARD(file)].icon;
796 pi = pgn_piece_to_int(p);
798 if (pi != OPEN_SQUARE && val_piece_side(g->turn, p))
799 return E_PGN_INVALID;
801 if (!validate) {
802 pgn_reset_enpassant(b);
803 p = b[RANKTOBOARD(srank)][FILETOBOARD(sfile)].icon;
804 pi = pgn_piece_to_int(p);
806 if (pi == PAWN) {
807 p = b[RANKTOBOARD(rank)][FILETOBOARD(file)].icon;
809 if (sfile != file && pgn_piece_to_int(p) == OPEN_SQUARE &&
810 TEST_FLAG(g->flags, GF_ENPASSANT)) {
811 p = (g->turn == WHITE) ? rank - 1 : rank + 1;
812 b[RANKTOBOARD(p)][FILETOBOARD(file)].icon =
813 pgn_int_to_piece(g->turn, OPEN_SQUARE);
816 if (abs(srank - rank) > 1) {
817 SET_FLAG(g->flags, GF_ENPASSANT);
818 b[RANKTOBOARD(((g->turn == WHITE) ? rank - 1 : rank + 1))][FILETOBOARD(file)].enpassant = 1;
821 else if (pi == ROOK) {
822 if (g->turn == WHITE) {
823 if (sfile == FILETOINT('h'))
824 CLEAR_FLAG(g->flags, GF_WK_CASTLE);
825 else if (sfile == FILETOINT('a'))
826 CLEAR_FLAG(g->flags, GF_WQ_CASTLE);
828 else {
829 if (sfile == FILETOINT('h'))
830 CLEAR_FLAG(g->flags, GF_BK_CASTLE);
831 else if (sfile == FILETOINT('a'))
832 CLEAR_FLAG(g->flags, GF_BQ_CASTLE);
836 if (pi != PAWN)
837 CLEAR_FLAG(g->flags, GF_ENPASSANT);
839 if (pi == KING && !castle) {
840 if (g->turn == WHITE)
841 CLEAR_FLAG(g->flags, GF_WK_CASTLE|GF_WQ_CASTLE);
842 else
843 CLEAR_FLAG(g->flags, GF_BK_CASTLE|GF_BQ_CASTLE);
846 if (castle) {
847 p = b[RANKTOBOARD(srank)][FILETOBOARD(sfile)].icon;
848 b[RANKTOBOARD(srank)][FILETOBOARD(sfile)].icon =
849 pgn_int_to_piece(g->turn, OPEN_SQUARE);
850 b[RANKTOBOARD(srank)][FILETOBOARD(
851 (file > FILETOINT('e') ? 8 : 1))].icon =
852 pgn_int_to_piece(g->turn, OPEN_SQUARE);
853 b[RANKTOBOARD(rank)][FILETOBOARD(file)].icon = p;
855 if (file > FILETOINT('e'))
856 b[RANKTOBOARD(rank)][FILETOBOARD((file - 1))].icon =
857 pgn_int_to_piece(g->turn, ROOK);
858 else
859 b[RANKTOBOARD(rank)][FILETOBOARD((file + 1))].icon =
860 pgn_int_to_piece(g->turn, ROOK);
862 if (g->turn == WHITE)
863 CLEAR_FLAG(g->flags, (file > FILETOINT('e')) ? GF_WK_CASTLE :
864 GF_WQ_CASTLE);
865 else
866 CLEAR_FLAG(g->flags, (file > FILETOINT('e')) ? GF_BK_CASTLE :
867 GF_BQ_CASTLE);
869 else {
870 if (promo)
871 p = pgn_int_to_piece(g->turn, promo);
872 else
873 p = b[RANKTOBOARD(srank)][FILETOBOARD(sfile)].icon;
875 b[RANKTOBOARD(srank)][FILETOBOARD(sfile)].icon =
876 pgn_int_to_piece(g->turn, OPEN_SQUARE);
877 b[RANKTOBOARD(rank)][FILETOBOARD(file)].icon = p;
881 castle = 0;
883 if (!check_testing) {
884 if (pgn_piece_to_int(p) == KING)
885 find_king_squares(*g, b, &kfile, &krank, &okfile, &okrank);
887 switch (check_test(*g, b)) {
888 case CHECK:
889 check = CHECK;
890 break;
891 case CHECK_MATE:
892 check = CHECK_MATE;
894 if (!validate) {
895 pgn_tag_add(&g->tag, "Result",
896 (g->turn == WHITE) ? "1-0" : "0-1");
897 SET_FLAG(g->flags, GF_GAMEOVER);
899 break;
900 default:
901 break;
904 // FIXME RAV
905 if (!validate && (pgn_fen_tag > 0 && !done_fen_tag) &&
906 !pgn_history_total(g->hp) && srank >= 7)
907 SET_FLAG(g->flags, GF_BLACK_OPENING);
910 if (!validate) {
911 p = pgn_piece_to_int(p);
913 if (p == PAWN || promo || capture)
914 g->ply = 0;
915 else
916 g->ply++;
918 if (g->ply / 2 == 50) {
919 pgn_tag_add(&g->tag, "Result", "1-2/1-2");
920 SET_FLAG(g->flags, GF_GAMEOVER);
924 return E_PGN_OK;
928 * Converts a2a3 formatted moves to SAN format. The promotion piece should be
929 * appended (a7a8q).
931 static int frfrtosan(GAME *g, BOARD b, char **m)
933 char *bp = *m;
934 int icon, p, dp, promo = 0;
935 int sfile, srank, file, rank;
936 int n;
937 int fc, rc;
939 sfile = FILETOINT(bp[0]);
940 srank = RANKTOINT(bp[1]);
941 file = FILETOINT(bp[2]);
942 rank = RANKTOINT(bp[3]);
944 if (bp[4]) {
945 if ((promo = pgn_piece_to_int(bp[4])) == -1 || promo == OPEN_SQUARE)
946 return E_PGN_PARSE;
949 icon = b[RANKTOBOARD(srank)][FILETOBOARD(sfile)].icon;
951 if ((p = pgn_piece_to_int(icon)) == -1 || p == OPEN_SQUARE)
952 return 0;
954 if (p != PAWN && promo)
955 return E_PGN_INVALID;
957 if (p == PAWN) {
958 if (find_source_square(*g, b, p, &sfile, &srank, file, rank) != 1)
959 return E_PGN_INVALID;
961 goto capture;
964 if (validate_piece(*g, b, p, sfile, srank, file, rank) != E_PGN_OK)
965 return E_PGN_INVALID;
967 if (p == KING && abs(sfile - file) > 1) {
968 strcpy(bp, (file > FILETOINT('e')) ? "O-O" : "O-O-O");
970 if (finalize_move(g, b, promo, sfile, srank, file, rank) != E_PGN_OK)
971 return E_PGN_INVALID;
973 if (check)
974 strcat(bp, (check == CHECK) ? "+" : "#");
976 return E_PGN_OK;
979 *bp++ = toupper(icon);
980 n = find_ambiguous(*g, b, p, 0, 0, file, rank);
982 if (!n)
983 return E_PGN_INVALID;
984 else if (n > 1) {
985 fc = find_ambiguous(*g, b, p, sfile, 0, file, rank);
986 rc = find_ambiguous(*g, b, p, 0, srank, file, rank);
988 if (fc == 1)
989 *bp++ = INTTOFILE(sfile);
990 else if (!fc && rc)
991 *bp++ = INTTORANK(srank);
992 else if (fc && rc) {
993 if (rc == 1)
994 *bp++ = INTTORANK(srank);
995 else {
996 *bp++ = INTTOFILE(sfile);
997 *bp++ = INTTORANK(srank);
1000 else
1001 return E_PGN_PARSE; // not reached.
1004 capture:
1005 icon = b[RANKTOBOARD(rank)][FILETOBOARD(file)].icon;
1007 if ((dp = pgn_piece_to_int(icon)) == -1)
1008 return E_PGN_PARSE;
1011 * [Pf][fr]x
1013 if (dp != OPEN_SQUARE || (dp == OPEN_SQUARE && p == PAWN && sfile != file)) {
1014 if (p == PAWN)
1015 *bp++ = INTTOFILE(sfile);
1017 *bp++ = 'x';
1021 * [Pf][fr][x]fr
1024 *bp++ = INTTOFILE(file);
1025 *bp++ = INTTORANK(rank);
1028 * [Pf][fr][x]fr[=P]
1030 if (promo) {
1031 if (p != PAWN || (g->turn == WHITE && (srank != 7 || rank != 8)) ||
1032 (g->turn == BLACK && (srank != 2 || rank != 1)))
1033 return E_PGN_INVALID;
1035 *bp++ = '=';
1036 *bp++ = pgn_int_to_piece(WHITE, promo);
1039 *bp = 0;
1041 if (find_source_square(*g, b, p, &sfile, &srank, file, rank) != 1)
1042 return E_PGN_INVALID;
1044 if (finalize_move(g, b, promo, sfile, srank, file, rank) != E_PGN_OK)
1045 return E_PGN_INVALID;
1047 if (check)
1048 *bp++ = (check == CHECK) ? '+' : '#';
1050 *bp = check = 0;
1051 return E_PGN_OK;
1055 * Valididate move 'mp' against the game state 'g' and game board 'b' and
1056 * update board 'b'. 'mp' is updated to SAN format for moves which aren't
1057 * (frfr or e8Q for example). Returns E_PGN_PARSE if there was a move text
1058 * parsing error, E_PGN_INVALID if the move is invalid or E_PGN_OK if
1059 * successful.
1061 int pgn_parse_move(GAME *g, BOARD b, char **mp)
1063 char *p;
1064 int piece;
1065 int i = 0;
1066 int srank = 0, sfile = 0, rank, file;
1067 int promo = -1;
1068 char *m = *mp;
1070 capture = check_testing = 0;
1071 srank = rank = file = sfile = promo = piece = 0;
1072 find_king_squares(*g, b, &kfile, &krank, &okfile, &okrank);
1074 if (VALIDCOL(*m) && VALIDROW(*(m + 1)) && VALIDCOL(*(m + 2)) &&
1075 VALIDROW(*(m + 3)))
1076 return frfrtosan(g, b, mp);
1077 else if (*m == 'O') {
1078 if (strcmp(m, "O-O") == 0)
1079 i = KINGSIDE;
1080 else if (strcmp(m, "O-O-O") == 0)
1081 i = QUEENSIDE;
1082 else
1083 return E_PGN_PARSE;
1085 if (parse_castle_move(*g, b, i, &sfile, &srank, &file, &rank) !=
1086 E_PGN_OK)
1087 return E_PGN_INVALID;
1089 *m++ = INTTOFILE(sfile);
1090 *m++ = INTTORANK(srank);
1091 *m++ = INTTOFILE(file);
1092 *m++ = INTTORANK(rank);
1093 *m = 0;
1094 return frfrtosan(g, b, mp);
1097 again:
1098 if (strlen(m) < 2)
1099 return E_PGN_PARSE;
1101 p = (m) + strlen(m);
1103 while (!isdigit(*--p) && *p != 'O') {
1104 if (*p == '=') {
1105 promo = toupper(pgn_piece_to_int(i));
1106 i = 0;
1107 break;
1110 i = *p;
1111 *p = '\0';
1114 /* Alternate promotion text (e8Q). Convert to SAN. */
1115 if (pgn_piece_to_int(i) != -1) {
1116 p = (m) + strlen(m);
1117 *p++ = '=';
1118 *p++ = toupper(i);
1119 *p = '\0';
1120 goto again;
1123 p = m;
1125 /* Skip 'P' (pawn). */
1126 if (pgn_piece_to_int(*p) == PAWN)
1127 p++;
1129 /* Pawn. */
1130 if (VALIDCOL(*p)) {
1131 for (i = 0; *p; i++) {
1132 if (VALIDCOL(*p)) {
1133 if (i > 0)
1134 file = FILETOINT(*p++);
1135 else
1136 file = sfile = FILETOINT(*p++);
1138 else if (VALIDROW(*p)) {
1139 if (1 > 1)
1140 rank = RANKTOINT(*p++);
1141 else
1142 rank = RANKTOINT(*p++);
1144 else if (*p == 'x') {
1145 file = FILETOINT(*++p);
1146 rank = RANKTOINT(*++p);
1147 capture++;
1149 else if (*p == '=') {
1150 if (promo == -1 || promo == KING || promo == PAWN)
1151 return E_PGN_PARSE;
1153 *p++ = '=';
1154 *p++ = toupper(pgn_int_to_piece(g->turn, promo));
1155 *p = '\0';
1156 break;
1158 else {
1159 #ifdef DEBUG
1160 DUMP("Pawn (move: '%s'): %c\n", m, *p++);
1161 #else
1162 p++;
1163 #endif
1167 if (find_source_square(*g, b, PAWN, &sfile, &srank, file, rank) != 1)
1168 return E_PGN_INVALID;
1170 /* Not a pawn. */
1171 else {
1172 p = m;
1175 * P[fr][x]fr
1177 * The first character is the piece but only if not a pawn.
1179 if ((piece = pgn_piece_to_int(*p++)) == -1)
1180 return E_PGN_PARSE;
1183 * [fr]fr
1185 if (strlen(m) > 3) {
1187 * rfr
1189 if (isdigit(*p))
1190 srank = RANKTOINT(*p++);
1192 * f[r]fr
1194 else if (VALIDCOL(*p)) {
1195 sfile = FILETOINT(*p++);
1198 * frfr
1200 if (isdigit(*p))
1201 srank = RANKTOINT(*p++);
1205 * xfr
1207 if (*p == 'x') {
1208 capture++;
1209 p++;
1214 * fr
1216 * The destination square.
1218 file = FILETOINT(*p++);
1219 rank = RANKTOINT(*p++);
1221 if (*p == '=')
1222 promo == *++p;
1224 if ((i = find_ambiguous(*g, b, piece, sfile, srank, file, rank))
1225 != 1)
1226 return (i == 0) ? E_PGN_INVALID : E_PGN_AMBIGUOUS;
1229 * The move is a valid one. Find the source file and rank so we
1230 * can later update the board positions.
1232 if (find_source_square(*g, b, piece, &sfile, &srank, file, rank)
1233 != 1 && !check)
1234 return E_PGN_INVALID;
1237 *p = 0;
1239 if (finalize_move(g, b, promo, sfile, srank, file, rank) != E_PGN_OK)
1240 return E_PGN_INVALID;
1242 if (check)
1243 *p++ = (check == CHECK) ? '+' : '#';
1245 *p = check = 0;
1246 *mp = m;
1247 return E_PGN_OK;
1251 * Like pgn_parse_move() but don't modify game flags in 'g' or board 'b'.
1253 int pgn_validate_move(GAME *g, BOARD b, char **m)
1255 int ret;
1257 validate = 1;
1258 ret = pgn_parse_move(g, b, m);
1259 validate = 0;
1260 return ret;
1264 * Sets valid moves from game 'g' using board 'b'. The valid moves are for the
1265 * piece on the board 'b' at 'rank' and 'file'. Returns nothing.
1267 void pgn_find_valid_moves(GAME g, BOARD b, int file, int rank)
1269 register int p = pgn_piece_to_int(b[RANKTOBOARD(rank)][FILETOBOARD(file)].icon);
1270 register int r, f;
1272 find_king_squares(g, b, &kfile, &krank, &okfile, &okrank);
1274 for (r = 1; VALIDRANK(r); r++) {
1275 for (f = 1; VALIDFILE(f); f++) {
1276 if (val_piece_side(g.turn, b[RANKTOBOARD(r)][FILETOBOARD(f)].icon))
1277 continue;
1279 if (find_source_square(g, b, p, &file, &rank, f, r) != 0)
1280 b[RANKTOBOARD(r)][FILETOBOARD(f)].valid = 1;
1284 check_testing = 0;