Fix engine status.
[cboard.git] / src / pgn.c
blobe3a3e133e6f705cd3e4224749c7786035e091c24
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"
44 #include "misc.h"
46 #ifdef DEBUG
47 #include "debug.h"
48 #endif
50 #ifdef WITH_DMALLOC
51 #include <dmalloc.h>
52 #endif
55 * Creates a FEN tag from the current game 'g' and board 'b'. Returns a FEN
56 * tag.
58 char *pgn_game_to_fen(GAME g, BOARD b)
60 int row, col;
61 int i;
62 static char buf[MAX_PGN_LINE_LEN], *p;
63 int oldturn = g.turn;
64 char enpassant[3] = {0}, *e;
65 int castle = 0;
67 for (i = history_total(g.hp); i >= g.hindex - 1; i--)
68 pgn_switch_turn(&g);
70 p = buf;
72 for (row = 0; row < 8; row++) {
73 int count = 0;
75 for (col = 0; col < 8; col++) {
76 if (b[row][col].enpassant) {
77 b[row][col].icon = pgn_int_to_piece(WHITE, OPEN_SQUARE);
78 e = enpassant;
79 *e++ = 'a' + col;
80 *e++ = ('0' + 8) - row;
81 *e = 0;
84 if (pgn_piece_to_int(b[row][col].icon) == OPEN_SQUARE) {
85 count++;
86 continue;
89 if (count) {
90 *p++ = '0' + count;
91 count = 0;
94 *p++ = b[row][col].icon;
95 *p = 0;
98 if (count) {
99 *p++ = '0' + count;
100 count = 0;
103 *p++ = '/';
106 --p;
107 *p++ = ' ';
108 *p++ = (g.turn == WHITE) ? 'w' : 'b';
109 *p++ = ' ';
111 if (TEST_FLAG(g.flags, GF_WK_CASTLE) && pgn_piece_to_int(b[7][7].icon) ==
112 ROOK && isupper(b[7][7].icon) && pgn_piece_to_int(b[7][4].icon) ==
113 KING && isupper(b[7][4].icon)) {
114 *p++ = 'K';
115 castle = 1;
118 if (TEST_FLAG(g.flags, GF_WQ_CASTLE) && pgn_piece_to_int(b[7][0].icon) ==
119 ROOK && isupper(b[7][0].icon) && pgn_piece_to_int(b[7][4].icon) ==
120 KING && isupper(b[7][4].icon)) {
121 *p++ = 'Q';
122 castle = 1;
125 if (TEST_FLAG(g.flags, GF_BK_CASTLE) && pgn_piece_to_int(b[0][7].icon) ==
126 ROOK && islower(b[0][7].icon) && pgn_piece_to_int(b[0][4].icon) ==
127 KING && islower(b[0][4].icon)) {
128 *p++ = 'k';
129 castle = 1;
132 if (TEST_FLAG(g.flags, GF_BQ_CASTLE) && pgn_piece_to_int(b[0][0].icon) ==
133 ROOK && islower(b[0][0].icon) && pgn_piece_to_int(b[0][4].icon) ==
134 KING && islower(b[0][4].icon)) {
135 *p++ = 'q';
136 castle = 1;
139 if (!castle)
140 *p++ = '-';
142 *p++ = ' ';
144 if (enpassant[0]) {
145 e = enpassant;
146 *p++ = *e++;
147 *p++ = *e++;
149 else
150 *p++ = '-';
152 *p++ = ' ';
154 // Halfmove clock.
155 *p = 0;
156 strcat(p, itoa(g.ply));
157 p = buf + strlen(buf);
158 *p++ = ' ';
160 // Fullmove number.
161 i = (g.hindex + 1) / 2;
162 *p = 0;
163 strcat(p, itoa((g.hindex / 2) + (g.hindex % 2)));
165 g.turn = oldturn;
166 return buf;
170 * Returns the total number of moves in 'h' or 0 if none.
172 int history_total(HISTORY **h)
174 int i;
176 if (!h)
177 return 0;
179 for (i = 0; h[i]; i++);
180 return i;
184 * Deallocates the all the data for 'h' from position 'start' in the array.
186 void history_free(HISTORY **h, int start)
188 int i;
190 if (!h)
191 return;
193 for (i = start; h[i]; i++) {
194 if (h[i]->comment)
195 free(h[i]->comment);
197 if (h[i]->rav)
198 history_free(h[i]->rav, 0);
200 if (h[i]->move)
201 free(h[i]->move);
204 free(h);
208 * Returns the history ply 'n' from 'h'. If 'n' is out of range then NULL is
209 * returned.
211 HISTORY *history_by_n(HISTORY **h, int n)
213 if (n < 0 || n > history_total(h) - 1)
214 return NULL;
216 return h[n];
220 * Appends move 'm' to game 'g' history pointer. The history pointer may be a
221 * in a RAV so g->rav.hp is also updated to the new (realloc()'ed) pointer. If
222 * not in a RAV then g->history will be updated. Returns 1 if realloc() failed
223 * or 0 on success.
225 int history_add(GAME *g, const char *m)
227 int t = history_total(g->hp);
228 int o;
229 HISTORY **h = NULL;
231 if (g->ravlevel)
232 o = g->rav[g->ravlevel].hp - g->hp;
233 else
234 o = g->history - g->hp;
236 if ((h = realloc(g->hp, (t + 2) * sizeof(HISTORY *))) == NULL)
237 return 1;
239 g->hp = h;
241 if (g->ravlevel)
242 g->rav[g->ravlevel].hp = g->hp + o;
243 else
244 g->history = g->hp + o;
246 if ((g->hp[t] = calloc(1, sizeof(HISTORY))) == NULL)
247 return 1;
249 g->hp[t++]->move = strdup(m);
250 g->hp[t] = NULL;
251 g->hindex = history_total(g->hp);
252 return 0;
256 * Resets the game 'g' using board 'b' up to history move 'n'.
258 int history_update_board(GAME *g, BOARD b, int n)
260 int i = 0;
261 BOARD tb;
262 int ret = 0;
264 if (TEST_FLAG(g->flags, GF_BLACK_OPENING))
265 g->turn = BLACK;
266 else
267 g->turn = WHITE;
269 #if 0
270 if (TEST_FLAG(g->flags, GF_PERROR))
271 SET_FLAG(flags, GF_PERROR);
273 if (TEST_FLAG(g->flags, GF_MODIFIED))
274 SET_FLAG(flags, GF_MODIFIED);
276 if (TEST_FLAG(g->flags, GF_DELETE))
277 SET_FLAG(flags, GF_DELETE);
279 if (TEST_FLAG(g->flags, GF_GAMEOVER))
280 SET_FLAG(flags, GF_GAMEOVER);
282 g->flags = flags;
283 #endif
285 pgn_init_board(tb);
287 if (pgn_find_tag(g->tag, "FEN") != -1 &&
288 pgn_init_fen_board(g, tb, NULL))
289 return 1;
291 for (i = 0; i < n; i++) {
292 HISTORY *h;
295 if ((h = history_by_n(g->hp, i)) == NULL)
296 break;
298 if (pgn_validate_move(g, tb, h->move)) {
299 ret = 1;
300 break;
303 pgn_switch_turn(g);
306 if (ret == 0)
307 memcpy(b, tb, sizeof(BOARD));
309 return ret;
313 * Updates the game 'g' using board 'b' to the next 'n'th history move. The
314 * 's' parameter is either 2 for a wholestep or 1 for a halfstep.
316 void history_previous(GAME *g, BOARD b, int n)
318 if (g->hindex - n < 0) {
319 if (n <= 2)
320 g->hindex = history_total(g->hp);
321 else
322 g->hindex = 0;
324 else
325 g->hindex -= n;
327 history_update_board(g, b, g->hindex);
331 * Updates the game 'g' using board 'b' to the previous 'n'th history move.
332 * 's' parameter is either 2 for a wholestep or 1 for a halfstep.
334 void history_next(GAME *g, BOARD b, int n)
336 if (g->hindex + n > history_total(g->hp)) {
337 if (n <= 2)
338 g->hindex = 0;
339 else
340 g->hindex = history_total(g->hp);
342 else
343 g->hindex += n;
345 history_update_board(g, b, g->hindex);
348 * Converts the character piece 'p' to an integer.
350 int pgn_piece_to_int(int p)
352 if (p == '.')
353 return OPEN_SQUARE;
355 p = tolower(p);
357 switch (p) {
358 case 'p':
359 return PAWN;
360 case 'r':
361 return ROOK;
362 case 'n':
363 return KNIGHT;
364 case 'b':
365 return BISHOP;
366 case 'q':
367 return QUEEN;
368 case 'k':
369 return KING;
370 default:
371 break;
374 return -1;
378 * Converts the integer piece 'n' to a character.
380 int pgn_int_to_piece(char turn, int n)
382 int p = 0;
384 switch (n) {
385 case PAWN:
386 p = 'p';
387 break;
388 case ROOK:
389 p = 'r';
390 break;
391 case KNIGHT:
392 p = 'n';
393 break;
394 case BISHOP:
395 p = 'b';
396 break;
397 case QUEEN:
398 p = 'q';
399 break;
400 case KING:
401 p = 'k';
402 break;
403 case OPEN_SQUARE:
404 p = '.';
405 break;
406 default:
407 break;
410 return (turn == WHITE) ? toupper(p) : p;
414 * Finds a tag 'name' in the structure array 't'. Returns the location in the
415 * array of the found tag or -1 on failure.
417 int pgn_find_tag(TAG **t, const char *name)
419 int i;
421 for (i = 0; t[i]; i++) {
422 if (strcasecmp(t[i]->name, name) == 0)
423 return i;
426 return -1;
429 static int tag_compare(const void *a, const void *b)
431 TAG * const *ta = a;
432 TAG * const *tb = b;
434 return strcmp((*ta)->name, (*tb)->name);
438 * Sorts a tag array. The first seven tags are in order of the PGN standard so
439 * don't sort'em.
441 void pgn_sort_tags(TAG **tags)
443 if (pgn_tag_total(tags) <= 7)
444 return;
446 qsort(tags + 7, pgn_tag_total(tags) - 7, sizeof(TAG *), tag_compare);
449 // FIXME ???
450 #if 0
451 static int end_of_game(GAME g, const char *str)
453 int i;
454 int len;
456 for (i = 0; i < NARRAY(fancy_results); i++) {
457 if (strstr(str, fancy_results[i].pgn) != NULL) {
458 len = strlen(fancy_results[i].pgn) + 1;
459 g.tag[TAG_RESULT].value = Realloc(g.tag[TAG_RESULT].value, len);
460 strncpy(g.tag[TAG_RESULT].value, fancy_results[i].pgn, len);
461 return 1;
465 return 0;
467 #endif
469 int pgn_tag_total(TAG **tags)
471 int i = 0;
473 if (!tags)
474 return 0;
476 while (tags[i])
477 i++;
479 return i;
483 * Adds a tag 'name' with value 'value' to the pointer to array 'dst'. The 'n'
484 * parameter is incremented to the new total of array 'dst'. If a duplicate
485 * tag 'name' was found then the existing tag is updated to the new 'value'.
486 * Returns 1 if a duplicate tag was found or 0 otherwise.
488 int pgn_add_tag(TAG ***dst, char *name, char *value)
490 int i;
491 TAG **tdata = *dst;
492 int len = 0;
493 int t = pgn_tag_total(tdata);
495 name = trim(name);
496 value = trim(value);
498 // Find an existing tag with 'name'.
499 for (i = 0; i < t; i++) {
500 if (strcasecmp(tdata[i]->name, name) == 0) {
501 len = (value) ? strlen(value) + 1 : 1;
502 tdata[i]->value = Realloc(tdata[i]->value, len);
503 strncpy(tdata[i]->value, (value) ? value : "", len);
504 *dst = tdata;
505 return 1;
509 tdata = Realloc(tdata, (t + 2) * sizeof(TAG *));
510 tdata[t] = Malloc(sizeof(TAG));
511 len = strlen(name) + 1;
512 tdata[t]->name = Malloc(len);
513 strncpy(tdata[t]->name, name, len);
515 if (value) {
516 len = strlen(value) + 1;
517 tdata[t]->value = Malloc(len);
518 strncpy(tdata[t]->value, value, len);
520 else
521 tdata[t]->value = NULL;
523 tdata[++t] = NULL;
524 *dst = tdata;
525 return 0;
528 static char *remove_tag_escapes(const char *str)
530 int i, n;
531 int len = strlen(str);
532 static char buf[MAX_PGN_LINE_LEN] = {0};
534 for (i = n = 0; i < len; i++, n++) {
535 switch (str[i]) {
536 case '\\':
537 i++;
538 default:
539 break;
542 buf[n] = str[i];
545 buf[n] = '\0';
546 return buf;
550 * Initializes a new game board.
552 void pgn_init_board(BOARD b)
554 int row, col;
556 memset(b, 0, sizeof(BOARD));
558 for (row = 0; row < 8; row++) {
559 for (col = 0; col < 8; col++) {
560 int c = '.';
562 switch (row) {
563 case 0:
564 case 7:
565 switch (col) {
566 case 0:
567 case 7:
568 c = 'r';
569 break;
570 case 1:
571 case 6:
572 c = 'n';
573 break;
574 case 2:
575 case 5:
576 c = 'b';
577 break;
578 case 3:
579 c = 'q';
580 break;
581 case 4:
582 c = 'k';
583 break;
585 break;
586 case 1:
587 case 6:
588 c = 'p';
589 break;
592 b[row][col].icon = (row < 2) ? c : toupper(c);
598 * Adds the standard PGN roster tags to game 'g'.
600 static void set_default_tags(GAME *g)
602 time_t now;
603 char tbuf[12] = {0};
604 struct tm *tp;
605 struct passwd *pw = getpwuid(getuid());
607 time(&now);
608 tp = localtime(&now);
609 strftime(tbuf, sizeof(tbuf), PGN_TIME_FORMAT, tp);
610 tbuf[11] = 0;
612 /* The standard seven tag roster (in order of appearance). */
613 pgn_add_tag(&g->tag, "Event", "?");
614 pgn_add_tag(&g->tag, "Site", "?");
615 pgn_add_tag(&g->tag, "Date", tbuf);
616 pgn_add_tag(&g->tag, "Round", "-");
617 pgn_add_tag(&g->tag, "White", pw->pw_gecos);
618 pgn_add_tag(&g->tag, "Black", "?");
619 pgn_add_tag(&g->tag, "Result", "*");
622 void pgn_tag_free(TAG **tags)
624 int i;
626 if (!tags)
627 return;
629 for (i = 0; tags[i]; i++) {
630 free(tags[i]->name);
631 free(tags[i]->value);
632 free(tags[i]);
635 free(tags);
638 void pgn_free(GAME g)
640 history_free(g.history, 0);
641 pgn_tag_free(g.tag);
642 memset(&g, 0, sizeof(GAME));
645 void pgn_free_all()
647 int i;
649 for (i = 0; i < gtotal; i++) {
650 pgn_free(game[i]);
652 if (game[i].rav) {
653 for (game[i].ravlevel--; game[i].ravlevel >= 0; game[i].ravlevel--)
654 free(game[i].rav[game[i].ravlevel].fen);
656 free(game[i].rav);
660 if (game)
661 free(game);
662 game = NULL;
665 static void reset_game_data()
667 pgn_free_all();
668 gtotal = gindex = 0;
671 static void skip_leading_space(FILE *fp)
673 int c;
675 while ((c = fgetc(fp)) != EOF && !feof(fp)) {
676 if (!isspace(c))
677 break;
680 ungetc(c, fp);
684 * PGN move text section.
686 static int move_text(GAME *g, FILE *fp)
688 char m[MAX_SAN_MOVE_LEN + 1] = {0}, *p;
689 int c;
690 int count;
691 int dots = 0;
692 int digit = 0;
694 while((c = fgetc(fp)) != EOF) {
695 if (isspace(c))
696 continue;
698 if (isdigit(c)) {
699 digit = 1;
700 continue;
703 if (c == '.') {
704 dots++;
705 continue;
708 break;
711 if (digit) {
712 if (dots > 1) {
713 g->turn = BLACK;
715 if (g->hindex == 0 && g->ravlevel == 0)
716 SET_FLAG(g->flags, GF_BLACK_OPENING);
718 else {
719 g->turn = WHITE;
721 if (g->hindex == 0)
722 CLEAR_FLAG(g->flags, GF_BLACK_OPENING);
726 ungetc(c, fp);
728 if (fscanf(fp, " %[a-hPRNBQK1-9#+=Ox-]%n", m, &count) != 1)
729 return 1;
731 p = m + strlen(m) - 1;
733 if (!history_total(g->hp) && g->ravlevel == 0 && VALIDRANK(ROWTOINT(*p)) &&
734 VALIDFILE(COLTOINT(*(p-1))) && ROWTOINT(*p) > 4) {
735 g->turn = BLACK;
736 SET_FLAG(g->flags, GF_BLACK_OPENING);
739 p = m;
741 // In case the file is in a2a4 format, convert this move to SAN format.
742 if ((p = pgn_a2a4tosan(g, g->b, m)) == NULL)
743 return 1;
745 if (pgn_validate_move(g, g->b, p)) {
746 pgn_switch_turn(g);
747 return 1;
750 #ifdef DEBUG
751 DUMP("%s\n", p);
752 dump_board(0, g->b);
753 #endif
755 history_add(g, p);
756 pgn_switch_turn(g);
757 return 0;
761 * PGN nag text.
763 static void nag_text(GAME *g, FILE *fp)
765 int c, i, t;
766 char nags[5], *n = nags;
767 int nag = 0;
769 while ((c = fgetc(fp)) != EOF && !isspace(c)) {
770 if (c == '$') {
771 while ((c = fgetc(fp)) != EOF && isdigit(c))
772 *n++ = c;
774 break;
777 if (c == '!') {
778 if ((c = fgetc(fp)) == '!')
779 nag = 3;
780 else if (c == '?')
781 nag = 5;
782 else {
783 ungetc(c, fp);
784 nag = 1;
787 break;
789 else if (c == '?') {
790 if ((c = fgetc(fp)) == '?')
791 nag = 4;
792 else if (c == '!')
793 nag = 6;
794 else {
795 ungetc(c, fp);
796 nag = 2;
799 break;
801 else if (c == '~')
802 nag = 13;
803 else if (c == '=') {
804 if ((c = fgetc(fp)) == '+')
805 nag = 15;
806 else {
807 ungetc(c, fp);
808 nag = 10;
811 break;
813 else if (c == '+') {
814 if ((t = fgetc(fp)) == '=')
815 nag = 14;
816 else if (t == '-')
817 nag = 18;
818 else if (t == '/') {
819 if ((i = fgetc(fp)) == '-')
820 nag = 16;
821 else
822 ungetc(i, fp);
824 break;
826 else
827 ungetc(t, fp);
829 break;
831 else if (c == '-') {
832 if ((t = fgetc(fp)) == '+')
833 nag = 18;
834 else if (t == '/') {
835 if ((i = fgetc(fp)) == '+')
836 nag = 17;
837 else
838 ungetc(i, fp);
840 break;
842 else
843 ungetc(t, fp);
845 break;
849 *n = '\0';
851 if (!nag)
852 nag = (nags[0]) ? atoi(nags) : 0;
854 if (!nag || nag < 0 || nag > 255)
855 return;
857 for (i = 0; i < MAX_PGN_NAG; i++) {
858 if (g->hp[g->hindex]->nag[i])
859 continue;
861 g->hp[g->hindex]->nag[i] = nag;
862 break;
865 skip_leading_space(fp);
869 * PGN move annotation.
871 static void annotation_text(GAME *g, FILE *fp, int terminator)
873 int c, lastchar = 0;
874 int len = 0;
875 int hindex = history_total(g->hp) - 1;
876 char buf[MAX_PGN_LINE_LEN], *a = buf;
878 skip_leading_space(fp);
880 while ((c = fgetc(fp)) != EOF && c != terminator) {
881 if (c == '\n')
882 c = ' ';
884 if (isspace(c) && isspace(lastchar))
885 continue;
887 if (len + 1 == sizeof(buf))
888 continue;
890 *a++ = lastchar = c;
891 len++;
894 *a = '\0';
895 g->hp[hindex]->comment = Realloc(g->hp[hindex]->comment, ++len);
896 strncpy(g->hp[hindex]->comment, buf, len);
900 * PGN roster tag.
902 static int tag_text(GAME *g, FILE *fp)
904 char name[LINE_MAX], *n = name;
905 char value[LINE_MAX], *v = value;
906 int c, i = 0;
907 int quoted_string = 0;
908 int lastchar = 0;
910 skip_leading_space(fp);
912 /* The tag name is up until the first whitespace. */
913 while ((c = fgetc(fp)) != EOF && !isspace(c))
914 *n++ = c;
916 *n = '\0';
917 *name = toupper(*name);
918 skip_leading_space(fp);
920 /* The value is until the first closing bracket. */
921 while ((c = fgetc(fp)) != EOF && c != ']') {
922 if (i++ == '\0' && c == '\"') {
923 quoted_string = 1;
924 continue;
927 if (c == '\n' || c == '\t')
928 c = ' ';
930 if (c == ' ' && lastchar == ' ')
931 continue;
933 lastchar = *v++ = c;
936 *v = '\0';
938 while (isspace(*--v))
939 *v = '\0';
941 if (*v == '\"')
942 *v = '\0';
944 if (value[0] == '\0') {
945 if (strcmp(name, "Result") == 0)
946 value[0] = '*';
947 else
948 value[0] = '?';
950 value[1] = '\0';
953 strncpy(value, remove_tag_escapes(value), sizeof(value));
954 pgn_add_tag(&g->tag, name, value);
955 return 0;
959 * PGN end-of-game marker.
961 static int eog_text(GAME *g, FILE *fp)
963 int c, i = 0;
964 char buf[8], *p = buf;
966 while ((c = fgetc(fp)) != EOF && !isspace(c) && i++ < sizeof(buf))
967 *p++ = c;
969 if (isspace(c))
970 ungetc(c, fp);
972 *p = 0;
973 g->tag[TAG_RESULT]->value = Realloc(g->tag[TAG_RESULT]->value, strlen(buf) + 1);
974 strcpy(g->tag[TAG_RESULT]->value, buf);
975 return 1;
979 * Parse RAV text and keep track of g.hp. The 'o' argument is the board state
980 * before the current move (.hindex) was parsed.
982 static int read_file(FILE *);
983 static int rav_text(GAME *g, FILE *fp, int which, BOARD o)
985 int ravindex = ravlevel;
986 GAME tg;
988 // Begin RAV for the current move.
989 if (which == '(') {
991 * Save the current game state for this RAV depth/level.
993 rav = Realloc(rav, (ravindex + 1) * sizeof(RAV));
994 rav[ravindex].fen = strdup(pgn_game_to_fen((*g), g->b));
995 rav[ravindex].hp = g->hp;
996 memcpy(&tg, g, sizeof(GAME));
997 memcpy(g->b, o, sizeof(BOARD));
999 g->hp[g->hindex]->rav = Calloc(1, sizeof(HISTORY));
1001 // history_add() will now append to this new RAV.
1002 g->hp = g->hp[g->hindex]->rav;
1005 * Reset. Will be restored later from 'tg' which is a local variable
1006 * so recursion is possible.
1008 g->hindex = 0;
1009 ravlevel++;
1012 * Now continue as normal as if there were no RAV. Moves will be
1013 * parsed and appended to the new .hp.
1015 if (read_file(fp))
1016 return 1;
1019 * read_file() has returned. The means that a RAV has ended by this
1020 * function returning -1 (see below). So we restore the game state
1021 * that was saved before calling read_file().
1023 pgn_init_fen_board(&tg, g->b, rav[ravindex].fen);
1024 free(rav[ravindex].fen);
1025 memcpy(g, &tg, sizeof(GAME));
1026 g->hp = rav[ravindex].hp;
1027 ravlevel--;
1030 * The end of a RAV. This makes read_file() that called this function
1031 * rav_text() return (see above).
1033 else if (which == ')')
1034 return -1;
1036 return 0;
1040 * See pgn_init_fen_board(). Returns -1 on parse error. 0 may be returned on
1041 * success when there is no move count in the FEN tag otherwise the move count
1042 * is returned.
1044 static int parse_fen_line(BOARD b, unsigned *flags, char *turn, char *ply,
1045 char *str)
1047 char *tmp;
1048 char line[LINE_MAX], *s;
1049 int row = 8, col = 1;
1050 int moven;
1052 strncpy(line, str, sizeof(line));
1053 s = line;
1054 pgn_reset_enpassant(b);
1056 while ((tmp = strsep(&s, "/")) != NULL) {
1057 int n;
1059 if (!VALIDFILE(row))
1060 return -1;
1062 while (*tmp) {
1063 if (*tmp == ' ')
1064 goto other;
1066 if (isdigit(*tmp)) {
1067 n = *tmp - '0';
1069 if (!VALIDFILE(n))
1070 return -1;
1072 for (; n; --n, col++)
1073 b[ROWTOBOARD(row)][COLTOBOARD(col)].icon =
1074 pgn_int_to_piece(WHITE, OPEN_SQUARE);
1076 else if (pgn_piece_to_int(*tmp) != -1)
1077 b[ROWTOBOARD(row)][COLTOBOARD(col++)].icon = *tmp;
1078 else
1079 return -1;
1081 tmp++;
1084 row--;
1085 col = 1;
1088 other:
1089 tmp++;
1091 switch (*tmp++) {
1092 case 'b':
1093 *turn = BLACK;
1094 break;
1095 case 'w':
1096 *turn = WHITE;
1097 break;
1098 default:
1099 return -1;
1102 tmp++;
1104 while (*tmp && *tmp != ' ') {
1105 switch (*tmp++) {
1106 case 'K':
1107 SET_FLAG(*flags, GF_WK_CASTLE);
1108 break;
1109 case 'Q':
1110 SET_FLAG(*flags, GF_WQ_CASTLE);
1111 break;
1112 case 'k':
1113 SET_FLAG(*flags, GF_BK_CASTLE);
1114 break;
1115 case 'q':
1116 SET_FLAG(*flags, GF_BQ_CASTLE);
1117 break;
1118 default:
1119 return -1;
1123 // En passant.
1124 if (*++tmp != '-') {
1125 if (!VALIDCOL(*tmp))
1126 return -1;
1128 col = *tmp++ - 'a';
1130 if (!VALIDROW(*tmp))
1131 return -1;
1133 row = 8 - atoi(tmp++);
1134 b[row][col].enpassant = 1;
1137 if (*tmp)
1138 tmp++;
1140 if (*tmp)
1141 *ply = atoi(tmp);
1143 while (*tmp && isdigit(*tmp))
1144 tmp++;
1146 if (*tmp)
1147 tmp++;
1149 moven = atoi(tmp);
1150 return moven;
1154 * This is called at the EOG marker and the beginning of the move text
1155 * section. So at least a move or EOG marker has to exist. It initializes the
1156 * board (b) to the FEN tag (if found) and sets the castling and enpassant
1157 * info for the game 'g'. If 'fen' is set it should be a fen tag and will be
1158 * parsed rather than the game 'g'.tag FEN tag. Returns 0 on success or if
1159 * there was both a FEN and SetUp tag with the SetUp tag set to 0. Returns 1
1160 * if there was a FEN parse error or no FEN tag at all.
1162 int pgn_init_fen_board(GAME *g, BOARD b, char *fen)
1164 int n = -1, i = -1;
1165 BOARD tmpboard;
1166 unsigned flags = 0;
1167 char turn = g->turn;
1168 char ply = 0;
1170 pgn_init_board(tmpboard);
1172 if (!fen) {
1173 n = pgn_find_tag(g->tag, "Setup");
1174 i = pgn_find_tag(g->tag, "FEN");
1178 * If the FEN tag exists and there is no SetUp tag go ahead and parse it.
1179 * If there is a SetUp tag only parse the FEN tag if the value is 1.
1181 if ((n >= 0 && i >= 0 && atoi(g->tag[n]->value) == 1)
1182 || (i >= 0 && n == -1) || fen) {
1183 if ((n = parse_fen_line(tmpboard, &flags, &turn, &ply,
1184 (fen) ? fen : g->tag[i]->value)) == -1)
1185 return 1;
1186 else {
1187 memcpy(b, tmpboard, sizeof(BOARD));
1188 CLEAR_FLAG(g->flags, GF_WK_CASTLE);
1189 CLEAR_FLAG(g->flags, GF_WQ_CASTLE);
1190 CLEAR_FLAG(g->flags, GF_BK_CASTLE);
1191 CLEAR_FLAG(g->flags, GF_BQ_CASTLE);
1192 g->flags |= flags;
1193 g->turn = turn;
1194 g->ply = ply;
1197 else
1198 return (i >= 0 && n >= 0) ? 0 : 1;
1200 return 0;
1204 * Allocates a new game and increments gindex (the current game) and gtotal
1205 * (the total number of games).
1207 void pgn_new_game()
1209 gindex = ++gtotal - 1;
1210 game = Realloc(game, gtotal * sizeof(GAME));
1211 memset(&game[gindex], 0, sizeof(GAME));
1212 game[gindex].hp = Calloc(1, sizeof(HISTORY *));
1213 game[gindex].hp[0] = NULL;
1214 game[gindex].history = game[gindex].hp;
1215 game[gindex].side = game[gindex].turn = WHITE;
1216 SET_FLAG(game[gindex].flags, GF_WK_CASTLE|GF_WQ_CASTLE|GF_WQ_CASTLE|GF_BK_CASTLE|GF_BQ_CASTLE);
1217 pgn_init_board(game[gindex].b);
1218 set_default_tags(&game[gindex]);
1221 static int read_file(FILE *fp)
1223 #ifdef DEBUG
1224 char buf[LINE_MAX] = {0}, *p = buf;
1225 #endif
1226 int c = 0;
1227 int parse_error = 0;
1228 BOARD old;
1230 while (1) {
1231 int nextchar = 0;
1232 int lastchar = c;
1234 if ((c = fgetc(fp)) == EOF) {
1235 if (feof(fp))
1236 break;
1238 if (ferror(fp)) {
1239 clearerr(fp);
1240 continue;
1244 if (!isascii(c)) {
1245 parse_error = 1;
1246 continue;
1249 if (c == '\015')
1250 continue;
1252 nextchar = fgetc(fp);
1253 ungetc(nextchar, fp);
1256 * If there was a move text parsing error, keep reading until the end
1257 * of the current game discarding the data.
1259 if (parse_error) {
1260 pgn_ret = 1;
1262 if (ravlevel)
1263 return 1;
1265 if (pgn_config.stop)
1266 return 1;
1268 if (c == '\n' && (nextchar == '\n' || nextchar == '\015')) {
1269 parse_error = 0;
1270 nulltags = 1;
1271 tag_section = 0;
1273 else
1274 continue;
1277 // New game reached.
1278 if (c == '\n' && (nextchar == '\n' || nextchar == '\015')) {
1279 if (tag_section) {
1280 tag_section = 0;
1281 continue;
1284 nulltags = 1;
1285 tag_section = 0;
1286 continue;
1290 * PGN: Application comment. The '%' must be on the first column of
1291 * the line. The comment continues until the end of the current line.
1293 if (c == '%') {
1294 if (lastchar == '\n' || lastchar == 0) {
1295 while ((c = fgetc(fp)) != EOF && c != '\n');
1296 continue;
1299 // Not sure what to do here.
1302 if (isspace(c))
1303 continue;
1305 // PGN: Reserved.
1306 if (c == '<' || c == '>')
1307 continue;
1310 * PGN: Recurrsive Annotation Variation. Read rav_text() for more
1311 * info.
1313 if (c == '(' || c == ')') {
1314 switch (rav_text(&game[gindex], fp, c, old)) {
1315 case -1:
1317 * This is the end of the current RAV. This function has
1318 * been called from rav_text(). Returning from this point
1319 * will put us back in rav_text().
1321 if (ravlevel > 0)
1322 return pgn_ret;
1325 * We're back at the root move. Continue as normal.
1327 break;
1328 case 1:
1329 parse_error = 1;
1330 continue;
1331 default:
1333 * Continue processing. Probably the root move.
1335 break;
1338 continue;
1341 // PGN: Numeric Annotation Glyph.
1342 if (c == '$' || c == '!' || c == '?' || c == '+' || c == '-' ||
1343 c == '~' || c == '=') {
1344 ungetc(c, fp);
1345 nag_text(&game[gindex], fp);
1346 continue;
1350 * PGN: Annotation. The ';' comment continues until the end of the
1351 * current line. The '{' type comment continues until a '}' is
1352 * reached.
1354 if (c == '{' || c == ';') {
1355 annotation_text(&game[gindex], fp, (c == '{') ? '}' : '\n');
1356 continue;
1359 // PGN: Roster tag.
1360 if (c == '[') {
1361 // First roster tag found. Initialize the data structures.
1362 if (!tag_section) {
1363 nulltags = 0;
1364 tag_section = 1;
1366 if (gtotal && history_total(game[gindex].hp))
1367 game[gindex].hindex = history_total(game[gindex].hp) - 1;
1369 pgn_new_game();
1370 memcpy(old, game[gindex].b, sizeof(BOARD));
1373 if (tag_text(&game[gindex], fp))
1374 parse_error = 1; // FEN tag parse error.
1376 continue;
1379 // PGN: End-of-game markers.
1380 if ((isdigit(c) && (nextchar == '-' || nextchar == '/')) || c == '*') {
1381 ungetc(c, fp);
1382 eog_text(&game[gindex], fp);
1383 nulltags = 1;
1384 tag_section = 0;
1386 if (!done_fen_tag) {
1387 if (pgn_find_tag(game[gindex].tag, "FEN") != -1 &&
1388 pgn_init_fen_board(&game[gindex], game[gindex].b,
1389 NULL)) {
1390 parse_error = 1;
1391 continue;
1394 done_fen_tag = 1;
1397 continue;
1400 // PGN: Move text.
1401 if ((isdigit(c) && c != '0') || VALIDCOL(c) || c == 'N' || c == 'K'
1402 || c == 'Q' || c == 'B' || c == 'R' || c == 'P' ||
1403 c == 'O') {
1404 ungetc(c, fp);
1406 // PGN: If a FEN tag exists, initialize the board to the value.
1407 if (tag_section) {
1408 if (pgn_find_tag(game[gindex].tag, "FEN") != -1 &&
1409 pgn_init_fen_board(&game[gindex], game[gindex].b,
1410 NULL)) {
1411 parse_error = 1;
1412 continue;
1415 done_fen_tag = 1;
1416 tag_section = 0;
1420 * PGN: Import format doesn't require a roster tag section. We've
1421 * arrived to the move text section without any tags so we
1422 * initialize a new game which set's the default tags and any tags
1423 * from the configuration file.
1425 if (nulltags) {
1426 if (gtotal)
1427 game[gindex].hindex = history_total(game[gindex].hp) - 1;
1429 pgn_new_game(game[gindex].b);
1430 memcpy(old, game[gindex].b, sizeof(BOARD));
1431 nulltags = 0;
1434 memcpy(old, game[gindex].b, sizeof(BOARD));
1436 if (move_text(&game[gindex], fp)) {
1437 SET_FLAG(game[gindex].flags, GF_PERROR);
1438 parse_error = 1;
1441 continue;
1444 #ifdef DEBUG
1445 *p++ = c;
1447 DUMP("unparsed: '%s'\n", buf);
1449 if (strlen(buf) + 1 == sizeof(buf))
1450 bzero(buf, sizeof(buf));
1451 #endif
1453 continue;
1456 return pgn_ret;
1460 * Parses a file whose file pointer is 'fp'. 'fp' may have been returned by
1461 * pgn_open(). If 'fp' is NULL then a single empty game will be allocated. If
1462 * there is a parsing error 1 is returned otherwise 0 is returned and the
1463 * global 'gindex' is set to the last parsed game in the file and the global
1464 * 'gtotal' is set to the total number of games in the file. The file will be
1465 * closed when the parsing is done.
1467 int pgn_parse(FILE *fp)
1469 int i;
1471 if (!fp) {
1472 reset_game_data();
1473 pgn_new_game();
1474 return 0;
1477 reset_game_data();
1478 nulltags = 1;
1479 pgn_ret = read_file(fp);
1480 fclose(fp);
1482 if (rav)
1483 free(rav);
1485 if (gtotal < 1) {
1486 pgn_new_game();
1487 goto done;
1490 gtotal = gindex + 1;
1492 for (i = 0; i < gtotal; i++) {
1493 game[i].history = game[i].hp;
1494 game[i].hindex = history_total(game[i].hp);
1497 done:
1498 return pgn_ret;
1502 * Escape '"' and '\' in tag values.
1504 static char *pgn_add_tag_escapes(const char *str)
1506 int i, n;
1507 int len = strlen(str);
1508 static char buf[MAX_PGN_LINE_LEN] = {0};
1510 for (i = n = 0; i < len; i++, n++) {
1511 switch (str[i]) {
1512 case '\\':
1513 case '\"':
1514 buf[n++] = '\\';
1515 break;
1516 default:
1517 break;
1520 buf[n] = str[i];
1523 buf[n] = '\0';
1524 return buf;
1527 static void Fputc(int c, FILE *fp, int *len)
1529 int i = *len;
1531 if (c != '\n' && i + 1 > 80)
1532 Fputc('\n', fp, &i);
1534 if (pgn_lastc == '\n' && c == ' ') {
1535 *len = 0;
1536 return;
1539 if (fputc(c, fp) == EOF)
1540 warn("PGN Save");
1541 else {
1542 if (c == '\n')
1543 i = 0;
1544 else
1545 i++;
1548 *len = i;
1549 pgn_lastc = c;
1552 static void putstring(FILE *fp, char *str, int *len)
1554 char *p;
1556 for (p = str; *p; p++) {
1557 int n = 0;
1559 while (*p && *p != ' ')
1560 n++, p++;
1562 if (n + *len > 80)
1563 Fputc('\n', fp, len);
1565 p -= n;
1566 Fputc(*p, fp, len);
1571 * See pgn_write() for more info.
1573 static void write_comments_and_nag(FILE *fp, HISTORY *h, int *len)
1575 int i;
1576 int type = 0;
1578 for (i = 0; i < MAX_PGN_NAG; i++) {
1579 if (h->nag[i]) {
1580 Fputc(' ', fp, len);
1581 Fputc('$', fp, len);
1582 putstring(fp, itoa(h->nag[i]), len);
1586 if (h->comment) {
1587 if (strlen(h->comment) + *len + 1 > 80) {
1588 type = 1;
1589 putstring(fp, " {", len);
1591 else
1592 putstring(fp, " ;", len);
1594 putstring(fp, h->comment, len);
1596 if (type)
1597 putstring(fp, "}", len);
1598 else
1599 Fputc('\n', fp, len);
1603 static void write_move_text(FILE *fp, HISTORY *h, int *len)
1605 Fputc(' ', fp, len);
1606 putstring(fp, h->move, len);
1608 if (!pgn_config.reduced)
1609 write_comments_and_nag(fp, h, len);
1612 static void write_all_move_text(FILE *fp, HISTORY **h, int m, int *len)
1614 int i;
1615 HISTORY **hp = NULL;
1617 for (i = 0; h[i]; i++) {
1618 if (pgn_write_turn == WHITE) {
1619 if (pgn_config.mpl && pgn_mpl == pgn_config.mpl) {
1620 pgn_mpl = 0;
1621 Fputc('\n', fp, len);
1624 if (m > 1 && i > 0)
1625 Fputc(' ', fp, len);
1627 putstring(fp, itoa(m), len);
1628 Fputc('.', fp, len);
1629 pgn_mpl++;
1632 write_move_text(fp, h[i], len);
1634 if (!pgn_config.reduced && h[i]->rav) {
1635 int oldm = m;
1636 int oldturn = pgn_write_turn;
1638 ravlevel++;
1639 putstring(fp, " (", len);
1642 * If it's WHITE's turn the move number will be added above after
1643 * the call to write_all_move_text() below.
1645 if (pgn_write_turn == BLACK) {
1646 putstring(fp, itoa(m), len);
1647 putstring(fp, "...", len);
1650 hp = h[i]->rav;
1651 write_all_move_text(fp, hp, m, len);
1652 m = oldm;
1653 pgn_write_turn = oldturn;
1654 putstring(fp, ")", len);
1655 ravlevel--;
1657 if (h[i + 1] && !ravlevel)
1658 Fputc(' ', fp, len);
1660 if (pgn_write_turn == WHITE && h[i + 1]) {
1661 putstring(fp, itoa(m), len);
1662 putstring(fp, "...", len);
1666 if (pgn_write_turn == BLACK)
1667 m++;
1669 pgn_write_turn = (pgn_write_turn == WHITE) ? BLACK : WHITE;
1673 #ifdef WITH_COMPRESSED
1674 static char *compression_cmd(const char *filename, int expand)
1676 static char command[FILENAME_MAX];
1677 int len = strlen(filename);
1679 if (filename[len - 4] == '.' && filename[len - 3] == 'z' &&
1680 filename[len - 2] == 'i' && filename[len - 1] == 'p' &&
1681 filename[len] == '\0') {
1682 if (expand)
1683 snprintf(command, sizeof(command), "unzip -p %s 2>/dev/null",
1684 filename);
1685 else
1686 snprintf(command, sizeof(command), "zip -9 >%s 2>/dev/null",
1687 filename);
1689 return command;
1691 else if (filename[len - 3] == '.' && filename[len - 2] == 'g' &&
1692 filename[len - 1] == 'z' && filename[len] == '\0') {
1693 if (expand)
1694 snprintf(command, sizeof(command), "gzip -dc %s", filename);
1695 else
1696 snprintf(command, sizeof(command), "gzip -c 1>%s", filename);
1698 return command;
1700 else if (filename[len - 2] == '.' && filename[len - 1] == 'Z' &&
1701 filename[len] == '\0') {
1702 if (expand)
1703 snprintf(command, sizeof(command), "uncompress -c %s", filename);
1704 else
1705 snprintf(command, sizeof(command), "compress -c 1>%s", filename);
1707 return command;
1709 else if ((filename[len - 4] == '.' && filename[len - 3] == 'b' &&
1710 filename[len - 2] == 'z' && filename[len - 1] == '2' &&
1711 filename[len] == '\0') || (filename[len - 3] == '.' &&
1712 filename[len - 2] == 'b' && filename[len - 1] == 'z' &&
1713 filename[len] == '\0')) {
1714 if (expand)
1715 snprintf(command, sizeof(command), "bzip2 -dc %s", filename);
1716 else
1717 snprintf(command, sizeof(command), "bzip2 -zc 1>%s", filename);
1719 return command;
1722 return NULL;
1724 #endif
1727 * Returns a file pointer associated with 'filename' or NULL on error with
1728 * errno set to indicate the error. If compressed file support was enabled at
1729 * compile time and the filetype is supported and the utility is installed
1730 * then the file will be decompressed.
1732 FILE *pgn_open(const char *filename)
1734 FILE *fp = NULL;
1735 #ifdef WITH_COMPRESSED
1736 FILE *tfp = NULL;
1737 char buf[LINE_MAX];
1738 char *p;
1739 char *command = NULL;
1740 #endif
1742 if (access(filename, R_OK) == -1)
1743 return NULL;
1745 #ifdef WITH_COMPRESSED
1746 if ((command = compression_cmd(filename, 1)) != NULL) {
1747 if ((tfp = tmpfile()) == NULL)
1748 return NULL;
1750 if ((fp = popen(command, "r")) == NULL)
1751 return NULL;
1753 while ((p = fgets(buf, sizeof(buf), fp)) != NULL)
1754 fprintf(tfp, "%s", p);
1756 pclose(fp);
1759 if (tfp)
1760 fseek(tfp, 0, SEEK_SET);
1761 else {
1762 if ((tfp = fopen(filename, "r")) == NULL)
1763 return NULL;
1766 return tfp;
1767 #else
1768 if ((fp = fopen(filename, "r")) == NULL)
1769 return NULL;
1771 return fp;
1772 #endif
1776 * Returns 1 if 'filename' is a recognized compressed filetype.
1778 int pgn_is_compressed(const char *filename)
1780 #ifdef WITH_COMPRESSED
1781 if (compression_cmd(filename, 0))
1782 return 1;
1783 #endif
1785 return 0;
1789 * Set game flags. See chess.h for available flags.
1791 int pgn_config_set(pgn_config_flag f, int val)
1793 if (val < 0)
1794 return 1;
1796 switch (f) {
1797 case PGN_REDUCED:
1798 if (val == 1)
1799 pgn_config.reduced = 1;
1800 else if (val == 0)
1801 pgn_config.reduced = 0;
1802 else
1803 return 1;
1804 break;
1805 case PGN_MPL:
1806 pgn_config.mpl = val;
1807 break;
1808 case PGN_STOP_ON_ERROR:
1809 if (val == 1)
1810 pgn_config.stop = 1;
1811 else if (val == 0)
1812 pgn_config.stop = 0;
1813 else
1814 return 1;
1815 break;
1816 default:
1817 return 1;
1820 return 0;
1824 * Returns the value accociated with 'f' or -1 if 'f' is invalid.
1826 int pgn_config_get(pgn_config_flag f)
1828 switch (f) {
1829 case PGN_REDUCED:
1830 return pgn_config.reduced;
1831 case PGN_STOP_ON_ERROR:
1832 return pgn_config.stop;
1833 case PGN_MPL:
1834 return pgn_config.mpl;
1835 default:
1836 break;
1839 return -1;
1843 * Writes a PGN formatted game 'g' to the file pointed to by 'fp'. Returns 1
1844 * if the written move count doesn't match the game's ply count (FEN tag) or 0
1845 * on success.
1847 void pgn_write(FILE *fp, GAME g)
1849 int i;
1850 int len = 0;
1852 pgn_write_turn = (TEST_FLAG(g.flags, GF_BLACK_OPENING)) ? BLACK : WHITE;
1854 //FIXME
1856 if (!isfifo && g.hindex != g.htotal) {
1857 snprintf(buf, sizeof(buf), "%s (#%i)", GAME_SAVE_FROM_HISTORY_TITLE,
1858 idx + 1);
1859 i = message(buf, GAME_SAVE_FROM_HISTORY_PROMPT, "%s",
1860 GAME_SAVE_FROM_HISTORY_TEXT);
1862 if (i == 'c')
1863 g.htotal = g.hindex;
1867 pgn_sort_tags(g.tag);
1869 for (i = 0; g.tag[i]; i++) {
1870 struct tm tp;
1871 char tbuf[64 + 1]; //FIXME
1873 if (pgn_config.reduced && i == 7)
1874 break;
1876 if (strcmp(g.tag[i]->name, "Date") == 0) {
1877 if (strptime(g.tag[i]->value, TIME_FORMAT, &tp) != NULL) {
1878 len = strftime(tbuf, sizeof(tbuf), PGN_TIME_FORMAT, &tp) + 1;
1879 g.tag[i]->value = Realloc(g.tag[i]->value, len);
1880 strncpy(g.tag[i]->value, tbuf, len);
1883 else if (strcmp(g.tag[i]->name, "Event") == 0) {
1884 if (g.tag[i]->value[0] == '\0') {
1885 g.tag[i]->value = Realloc(g.tag[i]->value, 2);
1886 g.tag[i]->value[0] = '?';
1887 g.tag[i]->value[1] = '\0';
1890 else if (strcmp(g.tag[i]->name, "Site") == 0) {
1891 if (g.tag[i]->value[0] == '\0') {
1892 g.tag[i]->value = Realloc(g.tag[i]->value, 2);
1893 g.tag[i]->value[0] = '?';
1894 g.tag[i]->value[1] = '\0';
1897 else if (strcmp(g.tag[i]->name, "Round") == 0) {
1898 if (g.tag[i]->value[0] == '\0') {
1899 g.tag[i]->value = Realloc(g.tag[i]->value, 2);
1900 g.tag[i]->value[0] = '?';
1901 g.tag[i]->value[1] = '\0';
1904 else if (strcmp(g.tag[i]->name, "Result") == 0) {
1905 if (g.tag[i]->value[0] == '\0') {
1906 g.tag[i]->value = Realloc(g.tag[i]->value, 2);
1907 g.tag[i]->value[0] = '*';
1908 g.tag[i]->value[1] = '\0';
1911 else if (strcmp(g.tag[i]->name, "Black") == 0) {
1912 if (g.tag[i]->value[0] == '\0') {
1913 g.tag[i]->value = Realloc(g.tag[i]->value, 2);
1914 g.tag[i]->value[0] = '?';
1915 g.tag[i]->value[1] = '\0';
1918 else if (strcmp(g.tag[i]->name, "White") == 0) {
1919 if (g.tag[i]->value[0] == '\0') {
1920 g.tag[i]->value = Realloc(g.tag[i]->value, 2);
1921 g.tag[i]->value[0] = '?';
1922 g.tag[i]->value[1] = '\0';
1926 fprintf(fp, "[%s \"%s\"]\n", g.tag[i]->name,
1927 (g.tag[i]->value && g.tag[i]->value[0]) ?
1928 pgn_add_tag_escapes(g.tag[i]->value) : "");
1931 Fputc('\n', fp, &len);
1932 g.hp = g.history;
1933 ravlevel = 0;
1935 if (history_total(g.hp) && pgn_write_turn == BLACK)
1936 putstring(fp, "1...", &len);
1938 write_all_move_text(fp, g.hp, 1, &len);
1940 Fputc(' ', fp, &len);
1941 putstring(fp, g.tag[TAG_RESULT]->value, &len);
1942 putstring(fp, "\n\n", &len);
1944 if (!pgn_config.reduced) {
1945 CLEAR_FLAG(g.flags, GF_MODIFIED);
1946 CLEAR_FLAG(g.flags, GF_PERROR);