Temporary fix of NAG parsing bug.
[cboard.git] / libchess / pgn.c
blob071e45138530e6d1d3e77ffc30769c3dd58f8c37
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
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <unistd.h>
23 #include <sys/types.h>
24 #include <sys/wait.h>
25 #include <sys/stat.h>
26 #include <pwd.h>
27 #include <err.h>
28 #include <string.h>
29 #include <time.h>
30 #include <ctype.h>
31 #include <errno.h>
32 #include <fcntl.h>
34 #ifdef HAVE_CONFIG_H
35 #include <config.h>
36 #endif
38 #ifdef HAVE_LIMITS_H
39 #include <limits.h>
40 #endif
42 #include "chess.h"
43 #include "pgn.h"
45 #ifdef DEBUG
46 #include "debug.h"
47 #endif
49 #ifdef WITH_DMALLOC
50 #include <dmalloc.h>
51 #endif
53 #include "move.c"
55 static void *Malloc(size_t size)
57 void *ptr;
59 if ((ptr = malloc(size)) == NULL)
60 err(EXIT_FAILURE, "malloc()");
62 return ptr;
65 static void *Realloc(void *ptr, size_t size)
67 void *ptr2;
69 if ((ptr2 = realloc(ptr, size)) == NULL)
70 err(EXIT_FAILURE, "realloc()");
72 return ptr2;
75 static void *Calloc(size_t n, size_t size)
77 void *p;
79 if ((p = calloc(n, size)) == NULL)
80 err(EXIT_FAILURE, "calloc()");
82 return p;
85 static char *trim(char *str)
87 int i = 0;
89 if (!str)
90 return NULL;
92 while (isspace(*str))
93 str++;
95 for (i = strlen(str) - 1; isspace(str[i]); i--)
96 str[i] = 0;
98 return str;
101 static char *itoa(long n)
103 static char buf[16];
105 snprintf(buf, sizeof(buf), "%li", n);
106 return buf;
109 void pgn_reset_valid_moves(BOARD b)
111 int row, col;
113 for (row = 0; row < 8; row++) {
114 for (col = 0; col < 8; col++)
115 b[row][col].valid = 0;
119 void pgn_get_valid_moves(GAME *g, BOARD b, int r, int f)
121 int row, col;
122 int p = pgn_piece_to_int(b[ROWTOBOARD(r)][COLTOBOARD(f)].icon);
125 * Don't update board 'b'. Only check for valid moves.
127 validate = 1;
129 for (row = 1; VALIDFILE(row); row++) {
130 for (col = 1; VALIDFILE(col); col++) {
131 int sr = 0, sc = 0;
133 if (get_source_yx(g, b, p, row, col, &sr, &sc)) {
134 sr = 0;
135 sc = f;
137 if (get_source_yx(g, b, p, row, col, &sr, &sc)) {
138 sc = 0;
139 sr = r;
141 if (get_source_yx(g, b, p, row, col, &sr, &sc)) {
142 continue;
147 if (sr != r || sc != f)
148 continue;
150 b[ROWTOBOARD(row)][COLTOBOARD(col)].valid = 1;
154 validate = 0;
157 void pgn_switch_turn(GAME *g)
159 g->turn = (g->turn == WHITE) ? BLACK : WHITE;
163 * Creates a FEN tag from the current game 'g' and board 'b'. Returns a FEN
164 * tag.
166 char *pgn_game_to_fen(GAME g, BOARD b)
168 int row, col;
169 int i;
170 static char buf[MAX_PGN_LINE_LEN], *p;
171 int oldturn = g.turn;
172 char enpassant[3] = {0}, *e;
173 int castle = 0;
175 for (i = pgn_history_total(g.hp); i >= g.hindex - 1; i--)
176 pgn_switch_turn(&g);
178 p = buf;
180 for (row = 0; row < 8; row++) {
181 int count = 0;
183 for (col = 0; col < 8; col++) {
184 if (b[row][col].enpassant) {
185 b[row][col].icon = pgn_int_to_piece(WHITE, OPEN_SQUARE);
186 e = enpassant;
187 *e++ = 'a' + col;
188 *e++ = ('0' + 8) - row;
189 *e = 0;
192 if (pgn_piece_to_int(b[row][col].icon) == OPEN_SQUARE) {
193 count++;
194 continue;
197 if (count) {
198 *p++ = '0' + count;
199 count = 0;
202 *p++ = b[row][col].icon;
203 *p = 0;
206 if (count) {
207 *p++ = '0' + count;
208 count = 0;
211 *p++ = '/';
214 --p;
215 *p++ = ' ';
216 *p++ = (g.turn == WHITE) ? 'w' : 'b';
217 *p++ = ' ';
219 if (TEST_FLAG(g.flags, GF_WK_CASTLE) && pgn_piece_to_int(b[7][7].icon) ==
220 ROOK && isupper(b[7][7].icon) && pgn_piece_to_int(b[7][4].icon) ==
221 KING && isupper(b[7][4].icon)) {
222 *p++ = 'K';
223 castle = 1;
226 if (TEST_FLAG(g.flags, GF_WQ_CASTLE) && pgn_piece_to_int(b[7][0].icon) ==
227 ROOK && isupper(b[7][0].icon) && pgn_piece_to_int(b[7][4].icon) ==
228 KING && isupper(b[7][4].icon)) {
229 *p++ = 'Q';
230 castle = 1;
233 if (TEST_FLAG(g.flags, GF_BK_CASTLE) && pgn_piece_to_int(b[0][7].icon) ==
234 ROOK && islower(b[0][7].icon) && pgn_piece_to_int(b[0][4].icon) ==
235 KING && islower(b[0][4].icon)) {
236 *p++ = 'k';
237 castle = 1;
240 if (TEST_FLAG(g.flags, GF_BQ_CASTLE) && pgn_piece_to_int(b[0][0].icon) ==
241 ROOK && islower(b[0][0].icon) && pgn_piece_to_int(b[0][4].icon) ==
242 KING && islower(b[0][4].icon)) {
243 *p++ = 'q';
244 castle = 1;
247 if (!castle)
248 *p++ = '-';
250 *p++ = ' ';
252 if (enpassant[0]) {
253 e = enpassant;
254 *p++ = *e++;
255 *p++ = *e++;
257 else
258 *p++ = '-';
260 *p++ = ' ';
262 // Halfmove clock.
263 *p = 0;
264 strcat(p, itoa(g.ply));
265 p = buf + strlen(buf);
266 *p++ = ' ';
268 // Fullmove number.
269 i = (g.hindex + 1) / 2;
270 *p = 0;
271 strcat(p, itoa((g.hindex / 2) + (g.hindex % 2)));
273 g.turn = oldturn;
274 return buf;
278 * Returns the total number of moves in 'h' or 0 if none.
280 int pgn_history_total(HISTORY **h)
282 int i;
284 if (!h)
285 return 0;
287 for (i = 0; h[i]; i++);
288 return i;
292 * Deallocates the all the data for 'h' from position 'start' in the array.
294 void pgn_history_free(HISTORY **h, int start)
296 int i;
298 if (!h || start > pgn_history_total(h))
299 return;
301 if (start < 0)
302 start = 0;
304 for (i = start; h[i]; i++) {
305 if (h[i]->comment)
306 free(h[i]->comment);
308 if (h[i]->rav) {
309 pgn_history_free(h[i]->rav, 0);
310 free(h[i]->rav);
313 if (h[i]->move)
314 free(h[i]->move);
317 h[start] = NULL;
321 * Returns the history ply 'n' from 'h'. If 'n' is out of range then NULL is
322 * returned.
324 HISTORY *pgn_history_by_n(HISTORY **h, int n)
326 if (n < 0 || n > pgn_history_total(h) - 1)
327 return NULL;
329 return h[n];
333 * Appends move 'm' to game 'g' history pointer. The history pointer may be a
334 * in a RAV so g->rav.hp is also updated to the new (realloc()'ed) pointer. If
335 * not in a RAV then g->history will be updated. Returns 1 if realloc() failed
336 * or 0 on success.
338 int pgn_history_add(GAME *g, const char *m)
340 int t = pgn_history_total(g->hp);
341 int o;
342 HISTORY **h = NULL;
344 if (g->ravlevel)
345 o = g->rav[g->ravlevel].hp - g->hp;
346 else
347 o = g->history - g->hp;
349 if ((h = realloc(g->hp, (t + 2) * sizeof(HISTORY *))) == NULL)
350 return E_PGN_ERR;
352 g->hp = h;
354 if (g->ravlevel)
355 g->rav[g->ravlevel].hp = g->hp + o;
356 else
357 g->history = g->hp + o;
359 if ((g->hp[t] = calloc(1, sizeof(HISTORY))) == NULL)
360 return E_PGN_ERR;
362 g->hp[t++]->move = strdup(m);
363 g->hp[t] = NULL;
364 g->hindex = pgn_history_total(g->hp);
365 return E_PGN_OK;
369 * Resets the game 'g' using board 'b' up to history move 'n'.
371 int pgn_board_update(GAME *g, BOARD b, int n)
373 int i = 0;
374 BOARD tb;
375 int ret = E_PGN_OK;
377 if (TEST_FLAG(g->flags, GF_BLACK_OPENING))
378 g->turn = BLACK;
379 else
380 g->turn = WHITE;
382 #if 0
383 if (TEST_FLAG(g->flags, GF_PERROR))
384 SET_FLAG(flags, GF_PERROR);
386 if (TEST_FLAG(g->flags, GF_MODIFIED))
387 SET_FLAG(flags, GF_MODIFIED);
389 if (TEST_FLAG(g->flags, GF_DELETE))
390 SET_FLAG(flags, GF_DELETE);
392 if (TEST_FLAG(g->flags, GF_GAMEOVER))
393 SET_FLAG(flags, GF_GAMEOVER);
395 g->flags = flags;
396 #endif
398 SET_FLAG(game[gindex].flags, GF_WK_CASTLE|GF_WQ_CASTLE|GF_WQ_CASTLE|
399 GF_BK_CASTLE|GF_BQ_CASTLE);
400 pgn_board_init(tb);
402 if (pgn_tag_find(g->tag, "FEN") != -1 &&
403 pgn_board_init_fen(g, tb, NULL))
404 return E_PGN_PARSE;
406 for (i = 0; i < n; i++) {
407 HISTORY *h;
408 char *p;
410 if ((h = pgn_history_by_n(g->hp, i)) == NULL)
411 break;
413 p = h->move;
415 if ((ret = pgn_validate_move(g, tb, &p)) != E_PGN_OK)
416 break;
418 pgn_switch_turn(g);
421 if (ret == 0)
422 memcpy(b, tb, sizeof(BOARD));
424 return ret;
428 * Updates the game 'g' using board 'b' to the next 'n'th history move. The
429 * 's' parameter is either 2 for a wholestep or 1 for a halfstep.
431 void pgn_history_prev(GAME *g, BOARD b, int n)
433 if (g->hindex - n < 0) {
434 if (n <= 2)
435 g->hindex = pgn_history_total(g->hp);
436 else
437 g->hindex = 0;
439 else
440 g->hindex -= n;
442 pgn_board_update(g, b, g->hindex);
446 * Updates the game 'g' using board 'b' to the previous 'n'th history move.
447 * 's' parameter is either 2 for a wholestep or 1 for a halfstep.
449 void pgn_history_next(GAME *g, BOARD b, int n)
451 if (g->hindex + n > pgn_history_total(g->hp)) {
452 if (n <= 2)
453 g->hindex = 0;
454 else
455 g->hindex = pgn_history_total(g->hp);
457 else
458 g->hindex += n;
460 pgn_board_update(g, b, g->hindex);
463 * Converts the character piece 'p' to an integer.
465 int pgn_piece_to_int(int p)
467 if (p == '.')
468 return OPEN_SQUARE;
470 p = tolower(p);
472 switch (p) {
473 case 'p':
474 return PAWN;
475 case 'r':
476 return ROOK;
477 case 'n':
478 return KNIGHT;
479 case 'b':
480 return BISHOP;
481 case 'q':
482 return QUEEN;
483 case 'k':
484 return KING;
485 default:
486 break;
489 return E_PGN_ERR;
493 * Converts the integer piece 'n' to a character.
495 int pgn_int_to_piece(char turn, int n)
497 int p = 0;
499 switch (n) {
500 case PAWN:
501 p = 'p';
502 break;
503 case ROOK:
504 p = 'r';
505 break;
506 case KNIGHT:
507 p = 'n';
508 break;
509 case BISHOP:
510 p = 'b';
511 break;
512 case QUEEN:
513 p = 'q';
514 break;
515 case KING:
516 p = 'k';
517 break;
518 case OPEN_SQUARE:
519 p = '.';
520 break;
521 default:
522 return E_PGN_ERR;
523 break;
526 return (turn == WHITE) ? toupper(p) : p;
530 * Finds a tag 'name' in the structure array 't'. Returns the location in the
531 * array of the found tag or -1 on failure.
533 int pgn_tag_find(TAG **t, const char *name)
535 int i;
537 for (i = 0; t[i]; i++) {
538 if (strcasecmp(t[i]->name, name) == 0)
539 return i;
542 return E_PGN_ERR;
545 static int tag_compare(const void *a, const void *b)
547 TAG * const *ta = a;
548 TAG * const *tb = b;
550 return strcmp((*ta)->name, (*tb)->name);
554 * Sorts a tag array. The first seven tags are in order of the PGN standard so
555 * don't sort'em.
557 void pgn_tag_sort(TAG **tags)
559 if (pgn_tag_total(tags) <= 7)
560 return;
562 qsort(tags + 7, pgn_tag_total(tags) - 7, sizeof(TAG *), tag_compare);
565 int pgn_tag_total(TAG **tags)
567 int i = 0;
569 if (!tags)
570 return 0;
572 while (tags[i])
573 i++;
575 return i;
579 * Adds a tag 'name' with value 'value' to the pointer to array 'dst'. The 'n'
580 * parameter is incremented to the new total of array 'dst'. If a duplicate
581 * tag 'name' was found then the existing tag is updated to the new 'value'.
582 * Returns 1 if a duplicate tag was found or 0 otherwise.
584 int pgn_tag_add(TAG ***dst, char *name, char *value)
586 int i;
587 TAG **tdata = *dst;
588 int len = 0;
589 int t = pgn_tag_total(tdata);
591 name = trim(name);
592 value = trim(value);
594 // Find an existing tag with 'name'.
595 for (i = 0; i < t; i++) {
596 if (strcasecmp(tdata[i]->name, name) == 0) {
597 len = (value) ? strlen(value) + 1 : 1;
598 tdata[i]->value = Realloc(tdata[i]->value, len);
599 strncpy(tdata[i]->value, (value) ? value : "", len);
600 *dst = tdata;
601 return E_PGN_ERR;
605 tdata = Realloc(tdata, (t + 2) * sizeof(TAG *));
606 tdata[t] = Malloc(sizeof(TAG));
607 len = strlen(name) + 1;
608 tdata[t]->name = Malloc(len);
609 strncpy(tdata[t]->name, name, len);
611 if (value) {
612 len = strlen(value) + 1;
613 tdata[t]->value = Malloc(len);
614 strncpy(tdata[t]->value, value, len);
616 else
617 tdata[t]->value = NULL;
619 tdata[++t] = NULL;
620 *dst = tdata;
621 return E_PGN_OK;
624 static char *remove_tag_escapes(const char *str)
626 int i, n;
627 int len = strlen(str);
628 static char buf[MAX_PGN_LINE_LEN] = {0};
630 for (i = n = 0; i < len; i++, n++) {
631 switch (str[i]) {
632 case '\\':
633 i++;
634 default:
635 break;
638 buf[n] = str[i];
641 buf[n] = '\0';
642 return buf;
646 * Initializes a new game board.
648 void pgn_board_init(BOARD b)
650 int row, col;
652 memset(b, 0, sizeof(BOARD));
654 for (row = 0; row < 8; row++) {
655 for (col = 0; col < 8; col++) {
656 int c = '.';
658 switch (row) {
659 case 0:
660 case 7:
661 switch (col) {
662 case 0:
663 case 7:
664 c = 'r';
665 break;
666 case 1:
667 case 6:
668 c = 'n';
669 break;
670 case 2:
671 case 5:
672 c = 'b';
673 break;
674 case 3:
675 c = 'q';
676 break;
677 case 4:
678 c = 'k';
679 break;
681 break;
682 case 1:
683 case 6:
684 c = 'p';
685 break;
688 b[row][col].icon = (row < 2) ? c : toupper(c);
694 * Adds the standard PGN roster tags to game 'g'.
696 static void set_default_tags(GAME *g)
698 time_t now;
699 char tbuf[11] = {0};
700 struct tm *tp;
701 struct passwd *pw = getpwuid(getuid());
703 time(&now);
704 tp = localtime(&now);
705 strftime(tbuf, sizeof(tbuf), PGN_TIME_FORMAT, tp);
707 /* The standard seven tag roster (in order of appearance). */
708 pgn_tag_add(&g->tag, "Event", "?");
709 pgn_tag_add(&g->tag, "Site", "?");
710 pgn_tag_add(&g->tag, "Date", tbuf);
711 pgn_tag_add(&g->tag, "Round", "-");
712 pgn_tag_add(&g->tag, "White", pw->pw_gecos);
713 pgn_tag_add(&g->tag, "Black", "?");
714 pgn_tag_add(&g->tag, "Result", "*");
717 void pgn_tag_free(TAG **tags)
719 int i;
721 if (!tags)
722 return;
724 for (i = 0; tags[i]; i++) {
725 free(tags[i]->name);
726 free(tags[i]->value);
727 free(tags[i]);
730 free(tags);
733 void pgn_free(GAME g)
735 pgn_history_free(g.history, 0);
736 free(g.history);
737 pgn_tag_free(g.tag);
738 memset(&g, 0, sizeof(GAME));
741 void pgn_free_all()
743 int i;
745 for (i = 0; i < gtotal; i++) {
746 pgn_free(game[i]);
748 if (game[i].rav) {
749 for (game[i].ravlevel--; game[i].ravlevel >= 0; game[i].ravlevel--)
750 free(game[i].rav[game[i].ravlevel].fen);
752 free(game[i].rav);
756 if (game)
757 free(game);
758 game = NULL;
761 static void reset_game_data()
763 pgn_free_all();
764 gtotal = gindex = 0;
765 pgn_isfile = 0;
768 static void skip_leading_space(FILE *fp)
770 int c;
772 while ((c = fgetc(fp)) != EOF && !feof(fp)) {
773 if (!isspace(c))
774 break;
777 ungetc(c, fp);
781 * PGN move text section.
783 static int move_text(GAME *g, FILE *fp)
785 char m[MAX_SAN_MOVE_LEN + 1] = {0}, *p;
786 int c;
787 int count;
788 int dots = 0;
789 int digit = 0;
791 while((c = fgetc(fp)) != EOF) {
792 if (isspace(c))
793 continue;
795 if (isdigit(c)) {
796 digit = 1;
797 continue;
800 if (c == '.') {
801 dots++;
802 continue;
805 break;
808 if (digit) {
809 if (dots > 1) {
810 g->turn = BLACK;
812 if (g->hindex == 0 && g->ravlevel == 0)
813 SET_FLAG(g->flags, GF_BLACK_OPENING);
815 else {
816 g->turn = WHITE;
818 if (g->hindex == 0)
819 CLEAR_FLAG(g->flags, GF_BLACK_OPENING);
823 ungetc(c, fp);
825 if (fscanf(fp, " %[a-hPRNBQK1-9#+=Ox-]%n", m, &count) != 1)
826 return 1;
828 p = m + strlen(m) - 1;
830 if (!pgn_history_total(g->hp) && g->ravlevel == 0 && VALIDRANK(ROWTOINT(*p)) &&
831 VALIDFILE(COLTOINT(*(p-1))) && ROWTOINT(*p) > 4) {
832 g->turn = BLACK;
833 SET_FLAG(g->flags, GF_BLACK_OPENING);
836 p = m;
838 if (pgn_validate_move(g, g->b, &p)) {
839 pgn_switch_turn(g);
840 return 1;
843 #ifdef DEBUG
844 DUMP("%s\n", p);
845 dump_board(0, g->b);
846 #endif
848 pgn_history_add(g, p);
849 pgn_switch_turn(g);
850 return 0;
854 * PGN nag text.
856 static void nag_text(GAME *g, FILE *fp)
858 int c, i, t;
859 char nags[5], *n = nags;
860 int nag = 0;
862 while ((c = fgetc(fp)) != EOF && !isspace(c)) {
863 if (c == '$') {
864 while ((c = fgetc(fp)) != EOF && isdigit(c))
865 *n++ = c;
867 break;
870 if (c == '!') {
871 if ((c = fgetc(fp)) == '!')
872 nag = 3;
873 else if (c == '?')
874 nag = 5;
875 else {
876 ungetc(c, fp);
877 nag = 1;
880 break;
882 else if (c == '?') {
883 if ((c = fgetc(fp)) == '?')
884 nag = 4;
885 else if (c == '!')
886 nag = 6;
887 else {
888 ungetc(c, fp);
889 nag = 2;
892 break;
894 else if (c == '~')
895 nag = 13;
896 else if (c == '=') {
897 if ((c = fgetc(fp)) == '+')
898 nag = 15;
899 else {
900 ungetc(c, fp);
901 nag = 10;
904 break;
906 else if (c == '+') {
907 if ((t = fgetc(fp)) == '=')
908 nag = 14;
909 else if (t == '-')
910 nag = 18;
911 else if (t == '/') {
912 if ((i = fgetc(fp)) == '-')
913 nag = 16;
914 else
915 ungetc(i, fp);
917 break;
919 else
920 ungetc(t, fp);
922 break;
924 else if (c == '-') {
925 if ((t = fgetc(fp)) == '+')
926 nag = 18;
927 else if (t == '/') {
928 if ((i = fgetc(fp)) == '+')
929 nag = 17;
930 else
931 ungetc(i, fp);
933 break;
935 else
936 ungetc(t, fp);
938 break;
942 *n = '\0';
944 if (!nag)
945 nag = (nags[0]) ? atoi(nags) : 0;
947 if (!nag || nag < 0 || nag > 255)
948 return;
950 // FIXME -1 is because move_text() increments g.hindex. The NAG
951 // annoatation isnt guaranteed to be after the move text in import format.
952 for (i = 0; i < MAX_PGN_NAG; i++) {
953 if (g->hp[g->hindex - 1]->nag[i])
954 continue;
956 g->hp[g->hindex - 1]->nag[i] = nag;
957 break;
960 skip_leading_space(fp);
964 * PGN move annotation.
966 static void annotation_text(GAME *g, FILE *fp, int terminator)
968 int c, lastchar = 0;
969 int len = 0;
970 int hindex = pgn_history_total(g->hp) - 1;
971 char buf[MAX_PGN_LINE_LEN], *a = buf;
973 skip_leading_space(fp);
975 while ((c = fgetc(fp)) != EOF && c != terminator) {
976 if (c == '\n')
977 c = ' ';
979 if (isspace(c) && isspace(lastchar))
980 continue;
982 if (len + 1 == sizeof(buf))
983 continue;
985 *a++ = lastchar = c;
986 len++;
989 *a = '\0';
990 g->hp[hindex]->comment = Realloc(g->hp[hindex]->comment, ++len);
991 strncpy(g->hp[hindex]->comment, buf, len);
995 * PGN roster tag.
997 static int tag_text(GAME *g, FILE *fp)
999 char name[LINE_MAX], *n = name;
1000 char value[LINE_MAX], *v = value;
1001 int c, i = 0;
1002 int quoted_string = 0;
1003 int lastchar = 0;
1005 skip_leading_space(fp);
1007 /* The tag name is up until the first whitespace. */
1008 while ((c = fgetc(fp)) != EOF && !isspace(c))
1009 *n++ = c;
1011 *n = '\0';
1012 *name = toupper(*name);
1013 skip_leading_space(fp);
1015 /* The value is until the first closing bracket. */
1016 while ((c = fgetc(fp)) != EOF && c != ']') {
1017 if (i++ == '\0' && c == '\"') {
1018 quoted_string = 1;
1019 continue;
1022 if (c == '\n' || c == '\t')
1023 c = ' ';
1025 if (c == ' ' && lastchar == ' ')
1026 continue;
1028 lastchar = *v++ = c;
1031 *v = '\0';
1033 while (isspace(*--v))
1034 *v = '\0';
1036 if (*v == '\"')
1037 *v = '\0';
1039 if (value[0] == '\0') {
1040 if (strcmp(name, "Result") == 0)
1041 value[0] = '*';
1042 else
1043 value[0] = '?';
1045 value[1] = '\0';
1048 strncpy(value, remove_tag_escapes(value), sizeof(value));
1049 pgn_tag_add(&g->tag, name, value);
1050 return 0;
1054 * PGN end-of-game marker.
1056 static int eog_text(GAME *g, FILE *fp)
1058 int c, i = 0;
1059 char buf[8], *p = buf;
1061 while ((c = fgetc(fp)) != EOF && !isspace(c) && i++ < sizeof(buf))
1062 *p++ = c;
1064 if (isspace(c))
1065 ungetc(c, fp);
1067 *p = 0;
1068 g->tag[TAG_RESULT]->value = Realloc(g->tag[TAG_RESULT]->value, strlen(buf) + 1);
1069 strcpy(g->tag[TAG_RESULT]->value, buf);
1070 return 1;
1074 * Parse RAV text and keep track of g.hp. The 'o' argument is the board state
1075 * before the current move (.hindex) was parsed.
1077 static int read_file(FILE *);
1078 static int rav_text(GAME *g, FILE *fp, int which, BOARD o)
1080 int ravindex = ravlevel;
1081 GAME tg;
1083 // Begin RAV for the current move.
1084 if (which == '(') {
1086 * Save the current game state for this RAV depth/level.
1088 rav = Realloc(rav, (ravindex + 1) * sizeof(RAV));
1089 rav[ravindex].fen = strdup(pgn_game_to_fen((*g), g->b));
1090 rav[ravindex].hp = g->hp;
1091 memcpy(&tg, g, sizeof(GAME));
1092 memcpy(g->b, o, sizeof(BOARD));
1094 g->hp[g->hindex]->rav = Calloc(1, sizeof(HISTORY));
1096 // pgn_history_add() will now append to this new RAV.
1097 g->hp = g->hp[g->hindex]->rav;
1100 * Reset. Will be restored later from 'tg' which is a local variable
1101 * so recursion is possible.
1103 g->hindex = 0;
1104 ravlevel++;
1107 * Now continue as normal as if there were no RAV. Moves will be
1108 * parsed and appended to the new .hp.
1110 if (read_file(fp))
1111 return 1;
1114 * read_file() has returned. The means that a RAV has ended by this
1115 * function returning -1 (see below). So we restore the game state
1116 * that was saved before calling read_file().
1118 pgn_board_init_fen(&tg, g->b, rav[ravindex].fen);
1119 free(rav[ravindex].fen);
1120 memcpy(g, &tg, sizeof(GAME));
1121 g->hp = rav[ravindex].hp;
1122 ravlevel--;
1125 * The end of a RAV. This makes read_file() that called this function
1126 * rav_text() return (see above).
1128 else if (which == ')')
1129 return -1;
1131 return 0;
1135 * See pgn_board_init_fen(). Returns -1 on parse error. 0 may be returned on
1136 * success when there is no move count in the FEN tag otherwise the move count
1137 * is returned.
1139 static int parse_fen_line(BOARD b, unsigned *flags, char *turn, char *ply,
1140 char *str)
1142 char *tmp;
1143 char line[LINE_MAX], *s;
1144 int row = 8, col = 1;
1145 int moven;
1147 strncpy(line, str, sizeof(line));
1148 s = line;
1149 pgn_reset_enpassant(b);
1151 while ((tmp = strsep(&s, "/")) != NULL) {
1152 int n;
1154 if (!VALIDFILE(row))
1155 return -1;
1157 while (*tmp) {
1158 if (*tmp == ' ')
1159 goto other;
1161 if (isdigit(*tmp)) {
1162 n = *tmp - '0';
1164 if (!VALIDFILE(n))
1165 return -1;
1167 for (; n; --n, col++)
1168 b[ROWTOBOARD(row)][COLTOBOARD(col)].icon =
1169 pgn_int_to_piece(WHITE, OPEN_SQUARE);
1171 else if (pgn_piece_to_int(*tmp) != -1)
1172 b[ROWTOBOARD(row)][COLTOBOARD(col++)].icon = *tmp;
1173 else
1174 return -1;
1176 tmp++;
1179 row--;
1180 col = 1;
1183 other:
1184 tmp++;
1186 switch (*tmp++) {
1187 case 'b':
1188 *turn = BLACK;
1189 break;
1190 case 'w':
1191 *turn = WHITE;
1192 break;
1193 default:
1194 return -1;
1197 tmp++;
1199 while (*tmp && *tmp != ' ') {
1200 switch (*tmp++) {
1201 case 'K':
1202 SET_FLAG(*flags, GF_WK_CASTLE);
1203 break;
1204 case 'Q':
1205 SET_FLAG(*flags, GF_WQ_CASTLE);
1206 break;
1207 case 'k':
1208 SET_FLAG(*flags, GF_BK_CASTLE);
1209 break;
1210 case 'q':
1211 SET_FLAG(*flags, GF_BQ_CASTLE);
1212 break;
1213 default:
1214 return -1;
1218 // En passant.
1219 if (*++tmp != '-') {
1220 if (!VALIDCOL(*tmp))
1221 return -1;
1223 col = *tmp++ - 'a';
1225 if (!VALIDROW(*tmp))
1226 return -1;
1228 row = 8 - atoi(tmp++);
1229 b[row][col].enpassant = 1;
1232 if (*tmp)
1233 tmp++;
1235 if (*tmp)
1236 *ply = atoi(tmp);
1238 while (*tmp && isdigit(*tmp))
1239 tmp++;
1241 if (*tmp)
1242 tmp++;
1244 moven = atoi(tmp);
1245 return moven;
1249 * This is called at the EOG marker and the beginning of the move text
1250 * section. So at least a move or EOG marker has to exist. It initializes the
1251 * board (b) to the FEN tag (if found) and sets the castling and enpassant
1252 * info for the game 'g'. If 'fen' is set it should be a fen tag and will be
1253 * parsed rather than the game 'g'.tag FEN tag. Returns 0 on success or if
1254 * there was both a FEN and SetUp tag with the SetUp tag set to 0. Returns 1
1255 * if there was a FEN parse error or no FEN tag at all.
1257 int pgn_board_init_fen(GAME *g, BOARD b, char *fen)
1259 int n = -1, i = -1;
1260 BOARD tmpboard;
1261 unsigned flags = 0;
1262 char turn = g->turn;
1263 char ply = 0;
1265 pgn_board_init(tmpboard);
1267 if (!fen) {
1268 n = pgn_tag_find(g->tag, "Setup");
1269 i = pgn_tag_find(g->tag, "FEN");
1273 * If the FEN tag exists and there is no SetUp tag go ahead and parse it.
1274 * If there is a SetUp tag only parse the FEN tag if the value is 1.
1276 if ((n >= 0 && i >= 0 && atoi(g->tag[n]->value) == 1)
1277 || (i >= 0 && n == -1) || fen) {
1278 if ((n = parse_fen_line(tmpboard, &flags, &turn, &ply,
1279 (fen) ? fen : g->tag[i]->value)) == -1)
1280 return E_PGN_PARSE;
1281 else {
1282 memcpy(b, tmpboard, sizeof(BOARD));
1283 CLEAR_FLAG(g->flags, GF_WK_CASTLE);
1284 CLEAR_FLAG(g->flags, GF_WQ_CASTLE);
1285 CLEAR_FLAG(g->flags, GF_BK_CASTLE);
1286 CLEAR_FLAG(g->flags, GF_BQ_CASTLE);
1287 g->flags |= flags;
1288 g->turn = turn;
1289 g->ply = ply;
1292 else
1293 return (i >= 0 && n >= 0) ? E_PGN_OK : E_PGN_ERR;
1295 return E_PGN_OK;
1299 * Allocates a new game and increments gindex (the current game) and gtotal
1300 * (the total number of games).
1302 void pgn_new_game()
1304 gindex = ++gtotal - 1;
1305 game = Realloc(game, gtotal * sizeof(GAME));
1306 memset(&game[gindex], 0, sizeof(GAME));
1307 game[gindex].hp = Calloc(1, sizeof(HISTORY *));
1308 game[gindex].hp[0] = NULL;
1309 game[gindex].history = game[gindex].hp;
1310 game[gindex].side = game[gindex].turn = WHITE;
1311 SET_FLAG(game[gindex].flags, GF_WK_CASTLE|GF_WQ_CASTLE|GF_WQ_CASTLE|
1312 GF_BK_CASTLE|GF_BQ_CASTLE);
1313 pgn_board_init(game[gindex].b);
1314 set_default_tags(&game[gindex]);
1316 if (pgn_isfile && !(gtotal % 50))
1317 kill(getpid(), SIGUSR1);
1320 static int read_file(FILE *fp)
1322 #ifdef DEBUG
1323 char buf[LINE_MAX] = {0}, *p = buf;
1324 #endif
1325 int c = 0;
1326 int parse_error = 0;
1327 BOARD old;
1329 while (1) {
1330 int nextchar = 0;
1331 int lastchar = c;
1333 if ((c = fgetc(fp)) == EOF) {
1334 if (feof(fp))
1335 break;
1337 if (ferror(fp)) {
1338 clearerr(fp);
1339 continue;
1343 if (!isascii(c)) {
1344 parse_error = 1;
1345 continue;
1348 if (c == '\015')
1349 continue;
1351 nextchar = fgetc(fp);
1352 ungetc(nextchar, fp);
1355 * If there was a move text parsing error, keep reading until the end
1356 * of the current game discarding the data.
1358 if (parse_error) {
1359 pgn_ret = E_PGN_PARSE;
1361 if (ravlevel)
1362 return 1;
1364 if (pgn_config.stop)
1365 return 1;
1367 if (c == '\n' && (nextchar == '\n' || nextchar == '\015')) {
1368 parse_error = 0;
1369 nulltags = 1;
1370 tag_section = 0;
1372 else
1373 continue;
1376 // New game reached.
1377 if (c == '\n' && (nextchar == '\n' || nextchar == '\015')) {
1378 if (tag_section) {
1379 tag_section = 0;
1380 continue;
1383 nulltags = 1;
1384 tag_section = 0;
1385 continue;
1389 * PGN: Application comment. The '%' must be on the first column of
1390 * the line. The comment continues until the end of the current line.
1392 if (c == '%') {
1393 if (lastchar == '\n' || lastchar == 0) {
1394 while ((c = fgetc(fp)) != EOF && c != '\n');
1395 continue;
1398 // Not sure what to do here.
1401 if (isspace(c))
1402 continue;
1404 // PGN: Reserved.
1405 if (c == '<' || c == '>')
1406 continue;
1409 * PGN: Recurrsive Annotation Variation. Read rav_text() for more
1410 * info.
1412 if (c == '(' || c == ')') {
1413 switch (rav_text(&game[gindex], fp, c, old)) {
1414 case -1:
1416 * This is the end of the current RAV. This function has
1417 * been called from rav_text(). Returning from this point
1418 * will put us back in rav_text().
1420 if (ravlevel > 0)
1421 return pgn_ret;
1424 * We're back at the root move. Continue as normal.
1426 break;
1427 case 1:
1428 parse_error = 1;
1429 continue;
1430 default:
1432 * Continue processing. Probably the root move.
1434 break;
1437 continue;
1440 // PGN: Numeric Annotation Glyph.
1441 if (c == '$' || c == '!' || c == '?' || c == '+' || c == '-' ||
1442 c == '~' || c == '=') {
1443 ungetc(c, fp);
1444 nag_text(&game[gindex], fp);
1445 continue;
1449 * PGN: Annotation. The ';' comment continues until the end of the
1450 * current line. The '{' type comment continues until a '}' is
1451 * reached.
1453 if (c == '{' || c == ';') {
1454 annotation_text(&game[gindex], fp, (c == '{') ? '}' : '\n');
1455 continue;
1458 // PGN: Roster tag.
1459 if (c == '[') {
1460 // First roster tag found. Initialize the data structures.
1461 if (!tag_section) {
1462 nulltags = 0;
1463 tag_section = 1;
1465 if (gtotal && pgn_history_total(game[gindex].hp))
1466 game[gindex].hindex = pgn_history_total(game[gindex].hp) - 1;
1468 pgn_new_game();
1469 memcpy(old, game[gindex].b, sizeof(BOARD));
1472 if (tag_text(&game[gindex], fp))
1473 parse_error = 1; // FEN tag parse error.
1475 continue;
1478 // PGN: End-of-game markers.
1479 if ((isdigit(c) && (nextchar == '-' || nextchar == '/')) || c == '*') {
1480 ungetc(c, fp);
1481 eog_text(&game[gindex], fp);
1482 nulltags = 1;
1483 tag_section = 0;
1485 if (!done_fen_tag) {
1486 if (pgn_tag_find(game[gindex].tag, "FEN") != -1 &&
1487 pgn_board_init_fen(&game[gindex], game[gindex].b,
1488 NULL)) {
1489 parse_error = 1;
1490 continue;
1493 done_fen_tag = 1;
1496 continue;
1499 // PGN: Move text.
1500 if ((isdigit(c) && c != '0') || VALIDCOL(c) || c == 'N' || c == 'K'
1501 || c == 'Q' || c == 'B' || c == 'R' || c == 'P' ||
1502 c == 'O') {
1503 ungetc(c, fp);
1505 // PGN: If a FEN tag exists, initialize the board to the value.
1506 if (tag_section) {
1507 if (pgn_tag_find(game[gindex].tag, "FEN") != -1 &&
1508 pgn_board_init_fen(&game[gindex], game[gindex].b,
1509 NULL)) {
1510 parse_error = 1;
1511 continue;
1514 done_fen_tag = 1;
1515 tag_section = 0;
1519 * PGN: Import format doesn't require a roster tag section. We've
1520 * arrived to the move text section without any tags so we
1521 * initialize a new game which set's the default tags and any tags
1522 * from the configuration file.
1524 if (nulltags) {
1525 if (gtotal)
1526 game[gindex].hindex = pgn_history_total(game[gindex].hp) - 1;
1528 pgn_new_game(game[gindex].b);
1529 memcpy(old, game[gindex].b, sizeof(BOARD));
1530 nulltags = 0;
1533 memcpy(old, game[gindex].b, sizeof(BOARD));
1535 if (move_text(&game[gindex], fp)) {
1536 SET_FLAG(game[gindex].flags, GF_PERROR);
1537 parse_error = 1;
1540 continue;
1543 #ifdef DEBUG
1544 *p++ = c;
1546 DUMP("unparsed: '%s'\n", buf);
1548 if (strlen(buf) + 1 == sizeof(buf))
1549 bzero(buf, sizeof(buf));
1550 #endif
1552 continue;
1555 return pgn_ret;
1559 * Parses a file whose file pointer is 'fp'. 'fp' may have been returned by
1560 * pgn_open(). If 'fp' is NULL then a single empty game will be allocated. If
1561 * there is a parsing error 1 is returned otherwise 0 is returned and the
1562 * global 'gindex' is set to the last parsed game in the file and the global
1563 * 'gtotal' is set to the total number of games in the file. The file will be
1564 * closed when the parsing is done.
1566 int pgn_parse(FILE *fp)
1568 int i;
1570 if (!fp) {
1571 reset_game_data();
1572 pgn_new_game();
1573 pgn_ret = E_PGN_OK;
1574 goto done;
1577 reset_game_data();
1578 nulltags = 1;
1579 pgn_isfile = 1;
1580 pgn_ret = read_file(fp);
1581 fclose(fp);
1583 if (rav)
1584 free(rav);
1586 if (gtotal < 1)
1587 pgn_new_game();
1589 done:
1590 gtotal = gindex + 1;
1592 for (i = 0; i < gtotal; i++) {
1593 game[i].history = game[i].hp;
1594 game[i].hindex = pgn_history_total(game[i].hp);
1597 return pgn_ret;
1601 * Escape '"' and '\' in tag values.
1603 static char *pgn_tag_add_escapes(const char *str)
1605 int i, n;
1606 int len = strlen(str);
1607 static char buf[MAX_PGN_LINE_LEN] = {0};
1609 for (i = n = 0; i < len; i++, n++) {
1610 switch (str[i]) {
1611 case '\\':
1612 case '\"':
1613 buf[n++] = '\\';
1614 break;
1615 default:
1616 break;
1619 buf[n] = str[i];
1622 buf[n] = '\0';
1623 return buf;
1626 static void Fputc(int c, FILE *fp, int *len)
1628 int i = *len;
1630 if (c != '\n' && i + 1 > 80)
1631 Fputc('\n', fp, &i);
1633 if (pgn_lastc == '\n' && c == ' ') {
1634 *len = 0;
1635 return;
1638 if (fputc(c, fp) == EOF)
1639 warn("PGN Save");
1640 else {
1641 if (c == '\n')
1642 i = 0;
1643 else
1644 i++;
1647 *len = i;
1648 pgn_lastc = c;
1651 static void putstring(FILE *fp, char *str, int *len)
1653 char *p;
1655 for (p = str; *p; p++) {
1656 int n = 0;
1658 while (*p && *p != ' ')
1659 n++, p++;
1661 if (n + *len > 80)
1662 Fputc('\n', fp, len);
1664 p -= n;
1665 Fputc(*p, fp, len);
1670 * See pgn_write() for more info.
1672 static void write_comments_and_nag(FILE *fp, HISTORY *h, int *len)
1674 int i;
1675 int type = 0;
1677 for (i = 0; i < MAX_PGN_NAG; i++) {
1678 if (h->nag[i]) {
1679 Fputc(' ', fp, len);
1680 Fputc('$', fp, len);
1681 putstring(fp, itoa(h->nag[i]), len);
1685 if (h->comment) {
1686 if (strlen(h->comment) + *len + 1 > 80) {
1687 type = 1;
1688 putstring(fp, " {", len);
1690 else
1691 putstring(fp, " ;", len);
1693 putstring(fp, h->comment, len);
1695 if (type)
1696 putstring(fp, "}", len);
1697 else
1698 Fputc('\n', fp, len);
1702 static void write_move_text(FILE *fp, HISTORY *h, int *len)
1704 Fputc(' ', fp, len);
1705 putstring(fp, h->move, len);
1707 if (!pgn_config.reduced)
1708 write_comments_and_nag(fp, h, len);
1711 static void write_all_move_text(FILE *fp, HISTORY **h, int m, int *len)
1713 int i;
1714 HISTORY **hp = NULL;
1716 for (i = 0; h[i]; i++) {
1717 if (pgn_write_turn == WHITE) {
1718 if (pgn_config.mpl && pgn_mpl == pgn_config.mpl) {
1719 pgn_mpl = 0;
1720 Fputc('\n', fp, len);
1723 if (m > 1 && i > 0)
1724 Fputc(' ', fp, len);
1726 if (strlen(itoa(m)) + 1 + *len > 80)
1727 Fputc('\n', fp, len);
1729 putstring(fp, itoa(m), len);
1730 Fputc('.', fp, len);
1731 pgn_mpl++;
1734 write_move_text(fp, h[i], len);
1736 if (!pgn_config.reduced && h[i]->rav) {
1737 int oldm = m;
1738 int oldturn = pgn_write_turn;
1740 ravlevel++;
1741 putstring(fp, " (", len);
1744 * If it's WHITE's turn the move number will be added above after
1745 * the call to write_all_move_text() below.
1747 if (pgn_write_turn == BLACK) {
1748 putstring(fp, itoa(m), len);
1749 putstring(fp, "...", len);
1752 hp = h[i]->rav;
1753 write_all_move_text(fp, hp, m, len);
1754 m = oldm;
1755 pgn_write_turn = oldturn;
1756 putstring(fp, ")", len);
1757 ravlevel--;
1759 if (h[i + 1] && !ravlevel)
1760 Fputc(' ', fp, len);
1762 if (pgn_write_turn == WHITE && h[i + 1]) {
1763 putstring(fp, itoa(m), len);
1764 putstring(fp, "...", len);
1768 if (pgn_write_turn == BLACK)
1769 m++;
1771 pgn_write_turn = (pgn_write_turn == WHITE) ? BLACK : WHITE;
1775 #ifdef WITH_COMPRESSED
1776 static char *compression_cmd(const char *filename, int expand)
1778 static char command[FILENAME_MAX];
1779 int len = strlen(filename);
1781 if (filename[len - 4] == '.' && filename[len - 3] == 'z' &&
1782 filename[len - 2] == 'i' && filename[len - 1] == 'p' &&
1783 filename[len] == '\0') {
1784 if (expand)
1785 snprintf(command, sizeof(command), "unzip -p %s 2>/dev/null",
1786 filename);
1787 else
1788 snprintf(command, sizeof(command), "zip -9 >%s 2>/dev/null",
1789 filename);
1791 return command;
1793 else if (filename[len - 3] == '.' && filename[len - 2] == 'g' &&
1794 filename[len - 1] == 'z' && filename[len] == '\0') {
1795 if (expand)
1796 snprintf(command, sizeof(command), "gzip -dc %s", filename);
1797 else
1798 snprintf(command, sizeof(command), "gzip -c 1>%s", filename);
1800 return command;
1802 else if (filename[len - 2] == '.' && filename[len - 1] == 'Z' &&
1803 filename[len] == '\0') {
1804 if (expand)
1805 snprintf(command, sizeof(command), "uncompress -c %s", filename);
1806 else
1807 snprintf(command, sizeof(command), "compress -c 1>%s", filename);
1809 return command;
1811 else if ((filename[len - 4] == '.' && filename[len - 3] == 'b' &&
1812 filename[len - 2] == 'z' && filename[len - 1] == '2' &&
1813 filename[len] == '\0') || (filename[len - 3] == '.' &&
1814 filename[len - 2] == 'b' && filename[len - 1] == 'z' &&
1815 filename[len] == '\0')) {
1816 if (expand)
1817 snprintf(command, sizeof(command), "bzip2 -dc %s", filename);
1818 else
1819 snprintf(command, sizeof(command), "bzip2 -zc 1>%s", filename);
1821 return command;
1824 return NULL;
1826 #endif
1829 * Returns a file pointer associated with 'filename' or NULL on error with
1830 * errno set to indicate the error. If compressed file support was enabled at
1831 * compile time and the filetype is supported and the utility is installed
1832 * then the file will be decompressed.
1834 FILE *pgn_open(const char *filename)
1836 FILE *fp = NULL;
1837 #ifdef WITH_COMPRESSED
1838 FILE *tfp = NULL;
1839 char buf[LINE_MAX];
1840 char *p;
1841 char *command = NULL;
1842 #endif
1844 if (access(filename, R_OK) == -1)
1845 return NULL;
1847 #ifdef WITH_COMPRESSED
1848 if ((command = compression_cmd(filename, 1)) != NULL) {
1849 if ((tfp = tmpfile()) == NULL)
1850 return NULL;
1852 if ((fp = popen(command, "r")) == NULL)
1853 return NULL;
1855 while ((p = fgets(buf, sizeof(buf), fp)) != NULL)
1856 fprintf(tfp, "%s", p);
1858 pclose(fp);
1861 if (tfp)
1862 fseek(tfp, 0, SEEK_SET);
1863 else {
1864 if ((tfp = fopen(filename, "r")) == NULL)
1865 return NULL;
1868 return tfp;
1869 #else
1870 if ((fp = fopen(filename, "r")) == NULL)
1871 return NULL;
1873 return fp;
1874 #endif
1878 * Returns 1 if 'filename' is a recognized compressed filetype.
1880 int pgn_is_compressed(const char *filename)
1882 #ifdef WITH_COMPRESSED
1883 if (compression_cmd(filename, 0))
1884 return E_PGN_OK;
1885 #endif
1887 return E_PGN_ERR;
1891 * Set game flags. See chess.h for available flags.
1893 int pgn_config_set(pgn_config_flag f, int val)
1895 if (val < 0)
1896 return E_PGN_ERR;
1898 switch (f) {
1899 case PGN_REDUCED:
1900 if (val == 1)
1901 pgn_config.reduced = 1;
1902 else if (val == 0)
1903 pgn_config.reduced = 0;
1904 else
1905 return E_PGN_ERR;
1906 break;
1907 case PGN_MPL:
1908 pgn_config.mpl = val;
1909 break;
1910 case PGN_STOP_ON_ERROR:
1911 if (val == 1)
1912 pgn_config.stop = 1;
1913 else if (val == 0)
1914 pgn_config.stop = 0;
1915 else
1916 return E_PGN_ERR;
1917 break;
1918 default:
1919 return E_PGN_ERR;
1922 return E_PGN_OK;
1926 * Returns the value accociated with 'f' or -1 if 'f' is invalid.
1928 int pgn_config_get(pgn_config_flag f)
1930 switch (f) {
1931 case PGN_REDUCED:
1932 return pgn_config.reduced;
1933 case PGN_STOP_ON_ERROR:
1934 return pgn_config.stop;
1935 case PGN_MPL:
1936 return pgn_config.mpl;
1937 default:
1938 break;
1941 return E_PGN_ERR;
1945 * Writes a PGN formatted game 'g' to the file pointed to by 'fp'. Returns 1
1946 * if the written move count doesn't match the game's ply count (FEN tag) or 0
1947 * on success.
1949 void pgn_write(FILE *fp, GAME g)
1951 int i;
1952 int len = 0;
1954 pgn_write_turn = (TEST_FLAG(g.flags, GF_BLACK_OPENING)) ? BLACK : WHITE;
1956 //FIXME
1958 if (!isfifo && g.hindex != g.htotal) {
1959 snprintf(buf, sizeof(buf), "%s (#%i)", GAME_SAVE_FROM_HISTORY_TITLE,
1960 idx + 1);
1961 i = message(buf, GAME_SAVE_FROM_HISTORY_PROMPT, "%s",
1962 GAME_SAVE_FROM_HISTORY_TEXT);
1964 if (i == 'c')
1965 g.htotal = g.hindex;
1969 pgn_tag_sort(g.tag);
1971 for (i = 0; g.tag[i]; i++) {
1972 struct tm tp;
1973 char tbuf[11] = {0};
1975 if (pgn_config.reduced && i == 7)
1976 break;
1978 if (strcmp(g.tag[i]->name, "Date") == 0) {
1979 if (strptime(g.tag[i]->value, PGN_TIME_FORMAT, &tp) != NULL) {
1980 len = strftime(tbuf, sizeof(tbuf), PGN_TIME_FORMAT, &tp) + 1;
1981 g.tag[i]->value = Realloc(g.tag[i]->value, len);
1982 strncpy(g.tag[i]->value, tbuf, len);
1985 else if (strcmp(g.tag[i]->name, "Event") == 0) {
1986 if (g.tag[i]->value[0] == '\0') {
1987 g.tag[i]->value = Realloc(g.tag[i]->value, 2);
1988 g.tag[i]->value[0] = '?';
1989 g.tag[i]->value[1] = '\0';
1992 else if (strcmp(g.tag[i]->name, "Site") == 0) {
1993 if (g.tag[i]->value[0] == '\0') {
1994 g.tag[i]->value = Realloc(g.tag[i]->value, 2);
1995 g.tag[i]->value[0] = '?';
1996 g.tag[i]->value[1] = '\0';
1999 else if (strcmp(g.tag[i]->name, "Round") == 0) {
2000 if (g.tag[i]->value[0] == '\0') {
2001 g.tag[i]->value = Realloc(g.tag[i]->value, 2);
2002 g.tag[i]->value[0] = '?';
2003 g.tag[i]->value[1] = '\0';
2006 else if (strcmp(g.tag[i]->name, "Result") == 0) {
2007 if (g.tag[i]->value[0] == '\0') {
2008 g.tag[i]->value = Realloc(g.tag[i]->value, 2);
2009 g.tag[i]->value[0] = '*';
2010 g.tag[i]->value[1] = '\0';
2013 else if (strcmp(g.tag[i]->name, "Black") == 0) {
2014 if (g.tag[i]->value[0] == '\0') {
2015 g.tag[i]->value = Realloc(g.tag[i]->value, 2);
2016 g.tag[i]->value[0] = '?';
2017 g.tag[i]->value[1] = '\0';
2020 else if (strcmp(g.tag[i]->name, "White") == 0) {
2021 if (g.tag[i]->value[0] == '\0') {
2022 g.tag[i]->value = Realloc(g.tag[i]->value, 2);
2023 g.tag[i]->value[0] = '?';
2024 g.tag[i]->value[1] = '\0';
2028 fprintf(fp, "[%s \"%s\"]\n", g.tag[i]->name,
2029 (g.tag[i]->value && g.tag[i]->value[0]) ?
2030 pgn_tag_add_escapes(g.tag[i]->value) : "");
2033 Fputc('\n', fp, &len);
2034 g.hp = g.history;
2035 ravlevel = 0;
2037 if (pgn_history_total(g.hp) && pgn_write_turn == BLACK)
2038 putstring(fp, "1...", &len);
2040 write_all_move_text(fp, g.hp, 1, &len);
2042 Fputc(' ', fp, &len);
2043 putstring(fp, g.tag[TAG_RESULT]->value, &len);
2044 putstring(fp, "\n\n", &len);
2046 if (!pgn_config.reduced) {
2047 CLEAR_FLAG(g.flags, GF_MODIFIED);
2048 CLEAR_FLAG(g.flags, GF_PERROR);
2052 void pgn_reset_enpassant(BOARD b)
2054 int r, c;
2056 for (r = 0; r < 8; r++) {
2057 for (c = 0; c < 8; c++)
2058 b[r][c].enpassant = 0;
2062 int pgn_validate_move(GAME *g, BOARD b, char **mp)
2064 char *p;
2065 int piece, dstpiece;
2066 int i = 0;
2067 int srow = 0, scol = 0, row, col;
2068 int dist = 0;
2069 int promo = -1;
2070 int kr = 0, kc = 0, okr = 0, okc = 0;
2071 char *m = *mp;
2073 if ((m = pgn_a2a4tosan(g, b, m)) == NULL)
2074 return E_PGN_PARSE;
2076 if (strlen(m) < 2)
2077 return E_PGN_PARSE;
2079 capture = 0;
2080 srow = row = col = scol = promo = piece = 0;
2081 again:
2082 p = (m) + strlen(m);
2084 while (!isdigit(*--p) && *p != 'O') {
2085 if (*p == '=') {
2086 promo = pgn_piece_to_int(i);
2087 i = 0;
2088 break;
2091 i = *p;
2092 *p = '\0';
2095 // Old promotion text (e8Q). Convert to SAN.
2096 if (pgn_piece_to_int(i) != -1) {
2097 p = (m) + strlen(m);
2098 *p++ = '=';
2099 *p++ = i;
2100 *p = '\0';
2101 goto again;
2104 if (strlen(m) < 2)
2105 return E_PGN_PARSE;
2107 p = m;
2109 /* Skip 'P'. */
2110 if (pgn_piece_to_int(*p) == PAWN)
2111 p++;
2112 /* Pawn. */
2113 if (VALIDCOL(*p)) {
2114 for (i = 0; *p; i++) {
2115 if (VALIDCOL(*p)) {
2116 if (i > 0)
2117 col = COLTOINT(*p++);
2118 else
2119 col = scol = COLTOINT(*p++);
2121 else if (VALIDROW(*p)) {
2122 if (1 > 1)
2123 row = ROWTOINT(*p++);
2124 else
2125 row = ROWTOINT(*p++);
2127 else if (*p == 'x') {
2128 col = COLTOINT(*++p);
2129 row = ROWTOINT(*++p);
2130 capture++;
2132 else if (*p == '=') {
2133 if (promo == -1 || promo == KING || promo == PAWN)
2134 return E_PGN_PARSE;
2136 *p++ = '=';
2137 *p++ = toupper(pgn_int_to_piece(g->turn, promo));
2138 *p = '\0';
2139 break;
2141 else {
2142 #ifdef DEBUG
2143 DUMP("Pawn (move: '%s'): %c\n", m, *p++);
2144 #else
2145 p++;
2146 #endif
2150 if (get_source_yx(g, b, PAWN, row, col, &srow, &scol))
2151 return E_PGN_INVALID;
2153 /* Not a pawn. */
2154 else {
2155 if (strcmp(m, "O-O") == 0)
2156 g->castle = KINGSIDE;
2157 else if (strcmp(m, "O-O-O") == 0)
2158 g->castle = QUEENSIDE;
2159 else {
2160 p = m;
2162 if ((piece = pgn_piece_to_int(*p++)) == -1)
2163 return E_PGN_PARSE;
2165 if (strlen(m) > 3) {
2166 if (isdigit(*p))
2167 srow = ROWTOINT(*p++);
2168 else if (VALIDCOL(*p))
2169 scol = COLTOINT(*p++);
2171 if (*p == 'x') {
2172 capture++;
2173 p++;
2177 col = COLTOINT(*p++);
2178 row = ROWTOINT(*p++);
2180 /* Get the source row and column. */
2181 if (srow == 0) {
2182 if (scol > 0) {
2183 for (i = 1; VALIDFILE(i); i++) {
2184 int fpiece = b[ROWTOBOARD(i)][COLTOBOARD(scol)].icon;
2186 if (piece == pgn_piece_to_int(fpiece) &&
2187 val_piece_side(g->turn, fpiece)) {
2188 srow = i;
2189 break;
2193 if (srow == 0)
2194 return E_PGN_INVALID;
2196 else {
2197 if (get_source_yx(g, b, piece, row, col, &srow, &scol))
2198 return E_PGN_INVALID;
2201 else if (scol == 0) {
2202 if (srow > 0) {
2203 for (i = 1; VALIDFILE(i); i++) {
2204 int fpiece = pgn_piece_to_int(b[ROWTOBOARD(srow)][COLTOBOARD(i)].icon);
2206 if (piece == fpiece) {
2207 scol = i;
2208 break;
2212 if (scol == 0)
2213 return E_PGN_INVALID;
2215 else {
2216 if (get_source_yx(g, b, piece, row, col, &srow, &scol))
2217 return E_PGN_INVALID;
2223 piece = pgn_piece_to_int(b[ROWTOBOARD(srow)][COLTOBOARD(scol)].icon);
2224 dist = abs(srow - row);
2226 if (!validate) {
2227 if (piece == PAWN || capture)
2228 g->ply = 0;
2229 else
2230 g->ply++;
2232 pgn_reset_enpassant(b);
2234 if (piece == PAWN && dist == 2) {
2235 if (g->turn == WHITE)
2236 b[ROWTOBOARD(srow - 1)][COLTOBOARD(scol)].enpassant = 1;
2237 else
2238 b[ROWTOBOARD(srow + 1)][COLTOBOARD(scol)].enpassant = 1;
2240 SET_FLAG(g->flags, GF_ENPASSANT);
2242 else {
2243 CLEAR_FLAG(g->flags, GF_ENPASSANT);
2247 dstpiece = piece = b[ROWTOBOARD(row)][COLTOBOARD(col)].icon;
2249 if (g->castle) {
2250 if (castle_move(g, b, g->castle)) {
2251 g->castle = 0;
2252 return E_PGN_INVALID;
2255 goto done;
2258 if (pgn_piece_to_int(piece) != OPEN_SQUARE) {
2259 if (val_piece_side(g->turn, piece))
2260 return E_PGN_INVALID;
2263 if (!validate) {
2264 if (promo)
2265 piece = pgn_int_to_piece(g->turn, promo);
2266 else
2267 piece = b[ROWTOBOARD(srow)][COLTOBOARD(scol)].icon;
2269 else
2270 piece = b[ROWTOBOARD(srow)][COLTOBOARD(scol)].icon;
2272 if (!validate) {
2273 b[ROWTOBOARD(srow)][COLTOBOARD(scol)].icon =
2274 pgn_int_to_piece(g->turn, OPEN_SQUARE);
2275 b[ROWTOBOARD(row)][COLTOBOARD(col)].icon = piece;
2278 done:
2279 if (!validate && capture && pgn_piece_to_int(dstpiece) == ROOK) {
2280 if (row == 1 && col == 1)
2281 CLEAR_FLAG(g->flags, GF_WQ_CASTLE);
2282 else if (row == 8 && col == 1)
2283 CLEAR_FLAG(g->flags, GF_BQ_CASTLE);
2284 else if (row == 1 && col == 8)
2285 CLEAR_FLAG(g->flags, GF_WK_CASTLE);
2286 else if (row == 8 && col == 8)
2287 CLEAR_FLAG(g->flags, GF_BK_CASTLE);
2290 kingsquare(*g, b, &kr, &kc, &okr, &okc);
2291 pgn_switch_turn(g);
2293 if (g->castle) {
2294 p = m + strlen(m);
2295 g->castle = 0;
2298 CLEAR_FLAG(g->flags, GF_GAMEOVER);
2299 i = validate;
2300 validate = 1;
2302 if (drawtest(b)) {
2303 g->tag[TAG_RESULT]->value = Realloc(g->tag[TAG_RESULT]->value, 8);
2304 strncpy(g->tag[TAG_RESULT]->value, "1/2-1/2", 8);
2305 SET_FLAG(g->flags, GF_GAMEOVER);
2307 else {
2308 switch (checktest(g, b, kr, kc, okr, okc, 0)) {
2309 case 0:
2310 break;
2311 case -1:
2312 validate = i;
2313 pgn_switch_turn(g);
2314 return E_PGN_INVALID;
2315 default:
2316 if (checkmatetest(g, b, kr, kc, okr, okc)) {
2317 *p++ = '#';
2319 if (result == WHITEWINS) {
2320 g->tag[TAG_RESULT]->value = Realloc(g->tag[TAG_RESULT]->value, 4);
2321 strncpy(g->tag[TAG_RESULT]->value, "1-0", 4);
2323 else if (result == BLACKWINS) {
2324 g->tag[TAG_RESULT]->value = Realloc(g->tag[TAG_RESULT]->value, 4);
2325 strncpy(g->tag[TAG_RESULT]->value, "0-1", 4);
2328 SET_FLAG(g->flags, GF_GAMEOVER);
2330 else
2331 *p++ = '+';
2333 *p = '\0';
2334 break;
2338 pgn_switch_turn(g);
2339 validate = i;
2340 *mp = m;
2341 return E_PGN_OK;
2344 int pgn_validate_only(GAME *g, BOARD b, char **m)
2346 int ret;
2348 validate = 1;
2349 ret = pgn_validate_move(g, b, m);
2350 validate = 0;
2351 return ret;