Enpassant and FEN tag fix.
[cboard.git] / libchess / pgn.c
blobab3971005a1a702e17c40840a1b0d49d7006f7d5
1 /* vim:tw=78:ts=8:sw=4:set ft=c: */
2 /*
3 Copyright (C) 2002-2006 Ben Kibbey <bjk@luxsci.net>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <unistd.h>
23 #include <sys/types.h>
24 #include <sys/wait.h>
25 #include <sys/stat.h>
26 #include <pwd.h>
27 #include <err.h>
28 #include <string.h>
29 #include <time.h>
30 #include <ctype.h>
31 #include <errno.h>
32 #include <fcntl.h>
34 #ifdef HAVE_CONFIG_H
35 #include <config.h>
36 #endif
38 #ifdef HAVE_LIMITS_H
39 #include <limits.h>
40 #endif
42 #include "chess.h"
43 #include "pgn.h"
45 #ifdef DEBUG
46 #include "debug.h"
47 #endif
49 #ifdef WITH_DMALLOC
50 #include <dmalloc.h>
51 #endif
53 char *pgn_version()
55 return "libchess " PACKAGE_VERSION;
58 static char *trim(char *str)
60 int i = 0;
62 if (!str)
63 return NULL;
65 while (isspace(*str))
66 str++;
68 for (i = strlen(str) - 1; isspace(str[i]); i--)
69 str[i] = 0;
71 return str;
74 static char *itoa(long n)
76 static char buf[16];
78 snprintf(buf, sizeof(buf), "%li", n);
79 return buf;
83 * Clears the valid move flag for all positions on board 'b'. Returns nothing.
85 void pgn_reset_valid_moves(BOARD b)
87 int row, col;
89 for (row = 0; row < 8; row++) {
90 for (col = 0; col < 8; col++)
91 b[row][col].valid = 0;
96 * Toggles g->turn. Returns nothing.
98 void pgn_switch_turn(GAME *g)
100 g->turn = (g->turn == WHITE) ? BLACK : WHITE;
104 * Toggles g->side and switches the White and Black roster tags. Returns
105 * nothing.
107 void pgn_switch_side(GAME *g)
109 int i = pgn_tag_find(g->tag, "White");
110 int n = pgn_tag_find(g->tag, "Black");
111 char *w = g->tag[i]->value;
113 g->tag[i]->value = g->tag[n]->value;
114 g->tag[n]->value = w;
115 g->side = (g->side == WHITE) ? BLACK : WHITE;
119 * Creates a FEN tag from the current game 'g', history move (g.hindex) and
120 * board 'b'. Returns a FEN tag.
122 char *pgn_game_to_fen(GAME g, BOARD b)
124 int row, col;
125 int i;
126 static char buf[MAX_PGN_LINE_LEN], *p;
127 int oldturn = g.turn;
128 char enpassant[3] = {0}, *e;
129 int castle = 0;
131 for (i = pgn_history_total(g.hp); i >= g.hindex - 1; i--)
132 pgn_switch_turn(&g);
134 p = buf;
136 for (row = 0; row < 8; row++) {
137 int count = 0;
139 for (col = 0; col < 8; col++) {
140 if (b[row][col].enpassant) {
141 b[row][col].icon = pgn_int_to_piece(WHITE, OPEN_SQUARE);
142 e = enpassant;
143 *e++ = 'a' + col;
144 *e++ = ('0' + 8) - row;
145 *e = 0;
148 if (pgn_piece_to_int(b[row][col].icon) == OPEN_SQUARE) {
149 count++;
150 continue;
153 if (count) {
154 *p++ = '0' + count;
155 count = 0;
158 *p++ = b[row][col].icon;
159 *p = 0;
162 if (count) {
163 *p++ = '0' + count;
164 count = 0;
167 *p++ = '/';
170 --p;
171 *p++ = ' ';
172 *p++ = (g.turn == WHITE) ? 'w' : 'b';
173 *p++ = ' ';
175 if (TEST_FLAG(g.flags, GF_WK_CASTLE) && pgn_piece_to_int(b[7][7].icon) ==
176 ROOK && isupper(b[7][7].icon) && pgn_piece_to_int(b[7][4].icon) ==
177 KING && isupper(b[7][4].icon)) {
178 *p++ = 'K';
179 castle = 1;
182 if (TEST_FLAG(g.flags, GF_WQ_CASTLE) && pgn_piece_to_int(b[7][0].icon) ==
183 ROOK && isupper(b[7][0].icon) && pgn_piece_to_int(b[7][4].icon) ==
184 KING && isupper(b[7][4].icon)) {
185 *p++ = 'Q';
186 castle = 1;
189 if (TEST_FLAG(g.flags, GF_BK_CASTLE) && pgn_piece_to_int(b[0][7].icon) ==
190 ROOK && islower(b[0][7].icon) && pgn_piece_to_int(b[0][4].icon) ==
191 KING && islower(b[0][4].icon)) {
192 *p++ = 'k';
193 castle = 1;
196 if (TEST_FLAG(g.flags, GF_BQ_CASTLE) && pgn_piece_to_int(b[0][0].icon) ==
197 ROOK && islower(b[0][0].icon) && pgn_piece_to_int(b[0][4].icon) ==
198 KING && islower(b[0][4].icon)) {
199 *p++ = 'q';
200 castle = 1;
203 if (!castle)
204 *p++ = '-';
206 *p++ = ' ';
208 if (enpassant[0]) {
209 e = enpassant;
210 *p++ = *e++;
211 *p++ = *e++;
213 else
214 *p++ = '-';
216 *p++ = ' ';
218 // Halfmove clock.
219 *p = 0;
220 strcat(p, itoa(g.ply));
221 p = buf + strlen(buf);
222 *p++ = ' ';
224 // Fullmove number.
225 i = (g.hindex + 1) / 2;
226 *p = 0;
227 strcat(p, itoa((g.hindex / 2) + (g.hindex % 2)));
229 g.turn = oldturn;
230 return buf;
234 * Returns the total number of moves in 'h' or 0 if there are none.
236 int pgn_history_total(HISTORY **h)
238 int i;
240 if (!h)
241 return 0;
243 for (i = 0; h[i]; i++);
244 return i;
248 * Deallocates all of the history data from position 'start' in the array 'h'.
249 * Returns nothing.
251 void pgn_history_free(HISTORY **h, int start)
253 int i;
255 if (!h || start > pgn_history_total(h))
256 return;
258 if (start < 0)
259 start = 0;
261 for (i = start; h[i]; i++) {
262 if (h[i]->comment)
263 free(h[i]->comment);
265 if (h[i]->rav) {
266 pgn_history_free(h[i]->rav, 0);
267 free(h[i]->rav);
270 if (h[i]->move)
271 free(h[i]->move);
273 free(h[i]);
276 h[start] = NULL;
280 * Returns the history ply 'n' from 'h'. If 'n' is out of range then NULL is
281 * returned.
283 HISTORY *pgn_history_by_n(HISTORY **h, int n)
285 if (n < 0 || n > pgn_history_total(h) - 1)
286 return NULL;
288 return h[n];
292 * Appends move 'm' to game 'g' history pointer. The history pointer may be a
293 * in a RAV so g->rav.hp is also updated to the new (realloc()'ed) pointer. If
294 * not in a RAV then g->history will be updated. Returns E_PGN_ERR if
295 * realloc() failed or E_PGN_OK on success.
297 int pgn_history_add(GAME *g, const char *m)
299 int t = pgn_history_total(g->hp);
300 int o;
301 HISTORY **h = NULL;
303 if (g->ravlevel)
304 o = g->rav[g->ravlevel].hp - g->hp;
305 else
306 o = g->history - g->hp;
308 if ((h = realloc(g->hp, (t + 2) * sizeof(HISTORY *))) == NULL)
309 return E_PGN_ERR;
311 g->hp = h;
313 if (g->ravlevel)
314 g->rav[g->ravlevel].hp = g->hp + o;
315 else
316 g->history = g->hp + o;
318 if ((g->hp[t] = calloc(1, sizeof(HISTORY))) == NULL)
319 return E_PGN_ERR;
321 if ((g->hp[t]->move = strdup(m)) == NULL) {
322 g->hp[t] = NULL;
323 return E_PGN_ERR;
326 t++;
327 g->hp[t] = NULL;
328 g->hindex = pgn_history_total(g->hp);
329 return E_PGN_OK;
333 * Resets the game 'g' using board 'b' up to history move (g.hindex) 'n'.
334 * Returns E_PGN_OK on success or E_PGN_PARSE if there was a FEN tag but the
335 * parsing of it failed. Or returns E_PGN_INVALID if somehow the move failed
336 * validation while resetting.
338 int pgn_board_update(GAME *g, BOARD b, int n)
340 int i = 0;
341 BOARD tb;
342 int ret = E_PGN_OK;
344 if (TEST_FLAG(g->flags, GF_BLACK_OPENING))
345 g->turn = BLACK;
346 else
347 g->turn = WHITE;
349 #if 0
350 if (TEST_FLAG(g->flags, GF_PERROR))
351 SET_FLAG(flags, GF_PERROR);
353 if (TEST_FLAG(g->flags, GF_MODIFIED))
354 SET_FLAG(flags, GF_MODIFIED);
356 if (TEST_FLAG(g->flags, GF_DELETE))
357 SET_FLAG(flags, GF_DELETE);
359 if (TEST_FLAG(g->flags, GF_GAMEOVER))
360 SET_FLAG(flags, GF_GAMEOVER);
362 g->flags = flags;
363 #endif
365 SET_FLAG(g->flags, GF_WK_CASTLE|GF_WQ_CASTLE|GF_WQ_CASTLE|
366 GF_BK_CASTLE|GF_BQ_CASTLE);
367 pgn_board_init(tb);
369 if (pgn_tag_find(g->tag, "FEN") != -1 &&
370 pgn_board_init_fen(g, tb, NULL))
371 return E_PGN_PARSE;
373 for (i = 0; i < n; i++) {
374 HISTORY *h;
375 char *p;
377 if ((h = pgn_history_by_n(g->hp, i)) == NULL)
378 break;
380 p = h->move;
382 if ((ret = pgn_parse_move(g, tb, &p)) != E_PGN_OK)
383 break;
385 pgn_switch_turn(g);
388 if (ret == 0)
389 memcpy(b, tb, sizeof(BOARD));
391 return ret;
395 * Updates the game 'g' using board 'b' to the next 'n'th history move.
396 * Returns nothing.
398 void pgn_history_prev(GAME *g, BOARD b, int n)
400 if (g->hindex - n < 0) {
401 if (n <= 2)
402 g->hindex = pgn_history_total(g->hp);
403 else
404 g->hindex = 0;
406 else
407 g->hindex -= n;
409 pgn_board_update(g, b, g->hindex);
413 * Updates the game 'g' using board 'b' to the previous 'n'th history move.
414 * Returns nothing.
416 void pgn_history_next(GAME *g, BOARD b, int n)
418 if (g->hindex + n > pgn_history_total(g->hp)) {
419 if (n <= 2)
420 g->hindex = 0;
421 else
422 g->hindex = pgn_history_total(g->hp);
424 else
425 g->hindex += n;
427 pgn_board_update(g, b, g->hindex);
431 * Converts the character piece 'p' to an integer. Returns the integer
432 * associated with 'p' or E_PGN_ERR if 'p' is invalid.
434 int pgn_piece_to_int(register int p)
436 if (p == '.')
437 return OPEN_SQUARE;
439 p = tolower(p);
441 switch (p) {
442 case 'p':
443 return PAWN;
444 case 'r':
445 return ROOK;
446 case 'n':
447 return KNIGHT;
448 case 'b':
449 return BISHOP;
450 case 'q':
451 return QUEEN;
452 case 'k':
453 return KING;
454 default:
455 break;
458 return E_PGN_ERR;
462 * Converts the integer piece 'n' to a character whose turn is 'turn'. WHITE
463 * piece are uppercase and BLACK pieces are lowercase. Returns the character
464 * associated with 'n' or E_PGN_ERR if 'n' is an invalid piece.
466 int pgn_int_to_piece(char turn, int n)
468 int p = 0;
470 switch (n) {
471 case PAWN:
472 p = 'p';
473 break;
474 case ROOK:
475 p = 'r';
476 break;
477 case KNIGHT:
478 p = 'n';
479 break;
480 case BISHOP:
481 p = 'b';
482 break;
483 case QUEEN:
484 p = 'q';
485 break;
486 case KING:
487 p = 'k';
488 break;
489 case OPEN_SQUARE:
490 p = '.';
491 break;
492 default:
493 return E_PGN_ERR;
494 break;
497 return (turn == WHITE) ? toupper(p) : p;
501 * Finds a tag 'name' in the structure array 't'. Returns the location in the
502 * array of the found tag or E_PGN_ERR if the tag could not be found.
504 int pgn_tag_find(TAG **t, const char *name)
506 int i;
508 for (i = 0; t[i]; i++) {
509 if (strcasecmp(t[i]->name, name) == 0)
510 return i;
513 return E_PGN_ERR;
516 static int tag_compare(const void *a, const void *b)
518 TAG * const *ta = a;
519 TAG * const *tb = b;
521 return strcmp((*ta)->name, (*tb)->name);
525 * Sorts a tag array. The first seven tags are in order of the PGN standard so
526 * don't sort'em. Returns nothing.
528 void pgn_tag_sort(TAG **tags)
530 if (pgn_tag_total(tags) <= 7)
531 return;
533 qsort(tags + 7, pgn_tag_total(tags) - 7, sizeof(TAG *), tag_compare);
537 * Returns the total number of tags in 't' or 0 if 't' is NULL.
539 int pgn_tag_total(TAG **tags)
541 int i = 0;
543 if (!tags)
544 return 0;
546 while (tags[i])
547 i++;
549 return i;
553 * Adds a tag 'name' with value 'value' to the pointer to array of TAG
554 * pointers 'dst'. If a duplicate tag 'name' was found then the existing tag
555 * is updated to the new 'value'. Returns E_PGN_ERR if there was a memory
556 * allocation error or E_PGN_OK on success.
558 int pgn_tag_add(TAG ***dst, char *name, char *value)
560 int i;
561 TAG **tdata = *dst;
562 TAG **a = NULL;
563 int len = 0;
564 int t = pgn_tag_total(tdata);
566 name = trim(name);
567 value = trim(value);
569 // Find an existing tag with 'name'.
570 for (i = 0; i < t; i++) {
571 char *tmp;
573 if (strcasecmp(tdata[i]->name, name) == 0) {
574 if ((tmp = strdup(value)) == NULL)
575 return E_PGN_ERR;
577 free(tdata[i]->value);
578 tdata[i]->value = tmp;
579 *dst = tdata;
580 return E_PGN_OK;
584 if ((a = realloc(tdata, (t + 2) * sizeof(TAG *))) == NULL)
585 return E_PGN_ERR;
587 tdata = a;
589 if ((tdata[t] = malloc(sizeof(TAG))) == NULL)
590 return E_PGN_ERR;
592 len = strlen(name) + 1;
594 if ((tdata[t]->name = strdup(name)) == NULL) {
595 tdata[t] = NULL;
596 return E_PGN_ERR;
599 if (value) {
600 if ((tdata[t]->value = strdup(value)) == NULL) {
601 free(tdata[t]->name);
602 tdata[t] = NULL;
603 return E_PGN_ERR;
606 else
607 tdata[t]->value = NULL;
609 tdata[++t] = NULL;
610 *dst = tdata;
611 return E_PGN_OK;
614 static char *remove_tag_escapes(const char *str)
616 int i, n;
617 int len = strlen(str);
618 static char buf[MAX_PGN_LINE_LEN] = {0};
620 for (i = n = 0; i < len; i++, n++) {
621 switch (str[i]) {
622 case '\\':
623 i++;
624 default:
625 break;
628 buf[n] = str[i];
631 buf[n] = '\0';
632 return buf;
636 * Resets or initializes a new game board 'b'. Returns nothing.
638 void pgn_board_init(BOARD b)
640 int row, col;
642 memset(b, 0, sizeof(BOARD));
644 for (row = 0; row < 8; row++) {
645 for (col = 0; col < 8; col++) {
646 int c = '.';
648 switch (row) {
649 case 0:
650 case 7:
651 switch (col) {
652 case 0:
653 case 7:
654 c = 'r';
655 break;
656 case 1:
657 case 6:
658 c = 'n';
659 break;
660 case 2:
661 case 5:
662 c = 'b';
663 break;
664 case 3:
665 c = 'q';
666 break;
667 case 4:
668 c = 'k';
669 break;
671 break;
672 case 1:
673 case 6:
674 c = 'p';
675 break;
678 b[row][col].icon = (row < 2) ? c : toupper(c);
684 * Adds the standard PGN roster tags to game 'g'.
686 static void set_default_tags(GAME *g)
688 time_t now;
689 char tbuf[11] = {0};
690 struct tm *tp;
691 struct passwd *pw = getpwuid(getuid());
693 time(&now);
694 tp = localtime(&now);
695 strftime(tbuf, sizeof(tbuf), PGN_TIME_FORMAT, tp);
697 /* The standard seven tag roster (in order of appearance). */
698 if (pgn_tag_add(&g->tag, "Event", "?"))
699 warn("pgn_tag_add()");
701 if (pgn_tag_add(&g->tag, "Site", "?"))
702 warn("pgn_tag_add()");
704 if (pgn_tag_add(&g->tag, "Date", tbuf))
705 warn("pgn_tag_add()");
707 if (pgn_tag_add(&g->tag, "Round", "-"))
708 warn("pgn_tag_add()");
710 if (pgn_tag_add(&g->tag, "White", pw->pw_gecos))
711 warn("pgn_tag_add()");
713 if (pgn_tag_add(&g->tag, "Black", "?"))
714 warn("pgn_tag_add()");
716 if (pgn_tag_add(&g->tag, "Result", "*"))
717 warn("pgn_tag_add()");
721 * Frees a TAG array. Returns nothing.
723 void pgn_tag_free(TAG **tags)
725 int i;
726 int t = pgn_tag_total(tags);
728 if (!tags)
729 return;
731 for (i = 0; i < t; i++) {
732 free(tags[i]->name);
733 free(tags[i]->value);
734 free(tags[i]);
737 free(tags);
741 * Frees a single game 'g'. Returns nothing.
743 void pgn_free(GAME g)
745 pgn_history_free(g.history, 0);
746 free(g.history);
747 pgn_tag_free(g.tag);
748 memset(&g, 0, sizeof(GAME));
752 * Frees all games in the global 'game' array. Returns nothing.
754 void pgn_free_all()
756 int i;
758 for (i = 0; i < gtotal; i++) {
759 pgn_free(game[i]);
761 if (game[i].rav) {
762 for (game[i].ravlevel--; game[i].ravlevel >= 0; game[i].ravlevel--)
763 free(game[i].rav[game[i].ravlevel].fen);
765 free(game[i].rav);
769 if (game)
770 free(game);
771 game = NULL;
774 static void reset_game_data()
776 pgn_free_all();
777 gtotal = gindex = 0;
778 pgn_isfile = 0;
781 static void skip_leading_space(FILE *fp)
783 int c;
785 while ((c = fgetc(fp)) != EOF && !feof(fp)) {
786 if (!isspace(c))
787 break;
790 ungetc(c, fp);
794 * PGN move text section.
796 static int move_text(GAME *g, FILE *fp)
798 char m[MAX_SAN_MOVE_LEN + 1] = {0}, *p;
799 int c;
800 int count;
801 int dots = 0;
802 int digit = 0;
804 while((c = fgetc(fp)) != EOF) {
805 if (isspace(c))
806 continue;
808 if (isdigit(c)) {
809 digit = 1;
810 continue;
813 if (c == '.') {
814 dots++;
815 continue;
818 break;
821 if (!pgn_history_total(g->hp) && digit) {
822 if (dots > 1) {
823 g->turn = BLACK;
825 if (g->hindex == 0 && g->ravlevel == 0)
826 SET_FLAG(g->flags, GF_BLACK_OPENING);
828 else {
829 g->turn = WHITE;
831 if (g->hindex == 0)
832 CLEAR_FLAG(g->flags, GF_BLACK_OPENING);
836 ungetc(c, fp);
838 if (fscanf(fp, " %[a-hPRNBQK1-9#+=Ox-]%n", m, &count) != 1)
839 return 1;
841 p = m;
843 if (pgn_parse_move(g, pgn_board, &p)) {
844 pgn_switch_turn(g);
845 return 1;
848 #ifdef DEBUG
849 DUMP("%s\n", p);
850 dump_board(0, pgn_board);
851 #endif
853 pgn_history_add(g, p);
854 pgn_switch_turn(g);
855 return 0;
859 * PGN nag text.
861 static void nag_text(GAME *g, FILE *fp)
863 int c, i, t;
864 char nags[5], *n = nags;
865 int nag = 0;
867 while ((c = fgetc(fp)) != EOF && !isspace(c)) {
868 if (c == '$') {
869 while ((c = fgetc(fp)) != EOF && isdigit(c))
870 *n++ = c;
872 break;
875 if (c == '!') {
876 if ((c = fgetc(fp)) == '!')
877 nag = 3;
878 else if (c == '?')
879 nag = 5;
880 else {
881 ungetc(c, fp);
882 nag = 1;
885 break;
887 else if (c == '?') {
888 if ((c = fgetc(fp)) == '?')
889 nag = 4;
890 else if (c == '!')
891 nag = 6;
892 else {
893 ungetc(c, fp);
894 nag = 2;
897 break;
899 else if (c == '~')
900 nag = 13;
901 else if (c == '=') {
902 if ((c = fgetc(fp)) == '+')
903 nag = 15;
904 else {
905 ungetc(c, fp);
906 nag = 10;
909 break;
911 else if (c == '+') {
912 if ((t = fgetc(fp)) == '=')
913 nag = 14;
914 else if (t == '-')
915 nag = 18;
916 else if (t == '/') {
917 if ((i = fgetc(fp)) == '-')
918 nag = 16;
919 else
920 ungetc(i, fp);
922 break;
924 else
925 ungetc(t, fp);
927 break;
929 else if (c == '-') {
930 if ((t = fgetc(fp)) == '+')
931 nag = 18;
932 else if (t == '/') {
933 if ((i = fgetc(fp)) == '+')
934 nag = 17;
935 else
936 ungetc(i, fp);
938 break;
940 else
941 ungetc(t, fp);
943 break;
947 *n = '\0';
949 if (!nag)
950 nag = (nags[0]) ? atoi(nags) : 0;
952 if (!nag || nag < 0 || nag > 255)
953 return;
955 // FIXME -1 is because move_text() increments g.hindex. The NAG
956 // annoatation isnt guaranteed to be after the move text in import format.
957 for (i = 0; i < MAX_PGN_NAG; i++) {
958 if (g->hp[g->hindex - 1]->nag[i])
959 continue;
961 g->hp[g->hindex - 1]->nag[i] = nag;
962 break;
965 skip_leading_space(fp);
969 * PGN move annotation.
971 static int annotation_text(GAME *g, FILE *fp, int terminator)
973 int c, lastchar = 0;
974 int len = 0;
975 int hindex = pgn_history_total(g->hp) - 1;
976 char buf[MAX_PGN_LINE_LEN], *a = buf;
978 skip_leading_space(fp);
980 while ((c = fgetc(fp)) != EOF && c != terminator) {
981 if (c == '\n')
982 c = ' ';
984 if (isspace(c) && isspace(lastchar))
985 continue;
987 if (len + 1 == sizeof(buf))
988 continue;
990 *a++ = lastchar = c;
991 len++;
994 *a = '\0';
996 if ((g->hp[hindex]->comment = strdup(buf)) == NULL)
997 return E_PGN_ERR;
999 return E_PGN_OK;
1003 * PGN roster tag.
1005 static int tag_text(GAME *g, FILE *fp)
1007 char name[LINE_MAX], *n = name;
1008 char value[LINE_MAX], *v = value;
1009 int c, i = 0;
1010 int quoted_string = 0;
1011 int lastchar = 0;
1013 skip_leading_space(fp);
1015 /* The tag name is up until the first whitespace. */
1016 while ((c = fgetc(fp)) != EOF && !isspace(c))
1017 *n++ = c;
1019 *n = '\0';
1020 *name = toupper(*name);
1021 skip_leading_space(fp);
1023 /* The value is until the first closing bracket. */
1024 while ((c = fgetc(fp)) != EOF && c != ']') {
1025 if (i++ == '\0' && c == '\"') {
1026 quoted_string = 1;
1027 continue;
1030 if (c == '\n' || c == '\t')
1031 c = ' ';
1033 if (c == ' ' && lastchar == ' ')
1034 continue;
1036 lastchar = *v++ = c;
1039 *v = '\0';
1041 while (isspace(*--v))
1042 *v = '\0';
1044 if (*v == '\"')
1045 *v = '\0';
1047 if (value[0] == '\0') {
1048 if (strcmp(name, "Result") == 0)
1049 value[0] = '*';
1050 else
1051 value[0] = '?';
1053 value[1] = '\0';
1056 strncpy(value, remove_tag_escapes(value), sizeof(value));
1058 if (pgn_tag_add(&g->tag, name, value) != E_PGN_OK)
1059 warn("pgn_tag_add()");
1061 return 0;
1065 * PGN end-of-game marker.
1067 static int eog_text(GAME *g, FILE *fp)
1069 int c, i = 0;
1070 char buf[8], *p = buf;
1072 while ((c = fgetc(fp)) != EOF && !isspace(c) && i++ < sizeof(buf))
1073 *p++ = c;
1075 if (isspace(c))
1076 ungetc(c, fp);
1078 *p = 0;
1080 if (pgn_tag_add(&g->tag, "Result", buf) != E_PGN_OK)
1081 warn("pgn_tag_add()");
1083 return 1;
1087 * Parse RAV text and keep track of g.hp. The 'o' argument is the board state
1088 * before the current move (.hindex) was parsed.
1090 static int read_file(FILE *);
1091 static int rav_text(GAME *g, FILE *fp, int which, BOARD o)
1093 int ravindex = ravlevel;
1094 GAME tg;
1095 RAV *r;
1097 // Begin RAV for the current move.
1098 if (which == '(') {
1100 * Save the current game state for this RAV depth/level.
1102 if ((r = realloc(rav, (ravindex + 1) * sizeof(RAV))) == NULL) {
1103 warn("realloc()");
1104 return 1;
1107 rav = r;
1109 if ((rav[ravindex].fen = strdup(pgn_game_to_fen((*g), pgn_board)))
1110 == NULL) {
1111 warn("strdup()");
1112 return 1;
1115 rav[ravindex].hp = g->hp;
1116 memcpy(&tg, g, sizeof(GAME));
1117 memcpy(pgn_board, o, sizeof(BOARD));
1119 if ((g->hp[g->hindex]->rav = calloc(1, sizeof(HISTORY))) == NULL) {
1120 warn("calloc()");
1121 return 1;
1124 // pgn_history_add() will now append to this new RAV.
1125 g->hp = g->hp[g->hindex]->rav;
1128 * Reset. Will be restored later from 'tg' which is a local variable
1129 * so recursion is possible.
1131 g->hindex = 0;
1132 ravlevel++;
1135 * Now continue as normal as if there were no RAV. Moves will be
1136 * parsed and appended to the new .hp.
1138 if (read_file(fp))
1139 return 1;
1142 * read_file() has returned. The means that a RAV has ended by this
1143 * function returning -1 (see below). So we restore the game state
1144 * that was saved before calling read_file().
1146 pgn_board_init_fen(&tg, pgn_board, rav[ravindex].fen);
1147 free(rav[ravindex].fen);
1148 memcpy(g, &tg, sizeof(GAME));
1149 g->hp = rav[ravindex].hp;
1150 ravlevel--;
1153 * The end of a RAV. This makes read_file() that called this function
1154 * rav_text() return (see above).
1156 else if (which == ')')
1157 return -1;
1159 return 0;
1163 * FIXME
1164 * See pgn_board_init_fen(). Returns E_PGN_PARSE on parse error. 0 may be
1165 * returned on success when there is no move count in the FEN tag otherwise
1166 * the move count is returned.
1168 static int parse_fen_line(BOARD b, unsigned *flags, char *turn, char *ply,
1169 char *str)
1171 char *tmp;
1172 char line[LINE_MAX], *s;
1173 int row = 8, col = 1;
1174 int moven;
1176 strncpy(line, str, sizeof(line));
1177 s = line;
1178 pgn_reset_enpassant(b);
1180 while ((tmp = strsep(&s, "/")) != NULL) {
1181 int n;
1183 if (!VALIDFILE(row))
1184 return E_PGN_PARSE;
1186 while (*tmp) {
1187 if (*tmp == ' ')
1188 goto other;
1190 if (isdigit(*tmp)) {
1191 n = *tmp - '0';
1193 if (!VALIDFILE(n))
1194 return E_PGN_PARSE;
1196 for (; n; --n, col++)
1197 b[ROWTOBOARD(row)][COLTOBOARD(col)].icon =
1198 pgn_int_to_piece(WHITE, OPEN_SQUARE);
1200 else if (pgn_piece_to_int(*tmp) != -1)
1201 b[ROWTOBOARD(row)][COLTOBOARD(col++)].icon = *tmp;
1202 else
1203 return E_PGN_PARSE;
1205 tmp++;
1208 row--;
1209 col = 1;
1212 other:
1213 tmp++;
1215 switch (*tmp++) {
1216 case 'b':
1217 *turn = BLACK;
1218 break;
1219 case 'w':
1220 *turn = WHITE;
1221 break;
1222 default:
1223 return E_PGN_PARSE;
1226 tmp++;
1228 while (*tmp && *tmp != ' ') {
1229 switch (*tmp++) {
1230 case 'K':
1231 SET_FLAG(*flags, GF_WK_CASTLE);
1232 break;
1233 case 'Q':
1234 SET_FLAG(*flags, GF_WQ_CASTLE);
1235 break;
1236 case 'k':
1237 SET_FLAG(*flags, GF_BK_CASTLE);
1238 break;
1239 case 'q':
1240 SET_FLAG(*flags, GF_BQ_CASTLE);
1241 break;
1242 case '-':
1243 break;
1244 default:
1245 return E_PGN_PARSE;
1249 tmp++;
1251 // En passant.
1252 if (*tmp != '-') {
1253 if (!VALIDCOL(*tmp))
1254 return E_PGN_PARSE;
1256 col = *tmp++ - 'a';
1258 if (!VALIDROW(*tmp))
1259 return E_PGN_PARSE;
1261 row = 8 - atoi(tmp++);
1262 b[row][col].enpassant = 1;
1263 SET_FLAG(*flags, GF_ENPASSANT);
1265 else
1266 tmp++;
1268 if (*tmp)
1269 tmp++;
1271 if (*tmp)
1272 *ply = atoi(tmp);
1274 while (*tmp && isdigit(*tmp))
1275 tmp++;
1277 if (*tmp)
1278 tmp++;
1280 moven = atoi(tmp);
1281 return E_PGN_OK;
1285 * It initializes the board (b) to the FEN tag (if found) and sets the
1286 * castling and enpassant info for the game 'g'. If 'fen' is set it should be
1287 * a fen tag and will be parsed rather than the game 'g'.tag FEN tag. Returns
1288 * E_PGN_OK on success or if there was both a FEN and SetUp tag with the SetUp
1289 * tag set to 0. Returns E_PGN_PARSE if there was a FEN parse error, E_PGN_ERR
1290 * if there was no FEN tag or there was a SetUp tag with a value of 0. Returns
1291 * E_PGN_OK on success.
1293 int pgn_board_init_fen(GAME *g, BOARD b, char *fen)
1295 int n = -1, i = -1;
1296 BOARD tmpboard;
1297 unsigned flags = 0;
1298 char turn = g->turn;
1299 char ply = 0;
1301 pgn_board_init(tmpboard);
1303 if (!fen) {
1304 n = pgn_tag_find(g->tag, "Setup");
1305 i = pgn_tag_find(g->tag, "FEN");
1309 * If the FEN tag exists and there is no SetUp tag go ahead and parse it.
1310 * If there is a SetUp tag only parse the FEN tag if the value is 1.
1312 if ((n >= 0 && i >= 0 && atoi(g->tag[n]->value) == 1)
1313 || (i >= 0 && n == -1) || fen) {
1314 if ((n = parse_fen_line(tmpboard, &flags, &turn, &ply,
1315 (fen) ? fen : g->tag[i]->value)) != E_PGN_OK)
1316 return E_PGN_PARSE;
1317 else {
1318 memcpy(b, tmpboard, sizeof(BOARD));
1319 CLEAR_FLAG(g->flags, GF_WK_CASTLE);
1320 CLEAR_FLAG(g->flags, GF_WQ_CASTLE);
1321 CLEAR_FLAG(g->flags, GF_BK_CASTLE);
1322 CLEAR_FLAG(g->flags, GF_BQ_CASTLE);
1323 g->flags |= flags;
1324 g->turn = turn;
1325 g->ply = ply;
1328 else
1329 return (i >= 0 && n >= 0) ? E_PGN_OK : E_PGN_ERR;
1331 return E_PGN_OK;
1335 * Allocates a new game and increments 'gtotal'. 'gindex' is then set to the
1336 * new game. Returns E_PGN_ERR if there was a memory allocation error or
1337 * E_PGN_OK on success.
1339 int pgn_new_game()
1341 GAME *g;
1343 gindex = ++gtotal - 1;
1345 if ((g = realloc(game, gtotal * sizeof(GAME))) == NULL) {
1346 warn("realloc()");
1347 return E_PGN_ERR;
1350 game = g;
1351 memset(&game[gindex], 0, sizeof(GAME));
1353 if ((game[gindex].hp = calloc(1, sizeof(HISTORY *))) == NULL) {
1354 warn("calloc()");
1355 return E_PGN_ERR;
1358 game[gindex].hp[0] = NULL;
1359 game[gindex].history = game[gindex].hp;
1360 game[gindex].side = game[gindex].turn = WHITE;
1361 SET_FLAG(game[gindex].flags, GF_WK_CASTLE|GF_WQ_CASTLE|GF_WQ_CASTLE|
1362 GF_BK_CASTLE|GF_BQ_CASTLE);
1363 pgn_board_init(pgn_board);
1364 set_default_tags(&game[gindex]);
1366 if (pgn_isfile && !(gtotal % 50))
1367 kill(getpid(), SIGUSR1);
1369 return E_PGN_OK;
1372 static int read_file(FILE *fp)
1374 #ifdef DEBUG
1375 char buf[LINE_MAX] = {0}, *p = buf;
1376 #endif
1377 int c = 0;
1378 int parse_error = 0;
1379 BOARD old;
1381 while (1) {
1382 int nextchar = 0;
1383 int lastchar = c;
1384 int n;
1386 if ((c = fgetc(fp)) == EOF) {
1387 if (feof(fp))
1388 break;
1390 if (ferror(fp)) {
1391 clearerr(fp);
1392 continue;
1396 if (!isascii(c)) {
1397 parse_error = 1;
1398 continue;
1401 if (c == '\015')
1402 continue;
1404 nextchar = fgetc(fp);
1405 ungetc(nextchar, fp);
1408 * If there was a move text parsing error, keep reading until the end
1409 * of the current game discarding the data.
1411 if (parse_error) {
1412 pgn_ret = E_PGN_PARSE;
1414 if (ravlevel)
1415 return 1;
1417 if (pgn_config.stop)
1418 return 1;
1420 if (c == '\n' && (nextchar == '\n' || nextchar == '\015')) {
1421 parse_error = 0;
1422 nulltags = 1;
1423 tag_section = 0;
1425 else
1426 continue;
1429 // New game reached.
1430 if (c == '\n' && (nextchar == '\n' || nextchar == '\015')) {
1431 if (tag_section)
1432 continue;
1434 nulltags = 1;
1435 tag_section = 0;
1436 continue;
1440 * PGN: Application comment. The '%' must be on the first column of
1441 * the line. The comment continues until the end of the current line.
1443 if (c == '%') {
1444 if (lastchar == '\n' || lastchar == 0) {
1445 while ((c = fgetc(fp)) != EOF && c != '\n');
1446 continue;
1449 // Not sure what to do here.
1452 if (isspace(c))
1453 continue;
1455 // PGN: Reserved.
1456 if (c == '<' || c == '>')
1457 continue;
1460 * PGN: Recurrsive Annotation Variation. Read rav_text() for more
1461 * info.
1463 if (c == '(' || c == ')') {
1464 switch (rav_text(&game[gindex], fp, c, old)) {
1465 case -1:
1467 * This is the end of the current RAV. This function has
1468 * been called from rav_text(). Returning from this point
1469 * will put us back in rav_text().
1471 if (ravlevel > 0)
1472 return pgn_ret;
1475 * We're back at the root move. Continue as normal.
1477 break;
1478 case 1:
1479 parse_error = 1;
1480 continue;
1481 default:
1483 * Continue processing. Probably the root move.
1485 break;
1488 continue;
1491 // PGN: Numeric Annotation Glyph.
1492 if (c == '$' || c == '!' || c == '?' || c == '+' || c == '-' ||
1493 c == '~' || c == '=') {
1494 ungetc(c, fp);
1495 nag_text(&game[gindex], fp);
1496 continue;
1500 * PGN: Annotation. The ';' comment continues until the end of the
1501 * current line. The '{' type comment continues until a '}' is
1502 * reached.
1504 if (c == '{' || c == ';') {
1505 annotation_text(&game[gindex], fp, (c == '{') ? '}' : '\n');
1506 continue;
1509 // PGN: Roster tag.
1510 if (c == '[') {
1511 // First roster tag found. Initialize the data structures.
1512 if (!tag_section) {
1513 nulltags = 0;
1514 tag_section = 1;
1516 if (gtotal && pgn_history_total(game[gindex].hp))
1517 game[gindex].hindex = pgn_history_total(game[gindex].hp) - 1;
1519 if (pgn_new_game() != E_PGN_OK) {
1520 pgn_ret = E_PGN_ERR;
1521 break;
1524 memcpy(old, pgn_board, sizeof(BOARD));
1527 if (tag_text(&game[gindex], fp))
1528 parse_error = 1; // FEN tag parse error.
1530 continue;
1533 // PGN: End-of-game markers.
1534 if ((isdigit(c) && (nextchar == '-' || nextchar == '/')) || c == '*') {
1535 ungetc(c, fp);
1536 eog_text(&game[gindex], fp);
1537 nulltags = 1;
1538 tag_section = 0;
1540 if (!done_fen_tag) {
1541 if (pgn_tag_find(game[gindex].tag, "FEN") != -1 &&
1542 pgn_board_init_fen(&game[gindex], pgn_board, NULL)) {
1543 parse_error = 1;
1544 continue;
1547 done_fen_tag = 1;
1550 continue;
1553 // PGN: Move text.
1554 if ((isdigit(c) && c != '0') || VALIDCOL(c) || c == 'N' || c == 'K'
1555 || c == 'Q' || c == 'B' || c == 'R' || c == 'P' ||
1556 c == 'O') {
1557 ungetc(c, fp);
1559 // PGN: If a FEN tag exists, initialize the board to the value.
1560 if (tag_section) {
1561 if (pgn_tag_find(game[gindex].tag, "FEN") != E_PGN_ERR &&
1562 (n = pgn_board_init_fen(&game[gindex], pgn_board,
1563 NULL)) == E_PGN_PARSE) {
1564 parse_error = 1;
1565 continue;
1568 done_fen_tag = 1;
1569 tag_section = 0;
1573 * PGN: Import format doesn't require a roster tag section. We've
1574 * arrived to the move text section without any tags so we
1575 * initialize a new game which set's the default tags and any tags
1576 * from the configuration file.
1578 if (nulltags) {
1579 if (gtotal)
1580 game[gindex].hindex = pgn_history_total(game[gindex].hp) - 1;
1582 if (pgn_new_game() != E_PGN_OK) {
1583 pgn_ret = E_PGN_ERR;
1584 break;
1587 memcpy(old, pgn_board, sizeof(BOARD));
1588 nulltags = 0;
1591 memcpy(old, pgn_board, sizeof(BOARD));
1593 if (move_text(&game[gindex], fp)) {
1594 SET_FLAG(game[gindex].flags, GF_PERROR);
1595 parse_error = 1;
1598 continue;
1601 #ifdef DEBUG
1602 *p++ = c;
1604 DUMP("unparsed: '%s'\n", buf);
1606 if (strlen(buf) + 1 == sizeof(buf))
1607 bzero(buf, sizeof(buf));
1608 #endif
1610 continue;
1613 return pgn_ret;
1617 * Parses a file whose file pointer is 'fp'. 'fp' may have been returned by
1618 * pgn_open(). If 'fp' is NULL then a single empty game will be allocated. If
1619 * there is a parsing error E_PGN_PARSE is returned, if there was a memory
1620 * allocation error E_PGN_ERR is returned, otherwise E_PGN_OK is returned and
1621 * the global 'gindex' is set to the last parsed game in the file and the
1622 * global 'gtotal' is set to the total number of games in the file. The file
1623 * will be closed when the parsing is done.
1625 int pgn_parse(FILE *fp)
1627 int i;
1629 if (!fp) {
1630 reset_game_data();
1631 pgn_ret = pgn_new_game();
1632 goto done;
1635 reset_game_data();
1636 nulltags = 1;
1637 pgn_isfile = 1;
1638 pgn_ret = read_file(fp);
1639 fclose(fp);
1641 if (rav)
1642 free(rav);
1644 if (gtotal < 1)
1645 pgn_new_game();
1647 done:
1648 gtotal = gindex + 1;
1650 for (i = 0; i < gtotal; i++) {
1651 game[i].history = game[i].hp;
1652 game[i].hindex = pgn_history_total(game[i].hp);
1655 return pgn_ret;
1659 * Escape '"' and '\' in tag values.
1661 static char *pgn_tag_add_escapes(const char *str)
1663 int i, n;
1664 int len = strlen(str);
1665 static char buf[MAX_PGN_LINE_LEN] = {0};
1667 for (i = n = 0; i < len; i++, n++) {
1668 switch (str[i]) {
1669 case '\\':
1670 case '\"':
1671 buf[n++] = '\\';
1672 break;
1673 default:
1674 break;
1677 buf[n] = str[i];
1680 buf[n] = '\0';
1681 return buf;
1684 static void Fputc(int c, FILE *fp, int *len)
1686 int i = *len;
1688 if (c != '\n' && i + 1 > 80)
1689 Fputc('\n', fp, &i);
1691 if (pgn_lastc == '\n' && c == ' ') {
1692 *len = 0;
1693 return;
1696 if (fputc(c, fp) == EOF)
1697 warn("PGN Save");
1698 else {
1699 if (c == '\n')
1700 i = 0;
1701 else
1702 i++;
1705 *len = i;
1706 pgn_lastc = c;
1709 static void putstring(FILE *fp, char *str, int *len)
1711 char *p;
1713 for (p = str; *p; p++) {
1714 int n = 0;
1716 while (*p && *p != ' ')
1717 n++, p++;
1719 if (n + *len > 80)
1720 Fputc('\n', fp, len);
1722 p -= n;
1723 Fputc(*p, fp, len);
1728 * See pgn_write() for more info.
1730 static void write_comments_and_nag(FILE *fp, HISTORY *h, int *len)
1732 int i;
1733 int type = 0;
1735 for (i = 0; i < MAX_PGN_NAG; i++) {
1736 if (h->nag[i]) {
1737 Fputc(' ', fp, len);
1738 Fputc('$', fp, len);
1739 putstring(fp, itoa(h->nag[i]), len);
1743 if (h->comment) {
1744 if (strlen(h->comment) + *len + 1 > 80) {
1745 type = 1;
1746 putstring(fp, " {", len);
1748 else
1749 putstring(fp, " ;", len);
1751 putstring(fp, h->comment, len);
1753 if (type)
1754 putstring(fp, "}", len);
1755 else
1756 Fputc('\n', fp, len);
1760 static void write_move_text(FILE *fp, HISTORY *h, int *len)
1762 Fputc(' ', fp, len);
1763 putstring(fp, h->move, len);
1765 if (!pgn_config.reduced)
1766 write_comments_and_nag(fp, h, len);
1769 static void write_all_move_text(FILE *fp, HISTORY **h, int m, int *len)
1771 int i;
1772 HISTORY **hp = NULL;
1774 for (i = 0; h[i]; i++) {
1775 if (pgn_write_turn == WHITE) {
1776 if (pgn_config.mpl && pgn_mpl == pgn_config.mpl) {
1777 pgn_mpl = 0;
1778 Fputc('\n', fp, len);
1781 if (m > 1 && i > 0)
1782 Fputc(' ', fp, len);
1784 if (strlen(itoa(m)) + 1 + *len > 80)
1785 Fputc('\n', fp, len);
1787 putstring(fp, itoa(m), len);
1788 Fputc('.', fp, len);
1789 pgn_mpl++;
1792 write_move_text(fp, h[i], len);
1794 if (!pgn_config.reduced && h[i]->rav) {
1795 int oldm = m;
1796 int oldturn = pgn_write_turn;
1798 ravlevel++;
1799 putstring(fp, " (", len);
1802 * If it's WHITE's turn the move number will be added above after
1803 * the call to write_all_move_text() below.
1805 if (pgn_write_turn == BLACK) {
1806 putstring(fp, itoa(m), len);
1807 putstring(fp, "...", len);
1810 hp = h[i]->rav;
1811 write_all_move_text(fp, hp, m, len);
1812 m = oldm;
1813 pgn_write_turn = oldturn;
1814 putstring(fp, ")", len);
1815 ravlevel--;
1817 if (h[i + 1] && !ravlevel)
1818 Fputc(' ', fp, len);
1820 if (pgn_write_turn == WHITE && h[i + 1]) {
1821 putstring(fp, itoa(m), len);
1822 putstring(fp, "...", len);
1826 if (pgn_write_turn == BLACK)
1827 m++;
1829 pgn_write_turn = (pgn_write_turn == WHITE) ? BLACK : WHITE;
1833 #ifdef WITH_COMPRESSED
1834 static char *compression_cmd(const char *filename, int expand)
1836 static char command[FILENAME_MAX];
1837 int len = strlen(filename);
1839 if (filename[len - 4] == '.' && filename[len - 3] == 'z' &&
1840 filename[len - 2] == 'i' && filename[len - 1] == 'p' &&
1841 filename[len] == '\0') {
1842 if (expand)
1843 snprintf(command, sizeof(command), "unzip -p %s 2>/dev/null",
1844 filename);
1845 else
1846 snprintf(command, sizeof(command), "zip -9 >%s 2>/dev/null",
1847 filename);
1849 return command;
1851 else if (filename[len - 3] == '.' && filename[len - 2] == 'g' &&
1852 filename[len - 1] == 'z' && filename[len] == '\0') {
1853 if (expand)
1854 snprintf(command, sizeof(command), "gzip -dc %s", filename);
1855 else
1856 snprintf(command, sizeof(command), "gzip -c 1>%s", filename);
1858 return command;
1860 else if (filename[len - 2] == '.' && filename[len - 1] == 'Z' &&
1861 filename[len] == '\0') {
1862 if (expand)
1863 snprintf(command, sizeof(command), "uncompress -c %s", filename);
1864 else
1865 snprintf(command, sizeof(command), "compress -c 1>%s", filename);
1867 return command;
1869 else if ((filename[len - 4] == '.' && filename[len - 3] == 'b' &&
1870 filename[len - 2] == 'z' && filename[len - 1] == '2' &&
1871 filename[len] == '\0') || (filename[len - 3] == '.' &&
1872 filename[len - 2] == 'b' && filename[len - 1] == 'z' &&
1873 filename[len] == '\0')) {
1874 if (expand)
1875 snprintf(command, sizeof(command), "bzip2 -dc %s", filename);
1876 else
1877 snprintf(command, sizeof(command), "bzip2 -zc 1>%s", filename);
1879 return command;
1882 return NULL;
1884 #endif
1887 * Returns a file pointer associated with 'filename' or NULL on error with
1888 * errno set to indicate the error. If compressed file support was enabled at
1889 * compile time and the filetype is supported and the utility is installed
1890 * then the file will be decompressed.
1892 FILE *pgn_open(const char *filename)
1894 FILE *fp = NULL;
1895 #ifdef WITH_COMPRESSED
1896 FILE *tfp = NULL;
1897 char buf[LINE_MAX];
1898 char *p;
1899 char *command = NULL;
1900 #endif
1902 if (access(filename, R_OK) == -1)
1903 return NULL;
1905 #ifdef WITH_COMPRESSED
1906 if ((command = compression_cmd(filename, 1)) != NULL) {
1907 if ((tfp = tmpfile()) == NULL)
1908 return NULL;
1910 if ((fp = popen(command, "r")) == NULL)
1911 return NULL;
1913 while ((p = fgets(buf, sizeof(buf), fp)) != NULL)
1914 fprintf(tfp, "%s", p);
1916 pclose(fp);
1919 if (tfp)
1920 fseek(tfp, 0, SEEK_SET);
1921 else {
1922 if ((tfp = fopen(filename, "r")) == NULL)
1923 return NULL;
1926 return tfp;
1927 #else
1928 if ((fp = fopen(filename, "r")) == NULL)
1929 return NULL;
1931 return fp;
1932 #endif
1936 * Returns E_PGN_OK if 'filename' is a recognized compressed filetype or
1937 * E_PGN_ERR if not.
1939 int pgn_is_compressed(const char *filename)
1941 #ifdef WITH_COMPRESSED
1942 if (compression_cmd(filename, 0))
1943 return E_PGN_OK;
1944 #endif
1946 return E_PGN_ERR;
1950 * Sets config flag 'f' to 'val'. Returns E_PGN_OK on success or E_PGN_ERR if
1951 * 'f' is an invalid flag or 'val' is an invalid value.
1953 int pgn_config_set(pgn_config_flag f, int val)
1955 if (val < 0)
1956 return E_PGN_ERR;
1958 switch (f) {
1959 case PGN_REDUCED:
1960 if (val == 1)
1961 pgn_config.reduced = 1;
1962 else if (val == 0)
1963 pgn_config.reduced = 0;
1964 else
1965 return E_PGN_ERR;
1966 break;
1967 case PGN_MPL:
1968 pgn_config.mpl = val;
1969 break;
1970 case PGN_STOP_ON_ERROR:
1971 if (val == 1)
1972 pgn_config.stop = 1;
1973 else if (val == 0)
1974 pgn_config.stop = 0;
1975 else
1976 return E_PGN_ERR;
1977 break;
1978 default:
1979 return E_PGN_ERR;
1982 return E_PGN_OK;
1986 * Returns the value accociated with 'f' or E_PGN_ERR if 'f' is invalid.
1988 int pgn_config_get(pgn_config_flag f)
1990 switch (f) {
1991 case PGN_REDUCED:
1992 return pgn_config.reduced;
1993 case PGN_STOP_ON_ERROR:
1994 return pgn_config.stop;
1995 case PGN_MPL:
1996 return pgn_config.mpl;
1997 default:
1998 break;
2001 return E_PGN_ERR;
2005 * Writes a PGN formatted game 'g' to the file pointed to by 'fp'. See
2006 * 'pgn_config_flag' for output options. Returns E_PGN_ERR if there was a
2007 * memory allocation or write error and E_PGN_OK on success.
2009 int pgn_write(FILE *fp, GAME g)
2011 int i;
2012 int len = 0;
2014 pgn_write_turn = (TEST_FLAG(g.flags, GF_BLACK_OPENING)) ? BLACK : WHITE;
2015 pgn_tag_sort(g.tag);
2017 for (i = 0; g.tag[i]; i++) {
2018 struct tm tp;
2019 char tbuf[11] = {0};
2020 char *tmp;
2022 if (pgn_config.reduced && i == 7)
2023 break;
2025 if (strcmp(g.tag[i]->name, "Date") == 0) {
2026 if (strptime(g.tag[i]->value, PGN_TIME_FORMAT, &tp) != NULL) {
2027 len = strftime(tbuf, sizeof(tbuf), PGN_TIME_FORMAT, &tp) + 1;
2029 if ((tmp = strdup(tbuf)) == NULL)
2030 return E_PGN_ERR;
2032 free(g.tag[i]->value);
2033 g.tag[i]->value = tmp;
2036 else if (strcmp(g.tag[i]->name, "Event") == 0) {
2037 if (g.tag[i]->value[0] == '\0') {
2038 if ((tmp = strdup("?")) == NULL)
2039 return E_PGN_ERR;
2041 free(g.tag[i]->value);
2042 g.tag[i]->value = tmp;
2045 else if (strcmp(g.tag[i]->name, "Site") == 0) {
2046 if (g.tag[i]->value[0] == '\0') {
2047 if ((tmp = strdup("?")) == NULL)
2048 return E_PGN_ERR;
2050 free(g.tag[i]->value);
2051 g.tag[i]->value = tmp;
2054 else if (strcmp(g.tag[i]->name, "Round") == 0) {
2055 if (g.tag[i]->value[0] == '\0') {
2056 if ((tmp = strdup("?")) == NULL)
2057 return E_PGN_ERR;
2059 free(g.tag[i]->value);
2060 g.tag[i]->value = tmp;
2063 else if (strcmp(g.tag[i]->name, "Result") == 0) {
2064 if (g.tag[i]->value[0] == '\0') {
2065 if ((tmp = strdup("*")) == NULL)
2066 return E_PGN_ERR;
2068 free(g.tag[i]->value);
2069 g.tag[i]->value = tmp;
2072 else if (strcmp(g.tag[i]->name, "Black") == 0) {
2073 if (g.tag[i]->value[0] == '\0') {
2074 if ((tmp = strdup("?")) == NULL)
2075 return E_PGN_ERR;
2077 free(g.tag[i]->value);
2078 g.tag[i]->value = tmp;
2081 else if (strcmp(g.tag[i]->name, "White") == 0) {
2082 if (g.tag[i]->value[0] == '\0') {
2083 if ((tmp = strdup("?")) == NULL)
2084 return E_PGN_ERR;
2086 free(g.tag[i]->value);
2087 g.tag[i]->value = tmp;
2091 fprintf(fp, "[%s \"%s\"]\n", g.tag[i]->name,
2092 (g.tag[i]->value && g.tag[i]->value[0]) ?
2093 pgn_tag_add_escapes(g.tag[i]->value) : "");
2096 Fputc('\n', fp, &len);
2097 g.hp = g.history;
2098 ravlevel = 0;
2100 if (pgn_history_total(g.hp) && pgn_write_turn == BLACK)
2101 putstring(fp, "1...", &len);
2103 write_all_move_text(fp, g.hp, 1, &len);
2105 Fputc(' ', fp, &len);
2106 putstring(fp, g.tag[pgn_tag_find(g.tag, "Result")]->value, &len);
2107 putstring(fp, "\n\n", &len);
2109 if (!pgn_config.reduced) {
2110 CLEAR_FLAG(g.flags, GF_MODIFIED);
2111 CLEAR_FLAG(g.flags, GF_PERROR);
2114 return E_PGN_OK;
2118 * Clears the enpassant flag for all positions on board 'b'. Returns nothing.
2120 void pgn_reset_enpassant(BOARD b)
2122 int r, c;
2124 for (r = 0; r < 8; r++) {
2125 for (c = 0; c < 8; c++)
2126 b[r][c].enpassant = 0;