src/chess/pgn.c

changeset 145
21b76056a178
parent 143
e9159c70ff9e
--- a/src/chess/pgn.c	Sat Jul 18 15:01:04 2026 +0200
+++ b/src/chess/pgn.c	Sat Jul 18 15:30:08 2026 +0200
@@ -36,13 +36,15 @@
 #include <time.h>
 
 enum {
-    pgn_error_missing_quote = 1,
+    pgn_no_error = 0,
+    pgn_error_missing_quote,
     pgn_error_missing_bracket,
     pgn_error_missing_brace,
     pgn_error_missing_dot,
     pgn_error_move_syntax,
     pgn_error_move_semantics,
     pgn_error_result_syntax,
+    pgn_error_result_mismatch,
     pgn_error_unexpected_eof,
 };
 
@@ -55,6 +57,7 @@
     "Move is syntactically incorrect.",
     "Move is not valid according to chess rules.",
     "Result syntax is incorrect. Expected 1-0, 0-1, 1/2-1/2, or *.",
+    "Result at end of move text does not match result in tag roster.",
     "Unexpected end of data.",
 };
 
@@ -62,11 +65,29 @@
     return pgn_error_strings[code];
 }
 
+static const char *pgn_result(GameState *gamestate) {
+    if (gamestate->stalemate || gamestate->remis) {
+        return "1/2-1/2";
+    } else if (gamestate->wresign) {
+        return "0-1";
+    } else if (gamestate->bresign) {
+        return "1-0";
+    } else if (gamestate->checkmate) {
+        if (current_color(gamestate) == BLACK) {
+            return "1-0";
+        } else {
+            return "0-1";
+        }
+    } else {
+        return "*";
+    }
+}
+
 int parse_pgn(const char *pgndata, GameState *gamestate) {
     int i;
     char c;
     
-    char result[8];
+    char result[8] = {0};
     
     char tagkey[32];
     char tagvalue[128];
@@ -103,8 +124,8 @@
         }
 
         // TODO: read clock info
+
         if (strcmp("Result", tagkey) == 0) {
-            // TODO: why are we parsing this? it is never used!
             memcpy(result, tagvalue, 8);
         }
     }
@@ -117,6 +138,7 @@
     char movestr[10];
     Move move;
     uint8_t curcol = WHITE;
+    bool movetext_ends_with_result = false;
     
     while (true) {
         /* move */
@@ -132,7 +154,7 @@
         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;
         }
@@ -174,9 +196,11 @@
                         return pgn_error_result_syntax;
                     }
                 }
+                movetext_ends_with_result = true;
                 break;
             } else if (c == '/') {
                 gamestate->remis = !gamestate->stalemate;
+                movetext_ends_with_result = true;
                 break;
             } else {
                 /* oops, it was a move number, go back! */
@@ -196,8 +220,16 @@
         }
         curcol = opponent_color(curcol);
     }
+
+    /* sanity check result - if it was specified */
+    if (movetext_ends_with_result && result[0]) {
+        if (strncmp(result, pgn_result(gamestate), 8) != 0) {
+            fprintf(stderr, "Fuck it: %s\n", result);
+            return pgn_error_result_mismatch;
+        }
+    }
     
-    return 0;
+    return pgn_no_error;
 }
 
 int read_pgn(FILE *stream, GameState *gamestate) {
@@ -283,23 +315,7 @@
     fprintf(stream, "[Round \"%s\"]\n", "-");
     fprintf(stream, "[White \"%s\"]\n", pgn_player_name(gamestate, WHITE));
     fprintf(stream, "[Black \"%s\"]\n", pgn_player_name(gamestate, BLACK));
-
-    char *result;
-    if (gamestate->stalemate || gamestate->remis) {
-        result = "1/2-1/2";
-    } else if (gamestate->wresign) {
-        result = "0-1";
-    } else if (gamestate->bresign) {
-        result = "1-0";
-    } else if (gamestate->checkmate) {
-        if (current_color(gamestate) == BLACK) {
-            result = "1-0";
-        } else {
-            result = "0-1";
-        }
-    } else {
-        result = "*";
-    }
+    const char *result = pgn_result(gamestate);
     fprintf(stream, "[Result \"%s\"]\n\n", result);
 
     // TODO: add optional clock info

mercurial