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
);
285 SET_FLAG(game
[gindex
].flags
, GF_WK_CASTLE
|GF_WQ_CASTLE
|GF_WQ_CASTLE
|
286 GF_BK_CASTLE
|GF_BQ_CASTLE
);
289 if (pgn_find_tag(g
->tag
, "FEN") != -1 &&
290 pgn_init_fen_board(g
, tb
, NULL
))
293 for (i
= 0; i
< n
; i
++) {
297 if ((h
= history_by_n(g
->hp
, i
)) == NULL
)
300 if (pgn_validate_move(g
, tb
, h
->move
)) {
309 memcpy(b
, tb
, sizeof(BOARD
));
315 * Updates the game 'g' using board 'b' to the next 'n'th history move. The
316 * 's' parameter is either 2 for a wholestep or 1 for a halfstep.
318 void history_previous(GAME
*g
, BOARD b
, int n
)
320 if (g
->hindex
- n
< 0) {
322 g
->hindex
= history_total(g
->hp
);
329 history_update_board(g
, b
, g
->hindex
);
333 * Updates the game 'g' using board 'b' to the previous 'n'th history move.
334 * 's' parameter is either 2 for a wholestep or 1 for a halfstep.
336 void history_next(GAME
*g
, BOARD b
, int n
)
338 if (g
->hindex
+ n
> history_total(g
->hp
)) {
342 g
->hindex
= history_total(g
->hp
);
347 history_update_board(g
, b
, g
->hindex
);
350 * Converts the character piece 'p' to an integer.
352 int pgn_piece_to_int(int p
)
380 * Converts the integer piece 'n' to a character.
382 int pgn_int_to_piece(char turn
, int n
)
412 return (turn
== WHITE
) ? toupper(p
) : p
;
416 * Finds a tag 'name' in the structure array 't'. Returns the location in the
417 * array of the found tag or -1 on failure.
419 int pgn_find_tag(TAG
**t
, const char *name
)
423 for (i
= 0; t
[i
]; i
++) {
424 if (strcasecmp(t
[i
]->name
, name
) == 0)
431 static int tag_compare(const void *a
, const void *b
)
436 return strcmp((*ta
)->name
, (*tb
)->name
);
440 * Sorts a tag array. The first seven tags are in order of the PGN standard so
443 void pgn_sort_tags(TAG
**tags
)
445 if (pgn_tag_total(tags
) <= 7)
448 qsort(tags
+ 7, pgn_tag_total(tags
) - 7, sizeof(TAG
*), tag_compare
);
453 static int end_of_game(GAME g
, const char *str
)
458 for (i
= 0; i
< NARRAY(fancy_results
); i
++) {
459 if (strstr(str
, fancy_results
[i
].pgn
) != NULL
) {
460 len
= strlen(fancy_results
[i
].pgn
) + 1;
461 g
.tag
[TAG_RESULT
].value
= Realloc(g
.tag
[TAG_RESULT
].value
, len
);
462 strncpy(g
.tag
[TAG_RESULT
].value
, fancy_results
[i
].pgn
, len
);
471 int pgn_tag_total(TAG
**tags
)
485 * Adds a tag 'name' with value 'value' to the pointer to array 'dst'. The 'n'
486 * parameter is incremented to the new total of array 'dst'. If a duplicate
487 * tag 'name' was found then the existing tag is updated to the new 'value'.
488 * Returns 1 if a duplicate tag was found or 0 otherwise.
490 int pgn_add_tag(TAG
***dst
, char *name
, char *value
)
495 int t
= pgn_tag_total(tdata
);
500 // Find an existing tag with 'name'.
501 for (i
= 0; i
< t
; i
++) {
502 if (strcasecmp(tdata
[i
]->name
, name
) == 0) {
503 len
= (value
) ? strlen(value
) + 1 : 1;
504 tdata
[i
]->value
= Realloc(tdata
[i
]->value
, len
);
505 strncpy(tdata
[i
]->value
, (value
) ? value
: "", len
);
511 tdata
= Realloc(tdata
, (t
+ 2) * sizeof(TAG
*));
512 tdata
[t
] = Malloc(sizeof(TAG
));
513 len
= strlen(name
) + 1;
514 tdata
[t
]->name
= Malloc(len
);
515 strncpy(tdata
[t
]->name
, name
, len
);
518 len
= strlen(value
) + 1;
519 tdata
[t
]->value
= Malloc(len
);
520 strncpy(tdata
[t
]->value
, value
, len
);
523 tdata
[t
]->value
= NULL
;
530 static char *remove_tag_escapes(const char *str
)
533 int len
= strlen(str
);
534 static char buf
[MAX_PGN_LINE_LEN
] = {0};
536 for (i
= n
= 0; i
< len
; i
++, n
++) {
552 * Initializes a new game board.
554 void pgn_init_board(BOARD b
)
558 memset(b
, 0, sizeof(BOARD
));
560 for (row
= 0; row
< 8; row
++) {
561 for (col
= 0; col
< 8; col
++) {
594 b
[row
][col
].icon
= (row
< 2) ? c
: toupper(c
);
600 * Adds the standard PGN roster tags to game 'g'.
602 static void set_default_tags(GAME
*g
)
607 struct passwd
*pw
= getpwuid(getuid());
610 tp
= localtime(&now
);
611 strftime(tbuf
, sizeof(tbuf
), PGN_TIME_FORMAT
, tp
);
614 /* The standard seven tag roster (in order of appearance). */
615 pgn_add_tag(&g
->tag
, "Event", "?");
616 pgn_add_tag(&g
->tag
, "Site", "?");
617 pgn_add_tag(&g
->tag
, "Date", tbuf
);
618 pgn_add_tag(&g
->tag
, "Round", "-");
619 pgn_add_tag(&g
->tag
, "White", pw
->pw_gecos
);
620 pgn_add_tag(&g
->tag
, "Black", "?");
621 pgn_add_tag(&g
->tag
, "Result", "*");
624 void pgn_tag_free(TAG
**tags
)
631 for (i
= 0; tags
[i
]; i
++) {
633 free(tags
[i
]->value
);
640 void pgn_free(GAME g
)
642 history_free(g
.history
, 0);
644 memset(&g
, 0, sizeof(GAME
));
651 for (i
= 0; i
< gtotal
; i
++) {
655 for (game
[i
].ravlevel
--; game
[i
].ravlevel
>= 0; game
[i
].ravlevel
--)
656 free(game
[i
].rav
[game
[i
].ravlevel
].fen
);
667 static void reset_game_data()
673 static void skip_leading_space(FILE *fp
)
677 while ((c
= fgetc(fp
)) != EOF
&& !feof(fp
)) {
686 * PGN move text section.
688 static int move_text(GAME
*g
, FILE *fp
)
690 char m
[MAX_SAN_MOVE_LEN
+ 1] = {0}, *p
;
696 while((c
= fgetc(fp
)) != EOF
) {
717 if (g
->hindex
== 0 && g
->ravlevel
== 0)
718 SET_FLAG(g
->flags
, GF_BLACK_OPENING
);
724 CLEAR_FLAG(g
->flags
, GF_BLACK_OPENING
);
730 if (fscanf(fp
, " %[a-hPRNBQK1-9#+=Ox-]%n", m
, &count
) != 1)
733 p
= m
+ strlen(m
) - 1;
735 if (!history_total(g
->hp
) && g
->ravlevel
== 0 && VALIDRANK(ROWTOINT(*p
)) &&
736 VALIDFILE(COLTOINT(*(p
-1))) && ROWTOINT(*p
) > 4) {
738 SET_FLAG(g
->flags
, GF_BLACK_OPENING
);
743 // In case the file is in a2a4 format, convert this move to SAN format.
744 if ((p
= pgn_a2a4tosan(g
, g
->b
, m
)) == NULL
)
747 if (pgn_validate_move(g
, g
->b
, p
)) {
765 static void nag_text(GAME
*g
, FILE *fp
)
768 char nags
[5], *n
= nags
;
771 while ((c
= fgetc(fp
)) != EOF
&& !isspace(c
)) {
773 while ((c
= fgetc(fp
)) != EOF
&& isdigit(c
))
780 if ((c
= fgetc(fp
)) == '!')
792 if ((c
= fgetc(fp
)) == '?')
806 if ((c
= fgetc(fp
)) == '+')
816 if ((t
= fgetc(fp
)) == '=')
821 if ((i
= fgetc(fp
)) == '-')
834 if ((t
= fgetc(fp
)) == '+')
837 if ((i
= fgetc(fp
)) == '+')
854 nag
= (nags
[0]) ? atoi(nags
) : 0;
856 if (!nag
|| nag
< 0 || nag
> 255)
859 for (i
= 0; i
< MAX_PGN_NAG
; i
++) {
860 if (g
->hp
[g
->hindex
]->nag
[i
])
863 g
->hp
[g
->hindex
]->nag
[i
] = nag
;
867 skip_leading_space(fp
);
871 * PGN move annotation.
873 static void annotation_text(GAME
*g
, FILE *fp
, int terminator
)
877 int hindex
= history_total(g
->hp
) - 1;
878 char buf
[MAX_PGN_LINE_LEN
], *a
= buf
;
880 skip_leading_space(fp
);
882 while ((c
= fgetc(fp
)) != EOF
&& c
!= terminator
) {
886 if (isspace(c
) && isspace(lastchar
))
889 if (len
+ 1 == sizeof(buf
))
897 g
->hp
[hindex
]->comment
= Realloc(g
->hp
[hindex
]->comment
, ++len
);
898 strncpy(g
->hp
[hindex
]->comment
, buf
, len
);
904 static int tag_text(GAME
*g
, FILE *fp
)
906 char name
[LINE_MAX
], *n
= name
;
907 char value
[LINE_MAX
], *v
= value
;
909 int quoted_string
= 0;
912 skip_leading_space(fp
);
914 /* The tag name is up until the first whitespace. */
915 while ((c
= fgetc(fp
)) != EOF
&& !isspace(c
))
919 *name
= toupper(*name
);
920 skip_leading_space(fp
);
922 /* The value is until the first closing bracket. */
923 while ((c
= fgetc(fp
)) != EOF
&& c
!= ']') {
924 if (i
++ == '\0' && c
== '\"') {
929 if (c
== '\n' || c
== '\t')
932 if (c
== ' ' && lastchar
== ' ')
940 while (isspace(*--v
))
946 if (value
[0] == '\0') {
947 if (strcmp(name
, "Result") == 0)
955 strncpy(value
, remove_tag_escapes(value
), sizeof(value
));
956 pgn_add_tag(&g
->tag
, name
, value
);
961 * PGN end-of-game marker.
963 static int eog_text(GAME
*g
, FILE *fp
)
966 char buf
[8], *p
= buf
;
968 while ((c
= fgetc(fp
)) != EOF
&& !isspace(c
) && i
++ < sizeof(buf
))
975 g
->tag
[TAG_RESULT
]->value
= Realloc(g
->tag
[TAG_RESULT
]->value
, strlen(buf
) + 1);
976 strcpy(g
->tag
[TAG_RESULT
]->value
, buf
);
981 * Parse RAV text and keep track of g.hp. The 'o' argument is the board state
982 * before the current move (.hindex) was parsed.
984 static int read_file(FILE *);
985 static int rav_text(GAME
*g
, FILE *fp
, int which
, BOARD o
)
987 int ravindex
= ravlevel
;
990 // Begin RAV for the current move.
993 * Save the current game state for this RAV depth/level.
995 rav
= Realloc(rav
, (ravindex
+ 1) * sizeof(RAV
));
996 rav
[ravindex
].fen
= strdup(pgn_game_to_fen((*g
), g
->b
));
997 rav
[ravindex
].hp
= g
->hp
;
998 memcpy(&tg
, g
, sizeof(GAME
));
999 memcpy(g
->b
, o
, sizeof(BOARD
));
1001 g
->hp
[g
->hindex
]->rav
= Calloc(1, sizeof(HISTORY
));
1003 // history_add() will now append to this new RAV.
1004 g
->hp
= g
->hp
[g
->hindex
]->rav
;
1007 * Reset. Will be restored later from 'tg' which is a local variable
1008 * so recursion is possible.
1014 * Now continue as normal as if there were no RAV. Moves will be
1015 * parsed and appended to the new .hp.
1021 * read_file() has returned. The means that a RAV has ended by this
1022 * function returning -1 (see below). So we restore the game state
1023 * that was saved before calling read_file().
1025 pgn_init_fen_board(&tg
, g
->b
, rav
[ravindex
].fen
);
1026 free(rav
[ravindex
].fen
);
1027 memcpy(g
, &tg
, sizeof(GAME
));
1028 g
->hp
= rav
[ravindex
].hp
;
1032 * The end of a RAV. This makes read_file() that called this function
1033 * rav_text() return (see above).
1035 else if (which
== ')')
1042 * See pgn_init_fen_board(). Returns -1 on parse error. 0 may be returned on
1043 * success when there is no move count in the FEN tag otherwise the move count
1046 static int parse_fen_line(BOARD b
, unsigned *flags
, char *turn
, char *ply
,
1050 char line
[LINE_MAX
], *s
;
1051 int row
= 8, col
= 1;
1054 strncpy(line
, str
, sizeof(line
));
1056 pgn_reset_enpassant(b
);
1058 while ((tmp
= strsep(&s
, "/")) != NULL
) {
1061 if (!VALIDFILE(row
))
1068 if (isdigit(*tmp
)) {
1074 for (; n
; --n
, col
++)
1075 b
[ROWTOBOARD(row
)][COLTOBOARD(col
)].icon
=
1076 pgn_int_to_piece(WHITE
, OPEN_SQUARE
);
1078 else if (pgn_piece_to_int(*tmp
) != -1)
1079 b
[ROWTOBOARD(row
)][COLTOBOARD(col
++)].icon
= *tmp
;
1106 while (*tmp
&& *tmp
!= ' ') {
1109 SET_FLAG(*flags
, GF_WK_CASTLE
);
1112 SET_FLAG(*flags
, GF_WQ_CASTLE
);
1115 SET_FLAG(*flags
, GF_BK_CASTLE
);
1118 SET_FLAG(*flags
, GF_BQ_CASTLE
);
1126 if (*++tmp
!= '-') {
1127 if (!VALIDCOL(*tmp
))
1132 if (!VALIDROW(*tmp
))
1135 row
= 8 - atoi(tmp
++);
1136 b
[row
][col
].enpassant
= 1;
1145 while (*tmp
&& isdigit(*tmp
))
1156 * This is called at the EOG marker and the beginning of the move text
1157 * section. So at least a move or EOG marker has to exist. It initializes the
1158 * board (b) to the FEN tag (if found) and sets the castling and enpassant
1159 * info for the game 'g'. If 'fen' is set it should be a fen tag and will be
1160 * parsed rather than the game 'g'.tag FEN tag. Returns 0 on success or if
1161 * there was both a FEN and SetUp tag with the SetUp tag set to 0. Returns 1
1162 * if there was a FEN parse error or no FEN tag at all.
1164 int pgn_init_fen_board(GAME
*g
, BOARD b
, char *fen
)
1169 char turn
= g
->turn
;
1172 pgn_init_board(tmpboard
);
1175 n
= pgn_find_tag(g
->tag
, "Setup");
1176 i
= pgn_find_tag(g
->tag
, "FEN");
1180 * If the FEN tag exists and there is no SetUp tag go ahead and parse it.
1181 * If there is a SetUp tag only parse the FEN tag if the value is 1.
1183 if ((n
>= 0 && i
>= 0 && atoi(g
->tag
[n
]->value
) == 1)
1184 || (i
>= 0 && n
== -1) || fen
) {
1185 if ((n
= parse_fen_line(tmpboard
, &flags
, &turn
, &ply
,
1186 (fen
) ? fen
: g
->tag
[i
]->value
)) == -1)
1189 memcpy(b
, tmpboard
, sizeof(BOARD
));
1190 CLEAR_FLAG(g
->flags
, GF_WK_CASTLE
);
1191 CLEAR_FLAG(g
->flags
, GF_WQ_CASTLE
);
1192 CLEAR_FLAG(g
->flags
, GF_BK_CASTLE
);
1193 CLEAR_FLAG(g
->flags
, GF_BQ_CASTLE
);
1200 return (i
>= 0 && n
>= 0) ? 0 : 1;
1206 * Allocates a new game and increments gindex (the current game) and gtotal
1207 * (the total number of games).
1211 gindex
= ++gtotal
- 1;
1212 game
= Realloc(game
, gtotal
* sizeof(GAME
));
1213 memset(&game
[gindex
], 0, sizeof(GAME
));
1214 game
[gindex
].hp
= Calloc(1, sizeof(HISTORY
*));
1215 game
[gindex
].hp
[0] = NULL
;
1216 game
[gindex
].history
= game
[gindex
].hp
;
1217 game
[gindex
].side
= game
[gindex
].turn
= WHITE
;
1218 SET_FLAG(game
[gindex
].flags
, GF_WK_CASTLE
|GF_WQ_CASTLE
|GF_WQ_CASTLE
|
1219 GF_BK_CASTLE
|GF_BQ_CASTLE
);
1220 pgn_init_board(game
[gindex
].b
);
1221 set_default_tags(&game
[gindex
]);
1224 kill(getpid(), SIGUSR1
);
1227 static int read_file(FILE *fp
)
1230 char buf
[LINE_MAX
] = {0}, *p
= buf
;
1233 int parse_error
= 0;
1240 if ((c
= fgetc(fp
)) == EOF
) {
1258 nextchar
= fgetc(fp
);
1259 ungetc(nextchar
, fp
);
1262 * If there was a move text parsing error, keep reading until the end
1263 * of the current game discarding the data.
1271 if (pgn_config
.stop
)
1274 if (c
== '\n' && (nextchar
== '\n' || nextchar
== '\015')) {
1283 // New game reached.
1284 if (c
== '\n' && (nextchar
== '\n' || nextchar
== '\015')) {
1296 * PGN: Application comment. The '%' must be on the first column of
1297 * the line. The comment continues until the end of the current line.
1300 if (lastchar
== '\n' || lastchar
== 0) {
1301 while ((c
= fgetc(fp
)) != EOF
&& c
!= '\n');
1305 // Not sure what to do here.
1312 if (c
== '<' || c
== '>')
1316 * PGN: Recurrsive Annotation Variation. Read rav_text() for more
1319 if (c
== '(' || c
== ')') {
1320 switch (rav_text(&game
[gindex
], fp
, c
, old
)) {
1323 * This is the end of the current RAV. This function has
1324 * been called from rav_text(). Returning from this point
1325 * will put us back in rav_text().
1331 * We're back at the root move. Continue as normal.
1339 * Continue processing. Probably the root move.
1347 // PGN: Numeric Annotation Glyph.
1348 if (c
== '$' || c
== '!' || c
== '?' || c
== '+' || c
== '-' ||
1349 c
== '~' || c
== '=') {
1351 nag_text(&game
[gindex
], fp
);
1356 * PGN: Annotation. The ';' comment continues until the end of the
1357 * current line. The '{' type comment continues until a '}' is
1360 if (c
== '{' || c
== ';') {
1361 annotation_text(&game
[gindex
], fp
, (c
== '{') ? '}' : '\n');
1367 // First roster tag found. Initialize the data structures.
1372 if (gtotal
&& history_total(game
[gindex
].hp
))
1373 game
[gindex
].hindex
= history_total(game
[gindex
].hp
) - 1;
1376 memcpy(old
, game
[gindex
].b
, sizeof(BOARD
));
1379 if (tag_text(&game
[gindex
], fp
))
1380 parse_error
= 1; // FEN tag parse error.
1385 // PGN: End-of-game markers.
1386 if ((isdigit(c
) && (nextchar
== '-' || nextchar
== '/')) || c
== '*') {
1388 eog_text(&game
[gindex
], fp
);
1392 if (!done_fen_tag
) {
1393 if (pgn_find_tag(game
[gindex
].tag
, "FEN") != -1 &&
1394 pgn_init_fen_board(&game
[gindex
], game
[gindex
].b
,
1407 if ((isdigit(c
) && c
!= '0') || VALIDCOL(c
) || c
== 'N' || c
== 'K'
1408 || c
== 'Q' || c
== 'B' || c
== 'R' || c
== 'P' ||
1412 // PGN: If a FEN tag exists, initialize the board to the value.
1414 if (pgn_find_tag(game
[gindex
].tag
, "FEN") != -1 &&
1415 pgn_init_fen_board(&game
[gindex
], game
[gindex
].b
,
1426 * PGN: Import format doesn't require a roster tag section. We've
1427 * arrived to the move text section without any tags so we
1428 * initialize a new game which set's the default tags and any tags
1429 * from the configuration file.
1433 game
[gindex
].hindex
= history_total(game
[gindex
].hp
) - 1;
1435 pgn_new_game(game
[gindex
].b
);
1436 memcpy(old
, game
[gindex
].b
, sizeof(BOARD
));
1440 memcpy(old
, game
[gindex
].b
, sizeof(BOARD
));
1442 if (move_text(&game
[gindex
], fp
)) {
1443 SET_FLAG(game
[gindex
].flags
, GF_PERROR
);
1453 DUMP("unparsed: '%s'\n", buf
);
1455 if (strlen(buf
) + 1 == sizeof(buf
))
1456 bzero(buf
, sizeof(buf
));
1466 * Parses a file whose file pointer is 'fp'. 'fp' may have been returned by
1467 * pgn_open(). If 'fp' is NULL then a single empty game will be allocated. If
1468 * there is a parsing error 1 is returned otherwise 0 is returned and the
1469 * global 'gindex' is set to the last parsed game in the file and the global
1470 * 'gtotal' is set to the total number of games in the file. The file will be
1471 * closed when the parsing is done.
1473 int pgn_parse(FILE *fp
)
1486 pgn_ret
= read_file(fp
);
1496 gtotal
= gindex
+ 1;
1498 for (i
= 0; i
< gtotal
; i
++) {
1499 game
[i
].history
= game
[i
].hp
;
1500 game
[i
].hindex
= history_total(game
[i
].hp
);
1507 * Escape '"' and '\' in tag values.
1509 static char *pgn_add_tag_escapes(const char *str
)
1512 int len
= strlen(str
);
1513 static char buf
[MAX_PGN_LINE_LEN
] = {0};
1515 for (i
= n
= 0; i
< len
; i
++, n
++) {
1532 static void Fputc(int c
, FILE *fp
, int *len
)
1536 if (c
!= '\n' && i
+ 1 > 80)
1537 Fputc('\n', fp
, &i
);
1539 if (pgn_lastc
== '\n' && c
== ' ') {
1544 if (fputc(c
, fp
) == EOF
)
1557 static void putstring(FILE *fp
, char *str
, int *len
)
1561 for (p
= str
; *p
; p
++) {
1564 while (*p
&& *p
!= ' ')
1568 Fputc('\n', fp
, len
);
1576 * See pgn_write() for more info.
1578 static void write_comments_and_nag(FILE *fp
, HISTORY
*h
, int *len
)
1583 for (i
= 0; i
< MAX_PGN_NAG
; i
++) {
1585 Fputc(' ', fp
, len
);
1586 Fputc('$', fp
, len
);
1587 putstring(fp
, itoa(h
->nag
[i
]), len
);
1592 if (strlen(h
->comment
) + *len
+ 1 > 80) {
1594 putstring(fp
, " {", len
);
1597 putstring(fp
, " ;", len
);
1599 putstring(fp
, h
->comment
, len
);
1602 putstring(fp
, "}", len
);
1604 Fputc('\n', fp
, len
);
1608 static void write_move_text(FILE *fp
, HISTORY
*h
, int *len
)
1610 Fputc(' ', fp
, len
);
1611 putstring(fp
, h
->move
, len
);
1613 if (!pgn_config
.reduced
)
1614 write_comments_and_nag(fp
, h
, len
);
1617 static void write_all_move_text(FILE *fp
, HISTORY
**h
, int m
, int *len
)
1620 HISTORY
**hp
= NULL
;
1622 for (i
= 0; h
[i
]; i
++) {
1623 if (pgn_write_turn
== WHITE
) {
1624 if (pgn_config
.mpl
&& pgn_mpl
== pgn_config
.mpl
) {
1626 Fputc('\n', fp
, len
);
1630 Fputc(' ', fp
, len
);
1632 if (strlen(itoa(m
)) + 1 + *len
> 80)
1633 Fputc('\n', fp
, len
);
1635 putstring(fp
, itoa(m
), len
);
1636 Fputc('.', fp
, len
);
1640 write_move_text(fp
, h
[i
], len
);
1642 if (!pgn_config
.reduced
&& h
[i
]->rav
) {
1644 int oldturn
= pgn_write_turn
;
1647 putstring(fp
, " (", len
);
1650 * If it's WHITE's turn the move number will be added above after
1651 * the call to write_all_move_text() below.
1653 if (pgn_write_turn
== BLACK
) {
1654 putstring(fp
, itoa(m
), len
);
1655 putstring(fp
, "...", len
);
1659 write_all_move_text(fp
, hp
, m
, len
);
1661 pgn_write_turn
= oldturn
;
1662 putstring(fp
, ")", len
);
1665 if (h
[i
+ 1] && !ravlevel
)
1666 Fputc(' ', fp
, len
);
1668 if (pgn_write_turn
== WHITE
&& h
[i
+ 1]) {
1669 putstring(fp
, itoa(m
), len
);
1670 putstring(fp
, "...", len
);
1674 if (pgn_write_turn
== BLACK
)
1677 pgn_write_turn
= (pgn_write_turn
== WHITE
) ? BLACK
: WHITE
;
1681 #ifdef WITH_COMPRESSED
1682 static char *compression_cmd(const char *filename
, int expand
)
1684 static char command
[FILENAME_MAX
];
1685 int len
= strlen(filename
);
1687 if (filename
[len
- 4] == '.' && filename
[len
- 3] == 'z' &&
1688 filename
[len
- 2] == 'i' && filename
[len
- 1] == 'p' &&
1689 filename
[len
] == '\0') {
1691 snprintf(command
, sizeof(command
), "unzip -p %s 2>/dev/null",
1694 snprintf(command
, sizeof(command
), "zip -9 >%s 2>/dev/null",
1699 else if (filename
[len
- 3] == '.' && filename
[len
- 2] == 'g' &&
1700 filename
[len
- 1] == 'z' && filename
[len
] == '\0') {
1702 snprintf(command
, sizeof(command
), "gzip -dc %s", filename
);
1704 snprintf(command
, sizeof(command
), "gzip -c 1>%s", filename
);
1708 else if (filename
[len
- 2] == '.' && filename
[len
- 1] == 'Z' &&
1709 filename
[len
] == '\0') {
1711 snprintf(command
, sizeof(command
), "uncompress -c %s", filename
);
1713 snprintf(command
, sizeof(command
), "compress -c 1>%s", filename
);
1717 else if ((filename
[len
- 4] == '.' && filename
[len
- 3] == 'b' &&
1718 filename
[len
- 2] == 'z' && filename
[len
- 1] == '2' &&
1719 filename
[len
] == '\0') || (filename
[len
- 3] == '.' &&
1720 filename
[len
- 2] == 'b' && filename
[len
- 1] == 'z' &&
1721 filename
[len
] == '\0')) {
1723 snprintf(command
, sizeof(command
), "bzip2 -dc %s", filename
);
1725 snprintf(command
, sizeof(command
), "bzip2 -zc 1>%s", filename
);
1735 * Returns a file pointer associated with 'filename' or NULL on error with
1736 * errno set to indicate the error. If compressed file support was enabled at
1737 * compile time and the filetype is supported and the utility is installed
1738 * then the file will be decompressed.
1740 FILE *pgn_open(const char *filename
)
1743 #ifdef WITH_COMPRESSED
1747 char *command
= NULL
;
1750 if (access(filename
, R_OK
) == -1)
1753 #ifdef WITH_COMPRESSED
1754 if ((command
= compression_cmd(filename
, 1)) != NULL
) {
1755 if ((tfp
= tmpfile()) == NULL
)
1758 if ((fp
= popen(command
, "r")) == NULL
)
1761 while ((p
= fgets(buf
, sizeof(buf
), fp
)) != NULL
)
1762 fprintf(tfp
, "%s", p
);
1768 fseek(tfp
, 0, SEEK_SET
);
1770 if ((tfp
= fopen(filename
, "r")) == NULL
)
1776 if ((fp
= fopen(filename
, "r")) == NULL
)
1784 * Returns 1 if 'filename' is a recognized compressed filetype.
1786 int pgn_is_compressed(const char *filename
)
1788 #ifdef WITH_COMPRESSED
1789 if (compression_cmd(filename
, 0))
1797 * Set game flags. See chess.h for available flags.
1799 int pgn_config_set(pgn_config_flag f
, int val
)
1807 pgn_config
.reduced
= 1;
1809 pgn_config
.reduced
= 0;
1814 pgn_config
.mpl
= val
;
1816 case PGN_STOP_ON_ERROR
:
1818 pgn_config
.stop
= 1;
1820 pgn_config
.stop
= 0;
1832 * Returns the value accociated with 'f' or -1 if 'f' is invalid.
1834 int pgn_config_get(pgn_config_flag f
)
1838 return pgn_config
.reduced
;
1839 case PGN_STOP_ON_ERROR
:
1840 return pgn_config
.stop
;
1842 return pgn_config
.mpl
;
1851 * Writes a PGN formatted game 'g' to the file pointed to by 'fp'. Returns 1
1852 * if the written move count doesn't match the game's ply count (FEN tag) or 0
1855 void pgn_write(FILE *fp
, GAME g
)
1860 pgn_write_turn
= (TEST_FLAG(g
.flags
, GF_BLACK_OPENING
)) ? BLACK
: WHITE
;
1864 if (!isfifo && g.hindex != g.htotal) {
1865 snprintf(buf, sizeof(buf), "%s (#%i)", GAME_SAVE_FROM_HISTORY_TITLE,
1867 i = message(buf, GAME_SAVE_FROM_HISTORY_PROMPT, "%s",
1868 GAME_SAVE_FROM_HISTORY_TEXT);
1871 g.htotal = g.hindex;
1875 pgn_sort_tags(g
.tag
);
1877 for (i
= 0; g
.tag
[i
]; i
++) {
1879 char tbuf
[64 + 1]; //FIXME
1881 if (pgn_config
.reduced
&& i
== 7)
1884 if (strcmp(g
.tag
[i
]->name
, "Date") == 0) {
1885 if (strptime(g
.tag
[i
]->value
, TIME_FORMAT
, &tp
) != NULL
) {
1886 len
= strftime(tbuf
, sizeof(tbuf
), PGN_TIME_FORMAT
, &tp
) + 1;
1887 g
.tag
[i
]->value
= Realloc(g
.tag
[i
]->value
, len
);
1888 strncpy(g
.tag
[i
]->value
, tbuf
, len
);
1891 else if (strcmp(g
.tag
[i
]->name
, "Event") == 0) {
1892 if (g
.tag
[i
]->value
[0] == '\0') {
1893 g
.tag
[i
]->value
= Realloc(g
.tag
[i
]->value
, 2);
1894 g
.tag
[i
]->value
[0] = '?';
1895 g
.tag
[i
]->value
[1] = '\0';
1898 else if (strcmp(g
.tag
[i
]->name
, "Site") == 0) {
1899 if (g
.tag
[i
]->value
[0] == '\0') {
1900 g
.tag
[i
]->value
= Realloc(g
.tag
[i
]->value
, 2);
1901 g
.tag
[i
]->value
[0] = '?';
1902 g
.tag
[i
]->value
[1] = '\0';
1905 else if (strcmp(g
.tag
[i
]->name
, "Round") == 0) {
1906 if (g
.tag
[i
]->value
[0] == '\0') {
1907 g
.tag
[i
]->value
= Realloc(g
.tag
[i
]->value
, 2);
1908 g
.tag
[i
]->value
[0] = '?';
1909 g
.tag
[i
]->value
[1] = '\0';
1912 else if (strcmp(g
.tag
[i
]->name
, "Result") == 0) {
1913 if (g
.tag
[i
]->value
[0] == '\0') {
1914 g
.tag
[i
]->value
= Realloc(g
.tag
[i
]->value
, 2);
1915 g
.tag
[i
]->value
[0] = '*';
1916 g
.tag
[i
]->value
[1] = '\0';
1919 else if (strcmp(g
.tag
[i
]->name
, "Black") == 0) {
1920 if (g
.tag
[i
]->value
[0] == '\0') {
1921 g
.tag
[i
]->value
= Realloc(g
.tag
[i
]->value
, 2);
1922 g
.tag
[i
]->value
[0] = '?';
1923 g
.tag
[i
]->value
[1] = '\0';
1926 else if (strcmp(g
.tag
[i
]->name
, "White") == 0) {
1927 if (g
.tag
[i
]->value
[0] == '\0') {
1928 g
.tag
[i
]->value
= Realloc(g
.tag
[i
]->value
, 2);
1929 g
.tag
[i
]->value
[0] = '?';
1930 g
.tag
[i
]->value
[1] = '\0';
1934 fprintf(fp
, "[%s \"%s\"]\n", g
.tag
[i
]->name
,
1935 (g
.tag
[i
]->value
&& g
.tag
[i
]->value
[0]) ?
1936 pgn_add_tag_escapes(g
.tag
[i
]->value
) : "");
1939 Fputc('\n', fp
, &len
);
1943 if (history_total(g
.hp
) && pgn_write_turn
== BLACK
)
1944 putstring(fp
, "1...", &len
);
1946 write_all_move_text(fp
, g
.hp
, 1, &len
);
1948 Fputc(' ', fp
, &len
);
1949 putstring(fp
, g
.tag
[TAG_RESULT
]->value
, &len
);
1950 putstring(fp
, "\n\n", &len
);
1952 if (!pgn_config
.reduced
) {
1953 CLEAR_FLAG(g
.flags
, GF_MODIFIED
);
1954 CLEAR_FLAG(g
.flags
, GF_PERROR
);