1 /* vim:tw=78:ts=8:sw=4:set ft=c: */
3 Copyright (C) 2002-2006 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 * Creates a FEN tag from the current game 'g' and board 'b'. Returns a FEN
58 char *pgn_game_to_fen(GAME g
, BOARD b
)
62 static char buf
[MAX_PGN_LINE_LEN
], *p
;
64 char enpassant
[3] = {0}, *e
;
67 for (i
= history_total(g
.hp
); i
>= g
.hindex
- 1; i
--)
72 for (row
= 0; row
< 8; row
++) {
75 for (col
= 0; col
< 8; col
++) {
76 if (b
[row
][col
].enpassant
) {
77 b
[row
][col
].icon
= pgn_int_to_piece(WHITE
, OPEN_SQUARE
);
80 *e
++ = ('0' + 8) - row
;
84 if (pgn_piece_to_int(b
[row
][col
].icon
) == OPEN_SQUARE
) {
94 *p
++ = b
[row
][col
].icon
;
108 *p
++ = (g
.turn
== WHITE
) ? 'w' : 'b';
111 if (TEST_FLAG(g
.flags
, GF_WK_CASTLE
) && pgn_piece_to_int(b
[7][7].icon
) ==
112 ROOK
&& isupper(b
[7][7].icon
) && pgn_piece_to_int(b
[7][4].icon
) ==
113 KING
&& isupper(b
[7][4].icon
)) {
118 if (TEST_FLAG(g
.flags
, GF_WQ_CASTLE
) && pgn_piece_to_int(b
[7][0].icon
) ==
119 ROOK
&& isupper(b
[7][0].icon
) && pgn_piece_to_int(b
[7][4].icon
) ==
120 KING
&& isupper(b
[7][4].icon
)) {
125 if (TEST_FLAG(g
.flags
, GF_BK_CASTLE
) && pgn_piece_to_int(b
[0][7].icon
) ==
126 ROOK
&& islower(b
[0][7].icon
) && pgn_piece_to_int(b
[0][4].icon
) ==
127 KING
&& islower(b
[0][4].icon
)) {
132 if (TEST_FLAG(g
.flags
, GF_BQ_CASTLE
) && pgn_piece_to_int(b
[0][0].icon
) ==
133 ROOK
&& islower(b
[0][0].icon
) && pgn_piece_to_int(b
[0][4].icon
) ==
134 KING
&& islower(b
[0][4].icon
)) {
156 strcat(p
, itoa(g
.ply
));
157 p
= buf
+ strlen(buf
);
161 i
= (g
.hindex
+ 1) / 2;
163 strcat(p
, itoa((g
.hindex
/ 2) + (g
.hindex
% 2)));
170 * Returns the total number of moves in 'h' or 0 if none.
172 int history_total(HISTORY
**h
)
179 for (i
= 0; h
[i
]; i
++);
184 * Deallocates the all the data for 'h' from position 'start' in the array.
186 void history_free(HISTORY
**h
, int start
)
193 for (i
= start
; h
[i
]; i
++) {
198 history_free(h
[i
]->rav
, 0);
208 * Returns the history ply 'n' from 'h'. If 'n' is out of range then NULL is
211 HISTORY
*history_by_n(HISTORY
**h
, int n
)
213 if (n
< 0 || n
> history_total(h
) - 1)
220 * Appends move 'm' to game 'g' history pointer. The history pointer may be a
221 * in a RAV so g->rav.hp is also updated to the new (realloc()'ed) pointer. If
222 * not in a RAV then g->history will be updated. Returns 1 if realloc() failed
225 int history_add(GAME
*g
, const char *m
)
227 int t
= history_total(g
->hp
);
232 o
= g
->rav
[g
->ravlevel
].hp
- g
->hp
;
234 o
= g
->history
- g
->hp
;
236 if ((h
= realloc(g
->hp
, (t
+ 2) * sizeof(HISTORY
*))) == NULL
)
242 g
->rav
[g
->ravlevel
].hp
= g
->hp
+ o
;
244 g
->history
= g
->hp
+ o
;
246 if ((g
->hp
[t
] = calloc(1, sizeof(HISTORY
))) == NULL
)
249 g
->hp
[t
++]->move
= strdup(m
);
251 g
->hindex
= history_total(g
->hp
);
256 * Resets the game 'g' using board 'b' up to history move 'n'.
258 int history_update_board(GAME
*g
, BOARD b
, int n
)
264 if (TEST_FLAG(g
->flags
, GF_BLACK_OPENING
))
270 if (TEST_FLAG(g
->flags
, GF_PERROR
))
271 SET_FLAG(flags
, GF_PERROR
);
273 if (TEST_FLAG(g
->flags
, GF_MODIFIED
))
274 SET_FLAG(flags
, GF_MODIFIED
);
276 if (TEST_FLAG(g
->flags
, GF_DELETE
))
277 SET_FLAG(flags
, GF_DELETE
);
279 if (TEST_FLAG(g
->flags
, GF_GAMEOVER
))
280 SET_FLAG(flags
, GF_GAMEOVER
);
287 if (pgn_find_tag(g
->tag
, "FEN") != -1 &&
288 pgn_init_fen_board(g
, tb
, NULL
))
291 for (i
= 0; i
< n
; i
++) {
295 if ((h
= history_by_n(g
->hp
, i
)) == NULL
)
298 if (pgn_validate_move(g
, tb
, h
->move
)) {
307 memcpy(b
, tb
, sizeof(BOARD
));
313 * Updates the game 'g' using board 'b' to the next 'n'th history move. The
314 * 's' parameter is either 2 for a wholestep or 1 for a halfstep.
316 void history_previous(GAME
*g
, BOARD b
, int n
)
318 if (g
->hindex
- n
< 0) {
320 g
->hindex
= history_total(g
->hp
);
327 history_update_board(g
, b
, g
->hindex
);
331 * Updates the game 'g' using board 'b' to the previous 'n'th history move.
332 * 's' parameter is either 2 for a wholestep or 1 for a halfstep.
334 void history_next(GAME
*g
, BOARD b
, int n
)
336 if (g
->hindex
+ n
> history_total(g
->hp
)) {
340 g
->hindex
= history_total(g
->hp
);
345 history_update_board(g
, b
, g
->hindex
);
348 * Converts the character piece 'p' to an integer.
350 int pgn_piece_to_int(int p
)
378 * Converts the integer piece 'n' to a character.
380 int pgn_int_to_piece(char turn
, int n
)
410 return (turn
== WHITE
) ? toupper(p
) : p
;
414 * Finds a tag 'name' in the structure array 't'. Returns the location in the
415 * array of the found tag or -1 on failure.
417 int pgn_find_tag(TAG
**t
, const char *name
)
421 for (i
= 0; t
[i
]; i
++) {
422 if (strcasecmp(t
[i
]->name
, name
) == 0)
429 static int tag_compare(const void *a
, const void *b
)
434 return strcmp((*ta
)->name
, (*tb
)->name
);
438 * Sorts a tag array. The first seven tags are in order of the PGN standard so
441 void pgn_sort_tags(TAG
**tags
)
443 if (pgn_tag_total(tags
) <= 7)
446 qsort(tags
+ 7, pgn_tag_total(tags
) - 7, sizeof(TAG
*), tag_compare
);
451 static int end_of_game(GAME g
, const char *str
)
456 for (i
= 0; i
< NARRAY(fancy_results
); i
++) {
457 if (strstr(str
, fancy_results
[i
].pgn
) != NULL
) {
458 len
= strlen(fancy_results
[i
].pgn
) + 1;
459 g
.tag
[TAG_RESULT
].value
= Realloc(g
.tag
[TAG_RESULT
].value
, len
);
460 strncpy(g
.tag
[TAG_RESULT
].value
, fancy_results
[i
].pgn
, len
);
469 int pgn_tag_total(TAG
**tags
)
483 * Adds a tag 'name' with value 'value' to the pointer to array 'dst'. The 'n'
484 * parameter is incremented to the new total of array 'dst'. If a duplicate
485 * tag 'name' was found then the existing tag is updated to the new 'value'.
486 * Returns 1 if a duplicate tag was found or 0 otherwise.
488 int pgn_add_tag(TAG
***dst
, char *name
, char *value
)
493 int t
= pgn_tag_total(tdata
);
498 // Find an existing tag with 'name'.
499 for (i
= 0; i
< t
; i
++) {
500 if (strcasecmp(tdata
[i
]->name
, name
) == 0) {
501 len
= (value
) ? strlen(value
) + 1 : 1;
502 tdata
[i
]->value
= Realloc(tdata
[i
]->value
, len
);
503 strncpy(tdata
[i
]->value
, (value
) ? value
: "", len
);
509 tdata
= Realloc(tdata
, (t
+ 2) * sizeof(TAG
*));
510 tdata
[t
] = Malloc(sizeof(TAG
));
511 len
= strlen(name
) + 1;
512 tdata
[t
]->name
= Malloc(len
);
513 strncpy(tdata
[t
]->name
, name
, len
);
516 len
= strlen(value
) + 1;
517 tdata
[t
]->value
= Malloc(len
);
518 strncpy(tdata
[t
]->value
, value
, len
);
521 tdata
[t
]->value
= NULL
;
528 static char *remove_tag_escapes(const char *str
)
531 int len
= strlen(str
);
532 static char buf
[MAX_PGN_LINE_LEN
] = {0};
534 for (i
= n
= 0; i
< len
; i
++, n
++) {
550 * Initializes a new game board.
552 void pgn_init_board(BOARD b
)
556 memset(b
, 0, sizeof(BOARD
));
558 for (row
= 0; row
< 8; row
++) {
559 for (col
= 0; col
< 8; col
++) {
592 b
[row
][col
].icon
= (row
< 2) ? c
: toupper(c
);
598 * Adds the standard PGN roster tags to game 'g'.
600 static void set_default_tags(GAME
*g
)
605 struct passwd
*pw
= getpwuid(getuid());
608 tp
= localtime(&now
);
609 strftime(tbuf
, sizeof(tbuf
), PGN_TIME_FORMAT
, tp
);
612 /* The standard seven tag roster (in order of appearance). */
613 pgn_add_tag(&g
->tag
, "Event", "?");
614 pgn_add_tag(&g
->tag
, "Site", "?");
615 pgn_add_tag(&g
->tag
, "Date", tbuf
);
616 pgn_add_tag(&g
->tag
, "Round", "-");
617 pgn_add_tag(&g
->tag
, "White", pw
->pw_gecos
);
618 pgn_add_tag(&g
->tag
, "Black", "?");
619 pgn_add_tag(&g
->tag
, "Result", "*");
622 void pgn_tag_free(TAG
**tags
)
629 for (i
= 0; tags
[i
]; i
++) {
631 free(tags
[i
]->value
);
638 void pgn_free(GAME g
)
640 history_free(g
.history
, 0);
642 memset(&g
, 0, sizeof(GAME
));
649 for (i
= 0; i
< gtotal
; i
++) {
653 for (game
[i
].ravlevel
--; game
[i
].ravlevel
>= 0; game
[i
].ravlevel
--)
654 free(game
[i
].rav
[game
[i
].ravlevel
].fen
);
665 static void reset_game_data()
671 static void skip_leading_space(FILE *fp
)
675 while ((c
= fgetc(fp
)) != EOF
&& !feof(fp
)) {
684 * PGN move text section.
686 static int move_text(GAME
*g
, FILE *fp
)
688 char m
[MAX_SAN_MOVE_LEN
+ 1] = {0}, *p
;
694 while((c
= fgetc(fp
)) != EOF
) {
715 if (g
->hindex
== 0 && g
->ravlevel
== 0)
716 SET_FLAG(g
->flags
, GF_BLACK_OPENING
);
722 CLEAR_FLAG(g
->flags
, GF_BLACK_OPENING
);
728 if (fscanf(fp
, " %[a-hPRNBQK1-9#+=Ox-]%n", m
, &count
) != 1)
731 p
= m
+ strlen(m
) - 1;
733 if (!history_total(g
->hp
) && g
->ravlevel
== 0 && VALIDRANK(ROWTOINT(*p
)) &&
734 VALIDFILE(COLTOINT(*(p
-1))) && ROWTOINT(*p
) > 4) {
736 SET_FLAG(g
->flags
, GF_BLACK_OPENING
);
741 // In case the file is in a2a4 format, convert this move to SAN format.
742 if ((p
= pgn_a2a4tosan(g
, g
->b
, m
)) == NULL
)
745 if (pgn_validate_move(g
, g
->b
, p
)) {
763 static void nag_text(GAME
*g
, FILE *fp
)
766 char nags
[5], *n
= nags
;
769 while ((c
= fgetc(fp
)) != EOF
&& !isspace(c
)) {
771 while ((c
= fgetc(fp
)) != EOF
&& isdigit(c
))
778 if ((c
= fgetc(fp
)) == '!')
790 if ((c
= fgetc(fp
)) == '?')
804 if ((c
= fgetc(fp
)) == '+')
814 if ((t
= fgetc(fp
)) == '=')
819 if ((i
= fgetc(fp
)) == '-')
832 if ((t
= fgetc(fp
)) == '+')
835 if ((i
= fgetc(fp
)) == '+')
852 nag
= (nags
[0]) ? atoi(nags
) : 0;
854 if (!nag
|| nag
< 0 || nag
> 255)
857 for (i
= 0; i
< MAX_PGN_NAG
; i
++) {
858 if (g
->hp
[g
->hindex
]->nag
[i
])
861 g
->hp
[g
->hindex
]->nag
[i
] = nag
;
865 skip_leading_space(fp
);
869 * PGN move annotation.
871 static void annotation_text(GAME
*g
, FILE *fp
, int terminator
)
875 int hindex
= history_total(g
->hp
) - 1;
876 char buf
[MAX_PGN_LINE_LEN
], *a
= buf
;
878 skip_leading_space(fp
);
880 while ((c
= fgetc(fp
)) != EOF
&& c
!= terminator
) {
884 if (isspace(c
) && isspace(lastchar
))
887 if (len
+ 1 == sizeof(buf
))
895 g
->hp
[hindex
]->comment
= Realloc(g
->hp
[hindex
]->comment
, ++len
);
896 strncpy(g
->hp
[hindex
]->comment
, buf
, len
);
902 static int tag_text(GAME
*g
, FILE *fp
)
904 char name
[LINE_MAX
], *n
= name
;
905 char value
[LINE_MAX
], *v
= value
;
907 int quoted_string
= 0;
910 skip_leading_space(fp
);
912 /* The tag name is up until the first whitespace. */
913 while ((c
= fgetc(fp
)) != EOF
&& !isspace(c
))
917 *name
= toupper(*name
);
918 skip_leading_space(fp
);
920 /* The value is until the first closing bracket. */
921 while ((c
= fgetc(fp
)) != EOF
&& c
!= ']') {
922 if (i
++ == '\0' && c
== '\"') {
927 if (c
== '\n' || c
== '\t')
930 if (c
== ' ' && lastchar
== ' ')
938 while (isspace(*--v
))
944 if (value
[0] == '\0') {
945 if (strcmp(name
, "Result") == 0)
953 strncpy(value
, remove_tag_escapes(value
), sizeof(value
));
954 pgn_add_tag(&g
->tag
, name
, value
);
959 * PGN end-of-game marker.
961 static int eog_text(GAME
*g
, FILE *fp
)
964 char buf
[8], *p
= buf
;
966 while ((c
= fgetc(fp
)) != EOF
&& !isspace(c
) && i
++ < sizeof(buf
))
973 g
->tag
[TAG_RESULT
]->value
= Realloc(g
->tag
[TAG_RESULT
]->value
, strlen(buf
) + 1);
974 strcpy(g
->tag
[TAG_RESULT
]->value
, buf
);
979 * Parse RAV text and keep track of g.hp. The 'o' argument is the board state
980 * before the current move (.hindex) was parsed.
982 static int read_file(FILE *);
983 static int rav_text(GAME
*g
, FILE *fp
, int which
, BOARD o
)
985 int ravindex
= ravlevel
;
988 // Begin RAV for the current move.
991 * Save the current game state for this RAV depth/level.
993 rav
= Realloc(rav
, (ravindex
+ 1) * sizeof(RAV
));
994 rav
[ravindex
].fen
= strdup(pgn_game_to_fen((*g
), g
->b
));
995 rav
[ravindex
].hp
= g
->hp
;
996 memcpy(&tg
, g
, sizeof(GAME
));
997 memcpy(g
->b
, o
, sizeof(BOARD
));
999 g
->hp
[g
->hindex
]->rav
= Calloc(1, sizeof(HISTORY
));
1001 // history_add() will now append to this new RAV.
1002 g
->hp
= g
->hp
[g
->hindex
]->rav
;
1005 * Reset. Will be restored later from 'tg' which is a local variable
1006 * so recursion is possible.
1012 * Now continue as normal as if there were no RAV. Moves will be
1013 * parsed and appended to the new .hp.
1019 * read_file() has returned. The means that a RAV has ended by this
1020 * function returning -1 (see below). So we restore the game state
1021 * that was saved before calling read_file().
1023 pgn_init_fen_board(&tg
, g
->b
, rav
[ravindex
].fen
);
1024 free(rav
[ravindex
].fen
);
1025 memcpy(g
, &tg
, sizeof(GAME
));
1026 g
->hp
= rav
[ravindex
].hp
;
1030 * The end of a RAV. This makes read_file() that called this function
1031 * rav_text() return (see above).
1033 else if (which
== ')')
1040 * See pgn_init_fen_board(). Returns -1 on parse error. 0 may be returned on
1041 * success when there is no move count in the FEN tag otherwise the move count
1044 static int parse_fen_line(BOARD b
, unsigned *flags
, char *turn
, char *ply
,
1048 char line
[LINE_MAX
], *s
;
1049 int row
= 8, col
= 1;
1052 strncpy(line
, str
, sizeof(line
));
1054 pgn_reset_enpassant(b
);
1056 while ((tmp
= strsep(&s
, "/")) != NULL
) {
1059 if (!VALIDFILE(row
))
1066 if (isdigit(*tmp
)) {
1072 for (; n
; --n
, col
++)
1073 b
[ROWTOBOARD(row
)][COLTOBOARD(col
)].icon
=
1074 pgn_int_to_piece(WHITE
, OPEN_SQUARE
);
1076 else if (pgn_piece_to_int(*tmp
) != -1)
1077 b
[ROWTOBOARD(row
)][COLTOBOARD(col
++)].icon
= *tmp
;
1104 while (*tmp
&& *tmp
!= ' ') {
1107 SET_FLAG(*flags
, GF_WK_CASTLE
);
1110 SET_FLAG(*flags
, GF_WQ_CASTLE
);
1113 SET_FLAG(*flags
, GF_BK_CASTLE
);
1116 SET_FLAG(*flags
, GF_BQ_CASTLE
);
1124 if (*++tmp
!= '-') {
1125 if (!VALIDCOL(*tmp
))
1130 if (!VALIDROW(*tmp
))
1133 row
= 8 - atoi(tmp
++);
1134 b
[row
][col
].enpassant
= 1;
1143 while (*tmp
&& isdigit(*tmp
))
1154 * This is called at the EOG marker and the beginning of the move text
1155 * section. So at least a move or EOG marker has to exist. It initializes the
1156 * board (b) to the FEN tag (if found) and sets the castling and enpassant
1157 * info for the game 'g'. If 'fen' is set it should be a fen tag and will be
1158 * parsed rather than the game 'g'.tag FEN tag. Returns 0 on success or if
1159 * there was both a FEN and SetUp tag with the SetUp tag set to 0. Returns 1
1160 * if there was a FEN parse error or no FEN tag at all.
1162 int pgn_init_fen_board(GAME
*g
, BOARD b
, char *fen
)
1167 char turn
= g
->turn
;
1170 pgn_init_board(tmpboard
);
1173 n
= pgn_find_tag(g
->tag
, "Setup");
1174 i
= pgn_find_tag(g
->tag
, "FEN");
1178 * If the FEN tag exists and there is no SetUp tag go ahead and parse it.
1179 * If there is a SetUp tag only parse the FEN tag if the value is 1.
1181 if ((n
>= 0 && i
>= 0 && atoi(g
->tag
[n
]->value
) == 1)
1182 || (i
>= 0 && n
== -1) || fen
) {
1183 if ((n
= parse_fen_line(tmpboard
, &flags
, &turn
, &ply
,
1184 (fen
) ? fen
: g
->tag
[i
]->value
)) == -1)
1187 memcpy(b
, tmpboard
, sizeof(BOARD
));
1188 CLEAR_FLAG(g
->flags
, GF_WK_CASTLE
);
1189 CLEAR_FLAG(g
->flags
, GF_WQ_CASTLE
);
1190 CLEAR_FLAG(g
->flags
, GF_BK_CASTLE
);
1191 CLEAR_FLAG(g
->flags
, GF_BQ_CASTLE
);
1198 return (i
>= 0 && n
>= 0) ? 0 : 1;
1204 * Allocates a new game and increments gindex (the current game) and gtotal
1205 * (the total number of games).
1209 gindex
= ++gtotal
- 1;
1210 game
= Realloc(game
, gtotal
* sizeof(GAME
));
1211 memset(&game
[gindex
], 0, sizeof(GAME
));
1212 game
[gindex
].hp
= Calloc(1, sizeof(HISTORY
*));
1213 game
[gindex
].hp
[0] = NULL
;
1214 game
[gindex
].history
= game
[gindex
].hp
;
1215 game
[gindex
].side
= game
[gindex
].turn
= WHITE
;
1216 SET_FLAG(game
[gindex
].flags
, GF_WK_CASTLE
|GF_WQ_CASTLE
|GF_WQ_CASTLE
|GF_BK_CASTLE
|GF_BQ_CASTLE
);
1217 pgn_init_board(game
[gindex
].b
);
1218 set_default_tags(&game
[gindex
]);
1221 static int read_file(FILE *fp
)
1224 char buf
[LINE_MAX
] = {0}, *p
= buf
;
1227 int parse_error
= 0;
1234 if ((c
= fgetc(fp
)) == EOF
) {
1252 nextchar
= fgetc(fp
);
1253 ungetc(nextchar
, fp
);
1256 * If there was a move text parsing error, keep reading until the end
1257 * of the current game discarding the data.
1265 if (pgn_config
.stop
)
1268 if (c
== '\n' && (nextchar
== '\n' || nextchar
== '\015')) {
1277 // New game reached.
1278 if (c
== '\n' && (nextchar
== '\n' || nextchar
== '\015')) {
1290 * PGN: Application comment. The '%' must be on the first column of
1291 * the line. The comment continues until the end of the current line.
1294 if (lastchar
== '\n' || lastchar
== 0) {
1295 while ((c
= fgetc(fp
)) != EOF
&& c
!= '\n');
1299 // Not sure what to do here.
1306 if (c
== '<' || c
== '>')
1310 * PGN: Recurrsive Annotation Variation. Read rav_text() for more
1313 if (c
== '(' || c
== ')') {
1314 switch (rav_text(&game
[gindex
], fp
, c
, old
)) {
1317 * This is the end of the current RAV. This function has
1318 * been called from rav_text(). Returning from this point
1319 * will put us back in rav_text().
1325 * We're back at the root move. Continue as normal.
1333 * Continue processing. Probably the root move.
1341 // PGN: Numeric Annotation Glyph.
1342 if (c
== '$' || c
== '!' || c
== '?' || c
== '+' || c
== '-' ||
1343 c
== '~' || c
== '=') {
1345 nag_text(&game
[gindex
], fp
);
1350 * PGN: Annotation. The ';' comment continues until the end of the
1351 * current line. The '{' type comment continues until a '}' is
1354 if (c
== '{' || c
== ';') {
1355 annotation_text(&game
[gindex
], fp
, (c
== '{') ? '}' : '\n');
1361 // First roster tag found. Initialize the data structures.
1366 if (gtotal
&& history_total(game
[gindex
].hp
))
1367 game
[gindex
].hindex
= history_total(game
[gindex
].hp
) - 1;
1370 memcpy(old
, game
[gindex
].b
, sizeof(BOARD
));
1373 if (tag_text(&game
[gindex
], fp
))
1374 parse_error
= 1; // FEN tag parse error.
1379 // PGN: End-of-game markers.
1380 if ((isdigit(c
) && (nextchar
== '-' || nextchar
== '/')) || c
== '*') {
1382 eog_text(&game
[gindex
], fp
);
1386 if (!done_fen_tag
) {
1387 if (pgn_find_tag(game
[gindex
].tag
, "FEN") != -1 &&
1388 pgn_init_fen_board(&game
[gindex
], game
[gindex
].b
,
1401 if ((isdigit(c
) && c
!= '0') || VALIDCOL(c
) || c
== 'N' || c
== 'K'
1402 || c
== 'Q' || c
== 'B' || c
== 'R' || c
== 'P' ||
1406 // PGN: If a FEN tag exists, initialize the board to the value.
1408 if (pgn_find_tag(game
[gindex
].tag
, "FEN") != -1 &&
1409 pgn_init_fen_board(&game
[gindex
], game
[gindex
].b
,
1420 * PGN: Import format doesn't require a roster tag section. We've
1421 * arrived to the move text section without any tags so we
1422 * initialize a new game which set's the default tags and any tags
1423 * from the configuration file.
1427 game
[gindex
].hindex
= history_total(game
[gindex
].hp
) - 1;
1429 pgn_new_game(game
[gindex
].b
);
1430 memcpy(old
, game
[gindex
].b
, sizeof(BOARD
));
1434 memcpy(old
, game
[gindex
].b
, sizeof(BOARD
));
1436 if (move_text(&game
[gindex
], fp
)) {
1437 SET_FLAG(game
[gindex
].flags
, GF_PERROR
);
1447 DUMP("unparsed: '%s'\n", buf
);
1449 if (strlen(buf
) + 1 == sizeof(buf
))
1450 bzero(buf
, sizeof(buf
));
1460 * Parses a file whose file pointer is 'fp'. 'fp' may have been returned by
1461 * pgn_open(). If 'fp' is NULL then a single empty game will be allocated. If
1462 * there is a parsing error 1 is returned otherwise 0 is returned and the
1463 * global 'gindex' is set to the last parsed game in the file and the global
1464 * 'gtotal' is set to the total number of games in the file. The file will be
1465 * closed when the parsing is done.
1467 int pgn_parse(FILE *fp
)
1479 pgn_ret
= read_file(fp
);
1490 gtotal
= gindex
+ 1;
1492 for (i
= 0; i
< gtotal
; i
++) {
1493 game
[i
].history
= game
[i
].hp
;
1494 game
[i
].hindex
= history_total(game
[i
].hp
);
1502 * Escape '"' and '\' in tag values.
1504 static char *pgn_add_tag_escapes(const char *str
)
1507 int len
= strlen(str
);
1508 static char buf
[MAX_PGN_LINE_LEN
] = {0};
1510 for (i
= n
= 0; i
< len
; i
++, n
++) {
1527 static void Fputc(int c
, FILE *fp
, int *len
)
1531 if (c
!= '\n' && i
+ 1 > 80)
1532 Fputc('\n', fp
, &i
);
1534 if (pgn_lastc
== '\n' && c
== ' ') {
1539 if (fputc(c
, fp
) == EOF
)
1552 static void putstring(FILE *fp
, char *str
, int *len
)
1556 for (p
= str
; *p
; p
++) {
1559 while (*p
&& *p
!= ' ')
1563 Fputc('\n', fp
, len
);
1571 * See pgn_write() for more info.
1573 static void write_comments_and_nag(FILE *fp
, HISTORY
*h
, int *len
)
1578 for (i
= 0; i
< MAX_PGN_NAG
; i
++) {
1580 Fputc(' ', fp
, len
);
1581 Fputc('$', fp
, len
);
1582 putstring(fp
, itoa(h
->nag
[i
]), len
);
1587 if (strlen(h
->comment
) + *len
+ 1 > 80) {
1589 putstring(fp
, " {", len
);
1592 putstring(fp
, " ;", len
);
1594 putstring(fp
, h
->comment
, len
);
1597 putstring(fp
, "}", len
);
1599 Fputc('\n', fp
, len
);
1603 static void write_move_text(FILE *fp
, HISTORY
*h
, int *len
)
1605 Fputc(' ', fp
, len
);
1606 putstring(fp
, h
->move
, len
);
1608 if (!pgn_config
.reduced
)
1609 write_comments_and_nag(fp
, h
, len
);
1612 static void write_all_move_text(FILE *fp
, HISTORY
**h
, int m
, int *len
)
1615 HISTORY
**hp
= NULL
;
1617 for (i
= 0; h
[i
]; i
++) {
1618 if (pgn_write_turn
== WHITE
) {
1619 if (pgn_config
.mpl
&& pgn_mpl
== pgn_config
.mpl
) {
1621 Fputc('\n', fp
, len
);
1625 Fputc(' ', fp
, len
);
1627 putstring(fp
, itoa(m
), len
);
1628 Fputc('.', fp
, len
);
1632 write_move_text(fp
, h
[i
], len
);
1634 if (!pgn_config
.reduced
&& h
[i
]->rav
) {
1636 int oldturn
= pgn_write_turn
;
1639 putstring(fp
, " (", len
);
1642 * If it's WHITE's turn the move number will be added above after
1643 * the call to write_all_move_text() below.
1645 if (pgn_write_turn
== BLACK
) {
1646 putstring(fp
, itoa(m
), len
);
1647 putstring(fp
, "...", len
);
1651 write_all_move_text(fp
, hp
, m
, len
);
1653 pgn_write_turn
= oldturn
;
1654 putstring(fp
, ")", len
);
1657 if (h
[i
+ 1] && !ravlevel
)
1658 Fputc(' ', fp
, len
);
1660 if (pgn_write_turn
== WHITE
&& h
[i
+ 1]) {
1661 putstring(fp
, itoa(m
), len
);
1662 putstring(fp
, "...", len
);
1666 if (pgn_write_turn
== BLACK
)
1669 pgn_write_turn
= (pgn_write_turn
== WHITE
) ? BLACK
: WHITE
;
1673 #ifdef WITH_COMPRESSED
1674 static char *compression_cmd(const char *filename
, int expand
)
1676 static char command
[FILENAME_MAX
];
1677 int len
= strlen(filename
);
1679 if (filename
[len
- 4] == '.' && filename
[len
- 3] == 'z' &&
1680 filename
[len
- 2] == 'i' && filename
[len
- 1] == 'p' &&
1681 filename
[len
] == '\0') {
1683 snprintf(command
, sizeof(command
), "unzip -p %s 2>/dev/null",
1686 snprintf(command
, sizeof(command
), "zip -9 >%s 2>/dev/null",
1691 else if (filename
[len
- 3] == '.' && filename
[len
- 2] == 'g' &&
1692 filename
[len
- 1] == 'z' && filename
[len
] == '\0') {
1694 snprintf(command
, sizeof(command
), "gzip -dc %s", filename
);
1696 snprintf(command
, sizeof(command
), "gzip -c 1>%s", filename
);
1700 else if (filename
[len
- 2] == '.' && filename
[len
- 1] == 'Z' &&
1701 filename
[len
] == '\0') {
1703 snprintf(command
, sizeof(command
), "uncompress -c %s", filename
);
1705 snprintf(command
, sizeof(command
), "compress -c 1>%s", filename
);
1709 else if ((filename
[len
- 4] == '.' && filename
[len
- 3] == 'b' &&
1710 filename
[len
- 2] == 'z' && filename
[len
- 1] == '2' &&
1711 filename
[len
] == '\0') || (filename
[len
- 3] == '.' &&
1712 filename
[len
- 2] == 'b' && filename
[len
- 1] == 'z' &&
1713 filename
[len
] == '\0')) {
1715 snprintf(command
, sizeof(command
), "bzip2 -dc %s", filename
);
1717 snprintf(command
, sizeof(command
), "bzip2 -zc 1>%s", filename
);
1727 * Returns a file pointer associated with 'filename' or NULL on error with
1728 * errno set to indicate the error. If compressed file support was enabled at
1729 * compile time and the filetype is supported and the utility is installed
1730 * then the file will be decompressed.
1732 FILE *pgn_open(const char *filename
)
1735 #ifdef WITH_COMPRESSED
1739 char *command
= NULL
;
1742 if (access(filename
, R_OK
) == -1)
1745 #ifdef WITH_COMPRESSED
1746 if ((command
= compression_cmd(filename
, 1)) != NULL
) {
1747 if ((tfp
= tmpfile()) == NULL
)
1750 if ((fp
= popen(command
, "r")) == NULL
)
1753 while ((p
= fgets(buf
, sizeof(buf
), fp
)) != NULL
)
1754 fprintf(tfp
, "%s", p
);
1760 fseek(tfp
, 0, SEEK_SET
);
1762 if ((tfp
= fopen(filename
, "r")) == NULL
)
1768 if ((fp
= fopen(filename
, "r")) == NULL
)
1776 * Returns 1 if 'filename' is a recognized compressed filetype.
1778 int pgn_is_compressed(const char *filename
)
1780 #ifdef WITH_COMPRESSED
1781 if (compression_cmd(filename
, 0))
1789 * Set game flags. See chess.h for available flags.
1791 int pgn_config_set(pgn_config_flag f
, int val
)
1799 pgn_config
.reduced
= 1;
1801 pgn_config
.reduced
= 0;
1806 pgn_config
.mpl
= val
;
1808 case PGN_STOP_ON_ERROR
:
1810 pgn_config
.stop
= 1;
1812 pgn_config
.stop
= 0;
1824 * Returns the value accociated with 'f' or -1 if 'f' is invalid.
1826 int pgn_config_get(pgn_config_flag f
)
1830 return pgn_config
.reduced
;
1831 case PGN_STOP_ON_ERROR
:
1832 return pgn_config
.stop
;
1834 return pgn_config
.mpl
;
1843 * Writes a PGN formatted game 'g' to the file pointed to by 'fp'. Returns 1
1844 * if the written move count doesn't match the game's ply count (FEN tag) or 0
1847 void pgn_write(FILE *fp
, GAME g
)
1852 pgn_write_turn
= (TEST_FLAG(g
.flags
, GF_BLACK_OPENING
)) ? BLACK
: WHITE
;
1856 if (!isfifo && g.hindex != g.htotal) {
1857 snprintf(buf, sizeof(buf), "%s (#%i)", GAME_SAVE_FROM_HISTORY_TITLE,
1859 i = message(buf, GAME_SAVE_FROM_HISTORY_PROMPT, "%s",
1860 GAME_SAVE_FROM_HISTORY_TEXT);
1863 g.htotal = g.hindex;
1867 pgn_sort_tags(g
.tag
);
1869 for (i
= 0; g
.tag
[i
]; i
++) {
1871 char tbuf
[64 + 1]; //FIXME
1873 if (pgn_config
.reduced
&& i
== 7)
1876 if (strcmp(g
.tag
[i
]->name
, "Date") == 0) {
1877 if (strptime(g
.tag
[i
]->value
, TIME_FORMAT
, &tp
) != NULL
) {
1878 len
= strftime(tbuf
, sizeof(tbuf
), PGN_TIME_FORMAT
, &tp
) + 1;
1879 g
.tag
[i
]->value
= Realloc(g
.tag
[i
]->value
, len
);
1880 strncpy(g
.tag
[i
]->value
, tbuf
, len
);
1883 else if (strcmp(g
.tag
[i
]->name
, "Event") == 0) {
1884 if (g
.tag
[i
]->value
[0] == '\0') {
1885 g
.tag
[i
]->value
= Realloc(g
.tag
[i
]->value
, 2);
1886 g
.tag
[i
]->value
[0] = '?';
1887 g
.tag
[i
]->value
[1] = '\0';
1890 else if (strcmp(g
.tag
[i
]->name
, "Site") == 0) {
1891 if (g
.tag
[i
]->value
[0] == '\0') {
1892 g
.tag
[i
]->value
= Realloc(g
.tag
[i
]->value
, 2);
1893 g
.tag
[i
]->value
[0] = '?';
1894 g
.tag
[i
]->value
[1] = '\0';
1897 else if (strcmp(g
.tag
[i
]->name
, "Round") == 0) {
1898 if (g
.tag
[i
]->value
[0] == '\0') {
1899 g
.tag
[i
]->value
= Realloc(g
.tag
[i
]->value
, 2);
1900 g
.tag
[i
]->value
[0] = '?';
1901 g
.tag
[i
]->value
[1] = '\0';
1904 else if (strcmp(g
.tag
[i
]->name
, "Result") == 0) {
1905 if (g
.tag
[i
]->value
[0] == '\0') {
1906 g
.tag
[i
]->value
= Realloc(g
.tag
[i
]->value
, 2);
1907 g
.tag
[i
]->value
[0] = '*';
1908 g
.tag
[i
]->value
[1] = '\0';
1911 else if (strcmp(g
.tag
[i
]->name
, "Black") == 0) {
1912 if (g
.tag
[i
]->value
[0] == '\0') {
1913 g
.tag
[i
]->value
= Realloc(g
.tag
[i
]->value
, 2);
1914 g
.tag
[i
]->value
[0] = '?';
1915 g
.tag
[i
]->value
[1] = '\0';
1918 else if (strcmp(g
.tag
[i
]->name
, "White") == 0) {
1919 if (g
.tag
[i
]->value
[0] == '\0') {
1920 g
.tag
[i
]->value
= Realloc(g
.tag
[i
]->value
, 2);
1921 g
.tag
[i
]->value
[0] = '?';
1922 g
.tag
[i
]->value
[1] = '\0';
1926 fprintf(fp
, "[%s \"%s\"]\n", g
.tag
[i
]->name
,
1927 (g
.tag
[i
]->value
&& g
.tag
[i
]->value
[0]) ?
1928 pgn_add_tag_escapes(g
.tag
[i
]->value
) : "");
1931 Fputc('\n', fp
, &len
);
1935 if (history_total(g
.hp
) && pgn_write_turn
== BLACK
)
1936 putstring(fp
, "1...", &len
);
1938 write_all_move_text(fp
, g
.hp
, 1, &len
);
1940 Fputc(' ', fp
, &len
);
1941 putstring(fp
, g
.tag
[TAG_RESULT
]->value
, &len
);
1942 putstring(fp
, "\n\n", &len
);
1944 if (!pgn_config
.reduced
) {
1945 CLEAR_FLAG(g
.flags
, GF_MODIFIED
);
1946 CLEAR_FLAG(g
.flags
, GF_PERROR
);