src/chess/rules.c

changeset 170
bde99d803caf
parent 169
9962f5d98764
--- a/src/chess/rules.c	Thu Aug 06 14:45:55 2026 +0200
+++ b/src/chess/rules.c	Thu Aug 06 16:11:37 2026 +0200
@@ -225,6 +225,45 @@
     }
 }
 
+size_t piece_moves_allowed(const GameState *gamestate,
+        Row r, File f, Move *moves) {
+    Piece p = piece_at(gamestate, r, f);
+    Color c = piece_color(p);
+    switch (piece_type(p)) {
+        case KING:
+            return king_moves_allowed(gamestate, c, r, f, moves);
+        case QUEEN:
+            return queen_moves_allowed(gamestate, c, r, f, moves);
+        case ROOK:
+            return rook_moves_allowed(gamestate, c, r, f, moves);
+        case KNIGHT:
+            return knight_moves_allowed(gamestate, c, r, f, moves);
+        case BISHOP:
+            return bishop_moves_allowed(gamestate, c, r, f, moves);
+        case PAWN:
+            return pawn_moves_allowed(gamestate, c, r, f, moves);
+        default:
+            return 0;
+    }
+}
+
+static bool is_stalemate(const GameState *gamestate) {
+    Color next_player = gamestate->movecount % 2 == 0 ? WHITE : BLACK;
+
+    /* scan the board for pieces of the next player's color */
+    Move moves[QUEEN_MOVES_MAX];
+    for (Row r = 0; r < 8; r++) {
+        for (File f = 0; f < 8; f++) {
+            if (piece_color(piece_at(gamestate, r, f)) == next_player
+                && piece_moves_allowed(gamestate, r, f, moves) > 0) {
+                return false;
+            }
+        }
+    }
+
+    return true;
+}
+
 char getpiecechr(Piece piece) {
     switch (piece_type(piece)) {
     case ROOK: return 'R';
@@ -271,7 +310,7 @@
     }
 }
 
-void apply_move(GameState *gamestate, Move *move) {
+static void apply_move_internal(GameState *gamestate, Move *move) {
     /* en passant capture */
     if (move->capture && piece_type(move->piece) == PAWN &&
             piece_at(gamestate, mdst(move)) == 0) {
@@ -341,6 +380,13 @@
     gamestate->checkmate = move->checkmate;
 }
 
+void apply_move(GameState *gamestate, Move *move) {
+    apply_move_internal(gamestate, move);
+    if (!gamestate->checkmate) {
+        gamestate->stalemate = is_stalemate(gamestate);
+    }
+}
+
 void gamestate_at_move(const GameState *gamestate,
         unsigned move_number, GameState *replay) {
     gamestate_init(replay);
@@ -361,7 +407,7 @@
     /* simulate the move */
     GameState simulation = gamestate_copy_sim(gamestate);
     Move simmove = *move;
-    apply_move(&simulation, &simmove);
+    apply_move_internal(&simulation, &simmove);
 
     /* find the opposing king */
     Color piececolor = piece_color(move->piece);
@@ -417,7 +463,7 @@
                 move_retaliate.torow = er;
                 move_retaliate.tofile = ef;
                 move_retaliate.capture = true;
-                apply_move(&sim_retaliate, &move_retaliate);
+                apply_move_internal(&sim_retaliate, &move_retaliate);
                 canescape = !is_covered(&sim_retaliate, er, ef, piececolor);
                 gamestate_cleanup(&sim_retaliate);
                 continue;
@@ -560,7 +606,7 @@
     /* test if the move would expose our own king */
     GameState simulation = gamestate_copy_sim(gamestate);
     Move simmove = *move;
-    apply_move(&simulation, &simmove);
+    apply_move_internal(&simulation, &simmove);
     Color piececolor = piece_color(move->piece);
     Color oppcolor = opponent_color(piececolor);
     File kingfile = 0;
@@ -587,6 +633,10 @@
     }
     gamestate_cleanup(&simulation);
 
+    if (result != VALID_MOVE_SEMANTICS) {
+        return result;
+    }
+
     /* validate check and checkmate flags */
     int cocm = determine_check_or_checkmate(gamestate, move);
     if (cocm == 2) {
@@ -722,7 +772,7 @@
 
     GameState simulation = gamestate_copy_sim(gamestate);
     Move simmove = *move;
-    apply_move(&simulation, &simmove);
+    apply_move_internal(&simulation, &simmove);
     
     File kingfile = 0;
     Row kingrow = 0;
@@ -1136,7 +1186,7 @@
     return false;
 }
 
-size_t filter_moves_allowed(GameState *gamestate,
+size_t filter_moves_allowed(const GameState *gamestate,
         Color c, Row r, File f, Move *moves, moves_generator_func func) {
 
     /* worst case: the queen has the most moves */

mercurial