1 /* vim:tw=78:ts=8:sw=4:set ft=c: */
3 Copyright (C) 2002-2018 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
26 #include <sys/types.h>
50 static BOARD pgn_board
;
52 static int tag_section
;
54 static int pgn_write_turn
;
58 static long pgn_fsize
;
60 static RAV
*pgn_rav_p
;
63 extern char *strptime (const char *, const char *, struct tm
*);
71 if ((c
= fgetc (fp
)) != EOF
)
73 if (pgn_config
.progress
&& pgn_config
.pfunc
)
75 if (!(ftell (fp
) % pgn_config
.progress
))
76 pgn_config
.pfunc (pgn_fsize
, ftell (fp
));
84 Ungetc (int c
, FILE * fp
)
86 return ungetc (c
, fp
);
92 return "libchess " PACKAGE_VERSION
;
103 while (isspace (*str
))
106 for (i
= strlen (str
) - 1; isspace (str
[i
]); i
--)
113 itoa (long n
, char *buf
)
115 sprintf (buf
, "%li", n
);
120 * Clears the valid move flag for all positions on board 'b'. Returns nothing.
123 pgn_reset_valid_moves (BOARD b
)
128 PGN_DUMP ("%s:%d: resetting valid moves\n", __FILE__
, __LINE__
);
131 for (row
= 0; row
< 8; row
++)
133 for (col
= 0; col
< 8; col
++)
134 b
[row
][col
].valid
= 0;
139 * Toggles g->turn. Returns nothing.
142 pgn_switch_turn (GAME g
)
144 g
->turn
= (g
->turn
== WHITE
) ? BLACK
: WHITE
;
148 * Toggles g->side and switches the White and Black roster tags. Returns
152 pgn_switch_side (GAME g
, int t
)
156 char *w
= g
->tag
[4]->value
;
158 g
->tag
[4]->value
= g
->tag
[5]->value
;
159 g
->tag
[5]->value
= w
;
166 * Creates a FEN tag from the current game 'g', history move (g->hindex) and
167 * board 'b'. Returns a FEN tag.
170 pgn_game_to_fen (GAME g
, BOARD b
)
175 char buf
[MAX_PGN_LINE_LEN
] = { 0 }, *p
;
176 int oldturn
= g
->turn
;
177 char enpassant
[3] = { 0 }, *e
;
181 PGN_DUMP ("%s:%d: creating FEN tag\n", __FILE__
, __LINE__
);
184 for (i
= pgn_history_total (g
->hp
); i
>= g
->hindex
- 1; i
--)
189 for (row
= 0; row
< 8; row
++)
193 for (col
= 0; col
< 8; col
++)
195 if (b
[row
][col
].enpassant
)
197 b
[row
][col
].icon
= pgn_int_to_piece (WHITE
, OPEN_SQUARE
);
200 *e
++ = ('0' + 8) - row
;
204 if (pgn_piece_to_int (b
[row
][col
].icon
) == OPEN_SQUARE
)
216 *p
++ = b
[row
][col
].icon
;
231 *p
++ = (g
->turn
== WHITE
) ? 'w' : 'b';
234 if (TEST_FLAG (g
->flags
, GF_WK_CASTLE
) && pgn_piece_to_int (b
[7][7].icon
) ==
235 ROOK
&& isupper (b
[7][7].icon
) && pgn_piece_to_int (b
[7][4].icon
) ==
236 KING
&& isupper (b
[7][4].icon
))
242 if (TEST_FLAG (g
->flags
, GF_WQ_CASTLE
) && pgn_piece_to_int (b
[7][0].icon
) ==
243 ROOK
&& isupper (b
[7][0].icon
) && pgn_piece_to_int (b
[7][4].icon
) ==
244 KING
&& isupper (b
[7][4].icon
))
250 if (TEST_FLAG (g
->flags
, GF_BK_CASTLE
) && pgn_piece_to_int (b
[0][7].icon
) ==
251 ROOK
&& islower (b
[0][7].icon
) && pgn_piece_to_int (b
[0][4].icon
) ==
252 KING
&& islower (b
[0][4].icon
))
258 if (TEST_FLAG (g
->flags
, GF_BQ_CASTLE
) && pgn_piece_to_int (b
[0][0].icon
) ==
259 ROOK
&& islower (b
[0][0].icon
) && pgn_piece_to_int (b
[0][4].icon
) ==
260 KING
&& islower (b
[0][4].icon
))
284 strcat (p
, itoa (g
->ply
, tmp
));
285 p
= buf
+ strlen (buf
);
289 i
= (g
->hindex
+ 1) / 2;
291 strcat (p
, itoa ((g
->hindex
/ 2) + (g
->hindex
% 2), tmp
));
294 return buf
[0] ? strdup (buf
) : NULL
;
298 * Returns the total number of moves in 'h' or 0 if there are none.
301 pgn_history_total (HISTORY
** h
)
308 for (i
= 0; h
[i
]; i
++);
313 * Deallocates all of the history data from position 'start' in the array 'h'.
317 pgn_history_free (HISTORY
** h
, int start
)
322 PGN_DUMP ("%s:%d: freeing history\n", __FILE__
, __LINE__
);
325 if (!h
|| start
> pgn_history_total (h
))
331 for (i
= start
; h
[i
]; i
++)
335 pgn_history_free (h
[i
]->rav
, 0);
339 free (h
[i
]->comment
);
349 * Returns the history ply 'n' from 'h'. If 'n' is out of range then NULL is
353 pgn_history_by_n (HISTORY
** h
, int n
)
355 if (n
< 0 || n
> pgn_history_total (h
) - 1)
362 * Appends move 'm' to game 'g' history pointer and creates a FEN tag for the
363 * current game state using board 'b'. The FEN tag makes things faster than
364 * validating the entire move history by validating only the current move to
365 * the previous moves FEN tag. The history pointer may be a in a RAV so
366 * g->rav.hp is also updated to the new (realloc()'ed) pointer. If not in a
367 * RAV then g->history will be updated. Returns E_PGN_ERR if realloc() failed
368 * or E_PGN_OK on success.
371 pgn_history_add (GAME g
, BOARD b
, const char *m
)
373 int t
= pgn_history_total (g
->hp
);
375 HISTORY
**h
= NULL
, *tmp
;
376 int ri
= (g
->ravlevel
) ? g
->rav
[g
->ravlevel
- 1].hindex
: 0;
379 PGN_DUMP ("%s:%d: adding '%s' to move history\n", __FILE__
, __LINE__
, m
);
383 o
= g
->rav
[g
->ravlevel
- 1].hp
[ri
- 1]->rav
- g
->hp
;
385 o
= g
->history
- g
->hp
;
387 if ((h
= realloc (g
->hp
, (t
+ 2) * sizeof (HISTORY
*))) == NULL
)
393 g
->rav
[g
->ravlevel
- 1].hp
[ri
- 1]->rav
= g
->hp
+ o
;
395 g
->history
= g
->hp
+ o
;
397 if ((g
->hp
[t
] = calloc (1, sizeof (HISTORY
))) == NULL
)
400 if ((g
->hp
[t
]->move
= strdup (m
)) == NULL
)
410 g
->hindex
= pgn_history_total (g
->hp
);
412 tmp
->fen
= pgn_game_to_fen (g
, b
);
418 * Resets the game 'g' using board 'b' up to history move (g->hindex) 'n'.
419 * Returns E_PGN_OK on success or E_PGN_PARSE if there was a FEN tag but the
420 * parsing of it failed. Or returns E_PGN_INVALID if somehow the move failed
421 * validation while resetting.
424 pgn_board_update (GAME g
, BOARD b
, int n
)
428 int p_error
= TEST_FLAG (g
->flags
, GF_PERROR
);
429 int black_opening
= TEST_FLAG (g
->flags
, GF_BLACK_OPENING
);
432 PGN_DUMP ("%s:%d: updating board\n", __FILE__
, __LINE__
);
435 if (!g
->ravlevel
&& TEST_FLAG (g
->flags
, GF_BLACK_OPENING
))
441 SET_FLAG (g
->flags
, GF_WK_CASTLE
| GF_WQ_CASTLE
| GF_WQ_CASTLE
|
442 GF_BK_CASTLE
| GF_BQ_CASTLE
);
446 pgn_board_init_fen (g
, tb
, g
->rav
[g
->ravlevel
- 1].fen
);
447 else if (pgn_tag_find (g
->tag
, "FEN") != -1 &&
448 pgn_board_init_fen (g
, tb
, NULL
))
453 HISTORY
*h
= pgn_history_by_n (g
->hp
, n
- 1);
457 ret
= pgn_board_init_fen (g
, tb
, h
->fen
);
460 h
= pgn_history_by_n (g
->hp
, n
);
465 ret
= pgn_parse_move (g
, tb
, &h
->move
, &frfr
);
468 h
= pgn_history_by_n (g
->hp
, n
- 1);
469 ret
= pgn_board_init_fen (g
, tb
, h
->fen
);
479 memcpy (b
, tb
, sizeof (BOARD
));
482 SET_FLAG (g
->flags
, GF_PERROR
);
485 SET_FLAG (g
->flags
, GF_BLACK_OPENING
);
491 * Updates the game 'g' using board 'b' to the next 'n'th history move.
495 pgn_history_prev (GAME g
, BOARD b
, int n
)
497 if (g
->hindex
- n
< 0)
500 g
->hindex
= pgn_history_total (g
->hp
);
507 pgn_board_update (g
, b
, g
->hindex
);
511 * Updates the game 'g' using board 'b' to the previous 'n'th history move.
515 pgn_history_next (GAME g
, BOARD b
, int n
)
517 if (g
->hindex
+ n
> pgn_history_total (g
->hp
))
522 g
->hindex
= pgn_history_total (g
->hp
);
527 pgn_board_update (g
, b
, g
->hindex
);
531 * Converts the character piece 'p' to an integer. Returns the integer
532 * associated with 'p' or E_PGN_ERR if 'p' is invalid.
535 pgn_piece_to_int (int p
)
561 PGN_DUMP ("%s:%d: invalid piece '%c'\n", __FILE__
, __LINE__
, p
);
567 * Converts the integer piece 'n' to a character whose turn is 'turn'. WHITE
568 * piece are uppercase and BLACK pieces are lowercase. Returns the character
569 * associated with 'n' or E_PGN_ERR if 'n' is an invalid piece.
572 pgn_int_to_piece (char turn
, int n
)
601 PGN_DUMP ("%s:%d: unknown piece integer %i\n", __FILE__
, __LINE__
, n
);
607 return (turn
== WHITE
) ? toupper (p
) : p
;
611 * Finds a tag 'name' in the structure array 't'. Returns the location in the
612 * array of the found tag or E_PGN_ERR if the tag could not be found.
615 pgn_tag_find (TAG
** t
, const char *name
)
619 for (i
= 0; t
[i
]; i
++)
621 if (strcasecmp (t
[i
]->name
, name
) == 0)
629 remove_tag (TAG
*** array
, const char *tag
)
632 int n
= pgn_tag_find (tags
, tag
);
638 for (i
= t
= 0; tags
[i
]; i
++)
642 free (tags
[i
]->name
);
643 free (tags
[i
]->value
);
651 tags
= realloc (*array
, (t
+ 1) * sizeof (TAG
*));
655 PGN_DUMP ("%s:%d: removed tag: name='%s'\n", __FILE__
, __LINE__
, tag
);
661 tag_compare (const void *a
, const void *b
)
666 return strcmp ((*ta
)->name
, (*tb
)->name
);
670 * Sorts a tag array. The first seven tags are in order of the PGN standard so
671 * don't sort'em. Returns nothing.
674 pgn_tag_sort (TAG
** tags
)
676 if (pgn_tag_total (tags
) <= 7)
679 qsort (tags
+ 7, pgn_tag_total (tags
) - 7, sizeof (TAG
*), tag_compare
);
683 * Returns the total number of tags in 't' or 0 if 't' is NULL.
686 pgn_tag_total (TAG
** tags
)
700 * Adds a tag 'name' with value 'value' to the pointer to array of TAG
701 * pointers 'dst'. If a duplicate tag 'name' was found then the existing tag
702 * is updated to the new 'value'. If 'value' is NULL, the tag is removed.
703 * Returns E_PGN_ERR if there was a memory allocation error or E_PGN_OK on
707 pgn_tag_add (TAG
*** dst
, char *name
, char *value
)
712 int t
= pgn_tag_total (tdata
);
716 PGN_DUMP ("%s:%d: adding tag\n", __FILE__
, __LINE__
);
725 value
= trim (value
);
727 // Find an existing tag with 'name'.
728 for (i
= 0; i
< t
; i
++)
732 if (strcasecmp (tdata
[i
]->name
, name
) == 0)
736 if ((tmp
= strdup (value
)) == NULL
)
741 remove_tag (dst
, name
);
745 free (tdata
[i
]->value
);
746 tdata
[i
]->value
= tmp
;
752 newtag
= calloc (1, sizeof (TAG
));
756 if ((newtag
->name
= strdup (name
)) == NULL
)
764 if ((newtag
->value
= strdup (value
)) == NULL
)
772 newtag
->value
= NULL
;
774 if ((a
= realloc (tdata
, (t
+ 2) * sizeof (TAG
*))) == NULL
)
777 free (newtag
->value
);
784 tdata
[t
]->name
[0] = toupper (tdata
[t
]->name
[0]);
789 PGN_DUMP ("%s:%d: added tag: name='%s' value='%s'\n", __FILE__
, __LINE__
,
790 name
, (value
) ? value
: "null");
797 remove_tag_escapes (const char *str
)
800 int len
= strlen (str
);
801 char buf
[MAX_PGN_LINE_LEN
] = { 0 };
803 for (i
= n
= 0; i
< len
; i
++, n
++)
817 return buf
[0] ? strdup (buf
) : NULL
;
821 * Resets or initializes a new game board 'b'. Returns nothing.
824 pgn_board_init (BOARD b
)
829 PGN_DUMP ("%s:%d: initializing board\n", __FILE__
, __LINE__
);
832 memset (b
, 0, sizeof (BOARD
));
834 for (row
= 0; row
< 8; row
++)
836 for (col
= 0; col
< 8; col
++)
872 b
[row
][col
].icon
= (row
< 2) ? c
: toupper (c
);
878 * Adds the standard PGN roster tags to game 'g'.
881 set_default_tags (GAME g
)
884 char tbuf
[11] = { 0 };
886 struct passwd
*pw
= getpwuid (getuid ());
887 char host
[64] = { 0 };
890 tp
= localtime (&now
);
891 strftime (tbuf
, sizeof (tbuf
), PGN_TIME_FORMAT
, tp
);
893 /* The standard seven tag roster (in order of appearance). */
894 if (pgn_tag_add (&g
->tag
, (char *) "Event", (char *) "?") != E_PGN_OK
)
895 warn ("pgn_tag_add()");
897 gethostname (host
, sizeof (host
) - 1);
898 if (pgn_tag_add (&g
->tag
, (char *) "Site", host
) != E_PGN_OK
)
899 warn ("pgn_tag_add()");
901 if (pgn_tag_add (&g
->tag
, (char *) "Date", tbuf
) != E_PGN_OK
)
902 warn ("pgn_tag_add()");
904 if (pgn_tag_add (&g
->tag
, (char *) "Round", (char *) "-") != E_PGN_OK
)
905 warn ("pgn_tag_add()");
907 if (pgn_tag_add (&g
->tag
, (char *) "White", pw
->pw_gecos
) != E_PGN_OK
)
908 warn ("pgn_tag_add()");
910 if (pgn_tag_add (&g
->tag
, (char *) "Black", (char *) "?") != E_PGN_OK
)
911 warn ("pgn_tag_add()");
913 if (pgn_tag_add (&g
->tag
, (char *) "Result", (char *) "*") != E_PGN_OK
)
914 warn ("pgn_tag_add()");
918 * Frees a TAG array. Returns nothing.
921 pgn_tag_free (TAG
** tags
)
924 int t
= pgn_tag_total (tags
);
927 PGN_DUMP ("%s:%d: freeing tags\n", __FILE__
, __LINE__
);
933 for (i
= 0; i
< t
; i
++)
935 free (tags
[i
]->name
);
936 free (tags
[i
]->value
);
944 * Frees a single game 'g'. Returns nothing.
950 PGN_DUMP ("%s:%d: freeing game\n", __FILE__
, __LINE__
);
952 pgn_history_free (g
->history
, 0);
954 pgn_tag_free (g
->tag
);
958 for (g
->ravlevel
--; g
->ravlevel
>= 0; g
->ravlevel
--)
959 free (g
->rav
[g
->ravlevel
].fen
);
968 * Frees all games in the global 'game' array. Returns nothing.
976 PGN_DUMP ("%s:%d: freeing game data\n", __FILE__
, __LINE__
);
979 for (i
= 0; i
< gtotal
; i
++)
994 PGN_DUMP ("%s:%d: resetting game data\n", __FILE__
, __LINE__
);
1001 skip_leading_space (FILE * fp
)
1005 while ((c
= Fgetc (fp
)) != EOF
&& !feof (fp
))
1015 * PGN move text section.
1018 move_text (GAME g
, FILE * fp
)
1020 char m
[MAX_SAN_MOVE_LEN
+ 1] = { 0 }, *p
;
1025 g
->oflags
= g
->flags
;
1027 while ((c
= Fgetc (fp
)) != EOF
)
1029 if (isdigit (c
) || isspace (c
) || c
== '.')
1037 if (fscanf (fp
, " %[a-hPpRrNnBbQqKk1-9#+=Ox-]%n", m
, &count
) != 1)
1040 m
[MAX_SAN_MOVE_LEN
] = 0;
1043 if (pgn_parse_move (g
, pgn_board
, &p
, &frfr
))
1045 pgn_switch_turn (g
);
1051 PGN_DUMP ("%s\n%s", p
, debug_board (pgn_board
));
1054 pgn_history_add (g
, pgn_board
, p
);
1055 pgn_switch_turn (g
);
1063 nag_text (GAME g
, FILE * fp
)
1066 char nags
[5], *n
= nags
;
1069 while ((c
= Fgetc (fp
)) != EOF
&& !isspace (c
))
1073 while ((c
= Fgetc (fp
)) != EOF
&& isdigit (c
))
1082 if ((c
= Fgetc (fp
)) == '!')
1096 if ((c
= Fgetc (fp
)) == '?')
1112 if ((c
= Fgetc (fp
)) == '+')
1124 if ((t
= Fgetc (fp
)) == '=')
1130 if ((i
= Fgetc (fp
)) == '-')
1144 if ((t
= Fgetc (fp
)) == '+')
1148 if ((i
= Fgetc (fp
)) == '+')
1165 nag
= (nags
[0]) ? atoi (nags
) : 0;
1167 if (!nag
|| nag
< 0 || nag
> 255)
1170 // FIXME -1 is because move_text() increments g->hindex. The NAG
1171 // annoatation isnt guaranteed to be after the move text in import format.
1172 for (i
= 0; i
< MAX_PGN_NAG
; i
++)
1174 if (g
->hp
[g
->hindex
- 1]->nag
[i
])
1177 g
->hp
[g
->hindex
- 1]->nag
[i
] = nag
;
1181 skip_leading_space (fp
);
1185 * PGN move annotation.
1188 annotation_text (GAME g
, FILE * fp
, int terminator
)
1190 int c
, lastchar
= 0;
1192 int hindex
= pgn_history_total (g
->hp
) - 1;
1193 char buf
[MAX_PGN_LINE_LEN
], *a
= buf
;
1195 skip_leading_space (fp
);
1197 while ((c
= Fgetc (fp
)) != EOF
&& c
!= terminator
)
1202 if (isspace (c
) && isspace (lastchar
))
1205 if (len
+ 1 == sizeof (buf
))
1208 *a
++ = lastchar
= c
;
1218 * This annotation is before any move text or NAg-> Allocate a new move.
1222 if ((g
->hp
[hindex
] = calloc (1, sizeof (HISTORY
))) == NULL
)
1226 if ((g
->hp
[hindex
]->comment
= strdup (buf
)) == NULL
)
1236 tag_text (GAME g
, FILE * fp
)
1238 char name
[LINE_MAX
] = { 0 }, *n
= name
;
1239 char value
[LINE_MAX
] = { 0 }, *v
= value
;
1244 skip_leading_space (fp
);
1246 /* The tag name is up until the first whitespace. */
1247 while ((c
= Fgetc (fp
)) != EOF
&& !isspace (c
))
1251 *name
= toupper (*name
);
1252 skip_leading_space (fp
);
1254 /* The value is until the first closing bracket. */
1255 while ((c
= Fgetc (fp
)) != EOF
&& c
!= ']')
1257 if (i
++ == '\0' && c
== '\"')
1260 if (c
== '\n' || c
== '\t')
1263 if (c
== ' ' && lastchar
== ' ')
1266 lastchar
= *v
++ = c
;
1271 while (isspace (*--v
))
1277 if (value
[0] == '\0')
1279 if (strcmp (name
, "Result") == 0)
1287 tmp
= remove_tag_escapes (value
);
1288 strncpy (value
, tmp
, sizeof (value
) - 1);
1292 * See eog_text() for an explanation.
1294 if (strcmp (name
, "Result") == 0)
1296 if (strcmp (value
, "1/2-1/2") == 0)
1298 if (pgn_tag_add (&g
->tag
, name
, value
) != E_PGN_OK
)
1299 warn ("pgn_tag_add()");
1304 if (pgn_tag_add (&g
->tag
, name
, value
) != E_PGN_OK
)
1305 warn ("pgn_tag_add()");
1312 * PGN end-of-game marker.
1315 eog_text (GAME g
, FILE * fp
)
1318 char buf
[8], *p
= buf
;
1320 while ((c
= Fgetc (fp
)) != EOF
&& !isspace (c
) && i
++ < sizeof (buf
))
1328 if (strcmp (buf
, "1-0") != 0 && strcmp (buf
, "0-1") != 0 &&
1329 strcmp (buf
, "1/2-1/2") != 0 && strcmp (buf
, "*") != 0)
1333 * The eog marker in the move text may not match the actual game result.
1334 * We'll leave it up to the move validator to determine the result unless
1335 * the move validator cannot determine who won and the move text says it's
1338 if (g
->tag
[6]->value
[0] == '*' && strcmp ("1/2-1/2", buf
) == 0)
1340 if (pgn_tag_add (&g
->tag
, (char *) "Result", buf
) != E_PGN_OK
)
1341 warn ("pgn_tag_add()");
1348 * Parse RAV text and keep track of g->hp. The 'o' argument is the board state
1349 * before the current move (.hindex) was parsed.
1351 static int read_file (FILE *);
1353 rav_text (GAME g
, FILE * fp
, int which
, BOARD o
)
1358 // Begin RAV for the current move.
1364 pgn_rav
++; // For detecting parse errors.
1367 * Save the current game state for this RAV depth/level.
1369 if ((r
= realloc (g
->rav
, (g
->ravlevel
+ 1) * sizeof (RAV
))) == NULL
)
1375 g
->rav
= pgn_rav_p
= r
;
1377 if ((g
->rav
[g
->ravlevel
].fen
= pgn_game_to_fen (g
, pgn_board
)) == NULL
)
1383 g
->rav
[g
->ravlevel
].hp
= g
->hp
;
1384 g
->rav
[g
->ravlevel
].hindex
= g
->hindex
;
1385 memcpy (&tg
, &(*g
), sizeof (struct game_s
));
1386 g
->flags
= g
->oflags
;
1387 memcpy (pgn_board
, o
, sizeof (BOARD
));
1389 if ((g
->hp
[g
->hindex
- 1]->rav
=
1390 calloc (1, sizeof (HISTORY
*))) == NULL
)
1397 * pgn_history_add() will now append to the new history pointer that
1398 * is g->hp[previous_move]->rav.
1400 g
->hp
= g
->hp
[g
->hindex
- 1]->rav
;
1403 * Reset. Will be restored later from 'tg' which is a local variable
1404 * so recursion is possible.
1410 * Undo move_text()'s switch.
1412 pgn_switch_turn (g
);
1415 * Now continue as normal as if there were no RAV. Moves will be
1416 * parsed and appended to the new history pointer.
1422 * read_file() has returned. This means that a RAV has ended by this
1423 * function returning -1 (see below). So we restore the game state
1424 * (tg) that was saved before calling read_file().
1426 pgn_board_init_fen (&tg
, pgn_board
, g
->rav
[tg
.ravlevel
].fen
);
1427 free (g
->rav
[tg
.ravlevel
].fen
);
1428 memcpy (&(*g
), &tg
, sizeof (struct game_s
));
1432 * The end of a RAV. This makes the read_file() that called this function
1433 * return (see above and the switch statement in read_file()).
1435 else if (which
== ')')
1437 pgn_rav
--; // For detecting parse errors.
1438 return (pgn_rav
< 0) ? 1 : -1;
1446 * See pgn_board_init_fen(). Returns E_PGN_PARSE on parse error. 0 may be
1447 * returned on success when there is no move count in the FEN tag otherwise
1448 * the move count is returned.
1451 parse_fen_line (BOARD b
, unsigned *flags
, char *turn
, char *ply
, char *str
)
1454 char line
[LINE_MAX
] = { 0 }, *s
;
1455 int row
= 8, col
= 1;
1458 PGN_DUMP ("%s:%d: FEN line is '%s'\n", __FILE__
, __LINE__
, str
);
1460 strncpy (line
, str
, sizeof (line
) - 1);
1462 pgn_reset_enpassant (b
);
1464 while ((tmp
= strsep (&s
, "/")) != NULL
)
1468 if (!VALIDFILE (row
))
1483 for (; n
; --n
, col
++)
1484 b
[RANKTOBOARD (row
)][FILETOBOARD (col
)].icon
=
1485 pgn_int_to_piece (WHITE
, OPEN_SQUARE
);
1487 else if (pgn_piece_to_int (*tmp
) != -1)
1488 b
[RANKTOBOARD (row
)][FILETOBOARD (col
++)].icon
= *tmp
;
1516 while (*tmp
&& *tmp
!= ' ')
1521 SET_FLAG (*flags
, GF_WK_CASTLE
);
1524 SET_FLAG (*flags
, GF_WQ_CASTLE
);
1527 SET_FLAG (*flags
, GF_BK_CASTLE
);
1530 SET_FLAG (*flags
, GF_BQ_CASTLE
);
1544 if (!VALIDCOL (*tmp
))
1548 if (!VALIDROW (*tmp
))
1550 row
= 8 - atoi (tmp
++);
1552 b
[row
][col
].enpassant
= 1;
1553 SET_FLAG (*flags
, GF_ENPASSANT
);
1564 while (*tmp
&& isdigit (*tmp
))
1574 * It initializes the board (b) to the FEN tag (if found) and sets the
1575 * castling and enpassant info for the game 'g'. If 'fen' is set it should be
1576 * a fen tag and will be parsed rather than the game 'g'.tag FEN tag-> Returns
1577 * E_PGN_OK on success or if there was both a FEN and SetUp tag with the SetUp
1578 * tag set to 0. Returns E_PGN_PARSE if there was a FEN parse error, E_PGN_ERR
1579 * if there was no FEN tag or there was a SetUp tag with a value of 0. Returns
1580 * E_PGN_OK on success.
1583 pgn_board_init_fen (GAME g
, BOARD b
, char *fen
)
1588 char turn
= g
->turn
;
1592 PGN_DUMP ("%s:%d: initializing board from FEN\n", __FILE__
, __LINE__
);
1594 pgn_board_init (tmpboard
);
1598 n
= pgn_tag_find (g
->tag
, "Setup");
1599 i
= pgn_tag_find (g
->tag
, "FEN");
1603 * If the FEN tag exists and there is no SetUp tag go ahead and parse it.
1604 * If there is a SetUp tag only parse the FEN tag if the value is 1.
1606 if ((n
>= 0 && i
>= 0 && atoi (g
->tag
[n
]->value
) == 1)
1607 || (i
>= 0 && n
== -1) || fen
)
1609 if ((n
= parse_fen_line (tmpboard
, &flags
, &turn
, &ply
,
1610 (fen
) ? fen
: g
->tag
[i
]->value
)) != E_PGN_OK
)
1614 memcpy (b
, tmpboard
, sizeof (BOARD
));
1615 CLEAR_FLAG (g
->flags
, GF_WK_CASTLE
);
1616 CLEAR_FLAG (g
->flags
, GF_WQ_CASTLE
);
1617 CLEAR_FLAG (g
->flags
, GF_BK_CASTLE
);
1618 CLEAR_FLAG (g
->flags
, GF_BQ_CASTLE
);
1625 return (i
>= 0 && n
>= 0) ? E_PGN_OK
: E_PGN_ERR
;
1631 * Allocates a new game and increments 'gtotal'. 'gindex' is then set to the
1632 * new game. Returns E_PGN_ERR if there was a memory allocation error or
1633 * E_PGN_OK on success.
1643 PGN_DUMP ("%s:%d: allocating new game\n", __FILE__
, __LINE__
);
1647 if ((g
= realloc (game
, t
* sizeof (GAME
))) == NULL
)
1655 if ((newg
= calloc (1, sizeof (struct game_s
))) == NULL
)
1661 game
[gindex
] = newg
;
1663 if ((game
[gindex
]->hp
= calloc (1, sizeof (HISTORY
*))) == NULL
)
1665 free (game
[gindex
]);
1670 game
[gindex
]->hp
[0] = NULL
;
1671 game
[gindex
]->history
= game
[gindex
]->hp
;
1672 game
[gindex
]->side
= game
[gindex
]->turn
= WHITE
;
1673 SET_FLAG (game
[gindex
]->flags
, GF_WK_CASTLE
| GF_WQ_CASTLE
| GF_WQ_CASTLE
|
1674 GF_BK_CASTLE
| GF_BQ_CASTLE
);
1675 pgn_board_init (pgn_board
);
1676 set_default_tags (game
[gindex
]);
1682 read_file (FILE * fp
)
1685 char buf
[LINE_MAX
] = { 0 }, *p
= buf
;
1688 int parse_error
= 0;
1698 * A parse error may have occured at EOF.
1702 pgn_ret
= E_PGN_PARSE
;
1707 SET_FLAG (game
[gindex
]->flags
, GF_PERROR
);
1710 if ((c
= Fgetc (fp
)) == EOF
)
1731 nextchar
= Fgetc (fp
);
1732 Ungetc (nextchar
, fp
);
1735 * If there was a move text parsing error, keep reading until the end
1736 * of the current game discarding the data.
1740 pgn_ret
= E_PGN_PARSE
;
1742 if (game
[gindex
]->ravlevel
)
1745 if (pgn_config
.stop
)
1748 if (c
== '\n' && (nextchar
== '\n' || nextchar
== '\015'))
1758 // New game reached.
1759 if (c
== '\n' && (nextchar
== '\n' || nextchar
== '\015'))
1770 * PGN: Application comment. The '%' must be on the first column of
1771 * the line. The comment continues until the end of the current line.
1775 if (lastchar
== '\n' || lastchar
== 0)
1777 while ((c
= Fgetc (fp
)) != EOF
&& c
!= '\n');
1781 // Not sure what to do here.
1788 if (c
== '<' || c
== '>')
1792 * PGN: Recurrsive Annotated Variation. Read rav_text() for more
1795 if (c
== '(' || c
== ')')
1797 switch (rav_text (game
[gindex
], fp
, c
, old
))
1801 * This is the end of the current RAV. This function has
1802 * been called from rav_text(). Returning from this point
1803 * will put us back in rav_text().
1805 if (game
[gindex
]->ravlevel
> 0)
1809 * We're back at the root move. Continue as normal.
1817 * Continue processing-> Probably the root move.
1822 if (!game
[gindex
]->ravlevel
&& pgn_rav
)
1828 // PGN: Numeric Annotation Glyph.
1829 if (c
== '$' || c
== '!' || c
== '?' || c
== '+' || c
== '-' ||
1830 c
== '~' || c
== '=')
1833 nag_text (game
[gindex
], fp
);
1838 * PGN: Annotation. The ';' comment continues until the end of the
1839 * current line. The '{' type comment continues until a '}' is
1842 if (c
== '{' || c
== ';')
1844 annotation_text (game
[gindex
], fp
, (c
== '{') ? '}' : '\n');
1848 // PGN: Roster tag->
1851 // First roster tag found. Initialize the data structures.
1857 if (gtotal
&& pgn_history_total (game
[gindex
]->hp
))
1858 game
[gindex
]->hindex
=
1859 pgn_history_total (game
[gindex
]->hp
) - 1;
1861 if (pgn_new_game () != E_PGN_OK
)
1863 pgn_ret
= E_PGN_ERR
;
1867 memcpy (old
, pgn_board
, sizeof (BOARD
));
1870 if (tag_text (game
[gindex
], fp
))
1871 parse_error
= 1; // FEN tag parse error.
1876 // PGN: End-of-game markers.
1877 if ((isdigit (c
) && (nextchar
== '-' || nextchar
== '/')) || c
== '*')
1881 if (eog_text (game
[gindex
], fp
))
1890 if (!game
[gindex
]->done_fen_tag
)
1892 if (pgn_tag_find (game
[gindex
]->tag
, "FEN") != -1 &&
1893 pgn_board_init_fen (game
[gindex
], pgn_board
, NULL
))
1899 game
[gindex
]->pgn_fen_tag
=
1900 pgn_tag_find (game
[gindex
]->tag
, "FEN");
1901 game
[gindex
]->done_fen_tag
= 1;
1908 if ((isdigit (c
) && c
!= '0') || VALIDCOL (c
) || c
== 'N' || c
== 'K'
1909 || c
== 'Q' || c
== 'B' || c
== 'R' || c
== 'P' || c
== 'O')
1913 // PGN: If a FEN tag exists, initialize the board to the value.
1916 if (pgn_tag_find (game
[gindex
]->tag
, "FEN") != E_PGN_ERR
&&
1917 (n
= pgn_board_init_fen (game
[gindex
], pgn_board
,
1918 NULL
)) == E_PGN_PARSE
)
1924 game
[gindex
]->done_fen_tag
= 1;
1925 game
[gindex
]->pgn_fen_tag
=
1926 pgn_tag_find (game
[gindex
]->tag
, "FEN");
1931 * PGN: Import format doesn't require a roster tag section. We've
1932 * arrived to the move text section without any tags so we
1933 * initialize a new game which set's the default tags and any tags
1934 * from the configuration file.
1939 game
[gindex
]->hindex
=
1940 pgn_history_total (game
[gindex
]->hp
) - 1;
1942 if (pgn_new_game () != E_PGN_OK
)
1944 pgn_ret
= E_PGN_ERR
;
1948 memcpy (old
, pgn_board
, sizeof (BOARD
));
1952 memcpy (old
, pgn_board
, sizeof (BOARD
));
1954 if (move_text (game
[gindex
], fp
))
1956 if (pgn_tag_add (&game
[gindex
]->tag
, (char *) "Result",
1957 (char *) "*") == E_PGN_ERR
)
1959 warn ("pgn_tag_add()");
1960 pgn_ret
= E_PGN_ERR
;
1963 SET_FLAG (game
[gindex
]->flags
, GF_PERROR
);
1973 PGN_DUMP ("%s:%d: unparsed: '%s'\n", __FILE__
, __LINE__
, buf
);
1975 if (strlen (buf
) + 1 == sizeof (buf
))
1976 bzero (buf
, sizeof (buf
));
1986 * Parses a PGN_FILE which was opened with pgn_open(). If 'pgn' is NULL then a
1987 * single empty game will be allocated. If there is a parsing error
1988 * E_PGN_PARSE is returned, if there was a memory allocation error E_PGN_ERR
1989 * is returned, otherwise E_PGN_OK is returned and the global 'gindex' is set
1990 * to the last parsed game in the file and the global 'gtotal' is set to the
1991 * total number of games in the file. The file should be closed with
1992 * pgn_close() after processing.
1995 pgn_parse (PGN_FILE
* pgn
)
2002 pgn_ret
= pgn_new_game ();
2008 fseek (pgn
->fp
, 0, SEEK_END
);
2009 pgn_fsize
= ftell (pgn
->fp
);
2010 fseek (pgn
->fp
, 0, SEEK_SET
);
2012 PGN_DUMP ("%s:%d: BEGIN parsing->..\n", __FILE__
, __LINE__
);
2015 pgn_ret
= read_file (pgn
->fp
);
2019 PGN_DUMP ("%s:%d: END parsing->..\n", __FILE__
, __LINE__
);
2026 gtotal
= gindex
+ 1;
2028 for (i
= 0; i
< gtotal
; i
++)
2030 game
[i
]->history
= game
[i
]->hp
;
2031 game
[i
]->hindex
= pgn_history_total (game
[i
]->hp
);
2038 * Escape '"' and '\' in tag values.
2041 pgn_tag_add_escapes (const char *str
)
2044 int len
= strlen (str
);
2045 char buf
[MAX_PGN_LINE_LEN
] = { 0 };
2047 for (i
= n
= 0; i
< len
; i
++, n
++)
2063 return buf
[0] ? strdup (buf
) : NULL
;
2067 Fputc (int c
, FILE * fp
, int *len
)
2071 if (c
!= '\n' && i
+ 1 > 80)
2072 Fputc ('\n', fp
, &i
);
2074 if (pgn_lastc
== '\n' && c
== ' ')
2080 if (fputc (c
, fp
) == EOF
)
2095 putstring (FILE * fp
, char *str
, int *len
)
2099 for (p
= str
; *p
; p
++)
2103 while (*p
&& *p
!= ' ')
2107 Fputc ('\n', fp
, len
);
2110 Fputc (*p
, fp
, len
);
2115 * See pgn_write() for more info.
2118 write_comments_and_nag (FILE * fp
, HISTORY
* h
, int *len
)
2124 PGN_DUMP ("%s:%d: writing comments and nag\n", __FILE__
, __LINE__
);
2127 for (i
= 0; i
< MAX_PGN_NAG
; i
++)
2131 Fputc (' ', fp
, len
);
2132 Fputc ('$', fp
, len
);
2133 putstring (fp
, itoa (h
->nag
[i
], tmp
), len
);
2139 Fputc ('\n', fp
, len
);
2140 putstring (fp
, (char *) " {", len
);
2141 putstring (fp
, h
->comment
, len
);
2142 Fputc ('}', fp
, len
);
2147 write_move_text (FILE * fp
, HISTORY
* h
, int *len
)
2149 Fputc (' ', fp
, len
);
2150 putstring (fp
, h
->move
, len
);
2152 if (!pgn_config
.reduced
)
2153 write_comments_and_nag (fp
, h
, len
);
2157 write_all_move_text (FILE * fp
, HISTORY
** h
, int m
, int *len
)
2160 HISTORY
**hp
= NULL
;
2163 for (i
= 0; h
[i
]; i
++)
2165 if (pgn_write_turn
== WHITE
)
2167 if (pgn_config
.mpl
&& pgn_mpl
== pgn_config
.mpl
)
2170 Fputc ('\n', fp
, len
);
2174 Fputc (' ', fp
, len
);
2176 if (strlen (itoa (m
, tmp
)) + 1 + *len
> 80)
2177 Fputc ('\n', fp
, len
);
2179 putstring (fp
, itoa (m
, tmp
), len
);
2180 Fputc ('.', fp
, len
);
2184 write_move_text (fp
, h
[i
], len
);
2186 if (!pgn_config
.reduced
&& h
[i
]->rav
)
2189 int oldturn
= pgn_write_turn
;
2192 putstring (fp
, (char *) " (", len
);
2195 * If it's WHITE's turn the move number will be added above after
2196 * the call to write_all_move_text() below.
2198 if (pgn_write_turn
== BLACK
)
2200 putstring (fp
, itoa (m
, tmp
), len
);
2201 putstring (fp
, (char *) "...", len
);
2205 write_all_move_text (fp
, hp
, m
, len
);
2207 pgn_write_turn
= oldturn
;
2208 putstring (fp
, (char *) ")", len
);
2211 if (ravlevel
&& h
[i
+ 1])
2212 Fputc (' ', fp
, len
);
2214 if (h
[i
+ 1] && !ravlevel
)
2215 Fputc (' ', fp
, len
);
2217 if (pgn_write_turn
== WHITE
&& h
[i
+ 1])
2219 putstring (fp
, itoa (m
, tmp
), len
);
2220 putstring (fp
, (char *) "...", len
);
2224 if (pgn_write_turn
== BLACK
)
2227 pgn_write_turn
= (pgn_write_turn
== WHITE
) ? BLACK
: WHITE
;
2232 compression_cmd (const char *filename
, int expand
)
2234 char command
[PATH_MAX
];
2235 int len
= strlen (filename
);
2237 if (filename
[len
- 4] == '.' && filename
[len
- 3] == 'z' &&
2238 filename
[len
- 2] == 'i' && filename
[len
- 1] == 'p' &&
2239 filename
[len
] == '\0')
2242 snprintf (command
, sizeof (command
), "unzip -p %s 2>/dev/null",
2245 snprintf (command
, sizeof (command
), "zip -9 >%s 2>/dev/null",
2248 return strdup (command
);
2250 else if (filename
[len
- 3] == '.' && filename
[len
- 2] == 'g' &&
2251 filename
[len
- 1] == 'z' && filename
[len
] == '\0')
2254 snprintf (command
, sizeof (command
), "gzip -dc %s", filename
);
2256 snprintf (command
, sizeof (command
), "gzip -c 1>%s", filename
);
2258 return strdup (command
);
2260 else if (filename
[len
- 2] == '.' && filename
[len
- 1] == 'Z' &&
2261 filename
[len
] == '\0')
2264 snprintf (command
, sizeof (command
), "uncompress -c %s", filename
);
2266 snprintf (command
, sizeof (command
), "compress -c 1>%s", filename
);
2268 return strdup (command
);
2270 else if ((filename
[len
- 4] == '.' && filename
[len
- 3] == 'b' &&
2271 filename
[len
- 2] == 'z' && filename
[len
- 1] == '2' &&
2272 filename
[len
] == '\0') || (filename
[len
- 3] == '.' &&
2273 filename
[len
- 2] == 'b'
2274 && filename
[len
- 1] == 'z'
2275 && filename
[len
] == '\0'))
2278 snprintf (command
, sizeof (command
), "bzip2 -dc %s", filename
);
2280 snprintf (command
, sizeof (command
), "bzip2 -zc 1>%s", filename
);
2282 return strdup (command
);
2289 copy_file (FILE * fp
, const char *dst
)
2292 char line
[LINE_MAX
];
2293 char *cmd
= compression_cmd (dst
, 0);
2295 if ((ofp
= popen (cmd
, "w")) == NULL
)
2302 fseek (fp
, 0, SEEK_SET
);
2304 while ((fgets (line
, sizeof (line
), fp
)) != NULL
)
2305 fprintf (ofp
, "%s", line
);
2312 * Closes and free's a PGN file handle.
2315 pgn_close (PGN_FILE
* pgn
)
2318 return E_PGN_INVALID
;
2323 * Appending to a compressed file.
2327 if (copy_file (pgn
->fp
, pgn
->filename
))
2331 unlink (pgn
->tmpfile
);
2332 free (pgn
->tmpfile
);
2340 free (pgn
->filename
);
2346 * Opens a file 'filename' with the given 'mode'. 'mode' should be "r" for
2347 * reading, "w" for writing (will truncate if the file exists) or "a" for
2348 * appending to an existing file or creating a new one. Returns E_PGN_OK on
2349 * success and sets 'result' to a file handle for use will the other file
2350 * functions or E_PGN_ERR if there is an error opening the file in which case
2351 * errno will be set to the error or E_PGN_INVALID if 'mode' is an invalid
2352 * mode or if 'filename' is not a regular file.
2355 pgn_open (const char *filename
, const char *mode
, PGN_FILE
** result
)
2357 FILE *fp
= NULL
, *tfp
= NULL
, *pfp
= NULL
;
2358 char buf
[PATH_MAX
], *p
;
2364 int ret
= E_PGN_ERR
;
2368 PGN_DUMP ("%s:%d: BEGIN opening %s\n", __FILE__
, __LINE__
, filename
);
2371 if (!filename
|| !mode
)
2372 return E_PGN_INVALID
;
2374 if (strcmp (mode
, "r") == 0)
2376 else if (strcmp (mode
, "w") == 0)
2378 else if (strcmp (mode
, "a") == 0)
2385 return E_PGN_INVALID
;
2388 pgn
= calloc (1, sizeof (PGN_FILE
));
2393 if (strcmp (filename
, "-") != 0)
2395 if (m
&& access (filename
, R_OK
) == -1)
2398 if (stat (filename
, &st
) == -1 && !m
&& errno
!= ENOENT
)
2401 if (m
&& !S_ISREG (st
.st_mode
))
2403 ret
= E_PGN_INVALID
;
2407 if ((cmd
= compression_cmd (filename
, m
)) != NULL
)
2413 snprintf (tmp
, sizeof (tmp
), "cboard.XXXXXX");
2415 if (tmpnam (tmp
) == NULL
)
2421 if (append
&& access (filename
, R_OK
) == 0)
2428 cmd
= compression_cmd (filename
, 1);
2429 if ((pfp
= popen (cmd
, "r")) == NULL
)
2433 tmode
= umask (600);
2439 if ((fd
= open (tmp
, O_RDWR
| O_EXCL
| O_CREAT
, 0600)) == -1)
2442 if ((tfp
= fdopen (fd
, "a+")) == NULL
)
2445 while ((p
= fgets (buf
, sizeof (buf
), pfp
)) != NULL
)
2446 fprintf (tfp
, "%s", p
);
2451 pgn
->tmpfile
= strdup (tmp
);
2455 if ((pfp
= popen (cmd
, m
? "r" : "w")) == NULL
)
2461 mode_t tmode
= umask (600);
2469 open (tmp
, O_RDWR
| O_EXCL
| O_CREAT
| O_TRUNC
,
2473 if ((tfp
= fdopen (fd
, "w+")) == NULL
)
2476 while ((p
= fgets (buf
, sizeof (buf
), pfp
)) != NULL
)
2477 fprintf (tfp
, "%s", p
);
2482 pgn
->tmpfile
= strdup (tmp
);
2489 if ((fp
= fopen (filename
, mode
)) == NULL
)
2499 if (*filename
!= '/')
2501 if (getcwd (buf
, sizeof (buf
)) == NULL
)
2504 free (pgn
->tmpfile
);
2509 asprintf (&p
, "%s/%s", buf
, filename
);
2513 pgn
->filename
= strdup (filename
);
2532 * Returns E_PGN_OK if 'filename' is a recognized compressed filetype or
2536 pgn_is_compressed (const char *filename
)
2538 char *s
= compression_cmd (filename
, 0);
2539 pgn_error_t e
= s
? E_PGN_OK
: E_PGN_ERR
;
2546 * Gets the value of config flag 'f'. The next argument should be a pointer of
2547 * the config type which is set to the value of 'f'. Returns E_PGN_ERR if 'f'
2548 * is an invalid flag or E_PGN_OK on success.
2551 pgn_config_get (pgn_config_flag f
, ...)
2556 pgn_progress
*progress
;
2562 case PGN_STRICT_CASTLING
:
2563 intval
= va_arg (ap
, int *);
2564 *intval
= pgn_config
.strict_castling
;
2568 intval
= va_arg (ap
, int *);
2569 *intval
= pgn_config
.reduced
;
2573 intval
= va_arg (ap
, int *);
2574 *intval
= pgn_config
.mpl
;
2577 case PGN_STOP_ON_ERROR
:
2578 intval
= va_arg (ap
, int *);
2579 *intval
= pgn_config
.stop
;
2583 longval
= va_arg (ap
, long *);
2584 *longval
= pgn_config
.stop
;
2587 case PGN_PROGRESS_FUNC
:
2588 progress
= va_arg (ap
, pgn_progress
*);
2589 *progress
= pgn_config
.pfunc
;
2594 intval
= va_arg (ap
, int *);
2595 *intval
= dumptofile
;
2607 * Sets config flag 'f' to the next argument. Returns E_PGN_OK on success or
2608 * E_PGN_ERR if 'f' is an invalid flag or E_PGN_INVALID if 'val' is an invalid
2612 pgn_config_set (pgn_config_flag f
, ...)
2623 n
= va_arg (ap
, int);
2625 if (n
!= 1 && n
!= 0)
2627 ret
= E_PGN_INVALID
;
2631 pgn_config
.reduced
= n
;
2634 n
= va_arg (ap
, int);
2638 ret
= E_PGN_INVALID
;
2644 case PGN_STOP_ON_ERROR
:
2645 n
= va_arg (ap
, int);
2647 if (n
!= 1 && n
!= 0)
2649 ret
= E_PGN_INVALID
;
2653 pgn_config
.stop
= n
;
2656 n
= va_arg (ap
, long);
2657 pgn_config
.progress
= n
;
2659 case PGN_PROGRESS_FUNC
:
2660 pgn_config
.pfunc
= va_arg (ap
, pgn_progress
);
2662 case PGN_STRICT_CASTLING
:
2663 n
= va_arg (ap
, int);
2664 pgn_config
.strict_castling
= n
;
2668 n
= va_arg (ap
, int);
2669 dumptofile
= (n
> 0) ? 1 : 0;
2682 * Writes a PGN formatted game 'g' to a file which was opened with pgn_open().
2683 * See 'pgn_config_flag' for output options. Returns E_PGN_ERR if there was a
2684 * memory allocation or write error and E_PGN_OK on success. It is important
2685 * to use pgn_close() afterwards if the file is a recognized compressed file
2686 * type otherwise the created temporary file wont be copied to the destination
2690 pgn_write (PGN_FILE
* pgn
, GAME g
)
2698 pgn_write_turn
= (TEST_FLAG (g
->flags
, GF_BLACK_OPENING
)) ? BLACK
: WHITE
;
2699 pgn_tag_sort (g
->tag
);
2702 PGN_DUMP ("%s:%d: writing tag section\n", __FILE__
, __LINE__
);
2705 for (i
= 0; g
->tag
[i
]; i
++)
2708 char tbuf
[11] = { 0 };
2712 if (pgn_config
.reduced
&& i
== 7)
2715 if (strcmp (g
->tag
[i
]->name
, "Date") == 0)
2717 memset (&tp
, 0, sizeof (struct tm
));
2719 if (strptime (g
->tag
[i
]->value
, PGN_TIME_FORMAT
, &tp
) != NULL
)
2721 len
= strftime (tbuf
, sizeof (tbuf
), PGN_TIME_FORMAT
, &tp
) + 1;
2723 if ((tmp
= strdup (tbuf
)) == NULL
)
2726 free (g
->tag
[i
]->value
);
2727 g
->tag
[i
]->value
= tmp
;
2730 else if (strcmp (g
->tag
[i
]->name
, "Event") == 0)
2732 if (g
->tag
[i
]->value
[0] == '\0')
2734 if ((tmp
= strdup ("?")) == NULL
)
2737 free (g
->tag
[i
]->value
);
2738 g
->tag
[i
]->value
= tmp
;
2741 else if (strcmp (g
->tag
[i
]->name
, "Site") == 0)
2743 if (g
->tag
[i
]->value
[0] == '\0')
2745 if ((tmp
= strdup ("?")) == NULL
)
2748 free (g
->tag
[i
]->value
);
2749 g
->tag
[i
]->value
= tmp
;
2752 else if (strcmp (g
->tag
[i
]->name
, "Round") == 0)
2754 if (g
->tag
[i
]->value
[0] == '\0')
2756 if ((tmp
= strdup ("?")) == NULL
)
2759 free (g
->tag
[i
]->value
);
2760 g
->tag
[i
]->value
= tmp
;
2763 else if (strcmp (g
->tag
[i
]->name
, "Result") == 0)
2765 if (g
->tag
[i
]->value
[0] == '\0')
2767 if ((tmp
= strdup ("*")) == NULL
)
2770 free (g
->tag
[i
]->value
);
2771 g
->tag
[i
]->value
= tmp
;
2774 else if (strcmp (g
->tag
[i
]->name
, "Black") == 0)
2776 if (g
->tag
[i
]->value
[0] == '\0')
2778 if ((tmp
= strdup ("?")) == NULL
)
2781 free (g
->tag
[i
]->value
);
2782 g
->tag
[i
]->value
= tmp
;
2785 else if (strcmp (g
->tag
[i
]->name
, "White") == 0)
2787 if (g
->tag
[i
]->value
[0] == '\0')
2789 if ((tmp
= strdup ("?")) == NULL
)
2792 free (g
->tag
[i
]->value
);
2793 g
->tag
[i
]->value
= tmp
;
2797 escaped
= pgn_tag_add_escapes (g
->tag
[i
]->value
);
2798 fprintf (pgn
->fp
, "[%s \"%s\"]\n", g
->tag
[i
]->name
,
2799 (g
->tag
[i
]->value
&& g
->tag
[i
]->value
[0]) ? escaped
: "");
2804 PGN_DUMP ("%s:%d: writing move section\n", __FILE__
, __LINE__
);
2806 Fputc ('\n', pgn
->fp
, &len
);
2808 ravlevel
= pgn_mpl
= 0;
2810 if (pgn_history_total (g
->hp
) && pgn_write_turn
== BLACK
)
2811 putstring (pgn
->fp
, (char *) "1...", &len
);
2813 write_all_move_text (pgn
->fp
, g
->hp
, 1, &len
);
2815 Fputc (' ', pgn
->fp
, &len
);
2816 putstring (pgn
->fp
, g
->tag
[6]->value
, &len
);
2817 putstring (pgn
->fp
, (char *) "\n\n", &len
);
2819 if (!pgn_config
.reduced
)
2820 CLEAR_FLAG (g
->flags
, GF_PERROR
);
2826 * Clears the enpassant flag for all positions on board 'b'. Returns nothing.
2829 pgn_reset_enpassant (BOARD b
)
2834 PGN_DUMP ("%s:%d: resetting enpassant\n", __FILE__
, __LINE__
);
2837 for (r
= 0; r
< 8; r
++)
2839 for (c
= 0; c
< 8; c
++)
2840 b
[r
][c
].enpassant
= 0;