src/chess/rules.c

changeset 163
2a6d83f4677e
parent 161
3ff96fec144a
--- a/src/chess/rules.c	Thu Jul 30 18:34:15 2026 +0200
+++ b/src/chess/rules.c	Thu Jul 30 19:17:38 2026 +0200
@@ -79,7 +79,7 @@
     gamestate->movecount = gamestate->movecapacity = 0;
 }
 
-static GameState gamestate_copy_sim(GameState *gamestate) {
+static GameState gamestate_copy_sim(const GameState *gamestate) {
     GameState simulation = *gamestate;
 
     /* create new move and position lists for the simulation */
@@ -102,12 +102,12 @@
     return simulation;
 }
 
-Color current_color(GameState *gamestate) {
+Color current_color(const GameState *gamestate) {
     return (gamestate->movecount % 2 == 0) ? WHITE : BLACK;
 }
 
 /* MUST be called BETWEEN validating AND applying a move to work correctly */
-static void format_move(GameState *gamestate, Move *move) {
+static void format_move(const GameState *gamestate, Move *move) {
     char *string = &(move->string[0]);
     
     /* at least 8 characters should be available, wipe them out */
@@ -136,18 +136,18 @@
             }
         } else if (piece_type(move->piece) != KING) {
             /* resolve ambiguities, if any */
-            Move threats[16];
-            size_t threatcount;
-            if (get_threats(gamestate, move->torow, move->tofile,
-                    piece_color(move->piece), threats, &threatcount)) {
+            Move candidates[16];
+            size_t ccount;
+            if (get_candidates(gamestate, move->torow, move->tofile,
+                    piece_color(move->piece), candidates, &ccount)) {
                 unsigned int ambrows = 0, ambfiles = 0, ambpiece = 0;
-                for (size_t i = 0 ; i < threatcount ; i++) {
-                    if (threats[i].piece == move->piece) {
+                for (size_t i = 0 ; i < ccount ; i++) {
+                    if (candidates[i].piece == move->piece) {
                         ambpiece++;
-                        if (threats[i].fromrow == move->fromrow) {
+                        if (candidates[i].fromrow == move->fromrow) {
                             ambrows++;
                         }
-                        if (threats[i].fromfile == move->fromfile) {
+                        if (candidates[i].fromfile == move->fromfile) {
                             ambfiles++;
                         }
                     }
@@ -187,8 +187,10 @@
     }
     
     /* check? */
-    if (move->check) {
-        string[idx++] = gamestate->checkmate?'#':'+';
+    if (move->checkmate) {
+        string[idx++] = '#';
+    } else if (move->check) {
+        string[idx++] = '+';
     }
 }
 
@@ -269,15 +271,7 @@
     }
 }
 
-// TODO: check if we really need this "simulate" flag for performance
-static void apply_move_impl(GameState *gamestate, Move *move, bool simulate) {
-    /* format move before moving (s.t. ambiguities can be resolved) */
-    if (!simulate) {
-        if (!move->string[0]) {
-            format_move(gamestate, move);
-        }
-    }
-
+void apply_move(GameState *gamestate, Move *move) {
     /* en passant capture */
     if (move->capture && piece_type(move->piece) == PAWN &&
             piece_at(gamestate, mdst(move)) == 0) {
@@ -342,13 +336,12 @@
 
     /* 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_impl(gamestate, move, false);
-}
-
-void gamestate_at_move(GameState *gamestate,
+void gamestate_at_move(const GameState *gamestate,
         unsigned move_number, GameState *replay) {
     gamestate_init(replay);
     memcpy(&replay->info, &gamestate->info, sizeof(GameInfo));
@@ -357,11 +350,137 @@
         move_number = gamestate->movecount;
     }
     for (unsigned i = 0 ; i < move_number ; i++) {
-        apply_move_impl(replay, &(gamestate->moves[i]), true);
+        apply_move(replay, &(gamestate->moves[i]));
     }
 }
 
-static int validate_move_rules(GameState *gamestate, Move *move) {
+/* 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(&simulation, &simmove);
+
+    /* find the opposing king */
+    Color piececolor = piece_color(move->piece);
+    Color oppcolor = opponent_color(piececolor);
+    File opkingfile = 0;
+    Row opkingrow = 0;
+    for (Row row = 0 ; row < 8 ; row++) {
+        for (File file = 0 ; file < 8 ; file++) {
+            Piece p = piece_at(&simulation, row, file);
+            if (p == mkpiece(KING, oppcolor)) {
+                opkingfile = file;
+                opkingrow = row;
+            }
+        }
+    }
+
+    /* determine if the opposing king is now threatened */
+    Move threats[16];
+    size_t threatcount;
+    bool incheck = get_threats(&simulation, opkingrow, opkingfile,
+        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;
+            Row er = opkingrow + 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, er, ef, 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.fromrow = opkingrow;
+                move_retaliate.fromfile = opkingfile;
+                move_retaliate.torow = er;
+                move_retaliate.tofile = ef;
+                move_retaliate.capture = true;
+                apply_move(&sim_retaliate, &move_retaliate);
+                canescape = !is_covered(&sim_retaliate, er, ef, 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].fromrow, threats[0].fromfile, 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->fromrow == threat->torow) {
+                /* rook aspect (on row) */
+                int d = threat->tofile > threat->fromfile ? 1 : -1;
+                File file = threat->fromfile;
+                while (!canescape && file != threat->tofile - d) {
+                    file += d;
+                    canescape |= is_protected(&simulation,
+                        threat->torow, file, oppcolor);
+                }
+            } else if (threat->fromfile == threat->tofile) {
+                /* rook aspect (on file) */
+                int d = threat->torow > threat->fromrow ? 1 : -1;
+                Row row = threat->fromrow;
+                while (!canescape && row != threat->torow - d) {
+                    row += d;
+                    canescape |= is_protected(&simulation,
+                        row, threat->tofile, oppcolor);
+                }
+            } else {
+                /* bishop aspect */
+                int dr = threat->torow > threat->fromrow ? 1 : -1;
+                int df = threat->tofile > threat->fromfile ? 1 : -1;
+
+                Row row = threat->fromrow;
+                File file = threat->fromfile;
+                while (!canescape && file != threat->tofile - df
+                        && row != threat->torow - dr) {
+                    row += dr;
+                    file += df;
+                    canescape |= is_protected(&simulation, row, file,
+                        oppcolor);
+                }
+            }
+        }
+    }
+    gamestate_cleanup(&simulation);
+    return canescape ? 1 : 2;
+}
+
+static int validate_move_rules(const GameState *gamestate, const Move *move) {
     assert((move->piece & ~(PIECE_MASK|COLOR_MASK)) == 0);
 
     /* validate indices (don't trust opponent) */
@@ -369,12 +488,12 @@
         !isidx(move->torow) || !isidx(move->tofile)) {
         return INVALID_MOVE_SYNTAX;
     }
-    
+
     /* must move */
     if (move->fromfile == move->tofile && move->fromrow == move->torow) {
         return INVALID_MOVE_SYNTAX;
     }
-    
+
     /* does piece exist */
     if (piece_at(gamestate, msrc(move)) != move->piece) {
         return PIECE_NOT_FOUND;
@@ -382,7 +501,7 @@
 
     /* is there any piece at the destination? */
     Piece piece_at_dst = piece_at(gamestate, mdst(move));
-    
+
     /* can't capture own pieces */
     if (piece_color(piece_at_dst) == piece_color(move->piece)) {
         return RULES_VIOLATED;
@@ -397,188 +516,100 @@
             return INVALID_MOVE_SYNTAX;
         }
     }
-    
+
     /* validate individual rules */
     bool chkrules;
     switch (piece_type(move->piece)) {
-    case PAWN:
-        chkrules = pawn_chkrules(gamestate, move) &&
-            !pawn_isblocked(gamestate, move);
-        break;
-    case ROOK:
-        chkrules = rook_chkrules(move) &&
-            !rook_isblocked(gamestate, move);
-        break;
-    case KNIGHT:
-        chkrules = knight_chkrules(move); /* knight is never blocked */
-        break;
-    case BISHOP:
-        chkrules = bishop_chkrules(move) &&
-            !bishop_isblocked(gamestate, move);
-        break;
-    case QUEEN:
-        chkrules = queen_chkrules(move) &&
-            !queen_isblocked(gamestate, move);
-        break;
-    case KING:
-        chkrules = king_chkrules(gamestate, move) &&
-            !king_isblocked(gamestate, move);
-        break;
-    default:
-        return INVALID_MOVE_SYNTAX;
+        case PAWN:
+            chkrules = pawn_chkrules(gamestate, move) &&
+                !pawn_isblocked(gamestate, move);
+            break;
+        case ROOK:
+            chkrules = rook_chkrules(move) &&
+                !rook_isblocked(gamestate, move);
+            break;
+        case KNIGHT:
+            chkrules = knight_chkrules(move) &&
+                !knight_isblocked(gamestate, move);
+            break;
+        case BISHOP:
+            chkrules = bishop_chkrules(move) &&
+                !bishop_isblocked(gamestate, move);
+            break;
+        case QUEEN:
+            chkrules = queen_chkrules(move) &&
+                !queen_isblocked(gamestate, move);
+            break;
+        case KING:
+            chkrules = king_chkrules(gamestate, move) &&
+                !king_isblocked(gamestate, move);
+            break;
+        default:
+            return INVALID_MOVE_SYNTAX;
+    }
+
+    /* cancel processing to save resources */
+    if (!chkrules) {
+        return RULES_VIOLATED;
     }
     
-    return chkrules ? VALID_MOVE_SEMANTICS : RULES_VIOLATED;
-}
-
-int validate_move(GameState *gamestate, Move *move) {
-    
-    int result = validate_move_rules(gamestate, move);
-    
-    /* cancel processing to save resources */
-    if (result != VALID_MOVE_SEMANTICS) {
-        return result;
-    }
-    
-    /* simulate move for check validation */
+    /* test if the move would expose our own king */
     GameState simulation = gamestate_copy_sim(gamestate);
     Move simmove = *move;
-    apply_move_impl(&simulation, &simmove, true);
-    
-    /* find kings for check validation */
+    apply_move(&simulation, &simmove);
     Color piececolor = piece_color(move->piece);
     Color oppcolor = opponent_color(piececolor);
-    
-    File mykingfile = 0, opkingfile = 0;
-    Row  mykingrow = 0, opkingrow = 0;
+    File kingfile = 0;
+    Row kingrow = 0;
     for (Row row = 0 ; row < 8 ; row++) {
         for (File file = 0 ; file < 8 ; file++) {
             Piece p = piece_at(&simulation, row, file);
             if (p == mkpiece(KING, piececolor)) {
-                mykingfile = file;
-                mykingrow = row;
-            } else if (p == mkpiece(KING, oppcolor)) {
-                opkingfile = file;
-                opkingrow = row;
+                kingfile = file;
+                kingrow = row;
             }
         }
     }
-    
-    /* don't move into or stay in check position */
-    if (is_covered(&simulation, mykingrow, mykingfile, oppcolor)) {
+    int result = VALID_MOVE_SEMANTICS;
+    if (is_covered(&simulation, kingrow, kingfile, oppcolor)) {
         gamestate_cleanup(&simulation);
         if (piece_type(move->piece) == KING) {
-            return KING_MOVES_INTO_CHECK;
+            result = KING_MOVES_INTO_CHECK;
         } else {
-            if (gamestate->moves[gamestate->movecount - 1].check) {
-                return KING_IN_CHECK;
+            if (is_check_position(gamestate)) {
+                result = KING_IN_CHECK;
             } else {
-                return PIECE_PINNED;
+                result = PIECE_PINNED;
             }
         }
     }
-    
-    /* correct check and checkmate flags (move is still valid) */
-    Move threats[16];
-    size_t threatcount;
-    move->check = get_threats(&simulation, opkingrow, opkingfile,
-        piececolor, threats, &threatcount);
-    
-    if (move->check) {
-        /* 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;
-                if (!isidx(opkingrow + dr)) continue;
-                if (!isidx(opkingfile + df)) continue;
-                Row er = opkingrow + dr;
-                File ef = opkingfile + df;
+    gamestate_cleanup(&simulation);
 
-                /* check if escape field is already covered (threatened) */
-                if (is_covered(&simulation, er, ef, piececolor))
-                    continue;
+    return result;
+}
 
-                /* check if piece of the king's color blocks the field */
-                if (piece_color(simulation.board[er][ef]) == oppcolor)
-                    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.fromrow = opkingrow;
-                    move_retaliate.fromfile = opkingfile;
-                    move_retaliate.torow = er;
-                    move_retaliate.tofile = ef;
-                    move_retaliate.capture = 1;
-                    apply_move_impl(&sim_retaliate, &move_retaliate, true);
-                    canescape = !is_covered(&sim_retaliate, er, ef, piececolor);
-                    gamestate_cleanup(&sim_retaliate);
-                } else {
-                    /* the field is not covered and unoccupied */
-                    canescape = true;
-                }
-            }
-        }
+int validate_move(const GameState *gamestate, const Move *move) {
+    int result = validate_move_rules(gamestate, move);
+    if (result != VALID_MOVE_SEMANTICS) {
+        return result;
+    }
 
-        /* can't escape, can the king be rescued? */
-        if (!canescape && threatcount == 1) {
-            canescape = is_protected(&simulation,
-                    threats[0].fromrow, threats[0].fromfile, oppcolor);
+    /* validate check and checkmate flags */
+    int cocm = determine_check_or_checkmate(gamestate, move);
+    if (cocm == 2) {
+        if (!move->checkmate) {
+            return MISSING_CHECKMATE;
         }
-        
-        /* 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->fromrow == threat->torow) {
-                    /* rook aspect (on row) */
-                    int d = threat->tofile > threat->fromfile ? 1 : -1;
-                    File file = threat->fromfile;
-                    while (!canescape && file != threat->tofile - d) {
-                        file += d;
-                        canescape |= is_protected(&simulation,
-                            threat->torow, file, oppcolor);
-                    }
-                } else if (threat->fromfile == threat->tofile) {
-                    /* rook aspect (on file) */
-                    int d = threat->torow > threat->fromrow ? 1 : -1;
-                    Row row = threat->fromrow;
-                    while (!canescape && row != threat->torow - d) {
-                        row += d;
-                        canescape |= is_protected(&simulation,
-                            row, threat->tofile, oppcolor);
-                    }
-                } else {
-                    /* bishop aspect */
-                    int dr = threat->torow > threat->fromrow ? 1 : -1;
-                    int df = threat->tofile > threat->fromfile ? 1 : -1;
+    } else if (cocm == 1) {
+        if (!move->check) {
+            return MISSING_CHECK;
+        }
+    } else if (move->checkmate) {
+        return INVALID_CHECKMATE;
+    } else if (move->check) {
+        return INVALID_CHECK;
+    }
 
-                    Row row = threat->fromrow;
-                    File file = threat->fromfile;
-                    while (!canescape && file != threat->tofile - df
-                            && row != threat->torow - dr) {
-                        row += dr;
-                        file += df;
-                        canescape |= is_protected(&simulation, row, file,
-                            oppcolor);
-                    }
-                }
-            }
-        }
-            
-        if (!canescape) {
-            gamestate->checkmate = true;
-        }
-    }
-    
-    gamestate_cleanup(&simulation);
     
     return VALID_MOVE_SEMANTICS;
 }
@@ -591,16 +622,16 @@
     gamestate->board[row][file] = piece;
 }
 
-bool get_threats(GameState *gamestate, Row row, File file,
-        Color color, Move *threats, size_t *threatcount) {
+bool get_candidates(const GameState *gamestate, Row row, File file,
+        Color color, Move *moves, size_t *movecount) {
     Move candidates[32];
     size_t ccount = 0;
     for (Row r = 0 ; r < 8 ; r++) {
         for (File f = 0 ; f < 8 ; f++) {
-            if (piece_color(gamestate->board[r][f]) == color) {
+            Piece p = piece_at(gamestate, r, f);
+            if (piece_color(p) == color) {
                 /* non-capturing move */
                 memset(&(candidates[ccount]), 0, sizeof(Move));
-                Piece p = piece_at(gamestate, r, f);
                 candidates[ccount].piece = p;
                 candidates[ccount].fromrow = r;
                 candidates[ccount].fromfile = f;
@@ -615,7 +646,55 @@
                 /* capturing move */
                 memcpy(&(candidates[ccount]),
                     &(candidates[ccount-1]), sizeof(Move));
-                candidates[ccount].capture = 1;
+                candidates[ccount].capture = true;
+                ccount++;
+            }
+        }
+    }
+
+    if (movecount) {
+        *movecount = 0;
+    }
+
+    bool result = false;
+
+    for (size_t i = 0 ; i < ccount ; i++) {
+        if (validate_move_rules(gamestate, &(candidates[i]))
+                == VALID_MOVE_SEMANTICS) {
+            result = true;
+            if (moves && movecount) {
+                moves[(*movecount)++] = candidates[i];
+            }
+        }
+    }
+
+    return result;
+}
+
+bool get_threats(const GameState *gamestate, Row row, File file,
+        Color color, Move *threats, size_t *threatcount) {
+
+    /* simulate a capturing move on the target position */
+    Color opcolor = opponent_color(color);
+    GameState simulation = gamestate_copy_sim(gamestate);
+    if (piece_color(piece_at(&simulation, row, file)) != opcolor) {
+        /* set a fake pawn if the field is not occupied by the opponent */
+        piece_set(&simulation, row, file, mkpiece(PAWN, opcolor));
+    }
+
+    Move candidates[16];
+    size_t ccount = 0;
+    for (Row r = 0 ; r < 8 ; r++) {
+        for (File f = 0 ; f < 8 ; f++) {
+            Piece p = piece_at(&simulation, r, f);
+            if (piece_color(p) == color) {
+                memset(&(candidates[ccount]), 0, sizeof(Move));
+                candidates[ccount].piece = p;
+                candidates[ccount].fromrow = r;
+                candidates[ccount].fromfile = f;
+                candidates[ccount].torow = row;
+                candidates[ccount].tofile = file;
+                candidates[ccount].capture = true;
                 ccount++;
             }
         }
@@ -625,11 +704,10 @@
         *threatcount = 0;
     }
     
-    
     bool result = false;
     
     for (size_t i = 0 ; i < ccount ; i++) {
-        if (validate_move_rules(gamestate, &(candidates[i]))
+        if (validate_move_rules(&simulation, &(candidates[i]))
                 == VALID_MOVE_SEMANTICS) {
             result = true;
             if (threats && threatcount) {
@@ -637,11 +715,13 @@
             }
         }
     }
+
+    gamestate_cleanup(&simulation);
     
     return result;
 }
 
-bool is_pinned(GameState *gamestate, Move *move) {
+bool is_pinned(const GameState *gamestate, const Move *move) {
     Color color = piece_color(move->piece);
 
     GameState simulation = gamestate_copy_sim(gamestate);
@@ -666,7 +746,7 @@
     return covered;
 }
 
-bool get_real_threats(GameState *gamestate, Row row, File file,
+bool get_real_threats(const GameState *gamestate, Row row, File file,
         Color color, Move *threats, size_t *threatcount) {
     
     if (threatcount) {
@@ -710,48 +790,48 @@
     }
 }
 
-static int getlocation(GameState *gamestate, Move *move) {   
+static int getlocation(const GameState *gamestate, Move *move) {
 
     Color color = piece_color(move->piece);
     bool incheck = false;
     if (gamestate->movecount > 0) {
-        incheck = gamestate->moves[gamestate->movecount - 1].check;
+        incheck = is_check_position(gamestate);
     }
     
-    Move threats[16], *threat = NULL;
-    size_t threatcount;
+    Move candidates[16], *candidate = NULL;
+    size_t candidatecount;
 
-    /* determine all threats and sort out the unreal threats on our own */
-    if (get_threats(gamestate, move->torow, move->tofile, color,
-            threats, &threatcount)) {
+    /* determine all candidate moves and sort out the invalid ones */
+    if (get_candidates(gamestate, move->torow, move->tofile, color,
+            candidates, &candidatecount)) {
         
         bool found = false;
         
-        /* find threats for the specified position */
-        for (size_t i = 0 ; i < threatcount ; i++) {
-            if (threats[i].piece == move->piece &&
+        for (size_t i = 0 ; i < candidatecount ; i++) {
+            /* filter by partial fromrow/fromfile information */
+            if (candidates[i].piece == move->piece &&
                     (move->fromrow == POS_UNSPECIFIED ||
-                    move->fromrow == threats[i].fromrow) &&
+                    move->fromrow == candidates[i].fromrow) &&
                     (move->fromfile == POS_UNSPECIFIED ||
-                    move->fromfile == threats[i].fromfile)) {
+                    move->fromfile == candidates[i].fromfile)) {
 
-                /* found a threat, here it does not matter if it's valid! */
+                /* found a candidate, here it does not matter if it's valid! */
                 found = true;
 
                 /* discard pinned pieces */
-                if (!is_pinned(gamestate, &(threats[i]))) {
-                    if (threat) {
-                        /* we've already found a valid threat */
+                if (!is_pinned(gamestate, &(candidates[i]))) {
+                    if (candidate) {
+                        /* we've already found a valid candidate */
                         return AMBIGUOUS_MOVE;
                     } else {
-                        threat = &(threats[i]);
+                        candidate = &(candidates[i]);
                     }
                 }
             }
         }
         
-        /* can't threaten specified position */
-        if (!threat) {
+        /* no valid candidate left */
+        if (!candidate) {
             if (found) {
                 if (piece_type(move->piece) == KING) {
                     return KING_MOVES_INTO_CHECK;
@@ -765,9 +845,9 @@
             }
         }
 
-        /* found a threat, copy the source location */
-        move->fromrow = threat->fromrow;
-        move->fromfile = threat->fromfile;
+        /* found a candidate, copy the source location */
+        move->fromrow = candidate->fromrow;
+        move->fromfile = candidate->fromfile;
         return VALID_MOVE_SYNTAX;
     } else {
         return PIECE_NOT_FOUND;
@@ -790,6 +870,7 @@
     if (mstr[len-1] == '+' || mstr[len-1] == '#') {
         len--; mstr[len] = '\0';
         move->check = true;
+        move->checkmate = mstr[len-1] == '#';
     }
     
     /* evaluate promotion */
@@ -829,7 +910,7 @@
         }
         if (mstr[1] == 'x') {
             /* capture (e.g. "Nxf3", "dxe5") */
-            move->capture = 1;
+            move->capture = true;
         } else {
             /* move (e.g. "Ndf3", "N2c3", "e2e4") */
             if (isfile(mstr[1])) {
@@ -855,7 +936,7 @@
         } else {
             move->piece = getpiece(mstr[0], color);
             if (mstr[2] == 'x') {
-                move->capture = 1;
+                move->capture = true;
                 if (move->piece) {
                     /* capture (e.g. "Ndxf3" or "R1xh3") */
                     if (isfile(mstr[1])) {
@@ -882,7 +963,7 @@
     } else if (len == 6) {
         /* long notation capture (e.g. "Nc5xf3") */
         if (mstr[3] == 'x') {
-            move->capture = 1;
+            move->capture = true;
             move->piece = getpiece(mstr[0], color);
             move->fromfile = fileidx(mstr[1]);
             move->fromrow = rowidx(mstr[2]);
@@ -914,24 +995,47 @@
     return VALID_MOVE_SYNTAX;
 }
 
-int eval_move(GameState *gamestate, const char *mstr,
-        Move *move, Color color) {
+int eval_move2(const GameState *gamestate,
+        const char *mstr, Color color, Move *move, bool lazy) {
     int result = eval_move1(mstr, move, color);
     if (result == VALID_MOVE_SYNTAX) {
         if (move->fromfile == POS_UNSPECIFIED
             || move->fromrow == POS_UNSPECIFIED) {
-            return getlocation(gamestate, move);
+            result = getlocation(gamestate, move);
+        }
+        if (result == VALID_MOVE_SYNTAX) {
+            /* correct check/checkmate flags */
+            if (lazy) {
+                move->check = move->checkmate = false;
+                switch (determine_check_or_checkmate(gamestate, move)) {
+                    case 2: move->checkmate = true;
+                    case 1: move->check = true;
+                }
+            }
+
+            /* format the move string */
+            format_move(gamestate, move);
         }
     }
     return result;
 }
 
+int eval_move_lazy(const GameState *gamestate,
+        const char *mstr, Color color, Move *move) {
+    return eval_move2(gamestate, mstr, color, move, true);
+}
+
+int eval_move(const GameState *gamestate,
+        const char *mstr, Color color, Move *move) {
+    return eval_move2(gamestate, mstr, color, move, false);
+}
+
 int check_move(const char *mstr, Color color) {
     Move move;
     return eval_move1(mstr, &move, color);
 }
 
-bool is_protected(GameState *gamestate, Row row, File file, Color color) {
+bool is_protected(const GameState *gamestate, Row row, File file, Color color) {
     Move threats[16];
     size_t threatcount;
     if (get_real_threats(gamestate, row, file, color, threats, &threatcount)) {
@@ -946,7 +1050,7 @@
     }
 }
 
-uint16_t remaining_movetime(GameState *gamestate, Color color) {
+uint16_t remaining_movetime(const GameState *gamestate, Color color) {
     unsigned move_number = gamestate->movecount;
     if (color == BLACK) {
         move_number |= 1;
@@ -956,7 +1060,7 @@
     return remaining_movetime2(gamestate, move_number);
 }
 
-uint16_t remaining_movetime2(GameState *gamestate, unsigned move_number) {
+uint16_t remaining_movetime2(const GameState *gamestate, unsigned move_number) {
     if (!gamestate->info.timecontrol) {
         return 0;
     }
@@ -1023,7 +1127,7 @@
     }
 }
 
-bool check_threefold_repetition(GameState *gamestate) {
+bool check_threefold_repetition(const GameState *gamestate) {
     // TODO: implement threefold repetition detection
     return false;
 }

mercurial