Manual page update.
[cboard.git] / libchess / pgn.c
blobf25b5da8c494ba0ed8c0cf150dcd5cb7dabb448b
1 /* vim:tw=78:ts=8:sw=4:set ft=c: */
2 /*
3 Copyright (C) 2002-2006 Ben Kibbey <bjk@luxsci.net>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <unistd.h>
23 #include <sys/types.h>
24 #include <sys/wait.h>
25 #include <sys/stat.h>
26 #include <pwd.h>
27 #include <err.h>
28 #include <string.h>
29 #include <time.h>
30 #include <ctype.h>
31 #include <errno.h>
32 #include <fcntl.h>
34 #ifdef HAVE_CONFIG_H
35 #include <config.h>
36 #endif
38 #ifdef HAVE_LIMITS_H
39 #include <limits.h>
40 #endif
42 #include "chess.h"
43 #include "pgn.h"
45 #ifdef DEBUG
46 #include "debug.h"
47 #endif
49 #ifdef WITH_DMALLOC
50 #include <dmalloc.h>
51 #endif
53 static int Fgetc(FILE *fp)
55 register int c;
57 if ((c = fgetc(fp)) != EOF) {
58 if (!(ftell(fp) % pgn_config.progress))
59 (*pgn_config.pfunc)(pgn_fsize, ftell(fp));
62 return c;
65 static int Ungetc(int c, FILE *fp)
67 return ungetc(c, fp);
70 char *pgn_version()
72 return "libchess " PACKAGE_VERSION;
75 static char *trim(char *str)
77 int i = 0;
79 if (!str)
80 return NULL;
82 while (isspace(*str))
83 str++;
85 for (i = strlen(str) - 1; isspace(str[i]); i--)
86 str[i] = 0;
88 return str;
91 static char *itoa(long n)
93 static char buf[16];
95 snprintf(buf, sizeof(buf), "%li", n);
96 return buf;
100 * Clears the valid move flag for all positions on board 'b'. Returns nothing.
102 void pgn_reset_valid_moves(BOARD b)
104 int row, col;
106 for (row = 0; row < 8; row++) {
107 for (col = 0; col < 8; col++)
108 b[row][col].valid = 0;
113 * Toggles g->turn. Returns nothing.
115 void pgn_switch_turn(GAME *g)
117 g->turn = (g->turn == WHITE) ? BLACK : WHITE;
121 * Toggles g->side and switches the White and Black roster tags. Returns
122 * nothing.
124 void pgn_switch_side(GAME *g)
126 int i = pgn_tag_find(g->tag, "White");
127 int n = pgn_tag_find(g->tag, "Black");
128 char *w = g->tag[i]->value;
130 g->tag[i]->value = g->tag[n]->value;
131 g->tag[n]->value = w;
132 g->side = (g->side == WHITE) ? BLACK : WHITE;
136 * Creates a FEN tag from the current game 'g', history move (g.hindex) and
137 * board 'b'. Returns a FEN tag.
139 char *pgn_game_to_fen(GAME g, BOARD b)
141 int row, col;
142 int i;
143 static char buf[MAX_PGN_LINE_LEN], *p;
144 int oldturn = g.turn;
145 char enpassant[3] = {0}, *e;
146 int castle = 0;
148 for (i = pgn_history_total(g.hp); i >= g.hindex - 1; i--)
149 pgn_switch_turn(&g);
151 p = buf;
153 for (row = 0; row < 8; row++) {
154 int count = 0;
156 for (col = 0; col < 8; col++) {
157 if (b[row][col].enpassant) {
158 b[row][col].icon = pgn_int_to_piece(WHITE, OPEN_SQUARE);
159 e = enpassant;
160 *e++ = 'a' + col;
161 *e++ = ('0' + 8) - row;
162 *e = 0;
165 if (pgn_piece_to_int(b[row][col].icon) == OPEN_SQUARE) {
166 count++;
167 continue;
170 if (count) {
171 *p++ = '0' + count;
172 count = 0;
175 *p++ = b[row][col].icon;
176 *p = 0;
179 if (count) {
180 *p++ = '0' + count;
181 count = 0;
184 *p++ = '/';
187 --p;
188 *p++ = ' ';
189 *p++ = (g.turn == WHITE) ? 'w' : 'b';
190 *p++ = ' ';
192 if (TEST_FLAG(g.flags, GF_WK_CASTLE) && pgn_piece_to_int(b[7][7].icon) ==
193 ROOK && isupper(b[7][7].icon) && pgn_piece_to_int(b[7][4].icon) ==
194 KING && isupper(b[7][4].icon)) {
195 *p++ = 'K';
196 castle = 1;
199 if (TEST_FLAG(g.flags, GF_WQ_CASTLE) && pgn_piece_to_int(b[7][0].icon) ==
200 ROOK && isupper(b[7][0].icon) && pgn_piece_to_int(b[7][4].icon) ==
201 KING && isupper(b[7][4].icon)) {
202 *p++ = 'Q';
203 castle = 1;
206 if (TEST_FLAG(g.flags, GF_BK_CASTLE) && pgn_piece_to_int(b[0][7].icon) ==
207 ROOK && islower(b[0][7].icon) && pgn_piece_to_int(b[0][4].icon) ==
208 KING && islower(b[0][4].icon)) {
209 *p++ = 'k';
210 castle = 1;
213 if (TEST_FLAG(g.flags, GF_BQ_CASTLE) && pgn_piece_to_int(b[0][0].icon) ==
214 ROOK && islower(b[0][0].icon) && pgn_piece_to_int(b[0][4].icon) ==
215 KING && islower(b[0][4].icon)) {
216 *p++ = 'q';
217 castle = 1;
220 if (!castle)
221 *p++ = '-';
223 *p++ = ' ';
225 if (enpassant[0]) {
226 e = enpassant;
227 *p++ = *e++;
228 *p++ = *e++;
230 else
231 *p++ = '-';
233 *p++ = ' ';
235 // Halfmove clock.
236 *p = 0;
237 strcat(p, itoa(g.ply));
238 p = buf + strlen(buf);
239 *p++ = ' ';
241 // Fullmove number.
242 i = (g.hindex + 1) / 2;
243 *p = 0;
244 strcat(p, itoa((g.hindex / 2) + (g.hindex % 2)));
246 g.turn = oldturn;
247 return buf;
251 * Returns the total number of moves in 'h' or 0 if there are none.
253 int pgn_history_total(HISTORY **h)
255 int i;
257 if (!h)
258 return 0;
260 for (i = 0; h[i]; i++);
261 return i;
265 * Deallocates all of the history data from position 'start' in the array 'h'.
266 * Returns nothing.
268 void pgn_history_free(HISTORY **h, int start)
270 int i;
272 if (!h || start > pgn_history_total(h))
273 return;
275 if (start < 0)
276 start = 0;
278 for (i = start; h[i]; i++) {
279 if (h[i]->comment)
280 free(h[i]->comment);
282 if (h[i]->rav) {
283 pgn_history_free(h[i]->rav, 0);
284 free(h[i]->rav);
287 if (h[i]->move)
288 free(h[i]->move);
290 free(h[i]);
293 h[start] = NULL;
297 * Returns the history ply 'n' from 'h'. If 'n' is out of range then NULL is
298 * returned.
300 HISTORY *pgn_history_by_n(HISTORY **h, int n)
302 if (n < 0 || n > pgn_history_total(h) - 1)
303 return NULL;
305 return h[n];
309 * Appends move 'm' to game 'g' history pointer. The history pointer may be a
310 * in a RAV so g->rav.hp is also updated to the new (realloc()'ed) pointer. If
311 * not in a RAV then g->history will be updated. Returns E_PGN_ERR if
312 * realloc() failed or E_PGN_OK on success.
314 int pgn_history_add(GAME *g, const char *m)
316 int t = pgn_history_total(g->hp);
317 int o;
318 HISTORY **h = NULL;
320 if (g->ravlevel)
321 o = g->rav[g->ravlevel].hp - g->hp;
322 else
323 o = g->history - g->hp;
325 if ((h = realloc(g->hp, (t + 2) * sizeof(HISTORY *))) == NULL)
326 return E_PGN_ERR;
328 g->hp = h;
330 if (g->ravlevel)
331 g->rav[g->ravlevel].hp = g->hp + o;
332 else
333 g->history = g->hp + o;
335 if ((g->hp[t] = calloc(1, sizeof(HISTORY))) == NULL)
336 return E_PGN_ERR;
338 if ((g->hp[t]->move = strdup(m)) == NULL) {
339 g->hp[t] = NULL;
340 return E_PGN_ERR;
343 t++;
344 g->hp[t] = NULL;
345 g->hindex = pgn_history_total(g->hp);
346 return E_PGN_OK;
350 * Resets the game 'g' using board 'b' up to history move (g.hindex) 'n'.
351 * Returns E_PGN_OK on success or E_PGN_PARSE if there was a FEN tag but the
352 * parsing of it failed. Or returns E_PGN_INVALID if somehow the move failed
353 * validation while resetting.
355 int pgn_board_update(GAME *g, BOARD b, int n)
357 int i = 0;
358 BOARD tb;
359 int ret = E_PGN_OK;
361 if (TEST_FLAG(g->flags, GF_BLACK_OPENING))
362 g->turn = BLACK;
363 else
364 g->turn = WHITE;
366 #if 0
367 if (TEST_FLAG(g->flags, GF_PERROR))
368 SET_FLAG(flags, GF_PERROR);
370 if (TEST_FLAG(g->flags, GF_MODIFIED))
371 SET_FLAG(flags, GF_MODIFIED);
373 if (TEST_FLAG(g->flags, GF_DELETE))
374 SET_FLAG(flags, GF_DELETE);
376 if (TEST_FLAG(g->flags, GF_GAMEOVER))
377 SET_FLAG(flags, GF_GAMEOVER);
379 g->flags = flags;
380 #endif
382 SET_FLAG(g->flags, GF_WK_CASTLE|GF_WQ_CASTLE|GF_WQ_CASTLE|
383 GF_BK_CASTLE|GF_BQ_CASTLE);
384 pgn_board_init(tb);
386 if (pgn_tag_find(g->tag, "FEN") != -1 &&
387 pgn_board_init_fen(g, tb, NULL))
388 return E_PGN_PARSE;
390 for (i = 0; i < n; i++) {
391 HISTORY *h;
392 char *p;
394 if ((h = pgn_history_by_n(g->hp, i)) == NULL)
395 break;
397 p = h->move;
399 if ((ret = pgn_parse_move(g, tb, &p)) != E_PGN_OK)
400 break;
402 pgn_switch_turn(g);
405 if (ret == 0)
406 memcpy(b, tb, sizeof(BOARD));
408 return ret;
412 * Updates the game 'g' using board 'b' to the next 'n'th history move.
413 * Returns nothing.
415 void pgn_history_prev(GAME *g, BOARD b, int n)
417 if (g->hindex - n < 0) {
418 if (n <= 2)
419 g->hindex = pgn_history_total(g->hp);
420 else
421 g->hindex = 0;
423 else
424 g->hindex -= n;
426 pgn_board_update(g, b, g->hindex);
430 * Updates the game 'g' using board 'b' to the previous 'n'th history move.
431 * Returns nothing.
433 void pgn_history_next(GAME *g, BOARD b, int n)
435 if (g->hindex + n > pgn_history_total(g->hp)) {
436 if (n <= 2)
437 g->hindex = 0;
438 else
439 g->hindex = pgn_history_total(g->hp);
441 else
442 g->hindex += n;
444 pgn_board_update(g, b, g->hindex);
448 * Converts the character piece 'p' to an integer. Returns the integer
449 * associated with 'p' or E_PGN_ERR if 'p' is invalid.
451 int pgn_piece_to_int(register int p)
453 if (p == '.')
454 return OPEN_SQUARE;
456 p = tolower(p);
458 switch (p) {
459 case 'p':
460 return PAWN;
461 case 'r':
462 return ROOK;
463 case 'n':
464 return KNIGHT;
465 case 'b':
466 return BISHOP;
467 case 'q':
468 return QUEEN;
469 case 'k':
470 return KING;
471 default:
472 break;
475 return E_PGN_ERR;
479 * Converts the integer piece 'n' to a character whose turn is 'turn'. WHITE
480 * piece are uppercase and BLACK pieces are lowercase. Returns the character
481 * associated with 'n' or E_PGN_ERR if 'n' is an invalid piece.
483 int pgn_int_to_piece(char turn, int n)
485 int p = 0;
487 switch (n) {
488 case PAWN:
489 p = 'p';
490 break;
491 case ROOK:
492 p = 'r';
493 break;
494 case KNIGHT:
495 p = 'n';
496 break;
497 case BISHOP:
498 p = 'b';
499 break;
500 case QUEEN:
501 p = 'q';
502 break;
503 case KING:
504 p = 'k';
505 break;
506 case OPEN_SQUARE:
507 p = '.';
508 break;
509 default:
510 return E_PGN_ERR;
511 break;
514 return (turn == WHITE) ? toupper(p) : p;
518 * Finds a tag 'name' in the structure array 't'. Returns the location in the
519 * array of the found tag or E_PGN_ERR if the tag could not be found.
521 int pgn_tag_find(TAG **t, const char *name)
523 int i;
525 for (i = 0; t[i]; i++) {
526 if (strcasecmp(t[i]->name, name) == 0)
527 return i;
530 return E_PGN_ERR;
533 static int tag_compare(const void *a, const void *b)
535 TAG * const *ta = a;
536 TAG * const *tb = b;
538 return strcmp((*ta)->name, (*tb)->name);
542 * Sorts a tag array. The first seven tags are in order of the PGN standard so
543 * don't sort'em. Returns nothing.
545 void pgn_tag_sort(TAG **tags)
547 if (pgn_tag_total(tags) <= 7)
548 return;
550 qsort(tags + 7, pgn_tag_total(tags) - 7, sizeof(TAG *), tag_compare);
554 * Returns the total number of tags in 't' or 0 if 't' is NULL.
556 int pgn_tag_total(TAG **tags)
558 int i = 0;
560 if (!tags)
561 return 0;
563 while (tags[i])
564 i++;
566 return i;
570 * Adds a tag 'name' with value 'value' to the pointer to array of TAG
571 * pointers 'dst'. If a duplicate tag 'name' was found then the existing tag
572 * is updated to the new 'value'. Returns E_PGN_ERR if there was a memory
573 * allocation error or E_PGN_OK on success.
575 int pgn_tag_add(TAG ***dst, char *name, char *value)
577 int i;
578 TAG **tdata = *dst;
579 TAG **a = NULL;
580 int len = 0;
581 int t = pgn_tag_total(tdata);
583 name = trim(name);
584 value = trim(value);
586 // Find an existing tag with 'name'.
587 for (i = 0; i < t; i++) {
588 char *tmp;
590 if (strcasecmp(tdata[i]->name, name) == 0) {
591 if ((tmp = strdup(value)) == NULL)
592 return E_PGN_ERR;
594 free(tdata[i]->value);
595 tdata[i]->value = tmp;
596 *dst = tdata;
597 return E_PGN_OK;
601 if ((a = realloc(tdata, (t + 2) * sizeof(TAG *))) == NULL)
602 return E_PGN_ERR;
604 tdata = a;
606 if ((tdata[t] = malloc(sizeof(TAG))) == NULL)
607 return E_PGN_ERR;
609 len = strlen(name) + 1;
611 if ((tdata[t]->name = strdup(name)) == NULL) {
612 tdata[t] = NULL;
613 return E_PGN_ERR;
616 if (value) {
617 if ((tdata[t]->value = strdup(value)) == NULL) {
618 free(tdata[t]->name);
619 tdata[t] = NULL;
620 return E_PGN_ERR;
623 else
624 tdata[t]->value = NULL;
626 tdata[++t] = NULL;
627 *dst = tdata;
628 return E_PGN_OK;
631 static char *remove_tag_escapes(const char *str)
633 int i, n;
634 int len = strlen(str);
635 static char buf[MAX_PGN_LINE_LEN] = {0};
637 for (i = n = 0; i < len; i++, n++) {
638 switch (str[i]) {
639 case '\\':
640 i++;
641 default:
642 break;
645 buf[n] = str[i];
648 buf[n] = '\0';
649 return buf;
653 * Resets or initializes a new game board 'b'. Returns nothing.
655 void pgn_board_init(BOARD b)
657 int row, col;
659 memset(b, 0, sizeof(BOARD));
661 for (row = 0; row < 8; row++) {
662 for (col = 0; col < 8; col++) {
663 int c = '.';
665 switch (row) {
666 case 0:
667 case 7:
668 switch (col) {
669 case 0:
670 case 7:
671 c = 'r';
672 break;
673 case 1:
674 case 6:
675 c = 'n';
676 break;
677 case 2:
678 case 5:
679 c = 'b';
680 break;
681 case 3:
682 c = 'q';
683 break;
684 case 4:
685 c = 'k';
686 break;
688 break;
689 case 1:
690 case 6:
691 c = 'p';
692 break;
695 b[row][col].icon = (row < 2) ? c : toupper(c);
701 * Adds the standard PGN roster tags to game 'g'.
703 static void set_default_tags(GAME *g)
705 time_t now;
706 char tbuf[11] = {0};
707 struct tm *tp;
708 struct passwd *pw = getpwuid(getuid());
710 time(&now);
711 tp = localtime(&now);
712 strftime(tbuf, sizeof(tbuf), PGN_TIME_FORMAT, tp);
714 /* The standard seven tag roster (in order of appearance). */
715 if (pgn_tag_add(&g->tag, "Event", "?"))
716 warn("pgn_tag_add()");
718 if (pgn_tag_add(&g->tag, "Site", "?"))
719 warn("pgn_tag_add()");
721 if (pgn_tag_add(&g->tag, "Date", tbuf))
722 warn("pgn_tag_add()");
724 if (pgn_tag_add(&g->tag, "Round", "-"))
725 warn("pgn_tag_add()");
727 if (pgn_tag_add(&g->tag, "White", pw->pw_gecos))
728 warn("pgn_tag_add()");
730 if (pgn_tag_add(&g->tag, "Black", "?"))
731 warn("pgn_tag_add()");
733 if (pgn_tag_add(&g->tag, "Result", "*"))
734 warn("pgn_tag_add()");
738 * Frees a TAG array. Returns nothing.
740 void pgn_tag_free(TAG **tags)
742 int i;
743 int t = pgn_tag_total(tags);
745 if (!tags)
746 return;
748 for (i = 0; i < t; i++) {
749 free(tags[i]->name);
750 free(tags[i]->value);
751 free(tags[i]);
754 free(tags);
758 * Frees a single game 'g'. Returns nothing.
760 void pgn_free(GAME g)
762 pgn_history_free(g.history, 0);
763 free(g.history);
764 pgn_tag_free(g.tag);
765 memset(&g, 0, sizeof(GAME));
769 * Frees all games in the global 'game' array. Returns nothing.
771 void pgn_free_all()
773 int i;
775 for (i = 0; i < gtotal; i++) {
776 pgn_free(game[i]);
778 if (game[i].rav) {
779 for (game[i].ravlevel--; game[i].ravlevel >= 0; game[i].ravlevel--)
780 free(game[i].rav[game[i].ravlevel].fen);
782 free(game[i].rav);
786 if (game)
787 free(game);
788 game = NULL;
791 static void reset_game_data()
793 pgn_free_all();
794 gtotal = gindex = 0;
795 pgn_isfile = 0;
798 static void skip_leading_space(FILE *fp)
800 int c;
802 while ((c = Fgetc(fp)) != EOF && !feof(fp)) {
803 if (!isspace(c))
804 break;
807 Ungetc(c, fp);
811 * PGN move text section.
813 static int move_text(GAME *g, FILE *fp)
815 char m[MAX_SAN_MOVE_LEN + 1] = {0}, *p;
816 int c;
817 int count;
818 int dots = 0;
819 int digit = 0;
821 while((c = Fgetc(fp)) != EOF) {
822 if (isspace(c))
823 continue;
825 if (isdigit(c)) {
826 digit = 1;
827 continue;
830 if (c == '.') {
831 dots++;
832 continue;
835 break;
838 if (!pgn_history_total(g->hp) && digit) {
839 if (dots > 1) {
840 g->turn = BLACK;
842 if (g->hindex == 0 && g->ravlevel == 0)
843 SET_FLAG(g->flags, GF_BLACK_OPENING);
845 else {
846 g->turn = WHITE;
848 if (g->hindex == 0)
849 CLEAR_FLAG(g->flags, GF_BLACK_OPENING);
853 Ungetc(c, fp);
855 if (fscanf(fp, " %[a-hPRNBQK1-9#+=Ox-]%n", m, &count) != 1)
856 return 1;
858 p = m;
860 if (pgn_parse_move(g, pgn_board, &p)) {
861 pgn_switch_turn(g);
862 return 1;
865 #ifdef DEBUG
866 DUMP("%s\n", p);
867 dump_board(0, pgn_board);
868 #endif
870 pgn_history_add(g, p);
871 pgn_switch_turn(g);
872 return 0;
876 * PGN nag text.
878 static void nag_text(GAME *g, FILE *fp)
880 int c, i, t;
881 char nags[5], *n = nags;
882 int nag = 0;
884 while ((c = Fgetc(fp)) != EOF && !isspace(c)) {
885 if (c == '$') {
886 while ((c = Fgetc(fp)) != EOF && isdigit(c))
887 *n++ = c;
889 break;
892 if (c == '!') {
893 if ((c = Fgetc(fp)) == '!')
894 nag = 3;
895 else if (c == '?')
896 nag = 5;
897 else {
898 Ungetc(c, fp);
899 nag = 1;
902 break;
904 else if (c == '?') {
905 if ((c = Fgetc(fp)) == '?')
906 nag = 4;
907 else if (c == '!')
908 nag = 6;
909 else {
910 Ungetc(c, fp);
911 nag = 2;
914 break;
916 else if (c == '~')
917 nag = 13;
918 else if (c == '=') {
919 if ((c = Fgetc(fp)) == '+')
920 nag = 15;
921 else {
922 Ungetc(c, fp);
923 nag = 10;
926 break;
928 else if (c == '+') {
929 if ((t = Fgetc(fp)) == '=')
930 nag = 14;
931 else if (t == '-')
932 nag = 18;
933 else if (t == '/') {
934 if ((i = Fgetc(fp)) == '-')
935 nag = 16;
936 else
937 Ungetc(i, fp);
939 break;
941 else
942 Ungetc(t, fp);
944 break;
946 else if (c == '-') {
947 if ((t = Fgetc(fp)) == '+')
948 nag = 18;
949 else if (t == '/') {
950 if ((i = Fgetc(fp)) == '+')
951 nag = 17;
952 else
953 Ungetc(i, fp);
955 break;
957 else
958 Ungetc(t, fp);
960 break;
964 *n = '\0';
966 if (!nag)
967 nag = (nags[0]) ? atoi(nags) : 0;
969 if (!nag || nag < 0 || nag > 255)
970 return;
972 // FIXME -1 is because move_text() increments g.hindex. The NAG
973 // annoatation isnt guaranteed to be after the move text in import format.
974 for (i = 0; i < MAX_PGN_NAG; i++) {
975 if (g->hp[g->hindex - 1]->nag[i])
976 continue;
978 g->hp[g->hindex - 1]->nag[i] = nag;
979 break;
982 skip_leading_space(fp);
986 * PGN move annotation.
988 static int annotation_text(GAME *g, FILE *fp, int terminator)
990 int c, lastchar = 0;
991 int len = 0;
992 int hindex = pgn_history_total(g->hp) - 1;
993 char buf[MAX_PGN_LINE_LEN], *a = buf;
995 skip_leading_space(fp);
997 while ((c = Fgetc(fp)) != EOF && c != terminator) {
998 if (c == '\n')
999 c = ' ';
1001 if (isspace(c) && isspace(lastchar))
1002 continue;
1004 if (len + 1 == sizeof(buf))
1005 continue;
1007 *a++ = lastchar = c;
1008 len++;
1011 *a = '\0';
1013 if ((g->hp[hindex]->comment = strdup(buf)) == NULL)
1014 return E_PGN_ERR;
1016 return E_PGN_OK;
1020 * PGN roster tag.
1022 static int tag_text(GAME *g, FILE *fp)
1024 char name[LINE_MAX], *n = name;
1025 char value[LINE_MAX], *v = value;
1026 int c, i = 0;
1027 int quoted_string = 0;
1028 int lastchar = 0;
1030 skip_leading_space(fp);
1032 /* The tag name is up until the first whitespace. */
1033 while ((c = Fgetc(fp)) != EOF && !isspace(c))
1034 *n++ = c;
1036 *n = '\0';
1037 *name = toupper(*name);
1038 skip_leading_space(fp);
1040 /* The value is until the first closing bracket. */
1041 while ((c = Fgetc(fp)) != EOF && c != ']') {
1042 if (i++ == '\0' && c == '\"') {
1043 quoted_string = 1;
1044 continue;
1047 if (c == '\n' || c == '\t')
1048 c = ' ';
1050 if (c == ' ' && lastchar == ' ')
1051 continue;
1053 lastchar = *v++ = c;
1056 *v = '\0';
1058 while (isspace(*--v))
1059 *v = '\0';
1061 if (*v == '\"')
1062 *v = '\0';
1064 if (value[0] == '\0') {
1065 if (strcmp(name, "Result") == 0)
1066 value[0] = '*';
1067 else
1068 value[0] = '?';
1070 value[1] = '\0';
1073 strncpy(value, remove_tag_escapes(value), sizeof(value));
1075 if (pgn_tag_add(&g->tag, name, value) != E_PGN_OK)
1076 warn("pgn_tag_add()");
1078 return 0;
1082 * PGN end-of-game marker.
1084 static int eog_text(GAME *g, FILE *fp)
1086 int c, i = 0;
1087 char buf[8], *p = buf;
1089 while ((c = Fgetc(fp)) != EOF && !isspace(c) && i++ < sizeof(buf))
1090 *p++ = c;
1092 if (isspace(c))
1093 Ungetc(c, fp);
1095 *p = 0;
1097 if (pgn_tag_add(&g->tag, "Result", buf) != E_PGN_OK)
1098 warn("pgn_tag_add()");
1100 return 1;
1104 * Parse RAV text and keep track of g.hp. The 'o' argument is the board state
1105 * before the current move (.hindex) was parsed.
1107 static int read_file(FILE *);
1108 static int rav_text(GAME *g, FILE *fp, int which, BOARD o)
1110 int ravindex = ravlevel;
1111 GAME tg;
1112 RAV *r;
1114 // Begin RAV for the current move.
1115 if (which == '(') {
1117 * Save the current game state for this RAV depth/level.
1119 if ((r = realloc(rav, (ravindex + 1) * sizeof(RAV))) == NULL) {
1120 warn("realloc()");
1121 return 1;
1124 rav = r;
1126 if ((rav[ravindex].fen = strdup(pgn_game_to_fen((*g), pgn_board)))
1127 == NULL) {
1128 warn("strdup()");
1129 return 1;
1132 rav[ravindex].hp = g->hp;
1133 memcpy(&tg, g, sizeof(GAME));
1134 memcpy(pgn_board, o, sizeof(BOARD));
1136 if ((g->hp[g->hindex]->rav = calloc(1, sizeof(HISTORY))) == NULL) {
1137 warn("calloc()");
1138 return 1;
1141 // pgn_history_add() will now append to this new RAV.
1142 g->hp = g->hp[g->hindex]->rav;
1145 * Reset. Will be restored later from 'tg' which is a local variable
1146 * so recursion is possible.
1148 g->hindex = 0;
1149 ravlevel++;
1152 * Now continue as normal as if there were no RAV. Moves will be
1153 * parsed and appended to the new .hp.
1155 if (read_file(fp))
1156 return 1;
1159 * read_file() has returned. The means that a RAV has ended by this
1160 * function returning -1 (see below). So we restore the game state
1161 * that was saved before calling read_file().
1163 pgn_board_init_fen(&tg, pgn_board, rav[ravindex].fen);
1164 free(rav[ravindex].fen);
1165 memcpy(g, &tg, sizeof(GAME));
1166 g->hp = rav[ravindex].hp;
1167 ravlevel--;
1170 * The end of a RAV. This makes read_file() that called this function
1171 * rav_text() return (see above).
1173 else if (which == ')')
1174 return -1;
1176 return 0;
1180 * FIXME
1181 * See pgn_board_init_fen(). Returns E_PGN_PARSE on parse error. 0 may be
1182 * returned on success when there is no move count in the FEN tag otherwise
1183 * the move count is returned.
1185 static int parse_fen_line(BOARD b, unsigned *flags, char *turn, char *ply,
1186 char *str)
1188 char *tmp;
1189 char line[LINE_MAX], *s;
1190 int row = 8, col = 1;
1191 int moven;
1193 strncpy(line, str, sizeof(line));
1194 s = line;
1195 pgn_reset_enpassant(b);
1197 while ((tmp = strsep(&s, "/")) != NULL) {
1198 int n;
1200 if (!VALIDFILE(row))
1201 return E_PGN_PARSE;
1203 while (*tmp) {
1204 if (*tmp == ' ')
1205 goto other;
1207 if (isdigit(*tmp)) {
1208 n = *tmp - '0';
1210 if (!VALIDFILE(n))
1211 return E_PGN_PARSE;
1213 for (; n; --n, col++)
1214 b[ROWTOBOARD(row)][COLTOBOARD(col)].icon =
1215 pgn_int_to_piece(WHITE, OPEN_SQUARE);
1217 else if (pgn_piece_to_int(*tmp) != -1)
1218 b[ROWTOBOARD(row)][COLTOBOARD(col++)].icon = *tmp;
1219 else
1220 return E_PGN_PARSE;
1222 tmp++;
1225 row--;
1226 col = 1;
1229 other:
1230 tmp++;
1232 switch (*tmp++) {
1233 case 'b':
1234 *turn = BLACK;
1235 break;
1236 case 'w':
1237 *turn = WHITE;
1238 break;
1239 default:
1240 return E_PGN_PARSE;
1243 tmp++;
1245 while (*tmp && *tmp != ' ') {
1246 switch (*tmp++) {
1247 case 'K':
1248 SET_FLAG(*flags, GF_WK_CASTLE);
1249 break;
1250 case 'Q':
1251 SET_FLAG(*flags, GF_WQ_CASTLE);
1252 break;
1253 case 'k':
1254 SET_FLAG(*flags, GF_BK_CASTLE);
1255 break;
1256 case 'q':
1257 SET_FLAG(*flags, GF_BQ_CASTLE);
1258 break;
1259 case '-':
1260 break;
1261 default:
1262 return E_PGN_PARSE;
1266 tmp++;
1268 // En passant.
1269 if (*tmp != '-') {
1270 if (!VALIDCOL(*tmp))
1271 return E_PGN_PARSE;
1273 col = *tmp++ - 'a';
1275 if (!VALIDROW(*tmp))
1276 return E_PGN_PARSE;
1278 row = 8 - atoi(tmp++);
1279 b[row][col].enpassant = 1;
1280 SET_FLAG(*flags, GF_ENPASSANT);
1282 else
1283 tmp++;
1285 if (*tmp)
1286 tmp++;
1288 if (*tmp)
1289 *ply = atoi(tmp);
1291 while (*tmp && isdigit(*tmp))
1292 tmp++;
1294 if (*tmp)
1295 tmp++;
1297 moven = atoi(tmp);
1298 return E_PGN_OK;
1302 * It initializes the board (b) to the FEN tag (if found) and sets the
1303 * castling and enpassant info for the game 'g'. If 'fen' is set it should be
1304 * a fen tag and will be parsed rather than the game 'g'.tag FEN tag. Returns
1305 * E_PGN_OK on success or if there was both a FEN and SetUp tag with the SetUp
1306 * tag set to 0. Returns E_PGN_PARSE if there was a FEN parse error, E_PGN_ERR
1307 * if there was no FEN tag or there was a SetUp tag with a value of 0. Returns
1308 * E_PGN_OK on success.
1310 int pgn_board_init_fen(GAME *g, BOARD b, char *fen)
1312 int n = -1, i = -1;
1313 BOARD tmpboard;
1314 unsigned flags = 0;
1315 char turn = g->turn;
1316 char ply = 0;
1318 pgn_board_init(tmpboard);
1320 if (!fen) {
1321 n = pgn_tag_find(g->tag, "Setup");
1322 i = pgn_tag_find(g->tag, "FEN");
1326 * If the FEN tag exists and there is no SetUp tag go ahead and parse it.
1327 * If there is a SetUp tag only parse the FEN tag if the value is 1.
1329 if ((n >= 0 && i >= 0 && atoi(g->tag[n]->value) == 1)
1330 || (i >= 0 && n == -1) || fen) {
1331 if ((n = parse_fen_line(tmpboard, &flags, &turn, &ply,
1332 (fen) ? fen : g->tag[i]->value)) != E_PGN_OK)
1333 return E_PGN_PARSE;
1334 else {
1335 memcpy(b, tmpboard, sizeof(BOARD));
1336 CLEAR_FLAG(g->flags, GF_WK_CASTLE);
1337 CLEAR_FLAG(g->flags, GF_WQ_CASTLE);
1338 CLEAR_FLAG(g->flags, GF_BK_CASTLE);
1339 CLEAR_FLAG(g->flags, GF_BQ_CASTLE);
1340 g->flags |= flags;
1341 g->turn = turn;
1342 g->ply = ply;
1345 else
1346 return (i >= 0 && n >= 0) ? E_PGN_OK : E_PGN_ERR;
1348 return E_PGN_OK;
1352 * Allocates a new game and increments 'gtotal'. 'gindex' is then set to the
1353 * new game. Returns E_PGN_ERR if there was a memory allocation error or
1354 * E_PGN_OK on success.
1356 int pgn_new_game()
1358 GAME *g;
1360 gindex = ++gtotal - 1;
1362 if ((g = realloc(game, gtotal * sizeof(GAME))) == NULL) {
1363 warn("realloc()");
1364 return E_PGN_ERR;
1367 game = g;
1368 memset(&game[gindex], 0, sizeof(GAME));
1370 if ((game[gindex].hp = calloc(1, sizeof(HISTORY *))) == NULL) {
1371 warn("calloc()");
1372 return E_PGN_ERR;
1375 game[gindex].hp[0] = NULL;
1376 game[gindex].history = game[gindex].hp;
1377 game[gindex].side = game[gindex].turn = WHITE;
1378 SET_FLAG(game[gindex].flags, GF_WK_CASTLE|GF_WQ_CASTLE|GF_WQ_CASTLE|
1379 GF_BK_CASTLE|GF_BQ_CASTLE);
1380 pgn_board_init(pgn_board);
1381 set_default_tags(&game[gindex]);
1382 return E_PGN_OK;
1385 static int read_file(FILE *fp)
1387 #ifdef DEBUG
1388 char buf[LINE_MAX] = {0}, *p = buf;
1389 #endif
1390 int c = 0;
1391 int parse_error = 0;
1392 BOARD old;
1394 while (1) {
1395 int nextchar = 0;
1396 int lastchar = c;
1397 int n;
1399 if ((c = Fgetc(fp)) == EOF) {
1400 if (feof(fp))
1401 break;
1403 if (ferror(fp)) {
1404 clearerr(fp);
1405 continue;
1409 if (!isascii(c)) {
1410 parse_error = 1;
1411 continue;
1414 if (c == '\015')
1415 continue;
1417 nextchar = Fgetc(fp);
1418 Ungetc(nextchar, fp);
1421 * If there was a move text parsing error, keep reading until the end
1422 * of the current game discarding the data.
1424 if (parse_error) {
1425 pgn_ret = E_PGN_PARSE;
1427 if (ravlevel)
1428 return 1;
1430 if (pgn_config.stop)
1431 return 1;
1433 if (c == '\n' && (nextchar == '\n' || nextchar == '\015')) {
1434 parse_error = 0;
1435 nulltags = 1;
1436 tag_section = 0;
1438 else
1439 continue;
1442 // New game reached.
1443 if (c == '\n' && (nextchar == '\n' || nextchar == '\015')) {
1444 if (tag_section)
1445 continue;
1447 nulltags = 1;
1448 tag_section = 0;
1449 continue;
1453 * PGN: Application comment. The '%' must be on the first column of
1454 * the line. The comment continues until the end of the current line.
1456 if (c == '%') {
1457 if (lastchar == '\n' || lastchar == 0) {
1458 while ((c = Fgetc(fp)) != EOF && c != '\n');
1459 continue;
1462 // Not sure what to do here.
1465 if (isspace(c))
1466 continue;
1468 // PGN: Reserved.
1469 if (c == '<' || c == '>')
1470 continue;
1473 * PGN: Recurrsive Annotation Variation. Read rav_text() for more
1474 * info.
1476 if (c == '(' || c == ')') {
1477 switch (rav_text(&game[gindex], fp, c, old)) {
1478 case -1:
1480 * This is the end of the current RAV. This function has
1481 * been called from rav_text(). Returning from this point
1482 * will put us back in rav_text().
1484 if (ravlevel > 0)
1485 return pgn_ret;
1488 * We're back at the root move. Continue as normal.
1490 break;
1491 case 1:
1492 parse_error = 1;
1493 continue;
1494 default:
1496 * Continue processing. Probably the root move.
1498 break;
1501 continue;
1504 // PGN: Numeric Annotation Glyph.
1505 if (c == '$' || c == '!' || c == '?' || c == '+' || c == '-' ||
1506 c == '~' || c == '=') {
1507 Ungetc(c, fp);
1508 nag_text(&game[gindex], fp);
1509 continue;
1513 * PGN: Annotation. The ';' comment continues until the end of the
1514 * current line. The '{' type comment continues until a '}' is
1515 * reached.
1517 if (c == '{' || c == ';') {
1518 annotation_text(&game[gindex], fp, (c == '{') ? '}' : '\n');
1519 continue;
1522 // PGN: Roster tag.
1523 if (c == '[') {
1524 // First roster tag found. Initialize the data structures.
1525 if (!tag_section) {
1526 nulltags = 0;
1527 tag_section = 1;
1529 if (gtotal && pgn_history_total(game[gindex].hp))
1530 game[gindex].hindex = pgn_history_total(game[gindex].hp) - 1;
1532 if (pgn_new_game() != E_PGN_OK) {
1533 pgn_ret = E_PGN_ERR;
1534 break;
1537 memcpy(old, pgn_board, sizeof(BOARD));
1540 if (tag_text(&game[gindex], fp))
1541 parse_error = 1; // FEN tag parse error.
1543 continue;
1546 // PGN: End-of-game markers.
1547 if ((isdigit(c) && (nextchar == '-' || nextchar == '/')) || c == '*') {
1548 Ungetc(c, fp);
1549 eog_text(&game[gindex], fp);
1550 nulltags = 1;
1551 tag_section = 0;
1553 if (!done_fen_tag) {
1554 if (pgn_tag_find(game[gindex].tag, "FEN") != -1 &&
1555 pgn_board_init_fen(&game[gindex], pgn_board, NULL)) {
1556 parse_error = 1;
1557 continue;
1560 pgn_fen_tag = pgn_tag_find(game[gindex].tag, "FEN");
1561 done_fen_tag = 1;
1562 #ifdef DEBUG
1563 dump_board(0, pgn_board);
1564 #endif
1567 continue;
1570 // PGN: Move text.
1571 if ((isdigit(c) && c != '0') || VALIDCOL(c) || c == 'N' || c == 'K'
1572 || c == 'Q' || c == 'B' || c == 'R' || c == 'P' ||
1573 c == 'O') {
1574 Ungetc(c, fp);
1576 // PGN: If a FEN tag exists, initialize the board to the value.
1577 if (tag_section) {
1578 if (pgn_tag_find(game[gindex].tag, "FEN") != E_PGN_ERR &&
1579 (n = pgn_board_init_fen(&game[gindex], pgn_board,
1580 NULL)) == E_PGN_PARSE) {
1581 parse_error = 1;
1582 continue;
1585 done_fen_tag = 1;
1586 pgn_fen_tag = pgn_tag_find(game[gindex].tag, "FEN");
1587 tag_section = 0;
1588 #ifdef DEBUG
1589 dump_board(0, pgn_board);
1590 #endif
1594 * PGN: Import format doesn't require a roster tag section. We've
1595 * arrived to the move text section without any tags so we
1596 * initialize a new game which set's the default tags and any tags
1597 * from the configuration file.
1599 if (nulltags) {
1600 if (gtotal)
1601 game[gindex].hindex = pgn_history_total(game[gindex].hp) - 1;
1603 if (pgn_new_game() != E_PGN_OK) {
1604 pgn_ret = E_PGN_ERR;
1605 break;
1608 memcpy(old, pgn_board, sizeof(BOARD));
1609 nulltags = 0;
1612 memcpy(old, pgn_board, sizeof(BOARD));
1614 if (move_text(&game[gindex], fp)) {
1615 SET_FLAG(game[gindex].flags, GF_PERROR);
1616 parse_error = 1;
1619 continue;
1622 #ifdef DEBUG
1623 *p++ = c;
1625 DUMP("unparsed: '%s'\n", buf);
1627 if (strlen(buf) + 1 == sizeof(buf))
1628 bzero(buf, sizeof(buf));
1629 #endif
1631 continue;
1634 return pgn_ret;
1638 * Parses a file whose file pointer is 'fp'. 'fp' may have been returned by
1639 * pgn_open(). If 'fp' is NULL then a single empty game will be allocated. If
1640 * there is a parsing error E_PGN_PARSE is returned, if there was a memory
1641 * allocation error E_PGN_ERR is returned, otherwise E_PGN_OK is returned and
1642 * the global 'gindex' is set to the last parsed game in the file and the
1643 * global 'gtotal' is set to the total number of games in the file. The file
1644 * will be closed when the parsing is done.
1646 int pgn_parse(FILE *fp)
1648 int i;
1649 long offset;
1651 if (!fp) {
1652 reset_game_data();
1653 pgn_ret = pgn_new_game();
1654 goto done;
1657 reset_game_data();
1658 nulltags = 1;
1659 pgn_isfile = 1;
1660 offset = ftell(fp);
1661 fseek(fp, 0, SEEK_END);
1662 pgn_fsize = ftell(fp);
1663 fseek(fp, offset, SEEK_SET);
1664 pgn_ret = read_file(fp);
1665 fclose(fp);
1667 if (rav)
1668 free(rav);
1670 if (gtotal < 1)
1671 pgn_new_game();
1673 done:
1674 gtotal = gindex + 1;
1676 for (i = 0; i < gtotal; i++) {
1677 game[i].history = game[i].hp;
1678 game[i].hindex = pgn_history_total(game[i].hp);
1681 return pgn_ret;
1685 * Escape '"' and '\' in tag values.
1687 static char *pgn_tag_add_escapes(const char *str)
1689 int i, n;
1690 int len = strlen(str);
1691 static char buf[MAX_PGN_LINE_LEN] = {0};
1693 for (i = n = 0; i < len; i++, n++) {
1694 switch (str[i]) {
1695 case '\\':
1696 case '\"':
1697 buf[n++] = '\\';
1698 break;
1699 default:
1700 break;
1703 buf[n] = str[i];
1706 buf[n] = '\0';
1707 return buf;
1710 static void Fputc(int c, FILE *fp, int *len)
1712 int i = *len;
1714 if (c != '\n' && i + 1 > 80)
1715 Fputc('\n', fp, &i);
1717 if (pgn_lastc == '\n' && c == ' ') {
1718 *len = 0;
1719 return;
1722 if (fputc(c, fp) == EOF)
1723 warn("PGN Save");
1724 else {
1725 if (c == '\n')
1726 i = 0;
1727 else
1728 i++;
1731 *len = i;
1732 pgn_lastc = c;
1735 static void putstring(FILE *fp, char *str, int *len)
1737 char *p;
1739 for (p = str; *p; p++) {
1740 int n = 0;
1742 while (*p && *p != ' ')
1743 n++, p++;
1745 if (n + *len > 80)
1746 Fputc('\n', fp, len);
1748 p -= n;
1749 Fputc(*p, fp, len);
1754 * See pgn_write() for more info.
1756 static void write_comments_and_nag(FILE *fp, HISTORY *h, int *len)
1758 int i;
1759 int type = 0;
1761 for (i = 0; i < MAX_PGN_NAG; i++) {
1762 if (h->nag[i]) {
1763 Fputc(' ', fp, len);
1764 Fputc('$', fp, len);
1765 putstring(fp, itoa(h->nag[i]), len);
1769 if (h->comment) {
1770 if (strlen(h->comment) + *len + 1 > 80) {
1771 type = 1;
1772 putstring(fp, " {", len);
1774 else
1775 putstring(fp, " ;", len);
1777 putstring(fp, h->comment, len);
1779 if (type)
1780 putstring(fp, "}", len);
1781 else
1782 Fputc('\n', fp, len);
1786 static void write_move_text(FILE *fp, HISTORY *h, int *len)
1788 Fputc(' ', fp, len);
1789 putstring(fp, h->move, len);
1791 if (!pgn_config.reduced)
1792 write_comments_and_nag(fp, h, len);
1795 static void write_all_move_text(FILE *fp, HISTORY **h, int m, int *len)
1797 int i;
1798 HISTORY **hp = NULL;
1800 for (i = 0; h[i]; i++) {
1801 if (pgn_write_turn == WHITE) {
1802 if (pgn_config.mpl && pgn_mpl == pgn_config.mpl) {
1803 pgn_mpl = 0;
1804 Fputc('\n', fp, len);
1807 if (m > 1 && i > 0)
1808 Fputc(' ', fp, len);
1810 if (strlen(itoa(m)) + 1 + *len > 80)
1811 Fputc('\n', fp, len);
1813 putstring(fp, itoa(m), len);
1814 Fputc('.', fp, len);
1815 pgn_mpl++;
1818 write_move_text(fp, h[i], len);
1820 if (!pgn_config.reduced && h[i]->rav) {
1821 int oldm = m;
1822 int oldturn = pgn_write_turn;
1824 ravlevel++;
1825 putstring(fp, " (", len);
1828 * If it's WHITE's turn the move number will be added above after
1829 * the call to write_all_move_text() below.
1831 if (pgn_write_turn == BLACK) {
1832 putstring(fp, itoa(m), len);
1833 putstring(fp, "...", len);
1836 hp = h[i]->rav;
1837 write_all_move_text(fp, hp, m, len);
1838 m = oldm;
1839 pgn_write_turn = oldturn;
1840 putstring(fp, ")", len);
1841 ravlevel--;
1843 if (h[i + 1] && !ravlevel)
1844 Fputc(' ', fp, len);
1846 if (pgn_write_turn == WHITE && h[i + 1]) {
1847 putstring(fp, itoa(m), len);
1848 putstring(fp, "...", len);
1852 if (pgn_write_turn == BLACK)
1853 m++;
1855 pgn_write_turn = (pgn_write_turn == WHITE) ? BLACK : WHITE;
1859 #ifdef WITH_COMPRESSED
1860 static char *compression_cmd(const char *filename, int expand)
1862 static char command[FILENAME_MAX];
1863 int len = strlen(filename);
1865 if (filename[len - 4] == '.' && filename[len - 3] == 'z' &&
1866 filename[len - 2] == 'i' && filename[len - 1] == 'p' &&
1867 filename[len] == '\0') {
1868 if (expand)
1869 snprintf(command, sizeof(command), "unzip -p %s 2>/dev/null",
1870 filename);
1871 else
1872 snprintf(command, sizeof(command), "zip -9 >%s 2>/dev/null",
1873 filename);
1875 return command;
1877 else if (filename[len - 3] == '.' && filename[len - 2] == 'g' &&
1878 filename[len - 1] == 'z' && filename[len] == '\0') {
1879 if (expand)
1880 snprintf(command, sizeof(command), "gzip -dc %s", filename);
1881 else
1882 snprintf(command, sizeof(command), "gzip -c 1>%s", filename);
1884 return command;
1886 else if (filename[len - 2] == '.' && filename[len - 1] == 'Z' &&
1887 filename[len] == '\0') {
1888 if (expand)
1889 snprintf(command, sizeof(command), "uncompress -c %s", filename);
1890 else
1891 snprintf(command, sizeof(command), "compress -c 1>%s", filename);
1893 return command;
1895 else if ((filename[len - 4] == '.' && filename[len - 3] == 'b' &&
1896 filename[len - 2] == 'z' && filename[len - 1] == '2' &&
1897 filename[len] == '\0') || (filename[len - 3] == '.' &&
1898 filename[len - 2] == 'b' && filename[len - 1] == 'z' &&
1899 filename[len] == '\0')) {
1900 if (expand)
1901 snprintf(command, sizeof(command), "bzip2 -dc %s", filename);
1902 else
1903 snprintf(command, sizeof(command), "bzip2 -zc 1>%s", filename);
1905 return command;
1908 return NULL;
1910 #endif
1913 * Returns a file pointer associated with 'filename' or NULL on error with
1914 * errno set to indicate the error. If compressed file support was enabled at
1915 * compile time and the filetype is supported and the utility is installed
1916 * then the file will be decompressed.
1918 FILE *pgn_open(const char *filename)
1920 FILE *fp = NULL;
1921 #ifdef WITH_COMPRESSED
1922 FILE *tfp = NULL;
1923 char buf[LINE_MAX];
1924 char *p;
1925 char *command = NULL;
1926 #endif
1928 if (access(filename, R_OK) == -1)
1929 return NULL;
1931 #ifdef WITH_COMPRESSED
1932 if ((command = compression_cmd(filename, 1)) != NULL) {
1933 if ((tfp = tmpfile()) == NULL)
1934 return NULL;
1936 if ((fp = popen(command, "r")) == NULL)
1937 return NULL;
1939 while ((p = fgets(buf, sizeof(buf), fp)) != NULL)
1940 fprintf(tfp, "%s", p);
1942 pclose(fp);
1945 if (tfp)
1946 fseek(tfp, 0, SEEK_SET);
1947 else {
1948 if ((tfp = fopen(filename, "r")) == NULL)
1949 return NULL;
1952 return tfp;
1953 #else
1954 if ((fp = fopen(filename, "r")) == NULL)
1955 return NULL;
1957 return fp;
1958 #endif
1962 * Returns E_PGN_OK if 'filename' is a recognized compressed filetype or
1963 * E_PGN_ERR if not.
1965 int pgn_is_compressed(const char *filename)
1967 #ifdef WITH_COMPRESSED
1968 if (compression_cmd(filename, 0))
1969 return E_PGN_OK;
1970 #endif
1972 return E_PGN_ERR;
1976 * Sets config flag 'f' to 'val'. Returns E_PGN_OK on success or E_PGN_ERR if
1977 * 'f' is an invalid flag or 'val' is an invalid value.
1979 int pgn_config_set(pgn_config_flag f, void *val)
1981 int n;
1983 switch (f) {
1984 case PGN_REDUCED:
1985 n = (int)val;
1987 if (n == 1)
1988 pgn_config.reduced = 1;
1989 else if (n == 0)
1990 pgn_config.reduced = 0;
1991 else
1992 return E_PGN_ERR;
1993 break;
1994 case PGN_MPL:
1995 pgn_config.mpl = (int)val;
1996 break;
1997 case PGN_STOP_ON_ERROR:
1998 n = (int)val;
2000 if (n == 1)
2001 pgn_config.stop = 1;
2002 else if (n == 0)
2003 pgn_config.stop = 0;
2004 else
2005 return E_PGN_ERR;
2006 break;
2007 case PGN_PROGRESS:
2008 pgn_config.progress = (long)val;
2009 break;
2010 case PGN_PROGRESS_FUNC:
2011 pgn_config.pfunc = (pgn_progress*)val;
2012 break;
2013 default:
2014 return E_PGN_ERR;
2017 return E_PGN_OK;
2021 * Returns the value accociated with 'f' or NULL if 'f' is invalid.
2023 void *pgn_config_get(pgn_config_flag f)
2025 switch (f) {
2026 case PGN_REDUCED:
2027 return (int *)pgn_config.reduced;
2028 case PGN_STOP_ON_ERROR:
2029 return (int *)pgn_config.stop;
2030 case PGN_MPL:
2031 return (int *)pgn_config.mpl;
2032 case PGN_PROGRESS:
2033 return (long *)pgn_config.progress;
2034 case PGN_PROGRESS_FUNC:
2035 return (pgn_progress *)pgn_config.pfunc;
2036 default:
2037 break;
2040 return NULL;
2044 * Writes a PGN formatted game 'g' to the file pointed to by 'fp'. See
2045 * 'pgn_config_flag' for output options. Returns E_PGN_ERR if there was a
2046 * memory allocation or write error and E_PGN_OK on success.
2048 int pgn_write(FILE *fp, GAME g)
2050 int i;
2051 int len = 0;
2053 pgn_write_turn = (TEST_FLAG(g.flags, GF_BLACK_OPENING)) ? BLACK : WHITE;
2054 pgn_tag_sort(g.tag);
2056 for (i = 0; g.tag[i]; i++) {
2057 struct tm tp;
2058 char tbuf[11] = {0};
2059 char *tmp;
2061 if (pgn_config.reduced && i == 7)
2062 break;
2064 if (strcmp(g.tag[i]->name, "Date") == 0) {
2065 if (strptime(g.tag[i]->value, PGN_TIME_FORMAT, &tp) != NULL) {
2066 len = strftime(tbuf, sizeof(tbuf), PGN_TIME_FORMAT, &tp) + 1;
2068 if ((tmp = strdup(tbuf)) == NULL)
2069 return E_PGN_ERR;
2071 free(g.tag[i]->value);
2072 g.tag[i]->value = tmp;
2075 else if (strcmp(g.tag[i]->name, "Event") == 0) {
2076 if (g.tag[i]->value[0] == '\0') {
2077 if ((tmp = strdup("?")) == NULL)
2078 return E_PGN_ERR;
2080 free(g.tag[i]->value);
2081 g.tag[i]->value = tmp;
2084 else if (strcmp(g.tag[i]->name, "Site") == 0) {
2085 if (g.tag[i]->value[0] == '\0') {
2086 if ((tmp = strdup("?")) == NULL)
2087 return E_PGN_ERR;
2089 free(g.tag[i]->value);
2090 g.tag[i]->value = tmp;
2093 else if (strcmp(g.tag[i]->name, "Round") == 0) {
2094 if (g.tag[i]->value[0] == '\0') {
2095 if ((tmp = strdup("?")) == NULL)
2096 return E_PGN_ERR;
2098 free(g.tag[i]->value);
2099 g.tag[i]->value = tmp;
2102 else if (strcmp(g.tag[i]->name, "Result") == 0) {
2103 if (g.tag[i]->value[0] == '\0') {
2104 if ((tmp = strdup("*")) == NULL)
2105 return E_PGN_ERR;
2107 free(g.tag[i]->value);
2108 g.tag[i]->value = tmp;
2111 else if (strcmp(g.tag[i]->name, "Black") == 0) {
2112 if (g.tag[i]->value[0] == '\0') {
2113 if ((tmp = strdup("?")) == NULL)
2114 return E_PGN_ERR;
2116 free(g.tag[i]->value);
2117 g.tag[i]->value = tmp;
2120 else if (strcmp(g.tag[i]->name, "White") == 0) {
2121 if (g.tag[i]->value[0] == '\0') {
2122 if ((tmp = strdup("?")) == NULL)
2123 return E_PGN_ERR;
2125 free(g.tag[i]->value);
2126 g.tag[i]->value = tmp;
2130 fprintf(fp, "[%s \"%s\"]\n", g.tag[i]->name,
2131 (g.tag[i]->value && g.tag[i]->value[0]) ?
2132 pgn_tag_add_escapes(g.tag[i]->value) : "");
2135 Fputc('\n', fp, &len);
2136 g.hp = g.history;
2137 ravlevel = 0;
2139 if (pgn_history_total(g.hp) && pgn_write_turn == BLACK)
2140 putstring(fp, "1...", &len);
2142 write_all_move_text(fp, g.hp, 1, &len);
2144 Fputc(' ', fp, &len);
2145 putstring(fp, g.tag[pgn_tag_find(g.tag, "Result")]->value, &len);
2146 putstring(fp, "\n\n", &len);
2148 if (!pgn_config.reduced) {
2149 CLEAR_FLAG(g.flags, GF_MODIFIED);
2150 CLEAR_FLAG(g.flags, GF_PERROR);
2153 return E_PGN_OK;
2157 * Clears the enpassant flag for all positions on board 'b'. Returns nothing.
2159 void pgn_reset_enpassant(BOARD b)
2161 int r, c;
2163 for (r = 0; r < 8; r++) {
2164 for (c = 0; c < 8; c++)
2165 b[r][c].enpassant = 0;