No longer send SIGINT to the engine before each command.
[cboard.git] / libchess / pgn.c
blob1d6ff42eb908bb1ed64bac9ae7e1cccb7a7a3a31
1 /* vim:tw=78:ts=8:sw=4:set ft=c: */
2 /*
3 Copyright (C) 2002-2007 Ben Kibbey <bjk@luxsci.net>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <unistd.h>
23 #include <sys/types.h>
24 #include <sys/wait.h>
25 #include <sys/stat.h>
26 #include <pwd.h>
27 #include <string.h>
28 #include <time.h>
29 #include <ctype.h>
30 #include <errno.h>
31 #include <fcntl.h>
32 #include <stdarg.h>
33 #include <err.h>
35 #ifdef HAVE_CONFIG_H
36 #include <config.h>
37 #endif
39 #ifdef HAVE_LIMITS_H
40 #include <limits.h>
41 #endif
43 #include "chess.h"
44 #include "common.h"
45 #include "pgn.h"
47 #ifdef DEBUG
48 #include "debug.h"
49 #endif
51 #ifdef WITH_DMALLOC
52 #include <dmalloc.h>
53 #endif
55 static int Fgetc(FILE *fp)
57 register int c;
59 if ((c = fgetc(fp)) != EOF) {
60 if (pgn_config.progress && pgn_config.pfunc) {
61 if (!(ftell(fp) % pgn_config.progress))
62 (*pgn_config.pfunc)(pgn_fsize, ftell(fp));
66 return c;
69 static int Ungetc(int c, FILE *fp)
71 return ungetc(c, fp);
74 char *pgn_version()
76 return "libchess " PACKAGE_VERSION;
79 static char *trim(char *str)
81 int i = 0;
83 if (!str)
84 return NULL;
86 while (isspace(*str))
87 str++;
89 for (i = strlen(str) - 1; isspace(str[i]); i--)
90 str[i] = 0;
92 return str;
95 static char *itoa(long n)
97 static char buf[16];
99 snprintf(buf, sizeof(buf), "%li", n);
100 return buf;
104 * Clears the valid move flag for all positions on board 'b'. Returns nothing.
106 void pgn_reset_valid_moves(BOARD b)
108 int row, col;
110 #ifdef DEBUG
111 PGN_DUMP("%s:%d: resetting valid moves\n", __FILE__, __LINE__);
112 #endif
114 for (row = 0; row < 8; row++) {
115 for (col = 0; col < 8; col++)
116 b[row][col].valid = 0;
121 * Toggles g->turn. Returns nothing.
123 void pgn_switch_turn(GAME g)
125 g->turn = (g->turn == WHITE) ? BLACK : WHITE;
129 * Toggles g->side and switches the White and Black roster tags. Returns
130 * nothing.
132 void pgn_switch_side(GAME g)
134 char *w = g->tag[4]->value;
136 g->tag[4]->value = g->tag[5]->value;
137 g->tag[5]->value = w;
138 g->side = (g->side == WHITE) ? BLACK : WHITE;
142 * Creates a FEN tag from the current game 'g', history move (g->hindex) and
143 * board 'b'. Returns a FEN tag.
145 char *pgn_game_to_fen(GAME g, BOARD b)
147 int row, col;
148 int i;
149 static char buf[MAX_PGN_LINE_LEN], *p;
150 int oldturn = g->turn;
151 char enpassant[3] = {0}, *e;
152 int castle = 0;
154 #ifdef DEBUG
155 PGN_DUMP("%s:%d: creating FEN tag\n", __FILE__, __LINE__);
156 #endif
158 for (i = pgn_history_total(g->hp); i >= g->hindex - 1; i--)
159 pgn_switch_turn(g);
161 p = buf;
163 for (row = 0; row < 8; row++) {
164 int count = 0;
166 for (col = 0; col < 8; col++) {
167 if (b[row][col].enpassant) {
168 b[row][col].icon = pgn_int_to_piece(WHITE, OPEN_SQUARE);
169 e = enpassant;
170 *e++ = 'a' + col;
171 *e++ = ('0' + 8) - row;
172 *e = 0;
175 if (pgn_piece_to_int(b[row][col].icon) == OPEN_SQUARE) {
176 count++;
177 continue;
180 if (count) {
181 *p++ = '0' + count;
182 count = 0;
185 *p++ = b[row][col].icon;
186 *p = 0;
189 if (count) {
190 *p++ = '0' + count;
191 count = 0;
194 *p++ = '/';
197 --p;
198 *p++ = ' ';
199 *p++ = (g->turn == WHITE) ? 'w' : 'b';
200 *p++ = ' ';
202 if (TEST_FLAG(g->flags, GF_WK_CASTLE) && pgn_piece_to_int(b[7][7].icon) ==
203 ROOK && isupper(b[7][7].icon) && pgn_piece_to_int(b[7][4].icon) ==
204 KING && isupper(b[7][4].icon)) {
205 *p++ = 'K';
206 castle = 1;
209 if (TEST_FLAG(g->flags, GF_WQ_CASTLE) && pgn_piece_to_int(b[7][0].icon) ==
210 ROOK && isupper(b[7][0].icon) && pgn_piece_to_int(b[7][4].icon) ==
211 KING && isupper(b[7][4].icon)) {
212 *p++ = 'Q';
213 castle = 1;
216 if (TEST_FLAG(g->flags, GF_BK_CASTLE) && pgn_piece_to_int(b[0][7].icon) ==
217 ROOK && islower(b[0][7].icon) && pgn_piece_to_int(b[0][4].icon) ==
218 KING && islower(b[0][4].icon)) {
219 *p++ = 'k';
220 castle = 1;
223 if (TEST_FLAG(g->flags, GF_BQ_CASTLE) && pgn_piece_to_int(b[0][0].icon) ==
224 ROOK && islower(b[0][0].icon) && pgn_piece_to_int(b[0][4].icon) ==
225 KING && islower(b[0][4].icon)) {
226 *p++ = 'q';
227 castle = 1;
230 if (!castle)
231 *p++ = '-';
233 *p++ = ' ';
235 if (enpassant[0]) {
236 e = enpassant;
237 *p++ = *e++;
238 *p++ = *e++;
240 else
241 *p++ = '-';
243 *p++ = ' ';
245 // Halfmove clock.
246 *p = 0;
247 strcat(p, itoa(g->ply));
248 p = buf + strlen(buf);
249 *p++ = ' ';
251 // Fullmove number.
252 i = (g->hindex + 1) / 2;
253 *p = 0;
254 strcat(p, itoa((g->hindex / 2) + (g->hindex % 2)));
256 g->turn = oldturn;
257 return buf;
261 * Returns the total number of moves in 'h' or 0 if there are none.
263 int pgn_history_total(HISTORY **h)
265 int i;
267 if (!h)
268 return 0;
270 for (i = 0; h[i]; i++);
271 return i;
275 * Deallocates all of the history data from position 'start' in the array 'h'.
276 * Returns nothing.
278 void pgn_history_free(HISTORY **h, int start)
280 int i;
282 #ifdef DEBUG
283 PGN_DUMP("%s:%d: freeing history\n", __FILE__, __LINE__);
284 #endif
286 if (!h || start > pgn_history_total(h))
287 return;
289 if (start < 0)
290 start = 0;
292 for (i = start; h[i]; i++) {
293 if (h[i]->comment)
294 free(h[i]->comment);
296 if (h[i]->rav) {
297 pgn_history_free(h[i]->rav, 0);
298 free(h[i]->rav);
301 if (h[i]->move)
302 free(h[i]->move);
304 free(h[i]);
307 h[start] = NULL;
311 * Returns the history ply 'n' from 'h'. If 'n' is out of range then NULL is
312 * returned.
314 HISTORY *pgn_history_by_n(HISTORY **h, int n)
316 if (n < 0 || n > pgn_history_total(h) - 1)
317 return NULL;
319 return h[n];
323 * Appends move 'm' to game 'g' history pointer. The history pointer may be a
324 * in a RAV so g->rav.hp is also updated to the new (realloc()'ed) pointer. If
325 * not in a RAV then g->history will be updated. Returns E_PGN_ERR if
326 * realloc() failed or E_PGN_OK on success.
328 pgn_error_t pgn_history_add(GAME g, const char *m)
330 int t = pgn_history_total(g->hp);
331 int o;
332 HISTORY **h = NULL;
333 int ri = (g->ravlevel) ? g->rav[g->ravlevel - 1].hindex : 0;
335 #ifdef DEBUG
336 PGN_DUMP("%s:%d: adding '%s' to move history\n", __FILE__, __LINE__, m);
337 #endif
339 if (g->ravlevel)
340 o = g->rav[g->ravlevel - 1].hp[ri-1]->rav - g->hp;
341 else
342 o = g->history - g->hp;
344 if ((h = realloc(g->hp, (t + 2) * sizeof(HISTORY *))) == NULL)
345 return E_PGN_ERR;
347 g->hp = h;
349 if (g->ravlevel)
350 g->rav[g->ravlevel - 1].hp[ri-1]->rav = g->hp + o;
351 else
352 g->history = g->hp + o;
354 if ((g->hp[t] = calloc(1, sizeof(HISTORY))) == NULL)
355 return E_PGN_ERR;
357 if ((g->hp[t]->move = strdup(m)) == NULL) {
358 free(g->hp[t]);
359 g->hp[t] = NULL;
360 return E_PGN_ERR;
363 t++;
364 g->hp[t] = NULL;
365 g->hindex = pgn_history_total(g->hp);
366 return E_PGN_OK;
370 * Resets the game 'g' using board 'b' up to history move (g->hindex) 'n'.
371 * Returns E_PGN_OK on success or E_PGN_PARSE if there was a FEN tag but the
372 * parsing of it failed. Or returns E_PGN_INVALID if somehow the move failed
373 * validation while resetting.
375 pgn_error_t pgn_board_update(GAME g, BOARD b, int n)
377 int i = 0;
378 BOARD tb;
379 int ret = E_PGN_OK;
380 int p_error = TEST_FLAG(g->flags, GF_PERROR);
381 int black_opening = TEST_FLAG(g->flags, GF_BLACK_OPENING);
382 char *frfr;
384 #ifdef DEBUG
385 PGN_DUMP("%s:%d: updating board\n", __FILE__, __LINE__);
386 #endif
388 if (!g->ravlevel && TEST_FLAG(g->flags, GF_BLACK_OPENING))
389 g->turn = BLACK;
390 else
391 g->turn = WHITE;
393 g->flags = 0;
394 SET_FLAG(g->flags, GF_WK_CASTLE|GF_WQ_CASTLE|GF_WQ_CASTLE|
395 GF_BK_CASTLE|GF_BQ_CASTLE);
396 pgn_board_init(tb);
398 if (g->ravlevel)
399 pgn_board_init_fen(g, tb, g->rav[g->ravlevel - 1].fen);
400 else if (pgn_tag_find(g->tag, "FEN") != -1 &&
401 pgn_board_init_fen(g, tb, NULL))
402 return E_PGN_PARSE;
404 for (i = 0; i < n; i++) {
405 HISTORY *h;
406 char *p;
408 if ((h = pgn_history_by_n(g->hp, i)) == NULL)
409 break;
411 p = h->move;
413 if ((ret = pgn_parse_move(g, tb, &p, &frfr)) != E_PGN_OK)
414 break;
416 pgn_switch_turn(g);
419 if (ret == E_PGN_OK)
420 memcpy(b, tb, sizeof(BOARD));
422 if (p_error)
423 SET_FLAG(g->flags, GF_PERROR);
425 if (black_opening)
426 SET_FLAG(g->flags, GF_BLACK_OPENING);
428 return ret;
432 * Updates the game 'g' using board 'b' to the next 'n'th history move.
433 * Returns nothing.
435 void pgn_history_prev(GAME g, BOARD b, int n)
437 if (g->hindex - n < 0) {
438 if (n <= 2)
439 g->hindex = pgn_history_total(g->hp);
440 else
441 g->hindex = 0;
443 else
444 g->hindex -= n;
446 pgn_board_update(g, b, g->hindex);
450 * Updates the game 'g' using board 'b' to the previous 'n'th history move.
451 * Returns nothing.
453 void pgn_history_next(GAME g, BOARD b, int n)
455 if (g->hindex + n > pgn_history_total(g->hp)) {
456 if (n <= 2)
457 g->hindex = 0;
458 else
459 g->hindex = pgn_history_total(g->hp);
461 else
462 g->hindex += n;
464 pgn_board_update(g, b, g->hindex);
468 * Converts the character piece 'p' to an integer. Returns the integer
469 * associated with 'p' or E_PGN_ERR if 'p' is invalid.
471 int pgn_piece_to_int(int p)
473 if (p == '.')
474 return OPEN_SQUARE;
476 p = tolower(p);
478 switch (p) {
479 case 'p':
480 return PAWN;
481 case 'r':
482 return ROOK;
483 case 'n':
484 return KNIGHT;
485 case 'b':
486 return BISHOP;
487 case 'q':
488 return QUEEN;
489 case 'k':
490 return KING;
491 default:
492 break;
495 #ifdef DEBUG
496 PGN_DUMP("%s:%d: invalid piece '%c'\n", __FILE__, __LINE__, p);
497 #endif
498 return E_PGN_ERR;
502 * Converts the integer piece 'n' to a character whose turn is 'turn'. WHITE
503 * piece are uppercase and BLACK pieces are lowercase. Returns the character
504 * associated with 'n' or E_PGN_ERR if 'n' is an invalid piece.
506 pgn_error_t pgn_int_to_piece(char turn, int n)
508 int p = 0;
510 switch (n) {
511 case PAWN:
512 p = 'p';
513 break;
514 case ROOK:
515 p = 'r';
516 break;
517 case KNIGHT:
518 p = 'n';
519 break;
520 case BISHOP:
521 p = 'b';
522 break;
523 case QUEEN:
524 p = 'q';
525 break;
526 case KING:
527 p = 'k';
528 break;
529 case OPEN_SQUARE:
530 p = '.';
531 break;
532 default:
533 #ifdef DEBUG
534 PGN_DUMP("%s:%d: unknown piece integer %i\n", __FILE__,
535 __LINE__, n);
536 #endif
537 return E_PGN_ERR;
538 break;
541 return (turn == WHITE) ? toupper(p) : p;
545 * Finds a tag 'name' in the structure array 't'. Returns the location in the
546 * array of the found tag or E_PGN_ERR if the tag could not be found.
548 pgn_error_t pgn_tag_find(TAG **t, const char *name)
550 int i;
552 for (i = 0; t[i]; i++) {
553 if (strcasecmp(t[i]->name, name) == 0)
554 return i;
557 return E_PGN_ERR;
560 static pgn_error_t remove_tag(TAG ***array, const char *tag)
562 TAG **tags = *array;
563 int n = pgn_tag_find(tags, tag);
564 int i, t;
566 if (n == E_PGN_ERR)
567 return E_PGN_ERR;
569 for (i = t = 0; tags[i]; i++) {
570 if (i == n) {
571 free(tags[i]->name);
572 free(tags[i]->value);
573 free(tags[i]);
574 continue;
577 tags[t++] = tags[i];
580 tags = realloc(*array, (t + 1) * sizeof(TAG *));
581 tags[t] = NULL;
582 *array = tags;
583 #ifdef DEBUG
584 PGN_DUMP("%s:%d: removed tag: name='%s'\n", __FILE__, __LINE__, tag);
585 #endif
586 return E_PGN_OK;
589 static int tag_compare(const void *a, const void *b)
591 TAG * const *ta = a;
592 TAG * const *tb = b;
594 return strcmp((*ta)->name, (*tb)->name);
598 * Sorts a tag array. The first seven tags are in order of the PGN standard so
599 * don't sort'em. Returns nothing.
601 void pgn_tag_sort(TAG **tags)
603 if (pgn_tag_total(tags) <= 7)
604 return;
606 qsort(tags + 7, pgn_tag_total(tags) - 7, sizeof(TAG *), tag_compare);
610 * Returns the total number of tags in 't' or 0 if 't' is NULL.
612 int pgn_tag_total(TAG **tags)
614 int i = 0;
616 if (!tags)
617 return 0;
619 while (tags[i])
620 i++;
622 return i;
626 * Adds a tag 'name' with value 'value' to the pointer to array of TAG
627 * pointers 'dst'. If a duplicate tag 'name' was found then the existing tag
628 * is updated to the new 'value'. If 'value' is NULL, the tag is removed.
629 * Returns E_PGN_ERR if there was a memory allocation error or E_PGN_OK on
630 * success.
632 pgn_error_t pgn_tag_add(TAG ***dst, char *name, char *value)
634 int i;
635 TAG **tdata = *dst;
636 TAG **a = NULL;
637 int t = pgn_tag_total(tdata);
639 #ifdef DEBUG
640 PGN_DUMP("%s:%d: adding tag\n", __FILE__, __LINE__);
641 #endif
643 if (!name)
644 return E_PGN_ERR;
646 name = trim(name);
648 if (value)
649 value = trim(value);
651 // Find an existing tag with 'name'.
652 for (i = 0; i < t; i++) {
653 char *tmp = NULL;
655 if (strcasecmp(tdata[i]->name, name) == 0) {
656 if (value) {
657 if ((tmp = strdup(value)) == NULL)
658 return E_PGN_ERR;
660 else {
661 remove_tag(dst, name);
662 return E_PGN_OK;
665 free(tdata[i]->value);
666 tdata[i]->value = tmp;
667 *dst = tdata;
668 return E_PGN_OK;
672 if ((a = realloc(tdata, (t + 2) * sizeof(TAG *))) == NULL)
673 return E_PGN_ERR;
675 tdata = a;
677 if ((tdata[t] = malloc(sizeof(TAG))) == NULL)
678 return E_PGN_ERR;
680 if ((tdata[t]->name = strdup(name)) == NULL) {
681 tdata[t] = NULL;
682 return E_PGN_ERR;
685 if (value) {
686 if ((tdata[t]->value = strdup(value)) == NULL) {
687 free(tdata[t]->name);
688 tdata[t] = NULL;
689 return E_PGN_ERR;
692 else
693 tdata[t]->value = NULL;
695 tdata[t]->name[0] = toupper(tdata[t]->name[0]);
696 tdata[++t] = NULL;
697 *dst = tdata;
699 #ifdef DEBUG
700 PGN_DUMP("%s:%d: added tag: name='%s' value='%s'\n", __FILE__, __LINE__,
701 name, (value) ? value : "null");
702 #endif
704 return E_PGN_OK;
707 static char *remove_tag_escapes(const char *str)
709 int i, n;
710 int len = strlen(str);
711 static char buf[MAX_PGN_LINE_LEN] = {0};
713 for (i = n = 0; i < len; i++, n++) {
714 switch (str[i]) {
715 case '\\':
716 i++;
717 default:
718 break;
721 buf[n] = str[i];
724 buf[n] = '\0';
725 return buf;
729 * Resets or initializes a new game board 'b'. Returns nothing.
731 void pgn_board_init(BOARD b)
733 int row, col;
735 #ifdef DEBUG
736 PGN_DUMP("%s:%d: initializing board\n", __FILE__, __LINE__);
737 #endif
739 memset(b, 0, sizeof(BOARD));
741 for (row = 0; row < 8; row++) {
742 for (col = 0; col < 8; col++) {
743 int c = '.';
745 switch (row) {
746 case 0:
747 case 7:
748 switch (col) {
749 case 0:
750 case 7:
751 c = 'r';
752 break;
753 case 1:
754 case 6:
755 c = 'n';
756 break;
757 case 2:
758 case 5:
759 c = 'b';
760 break;
761 case 3:
762 c = 'q';
763 break;
764 case 4:
765 c = 'k';
766 break;
768 break;
769 case 1:
770 case 6:
771 c = 'p';
772 break;
775 b[row][col].icon = (row < 2) ? c : toupper(c);
781 * Adds the standard PGN roster tags to game 'g'.
783 static void set_default_tags(GAME g)
785 time_t now;
786 char tbuf[11] = {0};
787 struct tm *tp;
788 struct passwd *pw = getpwuid(getuid());
790 time(&now);
791 tp = localtime(&now);
792 strftime(tbuf, sizeof(tbuf), PGN_TIME_FORMAT, tp);
794 /* The standard seven tag roster (in order of appearance). */
795 if (pgn_tag_add(&g->tag, "Event", "?") != E_PGN_OK)
796 warn("pgn_tag_add()");
798 if (pgn_tag_add(&g->tag, "Site", "?") != E_PGN_OK)
799 warn("pgn_tag_add()");
801 if (pgn_tag_add(&g->tag, "Date", tbuf) != E_PGN_OK)
802 warn("pgn_tag_add()");
804 if (pgn_tag_add(&g->tag, "Round", "-") != E_PGN_OK)
805 warn("pgn_tag_add()");
807 if (pgn_tag_add(&g->tag, "White", pw->pw_gecos) != E_PGN_OK)
808 warn("pgn_tag_add()");
810 if (pgn_tag_add(&g->tag, "Black", "?") != E_PGN_OK)
811 warn("pgn_tag_add()");
813 if (pgn_tag_add(&g->tag, "Result", "*") != E_PGN_OK)
814 warn("pgn_tag_add()");
818 * Frees a TAG array. Returns nothing.
820 void pgn_tag_free(TAG **tags)
822 int i;
823 int t = pgn_tag_total(tags);
825 #ifdef DEBUG
826 PGN_DUMP("%s:%d: freeing tags\n", __FILE__, __LINE__);
827 #endif
829 if (!tags)
830 return;
832 for (i = 0; i < t; i++) {
833 free(tags[i]->name);
834 free(tags[i]->value);
835 free(tags[i]);
838 free(tags);
842 * Frees a single game 'g'. Returns nothing.
844 void pgn_free(GAME g)
846 #ifdef DEBUG
847 PGN_DUMP("%s:%d: freeing game\n", __FILE__, __LINE__);
848 #endif
849 pgn_history_free(g->history, 0);
850 free(g->history);
851 pgn_tag_free(g->tag);
853 if (g->rav) {
854 for (g->ravlevel--; g->ravlevel >= 0; g->ravlevel--)
855 free(g->rav[g->ravlevel].fen);
857 free(g->rav);
860 free(g);
864 * Frees all games in the global 'game' array. Returns nothing.
866 void pgn_free_all()
868 int i;
870 #ifdef DEBUG
871 PGN_DUMP("%s:%d: freeing game data\n", __FILE__, __LINE__);
872 #endif
874 for (i = 0; i < gtotal; i++) {
875 pgn_free(game[i]);
878 if (game)
879 free(game);
881 game = NULL;
884 static void reset_game_data()
886 #ifdef DEBUG
887 PGN_DUMP("%s:%d: resetting game data\n", __FILE__, __LINE__);
888 #endif
889 pgn_free_all();
890 gtotal = gindex = 0;
893 static void skip_leading_space(FILE *fp)
895 int c;
897 while ((c = Fgetc(fp)) != EOF && !feof(fp)) {
898 if (!isspace(c))
899 break;
902 Ungetc(c, fp);
906 * PGN move text section.
908 static int move_text(GAME g, FILE *fp)
910 char m[MAX_SAN_MOVE_LEN + 1] = {0}, *p;
911 int c;
912 int count;
913 char *frfr;
915 g->oflags = g->flags;
917 while((c = Fgetc(fp)) != EOF) {
918 if (isdigit(c) || isspace(c) || c == '.')
919 continue;
921 break;
924 Ungetc(c, fp);
926 if (fscanf(fp, " %[a-hPpRrNnBbQqKk1-9#+=Ox-]%n", m, &count) != 1)
927 return 1;
929 m[MAX_SAN_MOVE_LEN] = 0;
930 p = m;
932 if (pgn_parse_move(g, pgn_board, &p, &frfr)) {
933 pgn_switch_turn(g);
934 return 1;
937 #ifdef DEBUG
938 PGN_DUMP("%s\n%s", p, debug_board(pgn_board));
939 #endif
941 pgn_history_add(g, p);
942 pgn_switch_turn(g);
943 return 0;
947 * PGN nag text.
949 static void nag_text(GAME g, FILE *fp)
951 int c, i, t;
952 char nags[5], *n = nags;
953 int nag = 0;
955 while ((c = Fgetc(fp)) != EOF && !isspace(c)) {
956 if (c == '$') {
957 while ((c = Fgetc(fp)) != EOF && isdigit(c))
958 *n++ = c;
960 Ungetc(c, fp);
961 break;
964 if (c == '!') {
965 if ((c = Fgetc(fp)) == '!')
966 nag = 3;
967 else if (c == '?')
968 nag = 5;
969 else {
970 Ungetc(c, fp);
971 nag = 1;
974 break;
976 else if (c == '?') {
977 if ((c = Fgetc(fp)) == '?')
978 nag = 4;
979 else if (c == '!')
980 nag = 6;
981 else {
982 Ungetc(c, fp);
983 nag = 2;
986 break;
988 else if (c == '~')
989 nag = 13;
990 else if (c == '=') {
991 if ((c = Fgetc(fp)) == '+')
992 nag = 15;
993 else {
994 Ungetc(c, fp);
995 nag = 10;
998 break;
1000 else if (c == '+') {
1001 if ((t = Fgetc(fp)) == '=')
1002 nag = 14;
1003 else if (t == '-')
1004 nag = 18;
1005 else if (t == '/') {
1006 if ((i = Fgetc(fp)) == '-')
1007 nag = 16;
1008 else
1009 Ungetc(i, fp);
1011 break;
1013 else
1014 Ungetc(t, fp);
1016 break;
1018 else if (c == '-') {
1019 if ((t = Fgetc(fp)) == '+')
1020 nag = 18;
1021 else if (t == '/') {
1022 if ((i = Fgetc(fp)) == '+')
1023 nag = 17;
1024 else
1025 Ungetc(i, fp);
1027 break;
1029 else
1030 Ungetc(t, fp);
1032 break;
1036 *n = '\0';
1038 if (!nag)
1039 nag = (nags[0]) ? atoi(nags) : 0;
1041 if (!nag || nag < 0 || nag > 255)
1042 return;
1044 // FIXME -1 is because move_text() increments g->hindex. The NAG
1045 // annoatation isnt guaranteed to be after the move text in import format.
1046 for (i = 0; i < MAX_PGN_NAG; i++) {
1047 if (g->hp[g->hindex - 1]->nag[i])
1048 continue;
1050 g->hp[g->hindex - 1]->nag[i] = nag;
1051 break;
1054 skip_leading_space(fp);
1058 * PGN move annotation.
1060 static int annotation_text(GAME g, FILE *fp, int terminator)
1062 int c, lastchar = 0;
1063 int len = 0;
1064 int hindex = pgn_history_total(g->hp) - 1;
1065 char buf[MAX_PGN_LINE_LEN], *a = buf;
1067 skip_leading_space(fp);
1069 while ((c = Fgetc(fp)) != EOF && c != terminator) {
1070 if (c == '\n')
1071 c = ' ';
1073 if (isspace(c) && isspace(lastchar))
1074 continue;
1076 if (len + 1 == sizeof(buf))
1077 continue;
1079 *a++ = lastchar = c;
1080 len++;
1083 *a = '\0';
1085 if (hindex < 0)
1086 hindex = 0;
1089 * This annotation is before any move text or NAg-> Allocate a new move.
1091 if (!g->hp[hindex]) {
1092 if ((g->hp[hindex] = calloc(1, sizeof(HISTORY))) == NULL)
1093 return E_PGN_ERR;
1096 if ((g->hp[hindex]->comment = strdup(buf)) == NULL)
1097 return E_PGN_ERR;
1099 return E_PGN_OK;
1103 * PGN roster tag->
1105 static int tag_text(GAME g, FILE *fp)
1107 char name[LINE_MAX], *n = name;
1108 char value[LINE_MAX], *v = value;
1109 int c, i = 0;
1110 int quoted_string = 0;
1111 int lastchar = 0;
1113 skip_leading_space(fp);
1115 /* The tag name is up until the first whitespace. */
1116 while ((c = Fgetc(fp)) != EOF && !isspace(c))
1117 *n++ = c;
1119 *n = '\0';
1120 *name = toupper(*name);
1121 skip_leading_space(fp);
1123 /* The value is until the first closing bracket. */
1124 while ((c = Fgetc(fp)) != EOF && c != ']') {
1125 if (i++ == '\0' && c == '\"') {
1126 quoted_string = 1;
1127 continue;
1130 if (c == '\n' || c == '\t')
1131 c = ' ';
1133 if (c == ' ' && lastchar == ' ')
1134 continue;
1136 lastchar = *v++ = c;
1139 *v = '\0';
1141 while (isspace(*--v))
1142 *v = '\0';
1144 if (*v == '\"')
1145 *v = '\0';
1147 if (value[0] == '\0') {
1148 if (strcmp(name, "Result") == 0)
1149 value[0] = '*';
1150 else
1151 value[0] = '?';
1153 value[1] = '\0';
1156 strncpy(value, remove_tag_escapes(value), sizeof(value));
1159 * See eog_text() for an explanation.
1161 if (strcmp(name, "Result") == 0) {
1162 if (strcmp(value, "1/2-1/2") == 0) {
1163 if (pgn_tag_add(&g->tag, name, value) != E_PGN_OK)
1164 warn("pgn_tag_add()");
1167 else {
1168 if (pgn_tag_add(&g->tag, name, value) != E_PGN_OK)
1169 warn("pgn_tag_add()");
1172 return 0;
1176 * PGN end-of-game marker.
1178 static int eog_text(GAME g, FILE *fp)
1180 int c, i = 0;
1181 char buf[8], *p = buf;
1183 while ((c = Fgetc(fp)) != EOF && !isspace(c) && i++ < sizeof(buf))
1184 *p++ = c;
1186 if (isspace(c))
1187 Ungetc(c, fp);
1189 *p = 0;
1191 if (strcmp(buf, "1-0") != 0 && strcmp(buf, "0-1") != 0 &&
1192 strcmp(buf, "1/2-1/2") != 0 && strcmp(buf, "*") != 0)
1193 return 1;
1196 * The eog marker in the move text may not match the actual game result.
1197 * We'll leave it up to the move validator to determine the result unless
1198 * the move validator cannot determine who won and the move text says it's
1199 * a draw.
1201 if (g->tag[6]->value[0] == '*' && strcmp("1/2-1/2", buf) == 0) {
1202 if (pgn_tag_add(&g->tag, "Result", buf) != E_PGN_OK)
1203 warn("pgn_tag_add()");
1206 return 0;
1210 * Parse RAV text and keep track of g->hp. The 'o' argument is the board state
1211 * before the current move (.hindex) was parsed.
1213 static int read_file(FILE *);
1214 static int rav_text(GAME g, FILE *fp, int which, BOARD o)
1216 struct game_s tg;
1217 RAV *r;
1219 // Begin RAV for the current move.
1220 if (which == '(') {
1221 if (!g->hindex)
1222 return 1;
1224 pgn_rav++; // For detecting parse errors.
1227 * Save the current game state for this RAV depth/level.
1229 if ((r = realloc(g->rav, (g->ravlevel + 1) * sizeof(RAV))) == NULL) {
1230 warn("realloc()");
1231 return 1;
1234 g->rav = pgn_rav_p = r;
1236 if ((g->rav[g->ravlevel].fen = strdup(pgn_game_to_fen(g, pgn_board)))
1237 == NULL) {
1238 warn("strdup()");
1239 return 1;
1242 g->rav[g->ravlevel].hp = g->hp;
1243 g->rav[g->ravlevel].hindex = g->hindex;
1244 memcpy(&tg, &(*g), sizeof(struct game_s));
1245 g->flags = g->oflags;
1246 memcpy(pgn_board, o, sizeof(BOARD));
1248 if ((g->hp[g->hindex - 1]->rav = calloc(1, sizeof(HISTORY *))) == NULL) {
1249 warn("calloc()");
1250 return 1;
1254 * pgn_history_add() will now append to the new history pointer that
1255 * is g->hp[previous_move]->rav.
1257 g->hp = g->hp[g->hindex - 1]->rav;
1260 * Reset. Will be restored later from 'tg' which is a local variable
1261 * so recursion is possible.
1263 g->hindex = 0;
1264 g->ravlevel++;
1267 * Undo move_text()'s switch.
1269 pgn_switch_turn(g);
1272 * Now continue as normal as if there were no RAV. Moves will be
1273 * parsed and appended to the new history pointer.
1275 if (read_file(fp))
1276 return 1;
1279 * read_file() has returned. This means that a RAV has ended by this
1280 * function returning -1 (see below). So we restore the game state
1281 * (tg) that was saved before calling read_file().
1283 pgn_board_init_fen(&tg, pgn_board, g->rav[tg.ravlevel].fen);
1284 free(g->rav[tg.ravlevel].fen);
1285 memcpy(&(*g), &tg, sizeof(struct game_s));
1286 g->rav = pgn_rav_p;
1289 * The end of a RAV. This makes the read_file() that called this function
1290 * return (see above and the switch statement in read_file()).
1292 else if (which == ')') {
1293 pgn_rav--; // For detecting parse errors.
1294 return (pgn_rav < 0) ? 1 : -1;
1297 return 0;
1301 * FIXME
1302 * See pgn_board_init_fen(). Returns E_PGN_PARSE on parse error. 0 may be
1303 * returned on success when there is no move count in the FEN tag otherwise
1304 * the move count is returned.
1306 static int parse_fen_line(BOARD b, unsigned *flags, char *turn, char *ply,
1307 char *str)
1309 char *tmp;
1310 char line[LINE_MAX], *s;
1311 int row = 8, col = 1;
1312 int moven;
1314 #ifdef DEBUG
1315 PGN_DUMP("%s:%d: FEN line is '%s'\n", __FILE__, __LINE__, str);
1316 #endif
1317 strncpy(line, str, sizeof(line));
1318 s = line;
1319 pgn_reset_enpassant(b);
1321 while ((tmp = strsep(&s, "/")) != NULL) {
1322 int n;
1324 if (!VALIDFILE(row))
1325 return E_PGN_PARSE;
1327 while (*tmp) {
1328 if (*tmp == ' ')
1329 goto other;
1331 if (isdigit(*tmp)) {
1332 n = *tmp - '0';
1334 if (!VALIDFILE(n))
1335 return E_PGN_PARSE;
1337 for (; n; --n, col++)
1338 b[RANKTOBOARD(row)][FILETOBOARD(col)].icon =
1339 pgn_int_to_piece(WHITE, OPEN_SQUARE);
1341 else if (pgn_piece_to_int(*tmp) != -1)
1342 b[RANKTOBOARD(row)][FILETOBOARD(col++)].icon = *tmp;
1343 else
1344 return E_PGN_PARSE;
1346 tmp++;
1349 row--;
1350 col = 1;
1353 other:
1354 tmp++;
1356 switch (*tmp++) {
1357 case 'b':
1358 *turn = BLACK;
1359 break;
1360 case 'w':
1361 *turn = WHITE;
1362 break;
1363 default:
1364 return E_PGN_PARSE;
1367 tmp++;
1369 while (*tmp && *tmp != ' ') {
1370 switch (*tmp++) {
1371 case 'K':
1372 SET_FLAG(*flags, GF_WK_CASTLE);
1373 break;
1374 case 'Q':
1375 SET_FLAG(*flags, GF_WQ_CASTLE);
1376 break;
1377 case 'k':
1378 SET_FLAG(*flags, GF_BK_CASTLE);
1379 break;
1380 case 'q':
1381 SET_FLAG(*flags, GF_BQ_CASTLE);
1382 break;
1383 case '-':
1384 break;
1385 default:
1386 return E_PGN_PARSE;
1390 tmp++;
1392 // En passant.
1393 if (*tmp != '-') {
1394 if (!VALIDCOL(*tmp))
1395 return E_PGN_PARSE;
1397 col = *tmp++ - 'a';
1399 if (!VALIDROW(*tmp))
1400 return E_PGN_PARSE;
1402 row = 8 - atoi(tmp++);
1403 b[row][col].enpassant = 1;
1404 SET_FLAG(*flags, GF_ENPASSANT);
1406 else
1407 tmp++;
1409 if (*tmp)
1410 tmp++;
1412 if (*tmp)
1413 *ply = atoi(tmp);
1415 while (*tmp && isdigit(*tmp))
1416 tmp++;
1418 if (*tmp)
1419 tmp++;
1421 moven = atoi(tmp);
1422 return E_PGN_OK;
1426 * It initializes the board (b) to the FEN tag (if found) and sets the
1427 * castling and enpassant info for the game 'g'. If 'fen' is set it should be
1428 * a fen tag and will be parsed rather than the game 'g'.tag FEN tag-> Returns
1429 * E_PGN_OK on success or if there was both a FEN and SetUp tag with the SetUp
1430 * tag set to 0. Returns E_PGN_PARSE if there was a FEN parse error, E_PGN_ERR
1431 * if there was no FEN tag or there was a SetUp tag with a value of 0. Returns
1432 * E_PGN_OK on success.
1434 pgn_error_t pgn_board_init_fen(GAME g, BOARD b, char *fen)
1436 int n = -1, i = -1;
1437 BOARD tmpboard;
1438 unsigned flags = 0;
1439 char turn = g->turn;
1440 char ply = 0;
1442 #ifdef DEBUG
1443 PGN_DUMP("%s:%d: initializing board from FEN\n", __FILE__, __LINE__);
1444 #endif
1445 pgn_board_init(tmpboard);
1447 if (!fen) {
1448 n = pgn_tag_find(g->tag, "Setup");
1449 i = pgn_tag_find(g->tag, "FEN");
1453 * If the FEN tag exists and there is no SetUp tag go ahead and parse it.
1454 * If there is a SetUp tag only parse the FEN tag if the value is 1.
1456 if ((n >= 0 && i >= 0 && atoi(g->tag[n]->value) == 1)
1457 || (i >= 0 && n == -1) || fen) {
1458 if ((n = parse_fen_line(tmpboard, &flags, &turn, &ply,
1459 (fen) ? fen : g->tag[i]->value)) != E_PGN_OK)
1460 return E_PGN_PARSE;
1461 else {
1462 memcpy(b, tmpboard, sizeof(BOARD));
1463 CLEAR_FLAG(g->flags, GF_WK_CASTLE);
1464 CLEAR_FLAG(g->flags, GF_WQ_CASTLE);
1465 CLEAR_FLAG(g->flags, GF_BK_CASTLE);
1466 CLEAR_FLAG(g->flags, GF_BQ_CASTLE);
1467 g->flags |= flags;
1468 g->turn = turn;
1469 g->ply = ply;
1472 else
1473 return (i >= 0 && n >= 0) ? E_PGN_OK : E_PGN_ERR;
1475 return E_PGN_OK;
1479 * Allocates a new game and increments 'gtotal'. 'gindex' is then set to the
1480 * new game. Returns E_PGN_ERR if there was a memory allocation error or
1481 * E_PGN_OK on success.
1483 pgn_error_t pgn_new_game()
1485 GAME *g;
1486 GAME newg;
1487 int t = gtotal + 1;
1489 #ifdef DEBUG
1490 PGN_DUMP("%s:%d: allocating new game\n", __FILE__, __LINE__);
1491 #endif
1492 gindex = t - 1;
1494 if ((g = realloc(game, t * sizeof(GAME *))) == NULL) {
1495 warn("realloc()");
1496 return E_PGN_ERR;
1499 game = g;
1501 if ((newg = calloc(1, sizeof(struct game_s))) == NULL) {
1502 warn("calloc()");
1503 return E_PGN_ERR;
1506 game[gindex] = newg;
1508 if ((game[gindex]->hp = calloc(1, sizeof(HISTORY *))) == NULL) {
1509 free(game[gindex]);
1510 warn("calloc()");
1511 return E_PGN_ERR;
1514 game[gindex]->hp[0] = NULL;
1515 game[gindex]->history = game[gindex]->hp;
1516 game[gindex]->side = game[gindex]->turn = WHITE;
1517 SET_FLAG(game[gindex]->flags, GF_WK_CASTLE|GF_WQ_CASTLE|GF_WQ_CASTLE|
1518 GF_BK_CASTLE|GF_BQ_CASTLE);
1519 pgn_board_init(pgn_board);
1520 set_default_tags(game[gindex]);
1521 gtotal = t;
1522 return E_PGN_OK;
1525 static int read_file(FILE *fp)
1527 #ifdef DEBUG
1528 char buf[LINE_MAX] = {0}, *p = buf;
1529 #endif
1530 int c = 0;
1531 int parse_error = 0;
1532 BOARD old;
1534 while (1) {
1535 int nextchar = 0;
1536 int lastchar = c;
1537 int n;
1540 * A parse error may have occured at EOF.
1542 if (parse_error) {
1543 pgn_ret = E_PGN_PARSE;
1545 if (!game)
1546 pgn_new_game();
1548 SET_FLAG(game[gindex]->flags, GF_PERROR);
1551 if ((c = Fgetc(fp)) == EOF) {
1552 if (feof(fp))
1553 break;
1555 if (ferror(fp)) {
1556 clearerr(fp);
1557 continue;
1561 if (!isascii(c)) {
1562 parse_error = 1;
1563 continue;
1566 if (c == '\015')
1567 continue;
1569 nextchar = Fgetc(fp);
1570 Ungetc(nextchar, fp);
1573 * If there was a move text parsing error, keep reading until the end
1574 * of the current game discarding the data.
1576 if (parse_error) {
1577 pgn_ret = E_PGN_PARSE;
1579 if (game[gindex]->ravlevel)
1580 return 1;
1582 if (pgn_config.stop)
1583 return E_PGN_PARSE;
1585 if (c == '\n' && (nextchar == '\n' || nextchar == '\015')) {
1586 parse_error = 0;
1587 nulltags = 1;
1588 tag_section = 0;
1590 else
1591 continue;
1594 // New game reached.
1595 if (c == '\n' && (nextchar == '\n' || nextchar == '\015')) {
1596 if (tag_section)
1597 continue;
1599 nulltags = 1;
1600 tag_section = 0;
1601 continue;
1605 * PGN: Application comment. The '%' must be on the first column of
1606 * the line. The comment continues until the end of the current line.
1608 if (c == '%') {
1609 if (lastchar == '\n' || lastchar == 0) {
1610 while ((c = Fgetc(fp)) != EOF && c != '\n');
1611 continue;
1614 // Not sure what to do here.
1617 if (isspace(c))
1618 continue;
1620 // PGN: Reserved.
1621 if (c == '<' || c == '>')
1622 continue;
1625 * PGN: Recurrsive Annotated Variation. Read rav_text() for more
1626 * info.
1628 if (c == '(' || c == ')') {
1629 switch (rav_text(game[gindex], fp, c, old)) {
1630 case -1:
1632 * This is the end of the current RAV. This function has
1633 * been called from rav_text(). Returning from this point
1634 * will put us back in rav_text().
1636 if (game[gindex]->ravlevel > 0)
1637 return pgn_ret;
1640 * We're back at the root move. Continue as normal.
1642 break;
1643 case 1:
1644 parse_error = 1;
1645 continue;
1646 default:
1648 * Continue processing-> Probably the root move.
1650 break;
1653 if (!game[gindex]->ravlevel && pgn_rav)
1654 parse_error = 1;
1656 continue;
1659 // PGN: Numeric Annotation Glyph.
1660 if (c == '$' || c == '!' || c == '?' || c == '+' || c == '-' ||
1661 c == '~' || c == '=') {
1662 Ungetc(c, fp);
1663 nag_text(game[gindex], fp);
1664 continue;
1668 * PGN: Annotation. The ';' comment continues until the end of the
1669 * current line. The '{' type comment continues until a '}' is
1670 * reached.
1672 if (c == '{' || c == ';') {
1673 annotation_text(game[gindex], fp, (c == '{') ? '}' : '\n');
1674 continue;
1677 // PGN: Roster tag->
1678 if (c == '[') {
1679 // First roster tag found. Initialize the data structures.
1680 if (!tag_section) {
1681 nulltags = 0;
1682 tag_section = 1;
1684 if (gtotal && pgn_history_total(game[gindex]->hp))
1685 game[gindex]->hindex = pgn_history_total(game[gindex]->hp) - 1;
1687 if (pgn_new_game() != E_PGN_OK) {
1688 pgn_ret = E_PGN_ERR;
1689 break;
1692 memcpy(old, pgn_board, sizeof(BOARD));
1695 if (tag_text(game[gindex], fp))
1696 parse_error = 1; // FEN tag parse error.
1698 continue;
1701 // PGN: End-of-game markers.
1702 if ((isdigit(c) && (nextchar == '-' || nextchar == '/')) || c == '*') {
1703 Ungetc(c, fp);
1705 if (eog_text(game[gindex], fp)) {
1706 parse_error = 1;
1707 continue;
1710 nulltags = 1;
1711 tag_section = 0;
1713 if (!done_fen_tag) {
1714 if (pgn_tag_find(game[gindex]->tag, "FEN") != -1 &&
1715 pgn_board_init_fen(game[gindex], pgn_board, NULL)) {
1716 parse_error = 1;
1717 continue;
1720 pgn_fen_tag = pgn_tag_find(game[gindex]->tag, "FEN");
1721 done_fen_tag = 1;
1724 continue;
1727 // PGN: Move text.
1728 if ((isdigit(c) && c != '0') || VALIDCOL(c) || c == 'N' || c == 'K'
1729 || c == 'Q' || c == 'B' || c == 'R' || c == 'P' ||
1730 c == 'O') {
1731 Ungetc(c, fp);
1733 // PGN: If a FEN tag exists, initialize the board to the value.
1734 if (tag_section) {
1735 if (pgn_tag_find(game[gindex]->tag, "FEN") != E_PGN_ERR &&
1736 (n = pgn_board_init_fen(game[gindex], pgn_board,
1737 NULL)) == E_PGN_PARSE) {
1738 parse_error = 1;
1739 continue;
1742 done_fen_tag = 1;
1743 pgn_fen_tag = pgn_tag_find(game[gindex]->tag, "FEN");
1744 tag_section = 0;
1748 * PGN: Import format doesn't require a roster tag section. We've
1749 * arrived to the move text section without any tags so we
1750 * initialize a new game which set's the default tags and any tags
1751 * from the configuration file.
1753 if (nulltags) {
1754 if (gtotal)
1755 game[gindex]->hindex = pgn_history_total(game[gindex]->hp) - 1;
1757 if (pgn_new_game() != E_PGN_OK) {
1758 pgn_ret = E_PGN_ERR;
1759 break;
1762 memcpy(old, pgn_board, sizeof(BOARD));
1763 nulltags = 0;
1766 memcpy(old, pgn_board, sizeof(BOARD));
1768 if (move_text(game[gindex], fp)) {
1769 if (pgn_tag_add(&game[gindex]->tag, "Result", "*") ==
1770 E_PGN_ERR) {
1771 warn("pgn_tag_add()");
1772 pgn_ret = E_PGN_ERR;
1775 SET_FLAG(game[gindex]->flags, GF_PERROR);
1776 parse_error = 1;
1779 continue;
1782 #ifdef DEBUG
1783 *p++ = c;
1785 PGN_DUMP("%s:%d: unparsed: '%s'\n", __FILE__, __LINE__, buf);
1787 if (strlen(buf) + 1 == sizeof(buf))
1788 bzero(buf, sizeof(buf));
1789 #endif
1791 continue;
1794 return pgn_ret;
1798 * Parses a PGN_FILE which was opened with pgn_open(). If 'pgn' is NULL then a
1799 * single empty game will be allocated. If there is a parsing error
1800 * E_PGN_PARSE is returned, if there was a memory allocation error E_PGN_ERR
1801 * is returned, otherwise E_PGN_OK is returned and the global 'gindex' is set
1802 * to the last parsed game in the file and the global 'gtotal' is set to the
1803 * total number of games in the file. The file should be closed with
1804 * pgn_close() after processing.
1806 pgn_error_t pgn_parse(PGN_FILE *pgn)
1808 int i;
1810 if (!pgn) {
1811 reset_game_data();
1812 pgn_ret = pgn_new_game();
1813 goto done;
1816 reset_game_data();
1817 nulltags = 1;
1818 fseek(pgn->fp, 0, SEEK_END);
1819 pgn_fsize = ftell(pgn->fp);
1820 fseek(pgn->fp, 0, SEEK_SET);
1821 #ifdef DEBUG
1822 PGN_DUMP("%s:%d: BEGIN parsing->..\n", __FILE__, __LINE__);
1823 #endif
1824 pgn_ret = read_file(pgn->fp);
1826 #ifdef DEBUG
1827 PGN_DUMP("%s:%d: END parsing->..\n", __FILE__, __LINE__);
1828 #endif
1830 if (gtotal < 1)
1831 pgn_new_game();
1833 done:
1834 gtotal = gindex + 1;
1836 for (i = 0; i < gtotal; i++) {
1837 game[i]->history = game[i]->hp;
1838 game[i]->hindex = pgn_history_total(game[i]->hp);
1841 return pgn_ret;
1845 * Escape '"' and '\' in tag values.
1847 static char *pgn_tag_add_escapes(const char *str)
1849 int i, n;
1850 int len = strlen(str);
1851 static char buf[MAX_PGN_LINE_LEN] = {0};
1853 for (i = n = 0; i < len; i++, n++) {
1854 switch (str[i]) {
1855 case '\\':
1856 case '\"':
1857 buf[n++] = '\\';
1858 break;
1859 default:
1860 break;
1863 buf[n] = str[i];
1866 buf[n] = '\0';
1867 return buf;
1870 static void Fputc(int c, FILE *fp, int *len)
1872 int i = *len;
1874 if (c != '\n' && i + 1 > 80)
1875 Fputc('\n', fp, &i);
1877 if (pgn_lastc == '\n' && c == ' ') {
1878 *len = pgn_mpl = 0;
1879 return;
1882 if (fputc(c, fp) == EOF)
1883 warn("PGN Save");
1884 else {
1885 if (c == '\n')
1886 i = pgn_mpl = 0;
1887 else
1888 i++;
1891 *len = i;
1892 pgn_lastc = c;
1895 static void putstring(FILE *fp, char *str, int *len)
1897 char *p;
1899 for (p = str; *p; p++) {
1900 int n = 0;
1902 while (*p && *p != ' ')
1903 n++, p++;
1905 if (n + *len > 80)
1906 Fputc('\n', fp, len);
1908 p -= n;
1909 Fputc(*p, fp, len);
1914 * See pgn_write() for more info.
1916 static void write_comments_and_nag(FILE *fp, HISTORY *h, int *len)
1918 int i;
1920 #ifdef DEBUG
1921 PGN_DUMP("%s:%d: writing comments and nag\n", __FILE__, __LINE__);
1922 #endif
1924 for (i = 0; i < MAX_PGN_NAG; i++) {
1925 if (h->nag[i]) {
1926 Fputc(' ', fp, len);
1927 Fputc('$', fp, len);
1928 putstring(fp, itoa(h->nag[i]), len);
1932 if (h->comment) {
1933 Fputc('\n', fp, len);
1934 putstring(fp, " {", len);
1935 putstring(fp, h->comment, len);
1936 Fputc('}', fp, len);
1940 static void write_move_text(FILE *fp, HISTORY *h, int *len)
1942 Fputc(' ', fp, len);
1943 putstring(fp, h->move, len);
1945 if (!pgn_config.reduced)
1946 write_comments_and_nag(fp, h, len);
1949 static void write_all_move_text(FILE *fp, HISTORY **h, int m, int *len)
1951 int i;
1952 HISTORY **hp = NULL;
1954 for (i = 0; h[i]; i++) {
1955 if (pgn_write_turn == WHITE) {
1956 if (pgn_config.mpl && pgn_mpl == pgn_config.mpl) {
1957 pgn_mpl = 0;
1958 Fputc('\n', fp, len);
1961 if (m > 1 && i > 0)
1962 Fputc(' ', fp, len);
1964 if (strlen(itoa(m)) + 1 + *len > 80)
1965 Fputc('\n', fp, len);
1967 putstring(fp, itoa(m), len);
1968 Fputc('.', fp, len);
1969 pgn_mpl++;
1972 write_move_text(fp, h[i], len);
1974 if (!pgn_config.reduced && h[i]->rav) {
1975 int oldm = m;
1976 int oldturn = pgn_write_turn;
1978 ravlevel++;
1979 putstring(fp, " (", len);
1982 * If it's WHITE's turn the move number will be added above after
1983 * the call to write_all_move_text() below.
1985 if (pgn_write_turn == BLACK) {
1986 putstring(fp, itoa(m), len);
1987 putstring(fp, "...", len);
1990 hp = h[i]->rav;
1991 write_all_move_text(fp, hp, m, len);
1992 m = oldm;
1993 pgn_write_turn = oldturn;
1994 putstring(fp, ")", len);
1995 ravlevel--;
1997 if (ravlevel && h[i + 1])
1998 Fputc(' ', fp, len);
2000 if (h[i + 1] && !ravlevel)
2001 Fputc(' ', fp, len);
2003 if (pgn_write_turn == WHITE && h[i + 1]) {
2004 putstring(fp, itoa(m), len);
2005 putstring(fp, "...", len);
2009 if (pgn_write_turn == BLACK)
2010 m++;
2012 pgn_write_turn = (pgn_write_turn == WHITE) ? BLACK : WHITE;
2016 static char *compression_cmd(const char *filename, int expand)
2018 static char command[FILENAME_MAX];
2019 int len = strlen(filename);
2021 if (filename[len - 4] == '.' && filename[len - 3] == 'z' &&
2022 filename[len - 2] == 'i' && filename[len - 1] == 'p' &&
2023 filename[len] == '\0') {
2024 if (expand)
2025 snprintf(command, sizeof(command), "unzip -p %s 2>/dev/null",
2026 filename);
2027 else
2028 snprintf(command, sizeof(command), "zip -9 >%s 2>/dev/null",
2029 filename);
2031 return command;
2033 else if (filename[len - 3] == '.' && filename[len - 2] == 'g' &&
2034 filename[len - 1] == 'z' && filename[len] == '\0') {
2035 if (expand)
2036 snprintf(command, sizeof(command), "gzip -dc %s", filename);
2037 else
2038 snprintf(command, sizeof(command), "gzip -c 1>%s", filename);
2040 return command;
2042 else if (filename[len - 2] == '.' && filename[len - 1] == 'Z' &&
2043 filename[len] == '\0') {
2044 if (expand)
2045 snprintf(command, sizeof(command), "uncompress -c %s", filename);
2046 else
2047 snprintf(command, sizeof(command), "compress -c 1>%s", filename);
2049 return command;
2051 else if ((filename[len - 4] == '.' && filename[len - 3] == 'b' &&
2052 filename[len - 2] == 'z' && filename[len - 1] == '2' &&
2053 filename[len] == '\0') || (filename[len - 3] == '.' &&
2054 filename[len - 2] == 'b' && filename[len - 1] == 'z' &&
2055 filename[len] == '\0')) {
2056 if (expand)
2057 snprintf(command, sizeof(command), "bzip2 -dc %s", filename);
2058 else
2059 snprintf(command, sizeof(command), "bzip2 -zc 1>%s", filename);
2061 return command;
2064 return NULL;
2067 static int copy_file(FILE *fp, const char *dst)
2069 FILE *ofp;
2070 char line[LINE_MAX];
2071 char *cmd = compression_cmd(dst, 0);
2073 if ((ofp = popen(cmd, "w")) == NULL)
2074 return 1;
2076 fseek(fp, 0, SEEK_SET);
2078 while ((fgets(line, sizeof(line), fp)) != NULL)
2079 fprintf(ofp, "%s", line);
2081 pclose(ofp);
2082 return 0;
2086 * Closes and free's a PGN file handle.
2088 pgn_error_t pgn_close(PGN_FILE *pgn)
2090 if (!pgn)
2091 return E_PGN_INVALID;
2093 if (pgn->pipe) {
2095 * Appending to a compressed file.
2097 if (pgn->tmpfile) {
2098 if (copy_file(pgn->fp, pgn->filename))
2099 return E_PGN_ERR;
2101 fclose(pgn->fp);
2102 unlink(pgn->tmpfile);
2103 free(pgn->tmpfile);
2105 else
2106 pclose(pgn->fp);
2108 else
2109 fclose(pgn->fp);
2111 free(pgn->filename);
2112 free(pgn);
2113 return E_PGN_OK;
2117 * Opens a file 'filename' with the given 'mode'. 'mode' should be "r" for
2118 * reading, "w" for writing (will truncate if the file exists) or "a" for
2119 * appending to an existing file or creating a new one. Returns E_PGN_OK on
2120 * success and sets 'result' to a file handle for use will the other file
2121 * functions or E_PGN_ERR if there is an error opening the file in which case
2122 * errno will be set to the error or E_PGN_INVALID if 'mode' is an invalid
2123 * mode or if 'filename' is not a regular file.
2125 pgn_error_t pgn_open(const char *filename, const char *mode, PGN_FILE **result)
2127 FILE *fp = NULL, *tfp = NULL;
2128 char buf[PATH_MAX], *p;
2129 char *cmd = NULL;
2130 PGN_FILE *pgn;
2131 int m;
2132 int append = 0;
2133 struct stat st;
2134 int ret = E_PGN_ERR;
2137 #ifdef DEBUG
2138 PGN_DUMP("%s:%d: BEGIN opening %s\n", __FILE__, __LINE__, filename);
2139 #endif
2141 if (!filename || !mode)
2142 return E_PGN_INVALID;
2144 if (strcmp(mode, "r") == 0)
2145 m = 1;
2146 else if (strcmp(mode, "w") == 0)
2147 m = 0;
2148 else if (strcmp(mode, "a") == 0) {
2149 m = 0;
2150 append = 1;
2152 else {
2153 return E_PGN_INVALID;
2156 pgn = calloc(1, sizeof(PGN_FILE));
2158 if (!pgn)
2159 goto fail;
2161 if (strcmp(filename, "-") != 0) {
2162 if (m && access(filename, R_OK) == -1)
2163 goto fail;
2165 if (stat(filename, &st) == -1 && !m && errno != ENOENT)
2166 goto fail;
2168 if (m && !S_ISREG(st.st_mode)) {
2169 ret = E_PGN_INVALID;
2170 goto fail;
2173 if ((cmd = compression_cmd(filename, m)) != NULL) {
2174 pgn->pipe = 1;
2176 if (append && access(filename, R_OK) == 0) {
2177 char tmp[21];
2178 int fd;
2180 cmd = compression_cmd(filename, 1);
2182 if ((fp = popen(cmd, "r")) == NULL)
2183 goto fail;
2185 if (tmpnam(tmp) == NULL)
2186 goto fail;
2188 if ((fd = open(tmp, O_RDWR|O_EXCL|O_CREAT)) == -1)
2189 goto fail;
2191 if ((tfp = fdopen(fd, "a+")) == NULL)
2192 goto fail;
2194 while ((p = fgets(buf, sizeof(buf), fp)) != NULL)
2195 fprintf(tfp, "%s", p);
2197 pclose(fp);
2198 pgn->fp = tfp;
2199 pgn->tmpfile = strdup(tmp);
2200 goto done;
2203 if ((fp = popen(cmd, m ? "r" : "w")) == NULL)
2204 goto fail;
2206 if (m) {
2207 if ((tfp = tmpfile()) == NULL)
2208 goto fail;
2210 while ((p = fgets(buf, sizeof(buf), fp)) != NULL)
2211 fprintf(tfp, "%s", p);
2213 pclose(fp);
2214 pgn->fp = tfp;
2216 else
2217 pgn->fp = fp;
2219 else {
2220 if ((fp = fopen(filename, mode)) == NULL)
2221 goto fail;
2223 pgn->fp = fp;
2226 else
2227 pgn->fp = stdout;
2229 done:
2230 if (*filename != '/') {
2231 if (getcwd(buf, sizeof(buf)) == NULL) {
2232 if (pgn->tmpfile)
2233 free(pgn->tmpfile);
2235 goto fail;
2238 asprintf(&p, "%s/%s", buf, filename);
2239 pgn->filename = p;
2241 else
2242 pgn->filename = strdup(filename);
2244 *result = pgn;
2245 return E_PGN_OK;
2247 fail:
2248 if (fp)
2249 fclose(fp);
2251 free(pgn);
2252 return ret;
2256 * Returns E_PGN_OK if 'filename' is a recognized compressed filetype or
2257 * E_PGN_ERR if not.
2259 pgn_error_t pgn_is_compressed(const char *filename)
2261 if (compression_cmd(filename, 0))
2262 return E_PGN_OK;
2264 return E_PGN_ERR;
2268 * Gets the value of config flag 'f'. The next argument should be a pointer of
2269 * the config type which is set to the value of 'f'. Returns E_PGN_ERR if 'f'
2270 * is an invalid flag or E_PGN_OK on success.
2272 pgn_error_t pgn_config_get(pgn_config_flag f, ...)
2274 va_list ap;
2275 int *intval;
2276 long *longval;
2277 pgn_progress *progress;
2279 va_start(ap, f);
2281 switch (f) {
2282 case PGN_STRICT_CASTLING:
2283 intval = va_arg(ap, int *);
2284 *intval = pgn_config.strict_castling;
2285 va_end(ap);
2286 return E_PGN_OK;
2287 case PGN_REDUCED:
2288 intval = va_arg(ap, int *);
2289 *intval = pgn_config.reduced;
2290 va_end(ap);
2291 return E_PGN_OK;
2292 case PGN_MPL:
2293 intval = va_arg(ap, int *);
2294 *intval = pgn_config.mpl;
2295 va_end(ap);
2296 return E_PGN_OK;
2297 case PGN_STOP_ON_ERROR:
2298 intval = va_arg(ap, int *);
2299 *intval = pgn_config.stop;
2300 va_end(ap);
2301 return E_PGN_OK;
2302 case PGN_PROGRESS:
2303 longval = va_arg(ap, long *);
2304 *longval = pgn_config.stop;
2305 va_end(ap);
2306 return E_PGN_OK;
2307 case PGN_PROGRESS_FUNC:
2308 progress = va_arg(ap, pgn_progress *);
2309 progress = pgn_config.pfunc;
2310 va_end(ap);
2311 return E_PGN_OK;
2312 #ifdef DEBUG
2313 case PGN_DEBUG:
2314 intval = va_arg(ap, int *);
2315 *intval = dumptofile;
2316 va_end(ap);
2317 return E_PGN_OK;
2318 #endif
2319 default:
2320 break;
2323 return E_PGN_ERR;
2327 * Sets config flag 'f' to the next argument. Returns E_PGN_OK on success or
2328 * E_PGN_ERR if 'f' is an invalid flag or E_PGN_INVALID if 'val' is an invalid
2329 * flag value.
2331 pgn_error_t pgn_config_set(pgn_config_flag f, ...)
2333 va_list ap;
2334 int n;
2335 int ret = E_PGN_OK;
2337 va_start(ap, f);
2339 switch (f) {
2340 case PGN_REDUCED:
2341 n = va_arg(ap, int);
2343 if (n != 1 && n != 0) {
2344 ret = E_PGN_INVALID;
2345 break;
2348 pgn_config.reduced = n;
2349 break;
2350 case PGN_MPL:
2351 n = va_arg(ap, int);
2353 if (n < 0) {
2354 ret = E_PGN_INVALID;
2355 break;
2358 pgn_config.mpl = n;
2359 break;
2360 case PGN_STOP_ON_ERROR:
2361 n = va_arg(ap, int);
2363 if (n != 1 && n != 0) {
2364 ret = E_PGN_INVALID;
2365 break;
2368 pgn_config.stop = n;
2369 break;
2370 case PGN_PROGRESS:
2371 n = va_arg(ap, long);
2372 pgn_config.progress = n;
2373 break;
2374 case PGN_PROGRESS_FUNC:
2375 pgn_config.pfunc = va_arg(ap, pgn_progress *);
2376 break;
2377 case PGN_STRICT_CASTLING:
2378 n = va_arg(ap, int);
2379 pgn_config.strict_castling = n;
2380 break;
2381 #ifdef DEBUG
2382 case PGN_DEBUG:
2383 n = va_arg(ap, int);
2384 dumptofile = (n > 0) ? 1 : 0;
2385 break;
2386 #endif
2387 default:
2388 ret = E_PGN_ERR;
2389 break;
2392 va_end(ap);
2393 return ret;
2397 * Writes a PGN formatted game 'g' to a file which was opened with pgn_open().
2398 * See 'pgn_config_flag' for output options. Returns E_PGN_ERR if there was a
2399 * memory allocation or write error and E_PGN_OK on success. It is important
2400 * to use pgn_close() afterwards if the file is a recognized compressed file
2401 * type otherwise the created temporary file wont be copied to the destination
2402 * filename.
2404 pgn_error_t pgn_write(PGN_FILE *pgn, GAME g)
2406 int i;
2407 int len = 0;
2409 if (!pgn)
2410 return E_PGN_ERR;
2412 pgn_write_turn = (TEST_FLAG(g->flags, GF_BLACK_OPENING)) ? BLACK : WHITE;
2413 pgn_tag_sort(g->tag);
2415 #ifdef DEBUG
2416 PGN_DUMP("%s:%d: writing tag section\n", __FILE__, __LINE__);
2417 #endif
2419 for (i = 0; g->tag[i]; i++) {
2420 struct tm tp;
2421 char tbuf[11] = {0};
2422 char *tmp;
2424 if (pgn_config.reduced && i == 7)
2425 break;
2427 if (strcmp(g->tag[i]->name, "Date") == 0) {
2428 memset(&tp, 0, sizeof(struct tm));
2430 if (strptime(g->tag[i]->value, PGN_TIME_FORMAT, &tp) != NULL) {
2431 len = strftime(tbuf, sizeof(tbuf), PGN_TIME_FORMAT, &tp) + 1;
2433 if ((tmp = strdup(tbuf)) == NULL)
2434 return E_PGN_ERR;
2436 free(g->tag[i]->value);
2437 g->tag[i]->value = tmp;
2440 else if (strcmp(g->tag[i]->name, "Event") == 0) {
2441 if (g->tag[i]->value[0] == '\0') {
2442 if ((tmp = strdup("?")) == NULL)
2443 return E_PGN_ERR;
2445 free(g->tag[i]->value);
2446 g->tag[i]->value = tmp;
2449 else if (strcmp(g->tag[i]->name, "Site") == 0) {
2450 if (g->tag[i]->value[0] == '\0') {
2451 if ((tmp = strdup("?")) == NULL)
2452 return E_PGN_ERR;
2454 free(g->tag[i]->value);
2455 g->tag[i]->value = tmp;
2458 else if (strcmp(g->tag[i]->name, "Round") == 0) {
2459 if (g->tag[i]->value[0] == '\0') {
2460 if ((tmp = strdup("?")) == NULL)
2461 return E_PGN_ERR;
2463 free(g->tag[i]->value);
2464 g->tag[i]->value = tmp;
2467 else if (strcmp(g->tag[i]->name, "Result") == 0) {
2468 if (g->tag[i]->value[0] == '\0') {
2469 if ((tmp = strdup("*")) == NULL)
2470 return E_PGN_ERR;
2472 free(g->tag[i]->value);
2473 g->tag[i]->value = tmp;
2476 else if (strcmp(g->tag[i]->name, "Black") == 0) {
2477 if (g->tag[i]->value[0] == '\0') {
2478 if ((tmp = strdup("?")) == NULL)
2479 return E_PGN_ERR;
2481 free(g->tag[i]->value);
2482 g->tag[i]->value = tmp;
2485 else if (strcmp(g->tag[i]->name, "White") == 0) {
2486 if (g->tag[i]->value[0] == '\0') {
2487 if ((tmp = strdup("?")) == NULL)
2488 return E_PGN_ERR;
2490 free(g->tag[i]->value);
2491 g->tag[i]->value = tmp;
2495 fprintf(pgn->fp, "[%s \"%s\"]\n", g->tag[i]->name,
2496 (g->tag[i]->value && g->tag[i]->value[0]) ?
2497 pgn_tag_add_escapes(g->tag[i]->value) : "");
2500 #ifdef DEBUG
2501 PGN_DUMP("%s:%d: writing move section\n", __FILE__, __LINE__);
2502 #endif
2503 Fputc('\n', pgn->fp, &len);
2504 g->hp = g->history;
2505 ravlevel = pgn_mpl = 0;
2507 if (pgn_history_total(g->hp) && pgn_write_turn == BLACK)
2508 putstring(pgn->fp, "1...", &len);
2510 write_all_move_text(pgn->fp, g->hp, 1, &len);
2512 Fputc(' ', pgn->fp, &len);
2513 putstring(pgn->fp, g->tag[6]->value, &len);
2514 putstring(pgn->fp, "\n\n", &len);
2516 if (!pgn_config.reduced)
2517 CLEAR_FLAG(g->flags, GF_PERROR);
2519 return E_PGN_OK;
2523 * Clears the enpassant flag for all positions on board 'b'. Returns nothing.
2525 void pgn_reset_enpassant(BOARD b)
2527 int r, c;
2529 #ifdef DEBUG
2530 PGN_DUMP("%s:%d: resetting enpassant\n", __FILE__, __LINE__);
2531 #endif
2533 for (r = 0; r < 8; r++) {
2534 for (c = 0; c < 8; c++)
2535 b[r][c].enpassant = 0;