Tue, 13 Aug 2019 00:33:59 +0200
pgn parser can now handle comments (although it ignores them for now)
1 /*
2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3 *
4 * Copyright 2016 Mike Becker. All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are met:
8 *
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 *
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 * POSSIBILITY OF SUCH DAMAGE.
27 *
28 */
30 #include "pgn.h"
31 #include <ctype.h>
32 #include <stdlib.h>
33 #include <string.h>
35 enum {
36 pgn_error_missing_quote = 1,
37 pgn_error_missing_bracket,
38 pgn_error_missing_brace,
39 pgn_error_missing_dot,
40 pgn_error_move_syntax,
41 pgn_error_move_semantics
42 };
44 static const char* pgn_error_strings[] = {
45 "No Error.",
46 "Tag values must be enclosed in double-quotes.",
47 "Missing closing brace '}' for comment.",
48 "Tags must be enclosed in square brackets: '[Key \"Value\"]'.",
49 "Move numbers must be terminated with a dot (e.g. '13.' - not '13').",
50 "Move is syntactically incorrect.",
51 "Move is not valid according to chess rules."
52 };
54 const char* pgn_error_str(int code) {
55 return pgn_error_strings[code];
56 }
58 int read_pgn(FILE* stream, GameState *gamestate, GameInfo *gameinfo) {
59 int c, i;
61 char result[8];
63 char tagkey[32];
64 char tagvalue[128];
66 /* read tag pairs */
67 _Bool readmoves = 0;
68 while (!readmoves) {
69 while (isspace(c = fgetc(stream)));
70 if (c == '1') {
71 readmoves = 1;
72 break;
73 }
74 if (c != '[') {
75 return pgn_error_missing_bracket;
76 }
77 while (isspace(c = fgetc(stream)));
78 i = 0;
79 do {
80 tagkey[i++] = c;
81 } while (!isspace(c = fgetc(stream)));
82 tagkey[i] = '\0';
83 while (isspace(c = fgetc(stream)));
84 if (c != '"') {
85 return pgn_error_missing_quote;
86 }
87 i = 0;
88 while ((c = fgetc(stream)) != '"') {
89 if (c == '\n' || c == EOF) {
90 return pgn_error_missing_quote;
91 }
92 tagvalue[i++] = c;
93 }
94 tagvalue[i] = '\0';
95 if (fgetc(stream) != ']') {
96 return pgn_error_missing_bracket;
97 }
99 if (strcmp("Result", tagkey) == 0) {
100 memcpy(result, tagvalue, 8);
101 }
102 }
104 /* read moves */
105 if (fgetc(stream) != '.') {
106 return pgn_error_missing_dot;
107 }
109 char movestr[10];
110 Move move;
111 uint8_t curcol = WHITE;
113 while (readmoves) {
114 /* move */
115 while (isspace(c = fgetc(stream)));
116 i = 0;
117 do {
118 movestr[i++] = c;
119 if (i >= 10) {
120 return 1;
121 }
122 } while (!isspace(c = fgetc(stream)));
123 movestr[i] = '\0';
124 if (eval_move(gamestate, movestr, &move, curcol)
125 != VALID_MOVE_SYNTAX) {
126 return pgn_error_move_syntax;
127 }
128 if (validate_move(gamestate, &move) != VALID_MOVE_SEMANTICS) {
129 return pgn_error_move_semantics;
130 }
131 apply_move(gamestate, &move);
133 /* skip spaces */
134 while (isspace(c = fgetc(stream)));
136 /* parse possible comment */
137 if (c == '{') {
138 // TODO: interpret comment (may contain clock info)
139 do {
140 c = fgetc(stream);
141 } while (c != '}' && c != EOF);
142 if (c == EOF) {
143 return pgn_error_missing_brace;
144 }
145 }
147 /* skip spaces */
148 while (isspace(c = fgetc(stream)));
150 /* end of game data encountered */
151 if (c == EOF) {
152 break;
153 }
154 if (c == '1' || c == '0') {
155 c = fgetc(stream);
156 if (c == '-') {
157 gamestate->resign = !gamestate->checkmate;
158 break;
159 } else if (c == '/') {
160 gamestate->remis = !gamestate->stalemate;
161 break;
162 } else {
163 /* oops, it was a move number, go back! */
164 fseek(stream, -1, SEEK_CUR);
165 }
166 }
168 /* we have eaten the next valuable byte, so go back */
169 fseek(stream, -1, SEEK_CUR);
171 /* skip move number after black move */
172 if (curcol == BLACK) {
173 while (isdigit(c = fgetc(stream)));
174 if (c != '.') {
175 return pgn_error_missing_dot;
176 }
177 }
178 curcol = opponent_color(curcol);
179 }
181 return 0;
182 }
184 size_t write_pgn(FILE* stream, GameState *gamestate, GameInfo *gameinfo) {
185 // TODO: tag pairs
186 size_t bytes = 0;
188 /* Result */
189 char *result;
190 if (gamestate->stalemate || gamestate->remis) {
191 result = "1/2-1/2";
192 } else if (gamestate->checkmate || gamestate->resign) {
193 if (gamestate->lastmove) {
194 result = (gamestate->lastmove->move.piece & COLOR_MASK) == WHITE ?
195 "1-0" : "0-1";
196 } else {
197 result = "0-1";
198 }
199 } else {
200 result = "*";
201 }
202 fprintf(stream, "[Result \"%s\"]\n\n", result);
204 /* moves */
205 int i = 1;
206 for (MoveList *movelist = gamestate->movelist ;
207 movelist ; movelist = movelist->next) {
209 if (++i % 2 == 0) {
210 fprintf(stream, "%d. %s", i/2, movelist->move.string);
211 } else {
212 fprintf(stream, " %s", movelist->move.string);
213 }
215 // TODO: move time and maybe other comments
217 /* line break every 10 moves */
218 if ((i-1) % 20) {
219 fputc(' ', stream);
220 } else {
221 fputc('\n', stream);
222 }
223 }
225 if (result[0] == '*') {
226 fputc('\n', stream);
227 } else {
228 fprintf(stream, "%s\n", result);
229 }
232 return bytes;
233 }
235 static size_t fen_pieces(char *str, GameState *gamestate) {
236 size_t i = 0;
237 for (int row = 7 ; row >= 0 ; row--) {
238 unsigned int skip = 0;
239 for (int file = 0 ; file < 8 ; file++) {
240 if (gamestate->board[row][file]) {
241 if (skip > 0) {
242 str[i++] = '0'+skip;
243 skip = 0;
244 }
245 switch (gamestate->board[row][file] & ~ENPASSANT_THREAT) {
246 case WHITE|KING: str[i++] = 'K'; break;
247 case WHITE|QUEEN: str[i++] = 'Q'; break;
248 case WHITE|BISHOP: str[i++] = 'B'; break;
249 case WHITE|KNIGHT: str[i++] = 'N'; break;
250 case WHITE|ROOK: str[i++] = 'R'; break;
251 case WHITE|PAWN: str[i++] = 'P'; break;
252 case BLACK|KING: str[i++] = 'k'; break;
253 case BLACK|QUEEN: str[i++] = 'q'; break;
254 case BLACK|BISHOP: str[i++] = 'b'; break;
255 case BLACK|KNIGHT: str[i++] = 'n'; break;
256 case BLACK|ROOK: str[i++] = 'r'; break;
257 case BLACK|PAWN: str[i++] = 'p'; break;
258 }
259 } else {
260 skip++;
261 }
262 }
263 if (skip > 0) {
264 str[i++] = '0'+skip;
265 }
266 if (row > 0) {
267 str[i++] = '/';
268 }
269 }
271 return i;
272 }
274 static size_t fen_color(char *str, GameState *gamestate) {
275 uint8_t color = opponent_color(gamestate->lastmove ?
276 (gamestate->lastmove->move.piece & COLOR_MASK) : BLACK);
278 str[0] = color == WHITE ? 'w' : 'b';
280 return 1;
281 }
283 static _Bool fen_castling_chkmoved(GameState *gamestate,
284 uint8_t row, uint8_t file) {
286 MoveList *ml = gamestate->movelist;
287 while (ml) {
288 if (ml->move.fromfile == file && ml->move.fromrow == row) {
289 return 1;
290 }
291 ml = ml->next;
292 }
294 return 0;
295 }
297 static size_t fen_castling(char *str, GameState *gamestate) {
298 _Bool K, Q, k, q;
300 if (fen_castling_chkmoved(gamestate, rowidx('1'), fileidx('e'))) {
301 K = Q = 0;
302 } else {
303 K = !fen_castling_chkmoved(gamestate, rowidx('1'), fileidx('h'));
304 Q = !fen_castling_chkmoved(gamestate, rowidx('1'), fileidx('a'));
305 }
306 if (fen_castling_chkmoved(gamestate, rowidx('8'), fileidx('e'))) {
307 k = q = 0;
308 } else {
309 k = !fen_castling_chkmoved(gamestate, rowidx('8'), fileidx('h'));
310 q = !fen_castling_chkmoved(gamestate, rowidx('8'), fileidx('a'));
311 }
313 size_t i = 0;
314 if (K) str[i++] = 'K';
315 if (Q) str[i++] = 'Q';
316 if (k) str[i++] = 'k';
317 if (q) str[i++] = 'q';
318 if (!i) str[i++] = '-';
320 return i;
321 }
323 static size_t fen_enpassant(char *str, GameState *gamestate) {
325 str[0] = '-'; str[1] = '\0';
327 for (int file = 0 ; file < 8 ; file++) {
328 if (gamestate->board[3][file] & ENPASSANT_THREAT) {
329 str[0] = filechr(file);
330 str[1] = rowchr(2);
331 }
332 if (gamestate->board[4][file] & ENPASSANT_THREAT) {
333 str[0] = filechr(file);
334 str[1] = rowchr(5);
335 }
336 }
338 return str[0] == '-' ? 1 : 2;
339 }
341 static size_t fen_halfmove(char *str, GameState *gamestate) {
343 unsigned int i = 0;
344 for (MoveList *move = gamestate->movelist ; move ; move = move->next) {
345 if (move->move.capture || (move->move.piece & PIECE_MASK) == PAWN) {
346 i = 0;
347 } else {
348 i++;
349 }
350 }
352 char m[8];
353 size_t len = sprintf(m, "%u", i);
354 memcpy(str, m, len);
356 return len;
357 }
359 static size_t fen_movenr(char *str, GameState *gamestate) {
361 MoveList *move = gamestate->movelist;
362 unsigned int i = 1;
363 while (move) {
364 i++;
365 move = move->next;
366 }
368 char m[8];
369 size_t len = sprintf(m, "%u", i);
370 memcpy(str, m, len);
372 return len;
373 }
375 void compute_fen(char *str, GameState *gamestate) {
376 str += fen_pieces(str, gamestate);
377 *str = ' '; str++;
378 str += fen_color(str, gamestate);
379 *str = ' '; str++;
380 str += fen_castling(str, gamestate);
381 *str = ' '; str++;
382 str += fen_enpassant(str, gamestate);
383 *str = ' '; str++;
384 str += fen_halfmove(str, gamestate);
385 *str = ' '; str++;
386 str += fen_movenr(str, gamestate);
387 str[0] = '\0';
388 }