Fix writing an '.' on the first column in pgn_write().
[cboard.git] / src / pgn.c
blob051c10d7cd87afd068cbff6c58d05f44e99c9420
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 SET_FLAG(game[gindex].flags, GF_WK_CASTLE|GF_WQ_CASTLE|GF_WQ_CASTLE|
286 GF_BK_CASTLE|GF_BQ_CASTLE);
287 pgn_init_board(tb);
289 if (pgn_find_tag(g->tag, "FEN") != -1 &&
290 pgn_init_fen_board(g, tb, NULL))
291 return 1;
293 for (i = 0; i < n; i++) {
294 HISTORY *h;
297 if ((h = history_by_n(g->hp, i)) == NULL)
298 break;
300 if (pgn_validate_move(g, tb, h->move)) {
301 ret = 1;
302 break;
305 pgn_switch_turn(g);
308 if (ret == 0)
309 memcpy(b, tb, sizeof(BOARD));
311 return ret;
315 * Updates the game 'g' using board 'b' to the next 'n'th history move. The
316 * 's' parameter is either 2 for a wholestep or 1 for a halfstep.
318 void history_previous(GAME *g, BOARD b, int n)
320 if (g->hindex - n < 0) {
321 if (n <= 2)
322 g->hindex = history_total(g->hp);
323 else
324 g->hindex = 0;
326 else
327 g->hindex -= n;
329 history_update_board(g, b, g->hindex);
333 * Updates the game 'g' using board 'b' to the previous 'n'th history move.
334 * 's' parameter is either 2 for a wholestep or 1 for a halfstep.
336 void history_next(GAME *g, BOARD b, int n)
338 if (g->hindex + n > history_total(g->hp)) {
339 if (n <= 2)
340 g->hindex = 0;
341 else
342 g->hindex = history_total(g->hp);
344 else
345 g->hindex += n;
347 history_update_board(g, b, g->hindex);
350 * Converts the character piece 'p' to an integer.
352 int pgn_piece_to_int(int p)
354 if (p == '.')
355 return OPEN_SQUARE;
357 p = tolower(p);
359 switch (p) {
360 case 'p':
361 return PAWN;
362 case 'r':
363 return ROOK;
364 case 'n':
365 return KNIGHT;
366 case 'b':
367 return BISHOP;
368 case 'q':
369 return QUEEN;
370 case 'k':
371 return KING;
372 default:
373 break;
376 return -1;
380 * Converts the integer piece 'n' to a character.
382 int pgn_int_to_piece(char turn, int n)
384 int p = 0;
386 switch (n) {
387 case PAWN:
388 p = 'p';
389 break;
390 case ROOK:
391 p = 'r';
392 break;
393 case KNIGHT:
394 p = 'n';
395 break;
396 case BISHOP:
397 p = 'b';
398 break;
399 case QUEEN:
400 p = 'q';
401 break;
402 case KING:
403 p = 'k';
404 break;
405 case OPEN_SQUARE:
406 p = '.';
407 break;
408 default:
409 break;
412 return (turn == WHITE) ? toupper(p) : p;
416 * Finds a tag 'name' in the structure array 't'. Returns the location in the
417 * array of the found tag or -1 on failure.
419 int pgn_find_tag(TAG **t, const char *name)
421 int i;
423 for (i = 0; t[i]; i++) {
424 if (strcasecmp(t[i]->name, name) == 0)
425 return i;
428 return -1;
431 static int tag_compare(const void *a, const void *b)
433 TAG * const *ta = a;
434 TAG * const *tb = b;
436 return strcmp((*ta)->name, (*tb)->name);
440 * Sorts a tag array. The first seven tags are in order of the PGN standard so
441 * don't sort'em.
443 void pgn_sort_tags(TAG **tags)
445 if (pgn_tag_total(tags) <= 7)
446 return;
448 qsort(tags + 7, pgn_tag_total(tags) - 7, sizeof(TAG *), tag_compare);
451 // FIXME ???
452 #if 0
453 static int end_of_game(GAME g, const char *str)
455 int i;
456 int len;
458 for (i = 0; i < NARRAY(fancy_results); i++) {
459 if (strstr(str, fancy_results[i].pgn) != NULL) {
460 len = strlen(fancy_results[i].pgn) + 1;
461 g.tag[TAG_RESULT].value = Realloc(g.tag[TAG_RESULT].value, len);
462 strncpy(g.tag[TAG_RESULT].value, fancy_results[i].pgn, len);
463 return 1;
467 return 0;
469 #endif
471 int pgn_tag_total(TAG **tags)
473 int i = 0;
475 if (!tags)
476 return 0;
478 while (tags[i])
479 i++;
481 return i;
485 * Adds a tag 'name' with value 'value' to the pointer to array 'dst'. The 'n'
486 * parameter is incremented to the new total of array 'dst'. If a duplicate
487 * tag 'name' was found then the existing tag is updated to the new 'value'.
488 * Returns 1 if a duplicate tag was found or 0 otherwise.
490 int pgn_add_tag(TAG ***dst, char *name, char *value)
492 int i;
493 TAG **tdata = *dst;
494 int len = 0;
495 int t = pgn_tag_total(tdata);
497 name = trim(name);
498 value = trim(value);
500 // Find an existing tag with 'name'.
501 for (i = 0; i < t; i++) {
502 if (strcasecmp(tdata[i]->name, name) == 0) {
503 len = (value) ? strlen(value) + 1 : 1;
504 tdata[i]->value = Realloc(tdata[i]->value, len);
505 strncpy(tdata[i]->value, (value) ? value : "", len);
506 *dst = tdata;
507 return 1;
511 tdata = Realloc(tdata, (t + 2) * sizeof(TAG *));
512 tdata[t] = Malloc(sizeof(TAG));
513 len = strlen(name) + 1;
514 tdata[t]->name = Malloc(len);
515 strncpy(tdata[t]->name, name, len);
517 if (value) {
518 len = strlen(value) + 1;
519 tdata[t]->value = Malloc(len);
520 strncpy(tdata[t]->value, value, len);
522 else
523 tdata[t]->value = NULL;
525 tdata[++t] = NULL;
526 *dst = tdata;
527 return 0;
530 static char *remove_tag_escapes(const char *str)
532 int i, n;
533 int len = strlen(str);
534 static char buf[MAX_PGN_LINE_LEN] = {0};
536 for (i = n = 0; i < len; i++, n++) {
537 switch (str[i]) {
538 case '\\':
539 i++;
540 default:
541 break;
544 buf[n] = str[i];
547 buf[n] = '\0';
548 return buf;
552 * Initializes a new game board.
554 void pgn_init_board(BOARD b)
556 int row, col;
558 memset(b, 0, sizeof(BOARD));
560 for (row = 0; row < 8; row++) {
561 for (col = 0; col < 8; col++) {
562 int c = '.';
564 switch (row) {
565 case 0:
566 case 7:
567 switch (col) {
568 case 0:
569 case 7:
570 c = 'r';
571 break;
572 case 1:
573 case 6:
574 c = 'n';
575 break;
576 case 2:
577 case 5:
578 c = 'b';
579 break;
580 case 3:
581 c = 'q';
582 break;
583 case 4:
584 c = 'k';
585 break;
587 break;
588 case 1:
589 case 6:
590 c = 'p';
591 break;
594 b[row][col].icon = (row < 2) ? c : toupper(c);
600 * Adds the standard PGN roster tags to game 'g'.
602 static void set_default_tags(GAME *g)
604 time_t now;
605 char tbuf[12] = {0};
606 struct tm *tp;
607 struct passwd *pw = getpwuid(getuid());
609 time(&now);
610 tp = localtime(&now);
611 strftime(tbuf, sizeof(tbuf), PGN_TIME_FORMAT, tp);
612 tbuf[11] = 0;
614 /* The standard seven tag roster (in order of appearance). */
615 pgn_add_tag(&g->tag, "Event", "?");
616 pgn_add_tag(&g->tag, "Site", "?");
617 pgn_add_tag(&g->tag, "Date", tbuf);
618 pgn_add_tag(&g->tag, "Round", "-");
619 pgn_add_tag(&g->tag, "White", pw->pw_gecos);
620 pgn_add_tag(&g->tag, "Black", "?");
621 pgn_add_tag(&g->tag, "Result", "*");
624 void pgn_tag_free(TAG **tags)
626 int i;
628 if (!tags)
629 return;
631 for (i = 0; tags[i]; i++) {
632 free(tags[i]->name);
633 free(tags[i]->value);
634 free(tags[i]);
637 free(tags);
640 void pgn_free(GAME g)
642 history_free(g.history, 0);
643 pgn_tag_free(g.tag);
644 memset(&g, 0, sizeof(GAME));
647 void pgn_free_all()
649 int i;
651 for (i = 0; i < gtotal; i++) {
652 pgn_free(game[i]);
654 if (game[i].rav) {
655 for (game[i].ravlevel--; game[i].ravlevel >= 0; game[i].ravlevel--)
656 free(game[i].rav[game[i].ravlevel].fen);
658 free(game[i].rav);
662 if (game)
663 free(game);
664 game = NULL;
667 static void reset_game_data()
669 pgn_free_all();
670 gtotal = gindex = 0;
673 static void skip_leading_space(FILE *fp)
675 int c;
677 while ((c = fgetc(fp)) != EOF && !feof(fp)) {
678 if (!isspace(c))
679 break;
682 ungetc(c, fp);
686 * PGN move text section.
688 static int move_text(GAME *g, FILE *fp)
690 char m[MAX_SAN_MOVE_LEN + 1] = {0}, *p;
691 int c;
692 int count;
693 int dots = 0;
694 int digit = 0;
696 while((c = fgetc(fp)) != EOF) {
697 if (isspace(c))
698 continue;
700 if (isdigit(c)) {
701 digit = 1;
702 continue;
705 if (c == '.') {
706 dots++;
707 continue;
710 break;
713 if (digit) {
714 if (dots > 1) {
715 g->turn = BLACK;
717 if (g->hindex == 0 && g->ravlevel == 0)
718 SET_FLAG(g->flags, GF_BLACK_OPENING);
720 else {
721 g->turn = WHITE;
723 if (g->hindex == 0)
724 CLEAR_FLAG(g->flags, GF_BLACK_OPENING);
728 ungetc(c, fp);
730 if (fscanf(fp, " %[a-hPRNBQK1-9#+=Ox-]%n", m, &count) != 1)
731 return 1;
733 p = m + strlen(m) - 1;
735 if (!history_total(g->hp) && g->ravlevel == 0 && VALIDRANK(ROWTOINT(*p)) &&
736 VALIDFILE(COLTOINT(*(p-1))) && ROWTOINT(*p) > 4) {
737 g->turn = BLACK;
738 SET_FLAG(g->flags, GF_BLACK_OPENING);
741 p = m;
743 // In case the file is in a2a4 format, convert this move to SAN format.
744 if ((p = pgn_a2a4tosan(g, g->b, m)) == NULL)
745 return 1;
747 if (pgn_validate_move(g, g->b, p)) {
748 pgn_switch_turn(g);
749 return 1;
752 #ifdef DEBUG
753 DUMP("%s\n", p);
754 dump_board(0, g->b);
755 #endif
757 history_add(g, p);
758 pgn_switch_turn(g);
759 return 0;
763 * PGN nag text.
765 static void nag_text(GAME *g, FILE *fp)
767 int c, i, t;
768 char nags[5], *n = nags;
769 int nag = 0;
771 while ((c = fgetc(fp)) != EOF && !isspace(c)) {
772 if (c == '$') {
773 while ((c = fgetc(fp)) != EOF && isdigit(c))
774 *n++ = c;
776 break;
779 if (c == '!') {
780 if ((c = fgetc(fp)) == '!')
781 nag = 3;
782 else if (c == '?')
783 nag = 5;
784 else {
785 ungetc(c, fp);
786 nag = 1;
789 break;
791 else if (c == '?') {
792 if ((c = fgetc(fp)) == '?')
793 nag = 4;
794 else if (c == '!')
795 nag = 6;
796 else {
797 ungetc(c, fp);
798 nag = 2;
801 break;
803 else if (c == '~')
804 nag = 13;
805 else if (c == '=') {
806 if ((c = fgetc(fp)) == '+')
807 nag = 15;
808 else {
809 ungetc(c, fp);
810 nag = 10;
813 break;
815 else if (c == '+') {
816 if ((t = fgetc(fp)) == '=')
817 nag = 14;
818 else if (t == '-')
819 nag = 18;
820 else if (t == '/') {
821 if ((i = fgetc(fp)) == '-')
822 nag = 16;
823 else
824 ungetc(i, fp);
826 break;
828 else
829 ungetc(t, fp);
831 break;
833 else if (c == '-') {
834 if ((t = fgetc(fp)) == '+')
835 nag = 18;
836 else if (t == '/') {
837 if ((i = fgetc(fp)) == '+')
838 nag = 17;
839 else
840 ungetc(i, fp);
842 break;
844 else
845 ungetc(t, fp);
847 break;
851 *n = '\0';
853 if (!nag)
854 nag = (nags[0]) ? atoi(nags) : 0;
856 if (!nag || nag < 0 || nag > 255)
857 return;
859 for (i = 0; i < MAX_PGN_NAG; i++) {
860 if (g->hp[g->hindex]->nag[i])
861 continue;
863 g->hp[g->hindex]->nag[i] = nag;
864 break;
867 skip_leading_space(fp);
871 * PGN move annotation.
873 static void annotation_text(GAME *g, FILE *fp, int terminator)
875 int c, lastchar = 0;
876 int len = 0;
877 int hindex = history_total(g->hp) - 1;
878 char buf[MAX_PGN_LINE_LEN], *a = buf;
880 skip_leading_space(fp);
882 while ((c = fgetc(fp)) != EOF && c != terminator) {
883 if (c == '\n')
884 c = ' ';
886 if (isspace(c) && isspace(lastchar))
887 continue;
889 if (len + 1 == sizeof(buf))
890 continue;
892 *a++ = lastchar = c;
893 len++;
896 *a = '\0';
897 g->hp[hindex]->comment = Realloc(g->hp[hindex]->comment, ++len);
898 strncpy(g->hp[hindex]->comment, buf, len);
902 * PGN roster tag.
904 static int tag_text(GAME *g, FILE *fp)
906 char name[LINE_MAX], *n = name;
907 char value[LINE_MAX], *v = value;
908 int c, i = 0;
909 int quoted_string = 0;
910 int lastchar = 0;
912 skip_leading_space(fp);
914 /* The tag name is up until the first whitespace. */
915 while ((c = fgetc(fp)) != EOF && !isspace(c))
916 *n++ = c;
918 *n = '\0';
919 *name = toupper(*name);
920 skip_leading_space(fp);
922 /* The value is until the first closing bracket. */
923 while ((c = fgetc(fp)) != EOF && c != ']') {
924 if (i++ == '\0' && c == '\"') {
925 quoted_string = 1;
926 continue;
929 if (c == '\n' || c == '\t')
930 c = ' ';
932 if (c == ' ' && lastchar == ' ')
933 continue;
935 lastchar = *v++ = c;
938 *v = '\0';
940 while (isspace(*--v))
941 *v = '\0';
943 if (*v == '\"')
944 *v = '\0';
946 if (value[0] == '\0') {
947 if (strcmp(name, "Result") == 0)
948 value[0] = '*';
949 else
950 value[0] = '?';
952 value[1] = '\0';
955 strncpy(value, remove_tag_escapes(value), sizeof(value));
956 pgn_add_tag(&g->tag, name, value);
957 return 0;
961 * PGN end-of-game marker.
963 static int eog_text(GAME *g, FILE *fp)
965 int c, i = 0;
966 char buf[8], *p = buf;
968 while ((c = fgetc(fp)) != EOF && !isspace(c) && i++ < sizeof(buf))
969 *p++ = c;
971 if (isspace(c))
972 ungetc(c, fp);
974 *p = 0;
975 g->tag[TAG_RESULT]->value = Realloc(g->tag[TAG_RESULT]->value, strlen(buf) + 1);
976 strcpy(g->tag[TAG_RESULT]->value, buf);
977 return 1;
981 * Parse RAV text and keep track of g.hp. The 'o' argument is the board state
982 * before the current move (.hindex) was parsed.
984 static int read_file(FILE *);
985 static int rav_text(GAME *g, FILE *fp, int which, BOARD o)
987 int ravindex = ravlevel;
988 GAME tg;
990 // Begin RAV for the current move.
991 if (which == '(') {
993 * Save the current game state for this RAV depth/level.
995 rav = Realloc(rav, (ravindex + 1) * sizeof(RAV));
996 rav[ravindex].fen = strdup(pgn_game_to_fen((*g), g->b));
997 rav[ravindex].hp = g->hp;
998 memcpy(&tg, g, sizeof(GAME));
999 memcpy(g->b, o, sizeof(BOARD));
1001 g->hp[g->hindex]->rav = Calloc(1, sizeof(HISTORY));
1003 // history_add() will now append to this new RAV.
1004 g->hp = g->hp[g->hindex]->rav;
1007 * Reset. Will be restored later from 'tg' which is a local variable
1008 * so recursion is possible.
1010 g->hindex = 0;
1011 ravlevel++;
1014 * Now continue as normal as if there were no RAV. Moves will be
1015 * parsed and appended to the new .hp.
1017 if (read_file(fp))
1018 return 1;
1021 * read_file() has returned. The means that a RAV has ended by this
1022 * function returning -1 (see below). So we restore the game state
1023 * that was saved before calling read_file().
1025 pgn_init_fen_board(&tg, g->b, rav[ravindex].fen);
1026 free(rav[ravindex].fen);
1027 memcpy(g, &tg, sizeof(GAME));
1028 g->hp = rav[ravindex].hp;
1029 ravlevel--;
1032 * The end of a RAV. This makes read_file() that called this function
1033 * rav_text() return (see above).
1035 else if (which == ')')
1036 return -1;
1038 return 0;
1042 * See pgn_init_fen_board(). Returns -1 on parse error. 0 may be returned on
1043 * success when there is no move count in the FEN tag otherwise the move count
1044 * is returned.
1046 static int parse_fen_line(BOARD b, unsigned *flags, char *turn, char *ply,
1047 char *str)
1049 char *tmp;
1050 char line[LINE_MAX], *s;
1051 int row = 8, col = 1;
1052 int moven;
1054 strncpy(line, str, sizeof(line));
1055 s = line;
1056 pgn_reset_enpassant(b);
1058 while ((tmp = strsep(&s, "/")) != NULL) {
1059 int n;
1061 if (!VALIDFILE(row))
1062 return -1;
1064 while (*tmp) {
1065 if (*tmp == ' ')
1066 goto other;
1068 if (isdigit(*tmp)) {
1069 n = *tmp - '0';
1071 if (!VALIDFILE(n))
1072 return -1;
1074 for (; n; --n, col++)
1075 b[ROWTOBOARD(row)][COLTOBOARD(col)].icon =
1076 pgn_int_to_piece(WHITE, OPEN_SQUARE);
1078 else if (pgn_piece_to_int(*tmp) != -1)
1079 b[ROWTOBOARD(row)][COLTOBOARD(col++)].icon = *tmp;
1080 else
1081 return -1;
1083 tmp++;
1086 row--;
1087 col = 1;
1090 other:
1091 tmp++;
1093 switch (*tmp++) {
1094 case 'b':
1095 *turn = BLACK;
1096 break;
1097 case 'w':
1098 *turn = WHITE;
1099 break;
1100 default:
1101 return -1;
1104 tmp++;
1106 while (*tmp && *tmp != ' ') {
1107 switch (*tmp++) {
1108 case 'K':
1109 SET_FLAG(*flags, GF_WK_CASTLE);
1110 break;
1111 case 'Q':
1112 SET_FLAG(*flags, GF_WQ_CASTLE);
1113 break;
1114 case 'k':
1115 SET_FLAG(*flags, GF_BK_CASTLE);
1116 break;
1117 case 'q':
1118 SET_FLAG(*flags, GF_BQ_CASTLE);
1119 break;
1120 default:
1121 return -1;
1125 // En passant.
1126 if (*++tmp != '-') {
1127 if (!VALIDCOL(*tmp))
1128 return -1;
1130 col = *tmp++ - 'a';
1132 if (!VALIDROW(*tmp))
1133 return -1;
1135 row = 8 - atoi(tmp++);
1136 b[row][col].enpassant = 1;
1139 if (*tmp)
1140 tmp++;
1142 if (*tmp)
1143 *ply = atoi(tmp);
1145 while (*tmp && isdigit(*tmp))
1146 tmp++;
1148 if (*tmp)
1149 tmp++;
1151 moven = atoi(tmp);
1152 return moven;
1156 * This is called at the EOG marker and the beginning of the move text
1157 * section. So at least a move or EOG marker has to exist. It initializes the
1158 * board (b) to the FEN tag (if found) and sets the castling and enpassant
1159 * info for the game 'g'. If 'fen' is set it should be a fen tag and will be
1160 * parsed rather than the game 'g'.tag FEN tag. Returns 0 on success or if
1161 * there was both a FEN and SetUp tag with the SetUp tag set to 0. Returns 1
1162 * if there was a FEN parse error or no FEN tag at all.
1164 int pgn_init_fen_board(GAME *g, BOARD b, char *fen)
1166 int n = -1, i = -1;
1167 BOARD tmpboard;
1168 unsigned flags = 0;
1169 char turn = g->turn;
1170 char ply = 0;
1172 pgn_init_board(tmpboard);
1174 if (!fen) {
1175 n = pgn_find_tag(g->tag, "Setup");
1176 i = pgn_find_tag(g->tag, "FEN");
1180 * If the FEN tag exists and there is no SetUp tag go ahead and parse it.
1181 * If there is a SetUp tag only parse the FEN tag if the value is 1.
1183 if ((n >= 0 && i >= 0 && atoi(g->tag[n]->value) == 1)
1184 || (i >= 0 && n == -1) || fen) {
1185 if ((n = parse_fen_line(tmpboard, &flags, &turn, &ply,
1186 (fen) ? fen : g->tag[i]->value)) == -1)
1187 return 1;
1188 else {
1189 memcpy(b, tmpboard, sizeof(BOARD));
1190 CLEAR_FLAG(g->flags, GF_WK_CASTLE);
1191 CLEAR_FLAG(g->flags, GF_WQ_CASTLE);
1192 CLEAR_FLAG(g->flags, GF_BK_CASTLE);
1193 CLEAR_FLAG(g->flags, GF_BQ_CASTLE);
1194 g->flags |= flags;
1195 g->turn = turn;
1196 g->ply = ply;
1199 else
1200 return (i >= 0 && n >= 0) ? 0 : 1;
1202 return 0;
1206 * Allocates a new game and increments gindex (the current game) and gtotal
1207 * (the total number of games).
1209 void pgn_new_game()
1211 gindex = ++gtotal - 1;
1212 game = Realloc(game, gtotal * sizeof(GAME));
1213 memset(&game[gindex], 0, sizeof(GAME));
1214 game[gindex].hp = Calloc(1, sizeof(HISTORY *));
1215 game[gindex].hp[0] = NULL;
1216 game[gindex].history = game[gindex].hp;
1217 game[gindex].side = game[gindex].turn = WHITE;
1218 SET_FLAG(game[gindex].flags, GF_WK_CASTLE|GF_WQ_CASTLE|GF_WQ_CASTLE|
1219 GF_BK_CASTLE|GF_BQ_CASTLE);
1220 pgn_init_board(game[gindex].b);
1221 set_default_tags(&game[gindex]);
1224 static int read_file(FILE *fp)
1226 #ifdef DEBUG
1227 char buf[LINE_MAX] = {0}, *p = buf;
1228 #endif
1229 int c = 0;
1230 int parse_error = 0;
1231 BOARD old;
1233 while (1) {
1234 int nextchar = 0;
1235 int lastchar = c;
1237 if ((c = fgetc(fp)) == EOF) {
1238 if (feof(fp))
1239 break;
1241 if (ferror(fp)) {
1242 clearerr(fp);
1243 continue;
1247 if (!isascii(c)) {
1248 parse_error = 1;
1249 continue;
1252 if (c == '\015')
1253 continue;
1255 nextchar = fgetc(fp);
1256 ungetc(nextchar, fp);
1259 * If there was a move text parsing error, keep reading until the end
1260 * of the current game discarding the data.
1262 if (parse_error) {
1263 pgn_ret = 1;
1265 if (ravlevel)
1266 return 1;
1268 if (pgn_config.stop)
1269 return 1;
1271 if (c == '\n' && (nextchar == '\n' || nextchar == '\015')) {
1272 parse_error = 0;
1273 nulltags = 1;
1274 tag_section = 0;
1276 else
1277 continue;
1280 // New game reached.
1281 if (c == '\n' && (nextchar == '\n' || nextchar == '\015')) {
1282 if (tag_section) {
1283 tag_section = 0;
1284 continue;
1287 nulltags = 1;
1288 tag_section = 0;
1289 continue;
1293 * PGN: Application comment. The '%' must be on the first column of
1294 * the line. The comment continues until the end of the current line.
1296 if (c == '%') {
1297 if (lastchar == '\n' || lastchar == 0) {
1298 while ((c = fgetc(fp)) != EOF && c != '\n');
1299 continue;
1302 // Not sure what to do here.
1305 if (isspace(c))
1306 continue;
1308 // PGN: Reserved.
1309 if (c == '<' || c == '>')
1310 continue;
1313 * PGN: Recurrsive Annotation Variation. Read rav_text() for more
1314 * info.
1316 if (c == '(' || c == ')') {
1317 switch (rav_text(&game[gindex], fp, c, old)) {
1318 case -1:
1320 * This is the end of the current RAV. This function has
1321 * been called from rav_text(). Returning from this point
1322 * will put us back in rav_text().
1324 if (ravlevel > 0)
1325 return pgn_ret;
1328 * We're back at the root move. Continue as normal.
1330 break;
1331 case 1:
1332 parse_error = 1;
1333 continue;
1334 default:
1336 * Continue processing. Probably the root move.
1338 break;
1341 continue;
1344 // PGN: Numeric Annotation Glyph.
1345 if (c == '$' || c == '!' || c == '?' || c == '+' || c == '-' ||
1346 c == '~' || c == '=') {
1347 ungetc(c, fp);
1348 nag_text(&game[gindex], fp);
1349 continue;
1353 * PGN: Annotation. The ';' comment continues until the end of the
1354 * current line. The '{' type comment continues until a '}' is
1355 * reached.
1357 if (c == '{' || c == ';') {
1358 annotation_text(&game[gindex], fp, (c == '{') ? '}' : '\n');
1359 continue;
1362 // PGN: Roster tag.
1363 if (c == '[') {
1364 // First roster tag found. Initialize the data structures.
1365 if (!tag_section) {
1366 nulltags = 0;
1367 tag_section = 1;
1369 if (gtotal && history_total(game[gindex].hp))
1370 game[gindex].hindex = history_total(game[gindex].hp) - 1;
1372 pgn_new_game();
1373 memcpy(old, game[gindex].b, sizeof(BOARD));
1376 if (tag_text(&game[gindex], fp))
1377 parse_error = 1; // FEN tag parse error.
1379 continue;
1382 // PGN: End-of-game markers.
1383 if ((isdigit(c) && (nextchar == '-' || nextchar == '/')) || c == '*') {
1384 ungetc(c, fp);
1385 eog_text(&game[gindex], fp);
1386 nulltags = 1;
1387 tag_section = 0;
1389 if (!done_fen_tag) {
1390 if (pgn_find_tag(game[gindex].tag, "FEN") != -1 &&
1391 pgn_init_fen_board(&game[gindex], game[gindex].b,
1392 NULL)) {
1393 parse_error = 1;
1394 continue;
1397 done_fen_tag = 1;
1400 continue;
1403 // PGN: Move text.
1404 if ((isdigit(c) && c != '0') || VALIDCOL(c) || c == 'N' || c == 'K'
1405 || c == 'Q' || c == 'B' || c == 'R' || c == 'P' ||
1406 c == 'O') {
1407 ungetc(c, fp);
1409 // PGN: If a FEN tag exists, initialize the board to the value.
1410 if (tag_section) {
1411 if (pgn_find_tag(game[gindex].tag, "FEN") != -1 &&
1412 pgn_init_fen_board(&game[gindex], game[gindex].b,
1413 NULL)) {
1414 parse_error = 1;
1415 continue;
1418 done_fen_tag = 1;
1419 tag_section = 0;
1423 * PGN: Import format doesn't require a roster tag section. We've
1424 * arrived to the move text section without any tags so we
1425 * initialize a new game which set's the default tags and any tags
1426 * from the configuration file.
1428 if (nulltags) {
1429 if (gtotal)
1430 game[gindex].hindex = history_total(game[gindex].hp) - 1;
1432 pgn_new_game(game[gindex].b);
1433 memcpy(old, game[gindex].b, sizeof(BOARD));
1434 nulltags = 0;
1437 memcpy(old, game[gindex].b, sizeof(BOARD));
1439 if (move_text(&game[gindex], fp)) {
1440 SET_FLAG(game[gindex].flags, GF_PERROR);
1441 parse_error = 1;
1444 continue;
1447 #ifdef DEBUG
1448 *p++ = c;
1450 DUMP("unparsed: '%s'\n", buf);
1452 if (strlen(buf) + 1 == sizeof(buf))
1453 bzero(buf, sizeof(buf));
1454 #endif
1456 continue;
1459 return pgn_ret;
1463 * Parses a file whose file pointer is 'fp'. 'fp' may have been returned by
1464 * pgn_open(). If 'fp' is NULL then a single empty game will be allocated. If
1465 * there is a parsing error 1 is returned otherwise 0 is returned and the
1466 * global 'gindex' is set to the last parsed game in the file and the global
1467 * 'gtotal' is set to the total number of games in the file. The file will be
1468 * closed when the parsing is done.
1470 int pgn_parse(FILE *fp)
1472 int i;
1474 if (!fp) {
1475 reset_game_data();
1476 pgn_new_game();
1477 return 0;
1480 reset_game_data();
1481 nulltags = 1;
1482 pgn_ret = read_file(fp);
1483 fclose(fp);
1485 if (rav)
1486 free(rav);
1488 if (gtotal < 1) {
1489 pgn_new_game();
1490 goto done;
1493 gtotal = gindex + 1;
1495 for (i = 0; i < gtotal; i++) {
1496 game[i].history = game[i].hp;
1497 game[i].hindex = history_total(game[i].hp);
1500 done:
1501 return pgn_ret;
1505 * Escape '"' and '\' in tag values.
1507 static char *pgn_add_tag_escapes(const char *str)
1509 int i, n;
1510 int len = strlen(str);
1511 static char buf[MAX_PGN_LINE_LEN] = {0};
1513 for (i = n = 0; i < len; i++, n++) {
1514 switch (str[i]) {
1515 case '\\':
1516 case '\"':
1517 buf[n++] = '\\';
1518 break;
1519 default:
1520 break;
1523 buf[n] = str[i];
1526 buf[n] = '\0';
1527 return buf;
1530 static void Fputc(int c, FILE *fp, int *len)
1532 int i = *len;
1534 if (c != '\n' && i + 1 > 80)
1535 Fputc('\n', fp, &i);
1537 if (pgn_lastc == '\n' && c == ' ') {
1538 *len = 0;
1539 return;
1542 if (fputc(c, fp) == EOF)
1543 warn("PGN Save");
1544 else {
1545 if (c == '\n')
1546 i = 0;
1547 else
1548 i++;
1551 *len = i;
1552 pgn_lastc = c;
1555 static void putstring(FILE *fp, char *str, int *len)
1557 char *p;
1559 for (p = str; *p; p++) {
1560 int n = 0;
1562 while (*p && *p != ' ')
1563 n++, p++;
1565 if (n + *len > 80)
1566 Fputc('\n', fp, len);
1568 p -= n;
1569 Fputc(*p, fp, len);
1574 * See pgn_write() for more info.
1576 static void write_comments_and_nag(FILE *fp, HISTORY *h, int *len)
1578 int i;
1579 int type = 0;
1581 for (i = 0; i < MAX_PGN_NAG; i++) {
1582 if (h->nag[i]) {
1583 Fputc(' ', fp, len);
1584 Fputc('$', fp, len);
1585 putstring(fp, itoa(h->nag[i]), len);
1589 if (h->comment) {
1590 if (strlen(h->comment) + *len + 1 > 80) {
1591 type = 1;
1592 putstring(fp, " {", len);
1594 else
1595 putstring(fp, " ;", len);
1597 putstring(fp, h->comment, len);
1599 if (type)
1600 putstring(fp, "}", len);
1601 else
1602 Fputc('\n', fp, len);
1606 static void write_move_text(FILE *fp, HISTORY *h, int *len)
1608 Fputc(' ', fp, len);
1609 putstring(fp, h->move, len);
1611 if (!pgn_config.reduced)
1612 write_comments_and_nag(fp, h, len);
1615 static void write_all_move_text(FILE *fp, HISTORY **h, int m, int *len)
1617 int i;
1618 HISTORY **hp = NULL;
1620 for (i = 0; h[i]; i++) {
1621 if (pgn_write_turn == WHITE) {
1622 if (pgn_config.mpl && pgn_mpl == pgn_config.mpl) {
1623 pgn_mpl = 0;
1624 Fputc('\n', fp, len);
1627 if (m > 1 && i > 0)
1628 Fputc(' ', fp, len);
1630 if (strlen(itoa(m)) + 1 + *len > 80)
1631 Fputc('\n', fp, len);
1633 putstring(fp, itoa(m), len);
1634 Fputc('.', fp, len);
1635 pgn_mpl++;
1638 write_move_text(fp, h[i], len);
1640 if (!pgn_config.reduced && h[i]->rav) {
1641 int oldm = m;
1642 int oldturn = pgn_write_turn;
1644 ravlevel++;
1645 putstring(fp, " (", len);
1648 * If it's WHITE's turn the move number will be added above after
1649 * the call to write_all_move_text() below.
1651 if (pgn_write_turn == BLACK) {
1652 putstring(fp, itoa(m), len);
1653 putstring(fp, "...", len);
1656 hp = h[i]->rav;
1657 write_all_move_text(fp, hp, m, len);
1658 m = oldm;
1659 pgn_write_turn = oldturn;
1660 putstring(fp, ")", len);
1661 ravlevel--;
1663 if (h[i + 1] && !ravlevel)
1664 Fputc(' ', fp, len);
1666 if (pgn_write_turn == WHITE && h[i + 1]) {
1667 putstring(fp, itoa(m), len);
1668 putstring(fp, "...", len);
1672 if (pgn_write_turn == BLACK)
1673 m++;
1675 pgn_write_turn = (pgn_write_turn == WHITE) ? BLACK : WHITE;
1679 #ifdef WITH_COMPRESSED
1680 static char *compression_cmd(const char *filename, int expand)
1682 static char command[FILENAME_MAX];
1683 int len = strlen(filename);
1685 if (filename[len - 4] == '.' && filename[len - 3] == 'z' &&
1686 filename[len - 2] == 'i' && filename[len - 1] == 'p' &&
1687 filename[len] == '\0') {
1688 if (expand)
1689 snprintf(command, sizeof(command), "unzip -p %s 2>/dev/null",
1690 filename);
1691 else
1692 snprintf(command, sizeof(command), "zip -9 >%s 2>/dev/null",
1693 filename);
1695 return command;
1697 else if (filename[len - 3] == '.' && filename[len - 2] == 'g' &&
1698 filename[len - 1] == 'z' && filename[len] == '\0') {
1699 if (expand)
1700 snprintf(command, sizeof(command), "gzip -dc %s", filename);
1701 else
1702 snprintf(command, sizeof(command), "gzip -c 1>%s", filename);
1704 return command;
1706 else if (filename[len - 2] == '.' && filename[len - 1] == 'Z' &&
1707 filename[len] == '\0') {
1708 if (expand)
1709 snprintf(command, sizeof(command), "uncompress -c %s", filename);
1710 else
1711 snprintf(command, sizeof(command), "compress -c 1>%s", filename);
1713 return command;
1715 else if ((filename[len - 4] == '.' && filename[len - 3] == 'b' &&
1716 filename[len - 2] == 'z' && filename[len - 1] == '2' &&
1717 filename[len] == '\0') || (filename[len - 3] == '.' &&
1718 filename[len - 2] == 'b' && filename[len - 1] == 'z' &&
1719 filename[len] == '\0')) {
1720 if (expand)
1721 snprintf(command, sizeof(command), "bzip2 -dc %s", filename);
1722 else
1723 snprintf(command, sizeof(command), "bzip2 -zc 1>%s", filename);
1725 return command;
1728 return NULL;
1730 #endif
1733 * Returns a file pointer associated with 'filename' or NULL on error with
1734 * errno set to indicate the error. If compressed file support was enabled at
1735 * compile time and the filetype is supported and the utility is installed
1736 * then the file will be decompressed.
1738 FILE *pgn_open(const char *filename)
1740 FILE *fp = NULL;
1741 #ifdef WITH_COMPRESSED
1742 FILE *tfp = NULL;
1743 char buf[LINE_MAX];
1744 char *p;
1745 char *command = NULL;
1746 #endif
1748 if (access(filename, R_OK) == -1)
1749 return NULL;
1751 #ifdef WITH_COMPRESSED
1752 if ((command = compression_cmd(filename, 1)) != NULL) {
1753 if ((tfp = tmpfile()) == NULL)
1754 return NULL;
1756 if ((fp = popen(command, "r")) == NULL)
1757 return NULL;
1759 while ((p = fgets(buf, sizeof(buf), fp)) != NULL)
1760 fprintf(tfp, "%s", p);
1762 pclose(fp);
1765 if (tfp)
1766 fseek(tfp, 0, SEEK_SET);
1767 else {
1768 if ((tfp = fopen(filename, "r")) == NULL)
1769 return NULL;
1772 return tfp;
1773 #else
1774 if ((fp = fopen(filename, "r")) == NULL)
1775 return NULL;
1777 return fp;
1778 #endif
1782 * Returns 1 if 'filename' is a recognized compressed filetype.
1784 int pgn_is_compressed(const char *filename)
1786 #ifdef WITH_COMPRESSED
1787 if (compression_cmd(filename, 0))
1788 return 1;
1789 #endif
1791 return 0;
1795 * Set game flags. See chess.h for available flags.
1797 int pgn_config_set(pgn_config_flag f, int val)
1799 if (val < 0)
1800 return 1;
1802 switch (f) {
1803 case PGN_REDUCED:
1804 if (val == 1)
1805 pgn_config.reduced = 1;
1806 else if (val == 0)
1807 pgn_config.reduced = 0;
1808 else
1809 return 1;
1810 break;
1811 case PGN_MPL:
1812 pgn_config.mpl = val;
1813 break;
1814 case PGN_STOP_ON_ERROR:
1815 if (val == 1)
1816 pgn_config.stop = 1;
1817 else if (val == 0)
1818 pgn_config.stop = 0;
1819 else
1820 return 1;
1821 break;
1822 default:
1823 return 1;
1826 return 0;
1830 * Returns the value accociated with 'f' or -1 if 'f' is invalid.
1832 int pgn_config_get(pgn_config_flag f)
1834 switch (f) {
1835 case PGN_REDUCED:
1836 return pgn_config.reduced;
1837 case PGN_STOP_ON_ERROR:
1838 return pgn_config.stop;
1839 case PGN_MPL:
1840 return pgn_config.mpl;
1841 default:
1842 break;
1845 return -1;
1849 * Writes a PGN formatted game 'g' to the file pointed to by 'fp'. Returns 1
1850 * if the written move count doesn't match the game's ply count (FEN tag) or 0
1851 * on success.
1853 void pgn_write(FILE *fp, GAME g)
1855 int i;
1856 int len = 0;
1858 pgn_write_turn = (TEST_FLAG(g.flags, GF_BLACK_OPENING)) ? BLACK : WHITE;
1860 //FIXME
1862 if (!isfifo && g.hindex != g.htotal) {
1863 snprintf(buf, sizeof(buf), "%s (#%i)", GAME_SAVE_FROM_HISTORY_TITLE,
1864 idx + 1);
1865 i = message(buf, GAME_SAVE_FROM_HISTORY_PROMPT, "%s",
1866 GAME_SAVE_FROM_HISTORY_TEXT);
1868 if (i == 'c')
1869 g.htotal = g.hindex;
1873 pgn_sort_tags(g.tag);
1875 for (i = 0; g.tag[i]; i++) {
1876 struct tm tp;
1877 char tbuf[64 + 1]; //FIXME
1879 if (pgn_config.reduced && i == 7)
1880 break;
1882 if (strcmp(g.tag[i]->name, "Date") == 0) {
1883 if (strptime(g.tag[i]->value, TIME_FORMAT, &tp) != NULL) {
1884 len = strftime(tbuf, sizeof(tbuf), PGN_TIME_FORMAT, &tp) + 1;
1885 g.tag[i]->value = Realloc(g.tag[i]->value, len);
1886 strncpy(g.tag[i]->value, tbuf, len);
1889 else if (strcmp(g.tag[i]->name, "Event") == 0) {
1890 if (g.tag[i]->value[0] == '\0') {
1891 g.tag[i]->value = Realloc(g.tag[i]->value, 2);
1892 g.tag[i]->value[0] = '?';
1893 g.tag[i]->value[1] = '\0';
1896 else if (strcmp(g.tag[i]->name, "Site") == 0) {
1897 if (g.tag[i]->value[0] == '\0') {
1898 g.tag[i]->value = Realloc(g.tag[i]->value, 2);
1899 g.tag[i]->value[0] = '?';
1900 g.tag[i]->value[1] = '\0';
1903 else if (strcmp(g.tag[i]->name, "Round") == 0) {
1904 if (g.tag[i]->value[0] == '\0') {
1905 g.tag[i]->value = Realloc(g.tag[i]->value, 2);
1906 g.tag[i]->value[0] = '?';
1907 g.tag[i]->value[1] = '\0';
1910 else if (strcmp(g.tag[i]->name, "Result") == 0) {
1911 if (g.tag[i]->value[0] == '\0') {
1912 g.tag[i]->value = Realloc(g.tag[i]->value, 2);
1913 g.tag[i]->value[0] = '*';
1914 g.tag[i]->value[1] = '\0';
1917 else if (strcmp(g.tag[i]->name, "Black") == 0) {
1918 if (g.tag[i]->value[0] == '\0') {
1919 g.tag[i]->value = Realloc(g.tag[i]->value, 2);
1920 g.tag[i]->value[0] = '?';
1921 g.tag[i]->value[1] = '\0';
1924 else if (strcmp(g.tag[i]->name, "White") == 0) {
1925 if (g.tag[i]->value[0] == '\0') {
1926 g.tag[i]->value = Realloc(g.tag[i]->value, 2);
1927 g.tag[i]->value[0] = '?';
1928 g.tag[i]->value[1] = '\0';
1932 fprintf(fp, "[%s \"%s\"]\n", g.tag[i]->name,
1933 (g.tag[i]->value && g.tag[i]->value[0]) ?
1934 pgn_add_tag_escapes(g.tag[i]->value) : "");
1937 Fputc('\n', fp, &len);
1938 g.hp = g.history;
1939 ravlevel = 0;
1941 if (history_total(g.hp) && pgn_write_turn == BLACK)
1942 putstring(fp, "1...", &len);
1944 write_all_move_text(fp, g.hp, 1, &len);
1946 Fputc(' ', fp, &len);
1947 putstring(fp, g.tag[TAG_RESULT]->value, &len);
1948 putstring(fp, "\n\n", &len);
1950 if (!pgn_config.reduced) {
1951 CLEAR_FLAG(g.flags, GF_MODIFIED);
1952 CLEAR_FLAG(g.flags, GF_PERROR);