src/chess/pgn.c

Wed, 26 Aug 2015 14:46:42 +0200

author
Mike Becker <universe@uap-core.de>
date
Wed, 26 Aug 2015 14:46:42 +0200
changeset 54
eef745ba3774
parent 50
41017d0a72c5
child 55
54ea19938d57
permissions
-rw-r--r--

implemented FEN

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

mercurial