src/chess/rules.c

changeset 173
e541b6002933
parent 170
bde99d803caf
--- a/src/chess/rules.c	Fri Aug 14 11:35:36 2026 +0200
+++ b/src/chess/rules.c	Fri Aug 14 13:40:26 2026 +0200
@@ -247,7 +247,7 @@
     }
 }
 
-static bool is_stalemate(const GameState *gamestate) {
+static bool check_stalemate(const GameState *gamestate) {
     Color next_player = gamestate->movecount % 2 == 0 ? WHITE : BLACK;
 
     /* scan the board for pieces of the next player's color */
@@ -264,6 +264,39 @@
     return true;
 }
 
+static size_t fen_len_without_moves(const char *fen) {
+    size_t len = strlen(fen);
+    /* find first space that separates the two move counters */
+    while (--len > 0 && fen[len] != ' ') {}
+    /* find second space that separates the move counters from the rest */
+    while (--len > 0 && fen[len] != ' ') {}
+    return len;
+}
+
+bool check_threefold_repetition(const GameState *gamestate) {
+    if (gamestate->movecount < 3) {
+        return false;
+    }
+
+    /* take the part of the FEN that only describes the board */
+    const char *fen = gamestate->fen[gamestate->movecount - 1];
+    size_t fen_len = fen_len_without_moves(fen);
+
+    // TODO: develop a test case that involves en passant and add support here
+
+    /* count the previous occurrences */
+    unsigned c = 0;
+    for (size_t i = gamestate->movecount - 1; i > 0;) {
+        const char *other = gamestate->fen[--i];
+        size_t other_len = fen_len_without_moves(other);
+        if (fen_len != other_len) continue;
+        if (strncmp(fen, other, fen_len) == 0) {
+            if (++c == 2) return true;
+        }
+    }
+    return false;
+}
+
 char getpiecechr(Piece piece) {
     switch (piece_type(piece)) {
     case ROOK: return 'R';
@@ -310,6 +343,7 @@
     }
 }
 
+/* applies the move without recalculating gamestate flags */
 static void apply_move_internal(GameState *gamestate, Move *move) {
     /* en passant capture */
     if (move->capture && piece_type(move->piece) == PAWN &&
@@ -375,15 +409,18 @@
 
     /* important: only "add" the move after calculating the time! */
     gamestate->movecount++;
-
-    /* did this move checkmate the other king? */
-    gamestate->checkmate = move->checkmate;
 }
 
 void apply_move(GameState *gamestate, Move *move) {
     apply_move_internal(gamestate, move);
-    if (!gamestate->checkmate) {
-        gamestate->stalemate = is_stalemate(gamestate);
+
+    /* calculate gamestate flags in order of efficiency */
+    if (move->checkmate) {
+        gamestate->checkmate = true;
+    } else if (check_threefold_repetition(gamestate)) {
+        gamestate->threefold = true;
+    } else if (check_stalemate(gamestate)) {
+        gamestate->stalemate = true;
     }
 }
 
@@ -1181,11 +1218,6 @@
     }
 }
 
-bool check_threefold_repetition(const GameState *gamestate) {
-    // TODO: implement threefold repetition detection
-    return false;
-}
-
 size_t filter_moves_allowed(const GameState *gamestate,
         Color c, Row r, File f, Move *moves, moves_generator_func func) {
 

mercurial