From a2ed231baacd3637d3a4727c73b5ae79951e5b54 Mon Sep 17 00:00:00 2001 From: Mark Harrison Date: Mon, 20 Nov 2023 21:31:59 -0800 Subject: [PATCH 1/6] End game if bot chooses illegal move --- engine_wrapper.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/engine_wrapper.py b/engine_wrapper.py index 1d29022cc..d89698f2d 100644 --- a/engine_wrapper.py +++ b/engine_wrapper.py @@ -159,7 +159,15 @@ def play_move(self, else: time_limit = game_clock_time(board, game, setup_timer, move_overhead) - best_move = self.search(board, time_limit, can_ponder, draw_offered, best_move) + try: + best_move = self.search(board, time_limit, can_ponder, draw_offered, best_move) + except chess.engine.EngineError as error: + if any(isinstance(error.args[0], MoveError) for MoveError in [chess.IllegalMoveError, chess.InvalidMoveError]): + if game.is_abortable(): + li.abort(game.id) + else: + li.resign(game.id) + raise # Heed min_time elapsed = setup_timer.time_since_reset() From ad923146da124daf52fb59c546817827419aed17 Mon Sep 17 00:00:00 2001 From: Mark Harrison Date: Mon, 20 Nov 2023 21:37:17 -0800 Subject: [PATCH 2/6] Create function for time limit For reducing flake8-calculated complexity of play_move() function. --- engine_wrapper.py | 39 ++++++++++++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/engine_wrapper.py b/engine_wrapper.py index d89698f2d..25991507d 100644 --- a/engine_wrapper.py +++ b/engine_wrapper.py @@ -151,13 +151,9 @@ def play_move(self, if isinstance(best_move, list) or best_move.move is None: draw_offered = check_for_draw_offer(game) - if len(board.move_stack) < 2: - time_limit = first_move_time(game) - can_ponder = False # No pondering after the first move since a new clock starts afterwards. - elif is_correspondence: - time_limit = single_move_time(board, game, correspondence_move_time, setup_timer, move_overhead) - else: - time_limit = game_clock_time(board, game, setup_timer, move_overhead) + time_limit, can_ponder = move_time(board, game, can_ponder, + setup_timer, move_overhead, + is_correspondence, correspondence_move_time) try: best_move = self.search(board, time_limit, can_ponder, draw_offered, best_move) @@ -596,6 +592,35 @@ def getHomemadeEngine(name: str) -> type[MinimalEngine]: return engine +def move_time(board: chess.Board, + game: model.Game, + can_ponder: bool, + setup_timer: Timer, + move_overhead: datetime.timedelta, + is_correspondence: bool, + correspondence_move_time: datetime.timedelta) -> tuple[chess.engine.Limit, bool]: + """ + Determine the game clock settings for the current move. + + :param Board: The current position. + :param game: Information about the current game. + :param setup_timer: How much time has passed since receiving the opponent's move. + :param move_overhead: How much time it takes to communicate with lichess. + :param can_ponder: Whether the bot is allowed to ponder after choosing a move. + :param is_correspondence: Whether the current game is a correspondence game. + :param correspondence_move_time: How much time to use for this move it it is a correspondence game. + :return: The time to choose a move. + """ + if len(board.move_stack) < 2: + time_limit = first_move_time(game) + can_ponder = False # No pondering after the first move since a new clock starts afterwards. + elif is_correspondence: + time_limit = single_move_time(board, game, correspondence_move_time, setup_timer, move_overhead) + else: + time_limit = game_clock_time(board, game, setup_timer, move_overhead) + return time_limit, can_ponder + + def single_move_time(board: chess.Board, game: model.Game, search_time: datetime.timedelta, setup_timer: Timer, move_overhead: datetime.timedelta) -> chess.engine.Limit: """ From 23c7a4295b1b155d4a89187775c33f066848f319 Mon Sep 17 00:00:00 2001 From: Mark Harrison Date: Mon, 20 Nov 2023 21:37:35 -0800 Subject: [PATCH 3/6] Fix parameter documentation --- engine_wrapper.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/engine_wrapper.py b/engine_wrapper.py index 25991507d..1928cc7aa 100644 --- a/engine_wrapper.py +++ b/engine_wrapper.py @@ -629,7 +629,7 @@ def single_move_time(board: chess.Board, game: model.Game, search_time: datetime :param board: The current positions. :param game: The game that the bot is playing. :param search_time: How long the engine should search. - :param start_time: The time we have left. + :param setup_timer: How much time has passed since receiving the opponent's move. :param move_overhead: The time it takes to communicate between the engine and lichess-bot. :return: The time to choose a move. """ @@ -664,7 +664,7 @@ def game_clock_time(board: chess.Board, :param board: The current positions. :param game: The game that the bot is playing. - :param start_time: The time we have left. + :param setup_timer: How much time has passed since receiving the opponent's move. :param move_overhead: The time it takes to communicate between the engine and lichess-bot. :return: The time to play a move. """ From be557e004903175142f99e527b73c5e07aff6071 Mon Sep 17 00:00:00 2001 From: Mark Harrison Date: Mon, 20 Nov 2023 21:56:48 -0800 Subject: [PATCH 4/6] Simpler check for illegal moves --- engine_wrapper.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/engine_wrapper.py b/engine_wrapper.py index 1928cc7aa..9a7a771a7 100644 --- a/engine_wrapper.py +++ b/engine_wrapper.py @@ -158,7 +158,8 @@ def play_move(self, try: best_move = self.search(board, time_limit, can_ponder, draw_offered, best_move) except chess.engine.EngineError as error: - if any(isinstance(error.args[0], MoveError) for MoveError in [chess.IllegalMoveError, chess.InvalidMoveError]): + BadMove = (chess.IllegalMoveError, chess.InvalidMoveError) + if any(isinstance(e, BadMove) for e in error.args): if game.is_abortable(): li.abort(game.id) else: From 2b2fc84247dd7c0ddb3e29e67267b85e2aec8fae Mon Sep 17 00:00:00 2001 From: Mark Harrison Date: Thu, 30 Nov 2023 23:59:40 -0800 Subject: [PATCH 5/6] Reduce complexity and log bad move --- engine_wrapper.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/engine_wrapper.py b/engine_wrapper.py index 9a7a771a7..4dde70ee1 100644 --- a/engine_wrapper.py +++ b/engine_wrapper.py @@ -160,10 +160,9 @@ def play_move(self, except chess.engine.EngineError as error: BadMove = (chess.IllegalMoveError, chess.InvalidMoveError) if any(isinstance(e, BadMove) for e in error.args): - if game.is_abortable(): - li.abort(game.id) - else: - li.resign(game.id) + logger.error("Ending game due to bot attempting an illegal move.") + game_ender = li.abort if game.is_abortable() else li.resign + game_ender(game.id) raise # Heed min_time From 1ca683644ed1d2a7349b8f30b9f9870be9dedc80 Mon Sep 17 00:00:00 2001 From: Mark Harrison Date: Fri, 1 Dec 2023 00:06:44 -0800 Subject: [PATCH 6/6] Fix move_time() - Correct docstring - Early returns --- engine_wrapper.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/engine_wrapper.py b/engine_wrapper.py index 4dde70ee1..144218776 100644 --- a/engine_wrapper.py +++ b/engine_wrapper.py @@ -609,16 +609,14 @@ def move_time(board: chess.Board, :param can_ponder: Whether the bot is allowed to ponder after choosing a move. :param is_correspondence: Whether the current game is a correspondence game. :param correspondence_move_time: How much time to use for this move it it is a correspondence game. - :return: The time to choose a move. + :return: The time to choose a move and whether the bot can ponder after the move. """ if len(board.move_stack) < 2: - time_limit = first_move_time(game) - can_ponder = False # No pondering after the first move since a new clock starts afterwards. + return first_move_time(game), False # No pondering after the first move since a new clock starts afterwards. elif is_correspondence: - time_limit = single_move_time(board, game, correspondence_move_time, setup_timer, move_overhead) + return single_move_time(board, game, correspondence_move_time, setup_timer, move_overhead), can_ponder else: - time_limit = game_clock_time(board, game, setup_timer, move_overhead) - return time_limit, can_ponder + return game_clock_time(board, game, setup_timer, move_overhead), can_ponder def single_move_time(board: chess.Board, game: model.Game, search_time: datetime.timedelta,