Wed, 29 Aug 2018 13:55:18 +0200
fixes castling not printed correctly to PGN
/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 2016 Mike Becker. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * */ #include "pgn.h" #include <ctype.h> #include <stdlib.h> #include <string.h> enum { pgn_error_missing_quote = 1, pgn_error_missing_bracket, pgn_error_missing_dot, pgn_error_move_syntax, pgn_error_move_semantics }; static const char* pgn_error_strings[] = { "No Error.", "Tag values must be enclosed in double-quotes.", "Tags must be enclosed in square brackets: '[Key \"Value\"]'.", "Move numbers must be terminated with a dot (e.g. '13.' - not '13').", "Move is syntactically incorrect.", "Move is not valid according to chess rules." }; const char* pgn_error_str(int code) { return pgn_error_strings[code]; } int read_pgn(FILE* stream, GameState *gamestate, GameInfo *gameinfo) { int c, i; char result[8]; char tagkey[32]; char tagvalue[128]; /* read tag pairs */ _Bool readmoves = 0; while (!readmoves) { while (isspace(c = fgetc(stream))); if (c == '1') { readmoves = 1; break; } if (c != '[') { return pgn_error_missing_bracket; } while (isspace(c = fgetc(stream))); i = 0; do { tagkey[i++] = c; } while (!isspace(c = fgetc(stream))); tagkey[i] = '\0'; while (isspace(c = fgetc(stream))); if (c != '"') { return pgn_error_missing_quote; } i = 0; while ((c = fgetc(stream)) != '"') { if (c == '\n' || c == EOF) { return pgn_error_missing_quote; } tagvalue[i++] = c; } tagvalue[i] = '\0'; if (fgetc(stream) != ']') { return pgn_error_missing_bracket; } if (strcmp("Result", tagkey) == 0) { memcpy(result, tagvalue, 8); } } /* read moves */ if (fgetc(stream) != '.') { return pgn_error_missing_dot; } char movestr[10]; Move move; uint8_t curcol = WHITE; while (readmoves) { /* move */ while (isspace(c = fgetc(stream))); i = 0; do { movestr[i++] = c; if (i >= 10) { return 1; } } while (!isspace(c = fgetc(stream))); movestr[i] = '\0'; if (eval_move(gamestate, movestr, &move, curcol) != VALID_MOVE_SYNTAX) { return pgn_error_move_syntax; } if (validate_move(gamestate, &move) != VALID_MOVE_SEMANTICS) { return pgn_error_move_semantics; } apply_move(gamestate, &move); // TODO: parse comments while (isspace(c = fgetc(stream))); /* end of game data encountered */ if (c == EOF) { break; } if (c == '1' || c == '0') { c = fgetc(stream); if (c == '-') { gamestate->resign = !gamestate->checkmate; break; } else if (c == '/') { gamestate->remis = !gamestate->stalemate; break; } else { /* oops, it was a move number, go back! */ fseek(stream, -1, SEEK_CUR); } } /* we have eaten the next valuable byte, so go back */ fseek(stream, -1, SEEK_CUR); /* skip move number after black move */ if (curcol == BLACK) { while (isdigit(c = fgetc(stream))); if (c != '.') { return pgn_error_missing_dot; } } curcol = opponent_color(curcol); } return 0; } size_t write_pgn(FILE* stream, GameState *gamestate, GameInfo *gameinfo) { // TODO: tag pairs size_t bytes = 0; /* Result */ char *result; if (gamestate->stalemate || gamestate->remis) { result = "1/2-1/2"; } else if (gamestate->checkmate || gamestate->resign) { if (gamestate->lastmove) { result = (gamestate->lastmove->move.piece & COLOR_MASK) == WHITE ? "1-0" : "0-1"; } else { result = "0-1"; } } else { result = "*"; } fprintf(stream, "[Result \"%s\"]\n\n", result); /* moves */ int i = 1; for (MoveList *movelist = gamestate->movelist ; movelist ; movelist = movelist->next) { if (++i % 2 == 0) { fprintf(stream, "%d. %s", i/2, movelist->move.string); } else { fprintf(stream, " %s", movelist->move.string); } // TODO: move time and maybe other comments /* line break every 10 moves */ if (i % 20) { fputc(' ', stream); } else { fputc('\n', stream); } } if (result[0] == '*') { fputc('\n', stream); } else { fprintf(stream, "%s\n", result); } return bytes; } static size_t fen_pieces(char *str, GameState *gamestate) { size_t i = 0; for (int row = 7 ; row >= 0 ; row--) { unsigned int skip = 0; for (int file = 0 ; file < 8 ; file++) { if (gamestate->board[row][file]) { if (skip > 0) { str[i++] = '0'+skip; skip = 0; } switch (gamestate->board[row][file] & ~ENPASSANT_THREAT) { case WHITE|KING: str[i++] = 'K'; break; case WHITE|QUEEN: str[i++] = 'Q'; break; case WHITE|BISHOP: str[i++] = 'B'; break; case WHITE|KNIGHT: str[i++] = 'N'; break; case WHITE|ROOK: str[i++] = 'R'; break; case WHITE|PAWN: str[i++] = 'P'; break; case BLACK|KING: str[i++] = 'k'; break; case BLACK|QUEEN: str[i++] = 'q'; break; case BLACK|BISHOP: str[i++] = 'b'; break; case BLACK|KNIGHT: str[i++] = 'n'; break; case BLACK|ROOK: str[i++] = 'r'; break; case BLACK|PAWN: str[i++] = 'p'; break; } } else { skip++; } } if (skip > 0) { str[i++] = '0'+skip; } if (row > 0) { str[i++] = '/'; } } return i; } static size_t fen_color(char *str, GameState *gamestate) { uint8_t color = opponent_color(gamestate->lastmove ? (gamestate->lastmove->move.piece & COLOR_MASK) : BLACK); str[0] = color == WHITE ? 'w' : 'b'; return 1; } static _Bool fen_castling_chkmoved(GameState *gamestate, uint8_t row, uint8_t file) { MoveList *ml = gamestate->movelist; while (ml) { if (ml->move.fromfile == file && ml->move.fromrow == row) { return 1; } ml = ml->next; } return 0; } static size_t fen_castling(char *str, GameState *gamestate) { _Bool K, Q, k, q; if (fen_castling_chkmoved(gamestate, rowidx('1'), fileidx('e'))) { K = Q = 0; } else { K = !fen_castling_chkmoved(gamestate, rowidx('1'), fileidx('h')); Q = !fen_castling_chkmoved(gamestate, rowidx('1'), fileidx('a')); } if (fen_castling_chkmoved(gamestate, rowidx('8'), fileidx('e'))) { k = q = 0; } else { k = !fen_castling_chkmoved(gamestate, rowidx('8'), fileidx('h')); q = !fen_castling_chkmoved(gamestate, rowidx('8'), fileidx('a')); } size_t i = 0; if (K) str[i++] = 'K'; if (Q) str[i++] = 'Q'; if (k) str[i++] = 'k'; if (q) str[i++] = 'q'; if (!i) str[i++] = '-'; return i; } static size_t fen_enpassant(char *str, GameState *gamestate) { str[0] = '-'; str[1] = '\0'; for (int file = 0 ; file < 8 ; file++) { if (gamestate->board[3][file] & ENPASSANT_THREAT) { str[0] = filechr(file); str[1] = rowchr(2); } if (gamestate->board[4][file] & ENPASSANT_THREAT) { str[0] = filechr(file); str[1] = rowchr(5); } } return str[0] == '-' ? 1 : 2; } static size_t fen_halfmove(char *str, GameState *gamestate) { unsigned int i = 0; for (MoveList *move = gamestate->movelist ; move ; move = move->next) { if (move->move.capture || (move->move.piece & PIECE_MASK) == PAWN) { i = 0; } else { i++; } } char m[8]; size_t len = sprintf(m, "%u", i); memcpy(str, m, len); return len; } static size_t fen_movenr(char *str, GameState *gamestate) { MoveList *move = gamestate->movelist; unsigned int i = 1; while (move) { i++; move = move->next; } char m[8]; size_t len = sprintf(m, "%u", i); memcpy(str, m, len); return len; } void compute_fen(char *str, GameState *gamestate) { str += fen_pieces(str, gamestate); *str = ' '; str++; str += fen_color(str, gamestate); *str = ' '; str++; str += fen_castling(str, gamestate); *str = ' '; str++; str += fen_enpassant(str, gamestate); *str = ' '; str++; str += fen_halfmove(str, gamestate); *str = ' '; str++; str += fen_movenr(str, gamestate); str[0] = '\0'; }