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 static void *Malloc(size_t size
)
59 if ((ptr
= malloc(size
)) == NULL
)
60 err(EXIT_FAILURE
, "malloc()");
65 static void *Realloc(void *ptr
, size_t size
)
69 if ((ptr2
= realloc(ptr
, size
)) == NULL
)
70 err(EXIT_FAILURE
, "realloc()");
75 static void *Calloc(size_t n
, size_t size
)
79 if ((p
= calloc(n
, size
)) == NULL
)
80 err(EXIT_FAILURE
, "calloc()");
85 static char *trim(char *str
)
95 for (i
= strlen(str
) - 1; isspace(str
[i
]); i
--)
101 static char *itoa(long n
)
105 snprintf(buf
, sizeof(buf
), "%li", n
);
109 void pgn_reset_valid_moves(BOARD b
)
113 for (row
= 0; row
< 8; row
++) {
114 for (col
= 0; col
< 8; col
++)
115 b
[row
][col
].valid
= 0;
119 void pgn_get_valid_moves(GAME
*g
, BOARD b
, int r
, int f
)
122 int p
= pgn_piece_to_int(b
[ROWTOBOARD(r
)][COLTOBOARD(f
)].icon
);
125 * Don't update board 'b'. Only check for valid moves.
129 for (row
= 1; VALIDFILE(row
); row
++) {
130 for (col
= 1; VALIDFILE(col
); col
++) {
133 if (get_source_yx(g
, b
, p
, row
, col
, &sr
, &sc
)) {
137 if (get_source_yx(g
, b
, p
, row
, col
, &sr
, &sc
)) {
141 if (get_source_yx(g
, b
, p
, row
, col
, &sr
, &sc
)) {
147 if (sr
!= r
|| sc
!= f
)
150 b
[ROWTOBOARD(row
)][COLTOBOARD(col
)].valid
= 1;
157 void pgn_switch_turn(GAME
*g
)
159 g
->turn
= (g
->turn
== WHITE
) ? BLACK
: WHITE
;
163 * Creates a FEN tag from the current game 'g' and board 'b'. Returns a FEN
166 char *pgn_game_to_fen(GAME g
, BOARD b
)
170 static char buf
[MAX_PGN_LINE_LEN
], *p
;
171 int oldturn
= g
.turn
;
172 char enpassant
[3] = {0}, *e
;
175 for (i
= pgn_history_total(g
.hp
); i
>= g
.hindex
- 1; i
--)
180 for (row
= 0; row
< 8; row
++) {
183 for (col
= 0; col
< 8; col
++) {
184 if (b
[row
][col
].enpassant
) {
185 b
[row
][col
].icon
= pgn_int_to_piece(WHITE
, OPEN_SQUARE
);
188 *e
++ = ('0' + 8) - row
;
192 if (pgn_piece_to_int(b
[row
][col
].icon
) == OPEN_SQUARE
) {
202 *p
++ = b
[row
][col
].icon
;
216 *p
++ = (g
.turn
== WHITE
) ? 'w' : 'b';
219 if (TEST_FLAG(g
.flags
, GF_WK_CASTLE
) && pgn_piece_to_int(b
[7][7].icon
) ==
220 ROOK
&& isupper(b
[7][7].icon
) && pgn_piece_to_int(b
[7][4].icon
) ==
221 KING
&& isupper(b
[7][4].icon
)) {
226 if (TEST_FLAG(g
.flags
, GF_WQ_CASTLE
) && pgn_piece_to_int(b
[7][0].icon
) ==
227 ROOK
&& isupper(b
[7][0].icon
) && pgn_piece_to_int(b
[7][4].icon
) ==
228 KING
&& isupper(b
[7][4].icon
)) {
233 if (TEST_FLAG(g
.flags
, GF_BK_CASTLE
) && pgn_piece_to_int(b
[0][7].icon
) ==
234 ROOK
&& islower(b
[0][7].icon
) && pgn_piece_to_int(b
[0][4].icon
) ==
235 KING
&& islower(b
[0][4].icon
)) {
240 if (TEST_FLAG(g
.flags
, GF_BQ_CASTLE
) && pgn_piece_to_int(b
[0][0].icon
) ==
241 ROOK
&& islower(b
[0][0].icon
) && pgn_piece_to_int(b
[0][4].icon
) ==
242 KING
&& islower(b
[0][4].icon
)) {
264 strcat(p
, itoa(g
.ply
));
265 p
= buf
+ strlen(buf
);
269 i
= (g
.hindex
+ 1) / 2;
271 strcat(p
, itoa((g
.hindex
/ 2) + (g
.hindex
% 2)));
278 * Returns the total number of moves in 'h' or 0 if none.
280 int pgn_history_total(HISTORY
**h
)
287 for (i
= 0; h
[i
]; i
++);
292 * Deallocates the all the data for 'h' from position 'start' in the array.
294 void pgn_history_free(HISTORY
**h
, int start
)
298 if (!h
|| start
> pgn_history_total(h
))
304 for (i
= start
; h
[i
]; i
++) {
309 pgn_history_free(h
[i
]->rav
, 0);
321 * Returns the history ply 'n' from 'h'. If 'n' is out of range then NULL is
324 HISTORY
*pgn_history_by_n(HISTORY
**h
, int n
)
326 if (n
< 0 || n
> pgn_history_total(h
) - 1)
333 * Appends move 'm' to game 'g' history pointer. The history pointer may be a
334 * in a RAV so g->rav.hp is also updated to the new (realloc()'ed) pointer. If
335 * not in a RAV then g->history will be updated. Returns 1 if realloc() failed
338 int pgn_history_add(GAME
*g
, const char *m
)
340 int t
= pgn_history_total(g
->hp
);
345 o
= g
->rav
[g
->ravlevel
].hp
- g
->hp
;
347 o
= g
->history
- g
->hp
;
349 if ((h
= realloc(g
->hp
, (t
+ 2) * sizeof(HISTORY
*))) == NULL
)
355 g
->rav
[g
->ravlevel
].hp
= g
->hp
+ o
;
357 g
->history
= g
->hp
+ o
;
359 if ((g
->hp
[t
] = calloc(1, sizeof(HISTORY
))) == NULL
)
362 g
->hp
[t
++]->move
= strdup(m
);
364 g
->hindex
= pgn_history_total(g
->hp
);
369 * Resets the game 'g' using board 'b' up to history move 'n'.
371 int pgn_board_update(GAME
*g
, BOARD b
, int n
)
377 if (TEST_FLAG(g
->flags
, GF_BLACK_OPENING
))
383 if (TEST_FLAG(g
->flags
, GF_PERROR
))
384 SET_FLAG(flags
, GF_PERROR
);
386 if (TEST_FLAG(g
->flags
, GF_MODIFIED
))
387 SET_FLAG(flags
, GF_MODIFIED
);
389 if (TEST_FLAG(g
->flags
, GF_DELETE
))
390 SET_FLAG(flags
, GF_DELETE
);
392 if (TEST_FLAG(g
->flags
, GF_GAMEOVER
))
393 SET_FLAG(flags
, GF_GAMEOVER
);
398 SET_FLAG(game
[gindex
].flags
, GF_WK_CASTLE
|GF_WQ_CASTLE
|GF_WQ_CASTLE
|
399 GF_BK_CASTLE
|GF_BQ_CASTLE
);
402 if (pgn_tag_find(g
->tag
, "FEN") != -1 &&
403 pgn_board_init_fen(g
, tb
, NULL
))
406 for (i
= 0; i
< n
; i
++) {
410 if ((h
= pgn_history_by_n(g
->hp
, i
)) == NULL
)
415 if ((ret
= pgn_validate_move(g
, tb
, &p
)) != E_PGN_OK
)
422 memcpy(b
, tb
, sizeof(BOARD
));
428 * Updates the game 'g' using board 'b' to the next 'n'th history move. The
429 * 's' parameter is either 2 for a wholestep or 1 for a halfstep.
431 void pgn_history_prev(GAME
*g
, BOARD b
, int n
)
433 if (g
->hindex
- n
< 0) {
435 g
->hindex
= pgn_history_total(g
->hp
);
442 pgn_board_update(g
, b
, g
->hindex
);
446 * Updates the game 'g' using board 'b' to the previous 'n'th history move.
447 * 's' parameter is either 2 for a wholestep or 1 for a halfstep.
449 void pgn_history_next(GAME
*g
, BOARD b
, int n
)
451 if (g
->hindex
+ n
> pgn_history_total(g
->hp
)) {
455 g
->hindex
= pgn_history_total(g
->hp
);
460 pgn_board_update(g
, b
, g
->hindex
);
463 * Converts the character piece 'p' to an integer.
465 int pgn_piece_to_int(int p
)
493 * Converts the integer piece 'n' to a character.
495 int pgn_int_to_piece(char turn
, int n
)
526 return (turn
== WHITE
) ? toupper(p
) : p
;
530 * Finds a tag 'name' in the structure array 't'. Returns the location in the
531 * array of the found tag or -1 on failure.
533 int pgn_tag_find(TAG
**t
, const char *name
)
537 for (i
= 0; t
[i
]; i
++) {
538 if (strcasecmp(t
[i
]->name
, name
) == 0)
545 static int tag_compare(const void *a
, const void *b
)
550 return strcmp((*ta
)->name
, (*tb
)->name
);
554 * Sorts a tag array. The first seven tags are in order of the PGN standard so
557 void pgn_tag_sort(TAG
**tags
)
559 if (pgn_tag_total(tags
) <= 7)
562 qsort(tags
+ 7, pgn_tag_total(tags
) - 7, sizeof(TAG
*), tag_compare
);
565 int pgn_tag_total(TAG
**tags
)
579 * Adds a tag 'name' with value 'value' to the pointer to array 'dst'. The 'n'
580 * parameter is incremented to the new total of array 'dst'. If a duplicate
581 * tag 'name' was found then the existing tag is updated to the new 'value'.
582 * Returns 1 if a duplicate tag was found or 0 otherwise.
584 int pgn_tag_add(TAG
***dst
, char *name
, char *value
)
589 int t
= pgn_tag_total(tdata
);
594 // Find an existing tag with 'name'.
595 for (i
= 0; i
< t
; i
++) {
596 if (strcasecmp(tdata
[i
]->name
, name
) == 0) {
597 len
= (value
) ? strlen(value
) + 1 : 1;
598 tdata
[i
]->value
= Realloc(tdata
[i
]->value
, len
);
599 strncpy(tdata
[i
]->value
, (value
) ? value
: "", len
);
605 tdata
= Realloc(tdata
, (t
+ 2) * sizeof(TAG
*));
606 tdata
[t
] = Malloc(sizeof(TAG
));
607 len
= strlen(name
) + 1;
608 tdata
[t
]->name
= Malloc(len
);
609 strncpy(tdata
[t
]->name
, name
, len
);
612 len
= strlen(value
) + 1;
613 tdata
[t
]->value
= Malloc(len
);
614 strncpy(tdata
[t
]->value
, value
, len
);
617 tdata
[t
]->value
= NULL
;
624 static char *remove_tag_escapes(const char *str
)
627 int len
= strlen(str
);
628 static char buf
[MAX_PGN_LINE_LEN
] = {0};
630 for (i
= n
= 0; i
< len
; i
++, n
++) {
646 * Initializes a new game board.
648 void pgn_board_init(BOARD b
)
652 memset(b
, 0, sizeof(BOARD
));
654 for (row
= 0; row
< 8; row
++) {
655 for (col
= 0; col
< 8; col
++) {
688 b
[row
][col
].icon
= (row
< 2) ? c
: toupper(c
);
694 * Adds the standard PGN roster tags to game 'g'.
696 static void set_default_tags(GAME
*g
)
701 struct passwd
*pw
= getpwuid(getuid());
704 tp
= localtime(&now
);
705 strftime(tbuf
, sizeof(tbuf
), PGN_TIME_FORMAT
, tp
);
707 /* The standard seven tag roster (in order of appearance). */
708 pgn_tag_add(&g
->tag
, "Event", "?");
709 pgn_tag_add(&g
->tag
, "Site", "?");
710 pgn_tag_add(&g
->tag
, "Date", tbuf
);
711 pgn_tag_add(&g
->tag
, "Round", "-");
712 pgn_tag_add(&g
->tag
, "White", pw
->pw_gecos
);
713 pgn_tag_add(&g
->tag
, "Black", "?");
714 pgn_tag_add(&g
->tag
, "Result", "*");
717 void pgn_tag_free(TAG
**tags
)
724 for (i
= 0; tags
[i
]; i
++) {
726 free(tags
[i
]->value
);
733 void pgn_free(GAME g
)
735 pgn_history_free(g
.history
, 0);
738 memset(&g
, 0, sizeof(GAME
));
745 for (i
= 0; i
< gtotal
; i
++) {
749 for (game
[i
].ravlevel
--; game
[i
].ravlevel
>= 0; game
[i
].ravlevel
--)
750 free(game
[i
].rav
[game
[i
].ravlevel
].fen
);
761 static void reset_game_data()
768 static void skip_leading_space(FILE *fp
)
772 while ((c
= fgetc(fp
)) != EOF
&& !feof(fp
)) {
781 * PGN move text section.
783 static int move_text(GAME
*g
, FILE *fp
)
785 char m
[MAX_SAN_MOVE_LEN
+ 1] = {0}, *p
;
791 while((c
= fgetc(fp
)) != EOF
) {
812 if (g
->hindex
== 0 && g
->ravlevel
== 0)
813 SET_FLAG(g
->flags
, GF_BLACK_OPENING
);
819 CLEAR_FLAG(g
->flags
, GF_BLACK_OPENING
);
825 if (fscanf(fp
, " %[a-hPRNBQK1-9#+=Ox-]%n", m
, &count
) != 1)
828 p
= m
+ strlen(m
) - 1;
830 if (!pgn_history_total(g
->hp
) && g
->ravlevel
== 0 && VALIDRANK(ROWTOINT(*p
)) &&
831 VALIDFILE(COLTOINT(*(p
-1))) && ROWTOINT(*p
) > 4) {
833 SET_FLAG(g
->flags
, GF_BLACK_OPENING
);
838 if (pgn_validate_move(g
, g
->b
, &p
)) {
848 pgn_history_add(g
, p
);
856 static void nag_text(GAME
*g
, FILE *fp
)
859 char nags
[5], *n
= nags
;
862 while ((c
= fgetc(fp
)) != EOF
&& !isspace(c
)) {
864 while ((c
= fgetc(fp
)) != EOF
&& isdigit(c
))
871 if ((c
= fgetc(fp
)) == '!')
883 if ((c
= fgetc(fp
)) == '?')
897 if ((c
= fgetc(fp
)) == '+')
907 if ((t
= fgetc(fp
)) == '=')
912 if ((i
= fgetc(fp
)) == '-')
925 if ((t
= fgetc(fp
)) == '+')
928 if ((i
= fgetc(fp
)) == '+')
945 nag
= (nags
[0]) ? atoi(nags
) : 0;
947 if (!nag
|| nag
< 0 || nag
> 255)
950 // FIXME -1 is because move_text() increments g.hindex. The NAG
951 // annoatation isnt guaranteed to be after the move text in import format.
952 for (i
= 0; i
< MAX_PGN_NAG
; i
++) {
953 if (g
->hp
[g
->hindex
- 1]->nag
[i
])
956 g
->hp
[g
->hindex
- 1]->nag
[i
] = nag
;
960 skip_leading_space(fp
);
964 * PGN move annotation.
966 static void annotation_text(GAME
*g
, FILE *fp
, int terminator
)
970 int hindex
= pgn_history_total(g
->hp
) - 1;
971 char buf
[MAX_PGN_LINE_LEN
], *a
= buf
;
973 skip_leading_space(fp
);
975 while ((c
= fgetc(fp
)) != EOF
&& c
!= terminator
) {
979 if (isspace(c
) && isspace(lastchar
))
982 if (len
+ 1 == sizeof(buf
))
990 g
->hp
[hindex
]->comment
= Realloc(g
->hp
[hindex
]->comment
, ++len
);
991 strncpy(g
->hp
[hindex
]->comment
, buf
, len
);
997 static int tag_text(GAME
*g
, FILE *fp
)
999 char name
[LINE_MAX
], *n
= name
;
1000 char value
[LINE_MAX
], *v
= value
;
1002 int quoted_string
= 0;
1005 skip_leading_space(fp
);
1007 /* The tag name is up until the first whitespace. */
1008 while ((c
= fgetc(fp
)) != EOF
&& !isspace(c
))
1012 *name
= toupper(*name
);
1013 skip_leading_space(fp
);
1015 /* The value is until the first closing bracket. */
1016 while ((c
= fgetc(fp
)) != EOF
&& c
!= ']') {
1017 if (i
++ == '\0' && c
== '\"') {
1022 if (c
== '\n' || c
== '\t')
1025 if (c
== ' ' && lastchar
== ' ')
1028 lastchar
= *v
++ = c
;
1033 while (isspace(*--v
))
1039 if (value
[0] == '\0') {
1040 if (strcmp(name
, "Result") == 0)
1048 strncpy(value
, remove_tag_escapes(value
), sizeof(value
));
1049 pgn_tag_add(&g
->tag
, name
, value
);
1054 * PGN end-of-game marker.
1056 static int eog_text(GAME
*g
, FILE *fp
)
1059 char buf
[8], *p
= buf
;
1061 while ((c
= fgetc(fp
)) != EOF
&& !isspace(c
) && i
++ < sizeof(buf
))
1068 g
->tag
[TAG_RESULT
]->value
= Realloc(g
->tag
[TAG_RESULT
]->value
, strlen(buf
) + 1);
1069 strcpy(g
->tag
[TAG_RESULT
]->value
, buf
);
1074 * Parse RAV text and keep track of g.hp. The 'o' argument is the board state
1075 * before the current move (.hindex) was parsed.
1077 static int read_file(FILE *);
1078 static int rav_text(GAME
*g
, FILE *fp
, int which
, BOARD o
)
1080 int ravindex
= ravlevel
;
1083 // Begin RAV for the current move.
1086 * Save the current game state for this RAV depth/level.
1088 rav
= Realloc(rav
, (ravindex
+ 1) * sizeof(RAV
));
1089 rav
[ravindex
].fen
= strdup(pgn_game_to_fen((*g
), g
->b
));
1090 rav
[ravindex
].hp
= g
->hp
;
1091 memcpy(&tg
, g
, sizeof(GAME
));
1092 memcpy(g
->b
, o
, sizeof(BOARD
));
1094 g
->hp
[g
->hindex
]->rav
= Calloc(1, sizeof(HISTORY
));
1096 // pgn_history_add() will now append to this new RAV.
1097 g
->hp
= g
->hp
[g
->hindex
]->rav
;
1100 * Reset. Will be restored later from 'tg' which is a local variable
1101 * so recursion is possible.
1107 * Now continue as normal as if there were no RAV. Moves will be
1108 * parsed and appended to the new .hp.
1114 * read_file() has returned. The means that a RAV has ended by this
1115 * function returning -1 (see below). So we restore the game state
1116 * that was saved before calling read_file().
1118 pgn_board_init_fen(&tg
, g
->b
, rav
[ravindex
].fen
);
1119 free(rav
[ravindex
].fen
);
1120 memcpy(g
, &tg
, sizeof(GAME
));
1121 g
->hp
= rav
[ravindex
].hp
;
1125 * The end of a RAV. This makes read_file() that called this function
1126 * rav_text() return (see above).
1128 else if (which
== ')')
1135 * See pgn_board_init_fen(). Returns -1 on parse error. 0 may be returned on
1136 * success when there is no move count in the FEN tag otherwise the move count
1139 static int parse_fen_line(BOARD b
, unsigned *flags
, char *turn
, char *ply
,
1143 char line
[LINE_MAX
], *s
;
1144 int row
= 8, col
= 1;
1147 strncpy(line
, str
, sizeof(line
));
1149 pgn_reset_enpassant(b
);
1151 while ((tmp
= strsep(&s
, "/")) != NULL
) {
1154 if (!VALIDFILE(row
))
1161 if (isdigit(*tmp
)) {
1167 for (; n
; --n
, col
++)
1168 b
[ROWTOBOARD(row
)][COLTOBOARD(col
)].icon
=
1169 pgn_int_to_piece(WHITE
, OPEN_SQUARE
);
1171 else if (pgn_piece_to_int(*tmp
) != -1)
1172 b
[ROWTOBOARD(row
)][COLTOBOARD(col
++)].icon
= *tmp
;
1199 while (*tmp
&& *tmp
!= ' ') {
1202 SET_FLAG(*flags
, GF_WK_CASTLE
);
1205 SET_FLAG(*flags
, GF_WQ_CASTLE
);
1208 SET_FLAG(*flags
, GF_BK_CASTLE
);
1211 SET_FLAG(*flags
, GF_BQ_CASTLE
);
1219 if (*++tmp
!= '-') {
1220 if (!VALIDCOL(*tmp
))
1225 if (!VALIDROW(*tmp
))
1228 row
= 8 - atoi(tmp
++);
1229 b
[row
][col
].enpassant
= 1;
1238 while (*tmp
&& isdigit(*tmp
))
1249 * This is called at the EOG marker and the beginning of the move text
1250 * section. So at least a move or EOG marker has to exist. It initializes the
1251 * board (b) to the FEN tag (if found) and sets the castling and enpassant
1252 * info for the game 'g'. If 'fen' is set it should be a fen tag and will be
1253 * parsed rather than the game 'g'.tag FEN tag. Returns 0 on success or if
1254 * there was both a FEN and SetUp tag with the SetUp tag set to 0. Returns 1
1255 * if there was a FEN parse error or no FEN tag at all.
1257 int pgn_board_init_fen(GAME
*g
, BOARD b
, char *fen
)
1262 char turn
= g
->turn
;
1265 pgn_board_init(tmpboard
);
1268 n
= pgn_tag_find(g
->tag
, "Setup");
1269 i
= pgn_tag_find(g
->tag
, "FEN");
1273 * If the FEN tag exists and there is no SetUp tag go ahead and parse it.
1274 * If there is a SetUp tag only parse the FEN tag if the value is 1.
1276 if ((n
>= 0 && i
>= 0 && atoi(g
->tag
[n
]->value
) == 1)
1277 || (i
>= 0 && n
== -1) || fen
) {
1278 if ((n
= parse_fen_line(tmpboard
, &flags
, &turn
, &ply
,
1279 (fen
) ? fen
: g
->tag
[i
]->value
)) == -1)
1282 memcpy(b
, tmpboard
, sizeof(BOARD
));
1283 CLEAR_FLAG(g
->flags
, GF_WK_CASTLE
);
1284 CLEAR_FLAG(g
->flags
, GF_WQ_CASTLE
);
1285 CLEAR_FLAG(g
->flags
, GF_BK_CASTLE
);
1286 CLEAR_FLAG(g
->flags
, GF_BQ_CASTLE
);
1293 return (i
>= 0 && n
>= 0) ? E_PGN_OK
: E_PGN_ERR
;
1299 * Allocates a new game and increments gindex (the current game) and gtotal
1300 * (the total number of games).
1304 gindex
= ++gtotal
- 1;
1305 game
= Realloc(game
, gtotal
* sizeof(GAME
));
1306 memset(&game
[gindex
], 0, sizeof(GAME
));
1307 game
[gindex
].hp
= Calloc(1, sizeof(HISTORY
*));
1308 game
[gindex
].hp
[0] = NULL
;
1309 game
[gindex
].history
= game
[gindex
].hp
;
1310 game
[gindex
].side
= game
[gindex
].turn
= WHITE
;
1311 SET_FLAG(game
[gindex
].flags
, GF_WK_CASTLE
|GF_WQ_CASTLE
|GF_WQ_CASTLE
|
1312 GF_BK_CASTLE
|GF_BQ_CASTLE
);
1313 pgn_board_init(game
[gindex
].b
);
1314 set_default_tags(&game
[gindex
]);
1316 if (pgn_isfile
&& !(gtotal
% 50))
1317 kill(getpid(), SIGUSR1
);
1320 static int read_file(FILE *fp
)
1323 char buf
[LINE_MAX
] = {0}, *p
= buf
;
1326 int parse_error
= 0;
1333 if ((c
= fgetc(fp
)) == EOF
) {
1351 nextchar
= fgetc(fp
);
1352 ungetc(nextchar
, fp
);
1355 * If there was a move text parsing error, keep reading until the end
1356 * of the current game discarding the data.
1359 pgn_ret
= E_PGN_PARSE
;
1364 if (pgn_config
.stop
)
1367 if (c
== '\n' && (nextchar
== '\n' || nextchar
== '\015')) {
1376 // New game reached.
1377 if (c
== '\n' && (nextchar
== '\n' || nextchar
== '\015')) {
1389 * PGN: Application comment. The '%' must be on the first column of
1390 * the line. The comment continues until the end of the current line.
1393 if (lastchar
== '\n' || lastchar
== 0) {
1394 while ((c
= fgetc(fp
)) != EOF
&& c
!= '\n');
1398 // Not sure what to do here.
1405 if (c
== '<' || c
== '>')
1409 * PGN: Recurrsive Annotation Variation. Read rav_text() for more
1412 if (c
== '(' || c
== ')') {
1413 switch (rav_text(&game
[gindex
], fp
, c
, old
)) {
1416 * This is the end of the current RAV. This function has
1417 * been called from rav_text(). Returning from this point
1418 * will put us back in rav_text().
1424 * We're back at the root move. Continue as normal.
1432 * Continue processing. Probably the root move.
1440 // PGN: Numeric Annotation Glyph.
1441 if (c
== '$' || c
== '!' || c
== '?' || c
== '+' || c
== '-' ||
1442 c
== '~' || c
== '=') {
1444 nag_text(&game
[gindex
], fp
);
1449 * PGN: Annotation. The ';' comment continues until the end of the
1450 * current line. The '{' type comment continues until a '}' is
1453 if (c
== '{' || c
== ';') {
1454 annotation_text(&game
[gindex
], fp
, (c
== '{') ? '}' : '\n');
1460 // First roster tag found. Initialize the data structures.
1465 if (gtotal
&& pgn_history_total(game
[gindex
].hp
))
1466 game
[gindex
].hindex
= pgn_history_total(game
[gindex
].hp
) - 1;
1469 memcpy(old
, game
[gindex
].b
, sizeof(BOARD
));
1472 if (tag_text(&game
[gindex
], fp
))
1473 parse_error
= 1; // FEN tag parse error.
1478 // PGN: End-of-game markers.
1479 if ((isdigit(c
) && (nextchar
== '-' || nextchar
== '/')) || c
== '*') {
1481 eog_text(&game
[gindex
], fp
);
1485 if (!done_fen_tag
) {
1486 if (pgn_tag_find(game
[gindex
].tag
, "FEN") != -1 &&
1487 pgn_board_init_fen(&game
[gindex
], game
[gindex
].b
,
1500 if ((isdigit(c
) && c
!= '0') || VALIDCOL(c
) || c
== 'N' || c
== 'K'
1501 || c
== 'Q' || c
== 'B' || c
== 'R' || c
== 'P' ||
1505 // PGN: If a FEN tag exists, initialize the board to the value.
1507 if (pgn_tag_find(game
[gindex
].tag
, "FEN") != -1 &&
1508 pgn_board_init_fen(&game
[gindex
], game
[gindex
].b
,
1519 * PGN: Import format doesn't require a roster tag section. We've
1520 * arrived to the move text section without any tags so we
1521 * initialize a new game which set's the default tags and any tags
1522 * from the configuration file.
1526 game
[gindex
].hindex
= pgn_history_total(game
[gindex
].hp
) - 1;
1528 pgn_new_game(game
[gindex
].b
);
1529 memcpy(old
, game
[gindex
].b
, sizeof(BOARD
));
1533 memcpy(old
, game
[gindex
].b
, sizeof(BOARD
));
1535 if (move_text(&game
[gindex
], fp
)) {
1536 SET_FLAG(game
[gindex
].flags
, GF_PERROR
);
1546 DUMP("unparsed: '%s'\n", buf
);
1548 if (strlen(buf
) + 1 == sizeof(buf
))
1549 bzero(buf
, sizeof(buf
));
1559 * Parses a file whose file pointer is 'fp'. 'fp' may have been returned by
1560 * pgn_open(). If 'fp' is NULL then a single empty game will be allocated. If
1561 * there is a parsing error 1 is returned otherwise 0 is returned and the
1562 * global 'gindex' is set to the last parsed game in the file and the global
1563 * 'gtotal' is set to the total number of games in the file. The file will be
1564 * closed when the parsing is done.
1566 int pgn_parse(FILE *fp
)
1580 pgn_ret
= read_file(fp
);
1590 gtotal
= gindex
+ 1;
1592 for (i
= 0; i
< gtotal
; i
++) {
1593 game
[i
].history
= game
[i
].hp
;
1594 game
[i
].hindex
= pgn_history_total(game
[i
].hp
);
1601 * Escape '"' and '\' in tag values.
1603 static char *pgn_tag_add_escapes(const char *str
)
1606 int len
= strlen(str
);
1607 static char buf
[MAX_PGN_LINE_LEN
] = {0};
1609 for (i
= n
= 0; i
< len
; i
++, n
++) {
1626 static void Fputc(int c
, FILE *fp
, int *len
)
1630 if (c
!= '\n' && i
+ 1 > 80)
1631 Fputc('\n', fp
, &i
);
1633 if (pgn_lastc
== '\n' && c
== ' ') {
1638 if (fputc(c
, fp
) == EOF
)
1651 static void putstring(FILE *fp
, char *str
, int *len
)
1655 for (p
= str
; *p
; p
++) {
1658 while (*p
&& *p
!= ' ')
1662 Fputc('\n', fp
, len
);
1670 * See pgn_write() for more info.
1672 static void write_comments_and_nag(FILE *fp
, HISTORY
*h
, int *len
)
1677 for (i
= 0; i
< MAX_PGN_NAG
; i
++) {
1679 Fputc(' ', fp
, len
);
1680 Fputc('$', fp
, len
);
1681 putstring(fp
, itoa(h
->nag
[i
]), len
);
1686 if (strlen(h
->comment
) + *len
+ 1 > 80) {
1688 putstring(fp
, " {", len
);
1691 putstring(fp
, " ;", len
);
1693 putstring(fp
, h
->comment
, len
);
1696 putstring(fp
, "}", len
);
1698 Fputc('\n', fp
, len
);
1702 static void write_move_text(FILE *fp
, HISTORY
*h
, int *len
)
1704 Fputc(' ', fp
, len
);
1705 putstring(fp
, h
->move
, len
);
1707 if (!pgn_config
.reduced
)
1708 write_comments_and_nag(fp
, h
, len
);
1711 static void write_all_move_text(FILE *fp
, HISTORY
**h
, int m
, int *len
)
1714 HISTORY
**hp
= NULL
;
1716 for (i
= 0; h
[i
]; i
++) {
1717 if (pgn_write_turn
== WHITE
) {
1718 if (pgn_config
.mpl
&& pgn_mpl
== pgn_config
.mpl
) {
1720 Fputc('\n', fp
, len
);
1724 Fputc(' ', fp
, len
);
1726 if (strlen(itoa(m
)) + 1 + *len
> 80)
1727 Fputc('\n', fp
, len
);
1729 putstring(fp
, itoa(m
), len
);
1730 Fputc('.', fp
, len
);
1734 write_move_text(fp
, h
[i
], len
);
1736 if (!pgn_config
.reduced
&& h
[i
]->rav
) {
1738 int oldturn
= pgn_write_turn
;
1741 putstring(fp
, " (", len
);
1744 * If it's WHITE's turn the move number will be added above after
1745 * the call to write_all_move_text() below.
1747 if (pgn_write_turn
== BLACK
) {
1748 putstring(fp
, itoa(m
), len
);
1749 putstring(fp
, "...", len
);
1753 write_all_move_text(fp
, hp
, m
, len
);
1755 pgn_write_turn
= oldturn
;
1756 putstring(fp
, ")", len
);
1759 if (h
[i
+ 1] && !ravlevel
)
1760 Fputc(' ', fp
, len
);
1762 if (pgn_write_turn
== WHITE
&& h
[i
+ 1]) {
1763 putstring(fp
, itoa(m
), len
);
1764 putstring(fp
, "...", len
);
1768 if (pgn_write_turn
== BLACK
)
1771 pgn_write_turn
= (pgn_write_turn
== WHITE
) ? BLACK
: WHITE
;
1775 #ifdef WITH_COMPRESSED
1776 static char *compression_cmd(const char *filename
, int expand
)
1778 static char command
[FILENAME_MAX
];
1779 int len
= strlen(filename
);
1781 if (filename
[len
- 4] == '.' && filename
[len
- 3] == 'z' &&
1782 filename
[len
- 2] == 'i' && filename
[len
- 1] == 'p' &&
1783 filename
[len
] == '\0') {
1785 snprintf(command
, sizeof(command
), "unzip -p %s 2>/dev/null",
1788 snprintf(command
, sizeof(command
), "zip -9 >%s 2>/dev/null",
1793 else if (filename
[len
- 3] == '.' && filename
[len
- 2] == 'g' &&
1794 filename
[len
- 1] == 'z' && filename
[len
] == '\0') {
1796 snprintf(command
, sizeof(command
), "gzip -dc %s", filename
);
1798 snprintf(command
, sizeof(command
), "gzip -c 1>%s", filename
);
1802 else if (filename
[len
- 2] == '.' && filename
[len
- 1] == 'Z' &&
1803 filename
[len
] == '\0') {
1805 snprintf(command
, sizeof(command
), "uncompress -c %s", filename
);
1807 snprintf(command
, sizeof(command
), "compress -c 1>%s", filename
);
1811 else if ((filename
[len
- 4] == '.' && filename
[len
- 3] == 'b' &&
1812 filename
[len
- 2] == 'z' && filename
[len
- 1] == '2' &&
1813 filename
[len
] == '\0') || (filename
[len
- 3] == '.' &&
1814 filename
[len
- 2] == 'b' && filename
[len
- 1] == 'z' &&
1815 filename
[len
] == '\0')) {
1817 snprintf(command
, sizeof(command
), "bzip2 -dc %s", filename
);
1819 snprintf(command
, sizeof(command
), "bzip2 -zc 1>%s", filename
);
1829 * Returns a file pointer associated with 'filename' or NULL on error with
1830 * errno set to indicate the error. If compressed file support was enabled at
1831 * compile time and the filetype is supported and the utility is installed
1832 * then the file will be decompressed.
1834 FILE *pgn_open(const char *filename
)
1837 #ifdef WITH_COMPRESSED
1841 char *command
= NULL
;
1844 if (access(filename
, R_OK
) == -1)
1847 #ifdef WITH_COMPRESSED
1848 if ((command
= compression_cmd(filename
, 1)) != NULL
) {
1849 if ((tfp
= tmpfile()) == NULL
)
1852 if ((fp
= popen(command
, "r")) == NULL
)
1855 while ((p
= fgets(buf
, sizeof(buf
), fp
)) != NULL
)
1856 fprintf(tfp
, "%s", p
);
1862 fseek(tfp
, 0, SEEK_SET
);
1864 if ((tfp
= fopen(filename
, "r")) == NULL
)
1870 if ((fp
= fopen(filename
, "r")) == NULL
)
1878 * Returns 1 if 'filename' is a recognized compressed filetype.
1880 int pgn_is_compressed(const char *filename
)
1882 #ifdef WITH_COMPRESSED
1883 if (compression_cmd(filename
, 0))
1891 * Set game flags. See chess.h for available flags.
1893 int pgn_config_set(pgn_config_flag f
, int val
)
1901 pgn_config
.reduced
= 1;
1903 pgn_config
.reduced
= 0;
1908 pgn_config
.mpl
= val
;
1910 case PGN_STOP_ON_ERROR
:
1912 pgn_config
.stop
= 1;
1914 pgn_config
.stop
= 0;
1926 * Returns the value accociated with 'f' or -1 if 'f' is invalid.
1928 int pgn_config_get(pgn_config_flag f
)
1932 return pgn_config
.reduced
;
1933 case PGN_STOP_ON_ERROR
:
1934 return pgn_config
.stop
;
1936 return pgn_config
.mpl
;
1945 * Writes a PGN formatted game 'g' to the file pointed to by 'fp'. Returns 1
1946 * if the written move count doesn't match the game's ply count (FEN tag) or 0
1949 void pgn_write(FILE *fp
, GAME g
)
1954 pgn_write_turn
= (TEST_FLAG(g
.flags
, GF_BLACK_OPENING
)) ? BLACK
: WHITE
;
1958 if (!isfifo && g.hindex != g.htotal) {
1959 snprintf(buf, sizeof(buf), "%s (#%i)", GAME_SAVE_FROM_HISTORY_TITLE,
1961 i = message(buf, GAME_SAVE_FROM_HISTORY_PROMPT, "%s",
1962 GAME_SAVE_FROM_HISTORY_TEXT);
1965 g.htotal = g.hindex;
1969 pgn_tag_sort(g
.tag
);
1971 for (i
= 0; g
.tag
[i
]; i
++) {
1973 char tbuf
[11] = {0};
1975 if (pgn_config
.reduced
&& i
== 7)
1978 if (strcmp(g
.tag
[i
]->name
, "Date") == 0) {
1979 if (strptime(g
.tag
[i
]->value
, PGN_TIME_FORMAT
, &tp
) != NULL
) {
1980 len
= strftime(tbuf
, sizeof(tbuf
), PGN_TIME_FORMAT
, &tp
) + 1;
1981 g
.tag
[i
]->value
= Realloc(g
.tag
[i
]->value
, len
);
1982 strncpy(g
.tag
[i
]->value
, tbuf
, len
);
1985 else if (strcmp(g
.tag
[i
]->name
, "Event") == 0) {
1986 if (g
.tag
[i
]->value
[0] == '\0') {
1987 g
.tag
[i
]->value
= Realloc(g
.tag
[i
]->value
, 2);
1988 g
.tag
[i
]->value
[0] = '?';
1989 g
.tag
[i
]->value
[1] = '\0';
1992 else if (strcmp(g
.tag
[i
]->name
, "Site") == 0) {
1993 if (g
.tag
[i
]->value
[0] == '\0') {
1994 g
.tag
[i
]->value
= Realloc(g
.tag
[i
]->value
, 2);
1995 g
.tag
[i
]->value
[0] = '?';
1996 g
.tag
[i
]->value
[1] = '\0';
1999 else if (strcmp(g
.tag
[i
]->name
, "Round") == 0) {
2000 if (g
.tag
[i
]->value
[0] == '\0') {
2001 g
.tag
[i
]->value
= Realloc(g
.tag
[i
]->value
, 2);
2002 g
.tag
[i
]->value
[0] = '?';
2003 g
.tag
[i
]->value
[1] = '\0';
2006 else if (strcmp(g
.tag
[i
]->name
, "Result") == 0) {
2007 if (g
.tag
[i
]->value
[0] == '\0') {
2008 g
.tag
[i
]->value
= Realloc(g
.tag
[i
]->value
, 2);
2009 g
.tag
[i
]->value
[0] = '*';
2010 g
.tag
[i
]->value
[1] = '\0';
2013 else if (strcmp(g
.tag
[i
]->name
, "Black") == 0) {
2014 if (g
.tag
[i
]->value
[0] == '\0') {
2015 g
.tag
[i
]->value
= Realloc(g
.tag
[i
]->value
, 2);
2016 g
.tag
[i
]->value
[0] = '?';
2017 g
.tag
[i
]->value
[1] = '\0';
2020 else if (strcmp(g
.tag
[i
]->name
, "White") == 0) {
2021 if (g
.tag
[i
]->value
[0] == '\0') {
2022 g
.tag
[i
]->value
= Realloc(g
.tag
[i
]->value
, 2);
2023 g
.tag
[i
]->value
[0] = '?';
2024 g
.tag
[i
]->value
[1] = '\0';
2028 fprintf(fp
, "[%s \"%s\"]\n", g
.tag
[i
]->name
,
2029 (g
.tag
[i
]->value
&& g
.tag
[i
]->value
[0]) ?
2030 pgn_tag_add_escapes(g
.tag
[i
]->value
) : "");
2033 Fputc('\n', fp
, &len
);
2037 if (pgn_history_total(g
.hp
) && pgn_write_turn
== BLACK
)
2038 putstring(fp
, "1...", &len
);
2040 write_all_move_text(fp
, g
.hp
, 1, &len
);
2042 Fputc(' ', fp
, &len
);
2043 putstring(fp
, g
.tag
[TAG_RESULT
]->value
, &len
);
2044 putstring(fp
, "\n\n", &len
);
2046 if (!pgn_config
.reduced
) {
2047 CLEAR_FLAG(g
.flags
, GF_MODIFIED
);
2048 CLEAR_FLAG(g
.flags
, GF_PERROR
);
2052 void pgn_reset_enpassant(BOARD b
)
2056 for (r
= 0; r
< 8; r
++) {
2057 for (c
= 0; c
< 8; c
++)
2058 b
[r
][c
].enpassant
= 0;
2062 int pgn_validate_move(GAME
*g
, BOARD b
, char **mp
)
2065 int piece
, dstpiece
;
2067 int srow
= 0, scol
= 0, row
, col
;
2070 int kr
= 0, kc
= 0, okr
= 0, okc
= 0;
2073 if ((m
= pgn_a2a4tosan(g
, b
, m
)) == NULL
)
2080 srow
= row
= col
= scol
= promo
= piece
= 0;
2082 p
= (m
) + strlen(m
);
2084 while (!isdigit(*--p
) && *p
!= 'O') {
2086 promo
= pgn_piece_to_int(i
);
2095 // Old promotion text (e8Q). Convert to SAN.
2096 if (pgn_piece_to_int(i
) != -1) {
2097 p
= (m
) + strlen(m
);
2110 if (pgn_piece_to_int(*p
) == PAWN
)
2114 for (i
= 0; *p
; i
++) {
2117 col
= COLTOINT(*p
++);
2119 col
= scol
= COLTOINT(*p
++);
2121 else if (VALIDROW(*p
)) {
2123 row
= ROWTOINT(*p
++);
2125 row
= ROWTOINT(*p
++);
2127 else if (*p
== 'x') {
2128 col
= COLTOINT(*++p
);
2129 row
= ROWTOINT(*++p
);
2132 else if (*p
== '=') {
2133 if (promo
== -1 || promo
== KING
|| promo
== PAWN
)
2137 *p
++ = toupper(pgn_int_to_piece(g
->turn
, promo
));
2143 DUMP("Pawn (move: '%s'): %c\n", m
, *p
++);
2150 if (get_source_yx(g
, b
, PAWN
, row
, col
, &srow
, &scol
))
2151 return E_PGN_INVALID
;
2155 if (strcmp(m
, "O-O") == 0)
2156 g
->castle
= KINGSIDE
;
2157 else if (strcmp(m
, "O-O-O") == 0)
2158 g
->castle
= QUEENSIDE
;
2162 if ((piece
= pgn_piece_to_int(*p
++)) == -1)
2165 if (strlen(m
) > 3) {
2167 srow
= ROWTOINT(*p
++);
2168 else if (VALIDCOL(*p
))
2169 scol
= COLTOINT(*p
++);
2177 col
= COLTOINT(*p
++);
2178 row
= ROWTOINT(*p
++);
2180 /* Get the source row and column. */
2183 for (i
= 1; VALIDFILE(i
); i
++) {
2184 int fpiece
= b
[ROWTOBOARD(i
)][COLTOBOARD(scol
)].icon
;
2186 if (piece
== pgn_piece_to_int(fpiece
) &&
2187 val_piece_side(g
->turn
, fpiece
)) {
2194 return E_PGN_INVALID
;
2197 if (get_source_yx(g
, b
, piece
, row
, col
, &srow
, &scol
))
2198 return E_PGN_INVALID
;
2201 else if (scol
== 0) {
2203 for (i
= 1; VALIDFILE(i
); i
++) {
2204 int fpiece
= pgn_piece_to_int(b
[ROWTOBOARD(srow
)][COLTOBOARD(i
)].icon
);
2206 if (piece
== fpiece
) {
2213 return E_PGN_INVALID
;
2216 if (get_source_yx(g
, b
, piece
, row
, col
, &srow
, &scol
))
2217 return E_PGN_INVALID
;
2223 piece
= pgn_piece_to_int(b
[ROWTOBOARD(srow
)][COLTOBOARD(scol
)].icon
);
2224 dist
= abs(srow
- row
);
2227 if (piece
== PAWN
|| capture
)
2232 pgn_reset_enpassant(b
);
2234 if (piece
== PAWN
&& dist
== 2) {
2235 if (g
->turn
== WHITE
)
2236 b
[ROWTOBOARD(srow
- 1)][COLTOBOARD(scol
)].enpassant
= 1;
2238 b
[ROWTOBOARD(srow
+ 1)][COLTOBOARD(scol
)].enpassant
= 1;
2240 SET_FLAG(g
->flags
, GF_ENPASSANT
);
2243 CLEAR_FLAG(g
->flags
, GF_ENPASSANT
);
2247 dstpiece
= piece
= b
[ROWTOBOARD(row
)][COLTOBOARD(col
)].icon
;
2250 if (castle_move(g
, b
, g
->castle
)) {
2252 return E_PGN_INVALID
;
2258 if (pgn_piece_to_int(piece
) != OPEN_SQUARE
) {
2259 if (val_piece_side(g
->turn
, piece
))
2260 return E_PGN_INVALID
;
2265 piece
= pgn_int_to_piece(g
->turn
, promo
);
2267 piece
= b
[ROWTOBOARD(srow
)][COLTOBOARD(scol
)].icon
;
2270 piece
= b
[ROWTOBOARD(srow
)][COLTOBOARD(scol
)].icon
;
2273 b
[ROWTOBOARD(srow
)][COLTOBOARD(scol
)].icon
=
2274 pgn_int_to_piece(g
->turn
, OPEN_SQUARE
);
2275 b
[ROWTOBOARD(row
)][COLTOBOARD(col
)].icon
= piece
;
2279 if (!validate
&& capture
&& pgn_piece_to_int(dstpiece
) == ROOK
) {
2280 if (row
== 1 && col
== 1)
2281 CLEAR_FLAG(g
->flags
, GF_WQ_CASTLE
);
2282 else if (row
== 8 && col
== 1)
2283 CLEAR_FLAG(g
->flags
, GF_BQ_CASTLE
);
2284 else if (row
== 1 && col
== 8)
2285 CLEAR_FLAG(g
->flags
, GF_WK_CASTLE
);
2286 else if (row
== 8 && col
== 8)
2287 CLEAR_FLAG(g
->flags
, GF_BK_CASTLE
);
2290 kingsquare(*g
, b
, &kr
, &kc
, &okr
, &okc
);
2298 CLEAR_FLAG(g
->flags
, GF_GAMEOVER
);
2303 g
->tag
[TAG_RESULT
]->value
= Realloc(g
->tag
[TAG_RESULT
]->value
, 8);
2304 strncpy(g
->tag
[TAG_RESULT
]->value
, "1/2-1/2", 8);
2305 SET_FLAG(g
->flags
, GF_GAMEOVER
);
2308 switch (checktest(g
, b
, kr
, kc
, okr
, okc
, 0)) {
2314 return E_PGN_INVALID
;
2316 if (checkmatetest(g
, b
, kr
, kc
, okr
, okc
)) {
2319 if (result
== WHITEWINS
) {
2320 g
->tag
[TAG_RESULT
]->value
= Realloc(g
->tag
[TAG_RESULT
]->value
, 4);
2321 strncpy(g
->tag
[TAG_RESULT
]->value
, "1-0", 4);
2323 else if (result
== BLACKWINS
) {
2324 g
->tag
[TAG_RESULT
]->value
= Realloc(g
->tag
[TAG_RESULT
]->value
, 4);
2325 strncpy(g
->tag
[TAG_RESULT
]->value
, "0-1", 4);
2328 SET_FLAG(g
->flags
, GF_GAMEOVER
);
2344 int pgn_validate_only(GAME
*g
, BOARD b
, char **m
)
2349 ret
= pgn_validate_move(g
, b
, m
);