From 1ef0f3b75dd1cdee25b1c633b8481db4b2107c28 Mon Sep 17 00:00:00 2001 From: "github-classroom[bot]" <66690702+github-classroom[bot]@users.noreply.github.com> Date: Sat, 25 Oct 2025 14:03:04 +0000 Subject: [PATCH 1/9] add deadline --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 8adb718..505e227 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +[![Review Assignment Due Date](https://classroom.github.com/assets/deadline-readme-button-22041afd0340ce965d47ae6ef1cefeee28c7c493a6346c4f15d667ab976d596c.svg)](https://classroom.github.com/a/FN98O3k7) ## Environment setup (Python 3.10+ recommended) - Clone repo - Open VSCode and go to the program folder From 74adecacbdb3a94939c5d33f662f74f84ced0999 Mon Sep 17 00:00:00 2001 From: tomio Date: Sat, 25 Oct 2025 11:21:37 -0400 Subject: [PATCH 2/9] cleaning out homemade, adding alpha beta --- config.yml.default | 330 +++++++++++++++++++++++---------------------- homemade.py | 126 +++++------------ 2 files changed, 200 insertions(+), 256 deletions(-) diff --git a/config.yml.default b/config.yml.default index 5ab470c..9c679ca 100644 --- a/config.yml.default +++ b/config.yml.default @@ -1,179 +1,181 @@ -token: "xxxxxxxxxxxxxxxxxxxxxx" # Lichess OAuth2 Token. -url: "https://lichess.org/" # Lichess base URL. - -engine: # Engine settings. - dir: "./engines/" # Directory containing the engine. This can be an absolute path or one relative to lichess-bot/. - name: "MyBot" # Binary name of the engine to use. -# interpreter: "java" -# interpreter_options: -# - "-jar" - working_dir: "" # Directory where the chess engine will read and write files. If blank or missing, the current directory is used. - # NOTE: If working_dir is set, the engine will look for files and directories relative to this directory, not where lichess-bot was launched. Absolute paths are unaffected. - protocol: "homemade" # "uci", "xboard" or "homemade" - ponder: false # Think on opponent's time. +token: "xxxxxxxxxxxxxxxxx" # Lichess OAuth2 Token. +url: "https://lichess.org/" # Lichess base URL. + +engine: # Engine settings. + dir: "./engines/" # Directory containing the engine. This can be an absolute path or one relative to lichess-bot/. + name: "MyBot" # Binary name of the engine to use. + # interpreter: "java" + # interpreter_options: + # - "-jar" + working_dir: + "" # Directory where the chess engine will read and write files. If blank or missing, the current directory is used. + # NOTE: If working_dir is set, the engine will look for files and directories relative to this directory, not where lichess-bot was launched. Absolute paths are unaffected. + protocol: "homemade" # "uci", "xboard" or "homemade" + ponder: false # Think on opponent's time. polyglot: - enabled: false # Activate polyglot book. + enabled: false # Activate polyglot book. book: - standard: # List of book file paths for variant standard. + standard: # List of book file paths for variant standard. - engines/book1.bin - engines/book2.bin -# atomic: # List of book file paths for variant atomic. -# - engines/atomicbook1.bin -# - engines/atomicbook2.bin -# etc. -# Use the same pattern for 'chess960', 'giveaway' (antichess), 'crazyhouse', 'horde', 'kingofthehill', 'racingkings' and '3check' as well. - min_weight: 1 # Does not select moves with weight below min_weight (min 0, max: 100 if normalization isn't "none" else 65535). - selection: "weighted_random" # Move selection is one of "weighted_random", "uniform_random" or "best_move" (but not below the min_weight in the 2nd and 3rd case). - max_depth: 20 # How many moves from the start to take from the book. - normalization: "none" # Normalization method for the book weights. One of "none", "sum", or "max". + # atomic: # List of book file paths for variant atomic. + # - engines/atomicbook1.bin + # - engines/atomicbook2.bin + # etc. + # Use the same pattern for 'chess960', 'giveaway' (antichess), 'crazyhouse', 'horde', 'kingofthehill', 'racingkings' and '3check' as well. + min_weight: 1 # Does not select moves with weight below min_weight (min 0, max: 100 if normalization isn't "none" else 65535). + selection: "weighted_random" # Move selection is one of "weighted_random", "uniform_random" or "best_move" (but not below the min_weight in the 2nd and 3rd case). + max_depth: 20 # How many moves from the start to take from the book. + normalization: "none" # Normalization method for the book weights. One of "none", "sum", or "max". draw_or_resign: - resign_enabled: false # Whether or not the bot should resign. - resign_score: -1000 # If the score is less than or equal to this value, the bot resigns (in cp). + resign_enabled: false # Whether or not the bot should resign. + resign_score: -1000 # If the score is less than or equal to this value, the bot resigns (in cp). resign_for_egtb_minus_two: true # If true the bot will resign in positions where the online_egtb returns a wdl of -2. - resign_moves: 3 # How many moves in a row the score has to be below the resign value. - offer_draw_enabled: true # Whether or not the bot should offer/accept draw. - offer_draw_score: 0 # If the absolute value of the score is less than or equal to this value, the bot offers/accepts draw (in cp). + resign_moves: 3 # How many moves in a row the score has to be below the resign value. + offer_draw_enabled: true # Whether or not the bot should offer/accept draw. + offer_draw_score: 0 # If the absolute value of the score is less than or equal to this value, the bot offers/accepts draw (in cp). offer_draw_for_egtb_zero: true # If true the bot will offer/accept draw in positions where the online_egtb returns a wdl of 0. - offer_draw_moves: 10 # How many moves in a row the absolute value of the score has to be below the draw value. - offer_draw_pieces: 10 # Only if the pieces on board are less than or equal to this value, the bot offers/accepts draw. + offer_draw_moves: 10 # How many moves in a row the absolute value of the score has to be below the draw value. + offer_draw_pieces: 10 # Only if the pieces on board are less than or equal to this value, the bot offers/accepts draw. online_moves: - max_out_of_book_moves: 4 # Stop using online opening books after they don't have a move for 'max_out_of_book_moves' positions. Doesn't apply to the online endgame tablebases. - max_retries: 2 # The maximum amount of retries when getting an online move. - max_depth: 5 # How many moves from the start to take from online books. Default is no limit. + max_out_of_book_moves: 4 # Stop using online opening books after they don't have a move for 'max_out_of_book_moves' positions. Doesn't apply to the online endgame tablebases. + max_retries: 2 # The maximum amount of retries when getting an online move. + max_depth: 5 # How many moves from the start to take from online books. Default is no limit. chessdb_book: - enabled: true # Whether or not to use chessdb book. - min_time: 20 # Minimum time (in seconds) to use chessdb book. - max_time: 10800 # Maximum starting game time (in seconds) to use chessdb book. - move_quality: "good" # One of "all", "good", "best". - min_depth: 20 # Only for move_quality: "best". + enabled: true # Whether or not to use chessdb book. + min_time: 20 # Minimum time (in seconds) to use chessdb book. + max_time: 10800 # Maximum starting game time (in seconds) to use chessdb book. + move_quality: "good" # One of "all", "good", "best". + min_depth: 20 # Only for move_quality: "best". lichess_cloud_analysis: - enabled: true # Whether or not to use lichess cloud analysis. - min_time: 20 # Minimum time (in seconds) the bot must have to use cloud analysis. - max_time: 10800 # Maximum starting game time (in seconds) the bot must have to use cloud analysis. - move_quality: "best" # One of "good", "best". - max_score_difference: 50 # Only for move_quality: "good". The maximum score difference (in cp) between the best move and the other moves. + enabled: true # Whether or not to use lichess cloud analysis. + min_time: 20 # Minimum time (in seconds) the bot must have to use cloud analysis. + max_time: 10800 # Maximum starting game time (in seconds) the bot must have to use cloud analysis. + move_quality: "best" # One of "good", "best". + max_score_difference: 50 # Only for move_quality: "good". The maximum score difference (in cp) between the best move and the other moves. min_depth: 20 min_knodes: 0 lichess_opening_explorer: enabled: true min_time: 20 - max_time: 10800 # Maximum starting game time (in seconds) the bot must have to use the lichess opening explorer. - source: "masters" # One of "lichess", "masters", "player" - player_name: "" # The lichess username. Leave empty for the bot's username to be used. Used only when source is "player". - sort: "winrate" # One of "winrate", "games_played" - min_games: 10 # Minimum number of times a move must have been played to be chosen. + max_time: 10800 # Maximum starting game time (in seconds) the bot must have to use the lichess opening explorer. + source: "masters" # One of "lichess", "masters", "player" + player_name: "" # The lichess username. Leave empty for the bot's username to be used. Used only when source is "player". + sort: "winrate" # One of "winrate", "games_played" + min_games: 10 # Minimum number of times a move must have been played to be chosen. online_egtb: - enabled: true # Whether or not to enable online endgame tablebases. - min_time: 20 # Minimum time (in seconds) the bot must have to use online EGTBs. - max_time: 10800 # Maximum starting game time (in seconds) the bot must have to use online EGTBs. - max_pieces: 7 # Maximum number of pieces on the board to use endgame tablebases. - source: "lichess" # One of "lichess", "chessdb". - move_quality: "best" # One of "best" or "suggest" (it takes all the moves with the same WDL and tells the engine to only consider these; will move instantly if there is only 1 "good" move). - - lichess_bot_tbs: # The tablebases list here will be read by lichess-bot, not the engine. + enabled: true # Whether or not to enable online endgame tablebases. + min_time: 20 # Minimum time (in seconds) the bot must have to use online EGTBs. + max_time: 10800 # Maximum starting game time (in seconds) the bot must have to use online EGTBs. + max_pieces: 7 # Maximum number of pieces on the board to use endgame tablebases. + source: "lichess" # One of "lichess", "chessdb". + move_quality: "best" # One of "best" or "suggest" (it takes all the moves with the same WDL and tells the engine to only consider these; will move instantly if there is only 1 "good" move). + + lichess_bot_tbs: # The tablebases list here will be read by lichess-bot, not the engine. syzygy: - enabled: false # Whether or not to use local syzygy endgame tablebases. - paths: # Paths to Syzygy endgame tablebases. + enabled: false # Whether or not to use local syzygy endgame tablebases. + paths: # Paths to Syzygy endgame tablebases. - "engines/syzygy" - max_pieces: 7 # Maximum number of pieces in the endgame tablebase. - move_quality: "best" # One of "best" or "suggest" (it takes all the moves with the same WDL and tells the engine to only consider these; will move instantly if there is only 1 "good" move). + max_pieces: 7 # Maximum number of pieces in the endgame tablebase. + move_quality: "best" # One of "best" or "suggest" (it takes all the moves with the same WDL and tells the engine to only consider these; will move instantly if there is only 1 "good" move). gaviota: - enabled: false # Whether or not to use local gaviota endgame tablebases. + enabled: false # Whether or not to use local gaviota endgame tablebases. paths: - "engines/gaviota" max_pieces: 5 min_dtm_to_consider_as_wdl_1: 120 # The minimum DTM to consider as syzygy WDL=1/-1. Set to 100 to disable. - move_quality: "best" # One of "best" or "suggest" (it takes all the moves with the same WDL and tells the engine to only consider these; will move instantly if there is only 1 "good" move). + move_quality: "best" # One of "best" or "suggest" (it takes all the moves with the same WDL and tells the engine to only consider these; will move instantly if there is only 1 "good" move). -# engine_options: # Any custom command line params to pass to the engine. -# cpuct: 3.1 + # engine_options: # Any custom command line params to pass to the engine. + # cpuct: 3.1 homemade_options: -# Hash: 256 - - uci_options: # Arbitrary UCI options passed to the engine. - Move Overhead: 100 # Increase if your bot flags games too often. - Threads: 4 # Max CPU threads the engine can use. - Hash: 512 # Max memory (in megabytes) the engine can allocate. - SyzygyPath: "./syzygy/" # Paths to Syzygy endgame tablebases that the engine reads. - UCI_ShowWDL: true # Show the chance of the engine winning. -# go_commands: # Additional options to pass to the UCI go command. -# nodes: 1 # Search so many nodes only. -# depth: 5 # Search depth ply only. -# movetime: 1000 # Integer. Search exactly movetime milliseconds. - -# xboard_options: # Arbitrary XBoard options passed to the engine. -# cores: "4" -# memory: "4096" -# egtpath: # Directory containing egtb (endgame tablabases), relative to this project. For 'xboard' engines. -# gaviota: "Gaviota path" -# nalimov: "Nalimov Path" -# scorpio: "Scorpio Path" -# syzygy: "Syzygy Path" -# go_commands: # Additional options to pass to the XBoard go command. -# depth: 5 # Search depth ply only. -# Do note that the go commands 'movetime' and 'nodes' are invalid and may cause bad time management for XBoard engines. - - silence_stderr: false # Some engines (yes you, Leela) are very noisy. - -abort_time: 30 # Time to abort a game in seconds when there is no activity. -fake_think_time: false # Artificially slow down the bot to pretend like it's thinking. -rate_limiting_delay: 0 # Time (in ms) to delay after sending a move to prevent "Too Many Requests" errors. -move_overhead: 2000 # Increase if your bot flags games too often. -max_takebacks_accepted: 0 # The number of times to allow an opponent to take back a move in a game. + # Hash: 256 + + uci_options: # Arbitrary UCI options passed to the engine. + Move Overhead: 100 # Increase if your bot flags games too often. + Threads: 4 # Max CPU threads the engine can use. + Hash: 512 # Max memory (in megabytes) the engine can allocate. + SyzygyPath: "./syzygy/" # Paths to Syzygy endgame tablebases that the engine reads. + UCI_ShowWDL: true # Show the chance of the engine winning. + # go_commands: # Additional options to pass to the UCI go command. + # nodes: 1 # Search so many nodes only. + # depth: 5 # Search depth ply only. + # movetime: 1000 # Integer. Search exactly movetime milliseconds. + + # xboard_options: # Arbitrary XBoard options passed to the engine. + # cores: "4" + # memory: "4096" + # egtpath: # Directory containing egtb (endgame tablabases), relative to this project. For 'xboard' engines. + # gaviota: "Gaviota path" + # nalimov: "Nalimov Path" + # scorpio: "Scorpio Path" + # syzygy: "Syzygy Path" + # go_commands: # Additional options to pass to the XBoard go command. + # depth: 5 # Search depth ply only. + # Do note that the go commands 'movetime' and 'nodes' are invalid and may cause bad time management for XBoard engines. + + silence_stderr: false # Some engines (yes you, Leela) are very noisy. + +abort_time: 30 # Time to abort a game in seconds when there is no activity. +fake_think_time: false # Artificially slow down the bot to pretend like it's thinking. +rate_limiting_delay: 0 # Time (in ms) to delay after sending a move to prevent "Too Many Requests" errors. +move_overhead: 2000 # Increase if your bot flags games too often. +max_takebacks_accepted: 0 # The number of times to allow an opponent to take back a move in a game. quit_after_all_games_finish: false # If set to true, then pressing Ctrl-C to quit will only stop lichess-bot after all current games have finished. correspondence: - move_time: 60 # Time in seconds to search in correspondence games. - checkin_period: 300 # How often to check for opponent moves in correspondence games after disconnecting. - disconnect_time: 150 # Time before disconnecting from a correspondence game. - ponder: false # Ponder in correspondence games the bot is connected to. - -challenge: # Incoming challenges. - concurrency: 1 # Number of games to play simultaneously. - sort_by: "best" # Possible values: "best" and "first". - preference: "none" # Possible values: "none", "human", "bot". - accept_bot: true # Accepts challenges coming from other bots. - only_bot: false # Accept challenges by bots only. - max_increment: 20 # Maximum amount of increment to accept a challenge in seconds. The max is 180. Set to 0 for no increment. - min_increment: 0 # Minimum amount of increment to accept a challenge in seconds. - max_base: 1800 # Maximum amount of base time to accept a challenge in seconds. The max is 10800 (3 hours). - min_base: 0 # Minimum amount of base time to accept a challenge in seconds. - max_days: 14 # Maximum number of days per move to accept a challenge for a correspondence game. - # Unlimited games can be accepted by removing this field or specifying .inf - min_days: 1 # Minimum number of days per move to accept a challenge for a correspondence game. - variants: # Chess variants to accept (https://lichess.org/variant). + move_time: 60 # Time in seconds to search in correspondence games. + checkin_period: 300 # How often to check for opponent moves in correspondence games after disconnecting. + disconnect_time: 150 # Time before disconnecting from a correspondence game. + ponder: false # Ponder in correspondence games the bot is connected to. + +challenge: # Incoming challenges. + concurrency: 1 # Number of games to play simultaneously. + sort_by: "best" # Possible values: "best" and "first". + preference: "none" # Possible values: "none", "human", "bot". + accept_bot: true # Accepts challenges coming from other bots. + only_bot: false # Accept challenges by bots only. + max_increment: 20 # Maximum amount of increment to accept a challenge in seconds. The max is 180. Set to 0 for no increment. + min_increment: 0 # Minimum amount of increment to accept a challenge in seconds. + max_base: 1800 # Maximum amount of base time to accept a challenge in seconds. The max is 10800 (3 hours). + min_base: 0 # Minimum amount of base time to accept a challenge in seconds. + max_days: + 14 # Maximum number of days per move to accept a challenge for a correspondence game. + # Unlimited games can be accepted by removing this field or specifying .inf + min_days: 1 # Minimum number of days per move to accept a challenge for a correspondence game. + variants: # Chess variants to accept (https://lichess.org/variant). - standard -# - fromPosition -# - antichess -# - atomic -# - chess960 -# - crazyhouse -# - horde -# - kingOfTheHill -# - racingKings -# - threeCheck - time_controls: # Time controls to accept (bots are not allowed to play ultraBullet). + # - fromPosition + # - antichess + # - atomic + # - chess960 + # - crazyhouse + # - horde + # - kingOfTheHill + # - racingKings + # - threeCheck + time_controls: # Time controls to accept (bots are not allowed to play ultraBullet). - bullet - blitz - modes: # Game modes to accept. - - casual # Unrated games. - - rated # Rated games - must comment if the engine doesn't try to win. -# block_list: # List of users from which the challenges are always declined. -# - user1 -# - user2 -# online_block_list: # The urls from which to retrieve a list of bot names that will not be challenged. The list should be a text file where each line contains the name of a blocked bot -# - example.com/blocklist -# allow_list: # List of users from which challenges are exclusively accepted, all others being declined. If empty, challenges from all users may be accepted. -# - user3 -# - user4 -# recent_bot_challenge_age: 60 # Maximum age of a bot challenge to be considered recent in seconds -# max_recent_bot_challenges: 2 # Maximum number of recent challenges that can be accepted from the same bot + modes: # Game modes to accept. + - casual # Unrated games. + - rated # Rated games - must comment if the engine doesn't try to win. + # block_list: # List of users from which the challenges are always declined. + # - user1 + # - user2 + # online_block_list: # The urls from which to retrieve a list of bot names that will not be challenged. The list should be a text file where each line contains the name of a blocked bot + # - example.com/blocklist + # allow_list: # List of users from which challenges are exclusively accepted, all others being declined. If empty, challenges from all users may be accepted. + # - user3 + # - user4 + # recent_bot_challenge_age: 60 # Maximum age of a bot challenge to be considered recent in seconds + # max_recent_bot_challenges: 2 # Maximum number of recent challenges that can be accepted from the same bot bullet_requires_increment: false # Require that bullet game challenges from bots have a non-zero increment - max_simultaneous_games_per_user: 5 # Maximum number of simultaneous games with the same user + max_simultaneous_games_per_user: 5 # Maximum number of simultaneous games with the same user greeting: # Optional substitution keywords (include curly braces): @@ -187,36 +189,36 @@ greeting: # pgn_directory: "game_records" # A directory where PGN-format records of the bot's games are kept # pgn_file_grouping: "game" # How to group games into files. Options are "game", "opponent", and "all" - # "game" (default) - every game is written to a different file named "{White name} vs. {Black name} - {lichess game ID}.pgn" - # "opponent" - every game with a given opponent is written to a file named "{Bot name} games vs. {Opponent name}.pgn" - # "all" - every game is written to a single file named "{Bot name} games.pgn" +# "game" (default) - every game is written to a different file named "{White name} vs. {Black name} - {lichess game ID}.pgn" +# "opponent" - every game with a given opponent is written to a file named "{Bot name} games vs. {Opponent name}.pgn" +# "all" - every game is written to a single file named "{Bot name} games.pgn" matchmaking: - allow_matchmaking: false # Set it to 'true' to challenge other bots. - allow_during_games: false # Set it to 'true' to create challenges during long games. - challenge_variant: "random" # If set to 'random', the bot will choose one variant from the variants enabled in 'challenge.variants'. - challenge_timeout: 30 # Create a challenge after being idle for 'challenge_timeout' minutes. The minimum is 1 minute. - challenge_initial_time: # Initial time in seconds of the challenge (to be chosen at random). + allow_matchmaking: false # Set it to 'true' to challenge other bots. + allow_during_games: false # Set it to 'true' to create challenges during long games. + challenge_variant: "random" # If set to 'random', the bot will choose one variant from the variants enabled in 'challenge.variants'. + challenge_timeout: 30 # Create a challenge after being idle for 'challenge_timeout' minutes. The minimum is 1 minute. + challenge_initial_time: # Initial time in seconds of the challenge (to be chosen at random). - 60 - 180 - challenge_increment: # Increment in seconds of the challenge (to be chosen at random). + challenge_increment: # Increment in seconds of the challenge (to be chosen at random). - 1 - 2 -# challenge_days: # Days for correspondence challenge (to be chosen at random). -# - 1 -# - 2 -# opponent_min_rating: 600 # Opponents rating should be above this value (600 is the minimum rating in lichess). -# opponent_max_rating: 4000 # Opponents rating should be below this value (4000 is the maximum rating in lichess). - opponent_rating_difference: 300 # The maximum difference in rating between the bot's rating and opponent's rating. - rating_preference: "none" # One of "none", "high", "low". - challenge_mode: "random" # Set it to the mode in which challenges are sent. Possible options are 'casual', 'rated' and 'random'. - challenge_filter: none # If a bot declines a challenge, do not issue a similar challenge to that bot. Possible options are 'none', 'coarse', and 'fine'. -# block_list: # The list of bots that will not be challenged -# - user1 -# - user2 -# online_block_list: # The urls from which to retrieve a list of bot names that will not be challenged. The list should be a text file where each line contains the name of a blocked bot -# - example.com/blocklist - include_challenge_block_list: false # Do not challenge bots in the challenge: block_list in addition to the matchmaking block list. + # challenge_days: # Days for correspondence challenge (to be chosen at random). + # - 1 + # - 2 + # opponent_min_rating: 600 # Opponents rating should be above this value (600 is the minimum rating in lichess). + # opponent_max_rating: 4000 # Opponents rating should be below this value (4000 is the maximum rating in lichess). + opponent_rating_difference: 300 # The maximum difference in rating between the bot's rating and opponent's rating. + rating_preference: "none" # One of "none", "high", "low". + challenge_mode: "random" # Set it to the mode in which challenges are sent. Possible options are 'casual', 'rated' and 'random'. + challenge_filter: none # If a bot declines a challenge, do not issue a similar challenge to that bot. Possible options are 'none', 'coarse', and 'fine'. + # block_list: # The list of bots that will not be challenged + # - user1 + # - user2 + # online_block_list: # The urls from which to retrieve a list of bot names that will not be challenged. The list should be a text file where each line contains the name of a blocked bot + # - example.com/blocklist + include_challenge_block_list: false # Do not challenge bots in the challenge: block_list in addition to the matchmaking block list. # overrides: # List of overrides for the matchmaking specifications above. When a challenge is created, either the default specification above or one of the overrides will be randomly chosen. # bullet_only_horde: # Name of the override. Can be anything as long as each override has a unique name ("bullet_only_horde" and "easy_chess960" in these examples). diff --git a/homemade.py b/homemade.py index 8d7f558..3be3044 100644 --- a/homemade.py +++ b/homemade.py @@ -20,82 +20,6 @@ class ExampleEngine(MinimalEngine): """An example engine that all homemade engines inherit.""" - -# Bot names and ideas from tom7's excellent eloWorld video - -class RandomMove(ExampleEngine): - """Get a random move.""" - - def search(self, board: chess.Board, *args: HOMEMADE_ARGS_TYPE) -> PlayResult: # noqa: ARG002 - """Choose a random move.""" - return PlayResult(random.choice(list(board.legal_moves)), None) - - -class Alphabetical(ExampleEngine): - """Get the first move when sorted by san representation.""" - - def search(self, board: chess.Board, *args: HOMEMADE_ARGS_TYPE) -> PlayResult: # noqa: ARG002 - """Choose the first move alphabetically.""" - moves = list(board.legal_moves) - moves.sort(key=board.san) - return PlayResult(moves[0], None) - - -class FirstMove(ExampleEngine): - """Get the first move when sorted by uci representation.""" - - def search(self, board: chess.Board, *args: HOMEMADE_ARGS_TYPE) -> PlayResult: # noqa: ARG002 - """Choose the first move alphabetically in uci representation.""" - moves = list(board.legal_moves) - moves.sort(key=str) - return PlayResult(moves[0], None) - - -class ComboEngine(ExampleEngine): - """ - Get a move using multiple different methods. - - This engine demonstrates how one can use `time_limit`, `draw_offered`, and `root_moves`. - """ - - def search(self, - board: chess.Board, - time_limit: Limit, - ponder: bool, # noqa: ARG002 - draw_offered: bool, - root_moves: MOVE) -> PlayResult: - """ - Choose a move using multiple different methods. - - :param board: The current position. - :param time_limit: Conditions for how long the engine can search (e.g. we have 10 seconds and search up to depth 10). - :param ponder: Whether the engine can ponder after playing a move. - :param draw_offered: Whether the bot was offered a draw. - :param root_moves: If it is a list, the engine should only play a move that is in `root_moves`. - :return: The move to play. - """ - if isinstance(time_limit.time, int): - my_time = time_limit.time - my_inc = 0 - elif board.turn == chess.WHITE: - my_time = time_limit.white_clock if isinstance(time_limit.white_clock, int) else 0 - my_inc = time_limit.white_inc if isinstance(time_limit.white_inc, int) else 0 - else: - my_time = time_limit.black_clock if isinstance(time_limit.black_clock, int) else 0 - my_inc = time_limit.black_inc if isinstance(time_limit.black_inc, int) else 0 - - possible_moves = root_moves if isinstance(root_moves, list) else list(board.legal_moves) - - if my_time / 60 + my_inc > 10: - # Choose a random move. - move = random.choice(possible_moves) - else: - # Choose the first move alphabetically in uci representation. - possible_moves.sort(key=str) - move = possible_moves[0] - return PlayResult(move, None, draw_offered=draw_offered) - - class MyBot(ExampleEngine): """Template code for hackathon participants to modify. @@ -104,15 +28,12 @@ class MyBot(ExampleEngine): Key limitations: - Fixed-depth search with only a very naive time-to-depth mapping (no true time management). - - Plain minimax: no alpha-beta pruning, so the search is much slower than it - could be for the same depth. - No iterative deepening: the engine does not progressively deepen and use PV-based ordering. - No move ordering or capture heuristics: moves are searched in arbitrary order. - No transposition table or caching: repeated positions are re-searched. - Evaluation is material-only and very simplistic; positional factors are ignored. - Use this as a starting point: replace minimax with alpha-beta, add - iterative deepening, quiescence search, move ordering (MVV/LVA, history), + Use this as a starting point: add iterative deepening, quiescence search, move ordering (MVV/LVA, history), transposition table, and a richer evaluator to make it competitive. """ @@ -120,7 +41,7 @@ def search(self, board: chess.Board, *args: HOMEMADE_ARGS_TYPE) -> PlayResult: # NOTE: The sections below are intentionally simple to keep the example short. # They demonstrate the structure of a search but also highlight the engine's # weaknesses (fixed depth, naive time handling, no pruning, no quiescence, etc.). - + print(dir(board)) # --- very simple time-based depth selection (naive) --- # Expect args to be (time_limit: Limit, ponder: bool, draw_offered: bool, root_moves: MOVE) time_limit = args[0] if (args and isinstance(args[0], Limit)) else None @@ -179,28 +100,38 @@ def evaluate(b: chess.Board) -> int: score += v * (len(b.pieces(pt, chess.WHITE)) - len(b.pieces(pt, chess.BLACK))) return score - # --- plain minimax (no alpha-beta) --- - def minimax(b: chess.Board, depth: int, maximizing: bool) -> int: + # up to what I've done before + # start with iterative deepening + # move onto transposition table + def traverseTree(b: chess.Board, depth: int, maximizing: bool, alpha, beta) -> int: if depth == 0 or b.is_game_over(): - return evaluate(b) - + return evaluate(b) + if maximizing: best = -10**12 for m in b.legal_moves: b.push(m) - val = minimax(b, depth - 1, False) + val = traverseTree(b, depth - 1, False) b.pop() if val > best: best = val + if val > alpha: + alpha=val + if(beta<=alpha): + return best return best else: best = 10**12 for m in b.legal_moves: b.push(m) - val = minimax(b, depth - 1, True) + val = traverseTree(b, depth - 1, True) b.pop() if val < best: best = val + if val < beta: + beta=val + if(beta<=alpha): + return best return best # --- root move selection --- @@ -212,17 +143,28 @@ def minimax(b: chess.Board, depth: int, maximizing: bool) -> int: maximizing = board.turn == chess.WHITE best_move = None best_eval = -10**12 if maximizing else 10**12 + alpha=-10**12 + beta=10**12 # Lookahead depth chosen by the simple time heuristic; subtract one for the root move for m in legal: board.push(m) - val = minimax(board, total_depth - 1, not maximizing) + val = traverseTree(board, total_depth - 1, not maximizing, alpha, beta) board.pop() - if maximizing and val > best_eval: - best_eval, best_move = val, m - elif not maximizing and val < best_eval: - best_eval, best_move = val, m + if maximizing: + if val > best_eval: + best_eval, best_move = val, m + if val > alpha: + alpha=val + elif not maximizing: + if val < best_eval: + best_eval, best_move = val, m + if val < beta: + beta=val + + if beta<=alpha: + return best_eval, m # Fallback in rare cases (shouldn't trigger) if best_move is None: From fca85f1b093979b466c7df936893d63f48d8b91d Mon Sep 17 00:00:00 2001 From: tomio Date: Sat, 25 Oct 2025 11:24:02 -0400 Subject: [PATCH 3/9] set depth to 1 --- homemade.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/homemade.py b/homemade.py index 3be3044..19a0733 100644 --- a/homemade.py +++ b/homemade.py @@ -67,13 +67,13 @@ def search(self, board: chess.Board, *args: HOMEMADE_ARGS_TYPE) -> PlayResult: inc = my_inc if isinstance(my_inc, (int, float)) else 0 budget = (remaining or 0) + 2 * inc # crude increment bonus if remaining is None: - total_depth = 4 + total_depth = 1 elif budget >= 60: - total_depth = 4 + total_depth = 1 elif budget >= 20: - total_depth = 3 + total_depth = 1 elif budget >= 5: - total_depth = 2 + total_depth = 1 else: total_depth = 1 total_depth = max(1, int(total_depth)) From 50209a872b55468bd9f88356de94f6a72250d126 Mon Sep 17 00:00:00 2001 From: tomio Date: Sat, 25 Oct 2025 11:51:13 -0400 Subject: [PATCH 4/9] implemented a naive iterative deepening system --- homemade.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/homemade.py b/homemade.py index 19a0733..c3a274e 100644 --- a/homemade.py +++ b/homemade.py @@ -9,6 +9,7 @@ from lib.engine_wrapper import MinimalEngine from lib.lichess_types import MOVE, HOMEMADE_ARGS_TYPE import logging +from collections import deque # Use this logger variable to print messages to the console or log files. @@ -41,7 +42,7 @@ def search(self, board: chess.Board, *args: HOMEMADE_ARGS_TYPE) -> PlayResult: # NOTE: The sections below are intentionally simple to keep the example short. # They demonstrate the structure of a search but also highlight the engine's # weaknesses (fixed depth, naive time handling, no pruning, no quiescence, etc.). - print(dir(board)) + print(board) # --- very simple time-based depth selection (naive) --- # Expect args to be (time_limit: Limit, ponder: bool, draw_offered: bool, root_moves: MOVE) time_limit = args[0] if (args and isinstance(args[0], Limit)) else None @@ -106,11 +107,19 @@ def evaluate(b: chess.Board) -> int: def traverseTree(b: chess.Board, depth: int, maximizing: bool, alpha, beta) -> int: if depth == 0 or b.is_game_over(): return evaluate(b) - + scored_moves=deque([]) if maximizing: best = -10**12 for m in b.legal_moves: b.push(m) + strength =evaluate(b) + if scored_moves.count==0 or scored_moves.pop[1]>strength: + scored_moves.append([m, strength]) + else: + scored_moves.appendleft([m,strength]) + b.pop() + for m in scored_moves: + b.push(m[0]) val = traverseTree(b, depth - 1, False) b.pop() if val > best: @@ -124,6 +133,14 @@ def traverseTree(b: chess.Board, depth: int, maximizing: bool, alpha, beta) -> i best = 10**12 for m in b.legal_moves: b.push(m) + strength =evaluate(b) + if scored_moves.count==0 or scored_moves.pop[1] Date: Sat, 25 Oct 2025 12:03:38 -0400 Subject: [PATCH 5/9] removed unnecessary print line --- homemade.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/homemade.py b/homemade.py index 19a0733..95c5a4f 100644 --- a/homemade.py +++ b/homemade.py @@ -41,7 +41,6 @@ def search(self, board: chess.Board, *args: HOMEMADE_ARGS_TYPE) -> PlayResult: # NOTE: The sections below are intentionally simple to keep the example short. # They demonstrate the structure of a search but also highlight the engine's # weaknesses (fixed depth, naive time handling, no pruning, no quiescence, etc.). - print(dir(board)) # --- very simple time-based depth selection (naive) --- # Expect args to be (time_limit: Limit, ponder: bool, draw_offered: bool, root_moves: MOVE) time_limit = args[0] if (args and isinstance(args[0], Limit)) else None @@ -67,15 +66,15 @@ def search(self, board: chess.Board, *args: HOMEMADE_ARGS_TYPE) -> PlayResult: inc = my_inc if isinstance(my_inc, (int, float)) else 0 budget = (remaining or 0) + 2 * inc # crude increment bonus if remaining is None: - total_depth = 1 + total_depth = 2 elif budget >= 60: - total_depth = 1 + total_depth = 2 elif budget >= 20: - total_depth = 1 + total_depth = 2 elif budget >= 5: - total_depth = 1 + total_depth = 2 else: - total_depth = 1 + total_depth = 2 total_depth = max(1, int(total_depth)) # --- simple material evaluator (White-positive score) --- From bf83d92b0709905a4494312ac1b30b484f5551ec Mon Sep 17 00:00:00 2001 From: tomio Date: Sat, 25 Oct 2025 12:05:00 -0400 Subject: [PATCH 6/9] fixed alpha beta implementation --- homemade.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/homemade.py b/homemade.py index 95c5a4f..af9ee8f 100644 --- a/homemade.py +++ b/homemade.py @@ -66,15 +66,15 @@ def search(self, board: chess.Board, *args: HOMEMADE_ARGS_TYPE) -> PlayResult: inc = my_inc if isinstance(my_inc, (int, float)) else 0 budget = (remaining or 0) + 2 * inc # crude increment bonus if remaining is None: - total_depth = 2 + total_depth = 3 elif budget >= 60: - total_depth = 2 + total_depth = 3 elif budget >= 20: - total_depth = 2 + total_depth = 3 elif budget >= 5: - total_depth = 2 + total_depth = 3 else: - total_depth = 2 + total_depth = 3 total_depth = max(1, int(total_depth)) # --- simple material evaluator (White-positive score) --- @@ -110,7 +110,7 @@ def traverseTree(b: chess.Board, depth: int, maximizing: bool, alpha, beta) -> i best = -10**12 for m in b.legal_moves: b.push(m) - val = traverseTree(b, depth - 1, False) + val = traverseTree(b, depth - 1, False, alpha, beta) b.pop() if val > best: best = val @@ -123,7 +123,7 @@ def traverseTree(b: chess.Board, depth: int, maximizing: bool, alpha, beta) -> i best = 10**12 for m in b.legal_moves: b.push(m) - val = traverseTree(b, depth - 1, True) + val = traverseTree(b, depth - 1, True, alpha, beta) b.pop() if val < best: best = val From f9fe2f7c2938592e0a77f05f6162f93f25200d3f Mon Sep 17 00:00:00 2001 From: tomio Date: Sat, 25 Oct 2025 12:09:18 -0400 Subject: [PATCH 7/9] increasing default depth --- homemade.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/homemade.py b/homemade.py index af9ee8f..e80fdad 100644 --- a/homemade.py +++ b/homemade.py @@ -66,15 +66,15 @@ def search(self, board: chess.Board, *args: HOMEMADE_ARGS_TYPE) -> PlayResult: inc = my_inc if isinstance(my_inc, (int, float)) else 0 budget = (remaining or 0) + 2 * inc # crude increment bonus if remaining is None: - total_depth = 3 + total_depth = 5 elif budget >= 60: - total_depth = 3 + total_depth = 5 elif budget >= 20: - total_depth = 3 + total_depth = 5 elif budget >= 5: - total_depth = 3 + total_depth = 5 else: - total_depth = 3 + total_depth = 5 total_depth = max(1, int(total_depth)) # --- simple material evaluator (White-positive score) --- From 4d8eb5dab65bdebaad2720a5eede2d41bc2eca12 Mon Sep 17 00:00:00 2001 From: tomio Date: Sat, 25 Oct 2025 12:11:22 -0400 Subject: [PATCH 8/9] fixed alpha beta bug --- homemade.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homemade.py b/homemade.py index a5c12e2..91c9794 100644 --- a/homemade.py +++ b/homemade.py @@ -119,7 +119,7 @@ def traverseTree(b: chess.Board, depth: int, maximizing: bool, alpha, beta) -> i b.pop() for m in scored_moves: b.push(m[0]) - val = traverseTree(b, depth - 1, False) + val = traverseTree(b, depth - 1, False, alpha, beta) b.pop() if val > best: best = val @@ -140,7 +140,7 @@ def traverseTree(b: chess.Board, depth: int, maximizing: bool, alpha, beta) -> i b.pop() for m in scored_moves: b.push(m[0]) - val = traverseTree(b, depth - 1, True) + val = traverseTree(b, depth - 1, True, alpha, beta) b.pop() if val < best: best = val From e1b5ee034f876ef52830f70cb305aa77eb26388b Mon Sep 17 00:00:00 2001 From: tomio Date: Sat, 25 Oct 2025 12:42:04 -0400 Subject: [PATCH 9/9] simple move oerdering working --- homemade.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/homemade.py b/homemade.py index 91c9794..f39a9ca 100644 --- a/homemade.py +++ b/homemade.py @@ -67,15 +67,15 @@ def search(self, board: chess.Board, *args: HOMEMADE_ARGS_TYPE) -> PlayResult: inc = my_inc if isinstance(my_inc, (int, float)) else 0 budget = (remaining or 0) + 2 * inc # crude increment bonus if remaining is None: - total_depth = 5 + total_depth = 3 elif budget >= 60: - total_depth = 5 + total_depth = 3 elif budget >= 20: - total_depth = 5 + total_depth = 3 elif budget >= 5: - total_depth = 5 + total_depth = 3 else: - total_depth = 5 + total_depth = 3 total_depth = max(1, int(total_depth)) # --- simple material evaluator (White-positive score) --- @@ -105,14 +105,15 @@ def evaluate(b: chess.Board) -> int: # move onto transposition table def traverseTree(b: chess.Board, depth: int, maximizing: bool, alpha, beta) -> int: if depth == 0 or b.is_game_over(): - return evaluate(b) + return evaluate(b) + print("here2") scored_moves=deque([]) if maximizing: best = -10**12 for m in b.legal_moves: b.push(m) strength =evaluate(b) - if scored_moves.count==0 or scored_moves.pop[1]>strength: + if len(scored_moves)==0 or scored_moves[0][1] i if val > alpha: alpha=val if(beta<=alpha): - return best + return best + print(best) return best else: + print("here3") best = 10**12 for m in b.legal_moves: b.push(m) strength =evaluate(b) - if scored_moves.count==0 or scored_moves.pop[1]strength: scored_moves.append([m, strength]) else: scored_moves.appendleft([m,strength]) @@ -148,6 +151,7 @@ def traverseTree(b: chess.Board, depth: int, maximizing: bool, alpha, beta) -> i beta=val if(beta<=alpha): return best + print("here4") return best # --- root move selection --- @@ -165,6 +169,7 @@ def traverseTree(b: chess.Board, depth: int, maximizing: bool, alpha, beta) -> i # Lookahead depth chosen by the simple time heuristic; subtract one for the root move for m in legal: board.push(m) + print("here") val = traverseTree(board, total_depth - 1, not maximizing, alpha, beta) board.pop()