From 37521f4d5e729f8dafa1fc005b4ceabaccd780ac Mon Sep 17 00:00:00 2001 From: kubekj Date: Sun, 17 Mar 2024 15:04:11 +0100 Subject: [PATCH 1/4] Added a way to save data from each run (excluding those which already appear and have same parameters) --- game_data.json | 70 +++++++++++++++++++++++ src/GameController.py | 15 ++--- src/benchmarking/GameAnalytics.py | 83 ++++++++++++++++++++++++++++ src/benchmarking/__init__.py | 0 src/players/GraphicsHumanPlayer.py | 1 + src/players/MinimaxAIPlayer.py | 1 + src/players/NonRepeatRandomPlayer.py | 1 + src/players/Player.py | 10 ++++ src/players/RandomPlayer.py | 5 ++ 9 files changed, 177 insertions(+), 9 deletions(-) create mode 100644 game_data.json create mode 100644 src/benchmarking/GameAnalytics.py create mode 100644 src/benchmarking/__init__.py diff --git a/game_data.json b/game_data.json new file mode 100644 index 0000000..08545d1 --- /dev/null +++ b/game_data.json @@ -0,0 +1,70 @@ +{ + "games": [ + { + "game_duration": 2.4415015410631895, + "total_turns": 129, + "players": [ + { + "player_type": "minimax", + "average_time_per_action": 0.002910146207289129, + "move_count": 61, + "expanded_states": 2408, + "max_depth": 2, + "player_id": 1 + }, + { + "player_type": "minimax", + "average_time_per_action": 0.02693444926647798, + "move_count": 68, + "expanded_states": 28215, + "max_depth": 4, + "player_id": 2 + } + ] + }, + { + "game_duration": 4.5904443331528455, + "total_turns": 129, + "players": [ + { + "player_type": "minimax", + "average_time_per_action": 0.029867485394114972, + "move_count": 54, + "expanded_states": 24515, + "max_depth": 4, + "player_id": 1 + }, + { + "player_type": "minimax", + "average_time_per_action": 0.021879869500796, + "move_count": 75, + "expanded_states": 24830, + "max_depth": 4, + "player_id": 2 + } + ] + }, + { + "game_duration": 1.0762333329766989, + "total_turns": 119, + "players": [ + { + "player_type": "minimax", + "average_time_per_action": 0.002952579141128808, + "move_count": 60, + "expanded_states": 2361, + "max_depth": 2, + "player_id": 1 + }, + { + "player_type": "minimax", + "average_time_per_action": 0.0029666391438094237, + "move_count": 59, + "expanded_states": 2342, + "max_depth": 2, + "player_id": 2 + } + ] + } + ] +} \ No newline at end of file diff --git a/src/GameController.py b/src/GameController.py index efdcc9f..2828072 100644 --- a/src/GameController.py +++ b/src/GameController.py @@ -1,5 +1,6 @@ import time +from benchmarking.GameAnalytics import GameAnalytics from players.NonRepeatRandomPlayer import NonRepeatingRandomPlayer from game_problem.Heuristic import WeightedHeuristic, SumOfPegsInCornerHeuristic, AverageManhattanToCornerHeuristic, \ AverageEuclideanToCornerHeuristic, MaxManhattanToCornerHeuristic, EnsuredNormalizedHeuristic @@ -25,6 +26,7 @@ def create_player(player_type, depth=6, gui=None, problem=None, max_player=None, class GameController: def __init__(self, verbose=True, use_graphics=True, args=None): + self.analytics = GameAnalytics() self.verbose = verbose # Flag to print the state and action applied self.use_graphics = use_graphics # Flag to use the GUI self.problem = ChineseCheckers(triangle_size=3) # Initialize the game problem @@ -42,8 +44,8 @@ def handle_game_setup(self, args): if args.first_player is None or args.second_player is None: self.players = [ - MinimaxAIPlayer(self.problem, 1, 6, heuristic, verbose=self.verbose), - MinimaxAIPlayer(self.problem, 2, 6, heuristic, verbose=self.verbose) + MinimaxAIPlayer(self.problem, 1, 2, heuristic, verbose=self.verbose), + MinimaxAIPlayer(self.problem, 2, 4, heuristic, verbose=self.verbose) ] else: player1_depth = args.first_minimax_depth if args.first_player == 'minimax' else None @@ -92,13 +94,8 @@ def game_loop(self): print(f'Player {state.player} has utility: {self.problem.utility(state, state.player)}') # Print the game duration and the performance metrics of the players - print(f'Game elapsed time: {game_duration:0.8f} | Turns = {turn}') - for i, player in enumerate(self.players): - print('-----') - print(f'Player {i + 1} average time: {player.average_time_spent_on_actions:0.8f}') - print(f'Player {i + 1} move count: {player.moves_count:0.8f}') - if hasattr(player, 'evaluated_states_count'): - print(f'Player {i + 1} expanded states: {player.evaluated_states_count}') + self.analytics.add_game_data(game_duration, turn, self.players) + self.analytics.print_game_data() # Wait until quit is pressed if self.gui: diff --git a/src/benchmarking/GameAnalytics.py b/src/benchmarking/GameAnalytics.py new file mode 100644 index 0000000..0594115 --- /dev/null +++ b/src/benchmarking/GameAnalytics.py @@ -0,0 +1,83 @@ +import json +import matplotlib.pyplot as plt + + +class GameAnalytics: + def __init__(self, filename='game_data.json'): + self.data = {'games': []} + self.filename = filename + self.load_existing_data() + + def load_existing_data(self): + try: + with open(self.filename, 'r') as file: + self.data = json.load(file) + except (FileNotFoundError, json.JSONDecodeError): + pass + + def add_game_data(self, game_duration, total_turns, players): + players_data = [] + for i, player in enumerate(players): + player_dict = player.to_dict() + player_dict['player_id'] = i + 1 + players_data.append(player_dict) + + existing_game_index = None + for index, existing_game in enumerate(self.data['games']): + existing_players = existing_game['players'] + if all(player['player_type'] == new_player['player_type'] and + player['max_depth'] == new_player['max_depth'] + for player, new_player in zip(existing_players, players_data)): + existing_game_index = index + break + + game_data = { + 'game_duration': game_duration, + 'total_turns': total_turns, + 'players': players_data + } + + if existing_game_index is not None: + self.data['games'][existing_game_index] = game_data + else: + self.data['games'].append(game_data) + + def print_game_data(self): + if not self.data['games']: + print("No games have been recorded.") + return + + # Access the most recent game's data + game = self.data['games'][-1] + + print('Most Recent Game:') + print(f"Game elapsed time: {game['game_duration']:0.8f} | Turns = {game['total_turns']}") + for i, player_data in enumerate(game['players']): + print('-----') + print(f"Player {player_data['player_id']} average time: {player_data['average_time_per_action']:0.8f}") + print(f"Player {player_data['player_id']} move count: {player_data['move_count']}") + if 'expanded_states' in player_data: + print(f"Player {player_data['player_id']} expanded states: {player_data['expanded_states']}") + print('\n') + + def save_to_file(self): + with open(self.filename, 'w') as file: + json.dump(self.data, file, indent=4) + + @staticmethod + def load_from_file(filename): + with open(filename, 'r') as file: + return json.load(file) + + def plot_average_time_per_action(self): + for game in self.data['games']: + player_ids = [player['player_id'] for player in game['players']] + average_times = [player['average_time_per_action'] for player in game['players']] + + plt.figure(figsize=(10, 6)) + plt.bar(player_ids, average_times, color='skyblue') + plt.xlabel('Player ID') + plt.ylabel('Average Time per Action (s)') + plt.title('Average Time per Action for Each Player') + plt.xticks(player_ids) + plt.show() diff --git a/src/benchmarking/__init__.py b/src/benchmarking/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/players/GraphicsHumanPlayer.py b/src/players/GraphicsHumanPlayer.py index 7796067..a9df339 100644 --- a/src/players/GraphicsHumanPlayer.py +++ b/src/players/GraphicsHumanPlayer.py @@ -12,6 +12,7 @@ class GraphicsHumanPlayer(Player): def __init__(self, gui: Graphics): super().__init__() + self._player_type = 'human' self.gui = gui def get_action(self, problem: GameProblem, state: State) -> Action: diff --git a/src/players/MinimaxAIPlayer.py b/src/players/MinimaxAIPlayer.py index ff35f61..3882ba6 100644 --- a/src/players/MinimaxAIPlayer.py +++ b/src/players/MinimaxAIPlayer.py @@ -29,6 +29,7 @@ def __init__( # Sets up multiprocessing super().__init__() mp.freeze_support() + self._player_type = 'minimax' self.prob = problem self.verbose = verbose self.MAX_PLAYER = max_player diff --git a/src/players/NonRepeatRandomPlayer.py b/src/players/NonRepeatRandomPlayer.py index abe1cc9..ca56de7 100644 --- a/src/players/NonRepeatRandomPlayer.py +++ b/src/players/NonRepeatRandomPlayer.py @@ -16,6 +16,7 @@ class NonRepeatingRandomPlayer(Player): def __init__(self): super().__init__() + self._player_type = 'nonrepeatrandom' self.previous_actions_and_states = [] def get_action(self, problem: GameProblem, state: State) -> Optional[Action]: diff --git a/src/players/Player.py b/src/players/Player.py index fc7beab..91e79bc 100644 --- a/src/players/Player.py +++ b/src/players/Player.py @@ -12,6 +12,7 @@ class Player(ABC): def __init__(self): self._total_time_spent_on_taking_actions = 0.0 self._moves_count = 0 + self._player_type = 'minimax' @abstractmethod def get_action(self, problem: GameProblem, state: State) -> Action: @@ -24,3 +25,12 @@ def average_time_spent_on_actions(self) -> float: @property def moves_count(self): return int(self._moves_count) + + def to_dict(self) -> dict: + return { + 'player_type': self._player_type, + 'average_time_per_action': self.average_time_spent_on_actions, + 'move_count': self.moves_count, + 'expanded_states': getattr(self, 'evaluated_states_count', 'Non applicable'), + 'max_depth': getattr(self, 'max_depth', 'Non applicable') + } diff --git a/src/players/RandomPlayer.py b/src/players/RandomPlayer.py index 1bd45bb..46a6f4c 100644 --- a/src/players/RandomPlayer.py +++ b/src/players/RandomPlayer.py @@ -11,6 +11,11 @@ class RandomPlayer(Player): """ Random player (confused AI) - selects an action randomly from the list of valid actions """ + + def __init__(self): + super().__init__() + self._player_type = 'random' + def get_action(self, problem: GameProblem, state: State) -> Action: timer = time.perf_counter() From 7d943e77d944bc4668e3f4a9373fac7a68658577 Mon Sep 17 00:00:00 2001 From: kubekj Date: Sun, 17 Mar 2024 17:12:26 +0100 Subject: [PATCH 2/4] Added winner to game_data.json --- game_data.json | 220 ++++++++++++++++++++++++++---- main.py | 6 +- src/GameController.py | 4 +- src/benchmarking/GameAnalytics.py | 48 +++---- 4 files changed, 215 insertions(+), 63 deletions(-) diff --git a/game_data.json b/game_data.json index 08545d1..c253e85 100644 --- a/game_data.json +++ b/game_data.json @@ -1,70 +1,234 @@ { "games": [ { - "game_duration": 2.4415015410631895, + "game_duration": 16.745494375005364, "total_turns": 129, "players": [ { "player_type": "minimax", - "average_time_per_action": 0.002910146207289129, - "move_count": 61, - "expanded_states": 2408, - "max_depth": 2, + "average_time_per_action": 0.2716845485718093, + "move_count": 54, + "expanded_states": 154477, + "max_depth": 6, "player_id": 1 }, { "player_type": "minimax", - "average_time_per_action": 0.02693444926647798, - "move_count": 68, - "expanded_states": 28215, + "average_time_per_action": 0.027594135034208496, + "move_count": 75, + "expanded_states": 24830, "max_depth": 4, "player_id": 2 } - ] + ], + "winner": 1 }, { - "game_duration": 4.5904443331528455, - "total_turns": 129, + "game_duration": 68.68971466599032, + "total_turns": 131, "players": [ { "player_type": "minimax", - "average_time_per_action": 0.029867485394114972, + "average_time_per_action": 0.25141715974098555, "move_count": 54, - "expanded_states": 24515, - "max_depth": 4, + "expanded_states": 154477, + "max_depth": 6, "player_id": 1 }, { "player_type": "minimax", - "average_time_per_action": 0.021879869500796, - "move_count": 75, - "expanded_states": 24830, - "max_depth": 4, + "average_time_per_action": 0.7157158245840534, + "move_count": 77, + "expanded_states": 701087, + "max_depth": 7, "player_id": 2 } - ] + ], + "winner": 1 }, { - "game_duration": 1.0762333329766989, - "total_turns": 119, + "game_duration": 19.38588395807892, + "total_turns": 165, "players": [ { "player_type": "minimax", - "average_time_per_action": 0.002952579141128808, - "move_count": 60, - "expanded_states": 2361, + "average_time_per_action": 0.2308116701308144, + "move_count": 83, + "expanded_states": 249102, + "max_depth": 6, + "player_id": 1 + }, + { + "player_type": "minimax", + "average_time_per_action": 0.0027445289959404165, + "move_count": 82, + "expanded_states": 3087, "max_depth": 2, + "player_id": 2 + } + ], + "winner": 1 + }, + { + "game_duration": 14.320972041925415, + "total_turns": 126, + "players": [ + { + "player_type": "minimax", + "average_time_per_action": 0.22224802276750485, + "move_count": 62, + "expanded_states": 176173, + "max_depth": 6, + "player_id": 1 + }, + { + "player_type": "minimax", + "average_time_per_action": 0.008416822915023658, + "move_count": 64, + "expanded_states": 6989, + "max_depth": 3, + "player_id": 2 + } + ], + "winner": 1 + }, + { + "game_duration": 14.113730333046988, + "total_turns": 126, + "players": [ + { + "player_type": "minimax", + "average_time_per_action": 0.2190078850722902, + "move_count": 62, + "expanded_states": 176173, + "max_depth": 6, + "player_id": 1 + }, + { + "player_type": "minimax", + "average_time_per_action": 0.008318463627801975, + "move_count": 64, + "expanded_states": 6989, + "max_depth": 3, + "player_id": 2 + } + ], + "winner": 1 + }, + { + "game_duration": 18.221036583883688, + "total_turns": 131, + "players": [ + { + "player_type": "minimax", + "average_time_per_action": 0.2326052507808156, + "move_count": 54, + "expanded_states": 154477, + "max_depth": 6, + "player_id": 1 + }, + { + "player_type": "minimax", + "average_time_per_action": 0.07347076082323956, + "move_count": 77, + "expanded_states": 76460, + "max_depth": 5, + "player_id": 2 + } + ], + "winner": 1 + }, + { + "game_duration": 28.010977083118632, + "total_turns": 131, + "players": [ + { + "player_type": "minimax", + "average_time_per_action": 0.22857431330528385, + "move_count": 54, + "expanded_states": 154477, + "max_depth": 6, + "player_id": 1 + }, + { + "player_type": "minimax", + "average_time_per_action": 0.20343920993074388, + "move_count": 77, + "expanded_states": 199211, + "max_depth": 6, + "player_id": 2 + } + ], + "winner": 1 + }, + { + "game_duration": 166.40177620900795, + "total_turns": 131, + "players": [ + { + "player_type": "minimax", + "average_time_per_action": 0.2275209467539012, + "move_count": 54, + "expanded_states": 154477, + "max_depth": 6, + "player_id": 1 + }, + { + "player_type": "minimax", + "average_time_per_action": 2.0014604518806878, + "move_count": 77, + "expanded_states": 2104428, + "max_depth": 8, + "player_id": 2 + } + ], + "winner": 1 + }, + { + "game_duration": 439.06379320891574, + "total_turns": 131, + "players": [ + { + "player_type": "minimax", + "average_time_per_action": 0.23054754627540847, + "move_count": 54, + "expanded_states": 154477, + "max_depth": 6, "player_id": 1 }, { "player_type": "minimax", - "average_time_per_action": 0.0029666391438094237, - "move_count": 59, - "expanded_states": 2342, + "average_time_per_action": 5.540401767385978, + "move_count": 77, + "expanded_states": 5256645, + "max_depth": 9, + "player_id": 2 + } + ], + "winner": 1 + }, + { + "game_duration": 2.0372937091160566, + "total_turns": 129, + "players": [ + { + "player_type": "minimax", + "average_time_per_action": 0.002859961134610606, + "move_count": 61, + "expanded_states": 2408, "max_depth": 2, + "player_id": 1 + }, + { + "player_type": "minimax", + "average_time_per_action": 0.027369096816983074, + "move_count": 68, + "expanded_states": 28215, + "max_depth": 4, "player_id": 2 } - ] + ], + "winner": 2 } ] } \ No newline at end of file diff --git a/main.py b/main.py index 7cdf31a..d500633 100644 --- a/main.py +++ b/main.py @@ -1,6 +1,8 @@ import argparse +import sys -from src.GameController import GameController +sys.path.append("src") +from GameController import GameController if __name__ == "__main__": parser = argparse.ArgumentParser(description='Chinese Checkers game with AI and player options.') @@ -16,6 +18,6 @@ args = parser.parse_args() - controller = GameController(verbose=False, use_graphics=True, args=args) + controller = GameController(verbose=False, use_graphics=False, args=args) controller.game_loop() diff --git a/src/GameController.py b/src/GameController.py index 2828072..00211a4 100644 --- a/src/GameController.py +++ b/src/GameController.py @@ -93,8 +93,10 @@ def game_loop(self): game_duration = time.perf_counter() - game_start_timer print(f'Player {state.player} has utility: {self.problem.utility(state, state.player)}') + winner = state.player if self.problem.utility(state, state.player) >= 0 else 3 - state.player + # Print the game duration and the performance metrics of the players - self.analytics.add_game_data(game_duration, turn, self.players) + self.analytics.add_game_data(game_duration, turn, self.players, winner) self.analytics.print_game_data() # Wait until quit is pressed diff --git a/src/benchmarking/GameAnalytics.py b/src/benchmarking/GameAnalytics.py index 0594115..9f070d8 100644 --- a/src/benchmarking/GameAnalytics.py +++ b/src/benchmarking/GameAnalytics.py @@ -1,46 +1,38 @@ import json import matplotlib.pyplot as plt +import os class GameAnalytics: def __init__(self, filename='game_data.json'): - self.data = {'games': []} self.filename = filename - self.load_existing_data() + if os.path.exists(self.filename): + self.data = self.load_from_file(self.filename) + else: + self.data = {'games': []} def load_existing_data(self): try: with open(self.filename, 'r') as file: self.data = json.load(file) except (FileNotFoundError, json.JSONDecodeError): - pass + self.data = {'games': []} - def add_game_data(self, game_duration, total_turns, players): + def add_game_data(self, game_duration, total_turns, players, winner): players_data = [] for i, player in enumerate(players): player_dict = player.to_dict() - player_dict['player_id'] = i + 1 + player_dict['player_id'] = i + 1 # Assign player ID based on enumeration players_data.append(player_dict) - existing_game_index = None - for index, existing_game in enumerate(self.data['games']): - existing_players = existing_game['players'] - if all(player['player_type'] == new_player['player_type'] and - player['max_depth'] == new_player['max_depth'] - for player, new_player in zip(existing_players, players_data)): - existing_game_index = index - break - game_data = { 'game_duration': game_duration, 'total_turns': total_turns, - 'players': players_data + 'players': players_data, + 'winner': winner } - - if existing_game_index is not None: - self.data['games'][existing_game_index] = game_data - else: - self.data['games'].append(game_data) + self.data['games'].append(game_data) + self.save_to_file() def print_game_data(self): if not self.data['games']: @@ -56,6 +48,7 @@ def print_game_data(self): print('-----') print(f"Player {player_data['player_id']} average time: {player_data['average_time_per_action']:0.8f}") print(f"Player {player_data['player_id']} move count: {player_data['move_count']}") + print(f"Player {player_data['player_id']} move count: {player_data['move_count']}") if 'expanded_states' in player_data: print(f"Player {player_data['player_id']} expanded states: {player_data['expanded_states']}") print('\n') @@ -69,15 +62,6 @@ def load_from_file(filename): with open(filename, 'r') as file: return json.load(file) - def plot_average_time_per_action(self): - for game in self.data['games']: - player_ids = [player['player_id'] for player in game['players']] - average_times = [player['average_time_per_action'] for player in game['players']] - - plt.figure(figsize=(10, 6)) - plt.bar(player_ids, average_times, color='skyblue') - plt.xlabel('Player ID') - plt.ylabel('Average Time per Action (s)') - plt.title('Average Time per Action for Each Player') - plt.xticks(player_ids) - plt.show() + def plot(self): + # TODO: Plot valuable data + return From a700dd60c5eb1209be9ff2447855cdeb00f38844 Mon Sep 17 00:00:00 2001 From: kubekj Date: Sun, 17 Mar 2024 17:41:27 +0100 Subject: [PATCH 3/4] Finished plotting function --- game_data.json | 22 ++++++------- main.py | 33 ++++++++++++-------- src/GameController.py | 3 ++ src/benchmarking/GameAnalytics.py | 51 +++++++++++++++++++++++++++++-- 4 files changed, 84 insertions(+), 25 deletions(-) diff --git a/game_data.json b/game_data.json index c253e85..1d41f42 100644 --- a/game_data.json +++ b/game_data.json @@ -208,27 +208,27 @@ "winner": 1 }, { - "game_duration": 2.0372937091160566, - "total_turns": 129, + "game_duration": 1329.2665478750132, + "total_turns": 131, "players": [ { "player_type": "minimax", - "average_time_per_action": 0.002859961134610606, - "move_count": 61, - "expanded_states": 2408, - "max_depth": 2, + "average_time_per_action": 0.23267057559590926, + "move_count": 54, + "expanded_states": 154477, + "max_depth": 6, "player_id": 1 }, { "player_type": "minimax", - "average_time_per_action": 0.027369096816983074, - "move_count": 68, - "expanded_states": 28215, - "max_depth": 4, + "average_time_per_action": 17.09996407194813, + "move_count": 77, + "expanded_states": 17763636, + "max_depth": 10, "player_id": 2 } ], - "winner": 2 + "winner": 1 } ] } \ No newline at end of file diff --git a/main.py b/main.py index d500633..43f0d08 100644 --- a/main.py +++ b/main.py @@ -1,23 +1,32 @@ import argparse import sys +from benchmarking.GameAnalytics import GameAnalytics + sys.path.append("src") from GameController import GameController if __name__ == "__main__": - parser = argparse.ArgumentParser(description='Chinese Checkers game with AI and player options.') - parser.add_argument('--first-player', choices=['human', 'minimax'], required=False, - help='Type of the first player.') - parser.add_argument('--first-minimax-depth', type=int, default=6, required=False, - help='Minimax depth for the first player, if applicable.') - parser.add_argument('--second-player', choices=['random', 'nonrepeatrandom', 'minimax'], required=False, - help='Type of the second player.') - parser.add_argument('--second-minimax-depth', type=int, default=6, required=False, - help='Minimax depth for the second player, if applicable.') + if True: + analytics = GameAnalytics() + analytics.load_from_file('game_data.json') + analytics.plot() + else: + + parser = argparse.ArgumentParser(description='Chinese Checkers game with AI and player options.') + + parser.add_argument('--first-player', choices=['human', 'minimax'], required=False, + help='Type of the first player.') + parser.add_argument('--first-minimax-depth', type=int, default=6, required=False, + help='Minimax depth for the first player, if applicable.') + parser.add_argument('--second-player', choices=['random', 'nonrepeatrandom', 'minimax'], required=False, + help='Type of the second player.') + parser.add_argument('--second-minimax-depth', type=int, default=6, required=False, + help='Minimax depth for the second player, if applicable.') - args = parser.parse_args() + args = parser.parse_args() - controller = GameController(verbose=False, use_graphics=False, args=args) + controller = GameController(verbose=False, use_graphics=False, args=args) - controller.game_loop() + controller.game_loop() diff --git a/src/GameController.py b/src/GameController.py index 00211a4..eba1c7e 100644 --- a/src/GameController.py +++ b/src/GameController.py @@ -99,6 +99,9 @@ def game_loop(self): self.analytics.add_game_data(game_duration, turn, self.players, winner) self.analytics.print_game_data() + self.analytics.load_from_file('game_data.json') + self.analytics.plot() + # Wait until quit is pressed if self.gui: while True: diff --git a/src/benchmarking/GameAnalytics.py b/src/benchmarking/GameAnalytics.py index 9f070d8..3a4ad21 100644 --- a/src/benchmarking/GameAnalytics.py +++ b/src/benchmarking/GameAnalytics.py @@ -1,7 +1,11 @@ import json +from collections import defaultdict + import matplotlib.pyplot as plt import os +import numpy as np + class GameAnalytics: def __init__(self, filename='game_data.json'): @@ -63,5 +67,48 @@ def load_from_file(filename): return json.load(file) def plot(self): - # TODO: Plot valuable data - return + expanded_states_per_depth = defaultdict(list) + average_time_per_depth = defaultdict(list) + + for game in self.data['games']: + for player in game['players']: + depth = player['max_depth'] + expanded_states_per_depth[depth].append(player['expanded_states']) + average_time_per_depth[depth].append(player['average_time_per_action']) + + # Calculate average expanded states and time per depth + avg_expanded_states = {depth: np.mean(states) for depth, states in expanded_states_per_depth.items()} + avg_time_per_action = {depth: np.mean(times) for depth, times in average_time_per_depth.items()} + + # Sort depths for consistent plotting + sorted_depths = sorted(avg_expanded_states.keys()) + avg_states = [avg_expanded_states[depth] for depth in sorted_depths] + avg_times = [avg_time_per_action[depth] for depth in sorted_depths] + + # Plotting + fig, ax1 = plt.subplots(figsize=(10, 6)) + + # Expanded states + color = 'tab:blue' + ax1.set_xlabel('Max Depth') + ax1.set_ylabel('Average Expanded States', color=color) + bars = ax1.bar(sorted_depths, avg_states, color='skyblue', label='Expanded States') + ax1.tick_params(axis='y', labelcolor=color) + + # Highlight the bar for depth 6 + for bar, depth in zip(bars, sorted_depths): + if depth == 6: + bar.set_color('lightgreen') # Highlight depth 6 + ax1.text(bar.get_x() + bar.get_width() / 2, bar.get_height(), 'Winner', ha='center', va='bottom') + + # Average time per action + ax2 = ax1.twinx() + color = 'tab:red' + ax2.set_ylabel('Average Time per Action (s)', color=color) + ax2.plot(sorted_depths, avg_times, color=color, marker='o', label='Time per Action') + ax2.tick_params(axis='y', labelcolor=color) + + fig.tight_layout() + plt.title('Game Depth Analysis: Expanded States and Time per Action') + plt.savefig('game_depth_analysis.jpg', format='jpg', dpi=300) + plt.show() \ No newline at end of file From dedada8cefd9512c042abb98fa8c0dcca0e81361 Mon Sep 17 00:00:00 2001 From: kubekj Date: Sun, 17 Mar 2024 17:42:07 +0100 Subject: [PATCH 4/4] Fixed main --- main.py | 33 ++++++++++++--------------------- 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/main.py b/main.py index 43f0d08..d500633 100644 --- a/main.py +++ b/main.py @@ -1,32 +1,23 @@ import argparse import sys -from benchmarking.GameAnalytics import GameAnalytics - sys.path.append("src") from GameController import GameController if __name__ == "__main__": + parser = argparse.ArgumentParser(description='Chinese Checkers game with AI and player options.') - if True: - analytics = GameAnalytics() - analytics.load_from_file('game_data.json') - analytics.plot() - else: - - parser = argparse.ArgumentParser(description='Chinese Checkers game with AI and player options.') - - parser.add_argument('--first-player', choices=['human', 'minimax'], required=False, - help='Type of the first player.') - parser.add_argument('--first-minimax-depth', type=int, default=6, required=False, - help='Minimax depth for the first player, if applicable.') - parser.add_argument('--second-player', choices=['random', 'nonrepeatrandom', 'minimax'], required=False, - help='Type of the second player.') - parser.add_argument('--second-minimax-depth', type=int, default=6, required=False, - help='Minimax depth for the second player, if applicable.') + parser.add_argument('--first-player', choices=['human', 'minimax'], required=False, + help='Type of the first player.') + parser.add_argument('--first-minimax-depth', type=int, default=6, required=False, + help='Minimax depth for the first player, if applicable.') + parser.add_argument('--second-player', choices=['random', 'nonrepeatrandom', 'minimax'], required=False, + help='Type of the second player.') + parser.add_argument('--second-minimax-depth', type=int, default=6, required=False, + help='Minimax depth for the second player, if applicable.') - args = parser.parse_args() + args = parser.parse_args() - controller = GameController(verbose=False, use_graphics=False, args=args) + controller = GameController(verbose=False, use_graphics=False, args=args) - controller.game_loop() + controller.game_loop()