Thu, 03 Sep 2026 16:18:24 +0200
make castling rights part of the game state
required for issue #939
because creating a state from FEN
will not contain a move history
bonus: make is_check_position() robust
| src/chess/fen.c | file | annotate | diff | comparison | revisions | |
| src/chess/king.c | file | annotate | diff | comparison | revisions | |
| src/chess/rules.c | file | annotate | diff | comparison | revisions | |
| src/chess/rules.h | file | annotate | diff | comparison | revisions |
--- a/src/chess/fen.c Thu Sep 03 15:28:54 2026 +0200 +++ b/src/chess/fen.c Thu Sep 03 16:18:24 2026 +0200 @@ -77,40 +77,12 @@ return 1; } -static bool fen_castling_chkmoved(GameState *gamestate, - Rank rank, File file) { - - for (unsigned i = 0 ; i < gamestate->movecount ; i++) { - if (gamestate->moves[i].fromfile == file - && gamestate->moves[i].fromrank == rank) { - return true; - } - } - - return false; -} - static size_t fen_castling(char *str, GameState *gamestate) { - bool K, Q, k, q; - - if (fen_castling_chkmoved(gamestate, rankidx('1'), fileidx('e'))) { - K = Q = false; - } else { - K = !fen_castling_chkmoved(gamestate, rankidx('1'), fileidx('h')); - Q = !fen_castling_chkmoved(gamestate, rankidx('1'), fileidx('a')); - } - if (fen_castling_chkmoved(gamestate, rankidx('8'), fileidx('e'))) { - k = q = false; - } else { - k = !fen_castling_chkmoved(gamestate, rankidx('8'), fileidx('h')); - q = !fen_castling_chkmoved(gamestate, rankidx('8'), fileidx('a')); - } - size_t i = 0; - if (K) str[i++] = 'K'; - if (Q) str[i++] = 'Q'; - if (k) str[i++] = 'k'; - if (q) str[i++] = 'q'; + if (!gamestate->castling.K) str[i++] = 'K'; + if (!gamestate->castling.Q) str[i++] = 'Q'; + if (!gamestate->castling.k) str[i++] = 'k'; + if (!gamestate->castling.q) str[i++] = 'q'; if (!i) str[i++] = '-'; return i;
--- a/src/chess/king.c Thu Sep 03 15:28:54 2026 +0200 +++ b/src/chess/king.c Thu Sep 03 16:18:24 2026 +0200 @@ -30,62 +30,57 @@ #include "rules.h" #include "king.h" -static bool king_castling_chkmoved( - const GameState *gamestate, File file, Rank rank) { - - for (unsigned i = 0; i < gamestate->movecount; i++) { - if (gamestate->moves[i].fromfile == file - && gamestate->moves[i].fromrank == rank) { - return true; - } - } - - return false; -} - bool king_chkrules(const GameState *gamestate, const Move* move) { if (abs(move->torank - move->fromrank) <= 1 && abs(move->tofile - move->fromfile) <= 1) { return true; - } else { + } else if (move->fromrank == move->torank) { /* castling */ - if (move->fromrank == move->torank && - move->fromrank == (piece_color(move->piece) == WHITE ? 0 : 7) && - move->fromfile == fileidx('e') && - (move->tofile == fileidx('c') || move->tofile == fileidx('g'))) { - - return !king_castling_chkmoved(gamestate, - move->fromfile, move->fromrank) && - !king_castling_chkmoved(gamestate, - move->tofile == fileidx('c') ? 0 : 7, move->fromrank); + Rank backrank; + bool k_allowed, q_allowed; + if (piece_color(move->piece) == WHITE) { + backrank = rankidx('1'); + k_allowed = !gamestate->castling.K; + q_allowed = !gamestate->castling.Q; } else { - return false; + backrank = rankidx('8'); + k_allowed = !gamestate->castling.k; + q_allowed = !gamestate->castling.q; } + bool castle_q = false, castle_k = false; + if (move->fromrank == backrank && move->fromfile == fileidx('e')) { + castle_q = move->tofile == fileidx('c'); + castle_k = move->tofile == fileidx('g'); + } + /* note that here we do not consider threats! + * that is what king_isblocked() does */ + return (castle_q && q_allowed) || (castle_k && k_allowed); + } else { + return false; } } bool king_isblocked(const GameState *gamestate, const Move *move) { - - Color op_color = opponent_color(piece_color(move->piece)); - - /* being in check does not "block" the king, so don't test it here */ - bool blocked = false; - /* just test, if castling move is blocked */ if (abs(move->tofile - move->fromfile) == 2) { if (move->tofile == fileidx('c')) { - blocked |= gamestate->board[move->torank][fileidx('b')]; + /* check if rook can travel the queen-side */ + if (gamestate->board[move->torank][fileidx('b')]) { + return true; + } } + /* check if new field for the rook is free */ File midfile = (move->tofile+move->fromfile)/2; - bool incheck = false; - if (gamestate->movecount > 0) { - incheck = is_check_position(gamestate); + if (gamestate->board[move->torank][midfile]) { + return true; } - blocked |= incheck || gamestate->board[move->torank][midfile] || - is_covered(gamestate, midfile, move->torank, op_color); + /* check if the king or the target field for the rook is threatened */ + if (is_check_position(gamestate) || is_covered(gamestate, + midfile, move->torank, opponent_color(piece_color(move->piece)))) { + return true; + } } - - return blocked; + return false; } size_t king_moves(const GameState *gamestate,
--- a/src/chess/rules.c Thu Sep 03 15:28:54 2026 +0200 +++ b/src/chess/rules.c Thu Sep 03 16:18:24 2026 +0200 @@ -346,14 +346,48 @@ } /* 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; + } + } } } @@ -941,11 +975,7 @@ static int getlocation(const GameState *gamestate, Move *move) { Color color = piece_color(move->piece); - bool incheck = false; - if (gamestate->movecount > 0) { - incheck = is_check_position(gamestate); - } - + Move candidates[16], *candidate = NULL; size_t candidatecount; @@ -983,7 +1013,7 @@ if (found) { if (piece_type(move->piece) == KING) { return KING_MOVES_INTO_CHECK; - } else if (incheck) { + } else if (is_check_position(gamestate)) { return KING_IN_CHECK; } else { return PIECE_PINNED;
--- a/src/chess/rules.h Thu Sep 03 15:28:54 2026 +0200 +++ b/src/chess/rules.h Thu Sep 03 16:18:24 2026 +0200 @@ -179,6 +179,12 @@ /** this flag is only supposed to be set when the opponent disconnects */ bool ragequit; bool review; + struct { + bool K; /* white lost right to castle king-side */ + bool Q; /* white lost right to castle queen-side */ + bool k; /* black lost right to castle king-side */ + bool q; /* black lost right to castle queen-side */ + } castling; /* castling rights */ } GameState; #define piece_type(piece) ((uint8_t)(piece)&PIECE_MASK) @@ -226,6 +232,7 @@ } static inline bool is_check_position(const GameState *gamestate) { + if (gamestate->movecount == 0) return false; return gamestate->moves[gamestate->movecount - 1].check; }