Update copyright year to 2015.
[cboard.git] / libchess / pgn.c
blob5bd00eec540b8c46b84c68192ce6eeca45ed8aa9
1 /* vim:tw=78:ts=8:sw=4:set ft=c: */
2 /*
3 Copyright (C) 2002-2015 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 if (!VALIDRANK(*tmp))
1427 return E_PGN_ERR;
1429 row = RANKTOBOARD(*tmp++);
1430 b[row][col].enpassant = 1;
1431 SET_FLAG(*flags, GF_ENPASSANT);
1433 else
1434 tmp++;
1436 if (*tmp)
1437 tmp++;
1439 if (*tmp)
1440 *ply = atoi(tmp);
1442 while (*tmp && isdigit(*tmp))
1443 tmp++;
1445 if (*tmp)
1446 tmp++;
1448 return E_PGN_OK;
1452 * It initializes the board (b) to the FEN tag (if found) and sets the
1453 * castling and enpassant info for the game 'g'. If 'fen' is set it should be
1454 * a fen tag and will be parsed rather than the game 'g'.tag FEN tag-> Returns
1455 * E_PGN_OK on success or if there was both a FEN and SetUp tag with the SetUp
1456 * tag set to 0. Returns E_PGN_PARSE if there was a FEN parse error, E_PGN_ERR
1457 * if there was no FEN tag or there was a SetUp tag with a value of 0. Returns
1458 * E_PGN_OK on success.
1460 pgn_error_t pgn_board_init_fen(GAME g, BOARD b, char *fen)
1462 int n = -1, i = -1;
1463 BOARD tmpboard;
1464 unsigned flags = 0;
1465 char turn = g->turn;
1466 char ply = 0;
1468 #ifdef DEBUG
1469 PGN_DUMP("%s:%d: initializing board from FEN\n", __FILE__, __LINE__);
1470 #endif
1471 pgn_board_init(tmpboard);
1473 if (!fen) {
1474 n = pgn_tag_find(g->tag, "Setup");
1475 i = pgn_tag_find(g->tag, "FEN");
1479 * If the FEN tag exists and there is no SetUp tag go ahead and parse it.
1480 * If there is a SetUp tag only parse the FEN tag if the value is 1.
1482 if ((n >= 0 && i >= 0 && atoi(g->tag[n]->value) == 1)
1483 || (i >= 0 && n == -1) || fen) {
1484 if ((n = parse_fen_line(tmpboard, &flags, &turn, &ply,
1485 (fen) ? fen : g->tag[i]->value)) != E_PGN_OK)
1486 return E_PGN_PARSE;
1487 else {
1488 memcpy(b, tmpboard, sizeof(BOARD));
1489 CLEAR_FLAG(g->flags, GF_WK_CASTLE);
1490 CLEAR_FLAG(g->flags, GF_WQ_CASTLE);
1491 CLEAR_FLAG(g->flags, GF_BK_CASTLE);
1492 CLEAR_FLAG(g->flags, GF_BQ_CASTLE);
1493 g->flags |= flags;
1494 g->turn = turn;
1495 g->ply = ply;
1498 else
1499 return (i >= 0 && n >= 0) ? E_PGN_OK : E_PGN_ERR;
1501 return E_PGN_OK;
1505 * Allocates a new game and increments 'gtotal'. 'gindex' is then set to the
1506 * new game. Returns E_PGN_ERR if there was a memory allocation error or
1507 * E_PGN_OK on success.
1509 pgn_error_t pgn_new_game()
1511 GAME *g;
1512 GAME newg;
1513 int t = gtotal + 1;
1515 #ifdef DEBUG
1516 PGN_DUMP("%s:%d: allocating new game\n", __FILE__, __LINE__);
1517 #endif
1518 gindex = t - 1;
1520 if ((g = realloc(game, t * sizeof(GAME))) == NULL) {
1521 warn("realloc()");
1522 return E_PGN_ERR;
1525 game = g;
1527 if ((newg = calloc(1, sizeof(struct game_s))) == NULL) {
1528 warn("calloc()");
1529 return E_PGN_ERR;
1532 game[gindex] = newg;
1534 if ((game[gindex]->hp = calloc(1, sizeof(HISTORY *))) == NULL) {
1535 free(game[gindex]);
1536 warn("calloc()");
1537 return E_PGN_ERR;
1540 game[gindex]->hp[0] = NULL;
1541 game[gindex]->history = game[gindex]->hp;
1542 game[gindex]->side = game[gindex]->turn = WHITE;
1543 SET_FLAG(game[gindex]->flags, GF_WK_CASTLE|GF_WQ_CASTLE|GF_WQ_CASTLE|
1544 GF_BK_CASTLE|GF_BQ_CASTLE);
1545 pgn_board_init(pgn_board);
1546 set_default_tags(game[gindex]);
1547 gtotal = t;
1548 return E_PGN_OK;
1551 static int read_file(FILE *fp)
1553 #ifdef DEBUG
1554 char buf[LINE_MAX] = {0}, *p = buf;
1555 #endif
1556 int c = 0;
1557 int parse_error = 0;
1558 BOARD old;
1560 while (1) {
1561 int nextchar = 0;
1562 int lastchar = c;
1563 int n;
1566 * A parse error may have occured at EOF.
1568 if (parse_error) {
1569 pgn_ret = E_PGN_PARSE;
1571 if (!game)
1572 pgn_new_game();
1574 SET_FLAG(game[gindex]->flags, GF_PERROR);
1577 if ((c = Fgetc(fp)) == EOF) {
1578 if (feof(fp))
1579 break;
1581 if (ferror(fp)) {
1582 clearerr(fp);
1583 continue;
1587 if (!isascii(c)) {
1588 parse_error = 1;
1589 continue;
1592 if (c == '\015')
1593 continue;
1595 nextchar = Fgetc(fp);
1596 Ungetc(nextchar, fp);
1599 * If there was a move text parsing error, keep reading until the end
1600 * of the current game discarding the data.
1602 if (parse_error) {
1603 pgn_ret = E_PGN_PARSE;
1605 if (game[gindex]->ravlevel)
1606 return 1;
1608 if (pgn_config.stop)
1609 return E_PGN_PARSE;
1611 if (c == '\n' && (nextchar == '\n' || nextchar == '\015')) {
1612 parse_error = 0;
1613 nulltags = 1;
1614 tag_section = 0;
1616 else
1617 continue;
1620 // New game reached.
1621 if (c == '\n' && (nextchar == '\n' || nextchar == '\015')) {
1622 if (tag_section)
1623 continue;
1625 nulltags = 1;
1626 tag_section = 0;
1627 continue;
1631 * PGN: Application comment. The '%' must be on the first column of
1632 * the line. The comment continues until the end of the current line.
1634 if (c == '%') {
1635 if (lastchar == '\n' || lastchar == 0) {
1636 while ((c = Fgetc(fp)) != EOF && c != '\n');
1637 continue;
1640 // Not sure what to do here.
1643 if (isspace(c))
1644 continue;
1646 // PGN: Reserved.
1647 if (c == '<' || c == '>')
1648 continue;
1651 * PGN: Recurrsive Annotated Variation. Read rav_text() for more
1652 * info.
1654 if (c == '(' || c == ')') {
1655 switch (rav_text(game[gindex], fp, c, old)) {
1656 case -1:
1658 * This is the end of the current RAV. This function has
1659 * been called from rav_text(). Returning from this point
1660 * will put us back in rav_text().
1662 if (game[gindex]->ravlevel > 0)
1663 return pgn_ret;
1666 * We're back at the root move. Continue as normal.
1668 break;
1669 case 1:
1670 parse_error = 1;
1671 continue;
1672 default:
1674 * Continue processing-> Probably the root move.
1676 break;
1679 if (!game[gindex]->ravlevel && pgn_rav)
1680 parse_error = 1;
1682 continue;
1685 // PGN: Numeric Annotation Glyph.
1686 if (c == '$' || c == '!' || c == '?' || c == '+' || c == '-' ||
1687 c == '~' || c == '=') {
1688 Ungetc(c, fp);
1689 nag_text(game[gindex], fp);
1690 continue;
1694 * PGN: Annotation. The ';' comment continues until the end of the
1695 * current line. The '{' type comment continues until a '}' is
1696 * reached.
1698 if (c == '{' || c == ';') {
1699 annotation_text(game[gindex], fp, (c == '{') ? '}' : '\n');
1700 continue;
1703 // PGN: Roster tag->
1704 if (c == '[') {
1705 // First roster tag found. Initialize the data structures.
1706 if (!tag_section) {
1707 nulltags = 0;
1708 tag_section = 1;
1710 if (gtotal && pgn_history_total(game[gindex]->hp))
1711 game[gindex]->hindex = pgn_history_total(game[gindex]->hp) - 1;
1713 if (pgn_new_game() != E_PGN_OK) {
1714 pgn_ret = E_PGN_ERR;
1715 break;
1718 memcpy(old, pgn_board, sizeof(BOARD));
1721 if (tag_text(game[gindex], fp))
1722 parse_error = 1; // FEN tag parse error.
1724 continue;
1727 // PGN: End-of-game markers.
1728 if ((isdigit(c) && (nextchar == '-' || nextchar == '/')) || c == '*') {
1729 Ungetc(c, fp);
1731 if (eog_text(game[gindex], fp)) {
1732 parse_error = 1;
1733 continue;
1736 nulltags = 1;
1737 tag_section = 0;
1739 if (!game[gindex]->done_fen_tag) {
1740 if (pgn_tag_find(game[gindex]->tag, "FEN") != -1 &&
1741 pgn_board_init_fen(game[gindex], pgn_board, NULL)) {
1742 parse_error = 1;
1743 continue;
1746 game[gindex]->pgn_fen_tag = pgn_tag_find(game[gindex]->tag, "FEN");
1747 game[gindex]->done_fen_tag = 1;
1750 continue;
1753 // PGN: Move text.
1754 if ((isdigit(c) && c != '0') || VALIDCOL(c) || c == 'N' || c == 'K'
1755 || c == 'Q' || c == 'B' || c == 'R' || c == 'P' ||
1756 c == 'O') {
1757 Ungetc(c, fp);
1759 // PGN: If a FEN tag exists, initialize the board to the value.
1760 if (tag_section) {
1761 if (pgn_tag_find(game[gindex]->tag, "FEN") != E_PGN_ERR &&
1762 (n = pgn_board_init_fen(game[gindex], pgn_board,
1763 NULL)) == E_PGN_PARSE) {
1764 parse_error = 1;
1765 continue;
1768 game[gindex]->done_fen_tag = 1;
1769 game[gindex]->pgn_fen_tag = pgn_tag_find(game[gindex]->tag, "FEN");
1770 tag_section = 0;
1774 * PGN: Import format doesn't require a roster tag section. We've
1775 * arrived to the move text section without any tags so we
1776 * initialize a new game which set's the default tags and any tags
1777 * from the configuration file.
1779 if (nulltags) {
1780 if (gtotal)
1781 game[gindex]->hindex = pgn_history_total(game[gindex]->hp) - 1;
1783 if (pgn_new_game() != E_PGN_OK) {
1784 pgn_ret = E_PGN_ERR;
1785 break;
1788 memcpy(old, pgn_board, sizeof(BOARD));
1789 nulltags = 0;
1792 memcpy(old, pgn_board, sizeof(BOARD));
1794 if (move_text(game[gindex], fp)) {
1795 if (pgn_tag_add(&game[gindex]->tag, "Result", "*") ==
1796 E_PGN_ERR) {
1797 warn("pgn_tag_add()");
1798 pgn_ret = E_PGN_ERR;
1801 SET_FLAG(game[gindex]->flags, GF_PERROR);
1802 parse_error = 1;
1805 continue;
1808 #ifdef DEBUG
1809 *p++ = c;
1811 PGN_DUMP("%s:%d: unparsed: '%s'\n", __FILE__, __LINE__, buf);
1813 if (strlen(buf) + 1 == sizeof(buf))
1814 bzero(buf, sizeof(buf));
1815 #endif
1817 continue;
1820 return pgn_ret;
1824 * Parses a PGN_FILE which was opened with pgn_open(). If 'pgn' is NULL then a
1825 * single empty game will be allocated. If there is a parsing error
1826 * E_PGN_PARSE is returned, if there was a memory allocation error E_PGN_ERR
1827 * is returned, otherwise E_PGN_OK is returned and the global 'gindex' is set
1828 * to the last parsed game in the file and the global 'gtotal' is set to the
1829 * total number of games in the file. The file should be closed with
1830 * pgn_close() after processing.
1832 pgn_error_t pgn_parse(PGN_FILE *pgn)
1834 int i;
1836 if (!pgn) {
1837 reset_game_data();
1838 pgn_ret = pgn_new_game();
1839 goto done;
1842 reset_game_data();
1843 nulltags = 1;
1844 fseek(pgn->fp, 0, SEEK_END);
1845 pgn_fsize = ftell(pgn->fp);
1846 fseek(pgn->fp, 0, SEEK_SET);
1847 #ifdef DEBUG
1848 PGN_DUMP("%s:%d: BEGIN parsing->..\n", __FILE__, __LINE__);
1849 #endif
1850 parsing_file = 1;
1851 pgn_ret = read_file(pgn->fp);
1852 parsing_file = 0;
1854 #ifdef DEBUG
1855 PGN_DUMP("%s:%d: END parsing->..\n", __FILE__, __LINE__);
1856 #endif
1858 if (gtotal < 1)
1859 pgn_new_game();
1861 done:
1862 gtotal = gindex + 1;
1864 for (i = 0; i < gtotal; i++) {
1865 game[i]->history = game[i]->hp;
1866 game[i]->hindex = pgn_history_total(game[i]->hp);
1869 return pgn_ret;
1873 * Escape '"' and '\' in tag values.
1875 static char *pgn_tag_add_escapes(const char *str)
1877 int i, n;
1878 int len = strlen(str);
1879 char buf[MAX_PGN_LINE_LEN] = {0};
1881 for (i = n = 0; i < len; i++, n++) {
1882 switch (str[i]) {
1883 case '\\':
1884 case '\"':
1885 buf[n++] = '\\';
1886 break;
1887 default:
1888 break;
1891 buf[n] = str[i];
1894 buf[n] = '\0';
1895 return buf[0] ? strdup (buf) : NULL;
1898 static void Fputc(int c, FILE *fp, int *len)
1900 int i = *len;
1902 if (c != '\n' && i + 1 > 80)
1903 Fputc('\n', fp, &i);
1905 if (pgn_lastc == '\n' && c == ' ') {
1906 *len = pgn_mpl = 0;
1907 return;
1910 if (fputc(c, fp) == EOF)
1911 warn("PGN Save");
1912 else {
1913 if (c == '\n')
1914 i = pgn_mpl = 0;
1915 else
1916 i++;
1919 *len = i;
1920 pgn_lastc = c;
1923 static void putstring(FILE *fp, char *str, int *len)
1925 char *p;
1927 for (p = str; *p; p++) {
1928 int n = 0;
1930 while (*p && *p != ' ')
1931 n++, p++;
1933 if (n + *len > 80)
1934 Fputc('\n', fp, len);
1936 p -= n;
1937 Fputc(*p, fp, len);
1942 * See pgn_write() for more info.
1944 static void write_comments_and_nag(FILE *fp, HISTORY *h, int *len)
1946 int i;
1947 char tmp[16];
1949 #ifdef DEBUG
1950 PGN_DUMP("%s:%d: writing comments and nag\n", __FILE__, __LINE__);
1951 #endif
1953 for (i = 0; i < MAX_PGN_NAG; i++) {
1954 if (h->nag[i]) {
1955 Fputc(' ', fp, len);
1956 Fputc('$', fp, len);
1957 putstring(fp, itoa(h->nag[i], tmp), len);
1961 if (h->comment) {
1962 Fputc('\n', fp, len);
1963 putstring(fp, " {", len);
1964 putstring(fp, h->comment, len);
1965 Fputc('}', fp, len);
1969 static void write_move_text(FILE *fp, HISTORY *h, int *len)
1971 Fputc(' ', fp, len);
1972 putstring(fp, h->move, len);
1974 if (!pgn_config.reduced)
1975 write_comments_and_nag(fp, h, len);
1978 static void write_all_move_text(FILE *fp, HISTORY **h, int m, int *len)
1980 int i;
1981 HISTORY **hp = NULL;
1982 char tmp[16];
1984 for (i = 0; h[i]; i++) {
1985 if (pgn_write_turn == WHITE) {
1986 if (pgn_config.mpl && pgn_mpl == pgn_config.mpl) {
1987 pgn_mpl = 0;
1988 Fputc('\n', fp, len);
1991 if (m > 1 && i > 0)
1992 Fputc(' ', fp, len);
1994 if (strlen(itoa(m, tmp)) + 1 + *len > 80)
1995 Fputc('\n', fp, len);
1997 putstring(fp, itoa(m, tmp), len);
1998 Fputc('.', fp, len);
1999 pgn_mpl++;
2002 write_move_text(fp, h[i], len);
2004 if (!pgn_config.reduced && h[i]->rav) {
2005 int oldm = m;
2006 int oldturn = pgn_write_turn;
2008 ravlevel++;
2009 putstring(fp, " (", len);
2012 * If it's WHITE's turn the move number will be added above after
2013 * the call to write_all_move_text() below.
2015 if (pgn_write_turn == BLACK) {
2016 putstring(fp, itoa(m, tmp), len);
2017 putstring(fp, "...", len);
2020 hp = h[i]->rav;
2021 write_all_move_text(fp, hp, m, len);
2022 m = oldm;
2023 pgn_write_turn = oldturn;
2024 putstring(fp, ")", len);
2025 ravlevel--;
2027 if (ravlevel && h[i + 1])
2028 Fputc(' ', fp, len);
2030 if (h[i + 1] && !ravlevel)
2031 Fputc(' ', fp, len);
2033 if (pgn_write_turn == WHITE && h[i + 1]) {
2034 putstring(fp, itoa(m, tmp), len);
2035 putstring(fp, "...", len);
2039 if (pgn_write_turn == BLACK)
2040 m++;
2042 pgn_write_turn = (pgn_write_turn == WHITE) ? BLACK : WHITE;
2046 static char *compression_cmd(const char *filename, int expand)
2048 char command[PATH_MAX];
2049 int len = strlen(filename);
2051 if (filename[len - 4] == '.' && filename[len - 3] == 'z' &&
2052 filename[len - 2] == 'i' && filename[len - 1] == 'p' &&
2053 filename[len] == '\0') {
2054 if (expand)
2055 snprintf(command, sizeof(command), "unzip -p %s 2>/dev/null",
2056 filename);
2057 else
2058 snprintf(command, sizeof(command), "zip -9 >%s 2>/dev/null",
2059 filename);
2061 return strdup (command);
2063 else if (filename[len - 3] == '.' && filename[len - 2] == 'g' &&
2064 filename[len - 1] == 'z' && filename[len] == '\0') {
2065 if (expand)
2066 snprintf(command, sizeof(command), "gzip -dc %s", filename);
2067 else
2068 snprintf(command, sizeof(command), "gzip -c 1>%s", filename);
2070 return strdup (command);
2072 else if (filename[len - 2] == '.' && filename[len - 1] == 'Z' &&
2073 filename[len] == '\0') {
2074 if (expand)
2075 snprintf(command, sizeof(command), "uncompress -c %s", filename);
2076 else
2077 snprintf(command, sizeof(command), "compress -c 1>%s", filename);
2079 return strdup (command);
2081 else if ((filename[len - 4] == '.' && filename[len - 3] == 'b' &&
2082 filename[len - 2] == 'z' && filename[len - 1] == '2' &&
2083 filename[len] == '\0') || (filename[len - 3] == '.' &&
2084 filename[len - 2] == 'b' && filename[len - 1] == 'z' &&
2085 filename[len] == '\0')) {
2086 if (expand)
2087 snprintf(command, sizeof(command), "bzip2 -dc %s", filename);
2088 else
2089 snprintf(command, sizeof(command), "bzip2 -zc 1>%s", filename);
2091 return strdup (command);
2094 return NULL;
2097 static int copy_file(FILE *fp, const char *dst)
2099 FILE *ofp;
2100 char line[LINE_MAX];
2101 char *cmd = compression_cmd(dst, 0);
2103 if ((ofp = popen(cmd, "w")) == NULL) {
2104 free (cmd);
2105 return 1;
2108 free (cmd);
2109 fseek(fp, 0, SEEK_SET);
2111 while ((fgets(line, sizeof(line), fp)) != NULL)
2112 fprintf(ofp, "%s", line);
2114 pclose(ofp);
2115 return 0;
2119 * Closes and free's a PGN file handle.
2121 pgn_error_t pgn_close(PGN_FILE *pgn)
2123 if (!pgn)
2124 return E_PGN_INVALID;
2126 if (pgn->pipe) {
2128 * Appending to a compressed file.
2130 if (pgn->tmpfile) {
2131 if (copy_file(pgn->fp, pgn->filename))
2132 return E_PGN_ERR;
2134 fclose(pgn->fp);
2135 unlink(pgn->tmpfile);
2136 free(pgn->tmpfile);
2138 else
2139 pclose(pgn->fp);
2141 else
2142 fclose(pgn->fp);
2144 free(pgn->filename);
2145 free(pgn);
2146 return E_PGN_OK;
2150 * Opens a file 'filename' with the given 'mode'. 'mode' should be "r" for
2151 * reading, "w" for writing (will truncate if the file exists) or "a" for
2152 * appending to an existing file or creating a new one. Returns E_PGN_OK on
2153 * success and sets 'result' to a file handle for use will the other file
2154 * functions or E_PGN_ERR if there is an error opening the file in which case
2155 * errno will be set to the error or E_PGN_INVALID if 'mode' is an invalid
2156 * mode or if 'filename' is not a regular file.
2158 pgn_error_t pgn_open(const char *filename, const char *mode, PGN_FILE **result)
2160 FILE *fp = NULL, *tfp = NULL, *pfp = NULL;
2161 char buf[PATH_MAX], *p;
2162 char *cmd = NULL;
2163 PGN_FILE *pgn;
2164 int m;
2165 int append = 0;
2166 struct stat st;
2167 int ret = E_PGN_ERR;
2170 #ifdef DEBUG
2171 PGN_DUMP("%s:%d: BEGIN opening %s\n", __FILE__, __LINE__, filename);
2172 #endif
2174 if (!filename || !mode)
2175 return E_PGN_INVALID;
2177 if (strcmp(mode, "r") == 0)
2178 m = 1;
2179 else if (strcmp(mode, "w") == 0)
2180 m = 0;
2181 else if (strcmp(mode, "a") == 0) {
2182 m = 0;
2183 append = 1;
2185 else {
2186 return E_PGN_INVALID;
2189 pgn = calloc(1, sizeof(PGN_FILE));
2191 if (!pgn)
2192 goto fail;
2194 if (strcmp(filename, "-") != 0) {
2195 if (m && access(filename, R_OK) == -1)
2196 goto fail;
2198 if (stat(filename, &st) == -1 && !m && errno != ENOENT)
2199 goto fail;
2201 if (m && !S_ISREG(st.st_mode)) {
2202 ret = E_PGN_INVALID;
2203 goto fail;
2206 if ((cmd = compression_cmd(filename, m)) != NULL) {
2207 char tmp[21];
2208 int fd;
2210 #ifdef HAVE_MKSTEMP
2211 snprintf (tmp, sizeof(tmp), "cboard.XXXXXX");
2212 #else
2213 if (tmpnam(tmp) == NULL)
2214 goto fail;
2215 #endif
2217 pgn->pipe = 1;
2219 if (append && access(filename, R_OK) == 0) {
2220 #ifdef HAVE_MKSTEMP
2221 mode_t mode;
2222 #endif
2224 free (cmd);
2225 cmd = compression_cmd(filename, 1);
2226 if ((pfp = popen(cmd, "r")) == NULL)
2227 goto fail;
2229 #ifdef HAVE_MKSTEMP
2230 mode = umask(600);
2231 fd = mkstemp (tmp);
2232 umask(mode);
2233 if (fd == -1)
2234 goto fail;
2235 #else
2236 if ((fd = open(tmp, O_RDWR|O_EXCL|O_CREAT, 0600)) == -1)
2237 goto fail;
2238 #endif
2239 if ((tfp = fdopen(fd, "a+")) == NULL)
2240 goto fail;
2242 while ((p = fgets(buf, sizeof(buf), pfp)) != NULL)
2243 fprintf(tfp, "%s", p);
2245 pgn->fp = tfp;
2246 pgn->tmpfile = strdup(tmp);
2247 goto done;
2250 if ((pfp = popen(cmd, m ? "r" : "w")) == NULL)
2251 goto fail;
2253 if (m) {
2254 #ifdef HAVE_MKSTEMP
2255 mode_t mode = umask (600);
2257 fd = mkstemp (tmp);
2258 umask (mode);
2259 if (fd == -1)
2260 goto fail;
2261 #else
2262 if ((fd = open(tmp, O_RDWR|O_EXCL|O_CREAT|O_TRUNC, 0600)) == -1)
2263 goto fail;
2264 #endif
2265 if ((tfp = fdopen(fd, "w+")) == NULL)
2266 goto fail;
2268 while ((p = fgets(buf, sizeof(buf), pfp)) != NULL)
2269 fprintf(tfp, "%s", p);
2271 pgn->fp = tfp;
2272 pgn->tmpfile = strdup(tmp);
2274 else
2275 pgn->fp = pfp;
2277 else {
2278 if ((fp = fopen(filename, mode)) == NULL)
2279 goto fail;
2281 pgn->fp = fp;
2284 else
2285 pgn->fp = stdout;
2287 done:
2288 if (*filename != '/') {
2289 if (getcwd(buf, sizeof(buf)) == NULL) {
2290 if (pgn->tmpfile)
2291 free(pgn->tmpfile);
2293 goto fail;
2296 asprintf(&p, "%s/%s", buf, filename);
2297 pgn->filename = p;
2299 else
2300 pgn->filename = strdup(filename);
2302 free (cmd);
2303 *result = pgn;
2304 return E_PGN_OK;
2306 fail:
2307 if (fp)
2308 fclose(fp);
2310 if (pfp)
2311 pclose(pfp);
2313 free (cmd);
2314 free(pgn);
2315 return ret;
2319 * Returns E_PGN_OK if 'filename' is a recognized compressed filetype or
2320 * E_PGN_ERR if not.
2322 pgn_error_t pgn_is_compressed(const char *filename)
2324 char *s = compression_cmd(filename, 0);
2325 pgn_error_t e = s ? E_PGN_OK : E_PGN_ERR;
2327 free (s);
2328 return e;
2332 * Gets the value of config flag 'f'. The next argument should be a pointer of
2333 * the config type which is set to the value of 'f'. Returns E_PGN_ERR if 'f'
2334 * is an invalid flag or E_PGN_OK on success.
2336 pgn_error_t pgn_config_get(pgn_config_flag f, ...)
2338 va_list ap;
2339 int *intval;
2340 long *longval;
2341 pgn_progress *progress;
2343 va_start(ap, f);
2345 switch (f) {
2346 case PGN_STRICT_CASTLING:
2347 intval = va_arg(ap, int *);
2348 *intval = pgn_config.strict_castling;
2349 va_end(ap);
2350 return E_PGN_OK;
2351 case PGN_REDUCED:
2352 intval = va_arg(ap, int *);
2353 *intval = pgn_config.reduced;
2354 va_end(ap);
2355 return E_PGN_OK;
2356 case PGN_MPL:
2357 intval = va_arg(ap, int *);
2358 *intval = pgn_config.mpl;
2359 va_end(ap);
2360 return E_PGN_OK;
2361 case PGN_STOP_ON_ERROR:
2362 intval = va_arg(ap, int *);
2363 *intval = pgn_config.stop;
2364 va_end(ap);
2365 return E_PGN_OK;
2366 case PGN_PROGRESS:
2367 longval = va_arg(ap, long *);
2368 *longval = pgn_config.stop;
2369 va_end(ap);
2370 return E_PGN_OK;
2371 case PGN_PROGRESS_FUNC:
2372 progress = va_arg(ap, pgn_progress*);
2373 *progress = pgn_config.pfunc;
2374 va_end(ap);
2375 return E_PGN_OK;
2376 #ifdef DEBUG
2377 case PGN_DEBUG:
2378 intval = va_arg(ap, int *);
2379 *intval = dumptofile;
2380 va_end(ap);
2381 return E_PGN_OK;
2382 #endif
2383 default:
2384 break;
2387 return E_PGN_ERR;
2391 * Sets config flag 'f' to the next argument. Returns E_PGN_OK on success or
2392 * E_PGN_ERR if 'f' is an invalid flag or E_PGN_INVALID if 'val' is an invalid
2393 * flag value.
2395 pgn_error_t pgn_config_set(pgn_config_flag f, ...)
2397 va_list ap;
2398 int n;
2399 int ret = E_PGN_OK;
2401 va_start(ap, f);
2403 switch (f) {
2404 case PGN_REDUCED:
2405 n = va_arg(ap, int);
2407 if (n != 1 && n != 0) {
2408 ret = E_PGN_INVALID;
2409 break;
2412 pgn_config.reduced = n;
2413 break;
2414 case PGN_MPL:
2415 n = va_arg(ap, int);
2417 if (n < 0) {
2418 ret = E_PGN_INVALID;
2419 break;
2422 pgn_config.mpl = n;
2423 break;
2424 case PGN_STOP_ON_ERROR:
2425 n = va_arg(ap, int);
2427 if (n != 1 && n != 0) {
2428 ret = E_PGN_INVALID;
2429 break;
2432 pgn_config.stop = n;
2433 break;
2434 case PGN_PROGRESS:
2435 n = va_arg(ap, long);
2436 pgn_config.progress = n;
2437 break;
2438 case PGN_PROGRESS_FUNC:
2439 pgn_config.pfunc = va_arg(ap, pgn_progress);
2440 break;
2441 case PGN_STRICT_CASTLING:
2442 n = va_arg(ap, int);
2443 pgn_config.strict_castling = n;
2444 break;
2445 #ifdef DEBUG
2446 case PGN_DEBUG:
2447 n = va_arg(ap, int);
2448 dumptofile = (n > 0) ? 1 : 0;
2449 break;
2450 #endif
2451 default:
2452 ret = E_PGN_ERR;
2453 break;
2456 va_end(ap);
2457 return ret;
2461 * Writes a PGN formatted game 'g' to a file which was opened with pgn_open().
2462 * See 'pgn_config_flag' for output options. Returns E_PGN_ERR if there was a
2463 * memory allocation or write error and E_PGN_OK on success. It is important
2464 * to use pgn_close() afterwards if the file is a recognized compressed file
2465 * type otherwise the created temporary file wont be copied to the destination
2466 * filename.
2468 pgn_error_t pgn_write(PGN_FILE *pgn, GAME g)
2470 int i;
2471 int len = 0;
2473 if (!pgn)
2474 return E_PGN_ERR;
2476 pgn_write_turn = (TEST_FLAG(g->flags, GF_BLACK_OPENING)) ? BLACK : WHITE;
2477 pgn_tag_sort(g->tag);
2479 #ifdef DEBUG
2480 PGN_DUMP("%s:%d: writing tag section\n", __FILE__, __LINE__);
2481 #endif
2483 for (i = 0; g->tag[i]; i++) {
2484 struct tm tp;
2485 char tbuf[11] = {0};
2486 char *tmp;
2487 char *escaped;
2489 if (pgn_config.reduced && i == 7)
2490 break;
2492 if (strcmp(g->tag[i]->name, "Date") == 0) {
2493 memset(&tp, 0, sizeof(struct tm));
2495 if (strptime(g->tag[i]->value, PGN_TIME_FORMAT, &tp) != NULL) {
2496 len = strftime(tbuf, sizeof(tbuf), PGN_TIME_FORMAT, &tp) + 1;
2498 if ((tmp = strdup(tbuf)) == NULL)
2499 return E_PGN_ERR;
2501 free(g->tag[i]->value);
2502 g->tag[i]->value = tmp;
2505 else if (strcmp(g->tag[i]->name, "Event") == 0) {
2506 if (g->tag[i]->value[0] == '\0') {
2507 if ((tmp = strdup("?")) == NULL)
2508 return E_PGN_ERR;
2510 free(g->tag[i]->value);
2511 g->tag[i]->value = tmp;
2514 else if (strcmp(g->tag[i]->name, "Site") == 0) {
2515 if (g->tag[i]->value[0] == '\0') {
2516 if ((tmp = strdup("?")) == NULL)
2517 return E_PGN_ERR;
2519 free(g->tag[i]->value);
2520 g->tag[i]->value = tmp;
2523 else if (strcmp(g->tag[i]->name, "Round") == 0) {
2524 if (g->tag[i]->value[0] == '\0') {
2525 if ((tmp = strdup("?")) == NULL)
2526 return E_PGN_ERR;
2528 free(g->tag[i]->value);
2529 g->tag[i]->value = tmp;
2532 else if (strcmp(g->tag[i]->name, "Result") == 0) {
2533 if (g->tag[i]->value[0] == '\0') {
2534 if ((tmp = strdup("*")) == NULL)
2535 return E_PGN_ERR;
2537 free(g->tag[i]->value);
2538 g->tag[i]->value = tmp;
2541 else if (strcmp(g->tag[i]->name, "Black") == 0) {
2542 if (g->tag[i]->value[0] == '\0') {
2543 if ((tmp = strdup("?")) == NULL)
2544 return E_PGN_ERR;
2546 free(g->tag[i]->value);
2547 g->tag[i]->value = tmp;
2550 else if (strcmp(g->tag[i]->name, "White") == 0) {
2551 if (g->tag[i]->value[0] == '\0') {
2552 if ((tmp = strdup("?")) == NULL)
2553 return E_PGN_ERR;
2555 free(g->tag[i]->value);
2556 g->tag[i]->value = tmp;
2560 escaped = pgn_tag_add_escapes(g->tag[i]->value);
2561 fprintf(pgn->fp, "[%s \"%s\"]\n", g->tag[i]->name,
2562 (g->tag[i]->value && g->tag[i]->value[0]) ? escaped : "");
2563 free (escaped);
2566 #ifdef DEBUG
2567 PGN_DUMP("%s:%d: writing move section\n", __FILE__, __LINE__);
2568 #endif
2569 Fputc('\n', pgn->fp, &len);
2570 g->hp = g->history;
2571 ravlevel = pgn_mpl = 0;
2573 if (pgn_history_total(g->hp) && pgn_write_turn == BLACK)
2574 putstring(pgn->fp, "1...", &len);
2576 write_all_move_text(pgn->fp, g->hp, 1, &len);
2578 Fputc(' ', pgn->fp, &len);
2579 putstring(pgn->fp, g->tag[6]->value, &len);
2580 putstring(pgn->fp, "\n\n", &len);
2582 if (!pgn_config.reduced)
2583 CLEAR_FLAG(g->flags, GF_PERROR);
2585 return E_PGN_OK;
2589 * Clears the enpassant flag for all positions on board 'b'. Returns nothing.
2591 void pgn_reset_enpassant(BOARD b)
2593 int r, c;
2595 #ifdef DEBUG
2596 PGN_DUMP("%s:%d: resetting enpassant\n", __FILE__, __LINE__);
2597 #endif
2599 for (r = 0; r < 8; r++) {
2600 for (c = 0; c < 8; c++)
2601 b[r][c].enpassant = 0;