--- 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; }