# HG changeset patch # User Mike Becker # Date 1786707626 -7200 # Node ID e541b60029330d9f3a6a4ee877f65151cb0cd6e2 # Parent ba1bf80d10ccbffbe656beb4faf7c6266b7b0d61 implement first naive draw by threefold repetition partially resolves #843 diff -r ba1bf80d10cc -r e541b6002933 src/chess/pgn.c --- a/src/chess/pgn.c Fri Aug 14 11:35:36 2026 +0200 +++ b/src/chess/pgn.c Fri Aug 14 13:40:26 2026 +0200 @@ -59,7 +59,7 @@ } static const char *pgn_result(GameState *gamestate) { - if (gamestate->stalemate || gamestate->remis) { + if (is_game_drawn(gamestate)) { return "1/2-1/2"; } else if (gamestate->wresign) { return "0-1"; @@ -196,7 +196,8 @@ movetext_ends_with_result = true; break; } else if (c == '/') { - gamestate->remis = !gamestate->stalemate; + /* only set remis flag if the game was not drawn otherwise */ + gamestate->remis = !is_game_drawn(gamestate); movetext_ends_with_result = true; break; } else { diff -r ba1bf80d10cc -r e541b6002933 src/chess/rules.c --- 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) { diff -r ba1bf80d10cc -r e541b6002933 src/chess/rules.h --- a/src/chess/rules.h Fri Aug 14 11:35:36 2026 +0200 +++ b/src/chess/rules.h Fri Aug 14 13:40:26 2026 +0200 @@ -195,10 +195,13 @@ return gamestate->board[row][file] & ENPASSANT_THREAT; } +static inline bool is_game_drawn(const GameState *gamestate) { + return gamestate->threefold || gamestate->stalemate || gamestate->remis; +} + static inline bool is_game_running(const GameState *gamestate) { return !(gamestate->checkmate || gamestate->wresign || gamestate->bresign - || gamestate->threefold || gamestate->stalemate || gamestate->remis - || gamestate->review); + || is_game_drawn(gamestate) || gamestate->review); } static inline bool is_check_position(const GameState *gamestate) {