Fixed showing the loading progress when in-game.
[cboard.git] / libchess / pgn.c
blob09a38217361aa55283e5b09d8f3ed66054c0a429
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
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 <string.h>
28 #include <time.h>
29 #include <ctype.h>
30 #include <errno.h>
31 #include <fcntl.h>
32 #include <stdarg.h>
33 #include <err.h>
35 #ifdef HAVE_CONFIG_H
36 #include <config.h>
37 #endif
39 #ifdef HAVE_LIMITS_H
40 #include <limits.h>
41 #endif
43 #include "chess.h"
44 #include "common.h"
45 #include "pgn.h"
47 #ifdef DEBUG
48 #include "debug.h"
49 #endif
51 #ifdef WITH_DMALLOC
52 #include <dmalloc.h>
53 #endif
55 static int Fgetc(FILE *fp)
57 register int c;
59 if ((c = fgetc(fp)) != EOF) {
60 if (pgn_config.progress && pgn_config.pfunc) {
61 if (!(ftell(fp) % pgn_config.progress))
62 pgn_config.pfunc(pgn_fsize, ftell(fp));
66 return c;
69 static int Ungetc(int c, FILE *fp)
71 return ungetc(c, fp);
74 char *pgn_version()
76 return "libchess " PACKAGE_VERSION;
79 static char *trim(char *str)
81 int i = 0;
83 if (!str)
84 return NULL;
86 while (isspace(*str))
87 str++;
89 for (i = strlen(str) - 1; isspace(str[i]); i--)
90 str[i] = 0;
92 return str;
95 static char *itoa(long n)
97 static char buf[16];
99 snprintf(buf, sizeof(buf), "%li", n);
100 return buf;
104 * Clears the valid move flag for all positions on board 'b'. Returns nothing.
106 void pgn_reset_valid_moves(BOARD b)
108 int row, col;
110 #ifdef DEBUG
111 PGN_DUMP("%s:%d: resetting valid moves\n", __FILE__, __LINE__);
112 #endif
114 for (row = 0; row < 8; row++) {
115 for (col = 0; col < 8; col++)
116 b[row][col].valid = 0;
121 * Toggles g->turn. Returns nothing.
123 void pgn_switch_turn(GAME g)
125 g->turn = (g->turn == WHITE) ? BLACK : WHITE;
129 * Toggles g->side and switches the White and Black roster tags. Returns
130 * nothing.
132 void pgn_switch_side(GAME g)
134 char *w = g->tag[4]->value;
136 g->tag[4]->value = g->tag[5]->value;
137 g->tag[5]->value = w;
138 g->side = (g->side == WHITE) ? BLACK : WHITE;
142 * Creates a FEN tag from the current game 'g', history move (g->hindex) and
143 * board 'b'. Returns a FEN tag.
145 char *pgn_game_to_fen(GAME g, BOARD b)
147 int row, col;
148 int i;
149 static char buf[MAX_PGN_LINE_LEN], *p;
150 int oldturn = g->turn;
151 char enpassant[3] = {0}, *e;
152 int castle = 0;
154 #ifdef DEBUG
155 PGN_DUMP("%s:%d: creating FEN tag\n", __FILE__, __LINE__);
156 #endif
158 for (i = pgn_history_total(g->hp); i >= g->hindex - 1; i--)
159 pgn_switch_turn(g);
161 p = buf;
163 for (row = 0; row < 8; row++) {
164 int count = 0;
166 for (col = 0; col < 8; col++) {
167 if (b[row][col].enpassant) {
168 b[row][col].icon = pgn_int_to_piece(WHITE, OPEN_SQUARE);
169 e = enpassant;
170 *e++ = 'a' + col;
171 *e++ = ('0' + 8) - row;
172 *e = 0;
175 if (pgn_piece_to_int(b[row][col].icon) == OPEN_SQUARE) {
176 count++;
177 continue;
180 if (count) {
181 *p++ = '0' + count;
182 count = 0;
185 *p++ = b[row][col].icon;
186 *p = 0;
189 if (count) {
190 *p++ = '0' + count;
191 count = 0;
194 *p++ = '/';
197 --p;
198 *p++ = ' ';
199 *p++ = (g->turn == WHITE) ? 'w' : 'b';
200 *p++ = ' ';
202 if (TEST_FLAG(g->flags, GF_WK_CASTLE) && pgn_piece_to_int(b[7][7].icon) ==
203 ROOK && isupper(b[7][7].icon) && pgn_piece_to_int(b[7][4].icon) ==
204 KING && isupper(b[7][4].icon)) {
205 *p++ = 'K';
206 castle = 1;
209 if (TEST_FLAG(g->flags, GF_WQ_CASTLE) && pgn_piece_to_int(b[7][0].icon) ==
210 ROOK && isupper(b[7][0].icon) && pgn_piece_to_int(b[7][4].icon) ==
211 KING && isupper(b[7][4].icon)) {
212 *p++ = 'Q';
213 castle = 1;
216 if (TEST_FLAG(g->flags, GF_BK_CASTLE) && pgn_piece_to_int(b[0][7].icon) ==
217 ROOK && islower(b[0][7].icon) && pgn_piece_to_int(b[0][4].icon) ==
218 KING && islower(b[0][4].icon)) {
219 *p++ = 'k';
220 castle = 1;
223 if (TEST_FLAG(g->flags, GF_BQ_CASTLE) && pgn_piece_to_int(b[0][0].icon) ==
224 ROOK && islower(b[0][0].icon) && pgn_piece_to_int(b[0][4].icon) ==
225 KING && islower(b[0][4].icon)) {
226 *p++ = 'q';
227 castle = 1;
230 if (!castle)
231 *p++ = '-';
233 *p++ = ' ';
235 if (enpassant[0]) {
236 e = enpassant;
237 *p++ = *e++;
238 *p++ = *e++;
240 else
241 *p++ = '-';
243 *p++ = ' ';
245 // Halfmove clock.
246 *p = 0;
247 strcat(p, itoa(g->ply));
248 p = buf + strlen(buf);
249 *p++ = ' ';
251 // Fullmove number.
252 i = (g->hindex + 1) / 2;
253 *p = 0;
254 strcat(p, itoa((g->hindex / 2) + (g->hindex % 2)));
256 g->turn = oldturn;
257 return buf;
261 * Returns the total number of moves in 'h' or 0 if there are none.
263 int pgn_history_total(HISTORY **h)
265 int i;
267 if (!h)
268 return 0;
270 for (i = 0; h[i]; i++);
271 return i;
275 * Deallocates all of the history data from position 'start' in the array 'h'.
276 * Returns nothing.
278 void pgn_history_free(HISTORY **h, int start)
280 int i;
282 #ifdef DEBUG
283 PGN_DUMP("%s:%d: freeing history\n", __FILE__, __LINE__);
284 #endif
286 if (!h || start > pgn_history_total(h))
287 return;
289 if (start < 0)
290 start = 0;
292 for (i = start; h[i]; i++) {
293 if (h[i]->rav) {
294 pgn_history_free(h[i]->rav, 0);
295 free(h[i]->rav);
298 free(h[i]->comment);
299 free(h[i]->move);
300 free(h[i]->fen);
301 free(h[i]);
304 h[start] = NULL;
308 * Returns the history ply 'n' from 'h'. If 'n' is out of range then NULL is
309 * returned.
311 HISTORY *pgn_history_by_n(HISTORY **h, int n)
313 if (n < 0 || n > pgn_history_total(h) - 1)
314 return NULL;
316 return h[n];
320 * Appends move 'm' to game 'g' history pointer and creates a FEN tag for the
321 * current game state using board 'b'. The FEN tag makes things faster than
322 * validating the entire move history by validating only the current move to
323 * the previous moves FEN tag. The history pointer may be a in a RAV so
324 * g->rav.hp is also updated to the new (realloc()'ed) pointer. If not in a
325 * RAV then g->history will be updated. Returns E_PGN_ERR if realloc() failed
326 * or E_PGN_OK on success.
328 pgn_error_t pgn_history_add(GAME g, BOARD b, const char *m)
330 int t = pgn_history_total(g->hp);
331 int o;
332 HISTORY **h = NULL, *tmp;
333 int ri = (g->ravlevel) ? g->rav[g->ravlevel - 1].hindex : 0;
335 #ifdef DEBUG
336 PGN_DUMP("%s:%d: adding '%s' to move history\n", __FILE__, __LINE__, m);
337 #endif
339 if (g->ravlevel)
340 o = g->rav[g->ravlevel - 1].hp[ri-1]->rav - g->hp;
341 else
342 o = g->history - g->hp;
344 if ((h = realloc(g->hp, (t + 2) * sizeof(HISTORY *))) == NULL)
345 return E_PGN_ERR;
347 g->hp = h;
349 if (g->ravlevel)
350 g->rav[g->ravlevel - 1].hp[ri-1]->rav = g->hp + o;
351 else
352 g->history = g->hp + o;
354 if ((g->hp[t] = calloc(1, sizeof(HISTORY))) == NULL)
355 return E_PGN_ERR;
357 if ((g->hp[t]->move = strdup(m)) == NULL) {
358 free(g->hp[t]);
359 g->hp[t] = NULL;
360 return E_PGN_ERR;
363 tmp = g->hp[t];
364 t++;
365 g->hp[t] = NULL;
366 g->hindex = pgn_history_total(g->hp);
367 pgn_switch_turn(g);
368 tmp->fen = strdup(pgn_game_to_fen(g, b));
369 pgn_switch_turn(g);
370 return E_PGN_OK;
374 * Resets the game 'g' using board 'b' up to history move (g->hindex) 'n'.
375 * Returns E_PGN_OK on success or E_PGN_PARSE if there was a FEN tag but the
376 * parsing of it failed. Or returns E_PGN_INVALID if somehow the move failed
377 * validation while resetting.
379 pgn_error_t pgn_board_update(GAME g, BOARD b, int n)
381 BOARD tb;
382 int ret = E_PGN_OK;
383 int p_error = TEST_FLAG(g->flags, GF_PERROR);
384 int black_opening = TEST_FLAG(g->flags, GF_BLACK_OPENING);
386 #ifdef DEBUG
387 PGN_DUMP("%s:%d: updating board\n", __FILE__, __LINE__);
388 #endif
390 if (!g->ravlevel && TEST_FLAG(g->flags, GF_BLACK_OPENING))
391 g->turn = BLACK;
392 else
393 g->turn = WHITE;
395 g->flags = 0;
396 SET_FLAG(g->flags, GF_WK_CASTLE|GF_WQ_CASTLE|GF_WQ_CASTLE|
397 GF_BK_CASTLE|GF_BQ_CASTLE);
398 pgn_board_init(tb);
400 if (g->ravlevel)
401 pgn_board_init_fen(g, tb, g->rav[g->ravlevel - 1].fen);
402 else if (pgn_tag_find(g->tag, "FEN") != -1 &&
403 pgn_board_init_fen(g, tb, NULL))
404 return E_PGN_PARSE;
406 if (n) {
407 HISTORY *h = pgn_history_by_n(g->hp, n-1);
409 if (h) {
410 ret = pgn_board_init_fen(g, tb, h->fen);
411 if (ret == E_PGN_OK) {
412 h = pgn_history_by_n(g->hp, n);
413 if (h) {
414 char *p = h->move, *frfr;
416 ret = pgn_parse_move(g, tb, &p, &frfr);
417 if (ret == E_PGN_OK) {
418 h = pgn_history_by_n(g->hp, n-1);
419 ret = pgn_board_init_fen(g, tb, h->fen);
426 if (ret == E_PGN_OK)
427 memcpy(b, tb, sizeof(BOARD));
429 if (p_error)
430 SET_FLAG(g->flags, GF_PERROR);
432 if (black_opening)
433 SET_FLAG(g->flags, GF_BLACK_OPENING);
435 return ret;
439 * Updates the game 'g' using board 'b' to the next 'n'th history move.
440 * Returns nothing.
442 void pgn_history_prev(GAME g, BOARD b, int n)
444 if (g->hindex - n < 0) {
445 if (n <= 2)
446 g->hindex = pgn_history_total(g->hp);
447 else
448 g->hindex = 0;
450 else
451 g->hindex -= n;
453 pgn_board_update(g, b, g->hindex);
457 * Updates the game 'g' using board 'b' to the previous 'n'th history move.
458 * Returns nothing.
460 void pgn_history_next(GAME g, BOARD b, int n)
462 if (g->hindex + n > pgn_history_total(g->hp)) {
463 if (n <= 2)
464 g->hindex = 0;
465 else
466 g->hindex = pgn_history_total(g->hp);
468 else
469 g->hindex += n;
471 pgn_board_update(g, b, g->hindex);
475 * Converts the character piece 'p' to an integer. Returns the integer
476 * associated with 'p' or E_PGN_ERR if 'p' is invalid.
478 int pgn_piece_to_int(int p)
480 if (p == '.')
481 return OPEN_SQUARE;
483 p = tolower(p);
485 switch (p) {
486 case 'p':
487 return PAWN;
488 case 'r':
489 return ROOK;
490 case 'n':
491 return KNIGHT;
492 case 'b':
493 return BISHOP;
494 case 'q':
495 return QUEEN;
496 case 'k':
497 return KING;
498 default:
499 break;
502 #ifdef DEBUG
503 PGN_DUMP("%s:%d: invalid piece '%c'\n", __FILE__, __LINE__, p);
504 #endif
505 return E_PGN_ERR;
509 * Converts the integer piece 'n' to a character whose turn is 'turn'. WHITE
510 * piece are uppercase and BLACK pieces are lowercase. Returns the character
511 * associated with 'n' or E_PGN_ERR if 'n' is an invalid piece.
513 pgn_error_t pgn_int_to_piece(char turn, int n)
515 int p = 0;
517 switch (n) {
518 case PAWN:
519 p = 'p';
520 break;
521 case ROOK:
522 p = 'r';
523 break;
524 case KNIGHT:
525 p = 'n';
526 break;
527 case BISHOP:
528 p = 'b';
529 break;
530 case QUEEN:
531 p = 'q';
532 break;
533 case KING:
534 p = 'k';
535 break;
536 case OPEN_SQUARE:
537 p = '.';
538 break;
539 default:
540 #ifdef DEBUG
541 PGN_DUMP("%s:%d: unknown piece integer %i\n", __FILE__,
542 __LINE__, n);
543 #endif
544 return E_PGN_ERR;
545 break;
548 return (turn == WHITE) ? toupper(p) : p;
552 * Finds a tag 'name' in the structure array 't'. Returns the location in the
553 * array of the found tag or E_PGN_ERR if the tag could not be found.
555 pgn_error_t pgn_tag_find(TAG **t, const char *name)
557 int i;
559 for (i = 0; t[i]; i++) {
560 if (strcasecmp(t[i]->name, name) == 0)
561 return i;
564 return E_PGN_ERR;
567 static pgn_error_t remove_tag(TAG ***array, const char *tag)
569 TAG **tags = *array;
570 int n = pgn_tag_find(tags, tag);
571 int i, t;
573 if (n == E_PGN_ERR)
574 return E_PGN_ERR;
576 for (i = t = 0; tags[i]; i++) {
577 if (i == n) {
578 free(tags[i]->name);
579 free(tags[i]->value);
580 free(tags[i]);
581 continue;
584 tags[t++] = tags[i];
587 tags = realloc(*array, (t + 1) * sizeof(TAG *));
588 tags[t] = NULL;
589 *array = tags;
590 #ifdef DEBUG
591 PGN_DUMP("%s:%d: removed tag: name='%s'\n", __FILE__, __LINE__, tag);
592 #endif
593 return E_PGN_OK;
596 static int tag_compare(const void *a, const void *b)
598 TAG * const *ta = a;
599 TAG * const *tb = b;
601 return strcmp((*ta)->name, (*tb)->name);
605 * Sorts a tag array. The first seven tags are in order of the PGN standard so
606 * don't sort'em. Returns nothing.
608 void pgn_tag_sort(TAG **tags)
610 if (pgn_tag_total(tags) <= 7)
611 return;
613 qsort(tags + 7, pgn_tag_total(tags) - 7, sizeof(TAG *), tag_compare);
617 * Returns the total number of tags in 't' or 0 if 't' is NULL.
619 int pgn_tag_total(TAG **tags)
621 int i = 0;
623 if (!tags)
624 return 0;
626 while (tags[i])
627 i++;
629 return i;
633 * Adds a tag 'name' with value 'value' to the pointer to array of TAG
634 * pointers 'dst'. If a duplicate tag 'name' was found then the existing tag
635 * is updated to the new 'value'. If 'value' is NULL, the tag is removed.
636 * Returns E_PGN_ERR if there was a memory allocation error or E_PGN_OK on
637 * success.
639 pgn_error_t pgn_tag_add(TAG ***dst, char *name, char *value)
641 int i;
642 TAG **tdata = *dst;
643 TAG **a = NULL;
644 int t = pgn_tag_total(tdata);
646 #ifdef DEBUG
647 PGN_DUMP("%s:%d: adding tag\n", __FILE__, __LINE__);
648 #endif
650 if (!name)
651 return E_PGN_ERR;
653 name = trim(name);
655 if (value)
656 value = trim(value);
658 // Find an existing tag with 'name'.
659 for (i = 0; i < t; i++) {
660 char *tmp = NULL;
662 if (strcasecmp(tdata[i]->name, name) == 0) {
663 if (value) {
664 if ((tmp = strdup(value)) == NULL)
665 return E_PGN_ERR;
667 else {
668 remove_tag(dst, name);
669 return E_PGN_OK;
672 free(tdata[i]->value);
673 tdata[i]->value = tmp;
674 *dst = tdata;
675 return E_PGN_OK;
679 if ((a = realloc(tdata, (t + 2) * sizeof(TAG *))) == NULL)
680 return E_PGN_ERR;
682 tdata = a;
684 if ((tdata[t] = malloc(sizeof(TAG))) == NULL)
685 return E_PGN_ERR;
687 if ((tdata[t]->name = strdup(name)) == NULL) {
688 tdata[t] = NULL;
689 return E_PGN_ERR;
692 if (value) {
693 if ((tdata[t]->value = strdup(value)) == NULL) {
694 free(tdata[t]->name);
695 tdata[t] = NULL;
696 return E_PGN_ERR;
699 else
700 tdata[t]->value = NULL;
702 tdata[t]->name[0] = toupper(tdata[t]->name[0]);
703 tdata[++t] = NULL;
704 *dst = tdata;
706 #ifdef DEBUG
707 PGN_DUMP("%s:%d: added tag: name='%s' value='%s'\n", __FILE__, __LINE__,
708 name, (value) ? value : "null");
709 #endif
711 return E_PGN_OK;
714 static char *remove_tag_escapes(const char *str)
716 int i, n;
717 int len = strlen(str);
718 static char buf[MAX_PGN_LINE_LEN] = {0};
720 for (i = n = 0; i < len; i++, n++) {
721 switch (str[i]) {
722 case '\\':
723 i++;
724 default:
725 break;
728 buf[n] = str[i];
731 buf[n] = '\0';
732 return buf;
736 * Resets or initializes a new game board 'b'. Returns nothing.
738 void pgn_board_init(BOARD b)
740 int row, col;
742 #ifdef DEBUG
743 PGN_DUMP("%s:%d: initializing board\n", __FILE__, __LINE__);
744 #endif
746 memset(b, 0, sizeof(BOARD));
748 for (row = 0; row < 8; row++) {
749 for (col = 0; col < 8; col++) {
750 int c = '.';
752 switch (row) {
753 case 0:
754 case 7:
755 switch (col) {
756 case 0:
757 case 7:
758 c = 'r';
759 break;
760 case 1:
761 case 6:
762 c = 'n';
763 break;
764 case 2:
765 case 5:
766 c = 'b';
767 break;
768 case 3:
769 c = 'q';
770 break;
771 case 4:
772 c = 'k';
773 break;
775 break;
776 case 1:
777 case 6:
778 c = 'p';
779 break;
782 b[row][col].icon = (row < 2) ? c : toupper(c);
788 * Adds the standard PGN roster tags to game 'g'.
790 static void set_default_tags(GAME g)
792 time_t now;
793 char tbuf[11] = {0};
794 struct tm *tp;
795 struct passwd *pw = getpwuid(getuid());
797 time(&now);
798 tp = localtime(&now);
799 strftime(tbuf, sizeof(tbuf), PGN_TIME_FORMAT, tp);
801 /* The standard seven tag roster (in order of appearance). */
802 if (pgn_tag_add(&g->tag, "Event", "?") != E_PGN_OK)
803 warn("pgn_tag_add()");
805 if (pgn_tag_add(&g->tag, "Site", "?") != E_PGN_OK)
806 warn("pgn_tag_add()");
808 if (pgn_tag_add(&g->tag, "Date", tbuf) != E_PGN_OK)
809 warn("pgn_tag_add()");
811 if (pgn_tag_add(&g->tag, "Round", "-") != E_PGN_OK)
812 warn("pgn_tag_add()");
814 if (pgn_tag_add(&g->tag, "White", pw->pw_gecos) != E_PGN_OK)
815 warn("pgn_tag_add()");
817 if (pgn_tag_add(&g->tag, "Black", "?") != E_PGN_OK)
818 warn("pgn_tag_add()");
820 if (pgn_tag_add(&g->tag, "Result", "*") != E_PGN_OK)
821 warn("pgn_tag_add()");
825 * Frees a TAG array. Returns nothing.
827 void pgn_tag_free(TAG **tags)
829 int i;
830 int t = pgn_tag_total(tags);
832 #ifdef DEBUG
833 PGN_DUMP("%s:%d: freeing tags\n", __FILE__, __LINE__);
834 #endif
836 if (!tags)
837 return;
839 for (i = 0; i < t; i++) {
840 free(tags[i]->name);
841 free(tags[i]->value);
842 free(tags[i]);
845 free(tags);
849 * Frees a single game 'g'. Returns nothing.
851 void pgn_free(GAME g)
853 #ifdef DEBUG
854 PGN_DUMP("%s:%d: freeing game\n", __FILE__, __LINE__);
855 #endif
856 pgn_history_free(g->history, 0);
857 free(g->history);
858 pgn_tag_free(g->tag);
860 if (g->rav) {
861 for (g->ravlevel--; g->ravlevel >= 0; g->ravlevel--)
862 free(g->rav[g->ravlevel].fen);
864 free(g->rav);
867 free(g);
871 * Frees all games in the global 'game' array. Returns nothing.
873 void pgn_free_all()
875 int i;
877 #ifdef DEBUG
878 PGN_DUMP("%s:%d: freeing game data\n", __FILE__, __LINE__);
879 #endif
881 for (i = 0; i < gtotal; i++) {
882 pgn_free(game[i]);
885 if (game)
886 free(game);
888 game = NULL;
891 static void reset_game_data()
893 #ifdef DEBUG
894 PGN_DUMP("%s:%d: resetting game data\n", __FILE__, __LINE__);
895 #endif
896 pgn_free_all();
897 gtotal = gindex = 0;
900 static void skip_leading_space(FILE *fp)
902 int c;
904 while ((c = Fgetc(fp)) != EOF && !feof(fp)) {
905 if (!isspace(c))
906 break;
909 Ungetc(c, fp);
913 * PGN move text section.
915 static int move_text(GAME g, FILE *fp)
917 char m[MAX_SAN_MOVE_LEN + 1] = {0}, *p;
918 int c;
919 int count;
920 char *frfr;
922 g->oflags = g->flags;
924 while((c = Fgetc(fp)) != EOF) {
925 if (isdigit(c) || isspace(c) || c == '.')
926 continue;
928 break;
931 Ungetc(c, fp);
933 if (fscanf(fp, " %[a-hPpRrNnBbQqKk1-9#+=Ox-]%n", m, &count) != 1)
934 return 1;
936 m[MAX_SAN_MOVE_LEN] = 0;
937 p = m;
939 if (pgn_parse_move(g, pgn_board, &p, &frfr)) {
940 pgn_switch_turn(g);
941 return 1;
944 #ifdef DEBUG
945 PGN_DUMP("%s\n%s", p, debug_board(pgn_board));
946 #endif
948 pgn_history_add(g, pgn_board, p);
949 pgn_switch_turn(g);
950 return 0;
954 * PGN nag text.
956 static void nag_text(GAME g, FILE *fp)
958 int c, i, t;
959 char nags[5], *n = nags;
960 int nag = 0;
962 while ((c = Fgetc(fp)) != EOF && !isspace(c)) {
963 if (c == '$') {
964 while ((c = Fgetc(fp)) != EOF && isdigit(c))
965 *n++ = c;
967 Ungetc(c, fp);
968 break;
971 if (c == '!') {
972 if ((c = Fgetc(fp)) == '!')
973 nag = 3;
974 else if (c == '?')
975 nag = 5;
976 else {
977 Ungetc(c, fp);
978 nag = 1;
981 break;
983 else if (c == '?') {
984 if ((c = Fgetc(fp)) == '?')
985 nag = 4;
986 else if (c == '!')
987 nag = 6;
988 else {
989 Ungetc(c, fp);
990 nag = 2;
993 break;
995 else if (c == '~')
996 nag = 13;
997 else if (c == '=') {
998 if ((c = Fgetc(fp)) == '+')
999 nag = 15;
1000 else {
1001 Ungetc(c, fp);
1002 nag = 10;
1005 break;
1007 else if (c == '+') {
1008 if ((t = Fgetc(fp)) == '=')
1009 nag = 14;
1010 else if (t == '-')
1011 nag = 18;
1012 else if (t == '/') {
1013 if ((i = Fgetc(fp)) == '-')
1014 nag = 16;
1015 else
1016 Ungetc(i, fp);
1018 break;
1020 else
1021 Ungetc(t, fp);
1023 break;
1025 else if (c == '-') {
1026 if ((t = Fgetc(fp)) == '+')
1027 nag = 18;
1028 else if (t == '/') {
1029 if ((i = Fgetc(fp)) == '+')
1030 nag = 17;
1031 else
1032 Ungetc(i, fp);
1034 break;
1036 else
1037 Ungetc(t, fp);
1039 break;
1043 *n = '\0';
1045 if (!nag)
1046 nag = (nags[0]) ? atoi(nags) : 0;
1048 if (!nag || nag < 0 || nag > 255)
1049 return;
1051 // FIXME -1 is because move_text() increments g->hindex. The NAG
1052 // annoatation isnt guaranteed to be after the move text in import format.
1053 for (i = 0; i < MAX_PGN_NAG; i++) {
1054 if (g->hp[g->hindex - 1]->nag[i])
1055 continue;
1057 g->hp[g->hindex - 1]->nag[i] = nag;
1058 break;
1061 skip_leading_space(fp);
1065 * PGN move annotation.
1067 static int annotation_text(GAME g, FILE *fp, int terminator)
1069 int c, lastchar = 0;
1070 int len = 0;
1071 int hindex = pgn_history_total(g->hp) - 1;
1072 char buf[MAX_PGN_LINE_LEN], *a = buf;
1074 skip_leading_space(fp);
1076 while ((c = Fgetc(fp)) != EOF && c != terminator) {
1077 if (c == '\n')
1078 c = ' ';
1080 if (isspace(c) && isspace(lastchar))
1081 continue;
1083 if (len + 1 == sizeof(buf))
1084 continue;
1086 *a++ = lastchar = c;
1087 len++;
1090 *a = '\0';
1092 if (hindex < 0)
1093 hindex = 0;
1096 * This annotation is before any move text or NAg-> Allocate a new move.
1098 if (!g->hp[hindex]) {
1099 if ((g->hp[hindex] = calloc(1, sizeof(HISTORY))) == NULL)
1100 return E_PGN_ERR;
1103 if ((g->hp[hindex]->comment = strdup(buf)) == NULL)
1104 return E_PGN_ERR;
1106 return E_PGN_OK;
1110 * PGN roster tag->
1112 static int tag_text(GAME g, FILE *fp)
1114 char name[LINE_MAX], *n = name;
1115 char value[LINE_MAX], *v = value;
1116 int c, i = 0;
1117 int lastchar = 0;
1119 skip_leading_space(fp);
1121 /* The tag name is up until the first whitespace. */
1122 while ((c = Fgetc(fp)) != EOF && !isspace(c))
1123 *n++ = c;
1125 *n = '\0';
1126 *name = toupper(*name);
1127 skip_leading_space(fp);
1129 /* The value is until the first closing bracket. */
1130 while ((c = Fgetc(fp)) != EOF && c != ']') {
1131 if (i++ == '\0' && c == '\"')
1132 continue;
1134 if (c == '\n' || c == '\t')
1135 c = ' ';
1137 if (c == ' ' && lastchar == ' ')
1138 continue;
1140 lastchar = *v++ = c;
1143 *v = '\0';
1145 while (isspace(*--v))
1146 *v = '\0';
1148 if (*v == '\"')
1149 *v = '\0';
1151 if (value[0] == '\0') {
1152 if (strcmp(name, "Result") == 0)
1153 value[0] = '*';
1154 else
1155 value[0] = '?';
1157 value[1] = '\0';
1160 strncpy(value, remove_tag_escapes(value), sizeof(value));
1163 * See eog_text() for an explanation.
1165 if (strcmp(name, "Result") == 0) {
1166 if (strcmp(value, "1/2-1/2") == 0) {
1167 if (pgn_tag_add(&g->tag, name, value) != E_PGN_OK)
1168 warn("pgn_tag_add()");
1171 else {
1172 if (pgn_tag_add(&g->tag, name, value) != E_PGN_OK)
1173 warn("pgn_tag_add()");
1176 return 0;
1180 * PGN end-of-game marker.
1182 static int eog_text(GAME g, FILE *fp)
1184 int c, i = 0;
1185 char buf[8], *p = buf;
1187 while ((c = Fgetc(fp)) != EOF && !isspace(c) && i++ < sizeof(buf))
1188 *p++ = c;
1190 if (isspace(c))
1191 Ungetc(c, fp);
1193 *p = 0;
1195 if (strcmp(buf, "1-0") != 0 && strcmp(buf, "0-1") != 0 &&
1196 strcmp(buf, "1/2-1/2") != 0 && strcmp(buf, "*") != 0)
1197 return 1;
1200 * The eog marker in the move text may not match the actual game result.
1201 * We'll leave it up to the move validator to determine the result unless
1202 * the move validator cannot determine who won and the move text says it's
1203 * a draw.
1205 if (g->tag[6]->value[0] == '*' && strcmp("1/2-1/2", buf) == 0) {
1206 if (pgn_tag_add(&g->tag, "Result", buf) != E_PGN_OK)
1207 warn("pgn_tag_add()");
1210 return 0;
1214 * Parse RAV text and keep track of g->hp. The 'o' argument is the board state
1215 * before the current move (.hindex) was parsed.
1217 static int read_file(FILE *);
1218 static int rav_text(GAME g, FILE *fp, int which, BOARD o)
1220 struct game_s tg;
1221 RAV *r;
1223 // Begin RAV for the current move.
1224 if (which == '(') {
1225 if (!g->hindex)
1226 return 1;
1228 pgn_rav++; // For detecting parse errors.
1231 * Save the current game state for this RAV depth/level.
1233 if ((r = realloc(g->rav, (g->ravlevel + 1) * sizeof(RAV))) == NULL) {
1234 warn("realloc()");
1235 return 1;
1238 g->rav = pgn_rav_p = r;
1240 if ((g->rav[g->ravlevel].fen = strdup(pgn_game_to_fen(g, pgn_board)))
1241 == NULL) {
1242 warn("strdup()");
1243 return 1;
1246 g->rav[g->ravlevel].hp = g->hp;
1247 g->rav[g->ravlevel].hindex = g->hindex;
1248 memcpy(&tg, &(*g), sizeof(struct game_s));
1249 g->flags = g->oflags;
1250 memcpy(pgn_board, o, sizeof(BOARD));
1252 if ((g->hp[g->hindex - 1]->rav = calloc(1, sizeof(HISTORY *))) == NULL) {
1253 warn("calloc()");
1254 return 1;
1258 * pgn_history_add() will now append to the new history pointer that
1259 * is g->hp[previous_move]->rav.
1261 g->hp = g->hp[g->hindex - 1]->rav;
1264 * Reset. Will be restored later from 'tg' which is a local variable
1265 * so recursion is possible.
1267 g->hindex = 0;
1268 g->ravlevel++;
1271 * Undo move_text()'s switch.
1273 pgn_switch_turn(g);
1276 * Now continue as normal as if there were no RAV. Moves will be
1277 * parsed and appended to the new history pointer.
1279 if (read_file(fp))
1280 return 1;
1283 * read_file() has returned. This means that a RAV has ended by this
1284 * function returning -1 (see below). So we restore the game state
1285 * (tg) that was saved before calling read_file().
1287 pgn_board_init_fen(&tg, pgn_board, g->rav[tg.ravlevel].fen);
1288 free(g->rav[tg.ravlevel].fen);
1289 memcpy(&(*g), &tg, sizeof(struct game_s));
1290 g->rav = pgn_rav_p;
1293 * The end of a RAV. This makes the read_file() that called this function
1294 * return (see above and the switch statement in read_file()).
1296 else if (which == ')') {
1297 pgn_rav--; // For detecting parse errors.
1298 return (pgn_rav < 0) ? 1 : -1;
1301 return 0;
1305 * FIXME
1306 * See pgn_board_init_fen(). Returns E_PGN_PARSE on parse error. 0 may be
1307 * returned on success when there is no move count in the FEN tag otherwise
1308 * the move count is returned.
1310 static int parse_fen_line(BOARD b, unsigned *flags, char *turn, char *ply,
1311 char *str)
1313 char *tmp;
1314 char line[LINE_MAX], *s;
1315 int row = 8, col = 1;
1317 #ifdef DEBUG
1318 PGN_DUMP("%s:%d: FEN line is '%s'\n", __FILE__, __LINE__, str);
1319 #endif
1320 strncpy(line, str, sizeof(line));
1321 s = line;
1322 pgn_reset_enpassant(b);
1324 while ((tmp = strsep(&s, "/")) != NULL) {
1325 int n;
1327 if (!VALIDFILE(row))
1328 return E_PGN_PARSE;
1330 while (*tmp) {
1331 if (*tmp == ' ')
1332 goto other;
1334 if (isdigit(*tmp)) {
1335 n = *tmp - '0';
1337 if (!VALIDFILE(n))
1338 return E_PGN_PARSE;
1340 for (; n; --n, col++)
1341 b[RANKTOBOARD(row)][FILETOBOARD(col)].icon =
1342 pgn_int_to_piece(WHITE, OPEN_SQUARE);
1344 else if (pgn_piece_to_int(*tmp) != -1)
1345 b[RANKTOBOARD(row)][FILETOBOARD(col++)].icon = *tmp;
1346 else
1347 return E_PGN_PARSE;
1349 tmp++;
1352 row--;
1353 col = 1;
1356 other:
1357 tmp++;
1359 switch (*tmp++) {
1360 case 'b':
1361 *turn = BLACK;
1362 break;
1363 case 'w':
1364 *turn = WHITE;
1365 break;
1366 default:
1367 return E_PGN_PARSE;
1370 tmp++;
1372 while (*tmp && *tmp != ' ') {
1373 switch (*tmp++) {
1374 case 'K':
1375 SET_FLAG(*flags, GF_WK_CASTLE);
1376 break;
1377 case 'Q':
1378 SET_FLAG(*flags, GF_WQ_CASTLE);
1379 break;
1380 case 'k':
1381 SET_FLAG(*flags, GF_BK_CASTLE);
1382 break;
1383 case 'q':
1384 SET_FLAG(*flags, GF_BQ_CASTLE);
1385 break;
1386 case '-':
1387 break;
1388 default:
1389 return E_PGN_PARSE;
1393 tmp++;
1395 // En passant.
1396 if (*tmp != '-') {
1397 if (!VALIDCOL(*tmp))
1398 return E_PGN_PARSE;
1400 col = *tmp++ - 'a';
1402 if (!VALIDROW(*tmp))
1403 return E_PGN_PARSE;
1405 row = 8 - atoi(tmp++);
1406 b[row][col].enpassant = 1;
1407 SET_FLAG(*flags, GF_ENPASSANT);
1409 else
1410 tmp++;
1412 if (*tmp)
1413 tmp++;
1415 if (*tmp)
1416 *ply = atoi(tmp);
1418 while (*tmp && isdigit(*tmp))
1419 tmp++;
1421 if (*tmp)
1422 tmp++;
1424 return E_PGN_OK;
1428 * It initializes the board (b) to the FEN tag (if found) and sets the
1429 * castling and enpassant info for the game 'g'. If 'fen' is set it should be
1430 * a fen tag and will be parsed rather than the game 'g'.tag FEN tag-> Returns
1431 * E_PGN_OK on success or if there was both a FEN and SetUp tag with the SetUp
1432 * tag set to 0. Returns E_PGN_PARSE if there was a FEN parse error, E_PGN_ERR
1433 * if there was no FEN tag or there was a SetUp tag with a value of 0. Returns
1434 * E_PGN_OK on success.
1436 pgn_error_t pgn_board_init_fen(GAME g, BOARD b, char *fen)
1438 int n = -1, i = -1;
1439 BOARD tmpboard;
1440 unsigned flags = 0;
1441 char turn = g->turn;
1442 char ply = 0;
1444 #ifdef DEBUG
1445 PGN_DUMP("%s:%d: initializing board from FEN\n", __FILE__, __LINE__);
1446 #endif
1447 pgn_board_init(tmpboard);
1449 if (!fen) {
1450 n = pgn_tag_find(g->tag, "Setup");
1451 i = pgn_tag_find(g->tag, "FEN");
1455 * If the FEN tag exists and there is no SetUp tag go ahead and parse it.
1456 * If there is a SetUp tag only parse the FEN tag if the value is 1.
1458 if ((n >= 0 && i >= 0 && atoi(g->tag[n]->value) == 1)
1459 || (i >= 0 && n == -1) || fen) {
1460 if ((n = parse_fen_line(tmpboard, &flags, &turn, &ply,
1461 (fen) ? fen : g->tag[i]->value)) != E_PGN_OK)
1462 return E_PGN_PARSE;
1463 else {
1464 memcpy(b, tmpboard, sizeof(BOARD));
1465 CLEAR_FLAG(g->flags, GF_WK_CASTLE);
1466 CLEAR_FLAG(g->flags, GF_WQ_CASTLE);
1467 CLEAR_FLAG(g->flags, GF_BK_CASTLE);
1468 CLEAR_FLAG(g->flags, GF_BQ_CASTLE);
1469 g->flags |= flags;
1470 g->turn = turn;
1471 g->ply = ply;
1474 else
1475 return (i >= 0 && n >= 0) ? E_PGN_OK : E_PGN_ERR;
1477 return E_PGN_OK;
1481 * Allocates a new game and increments 'gtotal'. 'gindex' is then set to the
1482 * new game. Returns E_PGN_ERR if there was a memory allocation error or
1483 * E_PGN_OK on success.
1485 pgn_error_t pgn_new_game()
1487 GAME *g;
1488 GAME newg;
1489 int t = gtotal + 1;
1491 #ifdef DEBUG
1492 PGN_DUMP("%s:%d: allocating new game\n", __FILE__, __LINE__);
1493 #endif
1494 gindex = t - 1;
1496 if ((g = realloc(game, t * sizeof(GAME *))) == NULL) {
1497 warn("realloc()");
1498 return E_PGN_ERR;
1501 game = g;
1503 if ((newg = calloc(1, sizeof(struct game_s))) == NULL) {
1504 warn("calloc()");
1505 return E_PGN_ERR;
1508 game[gindex] = newg;
1510 if ((game[gindex]->hp = calloc(1, sizeof(HISTORY *))) == NULL) {
1511 free(game[gindex]);
1512 warn("calloc()");
1513 return E_PGN_ERR;
1516 game[gindex]->hp[0] = NULL;
1517 game[gindex]->history = game[gindex]->hp;
1518 game[gindex]->side = game[gindex]->turn = WHITE;
1519 SET_FLAG(game[gindex]->flags, GF_WK_CASTLE|GF_WQ_CASTLE|GF_WQ_CASTLE|
1520 GF_BK_CASTLE|GF_BQ_CASTLE);
1521 pgn_board_init(pgn_board);
1522 set_default_tags(game[gindex]);
1523 gtotal = t;
1524 return E_PGN_OK;
1527 static int read_file(FILE *fp)
1529 #ifdef DEBUG
1530 char buf[LINE_MAX] = {0}, *p = buf;
1531 #endif
1532 int c = 0;
1533 int parse_error = 0;
1534 BOARD old;
1536 while (1) {
1537 int nextchar = 0;
1538 int lastchar = c;
1539 int n;
1542 * A parse error may have occured at EOF.
1544 if (parse_error) {
1545 pgn_ret = E_PGN_PARSE;
1547 if (!game)
1548 pgn_new_game();
1550 SET_FLAG(game[gindex]->flags, GF_PERROR);
1553 if ((c = Fgetc(fp)) == EOF) {
1554 if (feof(fp))
1555 break;
1557 if (ferror(fp)) {
1558 clearerr(fp);
1559 continue;
1563 if (!isascii(c)) {
1564 parse_error = 1;
1565 continue;
1568 if (c == '\015')
1569 continue;
1571 nextchar = Fgetc(fp);
1572 Ungetc(nextchar, fp);
1575 * If there was a move text parsing error, keep reading until the end
1576 * of the current game discarding the data.
1578 if (parse_error) {
1579 pgn_ret = E_PGN_PARSE;
1581 if (game[gindex]->ravlevel)
1582 return 1;
1584 if (pgn_config.stop)
1585 return E_PGN_PARSE;
1587 if (c == '\n' && (nextchar == '\n' || nextchar == '\015')) {
1588 parse_error = 0;
1589 nulltags = 1;
1590 tag_section = 0;
1592 else
1593 continue;
1596 // New game reached.
1597 if (c == '\n' && (nextchar == '\n' || nextchar == '\015')) {
1598 if (tag_section)
1599 continue;
1601 nulltags = 1;
1602 tag_section = 0;
1603 continue;
1607 * PGN: Application comment. The '%' must be on the first column of
1608 * the line. The comment continues until the end of the current line.
1610 if (c == '%') {
1611 if (lastchar == '\n' || lastchar == 0) {
1612 while ((c = Fgetc(fp)) != EOF && c != '\n');
1613 continue;
1616 // Not sure what to do here.
1619 if (isspace(c))
1620 continue;
1622 // PGN: Reserved.
1623 if (c == '<' || c == '>')
1624 continue;
1627 * PGN: Recurrsive Annotated Variation. Read rav_text() for more
1628 * info.
1630 if (c == '(' || c == ')') {
1631 switch (rav_text(game[gindex], fp, c, old)) {
1632 case -1:
1634 * This is the end of the current RAV. This function has
1635 * been called from rav_text(). Returning from this point
1636 * will put us back in rav_text().
1638 if (game[gindex]->ravlevel > 0)
1639 return pgn_ret;
1642 * We're back at the root move. Continue as normal.
1644 break;
1645 case 1:
1646 parse_error = 1;
1647 continue;
1648 default:
1650 * Continue processing-> Probably the root move.
1652 break;
1655 if (!game[gindex]->ravlevel && pgn_rav)
1656 parse_error = 1;
1658 continue;
1661 // PGN: Numeric Annotation Glyph.
1662 if (c == '$' || c == '!' || c == '?' || c == '+' || c == '-' ||
1663 c == '~' || c == '=') {
1664 Ungetc(c, fp);
1665 nag_text(game[gindex], fp);
1666 continue;
1670 * PGN: Annotation. The ';' comment continues until the end of the
1671 * current line. The '{' type comment continues until a '}' is
1672 * reached.
1674 if (c == '{' || c == ';') {
1675 annotation_text(game[gindex], fp, (c == '{') ? '}' : '\n');
1676 continue;
1679 // PGN: Roster tag->
1680 if (c == '[') {
1681 // First roster tag found. Initialize the data structures.
1682 if (!tag_section) {
1683 nulltags = 0;
1684 tag_section = 1;
1686 if (gtotal && pgn_history_total(game[gindex]->hp))
1687 game[gindex]->hindex = pgn_history_total(game[gindex]->hp) - 1;
1689 if (pgn_new_game() != E_PGN_OK) {
1690 pgn_ret = E_PGN_ERR;
1691 break;
1694 memcpy(old, pgn_board, sizeof(BOARD));
1697 if (tag_text(game[gindex], fp))
1698 parse_error = 1; // FEN tag parse error.
1700 continue;
1703 // PGN: End-of-game markers.
1704 if ((isdigit(c) && (nextchar == '-' || nextchar == '/')) || c == '*') {
1705 Ungetc(c, fp);
1707 if (eog_text(game[gindex], fp)) {
1708 parse_error = 1;
1709 continue;
1712 nulltags = 1;
1713 tag_section = 0;
1715 if (!game[gindex]->done_fen_tag) {
1716 if (pgn_tag_find(game[gindex]->tag, "FEN") != -1 &&
1717 pgn_board_init_fen(game[gindex], pgn_board, NULL)) {
1718 parse_error = 1;
1719 continue;
1722 game[gindex]->pgn_fen_tag = pgn_tag_find(game[gindex]->tag, "FEN");
1723 game[gindex]->done_fen_tag = 1;
1726 continue;
1729 // PGN: Move text.
1730 if ((isdigit(c) && c != '0') || VALIDCOL(c) || c == 'N' || c == 'K'
1731 || c == 'Q' || c == 'B' || c == 'R' || c == 'P' ||
1732 c == 'O') {
1733 Ungetc(c, fp);
1735 // PGN: If a FEN tag exists, initialize the board to the value.
1736 if (tag_section) {
1737 if (pgn_tag_find(game[gindex]->tag, "FEN") != E_PGN_ERR &&
1738 (n = pgn_board_init_fen(game[gindex], pgn_board,
1739 NULL)) == E_PGN_PARSE) {
1740 parse_error = 1;
1741 continue;
1744 game[gindex]->done_fen_tag = 1;
1745 game[gindex]->pgn_fen_tag = pgn_tag_find(game[gindex]->tag, "FEN");
1746 tag_section = 0;
1750 * PGN: Import format doesn't require a roster tag section. We've
1751 * arrived to the move text section without any tags so we
1752 * initialize a new game which set's the default tags and any tags
1753 * from the configuration file.
1755 if (nulltags) {
1756 if (gtotal)
1757 game[gindex]->hindex = pgn_history_total(game[gindex]->hp) - 1;
1759 if (pgn_new_game() != E_PGN_OK) {
1760 pgn_ret = E_PGN_ERR;
1761 break;
1764 memcpy(old, pgn_board, sizeof(BOARD));
1765 nulltags = 0;
1768 memcpy(old, pgn_board, sizeof(BOARD));
1770 if (move_text(game[gindex], fp)) {
1771 if (pgn_tag_add(&game[gindex]->tag, "Result", "*") ==
1772 E_PGN_ERR) {
1773 warn("pgn_tag_add()");
1774 pgn_ret = E_PGN_ERR;
1777 SET_FLAG(game[gindex]->flags, GF_PERROR);
1778 parse_error = 1;
1781 continue;
1784 #ifdef DEBUG
1785 *p++ = c;
1787 PGN_DUMP("%s:%d: unparsed: '%s'\n", __FILE__, __LINE__, buf);
1789 if (strlen(buf) + 1 == sizeof(buf))
1790 bzero(buf, sizeof(buf));
1791 #endif
1793 continue;
1796 return pgn_ret;
1800 * Parses a PGN_FILE which was opened with pgn_open(). If 'pgn' is NULL then a
1801 * single empty game will be allocated. If there is a parsing error
1802 * E_PGN_PARSE is returned, if there was a memory allocation error E_PGN_ERR
1803 * is returned, otherwise E_PGN_OK is returned and the global 'gindex' is set
1804 * to the last parsed game in the file and the global 'gtotal' is set to the
1805 * total number of games in the file. The file should be closed with
1806 * pgn_close() after processing.
1808 pgn_error_t pgn_parse(PGN_FILE *pgn)
1810 int i;
1812 if (!pgn) {
1813 reset_game_data();
1814 pgn_ret = pgn_new_game();
1815 goto done;
1818 reset_game_data();
1819 nulltags = 1;
1820 fseek(pgn->fp, 0, SEEK_END);
1821 pgn_fsize = ftell(pgn->fp);
1822 fseek(pgn->fp, 0, SEEK_SET);
1823 #ifdef DEBUG
1824 PGN_DUMP("%s:%d: BEGIN parsing->..\n", __FILE__, __LINE__);
1825 #endif
1826 pgn_ret = read_file(pgn->fp);
1828 #ifdef DEBUG
1829 PGN_DUMP("%s:%d: END parsing->..\n", __FILE__, __LINE__);
1830 #endif
1832 if (gtotal < 1)
1833 pgn_new_game();
1835 done:
1836 gtotal = gindex + 1;
1838 for (i = 0; i < gtotal; i++) {
1839 game[i]->history = game[i]->hp;
1840 game[i]->hindex = pgn_history_total(game[i]->hp);
1843 return pgn_ret;
1847 * Escape '"' and '\' in tag values.
1849 static char *pgn_tag_add_escapes(const char *str)
1851 int i, n;
1852 int len = strlen(str);
1853 static char buf[MAX_PGN_LINE_LEN] = {0};
1855 for (i = n = 0; i < len; i++, n++) {
1856 switch (str[i]) {
1857 case '\\':
1858 case '\"':
1859 buf[n++] = '\\';
1860 break;
1861 default:
1862 break;
1865 buf[n] = str[i];
1868 buf[n] = '\0';
1869 return buf;
1872 static void Fputc(int c, FILE *fp, int *len)
1874 int i = *len;
1876 if (c != '\n' && i + 1 > 80)
1877 Fputc('\n', fp, &i);
1879 if (pgn_lastc == '\n' && c == ' ') {
1880 *len = pgn_mpl = 0;
1881 return;
1884 if (fputc(c, fp) == EOF)
1885 warn("PGN Save");
1886 else {
1887 if (c == '\n')
1888 i = pgn_mpl = 0;
1889 else
1890 i++;
1893 *len = i;
1894 pgn_lastc = c;
1897 static void putstring(FILE *fp, char *str, int *len)
1899 char *p;
1901 for (p = str; *p; p++) {
1902 int n = 0;
1904 while (*p && *p != ' ')
1905 n++, p++;
1907 if (n + *len > 80)
1908 Fputc('\n', fp, len);
1910 p -= n;
1911 Fputc(*p, fp, len);
1916 * See pgn_write() for more info.
1918 static void write_comments_and_nag(FILE *fp, HISTORY *h, int *len)
1920 int i;
1922 #ifdef DEBUG
1923 PGN_DUMP("%s:%d: writing comments and nag\n", __FILE__, __LINE__);
1924 #endif
1926 for (i = 0; i < MAX_PGN_NAG; i++) {
1927 if (h->nag[i]) {
1928 Fputc(' ', fp, len);
1929 Fputc('$', fp, len);
1930 putstring(fp, itoa(h->nag[i]), len);
1934 if (h->comment) {
1935 Fputc('\n', fp, len);
1936 putstring(fp, " {", len);
1937 putstring(fp, h->comment, len);
1938 Fputc('}', fp, len);
1942 static void write_move_text(FILE *fp, HISTORY *h, int *len)
1944 Fputc(' ', fp, len);
1945 putstring(fp, h->move, len);
1947 if (!pgn_config.reduced)
1948 write_comments_and_nag(fp, h, len);
1951 static void write_all_move_text(FILE *fp, HISTORY **h, int m, int *len)
1953 int i;
1954 HISTORY **hp = NULL;
1956 for (i = 0; h[i]; i++) {
1957 if (pgn_write_turn == WHITE) {
1958 if (pgn_config.mpl && pgn_mpl == pgn_config.mpl) {
1959 pgn_mpl = 0;
1960 Fputc('\n', fp, len);
1963 if (m > 1 && i > 0)
1964 Fputc(' ', fp, len);
1966 if (strlen(itoa(m)) + 1 + *len > 80)
1967 Fputc('\n', fp, len);
1969 putstring(fp, itoa(m), len);
1970 Fputc('.', fp, len);
1971 pgn_mpl++;
1974 write_move_text(fp, h[i], len);
1976 if (!pgn_config.reduced && h[i]->rav) {
1977 int oldm = m;
1978 int oldturn = pgn_write_turn;
1980 ravlevel++;
1981 putstring(fp, " (", len);
1984 * If it's WHITE's turn the move number will be added above after
1985 * the call to write_all_move_text() below.
1987 if (pgn_write_turn == BLACK) {
1988 putstring(fp, itoa(m), len);
1989 putstring(fp, "...", len);
1992 hp = h[i]->rav;
1993 write_all_move_text(fp, hp, m, len);
1994 m = oldm;
1995 pgn_write_turn = oldturn;
1996 putstring(fp, ")", len);
1997 ravlevel--;
1999 if (ravlevel && h[i + 1])
2000 Fputc(' ', fp, len);
2002 if (h[i + 1] && !ravlevel)
2003 Fputc(' ', fp, len);
2005 if (pgn_write_turn == WHITE && h[i + 1]) {
2006 putstring(fp, itoa(m), len);
2007 putstring(fp, "...", len);
2011 if (pgn_write_turn == BLACK)
2012 m++;
2014 pgn_write_turn = (pgn_write_turn == WHITE) ? BLACK : WHITE;
2018 static char *compression_cmd(const char *filename, int expand)
2020 static char command[FILENAME_MAX];
2021 int len = strlen(filename);
2023 if (filename[len - 4] == '.' && filename[len - 3] == 'z' &&
2024 filename[len - 2] == 'i' && filename[len - 1] == 'p' &&
2025 filename[len] == '\0') {
2026 if (expand)
2027 snprintf(command, sizeof(command), "unzip -p %s 2>/dev/null",
2028 filename);
2029 else
2030 snprintf(command, sizeof(command), "zip -9 >%s 2>/dev/null",
2031 filename);
2033 return command;
2035 else if (filename[len - 3] == '.' && filename[len - 2] == 'g' &&
2036 filename[len - 1] == 'z' && filename[len] == '\0') {
2037 if (expand)
2038 snprintf(command, sizeof(command), "gzip -dc %s", filename);
2039 else
2040 snprintf(command, sizeof(command), "gzip -c 1>%s", filename);
2042 return command;
2044 else if (filename[len - 2] == '.' && filename[len - 1] == 'Z' &&
2045 filename[len] == '\0') {
2046 if (expand)
2047 snprintf(command, sizeof(command), "uncompress -c %s", filename);
2048 else
2049 snprintf(command, sizeof(command), "compress -c 1>%s", filename);
2051 return command;
2053 else if ((filename[len - 4] == '.' && filename[len - 3] == 'b' &&
2054 filename[len - 2] == 'z' && filename[len - 1] == '2' &&
2055 filename[len] == '\0') || (filename[len - 3] == '.' &&
2056 filename[len - 2] == 'b' && filename[len - 1] == 'z' &&
2057 filename[len] == '\0')) {
2058 if (expand)
2059 snprintf(command, sizeof(command), "bzip2 -dc %s", filename);
2060 else
2061 snprintf(command, sizeof(command), "bzip2 -zc 1>%s", filename);
2063 return command;
2066 return NULL;
2069 static int copy_file(FILE *fp, const char *dst)
2071 FILE *ofp;
2072 char line[LINE_MAX];
2073 char *cmd = compression_cmd(dst, 0);
2075 if ((ofp = popen(cmd, "w")) == NULL)
2076 return 1;
2078 fseek(fp, 0, SEEK_SET);
2080 while ((fgets(line, sizeof(line), fp)) != NULL)
2081 fprintf(ofp, "%s", line);
2083 pclose(ofp);
2084 return 0;
2088 * Closes and free's a PGN file handle.
2090 pgn_error_t pgn_close(PGN_FILE *pgn)
2092 if (!pgn)
2093 return E_PGN_INVALID;
2095 if (pgn->pipe) {
2097 * Appending to a compressed file.
2099 if (pgn->tmpfile) {
2100 if (copy_file(pgn->fp, pgn->filename))
2101 return E_PGN_ERR;
2103 fclose(pgn->fp);
2104 unlink(pgn->tmpfile);
2105 free(pgn->tmpfile);
2107 else
2108 pclose(pgn->fp);
2110 else
2111 fclose(pgn->fp);
2113 free(pgn->filename);
2114 free(pgn);
2115 return E_PGN_OK;
2119 * Opens a file 'filename' with the given 'mode'. 'mode' should be "r" for
2120 * reading, "w" for writing (will truncate if the file exists) or "a" for
2121 * appending to an existing file or creating a new one. Returns E_PGN_OK on
2122 * success and sets 'result' to a file handle for use will the other file
2123 * functions or E_PGN_ERR if there is an error opening the file in which case
2124 * errno will be set to the error or E_PGN_INVALID if 'mode' is an invalid
2125 * mode or if 'filename' is not a regular file.
2127 pgn_error_t pgn_open(const char *filename, const char *mode, PGN_FILE **result)
2129 FILE *fp = NULL, *tfp = NULL;
2130 char buf[PATH_MAX], *p;
2131 char *cmd = NULL;
2132 PGN_FILE *pgn;
2133 int m;
2134 int append = 0;
2135 struct stat st;
2136 int ret = E_PGN_ERR;
2139 #ifdef DEBUG
2140 PGN_DUMP("%s:%d: BEGIN opening %s\n", __FILE__, __LINE__, filename);
2141 #endif
2143 if (!filename || !mode)
2144 return E_PGN_INVALID;
2146 if (strcmp(mode, "r") == 0)
2147 m = 1;
2148 else if (strcmp(mode, "w") == 0)
2149 m = 0;
2150 else if (strcmp(mode, "a") == 0) {
2151 m = 0;
2152 append = 1;
2154 else {
2155 return E_PGN_INVALID;
2158 pgn = calloc(1, sizeof(PGN_FILE));
2160 if (!pgn)
2161 goto fail;
2163 if (strcmp(filename, "-") != 0) {
2164 if (m && access(filename, R_OK) == -1)
2165 goto fail;
2167 if (stat(filename, &st) == -1 && !m && errno != ENOENT)
2168 goto fail;
2170 if (m && !S_ISREG(st.st_mode)) {
2171 ret = E_PGN_INVALID;
2172 goto fail;
2175 if ((cmd = compression_cmd(filename, m)) != NULL) {
2176 pgn->pipe = 1;
2178 if (append && access(filename, R_OK) == 0) {
2179 char tmp[21];
2180 int fd;
2182 cmd = compression_cmd(filename, 1);
2184 if ((fp = popen(cmd, "r")) == NULL)
2185 goto fail;
2187 if (tmpnam(tmp) == NULL)
2188 goto fail;
2190 if ((fd = open(tmp, O_RDWR|O_EXCL|O_CREAT)) == -1)
2191 goto fail;
2193 if ((tfp = fdopen(fd, "a+")) == NULL)
2194 goto fail;
2196 while ((p = fgets(buf, sizeof(buf), fp)) != NULL)
2197 fprintf(tfp, "%s", p);
2199 pclose(fp);
2200 pgn->fp = tfp;
2201 pgn->tmpfile = strdup(tmp);
2202 goto done;
2205 if ((fp = popen(cmd, m ? "r" : "w")) == NULL)
2206 goto fail;
2208 if (m) {
2209 if ((tfp = tmpfile()) == NULL)
2210 goto fail;
2212 while ((p = fgets(buf, sizeof(buf), fp)) != NULL)
2213 fprintf(tfp, "%s", p);
2215 pclose(fp);
2216 pgn->fp = tfp;
2218 else
2219 pgn->fp = fp;
2221 else {
2222 if ((fp = fopen(filename, mode)) == NULL)
2223 goto fail;
2225 pgn->fp = fp;
2228 else
2229 pgn->fp = stdout;
2231 done:
2232 if (*filename != '/') {
2233 if (getcwd(buf, sizeof(buf)) == NULL) {
2234 if (pgn->tmpfile)
2235 free(pgn->tmpfile);
2237 goto fail;
2240 asprintf(&p, "%s/%s", buf, filename);
2241 pgn->filename = p;
2243 else
2244 pgn->filename = strdup(filename);
2246 *result = pgn;
2247 return E_PGN_OK;
2249 fail:
2250 if (fp)
2251 fclose(fp);
2253 free(pgn);
2254 return ret;
2258 * Returns E_PGN_OK if 'filename' is a recognized compressed filetype or
2259 * E_PGN_ERR if not.
2261 pgn_error_t pgn_is_compressed(const char *filename)
2263 if (compression_cmd(filename, 0))
2264 return E_PGN_OK;
2266 return E_PGN_ERR;
2270 * Gets the value of config flag 'f'. The next argument should be a pointer of
2271 * the config type which is set to the value of 'f'. Returns E_PGN_ERR if 'f'
2272 * is an invalid flag or E_PGN_OK on success.
2274 pgn_error_t pgn_config_get(pgn_config_flag f, ...)
2276 va_list ap;
2277 int *intval;
2278 long *longval;
2279 pgn_progress *progress;
2281 va_start(ap, f);
2283 switch (f) {
2284 case PGN_STRICT_CASTLING:
2285 intval = va_arg(ap, int *);
2286 *intval = pgn_config.strict_castling;
2287 va_end(ap);
2288 return E_PGN_OK;
2289 case PGN_REDUCED:
2290 intval = va_arg(ap, int *);
2291 *intval = pgn_config.reduced;
2292 va_end(ap);
2293 return E_PGN_OK;
2294 case PGN_MPL:
2295 intval = va_arg(ap, int *);
2296 *intval = pgn_config.mpl;
2297 va_end(ap);
2298 return E_PGN_OK;
2299 case PGN_STOP_ON_ERROR:
2300 intval = va_arg(ap, int *);
2301 *intval = pgn_config.stop;
2302 va_end(ap);
2303 return E_PGN_OK;
2304 case PGN_PROGRESS:
2305 longval = va_arg(ap, long *);
2306 *longval = pgn_config.stop;
2307 va_end(ap);
2308 return E_PGN_OK;
2309 case PGN_PROGRESS_FUNC:
2310 progress = va_arg(ap, pgn_progress*);
2311 *progress = pgn_config.pfunc;
2312 va_end(ap);
2313 return E_PGN_OK;
2314 #ifdef DEBUG
2315 case PGN_DEBUG:
2316 intval = va_arg(ap, int *);
2317 *intval = dumptofile;
2318 va_end(ap);
2319 return E_PGN_OK;
2320 #endif
2321 default:
2322 break;
2325 return E_PGN_ERR;
2329 * Sets config flag 'f' to the next argument. Returns E_PGN_OK on success or
2330 * E_PGN_ERR if 'f' is an invalid flag or E_PGN_INVALID if 'val' is an invalid
2331 * flag value.
2333 pgn_error_t pgn_config_set(pgn_config_flag f, ...)
2335 va_list ap;
2336 int n;
2337 int ret = E_PGN_OK;
2339 va_start(ap, f);
2341 switch (f) {
2342 case PGN_REDUCED:
2343 n = va_arg(ap, int);
2345 if (n != 1 && n != 0) {
2346 ret = E_PGN_INVALID;
2347 break;
2350 pgn_config.reduced = n;
2351 break;
2352 case PGN_MPL:
2353 n = va_arg(ap, int);
2355 if (n < 0) {
2356 ret = E_PGN_INVALID;
2357 break;
2360 pgn_config.mpl = n;
2361 break;
2362 case PGN_STOP_ON_ERROR:
2363 n = va_arg(ap, int);
2365 if (n != 1 && n != 0) {
2366 ret = E_PGN_INVALID;
2367 break;
2370 pgn_config.stop = n;
2371 break;
2372 case PGN_PROGRESS:
2373 n = va_arg(ap, long);
2374 pgn_config.progress = n;
2375 break;
2376 case PGN_PROGRESS_FUNC:
2377 pgn_config.pfunc = va_arg(ap, pgn_progress);
2378 break;
2379 case PGN_STRICT_CASTLING:
2380 n = va_arg(ap, int);
2381 pgn_config.strict_castling = n;
2382 break;
2383 #ifdef DEBUG
2384 case PGN_DEBUG:
2385 n = va_arg(ap, int);
2386 dumptofile = (n > 0) ? 1 : 0;
2387 break;
2388 #endif
2389 default:
2390 ret = E_PGN_ERR;
2391 break;
2394 va_end(ap);
2395 return ret;
2399 * Writes a PGN formatted game 'g' to a file which was opened with pgn_open().
2400 * See 'pgn_config_flag' for output options. Returns E_PGN_ERR if there was a
2401 * memory allocation or write error and E_PGN_OK on success. It is important
2402 * to use pgn_close() afterwards if the file is a recognized compressed file
2403 * type otherwise the created temporary file wont be copied to the destination
2404 * filename.
2406 pgn_error_t pgn_write(PGN_FILE *pgn, GAME g)
2408 int i;
2409 int len = 0;
2411 if (!pgn)
2412 return E_PGN_ERR;
2414 pgn_write_turn = (TEST_FLAG(g->flags, GF_BLACK_OPENING)) ? BLACK : WHITE;
2415 pgn_tag_sort(g->tag);
2417 #ifdef DEBUG
2418 PGN_DUMP("%s:%d: writing tag section\n", __FILE__, __LINE__);
2419 #endif
2421 for (i = 0; g->tag[i]; i++) {
2422 struct tm tp;
2423 char tbuf[11] = {0};
2424 char *tmp;
2426 if (pgn_config.reduced && i == 7)
2427 break;
2429 if (strcmp(g->tag[i]->name, "Date") == 0) {
2430 memset(&tp, 0, sizeof(struct tm));
2432 if (strptime(g->tag[i]->value, PGN_TIME_FORMAT, &tp) != NULL) {
2433 len = strftime(tbuf, sizeof(tbuf), PGN_TIME_FORMAT, &tp) + 1;
2435 if ((tmp = strdup(tbuf)) == NULL)
2436 return E_PGN_ERR;
2438 free(g->tag[i]->value);
2439 g->tag[i]->value = tmp;
2442 else if (strcmp(g->tag[i]->name, "Event") == 0) {
2443 if (g->tag[i]->value[0] == '\0') {
2444 if ((tmp = strdup("?")) == NULL)
2445 return E_PGN_ERR;
2447 free(g->tag[i]->value);
2448 g->tag[i]->value = tmp;
2451 else if (strcmp(g->tag[i]->name, "Site") == 0) {
2452 if (g->tag[i]->value[0] == '\0') {
2453 if ((tmp = strdup("?")) == NULL)
2454 return E_PGN_ERR;
2456 free(g->tag[i]->value);
2457 g->tag[i]->value = tmp;
2460 else if (strcmp(g->tag[i]->name, "Round") == 0) {
2461 if (g->tag[i]->value[0] == '\0') {
2462 if ((tmp = strdup("?")) == NULL)
2463 return E_PGN_ERR;
2465 free(g->tag[i]->value);
2466 g->tag[i]->value = tmp;
2469 else if (strcmp(g->tag[i]->name, "Result") == 0) {
2470 if (g->tag[i]->value[0] == '\0') {
2471 if ((tmp = strdup("*")) == NULL)
2472 return E_PGN_ERR;
2474 free(g->tag[i]->value);
2475 g->tag[i]->value = tmp;
2478 else if (strcmp(g->tag[i]->name, "Black") == 0) {
2479 if (g->tag[i]->value[0] == '\0') {
2480 if ((tmp = strdup("?")) == NULL)
2481 return E_PGN_ERR;
2483 free(g->tag[i]->value);
2484 g->tag[i]->value = tmp;
2487 else if (strcmp(g->tag[i]->name, "White") == 0) {
2488 if (g->tag[i]->value[0] == '\0') {
2489 if ((tmp = strdup("?")) == NULL)
2490 return E_PGN_ERR;
2492 free(g->tag[i]->value);
2493 g->tag[i]->value = tmp;
2497 fprintf(pgn->fp, "[%s \"%s\"]\n", g->tag[i]->name,
2498 (g->tag[i]->value && g->tag[i]->value[0]) ?
2499 pgn_tag_add_escapes(g->tag[i]->value) : "");
2502 #ifdef DEBUG
2503 PGN_DUMP("%s:%d: writing move section\n", __FILE__, __LINE__);
2504 #endif
2505 Fputc('\n', pgn->fp, &len);
2506 g->hp = g->history;
2507 ravlevel = pgn_mpl = 0;
2509 if (pgn_history_total(g->hp) && pgn_write_turn == BLACK)
2510 putstring(pgn->fp, "1...", &len);
2512 write_all_move_text(pgn->fp, g->hp, 1, &len);
2514 Fputc(' ', pgn->fp, &len);
2515 putstring(pgn->fp, g->tag[6]->value, &len);
2516 putstring(pgn->fp, "\n\n", &len);
2518 if (!pgn_config.reduced)
2519 CLEAR_FLAG(g->flags, GF_PERROR);
2521 return E_PGN_OK;
2525 * Clears the enpassant flag for all positions on board 'b'. Returns nothing.
2527 void pgn_reset_enpassant(BOARD b)
2529 int r, c;
2531 #ifdef DEBUG
2532 PGN_DUMP("%s:%d: resetting enpassant\n", __FILE__, __LINE__);
2533 #endif
2535 for (r = 0; r < 8; r++) {
2536 for (c = 0; c < 8; c++)
2537 b[r][c].enpassant = 0;