1 /* vim:tw=78:ts=8:sw=4:set ft=c: */
3 Copyright (C) 2002-2007 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
++) {
297 pgn_history_free(h
[i
]->rav
, 0);
311 * Returns the history ply 'n' from 'h'. If 'n' is out of range then NULL is
314 HISTORY
*pgn_history_by_n(HISTORY
**h
, int n
)
316 if (n
< 0 || n
> pgn_history_total(h
) - 1)
323 * Appends move 'm' to game 'g' history pointer. The history pointer may be a
324 * in a RAV so g->rav.hp is also updated to the new (realloc()'ed) pointer. If
325 * not in a RAV then g->history will be updated. Returns E_PGN_ERR if
326 * realloc() failed or E_PGN_OK on success.
328 pgn_error_t
pgn_history_add(GAME g
, const char *m
)
330 int t
= pgn_history_total(g
->hp
);
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
) {
365 g
->hindex
= pgn_history_total(g
->hp
);
370 * Resets the game 'g' using board 'b' up to history move (g->hindex) 'n'.
371 * Returns E_PGN_OK on success or E_PGN_PARSE if there was a FEN tag but the
372 * parsing of it failed. Or returns E_PGN_INVALID if somehow the move failed
373 * validation while resetting.
375 pgn_error_t
pgn_board_update(GAME g
, BOARD b
, int n
)
380 int p_error
= TEST_FLAG(g
->flags
, GF_PERROR
);
381 int black_opening
= TEST_FLAG(g
->flags
, GF_BLACK_OPENING
);
385 PGN_DUMP("%s:%d: updating board\n", __FILE__
, __LINE__
);
388 if (!g
->ravlevel
&& TEST_FLAG(g
->flags
, GF_BLACK_OPENING
))
394 SET_FLAG(g
->flags
, GF_WK_CASTLE
|GF_WQ_CASTLE
|GF_WQ_CASTLE
|
395 GF_BK_CASTLE
|GF_BQ_CASTLE
);
399 pgn_board_init_fen(g
, tb
, g
->rav
[g
->ravlevel
- 1].fen
);
400 else if (pgn_tag_find(g
->tag
, "FEN") != -1 &&
401 pgn_board_init_fen(g
, tb
, NULL
))
404 for (i
= 0; i
< n
; i
++) {
408 if ((h
= pgn_history_by_n(g
->hp
, i
)) == NULL
)
413 if ((ret
= pgn_parse_move(g
, tb
, &p
, &frfr
)) != E_PGN_OK
)
420 memcpy(b
, tb
, sizeof(BOARD
));
423 SET_FLAG(g
->flags
, GF_PERROR
);
426 SET_FLAG(g
->flags
, GF_BLACK_OPENING
);
432 * Updates the game 'g' using board 'b' to the next 'n'th history move.
435 void pgn_history_prev(GAME g
, BOARD b
, int n
)
437 if (g
->hindex
- n
< 0) {
439 g
->hindex
= pgn_history_total(g
->hp
);
446 pgn_board_update(g
, b
, g
->hindex
);
450 * Updates the game 'g' using board 'b' to the previous 'n'th history move.
453 void pgn_history_next(GAME g
, BOARD b
, int n
)
455 if (g
->hindex
+ n
> pgn_history_total(g
->hp
)) {
459 g
->hindex
= pgn_history_total(g
->hp
);
464 pgn_board_update(g
, b
, g
->hindex
);
468 * Converts the character piece 'p' to an integer. Returns the integer
469 * associated with 'p' or E_PGN_ERR if 'p' is invalid.
471 int pgn_piece_to_int(int p
)
496 PGN_DUMP("%s:%d: invalid piece '%c'\n", __FILE__
, __LINE__
, p
);
502 * Converts the integer piece 'n' to a character whose turn is 'turn'. WHITE
503 * piece are uppercase and BLACK pieces are lowercase. Returns the character
504 * associated with 'n' or E_PGN_ERR if 'n' is an invalid piece.
506 pgn_error_t
pgn_int_to_piece(char turn
, int n
)
534 PGN_DUMP("%s:%d: unknown piece integer %i\n", __FILE__
,
541 return (turn
== WHITE
) ? toupper(p
) : p
;
545 * Finds a tag 'name' in the structure array 't'. Returns the location in the
546 * array of the found tag or E_PGN_ERR if the tag could not be found.
548 pgn_error_t
pgn_tag_find(TAG
**t
, const char *name
)
552 for (i
= 0; t
[i
]; i
++) {
553 if (strcasecmp(t
[i
]->name
, name
) == 0)
560 static pgn_error_t
remove_tag(TAG
***array
, const char *tag
)
563 int n
= pgn_tag_find(tags
, tag
);
569 for (i
= t
= 0; tags
[i
]; i
++) {
572 free(tags
[i
]->value
);
580 tags
= realloc(*array
, (t
+ 1) * sizeof(TAG
*));
584 PGN_DUMP("%s:%d: removed tag: name='%s'\n", __FILE__
, __LINE__
, tag
);
589 static int tag_compare(const void *a
, const void *b
)
594 return strcmp((*ta
)->name
, (*tb
)->name
);
598 * Sorts a tag array. The first seven tags are in order of the PGN standard so
599 * don't sort'em. Returns nothing.
601 void pgn_tag_sort(TAG
**tags
)
603 if (pgn_tag_total(tags
) <= 7)
606 qsort(tags
+ 7, pgn_tag_total(tags
) - 7, sizeof(TAG
*), tag_compare
);
610 * Returns the total number of tags in 't' or 0 if 't' is NULL.
612 int pgn_tag_total(TAG
**tags
)
626 * Adds a tag 'name' with value 'value' to the pointer to array of TAG
627 * pointers 'dst'. If a duplicate tag 'name' was found then the existing tag
628 * is updated to the new 'value'. If 'value' is NULL, the tag is removed.
629 * Returns E_PGN_ERR if there was a memory allocation error or E_PGN_OK on
632 pgn_error_t
pgn_tag_add(TAG
***dst
, char *name
, char *value
)
637 int t
= pgn_tag_total(tdata
);
640 PGN_DUMP("%s:%d: adding tag\n", __FILE__
, __LINE__
);
651 // Find an existing tag with 'name'.
652 for (i
= 0; i
< t
; i
++) {
655 if (strcasecmp(tdata
[i
]->name
, name
) == 0) {
657 if ((tmp
= strdup(value
)) == NULL
)
661 remove_tag(dst
, name
);
665 free(tdata
[i
]->value
);
666 tdata
[i
]->value
= tmp
;
672 if ((a
= realloc(tdata
, (t
+ 2) * sizeof(TAG
*))) == NULL
)
677 if ((tdata
[t
] = malloc(sizeof(TAG
))) == NULL
)
680 if ((tdata
[t
]->name
= strdup(name
)) == NULL
) {
686 if ((tdata
[t
]->value
= strdup(value
)) == NULL
) {
687 free(tdata
[t
]->name
);
693 tdata
[t
]->value
= NULL
;
695 tdata
[t
]->name
[0] = toupper(tdata
[t
]->name
[0]);
700 PGN_DUMP("%s:%d: added tag: name='%s' value='%s'\n", __FILE__
, __LINE__
,
701 name
, (value
) ? value
: "null");
707 static char *remove_tag_escapes(const char *str
)
710 int len
= strlen(str
);
711 static char buf
[MAX_PGN_LINE_LEN
] = {0};
713 for (i
= n
= 0; i
< len
; i
++, n
++) {
729 * Resets or initializes a new game board 'b'. Returns nothing.
731 void pgn_board_init(BOARD b
)
736 PGN_DUMP("%s:%d: initializing board\n", __FILE__
, __LINE__
);
739 memset(b
, 0, sizeof(BOARD
));
741 for (row
= 0; row
< 8; row
++) {
742 for (col
= 0; col
< 8; col
++) {
775 b
[row
][col
].icon
= (row
< 2) ? c
: toupper(c
);
781 * Adds the standard PGN roster tags to game 'g'.
783 static void set_default_tags(GAME g
)
788 struct passwd
*pw
= getpwuid(getuid());
791 tp
= localtime(&now
);
792 strftime(tbuf
, sizeof(tbuf
), PGN_TIME_FORMAT
, tp
);
794 /* The standard seven tag roster (in order of appearance). */
795 if (pgn_tag_add(&g
->tag
, "Event", "?") != E_PGN_OK
)
796 warn("pgn_tag_add()");
798 if (pgn_tag_add(&g
->tag
, "Site", "?") != E_PGN_OK
)
799 warn("pgn_tag_add()");
801 if (pgn_tag_add(&g
->tag
, "Date", tbuf
) != E_PGN_OK
)
802 warn("pgn_tag_add()");
804 if (pgn_tag_add(&g
->tag
, "Round", "-") != E_PGN_OK
)
805 warn("pgn_tag_add()");
807 if (pgn_tag_add(&g
->tag
, "White", pw
->pw_gecos
) != E_PGN_OK
)
808 warn("pgn_tag_add()");
810 if (pgn_tag_add(&g
->tag
, "Black", "?") != E_PGN_OK
)
811 warn("pgn_tag_add()");
813 if (pgn_tag_add(&g
->tag
, "Result", "*") != E_PGN_OK
)
814 warn("pgn_tag_add()");
818 * Frees a TAG array. Returns nothing.
820 void pgn_tag_free(TAG
**tags
)
823 int t
= pgn_tag_total(tags
);
826 PGN_DUMP("%s:%d: freeing tags\n", __FILE__
, __LINE__
);
832 for (i
= 0; i
< t
; i
++) {
834 free(tags
[i
]->value
);
842 * Frees a single game 'g'. Returns nothing.
844 void pgn_free(GAME g
)
847 PGN_DUMP("%s:%d: freeing game\n", __FILE__
, __LINE__
);
849 pgn_history_free(g
->history
, 0);
851 pgn_tag_free(g
->tag
);
854 for (g
->ravlevel
--; g
->ravlevel
>= 0; g
->ravlevel
--)
855 free(g
->rav
[g
->ravlevel
].fen
);
864 * Frees all games in the global 'game' array. Returns nothing.
871 PGN_DUMP("%s:%d: freeing game data\n", __FILE__
, __LINE__
);
874 for (i
= 0; i
< gtotal
; i
++) {
884 static void reset_game_data()
887 PGN_DUMP("%s:%d: resetting game data\n", __FILE__
, __LINE__
);
893 static void skip_leading_space(FILE *fp
)
897 while ((c
= Fgetc(fp
)) != EOF
&& !feof(fp
)) {
906 * PGN move text section.
908 static int move_text(GAME g
, FILE *fp
)
910 char m
[MAX_SAN_MOVE_LEN
+ 1] = {0}, *p
;
915 g
->oflags
= g
->flags
;
917 while((c
= Fgetc(fp
)) != EOF
) {
918 if (isdigit(c
) || isspace(c
) || c
== '.')
926 if (fscanf(fp
, " %[a-hPpRrNnBbQqKk1-9#+=Ox-]%n", m
, &count
) != 1)
929 m
[MAX_SAN_MOVE_LEN
] = 0;
932 if (pgn_parse_move(g
, pgn_board
, &p
, &frfr
)) {
938 PGN_DUMP("%s\n%s", p
, debug_board(pgn_board
));
941 pgn_history_add(g
, p
);
949 static void nag_text(GAME g
, FILE *fp
)
952 char nags
[5], *n
= nags
;
955 while ((c
= Fgetc(fp
)) != EOF
&& !isspace(c
)) {
957 while ((c
= Fgetc(fp
)) != EOF
&& isdigit(c
))
965 if ((c
= Fgetc(fp
)) == '!')
977 if ((c
= Fgetc(fp
)) == '?')
991 if ((c
= Fgetc(fp
)) == '+')
1000 else if (c
== '+') {
1001 if ((t
= Fgetc(fp
)) == '=')
1005 else if (t
== '/') {
1006 if ((i
= Fgetc(fp
)) == '-')
1018 else if (c
== '-') {
1019 if ((t
= Fgetc(fp
)) == '+')
1021 else if (t
== '/') {
1022 if ((i
= Fgetc(fp
)) == '+')
1039 nag
= (nags
[0]) ? atoi(nags
) : 0;
1041 if (!nag
|| nag
< 0 || nag
> 255)
1044 // FIXME -1 is because move_text() increments g->hindex. The NAG
1045 // annoatation isnt guaranteed to be after the move text in import format.
1046 for (i
= 0; i
< MAX_PGN_NAG
; i
++) {
1047 if (g
->hp
[g
->hindex
- 1]->nag
[i
])
1050 g
->hp
[g
->hindex
- 1]->nag
[i
] = nag
;
1054 skip_leading_space(fp
);
1058 * PGN move annotation.
1060 static int annotation_text(GAME g
, FILE *fp
, int terminator
)
1062 int c
, lastchar
= 0;
1064 int hindex
= pgn_history_total(g
->hp
) - 1;
1065 char buf
[MAX_PGN_LINE_LEN
], *a
= buf
;
1067 skip_leading_space(fp
);
1069 while ((c
= Fgetc(fp
)) != EOF
&& c
!= terminator
) {
1073 if (isspace(c
) && isspace(lastchar
))
1076 if (len
+ 1 == sizeof(buf
))
1079 *a
++ = lastchar
= c
;
1089 * This annotation is before any move text or NAg-> Allocate a new move.
1091 if (!g
->hp
[hindex
]) {
1092 if ((g
->hp
[hindex
] = calloc(1, sizeof(HISTORY
))) == NULL
)
1096 if ((g
->hp
[hindex
]->comment
= strdup(buf
)) == NULL
)
1105 static int tag_text(GAME g
, FILE *fp
)
1107 char name
[LINE_MAX
], *n
= name
;
1108 char value
[LINE_MAX
], *v
= value
;
1110 int quoted_string
= 0;
1113 skip_leading_space(fp
);
1115 /* The tag name is up until the first whitespace. */
1116 while ((c
= Fgetc(fp
)) != EOF
&& !isspace(c
))
1120 *name
= toupper(*name
);
1121 skip_leading_space(fp
);
1123 /* The value is until the first closing bracket. */
1124 while ((c
= Fgetc(fp
)) != EOF
&& c
!= ']') {
1125 if (i
++ == '\0' && c
== '\"') {
1130 if (c
== '\n' || c
== '\t')
1133 if (c
== ' ' && lastchar
== ' ')
1136 lastchar
= *v
++ = c
;
1141 while (isspace(*--v
))
1147 if (value
[0] == '\0') {
1148 if (strcmp(name
, "Result") == 0)
1156 strncpy(value
, remove_tag_escapes(value
), sizeof(value
));
1159 * See eog_text() for an explanation.
1161 if (strcmp(name
, "Result") == 0) {
1162 if (strcmp(value
, "1/2-1/2") == 0) {
1163 if (pgn_tag_add(&g
->tag
, name
, value
) != E_PGN_OK
)
1164 warn("pgn_tag_add()");
1168 if (pgn_tag_add(&g
->tag
, name
, value
) != E_PGN_OK
)
1169 warn("pgn_tag_add()");
1176 * PGN end-of-game marker.
1178 static int eog_text(GAME g
, FILE *fp
)
1181 char buf
[8], *p
= buf
;
1183 while ((c
= Fgetc(fp
)) != EOF
&& !isspace(c
) && i
++ < sizeof(buf
))
1191 if (strcmp(buf
, "1-0") != 0 && strcmp(buf
, "0-1") != 0 &&
1192 strcmp(buf
, "1/2-1/2") != 0 && strcmp(buf
, "*") != 0)
1196 * The eog marker in the move text may not match the actual game result.
1197 * We'll leave it up to the move validator to determine the result unless
1198 * the move validator cannot determine who won and the move text says it's
1201 if (g
->tag
[6]->value
[0] == '*' && strcmp("1/2-1/2", buf
) == 0) {
1202 if (pgn_tag_add(&g
->tag
, "Result", buf
) != E_PGN_OK
)
1203 warn("pgn_tag_add()");
1210 * Parse RAV text and keep track of g->hp. The 'o' argument is the board state
1211 * before the current move (.hindex) was parsed.
1213 static int read_file(FILE *);
1214 static int rav_text(GAME g
, FILE *fp
, int which
, BOARD o
)
1219 // Begin RAV for the current move.
1224 pgn_rav
++; // For detecting parse errors.
1227 * Save the current game state for this RAV depth/level.
1229 if ((r
= realloc(g
->rav
, (g
->ravlevel
+ 1) * sizeof(RAV
))) == NULL
) {
1234 g
->rav
= pgn_rav_p
= r
;
1236 if ((g
->rav
[g
->ravlevel
].fen
= strdup(pgn_game_to_fen(g
, pgn_board
)))
1242 g
->rav
[g
->ravlevel
].hp
= g
->hp
;
1243 g
->rav
[g
->ravlevel
].hindex
= g
->hindex
;
1244 memcpy(&tg
, &(*g
), sizeof(struct game_s
));
1245 g
->flags
= g
->oflags
;
1246 memcpy(pgn_board
, o
, sizeof(BOARD
));
1248 if ((g
->hp
[g
->hindex
- 1]->rav
= calloc(1, sizeof(HISTORY
*))) == NULL
) {
1254 * pgn_history_add() will now append to the new history pointer that
1255 * is g->hp[previous_move]->rav.
1257 g
->hp
= g
->hp
[g
->hindex
- 1]->rav
;
1260 * Reset. Will be restored later from 'tg' which is a local variable
1261 * so recursion is possible.
1267 * Undo move_text()'s switch.
1272 * Now continue as normal as if there were no RAV. Moves will be
1273 * parsed and appended to the new history pointer.
1279 * read_file() has returned. This means that a RAV has ended by this
1280 * function returning -1 (see below). So we restore the game state
1281 * (tg) that was saved before calling read_file().
1283 pgn_board_init_fen(&tg
, pgn_board
, g
->rav
[tg
.ravlevel
].fen
);
1284 free(g
->rav
[tg
.ravlevel
].fen
);
1285 memcpy(&(*g
), &tg
, sizeof(struct game_s
));
1289 * The end of a RAV. This makes the read_file() that called this function
1290 * return (see above and the switch statement in read_file()).
1292 else if (which
== ')') {
1293 pgn_rav
--; // For detecting parse errors.
1294 return (pgn_rav
< 0) ? 1 : -1;
1302 * See pgn_board_init_fen(). Returns E_PGN_PARSE on parse error. 0 may be
1303 * returned on success when there is no move count in the FEN tag otherwise
1304 * the move count is returned.
1306 static int parse_fen_line(BOARD b
, unsigned *flags
, char *turn
, char *ply
,
1310 char line
[LINE_MAX
], *s
;
1311 int row
= 8, col
= 1;
1315 PGN_DUMP("%s:%d: FEN line is '%s'\n", __FILE__
, __LINE__
, str
);
1317 strncpy(line
, str
, sizeof(line
));
1319 pgn_reset_enpassant(b
);
1321 while ((tmp
= strsep(&s
, "/")) != NULL
) {
1324 if (!VALIDFILE(row
))
1331 if (isdigit(*tmp
)) {
1337 for (; n
; --n
, col
++)
1338 b
[RANKTOBOARD(row
)][FILETOBOARD(col
)].icon
=
1339 pgn_int_to_piece(WHITE
, OPEN_SQUARE
);
1341 else if (pgn_piece_to_int(*tmp
) != -1)
1342 b
[RANKTOBOARD(row
)][FILETOBOARD(col
++)].icon
= *tmp
;
1369 while (*tmp
&& *tmp
!= ' ') {
1372 SET_FLAG(*flags
, GF_WK_CASTLE
);
1375 SET_FLAG(*flags
, GF_WQ_CASTLE
);
1378 SET_FLAG(*flags
, GF_BK_CASTLE
);
1381 SET_FLAG(*flags
, GF_BQ_CASTLE
);
1394 if (!VALIDCOL(*tmp
))
1399 if (!VALIDROW(*tmp
))
1402 row
= 8 - atoi(tmp
++);
1403 b
[row
][col
].enpassant
= 1;
1404 SET_FLAG(*flags
, GF_ENPASSANT
);
1415 while (*tmp
&& isdigit(*tmp
))
1426 * It initializes the board (b) to the FEN tag (if found) and sets the
1427 * castling and enpassant info for the game 'g'. If 'fen' is set it should be
1428 * a fen tag and will be parsed rather than the game 'g'.tag FEN tag-> Returns
1429 * E_PGN_OK on success or if there was both a FEN and SetUp tag with the SetUp
1430 * tag set to 0. Returns E_PGN_PARSE if there was a FEN parse error, E_PGN_ERR
1431 * if there was no FEN tag or there was a SetUp tag with a value of 0. Returns
1432 * E_PGN_OK on success.
1434 pgn_error_t
pgn_board_init_fen(GAME g
, BOARD b
, char *fen
)
1439 char turn
= g
->turn
;
1443 PGN_DUMP("%s:%d: initializing board from FEN\n", __FILE__
, __LINE__
);
1445 pgn_board_init(tmpboard
);
1448 n
= pgn_tag_find(g
->tag
, "Setup");
1449 i
= pgn_tag_find(g
->tag
, "FEN");
1453 * If the FEN tag exists and there is no SetUp tag go ahead and parse it.
1454 * If there is a SetUp tag only parse the FEN tag if the value is 1.
1456 if ((n
>= 0 && i
>= 0 && atoi(g
->tag
[n
]->value
) == 1)
1457 || (i
>= 0 && n
== -1) || fen
) {
1458 if ((n
= parse_fen_line(tmpboard
, &flags
, &turn
, &ply
,
1459 (fen
) ? fen
: g
->tag
[i
]->value
)) != E_PGN_OK
)
1462 memcpy(b
, tmpboard
, sizeof(BOARD
));
1463 CLEAR_FLAG(g
->flags
, GF_WK_CASTLE
);
1464 CLEAR_FLAG(g
->flags
, GF_WQ_CASTLE
);
1465 CLEAR_FLAG(g
->flags
, GF_BK_CASTLE
);
1466 CLEAR_FLAG(g
->flags
, GF_BQ_CASTLE
);
1473 return (i
>= 0 && n
>= 0) ? E_PGN_OK
: E_PGN_ERR
;
1479 * Allocates a new game and increments 'gtotal'. 'gindex' is then set to the
1480 * new game. Returns E_PGN_ERR if there was a memory allocation error or
1481 * E_PGN_OK on success.
1483 pgn_error_t
pgn_new_game()
1490 PGN_DUMP("%s:%d: allocating new game\n", __FILE__
, __LINE__
);
1494 if ((g
= realloc(game
, t
* sizeof(GAME
*))) == NULL
) {
1501 if ((newg
= calloc(1, sizeof(struct game_s
))) == NULL
) {
1506 game
[gindex
] = newg
;
1508 if ((game
[gindex
]->hp
= calloc(1, sizeof(HISTORY
*))) == NULL
) {
1514 game
[gindex
]->hp
[0] = NULL
;
1515 game
[gindex
]->history
= game
[gindex
]->hp
;
1516 game
[gindex
]->side
= game
[gindex
]->turn
= WHITE
;
1517 SET_FLAG(game
[gindex
]->flags
, GF_WK_CASTLE
|GF_WQ_CASTLE
|GF_WQ_CASTLE
|
1518 GF_BK_CASTLE
|GF_BQ_CASTLE
);
1519 pgn_board_init(pgn_board
);
1520 set_default_tags(game
[gindex
]);
1525 static int read_file(FILE *fp
)
1528 char buf
[LINE_MAX
] = {0}, *p
= buf
;
1531 int parse_error
= 0;
1540 * A parse error may have occured at EOF.
1543 pgn_ret
= E_PGN_PARSE
;
1548 SET_FLAG(game
[gindex
]->flags
, GF_PERROR
);
1551 if ((c
= Fgetc(fp
)) == EOF
) {
1569 nextchar
= Fgetc(fp
);
1570 Ungetc(nextchar
, fp
);
1573 * If there was a move text parsing error, keep reading until the end
1574 * of the current game discarding the data.
1577 pgn_ret
= E_PGN_PARSE
;
1579 if (game
[gindex
]->ravlevel
)
1582 if (pgn_config
.stop
)
1585 if (c
== '\n' && (nextchar
== '\n' || nextchar
== '\015')) {
1594 // New game reached.
1595 if (c
== '\n' && (nextchar
== '\n' || nextchar
== '\015')) {
1605 * PGN: Application comment. The '%' must be on the first column of
1606 * the line. The comment continues until the end of the current line.
1609 if (lastchar
== '\n' || lastchar
== 0) {
1610 while ((c
= Fgetc(fp
)) != EOF
&& c
!= '\n');
1614 // Not sure what to do here.
1621 if (c
== '<' || c
== '>')
1625 * PGN: Recurrsive Annotated Variation. Read rav_text() for more
1628 if (c
== '(' || c
== ')') {
1629 switch (rav_text(game
[gindex
], fp
, c
, old
)) {
1632 * This is the end of the current RAV. This function has
1633 * been called from rav_text(). Returning from this point
1634 * will put us back in rav_text().
1636 if (game
[gindex
]->ravlevel
> 0)
1640 * We're back at the root move. Continue as normal.
1648 * Continue processing-> Probably the root move.
1653 if (!game
[gindex
]->ravlevel
&& pgn_rav
)
1659 // PGN: Numeric Annotation Glyph.
1660 if (c
== '$' || c
== '!' || c
== '?' || c
== '+' || c
== '-' ||
1661 c
== '~' || c
== '=') {
1663 nag_text(game
[gindex
], fp
);
1668 * PGN: Annotation. The ';' comment continues until the end of the
1669 * current line. The '{' type comment continues until a '}' is
1672 if (c
== '{' || c
== ';') {
1673 annotation_text(game
[gindex
], fp
, (c
== '{') ? '}' : '\n');
1677 // PGN: Roster tag->
1679 // First roster tag found. Initialize the data structures.
1684 if (gtotal
&& pgn_history_total(game
[gindex
]->hp
))
1685 game
[gindex
]->hindex
= pgn_history_total(game
[gindex
]->hp
) - 1;
1687 if (pgn_new_game() != E_PGN_OK
) {
1688 pgn_ret
= E_PGN_ERR
;
1692 memcpy(old
, pgn_board
, sizeof(BOARD
));
1695 if (tag_text(game
[gindex
], fp
))
1696 parse_error
= 1; // FEN tag parse error.
1701 // PGN: End-of-game markers.
1702 if ((isdigit(c
) && (nextchar
== '-' || nextchar
== '/')) || c
== '*') {
1705 if (eog_text(game
[gindex
], fp
)) {
1713 if (!done_fen_tag
) {
1714 if (pgn_tag_find(game
[gindex
]->tag
, "FEN") != -1 &&
1715 pgn_board_init_fen(game
[gindex
], pgn_board
, NULL
)) {
1720 pgn_fen_tag
= pgn_tag_find(game
[gindex
]->tag
, "FEN");
1728 if ((isdigit(c
) && c
!= '0') || VALIDCOL(c
) || c
== 'N' || c
== 'K'
1729 || c
== 'Q' || c
== 'B' || c
== 'R' || c
== 'P' ||
1733 // PGN: If a FEN tag exists, initialize the board to the value.
1735 if (pgn_tag_find(game
[gindex
]->tag
, "FEN") != E_PGN_ERR
&&
1736 (n
= pgn_board_init_fen(game
[gindex
], pgn_board
,
1737 NULL
)) == E_PGN_PARSE
) {
1743 pgn_fen_tag
= pgn_tag_find(game
[gindex
]->tag
, "FEN");
1748 * PGN: Import format doesn't require a roster tag section. We've
1749 * arrived to the move text section without any tags so we
1750 * initialize a new game which set's the default tags and any tags
1751 * from the configuration file.
1755 game
[gindex
]->hindex
= pgn_history_total(game
[gindex
]->hp
) - 1;
1757 if (pgn_new_game() != E_PGN_OK
) {
1758 pgn_ret
= E_PGN_ERR
;
1762 memcpy(old
, pgn_board
, sizeof(BOARD
));
1766 memcpy(old
, pgn_board
, sizeof(BOARD
));
1768 if (move_text(game
[gindex
], fp
)) {
1769 if (pgn_tag_add(&game
[gindex
]->tag
, "Result", "*") ==
1771 warn("pgn_tag_add()");
1772 pgn_ret
= E_PGN_ERR
;
1775 SET_FLAG(game
[gindex
]->flags
, GF_PERROR
);
1785 PGN_DUMP("%s:%d: unparsed: '%s'\n", __FILE__
, __LINE__
, buf
);
1787 if (strlen(buf
) + 1 == sizeof(buf
))
1788 bzero(buf
, sizeof(buf
));
1798 * Parses a PGN_FILE which was opened with pgn_open(). If 'pgn' is NULL then a
1799 * single empty game will be allocated. If there is a parsing error
1800 * E_PGN_PARSE is returned, if there was a memory allocation error E_PGN_ERR
1801 * is returned, otherwise E_PGN_OK is returned and the global 'gindex' is set
1802 * to the last parsed game in the file and the global 'gtotal' is set to the
1803 * total number of games in the file. The file should be closed with
1804 * pgn_close() after processing.
1806 pgn_error_t
pgn_parse(PGN_FILE
*pgn
)
1812 pgn_ret
= pgn_new_game();
1818 fseek(pgn
->fp
, 0, SEEK_END
);
1819 pgn_fsize
= ftell(pgn
->fp
);
1820 fseek(pgn
->fp
, 0, SEEK_SET
);
1822 PGN_DUMP("%s:%d: BEGIN parsing->..\n", __FILE__
, __LINE__
);
1824 pgn_ret
= read_file(pgn
->fp
);
1827 PGN_DUMP("%s:%d: END parsing->..\n", __FILE__
, __LINE__
);
1834 gtotal
= gindex
+ 1;
1836 for (i
= 0; i
< gtotal
; i
++) {
1837 game
[i
]->history
= game
[i
]->hp
;
1838 game
[i
]->hindex
= pgn_history_total(game
[i
]->hp
);
1845 * Escape '"' and '\' in tag values.
1847 static char *pgn_tag_add_escapes(const char *str
)
1850 int len
= strlen(str
);
1851 static char buf
[MAX_PGN_LINE_LEN
] = {0};
1853 for (i
= n
= 0; i
< len
; i
++, n
++) {
1870 static void Fputc(int c
, FILE *fp
, int *len
)
1874 if (c
!= '\n' && i
+ 1 > 80)
1875 Fputc('\n', fp
, &i
);
1877 if (pgn_lastc
== '\n' && c
== ' ') {
1882 if (fputc(c
, fp
) == EOF
)
1895 static void putstring(FILE *fp
, char *str
, int *len
)
1899 for (p
= str
; *p
; p
++) {
1902 while (*p
&& *p
!= ' ')
1906 Fputc('\n', fp
, len
);
1914 * See pgn_write() for more info.
1916 static void write_comments_and_nag(FILE *fp
, HISTORY
*h
, int *len
)
1921 PGN_DUMP("%s:%d: writing comments and nag\n", __FILE__
, __LINE__
);
1924 for (i
= 0; i
< MAX_PGN_NAG
; i
++) {
1926 Fputc(' ', fp
, len
);
1927 Fputc('$', fp
, len
);
1928 putstring(fp
, itoa(h
->nag
[i
]), len
);
1933 Fputc('\n', fp
, len
);
1934 putstring(fp
, " {", len
);
1935 putstring(fp
, h
->comment
, len
);
1936 Fputc('}', fp
, len
);
1940 static void write_move_text(FILE *fp
, HISTORY
*h
, int *len
)
1942 Fputc(' ', fp
, len
);
1943 putstring(fp
, h
->move
, len
);
1945 if (!pgn_config
.reduced
)
1946 write_comments_and_nag(fp
, h
, len
);
1949 static void write_all_move_text(FILE *fp
, HISTORY
**h
, int m
, int *len
)
1952 HISTORY
**hp
= NULL
;
1954 for (i
= 0; h
[i
]; i
++) {
1955 if (pgn_write_turn
== WHITE
) {
1956 if (pgn_config
.mpl
&& pgn_mpl
== pgn_config
.mpl
) {
1958 Fputc('\n', fp
, len
);
1962 Fputc(' ', fp
, len
);
1964 if (strlen(itoa(m
)) + 1 + *len
> 80)
1965 Fputc('\n', fp
, len
);
1967 putstring(fp
, itoa(m
), len
);
1968 Fputc('.', fp
, len
);
1972 write_move_text(fp
, h
[i
], len
);
1974 if (!pgn_config
.reduced
&& h
[i
]->rav
) {
1976 int oldturn
= pgn_write_turn
;
1979 putstring(fp
, " (", len
);
1982 * If it's WHITE's turn the move number will be added above after
1983 * the call to write_all_move_text() below.
1985 if (pgn_write_turn
== BLACK
) {
1986 putstring(fp
, itoa(m
), len
);
1987 putstring(fp
, "...", len
);
1991 write_all_move_text(fp
, hp
, m
, len
);
1993 pgn_write_turn
= oldturn
;
1994 putstring(fp
, ")", len
);
1997 if (ravlevel
&& h
[i
+ 1])
1998 Fputc(' ', fp
, len
);
2000 if (h
[i
+ 1] && !ravlevel
)
2001 Fputc(' ', fp
, len
);
2003 if (pgn_write_turn
== WHITE
&& h
[i
+ 1]) {
2004 putstring(fp
, itoa(m
), len
);
2005 putstring(fp
, "...", len
);
2009 if (pgn_write_turn
== BLACK
)
2012 pgn_write_turn
= (pgn_write_turn
== WHITE
) ? BLACK
: WHITE
;
2016 static char *compression_cmd(const char *filename
, int expand
)
2018 static char command
[FILENAME_MAX
];
2019 int len
= strlen(filename
);
2021 if (filename
[len
- 4] == '.' && filename
[len
- 3] == 'z' &&
2022 filename
[len
- 2] == 'i' && filename
[len
- 1] == 'p' &&
2023 filename
[len
] == '\0') {
2025 snprintf(command
, sizeof(command
), "unzip -p %s 2>/dev/null",
2028 snprintf(command
, sizeof(command
), "zip -9 >%s 2>/dev/null",
2033 else if (filename
[len
- 3] == '.' && filename
[len
- 2] == 'g' &&
2034 filename
[len
- 1] == 'z' && filename
[len
] == '\0') {
2036 snprintf(command
, sizeof(command
), "gzip -dc %s", filename
);
2038 snprintf(command
, sizeof(command
), "gzip -c 1>%s", filename
);
2042 else if (filename
[len
- 2] == '.' && filename
[len
- 1] == 'Z' &&
2043 filename
[len
] == '\0') {
2045 snprintf(command
, sizeof(command
), "uncompress -c %s", filename
);
2047 snprintf(command
, sizeof(command
), "compress -c 1>%s", filename
);
2051 else if ((filename
[len
- 4] == '.' && filename
[len
- 3] == 'b' &&
2052 filename
[len
- 2] == 'z' && filename
[len
- 1] == '2' &&
2053 filename
[len
] == '\0') || (filename
[len
- 3] == '.' &&
2054 filename
[len
- 2] == 'b' && filename
[len
- 1] == 'z' &&
2055 filename
[len
] == '\0')) {
2057 snprintf(command
, sizeof(command
), "bzip2 -dc %s", filename
);
2059 snprintf(command
, sizeof(command
), "bzip2 -zc 1>%s", filename
);
2067 static int copy_file(FILE *fp
, const char *dst
)
2070 char line
[LINE_MAX
];
2071 char *cmd
= compression_cmd(dst
, 0);
2073 if ((ofp
= popen(cmd
, "w")) == NULL
)
2076 fseek(fp
, 0, SEEK_SET
);
2078 while ((fgets(line
, sizeof(line
), fp
)) != NULL
)
2079 fprintf(ofp
, "%s", line
);
2086 * Closes and free's a PGN file handle.
2088 pgn_error_t
pgn_close(PGN_FILE
*pgn
)
2091 return E_PGN_INVALID
;
2095 * Appending to a compressed file.
2098 if (copy_file(pgn
->fp
, pgn
->filename
))
2102 unlink(pgn
->tmpfile
);
2111 free(pgn
->filename
);
2117 * Opens a file 'filename' with the given 'mode'. 'mode' should be "r" for
2118 * reading, "w" for writing (will truncate if the file exists) or "a" for
2119 * appending to an existing file or creating a new one. Returns E_PGN_OK on
2120 * success and sets 'result' to a file handle for use will the other file
2121 * functions or E_PGN_ERR if there is an error opening the file in which case
2122 * errno will be set to the error or E_PGN_INVALID if 'mode' is an invalid
2123 * mode or if 'filename' is not a regular file.
2125 pgn_error_t
pgn_open(const char *filename
, const char *mode
, PGN_FILE
**result
)
2127 FILE *fp
= NULL
, *tfp
= NULL
;
2128 char buf
[PATH_MAX
], *p
;
2134 int ret
= E_PGN_ERR
;
2138 PGN_DUMP("%s:%d: BEGIN opening %s\n", __FILE__
, __LINE__
, filename
);
2141 if (!filename
|| !mode
)
2142 return E_PGN_INVALID
;
2144 if (strcmp(mode
, "r") == 0)
2146 else if (strcmp(mode
, "w") == 0)
2148 else if (strcmp(mode
, "a") == 0) {
2153 return E_PGN_INVALID
;
2156 pgn
= calloc(1, sizeof(PGN_FILE
));
2161 if (strcmp(filename
, "-") != 0) {
2162 if (m
&& access(filename
, R_OK
) == -1)
2165 if (stat(filename
, &st
) == -1 && !m
&& errno
!= ENOENT
)
2168 if (m
&& !S_ISREG(st
.st_mode
)) {
2169 ret
= E_PGN_INVALID
;
2173 if ((cmd
= compression_cmd(filename
, m
)) != NULL
) {
2176 if (append
&& access(filename
, R_OK
) == 0) {
2180 cmd
= compression_cmd(filename
, 1);
2182 if ((fp
= popen(cmd
, "r")) == NULL
)
2185 if (tmpnam(tmp
) == NULL
)
2188 if ((fd
= open(tmp
, O_RDWR
|O_EXCL
|O_CREAT
)) == -1)
2191 if ((tfp
= fdopen(fd
, "a+")) == NULL
)
2194 while ((p
= fgets(buf
, sizeof(buf
), fp
)) != NULL
)
2195 fprintf(tfp
, "%s", p
);
2199 pgn
->tmpfile
= strdup(tmp
);
2203 if ((fp
= popen(cmd
, m
? "r" : "w")) == NULL
)
2207 if ((tfp
= tmpfile()) == NULL
)
2210 while ((p
= fgets(buf
, sizeof(buf
), fp
)) != NULL
)
2211 fprintf(tfp
, "%s", p
);
2220 if ((fp
= fopen(filename
, mode
)) == NULL
)
2230 if (*filename
!= '/') {
2231 if (getcwd(buf
, sizeof(buf
)) == NULL
) {
2238 asprintf(&p
, "%s/%s", buf
, filename
);
2242 pgn
->filename
= strdup(filename
);
2256 * Returns E_PGN_OK if 'filename' is a recognized compressed filetype or
2259 pgn_error_t
pgn_is_compressed(const char *filename
)
2261 if (compression_cmd(filename
, 0))
2268 * Gets the value of config flag 'f'. The next argument should be a pointer of
2269 * the config type which is set to the value of 'f'. Returns E_PGN_ERR if 'f'
2270 * is an invalid flag or E_PGN_OK on success.
2272 pgn_error_t
pgn_config_get(pgn_config_flag f
, ...)
2277 pgn_progress
*progress
;
2282 case PGN_STRICT_CASTLING
:
2283 intval
= va_arg(ap
, int *);
2284 *intval
= pgn_config
.strict_castling
;
2288 intval
= va_arg(ap
, int *);
2289 *intval
= pgn_config
.reduced
;
2293 intval
= va_arg(ap
, int *);
2294 *intval
= pgn_config
.mpl
;
2297 case PGN_STOP_ON_ERROR
:
2298 intval
= va_arg(ap
, int *);
2299 *intval
= pgn_config
.stop
;
2303 longval
= va_arg(ap
, long *);
2304 *longval
= pgn_config
.stop
;
2307 case PGN_PROGRESS_FUNC
:
2308 progress
= va_arg(ap
, pgn_progress
*);
2309 progress
= pgn_config
.pfunc
;
2314 intval
= va_arg(ap
, int *);
2315 *intval
= dumptofile
;
2327 * Sets config flag 'f' to the next argument. Returns E_PGN_OK on success or
2328 * E_PGN_ERR if 'f' is an invalid flag or E_PGN_INVALID if 'val' is an invalid
2331 pgn_error_t
pgn_config_set(pgn_config_flag f
, ...)
2341 n
= va_arg(ap
, int);
2343 if (n
!= 1 && n
!= 0) {
2344 ret
= E_PGN_INVALID
;
2348 pgn_config
.reduced
= n
;
2351 n
= va_arg(ap
, int);
2354 ret
= E_PGN_INVALID
;
2360 case PGN_STOP_ON_ERROR
:
2361 n
= va_arg(ap
, int);
2363 if (n
!= 1 && n
!= 0) {
2364 ret
= E_PGN_INVALID
;
2368 pgn_config
.stop
= n
;
2371 n
= va_arg(ap
, long);
2372 pgn_config
.progress
= n
;
2374 case PGN_PROGRESS_FUNC
:
2375 pgn_config
.pfunc
= va_arg(ap
, pgn_progress
*);
2377 case PGN_STRICT_CASTLING
:
2378 n
= va_arg(ap
, int);
2379 pgn_config
.strict_castling
= n
;
2383 n
= va_arg(ap
, int);
2384 dumptofile
= (n
> 0) ? 1 : 0;
2397 * Writes a PGN formatted game 'g' to a file which was opened with pgn_open().
2398 * See 'pgn_config_flag' for output options. Returns E_PGN_ERR if there was a
2399 * memory allocation or write error and E_PGN_OK on success. It is important
2400 * to use pgn_close() afterwards if the file is a recognized compressed file
2401 * type otherwise the created temporary file wont be copied to the destination
2404 pgn_error_t
pgn_write(PGN_FILE
*pgn
, GAME g
)
2412 pgn_write_turn
= (TEST_FLAG(g
->flags
, GF_BLACK_OPENING
)) ? BLACK
: WHITE
;
2413 pgn_tag_sort(g
->tag
);
2416 PGN_DUMP("%s:%d: writing tag section\n", __FILE__
, __LINE__
);
2419 for (i
= 0; g
->tag
[i
]; i
++) {
2421 char tbuf
[11] = {0};
2424 if (pgn_config
.reduced
&& i
== 7)
2427 if (strcmp(g
->tag
[i
]->name
, "Date") == 0) {
2428 memset(&tp
, 0, sizeof(struct tm
));
2430 if (strptime(g
->tag
[i
]->value
, PGN_TIME_FORMAT
, &tp
) != NULL
) {
2431 len
= strftime(tbuf
, sizeof(tbuf
), PGN_TIME_FORMAT
, &tp
) + 1;
2433 if ((tmp
= strdup(tbuf
)) == NULL
)
2436 free(g
->tag
[i
]->value
);
2437 g
->tag
[i
]->value
= tmp
;
2440 else if (strcmp(g
->tag
[i
]->name
, "Event") == 0) {
2441 if (g
->tag
[i
]->value
[0] == '\0') {
2442 if ((tmp
= strdup("?")) == NULL
)
2445 free(g
->tag
[i
]->value
);
2446 g
->tag
[i
]->value
= tmp
;
2449 else if (strcmp(g
->tag
[i
]->name
, "Site") == 0) {
2450 if (g
->tag
[i
]->value
[0] == '\0') {
2451 if ((tmp
= strdup("?")) == NULL
)
2454 free(g
->tag
[i
]->value
);
2455 g
->tag
[i
]->value
= tmp
;
2458 else if (strcmp(g
->tag
[i
]->name
, "Round") == 0) {
2459 if (g
->tag
[i
]->value
[0] == '\0') {
2460 if ((tmp
= strdup("?")) == NULL
)
2463 free(g
->tag
[i
]->value
);
2464 g
->tag
[i
]->value
= tmp
;
2467 else if (strcmp(g
->tag
[i
]->name
, "Result") == 0) {
2468 if (g
->tag
[i
]->value
[0] == '\0') {
2469 if ((tmp
= strdup("*")) == NULL
)
2472 free(g
->tag
[i
]->value
);
2473 g
->tag
[i
]->value
= tmp
;
2476 else if (strcmp(g
->tag
[i
]->name
, "Black") == 0) {
2477 if (g
->tag
[i
]->value
[0] == '\0') {
2478 if ((tmp
= strdup("?")) == NULL
)
2481 free(g
->tag
[i
]->value
);
2482 g
->tag
[i
]->value
= tmp
;
2485 else if (strcmp(g
->tag
[i
]->name
, "White") == 0) {
2486 if (g
->tag
[i
]->value
[0] == '\0') {
2487 if ((tmp
= strdup("?")) == NULL
)
2490 free(g
->tag
[i
]->value
);
2491 g
->tag
[i
]->value
= tmp
;
2495 fprintf(pgn
->fp
, "[%s \"%s\"]\n", g
->tag
[i
]->name
,
2496 (g
->tag
[i
]->value
&& g
->tag
[i
]->value
[0]) ?
2497 pgn_tag_add_escapes(g
->tag
[i
]->value
) : "");
2501 PGN_DUMP("%s:%d: writing move section\n", __FILE__
, __LINE__
);
2503 Fputc('\n', pgn
->fp
, &len
);
2505 ravlevel
= pgn_mpl
= 0;
2507 if (pgn_history_total(g
->hp
) && pgn_write_turn
== BLACK
)
2508 putstring(pgn
->fp
, "1...", &len
);
2510 write_all_move_text(pgn
->fp
, g
->hp
, 1, &len
);
2512 Fputc(' ', pgn
->fp
, &len
);
2513 putstring(pgn
->fp
, g
->tag
[6]->value
, &len
);
2514 putstring(pgn
->fp
, "\n\n", &len
);
2516 if (!pgn_config
.reduced
)
2517 CLEAR_FLAG(g
->flags
, GF_PERROR
);
2523 * Clears the enpassant flag for all positions on board 'b'. Returns nothing.
2525 void pgn_reset_enpassant(BOARD b
)
2530 PGN_DUMP("%s:%d: resetting enpassant\n", __FILE__
, __LINE__
);
2533 for (r
= 0; r
< 8; r
++) {
2534 for (c
= 0; c
< 8; c
++)
2535 b
[r
][c
].enpassant
= 0;