Proper gettext initialization.
[cboard.git] / libchess / pgn.c
blob43a34b68006ec7d5677ab0c8247c49eb5775011a
1 /* vim:tw=78:ts=8:sw=4:set ft=c: */
2 /*
3 Copyright (C) 2002-2011 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>
38 #ifdef HAVE_LIMITS_H
39 #include <limits.h>
40 #endif
42 #include "chess.h"
43 #include "common.h"
44 #include "pgn.h"
46 #ifdef DEBUG
47 #include "debug.h"
48 #endif
50 #ifdef WITH_DMALLOC
51 #include <dmalloc.h>
52 #endif
54 static int Fgetc(FILE *fp)
56 register int c;
58 if ((c = fgetc(fp)) != EOF) {
59 if (pgn_config.progress && pgn_config.pfunc) {
60 if (!(ftell(fp) % pgn_config.progress))
61 pgn_config.pfunc(pgn_fsize, ftell(fp));
65 return c;
68 static int Ungetc(int c, FILE *fp)
70 return ungetc(c, fp);
73 char *pgn_version()
75 return "libchess " PACKAGE_VERSION;
78 static char *trim(char *str)
80 int i = 0;
82 if (!str)
83 return NULL;
85 while (isspace(*str))
86 str++;
88 for (i = strlen(str) - 1; isspace(str[i]); i--)
89 str[i] = 0;
91 return str;
94 static char *itoa(long n)
96 static char buf[16];
98 snprintf(buf, sizeof(buf), "%li", n);
99 return buf;
103 * Clears the valid move flag for all positions on board 'b'. Returns nothing.
105 void pgn_reset_valid_moves(BOARD b)
107 int row, col;
109 #ifdef DEBUG
110 PGN_DUMP("%s:%d: resetting valid moves\n", __FILE__, __LINE__);
111 #endif
113 for (row = 0; row < 8; row++) {
114 for (col = 0; col < 8; col++)
115 b[row][col].valid = 0;
120 * Toggles g->turn. Returns nothing.
122 void pgn_switch_turn(GAME g)
124 g->turn = (g->turn == WHITE) ? BLACK : WHITE;
128 * Toggles g->side and switches the White and Black roster tags. Returns
129 * nothing.
131 void pgn_switch_side(GAME g)
133 char *w = g->tag[4]->value;
135 g->tag[4]->value = g->tag[5]->value;
136 g->tag[5]->value = w;
137 g->side = (g->side == WHITE) ? BLACK : WHITE;
141 * Creates a FEN tag from the current game 'g', history move (g->hindex) and
142 * board 'b'. Returns a FEN tag.
144 char *pgn_game_to_fen(GAME g, BOARD b)
146 int row, col;
147 int i;
148 static char buf[MAX_PGN_LINE_LEN], *p;
149 int oldturn = g->turn;
150 char enpassant[3] = {0}, *e;
151 int castle = 0;
153 #ifdef DEBUG
154 PGN_DUMP("%s:%d: creating FEN tag\n", __FILE__, __LINE__);
155 #endif
157 for (i = pgn_history_total(g->hp); i >= g->hindex - 1; i--)
158 pgn_switch_turn(g);
160 p = buf;
162 for (row = 0; row < 8; row++) {
163 int count = 0;
165 for (col = 0; col < 8; col++) {
166 if (b[row][col].enpassant) {
167 b[row][col].icon = pgn_int_to_piece(WHITE, OPEN_SQUARE);
168 e = enpassant;
169 *e++ = 'a' + col;
170 *e++ = ('0' + 8) - row;
171 *e = 0;
174 if (pgn_piece_to_int(b[row][col].icon) == OPEN_SQUARE) {
175 count++;
176 continue;
179 if (count) {
180 *p++ = '0' + count;
181 count = 0;
184 *p++ = b[row][col].icon;
185 *p = 0;
188 if (count) {
189 *p++ = '0' + count;
190 count = 0;
193 *p++ = '/';
196 --p;
197 *p++ = ' ';
198 *p++ = (g->turn == WHITE) ? 'w' : 'b';
199 *p++ = ' ';
201 if (TEST_FLAG(g->flags, GF_WK_CASTLE) && pgn_piece_to_int(b[7][7].icon) ==
202 ROOK && isupper(b[7][7].icon) && pgn_piece_to_int(b[7][4].icon) ==
203 KING && isupper(b[7][4].icon)) {
204 *p++ = 'K';
205 castle = 1;
208 if (TEST_FLAG(g->flags, GF_WQ_CASTLE) && pgn_piece_to_int(b[7][0].icon) ==
209 ROOK && isupper(b[7][0].icon) && pgn_piece_to_int(b[7][4].icon) ==
210 KING && isupper(b[7][4].icon)) {
211 *p++ = 'Q';
212 castle = 1;
215 if (TEST_FLAG(g->flags, GF_BK_CASTLE) && pgn_piece_to_int(b[0][7].icon) ==
216 ROOK && islower(b[0][7].icon) && pgn_piece_to_int(b[0][4].icon) ==
217 KING && islower(b[0][4].icon)) {
218 *p++ = 'k';
219 castle = 1;
222 if (TEST_FLAG(g->flags, GF_BQ_CASTLE) && pgn_piece_to_int(b[0][0].icon) ==
223 ROOK && islower(b[0][0].icon) && pgn_piece_to_int(b[0][4].icon) ==
224 KING && islower(b[0][4].icon)) {
225 *p++ = 'q';
226 castle = 1;
229 if (!castle)
230 *p++ = '-';
232 *p++ = ' ';
234 if (enpassant[0]) {
235 e = enpassant;
236 *p++ = *e++;
237 *p++ = *e++;
239 else
240 *p++ = '-';
242 *p++ = ' ';
244 // Halfmove clock.
245 *p = 0;
246 strcat(p, itoa(g->ply));
247 p = buf + strlen(buf);
248 *p++ = ' ';
250 // Fullmove number.
251 i = (g->hindex + 1) / 2;
252 *p = 0;
253 strcat(p, itoa((g->hindex / 2) + (g->hindex % 2)));
255 g->turn = oldturn;
256 return buf;
260 * Returns the total number of moves in 'h' or 0 if there are none.
262 int pgn_history_total(HISTORY **h)
264 int i;
266 if (!h)
267 return 0;
269 for (i = 0; h[i]; i++);
270 return i;
274 * Deallocates all of the history data from position 'start' in the array 'h'.
275 * Returns nothing.
277 void pgn_history_free(HISTORY **h, int start)
279 int i;
281 #ifdef DEBUG
282 PGN_DUMP("%s:%d: freeing history\n", __FILE__, __LINE__);
283 #endif
285 if (!h || start > pgn_history_total(h))
286 return;
288 if (start < 0)
289 start = 0;
291 for (i = start; h[i]; i++) {
292 if (h[i]->rav) {
293 pgn_history_free(h[i]->rav, 0);
294 free(h[i]->rav);
297 free(h[i]->comment);
298 free(h[i]->move);
299 free(h[i]->fen);
300 free(h[i]);
303 h[start] = NULL;
307 * Returns the history ply 'n' from 'h'. If 'n' is out of range then NULL is
308 * returned.
310 HISTORY *pgn_history_by_n(HISTORY **h, int n)
312 if (n < 0 || n > pgn_history_total(h) - 1)
313 return NULL;
315 return h[n];
319 * Appends move 'm' to game 'g' history pointer and creates a FEN tag for the
320 * current game state using board 'b'. The FEN tag makes things faster than
321 * validating the entire move history by validating only the current move to
322 * the previous moves FEN tag. The history pointer may be a in a RAV so
323 * g->rav.hp is also updated to the new (realloc()'ed) pointer. If not in a
324 * RAV then g->history will be updated. Returns E_PGN_ERR if realloc() failed
325 * or E_PGN_OK on success.
327 pgn_error_t pgn_history_add(GAME g, BOARD b, const char *m)
329 int t = pgn_history_total(g->hp);
330 int o;
331 HISTORY **h = NULL, *tmp;
332 int ri = (g->ravlevel) ? g->rav[g->ravlevel - 1].hindex : 0;
334 #ifdef DEBUG
335 PGN_DUMP("%s:%d: adding '%s' to move history\n", __FILE__, __LINE__, m);
336 #endif
338 if (g->ravlevel)
339 o = g->rav[g->ravlevel - 1].hp[ri-1]->rav - g->hp;
340 else
341 o = g->history - g->hp;
343 if ((h = realloc(g->hp, (t + 2) * sizeof(HISTORY *))) == NULL)
344 return E_PGN_ERR;
346 g->hp = h;
348 if (g->ravlevel)
349 g->rav[g->ravlevel - 1].hp[ri-1]->rav = g->hp + o;
350 else
351 g->history = g->hp + o;
353 if ((g->hp[t] = calloc(1, sizeof(HISTORY))) == NULL)
354 return E_PGN_ERR;
356 if ((g->hp[t]->move = strdup(m)) == NULL) {
357 free(g->hp[t]);
358 g->hp[t] = NULL;
359 return E_PGN_ERR;
362 tmp = g->hp[t];
363 t++;
364 g->hp[t] = NULL;
365 g->hindex = pgn_history_total(g->hp);
366 pgn_switch_turn(g);
367 tmp->fen = strdup(pgn_game_to_fen(g, b));
368 pgn_switch_turn(g);
369 return E_PGN_OK;
373 * Resets the game 'g' using board 'b' up to history move (g->hindex) 'n'.
374 * Returns E_PGN_OK on success or E_PGN_PARSE if there was a FEN tag but the
375 * parsing of it failed. Or returns E_PGN_INVALID if somehow the move failed
376 * validation while resetting.
378 pgn_error_t pgn_board_update(GAME g, BOARD b, int n)
380 BOARD tb;
381 int ret = E_PGN_OK;
382 int p_error = TEST_FLAG(g->flags, GF_PERROR);
383 int black_opening = TEST_FLAG(g->flags, GF_BLACK_OPENING);
385 #ifdef DEBUG
386 PGN_DUMP("%s:%d: updating board\n", __FILE__, __LINE__);
387 #endif
389 if (!g->ravlevel && TEST_FLAG(g->flags, GF_BLACK_OPENING))
390 g->turn = BLACK;
391 else
392 g->turn = WHITE;
394 g->flags = 0;
395 SET_FLAG(g->flags, GF_WK_CASTLE|GF_WQ_CASTLE|GF_WQ_CASTLE|
396 GF_BK_CASTLE|GF_BQ_CASTLE);
397 pgn_board_init(tb);
399 if (g->ravlevel)
400 pgn_board_init_fen(g, tb, g->rav[g->ravlevel - 1].fen);
401 else if (pgn_tag_find(g->tag, "FEN") != -1 &&
402 pgn_board_init_fen(g, tb, NULL))
403 return E_PGN_PARSE;
405 if (n) {
406 HISTORY *h = pgn_history_by_n(g->hp, n-1);
408 if (h) {
409 ret = pgn_board_init_fen(g, tb, h->fen);
410 if (ret == E_PGN_OK) {
411 h = pgn_history_by_n(g->hp, n);
412 if (h) {
413 char *p = h->move, *frfr = NULL;
415 ret = pgn_parse_move(g, tb, &p, &frfr);
416 if (ret == E_PGN_OK) {
417 h = pgn_history_by_n(g->hp, n-1);
418 ret = pgn_board_init_fen(g, tb, h->fen);
421 free(frfr);
427 if (ret == E_PGN_OK)
428 memcpy(b, tb, sizeof(BOARD));
430 if (p_error)
431 SET_FLAG(g->flags, GF_PERROR);
433 if (black_opening)
434 SET_FLAG(g->flags, GF_BLACK_OPENING);
436 return ret;
440 * Updates the game 'g' using board 'b' to the next 'n'th history move.
441 * Returns nothing.
443 void pgn_history_prev(GAME g, BOARD b, int n)
445 if (g->hindex - n < 0) {
446 if (n <= 2)
447 g->hindex = pgn_history_total(g->hp);
448 else
449 g->hindex = 0;
451 else
452 g->hindex -= n;
454 pgn_board_update(g, b, g->hindex);
458 * Updates the game 'g' using board 'b' to the previous 'n'th history move.
459 * Returns nothing.
461 void pgn_history_next(GAME g, BOARD b, int n)
463 if (g->hindex + n > pgn_history_total(g->hp)) {
464 if (n <= 2)
465 g->hindex = 0;
466 else
467 g->hindex = pgn_history_total(g->hp);
469 else
470 g->hindex += n;
472 pgn_board_update(g, b, g->hindex);
476 * Converts the character piece 'p' to an integer. Returns the integer
477 * associated with 'p' or E_PGN_ERR if 'p' is invalid.
479 int pgn_piece_to_int(int p)
481 if (p == '.')
482 return OPEN_SQUARE;
484 p = tolower(p);
486 switch (p) {
487 case 'p':
488 return PAWN;
489 case 'r':
490 return ROOK;
491 case 'n':
492 return KNIGHT;
493 case 'b':
494 return BISHOP;
495 case 'q':
496 return QUEEN;
497 case 'k':
498 return KING;
499 default:
500 break;
503 #ifdef DEBUG
504 PGN_DUMP("%s:%d: invalid piece '%c'\n", __FILE__, __LINE__, p);
505 #endif
506 return E_PGN_ERR;
510 * Converts the integer piece 'n' to a character whose turn is 'turn'. WHITE
511 * piece are uppercase and BLACK pieces are lowercase. Returns the character
512 * associated with 'n' or E_PGN_ERR if 'n' is an invalid piece.
514 pgn_error_t pgn_int_to_piece(char turn, int n)
516 int p = 0;
518 switch (n) {
519 case PAWN:
520 p = 'p';
521 break;
522 case ROOK:
523 p = 'r';
524 break;
525 case KNIGHT:
526 p = 'n';
527 break;
528 case BISHOP:
529 p = 'b';
530 break;
531 case QUEEN:
532 p = 'q';
533 break;
534 case KING:
535 p = 'k';
536 break;
537 case OPEN_SQUARE:
538 p = '.';
539 break;
540 default:
541 #ifdef DEBUG
542 PGN_DUMP("%s:%d: unknown piece integer %i\n", __FILE__,
543 __LINE__, n);
544 #endif
545 return E_PGN_ERR;
546 break;
549 return (turn == WHITE) ? toupper(p) : p;
553 * Finds a tag 'name' in the structure array 't'. Returns the location in the
554 * array of the found tag or E_PGN_ERR if the tag could not be found.
556 pgn_error_t pgn_tag_find(TAG **t, const char *name)
558 int i;
560 for (i = 0; t[i]; i++) {
561 if (strcasecmp(t[i]->name, name) == 0)
562 return i;
565 return E_PGN_ERR;
568 static pgn_error_t remove_tag(TAG ***array, const char *tag)
570 TAG **tags = *array;
571 int n = pgn_tag_find(tags, tag);
572 int i, t;
574 if (n == E_PGN_ERR)
575 return E_PGN_ERR;
577 for (i = t = 0; tags[i]; i++) {
578 if (i == n) {
579 free(tags[i]->name);
580 free(tags[i]->value);
581 free(tags[i]);
582 continue;
585 tags[t++] = tags[i];
588 tags = realloc(*array, (t + 1) * sizeof(TAG *));
589 tags[t] = NULL;
590 *array = tags;
591 #ifdef DEBUG
592 PGN_DUMP("%s:%d: removed tag: name='%s'\n", __FILE__, __LINE__, tag);
593 #endif
594 return E_PGN_OK;
597 static int tag_compare(const void *a, const void *b)
599 TAG * const *ta = a;
600 TAG * const *tb = b;
602 return strcmp((*ta)->name, (*tb)->name);
606 * Sorts a tag array. The first seven tags are in order of the PGN standard so
607 * don't sort'em. Returns nothing.
609 void pgn_tag_sort(TAG **tags)
611 if (pgn_tag_total(tags) <= 7)
612 return;
614 qsort(tags + 7, pgn_tag_total(tags) - 7, sizeof(TAG *), tag_compare);
618 * Returns the total number of tags in 't' or 0 if 't' is NULL.
620 int pgn_tag_total(TAG **tags)
622 int i = 0;
624 if (!tags)
625 return 0;
627 while (tags[i])
628 i++;
630 return i;
634 * Adds a tag 'name' with value 'value' to the pointer to array of TAG
635 * pointers 'dst'. If a duplicate tag 'name' was found then the existing tag
636 * is updated to the new 'value'. If 'value' is NULL, the tag is removed.
637 * Returns E_PGN_ERR if there was a memory allocation error or E_PGN_OK on
638 * success.
640 pgn_error_t pgn_tag_add(TAG ***dst, char *name, char *value)
642 int i;
643 TAG **tdata = *dst;
644 TAG **a = NULL;
645 int t = pgn_tag_total(tdata);
647 #ifdef DEBUG
648 PGN_DUMP("%s:%d: adding tag\n", __FILE__, __LINE__);
649 #endif
651 if (!name)
652 return E_PGN_ERR;
654 name = trim(name);
656 if (value)
657 value = trim(value);
659 // Find an existing tag with 'name'.
660 for (i = 0; i < t; i++) {
661 char *tmp = NULL;
663 if (strcasecmp(tdata[i]->name, name) == 0) {
664 if (value) {
665 if ((tmp = strdup(value)) == NULL)
666 return E_PGN_ERR;
668 else {
669 remove_tag(dst, name);
670 return E_PGN_OK;
673 free(tdata[i]->value);
674 tdata[i]->value = tmp;
675 *dst = tdata;
676 return E_PGN_OK;
680 if ((a = realloc(tdata, (t + 2) * sizeof(TAG *))) == NULL)
681 return E_PGN_ERR;
683 tdata = a;
685 if ((tdata[t] = malloc(sizeof(TAG))) == NULL)
686 return E_PGN_ERR;
688 if ((tdata[t]->name = strdup(name)) == NULL) {
689 tdata[t] = NULL;
690 return E_PGN_ERR;
693 if (value) {
694 if ((tdata[t]->value = strdup(value)) == NULL) {
695 free(tdata[t]->name);
696 tdata[t] = NULL;
697 return E_PGN_ERR;
700 else
701 tdata[t]->value = NULL;
703 tdata[t]->name[0] = toupper(tdata[t]->name[0]);
704 tdata[++t] = NULL;
705 *dst = tdata;
707 #ifdef DEBUG
708 PGN_DUMP("%s:%d: added tag: name='%s' value='%s'\n", __FILE__, __LINE__,
709 name, (value) ? value : "null");
710 #endif
712 return E_PGN_OK;
715 static char *remove_tag_escapes(const char *str)
717 int i, n;
718 int len = strlen(str);
719 static char buf[MAX_PGN_LINE_LEN] = {0};
721 for (i = n = 0; i < len; i++, n++) {
722 switch (str[i]) {
723 case '\\':
724 i++;
725 default:
726 break;
729 buf[n] = str[i];
732 buf[n] = '\0';
733 return buf;
737 * Resets or initializes a new game board 'b'. Returns nothing.
739 void pgn_board_init(BOARD b)
741 int row, col;
743 #ifdef DEBUG
744 PGN_DUMP("%s:%d: initializing board\n", __FILE__, __LINE__);
745 #endif
747 memset(b, 0, sizeof(BOARD));
749 for (row = 0; row < 8; row++) {
750 for (col = 0; col < 8; col++) {
751 int c = '.';
753 switch (row) {
754 case 0:
755 case 7:
756 switch (col) {
757 case 0:
758 case 7:
759 c = 'r';
760 break;
761 case 1:
762 case 6:
763 c = 'n';
764 break;
765 case 2:
766 case 5:
767 c = 'b';
768 break;
769 case 3:
770 c = 'q';
771 break;
772 case 4:
773 c = 'k';
774 break;
776 break;
777 case 1:
778 case 6:
779 c = 'p';
780 break;
783 b[row][col].icon = (row < 2) ? c : toupper(c);
789 * Adds the standard PGN roster tags to game 'g'.
791 static void set_default_tags(GAME g)
793 time_t now;
794 char tbuf[11] = {0};
795 struct tm *tp;
796 struct passwd *pw = getpwuid(getuid());
798 time(&now);
799 tp = localtime(&now);
800 strftime(tbuf, sizeof(tbuf), PGN_TIME_FORMAT, tp);
802 /* The standard seven tag roster (in order of appearance). */
803 if (pgn_tag_add(&g->tag, "Event", "?") != E_PGN_OK)
804 warn("pgn_tag_add()");
806 if (pgn_tag_add(&g->tag, "Site", "?") != E_PGN_OK)
807 warn("pgn_tag_add()");
809 if (pgn_tag_add(&g->tag, "Date", tbuf) != E_PGN_OK)
810 warn("pgn_tag_add()");
812 if (pgn_tag_add(&g->tag, "Round", "-") != E_PGN_OK)
813 warn("pgn_tag_add()");
815 if (pgn_tag_add(&g->tag, "White", pw->pw_gecos) != E_PGN_OK)
816 warn("pgn_tag_add()");
818 if (pgn_tag_add(&g->tag, "Black", "?") != E_PGN_OK)
819 warn("pgn_tag_add()");
821 if (pgn_tag_add(&g->tag, "Result", "*") != E_PGN_OK)
822 warn("pgn_tag_add()");
826 * Frees a TAG array. Returns nothing.
828 void pgn_tag_free(TAG **tags)
830 int i;
831 int t = pgn_tag_total(tags);
833 #ifdef DEBUG
834 PGN_DUMP("%s:%d: freeing tags\n", __FILE__, __LINE__);
835 #endif
837 if (!tags)
838 return;
840 for (i = 0; i < t; i++) {
841 free(tags[i]->name);
842 free(tags[i]->value);
843 free(tags[i]);
846 free(tags);
850 * Frees a single game 'g'. Returns nothing.
852 void pgn_free(GAME g)
854 #ifdef DEBUG
855 PGN_DUMP("%s:%d: freeing game\n", __FILE__, __LINE__);
856 #endif
857 pgn_history_free(g->history, 0);
858 free(g->history);
859 pgn_tag_free(g->tag);
861 if (g->rav) {
862 for (g->ravlevel--; g->ravlevel >= 0; g->ravlevel--)
863 free(g->rav[g->ravlevel].fen);
865 free(g->rav);
868 free(g);
872 * Frees all games in the global 'game' array. Returns nothing.
874 void pgn_free_all()
876 int i;
878 #ifdef DEBUG
879 PGN_DUMP("%s:%d: freeing game data\n", __FILE__, __LINE__);
880 #endif
882 for (i = 0; i < gtotal; i++) {
883 pgn_free(game[i]);
886 if (game)
887 free(game);
889 game = NULL;
892 static void reset_game_data()
894 #ifdef DEBUG
895 PGN_DUMP("%s:%d: resetting game data\n", __FILE__, __LINE__);
896 #endif
897 pgn_free_all();
898 gtotal = gindex = 0;
901 static void skip_leading_space(FILE *fp)
903 int c;
905 while ((c = Fgetc(fp)) != EOF && !feof(fp)) {
906 if (!isspace(c))
907 break;
910 Ungetc(c, fp);
914 * PGN move text section.
916 static int move_text(GAME g, FILE *fp)
918 char m[MAX_SAN_MOVE_LEN + 1] = {0}, *p;
919 int c;
920 int count;
921 char *frfr = NULL;
923 g->oflags = g->flags;
925 while((c = Fgetc(fp)) != EOF) {
926 if (isdigit(c) || isspace(c) || c == '.')
927 continue;
929 break;
932 Ungetc(c, fp);
934 if (fscanf(fp, " %[a-hPpRrNnBbQqKk1-9#+=Ox-]%n", m, &count) != 1)
935 return 1;
937 m[MAX_SAN_MOVE_LEN] = 0;
938 p = m;
940 if (pgn_parse_move(g, pgn_board, &p, &frfr)) {
941 pgn_switch_turn(g);
942 return 1;
945 free(frfr);
946 #ifdef DEBUG
947 PGN_DUMP("%s\n%s", p, debug_board(pgn_board));
948 #endif
950 pgn_history_add(g, pgn_board, p);
951 pgn_switch_turn(g);
952 return 0;
956 * PGN nag text.
958 static void nag_text(GAME g, FILE *fp)
960 int c, i, t;
961 char nags[5], *n = nags;
962 int nag = 0;
964 while ((c = Fgetc(fp)) != EOF && !isspace(c)) {
965 if (c == '$') {
966 while ((c = Fgetc(fp)) != EOF && isdigit(c))
967 *n++ = c;
969 Ungetc(c, fp);
970 break;
973 if (c == '!') {
974 if ((c = Fgetc(fp)) == '!')
975 nag = 3;
976 else if (c == '?')
977 nag = 5;
978 else {
979 Ungetc(c, fp);
980 nag = 1;
983 break;
985 else if (c == '?') {
986 if ((c = Fgetc(fp)) == '?')
987 nag = 4;
988 else if (c == '!')
989 nag = 6;
990 else {
991 Ungetc(c, fp);
992 nag = 2;
995 break;
997 else if (c == '~')
998 nag = 13;
999 else if (c == '=') {
1000 if ((c = Fgetc(fp)) == '+')
1001 nag = 15;
1002 else {
1003 Ungetc(c, fp);
1004 nag = 10;
1007 break;
1009 else if (c == '+') {
1010 if ((t = Fgetc(fp)) == '=')
1011 nag = 14;
1012 else if (t == '-')
1013 nag = 18;
1014 else if (t == '/') {
1015 if ((i = Fgetc(fp)) == '-')
1016 nag = 16;
1017 else
1018 Ungetc(i, fp);
1020 break;
1022 else
1023 Ungetc(t, fp);
1025 break;
1027 else if (c == '-') {
1028 if ((t = Fgetc(fp)) == '+')
1029 nag = 18;
1030 else if (t == '/') {
1031 if ((i = Fgetc(fp)) == '+')
1032 nag = 17;
1033 else
1034 Ungetc(i, fp);
1036 break;
1038 else
1039 Ungetc(t, fp);
1041 break;
1045 *n = '\0';
1047 if (!nag)
1048 nag = (nags[0]) ? atoi(nags) : 0;
1050 if (!nag || nag < 0 || nag > 255)
1051 return;
1053 // FIXME -1 is because move_text() increments g->hindex. The NAG
1054 // annoatation isnt guaranteed to be after the move text in import format.
1055 for (i = 0; i < MAX_PGN_NAG; i++) {
1056 if (g->hp[g->hindex - 1]->nag[i])
1057 continue;
1059 g->hp[g->hindex - 1]->nag[i] = nag;
1060 break;
1063 skip_leading_space(fp);
1067 * PGN move annotation.
1069 static int annotation_text(GAME g, FILE *fp, int terminator)
1071 int c, lastchar = 0;
1072 int len = 0;
1073 int hindex = pgn_history_total(g->hp) - 1;
1074 char buf[MAX_PGN_LINE_LEN], *a = buf;
1076 skip_leading_space(fp);
1078 while ((c = Fgetc(fp)) != EOF && c != terminator) {
1079 if (c == '\n')
1080 c = ' ';
1082 if (isspace(c) && isspace(lastchar))
1083 continue;
1085 if (len + 1 == sizeof(buf))
1086 continue;
1088 *a++ = lastchar = c;
1089 len++;
1092 *a = '\0';
1094 if (hindex < 0)
1095 hindex = 0;
1098 * This annotation is before any move text or NAg-> Allocate a new move.
1100 if (!g->hp[hindex]) {
1101 if ((g->hp[hindex] = calloc(1, sizeof(HISTORY))) == NULL)
1102 return E_PGN_ERR;
1105 if ((g->hp[hindex]->comment = strdup(buf)) == NULL)
1106 return E_PGN_ERR;
1108 return E_PGN_OK;
1112 * PGN roster tag->
1114 static int tag_text(GAME g, FILE *fp)
1116 char name[LINE_MAX], *n = name;
1117 char value[LINE_MAX], *v = value;
1118 int c, i = 0;
1119 int lastchar = 0;
1121 skip_leading_space(fp);
1123 /* The tag name is up until the first whitespace. */
1124 while ((c = Fgetc(fp)) != EOF && !isspace(c))
1125 *n++ = c;
1127 *n = '\0';
1128 *name = toupper(*name);
1129 skip_leading_space(fp);
1131 /* The value is until the first closing bracket. */
1132 while ((c = Fgetc(fp)) != EOF && c != ']') {
1133 if (i++ == '\0' && c == '\"')
1134 continue;
1136 if (c == '\n' || c == '\t')
1137 c = ' ';
1139 if (c == ' ' && lastchar == ' ')
1140 continue;
1142 lastchar = *v++ = c;
1145 *v = '\0';
1147 while (isspace(*--v))
1148 *v = '\0';
1150 if (*v == '\"')
1151 *v = '\0';
1153 if (value[0] == '\0') {
1154 if (strcmp(name, "Result") == 0)
1155 value[0] = '*';
1156 else
1157 value[0] = '?';
1159 value[1] = '\0';
1162 strncpy(value, remove_tag_escapes(value), sizeof(value));
1165 * See eog_text() for an explanation.
1167 if (strcmp(name, "Result") == 0) {
1168 if (strcmp(value, "1/2-1/2") == 0) {
1169 if (pgn_tag_add(&g->tag, name, value) != E_PGN_OK)
1170 warn("pgn_tag_add()");
1173 else {
1174 if (pgn_tag_add(&g->tag, name, value) != E_PGN_OK)
1175 warn("pgn_tag_add()");
1178 return 0;
1182 * PGN end-of-game marker.
1184 static int eog_text(GAME g, FILE *fp)
1186 int c, i = 0;
1187 char buf[8], *p = buf;
1189 while ((c = Fgetc(fp)) != EOF && !isspace(c) && i++ < sizeof(buf))
1190 *p++ = c;
1192 if (isspace(c))
1193 Ungetc(c, fp);
1195 *p = 0;
1197 if (strcmp(buf, "1-0") != 0 && strcmp(buf, "0-1") != 0 &&
1198 strcmp(buf, "1/2-1/2") != 0 && strcmp(buf, "*") != 0)
1199 return 1;
1202 * The eog marker in the move text may not match the actual game result.
1203 * We'll leave it up to the move validator to determine the result unless
1204 * the move validator cannot determine who won and the move text says it's
1205 * a draw.
1207 if (g->tag[6]->value[0] == '*' && strcmp("1/2-1/2", buf) == 0) {
1208 if (pgn_tag_add(&g->tag, "Result", buf) != E_PGN_OK)
1209 warn("pgn_tag_add()");
1212 return 0;
1216 * Parse RAV text and keep track of g->hp. The 'o' argument is the board state
1217 * before the current move (.hindex) was parsed.
1219 static int read_file(FILE *);
1220 static int rav_text(GAME g, FILE *fp, int which, BOARD o)
1222 struct game_s tg;
1223 RAV *r;
1225 // Begin RAV for the current move.
1226 if (which == '(') {
1227 if (!g->hindex)
1228 return 1;
1230 pgn_rav++; // For detecting parse errors.
1233 * Save the current game state for this RAV depth/level.
1235 if ((r = realloc(g->rav, (g->ravlevel + 1) * sizeof(RAV))) == NULL) {
1236 warn("realloc()");
1237 return 1;
1240 g->rav = pgn_rav_p = r;
1242 if ((g->rav[g->ravlevel].fen = strdup(pgn_game_to_fen(g, pgn_board)))
1243 == NULL) {
1244 warn("strdup()");
1245 return 1;
1248 g->rav[g->ravlevel].hp = g->hp;
1249 g->rav[g->ravlevel].hindex = g->hindex;
1250 memcpy(&tg, &(*g), sizeof(struct game_s));
1251 g->flags = g->oflags;
1252 memcpy(pgn_board, o, sizeof(BOARD));
1254 if ((g->hp[g->hindex - 1]->rav = calloc(1, sizeof(HISTORY *))) == NULL) {
1255 warn("calloc()");
1256 return 1;
1260 * pgn_history_add() will now append to the new history pointer that
1261 * is g->hp[previous_move]->rav.
1263 g->hp = g->hp[g->hindex - 1]->rav;
1266 * Reset. Will be restored later from 'tg' which is a local variable
1267 * so recursion is possible.
1269 g->hindex = 0;
1270 g->ravlevel++;
1273 * Undo move_text()'s switch.
1275 pgn_switch_turn(g);
1278 * Now continue as normal as if there were no RAV. Moves will be
1279 * parsed and appended to the new history pointer.
1281 if (read_file(fp))
1282 return 1;
1285 * read_file() has returned. This means that a RAV has ended by this
1286 * function returning -1 (see below). So we restore the game state
1287 * (tg) that was saved before calling read_file().
1289 pgn_board_init_fen(&tg, pgn_board, g->rav[tg.ravlevel].fen);
1290 free(g->rav[tg.ravlevel].fen);
1291 memcpy(&(*g), &tg, sizeof(struct game_s));
1292 g->rav = pgn_rav_p;
1295 * The end of a RAV. This makes the read_file() that called this function
1296 * return (see above and the switch statement in read_file()).
1298 else if (which == ')') {
1299 pgn_rav--; // For detecting parse errors.
1300 return (pgn_rav < 0) ? 1 : -1;
1303 return 0;
1307 * FIXME
1308 * See pgn_board_init_fen(). Returns E_PGN_PARSE on parse error. 0 may be
1309 * returned on success when there is no move count in the FEN tag otherwise
1310 * the move count is returned.
1312 static int parse_fen_line(BOARD b, unsigned *flags, char *turn, char *ply,
1313 char *str)
1315 char *tmp;
1316 char line[LINE_MAX], *s;
1317 int row = 8, col = 1;
1319 #ifdef DEBUG
1320 PGN_DUMP("%s:%d: FEN line is '%s'\n", __FILE__, __LINE__, str);
1321 #endif
1322 strncpy(line, str, sizeof(line));
1323 s = line;
1324 pgn_reset_enpassant(b);
1326 while ((tmp = strsep(&s, "/")) != NULL) {
1327 int n;
1329 if (!VALIDFILE(row))
1330 return E_PGN_PARSE;
1332 while (*tmp) {
1333 if (*tmp == ' ')
1334 goto other;
1336 if (isdigit(*tmp)) {
1337 n = *tmp - '0';
1339 if (!VALIDFILE(n))
1340 return E_PGN_PARSE;
1342 for (; n; --n, col++)
1343 b[RANKTOBOARD(row)][FILETOBOARD(col)].icon =
1344 pgn_int_to_piece(WHITE, OPEN_SQUARE);
1346 else if (pgn_piece_to_int(*tmp) != -1)
1347 b[RANKTOBOARD(row)][FILETOBOARD(col++)].icon = *tmp;
1348 else
1349 return E_PGN_PARSE;
1351 tmp++;
1354 row--;
1355 col = 1;
1358 other:
1359 tmp++;
1361 switch (*tmp++) {
1362 case 'b':
1363 *turn = BLACK;
1364 break;
1365 case 'w':
1366 *turn = WHITE;
1367 break;
1368 default:
1369 return E_PGN_PARSE;
1372 tmp++;
1374 while (*tmp && *tmp != ' ') {
1375 switch (*tmp++) {
1376 case 'K':
1377 SET_FLAG(*flags, GF_WK_CASTLE);
1378 break;
1379 case 'Q':
1380 SET_FLAG(*flags, GF_WQ_CASTLE);
1381 break;
1382 case 'k':
1383 SET_FLAG(*flags, GF_BK_CASTLE);
1384 break;
1385 case 'q':
1386 SET_FLAG(*flags, GF_BQ_CASTLE);
1387 break;
1388 case '-':
1389 break;
1390 default:
1391 return E_PGN_PARSE;
1395 tmp++;
1397 // En passant.
1398 if (*tmp != '-') {
1399 if (!VALIDCOL(*tmp))
1400 return E_PGN_PARSE;
1402 col = *tmp++ - 'a';
1404 if (!VALIDROW(*tmp))
1405 return E_PGN_PARSE;
1407 row = 8 - atoi(tmp++);
1408 b[row][col].enpassant = 1;
1409 SET_FLAG(*flags, GF_ENPASSANT);
1411 else
1412 tmp++;
1414 if (*tmp)
1415 tmp++;
1417 if (*tmp)
1418 *ply = atoi(tmp);
1420 while (*tmp && isdigit(*tmp))
1421 tmp++;
1423 if (*tmp)
1424 tmp++;
1426 return E_PGN_OK;
1430 * It initializes the board (b) to the FEN tag (if found) and sets the
1431 * castling and enpassant info for the game 'g'. If 'fen' is set it should be
1432 * a fen tag and will be parsed rather than the game 'g'.tag FEN tag-> Returns
1433 * E_PGN_OK on success or if there was both a FEN and SetUp tag with the SetUp
1434 * tag set to 0. Returns E_PGN_PARSE if there was a FEN parse error, E_PGN_ERR
1435 * if there was no FEN tag or there was a SetUp tag with a value of 0. Returns
1436 * E_PGN_OK on success.
1438 pgn_error_t pgn_board_init_fen(GAME g, BOARD b, char *fen)
1440 int n = -1, i = -1;
1441 BOARD tmpboard;
1442 unsigned flags = 0;
1443 char turn = g->turn;
1444 char ply = 0;
1446 #ifdef DEBUG
1447 PGN_DUMP("%s:%d: initializing board from FEN\n", __FILE__, __LINE__);
1448 #endif
1449 pgn_board_init(tmpboard);
1451 if (!fen) {
1452 n = pgn_tag_find(g->tag, "Setup");
1453 i = pgn_tag_find(g->tag, "FEN");
1457 * If the FEN tag exists and there is no SetUp tag go ahead and parse it.
1458 * If there is a SetUp tag only parse the FEN tag if the value is 1.
1460 if ((n >= 0 && i >= 0 && atoi(g->tag[n]->value) == 1)
1461 || (i >= 0 && n == -1) || fen) {
1462 if ((n = parse_fen_line(tmpboard, &flags, &turn, &ply,
1463 (fen) ? fen : g->tag[i]->value)) != E_PGN_OK)
1464 return E_PGN_PARSE;
1465 else {
1466 memcpy(b, tmpboard, sizeof(BOARD));
1467 CLEAR_FLAG(g->flags, GF_WK_CASTLE);
1468 CLEAR_FLAG(g->flags, GF_WQ_CASTLE);
1469 CLEAR_FLAG(g->flags, GF_BK_CASTLE);
1470 CLEAR_FLAG(g->flags, GF_BQ_CASTLE);
1471 g->flags |= flags;
1472 g->turn = turn;
1473 g->ply = ply;
1476 else
1477 return (i >= 0 && n >= 0) ? E_PGN_OK : E_PGN_ERR;
1479 return E_PGN_OK;
1483 * Allocates a new game and increments 'gtotal'. 'gindex' is then set to the
1484 * new game. Returns E_PGN_ERR if there was a memory allocation error or
1485 * E_PGN_OK on success.
1487 pgn_error_t pgn_new_game()
1489 GAME *g;
1490 GAME newg;
1491 int t = gtotal + 1;
1493 #ifdef DEBUG
1494 PGN_DUMP("%s:%d: allocating new game\n", __FILE__, __LINE__);
1495 #endif
1496 gindex = t - 1;
1498 if ((g = realloc(game, t * sizeof(GAME *))) == NULL) {
1499 warn("realloc()");
1500 return E_PGN_ERR;
1503 game = g;
1505 if ((newg = calloc(1, sizeof(struct game_s))) == NULL) {
1506 warn("calloc()");
1507 return E_PGN_ERR;
1510 game[gindex] = newg;
1512 if ((game[gindex]->hp = calloc(1, sizeof(HISTORY *))) == NULL) {
1513 free(game[gindex]);
1514 warn("calloc()");
1515 return E_PGN_ERR;
1518 game[gindex]->hp[0] = NULL;
1519 game[gindex]->history = game[gindex]->hp;
1520 game[gindex]->side = game[gindex]->turn = WHITE;
1521 SET_FLAG(game[gindex]->flags, GF_WK_CASTLE|GF_WQ_CASTLE|GF_WQ_CASTLE|
1522 GF_BK_CASTLE|GF_BQ_CASTLE);
1523 pgn_board_init(pgn_board);
1524 set_default_tags(game[gindex]);
1525 gtotal = t;
1526 return E_PGN_OK;
1529 static int read_file(FILE *fp)
1531 #ifdef DEBUG
1532 char buf[LINE_MAX] = {0}, *p = buf;
1533 #endif
1534 int c = 0;
1535 int parse_error = 0;
1536 BOARD old;
1538 while (1) {
1539 int nextchar = 0;
1540 int lastchar = c;
1541 int n;
1544 * A parse error may have occured at EOF.
1546 if (parse_error) {
1547 pgn_ret = E_PGN_PARSE;
1549 if (!game)
1550 pgn_new_game();
1552 SET_FLAG(game[gindex]->flags, GF_PERROR);
1555 if ((c = Fgetc(fp)) == EOF) {
1556 if (feof(fp))
1557 break;
1559 if (ferror(fp)) {
1560 clearerr(fp);
1561 continue;
1565 if (!isascii(c)) {
1566 parse_error = 1;
1567 continue;
1570 if (c == '\015')
1571 continue;
1573 nextchar = Fgetc(fp);
1574 Ungetc(nextchar, fp);
1577 * If there was a move text parsing error, keep reading until the end
1578 * of the current game discarding the data.
1580 if (parse_error) {
1581 pgn_ret = E_PGN_PARSE;
1583 if (game[gindex]->ravlevel)
1584 return 1;
1586 if (pgn_config.stop)
1587 return E_PGN_PARSE;
1589 if (c == '\n' && (nextchar == '\n' || nextchar == '\015')) {
1590 parse_error = 0;
1591 nulltags = 1;
1592 tag_section = 0;
1594 else
1595 continue;
1598 // New game reached.
1599 if (c == '\n' && (nextchar == '\n' || nextchar == '\015')) {
1600 if (tag_section)
1601 continue;
1603 nulltags = 1;
1604 tag_section = 0;
1605 continue;
1609 * PGN: Application comment. The '%' must be on the first column of
1610 * the line. The comment continues until the end of the current line.
1612 if (c == '%') {
1613 if (lastchar == '\n' || lastchar == 0) {
1614 while ((c = Fgetc(fp)) != EOF && c != '\n');
1615 continue;
1618 // Not sure what to do here.
1621 if (isspace(c))
1622 continue;
1624 // PGN: Reserved.
1625 if (c == '<' || c == '>')
1626 continue;
1629 * PGN: Recurrsive Annotated Variation. Read rav_text() for more
1630 * info.
1632 if (c == '(' || c == ')') {
1633 switch (rav_text(game[gindex], fp, c, old)) {
1634 case -1:
1636 * This is the end of the current RAV. This function has
1637 * been called from rav_text(). Returning from this point
1638 * will put us back in rav_text().
1640 if (game[gindex]->ravlevel > 0)
1641 return pgn_ret;
1644 * We're back at the root move. Continue as normal.
1646 break;
1647 case 1:
1648 parse_error = 1;
1649 continue;
1650 default:
1652 * Continue processing-> Probably the root move.
1654 break;
1657 if (!game[gindex]->ravlevel && pgn_rav)
1658 parse_error = 1;
1660 continue;
1663 // PGN: Numeric Annotation Glyph.
1664 if (c == '$' || c == '!' || c == '?' || c == '+' || c == '-' ||
1665 c == '~' || c == '=') {
1666 Ungetc(c, fp);
1667 nag_text(game[gindex], fp);
1668 continue;
1672 * PGN: Annotation. The ';' comment continues until the end of the
1673 * current line. The '{' type comment continues until a '}' is
1674 * reached.
1676 if (c == '{' || c == ';') {
1677 annotation_text(game[gindex], fp, (c == '{') ? '}' : '\n');
1678 continue;
1681 // PGN: Roster tag->
1682 if (c == '[') {
1683 // First roster tag found. Initialize the data structures.
1684 if (!tag_section) {
1685 nulltags = 0;
1686 tag_section = 1;
1688 if (gtotal && pgn_history_total(game[gindex]->hp))
1689 game[gindex]->hindex = pgn_history_total(game[gindex]->hp) - 1;
1691 if (pgn_new_game() != E_PGN_OK) {
1692 pgn_ret = E_PGN_ERR;
1693 break;
1696 memcpy(old, pgn_board, sizeof(BOARD));
1699 if (tag_text(game[gindex], fp))
1700 parse_error = 1; // FEN tag parse error.
1702 continue;
1705 // PGN: End-of-game markers.
1706 if ((isdigit(c) && (nextchar == '-' || nextchar == '/')) || c == '*') {
1707 Ungetc(c, fp);
1709 if (eog_text(game[gindex], fp)) {
1710 parse_error = 1;
1711 continue;
1714 nulltags = 1;
1715 tag_section = 0;
1717 if (!game[gindex]->done_fen_tag) {
1718 if (pgn_tag_find(game[gindex]->tag, "FEN") != -1 &&
1719 pgn_board_init_fen(game[gindex], pgn_board, NULL)) {
1720 parse_error = 1;
1721 continue;
1724 game[gindex]->pgn_fen_tag = pgn_tag_find(game[gindex]->tag, "FEN");
1725 game[gindex]->done_fen_tag = 1;
1728 continue;
1731 // PGN: Move text.
1732 if ((isdigit(c) && c != '0') || VALIDCOL(c) || c == 'N' || c == 'K'
1733 || c == 'Q' || c == 'B' || c == 'R' || c == 'P' ||
1734 c == 'O') {
1735 Ungetc(c, fp);
1737 // PGN: If a FEN tag exists, initialize the board to the value.
1738 if (tag_section) {
1739 if (pgn_tag_find(game[gindex]->tag, "FEN") != E_PGN_ERR &&
1740 (n = pgn_board_init_fen(game[gindex], pgn_board,
1741 NULL)) == E_PGN_PARSE) {
1742 parse_error = 1;
1743 continue;
1746 game[gindex]->done_fen_tag = 1;
1747 game[gindex]->pgn_fen_tag = pgn_tag_find(game[gindex]->tag, "FEN");
1748 tag_section = 0;
1752 * PGN: Import format doesn't require a roster tag section. We've
1753 * arrived to the move text section without any tags so we
1754 * initialize a new game which set's the default tags and any tags
1755 * from the configuration file.
1757 if (nulltags) {
1758 if (gtotal)
1759 game[gindex]->hindex = pgn_history_total(game[gindex]->hp) - 1;
1761 if (pgn_new_game() != E_PGN_OK) {
1762 pgn_ret = E_PGN_ERR;
1763 break;
1766 memcpy(old, pgn_board, sizeof(BOARD));
1767 nulltags = 0;
1770 memcpy(old, pgn_board, sizeof(BOARD));
1772 if (move_text(game[gindex], fp)) {
1773 if (pgn_tag_add(&game[gindex]->tag, "Result", "*") ==
1774 E_PGN_ERR) {
1775 warn("pgn_tag_add()");
1776 pgn_ret = E_PGN_ERR;
1779 SET_FLAG(game[gindex]->flags, GF_PERROR);
1780 parse_error = 1;
1783 continue;
1786 #ifdef DEBUG
1787 *p++ = c;
1789 PGN_DUMP("%s:%d: unparsed: '%s'\n", __FILE__, __LINE__, buf);
1791 if (strlen(buf) + 1 == sizeof(buf))
1792 bzero(buf, sizeof(buf));
1793 #endif
1795 continue;
1798 return pgn_ret;
1802 * Parses a PGN_FILE which was opened with pgn_open(). If 'pgn' is NULL then a
1803 * single empty game will be allocated. If there is a parsing error
1804 * E_PGN_PARSE is returned, if there was a memory allocation error E_PGN_ERR
1805 * is returned, otherwise E_PGN_OK is returned and the global 'gindex' is set
1806 * to the last parsed game in the file and the global 'gtotal' is set to the
1807 * total number of games in the file. The file should be closed with
1808 * pgn_close() after processing.
1810 pgn_error_t pgn_parse(PGN_FILE *pgn)
1812 int i;
1814 if (!pgn) {
1815 reset_game_data();
1816 pgn_ret = pgn_new_game();
1817 goto done;
1820 reset_game_data();
1821 nulltags = 1;
1822 fseek(pgn->fp, 0, SEEK_END);
1823 pgn_fsize = ftell(pgn->fp);
1824 fseek(pgn->fp, 0, SEEK_SET);
1825 #ifdef DEBUG
1826 PGN_DUMP("%s:%d: BEGIN parsing->..\n", __FILE__, __LINE__);
1827 #endif
1828 pgn_ret = read_file(pgn->fp);
1830 #ifdef DEBUG
1831 PGN_DUMP("%s:%d: END parsing->..\n", __FILE__, __LINE__);
1832 #endif
1834 if (gtotal < 1)
1835 pgn_new_game();
1837 done:
1838 gtotal = gindex + 1;
1840 for (i = 0; i < gtotal; i++) {
1841 game[i]->history = game[i]->hp;
1842 game[i]->hindex = pgn_history_total(game[i]->hp);
1845 return pgn_ret;
1849 * Escape '"' and '\' in tag values.
1851 static char *pgn_tag_add_escapes(const char *str)
1853 int i, n;
1854 int len = strlen(str);
1855 static char buf[MAX_PGN_LINE_LEN] = {0};
1857 for (i = n = 0; i < len; i++, n++) {
1858 switch (str[i]) {
1859 case '\\':
1860 case '\"':
1861 buf[n++] = '\\';
1862 break;
1863 default:
1864 break;
1867 buf[n] = str[i];
1870 buf[n] = '\0';
1871 return buf;
1874 static void Fputc(int c, FILE *fp, int *len)
1876 int i = *len;
1878 if (c != '\n' && i + 1 > 80)
1879 Fputc('\n', fp, &i);
1881 if (pgn_lastc == '\n' && c == ' ') {
1882 *len = pgn_mpl = 0;
1883 return;
1886 if (fputc(c, fp) == EOF)
1887 warn("PGN Save");
1888 else {
1889 if (c == '\n')
1890 i = pgn_mpl = 0;
1891 else
1892 i++;
1895 *len = i;
1896 pgn_lastc = c;
1899 static void putstring(FILE *fp, char *str, int *len)
1901 char *p;
1903 for (p = str; *p; p++) {
1904 int n = 0;
1906 while (*p && *p != ' ')
1907 n++, p++;
1909 if (n + *len > 80)
1910 Fputc('\n', fp, len);
1912 p -= n;
1913 Fputc(*p, fp, len);
1918 * See pgn_write() for more info.
1920 static void write_comments_and_nag(FILE *fp, HISTORY *h, int *len)
1922 int i;
1924 #ifdef DEBUG
1925 PGN_DUMP("%s:%d: writing comments and nag\n", __FILE__, __LINE__);
1926 #endif
1928 for (i = 0; i < MAX_PGN_NAG; i++) {
1929 if (h->nag[i]) {
1930 Fputc(' ', fp, len);
1931 Fputc('$', fp, len);
1932 putstring(fp, itoa(h->nag[i]), len);
1936 if (h->comment) {
1937 Fputc('\n', fp, len);
1938 putstring(fp, " {", len);
1939 putstring(fp, h->comment, len);
1940 Fputc('}', fp, len);
1944 static void write_move_text(FILE *fp, HISTORY *h, int *len)
1946 Fputc(' ', fp, len);
1947 putstring(fp, h->move, len);
1949 if (!pgn_config.reduced)
1950 write_comments_and_nag(fp, h, len);
1953 static void write_all_move_text(FILE *fp, HISTORY **h, int m, int *len)
1955 int i;
1956 HISTORY **hp = NULL;
1958 for (i = 0; h[i]; i++) {
1959 if (pgn_write_turn == WHITE) {
1960 if (pgn_config.mpl && pgn_mpl == pgn_config.mpl) {
1961 pgn_mpl = 0;
1962 Fputc('\n', fp, len);
1965 if (m > 1 && i > 0)
1966 Fputc(' ', fp, len);
1968 if (strlen(itoa(m)) + 1 + *len > 80)
1969 Fputc('\n', fp, len);
1971 putstring(fp, itoa(m), len);
1972 Fputc('.', fp, len);
1973 pgn_mpl++;
1976 write_move_text(fp, h[i], len);
1978 if (!pgn_config.reduced && h[i]->rav) {
1979 int oldm = m;
1980 int oldturn = pgn_write_turn;
1982 ravlevel++;
1983 putstring(fp, " (", len);
1986 * If it's WHITE's turn the move number will be added above after
1987 * the call to write_all_move_text() below.
1989 if (pgn_write_turn == BLACK) {
1990 putstring(fp, itoa(m), len);
1991 putstring(fp, "...", len);
1994 hp = h[i]->rav;
1995 write_all_move_text(fp, hp, m, len);
1996 m = oldm;
1997 pgn_write_turn = oldturn;
1998 putstring(fp, ")", len);
1999 ravlevel--;
2001 if (ravlevel && h[i + 1])
2002 Fputc(' ', fp, len);
2004 if (h[i + 1] && !ravlevel)
2005 Fputc(' ', fp, len);
2007 if (pgn_write_turn == WHITE && h[i + 1]) {
2008 putstring(fp, itoa(m), len);
2009 putstring(fp, "...", len);
2013 if (pgn_write_turn == BLACK)
2014 m++;
2016 pgn_write_turn = (pgn_write_turn == WHITE) ? BLACK : WHITE;
2020 static char *compression_cmd(const char *filename, int expand)
2022 static char command[FILENAME_MAX];
2023 int len = strlen(filename);
2025 if (filename[len - 4] == '.' && filename[len - 3] == 'z' &&
2026 filename[len - 2] == 'i' && filename[len - 1] == 'p' &&
2027 filename[len] == '\0') {
2028 if (expand)
2029 snprintf(command, sizeof(command), "unzip -p %s 2>/dev/null",
2030 filename);
2031 else
2032 snprintf(command, sizeof(command), "zip -9 >%s 2>/dev/null",
2033 filename);
2035 return command;
2037 else if (filename[len - 3] == '.' && filename[len - 2] == 'g' &&
2038 filename[len - 1] == 'z' && filename[len] == '\0') {
2039 if (expand)
2040 snprintf(command, sizeof(command), "gzip -dc %s", filename);
2041 else
2042 snprintf(command, sizeof(command), "gzip -c 1>%s", filename);
2044 return command;
2046 else if (filename[len - 2] == '.' && filename[len - 1] == 'Z' &&
2047 filename[len] == '\0') {
2048 if (expand)
2049 snprintf(command, sizeof(command), "uncompress -c %s", filename);
2050 else
2051 snprintf(command, sizeof(command), "compress -c 1>%s", filename);
2053 return command;
2055 else if ((filename[len - 4] == '.' && filename[len - 3] == 'b' &&
2056 filename[len - 2] == 'z' && filename[len - 1] == '2' &&
2057 filename[len] == '\0') || (filename[len - 3] == '.' &&
2058 filename[len - 2] == 'b' && filename[len - 1] == 'z' &&
2059 filename[len] == '\0')) {
2060 if (expand)
2061 snprintf(command, sizeof(command), "bzip2 -dc %s", filename);
2062 else
2063 snprintf(command, sizeof(command), "bzip2 -zc 1>%s", filename);
2065 return command;
2068 return NULL;
2071 static int copy_file(FILE *fp, const char *dst)
2073 FILE *ofp;
2074 char line[LINE_MAX];
2075 char *cmd = compression_cmd(dst, 0);
2077 if ((ofp = popen(cmd, "w")) == NULL)
2078 return 1;
2080 fseek(fp, 0, SEEK_SET);
2082 while ((fgets(line, sizeof(line), fp)) != NULL)
2083 fprintf(ofp, "%s", line);
2085 pclose(ofp);
2086 return 0;
2090 * Closes and free's a PGN file handle.
2092 pgn_error_t pgn_close(PGN_FILE *pgn)
2094 if (!pgn)
2095 return E_PGN_INVALID;
2097 if (pgn->pipe) {
2099 * Appending to a compressed file.
2101 if (pgn->tmpfile) {
2102 if (copy_file(pgn->fp, pgn->filename))
2103 return E_PGN_ERR;
2105 fclose(pgn->fp);
2106 unlink(pgn->tmpfile);
2107 free(pgn->tmpfile);
2109 else
2110 pclose(pgn->fp);
2112 else
2113 fclose(pgn->fp);
2115 free(pgn->filename);
2116 free(pgn);
2117 return E_PGN_OK;
2121 * Opens a file 'filename' with the given 'mode'. 'mode' should be "r" for
2122 * reading, "w" for writing (will truncate if the file exists) or "a" for
2123 * appending to an existing file or creating a new one. Returns E_PGN_OK on
2124 * success and sets 'result' to a file handle for use will the other file
2125 * functions or E_PGN_ERR if there is an error opening the file in which case
2126 * errno will be set to the error or E_PGN_INVALID if 'mode' is an invalid
2127 * mode or if 'filename' is not a regular file.
2129 pgn_error_t pgn_open(const char *filename, const char *mode, PGN_FILE **result)
2131 FILE *fp = NULL, *tfp = NULL;
2132 char buf[PATH_MAX], *p;
2133 char *cmd = NULL;
2134 PGN_FILE *pgn;
2135 int m;
2136 int append = 0;
2137 struct stat st;
2138 int ret = E_PGN_ERR;
2141 #ifdef DEBUG
2142 PGN_DUMP("%s:%d: BEGIN opening %s\n", __FILE__, __LINE__, filename);
2143 #endif
2145 if (!filename || !mode)
2146 return E_PGN_INVALID;
2148 if (strcmp(mode, "r") == 0)
2149 m = 1;
2150 else if (strcmp(mode, "w") == 0)
2151 m = 0;
2152 else if (strcmp(mode, "a") == 0) {
2153 m = 0;
2154 append = 1;
2156 else {
2157 return E_PGN_INVALID;
2160 pgn = calloc(1, sizeof(PGN_FILE));
2162 if (!pgn)
2163 goto fail;
2165 if (strcmp(filename, "-") != 0) {
2166 if (m && access(filename, R_OK) == -1)
2167 goto fail;
2169 if (stat(filename, &st) == -1 && !m && errno != ENOENT)
2170 goto fail;
2172 if (m && !S_ISREG(st.st_mode)) {
2173 ret = E_PGN_INVALID;
2174 goto fail;
2177 if ((cmd = compression_cmd(filename, m)) != NULL) {
2178 pgn->pipe = 1;
2180 if (append && access(filename, R_OK) == 0) {
2181 char tmp[21];
2182 int fd;
2184 cmd = compression_cmd(filename, 1);
2186 if ((fp = popen(cmd, "r")) == NULL)
2187 goto fail;
2189 if (tmpnam(tmp) == NULL)
2190 goto fail;
2192 if ((fd = open(tmp, O_RDWR|O_EXCL|O_CREAT)) == -1)
2193 goto fail;
2195 if ((tfp = fdopen(fd, "a+")) == NULL)
2196 goto fail;
2198 while ((p = fgets(buf, sizeof(buf), fp)) != NULL)
2199 fprintf(tfp, "%s", p);
2201 pclose(fp);
2202 pgn->fp = tfp;
2203 pgn->tmpfile = strdup(tmp);
2204 goto done;
2207 if ((fp = popen(cmd, m ? "r" : "w")) == NULL)
2208 goto fail;
2210 if (m) {
2211 if ((tfp = tmpfile()) == NULL)
2212 goto fail;
2214 while ((p = fgets(buf, sizeof(buf), fp)) != NULL)
2215 fprintf(tfp, "%s", p);
2217 pclose(fp);
2218 pgn->fp = tfp;
2220 else
2221 pgn->fp = fp;
2223 else {
2224 if ((fp = fopen(filename, mode)) == NULL)
2225 goto fail;
2227 pgn->fp = fp;
2230 else
2231 pgn->fp = stdout;
2233 done:
2234 if (*filename != '/') {
2235 if (getcwd(buf, sizeof(buf)) == NULL) {
2236 if (pgn->tmpfile)
2237 free(pgn->tmpfile);
2239 goto fail;
2242 asprintf(&p, "%s/%s", buf, filename);
2243 pgn->filename = p;
2245 else
2246 pgn->filename = strdup(filename);
2248 *result = pgn;
2249 return E_PGN_OK;
2251 fail:
2252 if (fp)
2253 fclose(fp);
2255 free(pgn);
2256 return ret;
2260 * Returns E_PGN_OK if 'filename' is a recognized compressed filetype or
2261 * E_PGN_ERR if not.
2263 pgn_error_t pgn_is_compressed(const char *filename)
2265 if (compression_cmd(filename, 0))
2266 return E_PGN_OK;
2268 return E_PGN_ERR;
2272 * Gets the value of config flag 'f'. The next argument should be a pointer of
2273 * the config type which is set to the value of 'f'. Returns E_PGN_ERR if 'f'
2274 * is an invalid flag or E_PGN_OK on success.
2276 pgn_error_t pgn_config_get(pgn_config_flag f, ...)
2278 va_list ap;
2279 int *intval;
2280 long *longval;
2281 pgn_progress *progress;
2283 va_start(ap, f);
2285 switch (f) {
2286 case PGN_STRICT_CASTLING:
2287 intval = va_arg(ap, int *);
2288 *intval = pgn_config.strict_castling;
2289 va_end(ap);
2290 return E_PGN_OK;
2291 case PGN_REDUCED:
2292 intval = va_arg(ap, int *);
2293 *intval = pgn_config.reduced;
2294 va_end(ap);
2295 return E_PGN_OK;
2296 case PGN_MPL:
2297 intval = va_arg(ap, int *);
2298 *intval = pgn_config.mpl;
2299 va_end(ap);
2300 return E_PGN_OK;
2301 case PGN_STOP_ON_ERROR:
2302 intval = va_arg(ap, int *);
2303 *intval = pgn_config.stop;
2304 va_end(ap);
2305 return E_PGN_OK;
2306 case PGN_PROGRESS:
2307 longval = va_arg(ap, long *);
2308 *longval = pgn_config.stop;
2309 va_end(ap);
2310 return E_PGN_OK;
2311 case PGN_PROGRESS_FUNC:
2312 progress = va_arg(ap, pgn_progress*);
2313 *progress = pgn_config.pfunc;
2314 va_end(ap);
2315 return E_PGN_OK;
2316 #ifdef DEBUG
2317 case PGN_DEBUG:
2318 intval = va_arg(ap, int *);
2319 *intval = dumptofile;
2320 va_end(ap);
2321 return E_PGN_OK;
2322 #endif
2323 default:
2324 break;
2327 return E_PGN_ERR;
2331 * Sets config flag 'f' to the next argument. Returns E_PGN_OK on success or
2332 * E_PGN_ERR if 'f' is an invalid flag or E_PGN_INVALID if 'val' is an invalid
2333 * flag value.
2335 pgn_error_t pgn_config_set(pgn_config_flag f, ...)
2337 va_list ap;
2338 int n;
2339 int ret = E_PGN_OK;
2341 va_start(ap, f);
2343 switch (f) {
2344 case PGN_REDUCED:
2345 n = va_arg(ap, int);
2347 if (n != 1 && n != 0) {
2348 ret = E_PGN_INVALID;
2349 break;
2352 pgn_config.reduced = n;
2353 break;
2354 case PGN_MPL:
2355 n = va_arg(ap, int);
2357 if (n < 0) {
2358 ret = E_PGN_INVALID;
2359 break;
2362 pgn_config.mpl = n;
2363 break;
2364 case PGN_STOP_ON_ERROR:
2365 n = va_arg(ap, int);
2367 if (n != 1 && n != 0) {
2368 ret = E_PGN_INVALID;
2369 break;
2372 pgn_config.stop = n;
2373 break;
2374 case PGN_PROGRESS:
2375 n = va_arg(ap, long);
2376 pgn_config.progress = n;
2377 break;
2378 case PGN_PROGRESS_FUNC:
2379 pgn_config.pfunc = va_arg(ap, pgn_progress);
2380 break;
2381 case PGN_STRICT_CASTLING:
2382 n = va_arg(ap, int);
2383 pgn_config.strict_castling = n;
2384 break;
2385 #ifdef DEBUG
2386 case PGN_DEBUG:
2387 n = va_arg(ap, int);
2388 dumptofile = (n > 0) ? 1 : 0;
2389 break;
2390 #endif
2391 default:
2392 ret = E_PGN_ERR;
2393 break;
2396 va_end(ap);
2397 return ret;
2401 * Writes a PGN formatted game 'g' to a file which was opened with pgn_open().
2402 * See 'pgn_config_flag' for output options. Returns E_PGN_ERR if there was a
2403 * memory allocation or write error and E_PGN_OK on success. It is important
2404 * to use pgn_close() afterwards if the file is a recognized compressed file
2405 * type otherwise the created temporary file wont be copied to the destination
2406 * filename.
2408 pgn_error_t pgn_write(PGN_FILE *pgn, GAME g)
2410 int i;
2411 int len = 0;
2413 if (!pgn)
2414 return E_PGN_ERR;
2416 pgn_write_turn = (TEST_FLAG(g->flags, GF_BLACK_OPENING)) ? BLACK : WHITE;
2417 pgn_tag_sort(g->tag);
2419 #ifdef DEBUG
2420 PGN_DUMP("%s:%d: writing tag section\n", __FILE__, __LINE__);
2421 #endif
2423 for (i = 0; g->tag[i]; i++) {
2424 struct tm tp;
2425 char tbuf[11] = {0};
2426 char *tmp;
2428 if (pgn_config.reduced && i == 7)
2429 break;
2431 if (strcmp(g->tag[i]->name, "Date") == 0) {
2432 memset(&tp, 0, sizeof(struct tm));
2434 if (strptime(g->tag[i]->value, PGN_TIME_FORMAT, &tp) != NULL) {
2435 len = strftime(tbuf, sizeof(tbuf), PGN_TIME_FORMAT, &tp) + 1;
2437 if ((tmp = strdup(tbuf)) == NULL)
2438 return E_PGN_ERR;
2440 free(g->tag[i]->value);
2441 g->tag[i]->value = tmp;
2444 else if (strcmp(g->tag[i]->name, "Event") == 0) {
2445 if (g->tag[i]->value[0] == '\0') {
2446 if ((tmp = strdup("?")) == NULL)
2447 return E_PGN_ERR;
2449 free(g->tag[i]->value);
2450 g->tag[i]->value = tmp;
2453 else if (strcmp(g->tag[i]->name, "Site") == 0) {
2454 if (g->tag[i]->value[0] == '\0') {
2455 if ((tmp = strdup("?")) == NULL)
2456 return E_PGN_ERR;
2458 free(g->tag[i]->value);
2459 g->tag[i]->value = tmp;
2462 else if (strcmp(g->tag[i]->name, "Round") == 0) {
2463 if (g->tag[i]->value[0] == '\0') {
2464 if ((tmp = strdup("?")) == NULL)
2465 return E_PGN_ERR;
2467 free(g->tag[i]->value);
2468 g->tag[i]->value = tmp;
2471 else if (strcmp(g->tag[i]->name, "Result") == 0) {
2472 if (g->tag[i]->value[0] == '\0') {
2473 if ((tmp = strdup("*")) == NULL)
2474 return E_PGN_ERR;
2476 free(g->tag[i]->value);
2477 g->tag[i]->value = tmp;
2480 else if (strcmp(g->tag[i]->name, "Black") == 0) {
2481 if (g->tag[i]->value[0] == '\0') {
2482 if ((tmp = strdup("?")) == NULL)
2483 return E_PGN_ERR;
2485 free(g->tag[i]->value);
2486 g->tag[i]->value = tmp;
2489 else if (strcmp(g->tag[i]->name, "White") == 0) {
2490 if (g->tag[i]->value[0] == '\0') {
2491 if ((tmp = strdup("?")) == NULL)
2492 return E_PGN_ERR;
2494 free(g->tag[i]->value);
2495 g->tag[i]->value = tmp;
2499 fprintf(pgn->fp, "[%s \"%s\"]\n", g->tag[i]->name,
2500 (g->tag[i]->value && g->tag[i]->value[0]) ?
2501 pgn_tag_add_escapes(g->tag[i]->value) : "");
2504 #ifdef DEBUG
2505 PGN_DUMP("%s:%d: writing move section\n", __FILE__, __LINE__);
2506 #endif
2507 Fputc('\n', pgn->fp, &len);
2508 g->hp = g->history;
2509 ravlevel = pgn_mpl = 0;
2511 if (pgn_history_total(g->hp) && pgn_write_turn == BLACK)
2512 putstring(pgn->fp, "1...", &len);
2514 write_all_move_text(pgn->fp, g->hp, 1, &len);
2516 Fputc(' ', pgn->fp, &len);
2517 putstring(pgn->fp, g->tag[6]->value, &len);
2518 putstring(pgn->fp, "\n\n", &len);
2520 if (!pgn_config.reduced)
2521 CLEAR_FLAG(g->flags, GF_PERROR);
2523 return E_PGN_OK;
2527 * Clears the enpassant flag for all positions on board 'b'. Returns nothing.
2529 void pgn_reset_enpassant(BOARD b)
2531 int r, c;
2533 #ifdef DEBUG
2534 PGN_DUMP("%s:%d: resetting enpassant\n", __FILE__, __LINE__);
2535 #endif
2537 for (r = 0; r < 8; r++) {
2538 for (c = 0; c < 8; c++)
2539 b[r][c].enpassant = 0;