1 /* vim:tw=78:ts=8:sw=4:set ft=c: */
3 Copyright (C) 2002-2011 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
23 #include <sys/types.h>
55 static int Fgetc(FILE *fp
)
59 if ((c
= fgetc(fp
)) != EOF
) {
60 if (pgn_config
.progress
&& pgn_config
.pfunc
) {
61 if (!(ftell(fp
) % pgn_config
.progress
))
62 pgn_config
.pfunc(pgn_fsize
, ftell(fp
));
69 static int Ungetc(int c
, FILE *fp
)
76 return "libchess " PACKAGE_VERSION
;
79 static char *trim(char *str
)
89 for (i
= strlen(str
) - 1; isspace(str
[i
]); i
--)
95 static char *itoa(long n
)
99 snprintf(buf
, sizeof(buf
), "%li", n
);
104 * Clears the valid move flag for all positions on board 'b'. Returns nothing.
106 void pgn_reset_valid_moves(BOARD b
)
111 PGN_DUMP("%s:%d: resetting valid moves\n", __FILE__
, __LINE__
);
114 for (row
= 0; row
< 8; row
++) {
115 for (col
= 0; col
< 8; col
++)
116 b
[row
][col
].valid
= 0;
121 * Toggles g->turn. Returns nothing.
123 void pgn_switch_turn(GAME g
)
125 g
->turn
= (g
->turn
== WHITE
) ? BLACK
: WHITE
;
129 * Toggles g->side and switches the White and Black roster tags. Returns
132 void pgn_switch_side(GAME g
)
134 char *w
= g
->tag
[4]->value
;
136 g
->tag
[4]->value
= g
->tag
[5]->value
;
137 g
->tag
[5]->value
= w
;
138 g
->side
= (g
->side
== WHITE
) ? BLACK
: WHITE
;
142 * Creates a FEN tag from the current game 'g', history move (g->hindex) and
143 * board 'b'. Returns a FEN tag.
145 char *pgn_game_to_fen(GAME g
, BOARD b
)
149 static char buf
[MAX_PGN_LINE_LEN
], *p
;
150 int oldturn
= g
->turn
;
151 char enpassant
[3] = {0}, *e
;
155 PGN_DUMP("%s:%d: creating FEN tag\n", __FILE__
, __LINE__
);
158 for (i
= pgn_history_total(g
->hp
); i
>= g
->hindex
- 1; i
--)
163 for (row
= 0; row
< 8; row
++) {
166 for (col
= 0; col
< 8; col
++) {
167 if (b
[row
][col
].enpassant
) {
168 b
[row
][col
].icon
= pgn_int_to_piece(WHITE
, OPEN_SQUARE
);
171 *e
++ = ('0' + 8) - row
;
175 if (pgn_piece_to_int(b
[row
][col
].icon
) == OPEN_SQUARE
) {
185 *p
++ = b
[row
][col
].icon
;
199 *p
++ = (g
->turn
== WHITE
) ? 'w' : 'b';
202 if (TEST_FLAG(g
->flags
, GF_WK_CASTLE
) && pgn_piece_to_int(b
[7][7].icon
) ==
203 ROOK
&& isupper(b
[7][7].icon
) && pgn_piece_to_int(b
[7][4].icon
) ==
204 KING
&& isupper(b
[7][4].icon
)) {
209 if (TEST_FLAG(g
->flags
, GF_WQ_CASTLE
) && pgn_piece_to_int(b
[7][0].icon
) ==
210 ROOK
&& isupper(b
[7][0].icon
) && pgn_piece_to_int(b
[7][4].icon
) ==
211 KING
&& isupper(b
[7][4].icon
)) {
216 if (TEST_FLAG(g
->flags
, GF_BK_CASTLE
) && pgn_piece_to_int(b
[0][7].icon
) ==
217 ROOK
&& islower(b
[0][7].icon
) && pgn_piece_to_int(b
[0][4].icon
) ==
218 KING
&& islower(b
[0][4].icon
)) {
223 if (TEST_FLAG(g
->flags
, GF_BQ_CASTLE
) && pgn_piece_to_int(b
[0][0].icon
) ==
224 ROOK
&& islower(b
[0][0].icon
) && pgn_piece_to_int(b
[0][4].icon
) ==
225 KING
&& islower(b
[0][4].icon
)) {
247 strcat(p
, itoa(g
->ply
));
248 p
= buf
+ strlen(buf
);
252 i
= (g
->hindex
+ 1) / 2;
254 strcat(p
, itoa((g
->hindex
/ 2) + (g
->hindex
% 2)));
261 * Returns the total number of moves in 'h' or 0 if there are none.
263 int pgn_history_total(HISTORY
**h
)
270 for (i
= 0; h
[i
]; i
++);
275 * Deallocates all of the history data from position 'start' in the array 'h'.
278 void pgn_history_free(HISTORY
**h
, int start
)
283 PGN_DUMP("%s:%d: freeing history\n", __FILE__
, __LINE__
);
286 if (!h
|| start
> pgn_history_total(h
))
292 for (i
= start
; h
[i
]; i
++) {
294 pgn_history_free(h
[i
]->rav
, 0);
308 * Returns the history ply 'n' from 'h'. If 'n' is out of range then NULL is
311 HISTORY
*pgn_history_by_n(HISTORY
**h
, int n
)
313 if (n
< 0 || n
> pgn_history_total(h
) - 1)
320 * Appends move 'm' to game 'g' history pointer and creates a FEN tag for the
321 * current game state using board 'b'. The FEN tag makes things faster than
322 * validating the entire move history by validating only the current move to
323 * the previous moves FEN tag. The history pointer may be a in a RAV so
324 * g->rav.hp is also updated to the new (realloc()'ed) pointer. If not in a
325 * RAV then g->history will be updated. Returns E_PGN_ERR if realloc() failed
326 * or E_PGN_OK on success.
328 pgn_error_t
pgn_history_add(GAME g
, BOARD b
, const char *m
)
330 int t
= pgn_history_total(g
->hp
);
332 HISTORY
**h
= NULL
, *tmp
;
333 int ri
= (g
->ravlevel
) ? g
->rav
[g
->ravlevel
- 1].hindex
: 0;
336 PGN_DUMP("%s:%d: adding '%s' to move history\n", __FILE__
, __LINE__
, m
);
340 o
= g
->rav
[g
->ravlevel
- 1].hp
[ri
-1]->rav
- g
->hp
;
342 o
= g
->history
- g
->hp
;
344 if ((h
= realloc(g
->hp
, (t
+ 2) * sizeof(HISTORY
*))) == NULL
)
350 g
->rav
[g
->ravlevel
- 1].hp
[ri
-1]->rav
= g
->hp
+ o
;
352 g
->history
= g
->hp
+ o
;
354 if ((g
->hp
[t
] = calloc(1, sizeof(HISTORY
))) == NULL
)
357 if ((g
->hp
[t
]->move
= strdup(m
)) == NULL
) {
366 g
->hindex
= pgn_history_total(g
->hp
);
368 tmp
->fen
= strdup(pgn_game_to_fen(g
, b
));
374 * Resets the game 'g' using board 'b' up to history move (g->hindex) 'n'.
375 * Returns E_PGN_OK on success or E_PGN_PARSE if there was a FEN tag but the
376 * parsing of it failed. Or returns E_PGN_INVALID if somehow the move failed
377 * validation while resetting.
379 pgn_error_t
pgn_board_update(GAME g
, BOARD b
, int n
)
383 int p_error
= TEST_FLAG(g
->flags
, GF_PERROR
);
384 int black_opening
= TEST_FLAG(g
->flags
, GF_BLACK_OPENING
);
387 PGN_DUMP("%s:%d: updating board\n", __FILE__
, __LINE__
);
390 if (!g
->ravlevel
&& TEST_FLAG(g
->flags
, GF_BLACK_OPENING
))
396 SET_FLAG(g
->flags
, GF_WK_CASTLE
|GF_WQ_CASTLE
|GF_WQ_CASTLE
|
397 GF_BK_CASTLE
|GF_BQ_CASTLE
);
401 pgn_board_init_fen(g
, tb
, g
->rav
[g
->ravlevel
- 1].fen
);
402 else if (pgn_tag_find(g
->tag
, "FEN") != -1 &&
403 pgn_board_init_fen(g
, tb
, NULL
))
407 HISTORY
*h
= pgn_history_by_n(g
->hp
, n
-1);
410 ret
= pgn_board_init_fen(g
, tb
, h
->fen
);
411 if (ret
== E_PGN_OK
) {
412 h
= pgn_history_by_n(g
->hp
, n
);
414 char *p
= h
->move
, *frfr
;
416 ret
= pgn_parse_move(g
, tb
, &p
, &frfr
);
417 if (ret
== E_PGN_OK
) {
418 h
= pgn_history_by_n(g
->hp
, n
-1);
419 ret
= pgn_board_init_fen(g
, tb
, h
->fen
);
427 memcpy(b
, tb
, sizeof(BOARD
));
430 SET_FLAG(g
->flags
, GF_PERROR
);
433 SET_FLAG(g
->flags
, GF_BLACK_OPENING
);
439 * Updates the game 'g' using board 'b' to the next 'n'th history move.
442 void pgn_history_prev(GAME g
, BOARD b
, int n
)
444 if (g
->hindex
- n
< 0) {
446 g
->hindex
= pgn_history_total(g
->hp
);
453 pgn_board_update(g
, b
, g
->hindex
);
457 * Updates the game 'g' using board 'b' to the previous 'n'th history move.
460 void pgn_history_next(GAME g
, BOARD b
, int n
)
462 if (g
->hindex
+ n
> pgn_history_total(g
->hp
)) {
466 g
->hindex
= pgn_history_total(g
->hp
);
471 pgn_board_update(g
, b
, g
->hindex
);
475 * Converts the character piece 'p' to an integer. Returns the integer
476 * associated with 'p' or E_PGN_ERR if 'p' is invalid.
478 int pgn_piece_to_int(int p
)
503 PGN_DUMP("%s:%d: invalid piece '%c'\n", __FILE__
, __LINE__
, p
);
509 * Converts the integer piece 'n' to a character whose turn is 'turn'. WHITE
510 * piece are uppercase and BLACK pieces are lowercase. Returns the character
511 * associated with 'n' or E_PGN_ERR if 'n' is an invalid piece.
513 pgn_error_t
pgn_int_to_piece(char turn
, int n
)
541 PGN_DUMP("%s:%d: unknown piece integer %i\n", __FILE__
,
548 return (turn
== WHITE
) ? toupper(p
) : p
;
552 * Finds a tag 'name' in the structure array 't'. Returns the location in the
553 * array of the found tag or E_PGN_ERR if the tag could not be found.
555 pgn_error_t
pgn_tag_find(TAG
**t
, const char *name
)
559 for (i
= 0; t
[i
]; i
++) {
560 if (strcasecmp(t
[i
]->name
, name
) == 0)
567 static pgn_error_t
remove_tag(TAG
***array
, const char *tag
)
570 int n
= pgn_tag_find(tags
, tag
);
576 for (i
= t
= 0; tags
[i
]; i
++) {
579 free(tags
[i
]->value
);
587 tags
= realloc(*array
, (t
+ 1) * sizeof(TAG
*));
591 PGN_DUMP("%s:%d: removed tag: name='%s'\n", __FILE__
, __LINE__
, tag
);
596 static int tag_compare(const void *a
, const void *b
)
601 return strcmp((*ta
)->name
, (*tb
)->name
);
605 * Sorts a tag array. The first seven tags are in order of the PGN standard so
606 * don't sort'em. Returns nothing.
608 void pgn_tag_sort(TAG
**tags
)
610 if (pgn_tag_total(tags
) <= 7)
613 qsort(tags
+ 7, pgn_tag_total(tags
) - 7, sizeof(TAG
*), tag_compare
);
617 * Returns the total number of tags in 't' or 0 if 't' is NULL.
619 int pgn_tag_total(TAG
**tags
)
633 * Adds a tag 'name' with value 'value' to the pointer to array of TAG
634 * pointers 'dst'. If a duplicate tag 'name' was found then the existing tag
635 * is updated to the new 'value'. If 'value' is NULL, the tag is removed.
636 * Returns E_PGN_ERR if there was a memory allocation error or E_PGN_OK on
639 pgn_error_t
pgn_tag_add(TAG
***dst
, char *name
, char *value
)
644 int t
= pgn_tag_total(tdata
);
647 PGN_DUMP("%s:%d: adding tag\n", __FILE__
, __LINE__
);
658 // Find an existing tag with 'name'.
659 for (i
= 0; i
< t
; i
++) {
662 if (strcasecmp(tdata
[i
]->name
, name
) == 0) {
664 if ((tmp
= strdup(value
)) == NULL
)
668 remove_tag(dst
, name
);
672 free(tdata
[i
]->value
);
673 tdata
[i
]->value
= tmp
;
679 if ((a
= realloc(tdata
, (t
+ 2) * sizeof(TAG
*))) == NULL
)
684 if ((tdata
[t
] = malloc(sizeof(TAG
))) == NULL
)
687 if ((tdata
[t
]->name
= strdup(name
)) == NULL
) {
693 if ((tdata
[t
]->value
= strdup(value
)) == NULL
) {
694 free(tdata
[t
]->name
);
700 tdata
[t
]->value
= NULL
;
702 tdata
[t
]->name
[0] = toupper(tdata
[t
]->name
[0]);
707 PGN_DUMP("%s:%d: added tag: name='%s' value='%s'\n", __FILE__
, __LINE__
,
708 name
, (value
) ? value
: "null");
714 static char *remove_tag_escapes(const char *str
)
717 int len
= strlen(str
);
718 static char buf
[MAX_PGN_LINE_LEN
] = {0};
720 for (i
= n
= 0; i
< len
; i
++, n
++) {
736 * Resets or initializes a new game board 'b'. Returns nothing.
738 void pgn_board_init(BOARD b
)
743 PGN_DUMP("%s:%d: initializing board\n", __FILE__
, __LINE__
);
746 memset(b
, 0, sizeof(BOARD
));
748 for (row
= 0; row
< 8; row
++) {
749 for (col
= 0; col
< 8; col
++) {
782 b
[row
][col
].icon
= (row
< 2) ? c
: toupper(c
);
788 * Adds the standard PGN roster tags to game 'g'.
790 static void set_default_tags(GAME g
)
795 struct passwd
*pw
= getpwuid(getuid());
798 tp
= localtime(&now
);
799 strftime(tbuf
, sizeof(tbuf
), PGN_TIME_FORMAT
, tp
);
801 /* The standard seven tag roster (in order of appearance). */
802 if (pgn_tag_add(&g
->tag
, "Event", "?") != E_PGN_OK
)
803 warn("pgn_tag_add()");
805 if (pgn_tag_add(&g
->tag
, "Site", "?") != E_PGN_OK
)
806 warn("pgn_tag_add()");
808 if (pgn_tag_add(&g
->tag
, "Date", tbuf
) != E_PGN_OK
)
809 warn("pgn_tag_add()");
811 if (pgn_tag_add(&g
->tag
, "Round", "-") != E_PGN_OK
)
812 warn("pgn_tag_add()");
814 if (pgn_tag_add(&g
->tag
, "White", pw
->pw_gecos
) != E_PGN_OK
)
815 warn("pgn_tag_add()");
817 if (pgn_tag_add(&g
->tag
, "Black", "?") != E_PGN_OK
)
818 warn("pgn_tag_add()");
820 if (pgn_tag_add(&g
->tag
, "Result", "*") != E_PGN_OK
)
821 warn("pgn_tag_add()");
825 * Frees a TAG array. Returns nothing.
827 void pgn_tag_free(TAG
**tags
)
830 int t
= pgn_tag_total(tags
);
833 PGN_DUMP("%s:%d: freeing tags\n", __FILE__
, __LINE__
);
839 for (i
= 0; i
< t
; i
++) {
841 free(tags
[i
]->value
);
849 * Frees a single game 'g'. Returns nothing.
851 void pgn_free(GAME g
)
854 PGN_DUMP("%s:%d: freeing game\n", __FILE__
, __LINE__
);
856 pgn_history_free(g
->history
, 0);
858 pgn_tag_free(g
->tag
);
861 for (g
->ravlevel
--; g
->ravlevel
>= 0; g
->ravlevel
--)
862 free(g
->rav
[g
->ravlevel
].fen
);
871 * Frees all games in the global 'game' array. Returns nothing.
878 PGN_DUMP("%s:%d: freeing game data\n", __FILE__
, __LINE__
);
881 for (i
= 0; i
< gtotal
; i
++) {
891 static void reset_game_data()
894 PGN_DUMP("%s:%d: resetting game data\n", __FILE__
, __LINE__
);
900 static void skip_leading_space(FILE *fp
)
904 while ((c
= Fgetc(fp
)) != EOF
&& !feof(fp
)) {
913 * PGN move text section.
915 static int move_text(GAME g
, FILE *fp
)
917 char m
[MAX_SAN_MOVE_LEN
+ 1] = {0}, *p
;
922 g
->oflags
= g
->flags
;
924 while((c
= Fgetc(fp
)) != EOF
) {
925 if (isdigit(c
) || isspace(c
) || c
== '.')
933 if (fscanf(fp
, " %[a-hPpRrNnBbQqKk1-9#+=Ox-]%n", m
, &count
) != 1)
936 m
[MAX_SAN_MOVE_LEN
] = 0;
939 if (pgn_parse_move(g
, pgn_board
, &p
, &frfr
)) {
945 PGN_DUMP("%s\n%s", p
, debug_board(pgn_board
));
948 pgn_history_add(g
, pgn_board
, p
);
956 static void nag_text(GAME g
, FILE *fp
)
959 char nags
[5], *n
= nags
;
962 while ((c
= Fgetc(fp
)) != EOF
&& !isspace(c
)) {
964 while ((c
= Fgetc(fp
)) != EOF
&& isdigit(c
))
972 if ((c
= Fgetc(fp
)) == '!')
984 if ((c
= Fgetc(fp
)) == '?')
998 if ((c
= Fgetc(fp
)) == '+')
1007 else if (c
== '+') {
1008 if ((t
= Fgetc(fp
)) == '=')
1012 else if (t
== '/') {
1013 if ((i
= Fgetc(fp
)) == '-')
1025 else if (c
== '-') {
1026 if ((t
= Fgetc(fp
)) == '+')
1028 else if (t
== '/') {
1029 if ((i
= Fgetc(fp
)) == '+')
1046 nag
= (nags
[0]) ? atoi(nags
) : 0;
1048 if (!nag
|| nag
< 0 || nag
> 255)
1051 // FIXME -1 is because move_text() increments g->hindex. The NAG
1052 // annoatation isnt guaranteed to be after the move text in import format.
1053 for (i
= 0; i
< MAX_PGN_NAG
; i
++) {
1054 if (g
->hp
[g
->hindex
- 1]->nag
[i
])
1057 g
->hp
[g
->hindex
- 1]->nag
[i
] = nag
;
1061 skip_leading_space(fp
);
1065 * PGN move annotation.
1067 static int annotation_text(GAME g
, FILE *fp
, int terminator
)
1069 int c
, lastchar
= 0;
1071 int hindex
= pgn_history_total(g
->hp
) - 1;
1072 char buf
[MAX_PGN_LINE_LEN
], *a
= buf
;
1074 skip_leading_space(fp
);
1076 while ((c
= Fgetc(fp
)) != EOF
&& c
!= terminator
) {
1080 if (isspace(c
) && isspace(lastchar
))
1083 if (len
+ 1 == sizeof(buf
))
1086 *a
++ = lastchar
= c
;
1096 * This annotation is before any move text or NAg-> Allocate a new move.
1098 if (!g
->hp
[hindex
]) {
1099 if ((g
->hp
[hindex
] = calloc(1, sizeof(HISTORY
))) == NULL
)
1103 if ((g
->hp
[hindex
]->comment
= strdup(buf
)) == NULL
)
1112 static int tag_text(GAME g
, FILE *fp
)
1114 char name
[LINE_MAX
], *n
= name
;
1115 char value
[LINE_MAX
], *v
= value
;
1119 skip_leading_space(fp
);
1121 /* The tag name is up until the first whitespace. */
1122 while ((c
= Fgetc(fp
)) != EOF
&& !isspace(c
))
1126 *name
= toupper(*name
);
1127 skip_leading_space(fp
);
1129 /* The value is until the first closing bracket. */
1130 while ((c
= Fgetc(fp
)) != EOF
&& c
!= ']') {
1131 if (i
++ == '\0' && c
== '\"')
1134 if (c
== '\n' || c
== '\t')
1137 if (c
== ' ' && lastchar
== ' ')
1140 lastchar
= *v
++ = c
;
1145 while (isspace(*--v
))
1151 if (value
[0] == '\0') {
1152 if (strcmp(name
, "Result") == 0)
1160 strncpy(value
, remove_tag_escapes(value
), sizeof(value
));
1163 * See eog_text() for an explanation.
1165 if (strcmp(name
, "Result") == 0) {
1166 if (strcmp(value
, "1/2-1/2") == 0) {
1167 if (pgn_tag_add(&g
->tag
, name
, value
) != E_PGN_OK
)
1168 warn("pgn_tag_add()");
1172 if (pgn_tag_add(&g
->tag
, name
, value
) != E_PGN_OK
)
1173 warn("pgn_tag_add()");
1180 * PGN end-of-game marker.
1182 static int eog_text(GAME g
, FILE *fp
)
1185 char buf
[8], *p
= buf
;
1187 while ((c
= Fgetc(fp
)) != EOF
&& !isspace(c
) && i
++ < sizeof(buf
))
1195 if (strcmp(buf
, "1-0") != 0 && strcmp(buf
, "0-1") != 0 &&
1196 strcmp(buf
, "1/2-1/2") != 0 && strcmp(buf
, "*") != 0)
1200 * The eog marker in the move text may not match the actual game result.
1201 * We'll leave it up to the move validator to determine the result unless
1202 * the move validator cannot determine who won and the move text says it's
1205 if (g
->tag
[6]->value
[0] == '*' && strcmp("1/2-1/2", buf
) == 0) {
1206 if (pgn_tag_add(&g
->tag
, "Result", buf
) != E_PGN_OK
)
1207 warn("pgn_tag_add()");
1214 * Parse RAV text and keep track of g->hp. The 'o' argument is the board state
1215 * before the current move (.hindex) was parsed.
1217 static int read_file(FILE *);
1218 static int rav_text(GAME g
, FILE *fp
, int which
, BOARD o
)
1223 // Begin RAV for the current move.
1228 pgn_rav
++; // For detecting parse errors.
1231 * Save the current game state for this RAV depth/level.
1233 if ((r
= realloc(g
->rav
, (g
->ravlevel
+ 1) * sizeof(RAV
))) == NULL
) {
1238 g
->rav
= pgn_rav_p
= r
;
1240 if ((g
->rav
[g
->ravlevel
].fen
= strdup(pgn_game_to_fen(g
, pgn_board
)))
1246 g
->rav
[g
->ravlevel
].hp
= g
->hp
;
1247 g
->rav
[g
->ravlevel
].hindex
= g
->hindex
;
1248 memcpy(&tg
, &(*g
), sizeof(struct game_s
));
1249 g
->flags
= g
->oflags
;
1250 memcpy(pgn_board
, o
, sizeof(BOARD
));
1252 if ((g
->hp
[g
->hindex
- 1]->rav
= calloc(1, sizeof(HISTORY
*))) == NULL
) {
1258 * pgn_history_add() will now append to the new history pointer that
1259 * is g->hp[previous_move]->rav.
1261 g
->hp
= g
->hp
[g
->hindex
- 1]->rav
;
1264 * Reset. Will be restored later from 'tg' which is a local variable
1265 * so recursion is possible.
1271 * Undo move_text()'s switch.
1276 * Now continue as normal as if there were no RAV. Moves will be
1277 * parsed and appended to the new history pointer.
1283 * read_file() has returned. This means that a RAV has ended by this
1284 * function returning -1 (see below). So we restore the game state
1285 * (tg) that was saved before calling read_file().
1287 pgn_board_init_fen(&tg
, pgn_board
, g
->rav
[tg
.ravlevel
].fen
);
1288 free(g
->rav
[tg
.ravlevel
].fen
);
1289 memcpy(&(*g
), &tg
, sizeof(struct game_s
));
1293 * The end of a RAV. This makes the read_file() that called this function
1294 * return (see above and the switch statement in read_file()).
1296 else if (which
== ')') {
1297 pgn_rav
--; // For detecting parse errors.
1298 return (pgn_rav
< 0) ? 1 : -1;
1306 * See pgn_board_init_fen(). Returns E_PGN_PARSE on parse error. 0 may be
1307 * returned on success when there is no move count in the FEN tag otherwise
1308 * the move count is returned.
1310 static int parse_fen_line(BOARD b
, unsigned *flags
, char *turn
, char *ply
,
1314 char line
[LINE_MAX
], *s
;
1315 int row
= 8, col
= 1;
1318 PGN_DUMP("%s:%d: FEN line is '%s'\n", __FILE__
, __LINE__
, str
);
1320 strncpy(line
, str
, sizeof(line
));
1322 pgn_reset_enpassant(b
);
1324 while ((tmp
= strsep(&s
, "/")) != NULL
) {
1327 if (!VALIDFILE(row
))
1334 if (isdigit(*tmp
)) {
1340 for (; n
; --n
, col
++)
1341 b
[RANKTOBOARD(row
)][FILETOBOARD(col
)].icon
=
1342 pgn_int_to_piece(WHITE
, OPEN_SQUARE
);
1344 else if (pgn_piece_to_int(*tmp
) != -1)
1345 b
[RANKTOBOARD(row
)][FILETOBOARD(col
++)].icon
= *tmp
;
1372 while (*tmp
&& *tmp
!= ' ') {
1375 SET_FLAG(*flags
, GF_WK_CASTLE
);
1378 SET_FLAG(*flags
, GF_WQ_CASTLE
);
1381 SET_FLAG(*flags
, GF_BK_CASTLE
);
1384 SET_FLAG(*flags
, GF_BQ_CASTLE
);
1397 if (!VALIDCOL(*tmp
))
1402 if (!VALIDROW(*tmp
))
1405 row
= 8 - atoi(tmp
++);
1406 b
[row
][col
].enpassant
= 1;
1407 SET_FLAG(*flags
, GF_ENPASSANT
);
1418 while (*tmp
&& isdigit(*tmp
))
1428 * It initializes the board (b) to the FEN tag (if found) and sets the
1429 * castling and enpassant info for the game 'g'. If 'fen' is set it should be
1430 * a fen tag and will be parsed rather than the game 'g'.tag FEN tag-> Returns
1431 * E_PGN_OK on success or if there was both a FEN and SetUp tag with the SetUp
1432 * tag set to 0. Returns E_PGN_PARSE if there was a FEN parse error, E_PGN_ERR
1433 * if there was no FEN tag or there was a SetUp tag with a value of 0. Returns
1434 * E_PGN_OK on success.
1436 pgn_error_t
pgn_board_init_fen(GAME g
, BOARD b
, char *fen
)
1441 char turn
= g
->turn
;
1445 PGN_DUMP("%s:%d: initializing board from FEN\n", __FILE__
, __LINE__
);
1447 pgn_board_init(tmpboard
);
1450 n
= pgn_tag_find(g
->tag
, "Setup");
1451 i
= pgn_tag_find(g
->tag
, "FEN");
1455 * If the FEN tag exists and there is no SetUp tag go ahead and parse it.
1456 * If there is a SetUp tag only parse the FEN tag if the value is 1.
1458 if ((n
>= 0 && i
>= 0 && atoi(g
->tag
[n
]->value
) == 1)
1459 || (i
>= 0 && n
== -1) || fen
) {
1460 if ((n
= parse_fen_line(tmpboard
, &flags
, &turn
, &ply
,
1461 (fen
) ? fen
: g
->tag
[i
]->value
)) != E_PGN_OK
)
1464 memcpy(b
, tmpboard
, sizeof(BOARD
));
1465 CLEAR_FLAG(g
->flags
, GF_WK_CASTLE
);
1466 CLEAR_FLAG(g
->flags
, GF_WQ_CASTLE
);
1467 CLEAR_FLAG(g
->flags
, GF_BK_CASTLE
);
1468 CLEAR_FLAG(g
->flags
, GF_BQ_CASTLE
);
1475 return (i
>= 0 && n
>= 0) ? E_PGN_OK
: E_PGN_ERR
;
1481 * Allocates a new game and increments 'gtotal'. 'gindex' is then set to the
1482 * new game. Returns E_PGN_ERR if there was a memory allocation error or
1483 * E_PGN_OK on success.
1485 pgn_error_t
pgn_new_game()
1492 PGN_DUMP("%s:%d: allocating new game\n", __FILE__
, __LINE__
);
1496 if ((g
= realloc(game
, t
* sizeof(GAME
*))) == NULL
) {
1503 if ((newg
= calloc(1, sizeof(struct game_s
))) == NULL
) {
1508 game
[gindex
] = newg
;
1510 if ((game
[gindex
]->hp
= calloc(1, sizeof(HISTORY
*))) == NULL
) {
1516 game
[gindex
]->hp
[0] = NULL
;
1517 game
[gindex
]->history
= game
[gindex
]->hp
;
1518 game
[gindex
]->side
= game
[gindex
]->turn
= WHITE
;
1519 SET_FLAG(game
[gindex
]->flags
, GF_WK_CASTLE
|GF_WQ_CASTLE
|GF_WQ_CASTLE
|
1520 GF_BK_CASTLE
|GF_BQ_CASTLE
);
1521 pgn_board_init(pgn_board
);
1522 set_default_tags(game
[gindex
]);
1527 static int read_file(FILE *fp
)
1530 char buf
[LINE_MAX
] = {0}, *p
= buf
;
1533 int parse_error
= 0;
1542 * A parse error may have occured at EOF.
1545 pgn_ret
= E_PGN_PARSE
;
1550 SET_FLAG(game
[gindex
]->flags
, GF_PERROR
);
1553 if ((c
= Fgetc(fp
)) == EOF
) {
1571 nextchar
= Fgetc(fp
);
1572 Ungetc(nextchar
, fp
);
1575 * If there was a move text parsing error, keep reading until the end
1576 * of the current game discarding the data.
1579 pgn_ret
= E_PGN_PARSE
;
1581 if (game
[gindex
]->ravlevel
)
1584 if (pgn_config
.stop
)
1587 if (c
== '\n' && (nextchar
== '\n' || nextchar
== '\015')) {
1596 // New game reached.
1597 if (c
== '\n' && (nextchar
== '\n' || nextchar
== '\015')) {
1607 * PGN: Application comment. The '%' must be on the first column of
1608 * the line. The comment continues until the end of the current line.
1611 if (lastchar
== '\n' || lastchar
== 0) {
1612 while ((c
= Fgetc(fp
)) != EOF
&& c
!= '\n');
1616 // Not sure what to do here.
1623 if (c
== '<' || c
== '>')
1627 * PGN: Recurrsive Annotated Variation. Read rav_text() for more
1630 if (c
== '(' || c
== ')') {
1631 switch (rav_text(game
[gindex
], fp
, c
, old
)) {
1634 * This is the end of the current RAV. This function has
1635 * been called from rav_text(). Returning from this point
1636 * will put us back in rav_text().
1638 if (game
[gindex
]->ravlevel
> 0)
1642 * We're back at the root move. Continue as normal.
1650 * Continue processing-> Probably the root move.
1655 if (!game
[gindex
]->ravlevel
&& pgn_rav
)
1661 // PGN: Numeric Annotation Glyph.
1662 if (c
== '$' || c
== '!' || c
== '?' || c
== '+' || c
== '-' ||
1663 c
== '~' || c
== '=') {
1665 nag_text(game
[gindex
], fp
);
1670 * PGN: Annotation. The ';' comment continues until the end of the
1671 * current line. The '{' type comment continues until a '}' is
1674 if (c
== '{' || c
== ';') {
1675 annotation_text(game
[gindex
], fp
, (c
== '{') ? '}' : '\n');
1679 // PGN: Roster tag->
1681 // First roster tag found. Initialize the data structures.
1686 if (gtotal
&& pgn_history_total(game
[gindex
]->hp
))
1687 game
[gindex
]->hindex
= pgn_history_total(game
[gindex
]->hp
) - 1;
1689 if (pgn_new_game() != E_PGN_OK
) {
1690 pgn_ret
= E_PGN_ERR
;
1694 memcpy(old
, pgn_board
, sizeof(BOARD
));
1697 if (tag_text(game
[gindex
], fp
))
1698 parse_error
= 1; // FEN tag parse error.
1703 // PGN: End-of-game markers.
1704 if ((isdigit(c
) && (nextchar
== '-' || nextchar
== '/')) || c
== '*') {
1707 if (eog_text(game
[gindex
], fp
)) {
1715 if (!game
[gindex
]->done_fen_tag
) {
1716 if (pgn_tag_find(game
[gindex
]->tag
, "FEN") != -1 &&
1717 pgn_board_init_fen(game
[gindex
], pgn_board
, NULL
)) {
1722 game
[gindex
]->pgn_fen_tag
= pgn_tag_find(game
[gindex
]->tag
, "FEN");
1723 game
[gindex
]->done_fen_tag
= 1;
1730 if ((isdigit(c
) && c
!= '0') || VALIDCOL(c
) || c
== 'N' || c
== 'K'
1731 || c
== 'Q' || c
== 'B' || c
== 'R' || c
== 'P' ||
1735 // PGN: If a FEN tag exists, initialize the board to the value.
1737 if (pgn_tag_find(game
[gindex
]->tag
, "FEN") != E_PGN_ERR
&&
1738 (n
= pgn_board_init_fen(game
[gindex
], pgn_board
,
1739 NULL
)) == E_PGN_PARSE
) {
1744 game
[gindex
]->done_fen_tag
= 1;
1745 game
[gindex
]->pgn_fen_tag
= pgn_tag_find(game
[gindex
]->tag
, "FEN");
1750 * PGN: Import format doesn't require a roster tag section. We've
1751 * arrived to the move text section without any tags so we
1752 * initialize a new game which set's the default tags and any tags
1753 * from the configuration file.
1757 game
[gindex
]->hindex
= pgn_history_total(game
[gindex
]->hp
) - 1;
1759 if (pgn_new_game() != E_PGN_OK
) {
1760 pgn_ret
= E_PGN_ERR
;
1764 memcpy(old
, pgn_board
, sizeof(BOARD
));
1768 memcpy(old
, pgn_board
, sizeof(BOARD
));
1770 if (move_text(game
[gindex
], fp
)) {
1771 if (pgn_tag_add(&game
[gindex
]->tag
, "Result", "*") ==
1773 warn("pgn_tag_add()");
1774 pgn_ret
= E_PGN_ERR
;
1777 SET_FLAG(game
[gindex
]->flags
, GF_PERROR
);
1787 PGN_DUMP("%s:%d: unparsed: '%s'\n", __FILE__
, __LINE__
, buf
);
1789 if (strlen(buf
) + 1 == sizeof(buf
))
1790 bzero(buf
, sizeof(buf
));
1800 * Parses a PGN_FILE which was opened with pgn_open(). If 'pgn' is NULL then a
1801 * single empty game will be allocated. If there is a parsing error
1802 * E_PGN_PARSE is returned, if there was a memory allocation error E_PGN_ERR
1803 * is returned, otherwise E_PGN_OK is returned and the global 'gindex' is set
1804 * to the last parsed game in the file and the global 'gtotal' is set to the
1805 * total number of games in the file. The file should be closed with
1806 * pgn_close() after processing.
1808 pgn_error_t
pgn_parse(PGN_FILE
*pgn
)
1814 pgn_ret
= pgn_new_game();
1820 fseek(pgn
->fp
, 0, SEEK_END
);
1821 pgn_fsize
= ftell(pgn
->fp
);
1822 fseek(pgn
->fp
, 0, SEEK_SET
);
1824 PGN_DUMP("%s:%d: BEGIN parsing->..\n", __FILE__
, __LINE__
);
1826 pgn_ret
= read_file(pgn
->fp
);
1829 PGN_DUMP("%s:%d: END parsing->..\n", __FILE__
, __LINE__
);
1836 gtotal
= gindex
+ 1;
1838 for (i
= 0; i
< gtotal
; i
++) {
1839 game
[i
]->history
= game
[i
]->hp
;
1840 game
[i
]->hindex
= pgn_history_total(game
[i
]->hp
);
1847 * Escape '"' and '\' in tag values.
1849 static char *pgn_tag_add_escapes(const char *str
)
1852 int len
= strlen(str
);
1853 static char buf
[MAX_PGN_LINE_LEN
] = {0};
1855 for (i
= n
= 0; i
< len
; i
++, n
++) {
1872 static void Fputc(int c
, FILE *fp
, int *len
)
1876 if (c
!= '\n' && i
+ 1 > 80)
1877 Fputc('\n', fp
, &i
);
1879 if (pgn_lastc
== '\n' && c
== ' ') {
1884 if (fputc(c
, fp
) == EOF
)
1897 static void putstring(FILE *fp
, char *str
, int *len
)
1901 for (p
= str
; *p
; p
++) {
1904 while (*p
&& *p
!= ' ')
1908 Fputc('\n', fp
, len
);
1916 * See pgn_write() for more info.
1918 static void write_comments_and_nag(FILE *fp
, HISTORY
*h
, int *len
)
1923 PGN_DUMP("%s:%d: writing comments and nag\n", __FILE__
, __LINE__
);
1926 for (i
= 0; i
< MAX_PGN_NAG
; i
++) {
1928 Fputc(' ', fp
, len
);
1929 Fputc('$', fp
, len
);
1930 putstring(fp
, itoa(h
->nag
[i
]), len
);
1935 Fputc('\n', fp
, len
);
1936 putstring(fp
, " {", len
);
1937 putstring(fp
, h
->comment
, len
);
1938 Fputc('}', fp
, len
);
1942 static void write_move_text(FILE *fp
, HISTORY
*h
, int *len
)
1944 Fputc(' ', fp
, len
);
1945 putstring(fp
, h
->move
, len
);
1947 if (!pgn_config
.reduced
)
1948 write_comments_and_nag(fp
, h
, len
);
1951 static void write_all_move_text(FILE *fp
, HISTORY
**h
, int m
, int *len
)
1954 HISTORY
**hp
= NULL
;
1956 for (i
= 0; h
[i
]; i
++) {
1957 if (pgn_write_turn
== WHITE
) {
1958 if (pgn_config
.mpl
&& pgn_mpl
== pgn_config
.mpl
) {
1960 Fputc('\n', fp
, len
);
1964 Fputc(' ', fp
, len
);
1966 if (strlen(itoa(m
)) + 1 + *len
> 80)
1967 Fputc('\n', fp
, len
);
1969 putstring(fp
, itoa(m
), len
);
1970 Fputc('.', fp
, len
);
1974 write_move_text(fp
, h
[i
], len
);
1976 if (!pgn_config
.reduced
&& h
[i
]->rav
) {
1978 int oldturn
= pgn_write_turn
;
1981 putstring(fp
, " (", len
);
1984 * If it's WHITE's turn the move number will be added above after
1985 * the call to write_all_move_text() below.
1987 if (pgn_write_turn
== BLACK
) {
1988 putstring(fp
, itoa(m
), len
);
1989 putstring(fp
, "...", len
);
1993 write_all_move_text(fp
, hp
, m
, len
);
1995 pgn_write_turn
= oldturn
;
1996 putstring(fp
, ")", len
);
1999 if (ravlevel
&& h
[i
+ 1])
2000 Fputc(' ', fp
, len
);
2002 if (h
[i
+ 1] && !ravlevel
)
2003 Fputc(' ', fp
, len
);
2005 if (pgn_write_turn
== WHITE
&& h
[i
+ 1]) {
2006 putstring(fp
, itoa(m
), len
);
2007 putstring(fp
, "...", len
);
2011 if (pgn_write_turn
== BLACK
)
2014 pgn_write_turn
= (pgn_write_turn
== WHITE
) ? BLACK
: WHITE
;
2018 static char *compression_cmd(const char *filename
, int expand
)
2020 static char command
[FILENAME_MAX
];
2021 int len
= strlen(filename
);
2023 if (filename
[len
- 4] == '.' && filename
[len
- 3] == 'z' &&
2024 filename
[len
- 2] == 'i' && filename
[len
- 1] == 'p' &&
2025 filename
[len
] == '\0') {
2027 snprintf(command
, sizeof(command
), "unzip -p %s 2>/dev/null",
2030 snprintf(command
, sizeof(command
), "zip -9 >%s 2>/dev/null",
2035 else if (filename
[len
- 3] == '.' && filename
[len
- 2] == 'g' &&
2036 filename
[len
- 1] == 'z' && filename
[len
] == '\0') {
2038 snprintf(command
, sizeof(command
), "gzip -dc %s", filename
);
2040 snprintf(command
, sizeof(command
), "gzip -c 1>%s", filename
);
2044 else if (filename
[len
- 2] == '.' && filename
[len
- 1] == 'Z' &&
2045 filename
[len
] == '\0') {
2047 snprintf(command
, sizeof(command
), "uncompress -c %s", filename
);
2049 snprintf(command
, sizeof(command
), "compress -c 1>%s", filename
);
2053 else if ((filename
[len
- 4] == '.' && filename
[len
- 3] == 'b' &&
2054 filename
[len
- 2] == 'z' && filename
[len
- 1] == '2' &&
2055 filename
[len
] == '\0') || (filename
[len
- 3] == '.' &&
2056 filename
[len
- 2] == 'b' && filename
[len
- 1] == 'z' &&
2057 filename
[len
] == '\0')) {
2059 snprintf(command
, sizeof(command
), "bzip2 -dc %s", filename
);
2061 snprintf(command
, sizeof(command
), "bzip2 -zc 1>%s", filename
);
2069 static int copy_file(FILE *fp
, const char *dst
)
2072 char line
[LINE_MAX
];
2073 char *cmd
= compression_cmd(dst
, 0);
2075 if ((ofp
= popen(cmd
, "w")) == NULL
)
2078 fseek(fp
, 0, SEEK_SET
);
2080 while ((fgets(line
, sizeof(line
), fp
)) != NULL
)
2081 fprintf(ofp
, "%s", line
);
2088 * Closes and free's a PGN file handle.
2090 pgn_error_t
pgn_close(PGN_FILE
*pgn
)
2093 return E_PGN_INVALID
;
2097 * Appending to a compressed file.
2100 if (copy_file(pgn
->fp
, pgn
->filename
))
2104 unlink(pgn
->tmpfile
);
2113 free(pgn
->filename
);
2119 * Opens a file 'filename' with the given 'mode'. 'mode' should be "r" for
2120 * reading, "w" for writing (will truncate if the file exists) or "a" for
2121 * appending to an existing file or creating a new one. Returns E_PGN_OK on
2122 * success and sets 'result' to a file handle for use will the other file
2123 * functions or E_PGN_ERR if there is an error opening the file in which case
2124 * errno will be set to the error or E_PGN_INVALID if 'mode' is an invalid
2125 * mode or if 'filename' is not a regular file.
2127 pgn_error_t
pgn_open(const char *filename
, const char *mode
, PGN_FILE
**result
)
2129 FILE *fp
= NULL
, *tfp
= NULL
;
2130 char buf
[PATH_MAX
], *p
;
2136 int ret
= E_PGN_ERR
;
2140 PGN_DUMP("%s:%d: BEGIN opening %s\n", __FILE__
, __LINE__
, filename
);
2143 if (!filename
|| !mode
)
2144 return E_PGN_INVALID
;
2146 if (strcmp(mode
, "r") == 0)
2148 else if (strcmp(mode
, "w") == 0)
2150 else if (strcmp(mode
, "a") == 0) {
2155 return E_PGN_INVALID
;
2158 pgn
= calloc(1, sizeof(PGN_FILE
));
2163 if (strcmp(filename
, "-") != 0) {
2164 if (m
&& access(filename
, R_OK
) == -1)
2167 if (stat(filename
, &st
) == -1 && !m
&& errno
!= ENOENT
)
2170 if (m
&& !S_ISREG(st
.st_mode
)) {
2171 ret
= E_PGN_INVALID
;
2175 if ((cmd
= compression_cmd(filename
, m
)) != NULL
) {
2178 if (append
&& access(filename
, R_OK
) == 0) {
2182 cmd
= compression_cmd(filename
, 1);
2184 if ((fp
= popen(cmd
, "r")) == NULL
)
2187 if (tmpnam(tmp
) == NULL
)
2190 if ((fd
= open(tmp
, O_RDWR
|O_EXCL
|O_CREAT
)) == -1)
2193 if ((tfp
= fdopen(fd
, "a+")) == NULL
)
2196 while ((p
= fgets(buf
, sizeof(buf
), fp
)) != NULL
)
2197 fprintf(tfp
, "%s", p
);
2201 pgn
->tmpfile
= strdup(tmp
);
2205 if ((fp
= popen(cmd
, m
? "r" : "w")) == NULL
)
2209 if ((tfp
= tmpfile()) == NULL
)
2212 while ((p
= fgets(buf
, sizeof(buf
), fp
)) != NULL
)
2213 fprintf(tfp
, "%s", p
);
2222 if ((fp
= fopen(filename
, mode
)) == NULL
)
2232 if (*filename
!= '/') {
2233 if (getcwd(buf
, sizeof(buf
)) == NULL
) {
2240 asprintf(&p
, "%s/%s", buf
, filename
);
2244 pgn
->filename
= strdup(filename
);
2258 * Returns E_PGN_OK if 'filename' is a recognized compressed filetype or
2261 pgn_error_t
pgn_is_compressed(const char *filename
)
2263 if (compression_cmd(filename
, 0))
2270 * Gets the value of config flag 'f'. The next argument should be a pointer of
2271 * the config type which is set to the value of 'f'. Returns E_PGN_ERR if 'f'
2272 * is an invalid flag or E_PGN_OK on success.
2274 pgn_error_t
pgn_config_get(pgn_config_flag f
, ...)
2279 pgn_progress
*progress
;
2284 case PGN_STRICT_CASTLING
:
2285 intval
= va_arg(ap
, int *);
2286 *intval
= pgn_config
.strict_castling
;
2290 intval
= va_arg(ap
, int *);
2291 *intval
= pgn_config
.reduced
;
2295 intval
= va_arg(ap
, int *);
2296 *intval
= pgn_config
.mpl
;
2299 case PGN_STOP_ON_ERROR
:
2300 intval
= va_arg(ap
, int *);
2301 *intval
= pgn_config
.stop
;
2305 longval
= va_arg(ap
, long *);
2306 *longval
= pgn_config
.stop
;
2309 case PGN_PROGRESS_FUNC
:
2310 progress
= va_arg(ap
, pgn_progress
*);
2311 *progress
= pgn_config
.pfunc
;
2316 intval
= va_arg(ap
, int *);
2317 *intval
= dumptofile
;
2329 * Sets config flag 'f' to the next argument. Returns E_PGN_OK on success or
2330 * E_PGN_ERR if 'f' is an invalid flag or E_PGN_INVALID if 'val' is an invalid
2333 pgn_error_t
pgn_config_set(pgn_config_flag f
, ...)
2343 n
= va_arg(ap
, int);
2345 if (n
!= 1 && n
!= 0) {
2346 ret
= E_PGN_INVALID
;
2350 pgn_config
.reduced
= n
;
2353 n
= va_arg(ap
, int);
2356 ret
= E_PGN_INVALID
;
2362 case PGN_STOP_ON_ERROR
:
2363 n
= va_arg(ap
, int);
2365 if (n
!= 1 && n
!= 0) {
2366 ret
= E_PGN_INVALID
;
2370 pgn_config
.stop
= n
;
2373 n
= va_arg(ap
, long);
2374 pgn_config
.progress
= n
;
2376 case PGN_PROGRESS_FUNC
:
2377 pgn_config
.pfunc
= va_arg(ap
, pgn_progress
);
2379 case PGN_STRICT_CASTLING
:
2380 n
= va_arg(ap
, int);
2381 pgn_config
.strict_castling
= n
;
2385 n
= va_arg(ap
, int);
2386 dumptofile
= (n
> 0) ? 1 : 0;
2399 * Writes a PGN formatted game 'g' to a file which was opened with pgn_open().
2400 * See 'pgn_config_flag' for output options. Returns E_PGN_ERR if there was a
2401 * memory allocation or write error and E_PGN_OK on success. It is important
2402 * to use pgn_close() afterwards if the file is a recognized compressed file
2403 * type otherwise the created temporary file wont be copied to the destination
2406 pgn_error_t
pgn_write(PGN_FILE
*pgn
, GAME g
)
2414 pgn_write_turn
= (TEST_FLAG(g
->flags
, GF_BLACK_OPENING
)) ? BLACK
: WHITE
;
2415 pgn_tag_sort(g
->tag
);
2418 PGN_DUMP("%s:%d: writing tag section\n", __FILE__
, __LINE__
);
2421 for (i
= 0; g
->tag
[i
]; i
++) {
2423 char tbuf
[11] = {0};
2426 if (pgn_config
.reduced
&& i
== 7)
2429 if (strcmp(g
->tag
[i
]->name
, "Date") == 0) {
2430 memset(&tp
, 0, sizeof(struct tm
));
2432 if (strptime(g
->tag
[i
]->value
, PGN_TIME_FORMAT
, &tp
) != NULL
) {
2433 len
= strftime(tbuf
, sizeof(tbuf
), PGN_TIME_FORMAT
, &tp
) + 1;
2435 if ((tmp
= strdup(tbuf
)) == NULL
)
2438 free(g
->tag
[i
]->value
);
2439 g
->tag
[i
]->value
= tmp
;
2442 else if (strcmp(g
->tag
[i
]->name
, "Event") == 0) {
2443 if (g
->tag
[i
]->value
[0] == '\0') {
2444 if ((tmp
= strdup("?")) == NULL
)
2447 free(g
->tag
[i
]->value
);
2448 g
->tag
[i
]->value
= tmp
;
2451 else if (strcmp(g
->tag
[i
]->name
, "Site") == 0) {
2452 if (g
->tag
[i
]->value
[0] == '\0') {
2453 if ((tmp
= strdup("?")) == NULL
)
2456 free(g
->tag
[i
]->value
);
2457 g
->tag
[i
]->value
= tmp
;
2460 else if (strcmp(g
->tag
[i
]->name
, "Round") == 0) {
2461 if (g
->tag
[i
]->value
[0] == '\0') {
2462 if ((tmp
= strdup("?")) == NULL
)
2465 free(g
->tag
[i
]->value
);
2466 g
->tag
[i
]->value
= tmp
;
2469 else if (strcmp(g
->tag
[i
]->name
, "Result") == 0) {
2470 if (g
->tag
[i
]->value
[0] == '\0') {
2471 if ((tmp
= strdup("*")) == NULL
)
2474 free(g
->tag
[i
]->value
);
2475 g
->tag
[i
]->value
= tmp
;
2478 else if (strcmp(g
->tag
[i
]->name
, "Black") == 0) {
2479 if (g
->tag
[i
]->value
[0] == '\0') {
2480 if ((tmp
= strdup("?")) == NULL
)
2483 free(g
->tag
[i
]->value
);
2484 g
->tag
[i
]->value
= tmp
;
2487 else if (strcmp(g
->tag
[i
]->name
, "White") == 0) {
2488 if (g
->tag
[i
]->value
[0] == '\0') {
2489 if ((tmp
= strdup("?")) == NULL
)
2492 free(g
->tag
[i
]->value
);
2493 g
->tag
[i
]->value
= tmp
;
2497 fprintf(pgn
->fp
, "[%s \"%s\"]\n", g
->tag
[i
]->name
,
2498 (g
->tag
[i
]->value
&& g
->tag
[i
]->value
[0]) ?
2499 pgn_tag_add_escapes(g
->tag
[i
]->value
) : "");
2503 PGN_DUMP("%s:%d: writing move section\n", __FILE__
, __LINE__
);
2505 Fputc('\n', pgn
->fp
, &len
);
2507 ravlevel
= pgn_mpl
= 0;
2509 if (pgn_history_total(g
->hp
) && pgn_write_turn
== BLACK
)
2510 putstring(pgn
->fp
, "1...", &len
);
2512 write_all_move_text(pgn
->fp
, g
->hp
, 1, &len
);
2514 Fputc(' ', pgn
->fp
, &len
);
2515 putstring(pgn
->fp
, g
->tag
[6]->value
, &len
);
2516 putstring(pgn
->fp
, "\n\n", &len
);
2518 if (!pgn_config
.reduced
)
2519 CLEAR_FLAG(g
->flags
, GF_PERROR
);
2525 * Clears the enpassant flag for all positions on board 'b'. Returns nothing.
2527 void pgn_reset_enpassant(BOARD b
)
2532 PGN_DUMP("%s:%d: resetting enpassant\n", __FILE__
, __LINE__
);
2535 for (r
= 0; r
< 8; r
++) {
2536 for (c
= 0; c
< 8; c
++)
2537 b
[r
][c
].enpassant
= 0;