1 /* vim:tw=78:ts=8:sw=4:set ft=c: */
3 Copyright (C) 2002-2024 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
;
64 struct pgn_config_s pgn_config
;
68 extern char *strptime (const char *, const char *, struct tm
*);
76 if ((c
= fgetc (fp
)) != EOF
)
78 if (pgn_config
.progress
&& pgn_config
.pfunc
)
80 if (!(ftell (fp
) % pgn_config
.progress
))
81 pgn_config
.pfunc (pgn_fsize
, ftell (fp
));
89 Ungetc (int c
, FILE * fp
)
91 return ungetc (c
, fp
);
97 return "libchess " PACKAGE_VERSION
;
108 while (isspace (*str
))
111 for (i
= strlen (str
) - 1; isspace (str
[i
]); i
--)
118 itoa (long n
, char *buf
)
120 sprintf (buf
, "%li", n
);
125 * Clears the valid move flag for all positions on board 'b'. Returns nothing.
128 pgn_reset_valid_moves (BOARD b
)
133 PGN_DUMP ("%s:%d: resetting valid moves\n", __FILE__
, __LINE__
);
136 for (row
= 0; row
< 8; row
++)
138 for (col
= 0; col
< 8; col
++)
139 b
[row
][col
].valid
= 0;
144 * Toggles g->turn. Returns nothing.
147 pgn_switch_turn (GAME g
)
149 g
->turn
= (g
->turn
== WHITE
) ? BLACK
: WHITE
;
153 * Toggles g->side and switches the White and Black roster tags. Returns
157 pgn_switch_side (GAME g
, int t
)
161 char *w
= g
->tag
[4]->value
;
163 g
->tag
[4]->value
= g
->tag
[5]->value
;
164 g
->tag
[5]->value
= w
;
171 * Creates a FEN tag from the current game 'g', history move (g->hindex) and
172 * board 'b'. Returns a FEN tag.
175 pgn_game_to_fen (GAME g
, BOARD b
)
180 char buf
[MAX_PGN_LINE_LEN
] = { 0 }, *p
;
181 int oldturn
= g
->turn
;
182 char enpassant
[3] = { 0 }, *e
;
186 PGN_DUMP ("%s:%d: creating FEN tag\n", __FILE__
, __LINE__
);
189 for (i
= pgn_history_total (g
->hp
); i
>= g
->hindex
- 1; i
--)
194 for (row
= 0; row
< 8; row
++)
198 for (col
= 0; col
< 8; col
++)
200 if (b
[row
][col
].enpassant
)
202 b
[row
][col
].icon
= pgn_int_to_piece (WHITE
, OPEN_SQUARE
);
205 *e
++ = ('0' + 8) - row
;
209 if (pgn_piece_to_int (b
[row
][col
].icon
) == OPEN_SQUARE
)
221 *p
++ = b
[row
][col
].icon
;
236 *p
++ = (g
->turn
== WHITE
) ? 'w' : 'b';
239 if (TEST_FLAG (g
->flags
, GF_WK_CASTLE
) && pgn_piece_to_int (b
[7][7].icon
) ==
240 ROOK
&& isupper (b
[7][7].icon
) && pgn_piece_to_int (b
[7][4].icon
) ==
241 KING
&& isupper (b
[7][4].icon
))
247 if (TEST_FLAG (g
->flags
, GF_WQ_CASTLE
) && pgn_piece_to_int (b
[7][0].icon
) ==
248 ROOK
&& isupper (b
[7][0].icon
) && pgn_piece_to_int (b
[7][4].icon
) ==
249 KING
&& isupper (b
[7][4].icon
))
255 if (TEST_FLAG (g
->flags
, GF_BK_CASTLE
) && pgn_piece_to_int (b
[0][7].icon
) ==
256 ROOK
&& islower (b
[0][7].icon
) && pgn_piece_to_int (b
[0][4].icon
) ==
257 KING
&& islower (b
[0][4].icon
))
263 if (TEST_FLAG (g
->flags
, GF_BQ_CASTLE
) && pgn_piece_to_int (b
[0][0].icon
) ==
264 ROOK
&& islower (b
[0][0].icon
) && pgn_piece_to_int (b
[0][4].icon
) ==
265 KING
&& islower (b
[0][4].icon
))
289 strcat (p
, itoa (g
->ply
, tmp
));
290 p
= buf
+ strlen (buf
);
294 i
= (g
->hindex
+ 1) / 2;
296 strcat (p
, itoa ((g
->hindex
/ 2) + (g
->hindex
% 2), tmp
));
299 return buf
[0] ? strdup (buf
) : NULL
;
303 * Returns the total number of moves in 'h' or 0 if there are none.
306 pgn_history_total (HISTORY
** h
)
313 for (i
= 0; h
[i
]; i
++);
318 * Deallocates all of the history data from position 'start' in the array 'h'.
322 pgn_history_free (HISTORY
** h
, int start
)
327 PGN_DUMP ("%s:%d: freeing history\n", __FILE__
, __LINE__
);
330 if (!h
|| start
> pgn_history_total (h
))
336 for (i
= start
; h
[i
]; i
++)
340 pgn_history_free (h
[i
]->rav
, 0);
344 free (h
[i
]->comment
);
354 * Returns the history ply 'n' from 'h'. If 'n' is out of range then NULL is
358 pgn_history_by_n (HISTORY
** h
, int n
)
360 if (n
< 0 || n
> pgn_history_total (h
) - 1)
367 * Appends move 'm' to game 'g' history pointer and creates a FEN tag for the
368 * current game state using board 'b'. The FEN tag makes things faster than
369 * validating the entire move history by validating only the current move to
370 * the previous moves FEN tag. The history pointer may be a in a RAV so
371 * g->rav.hp is also updated to the new (realloc()'ed) pointer. If not in a
372 * RAV then g->history will be updated. Returns E_PGN_ERR if realloc() failed
373 * or E_PGN_OK on success.
376 pgn_history_add (GAME g
, BOARD b
, const char *m
)
378 int t
= pgn_history_total (g
->hp
);
380 HISTORY
**h
= NULL
, *tmp
;
381 int ri
= (g
->ravlevel
) ? g
->rav
[g
->ravlevel
- 1].hindex
: 0;
384 PGN_DUMP ("%s:%d: adding '%s' to move history\n", __FILE__
, __LINE__
, m
);
388 o
= g
->rav
[g
->ravlevel
- 1].hp
[ri
- 1]->rav
- g
->hp
;
390 o
= g
->history
- g
->hp
;
392 if ((h
= realloc (g
->hp
, (t
+ 2) * sizeof (HISTORY
*))) == NULL
)
398 g
->rav
[g
->ravlevel
- 1].hp
[ri
- 1]->rav
= g
->hp
+ o
;
400 g
->history
= g
->hp
+ o
;
402 if ((g
->hp
[t
] = calloc (1, sizeof (HISTORY
))) == NULL
)
405 if ((g
->hp
[t
]->move
= strdup (m
)) == NULL
)
415 g
->hindex
= pgn_history_total (g
->hp
);
417 tmp
->fen
= pgn_game_to_fen (g
, b
);
423 * Resets the game 'g' using board 'b' up to history move (g->hindex) 'n'.
424 * Returns E_PGN_OK on success or E_PGN_PARSE if there was a FEN tag but the
425 * parsing of it failed. Or returns E_PGN_INVALID if somehow the move failed
426 * validation while resetting.
429 pgn_board_update (GAME g
, BOARD b
, int n
)
433 int p_error
= TEST_FLAG (g
->flags
, GF_PERROR
);
434 int black_opening
= TEST_FLAG (g
->flags
, GF_BLACK_OPENING
);
437 PGN_DUMP ("%s:%d: updating board\n", __FILE__
, __LINE__
);
440 if (!g
->ravlevel
&& TEST_FLAG (g
->flags
, GF_BLACK_OPENING
))
446 SET_FLAG (g
->flags
, GF_WK_CASTLE
| GF_WQ_CASTLE
| GF_WQ_CASTLE
|
447 GF_BK_CASTLE
| GF_BQ_CASTLE
);
451 pgn_board_init_fen (g
, tb
, g
->rav
[g
->ravlevel
- 1].fen
);
452 else if (pgn_tag_find (g
->tag
, "FEN") != -1 &&
453 pgn_board_init_fen (g
, tb
, NULL
))
458 HISTORY
*h
= pgn_history_by_n (g
->hp
, n
- 1);
462 ret
= pgn_board_init_fen (g
, tb
, h
->fen
);
465 h
= pgn_history_by_n (g
->hp
, n
);
470 ret
= pgn_parse_move (g
, tb
, &h
->move
, &frfr
);
473 h
= pgn_history_by_n (g
->hp
, n
- 1);
474 ret
= pgn_board_init_fen (g
, tb
, h
->fen
);
484 memcpy (b
, tb
, sizeof (BOARD
));
487 SET_FLAG (g
->flags
, GF_PERROR
);
490 SET_FLAG (g
->flags
, GF_BLACK_OPENING
);
496 * Updates the game 'g' using board 'b' to the next 'n'th history move.
500 pgn_history_prev (GAME g
, BOARD b
, int n
)
502 if (g
->hindex
- n
< 0)
505 g
->hindex
= pgn_history_total (g
->hp
);
512 pgn_board_update (g
, b
, g
->hindex
);
516 * Updates the game 'g' using board 'b' to the previous 'n'th history move.
520 pgn_history_next (GAME g
, BOARD b
, int n
)
522 if (g
->hindex
+ n
> pgn_history_total (g
->hp
))
527 g
->hindex
= pgn_history_total (g
->hp
);
532 pgn_board_update (g
, b
, g
->hindex
);
536 * Converts the character piece 'p' to an integer. Returns the integer
537 * associated with 'p' or E_PGN_ERR if 'p' is invalid.
540 pgn_piece_to_int (int p
)
566 PGN_DUMP ("%s:%d: invalid piece '%c'\n", __FILE__
, __LINE__
, p
);
572 * Converts the integer piece 'n' to a character whose turn is 'turn'. WHITE
573 * piece are uppercase and BLACK pieces are lowercase. Returns the character
574 * associated with 'n' or E_PGN_ERR if 'n' is an invalid piece.
577 pgn_int_to_piece (char turn
, int n
)
606 PGN_DUMP ("%s:%d: unknown piece integer %i\n", __FILE__
, __LINE__
, n
);
612 return (turn
== WHITE
) ? toupper (p
) : p
;
616 * Finds a tag 'name' in the structure array 't'. Returns the location in the
617 * array of the found tag or E_PGN_ERR if the tag could not be found.
620 pgn_tag_find (TAG
** t
, const char *name
)
624 for (i
= 0; t
[i
]; i
++)
626 if (strcasecmp (t
[i
]->name
, name
) == 0)
634 remove_tag (TAG
*** array
, const char *tag
)
637 int n
= pgn_tag_find (tags
, tag
);
643 for (i
= t
= 0; tags
[i
]; i
++)
647 free (tags
[i
]->name
);
648 free (tags
[i
]->value
);
656 tags
= realloc (*array
, (t
+ 1) * sizeof (TAG
*));
660 PGN_DUMP ("%s:%d: removed tag: name='%s'\n", __FILE__
, __LINE__
, tag
);
666 tag_compare (const void *a
, const void *b
)
671 return strcmp ((*ta
)->name
, (*tb
)->name
);
675 * Sorts a tag array. The first seven tags are in order of the PGN standard so
676 * don't sort'em. Returns nothing.
679 pgn_tag_sort (TAG
** tags
)
681 if (pgn_tag_total (tags
) <= 7)
684 qsort (tags
+ 7, pgn_tag_total (tags
) - 7, sizeof (TAG
*), tag_compare
);
688 * Returns the total number of tags in 't' or 0 if 't' is NULL.
691 pgn_tag_total (TAG
** tags
)
705 * Adds a tag 'name' with value 'value' to the pointer to array of TAG
706 * pointers 'dst'. If a duplicate tag 'name' was found then the existing tag
707 * is updated to the new 'value'. If 'value' is NULL, the tag is removed.
708 * Returns E_PGN_ERR if there was a memory allocation error or E_PGN_OK on
712 pgn_tag_add (TAG
*** dst
, char *name
, char *value
)
717 int t
= pgn_tag_total (tdata
);
721 PGN_DUMP ("%s:%d: adding tag\n", __FILE__
, __LINE__
);
730 value
= trim (value
);
732 // Find an existing tag with 'name'.
733 for (i
= 0; i
< t
; i
++)
737 if (strcasecmp (tdata
[i
]->name
, name
) == 0)
741 if ((tmp
= strdup (value
)) == NULL
)
746 remove_tag (dst
, name
);
750 free (tdata
[i
]->value
);
751 tdata
[i
]->value
= tmp
;
757 newtag
= calloc (1, sizeof (TAG
));
761 if ((newtag
->name
= strdup (name
)) == NULL
)
769 if ((newtag
->value
= strdup (value
)) == NULL
)
777 newtag
->value
= NULL
;
779 if ((a
= realloc (tdata
, (t
+ 2) * sizeof (TAG
*))) == NULL
)
782 free (newtag
->value
);
789 tdata
[t
]->name
[0] = toupper (tdata
[t
]->name
[0]);
794 PGN_DUMP ("%s:%d: added tag: name='%s' value='%s'\n", __FILE__
, __LINE__
,
795 name
, (value
) ? value
: "null");
802 remove_tag_escapes (const char *str
)
805 int len
= strlen (str
);
806 char buf
[MAX_PGN_LINE_LEN
] = { 0 };
808 for (i
= n
= 0; i
< len
; i
++, n
++)
822 return buf
[0] ? strdup (buf
) : NULL
;
826 * Resets or initializes a new game board 'b'. Returns nothing.
829 pgn_board_init (BOARD b
)
834 PGN_DUMP ("%s:%d: initializing board\n", __FILE__
, __LINE__
);
837 memset (b
, 0, sizeof (BOARD
));
839 for (row
= 0; row
< 8; row
++)
841 for (col
= 0; col
< 8; col
++)
877 b
[row
][col
].icon
= (row
< 2) ? c
: toupper (c
);
883 * Adds the standard PGN roster tags to game 'g'.
886 set_default_tags (GAME g
)
889 char tbuf
[11] = { 0 };
891 struct passwd
*pw
= getpwuid (getuid ());
892 char host
[64] = { 0 };
895 tp
= localtime (&now
);
896 strftime (tbuf
, sizeof (tbuf
), PGN_TIME_FORMAT
, tp
);
898 /* The standard seven tag roster (in order of appearance). */
899 if (pgn_tag_add (&g
->tag
, (char *) "Event", (char *) "?") != E_PGN_OK
)
900 warn ("pgn_tag_add()");
902 gethostname (host
, sizeof (host
) - 1);
903 if (pgn_tag_add (&g
->tag
, (char *) "Site", host
) != E_PGN_OK
)
904 warn ("pgn_tag_add()");
906 if (pgn_tag_add (&g
->tag
, (char *) "Date", tbuf
) != E_PGN_OK
)
907 warn ("pgn_tag_add()");
909 if (pgn_tag_add (&g
->tag
, (char *) "Round", (char *) "-") != E_PGN_OK
)
910 warn ("pgn_tag_add()");
912 if (pgn_tag_add (&g
->tag
, (char *) "White", pw
->pw_gecos
) != E_PGN_OK
)
913 warn ("pgn_tag_add()");
915 if (pgn_tag_add (&g
->tag
, (char *) "Black", (char *) "?") != E_PGN_OK
)
916 warn ("pgn_tag_add()");
918 if (pgn_tag_add (&g
->tag
, (char *) "Result", (char *) "*") != E_PGN_OK
)
919 warn ("pgn_tag_add()");
923 * Frees a TAG array. Returns nothing.
926 pgn_tag_free (TAG
** tags
)
929 int t
= pgn_tag_total (tags
);
932 PGN_DUMP ("%s:%d: freeing tags\n", __FILE__
, __LINE__
);
938 for (i
= 0; i
< t
; i
++)
940 free (tags
[i
]->name
);
941 free (tags
[i
]->value
);
949 * Frees a single game 'g'. Returns nothing.
955 PGN_DUMP ("%s:%d: freeing game\n", __FILE__
, __LINE__
);
957 pgn_history_free (g
->history
, 0);
959 pgn_tag_free (g
->tag
);
963 for (g
->ravlevel
--; g
->ravlevel
>= 0; g
->ravlevel
--)
964 free (g
->rav
[g
->ravlevel
].fen
);
973 * Frees all games in the global 'game' array. Returns nothing.
981 PGN_DUMP ("%s:%d: freeing game data\n", __FILE__
, __LINE__
);
984 for (i
= 0; i
< gtotal
; i
++)
999 PGN_DUMP ("%s:%d: resetting game data\n", __FILE__
, __LINE__
);
1002 gtotal
= gindex
= 0;
1006 skip_leading_space (FILE * fp
)
1010 while ((c
= Fgetc (fp
)) != EOF
&& !feof (fp
))
1020 * PGN move text section.
1023 move_text (GAME g
, FILE * fp
)
1025 char m
[MAX_SAN_MOVE_LEN
+ 1] = { 0 }, *p
;
1030 g
->oflags
= g
->flags
;
1032 while ((c
= Fgetc (fp
)) != EOF
)
1034 if (isdigit (c
) || isspace (c
) || c
== '.')
1042 if (fscanf (fp
, " %[a-hPpRrNnBbQqKk1-9#+=Ox-]%n", m
, &count
) != 1)
1045 m
[MAX_SAN_MOVE_LEN
] = 0;
1048 if (pgn_parse_move (g
, pgn_board
, &p
, &frfr
))
1050 pgn_switch_turn (g
);
1056 PGN_DUMP ("%s\n%s", p
, debug_board (pgn_board
));
1059 pgn_history_add (g
, pgn_board
, p
);
1060 pgn_switch_turn (g
);
1068 nag_text (GAME g
, FILE * fp
)
1071 char nags
[5], *n
= nags
;
1074 while ((c
= Fgetc (fp
)) != EOF
&& !isspace (c
))
1078 while ((c
= Fgetc (fp
)) != EOF
&& isdigit (c
))
1087 if ((c
= Fgetc (fp
)) == '!')
1101 if ((c
= Fgetc (fp
)) == '?')
1117 if ((c
= Fgetc (fp
)) == '+')
1129 if ((t
= Fgetc (fp
)) == '=')
1135 if ((i
= Fgetc (fp
)) == '-')
1149 if ((t
= Fgetc (fp
)) == '+')
1153 if ((i
= Fgetc (fp
)) == '+')
1170 nag
= (nags
[0]) ? atoi (nags
) : 0;
1172 if (!nag
|| nag
< 0 || nag
> 255)
1175 // FIXME -1 is because move_text() increments g->hindex. The NAG
1176 // annoatation isnt guaranteed to be after the move text in import format.
1177 for (i
= 0; i
< MAX_PGN_NAG
; i
++)
1179 if (g
->hp
[g
->hindex
- 1]->nag
[i
])
1182 g
->hp
[g
->hindex
- 1]->nag
[i
] = nag
;
1186 skip_leading_space (fp
);
1190 * PGN move annotation.
1193 annotation_text (GAME g
, FILE * fp
, int terminator
)
1195 int c
, lastchar
= 0;
1197 int hindex
= pgn_history_total (g
->hp
) - 1;
1198 char buf
[MAX_PGN_LINE_LEN
], *a
= buf
;
1200 skip_leading_space (fp
);
1202 while ((c
= Fgetc (fp
)) != EOF
&& c
!= terminator
)
1207 if (isspace (c
) && isspace (lastchar
))
1210 if (len
+ 1 == sizeof (buf
))
1213 *a
++ = lastchar
= c
;
1223 * This annotation is before any move text or NAg-> Allocate a new move.
1227 if ((g
->hp
[hindex
] = calloc (1, sizeof (HISTORY
))) == NULL
)
1231 if ((g
->hp
[hindex
]->comment
= strdup (buf
)) == NULL
)
1241 tag_text (GAME g
, FILE * fp
)
1243 char name
[LINE_MAX
] = { 0 }, *n
= name
;
1244 char value
[LINE_MAX
] = { 0 }, *v
= value
;
1249 skip_leading_space (fp
);
1251 /* The tag name is up until the first whitespace. */
1252 while ((c
= Fgetc (fp
)) != EOF
&& !isspace (c
))
1256 *name
= toupper (*name
);
1257 skip_leading_space (fp
);
1259 /* The value is until the first closing bracket. */
1260 while ((c
= Fgetc (fp
)) != EOF
&& c
!= ']')
1262 if (i
++ == '\0' && c
== '\"')
1265 if (c
== '\n' || c
== '\t')
1268 if (c
== ' ' && lastchar
== ' ')
1271 lastchar
= *v
++ = c
;
1276 while (isspace (*--v
))
1282 if (value
[0] == '\0')
1284 if (strcmp (name
, "Result") == 0)
1292 tmp
= remove_tag_escapes (value
);
1293 strncpy (value
, tmp
, sizeof (value
) - 1);
1297 * See eog_text() for an explanation.
1299 if (strcmp (name
, "Result") == 0)
1301 if (strcmp (value
, "1/2-1/2") == 0)
1303 if (pgn_tag_add (&g
->tag
, name
, value
) != E_PGN_OK
)
1304 warn ("pgn_tag_add()");
1309 if (pgn_tag_add (&g
->tag
, name
, value
) != E_PGN_OK
)
1310 warn ("pgn_tag_add()");
1317 * PGN end-of-game marker.
1320 eog_text (GAME g
, FILE * fp
)
1323 char buf
[8], *p
= buf
;
1325 while ((c
= Fgetc (fp
)) != EOF
&& !isspace (c
) && i
++ < sizeof (buf
))
1333 if (strcmp (buf
, "1-0") != 0 && strcmp (buf
, "0-1") != 0 &&
1334 strcmp (buf
, "1/2-1/2") != 0 && strcmp (buf
, "*") != 0)
1338 * The eog marker in the move text may not match the actual game result.
1339 * We'll leave it up to the move validator to determine the result unless
1340 * the move validator cannot determine who won and the move text says it's
1343 if (g
->tag
[6]->value
[0] == '*' && strcmp ("1/2-1/2", buf
) == 0)
1345 if (pgn_tag_add (&g
->tag
, (char *) "Result", buf
) != E_PGN_OK
)
1346 warn ("pgn_tag_add()");
1353 * Parse RAV text and keep track of g->hp. The 'o' argument is the board state
1354 * before the current move (.hindex) was parsed.
1356 static int read_file (FILE *);
1358 rav_text (GAME g
, FILE * fp
, int which
, BOARD o
)
1363 // Begin RAV for the current move.
1369 pgn_rav
++; // For detecting parse errors.
1372 * Save the current game state for this RAV depth/level.
1374 if ((r
= realloc (g
->rav
, (g
->ravlevel
+ 1) * sizeof (RAV
))) == NULL
)
1380 g
->rav
= pgn_rav_p
= r
;
1382 if ((g
->rav
[g
->ravlevel
].fen
= pgn_game_to_fen (g
, pgn_board
)) == NULL
)
1388 g
->rav
[g
->ravlevel
].hp
= g
->hp
;
1389 g
->rav
[g
->ravlevel
].hindex
= g
->hindex
;
1390 memcpy (&tg
, &(*g
), sizeof (struct game_s
));
1391 g
->flags
= g
->oflags
;
1392 memcpy (pgn_board
, o
, sizeof (BOARD
));
1394 if ((g
->hp
[g
->hindex
- 1]->rav
=
1395 calloc (1, sizeof (HISTORY
*))) == NULL
)
1402 * pgn_history_add() will now append to the new history pointer that
1403 * is g->hp[previous_move]->rav.
1405 g
->hp
= g
->hp
[g
->hindex
- 1]->rav
;
1408 * Reset. Will be restored later from 'tg' which is a local variable
1409 * so recursion is possible.
1415 * Undo move_text()'s switch.
1417 pgn_switch_turn (g
);
1420 * Now continue as normal as if there were no RAV. Moves will be
1421 * parsed and appended to the new history pointer.
1427 * read_file() has returned. This means that a RAV has ended by this
1428 * function returning -1 (see below). So we restore the game state
1429 * (tg) that was saved before calling read_file().
1431 pgn_board_init_fen (&tg
, pgn_board
, g
->rav
[tg
.ravlevel
].fen
);
1432 free (g
->rav
[tg
.ravlevel
].fen
);
1433 memcpy (&(*g
), &tg
, sizeof (struct game_s
));
1437 * The end of a RAV. This makes the read_file() that called this function
1438 * return (see above and the switch statement in read_file()).
1440 else if (which
== ')')
1442 pgn_rav
--; // For detecting parse errors.
1443 return (pgn_rav
< 0) ? 1 : -1;
1451 * See pgn_board_init_fen(). Returns E_PGN_PARSE on parse error. 0 may be
1452 * returned on success when there is no move count in the FEN tag otherwise
1453 * the move count is returned.
1456 parse_fen_line (BOARD b
, unsigned *flags
, char *turn
, char *ply
, char *str
)
1459 char line
[LINE_MAX
] = { 0 }, *s
;
1460 int row
= 8, col
= 1;
1463 PGN_DUMP ("%s:%d: FEN line is '%s'\n", __FILE__
, __LINE__
, str
);
1465 strncpy (line
, str
, sizeof (line
) - 1);
1467 pgn_reset_enpassant (b
);
1469 while ((tmp
= strsep (&s
, "/")) != NULL
)
1473 if (!VALIDFILE (row
))
1488 for (; n
; --n
, col
++)
1489 b
[RANKTOBOARD (row
)][FILETOBOARD (col
)].icon
=
1490 pgn_int_to_piece (WHITE
, OPEN_SQUARE
);
1492 else if (pgn_piece_to_int (*tmp
) != -1)
1493 b
[RANKTOBOARD (row
)][FILETOBOARD (col
++)].icon
= *tmp
;
1521 while (*tmp
&& *tmp
!= ' ')
1526 SET_FLAG (*flags
, GF_WK_CASTLE
);
1529 SET_FLAG (*flags
, GF_WQ_CASTLE
);
1532 SET_FLAG (*flags
, GF_BK_CASTLE
);
1535 SET_FLAG (*flags
, GF_BQ_CASTLE
);
1549 if (!VALIDCOL (*tmp
))
1553 if (!VALIDROW (*tmp
))
1555 row
= 8 - atoi (tmp
++);
1557 b
[row
][col
].enpassant
= 1;
1558 SET_FLAG (*flags
, GF_ENPASSANT
);
1569 while (*tmp
&& isdigit (*tmp
))
1579 * It initializes the board (b) to the FEN tag (if found) and sets the
1580 * castling and enpassant info for the game 'g'. If 'fen' is set it should be
1581 * a fen tag and will be parsed rather than the game 'g'.tag FEN tag-> Returns
1582 * E_PGN_OK on success or if there was both a FEN and SetUp tag with the SetUp
1583 * tag set to 0. Returns E_PGN_PARSE if there was a FEN parse error, E_PGN_ERR
1584 * if there was no FEN tag or there was a SetUp tag with a value of 0. Returns
1585 * E_PGN_OK on success.
1588 pgn_board_init_fen (GAME g
, BOARD b
, char *fen
)
1593 char turn
= g
->turn
;
1597 PGN_DUMP ("%s:%d: initializing board from FEN\n", __FILE__
, __LINE__
);
1599 pgn_board_init (tmpboard
);
1603 n
= pgn_tag_find (g
->tag
, "Setup");
1604 i
= pgn_tag_find (g
->tag
, "FEN");
1608 * If the FEN tag exists and there is no SetUp tag go ahead and parse it.
1609 * If there is a SetUp tag only parse the FEN tag if the value is 1.
1611 if ((n
>= 0 && i
>= 0 && atoi (g
->tag
[n
]->value
) == 1)
1612 || (i
>= 0 && n
== -1) || fen
)
1614 if ((n
= parse_fen_line (tmpboard
, &flags
, &turn
, &ply
,
1615 (fen
) ? fen
: g
->tag
[i
]->value
)) != E_PGN_OK
)
1619 memcpy (b
, tmpboard
, sizeof (BOARD
));
1620 CLEAR_FLAG (g
->flags
, GF_WK_CASTLE
);
1621 CLEAR_FLAG (g
->flags
, GF_WQ_CASTLE
);
1622 CLEAR_FLAG (g
->flags
, GF_BK_CASTLE
);
1623 CLEAR_FLAG (g
->flags
, GF_BQ_CASTLE
);
1630 return (i
>= 0 && n
>= 0) ? E_PGN_OK
: E_PGN_ERR
;
1636 * Allocates a new game and increments 'gtotal'. 'gindex' is then set to the
1637 * new game. Returns E_PGN_ERR if there was a memory allocation error or
1638 * E_PGN_OK on success.
1648 PGN_DUMP ("%s:%d: allocating new game\n", __FILE__
, __LINE__
);
1652 if ((g
= realloc (game
, t
* sizeof (GAME
))) == NULL
)
1660 if ((newg
= calloc (1, sizeof (struct game_s
))) == NULL
)
1666 game
[gindex
] = newg
;
1668 if ((game
[gindex
]->hp
= calloc (1, sizeof (HISTORY
*))) == NULL
)
1670 free (game
[gindex
]);
1675 game
[gindex
]->hp
[0] = NULL
;
1676 game
[gindex
]->history
= game
[gindex
]->hp
;
1677 game
[gindex
]->side
= game
[gindex
]->turn
= WHITE
;
1678 SET_FLAG (game
[gindex
]->flags
, GF_WK_CASTLE
| GF_WQ_CASTLE
| GF_WQ_CASTLE
|
1679 GF_BK_CASTLE
| GF_BQ_CASTLE
);
1680 pgn_board_init (pgn_board
);
1681 set_default_tags (game
[gindex
]);
1687 read_file (FILE * fp
)
1690 char buf
[LINE_MAX
] = { 0 }, *p
= buf
;
1693 int parse_error
= 0;
1703 * A parse error may have occured at EOF.
1707 pgn_ret
= E_PGN_PARSE
;
1712 SET_FLAG (game
[gindex
]->flags
, GF_PERROR
);
1715 if ((c
= Fgetc (fp
)) == EOF
)
1736 nextchar
= Fgetc (fp
);
1737 Ungetc (nextchar
, fp
);
1740 * If there was a move text parsing error, keep reading until the end
1741 * of the current game discarding the data.
1745 pgn_ret
= E_PGN_PARSE
;
1747 if (game
[gindex
]->ravlevel
)
1750 if (pgn_config
.stop
)
1753 if (c
== '\n' && (nextchar
== '\n' || nextchar
== '\015'))
1763 // New game reached.
1764 if (c
== '\n' && (nextchar
== '\n' || nextchar
== '\015'))
1775 * PGN: Application comment. The '%' must be on the first column of
1776 * the line. The comment continues until the end of the current line.
1780 if (lastchar
== '\n' || lastchar
== 0)
1782 while ((c
= Fgetc (fp
)) != EOF
&& c
!= '\n');
1786 // Not sure what to do here.
1793 if (c
== '<' || c
== '>')
1797 * PGN: Recurrsive Annotated Variation. Read rav_text() for more
1800 if (c
== '(' || c
== ')')
1802 switch (rav_text (game
[gindex
], fp
, c
, old
))
1806 * This is the end of the current RAV. This function has
1807 * been called from rav_text(). Returning from this point
1808 * will put us back in rav_text().
1810 if (game
[gindex
]->ravlevel
> 0)
1814 * We're back at the root move. Continue as normal.
1822 * Continue processing-> Probably the root move.
1827 if (!game
[gindex
]->ravlevel
&& pgn_rav
)
1833 // PGN: Numeric Annotation Glyph.
1834 if (c
== '$' || c
== '!' || c
== '?' || c
== '+' || c
== '-' ||
1835 c
== '~' || c
== '=')
1838 nag_text (game
[gindex
], fp
);
1843 * PGN: Annotation. The ';' comment continues until the end of the
1844 * current line. The '{' type comment continues until a '}' is
1847 if (c
== '{' || c
== ';')
1849 annotation_text (game
[gindex
], fp
, (c
== '{') ? '}' : '\n');
1853 // PGN: Roster tag->
1856 // First roster tag found. Initialize the data structures.
1862 if (gtotal
&& pgn_history_total (game
[gindex
]->hp
))
1863 game
[gindex
]->hindex
=
1864 pgn_history_total (game
[gindex
]->hp
) - 1;
1866 if (pgn_new_game () != E_PGN_OK
)
1868 pgn_ret
= E_PGN_ERR
;
1872 memcpy (old
, pgn_board
, sizeof (BOARD
));
1875 if (tag_text (game
[gindex
], fp
))
1876 parse_error
= 1; // FEN tag parse error.
1881 // PGN: End-of-game markers.
1882 if ((isdigit (c
) && (nextchar
== '-' || nextchar
== '/')) || c
== '*')
1886 if (eog_text (game
[gindex
], fp
))
1895 if (!game
[gindex
]->done_fen_tag
)
1897 if (pgn_tag_find (game
[gindex
]->tag
, "FEN") != -1 &&
1898 pgn_board_init_fen (game
[gindex
], pgn_board
, NULL
))
1904 game
[gindex
]->pgn_fen_tag
=
1905 pgn_tag_find (game
[gindex
]->tag
, "FEN");
1906 game
[gindex
]->done_fen_tag
= 1;
1913 if ((isdigit (c
) && c
!= '0') || VALIDCOL (c
) || c
== 'N' || c
== 'K'
1914 || c
== 'Q' || c
== 'B' || c
== 'R' || c
== 'P' || c
== 'O')
1918 // PGN: If a FEN tag exists, initialize the board to the value.
1921 if (pgn_tag_find (game
[gindex
]->tag
, "FEN") != E_PGN_ERR
&&
1922 (n
= pgn_board_init_fen (game
[gindex
], pgn_board
,
1923 NULL
)) == E_PGN_PARSE
)
1929 game
[gindex
]->done_fen_tag
= 1;
1930 game
[gindex
]->pgn_fen_tag
=
1931 pgn_tag_find (game
[gindex
]->tag
, "FEN");
1936 * PGN: Import format doesn't require a roster tag section. We've
1937 * arrived to the move text section without any tags so we
1938 * initialize a new game which set's the default tags and any tags
1939 * from the configuration file.
1944 game
[gindex
]->hindex
=
1945 pgn_history_total (game
[gindex
]->hp
) - 1;
1947 if (pgn_new_game () != E_PGN_OK
)
1949 pgn_ret
= E_PGN_ERR
;
1953 memcpy (old
, pgn_board
, sizeof (BOARD
));
1957 memcpy (old
, pgn_board
, sizeof (BOARD
));
1959 if (move_text (game
[gindex
], fp
))
1961 if (pgn_tag_add (&game
[gindex
]->tag
, (char *) "Result",
1962 (char *) "*") == E_PGN_ERR
)
1964 warn ("pgn_tag_add()");
1965 pgn_ret
= E_PGN_ERR
;
1968 SET_FLAG (game
[gindex
]->flags
, GF_PERROR
);
1978 PGN_DUMP ("%s:%d: unparsed: '%s'\n", __FILE__
, __LINE__
, buf
);
1980 if (strlen (buf
) + 1 == sizeof (buf
))
1981 bzero (buf
, sizeof (buf
));
1991 * Parses a PGN_FILE which was opened with pgn_open(). If 'pgn' is NULL then a
1992 * single empty game will be allocated. If there is a parsing error
1993 * E_PGN_PARSE is returned, if there was a memory allocation error E_PGN_ERR
1994 * is returned, otherwise E_PGN_OK is returned and the global 'gindex' is set
1995 * to the last parsed game in the file and the global 'gtotal' is set to the
1996 * total number of games in the file. The file should be closed with
1997 * pgn_close() after processing.
2000 pgn_parse (PGN_FILE
* pgn
)
2007 pgn_ret
= pgn_new_game ();
2013 fseek (pgn
->fp
, 0, SEEK_END
);
2014 pgn_fsize
= ftell (pgn
->fp
);
2015 fseek (pgn
->fp
, 0, SEEK_SET
);
2017 PGN_DUMP ("%s:%d: BEGIN parsing->..\n", __FILE__
, __LINE__
);
2020 pgn_ret
= read_file (pgn
->fp
);
2024 PGN_DUMP ("%s:%d: END parsing->..\n", __FILE__
, __LINE__
);
2031 gtotal
= gindex
+ 1;
2033 for (i
= 0; i
< gtotal
; i
++)
2035 game
[i
]->history
= game
[i
]->hp
;
2036 game
[i
]->hindex
= pgn_history_total (game
[i
]->hp
);
2043 * Escape '"' and '\' in tag values.
2046 pgn_tag_add_escapes (const char *str
)
2049 int len
= strlen (str
);
2050 char buf
[MAX_PGN_LINE_LEN
] = { 0 };
2052 for (i
= n
= 0; i
< len
; i
++, n
++)
2068 return buf
[0] ? strdup (buf
) : NULL
;
2072 Fputc (int c
, FILE * fp
, int *len
)
2076 if (c
!= '\n' && i
+ 1 > 80)
2077 Fputc ('\n', fp
, &i
);
2079 if (pgn_lastc
== '\n' && c
== ' ')
2085 if (fputc (c
, fp
) == EOF
)
2100 putstring (FILE * fp
, char *str
, int *len
)
2104 for (p
= str
; *p
; p
++)
2108 while (*p
&& *p
!= ' ')
2112 Fputc ('\n', fp
, len
);
2115 Fputc (*p
, fp
, len
);
2120 * See pgn_write() for more info.
2123 write_comments_and_nag (FILE * fp
, HISTORY
* h
, int *len
)
2129 PGN_DUMP ("%s:%d: writing comments and nag\n", __FILE__
, __LINE__
);
2132 for (i
= 0; i
< MAX_PGN_NAG
; i
++)
2136 Fputc (' ', fp
, len
);
2137 Fputc ('$', fp
, len
);
2138 putstring (fp
, itoa (h
->nag
[i
], tmp
), len
);
2144 Fputc ('\n', fp
, len
);
2145 putstring (fp
, (char *) " {", len
);
2146 putstring (fp
, h
->comment
, len
);
2147 Fputc ('}', fp
, len
);
2152 write_move_text (FILE * fp
, HISTORY
* h
, int *len
)
2154 Fputc (' ', fp
, len
);
2155 putstring (fp
, h
->move
, len
);
2157 if (!pgn_config
.reduced
)
2158 write_comments_and_nag (fp
, h
, len
);
2162 write_all_move_text (FILE * fp
, HISTORY
** h
, int m
, int *len
)
2165 HISTORY
**hp
= NULL
;
2168 for (i
= 0; h
[i
]; i
++)
2170 if (pgn_write_turn
== WHITE
)
2172 if (pgn_config
.mpl
&& pgn_mpl
== pgn_config
.mpl
)
2175 Fputc ('\n', fp
, len
);
2179 Fputc (' ', fp
, len
);
2181 if (strlen (itoa (m
, tmp
)) + 1 + *len
> 80)
2182 Fputc ('\n', fp
, len
);
2184 putstring (fp
, itoa (m
, tmp
), len
);
2185 Fputc ('.', fp
, len
);
2189 write_move_text (fp
, h
[i
], len
);
2191 if (!pgn_config
.reduced
&& h
[i
]->rav
)
2194 int oldturn
= pgn_write_turn
;
2197 putstring (fp
, (char *) " (", len
);
2200 * If it's WHITE's turn the move number will be added above after
2201 * the call to write_all_move_text() below.
2203 if (pgn_write_turn
== BLACK
)
2205 putstring (fp
, itoa (m
, tmp
), len
);
2206 putstring (fp
, (char *) "...", len
);
2210 write_all_move_text (fp
, hp
, m
, len
);
2212 pgn_write_turn
= oldturn
;
2213 putstring (fp
, (char *) ")", len
);
2216 if (ravlevel
&& h
[i
+ 1])
2217 Fputc (' ', fp
, len
);
2219 if (h
[i
+ 1] && !ravlevel
)
2220 Fputc (' ', fp
, len
);
2222 if (pgn_write_turn
== WHITE
&& h
[i
+ 1])
2224 putstring (fp
, itoa (m
, tmp
), len
);
2225 putstring (fp
, (char *) "...", len
);
2229 if (pgn_write_turn
== BLACK
)
2232 pgn_write_turn
= (pgn_write_turn
== WHITE
) ? BLACK
: WHITE
;
2237 compression_cmd (const char *filename
, int expand
)
2239 char command
[PATH_MAX
];
2240 int len
= strlen (filename
);
2242 if (filename
[len
- 4] == '.' && filename
[len
- 3] == 'z' &&
2243 filename
[len
- 2] == 'i' && filename
[len
- 1] == 'p' &&
2244 filename
[len
] == '\0')
2247 snprintf (command
, sizeof (command
), "unzip -p %s 2>/dev/null",
2250 snprintf (command
, sizeof (command
), "zip -9 >%s 2>/dev/null",
2253 return strdup (command
);
2255 else if (filename
[len
- 3] == '.' && filename
[len
- 2] == 'g' &&
2256 filename
[len
- 1] == 'z' && filename
[len
] == '\0')
2259 snprintf (command
, sizeof (command
), "gzip -dc %s", filename
);
2261 snprintf (command
, sizeof (command
), "gzip -c 1>%s", filename
);
2263 return strdup (command
);
2265 else if (filename
[len
- 2] == '.' && filename
[len
- 1] == 'Z' &&
2266 filename
[len
] == '\0')
2269 snprintf (command
, sizeof (command
), "uncompress -c %s", filename
);
2271 snprintf (command
, sizeof (command
), "compress -c 1>%s", filename
);
2273 return strdup (command
);
2275 else if ((filename
[len
- 4] == '.' && filename
[len
- 3] == 'b' &&
2276 filename
[len
- 2] == 'z' && filename
[len
- 1] == '2' &&
2277 filename
[len
] == '\0') || (filename
[len
- 3] == '.' &&
2278 filename
[len
- 2] == 'b'
2279 && filename
[len
- 1] == 'z'
2280 && filename
[len
] == '\0'))
2283 snprintf (command
, sizeof (command
), "bzip2 -dc %s", filename
);
2285 snprintf (command
, sizeof (command
), "bzip2 -zc 1>%s", filename
);
2287 return strdup (command
);
2294 copy_file (FILE * fp
, const char *dst
)
2297 char line
[LINE_MAX
];
2298 char *cmd
= compression_cmd (dst
, 0);
2300 if ((ofp
= popen (cmd
, "w")) == NULL
)
2307 fseek (fp
, 0, SEEK_SET
);
2309 while ((fgets (line
, sizeof (line
), fp
)) != NULL
)
2310 fprintf (ofp
, "%s", line
);
2317 * Closes and free's a PGN file handle.
2320 pgn_close (PGN_FILE
* pgn
)
2323 return E_PGN_INVALID
;
2328 * Appending to a compressed file.
2332 if (copy_file (pgn
->fp
, pgn
->filename
))
2336 unlink (pgn
->tmpfile
);
2337 free (pgn
->tmpfile
);
2345 free (pgn
->filename
);
2351 * Opens a file 'filename' with the given 'mode'. 'mode' should be "r" for
2352 * reading, "w" for writing (will truncate if the file exists) or "a" for
2353 * appending to an existing file or creating a new one. Returns E_PGN_OK on
2354 * success and sets 'result' to a file handle for use will the other file
2355 * functions or E_PGN_ERR if there is an error opening the file in which case
2356 * errno will be set to the error or E_PGN_INVALID if 'mode' is an invalid
2357 * mode or if 'filename' is not a regular file.
2360 pgn_open (const char *filename
, const char *mode
, PGN_FILE
** result
)
2362 FILE *fp
= NULL
, *tfp
= NULL
, *pfp
= NULL
;
2363 char buf
[PATH_MAX
], *p
;
2369 int ret
= E_PGN_ERR
;
2373 PGN_DUMP ("%s:%d: BEGIN opening %s\n", __FILE__
, __LINE__
, filename
);
2376 if (!filename
|| !mode
)
2377 return E_PGN_INVALID
;
2379 if (strcmp (mode
, "r") == 0)
2381 else if (strcmp (mode
, "w") == 0)
2383 else if (strcmp (mode
, "a") == 0)
2390 return E_PGN_INVALID
;
2393 pgn
= calloc (1, sizeof (PGN_FILE
));
2398 if (strcmp (filename
, "-") != 0)
2400 if (m
&& access (filename
, R_OK
) == -1)
2403 if (stat (filename
, &st
) == -1 && !m
&& errno
!= ENOENT
)
2406 if (m
&& !S_ISREG (st
.st_mode
))
2408 ret
= E_PGN_INVALID
;
2412 if ((cmd
= compression_cmd (filename
, m
)) != NULL
)
2418 snprintf (tmp
, sizeof (tmp
), "cboard.XXXXXX");
2420 if (tmpnam (tmp
) == NULL
)
2426 if (append
&& access (filename
, R_OK
) == 0)
2433 cmd
= compression_cmd (filename
, 1);
2434 if ((pfp
= popen (cmd
, "r")) == NULL
)
2438 tmode
= umask (600);
2444 if ((fd
= open (tmp
, O_RDWR
| O_EXCL
| O_CREAT
, 0600)) == -1)
2447 if ((tfp
= fdopen (fd
, "a+")) == NULL
)
2450 while ((p
= fgets (buf
, sizeof (buf
), pfp
)) != NULL
)
2451 fprintf (tfp
, "%s", p
);
2456 pgn
->tmpfile
= strdup (tmp
);
2460 if ((pfp
= popen (cmd
, m
? "r" : "w")) == NULL
)
2466 mode_t tmode
= umask (600);
2474 open (tmp
, O_RDWR
| O_EXCL
| O_CREAT
| O_TRUNC
,
2478 if ((tfp
= fdopen (fd
, "w+")) == NULL
)
2481 while ((p
= fgets (buf
, sizeof (buf
), pfp
)) != NULL
)
2482 fprintf (tfp
, "%s", p
);
2487 pgn
->tmpfile
= strdup (tmp
);
2494 if ((fp
= fopen (filename
, mode
)) == NULL
)
2504 if (*filename
!= '/')
2506 if (getcwd (buf
, sizeof (buf
)) == NULL
)
2509 free (pgn
->tmpfile
);
2514 asprintf (&p
, "%s/%s", buf
, filename
);
2518 pgn
->filename
= strdup (filename
);
2537 * Returns E_PGN_OK if 'filename' is a recognized compressed filetype or
2541 pgn_is_compressed (const char *filename
)
2543 char *s
= compression_cmd (filename
, 0);
2544 pgn_error_t e
= s
? E_PGN_OK
: E_PGN_ERR
;
2551 * Gets the value of config flag 'f'. The next argument should be a pointer of
2552 * the config type which is set to the value of 'f'. Returns E_PGN_ERR if 'f'
2553 * is an invalid flag or E_PGN_OK on success.
2556 pgn_config_get (pgn_config_flag f
, ...)
2561 pgn_progress
*progress
;
2567 case PGN_STRICT_CASTLING
:
2568 intval
= va_arg (ap
, int *);
2569 *intval
= pgn_config
.strict_castling
;
2573 intval
= va_arg (ap
, int *);
2574 *intval
= pgn_config
.reduced
;
2578 intval
= va_arg (ap
, int *);
2579 *intval
= pgn_config
.mpl
;
2582 case PGN_STOP_ON_ERROR
:
2583 intval
= va_arg (ap
, int *);
2584 *intval
= pgn_config
.stop
;
2588 longval
= va_arg (ap
, long *);
2589 *longval
= pgn_config
.stop
;
2592 case PGN_PROGRESS_FUNC
:
2593 progress
= va_arg (ap
, pgn_progress
*);
2594 *progress
= pgn_config
.pfunc
;
2599 intval
= va_arg (ap
, int *);
2600 *intval
= dumptofile
;
2612 * Sets config flag 'f' to the next argument. Returns E_PGN_OK on success or
2613 * E_PGN_ERR if 'f' is an invalid flag or E_PGN_INVALID if 'val' is an invalid
2617 pgn_config_set (pgn_config_flag f
, ...)
2628 n
= va_arg (ap
, int);
2630 if (n
!= 1 && n
!= 0)
2632 ret
= E_PGN_INVALID
;
2636 pgn_config
.reduced
= n
;
2639 n
= va_arg (ap
, int);
2643 ret
= E_PGN_INVALID
;
2649 case PGN_STOP_ON_ERROR
:
2650 n
= va_arg (ap
, int);
2652 if (n
!= 1 && n
!= 0)
2654 ret
= E_PGN_INVALID
;
2658 pgn_config
.stop
= n
;
2661 n
= va_arg (ap
, long);
2662 pgn_config
.progress
= n
;
2664 case PGN_PROGRESS_FUNC
:
2665 pgn_config
.pfunc
= va_arg (ap
, pgn_progress
);
2667 case PGN_STRICT_CASTLING
:
2668 n
= va_arg (ap
, int);
2669 pgn_config
.strict_castling
= n
;
2673 n
= va_arg (ap
, int);
2674 dumptofile
= (n
> 0) ? 1 : 0;
2687 * Writes a PGN formatted game 'g' to a file which was opened with pgn_open().
2688 * See 'pgn_config_flag' for output options. Returns E_PGN_ERR if there was a
2689 * memory allocation or write error and E_PGN_OK on success. It is important
2690 * to use pgn_close() afterwards if the file is a recognized compressed file
2691 * type otherwise the created temporary file wont be copied to the destination
2695 pgn_write (PGN_FILE
* pgn
, GAME g
)
2703 pgn_write_turn
= (TEST_FLAG (g
->flags
, GF_BLACK_OPENING
)) ? BLACK
: WHITE
;
2704 pgn_tag_sort (g
->tag
);
2707 PGN_DUMP ("%s:%d: writing tag section\n", __FILE__
, __LINE__
);
2710 for (i
= 0; g
->tag
[i
]; i
++)
2713 char tbuf
[11] = { 0 };
2717 if (pgn_config
.reduced
&& i
== 7)
2720 if (strcmp (g
->tag
[i
]->name
, "Date") == 0)
2722 memset (&tp
, 0, sizeof (struct tm
));
2724 if (strptime (g
->tag
[i
]->value
, PGN_TIME_FORMAT
, &tp
) != NULL
)
2726 len
= strftime (tbuf
, sizeof (tbuf
), PGN_TIME_FORMAT
, &tp
) + 1;
2728 if ((tmp
= strdup (tbuf
)) == NULL
)
2731 free (g
->tag
[i
]->value
);
2732 g
->tag
[i
]->value
= tmp
;
2735 else if (strcmp (g
->tag
[i
]->name
, "Event") == 0)
2737 if (g
->tag
[i
]->value
[0] == '\0')
2739 if ((tmp
= strdup ("?")) == NULL
)
2742 free (g
->tag
[i
]->value
);
2743 g
->tag
[i
]->value
= tmp
;
2746 else if (strcmp (g
->tag
[i
]->name
, "Site") == 0)
2748 if (g
->tag
[i
]->value
[0] == '\0')
2750 if ((tmp
= strdup ("?")) == NULL
)
2753 free (g
->tag
[i
]->value
);
2754 g
->tag
[i
]->value
= tmp
;
2757 else if (strcmp (g
->tag
[i
]->name
, "Round") == 0)
2759 if (g
->tag
[i
]->value
[0] == '\0')
2761 if ((tmp
= strdup ("?")) == NULL
)
2764 free (g
->tag
[i
]->value
);
2765 g
->tag
[i
]->value
= tmp
;
2768 else if (strcmp (g
->tag
[i
]->name
, "Result") == 0)
2770 if (g
->tag
[i
]->value
[0] == '\0')
2772 if ((tmp
= strdup ("*")) == NULL
)
2775 free (g
->tag
[i
]->value
);
2776 g
->tag
[i
]->value
= tmp
;
2779 else if (strcmp (g
->tag
[i
]->name
, "Black") == 0)
2781 if (g
->tag
[i
]->value
[0] == '\0')
2783 if ((tmp
= strdup ("?")) == NULL
)
2786 free (g
->tag
[i
]->value
);
2787 g
->tag
[i
]->value
= tmp
;
2790 else if (strcmp (g
->tag
[i
]->name
, "White") == 0)
2792 if (g
->tag
[i
]->value
[0] == '\0')
2794 if ((tmp
= strdup ("?")) == NULL
)
2797 free (g
->tag
[i
]->value
);
2798 g
->tag
[i
]->value
= tmp
;
2802 escaped
= pgn_tag_add_escapes (g
->tag
[i
]->value
);
2803 fprintf (pgn
->fp
, "[%s \"%s\"]\n", g
->tag
[i
]->name
,
2804 (g
->tag
[i
]->value
&& g
->tag
[i
]->value
[0]) ? escaped
: "");
2809 PGN_DUMP ("%s:%d: writing move section\n", __FILE__
, __LINE__
);
2811 Fputc ('\n', pgn
->fp
, &len
);
2813 ravlevel
= pgn_mpl
= 0;
2815 if (pgn_history_total (g
->hp
) && pgn_write_turn
== BLACK
)
2816 putstring (pgn
->fp
, (char *) "1...", &len
);
2818 write_all_move_text (pgn
->fp
, g
->hp
, 1, &len
);
2820 Fputc (' ', pgn
->fp
, &len
);
2821 putstring (pgn
->fp
, g
->tag
[6]->value
, &len
);
2822 putstring (pgn
->fp
, (char *) "\n\n", &len
);
2824 if (!pgn_config
.reduced
)
2825 CLEAR_FLAG (g
->flags
, GF_PERROR
);
2831 * Clears the enpassant flag for all positions on board 'b'. Returns nothing.
2834 pgn_reset_enpassant (BOARD b
)
2839 PGN_DUMP ("%s:%d: resetting enpassant\n", __FILE__
, __LINE__
);
2842 for (r
= 0; r
< 8; r
++)
2844 for (c
= 0; c
< 8; c
++)
2845 b
[r
][c
].enpassant
= 0;