Wed, 29 Aug 2018 14:01:41 +0200
PGN output now correctly breaks after black has also moved
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_dot,
39 pgn_error_move_syntax,
40 pgn_error_move_semantics
41 };
43 static const char* pgn_error_strings[] = {
44 "No Error.",
45 "Tag values must be enclosed in double-quotes.",
46 "Tags must be enclosed in square brackets: '[Key \"Value\"]'.",
47 "Move numbers must be terminated with a dot (e.g. '13.' - not '13').",
48 "Move is syntactically incorrect.",
49 "Move is not valid according to chess rules."
50 };
52 const char* pgn_error_str(int code) {
53 return pgn_error_strings[code];
54 }
56 int read_pgn(FILE* stream, GameState *gamestate, GameInfo *gameinfo) {
57 int c, i;
59 char result[8];
61 char tagkey[32];
62 char tagvalue[128];
64 /* read tag pairs */
65 _Bool readmoves = 0;
66 while (!readmoves) {
67 while (isspace(c = fgetc(stream)));
68 if (c == '1') {
69 readmoves = 1;
70 break;
71 }
72 if (c != '[') {
73 return pgn_error_missing_bracket;
74 }
75 while (isspace(c = fgetc(stream)));
76 i = 0;
77 do {
78 tagkey[i++] = c;
79 } while (!isspace(c = fgetc(stream)));
80 tagkey[i] = '\0';
81 while (isspace(c = fgetc(stream)));
82 if (c != '"') {
83 return pgn_error_missing_quote;
84 }
85 i = 0;
86 while ((c = fgetc(stream)) != '"') {
87 if (c == '\n' || c == EOF) {
88 return pgn_error_missing_quote;
89 }
90 tagvalue[i++] = c;
91 }
92 tagvalue[i] = '\0';
93 if (fgetc(stream) != ']') {
94 return pgn_error_missing_bracket;
95 }
97 if (strcmp("Result", tagkey) == 0) {
98 memcpy(result, tagvalue, 8);
99 }
100 }
102 /* read moves */
103 if (fgetc(stream) != '.') {
104 return pgn_error_missing_dot;
105 }
107 char movestr[10];
108 Move move;
109 uint8_t curcol = WHITE;
111 while (readmoves) {
112 /* move */
113 while (isspace(c = fgetc(stream)));
114 i = 0;
115 do {
116 movestr[i++] = c;
117 if (i >= 10) {
118 return 1;
119 }
120 } while (!isspace(c = fgetc(stream)));
121 movestr[i] = '\0';
122 if (eval_move(gamestate, movestr, &move, curcol)
123 != VALID_MOVE_SYNTAX) {
124 return pgn_error_move_syntax;
125 }
126 if (validate_move(gamestate, &move) != VALID_MOVE_SEMANTICS) {
127 return pgn_error_move_semantics;
128 }
129 apply_move(gamestate, &move);
131 // TODO: parse comments
132 while (isspace(c = fgetc(stream)));
134 /* end of game data encountered */
135 if (c == EOF) {
136 break;
137 }
138 if (c == '1' || c == '0') {
139 c = fgetc(stream);
140 if (c == '-') {
141 gamestate->resign = !gamestate->checkmate;
142 break;
143 } else if (c == '/') {
144 gamestate->remis = !gamestate->stalemate;
145 break;
146 } else {
147 /* oops, it was a move number, go back! */
148 fseek(stream, -1, SEEK_CUR);
149 }
150 }
152 /* we have eaten the next valuable byte, so go back */
153 fseek(stream, -1, SEEK_CUR);
155 /* skip move number after black move */
156 if (curcol == BLACK) {
157 while (isdigit(c = fgetc(stream)));
158 if (c != '.') {
159 return pgn_error_missing_dot;
160 }
161 }
162 curcol = opponent_color(curcol);
163 }
165 return 0;
166 }
168 size_t write_pgn(FILE* stream, GameState *gamestate, GameInfo *gameinfo) {
169 // TODO: tag pairs
170 size_t bytes = 0;
172 /* Result */
173 char *result;
174 if (gamestate->stalemate || gamestate->remis) {
175 result = "1/2-1/2";
176 } else if (gamestate->checkmate || gamestate->resign) {
177 if (gamestate->lastmove) {
178 result = (gamestate->lastmove->move.piece & COLOR_MASK) == WHITE ?
179 "1-0" : "0-1";
180 } else {
181 result = "0-1";
182 }
183 } else {
184 result = "*";
185 }
186 fprintf(stream, "[Result \"%s\"]\n\n", result);
188 /* moves */
189 int i = 1;
190 for (MoveList *movelist = gamestate->movelist ;
191 movelist ; movelist = movelist->next) {
193 if (++i % 2 == 0) {
194 fprintf(stream, "%d. %s", i/2, movelist->move.string);
195 } else {
196 fprintf(stream, " %s", movelist->move.string);
197 }
199 // TODO: move time and maybe other comments
201 /* line break every 10 moves */
202 if ((i-1) % 20) {
203 fputc(' ', stream);
204 } else {
205 fputc('\n', stream);
206 }
207 }
209 if (result[0] == '*') {
210 fputc('\n', stream);
211 } else {
212 fprintf(stream, "%s\n", result);
213 }
216 return bytes;
217 }
219 static size_t fen_pieces(char *str, GameState *gamestate) {
220 size_t i = 0;
221 for (int row = 7 ; row >= 0 ; row--) {
222 unsigned int skip = 0;
223 for (int file = 0 ; file < 8 ; file++) {
224 if (gamestate->board[row][file]) {
225 if (skip > 0) {
226 str[i++] = '0'+skip;
227 skip = 0;
228 }
229 switch (gamestate->board[row][file] & ~ENPASSANT_THREAT) {
230 case WHITE|KING: str[i++] = 'K'; break;
231 case WHITE|QUEEN: str[i++] = 'Q'; break;
232 case WHITE|BISHOP: str[i++] = 'B'; break;
233 case WHITE|KNIGHT: str[i++] = 'N'; break;
234 case WHITE|ROOK: str[i++] = 'R'; break;
235 case WHITE|PAWN: str[i++] = 'P'; break;
236 case BLACK|KING: str[i++] = 'k'; break;
237 case BLACK|QUEEN: str[i++] = 'q'; break;
238 case BLACK|BISHOP: str[i++] = 'b'; break;
239 case BLACK|KNIGHT: str[i++] = 'n'; break;
240 case BLACK|ROOK: str[i++] = 'r'; break;
241 case BLACK|PAWN: str[i++] = 'p'; break;
242 }
243 } else {
244 skip++;
245 }
246 }
247 if (skip > 0) {
248 str[i++] = '0'+skip;
249 }
250 if (row > 0) {
251 str[i++] = '/';
252 }
253 }
255 return i;
256 }
258 static size_t fen_color(char *str, GameState *gamestate) {
259 uint8_t color = opponent_color(gamestate->lastmove ?
260 (gamestate->lastmove->move.piece & COLOR_MASK) : BLACK);
262 str[0] = color == WHITE ? 'w' : 'b';
264 return 1;
265 }
267 static _Bool fen_castling_chkmoved(GameState *gamestate,
268 uint8_t row, uint8_t file) {
270 MoveList *ml = gamestate->movelist;
271 while (ml) {
272 if (ml->move.fromfile == file && ml->move.fromrow == row) {
273 return 1;
274 }
275 ml = ml->next;
276 }
278 return 0;
279 }
281 static size_t fen_castling(char *str, GameState *gamestate) {
282 _Bool K, Q, k, q;
284 if (fen_castling_chkmoved(gamestate, rowidx('1'), fileidx('e'))) {
285 K = Q = 0;
286 } else {
287 K = !fen_castling_chkmoved(gamestate, rowidx('1'), fileidx('h'));
288 Q = !fen_castling_chkmoved(gamestate, rowidx('1'), fileidx('a'));
289 }
290 if (fen_castling_chkmoved(gamestate, rowidx('8'), fileidx('e'))) {
291 k = q = 0;
292 } else {
293 k = !fen_castling_chkmoved(gamestate, rowidx('8'), fileidx('h'));
294 q = !fen_castling_chkmoved(gamestate, rowidx('8'), fileidx('a'));
295 }
297 size_t i = 0;
298 if (K) str[i++] = 'K';
299 if (Q) str[i++] = 'Q';
300 if (k) str[i++] = 'k';
301 if (q) str[i++] = 'q';
302 if (!i) str[i++] = '-';
304 return i;
305 }
307 static size_t fen_enpassant(char *str, GameState *gamestate) {
309 str[0] = '-'; str[1] = '\0';
311 for (int file = 0 ; file < 8 ; file++) {
312 if (gamestate->board[3][file] & ENPASSANT_THREAT) {
313 str[0] = filechr(file);
314 str[1] = rowchr(2);
315 }
316 if (gamestate->board[4][file] & ENPASSANT_THREAT) {
317 str[0] = filechr(file);
318 str[1] = rowchr(5);
319 }
320 }
322 return str[0] == '-' ? 1 : 2;
323 }
325 static size_t fen_halfmove(char *str, GameState *gamestate) {
327 unsigned int i = 0;
328 for (MoveList *move = gamestate->movelist ; move ; move = move->next) {
329 if (move->move.capture || (move->move.piece & PIECE_MASK) == PAWN) {
330 i = 0;
331 } else {
332 i++;
333 }
334 }
336 char m[8];
337 size_t len = sprintf(m, "%u", i);
338 memcpy(str, m, len);
340 return len;
341 }
343 static size_t fen_movenr(char *str, GameState *gamestate) {
345 MoveList *move = gamestate->movelist;
346 unsigned int i = 1;
347 while (move) {
348 i++;
349 move = move->next;
350 }
352 char m[8];
353 size_t len = sprintf(m, "%u", i);
354 memcpy(str, m, len);
356 return len;
357 }
359 void compute_fen(char *str, GameState *gamestate) {
360 str += fen_pieces(str, gamestate);
361 *str = ' '; str++;
362 str += fen_color(str, gamestate);
363 *str = ' '; str++;
364 str += fen_castling(str, gamestate);
365 *str = ' '; str++;
366 str += fen_enpassant(str, gamestate);
367 *str = ' '; str++;
368 str += fen_halfmove(str, gamestate);
369 *str = ' '; str++;
370 str += fen_movenr(str, gamestate);
371 str[0] = '\0';
372 }