From f8d96c4122ff704346de8b014ca495b90ab5bb78 Mon Sep 17 00:00:00 2001 From: alexomur Date: Fri, 14 Feb 2025 19:46:28 +0300 Subject: [PATCH 01/12] feat: Simulator mode --- main.py | 3 +- simulator.py | 99 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 simulator.py diff --git a/main.py b/main.py index ab3d412..2fde779 100644 --- a/main.py +++ b/main.py @@ -183,4 +183,5 @@ def update(): root.mainloop() -start_game() +if __name__ == "__main__": + start_game() diff --git a/simulator.py b/simulator.py new file mode 100644 index 0000000..76537e6 --- /dev/null +++ b/simulator.py @@ -0,0 +1,99 @@ +import main +from main import Board +import random +import time +import json +from pathlib import Path +import concurrent.futures + +NUM_GAMES = 10 +FILENAME = "game.json" + + +# Классы, облегченные для симуляции +class DummyBoard(Board): + def __init__(self, game, canvas, label): + main.PliTk = DummyPliTk + super().__init__(game, canvas, label) + +class DummyCanvas: + def config(self, **kwargs): + pass + def pack(self, **kwargs): + pass + def create_image(self, x, y, **kwargs): + return 0 + def delete(self, item): + pass + def itemconfigure(self, item, **kwargs): + pass + +class DummyLabel: + def __init__(self): + self.text = "" + def __setitem__(self, key, value): + self.text = value + +class DummyPliTk: + def __init__(self, canvas, x, y, cols, rows, tileset, scale): + self.canvas = canvas + self.x = x + self.y = y + self.cols = cols + self.rows = rows + def resize(self, cols, rows): + self.cols = cols + self.rows = rows + def set_tile(self, x, y, index): + pass + +# Функции симуляции +def simulate_game(game, seed=None) -> str: + """ + Выполняет одну симуляцию игры без графики + и возвращает имя победившего бота. + """ + if seed is not None: + random.seed(seed) + canvas = DummyCanvas() + label = DummyLabel() + board = DummyBoard(game, canvas, label) + while board.play(): + pass + players_sorted = sorted(board.players, key=lambda p: p.gold, reverse=True) + winner = players_sorted[0].name + return winner + + +def run_simulations(num_games, game): + """ + Запускает num_games симуляций игры в отдельных процессах и собирает статистику по победам. + После завершения выводит в консоли, сколько раз победил каждый бот. + """ + results = {} + + with concurrent.futures.ProcessPoolExecutor() as executor: + futures = [executor.submit(simulate_game, game, seed=i) for i in range(num_games)] + for future in concurrent.futures.as_completed(futures): + winner = future.result() + results[winner] = results.get(winner, 0) + 1 + + print("Simulation Results:") + for bot, wins in results.items(): + print(f"{bot}: {wins} wins") + + +def run_games(num_games=10, filename="game.json"): + """ + Выполняет замеры времени и запускает игру + """ + start_time = time.time() + print(f"Starting {num_games} simulations at {start_time}") + game = json.loads(Path(filename).read_text()) + run_simulations(num_games, game) + end_time = time.time() + print(f"Finished {num_games} simulations at {end_time}") + print(f"Total time: {(end_time - start_time)} s") + +if __name__ == "__main__": + run_games(NUM_GAMES, FILENAME) From 95c01a255c13effbebf30d72f2d78fd39243e85b Mon Sep 17 00:00:00 2001 From: alexomur Date: Fri, 14 Feb 2025 19:48:41 +0300 Subject: [PATCH 02/12] docs: commentaries in English --- simulator.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/simulator.py b/simulator.py index 76537e6..69ece46 100644 --- a/simulator.py +++ b/simulator.py @@ -10,7 +10,7 @@ FILENAME = "game.json" -# Классы, облегченные для симуляции +# Classes that are easy to simulate class DummyBoard(Board): def __init__(self, game, canvas, label): main.PliTk = DummyPliTk @@ -50,8 +50,8 @@ def set_tile(self, x, y, index): # Функции симуляции def simulate_game(game, seed=None) -> str: """ - Выполняет одну симуляцию игры без графики - и возвращает имя победившего бота. + Runs a single simulation of the game without graphics + and returns the name of the winning bot. """ if seed is not None: random.seed(seed) @@ -67,8 +67,8 @@ def simulate_game(game, seed=None) -> str: def run_simulations(num_games, game): """ - Запускает num_games симуляций игры в отдельных процессах и собирает статистику по победам. - После завершения выводит в консоли, сколько раз победил каждый бот. + Runs num_games game simulations in separate processes and collects statistics on wins. + After completion, displays in the console how many times each bot won. """ results = {} @@ -85,7 +85,7 @@ def run_simulations(num_games, game): def run_games(num_games=10, filename="game.json"): """ - Выполняет замеры времени и запускает игру + Performs timing measurements and starts the game """ start_time = time.time() print(f"Starting {num_games} simulations at {start_time}") From a1a92e4d5a100d3c04677eedcd84ad2eb9a3241e Mon Sep 17 00:00:00 2001 From: alexomur Date: Fri, 14 Feb 2025 20:30:01 +0300 Subject: [PATCH 03/12] docs: information about global parameters --- simulator.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/simulator.py b/simulator.py index 69ece46..fe73535 100644 --- a/simulator.py +++ b/simulator.py @@ -6,11 +6,11 @@ from pathlib import Path import concurrent.futures -NUM_GAMES = 10 -FILENAME = "game.json" +NUM_GAMES = 10 # Number of games running simultaneously. Uses multitasking (be careful using powerful AIs) +GAME_DATA = "game.json" # Location of the file with information about players and levels -# Classes that are easy to simulate +# Classes easy to calculate class DummyBoard(Board): def __init__(self, game, canvas, label): main.PliTk = DummyPliTk @@ -47,7 +47,8 @@ def resize(self, cols, rows): def set_tile(self, x, y, index): pass -# Функции симуляции + +# Simulation funcs def simulate_game(game, seed=None) -> str: """ Runs a single simulation of the game without graphics @@ -96,4 +97,4 @@ def run_games(num_games=10, filename="game.json"): print(f"Total time: {(end_time - start_time)} s") if __name__ == "__main__": - run_games(NUM_GAMES, FILENAME) + run_games(NUM_GAMES, GAME_DATA) From 873f1403a7ebdde1af83fc9cba21a069aaa9ce66 Mon Sep 17 00:00:00 2001 From: alexomur Date: Fri, 14 Feb 2025 20:30:47 +0300 Subject: [PATCH 04/12] docs: Simulation Mode in README --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index ab54e33..5c5df52 100644 --- a/README.md +++ b/README.md @@ -28,4 +28,11 @@ Available check types are: + `"wall"` - tells if tile is impassable + `"level"` - returns current level number starting from 1 +## Simulation Mode +The game supports Simulation Mode for multiple games at once without a graphical interface. + +Starting Simulation Mode: +1. `simulation.py` set up the `NUM_GAMES` and `GAME_DATA` fields +1. Run the `sumilation.py` file + ![screenshot](screenshot.png) From 7926d7f6e5671606b9ca657bf912965de96c4eea Mon Sep 17 00:00:00 2001 From: alexomur Date: Fri, 14 Feb 2025 22:51:29 +0300 Subject: [PATCH 05/12] feat: simulate_game returns Player --- simulator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/simulator.py b/simulator.py index fe73535..b66f5e3 100644 --- a/simulator.py +++ b/simulator.py @@ -1,5 +1,5 @@ import main -from main import Board +from main import Board, Player import random import time import json @@ -49,7 +49,7 @@ def set_tile(self, x, y, index): # Simulation funcs -def simulate_game(game, seed=None) -> str: +def simulate_game(game, seed=None) -> Player: """ Runs a single simulation of the game without graphics and returns the name of the winning bot. From e68df259b668c99c1a6059da00074a8ba04d72c6 Mon Sep 17 00:00:00 2001 From: alexomur Date: Fri, 14 Feb 2025 22:54:16 +0300 Subject: [PATCH 06/12] fix: simulate_game returns str instance --- simulator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/simulator.py b/simulator.py index b66f5e3..ca2e029 100644 --- a/simulator.py +++ b/simulator.py @@ -62,7 +62,7 @@ def simulate_game(game, seed=None) -> Player: while board.play(): pass players_sorted = sorted(board.players, key=lambda p: p.gold, reverse=True) - winner = players_sorted[0].name + winner = players_sorted[0] return winner From d0d8861ba392861fe8a9919f72d2bc45e04b7f57 Mon Sep 17 00:00:00 2001 From: alexomur Date: Fri, 14 Feb 2025 22:55:10 +0300 Subject: [PATCH 07/12] feat: run_simulations shows total gold of winners --- simulator.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/simulator.py b/simulator.py index ca2e029..04b0f87 100644 --- a/simulator.py +++ b/simulator.py @@ -71,17 +71,17 @@ def run_simulations(num_games, game): Runs num_games game simulations in separate processes and collects statistics on wins. After completion, displays in the console how many times each bot won. """ - results = {} + results: dict[str, (int, int)] = {} # player name : (wins, gold) with concurrent.futures.ProcessPoolExecutor() as executor: futures = [executor.submit(simulate_game, game, seed=i) for i in range(num_games)] for future in concurrent.futures.as_completed(futures): winner = future.result() - results[winner] = results.get(winner, 0) + 1 + results[winner.name] = tuple(a + b for a, b in zip(results.get(winner.name, (0, 0)), (1, winner.gold))) print("Simulation Results:") for bot, wins in results.items(): - print(f"{bot}: {wins} wins") + print(f"{bot}: {wins[0]} wins, {wins[1]} total gold") def run_games(num_games=10, filename="game.json"): From e2201522a864231163470262d8e8b1c93797e9d5 Mon Sep 17 00:00:00 2001 From: alexomur Date: Fri, 14 Feb 2025 23:14:02 +0300 Subject: [PATCH 08/12] feat: Simulator shows all the players instead of winners --- simulator.py | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/simulator.py b/simulator.py index 04b0f87..8f59d29 100644 --- a/simulator.py +++ b/simulator.py @@ -49,10 +49,10 @@ def set_tile(self, x, y, index): # Simulation funcs -def simulate_game(game, seed=None) -> Player: +def simulate_game(game, seed=None) -> list[Player]: """ Runs a single simulation of the game without graphics - and returns the name of the winning bot. + and returns the players leaderboard """ if seed is not None: random.seed(seed) @@ -62,26 +62,33 @@ def simulate_game(game, seed=None) -> Player: while board.play(): pass players_sorted = sorted(board.players, key=lambda p: p.gold, reverse=True) - winner = players_sorted[0] - return winner + return players_sorted def run_simulations(num_games, game): """ - Runs num_games game simulations in separate processes and collects statistics on wins. - After completion, displays in the console how many times each bot won. + Runs num_games game simulations in separate processes and collects statistics on all players. + After completion, displays in the console how many wins and total gold each bot accumulated. """ - results: dict[str, (int, int)] = {} # player name : (wins, gold) + # results: player name -> (wins, total_gold) + results: dict[str, tuple[int, int]] = {} with concurrent.futures.ProcessPoolExecutor() as executor: futures = [executor.submit(simulate_game, game, seed=i) for i in range(num_games)] for future in concurrent.futures.as_completed(futures): - winner = future.result() - results[winner.name] = tuple(a + b for a, b in zip(results.get(winner.name, (0, 0)), (1, winner.gold))) + leaderboard: list[Player] = future.result() + for idx, player in enumerate(leaderboard): + wins, total_gold = results.get(player.name, (0, 0)) + if idx == 0: + wins += 1 + total_gold += player.gold + results[player.name] = (wins, total_gold) + + sorted_results = sorted(results.items(), key=lambda item: item[1][0], reverse=True) print("Simulation Results:") - for bot, wins in results.items(): - print(f"{bot}: {wins[0]} wins, {wins[1]} total gold") + for bot, (wins, total_gold) in sorted_results: + print(f"{bot}: {wins} wins, {total_gold} total gold") def run_games(num_games=10, filename="game.json"): @@ -94,7 +101,7 @@ def run_games(num_games=10, filename="game.json"): run_simulations(num_games, game) end_time = time.time() print(f"Finished {num_games} simulations at {end_time}") - print(f"Total time: {(end_time - start_time)} s") + print(f"Total time: {(end_time - start_time):.2f} s") if __name__ == "__main__": run_games(NUM_GAMES, GAME_DATA) From ffdf10396500fd0508619ce99e38d77e3387ebe5 Mon Sep 17 00:00:00 2001 From: alexomur Date: Fri, 14 Feb 2025 23:33:17 +0300 Subject: [PATCH 09/12] feat: run_simulations shows players ranks --- simulator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/simulator.py b/simulator.py index 8f59d29..9936456 100644 --- a/simulator.py +++ b/simulator.py @@ -87,8 +87,8 @@ def run_simulations(num_games, game): sorted_results = sorted(results.items(), key=lambda item: item[1][0], reverse=True) print("Simulation Results:") - for bot, (wins, total_gold) in sorted_results: - print(f"{bot}: {wins} wins, {total_gold} total gold") + for rank, (bot, (wins, total_gold)) in enumerate(sorted_results, start=1): + print(f"{rank}. {bot}: {wins} wins, {total_gold} total gold") def run_games(num_games=10, filename="game.json"): From 28af3fe711757dcc9b8091e60ceb732c5897f8d9 Mon Sep 17 00:00:00 2001 From: alexomur Date: Sat, 15 Feb 2025 10:43:51 +0300 Subject: [PATCH 10/12] docs: dots --- README.md | 4 ++-- simulator.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 5c5df52..a8db00f 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ Available check types are: The game supports Simulation Mode for multiple games at once without a graphical interface. Starting Simulation Mode: -1. `simulation.py` set up the `NUM_GAMES` and `GAME_DATA` fields -1. Run the `sumilation.py` file +1. `simulation.py` set up the `NUM_GAMES` and `GAME_DATA` fields. +1. Run the `sumilation.py` file. ![screenshot](screenshot.png) diff --git a/simulator.py b/simulator.py index 9936456..4797569 100644 --- a/simulator.py +++ b/simulator.py @@ -51,8 +51,8 @@ def set_tile(self, x, y, index): # Simulation funcs def simulate_game(game, seed=None) -> list[Player]: """ - Runs a single simulation of the game without graphics - and returns the players leaderboard + Runs a single simulation of the game without graphics. + and returns the players leaderboard. """ if seed is not None: random.seed(seed) @@ -93,7 +93,7 @@ def run_simulations(num_games, game): def run_games(num_games=10, filename="game.json"): """ - Performs timing measurements and starts the game + Performs timing measurements and starts the game. """ start_time = time.time() print(f"Starting {num_games} simulations at {start_time}") From 1a5a83675f0fb526cd6f22bf9ba1891277c610c3 Mon Sep 17 00:00:00 2001 From: alexomur Date: Sat, 15 Feb 2025 10:51:39 +0300 Subject: [PATCH 11/12] chore: unneeded dot & func default values --- simulator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/simulator.py b/simulator.py index 4797569..cd3811b 100644 --- a/simulator.py +++ b/simulator.py @@ -51,7 +51,7 @@ def set_tile(self, x, y, index): # Simulation funcs def simulate_game(game, seed=None) -> list[Player]: """ - Runs a single simulation of the game without graphics. + Runs a single simulation of the game without graphics and returns the players leaderboard. """ if seed is not None: @@ -91,7 +91,7 @@ def run_simulations(num_games, game): print(f"{rank}. {bot}: {wins} wins, {total_gold} total gold") -def run_games(num_games=10, filename="game.json"): +def run_games(num_games=NUM_GAMES, filename=GAME_DATA): """ Performs timing measurements and starts the game. """ From 153fdea7d88827311d85b44d89e00a34614ac9fe Mon Sep 17 00:00:00 2001 From: alexomur Date: Tue, 29 Apr 2025 23:06:55 +0300 Subject: [PATCH 12/12] docs: typos --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a8db00f..9e8f469 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ Available check types are: The game supports Simulation Mode for multiple games at once without a graphical interface. Starting Simulation Mode: -1. `simulation.py` set up the `NUM_GAMES` and `GAME_DATA` fields. -1. Run the `sumilation.py` file. +1. `simulator.py` set up the `NUM_GAMES` and `GAME_DATA` fields. +1. Run the `simulator.py` file. ![screenshot](screenshot.png)