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

     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 }

mercurial