1 /* vim:tw=78:ts=8:sw=4:set ft=c: */
3 Copyright (C) 2002-2015 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
*);
66 static int Fgetc(FILE *fp
)
70 if ((c
= fgetc(fp
)) != EOF
) {
71 if (pgn_config
.progress
&& pgn_config
.pfunc
) {
72 if (!(ftell(fp
) % pgn_config
.progress
))
73 pgn_config
.pfunc(pgn_fsize
, ftell(fp
));
80 static int Ungetc(int c
, FILE *fp
)
87 return "libchess " PACKAGE_VERSION
;
90 static char *trim(char *str
)
100 for (i
= strlen(str
) - 1; isspace(str
[i
]); i
--)
106 static char *itoa(long n
, char *buf
)
108 sprintf(buf
, "%li", n
);
113 * Clears the valid move flag for all positions on board 'b'. Returns nothing.
115 void pgn_reset_valid_moves(BOARD b
)
120 PGN_DUMP("%s:%d: resetting valid moves\n", __FILE__
, __LINE__
);
123 for (row
= 0; row
< 8; row
++) {
124 for (col
= 0; col
< 8; col
++)
125 b
[row
][col
].valid
= 0;
130 * Toggles g->turn. Returns nothing.
132 void pgn_switch_turn(GAME g
)
134 g
->turn
= (g
->turn
== WHITE
) ? BLACK
: WHITE
;
138 * Toggles g->side and switches the White and Black roster tags. Returns
141 void pgn_switch_side(GAME g
, int t
)
144 char *w
= g
->tag
[4]->value
;
146 g
->tag
[4]->value
= g
->tag
[5]->value
;
147 g
->tag
[5]->value
= w
;
154 * Creates a FEN tag from the current game 'g', history move (g->hindex) and
155 * board 'b'. Returns a FEN tag.
157 char *pgn_game_to_fen(GAME g
, BOARD b
)
162 char buf
[MAX_PGN_LINE_LEN
] = {0}, *p
;
163 int oldturn
= g
->turn
;
164 char enpassant
[3] = {0}, *e
;
168 PGN_DUMP("%s:%d: creating FEN tag\n", __FILE__
, __LINE__
);
171 for (i
= pgn_history_total(g
->hp
); i
>= g
->hindex
- 1; i
--)
176 for (row
= 0; row
< 8; row
++) {
179 for (col
= 0; col
< 8; col
++) {
180 if (b
[row
][col
].enpassant
) {
181 b
[row
][col
].icon
= pgn_int_to_piece(WHITE
, OPEN_SQUARE
);
184 *e
++ = ('0' + 8) - row
;
188 if (pgn_piece_to_int(b
[row
][col
].icon
) == OPEN_SQUARE
) {
198 *p
++ = b
[row
][col
].icon
;
212 *p
++ = (g
->turn
== WHITE
) ? 'w' : 'b';
215 if (TEST_FLAG(g
->flags
, GF_WK_CASTLE
) && pgn_piece_to_int(b
[7][7].icon
) ==
216 ROOK
&& isupper(b
[7][7].icon
) && pgn_piece_to_int(b
[7][4].icon
) ==
217 KING
&& isupper(b
[7][4].icon
)) {
222 if (TEST_FLAG(g
->flags
, GF_WQ_CASTLE
) && pgn_piece_to_int(b
[7][0].icon
) ==
223 ROOK
&& isupper(b
[7][0].icon
) && pgn_piece_to_int(b
[7][4].icon
) ==
224 KING
&& isupper(b
[7][4].icon
)) {
229 if (TEST_FLAG(g
->flags
, GF_BK_CASTLE
) && pgn_piece_to_int(b
[0][7].icon
) ==
230 ROOK
&& islower(b
[0][7].icon
) && pgn_piece_to_int(b
[0][4].icon
) ==
231 KING
&& islower(b
[0][4].icon
)) {
236 if (TEST_FLAG(g
->flags
, GF_BQ_CASTLE
) && pgn_piece_to_int(b
[0][0].icon
) ==
237 ROOK
&& islower(b
[0][0].icon
) && pgn_piece_to_int(b
[0][4].icon
) ==
238 KING
&& islower(b
[0][4].icon
)) {
260 strcat(p
, itoa(g
->ply
, tmp
));
261 p
= buf
+ strlen(buf
);
265 i
= (g
->hindex
+ 1) / 2;
267 strcat(p
, itoa((g
->hindex
/ 2) + (g
->hindex
% 2), tmp
));
270 return buf
[0] ? strdup (buf
) : NULL
;
274 * Returns the total number of moves in 'h' or 0 if there are none.
276 int pgn_history_total(HISTORY
**h
)
283 for (i
= 0; h
[i
]; i
++);
288 * Deallocates all of the history data from position 'start' in the array 'h'.
291 void pgn_history_free(HISTORY
**h
, int start
)
296 PGN_DUMP("%s:%d: freeing history\n", __FILE__
, __LINE__
);
299 if (!h
|| start
> pgn_history_total(h
))
305 for (i
= start
; h
[i
]; i
++) {
307 pgn_history_free(h
[i
]->rav
, 0);
321 * Returns the history ply 'n' from 'h'. If 'n' is out of range then NULL is
324 HISTORY
*pgn_history_by_n(HISTORY
**h
, int n
)
326 if (n
< 0 || n
> pgn_history_total(h
) - 1)
333 * Appends move 'm' to game 'g' history pointer and creates a FEN tag for the
334 * current game state using board 'b'. The FEN tag makes things faster than
335 * validating the entire move history by validating only the current move to
336 * the previous moves FEN tag. The history pointer may be a in a RAV so
337 * g->rav.hp is also updated to the new (realloc()'ed) pointer. If not in a
338 * RAV then g->history will be updated. Returns E_PGN_ERR if realloc() failed
339 * or E_PGN_OK on success.
341 pgn_error_t
pgn_history_add(GAME g
, BOARD b
, const char *m
)
343 int t
= pgn_history_total(g
->hp
);
345 HISTORY
**h
= NULL
, *tmp
;
346 int ri
= (g
->ravlevel
) ? g
->rav
[g
->ravlevel
- 1].hindex
: 0;
349 PGN_DUMP("%s:%d: adding '%s' to move history\n", __FILE__
, __LINE__
, m
);
353 o
= g
->rav
[g
->ravlevel
- 1].hp
[ri
-1]->rav
- g
->hp
;
355 o
= g
->history
- g
->hp
;
357 if ((h
= realloc(g
->hp
, (t
+ 2) * sizeof(HISTORY
*))) == NULL
)
363 g
->rav
[g
->ravlevel
- 1].hp
[ri
-1]->rav
= g
->hp
+ o
;
365 g
->history
= g
->hp
+ o
;
367 if ((g
->hp
[t
] = calloc(1, sizeof(HISTORY
))) == NULL
)
370 if ((g
->hp
[t
]->move
= strdup(m
)) == NULL
) {
379 g
->hindex
= pgn_history_total(g
->hp
);
381 tmp
->fen
= pgn_game_to_fen(g
, b
);
387 * Resets the game 'g' using board 'b' up to history move (g->hindex) 'n'.
388 * Returns E_PGN_OK on success or E_PGN_PARSE if there was a FEN tag but the
389 * parsing of it failed. Or returns E_PGN_INVALID if somehow the move failed
390 * validation while resetting.
392 pgn_error_t
pgn_board_update(GAME g
, BOARD b
, int n
)
396 int p_error
= TEST_FLAG(g
->flags
, GF_PERROR
);
397 int black_opening
= TEST_FLAG(g
->flags
, GF_BLACK_OPENING
);
400 PGN_DUMP("%s:%d: updating board\n", __FILE__
, __LINE__
);
403 if (!g
->ravlevel
&& TEST_FLAG(g
->flags
, GF_BLACK_OPENING
))
409 SET_FLAG(g
->flags
, GF_WK_CASTLE
|GF_WQ_CASTLE
|GF_WQ_CASTLE
|
410 GF_BK_CASTLE
|GF_BQ_CASTLE
);
414 pgn_board_init_fen(g
, tb
, g
->rav
[g
->ravlevel
- 1].fen
);
415 else if (pgn_tag_find(g
->tag
, "FEN") != -1 &&
416 pgn_board_init_fen(g
, tb
, NULL
))
420 HISTORY
*h
= pgn_history_by_n(g
->hp
, n
-1);
423 ret
= pgn_board_init_fen(g
, tb
, h
->fen
);
424 if (ret
== E_PGN_OK
) {
425 h
= pgn_history_by_n(g
->hp
, n
);
429 ret
= pgn_parse_move(g
, tb
, &h
->move
, &frfr
);
430 if (ret
== E_PGN_OK
) {
431 h
= pgn_history_by_n(g
->hp
, n
-1);
432 ret
= pgn_board_init_fen(g
, tb
, h
->fen
);
442 memcpy(b
, tb
, sizeof(BOARD
));
445 SET_FLAG(g
->flags
, GF_PERROR
);
448 SET_FLAG(g
->flags
, GF_BLACK_OPENING
);
454 * Updates the game 'g' using board 'b' to the next 'n'th history move.
457 void pgn_history_prev(GAME g
, BOARD b
, int n
)
459 if (g
->hindex
- n
< 0) {
461 g
->hindex
= pgn_history_total(g
->hp
);
468 pgn_board_update(g
, b
, g
->hindex
);
472 * Updates the game 'g' using board 'b' to the previous 'n'th history move.
475 void pgn_history_next(GAME g
, BOARD b
, int n
)
477 if (g
->hindex
+ n
> pgn_history_total(g
->hp
)) {
481 g
->hindex
= pgn_history_total(g
->hp
);
486 pgn_board_update(g
, b
, g
->hindex
);
490 * Converts the character piece 'p' to an integer. Returns the integer
491 * associated with 'p' or E_PGN_ERR if 'p' is invalid.
493 int pgn_piece_to_int(int p
)
518 PGN_DUMP("%s:%d: invalid piece '%c'\n", __FILE__
, __LINE__
, p
);
524 * Converts the integer piece 'n' to a character whose turn is 'turn'. WHITE
525 * piece are uppercase and BLACK pieces are lowercase. Returns the character
526 * associated with 'n' or E_PGN_ERR if 'n' is an invalid piece.
528 pgn_error_t
pgn_int_to_piece(char turn
, int n
)
556 PGN_DUMP("%s:%d: unknown piece integer %i\n", __FILE__
,
563 return (turn
== WHITE
) ? toupper(p
) : p
;
567 * Finds a tag 'name' in the structure array 't'. Returns the location in the
568 * array of the found tag or E_PGN_ERR if the tag could not be found.
570 pgn_error_t
pgn_tag_find(TAG
**t
, const char *name
)
574 for (i
= 0; t
[i
]; i
++) {
575 if (strcasecmp(t
[i
]->name
, name
) == 0)
582 static pgn_error_t
remove_tag(TAG
***array
, const char *tag
)
585 int n
= pgn_tag_find(tags
, tag
);
591 for (i
= t
= 0; tags
[i
]; i
++) {
594 free(tags
[i
]->value
);
602 tags
= realloc(*array
, (t
+ 1) * sizeof(TAG
*));
606 PGN_DUMP("%s:%d: removed tag: name='%s'\n", __FILE__
, __LINE__
, tag
);
611 static int tag_compare(const void *a
, const void *b
)
616 return strcmp((*ta
)->name
, (*tb
)->name
);
620 * Sorts a tag array. The first seven tags are in order of the PGN standard so
621 * don't sort'em. Returns nothing.
623 void pgn_tag_sort(TAG
**tags
)
625 if (pgn_tag_total(tags
) <= 7)
628 qsort(tags
+ 7, pgn_tag_total(tags
) - 7, sizeof(TAG
*), tag_compare
);
632 * Returns the total number of tags in 't' or 0 if 't' is NULL.
634 int pgn_tag_total(TAG
**tags
)
648 * Adds a tag 'name' with value 'value' to the pointer to array of TAG
649 * pointers 'dst'. If a duplicate tag 'name' was found then the existing tag
650 * is updated to the new 'value'. If 'value' is NULL, the tag is removed.
651 * Returns E_PGN_ERR if there was a memory allocation error or E_PGN_OK on
654 pgn_error_t
pgn_tag_add(TAG
***dst
, char *name
, char *value
)
659 int t
= pgn_tag_total(tdata
);
662 PGN_DUMP("%s:%d: adding tag\n", __FILE__
, __LINE__
);
673 // Find an existing tag with 'name'.
674 for (i
= 0; i
< t
; i
++) {
677 if (strcasecmp(tdata
[i
]->name
, name
) == 0) {
679 if ((tmp
= strdup(value
)) == NULL
)
683 remove_tag(dst
, name
);
687 free(tdata
[i
]->value
);
688 tdata
[i
]->value
= tmp
;
694 if ((a
= realloc(tdata
, (t
+ 2) * sizeof(TAG
*))) == NULL
)
699 if ((tdata
[t
] = malloc(sizeof(TAG
))) == NULL
)
702 if ((tdata
[t
]->name
= strdup(name
)) == NULL
) {
708 if ((tdata
[t
]->value
= strdup(value
)) == NULL
) {
709 free(tdata
[t
]->name
);
715 tdata
[t
]->value
= NULL
;
717 tdata
[t
]->name
[0] = toupper(tdata
[t
]->name
[0]);
722 PGN_DUMP("%s:%d: added tag: name='%s' value='%s'\n", __FILE__
, __LINE__
,
723 name
, (value
) ? value
: "null");
729 static char *remove_tag_escapes(const char *str
)
732 int len
= strlen(str
);
733 char buf
[MAX_PGN_LINE_LEN
] = {0};
735 for (i
= n
= 0; i
< len
; i
++, n
++) {
747 return buf
[0] ? strdup (buf
) : NULL
;
751 * Resets or initializes a new game board 'b'. Returns nothing.
753 void pgn_board_init(BOARD b
)
758 PGN_DUMP("%s:%d: initializing board\n", __FILE__
, __LINE__
);
761 memset(b
, 0, sizeof(BOARD
));
763 for (row
= 0; row
< 8; row
++) {
764 for (col
= 0; col
< 8; col
++) {
797 b
[row
][col
].icon
= (row
< 2) ? c
: toupper(c
);
803 * Adds the standard PGN roster tags to game 'g'.
805 static void set_default_tags(GAME g
)
810 struct passwd
*pw
= getpwuid(getuid());
811 char host
[64] = { 0 };
814 tp
= localtime(&now
);
815 strftime(tbuf
, sizeof(tbuf
), PGN_TIME_FORMAT
, tp
);
817 /* The standard seven tag roster (in order of appearance). */
818 if (pgn_tag_add(&g
->tag
, "Event", "?") != E_PGN_OK
)
819 warn("pgn_tag_add()");
821 gethostname (host
, sizeof(host
)-1);
822 if (pgn_tag_add(&g
->tag
, "Site", host
) != E_PGN_OK
)
823 warn("pgn_tag_add()");
825 if (pgn_tag_add(&g
->tag
, "Date", tbuf
) != E_PGN_OK
)
826 warn("pgn_tag_add()");
828 if (pgn_tag_add(&g
->tag
, "Round", "-") != E_PGN_OK
)
829 warn("pgn_tag_add()");
831 if (pgn_tag_add(&g
->tag
, "White", pw
->pw_gecos
) != E_PGN_OK
)
832 warn("pgn_tag_add()");
834 if (pgn_tag_add(&g
->tag
, "Black", "?") != E_PGN_OK
)
835 warn("pgn_tag_add()");
837 if (pgn_tag_add(&g
->tag
, "Result", "*") != E_PGN_OK
)
838 warn("pgn_tag_add()");
842 * Frees a TAG array. Returns nothing.
844 void pgn_tag_free(TAG
**tags
)
847 int t
= pgn_tag_total(tags
);
850 PGN_DUMP("%s:%d: freeing tags\n", __FILE__
, __LINE__
);
856 for (i
= 0; i
< t
; i
++) {
858 free(tags
[i
]->value
);
866 * Frees a single game 'g'. Returns nothing.
868 void pgn_free(GAME g
)
871 PGN_DUMP("%s:%d: freeing game\n", __FILE__
, __LINE__
);
873 pgn_history_free(g
->history
, 0);
875 pgn_tag_free(g
->tag
);
878 for (g
->ravlevel
--; g
->ravlevel
>= 0; g
->ravlevel
--)
879 free(g
->rav
[g
->ravlevel
].fen
);
888 * Frees all games in the global 'game' array. Returns nothing.
895 PGN_DUMP("%s:%d: freeing game data\n", __FILE__
, __LINE__
);
898 for (i
= 0; i
< gtotal
; i
++) {
908 static void reset_game_data()
911 PGN_DUMP("%s:%d: resetting game data\n", __FILE__
, __LINE__
);
917 static void skip_leading_space(FILE *fp
)
921 while ((c
= Fgetc(fp
)) != EOF
&& !feof(fp
)) {
930 * PGN move text section.
932 static int move_text(GAME g
, FILE *fp
)
934 char m
[MAX_SAN_MOVE_LEN
+ 1] = {0}, *p
;
939 g
->oflags
= g
->flags
;
941 while((c
= Fgetc(fp
)) != EOF
) {
942 if (isdigit(c
) || isspace(c
) || c
== '.')
950 if (fscanf(fp
, " %[a-hPpRrNnBbQqKk1-9#+=Ox-]%n", m
, &count
) != 1)
953 m
[MAX_SAN_MOVE_LEN
] = 0;
956 if (pgn_parse_move(g
, pgn_board
, &p
, &frfr
)) {
963 PGN_DUMP("%s\n%s", p
, debug_board(pgn_board
));
966 pgn_history_add(g
, pgn_board
, p
);
974 static void nag_text(GAME g
, FILE *fp
)
977 char nags
[5], *n
= nags
;
980 while ((c
= Fgetc(fp
)) != EOF
&& !isspace(c
)) {
982 while ((c
= Fgetc(fp
)) != EOF
&& isdigit(c
))
990 if ((c
= Fgetc(fp
)) == '!')
1001 else if (c
== '?') {
1002 if ((c
= Fgetc(fp
)) == '?')
1015 else if (c
== '=') {
1016 if ((c
= Fgetc(fp
)) == '+')
1025 else if (c
== '+') {
1026 if ((t
= Fgetc(fp
)) == '=')
1030 else if (t
== '/') {
1031 if ((i
= Fgetc(fp
)) == '-')
1043 else if (c
== '-') {
1044 if ((t
= Fgetc(fp
)) == '+')
1046 else if (t
== '/') {
1047 if ((i
= Fgetc(fp
)) == '+')
1064 nag
= (nags
[0]) ? atoi(nags
) : 0;
1066 if (!nag
|| nag
< 0 || nag
> 255)
1069 // FIXME -1 is because move_text() increments g->hindex. The NAG
1070 // annoatation isnt guaranteed to be after the move text in import format.
1071 for (i
= 0; i
< MAX_PGN_NAG
; i
++) {
1072 if (g
->hp
[g
->hindex
- 1]->nag
[i
])
1075 g
->hp
[g
->hindex
- 1]->nag
[i
] = nag
;
1079 skip_leading_space(fp
);
1083 * PGN move annotation.
1085 static int annotation_text(GAME g
, FILE *fp
, int terminator
)
1087 int c
, lastchar
= 0;
1089 int hindex
= pgn_history_total(g
->hp
) - 1;
1090 char buf
[MAX_PGN_LINE_LEN
], *a
= buf
;
1092 skip_leading_space(fp
);
1094 while ((c
= Fgetc(fp
)) != EOF
&& c
!= terminator
) {
1098 if (isspace(c
) && isspace(lastchar
))
1101 if (len
+ 1 == sizeof(buf
))
1104 *a
++ = lastchar
= c
;
1114 * This annotation is before any move text or NAg-> Allocate a new move.
1116 if (!g
->hp
[hindex
]) {
1117 if ((g
->hp
[hindex
] = calloc(1, sizeof(HISTORY
))) == NULL
)
1121 if ((g
->hp
[hindex
]->comment
= strdup(buf
)) == NULL
)
1130 static int tag_text(GAME g
, FILE *fp
)
1132 char name
[LINE_MAX
] = {0}, *n
= name
;
1133 char value
[LINE_MAX
] = {0}, *v
= value
;
1138 skip_leading_space(fp
);
1140 /* The tag name is up until the first whitespace. */
1141 while ((c
= Fgetc(fp
)) != EOF
&& !isspace(c
))
1145 *name
= toupper(*name
);
1146 skip_leading_space(fp
);
1148 /* The value is until the first closing bracket. */
1149 while ((c
= Fgetc(fp
)) != EOF
&& c
!= ']') {
1150 if (i
++ == '\0' && c
== '\"')
1153 if (c
== '\n' || c
== '\t')
1156 if (c
== ' ' && lastchar
== ' ')
1159 lastchar
= *v
++ = c
;
1164 while (isspace(*--v
))
1170 if (value
[0] == '\0') {
1171 if (strcmp(name
, "Result") == 0)
1179 tmp
= remove_tag_escapes (value
);
1180 strncpy(value
, tmp
, sizeof(value
)-1);
1184 * See eog_text() for an explanation.
1186 if (strcmp(name
, "Result") == 0) {
1187 if (strcmp(value
, "1/2-1/2") == 0) {
1188 if (pgn_tag_add(&g
->tag
, name
, value
) != E_PGN_OK
)
1189 warn("pgn_tag_add()");
1193 if (pgn_tag_add(&g
->tag
, name
, value
) != E_PGN_OK
)
1194 warn("pgn_tag_add()");
1201 * PGN end-of-game marker.
1203 static int eog_text(GAME g
, FILE *fp
)
1206 char buf
[8], *p
= buf
;
1208 while ((c
= Fgetc(fp
)) != EOF
&& !isspace(c
) && i
++ < sizeof(buf
))
1216 if (strcmp(buf
, "1-0") != 0 && strcmp(buf
, "0-1") != 0 &&
1217 strcmp(buf
, "1/2-1/2") != 0 && strcmp(buf
, "*") != 0)
1221 * The eog marker in the move text may not match the actual game result.
1222 * We'll leave it up to the move validator to determine the result unless
1223 * the move validator cannot determine who won and the move text says it's
1226 if (g
->tag
[6]->value
[0] == '*' && strcmp("1/2-1/2", buf
) == 0) {
1227 if (pgn_tag_add(&g
->tag
, "Result", buf
) != E_PGN_OK
)
1228 warn("pgn_tag_add()");
1235 * Parse RAV text and keep track of g->hp. The 'o' argument is the board state
1236 * before the current move (.hindex) was parsed.
1238 static int read_file(FILE *);
1239 static int rav_text(GAME g
, FILE *fp
, int which
, BOARD o
)
1244 // Begin RAV for the current move.
1249 pgn_rav
++; // For detecting parse errors.
1252 * Save the current game state for this RAV depth/level.
1254 if ((r
= realloc(g
->rav
, (g
->ravlevel
+ 1) * sizeof(RAV
))) == NULL
) {
1259 g
->rav
= pgn_rav_p
= r
;
1261 if ((g
->rav
[g
->ravlevel
].fen
= pgn_game_to_fen(g
, pgn_board
))
1267 g
->rav
[g
->ravlevel
].hp
= g
->hp
;
1268 g
->rav
[g
->ravlevel
].hindex
= g
->hindex
;
1269 memcpy(&tg
, &(*g
), sizeof(struct game_s
));
1270 g
->flags
= g
->oflags
;
1271 memcpy(pgn_board
, o
, sizeof(BOARD
));
1273 if ((g
->hp
[g
->hindex
- 1]->rav
= calloc(1, sizeof(HISTORY
*))) == NULL
) {
1279 * pgn_history_add() will now append to the new history pointer that
1280 * is g->hp[previous_move]->rav.
1282 g
->hp
= g
->hp
[g
->hindex
- 1]->rav
;
1285 * Reset. Will be restored later from 'tg' which is a local variable
1286 * so recursion is possible.
1292 * Undo move_text()'s switch.
1297 * Now continue as normal as if there were no RAV. Moves will be
1298 * parsed and appended to the new history pointer.
1304 * read_file() has returned. This means that a RAV has ended by this
1305 * function returning -1 (see below). So we restore the game state
1306 * (tg) that was saved before calling read_file().
1308 pgn_board_init_fen(&tg
, pgn_board
, g
->rav
[tg
.ravlevel
].fen
);
1309 free(g
->rav
[tg
.ravlevel
].fen
);
1310 memcpy(&(*g
), &tg
, sizeof(struct game_s
));
1314 * The end of a RAV. This makes the read_file() that called this function
1315 * return (see above and the switch statement in read_file()).
1317 else if (which
== ')') {
1318 pgn_rav
--; // For detecting parse errors.
1319 return (pgn_rav
< 0) ? 1 : -1;
1327 * See pgn_board_init_fen(). Returns E_PGN_PARSE on parse error. 0 may be
1328 * returned on success when there is no move count in the FEN tag otherwise
1329 * the move count is returned.
1331 static int parse_fen_line(BOARD b
, unsigned *flags
, char *turn
, char *ply
,
1335 char line
[LINE_MAX
] = {0}, *s
;
1336 int row
= 8, col
= 1;
1339 PGN_DUMP("%s:%d: FEN line is '%s'\n", __FILE__
, __LINE__
, str
);
1341 strncpy(line
, str
, sizeof(line
)-1);
1343 pgn_reset_enpassant(b
);
1345 while ((tmp
= strsep(&s
, "/")) != NULL
) {
1348 if (!VALIDFILE(row
))
1355 if (isdigit(*tmp
)) {
1361 for (; n
; --n
, col
++)
1362 b
[RANKTOBOARD(row
)][FILETOBOARD(col
)].icon
=
1363 pgn_int_to_piece(WHITE
, OPEN_SQUARE
);
1365 else if (pgn_piece_to_int(*tmp
) != -1)
1366 b
[RANKTOBOARD(row
)][FILETOBOARD(col
++)].icon
= *tmp
;
1393 while (*tmp
&& *tmp
!= ' ') {
1396 SET_FLAG(*flags
, GF_WK_CASTLE
);
1399 SET_FLAG(*flags
, GF_WQ_CASTLE
);
1402 SET_FLAG(*flags
, GF_BK_CASTLE
);
1405 SET_FLAG(*flags
, GF_BQ_CASTLE
);
1418 if (!VALIDCOL(*tmp
))
1423 if (!VALIDROW(*tmp
))
1426 if (!VALIDRANK(*tmp
))
1429 row
= RANKTOBOARD(*tmp
++);
1430 b
[row
][col
].enpassant
= 1;
1431 SET_FLAG(*flags
, GF_ENPASSANT
);
1442 while (*tmp
&& isdigit(*tmp
))
1452 * It initializes the board (b) to the FEN tag (if found) and sets the
1453 * castling and enpassant info for the game 'g'. If 'fen' is set it should be
1454 * a fen tag and will be parsed rather than the game 'g'.tag FEN tag-> Returns
1455 * E_PGN_OK on success or if there was both a FEN and SetUp tag with the SetUp
1456 * tag set to 0. Returns E_PGN_PARSE if there was a FEN parse error, E_PGN_ERR
1457 * if there was no FEN tag or there was a SetUp tag with a value of 0. Returns
1458 * E_PGN_OK on success.
1460 pgn_error_t
pgn_board_init_fen(GAME g
, BOARD b
, char *fen
)
1465 char turn
= g
->turn
;
1469 PGN_DUMP("%s:%d: initializing board from FEN\n", __FILE__
, __LINE__
);
1471 pgn_board_init(tmpboard
);
1474 n
= pgn_tag_find(g
->tag
, "Setup");
1475 i
= pgn_tag_find(g
->tag
, "FEN");
1479 * If the FEN tag exists and there is no SetUp tag go ahead and parse it.
1480 * If there is a SetUp tag only parse the FEN tag if the value is 1.
1482 if ((n
>= 0 && i
>= 0 && atoi(g
->tag
[n
]->value
) == 1)
1483 || (i
>= 0 && n
== -1) || fen
) {
1484 if ((n
= parse_fen_line(tmpboard
, &flags
, &turn
, &ply
,
1485 (fen
) ? fen
: g
->tag
[i
]->value
)) != E_PGN_OK
)
1488 memcpy(b
, tmpboard
, sizeof(BOARD
));
1489 CLEAR_FLAG(g
->flags
, GF_WK_CASTLE
);
1490 CLEAR_FLAG(g
->flags
, GF_WQ_CASTLE
);
1491 CLEAR_FLAG(g
->flags
, GF_BK_CASTLE
);
1492 CLEAR_FLAG(g
->flags
, GF_BQ_CASTLE
);
1499 return (i
>= 0 && n
>= 0) ? E_PGN_OK
: E_PGN_ERR
;
1505 * Allocates a new game and increments 'gtotal'. 'gindex' is then set to the
1506 * new game. Returns E_PGN_ERR if there was a memory allocation error or
1507 * E_PGN_OK on success.
1509 pgn_error_t
pgn_new_game()
1516 PGN_DUMP("%s:%d: allocating new game\n", __FILE__
, __LINE__
);
1520 if ((g
= realloc(game
, t
* sizeof(GAME
))) == NULL
) {
1527 if ((newg
= calloc(1, sizeof(struct game_s
))) == NULL
) {
1532 game
[gindex
] = newg
;
1534 if ((game
[gindex
]->hp
= calloc(1, sizeof(HISTORY
*))) == NULL
) {
1540 game
[gindex
]->hp
[0] = NULL
;
1541 game
[gindex
]->history
= game
[gindex
]->hp
;
1542 game
[gindex
]->side
= game
[gindex
]->turn
= WHITE
;
1543 SET_FLAG(game
[gindex
]->flags
, GF_WK_CASTLE
|GF_WQ_CASTLE
|GF_WQ_CASTLE
|
1544 GF_BK_CASTLE
|GF_BQ_CASTLE
);
1545 pgn_board_init(pgn_board
);
1546 set_default_tags(game
[gindex
]);
1551 static int read_file(FILE *fp
)
1554 char buf
[LINE_MAX
] = {0}, *p
= buf
;
1557 int parse_error
= 0;
1566 * A parse error may have occured at EOF.
1569 pgn_ret
= E_PGN_PARSE
;
1574 SET_FLAG(game
[gindex
]->flags
, GF_PERROR
);
1577 if ((c
= Fgetc(fp
)) == EOF
) {
1595 nextchar
= Fgetc(fp
);
1596 Ungetc(nextchar
, fp
);
1599 * If there was a move text parsing error, keep reading until the end
1600 * of the current game discarding the data.
1603 pgn_ret
= E_PGN_PARSE
;
1605 if (game
[gindex
]->ravlevel
)
1608 if (pgn_config
.stop
)
1611 if (c
== '\n' && (nextchar
== '\n' || nextchar
== '\015')) {
1620 // New game reached.
1621 if (c
== '\n' && (nextchar
== '\n' || nextchar
== '\015')) {
1631 * PGN: Application comment. The '%' must be on the first column of
1632 * the line. The comment continues until the end of the current line.
1635 if (lastchar
== '\n' || lastchar
== 0) {
1636 while ((c
= Fgetc(fp
)) != EOF
&& c
!= '\n');
1640 // Not sure what to do here.
1647 if (c
== '<' || c
== '>')
1651 * PGN: Recurrsive Annotated Variation. Read rav_text() for more
1654 if (c
== '(' || c
== ')') {
1655 switch (rav_text(game
[gindex
], fp
, c
, old
)) {
1658 * This is the end of the current RAV. This function has
1659 * been called from rav_text(). Returning from this point
1660 * will put us back in rav_text().
1662 if (game
[gindex
]->ravlevel
> 0)
1666 * We're back at the root move. Continue as normal.
1674 * Continue processing-> Probably the root move.
1679 if (!game
[gindex
]->ravlevel
&& pgn_rav
)
1685 // PGN: Numeric Annotation Glyph.
1686 if (c
== '$' || c
== '!' || c
== '?' || c
== '+' || c
== '-' ||
1687 c
== '~' || c
== '=') {
1689 nag_text(game
[gindex
], fp
);
1694 * PGN: Annotation. The ';' comment continues until the end of the
1695 * current line. The '{' type comment continues until a '}' is
1698 if (c
== '{' || c
== ';') {
1699 annotation_text(game
[gindex
], fp
, (c
== '{') ? '}' : '\n');
1703 // PGN: Roster tag->
1705 // First roster tag found. Initialize the data structures.
1710 if (gtotal
&& pgn_history_total(game
[gindex
]->hp
))
1711 game
[gindex
]->hindex
= pgn_history_total(game
[gindex
]->hp
) - 1;
1713 if (pgn_new_game() != E_PGN_OK
) {
1714 pgn_ret
= E_PGN_ERR
;
1718 memcpy(old
, pgn_board
, sizeof(BOARD
));
1721 if (tag_text(game
[gindex
], fp
))
1722 parse_error
= 1; // FEN tag parse error.
1727 // PGN: End-of-game markers.
1728 if ((isdigit(c
) && (nextchar
== '-' || nextchar
== '/')) || c
== '*') {
1731 if (eog_text(game
[gindex
], fp
)) {
1739 if (!game
[gindex
]->done_fen_tag
) {
1740 if (pgn_tag_find(game
[gindex
]->tag
, "FEN") != -1 &&
1741 pgn_board_init_fen(game
[gindex
], pgn_board
, NULL
)) {
1746 game
[gindex
]->pgn_fen_tag
= pgn_tag_find(game
[gindex
]->tag
, "FEN");
1747 game
[gindex
]->done_fen_tag
= 1;
1754 if ((isdigit(c
) && c
!= '0') || VALIDCOL(c
) || c
== 'N' || c
== 'K'
1755 || c
== 'Q' || c
== 'B' || c
== 'R' || c
== 'P' ||
1759 // PGN: If a FEN tag exists, initialize the board to the value.
1761 if (pgn_tag_find(game
[gindex
]->tag
, "FEN") != E_PGN_ERR
&&
1762 (n
= pgn_board_init_fen(game
[gindex
], pgn_board
,
1763 NULL
)) == E_PGN_PARSE
) {
1768 game
[gindex
]->done_fen_tag
= 1;
1769 game
[gindex
]->pgn_fen_tag
= pgn_tag_find(game
[gindex
]->tag
, "FEN");
1774 * PGN: Import format doesn't require a roster tag section. We've
1775 * arrived to the move text section without any tags so we
1776 * initialize a new game which set's the default tags and any tags
1777 * from the configuration file.
1781 game
[gindex
]->hindex
= pgn_history_total(game
[gindex
]->hp
) - 1;
1783 if (pgn_new_game() != E_PGN_OK
) {
1784 pgn_ret
= E_PGN_ERR
;
1788 memcpy(old
, pgn_board
, sizeof(BOARD
));
1792 memcpy(old
, pgn_board
, sizeof(BOARD
));
1794 if (move_text(game
[gindex
], fp
)) {
1795 if (pgn_tag_add(&game
[gindex
]->tag
, "Result", "*") ==
1797 warn("pgn_tag_add()");
1798 pgn_ret
= E_PGN_ERR
;
1801 SET_FLAG(game
[gindex
]->flags
, GF_PERROR
);
1811 PGN_DUMP("%s:%d: unparsed: '%s'\n", __FILE__
, __LINE__
, buf
);
1813 if (strlen(buf
) + 1 == sizeof(buf
))
1814 bzero(buf
, sizeof(buf
));
1824 * Parses a PGN_FILE which was opened with pgn_open(). If 'pgn' is NULL then a
1825 * single empty game will be allocated. If there is a parsing error
1826 * E_PGN_PARSE is returned, if there was a memory allocation error E_PGN_ERR
1827 * is returned, otherwise E_PGN_OK is returned and the global 'gindex' is set
1828 * to the last parsed game in the file and the global 'gtotal' is set to the
1829 * total number of games in the file. The file should be closed with
1830 * pgn_close() after processing.
1832 pgn_error_t
pgn_parse(PGN_FILE
*pgn
)
1838 pgn_ret
= pgn_new_game();
1844 fseek(pgn
->fp
, 0, SEEK_END
);
1845 pgn_fsize
= ftell(pgn
->fp
);
1846 fseek(pgn
->fp
, 0, SEEK_SET
);
1848 PGN_DUMP("%s:%d: BEGIN parsing->..\n", __FILE__
, __LINE__
);
1851 pgn_ret
= read_file(pgn
->fp
);
1855 PGN_DUMP("%s:%d: END parsing->..\n", __FILE__
, __LINE__
);
1862 gtotal
= gindex
+ 1;
1864 for (i
= 0; i
< gtotal
; i
++) {
1865 game
[i
]->history
= game
[i
]->hp
;
1866 game
[i
]->hindex
= pgn_history_total(game
[i
]->hp
);
1873 * Escape '"' and '\' in tag values.
1875 static char *pgn_tag_add_escapes(const char *str
)
1878 int len
= strlen(str
);
1879 char buf
[MAX_PGN_LINE_LEN
] = {0};
1881 for (i
= n
= 0; i
< len
; i
++, n
++) {
1895 return buf
[0] ? strdup (buf
) : NULL
;
1898 static void Fputc(int c
, FILE *fp
, int *len
)
1902 if (c
!= '\n' && i
+ 1 > 80)
1903 Fputc('\n', fp
, &i
);
1905 if (pgn_lastc
== '\n' && c
== ' ') {
1910 if (fputc(c
, fp
) == EOF
)
1923 static void putstring(FILE *fp
, char *str
, int *len
)
1927 for (p
= str
; *p
; p
++) {
1930 while (*p
&& *p
!= ' ')
1934 Fputc('\n', fp
, len
);
1942 * See pgn_write() for more info.
1944 static void write_comments_and_nag(FILE *fp
, HISTORY
*h
, int *len
)
1950 PGN_DUMP("%s:%d: writing comments and nag\n", __FILE__
, __LINE__
);
1953 for (i
= 0; i
< MAX_PGN_NAG
; i
++) {
1955 Fputc(' ', fp
, len
);
1956 Fputc('$', fp
, len
);
1957 putstring(fp
, itoa(h
->nag
[i
], tmp
), len
);
1962 Fputc('\n', fp
, len
);
1963 putstring(fp
, " {", len
);
1964 putstring(fp
, h
->comment
, len
);
1965 Fputc('}', fp
, len
);
1969 static void write_move_text(FILE *fp
, HISTORY
*h
, int *len
)
1971 Fputc(' ', fp
, len
);
1972 putstring(fp
, h
->move
, len
);
1974 if (!pgn_config
.reduced
)
1975 write_comments_and_nag(fp
, h
, len
);
1978 static void write_all_move_text(FILE *fp
, HISTORY
**h
, int m
, int *len
)
1981 HISTORY
**hp
= NULL
;
1984 for (i
= 0; h
[i
]; i
++) {
1985 if (pgn_write_turn
== WHITE
) {
1986 if (pgn_config
.mpl
&& pgn_mpl
== pgn_config
.mpl
) {
1988 Fputc('\n', fp
, len
);
1992 Fputc(' ', fp
, len
);
1994 if (strlen(itoa(m
, tmp
)) + 1 + *len
> 80)
1995 Fputc('\n', fp
, len
);
1997 putstring(fp
, itoa(m
, tmp
), len
);
1998 Fputc('.', fp
, len
);
2002 write_move_text(fp
, h
[i
], len
);
2004 if (!pgn_config
.reduced
&& h
[i
]->rav
) {
2006 int oldturn
= pgn_write_turn
;
2009 putstring(fp
, " (", len
);
2012 * If it's WHITE's turn the move number will be added above after
2013 * the call to write_all_move_text() below.
2015 if (pgn_write_turn
== BLACK
) {
2016 putstring(fp
, itoa(m
, tmp
), len
);
2017 putstring(fp
, "...", len
);
2021 write_all_move_text(fp
, hp
, m
, len
);
2023 pgn_write_turn
= oldturn
;
2024 putstring(fp
, ")", len
);
2027 if (ravlevel
&& h
[i
+ 1])
2028 Fputc(' ', fp
, len
);
2030 if (h
[i
+ 1] && !ravlevel
)
2031 Fputc(' ', fp
, len
);
2033 if (pgn_write_turn
== WHITE
&& h
[i
+ 1]) {
2034 putstring(fp
, itoa(m
, tmp
), len
);
2035 putstring(fp
, "...", len
);
2039 if (pgn_write_turn
== BLACK
)
2042 pgn_write_turn
= (pgn_write_turn
== WHITE
) ? BLACK
: WHITE
;
2046 static char *compression_cmd(const char *filename
, int expand
)
2048 char command
[PATH_MAX
];
2049 int len
= strlen(filename
);
2051 if (filename
[len
- 4] == '.' && filename
[len
- 3] == 'z' &&
2052 filename
[len
- 2] == 'i' && filename
[len
- 1] == 'p' &&
2053 filename
[len
] == '\0') {
2055 snprintf(command
, sizeof(command
), "unzip -p %s 2>/dev/null",
2058 snprintf(command
, sizeof(command
), "zip -9 >%s 2>/dev/null",
2061 return strdup (command
);
2063 else if (filename
[len
- 3] == '.' && filename
[len
- 2] == 'g' &&
2064 filename
[len
- 1] == 'z' && filename
[len
] == '\0') {
2066 snprintf(command
, sizeof(command
), "gzip -dc %s", filename
);
2068 snprintf(command
, sizeof(command
), "gzip -c 1>%s", filename
);
2070 return strdup (command
);
2072 else if (filename
[len
- 2] == '.' && filename
[len
- 1] == 'Z' &&
2073 filename
[len
] == '\0') {
2075 snprintf(command
, sizeof(command
), "uncompress -c %s", filename
);
2077 snprintf(command
, sizeof(command
), "compress -c 1>%s", filename
);
2079 return strdup (command
);
2081 else if ((filename
[len
- 4] == '.' && filename
[len
- 3] == 'b' &&
2082 filename
[len
- 2] == 'z' && filename
[len
- 1] == '2' &&
2083 filename
[len
] == '\0') || (filename
[len
- 3] == '.' &&
2084 filename
[len
- 2] == 'b' && filename
[len
- 1] == 'z' &&
2085 filename
[len
] == '\0')) {
2087 snprintf(command
, sizeof(command
), "bzip2 -dc %s", filename
);
2089 snprintf(command
, sizeof(command
), "bzip2 -zc 1>%s", filename
);
2091 return strdup (command
);
2097 static int copy_file(FILE *fp
, const char *dst
)
2100 char line
[LINE_MAX
];
2101 char *cmd
= compression_cmd(dst
, 0);
2103 if ((ofp
= popen(cmd
, "w")) == NULL
) {
2109 fseek(fp
, 0, SEEK_SET
);
2111 while ((fgets(line
, sizeof(line
), fp
)) != NULL
)
2112 fprintf(ofp
, "%s", line
);
2119 * Closes and free's a PGN file handle.
2121 pgn_error_t
pgn_close(PGN_FILE
*pgn
)
2124 return E_PGN_INVALID
;
2128 * Appending to a compressed file.
2131 if (copy_file(pgn
->fp
, pgn
->filename
))
2135 unlink(pgn
->tmpfile
);
2144 free(pgn
->filename
);
2150 * Opens a file 'filename' with the given 'mode'. 'mode' should be "r" for
2151 * reading, "w" for writing (will truncate if the file exists) or "a" for
2152 * appending to an existing file or creating a new one. Returns E_PGN_OK on
2153 * success and sets 'result' to a file handle for use will the other file
2154 * functions or E_PGN_ERR if there is an error opening the file in which case
2155 * errno will be set to the error or E_PGN_INVALID if 'mode' is an invalid
2156 * mode or if 'filename' is not a regular file.
2158 pgn_error_t
pgn_open(const char *filename
, const char *mode
, PGN_FILE
**result
)
2160 FILE *fp
= NULL
, *tfp
= NULL
, *pfp
= NULL
;
2161 char buf
[PATH_MAX
], *p
;
2167 int ret
= E_PGN_ERR
;
2171 PGN_DUMP("%s:%d: BEGIN opening %s\n", __FILE__
, __LINE__
, filename
);
2174 if (!filename
|| !mode
)
2175 return E_PGN_INVALID
;
2177 if (strcmp(mode
, "r") == 0)
2179 else if (strcmp(mode
, "w") == 0)
2181 else if (strcmp(mode
, "a") == 0) {
2186 return E_PGN_INVALID
;
2189 pgn
= calloc(1, sizeof(PGN_FILE
));
2194 if (strcmp(filename
, "-") != 0) {
2195 if (m
&& access(filename
, R_OK
) == -1)
2198 if (stat(filename
, &st
) == -1 && !m
&& errno
!= ENOENT
)
2201 if (m
&& !S_ISREG(st
.st_mode
)) {
2202 ret
= E_PGN_INVALID
;
2206 if ((cmd
= compression_cmd(filename
, m
)) != NULL
) {
2211 snprintf (tmp
, sizeof(tmp
), "cboard.XXXXXX");
2213 if (tmpnam(tmp
) == NULL
)
2219 if (append
&& access(filename
, R_OK
) == 0) {
2225 cmd
= compression_cmd(filename
, 1);
2226 if ((pfp
= popen(cmd
, "r")) == NULL
)
2236 if ((fd
= open(tmp
, O_RDWR
|O_EXCL
|O_CREAT
, 0600)) == -1)
2239 if ((tfp
= fdopen(fd
, "a+")) == NULL
)
2242 while ((p
= fgets(buf
, sizeof(buf
), pfp
)) != NULL
)
2243 fprintf(tfp
, "%s", p
);
2246 pgn
->tmpfile
= strdup(tmp
);
2250 if ((pfp
= popen(cmd
, m
? "r" : "w")) == NULL
)
2255 mode_t mode
= umask (600);
2262 if ((fd
= open(tmp
, O_RDWR
|O_EXCL
|O_CREAT
|O_TRUNC
, 0600)) == -1)
2265 if ((tfp
= fdopen(fd
, "w+")) == NULL
)
2268 while ((p
= fgets(buf
, sizeof(buf
), pfp
)) != NULL
)
2269 fprintf(tfp
, "%s", p
);
2272 pgn
->tmpfile
= strdup(tmp
);
2278 if ((fp
= fopen(filename
, mode
)) == NULL
)
2288 if (*filename
!= '/') {
2289 if (getcwd(buf
, sizeof(buf
)) == NULL
) {
2296 asprintf(&p
, "%s/%s", buf
, filename
);
2300 pgn
->filename
= strdup(filename
);
2319 * Returns E_PGN_OK if 'filename' is a recognized compressed filetype or
2322 pgn_error_t
pgn_is_compressed(const char *filename
)
2324 char *s
= compression_cmd(filename
, 0);
2325 pgn_error_t e
= s
? E_PGN_OK
: E_PGN_ERR
;
2332 * Gets the value of config flag 'f'. The next argument should be a pointer of
2333 * the config type which is set to the value of 'f'. Returns E_PGN_ERR if 'f'
2334 * is an invalid flag or E_PGN_OK on success.
2336 pgn_error_t
pgn_config_get(pgn_config_flag f
, ...)
2341 pgn_progress
*progress
;
2346 case PGN_STRICT_CASTLING
:
2347 intval
= va_arg(ap
, int *);
2348 *intval
= pgn_config
.strict_castling
;
2352 intval
= va_arg(ap
, int *);
2353 *intval
= pgn_config
.reduced
;
2357 intval
= va_arg(ap
, int *);
2358 *intval
= pgn_config
.mpl
;
2361 case PGN_STOP_ON_ERROR
:
2362 intval
= va_arg(ap
, int *);
2363 *intval
= pgn_config
.stop
;
2367 longval
= va_arg(ap
, long *);
2368 *longval
= pgn_config
.stop
;
2371 case PGN_PROGRESS_FUNC
:
2372 progress
= va_arg(ap
, pgn_progress
*);
2373 *progress
= pgn_config
.pfunc
;
2378 intval
= va_arg(ap
, int *);
2379 *intval
= dumptofile
;
2391 * Sets config flag 'f' to the next argument. Returns E_PGN_OK on success or
2392 * E_PGN_ERR if 'f' is an invalid flag or E_PGN_INVALID if 'val' is an invalid
2395 pgn_error_t
pgn_config_set(pgn_config_flag f
, ...)
2405 n
= va_arg(ap
, int);
2407 if (n
!= 1 && n
!= 0) {
2408 ret
= E_PGN_INVALID
;
2412 pgn_config
.reduced
= n
;
2415 n
= va_arg(ap
, int);
2418 ret
= E_PGN_INVALID
;
2424 case PGN_STOP_ON_ERROR
:
2425 n
= va_arg(ap
, int);
2427 if (n
!= 1 && n
!= 0) {
2428 ret
= E_PGN_INVALID
;
2432 pgn_config
.stop
= n
;
2435 n
= va_arg(ap
, long);
2436 pgn_config
.progress
= n
;
2438 case PGN_PROGRESS_FUNC
:
2439 pgn_config
.pfunc
= va_arg(ap
, pgn_progress
);
2441 case PGN_STRICT_CASTLING
:
2442 n
= va_arg(ap
, int);
2443 pgn_config
.strict_castling
= n
;
2447 n
= va_arg(ap
, int);
2448 dumptofile
= (n
> 0) ? 1 : 0;
2461 * Writes a PGN formatted game 'g' to a file which was opened with pgn_open().
2462 * See 'pgn_config_flag' for output options. Returns E_PGN_ERR if there was a
2463 * memory allocation or write error and E_PGN_OK on success. It is important
2464 * to use pgn_close() afterwards if the file is a recognized compressed file
2465 * type otherwise the created temporary file wont be copied to the destination
2468 pgn_error_t
pgn_write(PGN_FILE
*pgn
, GAME g
)
2476 pgn_write_turn
= (TEST_FLAG(g
->flags
, GF_BLACK_OPENING
)) ? BLACK
: WHITE
;
2477 pgn_tag_sort(g
->tag
);
2480 PGN_DUMP("%s:%d: writing tag section\n", __FILE__
, __LINE__
);
2483 for (i
= 0; g
->tag
[i
]; i
++) {
2485 char tbuf
[11] = {0};
2489 if (pgn_config
.reduced
&& i
== 7)
2492 if (strcmp(g
->tag
[i
]->name
, "Date") == 0) {
2493 memset(&tp
, 0, sizeof(struct tm
));
2495 if (strptime(g
->tag
[i
]->value
, PGN_TIME_FORMAT
, &tp
) != NULL
) {
2496 len
= strftime(tbuf
, sizeof(tbuf
), PGN_TIME_FORMAT
, &tp
) + 1;
2498 if ((tmp
= strdup(tbuf
)) == NULL
)
2501 free(g
->tag
[i
]->value
);
2502 g
->tag
[i
]->value
= tmp
;
2505 else if (strcmp(g
->tag
[i
]->name
, "Event") == 0) {
2506 if (g
->tag
[i
]->value
[0] == '\0') {
2507 if ((tmp
= strdup("?")) == NULL
)
2510 free(g
->tag
[i
]->value
);
2511 g
->tag
[i
]->value
= tmp
;
2514 else if (strcmp(g
->tag
[i
]->name
, "Site") == 0) {
2515 if (g
->tag
[i
]->value
[0] == '\0') {
2516 if ((tmp
= strdup("?")) == NULL
)
2519 free(g
->tag
[i
]->value
);
2520 g
->tag
[i
]->value
= tmp
;
2523 else if (strcmp(g
->tag
[i
]->name
, "Round") == 0) {
2524 if (g
->tag
[i
]->value
[0] == '\0') {
2525 if ((tmp
= strdup("?")) == NULL
)
2528 free(g
->tag
[i
]->value
);
2529 g
->tag
[i
]->value
= tmp
;
2532 else if (strcmp(g
->tag
[i
]->name
, "Result") == 0) {
2533 if (g
->tag
[i
]->value
[0] == '\0') {
2534 if ((tmp
= strdup("*")) == NULL
)
2537 free(g
->tag
[i
]->value
);
2538 g
->tag
[i
]->value
= tmp
;
2541 else if (strcmp(g
->tag
[i
]->name
, "Black") == 0) {
2542 if (g
->tag
[i
]->value
[0] == '\0') {
2543 if ((tmp
= strdup("?")) == NULL
)
2546 free(g
->tag
[i
]->value
);
2547 g
->tag
[i
]->value
= tmp
;
2550 else if (strcmp(g
->tag
[i
]->name
, "White") == 0) {
2551 if (g
->tag
[i
]->value
[0] == '\0') {
2552 if ((tmp
= strdup("?")) == NULL
)
2555 free(g
->tag
[i
]->value
);
2556 g
->tag
[i
]->value
= tmp
;
2560 escaped
= pgn_tag_add_escapes(g
->tag
[i
]->value
);
2561 fprintf(pgn
->fp
, "[%s \"%s\"]\n", g
->tag
[i
]->name
,
2562 (g
->tag
[i
]->value
&& g
->tag
[i
]->value
[0]) ? escaped
: "");
2567 PGN_DUMP("%s:%d: writing move section\n", __FILE__
, __LINE__
);
2569 Fputc('\n', pgn
->fp
, &len
);
2571 ravlevel
= pgn_mpl
= 0;
2573 if (pgn_history_total(g
->hp
) && pgn_write_turn
== BLACK
)
2574 putstring(pgn
->fp
, "1...", &len
);
2576 write_all_move_text(pgn
->fp
, g
->hp
, 1, &len
);
2578 Fputc(' ', pgn
->fp
, &len
);
2579 putstring(pgn
->fp
, g
->tag
[6]->value
, &len
);
2580 putstring(pgn
->fp
, "\n\n", &len
);
2582 if (!pgn_config
.reduced
)
2583 CLEAR_FLAG(g
->flags
, GF_PERROR
);
2589 * Clears the enpassant flag for all positions on board 'b'. Returns nothing.
2591 void pgn_reset_enpassant(BOARD b
)
2596 PGN_DUMP("%s:%d: resetting enpassant\n", __FILE__
, __LINE__
);
2599 for (r
= 0; r
< 8; r
++) {
2600 for (c
= 0; c
< 8; c
++)
2601 b
[r
][c
].enpassant
= 0;