src/chess/rules.c

changeset 220
04da225a5677
parent 219
24b866230dd4
--- a/src/chess/rules.c	Sat Sep 05 12:36:04 2026 +0200
+++ b/src/chess/rules.c	Sat Sep 05 13:03:09 2026 +0200
@@ -144,7 +144,242 @@
     }
 }
 
-static bool check_stalemate(const GameState *gamestate) {
+
+/* 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 &&
+            piece_at(gamestate, move->tofile, move->torank) == 0) {
+        piece_remove(gamestate, move->tofile, move->fromrank);
+    }
+
+    /* remove old en passant threats */
+    for (File file = 0 ; file < 8 ; file++) {
+        enpassant_threat_remove(gamestate, file, 3);
+        enpassant_threat_remove(gamestate, file, 4);
+    }
+
+    /* move (and maybe capture or promote) */
+    piece_remove(gamestate, move->fromfile, move->fromrank);
+    if (move->promotion) {
+        piece_set(gamestate, move->tofile, move->torank, move->promotion);
+    } else {
+        piece_set(gamestate, move->tofile, move->torank, move->piece);
+    }
+
+    /* add new en passant threat */
+    if (piece_type(move->piece) == PAWN && (
+            (move->fromrank == 1 && move->torank == 3) ||
+            (move->fromrank == 6 && move->torank == 4))) {
+        enpassant_threat_add(gamestate, move->tofile, move->torank);
+    }
+
+    /* castling */
+    bool castling_happend = false;
+    if (piece_type(move->piece) == KING && move->fromfile == fileidx('e')) {
+        const Color color = piece_color(move->piece);
+        if (move->tofile == fileidx('g')) {
+            gamestate->board[move->torank][fileidx('h')] = 0;
+            gamestate->board[move->torank][fileidx('f')] = mkpiece(ROOK, color);
+            castling_happend = true;
+        } else if (move->tofile == fileidx('c')) {
+            gamestate->board[move->torank][fileidx('a')] = 0;
+            gamestate->board[move->torank][fileidx('d')] = mkpiece(ROOK, color);
+            castling_happend = true;
+        }
+    }
+    if (castling_happend) {
+        if (piece_color(move->piece) == WHITE) {
+            gamestate->castling.K = true;
+            gamestate->castling.Q = true;
+        } else {
+            gamestate->castling.k = true;
+            gamestate->castling.q = true;
+        }
+    } else {
+        if (piece_color(move->piece) == WHITE) {
+            if (move->fromrank == rankidx('1')) {
+                if (move->fromfile == fileidx('e')) {
+                    gamestate->castling.K = gamestate->castling.Q = true;
+                } else if (move->fromfile == fileidx('h')) {
+                    gamestate->castling.K = true;
+                } else if (move->fromfile == fileidx('a')) {
+                    gamestate->castling.Q = true;
+                }
+            }
+        } else {
+            if (move->fromrank == rankidx('8')) {
+                if (move->fromfile == fileidx('e')) {
+                    gamestate->castling.k = gamestate->castling.q = true;
+                } else if (move->fromfile == fileidx('h')) {
+                    gamestate->castling.k = true;
+                } else if (move->fromfile == fileidx('a')) {
+                    gamestate->castling.q = true;
+                }
+            }
+        }
+    }
+
+    /* add move to the moves array and the new position to the FEN array */
+    if (gamestate->movecount == gamestate->movecapacity) {
+        gamestate->movecapacity += 64; /* 32 more full moves */
+        gamestate->moves = realloc(gamestate->moves,
+            gamestate->movecapacity * sizeof(Move));
+        gamestate->fen = realloc(gamestate->fen,
+            gamestate->movecapacity * sizeof(char*));
+    }
+
+    /* copy the move data into the game's move array */
+    Move *melem = &gamestate->moves[gamestate->movecount];
+    *melem = *move;
+    calc_movetime(gamestate, melem);
+
+    /* important: only "add" the move after calculating the time! */
+    gamestate->movecount++;
+
+    /* calculate the FEN of the new position and store it in the FEN array */
+    char fen[FEN_MAX_LENGTH];
+    fen_compute(fen, gamestate);
+    gamestate->fen[gamestate->movecount - 1] = strdup(fen);
+}
+
+/* return 0 = no check, 1 = check, 2 = checkmate */
+static int determine_check_or_checkmate(
+        const GameState *gamestate, const Move *move) {
+
+    /* either simulate one more move or check for current state */
+    Color piececolor, oppcolor;
+    GameState simulation = gamestate_copy_sim(gamestate);
+    if (move != NULL) {
+        piececolor = piece_color(move->piece);
+        oppcolor = opponent_color(piececolor);
+        Move simmove = *move;
+        apply_move_internal(&simulation, &simmove);
+    } else {
+        oppcolor = current_color(gamestate);
+        piececolor = opponent_color(oppcolor);
+    }
+
+    /* find the opposing king */
+    File opkingfile = 0;
+    Rank opkingrank = 0;
+    for (Rank rank = 0 ; rank < 8 ; rank++) {
+        for (File file = 0 ; file < 8 ; file++) {
+            Piece p = piece_at(&simulation, file, rank);
+            if (p == mkpiece(KING, oppcolor)) {
+                opkingfile = file;
+                opkingrank = rank;
+            }
+        }
+    }
+
+    /* determine if the opposing king is now threatened */
+    Move threats[16];
+    size_t threatcount;
+    bool incheck = get_threats(&simulation, opkingfile, opkingrank,
+        piececolor, threats, &threatcount);
+
+    if (!incheck) {
+        gamestate_cleanup(&simulation);
+        return 0;
+    }
+
+    /* determine possible escape fields */
+    bool canescape = false;
+    for (int dr = -1 ; dr <= 1 && !canescape ; dr++) {
+        for (int df = -1 ; df <= 1 && !canescape ; df++) {
+            if (dr == 0 && df == 0) continue;
+            Rank er = opkingrank + dr;
+            File ef = opkingfile + df;
+            if (!isidx(er) || !isidx(ef)) continue;
+
+            /* check if piece of the king's color blocks the field */
+            if (piece_color(simulation.board[er][ef]) == oppcolor)
+                continue;
+
+            /* check if escape field is already covered (threatened) */
+            if (is_covered(&simulation, ef, er, piececolor))
+                continue;
+
+            /* check if an attacking piece blocks the field */
+            if (piece_color(simulation.board[er][ef]) == piececolor) {
+                /* test if the king can fight back */
+                GameState sim_retaliate = gamestate_copy_sim(&simulation);
+                Move move_retaliate = {0};
+                move_retaliate.piece = mkpiece(KING, oppcolor);
+                move_retaliate.fromrank = opkingrank;
+                move_retaliate.fromfile = opkingfile;
+                move_retaliate.torank = er;
+                move_retaliate.tofile = ef;
+                move_retaliate.capture = true;
+                apply_move_internal(&sim_retaliate, &move_retaliate);
+                canescape = !is_covered(&sim_retaliate, ef, er, piececolor);
+                gamestate_cleanup(&sim_retaliate);
+                continue;
+            }
+
+            /* the field is not covered and unoccupied */
+            canescape = true;
+        }
+    }
+
+    /* can't escape, can the king be rescued? */
+    if (!canescape && threatcount == 1) {
+        canescape = is_protected(&simulation,
+            threats[0].fromfile, threats[0].fromrank, oppcolor);
+    }
+
+    /* can't capture, can he block? */
+    if (!canescape && threatcount == 1) {
+        Move *threat = &(threats[0]);
+        unsigned tptype = piece_type(threat->piece);
+
+        /* knight, pawns and the king cannot be blocked */
+        if (tptype == BISHOP || tptype == ROOK || tptype == QUEEN) {
+            if (threat->fromrank == threat->torank) {
+                /* rook aspect (on rank) */
+                int d = threat->tofile > threat->fromfile ? 1 : -1;
+                File file = threat->fromfile;
+                while (!canescape && file != threat->tofile - d) {
+                    file += d;
+                    canescape |= is_protected(&simulation,
+                        file, threat->torank, oppcolor);
+                }
+            } else if (threat->fromfile == threat->tofile) {
+                /* rook aspect (on file) */
+                int d = threat->torank > threat->fromrank ? 1 : -1;
+                Rank rank = threat->fromrank;
+                while (!canescape && rank != threat->torank - d) {
+                    rank += d;
+                    canescape |= is_protected(&simulation,
+                        threat->tofile, rank, oppcolor);
+                }
+            } else {
+                /* bishop aspect */
+                int dr = threat->torank > threat->fromrank ? 1 : -1;
+                int df = threat->tofile > threat->fromfile ? 1 : -1;
+
+                Rank rank = threat->fromrank;
+                File file = threat->fromfile;
+                while (!canescape && file != threat->tofile - df
+                        && rank != threat->torank - dr) {
+                    rank += dr;
+                    file += df;
+                    canescape |= is_protected(&simulation,
+                        file, rank, oppcolor);
+                }
+            }
+        }
+    }
+    gamestate_cleanup(&simulation);
+    return canescape ? 1 : 2;
+}
+
+bool check_checkmate(const GameState *gamestate) {
+    return determine_check_or_checkmate(gamestate, NULL) == 2;
+}
+
+bool check_stalemate(const GameState *gamestate) {
     Color next_player = current_color(gamestate);
 
     /* scan the board for pieces of the next player's color */
@@ -313,104 +548,6 @@
     }
 }
 
-/* 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 &&
-            piece_at(gamestate, move->tofile, move->torank) == 0) {
-        piece_remove(gamestate, move->tofile, move->fromrank);
-    }
-    
-    /* remove old en passant threats */
-    for (File file = 0 ; file < 8 ; file++) {
-        enpassant_threat_remove(gamestate, file, 3);
-        enpassant_threat_remove(gamestate, file, 4);
-    }
-    
-    /* move (and maybe capture or promote) */
-    piece_remove(gamestate, move->fromfile, move->fromrank);
-    if (move->promotion) {
-        piece_set(gamestate, move->tofile, move->torank, move->promotion);
-    } else {
-        piece_set(gamestate, move->tofile, move->torank, move->piece);
-    }
-
-    /* add new en passant threat */
-    if (piece_type(move->piece) == PAWN && (
-            (move->fromrank == 1 && move->torank == 3) ||
-            (move->fromrank == 6 && move->torank == 4))) {
-        enpassant_threat_add(gamestate, move->tofile, move->torank);
-    }
-    
-    /* castling */
-    bool castling_happend = false;
-    if (piece_type(move->piece) == KING && move->fromfile == fileidx('e')) {
-        const Color color = piece_color(move->piece);
-        if (move->tofile == fileidx('g')) {
-            gamestate->board[move->torank][fileidx('h')] = 0;
-            gamestate->board[move->torank][fileidx('f')] = mkpiece(ROOK, color);
-            castling_happend = true;
-        } else if (move->tofile == fileidx('c')) {
-            gamestate->board[move->torank][fileidx('a')] = 0;
-            gamestate->board[move->torank][fileidx('d')] = mkpiece(ROOK, color);
-            castling_happend = true;
-        }
-    }
-    if (castling_happend) {
-        if (piece_color(move->piece) == WHITE) {
-            gamestate->castling.K = true;
-            gamestate->castling.Q = true;
-        } else {
-            gamestate->castling.k = true;
-            gamestate->castling.q = true;
-        }
-    } else {
-        if (piece_color(move->piece) == WHITE) {
-            if (move->fromrank == rankidx('1')) {
-                if (move->fromfile == fileidx('e')) {
-                    gamestate->castling.K = gamestate->castling.Q = true;
-                } else if (move->fromfile == fileidx('h')) {
-                    gamestate->castling.K = true;
-                } else if (move->fromfile == fileidx('a')) {
-                    gamestate->castling.Q = true;
-                }
-            }
-        } else {
-            if (move->fromrank == rankidx('8')) {
-                if (move->fromfile == fileidx('e')) {
-                    gamestate->castling.k = gamestate->castling.q = true;
-                } else if (move->fromfile == fileidx('h')) {
-                    gamestate->castling.k = true;
-                } else if (move->fromfile == fileidx('a')) {
-                    gamestate->castling.q = true;
-                }
-            }
-        }
-    }
-    
-    /* add move to the moves array and the new position to the FEN array */
-    if (gamestate->movecount == gamestate->movecapacity) {
-        gamestate->movecapacity += 64; /* 32 more full moves */
-        gamestate->moves = realloc(gamestate->moves,
-            gamestate->movecapacity * sizeof(Move));
-        gamestate->fen = realloc(gamestate->fen,
-            gamestate->movecapacity * sizeof(char*));
-    }
-
-    /* copy the move data into the game's move array */
-    Move *melem = &gamestate->moves[gamestate->movecount];
-    *melem = *move;
-    calc_movetime(gamestate, melem);
-
-    /* important: only "add" the move after calculating the time! */
-    gamestate->movecount++;
-
-    /* calculate the FEN of the new position and store it in the FEN array */
-    char fen[FEN_MAX_LENGTH];
-    fen_compute(fen, gamestate);
-    gamestate->fen[gamestate->movecount - 1] = strdup(fen);
-}
-
 void apply_move(GameState *gamestate, Move *move) {
     apply_move_internal(gamestate, move);
 
@@ -440,132 +577,6 @@
     }
 }
 
-/* return 0 = no check, 1 = check, 2 = checkmate */
-static int determine_check_or_checkmate(
-        const GameState *gamestate, const Move *move) {
-
-    /* simulate the move */
-    GameState simulation = gamestate_copy_sim(gamestate);
-    Move simmove = *move;
-    apply_move_internal(&simulation, &simmove);
-
-    /* find the opposing king */
-    Color piececolor = piece_color(move->piece);
-    Color oppcolor = opponent_color(piececolor);
-    File opkingfile = 0;
-    Rank opkingrank = 0;
-    for (Rank rank = 0 ; rank < 8 ; rank++) {
-        for (File file = 0 ; file < 8 ; file++) {
-            Piece p = piece_at(&simulation, file, rank);
-            if (p == mkpiece(KING, oppcolor)) {
-                opkingfile = file;
-                opkingrank = rank;
-            }
-        }
-    }
-
-    /* determine if the opposing king is now threatened */
-    Move threats[16];
-    size_t threatcount;
-    bool incheck = get_threats(&simulation, opkingfile, opkingrank,
-        piececolor, threats, &threatcount);
-
-    if (!incheck) {
-        gamestate_cleanup(&simulation);
-        return 0;
-    }
-
-    /* determine possible escape fields */
-    bool canescape = false;
-    for (int dr = -1 ; dr <= 1 && !canescape ; dr++) {
-        for (int df = -1 ; df <= 1 && !canescape ; df++) {
-            if (dr == 0 && df == 0) continue;
-            Rank er = opkingrank + dr;
-            File ef = opkingfile + df;
-            if (!isidx(er) || !isidx(ef)) continue;
-
-            /* check if piece of the king's color blocks the field */
-            if (piece_color(simulation.board[er][ef]) == oppcolor)
-                continue;
-
-            /* check if escape field is already covered (threatened) */
-            if (is_covered(&simulation, ef, er, piececolor))
-                continue;
-
-            /* check if an attacking piece blocks the field */
-            if (piece_color(simulation.board[er][ef]) == piececolor) {
-                /* test if the king can fight back */
-                GameState sim_retaliate = gamestate_copy_sim(&simulation);
-                Move move_retaliate = {0};
-                move_retaliate.piece = mkpiece(KING, oppcolor);
-                move_retaliate.fromrank = opkingrank;
-                move_retaliate.fromfile = opkingfile;
-                move_retaliate.torank = er;
-                move_retaliate.tofile = ef;
-                move_retaliate.capture = true;
-                apply_move_internal(&sim_retaliate, &move_retaliate);
-                canescape = !is_covered(&sim_retaliate, ef, er, piececolor);
-                gamestate_cleanup(&sim_retaliate);
-                continue;
-            }
-
-            /* the field is not covered and unoccupied */
-            canescape = true;
-        }
-    }
-
-    /* can't escape, can the king be rescued? */
-    if (!canescape && threatcount == 1) {
-        canescape = is_protected(&simulation,
-            threats[0].fromfile, threats[0].fromrank, oppcolor);
-    }
-
-    /* can't capture, can he block? */
-    if (!canescape && threatcount == 1) {
-        Move *threat = &(threats[0]);
-        unsigned tptype = piece_type(threat->piece);
-
-        /* knight, pawns and the king cannot be blocked */
-        if (tptype == BISHOP || tptype == ROOK || tptype == QUEEN) {
-            if (threat->fromrank == threat->torank) {
-                /* rook aspect (on rank) */
-                int d = threat->tofile > threat->fromfile ? 1 : -1;
-                File file = threat->fromfile;
-                while (!canescape && file != threat->tofile - d) {
-                    file += d;
-                    canescape |= is_protected(&simulation,
-                        file, threat->torank, oppcolor);
-                }
-            } else if (threat->fromfile == threat->tofile) {
-                /* rook aspect (on file) */
-                int d = threat->torank > threat->fromrank ? 1 : -1;
-                Rank rank = threat->fromrank;
-                while (!canescape && rank != threat->torank - d) {
-                    rank += d;
-                    canescape |= is_protected(&simulation,
-                        threat->tofile, rank, oppcolor);
-                }
-            } else {
-                /* bishop aspect */
-                int dr = threat->torank > threat->fromrank ? 1 : -1;
-                int df = threat->tofile > threat->fromfile ? 1 : -1;
-
-                Rank rank = threat->fromrank;
-                File file = threat->fromfile;
-                while (!canescape && file != threat->tofile - df
-                        && rank != threat->torank - dr) {
-                    rank += dr;
-                    file += df;
-                    canescape |= is_protected(&simulation,
-                        file, rank, oppcolor);
-                }
-            }
-        }
-    }
-    gamestate_cleanup(&simulation);
-    return canescape ? 1 : 2;
-}
-
 
 void format_move(const GameState *gamestate, Move *move) {
     char *string = &(move->string[0]);

mercurial