Autotools portability fixes and cleanups.
[cboard.git] / libchess / move.c
blobfcb74fd23c4609bc913c783eb2a3b3568a926941
1 /* vim:tw=78:ts=8:sw=4:set ft=c: */
2 /*
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
19 #ifdef HAVE_CONFIG_H
20 #include <config.h>
21 #endif
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <ctype.h>
28 #include "chess.h"
29 #include "common.h"
31 #ifdef DEBUG
32 #include "debug.h"
33 #endif
35 #ifdef WITH_DMALLOC
36 #include <dmalloc.h>
37 #endif
39 enum {
40 CHECK = 1,
41 CHECK_SELF,
42 CHECK_MATE
45 enum {
46 KINGSIDE = 1, QUEENSIDE
49 static int finalize_move(GAME g, BOARD b, int promo, int sfile, int srank,
50 int file, int rank);
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,
54 int rank);
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))
61 return 1;
63 return 0;
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,
68 register int *count)
70 register int p, pi;
72 if (!VALIDRANK(rank) || !VALIDFILE(file))
73 return 0;
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))
82 (*count)++;
84 else if (srank && rank == srank) {
85 if (!sfile)
86 (*count)++;
88 else if (!sfile && !srank)
89 (*count)++;
92 return 1;
95 return 0;
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,
108 register int rank)
110 int count = 0;
111 register int ul = 0, ur = 0, dl = 0, dr = 0;
112 register int i;
113 register int f, r;
115 for (i = 1; VALIDFILE(i); i++) {
116 r = rank + i;
117 f = file - i;
119 if (!ul && VALIDRANK(r) && VALIDFILE(f))
120 ul = count_piece(g, b, piece, sfile, srank, f, r, &count);
122 r = rank + i;
123 f = file + i;
125 if (!ur && VALIDRANK(r) && VALIDFILE(f))
126 ur = count_piece(g, b, piece, sfile, srank, f, r, &count);
128 r = rank - i;
129 f = file - i;
131 if (!dl && VALIDRANK(r) && VALIDFILE(f))
132 dl = count_piece(g, b, piece, sfile, srank, f, r, &count);
134 r = rank - i;
135 f = file + i;
137 if (!dr && VALIDRANK(r) && VALIDFILE(f))
138 dr = count_piece(g, b, piece, sfile, srank, f, r, &count);
141 return count;
144 static int count_knight(GAME g, BOARD b, int piece, int sfile, int srank,
145 int file, int rank)
147 int count = 0;
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);
157 return count;
160 static int count_by_rank(GAME g, BOARD b, register int piece,
161 register int sfile, register int srank, register int file,
162 register int rank)
164 register int i;
165 int count = 0;
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);
175 if (d && u)
176 break;
179 return count;
182 static int count_by_file(GAME g, BOARD b, register int piece,
183 register int sfile, register int srank, register int file,
184 register int rank)
186 register int i;
187 int count = 0;
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);
197 if (r && l)
198 break;
201 return 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,
206 register int rank)
208 register int count;
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,
215 register int rank)
217 int f, r;
218 register int p, pi;
219 int kf = g->kfile, kr = g->krank;
221 pgn_switch_turn(g);
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))
230 continue;
232 if (find_source_square(g, b, pi, &f, &r, file, rank) != 0) {
233 g->kfile = kf, g->krank = kr;
234 pgn_switch_turn(g);
235 return 1;
240 g->kfile = kf, g->krank = kr;
241 pgn_switch_turn(g);
242 return 0;
245 static int validate_castle_move(GAME g, BOARD b, int side, int sfile,
246 int srank, int file, int rank)
248 int n;
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;
255 else {
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;
277 else {
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;
307 g->castle = side;
308 return E_PGN_OK;
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)
314 register int f, r;
315 register int i, dist;
317 switch (p) {
318 case PAWN:
319 if (sfile == file) {
320 /* Find the first pawn in the current column. */
321 i = (g->turn == WHITE) ? -1 : 1;
323 if (!srank) {
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)
328 break;
331 if (pgn_piece_to_int(p) != PAWN || !val_piece_side(g->turn, p) || dist > 2)
332 return E_PGN_INVALID;
334 srank = r;
336 else {
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;
348 else {
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;
373 /* En Passant. */
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;
384 if (rank != r)
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;
403 break;
404 case KING:
405 f = abs(sfile - file);
406 r = abs(srank - rank);
408 if (r > 1 || f > 2)
409 return E_PGN_INVALID;
411 if (f == 2) {
412 if (sfile != FILETOINT('e'))
413 return E_PGN_INVALID;
414 else {
415 if (validate_castle_move(g, b, (file > FILETOINT('e')) ?
416 KINGSIDE : QUEENSIDE, sfile, srank, file, rank)
417 != E_PGN_OK)
418 return E_PGN_INVALID;
421 break;
422 default:
423 break;
426 return E_PGN_OK;
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
434 * the piece 'p'.
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)
439 int count = 0;
441 switch (p) {
442 case PAWN:
443 count = validate_pawn(g, b, sfile, srank, file, rank);
444 break;
445 case ROOK:
446 count = count_by_rank_file(g, b, p, sfile, srank, file, rank);
447 break;
448 case KNIGHT:
449 count = count_knight(g, b, p, sfile, srank, file, rank);
450 break;
451 case BISHOP:
452 count = count_by_diag(g, b, p, sfile, srank, file, rank);
453 break;
454 case QUEEN:
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);
457 break;
458 case KING:
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);
461 break;
462 default:
463 break;
466 return count;
469 static void find_king_squares(GAME g, BOARD b, register int *file,
470 register int *rank, register int *ofile, register int *orank)
472 register int f, r;
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)
480 continue;
482 if (val_piece_side(g->turn, p))
483 *file = f, *rank = r;
484 else
485 *ofile = f, *orank = r;
489 #ifdef DEBUG
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));
493 #endif
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');
504 else
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)
516 register int r, f;
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))
522 continue;
524 if (find_source_square(g, b, p, &file, &rank, f, r) != 0)
525 return 1;
529 return 0;
532 static int checkmate_test(GAME g, BOARD b)
534 register int f, r;
535 int n = CHECK_MATE;
536 register int kf = g->kfile, kr = g->krank, okf = g->okfile, okr = g->okrank;
538 #ifdef DEBUG
539 PGN_DUMP("%s:%d: BEGIN checkmate test\n", __FILE__, __LINE__);
540 #endif
542 * The king squares need to be switched also for find_source_squares()
543 * which calls check_self().
545 pgn_switch_turn(g);
546 g->kfile = g->okfile, g->krank = g->okrank;
548 for (r = 1; VALIDRANK(r); r++) {
549 for (f = 1; VALIDFILE(f); f++) {
550 int p;
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))
556 continue;
558 if (check_mate_thingy(g, b, sfile, srank)) {
559 n = CHECK;
560 goto done;
565 done:
566 pgn_switch_turn(g);
567 g->kfile = kf, g->krank = kr, g->okfile = okf, g->okrank = okr;
568 return n;
571 static int check_opponent(GAME g, BOARD b, register int file, register int rank)
573 int f, r;
574 register int p, pi;
576 #ifdef DEBUG
577 PGN_DUMP("%s:%d: BEGIN opponent check test\n");
578 #endif
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))
586 continue;
588 if (find_source_square(g, b, pi, &f, &r, file, rank) != 0)
589 return CHECK;
593 return 0;
596 static int check_self(GAME g, BOARD b, int file, int rank)
598 int f, r;
599 register int p, pi;
601 #ifdef DEBUG
602 PGN_DUMP("%s:%d: BEGIN self check test\n", __FILE__, __LINE__);
603 #endif
604 pgn_switch_turn(g);
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))
612 continue;
614 if (find_source_square(g, b, pi, &f, &r, file, rank) != 0) {
615 pgn_switch_turn(g);
616 return CHECK_SELF;
621 pgn_switch_turn(g);
622 return g->check;
625 static int check_test(GAME g, BOARD b)
627 #ifdef DEBUG
628 PGN_DUMP("%s:%d: BEGIN check test\n", __FILE__, __LINE__);
629 #endif
630 g->check = check_opponent(g, b, g->okfile, g->okrank);
632 if (g->check)
633 return checkmate_test(g, b);
635 g->check_testing = 0;
636 return g->check;
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);
643 register int p;
645 if (abs(sfile - file) > 1)
646 return 0;
648 if (g->turn == WHITE) {
649 if ((srank == 2 && n > 2) || (srank > 2 && n > 1))
650 return 0;
652 else {
653 if ((srank == 7 && n > 2) || (srank < 7 && n > 1))
654 return 0;
657 if (n > 1 && abs(sfile - file) != 0)
658 return 0;
660 p = b[RANKTOBOARD(srank)][FILETOBOARD(sfile)].icon;
662 if (!val_piece_side(g->turn, p) || pgn_piece_to_int(p) != PAWN)
663 return 0;
665 if (srank == rank || (g->turn == WHITE && rank < srank) ||
666 (g->turn == BLACK && rank > srank))
667 return 0;
669 if (sfile == file) {
670 p = b[RANKTOBOARD(rank)][FILETOBOARD(file)].icon;
672 if (pgn_piece_to_int(p) != OPEN_SQUARE)
673 return 0;
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)
679 return 0;
681 return 1;
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))
688 return 0;
690 return 1;
693 /* En Passant. */
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))
698 return 0;
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)
703 return 0;
706 // GF_ENPASSANT should take care of this
707 if (!b[RANKTOBOARD(rank)][FILETOBOARD(file)].enpassant)
708 return 0;
711 if ((g->turn == WHITE && rank != 6) || (g->turn == BLACK && rank != 3))
712 return 0;
714 #if 0
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)
720 return 0;
722 if (val_piece_side(g->turn, p))
723 return 0;
724 #endif
726 return 1;
729 static int check_self_test(GAME g, BOARD b, int p, int sfile, int srank,
730 int file, int rank)
732 BOARD tmpb;
733 struct game_s newg;
734 int oldv = g->validate;
735 int nkfile, nkrank, nokfile, nokrank;
736 int go;
738 g->validate = 0;
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)
744 != E_PGN_OK) {
745 g->check_testing = 0;
746 g->validate = oldv;
747 return 0;
750 if (p == KING)
751 find_king_squares(&newg, tmpb, &nkfile, &nkrank, &nokfile, &nokrank);
752 else
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;
760 g->validate = oldv;
761 return 0;
764 if (!g->validate && !g->validate_find) {
765 g->flags = newg.flags;
767 if (go)
768 SET_FLAG(g->flags, GF_GAMEOVER);
771 g->validate = oldv;
772 g->check_testing = 0;
773 return 1;
776 static int find_source_square(GAME g, BOARD b, int piece, int *sfile,
777 int *srank, register int file, register int rank)
779 int p = 0;
780 register int r, f, i;
781 register int dist = 0;
782 int count = 0;
784 #ifdef DEBUG
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),
789 INTTORANK(rank));
790 #endif
792 if (piece == PAWN) {
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)
801 break;
804 *srank = r;
807 if (!*srank)
808 *srank = (g->turn == WHITE) ? rank - 1 : rank + 1;
810 if (!validate_pawn(g, b, *sfile, *srank, file, rank))
811 return 0;
812 else
813 count = 1;
815 if (!g->check_testing) {
816 if (check_self_test(g, b, piece, *sfile, *srank, file, rank) == 0)
817 return 0;
820 else {
821 if (*sfile && *srank) {
822 count = find_ambiguous(g, b, piece, *sfile, *srank, file, rank);
824 if (count != 1)
825 return count;
827 if (!g->check_testing) {
828 if (check_self_test(g, b, piece, *sfile, *srank, file, rank)
829 == 0)
830 return 0;
833 else {
834 int ff = *sfile, rr = *srank;
836 for (r = 1; VALIDRANK(r); r++) {
837 for (f = 1; VALIDFILE(f); f++) {
838 int n;
840 if ((*sfile && f != ff) || (*srank && r != rr))
841 continue;
843 n = find_ambiguous(g, b, piece, f, r, file, rank);
845 if (n) {
846 if (!g->check_testing &&
847 check_self_test(g, b, piece, f, r, file, rank) == 0)
848 continue;
850 count += n;
851 ff = f;
852 rr = r;
857 if (count == 1) {
858 *sfile = ff;
859 *srank = rr;
863 if (validate_piece(g, b, piece, *sfile, *srank, file, rank) != E_PGN_OK)
864 return 0;
867 return count;
870 static int finalize_move(GAME g, BOARD b, int promo, int sfile, int srank,
871 int file, int rank)
873 int p, pi;
875 #ifdef DEBUG
876 if (!g->validate && !g->check_testing)
877 PGN_DUMP("%s:%d: BEGIN finalizing\n", __FILE__, __LINE__);
878 #endif
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;
886 if (!g->validate) {
887 #ifdef DEBUG
888 if (!g->check_testing)
889 PGN_DUMP("%s:%d: updating board and game flags\n", __FILE__,
890 __LINE__);
891 #endif
892 pgn_reset_enpassant(b);
893 p = b[RANKTOBOARD(srank)][FILETOBOARD(sfile)].icon;
894 pi = pgn_piece_to_int(p);
896 if (pi == PAWN) {
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);
918 else {
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);
926 if (pi != PAWN)
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);
932 else
933 CLEAR_FLAG(g->flags, GF_BK_CASTLE|GF_BQ_CASTLE);
936 if (g->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);
948 else
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 :
954 GF_WQ_CASTLE);
955 else
956 CLEAR_FLAG(g->flags, (file > FILETOINT('e')) ? GF_BK_CASTLE :
957 GF_BQ_CASTLE);
959 else {
960 if (promo)
961 p = pgn_int_to_piece(g->turn, promo);
962 else
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;
971 g->castle = 0;
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)) {
978 case CHECK:
979 g->check = CHECK;
980 break;
981 case CHECK_MATE:
982 g->check = CHECK_MATE;
984 if (!g->validate) {
985 pgn_tag_add(&g->tag, "Result",
986 (g->turn == WHITE) ? "1-0" : "0-1");
987 SET_FLAG(g->flags, GF_GAMEOVER);
989 break;
990 default:
991 break;
994 // FIXME RAV
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);
1000 #ifdef DEBUG
1001 if (!g->validate && !g->check_testing)
1002 PGN_DUMP("%s:%d: END finalizing\n", __FILE__, __LINE__);
1003 #endif
1005 if (!g->validate) {
1006 p = pgn_piece_to_int(p);
1008 if (p == PAWN || promo || g->capture)
1009 g->ply = 0;
1010 else
1011 g->ply++;
1013 if (g->ply >= 50) {
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);
1021 return E_PGN_OK;
1024 static void black_opening(GAME g, BOARD b, int rank)
1026 if (!g->ravlevel && !g->hindex && pgn_tag_find(g->tag, "FEN") == -1) {
1027 if (rank > 4) {
1028 g->turn = BLACK;
1029 find_king_squares(g, b, &g->kfile, &g->krank, &g->okfile, &g->okrank);
1031 if (!g->validate)
1032 SET_FLAG(g->flags, GF_BLACK_OPENING);
1034 else {
1035 g->turn = WHITE;
1037 if (!g->validate)
1038 CLEAR_FLAG(g->flags, GF_BLACK_OPENING);
1042 #ifdef DEBUG
1043 if (TEST_FLAG(g->flags, GF_BLACK_OPENING))
1044 PGN_DUMP("%s:%d: black opening\n", __FILE__, __LINE__);
1045 #endif
1048 static char *format_santofrfr(int promo, int sfile, int srank, int file,
1049 int rank)
1051 static char frfr[6] = {0};
1053 snprintf(frfr, sizeof(frfr), "%c%c%c%c", INTTOFILE(sfile),
1054 INTTORANK(srank), INTTOFILE(file), INTTORANK(rank));
1056 if (promo)
1057 frfr[4] = pgn_int_to_piece(BLACK, promo);
1059 return frfr;
1063 * Converts a2a3 formatted moves to SAN format. The promotion piece should be
1064 * appended (a7a8q).
1066 static int frfrtosan(GAME g, BOARD b, char **m, char **dst)
1068 char *bp = *m;
1069 int icon, p, dp, promo = 0;
1070 int sfile, srank, file, rank;
1071 int n;
1072 int fc, rc;
1073 int ff = 0, rr = 0;
1075 #ifdef DEBUG
1076 PGN_DUMP("%s:%d: converting to SAN format\n", __FILE__, __LINE__);
1077 #endif
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);
1086 if (bp[4]) {
1087 if ((promo = pgn_piece_to_int(bp[4])) == -1 || promo == OPEN_SQUARE)
1088 return E_PGN_PARSE;
1089 #ifdef DEBUG
1090 PGN_DUMP("%s:%d: promotion to %c\n", __FILE__, __LINE__,
1091 pgn_int_to_piece(g->turn, promo));
1092 #endif
1095 icon = b[RANKTOBOARD(srank)][FILETOBOARD(sfile)].icon;
1097 if ((p = pgn_piece_to_int(icon)) == -1 || p == OPEN_SQUARE)
1098 return E_PGN_PARSE;
1100 if (p != PAWN && promo)
1101 return E_PGN_INVALID;
1103 if (p == PAWN) {
1104 if (find_source_square(g, b, p, &sfile, &srank, file, rank) != 1)
1105 return E_PGN_INVALID;
1107 goto capture;
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;
1119 if (g->check)
1120 strcat(bp, (g->check == CHECK) ? "+" : "#");
1122 *dst = format_santofrfr(promo, sfile, srank, file, rank);
1123 return E_PGN_OK;
1126 *bp++ = toupper(icon);
1127 fc = rc = 0;
1128 n = find_source_square(g, b, p, &fc, &rc, file, rank);
1130 if (!n)
1131 return E_PGN_INVALID;
1132 else if (n > 1) {
1133 fc = find_source_square(g, b, p, &sfile, &rr, file, rank);
1134 rc = find_source_square(g, b, p, &ff, &srank, file, rank);
1136 if (fc == 1)
1137 *bp++ = INTTOFILE(sfile);
1138 else if (!fc && rc)
1139 *bp++ = INTTORANK(srank);
1140 else if (fc && rc) {
1141 if (rc == 1)
1142 *bp++ = INTTORANK(srank);
1143 else {
1144 *bp++ = INTTOFILE(sfile);
1145 *bp++ = INTTORANK(srank);
1148 else
1149 return E_PGN_PARSE; // not reached.
1152 capture:
1153 icon = b[RANKTOBOARD(rank)][FILETOBOARD(file)].icon;
1155 if ((dp = pgn_piece_to_int(icon)) == -1)
1156 return E_PGN_PARSE;
1159 * [Pf][fr]x
1161 if (dp != OPEN_SQUARE || (dp == OPEN_SQUARE && p == PAWN && sfile != file)) {
1162 if (p == PAWN)
1163 *bp++ = INTTOFILE(sfile);
1165 *bp++ = 'x';
1169 * [Pf][fr][x]fr
1172 *bp++ = INTTOFILE(file);
1173 *bp++ = INTTORANK(rank);
1175 if (p == PAWN && !promo && (rank == 8 || rank == 1))
1176 promo = pgn_piece_to_int('q');
1179 * [Pf][fr][x]fr[=P]
1181 if (promo) {
1182 if (p != PAWN || (g->turn == WHITE && (srank != 7 || rank != 8)) ||
1183 (g->turn == BLACK && (srank != 2 || rank != 1)))
1184 return E_PGN_INVALID;
1186 *bp++ = '=';
1187 *bp++ = pgn_int_to_piece(WHITE, promo);
1190 *bp = 0;
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;
1198 if (g->check)
1199 *bp++ = (g->check == CHECK) ? '+' : '#';
1201 *bp = g->check = 0;
1202 *dst = format_santofrfr(promo, sfile, srank, file, rank);
1203 #ifdef DEBUG
1204 PGN_DUMP("%s:%d: END validating %s\n", __FILE__, __LINE__, *m);
1205 #endif
1206 return E_PGN_OK;
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];
1213 char *p;
1214 int piece;
1215 int i = 0;
1216 char *m = *san;
1218 g->capture = 0;
1220 again:
1221 if (strlen(m) < 2)
1222 return E_PGN_PARSE;
1224 p = (m) + strlen(m);
1226 while (!isdigit(*--p) && *p != 'O') {
1227 if (*p == '=') {
1228 *promo = pgn_piece_to_int(i);
1229 i = 0;
1230 break;
1233 i = *p;
1234 *p = '\0';
1237 /* Alternate promotion text (e8Q). Convert to SAN. */
1238 if (i && pgn_piece_to_int(i) != E_PGN_ERR) {
1239 p = (m) + strlen(m);
1240 *p++ = '=';
1241 *p++ = toupper(i);
1242 *p = '\0';
1243 goto again;
1246 p = m;
1248 /* Skip 'P' (pawn). */
1249 if (pgn_piece_to_int(*p) == PAWN)
1250 p++;
1252 /* Pawn. */
1253 if (VALIDCOL(*p)) {
1254 for (i = 0; *p; i++) {
1255 if (VALIDCOL(*p)) {
1256 if (i > 0)
1257 *file = FILETOINT(*p++);
1258 else
1259 *file = *sfile = FILETOINT(*p++);
1261 else if (VALIDROW(*p)) {
1262 if (1 > 1)
1263 *rank = RANKTOINT(*p++);
1264 else
1265 *rank = RANKTOINT(*p++);
1267 else if (*p == 'x') {
1268 *file = FILETOINT(*++p);
1269 *rank = RANKTOINT(*++p);
1270 g->capture++;
1272 else if (*p == '=') {
1273 if (*promo == -1 || *promo == KING || *promo == PAWN)
1274 return E_PGN_PARSE;
1276 *p++ = '=';
1277 *p++ = toupper(pgn_int_to_piece(g->turn, *promo));
1278 *p = '\0';
1279 break;
1281 else {
1282 #ifdef DEBUG
1283 PGN_DUMP("Pawn (move: '%s'): %c\n", m, *p++);
1284 #else
1285 p++;
1286 #endif
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');
1297 *p++ = '=';
1298 *p++ = pgn_int_to_piece(WHITE, *promo);
1299 *p = 0;
1302 /* Not a pawn. */
1303 else {
1304 p = m;
1307 * P[fr][x]fr
1309 * The first character is the piece but only if not a pawn.
1311 if ((piece = pgn_piece_to_int(*p++)) == -1)
1312 return E_PGN_PARSE;
1315 * [fr]fr
1317 if (strlen(m) > 3) {
1319 * rfr
1321 if (isdigit(*p))
1322 *srank = RANKTOINT(*p++);
1324 * f[r]fr
1326 else if (VALIDCOL(*p)) {
1327 *sfile = FILETOINT(*p++);
1330 * frfr
1332 if (isdigit(*p))
1333 *srank = RANKTOINT(*p++);
1337 * xfr
1339 if (*p == 'x') {
1340 g->capture++;
1341 p++;
1346 * fr
1348 * The destination square.
1350 *file = FILETOINT(*p++);
1351 *rank = RANKTOINT(*p++);
1353 if (*p == '=')
1354 *promo = *++p;
1356 black_opening(g, b, *rank);
1358 if ((i = find_source_square(g, b, piece, sfile, srank, *file, *rank))
1359 != 1 && !g->check)
1360 return (i == 0) ? E_PGN_INVALID : E_PGN_AMBIGUOUS;
1362 #if 0
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)
1368 != 1 && !check)
1369 return E_PGN_INVALID;
1370 #endif
1373 *p = 0;
1374 snprintf(frfr, sizeof(frfr), "%c%c%c%c", INTTOFILE(*sfile),
1375 INTTORANK(*srank), INTTOFILE(*file), INTTORANK(*rank));
1377 *san = m;
1378 return E_PGN_OK;
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
1386 * successful.
1388 pgn_error_t pgn_parse_move(GAME g, BOARD b, char **mp, char **dst)
1390 int srank = 0, sfile = 0, rank, file;
1391 char *m = *mp, *p;
1392 int i;
1393 int promo = -1;
1396 * This may be an empty move with only an annotation. Kinda strange.
1398 if (!m)
1399 return E_PGN_OK;
1401 #ifdef DEBUG
1402 PGN_DUMP("%s:%d: BEGIN validating '%s' (%s)...\n", __FILE__, __LINE__, m,
1403 (g->turn == WHITE) ? "white" : "black");
1404 #endif
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)) &&
1414 VALIDROW(*(m + 3)))
1415 return frfrtosan(g, b, mp, dst);
1416 else if (*m == 'O') {
1417 if (strcmp(m, "O-O") == 0)
1418 i = KINGSIDE;
1419 else if (strcmp(m, "O-O-O") == 0)
1420 i = QUEENSIDE;
1421 else
1422 return E_PGN_PARSE;
1424 if (parse_castle_move(g, b, i, &sfile, &srank, &file, &rank) !=
1425 E_PGN_OK)
1426 return E_PGN_INVALID;
1428 *m++ = INTTOFILE(sfile);
1429 *m++ = INTTORANK(srank);
1430 *m++ = INTTOFILE(file);
1431 *m++ = INTTORANK(rank);
1432 *m = 0;
1433 return frfrtosan(g, b, mp, dst);
1436 if ((i = do_santofrfr(g, b, &m, &promo, &sfile, &srank, &file, &rank))
1437 != E_PGN_OK)
1438 return i;
1440 p = m + strlen(m);
1442 if (finalize_move(g, b, promo, sfile, srank, file, rank) != E_PGN_OK)
1443 return E_PGN_INVALID;
1445 if (g->check)
1446 *p++ = (g->check == CHECK) ? '+' : '#';
1448 *p = g->check = 0;
1449 *dst = format_santofrfr(promo, sfile, srank, file, rank);
1451 #ifdef DEBUG
1452 PGN_DUMP("%s:%d: END validating %s\n", __FILE__, __LINE__, m);
1453 #endif
1454 return E_PGN_OK;
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)
1462 int ret;
1464 #ifdef DEBUG
1465 PGN_DUMP("%s:%d: BEGIN validate only\n", __FILE__, __LINE__);
1466 #endif
1467 g->validate = 1;
1468 ret = pgn_parse_move(g, b, m, dst);
1469 g->validate = 0;
1470 #ifdef DEBUG
1471 PGN_DUMP("%s:%d: END validate only\n", __FILE__, __LINE__);
1472 #endif
1473 return ret;
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);
1483 register int r, f;
1485 #ifdef DEBUG
1486 PGN_DUMP("%s:%d: BEGIN valid destination squares for %c%c\n", __FILE__,
1487 __LINE__, INTTOFILE(file), INTTORANK(rank));
1488 #endif
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))
1496 continue;
1498 if (find_source_square(g, b, p, &file, &rank, f, r) != 0) {
1499 b[RANKTOBOARD(r)][FILETOBOARD(f)].valid = 1;
1500 #ifdef DEBUG
1501 PGN_DUMP("%s:%d: %c%c is valid\n", __FILE__, __LINE__,
1502 INTTOFILE(f), INTTORANK(r));
1503 #endif
1508 #ifdef DEBUG
1509 PGN_DUMP("%s:%d: END valid destination squares for %c%c\n", __FILE__,
1510 __LINE__, INTTOFILE(file), INTTORANK(rank));
1511 #endif
1512 g->check_testing = 0;
1513 g->validate_find = 0;