src/chess/pgn.c

Wed, 29 Aug 2018 14:01:41 +0200

author
Mike Becker <universe@uap-core.de>
date
Wed, 29 Aug 2018 14:01:41 +0200
changeset 65
dcc5bd2c56c0
parent 64
4eda5df55f86
child 70
5427beba96d1
permissions
-rw-r--r--

PGN output now correctly breaks after black has also moved

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

mercurial