Have automake use LDADD rather than LDFLAGS.
[cboard.git] / libchess / pgn.c
blob019ce25abd629eeefc220d2afe9c4786b9cfa87b
1 /* vim:tw=78:ts=8:sw=4:set ft=c: */
2 /*
3 Copyright (C) 2002-2018 Ben Kibbey <bjk@luxsci.net>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 #ifdef HAVE_CONFIG_H
20 #include <config.h>
21 #endif
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <unistd.h>
26 #include <sys/types.h>
27 #include <sys/wait.h>
28 #include <sys/stat.h>
29 #include <pwd.h>
30 #include <string.h>
31 #include <time.h>
32 #include <ctype.h>
33 #include <errno.h>
34 #include <fcntl.h>
35 #include <stdarg.h>
36 #include <err.h>
37 #include <time.h>
39 #ifdef HAVE_LIMITS_H
40 #include <limits.h>
41 #endif
43 #include "chess.h"
44 #include "common.h"
46 #ifdef DEBUG
47 #include "debug.h"
48 #endif
50 static BOARD pgn_board;
51 static int nulltags;
52 static int tag_section;
53 static int pgn_ret;
54 static int pgn_write_turn;
55 static int pgn_mpl;
56 static int pgn_lastc;
57 static int ravlevel;
58 static long pgn_fsize;
59 static int pgn_rav;
60 static RAV *pgn_rav_p;
62 #ifdef __linux__
63 extern char *strptime (const char *, const char *, struct tm *);
64 #endif
66 static int
67 Fgetc (FILE * fp)
69 int c;
71 if ((c = fgetc (fp)) != EOF)
73 if (pgn_config.progress && pgn_config.pfunc)
75 if (!(ftell (fp) % pgn_config.progress))
76 pgn_config.pfunc (pgn_fsize, ftell (fp));
80 return c;
83 static int
84 Ungetc (int c, FILE * fp)
86 return ungetc (c, fp);
89 const char *
90 pgn_version ()
92 return "libchess " PACKAGE_VERSION;
95 static char *
96 trim (char *str)
98 int i = 0;
100 if (!str)
101 return NULL;
103 while (isspace (*str))
104 str++;
106 for (i = strlen (str) - 1; isspace (str[i]); i--)
107 str[i] = 0;
109 return str;
112 static char *
113 itoa (long n, char *buf)
115 sprintf (buf, "%li", n);
116 return buf;
120 * Clears the valid move flag for all positions on board 'b'. Returns nothing.
122 void
123 pgn_reset_valid_moves (BOARD b)
125 int row, col;
127 #ifdef DEBUG
128 PGN_DUMP ("%s:%d: resetting valid moves\n", __FILE__, __LINE__);
129 #endif
131 for (row = 0; row < 8; row++)
133 for (col = 0; col < 8; col++)
134 b[row][col].valid = 0;
139 * Toggles g->turn. Returns nothing.
141 void
142 pgn_switch_turn (GAME g)
144 g->turn = (g->turn == WHITE) ? BLACK : WHITE;
148 * Toggles g->side and switches the White and Black roster tags. Returns
149 * nothing.
151 void
152 pgn_switch_side (GAME g, int t)
154 if (t)
156 char *w = g->tag[4]->value;
158 g->tag[4]->value = g->tag[5]->value;
159 g->tag[5]->value = w;
162 g->side = !g->side;
166 * Creates a FEN tag from the current game 'g', history move (g->hindex) and
167 * board 'b'. Returns a FEN tag.
169 char *
170 pgn_game_to_fen (GAME g, BOARD b)
172 char tmp[16];
173 int row, col;
174 int i;
175 char buf[MAX_PGN_LINE_LEN] = { 0 }, *p;
176 int oldturn = g->turn;
177 char enpassant[3] = { 0 }, *e;
178 int castle = 0;
180 #ifdef DEBUG
181 PGN_DUMP ("%s:%d: creating FEN tag\n", __FILE__, __LINE__);
182 #endif
184 for (i = pgn_history_total (g->hp); i >= g->hindex - 1; i--)
185 pgn_switch_turn (g);
187 p = buf;
189 for (row = 0; row < 8; row++)
191 int count = 0;
193 for (col = 0; col < 8; col++)
195 if (b[row][col].enpassant)
197 b[row][col].icon = pgn_int_to_piece (WHITE, OPEN_SQUARE);
198 e = enpassant;
199 *e++ = 'a' + col;
200 *e++ = ('0' + 8) - row;
201 *e = 0;
204 if (pgn_piece_to_int (b[row][col].icon) == OPEN_SQUARE)
206 count++;
207 continue;
210 if (count)
212 *p++ = '0' + count;
213 count = 0;
216 *p++ = b[row][col].icon;
217 *p = 0;
220 if (count)
222 *p++ = '0' + count;
223 count = 0;
226 *p++ = '/';
229 --p;
230 *p++ = ' ';
231 *p++ = (g->turn == WHITE) ? 'w' : 'b';
232 *p++ = ' ';
234 if (TEST_FLAG (g->flags, GF_WK_CASTLE) && pgn_piece_to_int (b[7][7].icon) ==
235 ROOK && isupper (b[7][7].icon) && pgn_piece_to_int (b[7][4].icon) ==
236 KING && isupper (b[7][4].icon))
238 *p++ = 'K';
239 castle = 1;
242 if (TEST_FLAG (g->flags, GF_WQ_CASTLE) && pgn_piece_to_int (b[7][0].icon) ==
243 ROOK && isupper (b[7][0].icon) && pgn_piece_to_int (b[7][4].icon) ==
244 KING && isupper (b[7][4].icon))
246 *p++ = 'Q';
247 castle = 1;
250 if (TEST_FLAG (g->flags, GF_BK_CASTLE) && pgn_piece_to_int (b[0][7].icon) ==
251 ROOK && islower (b[0][7].icon) && pgn_piece_to_int (b[0][4].icon) ==
252 KING && islower (b[0][4].icon))
254 *p++ = 'k';
255 castle = 1;
258 if (TEST_FLAG (g->flags, GF_BQ_CASTLE) && pgn_piece_to_int (b[0][0].icon) ==
259 ROOK && islower (b[0][0].icon) && pgn_piece_to_int (b[0][4].icon) ==
260 KING && islower (b[0][4].icon))
262 *p++ = 'q';
263 castle = 1;
266 if (!castle)
267 *p++ = '-';
269 *p++ = ' ';
271 if (enpassant[0])
273 e = enpassant;
274 *p++ = *e++;
275 *p++ = *e++;
277 else
278 *p++ = '-';
280 *p++ = ' ';
282 // Halfmove clock.
283 *p = 0;
284 strcat (p, itoa (g->ply, tmp));
285 p = buf + strlen (buf);
286 *p++ = ' ';
288 // Fullmove number.
289 i = (g->hindex + 1) / 2;
290 *p = 0;
291 strcat (p, itoa ((g->hindex / 2) + (g->hindex % 2), tmp));
293 g->turn = oldturn;
294 return buf[0] ? strdup (buf) : NULL;
298 * Returns the total number of moves in 'h' or 0 if there are none.
301 pgn_history_total (HISTORY ** h)
303 int i;
305 if (!h)
306 return 0;
308 for (i = 0; h[i]; i++);
309 return i;
313 * Deallocates all of the history data from position 'start' in the array 'h'.
314 * Returns nothing.
316 void
317 pgn_history_free (HISTORY ** h, int start)
319 int i;
321 #ifdef DEBUG
322 PGN_DUMP ("%s:%d: freeing history\n", __FILE__, __LINE__);
323 #endif
325 if (!h || start > pgn_history_total (h))
326 return;
328 if (start < 0)
329 start = 0;
331 for (i = start; h[i]; i++)
333 if (h[i]->rav)
335 pgn_history_free (h[i]->rav, 0);
336 free (h[i]->rav);
339 free (h[i]->comment);
340 free (h[i]->move);
341 free (h[i]->fen);
342 free (h[i]);
345 h[start] = NULL;
349 * Returns the history ply 'n' from 'h'. If 'n' is out of range then NULL is
350 * returned.
352 HISTORY *
353 pgn_history_by_n (HISTORY ** h, int n)
355 if (n < 0 || n > pgn_history_total (h) - 1)
356 return NULL;
358 return h[n];
362 * Appends move 'm' to game 'g' history pointer and creates a FEN tag for the
363 * current game state using board 'b'. The FEN tag makes things faster than
364 * validating the entire move history by validating only the current move to
365 * the previous moves FEN tag. The history pointer may be a in a RAV so
366 * g->rav.hp is also updated to the new (realloc()'ed) pointer. If not in a
367 * RAV then g->history will be updated. Returns E_PGN_ERR if realloc() failed
368 * or E_PGN_OK on success.
370 pgn_error_t
371 pgn_history_add (GAME g, BOARD b, const char *m)
373 int t = pgn_history_total (g->hp);
374 int o;
375 HISTORY **h = NULL, *tmp;
376 int ri = (g->ravlevel) ? g->rav[g->ravlevel - 1].hindex : 0;
378 #ifdef DEBUG
379 PGN_DUMP ("%s:%d: adding '%s' to move history\n", __FILE__, __LINE__, m);
380 #endif
382 if (g->ravlevel)
383 o = g->rav[g->ravlevel - 1].hp[ri - 1]->rav - g->hp;
384 else
385 o = g->history - g->hp;
387 if ((h = realloc (g->hp, (t + 2) * sizeof (HISTORY *))) == NULL)
388 return E_PGN_ERR;
390 g->hp = h;
392 if (g->ravlevel)
393 g->rav[g->ravlevel - 1].hp[ri - 1]->rav = g->hp + o;
394 else
395 g->history = g->hp + o;
397 if ((g->hp[t] = calloc (1, sizeof (HISTORY))) == NULL)
398 return E_PGN_ERR;
400 if ((g->hp[t]->move = strdup (m)) == NULL)
402 free (g->hp[t]);
403 g->hp[t] = NULL;
404 return E_PGN_ERR;
407 tmp = g->hp[t];
408 t++;
409 g->hp[t] = NULL;
410 g->hindex = pgn_history_total (g->hp);
411 pgn_switch_turn (g);
412 tmp->fen = pgn_game_to_fen (g, b);
413 pgn_switch_turn (g);
414 return E_PGN_OK;
418 * Resets the game 'g' using board 'b' up to history move (g->hindex) 'n'.
419 * Returns E_PGN_OK on success or E_PGN_PARSE if there was a FEN tag but the
420 * parsing of it failed. Or returns E_PGN_INVALID if somehow the move failed
421 * validation while resetting.
423 pgn_error_t
424 pgn_board_update (GAME g, BOARD b, int n)
426 BOARD tb;
427 int ret = E_PGN_OK;
428 int p_error = TEST_FLAG (g->flags, GF_PERROR);
429 int black_opening = TEST_FLAG (g->flags, GF_BLACK_OPENING);
431 #ifdef DEBUG
432 PGN_DUMP ("%s:%d: updating board\n", __FILE__, __LINE__);
433 #endif
435 if (!g->ravlevel && TEST_FLAG (g->flags, GF_BLACK_OPENING))
436 g->turn = BLACK;
437 else
438 g->turn = WHITE;
440 g->flags = 0;
441 SET_FLAG (g->flags, GF_WK_CASTLE | GF_WQ_CASTLE | GF_WQ_CASTLE |
442 GF_BK_CASTLE | GF_BQ_CASTLE);
443 pgn_board_init (tb);
445 if (g->ravlevel)
446 pgn_board_init_fen (g, tb, g->rav[g->ravlevel - 1].fen);
447 else if (pgn_tag_find (g->tag, "FEN") != -1 &&
448 pgn_board_init_fen (g, tb, NULL))
449 return E_PGN_PARSE;
451 if (n)
453 HISTORY *h = pgn_history_by_n (g->hp, n - 1);
455 if (h)
457 ret = pgn_board_init_fen (g, tb, h->fen);
458 if (ret == E_PGN_OK)
460 h = pgn_history_by_n (g->hp, n);
461 if (h)
463 char *frfr = NULL;
465 ret = pgn_parse_move (g, tb, &h->move, &frfr);
466 if (ret == E_PGN_OK)
468 h = pgn_history_by_n (g->hp, n - 1);
469 ret = pgn_board_init_fen (g, tb, h->fen);
472 free (frfr);
478 if (ret == E_PGN_OK)
479 memcpy (b, tb, sizeof (BOARD));
481 if (p_error)
482 SET_FLAG (g->flags, GF_PERROR);
484 if (black_opening)
485 SET_FLAG (g->flags, GF_BLACK_OPENING);
487 return ret;
491 * Updates the game 'g' using board 'b' to the next 'n'th history move.
492 * Returns nothing.
494 void
495 pgn_history_prev (GAME g, BOARD b, int n)
497 if (g->hindex - n < 0)
499 if (n <= 2)
500 g->hindex = pgn_history_total (g->hp);
501 else
502 g->hindex = 0;
504 else
505 g->hindex -= n;
507 pgn_board_update (g, b, g->hindex);
511 * Updates the game 'g' using board 'b' to the previous 'n'th history move.
512 * Returns nothing.
514 void
515 pgn_history_next (GAME g, BOARD b, int n)
517 if (g->hindex + n > pgn_history_total (g->hp))
519 if (n <= 2)
520 g->hindex = 0;
521 else
522 g->hindex = pgn_history_total (g->hp);
524 else
525 g->hindex += n;
527 pgn_board_update (g, b, g->hindex);
531 * Converts the character piece 'p' to an integer. Returns the integer
532 * associated with 'p' or E_PGN_ERR if 'p' is invalid.
535 pgn_piece_to_int (int p)
537 if (p == '.')
538 return OPEN_SQUARE;
540 p = tolower (p);
542 switch (p)
544 case 'p':
545 return PAWN;
546 case 'r':
547 return ROOK;
548 case 'n':
549 return KNIGHT;
550 case 'b':
551 return BISHOP;
552 case 'q':
553 return QUEEN;
554 case 'k':
555 return KING;
556 default:
557 break;
560 #ifdef DEBUG
561 PGN_DUMP ("%s:%d: invalid piece '%c'\n", __FILE__, __LINE__, p);
562 #endif
563 return E_PGN_ERR;
567 * Converts the integer piece 'n' to a character whose turn is 'turn'. WHITE
568 * piece are uppercase and BLACK pieces are lowercase. Returns the character
569 * associated with 'n' or E_PGN_ERR if 'n' is an invalid piece.
571 pgn_error_t
572 pgn_int_to_piece (char turn, int n)
574 int p = 0;
576 switch (n)
578 case PAWN:
579 p = 'p';
580 break;
581 case ROOK:
582 p = 'r';
583 break;
584 case KNIGHT:
585 p = 'n';
586 break;
587 case BISHOP:
588 p = 'b';
589 break;
590 case QUEEN:
591 p = 'q';
592 break;
593 case KING:
594 p = 'k';
595 break;
596 case OPEN_SQUARE:
597 p = '.';
598 break;
599 default:
600 #ifdef DEBUG
601 PGN_DUMP ("%s:%d: unknown piece integer %i\n", __FILE__, __LINE__, n);
602 #endif
603 return E_PGN_ERR;
604 break;
607 return (turn == WHITE) ? toupper (p) : p;
611 * Finds a tag 'name' in the structure array 't'. Returns the location in the
612 * array of the found tag or E_PGN_ERR if the tag could not be found.
614 pgn_error_t
615 pgn_tag_find (TAG ** t, const char *name)
617 int i;
619 for (i = 0; t[i]; i++)
621 if (strcasecmp (t[i]->name, name) == 0)
622 return i;
625 return E_PGN_ERR;
628 static pgn_error_t
629 remove_tag (TAG *** array, const char *tag)
631 TAG **tags = *array;
632 int n = pgn_tag_find (tags, tag);
633 int i, t;
635 if (n == E_PGN_ERR)
636 return E_PGN_ERR;
638 for (i = t = 0; tags[i]; i++)
640 if (i == n)
642 free (tags[i]->name);
643 free (tags[i]->value);
644 free (tags[i]);
645 continue;
648 tags[t++] = tags[i];
651 tags = realloc (*array, (t + 1) * sizeof (TAG *));
652 tags[t] = NULL;
653 *array = tags;
654 #ifdef DEBUG
655 PGN_DUMP ("%s:%d: removed tag: name='%s'\n", __FILE__, __LINE__, tag);
656 #endif
657 return E_PGN_OK;
660 static int
661 tag_compare (const void *a, const void *b)
663 TAG *const *ta = a;
664 TAG *const *tb = b;
666 return strcmp ((*ta)->name, (*tb)->name);
670 * Sorts a tag array. The first seven tags are in order of the PGN standard so
671 * don't sort'em. Returns nothing.
673 void
674 pgn_tag_sort (TAG ** tags)
676 if (pgn_tag_total (tags) <= 7)
677 return;
679 qsort (tags + 7, pgn_tag_total (tags) - 7, sizeof (TAG *), tag_compare);
683 * Returns the total number of tags in 't' or 0 if 't' is NULL.
686 pgn_tag_total (TAG ** tags)
688 int i = 0;
690 if (!tags)
691 return 0;
693 while (tags[i])
694 i++;
696 return i;
700 * Adds a tag 'name' with value 'value' to the pointer to array of TAG
701 * pointers 'dst'. If a duplicate tag 'name' was found then the existing tag
702 * is updated to the new 'value'. If 'value' is NULL, the tag is removed.
703 * Returns E_PGN_ERR if there was a memory allocation error or E_PGN_OK on
704 * success.
706 pgn_error_t
707 pgn_tag_add (TAG *** dst, char *name, char *value)
709 int i;
710 TAG **tdata = *dst;
711 TAG **a = NULL;
712 int t = pgn_tag_total (tdata);
713 TAG *newtag;
715 #ifdef DEBUG
716 PGN_DUMP ("%s:%d: adding tag\n", __FILE__, __LINE__);
717 #endif
719 if (!name)
720 return E_PGN_ERR;
722 name = trim (name);
724 if (value)
725 value = trim (value);
727 // Find an existing tag with 'name'.
728 for (i = 0; i < t; i++)
730 char *tmp = NULL;
732 if (strcasecmp (tdata[i]->name, name) == 0)
734 if (value)
736 if ((tmp = strdup (value)) == NULL)
737 return E_PGN_ERR;
739 else
741 remove_tag (dst, name);
742 return E_PGN_OK;
745 free (tdata[i]->value);
746 tdata[i]->value = tmp;
747 *dst = tdata;
748 return E_PGN_OK;
752 newtag = calloc (1, sizeof (TAG));
753 if (!newtag)
754 return E_PGN_ERR;
756 if ((newtag->name = strdup (name)) == NULL)
758 free (newtag);
759 return E_PGN_ERR;
762 if (value)
764 if ((newtag->value = strdup (value)) == NULL)
766 free (newtag->name);
767 free (newtag);
768 return E_PGN_ERR;
771 else
772 newtag->value = NULL;
774 if ((a = realloc (tdata, (t + 2) * sizeof (TAG *))) == NULL)
776 free (newtag->name);
777 free (newtag->value);
778 free (newtag);
779 return E_PGN_ERR;
782 tdata = a;
783 tdata[t] = newtag;
784 tdata[t]->name[0] = toupper (tdata[t]->name[0]);
785 tdata[++t] = NULL;
786 *dst = tdata;
788 #ifdef DEBUG
789 PGN_DUMP ("%s:%d: added tag: name='%s' value='%s'\n", __FILE__, __LINE__,
790 name, (value) ? value : "null");
791 #endif
793 return E_PGN_OK;
796 static char *
797 remove_tag_escapes (const char *str)
799 int i, n;
800 int len = strlen (str);
801 char buf[MAX_PGN_LINE_LEN] = { 0 };
803 for (i = n = 0; i < len; i++, n++)
805 switch (str[i])
807 case '\\':
808 i++;
809 default:
810 break;
813 buf[n] = str[i];
816 buf[n] = '\0';
817 return buf[0] ? strdup (buf) : NULL;
821 * Resets or initializes a new game board 'b'. Returns nothing.
823 void
824 pgn_board_init (BOARD b)
826 int row, col;
828 #ifdef DEBUG
829 PGN_DUMP ("%s:%d: initializing board\n", __FILE__, __LINE__);
830 #endif
832 memset (b, 0, sizeof (BOARD));
834 for (row = 0; row < 8; row++)
836 for (col = 0; col < 8; col++)
838 int c = '.';
840 switch (row)
842 case 0:
843 case 7:
844 switch (col)
846 case 0:
847 case 7:
848 c = 'r';
849 break;
850 case 1:
851 case 6:
852 c = 'n';
853 break;
854 case 2:
855 case 5:
856 c = 'b';
857 break;
858 case 3:
859 c = 'q';
860 break;
861 case 4:
862 c = 'k';
863 break;
865 break;
866 case 1:
867 case 6:
868 c = 'p';
869 break;
872 b[row][col].icon = (row < 2) ? c : toupper (c);
878 * Adds the standard PGN roster tags to game 'g'.
880 static void
881 set_default_tags (GAME g)
883 time_t now;
884 char tbuf[11] = { 0 };
885 struct tm *tp;
886 struct passwd *pw = getpwuid (getuid ());
887 char host[64] = { 0 };
889 time (&now);
890 tp = localtime (&now);
891 strftime (tbuf, sizeof (tbuf), PGN_TIME_FORMAT, tp);
893 /* The standard seven tag roster (in order of appearance). */
894 if (pgn_tag_add (&g->tag, (char *) "Event", (char *) "?") != E_PGN_OK)
895 warn ("pgn_tag_add()");
897 gethostname (host, sizeof (host) - 1);
898 if (pgn_tag_add (&g->tag, (char *) "Site", host) != E_PGN_OK)
899 warn ("pgn_tag_add()");
901 if (pgn_tag_add (&g->tag, (char *) "Date", tbuf) != E_PGN_OK)
902 warn ("pgn_tag_add()");
904 if (pgn_tag_add (&g->tag, (char *) "Round", (char *) "-") != E_PGN_OK)
905 warn ("pgn_tag_add()");
907 if (pgn_tag_add (&g->tag, (char *) "White", pw->pw_gecos) != E_PGN_OK)
908 warn ("pgn_tag_add()");
910 if (pgn_tag_add (&g->tag, (char *) "Black", (char *) "?") != E_PGN_OK)
911 warn ("pgn_tag_add()");
913 if (pgn_tag_add (&g->tag, (char *) "Result", (char *) "*") != E_PGN_OK)
914 warn ("pgn_tag_add()");
918 * Frees a TAG array. Returns nothing.
920 void
921 pgn_tag_free (TAG ** tags)
923 int i;
924 int t = pgn_tag_total (tags);
926 #ifdef DEBUG
927 PGN_DUMP ("%s:%d: freeing tags\n", __FILE__, __LINE__);
928 #endif
930 if (!tags)
931 return;
933 for (i = 0; i < t; i++)
935 free (tags[i]->name);
936 free (tags[i]->value);
937 free (tags[i]);
940 free (tags);
944 * Frees a single game 'g'. Returns nothing.
946 void
947 pgn_free (GAME g)
949 #ifdef DEBUG
950 PGN_DUMP ("%s:%d: freeing game\n", __FILE__, __LINE__);
951 #endif
952 pgn_history_free (g->history, 0);
953 free (g->history);
954 pgn_tag_free (g->tag);
956 if (g->rav)
958 for (g->ravlevel--; g->ravlevel >= 0; g->ravlevel--)
959 free (g->rav[g->ravlevel].fen);
961 free (g->rav);
964 free (g);
968 * Frees all games in the global 'game' array. Returns nothing.
970 void
971 pgn_free_all ()
973 int i;
975 #ifdef DEBUG
976 PGN_DUMP ("%s:%d: freeing game data\n", __FILE__, __LINE__);
977 #endif
979 for (i = 0; i < gtotal; i++)
981 pgn_free (game[i]);
984 if (game)
985 free (game);
987 game = NULL;
990 static void
991 reset_game_data ()
993 #ifdef DEBUG
994 PGN_DUMP ("%s:%d: resetting game data\n", __FILE__, __LINE__);
995 #endif
996 pgn_free_all ();
997 gtotal = gindex = 0;
1000 static void
1001 skip_leading_space (FILE * fp)
1003 int c;
1005 while ((c = Fgetc (fp)) != EOF && !feof (fp))
1007 if (!isspace (c))
1008 break;
1011 Ungetc (c, fp);
1015 * PGN move text section.
1017 static int
1018 move_text (GAME g, FILE * fp)
1020 char m[MAX_SAN_MOVE_LEN + 1] = { 0 }, *p;
1021 int c;
1022 int count;
1023 char *frfr = NULL;
1025 g->oflags = g->flags;
1027 while ((c = Fgetc (fp)) != EOF)
1029 if (isdigit (c) || isspace (c) || c == '.')
1030 continue;
1032 break;
1035 Ungetc (c, fp);
1037 if (fscanf (fp, " %[a-hPpRrNnBbQqKk1-9#+=Ox-]%n", m, &count) != 1)
1038 return 1;
1040 m[MAX_SAN_MOVE_LEN] = 0;
1041 p = m;
1043 if (pgn_parse_move (g, pgn_board, &p, &frfr))
1045 pgn_switch_turn (g);
1046 return 1;
1049 free (frfr);
1050 #ifdef DEBUG
1051 PGN_DUMP ("%s\n%s", p, debug_board (pgn_board));
1052 #endif
1054 pgn_history_add (g, pgn_board, p);
1055 pgn_switch_turn (g);
1056 return 0;
1060 * PGN nag text.
1062 static void
1063 nag_text (GAME g, FILE * fp)
1065 int c, i, t;
1066 char nags[5], *n = nags;
1067 int nag = 0;
1069 while ((c = Fgetc (fp)) != EOF && !isspace (c))
1071 if (c == '$')
1073 while ((c = Fgetc (fp)) != EOF && isdigit (c))
1074 *n++ = c;
1076 Ungetc (c, fp);
1077 break;
1080 if (c == '!')
1082 if ((c = Fgetc (fp)) == '!')
1083 nag = 3;
1084 else if (c == '?')
1085 nag = 5;
1086 else
1088 Ungetc (c, fp);
1089 nag = 1;
1092 break;
1094 else if (c == '?')
1096 if ((c = Fgetc (fp)) == '?')
1097 nag = 4;
1098 else if (c == '!')
1099 nag = 6;
1100 else
1102 Ungetc (c, fp);
1103 nag = 2;
1106 break;
1108 else if (c == '~')
1109 nag = 13;
1110 else if (c == '=')
1112 if ((c = Fgetc (fp)) == '+')
1113 nag = 15;
1114 else
1116 Ungetc (c, fp);
1117 nag = 10;
1120 break;
1122 else if (c == '+')
1124 if ((t = Fgetc (fp)) == '=')
1125 nag = 14;
1126 else if (t == '-')
1127 nag = 18;
1128 else if (t == '/')
1130 if ((i = Fgetc (fp)) == '-')
1131 nag = 16;
1132 else
1133 Ungetc (i, fp);
1135 break;
1137 else
1138 Ungetc (t, fp);
1140 break;
1142 else if (c == '-')
1144 if ((t = Fgetc (fp)) == '+')
1145 nag = 18;
1146 else if (t == '/')
1148 if ((i = Fgetc (fp)) == '+')
1149 nag = 17;
1150 else
1151 Ungetc (i, fp);
1153 break;
1155 else
1156 Ungetc (t, fp);
1158 break;
1162 *n = '\0';
1164 if (!nag)
1165 nag = (nags[0]) ? atoi (nags) : 0;
1167 if (!nag || nag < 0 || nag > 255)
1168 return;
1170 // FIXME -1 is because move_text() increments g->hindex. The NAG
1171 // annoatation isnt guaranteed to be after the move text in import format.
1172 for (i = 0; i < MAX_PGN_NAG; i++)
1174 if (g->hp[g->hindex - 1]->nag[i])
1175 continue;
1177 g->hp[g->hindex - 1]->nag[i] = nag;
1178 break;
1181 skip_leading_space (fp);
1185 * PGN move annotation.
1187 static int
1188 annotation_text (GAME g, FILE * fp, int terminator)
1190 int c, lastchar = 0;
1191 int len = 0;
1192 int hindex = pgn_history_total (g->hp) - 1;
1193 char buf[MAX_PGN_LINE_LEN], *a = buf;
1195 skip_leading_space (fp);
1197 while ((c = Fgetc (fp)) != EOF && c != terminator)
1199 if (c == '\n')
1200 c = ' ';
1202 if (isspace (c) && isspace (lastchar))
1203 continue;
1205 if (len + 1 == sizeof (buf))
1206 continue;
1208 *a++ = lastchar = c;
1209 len++;
1212 *a = '\0';
1214 if (hindex < 0)
1215 hindex = 0;
1218 * This annotation is before any move text or NAg-> Allocate a new move.
1220 if (!g->hp[hindex])
1222 if ((g->hp[hindex] = calloc (1, sizeof (HISTORY))) == NULL)
1223 return E_PGN_ERR;
1226 if ((g->hp[hindex]->comment = strdup (buf)) == NULL)
1227 return E_PGN_ERR;
1229 return E_PGN_OK;
1233 * PGN roster tag->
1235 static int
1236 tag_text (GAME g, FILE * fp)
1238 char name[LINE_MAX] = { 0 }, *n = name;
1239 char value[LINE_MAX] = { 0 }, *v = value;
1240 int c, i = 0;
1241 int lastchar = 0;
1242 char *tmp;
1244 skip_leading_space (fp);
1246 /* The tag name is up until the first whitespace. */
1247 while ((c = Fgetc (fp)) != EOF && !isspace (c))
1248 *n++ = c;
1250 *n = '\0';
1251 *name = toupper (*name);
1252 skip_leading_space (fp);
1254 /* The value is until the first closing bracket. */
1255 while ((c = Fgetc (fp)) != EOF && c != ']')
1257 if (i++ == '\0' && c == '\"')
1258 continue;
1260 if (c == '\n' || c == '\t')
1261 c = ' ';
1263 if (c == ' ' && lastchar == ' ')
1264 continue;
1266 lastchar = *v++ = c;
1269 *v = '\0';
1271 while (isspace (*--v))
1272 *v = '\0';
1274 if (*v == '\"')
1275 *v = '\0';
1277 if (value[0] == '\0')
1279 if (strcmp (name, "Result") == 0)
1280 value[0] = '*';
1281 else
1282 value[0] = '?';
1284 value[1] = '\0';
1287 tmp = remove_tag_escapes (value);
1288 strncpy (value, tmp, sizeof (value) - 1);
1289 free (tmp);
1292 * See eog_text() for an explanation.
1294 if (strcmp (name, "Result") == 0)
1296 if (strcmp (value, "1/2-1/2") == 0)
1298 if (pgn_tag_add (&g->tag, name, value) != E_PGN_OK)
1299 warn ("pgn_tag_add()");
1302 else
1304 if (pgn_tag_add (&g->tag, name, value) != E_PGN_OK)
1305 warn ("pgn_tag_add()");
1308 return 0;
1312 * PGN end-of-game marker.
1314 static int
1315 eog_text (GAME g, FILE * fp)
1317 int c, i = 0;
1318 char buf[8], *p = buf;
1320 while ((c = Fgetc (fp)) != EOF && !isspace (c) && i++ < sizeof (buf))
1321 *p++ = c;
1323 if (isspace (c))
1324 Ungetc (c, fp);
1326 *p = 0;
1328 if (strcmp (buf, "1-0") != 0 && strcmp (buf, "0-1") != 0 &&
1329 strcmp (buf, "1/2-1/2") != 0 && strcmp (buf, "*") != 0)
1330 return 1;
1333 * The eog marker in the move text may not match the actual game result.
1334 * We'll leave it up to the move validator to determine the result unless
1335 * the move validator cannot determine who won and the move text says it's
1336 * a draw.
1338 if (g->tag[6]->value[0] == '*' && strcmp ("1/2-1/2", buf) == 0)
1340 if (pgn_tag_add (&g->tag, (char *) "Result", buf) != E_PGN_OK)
1341 warn ("pgn_tag_add()");
1344 return 0;
1348 * Parse RAV text and keep track of g->hp. The 'o' argument is the board state
1349 * before the current move (.hindex) was parsed.
1351 static int read_file (FILE *);
1352 static int
1353 rav_text (GAME g, FILE * fp, int which, BOARD o)
1355 struct game_s tg;
1356 RAV *r;
1358 // Begin RAV for the current move.
1359 if (which == '(')
1361 if (!g->hindex)
1362 return 1;
1364 pgn_rav++; // For detecting parse errors.
1367 * Save the current game state for this RAV depth/level.
1369 if ((r = realloc (g->rav, (g->ravlevel + 1) * sizeof (RAV))) == NULL)
1371 warn ("realloc()");
1372 return 1;
1375 g->rav = pgn_rav_p = r;
1377 if ((g->rav[g->ravlevel].fen = pgn_game_to_fen (g, pgn_board)) == NULL)
1379 warn ("strdup()");
1380 return 1;
1383 g->rav[g->ravlevel].hp = g->hp;
1384 g->rav[g->ravlevel].hindex = g->hindex;
1385 memcpy (&tg, &(*g), sizeof (struct game_s));
1386 g->flags = g->oflags;
1387 memcpy (pgn_board, o, sizeof (BOARD));
1389 if ((g->hp[g->hindex - 1]->rav =
1390 calloc (1, sizeof (HISTORY *))) == NULL)
1392 warn ("calloc()");
1393 return 1;
1397 * pgn_history_add() will now append to the new history pointer that
1398 * is g->hp[previous_move]->rav.
1400 g->hp = g->hp[g->hindex - 1]->rav;
1403 * Reset. Will be restored later from 'tg' which is a local variable
1404 * so recursion is possible.
1406 g->hindex = 0;
1407 g->ravlevel++;
1410 * Undo move_text()'s switch.
1412 pgn_switch_turn (g);
1415 * Now continue as normal as if there were no RAV. Moves will be
1416 * parsed and appended to the new history pointer.
1418 if (read_file (fp))
1419 return 1;
1422 * read_file() has returned. This means that a RAV has ended by this
1423 * function returning -1 (see below). So we restore the game state
1424 * (tg) that was saved before calling read_file().
1426 pgn_board_init_fen (&tg, pgn_board, g->rav[tg.ravlevel].fen);
1427 free (g->rav[tg.ravlevel].fen);
1428 memcpy (&(*g), &tg, sizeof (struct game_s));
1429 g->rav = pgn_rav_p;
1432 * The end of a RAV. This makes the read_file() that called this function
1433 * return (see above and the switch statement in read_file()).
1435 else if (which == ')')
1437 pgn_rav--; // For detecting parse errors.
1438 return (pgn_rav < 0) ? 1 : -1;
1441 return 0;
1445 * FIXME
1446 * See pgn_board_init_fen(). Returns E_PGN_PARSE on parse error. 0 may be
1447 * returned on success when there is no move count in the FEN tag otherwise
1448 * the move count is returned.
1450 static int
1451 parse_fen_line (BOARD b, unsigned *flags, char *turn, char *ply, char *str)
1453 char *tmp;
1454 char line[LINE_MAX] = { 0 }, *s;
1455 int row = 8, col = 1;
1457 #ifdef DEBUG
1458 PGN_DUMP ("%s:%d: FEN line is '%s'\n", __FILE__, __LINE__, str);
1459 #endif
1460 strncpy (line, str, sizeof (line) - 1);
1461 s = line;
1462 pgn_reset_enpassant (b);
1464 while ((tmp = strsep (&s, "/")) != NULL)
1466 int n;
1468 if (!VALIDFILE (row))
1469 return E_PGN_PARSE;
1471 while (*tmp)
1473 if (*tmp == ' ')
1474 goto other;
1476 if (isdigit (*tmp))
1478 n = *tmp - '0';
1480 if (!VALIDFILE (n))
1481 return E_PGN_PARSE;
1483 for (; n; --n, col++)
1484 b[RANKTOBOARD (row)][FILETOBOARD (col)].icon =
1485 pgn_int_to_piece (WHITE, OPEN_SQUARE);
1487 else if (pgn_piece_to_int (*tmp) != -1)
1488 b[RANKTOBOARD (row)][FILETOBOARD (col++)].icon = *tmp;
1489 else
1490 return E_PGN_PARSE;
1492 tmp++;
1495 row--;
1496 col = 1;
1499 other:
1500 tmp++;
1502 switch (*tmp++)
1504 case 'b':
1505 *turn = BLACK;
1506 break;
1507 case 'w':
1508 *turn = WHITE;
1509 break;
1510 default:
1511 return E_PGN_PARSE;
1514 tmp++;
1516 while (*tmp && *tmp != ' ')
1518 switch (*tmp++)
1520 case 'K':
1521 SET_FLAG (*flags, GF_WK_CASTLE);
1522 break;
1523 case 'Q':
1524 SET_FLAG (*flags, GF_WQ_CASTLE);
1525 break;
1526 case 'k':
1527 SET_FLAG (*flags, GF_BK_CASTLE);
1528 break;
1529 case 'q':
1530 SET_FLAG (*flags, GF_BQ_CASTLE);
1531 break;
1532 case '-':
1533 break;
1534 default:
1535 return E_PGN_PARSE;
1539 tmp++;
1541 // En passant.
1542 if (*tmp != '-')
1544 if (!VALIDCOL (*tmp))
1545 return E_PGN_PARSE;
1546 col = *tmp++ - 'a';
1548 if (!VALIDROW (*tmp))
1549 return E_PGN_PARSE;
1550 row = 8 - atoi (tmp++);
1552 b[row][col].enpassant = 1;
1553 SET_FLAG (*flags, GF_ENPASSANT);
1555 else
1556 tmp++;
1558 if (*tmp)
1559 tmp++;
1561 if (*tmp)
1562 *ply = atoi (tmp);
1564 while (*tmp && isdigit (*tmp))
1565 tmp++;
1567 if (*tmp)
1568 tmp++;
1570 return E_PGN_OK;
1574 * It initializes the board (b) to the FEN tag (if found) and sets the
1575 * castling and enpassant info for the game 'g'. If 'fen' is set it should be
1576 * a fen tag and will be parsed rather than the game 'g'.tag FEN tag-> Returns
1577 * E_PGN_OK on success or if there was both a FEN and SetUp tag with the SetUp
1578 * tag set to 0. Returns E_PGN_PARSE if there was a FEN parse error, E_PGN_ERR
1579 * if there was no FEN tag or there was a SetUp tag with a value of 0. Returns
1580 * E_PGN_OK on success.
1582 pgn_error_t
1583 pgn_board_init_fen (GAME g, BOARD b, char *fen)
1585 int n = -1, i = -1;
1586 BOARD tmpboard;
1587 unsigned flags = 0;
1588 char turn = g->turn;
1589 char ply = 0;
1591 #ifdef DEBUG
1592 PGN_DUMP ("%s:%d: initializing board from FEN\n", __FILE__, __LINE__);
1593 #endif
1594 pgn_board_init (tmpboard);
1596 if (!fen)
1598 n = pgn_tag_find (g->tag, "Setup");
1599 i = pgn_tag_find (g->tag, "FEN");
1603 * If the FEN tag exists and there is no SetUp tag go ahead and parse it.
1604 * If there is a SetUp tag only parse the FEN tag if the value is 1.
1606 if ((n >= 0 && i >= 0 && atoi (g->tag[n]->value) == 1)
1607 || (i >= 0 && n == -1) || fen)
1609 if ((n = parse_fen_line (tmpboard, &flags, &turn, &ply,
1610 (fen) ? fen : g->tag[i]->value)) != E_PGN_OK)
1611 return E_PGN_PARSE;
1612 else
1614 memcpy (b, tmpboard, sizeof (BOARD));
1615 CLEAR_FLAG (g->flags, GF_WK_CASTLE);
1616 CLEAR_FLAG (g->flags, GF_WQ_CASTLE);
1617 CLEAR_FLAG (g->flags, GF_BK_CASTLE);
1618 CLEAR_FLAG (g->flags, GF_BQ_CASTLE);
1619 g->flags |= flags;
1620 g->turn = turn;
1621 g->ply = ply;
1624 else
1625 return (i >= 0 && n >= 0) ? E_PGN_OK : E_PGN_ERR;
1627 return E_PGN_OK;
1631 * Allocates a new game and increments 'gtotal'. 'gindex' is then set to the
1632 * new game. Returns E_PGN_ERR if there was a memory allocation error or
1633 * E_PGN_OK on success.
1635 pgn_error_t
1636 pgn_new_game ()
1638 GAME *g;
1639 GAME newg;
1640 int t = gtotal + 1;
1642 #ifdef DEBUG
1643 PGN_DUMP ("%s:%d: allocating new game\n", __FILE__, __LINE__);
1644 #endif
1645 gindex = t - 1;
1647 if ((g = realloc (game, t * sizeof (GAME))) == NULL)
1649 warn ("realloc()");
1650 return E_PGN_ERR;
1653 game = g;
1655 if ((newg = calloc (1, sizeof (struct game_s))) == NULL)
1657 warn ("calloc()");
1658 return E_PGN_ERR;
1661 game[gindex] = newg;
1663 if ((game[gindex]->hp = calloc (1, sizeof (HISTORY *))) == NULL)
1665 free (game[gindex]);
1666 warn ("calloc()");
1667 return E_PGN_ERR;
1670 game[gindex]->hp[0] = NULL;
1671 game[gindex]->history = game[gindex]->hp;
1672 game[gindex]->side = game[gindex]->turn = WHITE;
1673 SET_FLAG (game[gindex]->flags, GF_WK_CASTLE | GF_WQ_CASTLE | GF_WQ_CASTLE |
1674 GF_BK_CASTLE | GF_BQ_CASTLE);
1675 pgn_board_init (pgn_board);
1676 set_default_tags (game[gindex]);
1677 gtotal = t;
1678 return E_PGN_OK;
1681 static int
1682 read_file (FILE * fp)
1684 #ifdef DEBUG
1685 char buf[LINE_MAX] = { 0 }, *p = buf;
1686 #endif
1687 int c = 0;
1688 int parse_error = 0;
1689 BOARD old;
1691 while (1)
1693 int nextchar = 0;
1694 int lastchar = c;
1695 int n;
1698 * A parse error may have occured at EOF.
1700 if (parse_error)
1702 pgn_ret = E_PGN_PARSE;
1704 if (!game)
1705 pgn_new_game ();
1707 SET_FLAG (game[gindex]->flags, GF_PERROR);
1710 if ((c = Fgetc (fp)) == EOF)
1712 if (feof (fp))
1713 break;
1715 if (ferror (fp))
1717 clearerr (fp);
1718 continue;
1722 if (!isascii (c))
1724 parse_error = 1;
1725 continue;
1728 if (c == '\015')
1729 continue;
1731 nextchar = Fgetc (fp);
1732 Ungetc (nextchar, fp);
1735 * If there was a move text parsing error, keep reading until the end
1736 * of the current game discarding the data.
1738 if (parse_error)
1740 pgn_ret = E_PGN_PARSE;
1742 if (game[gindex]->ravlevel)
1743 return 1;
1745 if (pgn_config.stop)
1746 return E_PGN_PARSE;
1748 if (c == '\n' && (nextchar == '\n' || nextchar == '\015'))
1750 parse_error = 0;
1751 nulltags = 1;
1752 tag_section = 0;
1754 else
1755 continue;
1758 // New game reached.
1759 if (c == '\n' && (nextchar == '\n' || nextchar == '\015'))
1761 if (tag_section)
1762 continue;
1764 nulltags = 1;
1765 tag_section = 0;
1766 continue;
1770 * PGN: Application comment. The '%' must be on the first column of
1771 * the line. The comment continues until the end of the current line.
1773 if (c == '%')
1775 if (lastchar == '\n' || lastchar == 0)
1777 while ((c = Fgetc (fp)) != EOF && c != '\n');
1778 continue;
1781 // Not sure what to do here.
1784 if (isspace (c))
1785 continue;
1787 // PGN: Reserved.
1788 if (c == '<' || c == '>')
1789 continue;
1792 * PGN: Recurrsive Annotated Variation. Read rav_text() for more
1793 * info.
1795 if (c == '(' || c == ')')
1797 switch (rav_text (game[gindex], fp, c, old))
1799 case -1:
1801 * This is the end of the current RAV. This function has
1802 * been called from rav_text(). Returning from this point
1803 * will put us back in rav_text().
1805 if (game[gindex]->ravlevel > 0)
1806 return pgn_ret;
1809 * We're back at the root move. Continue as normal.
1811 break;
1812 case 1:
1813 parse_error = 1;
1814 continue;
1815 default:
1817 * Continue processing-> Probably the root move.
1819 break;
1822 if (!game[gindex]->ravlevel && pgn_rav)
1823 parse_error = 1;
1825 continue;
1828 // PGN: Numeric Annotation Glyph.
1829 if (c == '$' || c == '!' || c == '?' || c == '+' || c == '-' ||
1830 c == '~' || c == '=')
1832 Ungetc (c, fp);
1833 nag_text (game[gindex], fp);
1834 continue;
1838 * PGN: Annotation. The ';' comment continues until the end of the
1839 * current line. The '{' type comment continues until a '}' is
1840 * reached.
1842 if (c == '{' || c == ';')
1844 annotation_text (game[gindex], fp, (c == '{') ? '}' : '\n');
1845 continue;
1848 // PGN: Roster tag->
1849 if (c == '[')
1851 // First roster tag found. Initialize the data structures.
1852 if (!tag_section)
1854 nulltags = 0;
1855 tag_section = 1;
1857 if (gtotal && pgn_history_total (game[gindex]->hp))
1858 game[gindex]->hindex =
1859 pgn_history_total (game[gindex]->hp) - 1;
1861 if (pgn_new_game () != E_PGN_OK)
1863 pgn_ret = E_PGN_ERR;
1864 break;
1867 memcpy (old, pgn_board, sizeof (BOARD));
1870 if (tag_text (game[gindex], fp))
1871 parse_error = 1; // FEN tag parse error.
1873 continue;
1876 // PGN: End-of-game markers.
1877 if ((isdigit (c) && (nextchar == '-' || nextchar == '/')) || c == '*')
1879 Ungetc (c, fp);
1881 if (eog_text (game[gindex], fp))
1883 parse_error = 1;
1884 continue;
1887 nulltags = 1;
1888 tag_section = 0;
1890 if (!game[gindex]->done_fen_tag)
1892 if (pgn_tag_find (game[gindex]->tag, "FEN") != -1 &&
1893 pgn_board_init_fen (game[gindex], pgn_board, NULL))
1895 parse_error = 1;
1896 continue;
1899 game[gindex]->pgn_fen_tag =
1900 pgn_tag_find (game[gindex]->tag, "FEN");
1901 game[gindex]->done_fen_tag = 1;
1904 continue;
1907 // PGN: Move text.
1908 if ((isdigit (c) && c != '0') || VALIDCOL (c) || c == 'N' || c == 'K'
1909 || c == 'Q' || c == 'B' || c == 'R' || c == 'P' || c == 'O')
1911 Ungetc (c, fp);
1913 // PGN: If a FEN tag exists, initialize the board to the value.
1914 if (tag_section)
1916 if (pgn_tag_find (game[gindex]->tag, "FEN") != E_PGN_ERR &&
1917 (n = pgn_board_init_fen (game[gindex], pgn_board,
1918 NULL)) == E_PGN_PARSE)
1920 parse_error = 1;
1921 continue;
1924 game[gindex]->done_fen_tag = 1;
1925 game[gindex]->pgn_fen_tag =
1926 pgn_tag_find (game[gindex]->tag, "FEN");
1927 tag_section = 0;
1931 * PGN: Import format doesn't require a roster tag section. We've
1932 * arrived to the move text section without any tags so we
1933 * initialize a new game which set's the default tags and any tags
1934 * from the configuration file.
1936 if (nulltags)
1938 if (gtotal)
1939 game[gindex]->hindex =
1940 pgn_history_total (game[gindex]->hp) - 1;
1942 if (pgn_new_game () != E_PGN_OK)
1944 pgn_ret = E_PGN_ERR;
1945 break;
1948 memcpy (old, pgn_board, sizeof (BOARD));
1949 nulltags = 0;
1952 memcpy (old, pgn_board, sizeof (BOARD));
1954 if (move_text (game[gindex], fp))
1956 if (pgn_tag_add (&game[gindex]->tag, (char *) "Result",
1957 (char *) "*") == E_PGN_ERR)
1959 warn ("pgn_tag_add()");
1960 pgn_ret = E_PGN_ERR;
1963 SET_FLAG (game[gindex]->flags, GF_PERROR);
1964 parse_error = 1;
1967 continue;
1970 #ifdef DEBUG
1971 *p++ = c;
1973 PGN_DUMP ("%s:%d: unparsed: '%s'\n", __FILE__, __LINE__, buf);
1975 if (strlen (buf) + 1 == sizeof (buf))
1976 bzero (buf, sizeof (buf));
1977 #endif
1979 continue;
1982 return pgn_ret;
1986 * Parses a PGN_FILE which was opened with pgn_open(). If 'pgn' is NULL then a
1987 * single empty game will be allocated. If there is a parsing error
1988 * E_PGN_PARSE is returned, if there was a memory allocation error E_PGN_ERR
1989 * is returned, otherwise E_PGN_OK is returned and the global 'gindex' is set
1990 * to the last parsed game in the file and the global 'gtotal' is set to the
1991 * total number of games in the file. The file should be closed with
1992 * pgn_close() after processing.
1994 pgn_error_t
1995 pgn_parse (PGN_FILE * pgn)
1997 int i;
1999 if (!pgn)
2001 reset_game_data ();
2002 pgn_ret = pgn_new_game ();
2003 goto done;
2006 reset_game_data ();
2007 nulltags = 1;
2008 fseek (pgn->fp, 0, SEEK_END);
2009 pgn_fsize = ftell (pgn->fp);
2010 fseek (pgn->fp, 0, SEEK_SET);
2011 #ifdef DEBUG
2012 PGN_DUMP ("%s:%d: BEGIN parsing->..\n", __FILE__, __LINE__);
2013 #endif
2014 parsing_file = 1;
2015 pgn_ret = read_file (pgn->fp);
2016 parsing_file = 0;
2018 #ifdef DEBUG
2019 PGN_DUMP ("%s:%d: END parsing->..\n", __FILE__, __LINE__);
2020 #endif
2022 if (gtotal < 1)
2023 pgn_new_game ();
2025 done:
2026 gtotal = gindex + 1;
2028 for (i = 0; i < gtotal; i++)
2030 game[i]->history = game[i]->hp;
2031 game[i]->hindex = pgn_history_total (game[i]->hp);
2034 return pgn_ret;
2038 * Escape '"' and '\' in tag values.
2040 static char *
2041 pgn_tag_add_escapes (const char *str)
2043 int i, n;
2044 int len = strlen (str);
2045 char buf[MAX_PGN_LINE_LEN] = { 0 };
2047 for (i = n = 0; i < len; i++, n++)
2049 switch (str[i])
2051 case '\\':
2052 case '\"':
2053 buf[n++] = '\\';
2054 break;
2055 default:
2056 break;
2059 buf[n] = str[i];
2062 buf[n] = '\0';
2063 return buf[0] ? strdup (buf) : NULL;
2066 static void
2067 Fputc (int c, FILE * fp, int *len)
2069 int i = *len;
2071 if (c != '\n' && i + 1 > 80)
2072 Fputc ('\n', fp, &i);
2074 if (pgn_lastc == '\n' && c == ' ')
2076 *len = pgn_mpl = 0;
2077 return;
2080 if (fputc (c, fp) == EOF)
2081 warn ("PGN Save");
2082 else
2084 if (c == '\n')
2085 i = pgn_mpl = 0;
2086 else
2087 i++;
2090 *len = i;
2091 pgn_lastc = c;
2094 static void
2095 putstring (FILE * fp, char *str, int *len)
2097 char *p;
2099 for (p = str; *p; p++)
2101 int n = 0;
2103 while (*p && *p != ' ')
2104 n++, p++;
2106 if (n + *len > 80)
2107 Fputc ('\n', fp, len);
2109 p -= n;
2110 Fputc (*p, fp, len);
2115 * See pgn_write() for more info.
2117 static void
2118 write_comments_and_nag (FILE * fp, HISTORY * h, int *len)
2120 int i;
2121 char tmp[16];
2123 #ifdef DEBUG
2124 PGN_DUMP ("%s:%d: writing comments and nag\n", __FILE__, __LINE__);
2125 #endif
2127 for (i = 0; i < MAX_PGN_NAG; i++)
2129 if (h->nag[i])
2131 Fputc (' ', fp, len);
2132 Fputc ('$', fp, len);
2133 putstring (fp, itoa (h->nag[i], tmp), len);
2137 if (h->comment)
2139 Fputc ('\n', fp, len);
2140 putstring (fp, (char *) " {", len);
2141 putstring (fp, h->comment, len);
2142 Fputc ('}', fp, len);
2146 static void
2147 write_move_text (FILE * fp, HISTORY * h, int *len)
2149 Fputc (' ', fp, len);
2150 putstring (fp, h->move, len);
2152 if (!pgn_config.reduced)
2153 write_comments_and_nag (fp, h, len);
2156 static void
2157 write_all_move_text (FILE * fp, HISTORY ** h, int m, int *len)
2159 int i;
2160 HISTORY **hp = NULL;
2161 char tmp[16];
2163 for (i = 0; h[i]; i++)
2165 if (pgn_write_turn == WHITE)
2167 if (pgn_config.mpl && pgn_mpl == pgn_config.mpl)
2169 pgn_mpl = 0;
2170 Fputc ('\n', fp, len);
2173 if (m > 1 && i > 0)
2174 Fputc (' ', fp, len);
2176 if (strlen (itoa (m, tmp)) + 1 + *len > 80)
2177 Fputc ('\n', fp, len);
2179 putstring (fp, itoa (m, tmp), len);
2180 Fputc ('.', fp, len);
2181 pgn_mpl++;
2184 write_move_text (fp, h[i], len);
2186 if (!pgn_config.reduced && h[i]->rav)
2188 int oldm = m;
2189 int oldturn = pgn_write_turn;
2191 ravlevel++;
2192 putstring (fp, (char *) " (", len);
2195 * If it's WHITE's turn the move number will be added above after
2196 * the call to write_all_move_text() below.
2198 if (pgn_write_turn == BLACK)
2200 putstring (fp, itoa (m, tmp), len);
2201 putstring (fp, (char *) "...", len);
2204 hp = h[i]->rav;
2205 write_all_move_text (fp, hp, m, len);
2206 m = oldm;
2207 pgn_write_turn = oldturn;
2208 putstring (fp, (char *) ")", len);
2209 ravlevel--;
2211 if (ravlevel && h[i + 1])
2212 Fputc (' ', fp, len);
2214 if (h[i + 1] && !ravlevel)
2215 Fputc (' ', fp, len);
2217 if (pgn_write_turn == WHITE && h[i + 1])
2219 putstring (fp, itoa (m, tmp), len);
2220 putstring (fp, (char *) "...", len);
2224 if (pgn_write_turn == BLACK)
2225 m++;
2227 pgn_write_turn = (pgn_write_turn == WHITE) ? BLACK : WHITE;
2231 static char *
2232 compression_cmd (const char *filename, int expand)
2234 char command[PATH_MAX];
2235 int len = strlen (filename);
2237 if (filename[len - 4] == '.' && filename[len - 3] == 'z' &&
2238 filename[len - 2] == 'i' && filename[len - 1] == 'p' &&
2239 filename[len] == '\0')
2241 if (expand)
2242 snprintf (command, sizeof (command), "unzip -p %s 2>/dev/null",
2243 filename);
2244 else
2245 snprintf (command, sizeof (command), "zip -9 >%s 2>/dev/null",
2246 filename);
2248 return strdup (command);
2250 else if (filename[len - 3] == '.' && filename[len - 2] == 'g' &&
2251 filename[len - 1] == 'z' && filename[len] == '\0')
2253 if (expand)
2254 snprintf (command, sizeof (command), "gzip -dc %s", filename);
2255 else
2256 snprintf (command, sizeof (command), "gzip -c 1>%s", filename);
2258 return strdup (command);
2260 else if (filename[len - 2] == '.' && filename[len - 1] == 'Z' &&
2261 filename[len] == '\0')
2263 if (expand)
2264 snprintf (command, sizeof (command), "uncompress -c %s", filename);
2265 else
2266 snprintf (command, sizeof (command), "compress -c 1>%s", filename);
2268 return strdup (command);
2270 else if ((filename[len - 4] == '.' && filename[len - 3] == 'b' &&
2271 filename[len - 2] == 'z' && filename[len - 1] == '2' &&
2272 filename[len] == '\0') || (filename[len - 3] == '.' &&
2273 filename[len - 2] == 'b'
2274 && filename[len - 1] == 'z'
2275 && filename[len] == '\0'))
2277 if (expand)
2278 snprintf (command, sizeof (command), "bzip2 -dc %s", filename);
2279 else
2280 snprintf (command, sizeof (command), "bzip2 -zc 1>%s", filename);
2282 return strdup (command);
2285 return NULL;
2288 static int
2289 copy_file (FILE * fp, const char *dst)
2291 FILE *ofp;
2292 char line[LINE_MAX];
2293 char *cmd = compression_cmd (dst, 0);
2295 if ((ofp = popen (cmd, "w")) == NULL)
2297 free (cmd);
2298 return 1;
2301 free (cmd);
2302 fseek (fp, 0, SEEK_SET);
2304 while ((fgets (line, sizeof (line), fp)) != NULL)
2305 fprintf (ofp, "%s", line);
2307 pclose (ofp);
2308 return 0;
2312 * Closes and free's a PGN file handle.
2314 pgn_error_t
2315 pgn_close (PGN_FILE * pgn)
2317 if (!pgn)
2318 return E_PGN_INVALID;
2320 if (pgn->pipe)
2323 * Appending to a compressed file.
2325 if (pgn->tmpfile)
2327 if (copy_file (pgn->fp, pgn->filename))
2328 return E_PGN_ERR;
2330 fclose (pgn->fp);
2331 unlink (pgn->tmpfile);
2332 free (pgn->tmpfile);
2334 else
2335 pclose (pgn->fp);
2337 else
2338 fclose (pgn->fp);
2340 free (pgn->filename);
2341 free (pgn);
2342 return E_PGN_OK;
2346 * Opens a file 'filename' with the given 'mode'. 'mode' should be "r" for
2347 * reading, "w" for writing (will truncate if the file exists) or "a" for
2348 * appending to an existing file or creating a new one. Returns E_PGN_OK on
2349 * success and sets 'result' to a file handle for use will the other file
2350 * functions or E_PGN_ERR if there is an error opening the file in which case
2351 * errno will be set to the error or E_PGN_INVALID if 'mode' is an invalid
2352 * mode or if 'filename' is not a regular file.
2354 pgn_error_t
2355 pgn_open (const char *filename, const char *mode, PGN_FILE ** result)
2357 FILE *fp = NULL, *tfp = NULL, *pfp = NULL;
2358 char buf[PATH_MAX], *p;
2359 char *cmd = NULL;
2360 PGN_FILE *pgn;
2361 int m;
2362 int append = 0;
2363 struct stat st;
2364 int ret = E_PGN_ERR;
2367 #ifdef DEBUG
2368 PGN_DUMP ("%s:%d: BEGIN opening %s\n", __FILE__, __LINE__, filename);
2369 #endif
2371 if (!filename || !mode)
2372 return E_PGN_INVALID;
2374 if (strcmp (mode, "r") == 0)
2375 m = 1;
2376 else if (strcmp (mode, "w") == 0)
2377 m = 0;
2378 else if (strcmp (mode, "a") == 0)
2380 m = 0;
2381 append = 1;
2383 else
2385 return E_PGN_INVALID;
2388 pgn = calloc (1, sizeof (PGN_FILE));
2390 if (!pgn)
2391 goto fail;
2393 if (strcmp (filename, "-") != 0)
2395 if (m && access (filename, R_OK) == -1)
2396 goto fail;
2398 if (stat (filename, &st) == -1 && !m && errno != ENOENT)
2399 goto fail;
2401 if (m && !S_ISREG (st.st_mode))
2403 ret = E_PGN_INVALID;
2404 goto fail;
2407 if ((cmd = compression_cmd (filename, m)) != NULL)
2409 char tmp[21];
2410 int fd;
2412 #ifdef HAVE_MKSTEMP
2413 snprintf (tmp, sizeof (tmp), "cboard.XXXXXX");
2414 #else
2415 if (tmpnam (tmp) == NULL)
2416 goto fail;
2417 #endif
2419 pgn->pipe = 1;
2421 if (append && access (filename, R_OK) == 0)
2423 #ifdef HAVE_MKSTEMP
2424 mode_t tmode;
2425 #endif
2427 free (cmd);
2428 cmd = compression_cmd (filename, 1);
2429 if ((pfp = popen (cmd, "r")) == NULL)
2430 goto fail;
2432 #ifdef HAVE_MKSTEMP
2433 tmode = umask (600);
2434 fd = mkstemp (tmp);
2435 umask (tmode);
2436 if (fd == -1)
2437 goto fail;
2438 #else
2439 if ((fd = open (tmp, O_RDWR | O_EXCL | O_CREAT, 0600)) == -1)
2440 goto fail;
2441 #endif
2442 if ((tfp = fdopen (fd, "a+")) == NULL)
2443 goto fail;
2445 while ((p = fgets (buf, sizeof (buf), pfp)) != NULL)
2446 fprintf (tfp, "%s", p);
2448 pclose (pfp);
2449 pfp = NULL;
2450 pgn->fp = tfp;
2451 pgn->tmpfile = strdup (tmp);
2452 goto done;
2455 if ((pfp = popen (cmd, m ? "r" : "w")) == NULL)
2456 goto fail;
2458 if (m)
2460 #ifdef HAVE_MKSTEMP
2461 mode_t tmode = umask (600);
2463 fd = mkstemp (tmp);
2464 umask (tmode);
2465 if (fd == -1)
2466 goto fail;
2467 #else
2468 if ((fd =
2469 open (tmp, O_RDWR | O_EXCL | O_CREAT | O_TRUNC,
2470 0600)) == -1)
2471 goto fail;
2472 #endif
2473 if ((tfp = fdopen (fd, "w+")) == NULL)
2474 goto fail;
2476 while ((p = fgets (buf, sizeof (buf), pfp)) != NULL)
2477 fprintf (tfp, "%s", p);
2479 pclose (pfp);
2480 pfp = NULL;
2481 pgn->fp = tfp;
2482 pgn->tmpfile = strdup (tmp);
2484 else
2485 pgn->fp = pfp;
2487 else
2489 if ((fp = fopen (filename, mode)) == NULL)
2490 goto fail;
2492 pgn->fp = fp;
2495 else
2496 pgn->fp = stdout;
2498 done:
2499 if (*filename != '/')
2501 if (getcwd (buf, sizeof (buf)) == NULL)
2503 if (pgn->tmpfile)
2504 free (pgn->tmpfile);
2506 goto fail;
2509 asprintf (&p, "%s/%s", buf, filename);
2510 pgn->filename = p;
2512 else
2513 pgn->filename = strdup (filename);
2515 free (cmd);
2516 *result = pgn;
2517 return E_PGN_OK;
2519 fail:
2520 if (fp)
2521 fclose (fp);
2523 if (pfp)
2524 pclose (pfp);
2526 free (cmd);
2527 free (pgn);
2528 return ret;
2532 * Returns E_PGN_OK if 'filename' is a recognized compressed filetype or
2533 * E_PGN_ERR if not.
2535 pgn_error_t
2536 pgn_is_compressed (const char *filename)
2538 char *s = compression_cmd (filename, 0);
2539 pgn_error_t e = s ? E_PGN_OK : E_PGN_ERR;
2541 free (s);
2542 return e;
2546 * Gets the value of config flag 'f'. The next argument should be a pointer of
2547 * the config type which is set to the value of 'f'. Returns E_PGN_ERR if 'f'
2548 * is an invalid flag or E_PGN_OK on success.
2550 pgn_error_t
2551 pgn_config_get (pgn_config_flag f, ...)
2553 va_list ap;
2554 int *intval;
2555 long *longval;
2556 pgn_progress *progress;
2558 va_start (ap, f);
2560 switch (f)
2562 case PGN_STRICT_CASTLING:
2563 intval = va_arg (ap, int *);
2564 *intval = pgn_config.strict_castling;
2565 va_end (ap);
2566 return E_PGN_OK;
2567 case PGN_REDUCED:
2568 intval = va_arg (ap, int *);
2569 *intval = pgn_config.reduced;
2570 va_end (ap);
2571 return E_PGN_OK;
2572 case PGN_MPL:
2573 intval = va_arg (ap, int *);
2574 *intval = pgn_config.mpl;
2575 va_end (ap);
2576 return E_PGN_OK;
2577 case PGN_STOP_ON_ERROR:
2578 intval = va_arg (ap, int *);
2579 *intval = pgn_config.stop;
2580 va_end (ap);
2581 return E_PGN_OK;
2582 case PGN_PROGRESS:
2583 longval = va_arg (ap, long *);
2584 *longval = pgn_config.stop;
2585 va_end (ap);
2586 return E_PGN_OK;
2587 case PGN_PROGRESS_FUNC:
2588 progress = va_arg (ap, pgn_progress *);
2589 *progress = pgn_config.pfunc;
2590 va_end (ap);
2591 return E_PGN_OK;
2592 #ifdef DEBUG
2593 case PGN_DEBUG:
2594 intval = va_arg (ap, int *);
2595 *intval = dumptofile;
2596 va_end (ap);
2597 return E_PGN_OK;
2598 #endif
2599 default:
2600 break;
2603 return E_PGN_ERR;
2607 * Sets config flag 'f' to the next argument. Returns E_PGN_OK on success or
2608 * E_PGN_ERR if 'f' is an invalid flag or E_PGN_INVALID if 'val' is an invalid
2609 * flag value.
2611 pgn_error_t
2612 pgn_config_set (pgn_config_flag f, ...)
2614 va_list ap;
2615 int n;
2616 int ret = E_PGN_OK;
2618 va_start (ap, f);
2620 switch (f)
2622 case PGN_REDUCED:
2623 n = va_arg (ap, int);
2625 if (n != 1 && n != 0)
2627 ret = E_PGN_INVALID;
2628 break;
2631 pgn_config.reduced = n;
2632 break;
2633 case PGN_MPL:
2634 n = va_arg (ap, int);
2636 if (n < 0)
2638 ret = E_PGN_INVALID;
2639 break;
2642 pgn_config.mpl = n;
2643 break;
2644 case PGN_STOP_ON_ERROR:
2645 n = va_arg (ap, int);
2647 if (n != 1 && n != 0)
2649 ret = E_PGN_INVALID;
2650 break;
2653 pgn_config.stop = n;
2654 break;
2655 case PGN_PROGRESS:
2656 n = va_arg (ap, long);
2657 pgn_config.progress = n;
2658 break;
2659 case PGN_PROGRESS_FUNC:
2660 pgn_config.pfunc = va_arg (ap, pgn_progress);
2661 break;
2662 case PGN_STRICT_CASTLING:
2663 n = va_arg (ap, int);
2664 pgn_config.strict_castling = n;
2665 break;
2666 #ifdef DEBUG
2667 case PGN_DEBUG:
2668 n = va_arg (ap, int);
2669 dumptofile = (n > 0) ? 1 : 0;
2670 break;
2671 #endif
2672 default:
2673 ret = E_PGN_ERR;
2674 break;
2677 va_end (ap);
2678 return ret;
2682 * Writes a PGN formatted game 'g' to a file which was opened with pgn_open().
2683 * See 'pgn_config_flag' for output options. Returns E_PGN_ERR if there was a
2684 * memory allocation or write error and E_PGN_OK on success. It is important
2685 * to use pgn_close() afterwards if the file is a recognized compressed file
2686 * type otherwise the created temporary file wont be copied to the destination
2687 * filename.
2689 pgn_error_t
2690 pgn_write (PGN_FILE * pgn, GAME g)
2692 int i;
2693 int len = 0;
2695 if (!pgn)
2696 return E_PGN_ERR;
2698 pgn_write_turn = (TEST_FLAG (g->flags, GF_BLACK_OPENING)) ? BLACK : WHITE;
2699 pgn_tag_sort (g->tag);
2701 #ifdef DEBUG
2702 PGN_DUMP ("%s:%d: writing tag section\n", __FILE__, __LINE__);
2703 #endif
2705 for (i = 0; g->tag[i]; i++)
2707 struct tm tp;
2708 char tbuf[11] = { 0 };
2709 char *tmp;
2710 char *escaped;
2712 if (pgn_config.reduced && i == 7)
2713 break;
2715 if (strcmp (g->tag[i]->name, "Date") == 0)
2717 memset (&tp, 0, sizeof (struct tm));
2719 if (strptime (g->tag[i]->value, PGN_TIME_FORMAT, &tp) != NULL)
2721 len = strftime (tbuf, sizeof (tbuf), PGN_TIME_FORMAT, &tp) + 1;
2723 if ((tmp = strdup (tbuf)) == NULL)
2724 return E_PGN_ERR;
2726 free (g->tag[i]->value);
2727 g->tag[i]->value = tmp;
2730 else if (strcmp (g->tag[i]->name, "Event") == 0)
2732 if (g->tag[i]->value[0] == '\0')
2734 if ((tmp = strdup ("?")) == NULL)
2735 return E_PGN_ERR;
2737 free (g->tag[i]->value);
2738 g->tag[i]->value = tmp;
2741 else if (strcmp (g->tag[i]->name, "Site") == 0)
2743 if (g->tag[i]->value[0] == '\0')
2745 if ((tmp = strdup ("?")) == NULL)
2746 return E_PGN_ERR;
2748 free (g->tag[i]->value);
2749 g->tag[i]->value = tmp;
2752 else if (strcmp (g->tag[i]->name, "Round") == 0)
2754 if (g->tag[i]->value[0] == '\0')
2756 if ((tmp = strdup ("?")) == NULL)
2757 return E_PGN_ERR;
2759 free (g->tag[i]->value);
2760 g->tag[i]->value = tmp;
2763 else if (strcmp (g->tag[i]->name, "Result") == 0)
2765 if (g->tag[i]->value[0] == '\0')
2767 if ((tmp = strdup ("*")) == NULL)
2768 return E_PGN_ERR;
2770 free (g->tag[i]->value);
2771 g->tag[i]->value = tmp;
2774 else if (strcmp (g->tag[i]->name, "Black") == 0)
2776 if (g->tag[i]->value[0] == '\0')
2778 if ((tmp = strdup ("?")) == NULL)
2779 return E_PGN_ERR;
2781 free (g->tag[i]->value);
2782 g->tag[i]->value = tmp;
2785 else if (strcmp (g->tag[i]->name, "White") == 0)
2787 if (g->tag[i]->value[0] == '\0')
2789 if ((tmp = strdup ("?")) == NULL)
2790 return E_PGN_ERR;
2792 free (g->tag[i]->value);
2793 g->tag[i]->value = tmp;
2797 escaped = pgn_tag_add_escapes (g->tag[i]->value);
2798 fprintf (pgn->fp, "[%s \"%s\"]\n", g->tag[i]->name,
2799 (g->tag[i]->value && g->tag[i]->value[0]) ? escaped : "");
2800 free (escaped);
2803 #ifdef DEBUG
2804 PGN_DUMP ("%s:%d: writing move section\n", __FILE__, __LINE__);
2805 #endif
2806 Fputc ('\n', pgn->fp, &len);
2807 g->hp = g->history;
2808 ravlevel = pgn_mpl = 0;
2810 if (pgn_history_total (g->hp) && pgn_write_turn == BLACK)
2811 putstring (pgn->fp, (char *) "1...", &len);
2813 write_all_move_text (pgn->fp, g->hp, 1, &len);
2815 Fputc (' ', pgn->fp, &len);
2816 putstring (pgn->fp, g->tag[6]->value, &len);
2817 putstring (pgn->fp, (char *) "\n\n", &len);
2819 if (!pgn_config.reduced)
2820 CLEAR_FLAG (g->flags, GF_PERROR);
2822 return E_PGN_OK;
2826 * Clears the enpassant flag for all positions on board 'b'. Returns nothing.
2828 void
2829 pgn_reset_enpassant (BOARD b)
2831 int r, c;
2833 #ifdef DEBUG
2834 PGN_DUMP ("%s:%d: resetting enpassant\n", __FILE__, __LINE__);
2835 #endif
2837 for (r = 0; r < 8; r++)
2839 for (c = 0; c < 8; c++)
2840 b[r][c].enpassant = 0;