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)

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

mercurial