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
26 #include <sys/types.h>
54 static int Fgetc(FILE *fp
)
58 if ((c
= fgetc(fp
)) != EOF
) {
59 if (pgn_config
.progress
&& pgn_config
.pfunc
) {
60 if (!(ftell(fp
) % pgn_config
.progress
))
61 pgn_config
.pfunc(pgn_fsize
, ftell(fp
));
68 static int Ungetc(int c
, FILE *fp
)
75 return "libchess " PACKAGE_VERSION
;
78 static char *trim(char *str
)
88 for (i
= strlen(str
) - 1; isspace(str
[i
]); i
--)
94 static char *itoa(long n
)
98 snprintf(buf
, sizeof(buf
), "%li", n
);
103 * Clears the valid move flag for all positions on board 'b'. Returns nothing.
105 void pgn_reset_valid_moves(BOARD b
)
110 PGN_DUMP("%s:%d: resetting valid moves\n", __FILE__
, __LINE__
);
113 for (row
= 0; row
< 8; row
++) {
114 for (col
= 0; col
< 8; col
++)
115 b
[row
][col
].valid
= 0;
120 * Toggles g->turn. Returns nothing.
122 void pgn_switch_turn(GAME g
)
124 g
->turn
= (g
->turn
== WHITE
) ? BLACK
: WHITE
;
128 * Toggles g->side and switches the White and Black roster tags. Returns
131 void pgn_switch_side(GAME g
)
133 char *w
= g
->tag
[4]->value
;
135 g
->tag
[4]->value
= g
->tag
[5]->value
;
136 g
->tag
[5]->value
= w
;
137 g
->side
= (g
->side
== WHITE
) ? BLACK
: WHITE
;
141 * Creates a FEN tag from the current game 'g', history move (g->hindex) and
142 * board 'b'. Returns a FEN tag.
144 char *pgn_game_to_fen(GAME g
, BOARD b
)
148 static char buf
[MAX_PGN_LINE_LEN
], *p
;
149 int oldturn
= g
->turn
;
150 char enpassant
[3] = {0}, *e
;
154 PGN_DUMP("%s:%d: creating FEN tag\n", __FILE__
, __LINE__
);
157 for (i
= pgn_history_total(g
->hp
); i
>= g
->hindex
- 1; i
--)
162 for (row
= 0; row
< 8; row
++) {
165 for (col
= 0; col
< 8; col
++) {
166 if (b
[row
][col
].enpassant
) {
167 b
[row
][col
].icon
= pgn_int_to_piece(WHITE
, OPEN_SQUARE
);
170 *e
++ = ('0' + 8) - row
;
174 if (pgn_piece_to_int(b
[row
][col
].icon
) == OPEN_SQUARE
) {
184 *p
++ = b
[row
][col
].icon
;
198 *p
++ = (g
->turn
== WHITE
) ? 'w' : 'b';
201 if (TEST_FLAG(g
->flags
, GF_WK_CASTLE
) && pgn_piece_to_int(b
[7][7].icon
) ==
202 ROOK
&& isupper(b
[7][7].icon
) && pgn_piece_to_int(b
[7][4].icon
) ==
203 KING
&& isupper(b
[7][4].icon
)) {
208 if (TEST_FLAG(g
->flags
, GF_WQ_CASTLE
) && pgn_piece_to_int(b
[7][0].icon
) ==
209 ROOK
&& isupper(b
[7][0].icon
) && pgn_piece_to_int(b
[7][4].icon
) ==
210 KING
&& isupper(b
[7][4].icon
)) {
215 if (TEST_FLAG(g
->flags
, GF_BK_CASTLE
) && pgn_piece_to_int(b
[0][7].icon
) ==
216 ROOK
&& islower(b
[0][7].icon
) && pgn_piece_to_int(b
[0][4].icon
) ==
217 KING
&& islower(b
[0][4].icon
)) {
222 if (TEST_FLAG(g
->flags
, GF_BQ_CASTLE
) && pgn_piece_to_int(b
[0][0].icon
) ==
223 ROOK
&& islower(b
[0][0].icon
) && pgn_piece_to_int(b
[0][4].icon
) ==
224 KING
&& islower(b
[0][4].icon
)) {
246 strcat(p
, itoa(g
->ply
));
247 p
= buf
+ strlen(buf
);
251 i
= (g
->hindex
+ 1) / 2;
253 strcat(p
, itoa((g
->hindex
/ 2) + (g
->hindex
% 2)));
260 * Returns the total number of moves in 'h' or 0 if there are none.
262 int pgn_history_total(HISTORY
**h
)
269 for (i
= 0; h
[i
]; i
++);
274 * Deallocates all of the history data from position 'start' in the array 'h'.
277 void pgn_history_free(HISTORY
**h
, int start
)
282 PGN_DUMP("%s:%d: freeing history\n", __FILE__
, __LINE__
);
285 if (!h
|| start
> pgn_history_total(h
))
291 for (i
= start
; h
[i
]; i
++) {
293 pgn_history_free(h
[i
]->rav
, 0);
307 * Returns the history ply 'n' from 'h'. If 'n' is out of range then NULL is
310 HISTORY
*pgn_history_by_n(HISTORY
**h
, int n
)
312 if (n
< 0 || n
> pgn_history_total(h
) - 1)
319 * Appends move 'm' to game 'g' history pointer and creates a FEN tag for the
320 * current game state using board 'b'. The FEN tag makes things faster than
321 * validating the entire move history by validating only the current move to
322 * the previous moves FEN tag. The history pointer may be a in a RAV so
323 * g->rav.hp is also updated to the new (realloc()'ed) pointer. If not in a
324 * RAV then g->history will be updated. Returns E_PGN_ERR if realloc() failed
325 * or E_PGN_OK on success.
327 pgn_error_t
pgn_history_add(GAME g
, BOARD b
, const char *m
)
329 int t
= pgn_history_total(g
->hp
);
331 HISTORY
**h
= NULL
, *tmp
;
332 int ri
= (g
->ravlevel
) ? g
->rav
[g
->ravlevel
- 1].hindex
: 0;
335 PGN_DUMP("%s:%d: adding '%s' to move history\n", __FILE__
, __LINE__
, m
);
339 o
= g
->rav
[g
->ravlevel
- 1].hp
[ri
-1]->rav
- g
->hp
;
341 o
= g
->history
- g
->hp
;
343 if ((h
= realloc(g
->hp
, (t
+ 2) * sizeof(HISTORY
*))) == NULL
)
349 g
->rav
[g
->ravlevel
- 1].hp
[ri
-1]->rav
= g
->hp
+ o
;
351 g
->history
= g
->hp
+ o
;
353 if ((g
->hp
[t
] = calloc(1, sizeof(HISTORY
))) == NULL
)
356 if ((g
->hp
[t
]->move
= strdup(m
)) == NULL
) {
365 g
->hindex
= pgn_history_total(g
->hp
);
367 tmp
->fen
= strdup(pgn_game_to_fen(g
, b
));
373 * Resets the game 'g' using board 'b' up to history move (g->hindex) 'n'.
374 * Returns E_PGN_OK on success or E_PGN_PARSE if there was a FEN tag but the
375 * parsing of it failed. Or returns E_PGN_INVALID if somehow the move failed
376 * validation while resetting.
378 pgn_error_t
pgn_board_update(GAME g
, BOARD b
, int n
)
382 int p_error
= TEST_FLAG(g
->flags
, GF_PERROR
);
383 int black_opening
= TEST_FLAG(g
->flags
, GF_BLACK_OPENING
);
386 PGN_DUMP("%s:%d: updating board\n", __FILE__
, __LINE__
);
389 if (!g
->ravlevel
&& TEST_FLAG(g
->flags
, GF_BLACK_OPENING
))
395 SET_FLAG(g
->flags
, GF_WK_CASTLE
|GF_WQ_CASTLE
|GF_WQ_CASTLE
|
396 GF_BK_CASTLE
|GF_BQ_CASTLE
);
400 pgn_board_init_fen(g
, tb
, g
->rav
[g
->ravlevel
- 1].fen
);
401 else if (pgn_tag_find(g
->tag
, "FEN") != -1 &&
402 pgn_board_init_fen(g
, tb
, NULL
))
406 HISTORY
*h
= pgn_history_by_n(g
->hp
, n
-1);
409 ret
= pgn_board_init_fen(g
, tb
, h
->fen
);
410 if (ret
== E_PGN_OK
) {
411 h
= pgn_history_by_n(g
->hp
, n
);
413 char *p
= h
->move
, *frfr
= NULL
;
415 ret
= pgn_parse_move(g
, tb
, &p
, &frfr
);
416 if (ret
== E_PGN_OK
) {
417 h
= pgn_history_by_n(g
->hp
, n
-1);
418 ret
= pgn_board_init_fen(g
, tb
, h
->fen
);
428 memcpy(b
, tb
, sizeof(BOARD
));
431 SET_FLAG(g
->flags
, GF_PERROR
);
434 SET_FLAG(g
->flags
, GF_BLACK_OPENING
);
440 * Updates the game 'g' using board 'b' to the next 'n'th history move.
443 void pgn_history_prev(GAME g
, BOARD b
, int n
)
445 if (g
->hindex
- n
< 0) {
447 g
->hindex
= pgn_history_total(g
->hp
);
454 pgn_board_update(g
, b
, g
->hindex
);
458 * Updates the game 'g' using board 'b' to the previous 'n'th history move.
461 void pgn_history_next(GAME g
, BOARD b
, int n
)
463 if (g
->hindex
+ n
> pgn_history_total(g
->hp
)) {
467 g
->hindex
= pgn_history_total(g
->hp
);
472 pgn_board_update(g
, b
, g
->hindex
);
476 * Converts the character piece 'p' to an integer. Returns the integer
477 * associated with 'p' or E_PGN_ERR if 'p' is invalid.
479 int pgn_piece_to_int(int p
)
504 PGN_DUMP("%s:%d: invalid piece '%c'\n", __FILE__
, __LINE__
, p
);
510 * Converts the integer piece 'n' to a character whose turn is 'turn'. WHITE
511 * piece are uppercase and BLACK pieces are lowercase. Returns the character
512 * associated with 'n' or E_PGN_ERR if 'n' is an invalid piece.
514 pgn_error_t
pgn_int_to_piece(char turn
, int n
)
542 PGN_DUMP("%s:%d: unknown piece integer %i\n", __FILE__
,
549 return (turn
== WHITE
) ? toupper(p
) : p
;
553 * Finds a tag 'name' in the structure array 't'. Returns the location in the
554 * array of the found tag or E_PGN_ERR if the tag could not be found.
556 pgn_error_t
pgn_tag_find(TAG
**t
, const char *name
)
560 for (i
= 0; t
[i
]; i
++) {
561 if (strcasecmp(t
[i
]->name
, name
) == 0)
568 static pgn_error_t
remove_tag(TAG
***array
, const char *tag
)
571 int n
= pgn_tag_find(tags
, tag
);
577 for (i
= t
= 0; tags
[i
]; i
++) {
580 free(tags
[i
]->value
);
588 tags
= realloc(*array
, (t
+ 1) * sizeof(TAG
*));
592 PGN_DUMP("%s:%d: removed tag: name='%s'\n", __FILE__
, __LINE__
, tag
);
597 static int tag_compare(const void *a
, const void *b
)
602 return strcmp((*ta
)->name
, (*tb
)->name
);
606 * Sorts a tag array. The first seven tags are in order of the PGN standard so
607 * don't sort'em. Returns nothing.
609 void pgn_tag_sort(TAG
**tags
)
611 if (pgn_tag_total(tags
) <= 7)
614 qsort(tags
+ 7, pgn_tag_total(tags
) - 7, sizeof(TAG
*), tag_compare
);
618 * Returns the total number of tags in 't' or 0 if 't' is NULL.
620 int pgn_tag_total(TAG
**tags
)
634 * Adds a tag 'name' with value 'value' to the pointer to array of TAG
635 * pointers 'dst'. If a duplicate tag 'name' was found then the existing tag
636 * is updated to the new 'value'. If 'value' is NULL, the tag is removed.
637 * Returns E_PGN_ERR if there was a memory allocation error or E_PGN_OK on
640 pgn_error_t
pgn_tag_add(TAG
***dst
, char *name
, char *value
)
645 int t
= pgn_tag_total(tdata
);
648 PGN_DUMP("%s:%d: adding tag\n", __FILE__
, __LINE__
);
659 // Find an existing tag with 'name'.
660 for (i
= 0; i
< t
; i
++) {
663 if (strcasecmp(tdata
[i
]->name
, name
) == 0) {
665 if ((tmp
= strdup(value
)) == NULL
)
669 remove_tag(dst
, name
);
673 free(tdata
[i
]->value
);
674 tdata
[i
]->value
= tmp
;
680 if ((a
= realloc(tdata
, (t
+ 2) * sizeof(TAG
*))) == NULL
)
685 if ((tdata
[t
] = malloc(sizeof(TAG
))) == NULL
)
688 if ((tdata
[t
]->name
= strdup(name
)) == NULL
) {
694 if ((tdata
[t
]->value
= strdup(value
)) == NULL
) {
695 free(tdata
[t
]->name
);
701 tdata
[t
]->value
= NULL
;
703 tdata
[t
]->name
[0] = toupper(tdata
[t
]->name
[0]);
708 PGN_DUMP("%s:%d: added tag: name='%s' value='%s'\n", __FILE__
, __LINE__
,
709 name
, (value
) ? value
: "null");
715 static char *remove_tag_escapes(const char *str
)
718 int len
= strlen(str
);
719 static char buf
[MAX_PGN_LINE_LEN
] = {0};
721 for (i
= n
= 0; i
< len
; i
++, n
++) {
737 * Resets or initializes a new game board 'b'. Returns nothing.
739 void pgn_board_init(BOARD b
)
744 PGN_DUMP("%s:%d: initializing board\n", __FILE__
, __LINE__
);
747 memset(b
, 0, sizeof(BOARD
));
749 for (row
= 0; row
< 8; row
++) {
750 for (col
= 0; col
< 8; col
++) {
783 b
[row
][col
].icon
= (row
< 2) ? c
: toupper(c
);
789 * Adds the standard PGN roster tags to game 'g'.
791 static void set_default_tags(GAME g
)
796 struct passwd
*pw
= getpwuid(getuid());
799 tp
= localtime(&now
);
800 strftime(tbuf
, sizeof(tbuf
), PGN_TIME_FORMAT
, tp
);
802 /* The standard seven tag roster (in order of appearance). */
803 if (pgn_tag_add(&g
->tag
, "Event", "?") != E_PGN_OK
)
804 warn("pgn_tag_add()");
806 if (pgn_tag_add(&g
->tag
, "Site", "?") != E_PGN_OK
)
807 warn("pgn_tag_add()");
809 if (pgn_tag_add(&g
->tag
, "Date", tbuf
) != E_PGN_OK
)
810 warn("pgn_tag_add()");
812 if (pgn_tag_add(&g
->tag
, "Round", "-") != E_PGN_OK
)
813 warn("pgn_tag_add()");
815 if (pgn_tag_add(&g
->tag
, "White", pw
->pw_gecos
) != E_PGN_OK
)
816 warn("pgn_tag_add()");
818 if (pgn_tag_add(&g
->tag
, "Black", "?") != E_PGN_OK
)
819 warn("pgn_tag_add()");
821 if (pgn_tag_add(&g
->tag
, "Result", "*") != E_PGN_OK
)
822 warn("pgn_tag_add()");
826 * Frees a TAG array. Returns nothing.
828 void pgn_tag_free(TAG
**tags
)
831 int t
= pgn_tag_total(tags
);
834 PGN_DUMP("%s:%d: freeing tags\n", __FILE__
, __LINE__
);
840 for (i
= 0; i
< t
; i
++) {
842 free(tags
[i
]->value
);
850 * Frees a single game 'g'. Returns nothing.
852 void pgn_free(GAME g
)
855 PGN_DUMP("%s:%d: freeing game\n", __FILE__
, __LINE__
);
857 pgn_history_free(g
->history
, 0);
859 pgn_tag_free(g
->tag
);
862 for (g
->ravlevel
--; g
->ravlevel
>= 0; g
->ravlevel
--)
863 free(g
->rav
[g
->ravlevel
].fen
);
872 * Frees all games in the global 'game' array. Returns nothing.
879 PGN_DUMP("%s:%d: freeing game data\n", __FILE__
, __LINE__
);
882 for (i
= 0; i
< gtotal
; i
++) {
892 static void reset_game_data()
895 PGN_DUMP("%s:%d: resetting game data\n", __FILE__
, __LINE__
);
901 static void skip_leading_space(FILE *fp
)
905 while ((c
= Fgetc(fp
)) != EOF
&& !feof(fp
)) {
914 * PGN move text section.
916 static int move_text(GAME g
, FILE *fp
)
918 char m
[MAX_SAN_MOVE_LEN
+ 1] = {0}, *p
;
923 g
->oflags
= g
->flags
;
925 while((c
= Fgetc(fp
)) != EOF
) {
926 if (isdigit(c
) || isspace(c
) || c
== '.')
934 if (fscanf(fp
, " %[a-hPpRrNnBbQqKk1-9#+=Ox-]%n", m
, &count
) != 1)
937 m
[MAX_SAN_MOVE_LEN
] = 0;
940 if (pgn_parse_move(g
, pgn_board
, &p
, &frfr
)) {
947 PGN_DUMP("%s\n%s", p
, debug_board(pgn_board
));
950 pgn_history_add(g
, pgn_board
, p
);
958 static void nag_text(GAME g
, FILE *fp
)
961 char nags
[5], *n
= nags
;
964 while ((c
= Fgetc(fp
)) != EOF
&& !isspace(c
)) {
966 while ((c
= Fgetc(fp
)) != EOF
&& isdigit(c
))
974 if ((c
= Fgetc(fp
)) == '!')
986 if ((c
= Fgetc(fp
)) == '?')
1000 if ((c
= Fgetc(fp
)) == '+')
1009 else if (c
== '+') {
1010 if ((t
= Fgetc(fp
)) == '=')
1014 else if (t
== '/') {
1015 if ((i
= Fgetc(fp
)) == '-')
1027 else if (c
== '-') {
1028 if ((t
= Fgetc(fp
)) == '+')
1030 else if (t
== '/') {
1031 if ((i
= Fgetc(fp
)) == '+')
1048 nag
= (nags
[0]) ? atoi(nags
) : 0;
1050 if (!nag
|| nag
< 0 || nag
> 255)
1053 // FIXME -1 is because move_text() increments g->hindex. The NAG
1054 // annoatation isnt guaranteed to be after the move text in import format.
1055 for (i
= 0; i
< MAX_PGN_NAG
; i
++) {
1056 if (g
->hp
[g
->hindex
- 1]->nag
[i
])
1059 g
->hp
[g
->hindex
- 1]->nag
[i
] = nag
;
1063 skip_leading_space(fp
);
1067 * PGN move annotation.
1069 static int annotation_text(GAME g
, FILE *fp
, int terminator
)
1071 int c
, lastchar
= 0;
1073 int hindex
= pgn_history_total(g
->hp
) - 1;
1074 char buf
[MAX_PGN_LINE_LEN
], *a
= buf
;
1076 skip_leading_space(fp
);
1078 while ((c
= Fgetc(fp
)) != EOF
&& c
!= terminator
) {
1082 if (isspace(c
) && isspace(lastchar
))
1085 if (len
+ 1 == sizeof(buf
))
1088 *a
++ = lastchar
= c
;
1098 * This annotation is before any move text or NAg-> Allocate a new move.
1100 if (!g
->hp
[hindex
]) {
1101 if ((g
->hp
[hindex
] = calloc(1, sizeof(HISTORY
))) == NULL
)
1105 if ((g
->hp
[hindex
]->comment
= strdup(buf
)) == NULL
)
1114 static int tag_text(GAME g
, FILE *fp
)
1116 char name
[LINE_MAX
], *n
= name
;
1117 char value
[LINE_MAX
], *v
= value
;
1121 skip_leading_space(fp
);
1123 /* The tag name is up until the first whitespace. */
1124 while ((c
= Fgetc(fp
)) != EOF
&& !isspace(c
))
1128 *name
= toupper(*name
);
1129 skip_leading_space(fp
);
1131 /* The value is until the first closing bracket. */
1132 while ((c
= Fgetc(fp
)) != EOF
&& c
!= ']') {
1133 if (i
++ == '\0' && c
== '\"')
1136 if (c
== '\n' || c
== '\t')
1139 if (c
== ' ' && lastchar
== ' ')
1142 lastchar
= *v
++ = c
;
1147 while (isspace(*--v
))
1153 if (value
[0] == '\0') {
1154 if (strcmp(name
, "Result") == 0)
1162 strncpy(value
, remove_tag_escapes(value
), sizeof(value
));
1165 * See eog_text() for an explanation.
1167 if (strcmp(name
, "Result") == 0) {
1168 if (strcmp(value
, "1/2-1/2") == 0) {
1169 if (pgn_tag_add(&g
->tag
, name
, value
) != E_PGN_OK
)
1170 warn("pgn_tag_add()");
1174 if (pgn_tag_add(&g
->tag
, name
, value
) != E_PGN_OK
)
1175 warn("pgn_tag_add()");
1182 * PGN end-of-game marker.
1184 static int eog_text(GAME g
, FILE *fp
)
1187 char buf
[8], *p
= buf
;
1189 while ((c
= Fgetc(fp
)) != EOF
&& !isspace(c
) && i
++ < sizeof(buf
))
1197 if (strcmp(buf
, "1-0") != 0 && strcmp(buf
, "0-1") != 0 &&
1198 strcmp(buf
, "1/2-1/2") != 0 && strcmp(buf
, "*") != 0)
1202 * The eog marker in the move text may not match the actual game result.
1203 * We'll leave it up to the move validator to determine the result unless
1204 * the move validator cannot determine who won and the move text says it's
1207 if (g
->tag
[6]->value
[0] == '*' && strcmp("1/2-1/2", buf
) == 0) {
1208 if (pgn_tag_add(&g
->tag
, "Result", buf
) != E_PGN_OK
)
1209 warn("pgn_tag_add()");
1216 * Parse RAV text and keep track of g->hp. The 'o' argument is the board state
1217 * before the current move (.hindex) was parsed.
1219 static int read_file(FILE *);
1220 static int rav_text(GAME g
, FILE *fp
, int which
, BOARD o
)
1225 // Begin RAV for the current move.
1230 pgn_rav
++; // For detecting parse errors.
1233 * Save the current game state for this RAV depth/level.
1235 if ((r
= realloc(g
->rav
, (g
->ravlevel
+ 1) * sizeof(RAV
))) == NULL
) {
1240 g
->rav
= pgn_rav_p
= r
;
1242 if ((g
->rav
[g
->ravlevel
].fen
= strdup(pgn_game_to_fen(g
, pgn_board
)))
1248 g
->rav
[g
->ravlevel
].hp
= g
->hp
;
1249 g
->rav
[g
->ravlevel
].hindex
= g
->hindex
;
1250 memcpy(&tg
, &(*g
), sizeof(struct game_s
));
1251 g
->flags
= g
->oflags
;
1252 memcpy(pgn_board
, o
, sizeof(BOARD
));
1254 if ((g
->hp
[g
->hindex
- 1]->rav
= calloc(1, sizeof(HISTORY
*))) == NULL
) {
1260 * pgn_history_add() will now append to the new history pointer that
1261 * is g->hp[previous_move]->rav.
1263 g
->hp
= g
->hp
[g
->hindex
- 1]->rav
;
1266 * Reset. Will be restored later from 'tg' which is a local variable
1267 * so recursion is possible.
1273 * Undo move_text()'s switch.
1278 * Now continue as normal as if there were no RAV. Moves will be
1279 * parsed and appended to the new history pointer.
1285 * read_file() has returned. This means that a RAV has ended by this
1286 * function returning -1 (see below). So we restore the game state
1287 * (tg) that was saved before calling read_file().
1289 pgn_board_init_fen(&tg
, pgn_board
, g
->rav
[tg
.ravlevel
].fen
);
1290 free(g
->rav
[tg
.ravlevel
].fen
);
1291 memcpy(&(*g
), &tg
, sizeof(struct game_s
));
1295 * The end of a RAV. This makes the read_file() that called this function
1296 * return (see above and the switch statement in read_file()).
1298 else if (which
== ')') {
1299 pgn_rav
--; // For detecting parse errors.
1300 return (pgn_rav
< 0) ? 1 : -1;
1308 * See pgn_board_init_fen(). Returns E_PGN_PARSE on parse error. 0 may be
1309 * returned on success when there is no move count in the FEN tag otherwise
1310 * the move count is returned.
1312 static int parse_fen_line(BOARD b
, unsigned *flags
, char *turn
, char *ply
,
1316 char line
[LINE_MAX
], *s
;
1317 int row
= 8, col
= 1;
1320 PGN_DUMP("%s:%d: FEN line is '%s'\n", __FILE__
, __LINE__
, str
);
1322 strncpy(line
, str
, sizeof(line
));
1324 pgn_reset_enpassant(b
);
1326 while ((tmp
= strsep(&s
, "/")) != NULL
) {
1329 if (!VALIDFILE(row
))
1336 if (isdigit(*tmp
)) {
1342 for (; n
; --n
, col
++)
1343 b
[RANKTOBOARD(row
)][FILETOBOARD(col
)].icon
=
1344 pgn_int_to_piece(WHITE
, OPEN_SQUARE
);
1346 else if (pgn_piece_to_int(*tmp
) != -1)
1347 b
[RANKTOBOARD(row
)][FILETOBOARD(col
++)].icon
= *tmp
;
1374 while (*tmp
&& *tmp
!= ' ') {
1377 SET_FLAG(*flags
, GF_WK_CASTLE
);
1380 SET_FLAG(*flags
, GF_WQ_CASTLE
);
1383 SET_FLAG(*flags
, GF_BK_CASTLE
);
1386 SET_FLAG(*flags
, GF_BQ_CASTLE
);
1399 if (!VALIDCOL(*tmp
))
1404 if (!VALIDROW(*tmp
))
1407 row
= 8 - atoi(tmp
++);
1408 b
[row
][col
].enpassant
= 1;
1409 SET_FLAG(*flags
, GF_ENPASSANT
);
1420 while (*tmp
&& isdigit(*tmp
))
1430 * It initializes the board (b) to the FEN tag (if found) and sets the
1431 * castling and enpassant info for the game 'g'. If 'fen' is set it should be
1432 * a fen tag and will be parsed rather than the game 'g'.tag FEN tag-> Returns
1433 * E_PGN_OK on success or if there was both a FEN and SetUp tag with the SetUp
1434 * tag set to 0. Returns E_PGN_PARSE if there was a FEN parse error, E_PGN_ERR
1435 * if there was no FEN tag or there was a SetUp tag with a value of 0. Returns
1436 * E_PGN_OK on success.
1438 pgn_error_t
pgn_board_init_fen(GAME g
, BOARD b
, char *fen
)
1443 char turn
= g
->turn
;
1447 PGN_DUMP("%s:%d: initializing board from FEN\n", __FILE__
, __LINE__
);
1449 pgn_board_init(tmpboard
);
1452 n
= pgn_tag_find(g
->tag
, "Setup");
1453 i
= pgn_tag_find(g
->tag
, "FEN");
1457 * If the FEN tag exists and there is no SetUp tag go ahead and parse it.
1458 * If there is a SetUp tag only parse the FEN tag if the value is 1.
1460 if ((n
>= 0 && i
>= 0 && atoi(g
->tag
[n
]->value
) == 1)
1461 || (i
>= 0 && n
== -1) || fen
) {
1462 if ((n
= parse_fen_line(tmpboard
, &flags
, &turn
, &ply
,
1463 (fen
) ? fen
: g
->tag
[i
]->value
)) != E_PGN_OK
)
1466 memcpy(b
, tmpboard
, sizeof(BOARD
));
1467 CLEAR_FLAG(g
->flags
, GF_WK_CASTLE
);
1468 CLEAR_FLAG(g
->flags
, GF_WQ_CASTLE
);
1469 CLEAR_FLAG(g
->flags
, GF_BK_CASTLE
);
1470 CLEAR_FLAG(g
->flags
, GF_BQ_CASTLE
);
1477 return (i
>= 0 && n
>= 0) ? E_PGN_OK
: E_PGN_ERR
;
1483 * Allocates a new game and increments 'gtotal'. 'gindex' is then set to the
1484 * new game. Returns E_PGN_ERR if there was a memory allocation error or
1485 * E_PGN_OK on success.
1487 pgn_error_t
pgn_new_game()
1494 PGN_DUMP("%s:%d: allocating new game\n", __FILE__
, __LINE__
);
1498 if ((g
= realloc(game
, t
* sizeof(GAME
*))) == NULL
) {
1505 if ((newg
= calloc(1, sizeof(struct game_s
))) == NULL
) {
1510 game
[gindex
] = newg
;
1512 if ((game
[gindex
]->hp
= calloc(1, sizeof(HISTORY
*))) == NULL
) {
1518 game
[gindex
]->hp
[0] = NULL
;
1519 game
[gindex
]->history
= game
[gindex
]->hp
;
1520 game
[gindex
]->side
= game
[gindex
]->turn
= WHITE
;
1521 SET_FLAG(game
[gindex
]->flags
, GF_WK_CASTLE
|GF_WQ_CASTLE
|GF_WQ_CASTLE
|
1522 GF_BK_CASTLE
|GF_BQ_CASTLE
);
1523 pgn_board_init(pgn_board
);
1524 set_default_tags(game
[gindex
]);
1529 static int read_file(FILE *fp
)
1532 char buf
[LINE_MAX
] = {0}, *p
= buf
;
1535 int parse_error
= 0;
1544 * A parse error may have occured at EOF.
1547 pgn_ret
= E_PGN_PARSE
;
1552 SET_FLAG(game
[gindex
]->flags
, GF_PERROR
);
1555 if ((c
= Fgetc(fp
)) == EOF
) {
1573 nextchar
= Fgetc(fp
);
1574 Ungetc(nextchar
, fp
);
1577 * If there was a move text parsing error, keep reading until the end
1578 * of the current game discarding the data.
1581 pgn_ret
= E_PGN_PARSE
;
1583 if (game
[gindex
]->ravlevel
)
1586 if (pgn_config
.stop
)
1589 if (c
== '\n' && (nextchar
== '\n' || nextchar
== '\015')) {
1598 // New game reached.
1599 if (c
== '\n' && (nextchar
== '\n' || nextchar
== '\015')) {
1609 * PGN: Application comment. The '%' must be on the first column of
1610 * the line. The comment continues until the end of the current line.
1613 if (lastchar
== '\n' || lastchar
== 0) {
1614 while ((c
= Fgetc(fp
)) != EOF
&& c
!= '\n');
1618 // Not sure what to do here.
1625 if (c
== '<' || c
== '>')
1629 * PGN: Recurrsive Annotated Variation. Read rav_text() for more
1632 if (c
== '(' || c
== ')') {
1633 switch (rav_text(game
[gindex
], fp
, c
, old
)) {
1636 * This is the end of the current RAV. This function has
1637 * been called from rav_text(). Returning from this point
1638 * will put us back in rav_text().
1640 if (game
[gindex
]->ravlevel
> 0)
1644 * We're back at the root move. Continue as normal.
1652 * Continue processing-> Probably the root move.
1657 if (!game
[gindex
]->ravlevel
&& pgn_rav
)
1663 // PGN: Numeric Annotation Glyph.
1664 if (c
== '$' || c
== '!' || c
== '?' || c
== '+' || c
== '-' ||
1665 c
== '~' || c
== '=') {
1667 nag_text(game
[gindex
], fp
);
1672 * PGN: Annotation. The ';' comment continues until the end of the
1673 * current line. The '{' type comment continues until a '}' is
1676 if (c
== '{' || c
== ';') {
1677 annotation_text(game
[gindex
], fp
, (c
== '{') ? '}' : '\n');
1681 // PGN: Roster tag->
1683 // First roster tag found. Initialize the data structures.
1688 if (gtotal
&& pgn_history_total(game
[gindex
]->hp
))
1689 game
[gindex
]->hindex
= pgn_history_total(game
[gindex
]->hp
) - 1;
1691 if (pgn_new_game() != E_PGN_OK
) {
1692 pgn_ret
= E_PGN_ERR
;
1696 memcpy(old
, pgn_board
, sizeof(BOARD
));
1699 if (tag_text(game
[gindex
], fp
))
1700 parse_error
= 1; // FEN tag parse error.
1705 // PGN: End-of-game markers.
1706 if ((isdigit(c
) && (nextchar
== '-' || nextchar
== '/')) || c
== '*') {
1709 if (eog_text(game
[gindex
], fp
)) {
1717 if (!game
[gindex
]->done_fen_tag
) {
1718 if (pgn_tag_find(game
[gindex
]->tag
, "FEN") != -1 &&
1719 pgn_board_init_fen(game
[gindex
], pgn_board
, NULL
)) {
1724 game
[gindex
]->pgn_fen_tag
= pgn_tag_find(game
[gindex
]->tag
, "FEN");
1725 game
[gindex
]->done_fen_tag
= 1;
1732 if ((isdigit(c
) && c
!= '0') || VALIDCOL(c
) || c
== 'N' || c
== 'K'
1733 || c
== 'Q' || c
== 'B' || c
== 'R' || c
== 'P' ||
1737 // PGN: If a FEN tag exists, initialize the board to the value.
1739 if (pgn_tag_find(game
[gindex
]->tag
, "FEN") != E_PGN_ERR
&&
1740 (n
= pgn_board_init_fen(game
[gindex
], pgn_board
,
1741 NULL
)) == E_PGN_PARSE
) {
1746 game
[gindex
]->done_fen_tag
= 1;
1747 game
[gindex
]->pgn_fen_tag
= pgn_tag_find(game
[gindex
]->tag
, "FEN");
1752 * PGN: Import format doesn't require a roster tag section. We've
1753 * arrived to the move text section without any tags so we
1754 * initialize a new game which set's the default tags and any tags
1755 * from the configuration file.
1759 game
[gindex
]->hindex
= pgn_history_total(game
[gindex
]->hp
) - 1;
1761 if (pgn_new_game() != E_PGN_OK
) {
1762 pgn_ret
= E_PGN_ERR
;
1766 memcpy(old
, pgn_board
, sizeof(BOARD
));
1770 memcpy(old
, pgn_board
, sizeof(BOARD
));
1772 if (move_text(game
[gindex
], fp
)) {
1773 if (pgn_tag_add(&game
[gindex
]->tag
, "Result", "*") ==
1775 warn("pgn_tag_add()");
1776 pgn_ret
= E_PGN_ERR
;
1779 SET_FLAG(game
[gindex
]->flags
, GF_PERROR
);
1789 PGN_DUMP("%s:%d: unparsed: '%s'\n", __FILE__
, __LINE__
, buf
);
1791 if (strlen(buf
) + 1 == sizeof(buf
))
1792 bzero(buf
, sizeof(buf
));
1802 * Parses a PGN_FILE which was opened with pgn_open(). If 'pgn' is NULL then a
1803 * single empty game will be allocated. If there is a parsing error
1804 * E_PGN_PARSE is returned, if there was a memory allocation error E_PGN_ERR
1805 * is returned, otherwise E_PGN_OK is returned and the global 'gindex' is set
1806 * to the last parsed game in the file and the global 'gtotal' is set to the
1807 * total number of games in the file. The file should be closed with
1808 * pgn_close() after processing.
1810 pgn_error_t
pgn_parse(PGN_FILE
*pgn
)
1816 pgn_ret
= pgn_new_game();
1822 fseek(pgn
->fp
, 0, SEEK_END
);
1823 pgn_fsize
= ftell(pgn
->fp
);
1824 fseek(pgn
->fp
, 0, SEEK_SET
);
1826 PGN_DUMP("%s:%d: BEGIN parsing->..\n", __FILE__
, __LINE__
);
1828 pgn_ret
= read_file(pgn
->fp
);
1831 PGN_DUMP("%s:%d: END parsing->..\n", __FILE__
, __LINE__
);
1838 gtotal
= gindex
+ 1;
1840 for (i
= 0; i
< gtotal
; i
++) {
1841 game
[i
]->history
= game
[i
]->hp
;
1842 game
[i
]->hindex
= pgn_history_total(game
[i
]->hp
);
1849 * Escape '"' and '\' in tag values.
1851 static char *pgn_tag_add_escapes(const char *str
)
1854 int len
= strlen(str
);
1855 static char buf
[MAX_PGN_LINE_LEN
] = {0};
1857 for (i
= n
= 0; i
< len
; i
++, n
++) {
1874 static void Fputc(int c
, FILE *fp
, int *len
)
1878 if (c
!= '\n' && i
+ 1 > 80)
1879 Fputc('\n', fp
, &i
);
1881 if (pgn_lastc
== '\n' && c
== ' ') {
1886 if (fputc(c
, fp
) == EOF
)
1899 static void putstring(FILE *fp
, char *str
, int *len
)
1903 for (p
= str
; *p
; p
++) {
1906 while (*p
&& *p
!= ' ')
1910 Fputc('\n', fp
, len
);
1918 * See pgn_write() for more info.
1920 static void write_comments_and_nag(FILE *fp
, HISTORY
*h
, int *len
)
1925 PGN_DUMP("%s:%d: writing comments and nag\n", __FILE__
, __LINE__
);
1928 for (i
= 0; i
< MAX_PGN_NAG
; i
++) {
1930 Fputc(' ', fp
, len
);
1931 Fputc('$', fp
, len
);
1932 putstring(fp
, itoa(h
->nag
[i
]), len
);
1937 Fputc('\n', fp
, len
);
1938 putstring(fp
, " {", len
);
1939 putstring(fp
, h
->comment
, len
);
1940 Fputc('}', fp
, len
);
1944 static void write_move_text(FILE *fp
, HISTORY
*h
, int *len
)
1946 Fputc(' ', fp
, len
);
1947 putstring(fp
, h
->move
, len
);
1949 if (!pgn_config
.reduced
)
1950 write_comments_and_nag(fp
, h
, len
);
1953 static void write_all_move_text(FILE *fp
, HISTORY
**h
, int m
, int *len
)
1956 HISTORY
**hp
= NULL
;
1958 for (i
= 0; h
[i
]; i
++) {
1959 if (pgn_write_turn
== WHITE
) {
1960 if (pgn_config
.mpl
&& pgn_mpl
== pgn_config
.mpl
) {
1962 Fputc('\n', fp
, len
);
1966 Fputc(' ', fp
, len
);
1968 if (strlen(itoa(m
)) + 1 + *len
> 80)
1969 Fputc('\n', fp
, len
);
1971 putstring(fp
, itoa(m
), len
);
1972 Fputc('.', fp
, len
);
1976 write_move_text(fp
, h
[i
], len
);
1978 if (!pgn_config
.reduced
&& h
[i
]->rav
) {
1980 int oldturn
= pgn_write_turn
;
1983 putstring(fp
, " (", len
);
1986 * If it's WHITE's turn the move number will be added above after
1987 * the call to write_all_move_text() below.
1989 if (pgn_write_turn
== BLACK
) {
1990 putstring(fp
, itoa(m
), len
);
1991 putstring(fp
, "...", len
);
1995 write_all_move_text(fp
, hp
, m
, len
);
1997 pgn_write_turn
= oldturn
;
1998 putstring(fp
, ")", len
);
2001 if (ravlevel
&& h
[i
+ 1])
2002 Fputc(' ', fp
, len
);
2004 if (h
[i
+ 1] && !ravlevel
)
2005 Fputc(' ', fp
, len
);
2007 if (pgn_write_turn
== WHITE
&& h
[i
+ 1]) {
2008 putstring(fp
, itoa(m
), len
);
2009 putstring(fp
, "...", len
);
2013 if (pgn_write_turn
== BLACK
)
2016 pgn_write_turn
= (pgn_write_turn
== WHITE
) ? BLACK
: WHITE
;
2020 static char *compression_cmd(const char *filename
, int expand
)
2022 static char command
[FILENAME_MAX
];
2023 int len
= strlen(filename
);
2025 if (filename
[len
- 4] == '.' && filename
[len
- 3] == 'z' &&
2026 filename
[len
- 2] == 'i' && filename
[len
- 1] == 'p' &&
2027 filename
[len
] == '\0') {
2029 snprintf(command
, sizeof(command
), "unzip -p %s 2>/dev/null",
2032 snprintf(command
, sizeof(command
), "zip -9 >%s 2>/dev/null",
2037 else if (filename
[len
- 3] == '.' && filename
[len
- 2] == 'g' &&
2038 filename
[len
- 1] == 'z' && filename
[len
] == '\0') {
2040 snprintf(command
, sizeof(command
), "gzip -dc %s", filename
);
2042 snprintf(command
, sizeof(command
), "gzip -c 1>%s", filename
);
2046 else if (filename
[len
- 2] == '.' && filename
[len
- 1] == 'Z' &&
2047 filename
[len
] == '\0') {
2049 snprintf(command
, sizeof(command
), "uncompress -c %s", filename
);
2051 snprintf(command
, sizeof(command
), "compress -c 1>%s", filename
);
2055 else if ((filename
[len
- 4] == '.' && filename
[len
- 3] == 'b' &&
2056 filename
[len
- 2] == 'z' && filename
[len
- 1] == '2' &&
2057 filename
[len
] == '\0') || (filename
[len
- 3] == '.' &&
2058 filename
[len
- 2] == 'b' && filename
[len
- 1] == 'z' &&
2059 filename
[len
] == '\0')) {
2061 snprintf(command
, sizeof(command
), "bzip2 -dc %s", filename
);
2063 snprintf(command
, sizeof(command
), "bzip2 -zc 1>%s", filename
);
2071 static int copy_file(FILE *fp
, const char *dst
)
2074 char line
[LINE_MAX
];
2075 char *cmd
= compression_cmd(dst
, 0);
2077 if ((ofp
= popen(cmd
, "w")) == NULL
)
2080 fseek(fp
, 0, SEEK_SET
);
2082 while ((fgets(line
, sizeof(line
), fp
)) != NULL
)
2083 fprintf(ofp
, "%s", line
);
2090 * Closes and free's a PGN file handle.
2092 pgn_error_t
pgn_close(PGN_FILE
*pgn
)
2095 return E_PGN_INVALID
;
2099 * Appending to a compressed file.
2102 if (copy_file(pgn
->fp
, pgn
->filename
))
2106 unlink(pgn
->tmpfile
);
2115 free(pgn
->filename
);
2121 * Opens a file 'filename' with the given 'mode'. 'mode' should be "r" for
2122 * reading, "w" for writing (will truncate if the file exists) or "a" for
2123 * appending to an existing file or creating a new one. Returns E_PGN_OK on
2124 * success and sets 'result' to a file handle for use will the other file
2125 * functions or E_PGN_ERR if there is an error opening the file in which case
2126 * errno will be set to the error or E_PGN_INVALID if 'mode' is an invalid
2127 * mode or if 'filename' is not a regular file.
2129 pgn_error_t
pgn_open(const char *filename
, const char *mode
, PGN_FILE
**result
)
2131 FILE *fp
= NULL
, *tfp
= NULL
;
2132 char buf
[PATH_MAX
], *p
;
2138 int ret
= E_PGN_ERR
;
2142 PGN_DUMP("%s:%d: BEGIN opening %s\n", __FILE__
, __LINE__
, filename
);
2145 if (!filename
|| !mode
)
2146 return E_PGN_INVALID
;
2148 if (strcmp(mode
, "r") == 0)
2150 else if (strcmp(mode
, "w") == 0)
2152 else if (strcmp(mode
, "a") == 0) {
2157 return E_PGN_INVALID
;
2160 pgn
= calloc(1, sizeof(PGN_FILE
));
2165 if (strcmp(filename
, "-") != 0) {
2166 if (m
&& access(filename
, R_OK
) == -1)
2169 if (stat(filename
, &st
) == -1 && !m
&& errno
!= ENOENT
)
2172 if (m
&& !S_ISREG(st
.st_mode
)) {
2173 ret
= E_PGN_INVALID
;
2177 if ((cmd
= compression_cmd(filename
, m
)) != NULL
) {
2180 if (append
&& access(filename
, R_OK
) == 0) {
2184 cmd
= compression_cmd(filename
, 1);
2186 if ((fp
= popen(cmd
, "r")) == NULL
)
2189 if (tmpnam(tmp
) == NULL
)
2192 if ((fd
= open(tmp
, O_RDWR
|O_EXCL
|O_CREAT
)) == -1)
2195 if ((tfp
= fdopen(fd
, "a+")) == NULL
)
2198 while ((p
= fgets(buf
, sizeof(buf
), fp
)) != NULL
)
2199 fprintf(tfp
, "%s", p
);
2203 pgn
->tmpfile
= strdup(tmp
);
2207 if ((fp
= popen(cmd
, m
? "r" : "w")) == NULL
)
2211 if ((tfp
= tmpfile()) == NULL
)
2214 while ((p
= fgets(buf
, sizeof(buf
), fp
)) != NULL
)
2215 fprintf(tfp
, "%s", p
);
2224 if ((fp
= fopen(filename
, mode
)) == NULL
)
2234 if (*filename
!= '/') {
2235 if (getcwd(buf
, sizeof(buf
)) == NULL
) {
2242 asprintf(&p
, "%s/%s", buf
, filename
);
2246 pgn
->filename
= strdup(filename
);
2260 * Returns E_PGN_OK if 'filename' is a recognized compressed filetype or
2263 pgn_error_t
pgn_is_compressed(const char *filename
)
2265 if (compression_cmd(filename
, 0))
2272 * Gets the value of config flag 'f'. The next argument should be a pointer of
2273 * the config type which is set to the value of 'f'. Returns E_PGN_ERR if 'f'
2274 * is an invalid flag or E_PGN_OK on success.
2276 pgn_error_t
pgn_config_get(pgn_config_flag f
, ...)
2281 pgn_progress
*progress
;
2286 case PGN_STRICT_CASTLING
:
2287 intval
= va_arg(ap
, int *);
2288 *intval
= pgn_config
.strict_castling
;
2292 intval
= va_arg(ap
, int *);
2293 *intval
= pgn_config
.reduced
;
2297 intval
= va_arg(ap
, int *);
2298 *intval
= pgn_config
.mpl
;
2301 case PGN_STOP_ON_ERROR
:
2302 intval
= va_arg(ap
, int *);
2303 *intval
= pgn_config
.stop
;
2307 longval
= va_arg(ap
, long *);
2308 *longval
= pgn_config
.stop
;
2311 case PGN_PROGRESS_FUNC
:
2312 progress
= va_arg(ap
, pgn_progress
*);
2313 *progress
= pgn_config
.pfunc
;
2318 intval
= va_arg(ap
, int *);
2319 *intval
= dumptofile
;
2331 * Sets config flag 'f' to the next argument. Returns E_PGN_OK on success or
2332 * E_PGN_ERR if 'f' is an invalid flag or E_PGN_INVALID if 'val' is an invalid
2335 pgn_error_t
pgn_config_set(pgn_config_flag f
, ...)
2345 n
= va_arg(ap
, int);
2347 if (n
!= 1 && n
!= 0) {
2348 ret
= E_PGN_INVALID
;
2352 pgn_config
.reduced
= n
;
2355 n
= va_arg(ap
, int);
2358 ret
= E_PGN_INVALID
;
2364 case PGN_STOP_ON_ERROR
:
2365 n
= va_arg(ap
, int);
2367 if (n
!= 1 && n
!= 0) {
2368 ret
= E_PGN_INVALID
;
2372 pgn_config
.stop
= n
;
2375 n
= va_arg(ap
, long);
2376 pgn_config
.progress
= n
;
2378 case PGN_PROGRESS_FUNC
:
2379 pgn_config
.pfunc
= va_arg(ap
, pgn_progress
);
2381 case PGN_STRICT_CASTLING
:
2382 n
= va_arg(ap
, int);
2383 pgn_config
.strict_castling
= n
;
2387 n
= va_arg(ap
, int);
2388 dumptofile
= (n
> 0) ? 1 : 0;
2401 * Writes a PGN formatted game 'g' to a file which was opened with pgn_open().
2402 * See 'pgn_config_flag' for output options. Returns E_PGN_ERR if there was a
2403 * memory allocation or write error and E_PGN_OK on success. It is important
2404 * to use pgn_close() afterwards if the file is a recognized compressed file
2405 * type otherwise the created temporary file wont be copied to the destination
2408 pgn_error_t
pgn_write(PGN_FILE
*pgn
, GAME g
)
2416 pgn_write_turn
= (TEST_FLAG(g
->flags
, GF_BLACK_OPENING
)) ? BLACK
: WHITE
;
2417 pgn_tag_sort(g
->tag
);
2420 PGN_DUMP("%s:%d: writing tag section\n", __FILE__
, __LINE__
);
2423 for (i
= 0; g
->tag
[i
]; i
++) {
2425 char tbuf
[11] = {0};
2428 if (pgn_config
.reduced
&& i
== 7)
2431 if (strcmp(g
->tag
[i
]->name
, "Date") == 0) {
2432 memset(&tp
, 0, sizeof(struct tm
));
2434 if (strptime(g
->tag
[i
]->value
, PGN_TIME_FORMAT
, &tp
) != NULL
) {
2435 len
= strftime(tbuf
, sizeof(tbuf
), PGN_TIME_FORMAT
, &tp
) + 1;
2437 if ((tmp
= strdup(tbuf
)) == NULL
)
2440 free(g
->tag
[i
]->value
);
2441 g
->tag
[i
]->value
= tmp
;
2444 else if (strcmp(g
->tag
[i
]->name
, "Event") == 0) {
2445 if (g
->tag
[i
]->value
[0] == '\0') {
2446 if ((tmp
= strdup("?")) == NULL
)
2449 free(g
->tag
[i
]->value
);
2450 g
->tag
[i
]->value
= tmp
;
2453 else if (strcmp(g
->tag
[i
]->name
, "Site") == 0) {
2454 if (g
->tag
[i
]->value
[0] == '\0') {
2455 if ((tmp
= strdup("?")) == NULL
)
2458 free(g
->tag
[i
]->value
);
2459 g
->tag
[i
]->value
= tmp
;
2462 else if (strcmp(g
->tag
[i
]->name
, "Round") == 0) {
2463 if (g
->tag
[i
]->value
[0] == '\0') {
2464 if ((tmp
= strdup("?")) == NULL
)
2467 free(g
->tag
[i
]->value
);
2468 g
->tag
[i
]->value
= tmp
;
2471 else if (strcmp(g
->tag
[i
]->name
, "Result") == 0) {
2472 if (g
->tag
[i
]->value
[0] == '\0') {
2473 if ((tmp
= strdup("*")) == NULL
)
2476 free(g
->tag
[i
]->value
);
2477 g
->tag
[i
]->value
= tmp
;
2480 else if (strcmp(g
->tag
[i
]->name
, "Black") == 0) {
2481 if (g
->tag
[i
]->value
[0] == '\0') {
2482 if ((tmp
= strdup("?")) == NULL
)
2485 free(g
->tag
[i
]->value
);
2486 g
->tag
[i
]->value
= tmp
;
2489 else if (strcmp(g
->tag
[i
]->name
, "White") == 0) {
2490 if (g
->tag
[i
]->value
[0] == '\0') {
2491 if ((tmp
= strdup("?")) == NULL
)
2494 free(g
->tag
[i
]->value
);
2495 g
->tag
[i
]->value
= tmp
;
2499 fprintf(pgn
->fp
, "[%s \"%s\"]\n", g
->tag
[i
]->name
,
2500 (g
->tag
[i
]->value
&& g
->tag
[i
]->value
[0]) ?
2501 pgn_tag_add_escapes(g
->tag
[i
]->value
) : "");
2505 PGN_DUMP("%s:%d: writing move section\n", __FILE__
, __LINE__
);
2507 Fputc('\n', pgn
->fp
, &len
);
2509 ravlevel
= pgn_mpl
= 0;
2511 if (pgn_history_total(g
->hp
) && pgn_write_turn
== BLACK
)
2512 putstring(pgn
->fp
, "1...", &len
);
2514 write_all_move_text(pgn
->fp
, g
->hp
, 1, &len
);
2516 Fputc(' ', pgn
->fp
, &len
);
2517 putstring(pgn
->fp
, g
->tag
[6]->value
, &len
);
2518 putstring(pgn
->fp
, "\n\n", &len
);
2520 if (!pgn_config
.reduced
)
2521 CLEAR_FLAG(g
->flags
, GF_PERROR
);
2527 * Clears the enpassant flag for all positions on board 'b'. Returns nothing.
2529 void pgn_reset_enpassant(BOARD b
)
2534 PGN_DUMP("%s:%d: resetting enpassant\n", __FILE__
, __LINE__
);
2537 for (r
= 0; r
< 8; r
++) {
2538 for (c
= 0; c
< 8; c
++)
2539 b
[r
][c
].enpassant
= 0;