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 return "libchess " PACKAGE_VERSION
;
58 static char *trim(char *str
)
68 for (i
= strlen(str
) - 1; isspace(str
[i
]); i
--)
74 static char *itoa(long n
)
78 snprintf(buf
, sizeof(buf
), "%li", n
);
83 * Clears the valid move flag for all positions on board 'b'. Returns nothing.
85 void pgn_reset_valid_moves(BOARD b
)
89 for (row
= 0; row
< 8; row
++) {
90 for (col
= 0; col
< 8; col
++)
91 b
[row
][col
].valid
= 0;
96 * Toggles g->turn. Returns nothing.
98 void pgn_switch_turn(GAME
*g
)
100 g
->turn
= (g
->turn
== WHITE
) ? BLACK
: WHITE
;
104 * Toggles g->side and switches the White and Black roster tags. Returns
107 void pgn_switch_side(GAME
*g
)
109 int i
= pgn_tag_find(g
->tag
, "White");
110 int n
= pgn_tag_find(g
->tag
, "Black");
111 char *w
= g
->tag
[i
]->value
;
113 g
->tag
[i
]->value
= g
->tag
[n
]->value
;
114 g
->tag
[n
]->value
= w
;
115 g
->side
= (g
->side
== WHITE
) ? BLACK
: WHITE
;
119 * Creates a FEN tag from the current game 'g', history move (g.hindex) and
120 * board 'b'. Returns a FEN tag.
122 char *pgn_game_to_fen(GAME g
, BOARD b
)
126 static char buf
[MAX_PGN_LINE_LEN
], *p
;
127 int oldturn
= g
.turn
;
128 char enpassant
[3] = {0}, *e
;
131 for (i
= pgn_history_total(g
.hp
); i
>= g
.hindex
- 1; i
--)
136 for (row
= 0; row
< 8; row
++) {
139 for (col
= 0; col
< 8; col
++) {
140 if (b
[row
][col
].enpassant
) {
141 b
[row
][col
].icon
= pgn_int_to_piece(WHITE
, OPEN_SQUARE
);
144 *e
++ = ('0' + 8) - row
;
148 if (pgn_piece_to_int(b
[row
][col
].icon
) == OPEN_SQUARE
) {
158 *p
++ = b
[row
][col
].icon
;
172 *p
++ = (g
.turn
== WHITE
) ? 'w' : 'b';
175 if (TEST_FLAG(g
.flags
, GF_WK_CASTLE
) && pgn_piece_to_int(b
[7][7].icon
) ==
176 ROOK
&& isupper(b
[7][7].icon
) && pgn_piece_to_int(b
[7][4].icon
) ==
177 KING
&& isupper(b
[7][4].icon
)) {
182 if (TEST_FLAG(g
.flags
, GF_WQ_CASTLE
) && pgn_piece_to_int(b
[7][0].icon
) ==
183 ROOK
&& isupper(b
[7][0].icon
) && pgn_piece_to_int(b
[7][4].icon
) ==
184 KING
&& isupper(b
[7][4].icon
)) {
189 if (TEST_FLAG(g
.flags
, GF_BK_CASTLE
) && pgn_piece_to_int(b
[0][7].icon
) ==
190 ROOK
&& islower(b
[0][7].icon
) && pgn_piece_to_int(b
[0][4].icon
) ==
191 KING
&& islower(b
[0][4].icon
)) {
196 if (TEST_FLAG(g
.flags
, GF_BQ_CASTLE
) && pgn_piece_to_int(b
[0][0].icon
) ==
197 ROOK
&& islower(b
[0][0].icon
) && pgn_piece_to_int(b
[0][4].icon
) ==
198 KING
&& islower(b
[0][4].icon
)) {
220 strcat(p
, itoa(g
.ply
));
221 p
= buf
+ strlen(buf
);
225 i
= (g
.hindex
+ 1) / 2;
227 strcat(p
, itoa((g
.hindex
/ 2) + (g
.hindex
% 2)));
234 * Returns the total number of moves in 'h' or 0 if there are none.
236 int pgn_history_total(HISTORY
**h
)
243 for (i
= 0; h
[i
]; i
++);
248 * Deallocates all of the history data from position 'start' in the array 'h'.
251 void pgn_history_free(HISTORY
**h
, int start
)
255 if (!h
|| start
> pgn_history_total(h
))
261 for (i
= start
; h
[i
]; i
++) {
266 pgn_history_free(h
[i
]->rav
, 0);
280 * Returns the history ply 'n' from 'h'. If 'n' is out of range then NULL is
283 HISTORY
*pgn_history_by_n(HISTORY
**h
, int n
)
285 if (n
< 0 || n
> pgn_history_total(h
) - 1)
292 * Appends move 'm' to game 'g' history pointer. The history pointer may be a
293 * in a RAV so g->rav.hp is also updated to the new (realloc()'ed) pointer. If
294 * not in a RAV then g->history will be updated. Returns E_PGN_ERR if
295 * realloc() failed or E_PGN_OK on success.
297 int pgn_history_add(GAME
*g
, const char *m
)
299 int t
= pgn_history_total(g
->hp
);
304 o
= g
->rav
[g
->ravlevel
].hp
- g
->hp
;
306 o
= g
->history
- g
->hp
;
308 if ((h
= realloc(g
->hp
, (t
+ 2) * sizeof(HISTORY
*))) == NULL
)
314 g
->rav
[g
->ravlevel
].hp
= g
->hp
+ o
;
316 g
->history
= g
->hp
+ o
;
318 if ((g
->hp
[t
] = calloc(1, sizeof(HISTORY
))) == NULL
)
321 if ((g
->hp
[t
]->move
= strdup(m
)) == NULL
) {
328 g
->hindex
= pgn_history_total(g
->hp
);
333 * Resets the game 'g' using board 'b' up to history move (g.hindex) 'n'.
334 * Returns E_PGN_OK on success or E_PGN_PARSE if there was a FEN tag but the
335 * parsing of it failed. Or returns E_PGN_INVALID if somehow the move failed
336 * validation while resetting.
338 int pgn_board_update(GAME
*g
, BOARD b
, int n
)
344 if (TEST_FLAG(g
->flags
, GF_BLACK_OPENING
))
350 if (TEST_FLAG(g
->flags
, GF_PERROR
))
351 SET_FLAG(flags
, GF_PERROR
);
353 if (TEST_FLAG(g
->flags
, GF_MODIFIED
))
354 SET_FLAG(flags
, GF_MODIFIED
);
356 if (TEST_FLAG(g
->flags
, GF_DELETE
))
357 SET_FLAG(flags
, GF_DELETE
);
359 if (TEST_FLAG(g
->flags
, GF_GAMEOVER
))
360 SET_FLAG(flags
, GF_GAMEOVER
);
365 SET_FLAG(g
->flags
, GF_WK_CASTLE
|GF_WQ_CASTLE
|GF_WQ_CASTLE
|
366 GF_BK_CASTLE
|GF_BQ_CASTLE
);
369 if (pgn_tag_find(g
->tag
, "FEN") != -1 &&
370 pgn_board_init_fen(g
, tb
, NULL
))
373 for (i
= 0; i
< n
; i
++) {
377 if ((h
= pgn_history_by_n(g
->hp
, i
)) == NULL
)
382 if ((ret
= pgn_parse_move(g
, tb
, &p
)) != E_PGN_OK
)
389 memcpy(b
, tb
, sizeof(BOARD
));
395 * Updates the game 'g' using board 'b' to the next 'n'th history move.
398 void pgn_history_prev(GAME
*g
, BOARD b
, int n
)
400 if (g
->hindex
- n
< 0) {
402 g
->hindex
= pgn_history_total(g
->hp
);
409 pgn_board_update(g
, b
, g
->hindex
);
413 * Updates the game 'g' using board 'b' to the previous 'n'th history move.
416 void pgn_history_next(GAME
*g
, BOARD b
, int n
)
418 if (g
->hindex
+ n
> pgn_history_total(g
->hp
)) {
422 g
->hindex
= pgn_history_total(g
->hp
);
427 pgn_board_update(g
, b
, g
->hindex
);
431 * Converts the character piece 'p' to an integer. Returns the integer
432 * associated with 'p' or E_PGN_ERR if 'p' is invalid.
434 int pgn_piece_to_int(register int p
)
462 * Converts the integer piece 'n' to a character whose turn is 'turn'. WHITE
463 * piece are uppercase and BLACK pieces are lowercase. Returns the character
464 * associated with 'n' or E_PGN_ERR if 'n' is an invalid piece.
466 int pgn_int_to_piece(char turn
, int n
)
497 return (turn
== WHITE
) ? toupper(p
) : p
;
501 * Finds a tag 'name' in the structure array 't'. Returns the location in the
502 * array of the found tag or E_PGN_ERR if the tag could not be found.
504 int pgn_tag_find(TAG
**t
, const char *name
)
508 for (i
= 0; t
[i
]; i
++) {
509 if (strcasecmp(t
[i
]->name
, name
) == 0)
516 static int tag_compare(const void *a
, const void *b
)
521 return strcmp((*ta
)->name
, (*tb
)->name
);
525 * Sorts a tag array. The first seven tags are in order of the PGN standard so
526 * don't sort'em. Returns nothing.
528 void pgn_tag_sort(TAG
**tags
)
530 if (pgn_tag_total(tags
) <= 7)
533 qsort(tags
+ 7, pgn_tag_total(tags
) - 7, sizeof(TAG
*), tag_compare
);
537 * Returns the total number of tags in 't' or 0 if 't' is NULL.
539 int pgn_tag_total(TAG
**tags
)
553 * Adds a tag 'name' with value 'value' to the pointer to array of TAG
554 * pointers 'dst'. If a duplicate tag 'name' was found then the existing tag
555 * is updated to the new 'value'. Returns E_PGN_ERR if there was a memory
556 * allocation error or E_PGN_OK on success.
558 int pgn_tag_add(TAG
***dst
, char *name
, char *value
)
564 int t
= pgn_tag_total(tdata
);
569 // Find an existing tag with 'name'.
570 for (i
= 0; i
< t
; i
++) {
573 if (strcasecmp(tdata
[i
]->name
, name
) == 0) {
574 if ((tmp
= strdup(value
)) == NULL
)
577 free(tdata
[i
]->value
);
578 tdata
[i
]->value
= tmp
;
584 if ((a
= realloc(tdata
, (t
+ 2) * sizeof(TAG
*))) == NULL
)
589 if ((tdata
[t
] = malloc(sizeof(TAG
))) == NULL
)
592 len
= strlen(name
) + 1;
594 if ((tdata
[t
]->name
= strdup(name
)) == NULL
) {
600 if ((tdata
[t
]->value
= strdup(value
)) == NULL
) {
601 free(tdata
[t
]->name
);
607 tdata
[t
]->value
= NULL
;
614 static char *remove_tag_escapes(const char *str
)
617 int len
= strlen(str
);
618 static char buf
[MAX_PGN_LINE_LEN
] = {0};
620 for (i
= n
= 0; i
< len
; i
++, n
++) {
636 * Resets or initializes a new game board 'b'. Returns nothing.
638 void pgn_board_init(BOARD b
)
642 memset(b
, 0, sizeof(BOARD
));
644 for (row
= 0; row
< 8; row
++) {
645 for (col
= 0; col
< 8; col
++) {
678 b
[row
][col
].icon
= (row
< 2) ? c
: toupper(c
);
684 * Adds the standard PGN roster tags to game 'g'.
686 static void set_default_tags(GAME
*g
)
691 struct passwd
*pw
= getpwuid(getuid());
694 tp
= localtime(&now
);
695 strftime(tbuf
, sizeof(tbuf
), PGN_TIME_FORMAT
, tp
);
697 /* The standard seven tag roster (in order of appearance). */
698 if (pgn_tag_add(&g
->tag
, "Event", "?"))
699 warn("pgn_tag_add()");
701 if (pgn_tag_add(&g
->tag
, "Site", "?"))
702 warn("pgn_tag_add()");
704 if (pgn_tag_add(&g
->tag
, "Date", tbuf
))
705 warn("pgn_tag_add()");
707 if (pgn_tag_add(&g
->tag
, "Round", "-"))
708 warn("pgn_tag_add()");
710 if (pgn_tag_add(&g
->tag
, "White", pw
->pw_gecos
))
711 warn("pgn_tag_add()");
713 if (pgn_tag_add(&g
->tag
, "Black", "?"))
714 warn("pgn_tag_add()");
716 if (pgn_tag_add(&g
->tag
, "Result", "*"))
717 warn("pgn_tag_add()");
721 * Frees a TAG array. Returns nothing.
723 void pgn_tag_free(TAG
**tags
)
726 int t
= pgn_tag_total(tags
);
731 for (i
= 0; i
< t
; i
++) {
733 free(tags
[i
]->value
);
741 * Frees a single game 'g'. Returns nothing.
743 void pgn_free(GAME g
)
745 pgn_history_free(g
.history
, 0);
748 memset(&g
, 0, sizeof(GAME
));
752 * Frees all games in the global 'game' array. Returns nothing.
758 for (i
= 0; i
< gtotal
; i
++) {
762 for (game
[i
].ravlevel
--; game
[i
].ravlevel
>= 0; game
[i
].ravlevel
--)
763 free(game
[i
].rav
[game
[i
].ravlevel
].fen
);
774 static void reset_game_data()
781 static void skip_leading_space(FILE *fp
)
785 while ((c
= fgetc(fp
)) != EOF
&& !feof(fp
)) {
794 * PGN move text section.
796 static int move_text(GAME
*g
, FILE *fp
)
798 char m
[MAX_SAN_MOVE_LEN
+ 1] = {0}, *p
;
804 while((c
= fgetc(fp
)) != EOF
) {
821 if (!pgn_history_total(g
->hp
) && digit
) {
825 if (g
->hindex
== 0 && g
->ravlevel
== 0)
826 SET_FLAG(g
->flags
, GF_BLACK_OPENING
);
832 CLEAR_FLAG(g
->flags
, GF_BLACK_OPENING
);
838 if (fscanf(fp
, " %[a-hPRNBQK1-9#+=Ox-]%n", m
, &count
) != 1)
843 if (pgn_parse_move(g
, pgn_board
, &p
)) {
850 dump_board(0, pgn_board
);
853 pgn_history_add(g
, p
);
861 static void nag_text(GAME
*g
, FILE *fp
)
864 char nags
[5], *n
= nags
;
867 while ((c
= fgetc(fp
)) != EOF
&& !isspace(c
)) {
869 while ((c
= fgetc(fp
)) != EOF
&& isdigit(c
))
876 if ((c
= fgetc(fp
)) == '!')
888 if ((c
= fgetc(fp
)) == '?')
902 if ((c
= fgetc(fp
)) == '+')
912 if ((t
= fgetc(fp
)) == '=')
917 if ((i
= fgetc(fp
)) == '-')
930 if ((t
= fgetc(fp
)) == '+')
933 if ((i
= fgetc(fp
)) == '+')
950 nag
= (nags
[0]) ? atoi(nags
) : 0;
952 if (!nag
|| nag
< 0 || nag
> 255)
955 // FIXME -1 is because move_text() increments g.hindex. The NAG
956 // annoatation isnt guaranteed to be after the move text in import format.
957 for (i
= 0; i
< MAX_PGN_NAG
; i
++) {
958 if (g
->hp
[g
->hindex
- 1]->nag
[i
])
961 g
->hp
[g
->hindex
- 1]->nag
[i
] = nag
;
965 skip_leading_space(fp
);
969 * PGN move annotation.
971 static int annotation_text(GAME
*g
, FILE *fp
, int terminator
)
975 int hindex
= pgn_history_total(g
->hp
) - 1;
976 char buf
[MAX_PGN_LINE_LEN
], *a
= buf
;
978 skip_leading_space(fp
);
980 while ((c
= fgetc(fp
)) != EOF
&& c
!= terminator
) {
984 if (isspace(c
) && isspace(lastchar
))
987 if (len
+ 1 == sizeof(buf
))
996 if ((g
->hp
[hindex
]->comment
= strdup(buf
)) == NULL
)
1005 static int tag_text(GAME
*g
, FILE *fp
)
1007 char name
[LINE_MAX
], *n
= name
;
1008 char value
[LINE_MAX
], *v
= value
;
1010 int quoted_string
= 0;
1013 skip_leading_space(fp
);
1015 /* The tag name is up until the first whitespace. */
1016 while ((c
= fgetc(fp
)) != EOF
&& !isspace(c
))
1020 *name
= toupper(*name
);
1021 skip_leading_space(fp
);
1023 /* The value is until the first closing bracket. */
1024 while ((c
= fgetc(fp
)) != EOF
&& c
!= ']') {
1025 if (i
++ == '\0' && c
== '\"') {
1030 if (c
== '\n' || c
== '\t')
1033 if (c
== ' ' && lastchar
== ' ')
1036 lastchar
= *v
++ = c
;
1041 while (isspace(*--v
))
1047 if (value
[0] == '\0') {
1048 if (strcmp(name
, "Result") == 0)
1056 strncpy(value
, remove_tag_escapes(value
), sizeof(value
));
1058 if (pgn_tag_add(&g
->tag
, name
, value
) != E_PGN_OK
)
1059 warn("pgn_tag_add()");
1065 * PGN end-of-game marker.
1067 static int eog_text(GAME
*g
, FILE *fp
)
1070 char buf
[8], *p
= buf
;
1072 while ((c
= fgetc(fp
)) != EOF
&& !isspace(c
) && i
++ < sizeof(buf
))
1080 if (pgn_tag_add(&g
->tag
, "Result", buf
) != E_PGN_OK
)
1081 warn("pgn_tag_add()");
1087 * Parse RAV text and keep track of g.hp. The 'o' argument is the board state
1088 * before the current move (.hindex) was parsed.
1090 static int read_file(FILE *);
1091 static int rav_text(GAME
*g
, FILE *fp
, int which
, BOARD o
)
1093 int ravindex
= ravlevel
;
1097 // Begin RAV for the current move.
1100 * Save the current game state for this RAV depth/level.
1102 if ((r
= realloc(rav
, (ravindex
+ 1) * sizeof(RAV
))) == NULL
) {
1109 if ((rav
[ravindex
].fen
= strdup(pgn_game_to_fen((*g
), pgn_board
)))
1115 rav
[ravindex
].hp
= g
->hp
;
1116 memcpy(&tg
, g
, sizeof(GAME
));
1117 memcpy(pgn_board
, o
, sizeof(BOARD
));
1119 if ((g
->hp
[g
->hindex
]->rav
= calloc(1, sizeof(HISTORY
))) == NULL
) {
1124 // pgn_history_add() will now append to this new RAV.
1125 g
->hp
= g
->hp
[g
->hindex
]->rav
;
1128 * Reset. Will be restored later from 'tg' which is a local variable
1129 * so recursion is possible.
1135 * Now continue as normal as if there were no RAV. Moves will be
1136 * parsed and appended to the new .hp.
1142 * read_file() has returned. The means that a RAV has ended by this
1143 * function returning -1 (see below). So we restore the game state
1144 * that was saved before calling read_file().
1146 pgn_board_init_fen(&tg
, pgn_board
, rav
[ravindex
].fen
);
1147 free(rav
[ravindex
].fen
);
1148 memcpy(g
, &tg
, sizeof(GAME
));
1149 g
->hp
= rav
[ravindex
].hp
;
1153 * The end of a RAV. This makes read_file() that called this function
1154 * rav_text() return (see above).
1156 else if (which
== ')')
1164 * See pgn_board_init_fen(). Returns E_PGN_PARSE on parse error. 0 may be
1165 * returned on success when there is no move count in the FEN tag otherwise
1166 * the move count is returned.
1168 static int parse_fen_line(BOARD b
, unsigned *flags
, char *turn
, char *ply
,
1172 char line
[LINE_MAX
], *s
;
1173 int row
= 8, col
= 1;
1176 strncpy(line
, str
, sizeof(line
));
1178 pgn_reset_enpassant(b
);
1180 while ((tmp
= strsep(&s
, "/")) != NULL
) {
1183 if (!VALIDFILE(row
))
1190 if (isdigit(*tmp
)) {
1196 for (; n
; --n
, col
++)
1197 b
[ROWTOBOARD(row
)][COLTOBOARD(col
)].icon
=
1198 pgn_int_to_piece(WHITE
, OPEN_SQUARE
);
1200 else if (pgn_piece_to_int(*tmp
) != -1)
1201 b
[ROWTOBOARD(row
)][COLTOBOARD(col
++)].icon
= *tmp
;
1228 while (*tmp
&& *tmp
!= ' ') {
1231 SET_FLAG(*flags
, GF_WK_CASTLE
);
1234 SET_FLAG(*flags
, GF_WQ_CASTLE
);
1237 SET_FLAG(*flags
, GF_BK_CASTLE
);
1240 SET_FLAG(*flags
, GF_BQ_CASTLE
);
1253 if (!VALIDCOL(*tmp
))
1258 if (!VALIDROW(*tmp
))
1261 row
= 8 - atoi(tmp
++);
1262 b
[row
][col
].enpassant
= 1;
1263 SET_FLAG(*flags
, GF_ENPASSANT
);
1274 while (*tmp
&& isdigit(*tmp
))
1285 * It initializes the board (b) to the FEN tag (if found) and sets the
1286 * castling and enpassant info for the game 'g'. If 'fen' is set it should be
1287 * a fen tag and will be parsed rather than the game 'g'.tag FEN tag. Returns
1288 * E_PGN_OK on success or if there was both a FEN and SetUp tag with the SetUp
1289 * tag set to 0. Returns E_PGN_PARSE if there was a FEN parse error, E_PGN_ERR
1290 * if there was no FEN tag or there was a SetUp tag with a value of 0. Returns
1291 * E_PGN_OK on success.
1293 int pgn_board_init_fen(GAME
*g
, BOARD b
, char *fen
)
1298 char turn
= g
->turn
;
1301 pgn_board_init(tmpboard
);
1304 n
= pgn_tag_find(g
->tag
, "Setup");
1305 i
= pgn_tag_find(g
->tag
, "FEN");
1309 * If the FEN tag exists and there is no SetUp tag go ahead and parse it.
1310 * If there is a SetUp tag only parse the FEN tag if the value is 1.
1312 if ((n
>= 0 && i
>= 0 && atoi(g
->tag
[n
]->value
) == 1)
1313 || (i
>= 0 && n
== -1) || fen
) {
1314 if ((n
= parse_fen_line(tmpboard
, &flags
, &turn
, &ply
,
1315 (fen
) ? fen
: g
->tag
[i
]->value
)) != E_PGN_OK
)
1318 memcpy(b
, tmpboard
, sizeof(BOARD
));
1319 CLEAR_FLAG(g
->flags
, GF_WK_CASTLE
);
1320 CLEAR_FLAG(g
->flags
, GF_WQ_CASTLE
);
1321 CLEAR_FLAG(g
->flags
, GF_BK_CASTLE
);
1322 CLEAR_FLAG(g
->flags
, GF_BQ_CASTLE
);
1329 return (i
>= 0 && n
>= 0) ? E_PGN_OK
: E_PGN_ERR
;
1335 * Allocates a new game and increments 'gtotal'. 'gindex' is then set to the
1336 * new game. Returns E_PGN_ERR if there was a memory allocation error or
1337 * E_PGN_OK on success.
1343 gindex
= ++gtotal
- 1;
1345 if ((g
= realloc(game
, gtotal
* sizeof(GAME
))) == NULL
) {
1351 memset(&game
[gindex
], 0, sizeof(GAME
));
1353 if ((game
[gindex
].hp
= calloc(1, sizeof(HISTORY
*))) == NULL
) {
1358 game
[gindex
].hp
[0] = NULL
;
1359 game
[gindex
].history
= game
[gindex
].hp
;
1360 game
[gindex
].side
= game
[gindex
].turn
= WHITE
;
1361 SET_FLAG(game
[gindex
].flags
, GF_WK_CASTLE
|GF_WQ_CASTLE
|GF_WQ_CASTLE
|
1362 GF_BK_CASTLE
|GF_BQ_CASTLE
);
1363 pgn_board_init(pgn_board
);
1364 set_default_tags(&game
[gindex
]);
1366 if (pgn_isfile
&& !(gtotal
% 50))
1367 kill(getpid(), SIGUSR1
);
1372 static int read_file(FILE *fp
)
1375 char buf
[LINE_MAX
] = {0}, *p
= buf
;
1378 int parse_error
= 0;
1386 if ((c
= fgetc(fp
)) == EOF
) {
1404 nextchar
= fgetc(fp
);
1405 ungetc(nextchar
, fp
);
1408 * If there was a move text parsing error, keep reading until the end
1409 * of the current game discarding the data.
1412 pgn_ret
= E_PGN_PARSE
;
1417 if (pgn_config
.stop
)
1420 if (c
== '\n' && (nextchar
== '\n' || nextchar
== '\015')) {
1429 // New game reached.
1430 if (c
== '\n' && (nextchar
== '\n' || nextchar
== '\015')) {
1440 * PGN: Application comment. The '%' must be on the first column of
1441 * the line. The comment continues until the end of the current line.
1444 if (lastchar
== '\n' || lastchar
== 0) {
1445 while ((c
= fgetc(fp
)) != EOF
&& c
!= '\n');
1449 // Not sure what to do here.
1456 if (c
== '<' || c
== '>')
1460 * PGN: Recurrsive Annotation Variation. Read rav_text() for more
1463 if (c
== '(' || c
== ')') {
1464 switch (rav_text(&game
[gindex
], fp
, c
, old
)) {
1467 * This is the end of the current RAV. This function has
1468 * been called from rav_text(). Returning from this point
1469 * will put us back in rav_text().
1475 * We're back at the root move. Continue as normal.
1483 * Continue processing. Probably the root move.
1491 // PGN: Numeric Annotation Glyph.
1492 if (c
== '$' || c
== '!' || c
== '?' || c
== '+' || c
== '-' ||
1493 c
== '~' || c
== '=') {
1495 nag_text(&game
[gindex
], fp
);
1500 * PGN: Annotation. The ';' comment continues until the end of the
1501 * current line. The '{' type comment continues until a '}' is
1504 if (c
== '{' || c
== ';') {
1505 annotation_text(&game
[gindex
], fp
, (c
== '{') ? '}' : '\n');
1511 // First roster tag found. Initialize the data structures.
1516 if (gtotal
&& pgn_history_total(game
[gindex
].hp
))
1517 game
[gindex
].hindex
= pgn_history_total(game
[gindex
].hp
) - 1;
1519 if (pgn_new_game() != E_PGN_OK
) {
1520 pgn_ret
= E_PGN_ERR
;
1524 memcpy(old
, pgn_board
, sizeof(BOARD
));
1527 if (tag_text(&game
[gindex
], fp
))
1528 parse_error
= 1; // FEN tag parse error.
1533 // PGN: End-of-game markers.
1534 if ((isdigit(c
) && (nextchar
== '-' || nextchar
== '/')) || c
== '*') {
1536 eog_text(&game
[gindex
], fp
);
1540 if (!done_fen_tag
) {
1541 if (pgn_tag_find(game
[gindex
].tag
, "FEN") != -1 &&
1542 pgn_board_init_fen(&game
[gindex
], pgn_board
, NULL
)) {
1554 if ((isdigit(c
) && c
!= '0') || VALIDCOL(c
) || c
== 'N' || c
== 'K'
1555 || c
== 'Q' || c
== 'B' || c
== 'R' || c
== 'P' ||
1559 // PGN: If a FEN tag exists, initialize the board to the value.
1561 if (pgn_tag_find(game
[gindex
].tag
, "FEN") != E_PGN_ERR
&&
1562 (n
= pgn_board_init_fen(&game
[gindex
], pgn_board
,
1563 NULL
)) == E_PGN_PARSE
) {
1573 * PGN: Import format doesn't require a roster tag section. We've
1574 * arrived to the move text section without any tags so we
1575 * initialize a new game which set's the default tags and any tags
1576 * from the configuration file.
1580 game
[gindex
].hindex
= pgn_history_total(game
[gindex
].hp
) - 1;
1582 if (pgn_new_game() != E_PGN_OK
) {
1583 pgn_ret
= E_PGN_ERR
;
1587 memcpy(old
, pgn_board
, sizeof(BOARD
));
1591 memcpy(old
, pgn_board
, sizeof(BOARD
));
1593 if (move_text(&game
[gindex
], fp
)) {
1594 SET_FLAG(game
[gindex
].flags
, GF_PERROR
);
1604 DUMP("unparsed: '%s'\n", buf
);
1606 if (strlen(buf
) + 1 == sizeof(buf
))
1607 bzero(buf
, sizeof(buf
));
1617 * Parses a file whose file pointer is 'fp'. 'fp' may have been returned by
1618 * pgn_open(). If 'fp' is NULL then a single empty game will be allocated. If
1619 * there is a parsing error E_PGN_PARSE is returned, if there was a memory
1620 * allocation error E_PGN_ERR is returned, otherwise E_PGN_OK is returned and
1621 * the global 'gindex' is set to the last parsed game in the file and the
1622 * global 'gtotal' is set to the total number of games in the file. The file
1623 * will be closed when the parsing is done.
1625 int pgn_parse(FILE *fp
)
1631 pgn_ret
= pgn_new_game();
1638 pgn_ret
= read_file(fp
);
1648 gtotal
= gindex
+ 1;
1650 for (i
= 0; i
< gtotal
; i
++) {
1651 game
[i
].history
= game
[i
].hp
;
1652 game
[i
].hindex
= pgn_history_total(game
[i
].hp
);
1659 * Escape '"' and '\' in tag values.
1661 static char *pgn_tag_add_escapes(const char *str
)
1664 int len
= strlen(str
);
1665 static char buf
[MAX_PGN_LINE_LEN
] = {0};
1667 for (i
= n
= 0; i
< len
; i
++, n
++) {
1684 static void Fputc(int c
, FILE *fp
, int *len
)
1688 if (c
!= '\n' && i
+ 1 > 80)
1689 Fputc('\n', fp
, &i
);
1691 if (pgn_lastc
== '\n' && c
== ' ') {
1696 if (fputc(c
, fp
) == EOF
)
1709 static void putstring(FILE *fp
, char *str
, int *len
)
1713 for (p
= str
; *p
; p
++) {
1716 while (*p
&& *p
!= ' ')
1720 Fputc('\n', fp
, len
);
1728 * See pgn_write() for more info.
1730 static void write_comments_and_nag(FILE *fp
, HISTORY
*h
, int *len
)
1735 for (i
= 0; i
< MAX_PGN_NAG
; i
++) {
1737 Fputc(' ', fp
, len
);
1738 Fputc('$', fp
, len
);
1739 putstring(fp
, itoa(h
->nag
[i
]), len
);
1744 if (strlen(h
->comment
) + *len
+ 1 > 80) {
1746 putstring(fp
, " {", len
);
1749 putstring(fp
, " ;", len
);
1751 putstring(fp
, h
->comment
, len
);
1754 putstring(fp
, "}", len
);
1756 Fputc('\n', fp
, len
);
1760 static void write_move_text(FILE *fp
, HISTORY
*h
, int *len
)
1762 Fputc(' ', fp
, len
);
1763 putstring(fp
, h
->move
, len
);
1765 if (!pgn_config
.reduced
)
1766 write_comments_and_nag(fp
, h
, len
);
1769 static void write_all_move_text(FILE *fp
, HISTORY
**h
, int m
, int *len
)
1772 HISTORY
**hp
= NULL
;
1774 for (i
= 0; h
[i
]; i
++) {
1775 if (pgn_write_turn
== WHITE
) {
1776 if (pgn_config
.mpl
&& pgn_mpl
== pgn_config
.mpl
) {
1778 Fputc('\n', fp
, len
);
1782 Fputc(' ', fp
, len
);
1784 if (strlen(itoa(m
)) + 1 + *len
> 80)
1785 Fputc('\n', fp
, len
);
1787 putstring(fp
, itoa(m
), len
);
1788 Fputc('.', fp
, len
);
1792 write_move_text(fp
, h
[i
], len
);
1794 if (!pgn_config
.reduced
&& h
[i
]->rav
) {
1796 int oldturn
= pgn_write_turn
;
1799 putstring(fp
, " (", len
);
1802 * If it's WHITE's turn the move number will be added above after
1803 * the call to write_all_move_text() below.
1805 if (pgn_write_turn
== BLACK
) {
1806 putstring(fp
, itoa(m
), len
);
1807 putstring(fp
, "...", len
);
1811 write_all_move_text(fp
, hp
, m
, len
);
1813 pgn_write_turn
= oldturn
;
1814 putstring(fp
, ")", len
);
1817 if (h
[i
+ 1] && !ravlevel
)
1818 Fputc(' ', fp
, len
);
1820 if (pgn_write_turn
== WHITE
&& h
[i
+ 1]) {
1821 putstring(fp
, itoa(m
), len
);
1822 putstring(fp
, "...", len
);
1826 if (pgn_write_turn
== BLACK
)
1829 pgn_write_turn
= (pgn_write_turn
== WHITE
) ? BLACK
: WHITE
;
1833 #ifdef WITH_COMPRESSED
1834 static char *compression_cmd(const char *filename
, int expand
)
1836 static char command
[FILENAME_MAX
];
1837 int len
= strlen(filename
);
1839 if (filename
[len
- 4] == '.' && filename
[len
- 3] == 'z' &&
1840 filename
[len
- 2] == 'i' && filename
[len
- 1] == 'p' &&
1841 filename
[len
] == '\0') {
1843 snprintf(command
, sizeof(command
), "unzip -p %s 2>/dev/null",
1846 snprintf(command
, sizeof(command
), "zip -9 >%s 2>/dev/null",
1851 else if (filename
[len
- 3] == '.' && filename
[len
- 2] == 'g' &&
1852 filename
[len
- 1] == 'z' && filename
[len
] == '\0') {
1854 snprintf(command
, sizeof(command
), "gzip -dc %s", filename
);
1856 snprintf(command
, sizeof(command
), "gzip -c 1>%s", filename
);
1860 else if (filename
[len
- 2] == '.' && filename
[len
- 1] == 'Z' &&
1861 filename
[len
] == '\0') {
1863 snprintf(command
, sizeof(command
), "uncompress -c %s", filename
);
1865 snprintf(command
, sizeof(command
), "compress -c 1>%s", filename
);
1869 else if ((filename
[len
- 4] == '.' && filename
[len
- 3] == 'b' &&
1870 filename
[len
- 2] == 'z' && filename
[len
- 1] == '2' &&
1871 filename
[len
] == '\0') || (filename
[len
- 3] == '.' &&
1872 filename
[len
- 2] == 'b' && filename
[len
- 1] == 'z' &&
1873 filename
[len
] == '\0')) {
1875 snprintf(command
, sizeof(command
), "bzip2 -dc %s", filename
);
1877 snprintf(command
, sizeof(command
), "bzip2 -zc 1>%s", filename
);
1887 * Returns a file pointer associated with 'filename' or NULL on error with
1888 * errno set to indicate the error. If compressed file support was enabled at
1889 * compile time and the filetype is supported and the utility is installed
1890 * then the file will be decompressed.
1892 FILE *pgn_open(const char *filename
)
1895 #ifdef WITH_COMPRESSED
1899 char *command
= NULL
;
1902 if (access(filename
, R_OK
) == -1)
1905 #ifdef WITH_COMPRESSED
1906 if ((command
= compression_cmd(filename
, 1)) != NULL
) {
1907 if ((tfp
= tmpfile()) == NULL
)
1910 if ((fp
= popen(command
, "r")) == NULL
)
1913 while ((p
= fgets(buf
, sizeof(buf
), fp
)) != NULL
)
1914 fprintf(tfp
, "%s", p
);
1920 fseek(tfp
, 0, SEEK_SET
);
1922 if ((tfp
= fopen(filename
, "r")) == NULL
)
1928 if ((fp
= fopen(filename
, "r")) == NULL
)
1936 * Returns E_PGN_OK if 'filename' is a recognized compressed filetype or
1939 int pgn_is_compressed(const char *filename
)
1941 #ifdef WITH_COMPRESSED
1942 if (compression_cmd(filename
, 0))
1950 * Sets config flag 'f' to 'val'. Returns E_PGN_OK on success or E_PGN_ERR if
1951 * 'f' is an invalid flag or 'val' is an invalid value.
1953 int pgn_config_set(pgn_config_flag f
, int val
)
1961 pgn_config
.reduced
= 1;
1963 pgn_config
.reduced
= 0;
1968 pgn_config
.mpl
= val
;
1970 case PGN_STOP_ON_ERROR
:
1972 pgn_config
.stop
= 1;
1974 pgn_config
.stop
= 0;
1986 * Returns the value accociated with 'f' or E_PGN_ERR if 'f' is invalid.
1988 int pgn_config_get(pgn_config_flag f
)
1992 return pgn_config
.reduced
;
1993 case PGN_STOP_ON_ERROR
:
1994 return pgn_config
.stop
;
1996 return pgn_config
.mpl
;
2005 * Writes a PGN formatted game 'g' to the file pointed to by 'fp'. See
2006 * 'pgn_config_flag' for output options. Returns E_PGN_ERR if there was a
2007 * memory allocation or write error and E_PGN_OK on success.
2009 int pgn_write(FILE *fp
, GAME g
)
2014 pgn_write_turn
= (TEST_FLAG(g
.flags
, GF_BLACK_OPENING
)) ? BLACK
: WHITE
;
2015 pgn_tag_sort(g
.tag
);
2017 for (i
= 0; g
.tag
[i
]; i
++) {
2019 char tbuf
[11] = {0};
2022 if (pgn_config
.reduced
&& i
== 7)
2025 if (strcmp(g
.tag
[i
]->name
, "Date") == 0) {
2026 if (strptime(g
.tag
[i
]->value
, PGN_TIME_FORMAT
, &tp
) != NULL
) {
2027 len
= strftime(tbuf
, sizeof(tbuf
), PGN_TIME_FORMAT
, &tp
) + 1;
2029 if ((tmp
= strdup(tbuf
)) == NULL
)
2032 free(g
.tag
[i
]->value
);
2033 g
.tag
[i
]->value
= tmp
;
2036 else if (strcmp(g
.tag
[i
]->name
, "Event") == 0) {
2037 if (g
.tag
[i
]->value
[0] == '\0') {
2038 if ((tmp
= strdup("?")) == NULL
)
2041 free(g
.tag
[i
]->value
);
2042 g
.tag
[i
]->value
= tmp
;
2045 else if (strcmp(g
.tag
[i
]->name
, "Site") == 0) {
2046 if (g
.tag
[i
]->value
[0] == '\0') {
2047 if ((tmp
= strdup("?")) == NULL
)
2050 free(g
.tag
[i
]->value
);
2051 g
.tag
[i
]->value
= tmp
;
2054 else if (strcmp(g
.tag
[i
]->name
, "Round") == 0) {
2055 if (g
.tag
[i
]->value
[0] == '\0') {
2056 if ((tmp
= strdup("?")) == NULL
)
2059 free(g
.tag
[i
]->value
);
2060 g
.tag
[i
]->value
= tmp
;
2063 else if (strcmp(g
.tag
[i
]->name
, "Result") == 0) {
2064 if (g
.tag
[i
]->value
[0] == '\0') {
2065 if ((tmp
= strdup("*")) == NULL
)
2068 free(g
.tag
[i
]->value
);
2069 g
.tag
[i
]->value
= tmp
;
2072 else if (strcmp(g
.tag
[i
]->name
, "Black") == 0) {
2073 if (g
.tag
[i
]->value
[0] == '\0') {
2074 if ((tmp
= strdup("?")) == NULL
)
2077 free(g
.tag
[i
]->value
);
2078 g
.tag
[i
]->value
= tmp
;
2081 else if (strcmp(g
.tag
[i
]->name
, "White") == 0) {
2082 if (g
.tag
[i
]->value
[0] == '\0') {
2083 if ((tmp
= strdup("?")) == NULL
)
2086 free(g
.tag
[i
]->value
);
2087 g
.tag
[i
]->value
= tmp
;
2091 fprintf(fp
, "[%s \"%s\"]\n", g
.tag
[i
]->name
,
2092 (g
.tag
[i
]->value
&& g
.tag
[i
]->value
[0]) ?
2093 pgn_tag_add_escapes(g
.tag
[i
]->value
) : "");
2096 Fputc('\n', fp
, &len
);
2100 if (pgn_history_total(g
.hp
) && pgn_write_turn
== BLACK
)
2101 putstring(fp
, "1...", &len
);
2103 write_all_move_text(fp
, g
.hp
, 1, &len
);
2105 Fputc(' ', fp
, &len
);
2106 putstring(fp
, g
.tag
[pgn_tag_find(g
.tag
, "Result")]->value
, &len
);
2107 putstring(fp
, "\n\n", &len
);
2109 if (!pgn_config
.reduced
) {
2110 CLEAR_FLAG(g
.flags
, GF_MODIFIED
);
2111 CLEAR_FLAG(g
.flags
, GF_PERROR
);
2118 * Clears the enpassant flag for all positions on board 'b'. Returns nothing.
2120 void pgn_reset_enpassant(BOARD b
)
2124 for (r
= 0; r
< 8; r
++) {
2125 for (c
= 0; c
< 8; c
++)
2126 b
[r
][c
].enpassant
= 0;