src/chess/pgn.c

Tue, 13 Aug 2019 00:33:59 +0200

author
Mike Becker <universe@uap-core.de>
date
Tue, 13 Aug 2019 00:33:59 +0200
changeset 70
5427beba96d1
parent 65
dcc5bd2c56c0
permissions
-rw-r--r--

pgn parser can now handle comments (although it ignores them for now)

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

mercurial