Update copyright year for a few files.
[cboard.git] / libchess / pgn.c
blobf1f2f407de1091c900ed721c5747f61953847232
1 /* vim:tw=78:ts=8:sw=4:set ft=c: */
2 /*
3 Copyright (C) 2002-2013 Ben Kibbey <bjk@luxsci.net>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 #ifdef HAVE_CONFIG_H
20 #include <config.h>
21 #endif
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <unistd.h>
26 #include <sys/types.h>
27 #include <sys/wait.h>
28 #include <sys/stat.h>
29 #include <pwd.h>
30 #include <string.h>
31 #include <time.h>
32 #include <ctype.h>
33 #include <errno.h>
34 #include <fcntl.h>
35 #include <stdarg.h>
36 #include <err.h>
37 #include <time.h>
39 #ifdef HAVE_LIMITS_H
40 #include <limits.h>
41 #endif
43 #include "chess.h"
44 #include "common.h"
46 #ifdef DEBUG
47 #include "debug.h"
48 #endif
50 static BOARD pgn_board;
51 static int nulltags;
52 static int tag_section;
53 static int pgn_ret;
54 static int pgn_write_turn;
55 static int pgn_mpl;
56 static int pgn_lastc;
57 static int ravlevel;
58 static long pgn_fsize;
59 static int pgn_rav;
60 static RAV *pgn_rav_p;
62 #ifdef __linux__
63 extern char *strptime(const char *, const char *, struct tm *);
64 #endif
66 static int Fgetc(FILE *fp)
68 int c;
70 if ((c = fgetc(fp)) != EOF) {
71 if (pgn_config.progress && pgn_config.pfunc) {
72 if (!(ftell(fp) % pgn_config.progress))
73 pgn_config.pfunc(pgn_fsize, ftell(fp));
77 return c;
80 static int Ungetc(int c, FILE *fp)
82 return ungetc(c, fp);
85 char *pgn_version()
87 return "libchess " PACKAGE_VERSION;
90 static char *trim(char *str)
92 int i = 0;
94 if (!str)
95 return NULL;
97 while (isspace(*str))
98 str++;
100 for (i = strlen(str) - 1; isspace(str[i]); i--)
101 str[i] = 0;
103 return str;
106 static char *itoa(long n, char *buf)
108 sprintf(buf, "%li", n);
109 return buf;
113 * Clears the valid move flag for all positions on board 'b'. Returns nothing.
115 void pgn_reset_valid_moves(BOARD b)
117 int row, col;
119 #ifdef DEBUG
120 PGN_DUMP("%s:%d: resetting valid moves\n", __FILE__, __LINE__);
121 #endif
123 for (row = 0; row < 8; row++) {
124 for (col = 0; col < 8; col++)
125 b[row][col].valid = 0;
130 * Toggles g->turn. Returns nothing.
132 void pgn_switch_turn(GAME g)
134 g->turn = (g->turn == WHITE) ? BLACK : WHITE;
138 * Toggles g->side and switches the White and Black roster tags. Returns
139 * nothing.
141 void pgn_switch_side(GAME g, int t)
143 if (t) {
144 char *w = g->tag[4]->value;
146 g->tag[4]->value = g->tag[5]->value;
147 g->tag[5]->value = w;
150 g->side = !g->side;
154 * Creates a FEN tag from the current game 'g', history move (g->hindex) and
155 * board 'b'. Returns a FEN tag.
157 char *pgn_game_to_fen(GAME g, BOARD b)
159 char tmp[16];
160 int row, col;
161 int i;
162 char buf[MAX_PGN_LINE_LEN] = {0}, *p;
163 int oldturn = g->turn;
164 char enpassant[3] = {0}, *e;
165 int castle = 0;
167 #ifdef DEBUG
168 PGN_DUMP("%s:%d: creating FEN tag\n", __FILE__, __LINE__);
169 #endif
171 for (i = pgn_history_total(g->hp); i >= g->hindex - 1; i--)
172 pgn_switch_turn(g);
174 p = buf;
176 for (row = 0; row < 8; row++) {
177 int count = 0;
179 for (col = 0; col < 8; col++) {
180 if (b[row][col].enpassant) {
181 b[row][col].icon = pgn_int_to_piece(WHITE, OPEN_SQUARE);
182 e = enpassant;
183 *e++ = 'a' + col;
184 *e++ = ('0' + 8) - row;
185 *e = 0;
188 if (pgn_piece_to_int(b[row][col].icon) == OPEN_SQUARE) {
189 count++;
190 continue;
193 if (count) {
194 *p++ = '0' + count;
195 count = 0;
198 *p++ = b[row][col].icon;
199 *p = 0;
202 if (count) {
203 *p++ = '0' + count;
204 count = 0;
207 *p++ = '/';
210 --p;
211 *p++ = ' ';
212 *p++ = (g->turn == WHITE) ? 'w' : 'b';
213 *p++ = ' ';
215 if (TEST_FLAG(g->flags, GF_WK_CASTLE) && pgn_piece_to_int(b[7][7].icon) ==
216 ROOK && isupper(b[7][7].icon) && pgn_piece_to_int(b[7][4].icon) ==
217 KING && isupper(b[7][4].icon)) {
218 *p++ = 'K';
219 castle = 1;
222 if (TEST_FLAG(g->flags, GF_WQ_CASTLE) && pgn_piece_to_int(b[7][0].icon) ==
223 ROOK && isupper(b[7][0].icon) && pgn_piece_to_int(b[7][4].icon) ==
224 KING && isupper(b[7][4].icon)) {
225 *p++ = 'Q';
226 castle = 1;
229 if (TEST_FLAG(g->flags, GF_BK_CASTLE) && pgn_piece_to_int(b[0][7].icon) ==
230 ROOK && islower(b[0][7].icon) && pgn_piece_to_int(b[0][4].icon) ==
231 KING && islower(b[0][4].icon)) {
232 *p++ = 'k';
233 castle = 1;
236 if (TEST_FLAG(g->flags, GF_BQ_CASTLE) && pgn_piece_to_int(b[0][0].icon) ==
237 ROOK && islower(b[0][0].icon) && pgn_piece_to_int(b[0][4].icon) ==
238 KING && islower(b[0][4].icon)) {
239 *p++ = 'q';
240 castle = 1;
243 if (!castle)
244 *p++ = '-';
246 *p++ = ' ';
248 if (enpassant[0]) {
249 e = enpassant;
250 *p++ = *e++;
251 *p++ = *e++;
253 else
254 *p++ = '-';
256 *p++ = ' ';
258 // Halfmove clock.
259 *p = 0;
260 strcat(p, itoa(g->ply, tmp));
261 p = buf + strlen(buf);
262 *p++ = ' ';
264 // Fullmove number.
265 i = (g->hindex + 1) / 2;
266 *p = 0;
267 strcat(p, itoa((g->hindex / 2) + (g->hindex % 2), tmp));
269 g->turn = oldturn;
270 return buf[0] ? strdup (buf) : NULL;
274 * Returns the total number of moves in 'h' or 0 if there are none.
276 int pgn_history_total(HISTORY **h)
278 int i;
280 if (!h)
281 return 0;
283 for (i = 0; h[i]; i++);
284 return i;
288 * Deallocates all of the history data from position 'start' in the array 'h'.
289 * Returns nothing.
291 void pgn_history_free(HISTORY **h, int start)
293 int i;
295 #ifdef DEBUG
296 PGN_DUMP("%s:%d: freeing history\n", __FILE__, __LINE__);
297 #endif
299 if (!h || start > pgn_history_total(h))
300 return;
302 if (start < 0)
303 start = 0;
305 for (i = start; h[i]; i++) {
306 if (h[i]->rav) {
307 pgn_history_free(h[i]->rav, 0);
308 free(h[i]->rav);
311 free(h[i]->comment);
312 free(h[i]->move);
313 free(h[i]->fen);
314 free(h[i]);
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 and creates a FEN tag for the
334 * current game state using board 'b'. The FEN tag makes things faster than
335 * validating the entire move history by validating only the current move to
336 * the previous moves FEN tag. The history pointer may be a in a RAV so
337 * g->rav.hp is also updated to the new (realloc()'ed) pointer. If not in a
338 * RAV then g->history will be updated. Returns E_PGN_ERR if realloc() failed
339 * or E_PGN_OK on success.
341 pgn_error_t pgn_history_add(GAME g, BOARD b, const char *m)
343 int t = pgn_history_total(g->hp);
344 int o;
345 HISTORY **h = NULL, *tmp;
346 int ri = (g->ravlevel) ? g->rav[g->ravlevel - 1].hindex : 0;
348 #ifdef DEBUG
349 PGN_DUMP("%s:%d: adding '%s' to move history\n", __FILE__, __LINE__, m);
350 #endif
352 if (g->ravlevel)
353 o = g->rav[g->ravlevel - 1].hp[ri-1]->rav - g->hp;
354 else
355 o = g->history - g->hp;
357 if ((h = realloc(g->hp, (t + 2) * sizeof(HISTORY *))) == NULL)
358 return E_PGN_ERR;
360 g->hp = h;
362 if (g->ravlevel)
363 g->rav[g->ravlevel - 1].hp[ri-1]->rav = g->hp + o;
364 else
365 g->history = g->hp + o;
367 if ((g->hp[t] = calloc(1, sizeof(HISTORY))) == NULL)
368 return E_PGN_ERR;
370 if ((g->hp[t]->move = strdup(m)) == NULL) {
371 free(g->hp[t]);
372 g->hp[t] = NULL;
373 return E_PGN_ERR;
376 tmp = g->hp[t];
377 t++;
378 g->hp[t] = NULL;
379 g->hindex = pgn_history_total(g->hp);
380 pgn_switch_turn(g);
381 tmp->fen = pgn_game_to_fen(g, b);
382 pgn_switch_turn(g);
383 return E_PGN_OK;
387 * Resets the game 'g' using board 'b' up to history move (g->hindex) 'n'.
388 * Returns E_PGN_OK on success or E_PGN_PARSE if there was a FEN tag but the
389 * parsing of it failed. Or returns E_PGN_INVALID if somehow the move failed
390 * validation while resetting.
392 pgn_error_t pgn_board_update(GAME g, BOARD b, int n)
394 BOARD tb;
395 int ret = E_PGN_OK;
396 int p_error = TEST_FLAG(g->flags, GF_PERROR);
397 int black_opening = TEST_FLAG(g->flags, GF_BLACK_OPENING);
399 #ifdef DEBUG
400 PGN_DUMP("%s:%d: updating board\n", __FILE__, __LINE__);
401 #endif
403 if (!g->ravlevel && TEST_FLAG(g->flags, GF_BLACK_OPENING))
404 g->turn = BLACK;
405 else
406 g->turn = WHITE;
408 g->flags = 0;
409 SET_FLAG(g->flags, GF_WK_CASTLE|GF_WQ_CASTLE|GF_WQ_CASTLE|
410 GF_BK_CASTLE|GF_BQ_CASTLE);
411 pgn_board_init(tb);
413 if (g->ravlevel)
414 pgn_board_init_fen(g, tb, g->rav[g->ravlevel - 1].fen);
415 else if (pgn_tag_find(g->tag, "FEN") != -1 &&
416 pgn_board_init_fen(g, tb, NULL))
417 return E_PGN_PARSE;
419 if (n) {
420 HISTORY *h = pgn_history_by_n(g->hp, n-1);
422 if (h) {
423 ret = pgn_board_init_fen(g, tb, h->fen);
424 if (ret == E_PGN_OK) {
425 h = pgn_history_by_n(g->hp, n);
426 if (h) {
427 char *frfr = NULL;
429 ret = pgn_parse_move(g, tb, &h->move, &frfr);
430 if (ret == E_PGN_OK) {
431 h = pgn_history_by_n(g->hp, n-1);
432 ret = pgn_board_init_fen(g, tb, h->fen);
435 free(frfr);
441 if (ret == E_PGN_OK)
442 memcpy(b, tb, sizeof(BOARD));
444 if (p_error)
445 SET_FLAG(g->flags, GF_PERROR);
447 if (black_opening)
448 SET_FLAG(g->flags, GF_BLACK_OPENING);
450 return ret;
454 * Updates the game 'g' using board 'b' to the next 'n'th history move.
455 * Returns nothing.
457 void pgn_history_prev(GAME g, BOARD b, int n)
459 if (g->hindex - n < 0) {
460 if (n <= 2)
461 g->hindex = pgn_history_total(g->hp);
462 else
463 g->hindex = 0;
465 else
466 g->hindex -= n;
468 pgn_board_update(g, b, g->hindex);
472 * Updates the game 'g' using board 'b' to the previous 'n'th history move.
473 * Returns nothing.
475 void pgn_history_next(GAME g, BOARD b, int n)
477 if (g->hindex + n > pgn_history_total(g->hp)) {
478 if (n <= 2)
479 g->hindex = 0;
480 else
481 g->hindex = pgn_history_total(g->hp);
483 else
484 g->hindex += n;
486 pgn_board_update(g, b, g->hindex);
490 * Converts the character piece 'p' to an integer. Returns the integer
491 * associated with 'p' or E_PGN_ERR if 'p' is invalid.
493 int pgn_piece_to_int(int p)
495 if (p == '.')
496 return OPEN_SQUARE;
498 p = tolower(p);
500 switch (p) {
501 case 'p':
502 return PAWN;
503 case 'r':
504 return ROOK;
505 case 'n':
506 return KNIGHT;
507 case 'b':
508 return BISHOP;
509 case 'q':
510 return QUEEN;
511 case 'k':
512 return KING;
513 default:
514 break;
517 #ifdef DEBUG
518 PGN_DUMP("%s:%d: invalid piece '%c'\n", __FILE__, __LINE__, p);
519 #endif
520 return E_PGN_ERR;
524 * Converts the integer piece 'n' to a character whose turn is 'turn'. WHITE
525 * piece are uppercase and BLACK pieces are lowercase. Returns the character
526 * associated with 'n' or E_PGN_ERR if 'n' is an invalid piece.
528 pgn_error_t pgn_int_to_piece(char turn, int n)
530 int p = 0;
532 switch (n) {
533 case PAWN:
534 p = 'p';
535 break;
536 case ROOK:
537 p = 'r';
538 break;
539 case KNIGHT:
540 p = 'n';
541 break;
542 case BISHOP:
543 p = 'b';
544 break;
545 case QUEEN:
546 p = 'q';
547 break;
548 case KING:
549 p = 'k';
550 break;
551 case OPEN_SQUARE:
552 p = '.';
553 break;
554 default:
555 #ifdef DEBUG
556 PGN_DUMP("%s:%d: unknown piece integer %i\n", __FILE__,
557 __LINE__, n);
558 #endif
559 return E_PGN_ERR;
560 break;
563 return (turn == WHITE) ? toupper(p) : p;
567 * Finds a tag 'name' in the structure array 't'. Returns the location in the
568 * array of the found tag or E_PGN_ERR if the tag could not be found.
570 pgn_error_t pgn_tag_find(TAG **t, const char *name)
572 int i;
574 for (i = 0; t[i]; i++) {
575 if (strcasecmp(t[i]->name, name) == 0)
576 return i;
579 return E_PGN_ERR;
582 static pgn_error_t remove_tag(TAG ***array, const char *tag)
584 TAG **tags = *array;
585 int n = pgn_tag_find(tags, tag);
586 int i, t;
588 if (n == E_PGN_ERR)
589 return E_PGN_ERR;
591 for (i = t = 0; tags[i]; i++) {
592 if (i == n) {
593 free(tags[i]->name);
594 free(tags[i]->value);
595 free(tags[i]);
596 continue;
599 tags[t++] = tags[i];
602 tags = realloc(*array, (t + 1) * sizeof(TAG *));
603 tags[t] = NULL;
604 *array = tags;
605 #ifdef DEBUG
606 PGN_DUMP("%s:%d: removed tag: name='%s'\n", __FILE__, __LINE__, tag);
607 #endif
608 return E_PGN_OK;
611 static int tag_compare(const void *a, const void *b)
613 TAG * const *ta = a;
614 TAG * const *tb = b;
616 return strcmp((*ta)->name, (*tb)->name);
620 * Sorts a tag array. The first seven tags are in order of the PGN standard so
621 * don't sort'em. Returns nothing.
623 void pgn_tag_sort(TAG **tags)
625 if (pgn_tag_total(tags) <= 7)
626 return;
628 qsort(tags + 7, pgn_tag_total(tags) - 7, sizeof(TAG *), tag_compare);
632 * Returns the total number of tags in 't' or 0 if 't' is NULL.
634 int pgn_tag_total(TAG **tags)
636 int i = 0;
638 if (!tags)
639 return 0;
641 while (tags[i])
642 i++;
644 return i;
648 * Adds a tag 'name' with value 'value' to the pointer to array of TAG
649 * pointers 'dst'. If a duplicate tag 'name' was found then the existing tag
650 * is updated to the new 'value'. If 'value' is NULL, the tag is removed.
651 * Returns E_PGN_ERR if there was a memory allocation error or E_PGN_OK on
652 * success.
654 pgn_error_t pgn_tag_add(TAG ***dst, char *name, char *value)
656 int i;
657 TAG **tdata = *dst;
658 TAG **a = NULL;
659 int t = pgn_tag_total(tdata);
661 #ifdef DEBUG
662 PGN_DUMP("%s:%d: adding tag\n", __FILE__, __LINE__);
663 #endif
665 if (!name)
666 return E_PGN_ERR;
668 name = trim(name);
670 if (value)
671 value = trim(value);
673 // Find an existing tag with 'name'.
674 for (i = 0; i < t; i++) {
675 char *tmp = NULL;
677 if (strcasecmp(tdata[i]->name, name) == 0) {
678 if (value) {
679 if ((tmp = strdup(value)) == NULL)
680 return E_PGN_ERR;
682 else {
683 remove_tag(dst, name);
684 return E_PGN_OK;
687 free(tdata[i]->value);
688 tdata[i]->value = tmp;
689 *dst = tdata;
690 return E_PGN_OK;
694 if ((a = realloc(tdata, (t + 2) * sizeof(TAG *))) == NULL)
695 return E_PGN_ERR;
697 tdata = a;
699 if ((tdata[t] = malloc(sizeof(TAG))) == NULL)
700 return E_PGN_ERR;
702 if ((tdata[t]->name = strdup(name)) == NULL) {
703 tdata[t] = NULL;
704 return E_PGN_ERR;
707 if (value) {
708 if ((tdata[t]->value = strdup(value)) == NULL) {
709 free(tdata[t]->name);
710 tdata[t] = NULL;
711 return E_PGN_ERR;
714 else
715 tdata[t]->value = NULL;
717 tdata[t]->name[0] = toupper(tdata[t]->name[0]);
718 tdata[++t] = NULL;
719 *dst = tdata;
721 #ifdef DEBUG
722 PGN_DUMP("%s:%d: added tag: name='%s' value='%s'\n", __FILE__, __LINE__,
723 name, (value) ? value : "null");
724 #endif
726 return E_PGN_OK;
729 static char *remove_tag_escapes(const char *str)
731 int i, n;
732 int len = strlen(str);
733 char buf[MAX_PGN_LINE_LEN] = {0};
735 for (i = n = 0; i < len; i++, n++) {
736 switch (str[i]) {
737 case '\\':
738 i++;
739 default:
740 break;
743 buf[n] = str[i];
746 buf[n] = '\0';
747 return buf[0] ? strdup (buf) : NULL;
751 * Resets or initializes a new game board 'b'. Returns nothing.
753 void pgn_board_init(BOARD b)
755 int row, col;
757 #ifdef DEBUG
758 PGN_DUMP("%s:%d: initializing board\n", __FILE__, __LINE__);
759 #endif
761 memset(b, 0, sizeof(BOARD));
763 for (row = 0; row < 8; row++) {
764 for (col = 0; col < 8; col++) {
765 int c = '.';
767 switch (row) {
768 case 0:
769 case 7:
770 switch (col) {
771 case 0:
772 case 7:
773 c = 'r';
774 break;
775 case 1:
776 case 6:
777 c = 'n';
778 break;
779 case 2:
780 case 5:
781 c = 'b';
782 break;
783 case 3:
784 c = 'q';
785 break;
786 case 4:
787 c = 'k';
788 break;
790 break;
791 case 1:
792 case 6:
793 c = 'p';
794 break;
797 b[row][col].icon = (row < 2) ? c : toupper(c);
803 * Adds the standard PGN roster tags to game 'g'.
805 static void set_default_tags(GAME g)
807 time_t now;
808 char tbuf[11] = {0};
809 struct tm *tp;
810 struct passwd *pw = getpwuid(getuid());
811 char host[64] = { 0 };
813 time(&now);
814 tp = localtime(&now);
815 strftime(tbuf, sizeof(tbuf), PGN_TIME_FORMAT, tp);
817 /* The standard seven tag roster (in order of appearance). */
818 if (pgn_tag_add(&g->tag, "Event", "?") != E_PGN_OK)
819 warn("pgn_tag_add()");
821 gethostname (host, sizeof(host)-1);
822 if (pgn_tag_add(&g->tag, "Site", host) != E_PGN_OK)
823 warn("pgn_tag_add()");
825 if (pgn_tag_add(&g->tag, "Date", tbuf) != E_PGN_OK)
826 warn("pgn_tag_add()");
828 if (pgn_tag_add(&g->tag, "Round", "-") != E_PGN_OK)
829 warn("pgn_tag_add()");
831 if (pgn_tag_add(&g->tag, "White", pw->pw_gecos) != E_PGN_OK)
832 warn("pgn_tag_add()");
834 if (pgn_tag_add(&g->tag, "Black", "?") != E_PGN_OK)
835 warn("pgn_tag_add()");
837 if (pgn_tag_add(&g->tag, "Result", "*") != E_PGN_OK)
838 warn("pgn_tag_add()");
842 * Frees a TAG array. Returns nothing.
844 void pgn_tag_free(TAG **tags)
846 int i;
847 int t = pgn_tag_total(tags);
849 #ifdef DEBUG
850 PGN_DUMP("%s:%d: freeing tags\n", __FILE__, __LINE__);
851 #endif
853 if (!tags)
854 return;
856 for (i = 0; i < t; i++) {
857 free(tags[i]->name);
858 free(tags[i]->value);
859 free(tags[i]);
862 free(tags);
866 * Frees a single game 'g'. Returns nothing.
868 void pgn_free(GAME g)
870 #ifdef DEBUG
871 PGN_DUMP("%s:%d: freeing game\n", __FILE__, __LINE__);
872 #endif
873 pgn_history_free(g->history, 0);
874 free(g->history);
875 pgn_tag_free(g->tag);
877 if (g->rav) {
878 for (g->ravlevel--; g->ravlevel >= 0; g->ravlevel--)
879 free(g->rav[g->ravlevel].fen);
881 free(g->rav);
884 free(g);
888 * Frees all games in the global 'game' array. Returns nothing.
890 void pgn_free_all()
892 int i;
894 #ifdef DEBUG
895 PGN_DUMP("%s:%d: freeing game data\n", __FILE__, __LINE__);
896 #endif
898 for (i = 0; i < gtotal; i++) {
899 pgn_free(game[i]);
902 if (game)
903 free(game);
905 game = NULL;
908 static void reset_game_data()
910 #ifdef DEBUG
911 PGN_DUMP("%s:%d: resetting game data\n", __FILE__, __LINE__);
912 #endif
913 pgn_free_all();
914 gtotal = gindex = 0;
917 static void skip_leading_space(FILE *fp)
919 int c;
921 while ((c = Fgetc(fp)) != EOF && !feof(fp)) {
922 if (!isspace(c))
923 break;
926 Ungetc(c, fp);
930 * PGN move text section.
932 static int move_text(GAME g, FILE *fp)
934 char m[MAX_SAN_MOVE_LEN + 1] = {0}, *p;
935 int c;
936 int count;
937 char *frfr = NULL;
939 g->oflags = g->flags;
941 while((c = Fgetc(fp)) != EOF) {
942 if (isdigit(c) || isspace(c) || c == '.')
943 continue;
945 break;
948 Ungetc(c, fp);
950 if (fscanf(fp, " %[a-hPpRrNnBbQqKk1-9#+=Ox-]%n", m, &count) != 1)
951 return 1;
953 m[MAX_SAN_MOVE_LEN] = 0;
954 p = m;
956 if (pgn_parse_move(g, pgn_board, &p, &frfr)) {
957 pgn_switch_turn(g);
958 return 1;
961 free(frfr);
962 #ifdef DEBUG
963 PGN_DUMP("%s\n%s", p, debug_board(pgn_board));
964 #endif
966 pgn_history_add(g, pgn_board, p);
967 pgn_switch_turn(g);
968 return 0;
972 * PGN nag text.
974 static void nag_text(GAME g, FILE *fp)
976 int c, i, t;
977 char nags[5], *n = nags;
978 int nag = 0;
980 while ((c = Fgetc(fp)) != EOF && !isspace(c)) {
981 if (c == '$') {
982 while ((c = Fgetc(fp)) != EOF && isdigit(c))
983 *n++ = c;
985 Ungetc(c, fp);
986 break;
989 if (c == '!') {
990 if ((c = Fgetc(fp)) == '!')
991 nag = 3;
992 else if (c == '?')
993 nag = 5;
994 else {
995 Ungetc(c, fp);
996 nag = 1;
999 break;
1001 else if (c == '?') {
1002 if ((c = Fgetc(fp)) == '?')
1003 nag = 4;
1004 else if (c == '!')
1005 nag = 6;
1006 else {
1007 Ungetc(c, fp);
1008 nag = 2;
1011 break;
1013 else if (c == '~')
1014 nag = 13;
1015 else if (c == '=') {
1016 if ((c = Fgetc(fp)) == '+')
1017 nag = 15;
1018 else {
1019 Ungetc(c, fp);
1020 nag = 10;
1023 break;
1025 else if (c == '+') {
1026 if ((t = Fgetc(fp)) == '=')
1027 nag = 14;
1028 else if (t == '-')
1029 nag = 18;
1030 else if (t == '/') {
1031 if ((i = Fgetc(fp)) == '-')
1032 nag = 16;
1033 else
1034 Ungetc(i, fp);
1036 break;
1038 else
1039 Ungetc(t, fp);
1041 break;
1043 else if (c == '-') {
1044 if ((t = Fgetc(fp)) == '+')
1045 nag = 18;
1046 else if (t == '/') {
1047 if ((i = Fgetc(fp)) == '+')
1048 nag = 17;
1049 else
1050 Ungetc(i, fp);
1052 break;
1054 else
1055 Ungetc(t, fp);
1057 break;
1061 *n = '\0';
1063 if (!nag)
1064 nag = (nags[0]) ? atoi(nags) : 0;
1066 if (!nag || nag < 0 || nag > 255)
1067 return;
1069 // FIXME -1 is because move_text() increments g->hindex. The NAG
1070 // annoatation isnt guaranteed to be after the move text in import format.
1071 for (i = 0; i < MAX_PGN_NAG; i++) {
1072 if (g->hp[g->hindex - 1]->nag[i])
1073 continue;
1075 g->hp[g->hindex - 1]->nag[i] = nag;
1076 break;
1079 skip_leading_space(fp);
1083 * PGN move annotation.
1085 static int annotation_text(GAME g, FILE *fp, int terminator)
1087 int c, lastchar = 0;
1088 int len = 0;
1089 int hindex = pgn_history_total(g->hp) - 1;
1090 char buf[MAX_PGN_LINE_LEN], *a = buf;
1092 skip_leading_space(fp);
1094 while ((c = Fgetc(fp)) != EOF && c != terminator) {
1095 if (c == '\n')
1096 c = ' ';
1098 if (isspace(c) && isspace(lastchar))
1099 continue;
1101 if (len + 1 == sizeof(buf))
1102 continue;
1104 *a++ = lastchar = c;
1105 len++;
1108 *a = '\0';
1110 if (hindex < 0)
1111 hindex = 0;
1114 * This annotation is before any move text or NAg-> Allocate a new move.
1116 if (!g->hp[hindex]) {
1117 if ((g->hp[hindex] = calloc(1, sizeof(HISTORY))) == NULL)
1118 return E_PGN_ERR;
1121 if ((g->hp[hindex]->comment = strdup(buf)) == NULL)
1122 return E_PGN_ERR;
1124 return E_PGN_OK;
1128 * PGN roster tag->
1130 static int tag_text(GAME g, FILE *fp)
1132 char name[LINE_MAX] = {0}, *n = name;
1133 char value[LINE_MAX] = {0}, *v = value;
1134 int c, i = 0;
1135 int lastchar = 0;
1136 char *tmp;
1138 skip_leading_space(fp);
1140 /* The tag name is up until the first whitespace. */
1141 while ((c = Fgetc(fp)) != EOF && !isspace(c))
1142 *n++ = c;
1144 *n = '\0';
1145 *name = toupper(*name);
1146 skip_leading_space(fp);
1148 /* The value is until the first closing bracket. */
1149 while ((c = Fgetc(fp)) != EOF && c != ']') {
1150 if (i++ == '\0' && c == '\"')
1151 continue;
1153 if (c == '\n' || c == '\t')
1154 c = ' ';
1156 if (c == ' ' && lastchar == ' ')
1157 continue;
1159 lastchar = *v++ = c;
1162 *v = '\0';
1164 while (isspace(*--v))
1165 *v = '\0';
1167 if (*v == '\"')
1168 *v = '\0';
1170 if (value[0] == '\0') {
1171 if (strcmp(name, "Result") == 0)
1172 value[0] = '*';
1173 else
1174 value[0] = '?';
1176 value[1] = '\0';
1179 tmp = remove_tag_escapes (value);
1180 strncpy(value, tmp, sizeof(value)-1);
1181 free (tmp);
1184 * See eog_text() for an explanation.
1186 if (strcmp(name, "Result") == 0) {
1187 if (strcmp(value, "1/2-1/2") == 0) {
1188 if (pgn_tag_add(&g->tag, name, value) != E_PGN_OK)
1189 warn("pgn_tag_add()");
1192 else {
1193 if (pgn_tag_add(&g->tag, name, value) != E_PGN_OK)
1194 warn("pgn_tag_add()");
1197 return 0;
1201 * PGN end-of-game marker.
1203 static int eog_text(GAME g, FILE *fp)
1205 int c, i = 0;
1206 char buf[8], *p = buf;
1208 while ((c = Fgetc(fp)) != EOF && !isspace(c) && i++ < sizeof(buf))
1209 *p++ = c;
1211 if (isspace(c))
1212 Ungetc(c, fp);
1214 *p = 0;
1216 if (strcmp(buf, "1-0") != 0 && strcmp(buf, "0-1") != 0 &&
1217 strcmp(buf, "1/2-1/2") != 0 && strcmp(buf, "*") != 0)
1218 return 1;
1221 * The eog marker in the move text may not match the actual game result.
1222 * We'll leave it up to the move validator to determine the result unless
1223 * the move validator cannot determine who won and the move text says it's
1224 * a draw.
1226 if (g->tag[6]->value[0] == '*' && strcmp("1/2-1/2", buf) == 0) {
1227 if (pgn_tag_add(&g->tag, "Result", buf) != E_PGN_OK)
1228 warn("pgn_tag_add()");
1231 return 0;
1235 * Parse RAV text and keep track of g->hp. The 'o' argument is the board state
1236 * before the current move (.hindex) was parsed.
1238 static int read_file(FILE *);
1239 static int rav_text(GAME g, FILE *fp, int which, BOARD o)
1241 struct game_s tg;
1242 RAV *r;
1244 // Begin RAV for the current move.
1245 if (which == '(') {
1246 if (!g->hindex)
1247 return 1;
1249 pgn_rav++; // For detecting parse errors.
1252 * Save the current game state for this RAV depth/level.
1254 if ((r = realloc(g->rav, (g->ravlevel + 1) * sizeof(RAV))) == NULL) {
1255 warn("realloc()");
1256 return 1;
1259 g->rav = pgn_rav_p = r;
1261 if ((g->rav[g->ravlevel].fen = pgn_game_to_fen(g, pgn_board))
1262 == NULL) {
1263 warn("strdup()");
1264 return 1;
1267 g->rav[g->ravlevel].hp = g->hp;
1268 g->rav[g->ravlevel].hindex = g->hindex;
1269 memcpy(&tg, &(*g), sizeof(struct game_s));
1270 g->flags = g->oflags;
1271 memcpy(pgn_board, o, sizeof(BOARD));
1273 if ((g->hp[g->hindex - 1]->rav = calloc(1, sizeof(HISTORY *))) == NULL) {
1274 warn("calloc()");
1275 return 1;
1279 * pgn_history_add() will now append to the new history pointer that
1280 * is g->hp[previous_move]->rav.
1282 g->hp = g->hp[g->hindex - 1]->rav;
1285 * Reset. Will be restored later from 'tg' which is a local variable
1286 * so recursion is possible.
1288 g->hindex = 0;
1289 g->ravlevel++;
1292 * Undo move_text()'s switch.
1294 pgn_switch_turn(g);
1297 * Now continue as normal as if there were no RAV. Moves will be
1298 * parsed and appended to the new history pointer.
1300 if (read_file(fp))
1301 return 1;
1304 * read_file() has returned. This means that a RAV has ended by this
1305 * function returning -1 (see below). So we restore the game state
1306 * (tg) that was saved before calling read_file().
1308 pgn_board_init_fen(&tg, pgn_board, g->rav[tg.ravlevel].fen);
1309 free(g->rav[tg.ravlevel].fen);
1310 memcpy(&(*g), &tg, sizeof(struct game_s));
1311 g->rav = pgn_rav_p;
1314 * The end of a RAV. This makes the read_file() that called this function
1315 * return (see above and the switch statement in read_file()).
1317 else if (which == ')') {
1318 pgn_rav--; // For detecting parse errors.
1319 return (pgn_rav < 0) ? 1 : -1;
1322 return 0;
1326 * FIXME
1327 * See pgn_board_init_fen(). Returns E_PGN_PARSE on parse error. 0 may be
1328 * returned on success when there is no move count in the FEN tag otherwise
1329 * the move count is returned.
1331 static int parse_fen_line(BOARD b, unsigned *flags, char *turn, char *ply,
1332 char *str)
1334 char *tmp;
1335 char line[LINE_MAX] = {0}, *s;
1336 int row = 8, col = 1;
1338 #ifdef DEBUG
1339 PGN_DUMP("%s:%d: FEN line is '%s'\n", __FILE__, __LINE__, str);
1340 #endif
1341 strncpy(line, str, sizeof(line)-1);
1342 s = line;
1343 pgn_reset_enpassant(b);
1345 while ((tmp = strsep(&s, "/")) != NULL) {
1346 int n;
1348 if (!VALIDFILE(row))
1349 return E_PGN_PARSE;
1351 while (*tmp) {
1352 if (*tmp == ' ')
1353 goto other;
1355 if (isdigit(*tmp)) {
1356 n = *tmp - '0';
1358 if (!VALIDFILE(n))
1359 return E_PGN_PARSE;
1361 for (; n; --n, col++)
1362 b[RANKTOBOARD(row)][FILETOBOARD(col)].icon =
1363 pgn_int_to_piece(WHITE, OPEN_SQUARE);
1365 else if (pgn_piece_to_int(*tmp) != -1)
1366 b[RANKTOBOARD(row)][FILETOBOARD(col++)].icon = *tmp;
1367 else
1368 return E_PGN_PARSE;
1370 tmp++;
1373 row--;
1374 col = 1;
1377 other:
1378 tmp++;
1380 switch (*tmp++) {
1381 case 'b':
1382 *turn = BLACK;
1383 break;
1384 case 'w':
1385 *turn = WHITE;
1386 break;
1387 default:
1388 return E_PGN_PARSE;
1391 tmp++;
1393 while (*tmp && *tmp != ' ') {
1394 switch (*tmp++) {
1395 case 'K':
1396 SET_FLAG(*flags, GF_WK_CASTLE);
1397 break;
1398 case 'Q':
1399 SET_FLAG(*flags, GF_WQ_CASTLE);
1400 break;
1401 case 'k':
1402 SET_FLAG(*flags, GF_BK_CASTLE);
1403 break;
1404 case 'q':
1405 SET_FLAG(*flags, GF_BQ_CASTLE);
1406 break;
1407 case '-':
1408 break;
1409 default:
1410 return E_PGN_PARSE;
1414 tmp++;
1416 // En passant.
1417 if (*tmp != '-') {
1418 if (!VALIDCOL(*tmp))
1419 return E_PGN_PARSE;
1421 col = *tmp++ - 'a';
1423 if (!VALIDROW(*tmp))
1424 return E_PGN_PARSE;
1426 row = 8 - atoi(tmp++);
1427 b[row][col].enpassant = 1;
1428 SET_FLAG(*flags, GF_ENPASSANT);
1430 else
1431 tmp++;
1433 if (*tmp)
1434 tmp++;
1436 if (*tmp)
1437 *ply = atoi(tmp);
1439 while (*tmp && isdigit(*tmp))
1440 tmp++;
1442 if (*tmp)
1443 tmp++;
1445 return E_PGN_OK;
1449 * It initializes the board (b) to the FEN tag (if found) and sets the
1450 * castling and enpassant info for the game 'g'. If 'fen' is set it should be
1451 * a fen tag and will be parsed rather than the game 'g'.tag FEN tag-> Returns
1452 * E_PGN_OK on success or if there was both a FEN and SetUp tag with the SetUp
1453 * tag set to 0. Returns E_PGN_PARSE if there was a FEN parse error, E_PGN_ERR
1454 * if there was no FEN tag or there was a SetUp tag with a value of 0. Returns
1455 * E_PGN_OK on success.
1457 pgn_error_t pgn_board_init_fen(GAME g, BOARD b, char *fen)
1459 int n = -1, i = -1;
1460 BOARD tmpboard;
1461 unsigned flags = 0;
1462 char turn = g->turn;
1463 char ply = 0;
1465 #ifdef DEBUG
1466 PGN_DUMP("%s:%d: initializing board from FEN\n", __FILE__, __LINE__);
1467 #endif
1468 pgn_board_init(tmpboard);
1470 if (!fen) {
1471 n = pgn_tag_find(g->tag, "Setup");
1472 i = pgn_tag_find(g->tag, "FEN");
1476 * If the FEN tag exists and there is no SetUp tag go ahead and parse it.
1477 * If there is a SetUp tag only parse the FEN tag if the value is 1.
1479 if ((n >= 0 && i >= 0 && atoi(g->tag[n]->value) == 1)
1480 || (i >= 0 && n == -1) || fen) {
1481 if ((n = parse_fen_line(tmpboard, &flags, &turn, &ply,
1482 (fen) ? fen : g->tag[i]->value)) != E_PGN_OK)
1483 return E_PGN_PARSE;
1484 else {
1485 memcpy(b, tmpboard, sizeof(BOARD));
1486 CLEAR_FLAG(g->flags, GF_WK_CASTLE);
1487 CLEAR_FLAG(g->flags, GF_WQ_CASTLE);
1488 CLEAR_FLAG(g->flags, GF_BK_CASTLE);
1489 CLEAR_FLAG(g->flags, GF_BQ_CASTLE);
1490 g->flags |= flags;
1491 g->turn = turn;
1492 g->ply = ply;
1495 else
1496 return (i >= 0 && n >= 0) ? E_PGN_OK : E_PGN_ERR;
1498 return E_PGN_OK;
1502 * Allocates a new game and increments 'gtotal'. 'gindex' is then set to the
1503 * new game. Returns E_PGN_ERR if there was a memory allocation error or
1504 * E_PGN_OK on success.
1506 pgn_error_t pgn_new_game()
1508 GAME *g;
1509 GAME newg;
1510 int t = gtotal + 1;
1512 #ifdef DEBUG
1513 PGN_DUMP("%s:%d: allocating new game\n", __FILE__, __LINE__);
1514 #endif
1515 gindex = t - 1;
1517 if ((g = realloc(game, t * sizeof(GAME *))) == NULL) {
1518 warn("realloc()");
1519 return E_PGN_ERR;
1522 game = g;
1524 if ((newg = calloc(1, sizeof(struct game_s))) == NULL) {
1525 warn("calloc()");
1526 return E_PGN_ERR;
1529 game[gindex] = newg;
1531 if ((game[gindex]->hp = calloc(1, sizeof(HISTORY *))) == NULL) {
1532 free(game[gindex]);
1533 warn("calloc()");
1534 return E_PGN_ERR;
1537 game[gindex]->hp[0] = NULL;
1538 game[gindex]->history = game[gindex]->hp;
1539 game[gindex]->side = game[gindex]->turn = WHITE;
1540 SET_FLAG(game[gindex]->flags, GF_WK_CASTLE|GF_WQ_CASTLE|GF_WQ_CASTLE|
1541 GF_BK_CASTLE|GF_BQ_CASTLE);
1542 pgn_board_init(pgn_board);
1543 set_default_tags(game[gindex]);
1544 gtotal = t;
1545 return E_PGN_OK;
1548 static int read_file(FILE *fp)
1550 #ifdef DEBUG
1551 char buf[LINE_MAX] = {0}, *p = buf;
1552 #endif
1553 int c = 0;
1554 int parse_error = 0;
1555 BOARD old;
1557 while (1) {
1558 int nextchar = 0;
1559 int lastchar = c;
1560 int n;
1563 * A parse error may have occured at EOF.
1565 if (parse_error) {
1566 pgn_ret = E_PGN_PARSE;
1568 if (!game)
1569 pgn_new_game();
1571 SET_FLAG(game[gindex]->flags, GF_PERROR);
1574 if ((c = Fgetc(fp)) == EOF) {
1575 if (feof(fp))
1576 break;
1578 if (ferror(fp)) {
1579 clearerr(fp);
1580 continue;
1584 if (!isascii(c)) {
1585 parse_error = 1;
1586 continue;
1589 if (c == '\015')
1590 continue;
1592 nextchar = Fgetc(fp);
1593 Ungetc(nextchar, fp);
1596 * If there was a move text parsing error, keep reading until the end
1597 * of the current game discarding the data.
1599 if (parse_error) {
1600 pgn_ret = E_PGN_PARSE;
1602 if (game[gindex]->ravlevel)
1603 return 1;
1605 if (pgn_config.stop)
1606 return E_PGN_PARSE;
1608 if (c == '\n' && (nextchar == '\n' || nextchar == '\015')) {
1609 parse_error = 0;
1610 nulltags = 1;
1611 tag_section = 0;
1613 else
1614 continue;
1617 // New game reached.
1618 if (c == '\n' && (nextchar == '\n' || nextchar == '\015')) {
1619 if (tag_section)
1620 continue;
1622 nulltags = 1;
1623 tag_section = 0;
1624 continue;
1628 * PGN: Application comment. The '%' must be on the first column of
1629 * the line. The comment continues until the end of the current line.
1631 if (c == '%') {
1632 if (lastchar == '\n' || lastchar == 0) {
1633 while ((c = Fgetc(fp)) != EOF && c != '\n');
1634 continue;
1637 // Not sure what to do here.
1640 if (isspace(c))
1641 continue;
1643 // PGN: Reserved.
1644 if (c == '<' || c == '>')
1645 continue;
1648 * PGN: Recurrsive Annotated Variation. Read rav_text() for more
1649 * info.
1651 if (c == '(' || c == ')') {
1652 switch (rav_text(game[gindex], fp, c, old)) {
1653 case -1:
1655 * This is the end of the current RAV. This function has
1656 * been called from rav_text(). Returning from this point
1657 * will put us back in rav_text().
1659 if (game[gindex]->ravlevel > 0)
1660 return pgn_ret;
1663 * We're back at the root move. Continue as normal.
1665 break;
1666 case 1:
1667 parse_error = 1;
1668 continue;
1669 default:
1671 * Continue processing-> Probably the root move.
1673 break;
1676 if (!game[gindex]->ravlevel && pgn_rav)
1677 parse_error = 1;
1679 continue;
1682 // PGN: Numeric Annotation Glyph.
1683 if (c == '$' || c == '!' || c == '?' || c == '+' || c == '-' ||
1684 c == '~' || c == '=') {
1685 Ungetc(c, fp);
1686 nag_text(game[gindex], fp);
1687 continue;
1691 * PGN: Annotation. The ';' comment continues until the end of the
1692 * current line. The '{' type comment continues until a '}' is
1693 * reached.
1695 if (c == '{' || c == ';') {
1696 annotation_text(game[gindex], fp, (c == '{') ? '}' : '\n');
1697 continue;
1700 // PGN: Roster tag->
1701 if (c == '[') {
1702 // First roster tag found. Initialize the data structures.
1703 if (!tag_section) {
1704 nulltags = 0;
1705 tag_section = 1;
1707 if (gtotal && pgn_history_total(game[gindex]->hp))
1708 game[gindex]->hindex = pgn_history_total(game[gindex]->hp) - 1;
1710 if (pgn_new_game() != E_PGN_OK) {
1711 pgn_ret = E_PGN_ERR;
1712 break;
1715 memcpy(old, pgn_board, sizeof(BOARD));
1718 if (tag_text(game[gindex], fp))
1719 parse_error = 1; // FEN tag parse error.
1721 continue;
1724 // PGN: End-of-game markers.
1725 if ((isdigit(c) && (nextchar == '-' || nextchar == '/')) || c == '*') {
1726 Ungetc(c, fp);
1728 if (eog_text(game[gindex], fp)) {
1729 parse_error = 1;
1730 continue;
1733 nulltags = 1;
1734 tag_section = 0;
1736 if (!game[gindex]->done_fen_tag) {
1737 if (pgn_tag_find(game[gindex]->tag, "FEN") != -1 &&
1738 pgn_board_init_fen(game[gindex], pgn_board, NULL)) {
1739 parse_error = 1;
1740 continue;
1743 game[gindex]->pgn_fen_tag = pgn_tag_find(game[gindex]->tag, "FEN");
1744 game[gindex]->done_fen_tag = 1;
1747 continue;
1750 // PGN: Move text.
1751 if ((isdigit(c) && c != '0') || VALIDCOL(c) || c == 'N' || c == 'K'
1752 || c == 'Q' || c == 'B' || c == 'R' || c == 'P' ||
1753 c == 'O') {
1754 Ungetc(c, fp);
1756 // PGN: If a FEN tag exists, initialize the board to the value.
1757 if (tag_section) {
1758 if (pgn_tag_find(game[gindex]->tag, "FEN") != E_PGN_ERR &&
1759 (n = pgn_board_init_fen(game[gindex], pgn_board,
1760 NULL)) == E_PGN_PARSE) {
1761 parse_error = 1;
1762 continue;
1765 game[gindex]->done_fen_tag = 1;
1766 game[gindex]->pgn_fen_tag = pgn_tag_find(game[gindex]->tag, "FEN");
1767 tag_section = 0;
1771 * PGN: Import format doesn't require a roster tag section. We've
1772 * arrived to the move text section without any tags so we
1773 * initialize a new game which set's the default tags and any tags
1774 * from the configuration file.
1776 if (nulltags) {
1777 if (gtotal)
1778 game[gindex]->hindex = pgn_history_total(game[gindex]->hp) - 1;
1780 if (pgn_new_game() != E_PGN_OK) {
1781 pgn_ret = E_PGN_ERR;
1782 break;
1785 memcpy(old, pgn_board, sizeof(BOARD));
1786 nulltags = 0;
1789 memcpy(old, pgn_board, sizeof(BOARD));
1791 if (move_text(game[gindex], fp)) {
1792 if (pgn_tag_add(&game[gindex]->tag, "Result", "*") ==
1793 E_PGN_ERR) {
1794 warn("pgn_tag_add()");
1795 pgn_ret = E_PGN_ERR;
1798 SET_FLAG(game[gindex]->flags, GF_PERROR);
1799 parse_error = 1;
1802 continue;
1805 #ifdef DEBUG
1806 *p++ = c;
1808 PGN_DUMP("%s:%d: unparsed: '%s'\n", __FILE__, __LINE__, buf);
1810 if (strlen(buf) + 1 == sizeof(buf))
1811 bzero(buf, sizeof(buf));
1812 #endif
1814 continue;
1817 return pgn_ret;
1821 * Parses a PGN_FILE which was opened with pgn_open(). If 'pgn' is NULL then a
1822 * single empty game will be allocated. If there is a parsing error
1823 * E_PGN_PARSE is returned, if there was a memory allocation error E_PGN_ERR
1824 * is returned, otherwise E_PGN_OK is returned and the global 'gindex' is set
1825 * to the last parsed game in the file and the global 'gtotal' is set to the
1826 * total number of games in the file. The file should be closed with
1827 * pgn_close() after processing.
1829 pgn_error_t pgn_parse(PGN_FILE *pgn)
1831 int i;
1833 if (!pgn) {
1834 reset_game_data();
1835 pgn_ret = pgn_new_game();
1836 goto done;
1839 reset_game_data();
1840 nulltags = 1;
1841 fseek(pgn->fp, 0, SEEK_END);
1842 pgn_fsize = ftell(pgn->fp);
1843 fseek(pgn->fp, 0, SEEK_SET);
1844 #ifdef DEBUG
1845 PGN_DUMP("%s:%d: BEGIN parsing->..\n", __FILE__, __LINE__);
1846 #endif
1847 parsing_file = 1;
1848 pgn_ret = read_file(pgn->fp);
1849 parsing_file = 0;
1851 #ifdef DEBUG
1852 PGN_DUMP("%s:%d: END parsing->..\n", __FILE__, __LINE__);
1853 #endif
1855 if (gtotal < 1)
1856 pgn_new_game();
1858 done:
1859 gtotal = gindex + 1;
1861 for (i = 0; i < gtotal; i++) {
1862 game[i]->history = game[i]->hp;
1863 game[i]->hindex = pgn_history_total(game[i]->hp);
1866 return pgn_ret;
1870 * Escape '"' and '\' in tag values.
1872 static char *pgn_tag_add_escapes(const char *str)
1874 int i, n;
1875 int len = strlen(str);
1876 char buf[MAX_PGN_LINE_LEN] = {0};
1878 for (i = n = 0; i < len; i++, n++) {
1879 switch (str[i]) {
1880 case '\\':
1881 case '\"':
1882 buf[n++] = '\\';
1883 break;
1884 default:
1885 break;
1888 buf[n] = str[i];
1891 buf[n] = '\0';
1892 return buf[0] ? strdup (buf) : NULL;
1895 static void Fputc(int c, FILE *fp, int *len)
1897 int i = *len;
1899 if (c != '\n' && i + 1 > 80)
1900 Fputc('\n', fp, &i);
1902 if (pgn_lastc == '\n' && c == ' ') {
1903 *len = pgn_mpl = 0;
1904 return;
1907 if (fputc(c, fp) == EOF)
1908 warn("PGN Save");
1909 else {
1910 if (c == '\n')
1911 i = pgn_mpl = 0;
1912 else
1913 i++;
1916 *len = i;
1917 pgn_lastc = c;
1920 static void putstring(FILE *fp, char *str, int *len)
1922 char *p;
1924 for (p = str; *p; p++) {
1925 int n = 0;
1927 while (*p && *p != ' ')
1928 n++, p++;
1930 if (n + *len > 80)
1931 Fputc('\n', fp, len);
1933 p -= n;
1934 Fputc(*p, fp, len);
1939 * See pgn_write() for more info.
1941 static void write_comments_and_nag(FILE *fp, HISTORY *h, int *len)
1943 int i;
1944 char tmp[16];
1946 #ifdef DEBUG
1947 PGN_DUMP("%s:%d: writing comments and nag\n", __FILE__, __LINE__);
1948 #endif
1950 for (i = 0; i < MAX_PGN_NAG; i++) {
1951 if (h->nag[i]) {
1952 Fputc(' ', fp, len);
1953 Fputc('$', fp, len);
1954 putstring(fp, itoa(h->nag[i], tmp), len);
1958 if (h->comment) {
1959 Fputc('\n', fp, len);
1960 putstring(fp, " {", len);
1961 putstring(fp, h->comment, len);
1962 Fputc('}', fp, len);
1966 static void write_move_text(FILE *fp, HISTORY *h, int *len)
1968 Fputc(' ', fp, len);
1969 putstring(fp, h->move, len);
1971 if (!pgn_config.reduced)
1972 write_comments_and_nag(fp, h, len);
1975 static void write_all_move_text(FILE *fp, HISTORY **h, int m, int *len)
1977 int i;
1978 HISTORY **hp = NULL;
1979 char tmp[16];
1981 for (i = 0; h[i]; i++) {
1982 if (pgn_write_turn == WHITE) {
1983 if (pgn_config.mpl && pgn_mpl == pgn_config.mpl) {
1984 pgn_mpl = 0;
1985 Fputc('\n', fp, len);
1988 if (m > 1 && i > 0)
1989 Fputc(' ', fp, len);
1991 if (strlen(itoa(m, tmp)) + 1 + *len > 80)
1992 Fputc('\n', fp, len);
1994 putstring(fp, itoa(m, tmp), len);
1995 Fputc('.', fp, len);
1996 pgn_mpl++;
1999 write_move_text(fp, h[i], len);
2001 if (!pgn_config.reduced && h[i]->rav) {
2002 int oldm = m;
2003 int oldturn = pgn_write_turn;
2005 ravlevel++;
2006 putstring(fp, " (", len);
2009 * If it's WHITE's turn the move number will be added above after
2010 * the call to write_all_move_text() below.
2012 if (pgn_write_turn == BLACK) {
2013 putstring(fp, itoa(m, tmp), len);
2014 putstring(fp, "...", len);
2017 hp = h[i]->rav;
2018 write_all_move_text(fp, hp, m, len);
2019 m = oldm;
2020 pgn_write_turn = oldturn;
2021 putstring(fp, ")", len);
2022 ravlevel--;
2024 if (ravlevel && h[i + 1])
2025 Fputc(' ', fp, len);
2027 if (h[i + 1] && !ravlevel)
2028 Fputc(' ', fp, len);
2030 if (pgn_write_turn == WHITE && h[i + 1]) {
2031 putstring(fp, itoa(m, tmp), len);
2032 putstring(fp, "...", len);
2036 if (pgn_write_turn == BLACK)
2037 m++;
2039 pgn_write_turn = (pgn_write_turn == WHITE) ? BLACK : WHITE;
2043 static char *compression_cmd(const char *filename, int expand)
2045 char command[PATH_MAX];
2046 int len = strlen(filename);
2048 if (filename[len - 4] == '.' && filename[len - 3] == 'z' &&
2049 filename[len - 2] == 'i' && filename[len - 1] == 'p' &&
2050 filename[len] == '\0') {
2051 if (expand)
2052 snprintf(command, sizeof(command), "unzip -p %s 2>/dev/null",
2053 filename);
2054 else
2055 snprintf(command, sizeof(command), "zip -9 >%s 2>/dev/null",
2056 filename);
2058 return strdup (command);
2060 else if (filename[len - 3] == '.' && filename[len - 2] == 'g' &&
2061 filename[len - 1] == 'z' && filename[len] == '\0') {
2062 if (expand)
2063 snprintf(command, sizeof(command), "gzip -dc %s", filename);
2064 else
2065 snprintf(command, sizeof(command), "gzip -c 1>%s", filename);
2067 return strdup (command);
2069 else if (filename[len - 2] == '.' && filename[len - 1] == 'Z' &&
2070 filename[len] == '\0') {
2071 if (expand)
2072 snprintf(command, sizeof(command), "uncompress -c %s", filename);
2073 else
2074 snprintf(command, sizeof(command), "compress -c 1>%s", filename);
2076 return strdup (command);
2078 else if ((filename[len - 4] == '.' && filename[len - 3] == 'b' &&
2079 filename[len - 2] == 'z' && filename[len - 1] == '2' &&
2080 filename[len] == '\0') || (filename[len - 3] == '.' &&
2081 filename[len - 2] == 'b' && filename[len - 1] == 'z' &&
2082 filename[len] == '\0')) {
2083 if (expand)
2084 snprintf(command, sizeof(command), "bzip2 -dc %s", filename);
2085 else
2086 snprintf(command, sizeof(command), "bzip2 -zc 1>%s", filename);
2088 return strdup (command);
2091 return NULL;
2094 static int copy_file(FILE *fp, const char *dst)
2096 FILE *ofp;
2097 char line[LINE_MAX];
2098 char *cmd = compression_cmd(dst, 0);
2100 if ((ofp = popen(cmd, "w")) == NULL) {
2101 free (cmd);
2102 return 1;
2105 free (cmd);
2106 fseek(fp, 0, SEEK_SET);
2108 while ((fgets(line, sizeof(line), fp)) != NULL)
2109 fprintf(ofp, "%s", line);
2111 pclose(ofp);
2112 return 0;
2116 * Closes and free's a PGN file handle.
2118 pgn_error_t pgn_close(PGN_FILE *pgn)
2120 if (!pgn)
2121 return E_PGN_INVALID;
2123 if (pgn->pipe) {
2125 * Appending to a compressed file.
2127 if (pgn->tmpfile) {
2128 if (copy_file(pgn->fp, pgn->filename))
2129 return E_PGN_ERR;
2131 fclose(pgn->fp);
2132 unlink(pgn->tmpfile);
2133 free(pgn->tmpfile);
2135 else
2136 pclose(pgn->fp);
2138 else
2139 fclose(pgn->fp);
2141 free(pgn->filename);
2142 free(pgn);
2143 return E_PGN_OK;
2147 * Opens a file 'filename' with the given 'mode'. 'mode' should be "r" for
2148 * reading, "w" for writing (will truncate if the file exists) or "a" for
2149 * appending to an existing file or creating a new one. Returns E_PGN_OK on
2150 * success and sets 'result' to a file handle for use will the other file
2151 * functions or E_PGN_ERR if there is an error opening the file in which case
2152 * errno will be set to the error or E_PGN_INVALID if 'mode' is an invalid
2153 * mode or if 'filename' is not a regular file.
2155 pgn_error_t pgn_open(const char *filename, const char *mode, PGN_FILE **result)
2157 FILE *fp = NULL, *tfp = NULL;
2158 char buf[PATH_MAX], *p;
2159 char *cmd = NULL;
2160 PGN_FILE *pgn;
2161 int m;
2162 int append = 0;
2163 struct stat st;
2164 int ret = E_PGN_ERR;
2167 #ifdef DEBUG
2168 PGN_DUMP("%s:%d: BEGIN opening %s\n", __FILE__, __LINE__, filename);
2169 #endif
2171 if (!filename || !mode)
2172 return E_PGN_INVALID;
2174 if (strcmp(mode, "r") == 0)
2175 m = 1;
2176 else if (strcmp(mode, "w") == 0)
2177 m = 0;
2178 else if (strcmp(mode, "a") == 0) {
2179 m = 0;
2180 append = 1;
2182 else {
2183 return E_PGN_INVALID;
2186 pgn = calloc(1, sizeof(PGN_FILE));
2188 if (!pgn)
2189 goto fail;
2191 if (strcmp(filename, "-") != 0) {
2192 if (m && access(filename, R_OK) == -1)
2193 goto fail;
2195 if (stat(filename, &st) == -1 && !m && errno != ENOENT)
2196 goto fail;
2198 if (m && !S_ISREG(st.st_mode)) {
2199 ret = E_PGN_INVALID;
2200 goto fail;
2203 if ((cmd = compression_cmd(filename, m)) != NULL) {
2204 pgn->pipe = 1;
2206 if (append && access(filename, R_OK) == 0) {
2207 char tmp[21];
2208 int fd;
2210 free (cmd);
2211 cmd = compression_cmd(filename, 1);
2213 if ((fp = popen(cmd, "r")) == NULL)
2214 goto fail;
2216 if (tmpnam(tmp) == NULL)
2217 goto fail;
2219 if ((fd = open(tmp, O_RDWR|O_EXCL|O_CREAT)) == -1)
2220 goto fail;
2222 if ((tfp = fdopen(fd, "a+")) == NULL)
2223 goto fail;
2225 while ((p = fgets(buf, sizeof(buf), fp)) != NULL)
2226 fprintf(tfp, "%s", p);
2228 pclose(fp);
2229 pgn->fp = tfp;
2230 pgn->tmpfile = strdup(tmp);
2231 goto done;
2234 if ((fp = popen(cmd, m ? "r" : "w")) == NULL)
2235 goto fail;
2237 if (m) {
2238 if ((tfp = tmpfile()) == NULL)
2239 goto fail;
2241 while ((p = fgets(buf, sizeof(buf), fp)) != NULL)
2242 fprintf(tfp, "%s", p);
2244 pclose(fp);
2245 pgn->fp = tfp;
2247 else
2248 pgn->fp = fp;
2250 else {
2251 if ((fp = fopen(filename, mode)) == NULL)
2252 goto fail;
2254 pgn->fp = fp;
2257 else
2258 pgn->fp = stdout;
2260 done:
2261 if (*filename != '/') {
2262 if (getcwd(buf, sizeof(buf)) == NULL) {
2263 if (pgn->tmpfile)
2264 free(pgn->tmpfile);
2266 goto fail;
2269 asprintf(&p, "%s/%s", buf, filename);
2270 pgn->filename = p;
2272 else
2273 pgn->filename = strdup(filename);
2275 free (cmd);
2276 *result = pgn;
2277 return E_PGN_OK;
2279 fail:
2280 if (fp)
2281 fclose(fp);
2283 free (cmd);
2284 free(pgn);
2285 return ret;
2289 * Returns E_PGN_OK if 'filename' is a recognized compressed filetype or
2290 * E_PGN_ERR if not.
2292 pgn_error_t pgn_is_compressed(const char *filename)
2294 char *s = compression_cmd(filename, 0);
2295 pgn_error_t e = s ? E_PGN_OK : E_PGN_ERR;
2297 free (s);
2298 return e;
2302 * Gets the value of config flag 'f'. The next argument should be a pointer of
2303 * the config type which is set to the value of 'f'. Returns E_PGN_ERR if 'f'
2304 * is an invalid flag or E_PGN_OK on success.
2306 pgn_error_t pgn_config_get(pgn_config_flag f, ...)
2308 va_list ap;
2309 int *intval;
2310 long *longval;
2311 pgn_progress *progress;
2313 va_start(ap, f);
2315 switch (f) {
2316 case PGN_STRICT_CASTLING:
2317 intval = va_arg(ap, int *);
2318 *intval = pgn_config.strict_castling;
2319 va_end(ap);
2320 return E_PGN_OK;
2321 case PGN_REDUCED:
2322 intval = va_arg(ap, int *);
2323 *intval = pgn_config.reduced;
2324 va_end(ap);
2325 return E_PGN_OK;
2326 case PGN_MPL:
2327 intval = va_arg(ap, int *);
2328 *intval = pgn_config.mpl;
2329 va_end(ap);
2330 return E_PGN_OK;
2331 case PGN_STOP_ON_ERROR:
2332 intval = va_arg(ap, int *);
2333 *intval = pgn_config.stop;
2334 va_end(ap);
2335 return E_PGN_OK;
2336 case PGN_PROGRESS:
2337 longval = va_arg(ap, long *);
2338 *longval = pgn_config.stop;
2339 va_end(ap);
2340 return E_PGN_OK;
2341 case PGN_PROGRESS_FUNC:
2342 progress = va_arg(ap, pgn_progress*);
2343 *progress = pgn_config.pfunc;
2344 va_end(ap);
2345 return E_PGN_OK;
2346 #ifdef DEBUG
2347 case PGN_DEBUG:
2348 intval = va_arg(ap, int *);
2349 *intval = dumptofile;
2350 va_end(ap);
2351 return E_PGN_OK;
2352 #endif
2353 default:
2354 break;
2357 return E_PGN_ERR;
2361 * Sets config flag 'f' to the next argument. Returns E_PGN_OK on success or
2362 * E_PGN_ERR if 'f' is an invalid flag or E_PGN_INVALID if 'val' is an invalid
2363 * flag value.
2365 pgn_error_t pgn_config_set(pgn_config_flag f, ...)
2367 va_list ap;
2368 int n;
2369 int ret = E_PGN_OK;
2371 va_start(ap, f);
2373 switch (f) {
2374 case PGN_REDUCED:
2375 n = va_arg(ap, int);
2377 if (n != 1 && n != 0) {
2378 ret = E_PGN_INVALID;
2379 break;
2382 pgn_config.reduced = n;
2383 break;
2384 case PGN_MPL:
2385 n = va_arg(ap, int);
2387 if (n < 0) {
2388 ret = E_PGN_INVALID;
2389 break;
2392 pgn_config.mpl = n;
2393 break;
2394 case PGN_STOP_ON_ERROR:
2395 n = va_arg(ap, int);
2397 if (n != 1 && n != 0) {
2398 ret = E_PGN_INVALID;
2399 break;
2402 pgn_config.stop = n;
2403 break;
2404 case PGN_PROGRESS:
2405 n = va_arg(ap, long);
2406 pgn_config.progress = n;
2407 break;
2408 case PGN_PROGRESS_FUNC:
2409 pgn_config.pfunc = va_arg(ap, pgn_progress);
2410 break;
2411 case PGN_STRICT_CASTLING:
2412 n = va_arg(ap, int);
2413 pgn_config.strict_castling = n;
2414 break;
2415 #ifdef DEBUG
2416 case PGN_DEBUG:
2417 n = va_arg(ap, int);
2418 dumptofile = (n > 0) ? 1 : 0;
2419 break;
2420 #endif
2421 default:
2422 ret = E_PGN_ERR;
2423 break;
2426 va_end(ap);
2427 return ret;
2431 * Writes a PGN formatted game 'g' to a file which was opened with pgn_open().
2432 * See 'pgn_config_flag' for output options. Returns E_PGN_ERR if there was a
2433 * memory allocation or write error and E_PGN_OK on success. It is important
2434 * to use pgn_close() afterwards if the file is a recognized compressed file
2435 * type otherwise the created temporary file wont be copied to the destination
2436 * filename.
2438 pgn_error_t pgn_write(PGN_FILE *pgn, GAME g)
2440 int i;
2441 int len = 0;
2443 if (!pgn)
2444 return E_PGN_ERR;
2446 pgn_write_turn = (TEST_FLAG(g->flags, GF_BLACK_OPENING)) ? BLACK : WHITE;
2447 pgn_tag_sort(g->tag);
2449 #ifdef DEBUG
2450 PGN_DUMP("%s:%d: writing tag section\n", __FILE__, __LINE__);
2451 #endif
2453 for (i = 0; g->tag[i]; i++) {
2454 struct tm tp;
2455 char tbuf[11] = {0};
2456 char *tmp;
2457 char *escaped;
2459 if (pgn_config.reduced && i == 7)
2460 break;
2462 if (strcmp(g->tag[i]->name, "Date") == 0) {
2463 memset(&tp, 0, sizeof(struct tm));
2465 if (strptime(g->tag[i]->value, PGN_TIME_FORMAT, &tp) != NULL) {
2466 len = strftime(tbuf, sizeof(tbuf), PGN_TIME_FORMAT, &tp) + 1;
2468 if ((tmp = strdup(tbuf)) == NULL)
2469 return E_PGN_ERR;
2471 free(g->tag[i]->value);
2472 g->tag[i]->value = tmp;
2475 else if (strcmp(g->tag[i]->name, "Event") == 0) {
2476 if (g->tag[i]->value[0] == '\0') {
2477 if ((tmp = strdup("?")) == NULL)
2478 return E_PGN_ERR;
2480 free(g->tag[i]->value);
2481 g->tag[i]->value = tmp;
2484 else if (strcmp(g->tag[i]->name, "Site") == 0) {
2485 if (g->tag[i]->value[0] == '\0') {
2486 if ((tmp = strdup("?")) == NULL)
2487 return E_PGN_ERR;
2489 free(g->tag[i]->value);
2490 g->tag[i]->value = tmp;
2493 else if (strcmp(g->tag[i]->name, "Round") == 0) {
2494 if (g->tag[i]->value[0] == '\0') {
2495 if ((tmp = strdup("?")) == NULL)
2496 return E_PGN_ERR;
2498 free(g->tag[i]->value);
2499 g->tag[i]->value = tmp;
2502 else if (strcmp(g->tag[i]->name, "Result") == 0) {
2503 if (g->tag[i]->value[0] == '\0') {
2504 if ((tmp = strdup("*")) == NULL)
2505 return E_PGN_ERR;
2507 free(g->tag[i]->value);
2508 g->tag[i]->value = tmp;
2511 else if (strcmp(g->tag[i]->name, "Black") == 0) {
2512 if (g->tag[i]->value[0] == '\0') {
2513 if ((tmp = strdup("?")) == NULL)
2514 return E_PGN_ERR;
2516 free(g->tag[i]->value);
2517 g->tag[i]->value = tmp;
2520 else if (strcmp(g->tag[i]->name, "White") == 0) {
2521 if (g->tag[i]->value[0] == '\0') {
2522 if ((tmp = strdup("?")) == NULL)
2523 return E_PGN_ERR;
2525 free(g->tag[i]->value);
2526 g->tag[i]->value = tmp;
2530 escaped = pgn_tag_add_escapes(g->tag[i]->value);
2531 fprintf(pgn->fp, "[%s \"%s\"]\n", g->tag[i]->name,
2532 (g->tag[i]->value && g->tag[i]->value[0]) ? escaped : "");
2533 free (escaped);
2536 #ifdef DEBUG
2537 PGN_DUMP("%s:%d: writing move section\n", __FILE__, __LINE__);
2538 #endif
2539 Fputc('\n', pgn->fp, &len);
2540 g->hp = g->history;
2541 ravlevel = pgn_mpl = 0;
2543 if (pgn_history_total(g->hp) && pgn_write_turn == BLACK)
2544 putstring(pgn->fp, "1...", &len);
2546 write_all_move_text(pgn->fp, g->hp, 1, &len);
2548 Fputc(' ', pgn->fp, &len);
2549 putstring(pgn->fp, g->tag[6]->value, &len);
2550 putstring(pgn->fp, "\n\n", &len);
2552 if (!pgn_config.reduced)
2553 CLEAR_FLAG(g->flags, GF_PERROR);
2555 return E_PGN_OK;
2559 * Clears the enpassant flag for all positions on board 'b'. Returns nothing.
2561 void pgn_reset_enpassant(BOARD b)
2563 int r, c;
2565 #ifdef DEBUG
2566 PGN_DUMP("%s:%d: resetting enpassant\n", __FILE__, __LINE__);
2567 #endif
2569 for (r = 0; r < 8; r++) {
2570 for (c = 0; c < 8; c++)
2571 b[r][c].enpassant = 0;