diff --git a/scratchpad/game_engine_refractoring_plan.md b/scratchpad/game_engine_refractoring_plan.md index 8e7e7d4..f83adff 100644 --- a/scratchpad/game_engine_refractoring_plan.md +++ b/scratchpad/game_engine_refractoring_plan.md @@ -15,13 +15,12 @@ **Phase 2: Continue Game Functionality** 1. **Modify `enter_main_game_loop`:** - - Add a parameter `continue_from_state` to `enter_main_game_loop`. - - If `continue_from_state` is provided, call `load_state` to load the game state from the specified file. - - If `continue_from_state` is not provided, start a new game as usual. + - Remove the `continue_from_state` parameter as it's always assumed the game will load from a state. + - Directly call `load_state` at the beginning of the function to load the game state from the predefined file. 2. **Update Game Loop:** - - Modify the game loop to use the loaded `round_number` and `current_player_index` if a state was loaded. - - Ensure that the game loop continues from the correct round and player's turn. + - Use the loaded `round_number` and `current_player_index` to control the game loop. + - Instead of starting from `round_number = 0`, use the loaded `round_number` as the starting point. - Use `current_player_index` to determine which player should act next. - After each player's turn, increment `current_player_index` and wrap around to 0 if it reaches the end of the player list. @@ -36,35 +35,6 @@ - Provide a mechanism for users to select a saved state and continue playing from that point. - Display the current round number and the player whose turn it is in the GUI. -**Example Code Snippets:** - -```python -# src/game/game_engine.py - -class GameEngine(BaseModel): - # ... other attributes ... - round_number: int = 0 - current_player_index: int = 0 - - def save_state(self, filename: str) -> None: - with open(filename, "w") as f: - json.dump(self.dict(), f) - - def load_state(self, filename: str) -> None: - with open(filename, "r") as f: - data = json.load(f) - self.__dict__.update(data) - - def enter_main_game_loop(self, continue_from_state: Optional[str] = None) -> None: - if continue_from_state: - self.load_state(continue_from_state) - # ... rest of the game loop ... - self.round_number += 1 - current_player = self.state.players[self.current_player_index] - # ... get actions from current_player ... - self.current_player_index = (self.current_player_index + 1) % len(self.state.players) - # ... rest of the game loop ... -``` **Additional Considerations:** diff --git a/src/demo_gui.py b/src/demo_gui.py index 093d272..97a8bb2 100644 --- a/src/demo_gui.py +++ b/src/demo_gui.py @@ -24,7 +24,7 @@ def main(): model_name = "gpt-4o-mini" # Or any other suitable model name player_names = ["Wateusz", "Waciej", "Warek", "Wojtek", "WafaƂ", "Wymek"] - players = [FakeAIPlayer(name=player_names[i], llm_model_name=model_name) for i in range(3)] + players = [FakeAIPlayer(name=player_names[i], llm_model_name="fake") for i in range(6)] game_engine.load_players(players, impostor_count=1) game_engine.init_game() @@ -36,8 +36,10 @@ def main(): # json_game_state.players[0].state.tasks[0] = "DONE" # gui_handler.update_gui(json_game_state) - game_engine.enter_main_game_loop() + while not game_engine.perform_step(): + gui_handler.update_gui(game_engine.state) gui_handler.update_gui(game_engine.state) + st.text("Game has ended") st.json(game_engine.to_dict(), expanded=False) # Display final game state st.text("\n".join(game_engine.state.playthrough)) # Display final game log diff --git a/src/game/consts.py b/src/game/consts.py index e43d269..93f80e1 100644 --- a/src/game/consts.py +++ b/src/game/consts.py @@ -4,6 +4,7 @@ NUM_CHATS = 5 MIN_NAME = 2 IMPOSTOR_COOLDOWN = 1 +STATE_FILE = "game_state.json" PLAYER_COLORS = [ "red", "blue", diff --git a/src/game/game_engine.py b/src/game/game_engine.py index fac183e..b524dfd 100644 --- a/src/game/game_engine.py +++ b/src/game/game_engine.py @@ -1,4 +1,6 @@ import time + +import yaml from game.game_state import GameState from game.models.engine import ( GamePhase, @@ -17,11 +19,15 @@ from game.gui_handler import GUIHandler import json +from game.players.ai import AIPlayer +from game.players.fake_ai import FakeAIPlayer +from game.models.tasks import ShortTask, LongTask, Task + class GameEngine(BaseModel): state: GameState = Field(default_factory=GameState) nobody: HumanPlayer = Field(default_factory=lambda: HumanPlayer(name="Nobody")) - gui_handler: GUIHandler = Field(default_factory=GUIHandler) # Initialize GUIHandler + gui_handler: GUIHandler = Field(default_factory=GUIHandler) # Initialize GUIHandler def load_players(self, players: List[Player], impostor_count: int = 1) -> None: if len(players) < 3: @@ -57,116 +63,124 @@ def load_players(self, players: List[Player], impostor_count: int = 1) -> None: "Number of impostors cannot be greater than or equal to the number of crewmates." ) + def init_game(self, game_state: GameState) -> None: + self.state.set_stage(GamePhase.ACTION_PHASE) + def init_game(self) -> None: self.state.set_stage(GamePhase.ACTION_PHASE) + self.load_state() - def enter_main_game_loop(self, continue_from_state: Optional[str] = None) -> None: + def perform_step(self) -> bool: """ - Main game loop that controls the flow of the game. + This is the beginning of player turn. player state is already copied to history, cleared and can be freely modified - :param continue_from_state: If set, the game will load the state from the specified file. + :return: True if the game is over, False otherwise """ - check_game_over = self.set_game_stage(continue_from_state) - - print("Game started!") - while not check_game_over(): - print(f"Game stage: {self.state.game_stage}") - if self.state.DEBUG: - time.sleep(0.5) - - chosen_actions = self.get_player_actions() - someone_reported = self.update_game_state(chosen_actions) + print( + f"Round: {self.state.round_number}. Player to act: {self.state.player_to_act_next}" + ) + if self.state.game_stage == GamePhase.ACTION_PHASE: + self.perform_action_step() + elif self.state.game_stage == GamePhase.DISCUSS: + self.perform_discussion_step() + else: + raise ValueError("Probably main game stage. Init game first") + if self.check_game_over(): + self.end_game() + return True + self.state.player_to_act_next += 1 - self.gui_handler.update_gui(self.state) - - if someone_reported and continue_from_state is None: - self.discussion_loop() + if self.state.player_to_act_next == len(self.state.players): + self.state.player_to_act_next = 0 + self.state.round_number += 1 + for player in self.state.players: + player.log_state_new_round() + if ( + self.state.round_of_discussion_start + game_consts.NUM_CHATS + <= self.state.round_number + and self.state.game_stage == GamePhase.DISCUSS + ): self.go_to_voting() + while ( + self.state.players[self.state.player_to_act_next].state.life + == PlayerState.DEAD + ): + self.state.player_to_act_next = (self.state.player_to_act_next + 1) % len( + self.state.players + ) + print(f"Player to act next set to: {self.state.player_to_act_next}") + if self.state.player_to_act_next == 0: + self.state.round_number += 1 + for player in self.state.players: + # update player history + player.log_state_new_round() + if ( + self.state.round_of_discussion_start + game_consts.NUM_CHATS + <= self.state.round_number + and self.state.game_stage == GamePhase.DISCUSS + ): + self.go_to_voting() + self.save_state() + return False - # END OF GAME - if continue_from_state is None: - print("Game Over!") - self.end_game() - - def set_game_stage(self, continue_from_state: Optional[str]) -> Callable[[], bool]: - """Set the game stage and return the appropriate game over check function.""" - if continue_from_state: - self.load_state(continue_from_state) - return self.check_game_over_action_crewmate - return self.check_game_over + def perform_action_step(self) -> None: + chosen_action = self.get_player_action() + self.update_game_state(chosen_action) - def get_player_actions(self) -> list[GameAction]: + def get_player_action(self) -> GameAction: """Get actions from all alive players.""" - choosen_actions: list[GameAction] = [] - for player in self.state.players: - if player.state.life == PlayerState.ALIVE: - possible_actions = self.get_actions(player) - possible_actions_str = [action.text for action in possible_actions] - if self.state.DEBUG: - print(f"Player {player} actions: {possible_actions_str}") - action_int = player.prompt_action(possible_actions_str) - choosen_actions.append(possible_actions[action_int]) - if self.state.DEBUG: - print(f"Player {player} choosen action: {choosen_actions[-1]}") - return choosen_actions - - def update_game_state(self, chosen_actions: list[GameAction]) -> bool: + choosen_action: GameAction = None + # Use round_number and current_player_index to determine the player's turn + current_player = self.state.players[self.state.player_to_act_next] + possible_actions = self.get_actions(current_player) + possible_actions_str = [action.text for action in possible_actions] + action_int = current_player.prompt_action(possible_actions_str) + choosen_action = possible_actions[action_int] + return choosen_action + + def update_game_state(self, action: GameAction) -> None: """ - Update game state based on actions - actions are sorted by action type in way that actions with higher priority are first - ex: REPORT > KILL > DO_ACTION > MOVE > WAIT + Update game and other player states based on action taken by player. Players can see actions of other players in the same room """ - chosen_actions.sort(key=lambda x: x.type, reverse=True) - someone_reported = False - for action in chosen_actions: - self.state.playthrough.append(f"[{action.player}]: {action}") - if action.player.state.life != PlayerState.DEAD: - action.player.kill_cooldown = max(0, action.player.kill_cooldown - 1) - if action.type == GameActionType.REPORT: - self.broadcast_history( - "report", f"{action.player} reported a dead body" - ) - reported_players = self.state.get_dead_players_in_location( - action.player.state.location - ) - self.broadcast_history( - "dead_players", - f"Dead players found: {', '.join(reported_players)}", - ) - someone_reported = True # in case of report; go to discussion - if action.type == GameActionType.KILL and isinstance( - action.target, Player - ): - action.target.state.action_result = ( - f"You were eliminated by {action.player}" - ) + # REPORT + if action.type == GameActionType.REPORT: + self.broadcast_history("report", f"{action.player} reported a dead body") + reported_players = self.state.get_dead_players_in_location( + action.player.state.location + ) + assert reported_players + self.broadcast_history( + "dead_players", + f"Dead players found: {reported_players}", + ) + self.state.set_stage(GamePhase.DISCUSS) - action.player.state.action_result = action.do_action() + prev_location = action.player.state.location - # update stories of seen actions and players in room - for player in self.state.players: - if ( - player.state.life != PlayerState.DEAD - and player != action.player - ): - if ( - action.player.state.location == player.state.location - or action.player.state.location - == player.history.rounds[-1].location - ): - playthrough_text = f"Player {player} saw action {action.spectator} when {player} were in {action.player.state.location.value}" - self.state.playthrough.append(playthrough_text) - if self.state.DEBUG: - print(playthrough_text) - player.state.seen_actions.append( - f"you saw {action.spectator} when you were in {action.player.state.location.value}" - ) + # KILL, TASK, MOVE, WAIT - location changed + action.player.state.action_result = action.do_action() + + # update stories of seen actions and players in room + # when moving to a room, player can see actions of other players in both rooms + players_in_room = self.state.get_players_in_location(prev_location) + if action.player.state.location != prev_location: + players_in_room += self.state.get_players_in_location( + action.player.state.location + ) + + for player in players_in_room: + if player != action.player: + player.state.seen_actions.append( + f"you saw {action.spectator} when you were in {action.player.state.location.value}" + ) + + self.state.log_action(f"{action.spectator}") + if players_in_room: + self.state.log_action(f"{players_in_room} saw this action") + else: + self.state.log_action(f"No one saw this action") - for player in self.state.players: - # update player history - player.log_state_new_round() - # update players in room for player in self.state.players: players_in_room = [ @@ -176,18 +190,11 @@ def update_game_state(self, chosen_actions: list[GameAction]) -> bool: and player != other_player and other_player.state.life == PlayerState.ALIVE ] - - playthrough_text = f"Player {player} is in {player.state.location.value} with {players_in_room}" - self.state.playthrough.append(playthrough_text) - if self.state.DEBUG: - print(playthrough_text) if players_in_room: - player.state.player_in_room = f"Players in room with you: {', '.join([str(player) for player in players_in_room])}" + player.state.player_in_room = f"Players in room with you: {players_in_room}" else: player.state.player_in_room = "You are alone in the room" - return someone_reported - def get_actions(self, player: Player) -> list[GameAction]: actions = [] @@ -198,12 +205,12 @@ def get_actions(self, player: Player) -> list[GameAction]: dead_players_in_room = self.state.get_dead_players_in_location( player.state.location ) - if dead_players_in_room: + for dead in dead_players_in_room: actions.append( GameAction( type=GameActionType.REPORT, player=player, - target=dead_players_in_room, + target=dead, ) ) @@ -232,29 +239,14 @@ def get_actions(self, player: Player) -> list[GameAction]: return actions - def discussion_loop(self) -> None: - self.state.set_stage(GamePhase.DISCUSS) - for player in self.state.players: - player.state.prompt = ( - "Discussion phase has started. You can discuss and vote who to banish" - ) - - discussion_log: list[str] = [] - for round in range(game_consts.NUM_CHATS): - for player in self.state.players: - if player.state.life == PlayerState.ALIVE: - player.state.observations.append( - f"Discussion: [System]: You have {game_consts.NUM_CHATS - round} rounds left to discuss, then you will vote" - ) - answer: str = player.prompt_discussion() - answer_str = f"Discussion: [{player}]: {answer}" - discussion_log.append(answer_str) - self.broadcast_message(answer_str) - self.state.playthrough.append(f"Discussion log:") - self.state.playthrough.append("\n".join(discussion_log)) - if self.state.DEBUG: - print("Discussion log:") - print("\n".join(discussion_log)) + def perform_discussion_step(self) -> None: + player = self.state.players[self.state.player_to_act_next] + player.state.observations.append( + f"Discussion: [System]: You have {self.state.round_of_discussion_start+game_consts.NUM_CHATS - self.state.round_number} rounds left to discuss, then you will vote" + ) + answer: str = player.prompt_discussion() + answer_str = f"Discussion: [{player}]: {answer}" + self.broadcast_message(answer_str) def go_to_voting(self) -> None: dead_players = [ @@ -275,7 +267,7 @@ def go_to_voting(self) -> None: if player.state.life == PlayerState.ALIVE: action = player.prompt_vote(possible_voting_actions_str) - votes[player] = possible_actions[action].target + votes[player.name] = possible_actions[action].target.name player.state.observations.append( f"You voted for {possible_actions[action].target}" ) @@ -283,21 +275,17 @@ def go_to_voting(self) -> None: playthrough_text = ( f"{player} voted for {possible_actions[action].target}" ) - self.state.playthrough.append(playthrough_text) - if self.state.DEBUG: - print(playthrough_text) + self.state.log_action(playthrough_text) votes_counter = Counter(votes.values()) two_most_common = votes_counter.most_common(2) if len(two_most_common) > 1 and two_most_common[0][1] == two_most_common[1][1]: self.broadcast_history("vote", "It's a tie! No one will be banished") else: + player_to_banish = [x for x in self.state.players if x.name == two_most_common[0][0]][0] assert isinstance( - two_most_common[0][0], Player + player_to_banish, Player ) # Ensure that the expression is of type Player - player_to_banish: Player = two_most_common[0][ - 0 - ] # Explicitly cast the expression to Player if player_to_banish == self.nobody: self.broadcast_history("vote", "Nobody was banished!") elif player_to_banish.is_impostor: @@ -308,12 +296,12 @@ def go_to_voting(self) -> None: self.broadcast_history( "vote", f"{player_to_banish} was banished! They were a crewmate" ) - player_to_banish.state = PlayerState.DEAD + player_to_banish.state.life = PlayerState.DEAD for player, target in votes.items(): self.broadcast_history(f"vote {player}", f"{player} voted for {target}") - self.state.game_stage = GamePhase.ACTION_PHASE - self.remove_dead_players() + self.state.set_stage(GamePhase.ACTION_PHASE) + self.mark_dead_players_as_reported() def get_vote_actions(self, player: Player) -> list[GameAction]: actions = [] @@ -330,8 +318,9 @@ def get_vote_actions(self, player: Player) -> list[GameAction]: return actions def broadcast(self, key: str, message: str) -> None: + self.state.log_action(f"{key}: {message}") for player in self.state.get_alive_players(): - player.state.observations.append(message) + player.state.observations.append(f"{key}: {message}") def broadcast_history(self, key: str, message: str) -> None: self.broadcast(key, message) @@ -339,7 +328,7 @@ def broadcast_history(self, key: str, message: str) -> None: def broadcast_message(self, message: str) -> None: self.broadcast("chat", message) - def remove_dead_players(self) -> None: + def mark_dead_players_as_reported(self) -> None: for player in self.state.players: if player.state.life == PlayerState.DEAD: player.state.life = PlayerState.DEAD_REPORTED @@ -416,19 +405,60 @@ def end_game(self) -> None: print("Impostors win!") self.state.log_action("Impostors win!") + self.save_state() self._save_playthrough() - def save_state(self, filename: str) -> None: - with open(filename, "w") as f: - json.dump(self.state.model_dump_json(), f) + def save_state(self) -> None: + with open(game_consts.STATE_FILE, "w") as f: + json.dump(self.state.model_dump(), f) + # yaml.dump(self.state.model_dump(), f) + + def load_state(self) -> None: + try: + with open(game_consts.STATE_FILE, "r") as f: + data = json.load(f) + # Deserialize players based on their type + players = [ + self._create_player_from_dict(player_data) + for player_data in data["players"] + ] + data["players"] = players + print(data["players"][1].state.tasks[0]) + self.state = GameState.model_validate( + {**data} + ) # Update GameState with deserialized players + except FileNotFoundError: + print("No saved state found. Starting new game.") + + def _create_player_from_dict(self, player_data: dict) -> Player: + # Deserialize tasks for the player's current state + player_data["state"]["tasks"] = [ + self._create_task_from_dict(task_data) + for task_data in player_data["state"]["tasks"] + ] + + # Deserialize tasks for each round in the player's history + for round_data in player_data["history"]["rounds"]: + round_data["tasks"] = [ + self._create_task_from_dict(task_data) + for task_data in round_data["tasks"] + ] + llm_model_name = player_data.get("llm_model_name", "none") + if llm_model_name is None: + return HumanPlayer(**player_data) + elif llm_model_name == "fake": + return FakeAIPlayer(**player_data) + else: + return AIPlayer(**player_data) - def load_state(self, filename: str) -> None: - with open(filename, "r") as f: - data = json.load(f) - self.state = GameState.model_validate_json(data) + def _create_task_from_dict(self, task_data: dict) -> Task: + if "turns_left" in task_data: + return LongTask(**task_data) + else: + return ShortTask(**task_data) def __repr__(self): return f"GameEngine | Players: {self.state.players} | Stage: {self.state.game_stage}" def to_dict(self): - return self.state.dict() + return self.state.to_dict() diff --git a/src/game/game_state.py b/src/game/game_state.py index f4ccef5..d8af855 100644 --- a/src/game/game_state.py +++ b/src/game/game_state.py @@ -1,4 +1,4 @@ -from game.models.engine import GamePhase +from game.models.engine import GameLocation, GamePhase from game.models.history import PlayerState from game.players.base_player import Player from typing import List @@ -12,7 +12,8 @@ class GameState(BaseModel): save_playthrough: str = "" DEBUG: bool = Field(default=False) round_number: int = 0 - current_player_index: int = 0 + player_to_act_next: int = 0 + round_of_discussion_start: int = 0 def add_player(self, player: Player): self.players.append(player) @@ -21,9 +22,14 @@ def set_stage(self, stage: GamePhase): self.game_stage = stage for player in self.players: player.set_stage(stage) + if stage == GamePhase.DISCUSS: + self.player_to_act_next = 0 + self.round_of_discussion_start = self.round_number def log_action(self, action: str): self.playthrough.append(action) + if self.DEBUG: + print(action) def get_alive_players(self) -> List[Player]: return [p for p in self.players if p.state.life == PlayerState.ALIVE] @@ -31,13 +37,16 @@ def get_alive_players(self) -> List[Player]: def get_dead_players(self) -> List[Player]: return [p for p in self.players if p.state.life == PlayerState.DEAD] - def get_dead_players_in_location(self, location: int) -> List[Player]: + def get_dead_players_in_location(self, location: GameLocation) -> List[Player]: return [ p for p in self.players - if p.state == PlayerState.DEAD and p.location == location + if p.state.life == PlayerState.DEAD and p.state.location == location ] + def get_players_in_location(self, location: GameLocation) -> List[Player]: + return [p for p in self.players if p.state.location == location and p.state.life == PlayerState.ALIVE] + def get_player_targets(self, player: Player) -> List[Player]: return [ other_player @@ -55,5 +64,5 @@ def to_dict(self): "save_playthrough": self.save_playthrough, "DEBUG": self.DEBUG, "round_number": self.round_number, - "current_player_index": self.current_player_index, + "current_player_index": self.player_to_act_next, } diff --git a/src/game/models/action.py b/src/game/models/action.py index 034e6f6..ed9c76e 100644 --- a/src/game/models/action.py +++ b/src/game/models/action.py @@ -6,7 +6,7 @@ from game.models.engine import GameLocation from game.models.history import PlayerState from game.models.tasks import Task -from game.players.base_player import Player +from game.players.base_player import Player, PlayerRole class GameActionType(IntEnum): @@ -51,7 +51,7 @@ def set_stories(self): elif self.type == GameActionType.DO_ACTION: self.text = f"complete task: {self.target.name if isinstance(self.target, Task) else self.target}" self.result = f"You [{self.player}] {self.target}" - self.spectator = f"{self.player} doing task" + self.spectator = f"{self.player} doing task {self.target.name if isinstance(self.target, Task) else self.target}" elif self.type == GameActionType.REPORT: self.text = f"report dead body of {str(self.target)}" self.result = f"You [{self.player}] reported {str(self.target)}" @@ -67,6 +67,8 @@ def set_stories(self): return self def do_action(self): + if self.player.role == PlayerRole.IMPOSTOR: + self.player.kill_cooldown = max(0, self.player.kill_cooldown - 1) if self.type == GameActionType.REPORT or self.type == GameActionType.WAIT: pass if self.type == GameActionType.MOVE: @@ -76,10 +78,14 @@ def do_action(self): if self.type == GameActionType.KILL: self.target.state.life = PlayerState.DEAD self.player.kill_cooldown = IMPOSTOR_COOLDOWN + assert isinstance(self.target, Player) + self.target.state.action_result = ( + f"You were eliminated by {self.player}" + ) return self.result def __str__(self): - return f"{self.type} | {self.target}" + return f"{self.spectator}" def __repr__(self): - return f"{self.type} | {self.target}" + return f"{self.spectator}" diff --git a/src/game/players/ai.py b/src/game/players/ai.py index 9883148..cedde1e 100644 --- a/src/game/players/ai.py +++ b/src/game/players/ai.py @@ -65,3 +65,9 @@ def prompt_vote(self, voting_actions: List[str]) -> int: self.state.response = str(vote) self.state.prompt = vote_prompt return vote + + def __str__(self): + return self.name + + def __repr__(self): + return self.name diff --git a/src/game/players/base_player.py b/src/game/players/base_player.py index 57644d2..23d9dcb 100644 --- a/src/game/players/base_player.py +++ b/src/game/players/base_player.py @@ -32,6 +32,7 @@ class Player(BaseModel, ABC): adventure_agent: Optional[AdventureAgent] = None discussion_agent: Optional[DiscussionAgent] = None voting_agent: Optional[VotingAgent] = None + llm_model_name: Optional[str] = None model_config = ConfigDict(validate_assignment=True) @@ -44,14 +45,14 @@ def set_role(self, role: PlayerRole) -> None: if role == PlayerRole.IMPOSTOR: self.is_impostor = True self.kill_cooldown = game_consts.IMPOSTOR_COOLDOWN - self.state = RoundData(tasks=get_impostor_tasks()) + if not self.state.tasks: self.state = RoundData(tasks=get_impostor_tasks()) if self.adventure_agent: self.adventure_agent.role = PlayerRole.IMPOSTOR if self.discussion_agent: self.discussion_agent.role = PlayerRole.IMPOSTOR if self.voting_agent: self.voting_agent.role = PlayerRole.IMPOSTOR else: self.is_impostor = False self.kill_cooldown = 0 - self.state = RoundData(tasks=get_random_tasks()) + if not self.state.tasks: self.state = RoundData(tasks=get_random_tasks()) if self.adventure_agent: self.adventure_agent.role = PlayerRole.CREWMATE if self.discussion_agent: self.discussion_agent.role = PlayerRole.CREWMATE if self.voting_agent: self.voting_agent.role = PlayerRole.CREWMATE diff --git a/src/game/players/fake_ai.py b/src/game/players/fake_ai.py index 96059a4..ef9c515 100644 --- a/src/game/players/fake_ai.py +++ b/src/game/players/fake_ai.py @@ -1,10 +1,14 @@ import random from typing import List +from pydantic import Field + from game.players.base_player import Player, PlayerRole class FakeAIPlayer(Player): + llm_model_name: str + def prompt_action(self, actions: List[str]) -> int: random_action = random.randint(0, len(actions) - 1) self.state.actions = actions @@ -29,3 +33,9 @@ def prompt_vote(self, voting_actions: List[str]) -> int: self.state.llm_responses = ["This is a placeholder LLM response."] self.state.observations = ["This is a placeholder observation."] return random_player + + def __str__(self): + return self.name + + def __repr__(self): + return self.name diff --git a/src/game/players/human.py b/src/game/players/human.py index 1c9280c..2891114 100644 --- a/src/game/players/human.py +++ b/src/game/players/human.py @@ -52,3 +52,9 @@ def prompt_vote(self, voting_actions: List[str]) -> int: return int(answer) else: return self.prompt_vote(voting_actions) + + def __str__(self): + return self.name + + def __repr__(self): + return self.name