Update copyright year.
[cboard.git] / libchess / pgn.c
blob7074be548560fe30b5f78df2c2070b96205ef35c
1 /* vim:tw=78:ts=8:sw=4:set ft=c: */
2 /*
3 Copyright (C) 2002-2024 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 GAME *game;
63 int gindex, gtotal;
64 struct pgn_config_s pgn_config;
65 int parsing_file;
67 #ifdef __linux__
68 extern char *strptime (const char *, const char *, struct tm *);
69 #endif
71 static int
72 Fgetc (FILE * fp)
74 int c;
76 if ((c = fgetc (fp)) != EOF)
78 if (pgn_config.progress && pgn_config.pfunc)
80 if (!(ftell (fp) % pgn_config.progress))
81 pgn_config.pfunc (pgn_fsize, ftell (fp));
85 return c;
88 static int
89 Ungetc (int c, FILE * fp)
91 return ungetc (c, fp);
94 const char *
95 pgn_version ()
97 return "libchess " PACKAGE_VERSION;
100 static char *
101 trim (char *str)
103 int i = 0;
105 if (!str)
106 return NULL;
108 while (isspace (*str))
109 str++;
111 for (i = strlen (str) - 1; isspace (str[i]); i--)
112 str[i] = 0;
114 return str;
117 static char *
118 itoa (long n, char *buf)
120 sprintf (buf, "%li", n);
121 return buf;
125 * Clears the valid move flag for all positions on board 'b'. Returns nothing.
127 void
128 pgn_reset_valid_moves (BOARD b)
130 int row, col;
132 #ifdef DEBUG
133 PGN_DUMP ("%s:%d: resetting valid moves\n", __FILE__, __LINE__);
134 #endif
136 for (row = 0; row < 8; row++)
138 for (col = 0; col < 8; col++)
139 b[row][col].valid = 0;
144 * Toggles g->turn. Returns nothing.
146 void
147 pgn_switch_turn (GAME g)
149 g->turn = (g->turn == WHITE) ? BLACK : WHITE;
153 * Toggles g->side and switches the White and Black roster tags. Returns
154 * nothing.
156 void
157 pgn_switch_side (GAME g, int t)
159 if (t)
161 char *w = g->tag[4]->value;
163 g->tag[4]->value = g->tag[5]->value;
164 g->tag[5]->value = w;
167 g->side = !g->side;
171 * Creates a FEN tag from the current game 'g', history move (g->hindex) and
172 * board 'b'. Returns a FEN tag.
174 char *
175 pgn_game_to_fen (GAME g, BOARD b)
177 char tmp[16];
178 int row, col;
179 int i;
180 char buf[MAX_PGN_LINE_LEN] = { 0 }, *p;
181 int oldturn = g->turn;
182 char enpassant[3] = { 0 }, *e;
183 int castle = 0;
185 #ifdef DEBUG
186 PGN_DUMP ("%s:%d: creating FEN tag\n", __FILE__, __LINE__);
187 #endif
189 for (i = pgn_history_total (g->hp); i >= g->hindex - 1; i--)
190 pgn_switch_turn (g);
192 p = buf;
194 for (row = 0; row < 8; row++)
196 int count = 0;
198 for (col = 0; col < 8; col++)
200 if (b[row][col].enpassant)
202 b[row][col].icon = pgn_int_to_piece (WHITE, OPEN_SQUARE);
203 e = enpassant;
204 *e++ = 'a' + col;
205 *e++ = ('0' + 8) - row;
206 *e = 0;
209 if (pgn_piece_to_int (b[row][col].icon) == OPEN_SQUARE)
211 count++;
212 continue;
215 if (count)
217 *p++ = '0' + count;
218 count = 0;
221 *p++ = b[row][col].icon;
222 *p = 0;
225 if (count)
227 *p++ = '0' + count;
228 count = 0;
231 *p++ = '/';
234 --p;
235 *p++ = ' ';
236 *p++ = (g->turn == WHITE) ? 'w' : 'b';
237 *p++ = ' ';
239 if (TEST_FLAG (g->flags, GF_WK_CASTLE) && pgn_piece_to_int (b[7][7].icon) ==
240 ROOK && isupper (b[7][7].icon) && pgn_piece_to_int (b[7][4].icon) ==
241 KING && isupper (b[7][4].icon))
243 *p++ = 'K';
244 castle = 1;
247 if (TEST_FLAG (g->flags, GF_WQ_CASTLE) && pgn_piece_to_int (b[7][0].icon) ==
248 ROOK && isupper (b[7][0].icon) && pgn_piece_to_int (b[7][4].icon) ==
249 KING && isupper (b[7][4].icon))
251 *p++ = 'Q';
252 castle = 1;
255 if (TEST_FLAG (g->flags, GF_BK_CASTLE) && pgn_piece_to_int (b[0][7].icon) ==
256 ROOK && islower (b[0][7].icon) && pgn_piece_to_int (b[0][4].icon) ==
257 KING && islower (b[0][4].icon))
259 *p++ = 'k';
260 castle = 1;
263 if (TEST_FLAG (g->flags, GF_BQ_CASTLE) && pgn_piece_to_int (b[0][0].icon) ==
264 ROOK && islower (b[0][0].icon) && pgn_piece_to_int (b[0][4].icon) ==
265 KING && islower (b[0][4].icon))
267 *p++ = 'q';
268 castle = 1;
271 if (!castle)
272 *p++ = '-';
274 *p++ = ' ';
276 if (enpassant[0])
278 e = enpassant;
279 *p++ = *e++;
280 *p++ = *e++;
282 else
283 *p++ = '-';
285 *p++ = ' ';
287 // Halfmove clock.
288 *p = 0;
289 strcat (p, itoa (g->ply, tmp));
290 p = buf + strlen (buf);
291 *p++ = ' ';
293 // Fullmove number.
294 i = (g->hindex + 1) / 2;
295 *p = 0;
296 strcat (p, itoa ((g->hindex / 2) + (g->hindex % 2), tmp));
298 g->turn = oldturn;
299 return buf[0] ? strdup (buf) : NULL;
303 * Returns the total number of moves in 'h' or 0 if there are none.
306 pgn_history_total (HISTORY ** h)
308 int i;
310 if (!h)
311 return 0;
313 for (i = 0; h[i]; i++);
314 return i;
318 * Deallocates all of the history data from position 'start' in the array 'h'.
319 * Returns nothing.
321 void
322 pgn_history_free (HISTORY ** h, int start)
324 int i;
326 #ifdef DEBUG
327 PGN_DUMP ("%s:%d: freeing history\n", __FILE__, __LINE__);
328 #endif
330 if (!h || start > pgn_history_total (h))
331 return;
333 if (start < 0)
334 start = 0;
336 for (i = start; h[i]; i++)
338 if (h[i]->rav)
340 pgn_history_free (h[i]->rav, 0);
341 free (h[i]->rav);
344 free (h[i]->comment);
345 free (h[i]->move);
346 free (h[i]->fen);
347 free (h[i]);
350 h[start] = NULL;
354 * Returns the history ply 'n' from 'h'. If 'n' is out of range then NULL is
355 * returned.
357 HISTORY *
358 pgn_history_by_n (HISTORY ** h, int n)
360 if (n < 0 || n > pgn_history_total (h) - 1)
361 return NULL;
363 return h[n];
367 * Appends move 'm' to game 'g' history pointer and creates a FEN tag for the
368 * current game state using board 'b'. The FEN tag makes things faster than
369 * validating the entire move history by validating only the current move to
370 * the previous moves FEN tag. The history pointer may be a in a RAV so
371 * g->rav.hp is also updated to the new (realloc()'ed) pointer. If not in a
372 * RAV then g->history will be updated. Returns E_PGN_ERR if realloc() failed
373 * or E_PGN_OK on success.
375 pgn_error_t
376 pgn_history_add (GAME g, BOARD b, const char *m)
378 int t = pgn_history_total (g->hp);
379 int o;
380 HISTORY **h = NULL, *tmp;
381 int ri = (g->ravlevel) ? g->rav[g->ravlevel - 1].hindex : 0;
383 #ifdef DEBUG
384 PGN_DUMP ("%s:%d: adding '%s' to move history\n", __FILE__, __LINE__, m);
385 #endif
387 if (g->ravlevel)
388 o = g->rav[g->ravlevel - 1].hp[ri - 1]->rav - g->hp;
389 else
390 o = g->history - g->hp;
392 if ((h = realloc (g->hp, (t + 2) * sizeof (HISTORY *))) == NULL)
393 return E_PGN_ERR;
395 g->hp = h;
397 if (g->ravlevel)
398 g->rav[g->ravlevel - 1].hp[ri - 1]->rav = g->hp + o;
399 else
400 g->history = g->hp + o;
402 if ((g->hp[t] = calloc (1, sizeof (HISTORY))) == NULL)
403 return E_PGN_ERR;
405 if ((g->hp[t]->move = strdup (m)) == NULL)
407 free (g->hp[t]);
408 g->hp[t] = NULL;
409 return E_PGN_ERR;
412 tmp = g->hp[t];
413 t++;
414 g->hp[t] = NULL;
415 g->hindex = pgn_history_total (g->hp);
416 pgn_switch_turn (g);
417 tmp->fen = pgn_game_to_fen (g, b);
418 pgn_switch_turn (g);
419 return E_PGN_OK;
423 * Resets the game 'g' using board 'b' up to history move (g->hindex) 'n'.
424 * Returns E_PGN_OK on success or E_PGN_PARSE if there was a FEN tag but the
425 * parsing of it failed. Or returns E_PGN_INVALID if somehow the move failed
426 * validation while resetting.
428 pgn_error_t
429 pgn_board_update (GAME g, BOARD b, int n)
431 BOARD tb;
432 int ret = E_PGN_OK;
433 int p_error = TEST_FLAG (g->flags, GF_PERROR);
434 int black_opening = TEST_FLAG (g->flags, GF_BLACK_OPENING);
436 #ifdef DEBUG
437 PGN_DUMP ("%s:%d: updating board\n", __FILE__, __LINE__);
438 #endif
440 if (!g->ravlevel && TEST_FLAG (g->flags, GF_BLACK_OPENING))
441 g->turn = BLACK;
442 else
443 g->turn = WHITE;
445 g->flags = 0;
446 SET_FLAG (g->flags, GF_WK_CASTLE | GF_WQ_CASTLE | GF_WQ_CASTLE |
447 GF_BK_CASTLE | GF_BQ_CASTLE);
448 pgn_board_init (tb);
450 if (g->ravlevel)
451 pgn_board_init_fen (g, tb, g->rav[g->ravlevel - 1].fen);
452 else if (pgn_tag_find (g->tag, "FEN") != -1 &&
453 pgn_board_init_fen (g, tb, NULL))
454 return E_PGN_PARSE;
456 if (n)
458 HISTORY *h = pgn_history_by_n (g->hp, n - 1);
460 if (h)
462 ret = pgn_board_init_fen (g, tb, h->fen);
463 if (ret == E_PGN_OK)
465 h = pgn_history_by_n (g->hp, n);
466 if (h)
468 char *frfr = NULL;
470 ret = pgn_parse_move (g, tb, &h->move, &frfr);
471 if (ret == E_PGN_OK)
473 h = pgn_history_by_n (g->hp, n - 1);
474 ret = pgn_board_init_fen (g, tb, h->fen);
477 free (frfr);
483 if (ret == E_PGN_OK)
484 memcpy (b, tb, sizeof (BOARD));
486 if (p_error)
487 SET_FLAG (g->flags, GF_PERROR);
489 if (black_opening)
490 SET_FLAG (g->flags, GF_BLACK_OPENING);
492 return ret;
496 * Updates the game 'g' using board 'b' to the next 'n'th history move.
497 * Returns nothing.
499 void
500 pgn_history_prev (GAME g, BOARD b, int n)
502 if (g->hindex - n < 0)
504 if (n <= 2)
505 g->hindex = pgn_history_total (g->hp);
506 else
507 g->hindex = 0;
509 else
510 g->hindex -= n;
512 pgn_board_update (g, b, g->hindex);
516 * Updates the game 'g' using board 'b' to the previous 'n'th history move.
517 * Returns nothing.
519 void
520 pgn_history_next (GAME g, BOARD b, int n)
522 if (g->hindex + n > pgn_history_total (g->hp))
524 if (n <= 2)
525 g->hindex = 0;
526 else
527 g->hindex = pgn_history_total (g->hp);
529 else
530 g->hindex += n;
532 pgn_board_update (g, b, g->hindex);
536 * Converts the character piece 'p' to an integer. Returns the integer
537 * associated with 'p' or E_PGN_ERR if 'p' is invalid.
540 pgn_piece_to_int (int p)
542 if (p == '.')
543 return OPEN_SQUARE;
545 p = tolower (p);
547 switch (p)
549 case 'p':
550 return PAWN;
551 case 'r':
552 return ROOK;
553 case 'n':
554 return KNIGHT;
555 case 'b':
556 return BISHOP;
557 case 'q':
558 return QUEEN;
559 case 'k':
560 return KING;
561 default:
562 break;
565 #ifdef DEBUG
566 PGN_DUMP ("%s:%d: invalid piece '%c'\n", __FILE__, __LINE__, p);
567 #endif
568 return E_PGN_ERR;
572 * Converts the integer piece 'n' to a character whose turn is 'turn'. WHITE
573 * piece are uppercase and BLACK pieces are lowercase. Returns the character
574 * associated with 'n' or E_PGN_ERR if 'n' is an invalid piece.
576 pgn_error_t
577 pgn_int_to_piece (char turn, int n)
579 int p = 0;
581 switch (n)
583 case PAWN:
584 p = 'p';
585 break;
586 case ROOK:
587 p = 'r';
588 break;
589 case KNIGHT:
590 p = 'n';
591 break;
592 case BISHOP:
593 p = 'b';
594 break;
595 case QUEEN:
596 p = 'q';
597 break;
598 case KING:
599 p = 'k';
600 break;
601 case OPEN_SQUARE:
602 p = '.';
603 break;
604 default:
605 #ifdef DEBUG
606 PGN_DUMP ("%s:%d: unknown piece integer %i\n", __FILE__, __LINE__, n);
607 #endif
608 return E_PGN_ERR;
609 break;
612 return (turn == WHITE) ? toupper (p) : p;
616 * Finds a tag 'name' in the structure array 't'. Returns the location in the
617 * array of the found tag or E_PGN_ERR if the tag could not be found.
619 pgn_error_t
620 pgn_tag_find (TAG ** t, const char *name)
622 int i;
624 for (i = 0; t[i]; i++)
626 if (strcasecmp (t[i]->name, name) == 0)
627 return i;
630 return E_PGN_ERR;
633 static pgn_error_t
634 remove_tag (TAG *** array, const char *tag)
636 TAG **tags = *array;
637 int n = pgn_tag_find (tags, tag);
638 int i, t;
640 if (n == E_PGN_ERR)
641 return E_PGN_ERR;
643 for (i = t = 0; tags[i]; i++)
645 if (i == n)
647 free (tags[i]->name);
648 free (tags[i]->value);
649 free (tags[i]);
650 continue;
653 tags[t++] = tags[i];
656 tags = realloc (*array, (t + 1) * sizeof (TAG *));
657 tags[t] = NULL;
658 *array = tags;
659 #ifdef DEBUG
660 PGN_DUMP ("%s:%d: removed tag: name='%s'\n", __FILE__, __LINE__, tag);
661 #endif
662 return E_PGN_OK;
665 static int
666 tag_compare (const void *a, const void *b)
668 TAG *const *ta = a;
669 TAG *const *tb = b;
671 return strcmp ((*ta)->name, (*tb)->name);
675 * Sorts a tag array. The first seven tags are in order of the PGN standard so
676 * don't sort'em. Returns nothing.
678 void
679 pgn_tag_sort (TAG ** tags)
681 if (pgn_tag_total (tags) <= 7)
682 return;
684 qsort (tags + 7, pgn_tag_total (tags) - 7, sizeof (TAG *), tag_compare);
688 * Returns the total number of tags in 't' or 0 if 't' is NULL.
691 pgn_tag_total (TAG ** tags)
693 int i = 0;
695 if (!tags)
696 return 0;
698 while (tags[i])
699 i++;
701 return i;
705 * Adds a tag 'name' with value 'value' to the pointer to array of TAG
706 * pointers 'dst'. If a duplicate tag 'name' was found then the existing tag
707 * is updated to the new 'value'. If 'value' is NULL, the tag is removed.
708 * Returns E_PGN_ERR if there was a memory allocation error or E_PGN_OK on
709 * success.
711 pgn_error_t
712 pgn_tag_add (TAG *** dst, char *name, char *value)
714 int i;
715 TAG **tdata = *dst;
716 TAG **a = NULL;
717 int t = pgn_tag_total (tdata);
718 TAG *newtag;
720 #ifdef DEBUG
721 PGN_DUMP ("%s:%d: adding tag\n", __FILE__, __LINE__);
722 #endif
724 if (!name)
725 return E_PGN_ERR;
727 name = trim (name);
729 if (value)
730 value = trim (value);
732 // Find an existing tag with 'name'.
733 for (i = 0; i < t; i++)
735 char *tmp = NULL;
737 if (strcasecmp (tdata[i]->name, name) == 0)
739 if (value)
741 if ((tmp = strdup (value)) == NULL)
742 return E_PGN_ERR;
744 else
746 remove_tag (dst, name);
747 return E_PGN_OK;
750 free (tdata[i]->value);
751 tdata[i]->value = tmp;
752 *dst = tdata;
753 return E_PGN_OK;
757 newtag = calloc (1, sizeof (TAG));
758 if (!newtag)
759 return E_PGN_ERR;
761 if ((newtag->name = strdup (name)) == NULL)
763 free (newtag);
764 return E_PGN_ERR;
767 if (value)
769 if ((newtag->value = strdup (value)) == NULL)
771 free (newtag->name);
772 free (newtag);
773 return E_PGN_ERR;
776 else
777 newtag->value = NULL;
779 if ((a = realloc (tdata, (t + 2) * sizeof (TAG *))) == NULL)
781 free (newtag->name);
782 free (newtag->value);
783 free (newtag);
784 return E_PGN_ERR;
787 tdata = a;
788 tdata[t] = newtag;
789 tdata[t]->name[0] = toupper (tdata[t]->name[0]);
790 tdata[++t] = NULL;
791 *dst = tdata;
793 #ifdef DEBUG
794 PGN_DUMP ("%s:%d: added tag: name='%s' value='%s'\n", __FILE__, __LINE__,
795 name, (value) ? value : "null");
796 #endif
798 return E_PGN_OK;
801 static char *
802 remove_tag_escapes (const char *str)
804 int i, n;
805 int len = strlen (str);
806 char buf[MAX_PGN_LINE_LEN] = { 0 };
808 for (i = n = 0; i < len; i++, n++)
810 switch (str[i])
812 case '\\':
813 i++;
814 default:
815 break;
818 buf[n] = str[i];
821 buf[n] = '\0';
822 return buf[0] ? strdup (buf) : NULL;
826 * Resets or initializes a new game board 'b'. Returns nothing.
828 void
829 pgn_board_init (BOARD b)
831 int row, col;
833 #ifdef DEBUG
834 PGN_DUMP ("%s:%d: initializing board\n", __FILE__, __LINE__);
835 #endif
837 memset (b, 0, sizeof (BOARD));
839 for (row = 0; row < 8; row++)
841 for (col = 0; col < 8; col++)
843 int c = '.';
845 switch (row)
847 case 0:
848 case 7:
849 switch (col)
851 case 0:
852 case 7:
853 c = 'r';
854 break;
855 case 1:
856 case 6:
857 c = 'n';
858 break;
859 case 2:
860 case 5:
861 c = 'b';
862 break;
863 case 3:
864 c = 'q';
865 break;
866 case 4:
867 c = 'k';
868 break;
870 break;
871 case 1:
872 case 6:
873 c = 'p';
874 break;
877 b[row][col].icon = (row < 2) ? c : toupper (c);
883 * Adds the standard PGN roster tags to game 'g'.
885 static void
886 set_default_tags (GAME g)
888 time_t now;
889 char tbuf[11] = { 0 };
890 struct tm *tp;
891 struct passwd *pw = getpwuid (getuid ());
892 char host[64] = { 0 };
894 time (&now);
895 tp = localtime (&now);
896 strftime (tbuf, sizeof (tbuf), PGN_TIME_FORMAT, tp);
898 /* The standard seven tag roster (in order of appearance). */
899 if (pgn_tag_add (&g->tag, (char *) "Event", (char *) "?") != E_PGN_OK)
900 warn ("pgn_tag_add()");
902 gethostname (host, sizeof (host) - 1);
903 if (pgn_tag_add (&g->tag, (char *) "Site", host) != E_PGN_OK)
904 warn ("pgn_tag_add()");
906 if (pgn_tag_add (&g->tag, (char *) "Date", tbuf) != E_PGN_OK)
907 warn ("pgn_tag_add()");
909 if (pgn_tag_add (&g->tag, (char *) "Round", (char *) "-") != E_PGN_OK)
910 warn ("pgn_tag_add()");
912 if (pgn_tag_add (&g->tag, (char *) "White", pw->pw_gecos) != E_PGN_OK)
913 warn ("pgn_tag_add()");
915 if (pgn_tag_add (&g->tag, (char *) "Black", (char *) "?") != E_PGN_OK)
916 warn ("pgn_tag_add()");
918 if (pgn_tag_add (&g->tag, (char *) "Result", (char *) "*") != E_PGN_OK)
919 warn ("pgn_tag_add()");
923 * Frees a TAG array. Returns nothing.
925 void
926 pgn_tag_free (TAG ** tags)
928 int i;
929 int t = pgn_tag_total (tags);
931 #ifdef DEBUG
932 PGN_DUMP ("%s:%d: freeing tags\n", __FILE__, __LINE__);
933 #endif
935 if (!tags)
936 return;
938 for (i = 0; i < t; i++)
940 free (tags[i]->name);
941 free (tags[i]->value);
942 free (tags[i]);
945 free (tags);
949 * Frees a single game 'g'. Returns nothing.
951 void
952 pgn_free (GAME g)
954 #ifdef DEBUG
955 PGN_DUMP ("%s:%d: freeing game\n", __FILE__, __LINE__);
956 #endif
957 pgn_history_free (g->history, 0);
958 free (g->history);
959 pgn_tag_free (g->tag);
961 if (g->rav)
963 for (g->ravlevel--; g->ravlevel >= 0; g->ravlevel--)
964 free (g->rav[g->ravlevel].fen);
966 free (g->rav);
969 free (g);
973 * Frees all games in the global 'game' array. Returns nothing.
975 void
976 pgn_free_all ()
978 int i;
980 #ifdef DEBUG
981 PGN_DUMP ("%s:%d: freeing game data\n", __FILE__, __LINE__);
982 #endif
984 for (i = 0; i < gtotal; i++)
986 pgn_free (game[i]);
989 if (game)
990 free (game);
992 game = NULL;
995 static void
996 reset_game_data ()
998 #ifdef DEBUG
999 PGN_DUMP ("%s:%d: resetting game data\n", __FILE__, __LINE__);
1000 #endif
1001 pgn_free_all ();
1002 gtotal = gindex = 0;
1005 static void
1006 skip_leading_space (FILE * fp)
1008 int c;
1010 while ((c = Fgetc (fp)) != EOF && !feof (fp))
1012 if (!isspace (c))
1013 break;
1016 Ungetc (c, fp);
1020 * PGN move text section.
1022 static int
1023 move_text (GAME g, FILE * fp)
1025 char m[MAX_SAN_MOVE_LEN + 1] = { 0 }, *p;
1026 int c;
1027 int count;
1028 char *frfr = NULL;
1030 g->oflags = g->flags;
1032 while ((c = Fgetc (fp)) != EOF)
1034 if (isdigit (c) || isspace (c) || c == '.')
1035 continue;
1037 break;
1040 Ungetc (c, fp);
1042 if (fscanf (fp, " %[a-hPpRrNnBbQqKk1-9#+=Ox-]%n", m, &count) != 1)
1043 return 1;
1045 m[MAX_SAN_MOVE_LEN] = 0;
1046 p = m;
1048 if (pgn_parse_move (g, pgn_board, &p, &frfr))
1050 pgn_switch_turn (g);
1051 return 1;
1054 free (frfr);
1055 #ifdef DEBUG
1056 PGN_DUMP ("%s\n%s", p, debug_board (pgn_board));
1057 #endif
1059 pgn_history_add (g, pgn_board, p);
1060 pgn_switch_turn (g);
1061 return 0;
1065 * PGN nag text.
1067 static void
1068 nag_text (GAME g, FILE * fp)
1070 int c, i, t;
1071 char nags[5], *n = nags;
1072 int nag = 0;
1074 while ((c = Fgetc (fp)) != EOF && !isspace (c))
1076 if (c == '$')
1078 while ((c = Fgetc (fp)) != EOF && isdigit (c))
1079 *n++ = c;
1081 Ungetc (c, fp);
1082 break;
1085 if (c == '!')
1087 if ((c = Fgetc (fp)) == '!')
1088 nag = 3;
1089 else if (c == '?')
1090 nag = 5;
1091 else
1093 Ungetc (c, fp);
1094 nag = 1;
1097 break;
1099 else if (c == '?')
1101 if ((c = Fgetc (fp)) == '?')
1102 nag = 4;
1103 else if (c == '!')
1104 nag = 6;
1105 else
1107 Ungetc (c, fp);
1108 nag = 2;
1111 break;
1113 else if (c == '~')
1114 nag = 13;
1115 else if (c == '=')
1117 if ((c = Fgetc (fp)) == '+')
1118 nag = 15;
1119 else
1121 Ungetc (c, fp);
1122 nag = 10;
1125 break;
1127 else if (c == '+')
1129 if ((t = Fgetc (fp)) == '=')
1130 nag = 14;
1131 else if (t == '-')
1132 nag = 18;
1133 else if (t == '/')
1135 if ((i = Fgetc (fp)) == '-')
1136 nag = 16;
1137 else
1138 Ungetc (i, fp);
1140 break;
1142 else
1143 Ungetc (t, fp);
1145 break;
1147 else if (c == '-')
1149 if ((t = Fgetc (fp)) == '+')
1150 nag = 18;
1151 else if (t == '/')
1153 if ((i = Fgetc (fp)) == '+')
1154 nag = 17;
1155 else
1156 Ungetc (i, fp);
1158 break;
1160 else
1161 Ungetc (t, fp);
1163 break;
1167 *n = '\0';
1169 if (!nag)
1170 nag = (nags[0]) ? atoi (nags) : 0;
1172 if (!nag || nag < 0 || nag > 255)
1173 return;
1175 // FIXME -1 is because move_text() increments g->hindex. The NAG
1176 // annoatation isnt guaranteed to be after the move text in import format.
1177 for (i = 0; i < MAX_PGN_NAG; i++)
1179 if (g->hp[g->hindex - 1]->nag[i])
1180 continue;
1182 g->hp[g->hindex - 1]->nag[i] = nag;
1183 break;
1186 skip_leading_space (fp);
1190 * PGN move annotation.
1192 static int
1193 annotation_text (GAME g, FILE * fp, int terminator)
1195 int c, lastchar = 0;
1196 int len = 0;
1197 int hindex = pgn_history_total (g->hp) - 1;
1198 char buf[MAX_PGN_LINE_LEN], *a = buf;
1200 skip_leading_space (fp);
1202 while ((c = Fgetc (fp)) != EOF && c != terminator)
1204 if (c == '\n')
1205 c = ' ';
1207 if (isspace (c) && isspace (lastchar))
1208 continue;
1210 if (len + 1 == sizeof (buf))
1211 continue;
1213 *a++ = lastchar = c;
1214 len++;
1217 *a = '\0';
1219 if (hindex < 0)
1220 hindex = 0;
1223 * This annotation is before any move text or NAg-> Allocate a new move.
1225 if (!g->hp[hindex])
1227 if ((g->hp[hindex] = calloc (1, sizeof (HISTORY))) == NULL)
1228 return E_PGN_ERR;
1231 if ((g->hp[hindex]->comment = strdup (buf)) == NULL)
1232 return E_PGN_ERR;
1234 return E_PGN_OK;
1238 * PGN roster tag->
1240 static int
1241 tag_text (GAME g, FILE * fp)
1243 char name[LINE_MAX] = { 0 }, *n = name;
1244 char value[LINE_MAX] = { 0 }, *v = value;
1245 int c, i = 0;
1246 int lastchar = 0;
1247 char *tmp;
1249 skip_leading_space (fp);
1251 /* The tag name is up until the first whitespace. */
1252 while ((c = Fgetc (fp)) != EOF && !isspace (c))
1253 *n++ = c;
1255 *n = '\0';
1256 *name = toupper (*name);
1257 skip_leading_space (fp);
1259 /* The value is until the first closing bracket. */
1260 while ((c = Fgetc (fp)) != EOF && c != ']')
1262 if (i++ == '\0' && c == '\"')
1263 continue;
1265 if (c == '\n' || c == '\t')
1266 c = ' ';
1268 if (c == ' ' && lastchar == ' ')
1269 continue;
1271 lastchar = *v++ = c;
1274 *v = '\0';
1276 while (isspace (*--v))
1277 *v = '\0';
1279 if (*v == '\"')
1280 *v = '\0';
1282 if (value[0] == '\0')
1284 if (strcmp (name, "Result") == 0)
1285 value[0] = '*';
1286 else
1287 value[0] = '?';
1289 value[1] = '\0';
1292 tmp = remove_tag_escapes (value);
1293 strncpy (value, tmp, sizeof (value) - 1);
1294 free (tmp);
1297 * See eog_text() for an explanation.
1299 if (strcmp (name, "Result") == 0)
1301 if (strcmp (value, "1/2-1/2") == 0)
1303 if (pgn_tag_add (&g->tag, name, value) != E_PGN_OK)
1304 warn ("pgn_tag_add()");
1307 else
1309 if (pgn_tag_add (&g->tag, name, value) != E_PGN_OK)
1310 warn ("pgn_tag_add()");
1313 return 0;
1317 * PGN end-of-game marker.
1319 static int
1320 eog_text (GAME g, FILE * fp)
1322 int c, i = 0;
1323 char buf[8], *p = buf;
1325 while ((c = Fgetc (fp)) != EOF && !isspace (c) && i++ < sizeof (buf))
1326 *p++ = c;
1328 if (isspace (c))
1329 Ungetc (c, fp);
1331 *p = 0;
1333 if (strcmp (buf, "1-0") != 0 && strcmp (buf, "0-1") != 0 &&
1334 strcmp (buf, "1/2-1/2") != 0 && strcmp (buf, "*") != 0)
1335 return 1;
1338 * The eog marker in the move text may not match the actual game result.
1339 * We'll leave it up to the move validator to determine the result unless
1340 * the move validator cannot determine who won and the move text says it's
1341 * a draw.
1343 if (g->tag[6]->value[0] == '*' && strcmp ("1/2-1/2", buf) == 0)
1345 if (pgn_tag_add (&g->tag, (char *) "Result", buf) != E_PGN_OK)
1346 warn ("pgn_tag_add()");
1349 return 0;
1353 * Parse RAV text and keep track of g->hp. The 'o' argument is the board state
1354 * before the current move (.hindex) was parsed.
1356 static int read_file (FILE *);
1357 static int
1358 rav_text (GAME g, FILE * fp, int which, BOARD o)
1360 struct game_s tg;
1361 RAV *r;
1363 // Begin RAV for the current move.
1364 if (which == '(')
1366 if (!g->hindex)
1367 return 1;
1369 pgn_rav++; // For detecting parse errors.
1372 * Save the current game state for this RAV depth/level.
1374 if ((r = realloc (g->rav, (g->ravlevel + 1) * sizeof (RAV))) == NULL)
1376 warn ("realloc()");
1377 return 1;
1380 g->rav = pgn_rav_p = r;
1382 if ((g->rav[g->ravlevel].fen = pgn_game_to_fen (g, pgn_board)) == NULL)
1384 warn ("strdup()");
1385 return 1;
1388 g->rav[g->ravlevel].hp = g->hp;
1389 g->rav[g->ravlevel].hindex = g->hindex;
1390 memcpy (&tg, &(*g), sizeof (struct game_s));
1391 g->flags = g->oflags;
1392 memcpy (pgn_board, o, sizeof (BOARD));
1394 if ((g->hp[g->hindex - 1]->rav =
1395 calloc (1, sizeof (HISTORY *))) == NULL)
1397 warn ("calloc()");
1398 return 1;
1402 * pgn_history_add() will now append to the new history pointer that
1403 * is g->hp[previous_move]->rav.
1405 g->hp = g->hp[g->hindex - 1]->rav;
1408 * Reset. Will be restored later from 'tg' which is a local variable
1409 * so recursion is possible.
1411 g->hindex = 0;
1412 g->ravlevel++;
1415 * Undo move_text()'s switch.
1417 pgn_switch_turn (g);
1420 * Now continue as normal as if there were no RAV. Moves will be
1421 * parsed and appended to the new history pointer.
1423 if (read_file (fp))
1424 return 1;
1427 * read_file() has returned. This means that a RAV has ended by this
1428 * function returning -1 (see below). So we restore the game state
1429 * (tg) that was saved before calling read_file().
1431 pgn_board_init_fen (&tg, pgn_board, g->rav[tg.ravlevel].fen);
1432 free (g->rav[tg.ravlevel].fen);
1433 memcpy (&(*g), &tg, sizeof (struct game_s));
1434 g->rav = pgn_rav_p;
1437 * The end of a RAV. This makes the read_file() that called this function
1438 * return (see above and the switch statement in read_file()).
1440 else if (which == ')')
1442 pgn_rav--; // For detecting parse errors.
1443 return (pgn_rav < 0) ? 1 : -1;
1446 return 0;
1450 * FIXME
1451 * See pgn_board_init_fen(). Returns E_PGN_PARSE on parse error. 0 may be
1452 * returned on success when there is no move count in the FEN tag otherwise
1453 * the move count is returned.
1455 static int
1456 parse_fen_line (BOARD b, unsigned *flags, char *turn, char *ply, char *str)
1458 char *tmp;
1459 char line[LINE_MAX] = { 0 }, *s;
1460 int row = 8, col = 1;
1462 #ifdef DEBUG
1463 PGN_DUMP ("%s:%d: FEN line is '%s'\n", __FILE__, __LINE__, str);
1464 #endif
1465 strncpy (line, str, sizeof (line) - 1);
1466 s = line;
1467 pgn_reset_enpassant (b);
1469 while ((tmp = strsep (&s, "/")) != NULL)
1471 int n;
1473 if (!VALIDFILE (row))
1474 return E_PGN_PARSE;
1476 while (*tmp)
1478 if (*tmp == ' ')
1479 goto other;
1481 if (isdigit (*tmp))
1483 n = *tmp - '0';
1485 if (!VALIDFILE (n))
1486 return E_PGN_PARSE;
1488 for (; n; --n, col++)
1489 b[RANKTOBOARD (row)][FILETOBOARD (col)].icon =
1490 pgn_int_to_piece (WHITE, OPEN_SQUARE);
1492 else if (pgn_piece_to_int (*tmp) != -1)
1493 b[RANKTOBOARD (row)][FILETOBOARD (col++)].icon = *tmp;
1494 else
1495 return E_PGN_PARSE;
1497 tmp++;
1500 row--;
1501 col = 1;
1504 other:
1505 tmp++;
1507 switch (*tmp++)
1509 case 'b':
1510 *turn = BLACK;
1511 break;
1512 case 'w':
1513 *turn = WHITE;
1514 break;
1515 default:
1516 return E_PGN_PARSE;
1519 tmp++;
1521 while (*tmp && *tmp != ' ')
1523 switch (*tmp++)
1525 case 'K':
1526 SET_FLAG (*flags, GF_WK_CASTLE);
1527 break;
1528 case 'Q':
1529 SET_FLAG (*flags, GF_WQ_CASTLE);
1530 break;
1531 case 'k':
1532 SET_FLAG (*flags, GF_BK_CASTLE);
1533 break;
1534 case 'q':
1535 SET_FLAG (*flags, GF_BQ_CASTLE);
1536 break;
1537 case '-':
1538 break;
1539 default:
1540 return E_PGN_PARSE;
1544 tmp++;
1546 // En passant.
1547 if (*tmp != '-')
1549 if (!VALIDCOL (*tmp))
1550 return E_PGN_PARSE;
1551 col = *tmp++ - 'a';
1553 if (!VALIDROW (*tmp))
1554 return E_PGN_PARSE;
1555 row = 8 - atoi (tmp++);
1557 b[row][col].enpassant = 1;
1558 SET_FLAG (*flags, GF_ENPASSANT);
1560 else
1561 tmp++;
1563 if (*tmp)
1564 tmp++;
1566 if (*tmp)
1567 *ply = atoi (tmp);
1569 while (*tmp && isdigit (*tmp))
1570 tmp++;
1572 if (*tmp)
1573 tmp++;
1575 return E_PGN_OK;
1579 * It initializes the board (b) to the FEN tag (if found) and sets the
1580 * castling and enpassant info for the game 'g'. If 'fen' is set it should be
1581 * a fen tag and will be parsed rather than the game 'g'.tag FEN tag-> Returns
1582 * E_PGN_OK on success or if there was both a FEN and SetUp tag with the SetUp
1583 * tag set to 0. Returns E_PGN_PARSE if there was a FEN parse error, E_PGN_ERR
1584 * if there was no FEN tag or there was a SetUp tag with a value of 0. Returns
1585 * E_PGN_OK on success.
1587 pgn_error_t
1588 pgn_board_init_fen (GAME g, BOARD b, char *fen)
1590 int n = -1, i = -1;
1591 BOARD tmpboard;
1592 unsigned flags = 0;
1593 char turn = g->turn;
1594 char ply = 0;
1596 #ifdef DEBUG
1597 PGN_DUMP ("%s:%d: initializing board from FEN\n", __FILE__, __LINE__);
1598 #endif
1599 pgn_board_init (tmpboard);
1601 if (!fen)
1603 n = pgn_tag_find (g->tag, "Setup");
1604 i = pgn_tag_find (g->tag, "FEN");
1608 * If the FEN tag exists and there is no SetUp tag go ahead and parse it.
1609 * If there is a SetUp tag only parse the FEN tag if the value is 1.
1611 if ((n >= 0 && i >= 0 && atoi (g->tag[n]->value) == 1)
1612 || (i >= 0 && n == -1) || fen)
1614 if ((n = parse_fen_line (tmpboard, &flags, &turn, &ply,
1615 (fen) ? fen : g->tag[i]->value)) != E_PGN_OK)
1616 return E_PGN_PARSE;
1617 else
1619 memcpy (b, tmpboard, sizeof (BOARD));
1620 CLEAR_FLAG (g->flags, GF_WK_CASTLE);
1621 CLEAR_FLAG (g->flags, GF_WQ_CASTLE);
1622 CLEAR_FLAG (g->flags, GF_BK_CASTLE);
1623 CLEAR_FLAG (g->flags, GF_BQ_CASTLE);
1624 g->flags |= flags;
1625 g->turn = turn;
1626 g->ply = ply;
1629 else
1630 return (i >= 0 && n >= 0) ? E_PGN_OK : E_PGN_ERR;
1632 return E_PGN_OK;
1636 * Allocates a new game and increments 'gtotal'. 'gindex' is then set to the
1637 * new game. Returns E_PGN_ERR if there was a memory allocation error or
1638 * E_PGN_OK on success.
1640 pgn_error_t
1641 pgn_new_game ()
1643 GAME *g;
1644 GAME newg;
1645 int t = gtotal + 1;
1647 #ifdef DEBUG
1648 PGN_DUMP ("%s:%d: allocating new game\n", __FILE__, __LINE__);
1649 #endif
1650 gindex = t - 1;
1652 if ((g = realloc (game, t * sizeof (GAME))) == NULL)
1654 warn ("realloc()");
1655 return E_PGN_ERR;
1658 game = g;
1660 if ((newg = calloc (1, sizeof (struct game_s))) == NULL)
1662 warn ("calloc()");
1663 return E_PGN_ERR;
1666 game[gindex] = newg;
1668 if ((game[gindex]->hp = calloc (1, sizeof (HISTORY *))) == NULL)
1670 free (game[gindex]);
1671 warn ("calloc()");
1672 return E_PGN_ERR;
1675 game[gindex]->hp[0] = NULL;
1676 game[gindex]->history = game[gindex]->hp;
1677 game[gindex]->side = game[gindex]->turn = WHITE;
1678 SET_FLAG (game[gindex]->flags, GF_WK_CASTLE | GF_WQ_CASTLE | GF_WQ_CASTLE |
1679 GF_BK_CASTLE | GF_BQ_CASTLE);
1680 pgn_board_init (pgn_board);
1681 set_default_tags (game[gindex]);
1682 gtotal = t;
1683 return E_PGN_OK;
1686 static int
1687 read_file (FILE * fp)
1689 #ifdef DEBUG
1690 char buf[LINE_MAX] = { 0 }, *p = buf;
1691 #endif
1692 int c = 0;
1693 int parse_error = 0;
1694 BOARD old;
1696 while (1)
1698 int nextchar = 0;
1699 int lastchar = c;
1700 int n;
1703 * A parse error may have occured at EOF.
1705 if (parse_error)
1707 pgn_ret = E_PGN_PARSE;
1709 if (!game)
1710 pgn_new_game ();
1712 SET_FLAG (game[gindex]->flags, GF_PERROR);
1715 if ((c = Fgetc (fp)) == EOF)
1717 if (feof (fp))
1718 break;
1720 if (ferror (fp))
1722 clearerr (fp);
1723 continue;
1727 if (!isascii (c))
1729 parse_error = 1;
1730 continue;
1733 if (c == '\015')
1734 continue;
1736 nextchar = Fgetc (fp);
1737 Ungetc (nextchar, fp);
1740 * If there was a move text parsing error, keep reading until the end
1741 * of the current game discarding the data.
1743 if (parse_error)
1745 pgn_ret = E_PGN_PARSE;
1747 if (game[gindex]->ravlevel)
1748 return 1;
1750 if (pgn_config.stop)
1751 return E_PGN_PARSE;
1753 if (c == '\n' && (nextchar == '\n' || nextchar == '\015'))
1755 parse_error = 0;
1756 nulltags = 1;
1757 tag_section = 0;
1759 else
1760 continue;
1763 // New game reached.
1764 if (c == '\n' && (nextchar == '\n' || nextchar == '\015'))
1766 if (tag_section)
1767 continue;
1769 nulltags = 1;
1770 tag_section = 0;
1771 continue;
1775 * PGN: Application comment. The '%' must be on the first column of
1776 * the line. The comment continues until the end of the current line.
1778 if (c == '%')
1780 if (lastchar == '\n' || lastchar == 0)
1782 while ((c = Fgetc (fp)) != EOF && c != '\n');
1783 continue;
1786 // Not sure what to do here.
1789 if (isspace (c))
1790 continue;
1792 // PGN: Reserved.
1793 if (c == '<' || c == '>')
1794 continue;
1797 * PGN: Recurrsive Annotated Variation. Read rav_text() for more
1798 * info.
1800 if (c == '(' || c == ')')
1802 switch (rav_text (game[gindex], fp, c, old))
1804 case -1:
1806 * This is the end of the current RAV. This function has
1807 * been called from rav_text(). Returning from this point
1808 * will put us back in rav_text().
1810 if (game[gindex]->ravlevel > 0)
1811 return pgn_ret;
1814 * We're back at the root move. Continue as normal.
1816 break;
1817 case 1:
1818 parse_error = 1;
1819 continue;
1820 default:
1822 * Continue processing-> Probably the root move.
1824 break;
1827 if (!game[gindex]->ravlevel && pgn_rav)
1828 parse_error = 1;
1830 continue;
1833 // PGN: Numeric Annotation Glyph.
1834 if (c == '$' || c == '!' || c == '?' || c == '+' || c == '-' ||
1835 c == '~' || c == '=')
1837 Ungetc (c, fp);
1838 nag_text (game[gindex], fp);
1839 continue;
1843 * PGN: Annotation. The ';' comment continues until the end of the
1844 * current line. The '{' type comment continues until a '}' is
1845 * reached.
1847 if (c == '{' || c == ';')
1849 annotation_text (game[gindex], fp, (c == '{') ? '}' : '\n');
1850 continue;
1853 // PGN: Roster tag->
1854 if (c == '[')
1856 // First roster tag found. Initialize the data structures.
1857 if (!tag_section)
1859 nulltags = 0;
1860 tag_section = 1;
1862 if (gtotal && pgn_history_total (game[gindex]->hp))
1863 game[gindex]->hindex =
1864 pgn_history_total (game[gindex]->hp) - 1;
1866 if (pgn_new_game () != E_PGN_OK)
1868 pgn_ret = E_PGN_ERR;
1869 break;
1872 memcpy (old, pgn_board, sizeof (BOARD));
1875 if (tag_text (game[gindex], fp))
1876 parse_error = 1; // FEN tag parse error.
1878 continue;
1881 // PGN: End-of-game markers.
1882 if ((isdigit (c) && (nextchar == '-' || nextchar == '/')) || c == '*')
1884 Ungetc (c, fp);
1886 if (eog_text (game[gindex], fp))
1888 parse_error = 1;
1889 continue;
1892 nulltags = 1;
1893 tag_section = 0;
1895 if (!game[gindex]->done_fen_tag)
1897 if (pgn_tag_find (game[gindex]->tag, "FEN") != -1 &&
1898 pgn_board_init_fen (game[gindex], pgn_board, NULL))
1900 parse_error = 1;
1901 continue;
1904 game[gindex]->pgn_fen_tag =
1905 pgn_tag_find (game[gindex]->tag, "FEN");
1906 game[gindex]->done_fen_tag = 1;
1909 continue;
1912 // PGN: Move text.
1913 if ((isdigit (c) && c != '0') || VALIDCOL (c) || c == 'N' || c == 'K'
1914 || c == 'Q' || c == 'B' || c == 'R' || c == 'P' || c == 'O')
1916 Ungetc (c, fp);
1918 // PGN: If a FEN tag exists, initialize the board to the value.
1919 if (tag_section)
1921 if (pgn_tag_find (game[gindex]->tag, "FEN") != E_PGN_ERR &&
1922 (n = pgn_board_init_fen (game[gindex], pgn_board,
1923 NULL)) == E_PGN_PARSE)
1925 parse_error = 1;
1926 continue;
1929 game[gindex]->done_fen_tag = 1;
1930 game[gindex]->pgn_fen_tag =
1931 pgn_tag_find (game[gindex]->tag, "FEN");
1932 tag_section = 0;
1936 * PGN: Import format doesn't require a roster tag section. We've
1937 * arrived to the move text section without any tags so we
1938 * initialize a new game which set's the default tags and any tags
1939 * from the configuration file.
1941 if (nulltags)
1943 if (gtotal)
1944 game[gindex]->hindex =
1945 pgn_history_total (game[gindex]->hp) - 1;
1947 if (pgn_new_game () != E_PGN_OK)
1949 pgn_ret = E_PGN_ERR;
1950 break;
1953 memcpy (old, pgn_board, sizeof (BOARD));
1954 nulltags = 0;
1957 memcpy (old, pgn_board, sizeof (BOARD));
1959 if (move_text (game[gindex], fp))
1961 if (pgn_tag_add (&game[gindex]->tag, (char *) "Result",
1962 (char *) "*") == E_PGN_ERR)
1964 warn ("pgn_tag_add()");
1965 pgn_ret = E_PGN_ERR;
1968 SET_FLAG (game[gindex]->flags, GF_PERROR);
1969 parse_error = 1;
1972 continue;
1975 #ifdef DEBUG
1976 *p++ = c;
1978 PGN_DUMP ("%s:%d: unparsed: '%s'\n", __FILE__, __LINE__, buf);
1980 if (strlen (buf) + 1 == sizeof (buf))
1981 bzero (buf, sizeof (buf));
1982 #endif
1984 continue;
1987 return pgn_ret;
1991 * Parses a PGN_FILE which was opened with pgn_open(). If 'pgn' is NULL then a
1992 * single empty game will be allocated. If there is a parsing error
1993 * E_PGN_PARSE is returned, if there was a memory allocation error E_PGN_ERR
1994 * is returned, otherwise E_PGN_OK is returned and the global 'gindex' is set
1995 * to the last parsed game in the file and the global 'gtotal' is set to the
1996 * total number of games in the file. The file should be closed with
1997 * pgn_close() after processing.
1999 pgn_error_t
2000 pgn_parse (PGN_FILE * pgn)
2002 int i;
2004 if (!pgn)
2006 reset_game_data ();
2007 pgn_ret = pgn_new_game ();
2008 goto done;
2011 reset_game_data ();
2012 nulltags = 1;
2013 fseek (pgn->fp, 0, SEEK_END);
2014 pgn_fsize = ftell (pgn->fp);
2015 fseek (pgn->fp, 0, SEEK_SET);
2016 #ifdef DEBUG
2017 PGN_DUMP ("%s:%d: BEGIN parsing->..\n", __FILE__, __LINE__);
2018 #endif
2019 parsing_file = 1;
2020 pgn_ret = read_file (pgn->fp);
2021 parsing_file = 0;
2023 #ifdef DEBUG
2024 PGN_DUMP ("%s:%d: END parsing->..\n", __FILE__, __LINE__);
2025 #endif
2027 if (gtotal < 1)
2028 pgn_new_game ();
2030 done:
2031 gtotal = gindex + 1;
2033 for (i = 0; i < gtotal; i++)
2035 game[i]->history = game[i]->hp;
2036 game[i]->hindex = pgn_history_total (game[i]->hp);
2039 return pgn_ret;
2043 * Escape '"' and '\' in tag values.
2045 static char *
2046 pgn_tag_add_escapes (const char *str)
2048 int i, n;
2049 int len = strlen (str);
2050 char buf[MAX_PGN_LINE_LEN] = { 0 };
2052 for (i = n = 0; i < len; i++, n++)
2054 switch (str[i])
2056 case '\\':
2057 case '\"':
2058 buf[n++] = '\\';
2059 break;
2060 default:
2061 break;
2064 buf[n] = str[i];
2067 buf[n] = '\0';
2068 return buf[0] ? strdup (buf) : NULL;
2071 static void
2072 Fputc (int c, FILE * fp, int *len)
2074 int i = *len;
2076 if (c != '\n' && i + 1 > 80)
2077 Fputc ('\n', fp, &i);
2079 if (pgn_lastc == '\n' && c == ' ')
2081 *len = pgn_mpl = 0;
2082 return;
2085 if (fputc (c, fp) == EOF)
2086 warn ("PGN Save");
2087 else
2089 if (c == '\n')
2090 i = pgn_mpl = 0;
2091 else
2092 i++;
2095 *len = i;
2096 pgn_lastc = c;
2099 static void
2100 putstring (FILE * fp, char *str, int *len)
2102 char *p;
2104 for (p = str; *p; p++)
2106 int n = 0;
2108 while (*p && *p != ' ')
2109 n++, p++;
2111 if (n + *len > 80)
2112 Fputc ('\n', fp, len);
2114 p -= n;
2115 Fputc (*p, fp, len);
2120 * See pgn_write() for more info.
2122 static void
2123 write_comments_and_nag (FILE * fp, HISTORY * h, int *len)
2125 int i;
2126 char tmp[16];
2128 #ifdef DEBUG
2129 PGN_DUMP ("%s:%d: writing comments and nag\n", __FILE__, __LINE__);
2130 #endif
2132 for (i = 0; i < MAX_PGN_NAG; i++)
2134 if (h->nag[i])
2136 Fputc (' ', fp, len);
2137 Fputc ('$', fp, len);
2138 putstring (fp, itoa (h->nag[i], tmp), len);
2142 if (h->comment)
2144 Fputc ('\n', fp, len);
2145 putstring (fp, (char *) " {", len);
2146 putstring (fp, h->comment, len);
2147 Fputc ('}', fp, len);
2151 static void
2152 write_move_text (FILE * fp, HISTORY * h, int *len)
2154 Fputc (' ', fp, len);
2155 putstring (fp, h->move, len);
2157 if (!pgn_config.reduced)
2158 write_comments_and_nag (fp, h, len);
2161 static void
2162 write_all_move_text (FILE * fp, HISTORY ** h, int m, int *len)
2164 int i;
2165 HISTORY **hp = NULL;
2166 char tmp[16];
2168 for (i = 0; h[i]; i++)
2170 if (pgn_write_turn == WHITE)
2172 if (pgn_config.mpl && pgn_mpl == pgn_config.mpl)
2174 pgn_mpl = 0;
2175 Fputc ('\n', fp, len);
2178 if (m > 1 && i > 0)
2179 Fputc (' ', fp, len);
2181 if (strlen (itoa (m, tmp)) + 1 + *len > 80)
2182 Fputc ('\n', fp, len);
2184 putstring (fp, itoa (m, tmp), len);
2185 Fputc ('.', fp, len);
2186 pgn_mpl++;
2189 write_move_text (fp, h[i], len);
2191 if (!pgn_config.reduced && h[i]->rav)
2193 int oldm = m;
2194 int oldturn = pgn_write_turn;
2196 ravlevel++;
2197 putstring (fp, (char *) " (", len);
2200 * If it's WHITE's turn the move number will be added above after
2201 * the call to write_all_move_text() below.
2203 if (pgn_write_turn == BLACK)
2205 putstring (fp, itoa (m, tmp), len);
2206 putstring (fp, (char *) "...", len);
2209 hp = h[i]->rav;
2210 write_all_move_text (fp, hp, m, len);
2211 m = oldm;
2212 pgn_write_turn = oldturn;
2213 putstring (fp, (char *) ")", len);
2214 ravlevel--;
2216 if (ravlevel && h[i + 1])
2217 Fputc (' ', fp, len);
2219 if (h[i + 1] && !ravlevel)
2220 Fputc (' ', fp, len);
2222 if (pgn_write_turn == WHITE && h[i + 1])
2224 putstring (fp, itoa (m, tmp), len);
2225 putstring (fp, (char *) "...", len);
2229 if (pgn_write_turn == BLACK)
2230 m++;
2232 pgn_write_turn = (pgn_write_turn == WHITE) ? BLACK : WHITE;
2236 static char *
2237 compression_cmd (const char *filename, int expand)
2239 char command[PATH_MAX];
2240 int len = strlen (filename);
2242 if (filename[len - 4] == '.' && filename[len - 3] == 'z' &&
2243 filename[len - 2] == 'i' && filename[len - 1] == 'p' &&
2244 filename[len] == '\0')
2246 if (expand)
2247 snprintf (command, sizeof (command), "unzip -p %s 2>/dev/null",
2248 filename);
2249 else
2250 snprintf (command, sizeof (command), "zip -9 >%s 2>/dev/null",
2251 filename);
2253 return strdup (command);
2255 else if (filename[len - 3] == '.' && filename[len - 2] == 'g' &&
2256 filename[len - 1] == 'z' && filename[len] == '\0')
2258 if (expand)
2259 snprintf (command, sizeof (command), "gzip -dc %s", filename);
2260 else
2261 snprintf (command, sizeof (command), "gzip -c 1>%s", filename);
2263 return strdup (command);
2265 else if (filename[len - 2] == '.' && filename[len - 1] == 'Z' &&
2266 filename[len] == '\0')
2268 if (expand)
2269 snprintf (command, sizeof (command), "uncompress -c %s", filename);
2270 else
2271 snprintf (command, sizeof (command), "compress -c 1>%s", filename);
2273 return strdup (command);
2275 else if ((filename[len - 4] == '.' && filename[len - 3] == 'b' &&
2276 filename[len - 2] == 'z' && filename[len - 1] == '2' &&
2277 filename[len] == '\0') || (filename[len - 3] == '.' &&
2278 filename[len - 2] == 'b'
2279 && filename[len - 1] == 'z'
2280 && filename[len] == '\0'))
2282 if (expand)
2283 snprintf (command, sizeof (command), "bzip2 -dc %s", filename);
2284 else
2285 snprintf (command, sizeof (command), "bzip2 -zc 1>%s", filename);
2287 return strdup (command);
2290 return NULL;
2293 static int
2294 copy_file (FILE * fp, const char *dst)
2296 FILE *ofp;
2297 char line[LINE_MAX];
2298 char *cmd = compression_cmd (dst, 0);
2300 if ((ofp = popen (cmd, "w")) == NULL)
2302 free (cmd);
2303 return 1;
2306 free (cmd);
2307 fseek (fp, 0, SEEK_SET);
2309 while ((fgets (line, sizeof (line), fp)) != NULL)
2310 fprintf (ofp, "%s", line);
2312 pclose (ofp);
2313 return 0;
2317 * Closes and free's a PGN file handle.
2319 pgn_error_t
2320 pgn_close (PGN_FILE * pgn)
2322 if (!pgn)
2323 return E_PGN_INVALID;
2325 if (pgn->pipe)
2328 * Appending to a compressed file.
2330 if (pgn->tmpfile)
2332 if (copy_file (pgn->fp, pgn->filename))
2333 return E_PGN_ERR;
2335 fclose (pgn->fp);
2336 unlink (pgn->tmpfile);
2337 free (pgn->tmpfile);
2339 else
2340 pclose (pgn->fp);
2342 else
2343 fclose (pgn->fp);
2345 free (pgn->filename);
2346 free (pgn);
2347 return E_PGN_OK;
2351 * Opens a file 'filename' with the given 'mode'. 'mode' should be "r" for
2352 * reading, "w" for writing (will truncate if the file exists) or "a" for
2353 * appending to an existing file or creating a new one. Returns E_PGN_OK on
2354 * success and sets 'result' to a file handle for use will the other file
2355 * functions or E_PGN_ERR if there is an error opening the file in which case
2356 * errno will be set to the error or E_PGN_INVALID if 'mode' is an invalid
2357 * mode or if 'filename' is not a regular file.
2359 pgn_error_t
2360 pgn_open (const char *filename, const char *mode, PGN_FILE ** result)
2362 FILE *fp = NULL, *tfp = NULL, *pfp = NULL;
2363 char buf[PATH_MAX], *p;
2364 char *cmd = NULL;
2365 PGN_FILE *pgn;
2366 int m;
2367 int append = 0;
2368 struct stat st;
2369 int ret = E_PGN_ERR;
2372 #ifdef DEBUG
2373 PGN_DUMP ("%s:%d: BEGIN opening %s\n", __FILE__, __LINE__, filename);
2374 #endif
2376 if (!filename || !mode)
2377 return E_PGN_INVALID;
2379 if (strcmp (mode, "r") == 0)
2380 m = 1;
2381 else if (strcmp (mode, "w") == 0)
2382 m = 0;
2383 else if (strcmp (mode, "a") == 0)
2385 m = 0;
2386 append = 1;
2388 else
2390 return E_PGN_INVALID;
2393 pgn = calloc (1, sizeof (PGN_FILE));
2395 if (!pgn)
2396 goto fail;
2398 if (strcmp (filename, "-") != 0)
2400 if (m && access (filename, R_OK) == -1)
2401 goto fail;
2403 if (stat (filename, &st) == -1 && !m && errno != ENOENT)
2404 goto fail;
2406 if (m && !S_ISREG (st.st_mode))
2408 ret = E_PGN_INVALID;
2409 goto fail;
2412 if ((cmd = compression_cmd (filename, m)) != NULL)
2414 char tmp[21];
2415 int fd;
2417 #ifdef HAVE_MKSTEMP
2418 snprintf (tmp, sizeof (tmp), "cboard.XXXXXX");
2419 #else
2420 if (tmpnam (tmp) == NULL)
2421 goto fail;
2422 #endif
2424 pgn->pipe = 1;
2426 if (append && access (filename, R_OK) == 0)
2428 #ifdef HAVE_MKSTEMP
2429 mode_t tmode;
2430 #endif
2432 free (cmd);
2433 cmd = compression_cmd (filename, 1);
2434 if ((pfp = popen (cmd, "r")) == NULL)
2435 goto fail;
2437 #ifdef HAVE_MKSTEMP
2438 tmode = umask (600);
2439 fd = mkstemp (tmp);
2440 umask (tmode);
2441 if (fd == -1)
2442 goto fail;
2443 #else
2444 if ((fd = open (tmp, O_RDWR | O_EXCL | O_CREAT, 0600)) == -1)
2445 goto fail;
2446 #endif
2447 if ((tfp = fdopen (fd, "a+")) == NULL)
2448 goto fail;
2450 while ((p = fgets (buf, sizeof (buf), pfp)) != NULL)
2451 fprintf (tfp, "%s", p);
2453 pclose (pfp);
2454 pfp = NULL;
2455 pgn->fp = tfp;
2456 pgn->tmpfile = strdup (tmp);
2457 goto done;
2460 if ((pfp = popen (cmd, m ? "r" : "w")) == NULL)
2461 goto fail;
2463 if (m)
2465 #ifdef HAVE_MKSTEMP
2466 mode_t tmode = umask (600);
2468 fd = mkstemp (tmp);
2469 umask (tmode);
2470 if (fd == -1)
2471 goto fail;
2472 #else
2473 if ((fd =
2474 open (tmp, O_RDWR | O_EXCL | O_CREAT | O_TRUNC,
2475 0600)) == -1)
2476 goto fail;
2477 #endif
2478 if ((tfp = fdopen (fd, "w+")) == NULL)
2479 goto fail;
2481 while ((p = fgets (buf, sizeof (buf), pfp)) != NULL)
2482 fprintf (tfp, "%s", p);
2484 pclose (pfp);
2485 pfp = NULL;
2486 pgn->fp = tfp;
2487 pgn->tmpfile = strdup (tmp);
2489 else
2490 pgn->fp = pfp;
2492 else
2494 if ((fp = fopen (filename, mode)) == NULL)
2495 goto fail;
2497 pgn->fp = fp;
2500 else
2501 pgn->fp = stdout;
2503 done:
2504 if (*filename != '/')
2506 if (getcwd (buf, sizeof (buf)) == NULL)
2508 if (pgn->tmpfile)
2509 free (pgn->tmpfile);
2511 goto fail;
2514 asprintf (&p, "%s/%s", buf, filename);
2515 pgn->filename = p;
2517 else
2518 pgn->filename = strdup (filename);
2520 free (cmd);
2521 *result = pgn;
2522 return E_PGN_OK;
2524 fail:
2525 if (fp)
2526 fclose (fp);
2528 if (pfp)
2529 pclose (pfp);
2531 free (cmd);
2532 free (pgn);
2533 return ret;
2537 * Returns E_PGN_OK if 'filename' is a recognized compressed filetype or
2538 * E_PGN_ERR if not.
2540 pgn_error_t
2541 pgn_is_compressed (const char *filename)
2543 char *s = compression_cmd (filename, 0);
2544 pgn_error_t e = s ? E_PGN_OK : E_PGN_ERR;
2546 free (s);
2547 return e;
2551 * Gets the value of config flag 'f'. The next argument should be a pointer of
2552 * the config type which is set to the value of 'f'. Returns E_PGN_ERR if 'f'
2553 * is an invalid flag or E_PGN_OK on success.
2555 pgn_error_t
2556 pgn_config_get (pgn_config_flag f, ...)
2558 va_list ap;
2559 int *intval;
2560 long *longval;
2561 pgn_progress *progress;
2563 va_start (ap, f);
2565 switch (f)
2567 case PGN_STRICT_CASTLING:
2568 intval = va_arg (ap, int *);
2569 *intval = pgn_config.strict_castling;
2570 va_end (ap);
2571 return E_PGN_OK;
2572 case PGN_REDUCED:
2573 intval = va_arg (ap, int *);
2574 *intval = pgn_config.reduced;
2575 va_end (ap);
2576 return E_PGN_OK;
2577 case PGN_MPL:
2578 intval = va_arg (ap, int *);
2579 *intval = pgn_config.mpl;
2580 va_end (ap);
2581 return E_PGN_OK;
2582 case PGN_STOP_ON_ERROR:
2583 intval = va_arg (ap, int *);
2584 *intval = pgn_config.stop;
2585 va_end (ap);
2586 return E_PGN_OK;
2587 case PGN_PROGRESS:
2588 longval = va_arg (ap, long *);
2589 *longval = pgn_config.stop;
2590 va_end (ap);
2591 return E_PGN_OK;
2592 case PGN_PROGRESS_FUNC:
2593 progress = va_arg (ap, pgn_progress *);
2594 *progress = pgn_config.pfunc;
2595 va_end (ap);
2596 return E_PGN_OK;
2597 #ifdef DEBUG
2598 case PGN_DEBUG:
2599 intval = va_arg (ap, int *);
2600 *intval = dumptofile;
2601 va_end (ap);
2602 return E_PGN_OK;
2603 #endif
2604 default:
2605 break;
2608 return E_PGN_ERR;
2612 * Sets config flag 'f' to the next argument. Returns E_PGN_OK on success or
2613 * E_PGN_ERR if 'f' is an invalid flag or E_PGN_INVALID if 'val' is an invalid
2614 * flag value.
2616 pgn_error_t
2617 pgn_config_set (pgn_config_flag f, ...)
2619 va_list ap;
2620 int n;
2621 int ret = E_PGN_OK;
2623 va_start (ap, f);
2625 switch (f)
2627 case PGN_REDUCED:
2628 n = va_arg (ap, int);
2630 if (n != 1 && n != 0)
2632 ret = E_PGN_INVALID;
2633 break;
2636 pgn_config.reduced = n;
2637 break;
2638 case PGN_MPL:
2639 n = va_arg (ap, int);
2641 if (n < 0)
2643 ret = E_PGN_INVALID;
2644 break;
2647 pgn_config.mpl = n;
2648 break;
2649 case PGN_STOP_ON_ERROR:
2650 n = va_arg (ap, int);
2652 if (n != 1 && n != 0)
2654 ret = E_PGN_INVALID;
2655 break;
2658 pgn_config.stop = n;
2659 break;
2660 case PGN_PROGRESS:
2661 n = va_arg (ap, long);
2662 pgn_config.progress = n;
2663 break;
2664 case PGN_PROGRESS_FUNC:
2665 pgn_config.pfunc = va_arg (ap, pgn_progress);
2666 break;
2667 case PGN_STRICT_CASTLING:
2668 n = va_arg (ap, int);
2669 pgn_config.strict_castling = n;
2670 break;
2671 #ifdef DEBUG
2672 case PGN_DEBUG:
2673 n = va_arg (ap, int);
2674 dumptofile = (n > 0) ? 1 : 0;
2675 break;
2676 #endif
2677 default:
2678 ret = E_PGN_ERR;
2679 break;
2682 va_end (ap);
2683 return ret;
2687 * Writes a PGN formatted game 'g' to a file which was opened with pgn_open().
2688 * See 'pgn_config_flag' for output options. Returns E_PGN_ERR if there was a
2689 * memory allocation or write error and E_PGN_OK on success. It is important
2690 * to use pgn_close() afterwards if the file is a recognized compressed file
2691 * type otherwise the created temporary file wont be copied to the destination
2692 * filename.
2694 pgn_error_t
2695 pgn_write (PGN_FILE * pgn, GAME g)
2697 int i;
2698 int len = 0;
2700 if (!pgn)
2701 return E_PGN_ERR;
2703 pgn_write_turn = (TEST_FLAG (g->flags, GF_BLACK_OPENING)) ? BLACK : WHITE;
2704 pgn_tag_sort (g->tag);
2706 #ifdef DEBUG
2707 PGN_DUMP ("%s:%d: writing tag section\n", __FILE__, __LINE__);
2708 #endif
2710 for (i = 0; g->tag[i]; i++)
2712 struct tm tp;
2713 char tbuf[11] = { 0 };
2714 char *tmp;
2715 char *escaped;
2717 if (pgn_config.reduced && i == 7)
2718 break;
2720 if (strcmp (g->tag[i]->name, "Date") == 0)
2722 memset (&tp, 0, sizeof (struct tm));
2724 if (strptime (g->tag[i]->value, PGN_TIME_FORMAT, &tp) != NULL)
2726 len = strftime (tbuf, sizeof (tbuf), PGN_TIME_FORMAT, &tp) + 1;
2728 if ((tmp = strdup (tbuf)) == NULL)
2729 return E_PGN_ERR;
2731 free (g->tag[i]->value);
2732 g->tag[i]->value = tmp;
2735 else if (strcmp (g->tag[i]->name, "Event") == 0)
2737 if (g->tag[i]->value[0] == '\0')
2739 if ((tmp = strdup ("?")) == NULL)
2740 return E_PGN_ERR;
2742 free (g->tag[i]->value);
2743 g->tag[i]->value = tmp;
2746 else if (strcmp (g->tag[i]->name, "Site") == 0)
2748 if (g->tag[i]->value[0] == '\0')
2750 if ((tmp = strdup ("?")) == NULL)
2751 return E_PGN_ERR;
2753 free (g->tag[i]->value);
2754 g->tag[i]->value = tmp;
2757 else if (strcmp (g->tag[i]->name, "Round") == 0)
2759 if (g->tag[i]->value[0] == '\0')
2761 if ((tmp = strdup ("?")) == NULL)
2762 return E_PGN_ERR;
2764 free (g->tag[i]->value);
2765 g->tag[i]->value = tmp;
2768 else if (strcmp (g->tag[i]->name, "Result") == 0)
2770 if (g->tag[i]->value[0] == '\0')
2772 if ((tmp = strdup ("*")) == NULL)
2773 return E_PGN_ERR;
2775 free (g->tag[i]->value);
2776 g->tag[i]->value = tmp;
2779 else if (strcmp (g->tag[i]->name, "Black") == 0)
2781 if (g->tag[i]->value[0] == '\0')
2783 if ((tmp = strdup ("?")) == NULL)
2784 return E_PGN_ERR;
2786 free (g->tag[i]->value);
2787 g->tag[i]->value = tmp;
2790 else if (strcmp (g->tag[i]->name, "White") == 0)
2792 if (g->tag[i]->value[0] == '\0')
2794 if ((tmp = strdup ("?")) == NULL)
2795 return E_PGN_ERR;
2797 free (g->tag[i]->value);
2798 g->tag[i]->value = tmp;
2802 escaped = pgn_tag_add_escapes (g->tag[i]->value);
2803 fprintf (pgn->fp, "[%s \"%s\"]\n", g->tag[i]->name,
2804 (g->tag[i]->value && g->tag[i]->value[0]) ? escaped : "");
2805 free (escaped);
2808 #ifdef DEBUG
2809 PGN_DUMP ("%s:%d: writing move section\n", __FILE__, __LINE__);
2810 #endif
2811 Fputc ('\n', pgn->fp, &len);
2812 g->hp = g->history;
2813 ravlevel = pgn_mpl = 0;
2815 if (pgn_history_total (g->hp) && pgn_write_turn == BLACK)
2816 putstring (pgn->fp, (char *) "1...", &len);
2818 write_all_move_text (pgn->fp, g->hp, 1, &len);
2820 Fputc (' ', pgn->fp, &len);
2821 putstring (pgn->fp, g->tag[6]->value, &len);
2822 putstring (pgn->fp, (char *) "\n\n", &len);
2824 if (!pgn_config.reduced)
2825 CLEAR_FLAG (g->flags, GF_PERROR);
2827 return E_PGN_OK;
2831 * Clears the enpassant flag for all positions on board 'b'. Returns nothing.
2833 void
2834 pgn_reset_enpassant (BOARD b)
2836 int r, c;
2838 #ifdef DEBUG
2839 PGN_DUMP ("%s:%d: resetting enpassant\n", __FILE__, __LINE__);
2840 #endif
2842 for (r = 0; r < 8; r++)
2844 for (c = 0; c < 8; c++)
2845 b[r][c].enpassant = 0;