chameleon / src /game_chameleon.py
Eric Botti
prompt tweaking, added game rules as system message
21c7651
from collections import Counter
from typing import ClassVar
from game_utils import random_index
from output_formats import *
from player import ChameleonPlayer, Player
from prompts import fetch_prompt, format_prompt
from game import Game
# Default Values
NUMBER_OF_PLAYERS = 6
WINNING_SCORE = 7
AVAILABLE_ANIMALS = ["Dog", "Cat", "Mouse", "Hamster", "Monkey", "Rabbit", "Fox", "Bear", "Panda", "Koala", "Tiger",
"Lion", "Cow", "Pig", "Frog", "Owl", "Duck", "Chicken", "Butterfly", "Turtle", "Snake", "Octopus",
"Squid", "Hedgehog", "Elephant", "Rhinoceros", "Zebra", "Crocodile", "Whale", "Dolphin", "Camel",
"Giraffe", "Deer", "Gorilla", "Goat", "Llama", "Horse", "Unicorn", "Flamingo", "Skunk", "Shark"]
class ChameleonGame(Game):
"""The main game class, handles the game logic and player interactions."""
# Defaults
winning_score: int = WINNING_SCORE
"""The Number of points required to win the game."""
available_animals: List[str] = Field(AVAILABLE_ANIMALS, exclude=True)
"""The list of animals that can be chosen as the secret animal."""
chameleon_ids: List[str] = []
"""Record of which player was the chameleon for each round."""
herd_animals: List[str] = []
"""Record of what animal was the herd animal for each round."""
all_animal_descriptions: List[List[dict]] = []
"""Record of the animal descriptions each player has given for each round."""
chameleon_guesses: List[str] = []
"""Record of what animal the chameleon guessed for each round."""
herd_vote_tallies: List[List[dict]] = []
"""Record of the votes of each herd member for the chameleon for each round."""
# Class Variables
number_of_players: ClassVar[int] = NUMBER_OF_PLAYERS
"""The number of players in the game."""
player_class: ClassVar[Type[Player]] = ChameleonPlayer
"""The class of the player used in the game."""
@property
def chameleon(self) -> ChameleonPlayer:
"""Returns the current chameleon."""
return self.player_from_id(self.chameleon_ids[-1])
@property
def chameleon_id(self) -> str:
"""Returns the current chameleon's id."""
return self.chameleon_ids[-1]
@property
def herd_animal(self) -> str:
"""Returns the current herd animal."""
return self.herd_animals[-1]
@property
def round_animal_descriptions(self) -> List[dict]:
"""Returns the current animal descriptions."""
return self.all_animal_descriptions[-1]
@property
def chameleon_guess(self) -> str:
"""Returns the current chameleon guess."""
return self.chameleon_guesses[-1]
@property
def herd_vote_tally(self) -> List[dict]:
"""Returns the current herd vote tally."""
return self.herd_vote_tallies[-1]
def run_game(self):
"""Starts the game."""
# Check if the game has not been won
if self.game_state != "game_end":
if self.game_state == "game_start":
self.game_message(fetch_prompt("game_rules"), message_type="system")
self.game_state = "setup_round"
if self.game_state == "setup_round":
self.setup_round()
self.game_state = "animal_description"
if self.game_state in ["animal_description", "chameleon_guess", "herd_vote"]:
self.run_round()
if self.game_state == "resolve_round":
self.resolve_round()
points = [player.points for player in self.players]
if max(points) >= self.winning_score:
self.game_state = "game_end"
self.winner_id = self.players[points.index(max(points))].player_id
winner = self.player_from_id(self.winner_id)
self.game_message(f"The game is over {winner.name} has won!")
self.end_game()
else:
self.game_state = "setup_round"
# Go back to start
self.game_message(f"No player has won yet, the game will end when a player reaches {self.winning_score} points.")
self.game_message(f"Starting a new round...")
random.shuffle(self.players)
self.run_game()
def run_round(self):
"""Starts the round."""
# Phase I: Collect Player Animal Descriptions
if self.game_state == "animal_description":
for current_player in self.players:
if current_player.player_id not in [animal_description['player_id'] for animal_description in
self.round_animal_descriptions]:
response = self.player_turn_animal_description(current_player)
if not response:
break
if len(self.round_animal_descriptions) == len(self.players):
self.game_state = "chameleon_guess"
# Phase II: Chameleon Guesses the Animal
if self.game_state == "chameleon_guess":
self.player_turn_chameleon_guess(self.chameleon)
# Phase III: The Herd Votes for who they think the Chameleon is
if self.game_state == "herd_vote":
if not self.awaiting_input:
self.verbose_message("The Herd is voting...")
for current_player in self.players:
if current_player.role == "herd" and current_player.player_id not in [vote['voter_id'] for vote in
self.herd_vote_tally]:
response = self.player_turn_herd_vote(current_player)
if not response:
break
if len(self.herd_vote_tally) == len(self.players) - 1:
self.game_state = "resolve_round"
def setup_round(self):
"""Sets up the round. This includes assigning roles and gathering player names."""
# Choose Animal
herd_animal = self.random_animal()
self.herd_animals.append(herd_animal)
self.debug_message(f"The secret animal is {herd_animal}.")
# Assign Roles
chameleon_index = random_index(len(self.players))
chameleon = self.players[chameleon_index]
self.chameleon_ids.append(chameleon.player_id)
self.game_message(fetch_prompt("assign_chameleon"), chameleon)
herd = []
for i, player in enumerate(self.players):
if i == chameleon_index:
player.assign_role("chameleon")
self.debug_message(f"{player.name} is the Chameleon!")
else:
player.assign_role("herd")
herd.append(player)
self.game_message(format_prompt("assign_herd", herd_animal=herd_animal), herd)
for i, player in enumerate(self.players):
if i == chameleon_index:
player.assign_role("chameleon")
self.debug_message(f"{player.name} is the Chameleon!")
else:
player.assign_role("herd")
# Empty Animal Descriptions
self.all_animal_descriptions.append([])
# Empty Tally for Votes
self.herd_vote_tallies.append([])
self.game_message(f"Each player will now take turns describing themselves:")
def player_turn_animal_description(self, player: Player):
"""Handles a player's turn to describe themselves."""
if not self.awaiting_input:
self.verbose_message(f"{player.name} is thinking...", recipient=player, exclude=True)
self.game_message(fetch_prompt("player_describe_animal"), player)
# Get Player Animal Description
response = player.interface.generate_formatted_response(AnimalDescriptionFormat)
if response:
self.round_animal_descriptions.append({"player_id": player.player_id, "description": response.description})
self.game_message(f"{player.name}: {response.description}", player, exclude=True)
self.awaiting_input = False
else:
self.awaiting_input = True
return response
def player_turn_chameleon_guess(self, chameleon: Player):
"""Handles the Chameleon's turn to guess the secret animal."""
if not self.awaiting_input:
self.game_message("All players have spoken. The Chameleon will now guess the secret animal...")
self.verbose_message("The Chameleon is guessing...", recipient=chameleon, exclude=True)
player_responses = self.format_animal_descriptions(exclude=self.chameleon)
self.game_message(format_prompt("chameleon_guess_animal", player_responses=player_responses),
self.chameleon)
response = chameleon.interface.generate_formatted_response(ChameleonGuessFormat)
if response:
self.chameleon_guesses.append(response.animal)
self.game_message(
"The Chameleon has guessed the animal. Now the Herd will vote on who they think the chameleon is.")
self.awaiting_input = False
self.game_state = "herd_vote"
else:
# Await input and do not proceed to the next phase
self.awaiting_input = True
def player_turn_herd_vote(self, player: Player):
"""Handles a player's turn to vote for the Chameleon."""
if not self.awaiting_input:
player_responses = self.format_animal_descriptions(exclude=player)
self.game_message(format_prompt("vote", player_responses=player_responses), player)
# Get Player Vote
additional_fields = {"player_names": [p.name for p in self.players if p != player]}
response = player.interface.generate_formatted_response(HerdVoteFormat, additional_fields=additional_fields)
if response:
self.debug_message(f"{player.name} voted for {response.vote}", recipient=player, exclude=True)
voted_for_player = self.player_from_name(response.vote)
player_vote = {"voter_id": player.player_id, "voted_for_id": voted_for_player.player_id}
self.herd_vote_tally.append(player_vote)
self.awaiting_input = False
else:
self.awaiting_input = True
return response
def resolve_round(self):
"""Resolves the round, assigns points, and prints the results."""
self.game_message("All players have voted!")
for vote in self.herd_vote_tally:
voter = self.player_from_id(vote["voter_id"])
voted_for = self.player_from_id(vote["voted_for_id"])
self.game_message(f"{voter.name} voted for {voted_for.name}")
accused_player_id = self.count_chameleon_votes(self.herd_vote_tally)
self.game_message(f"The round is over. Calculating results...")
self.game_message(
f"The Chameleon was {self.chameleon.name}, and they guessed the secret animal was {self.chameleon_guess}.")
self.game_message(f"The secret animal was actually was {self.herd_animal}.")
if accused_player_id:
accused_name = self.player_from_id(accused_player_id).name
self.game_message(f"The Herd voted for {accused_name} as the Chameleon.")
else:
self.game_message(f"The Herd could not come to a consensus.")
# Point Logic
# If the Chameleon guesses the correct animal = +1 Point to the Chameleon
if self.chameleon_guess.lower() == self.herd_animal.lower():
self.chameleon.points += 1
# If the Chameleon guesses the incorrect animal = +1 Point to each Herd player
else:
for player in self.players:
if player.role == "herd":
player.points += 1
# If a Herd player votes for the Chameleon = +1 Point to that player
for vote in self.herd_vote_tally:
if vote["voted_for_id"] == self.chameleon.player_id:
self.player_from_id(vote['voter_id']).points += 1
# If the Herd fails to accuse the Chameleon = +1 Point to the Chameleon
if not accused_player_id or accused_player_id != self.chameleon.player_id:
self.chameleon.points += 1
# Print Scores
player_points = "\n".join([f"{player.name}: {player.points}" for player in self.players])
self.game_message(f"Current Game Score:\n{player_points}")
def random_animal(self) -> str:
"""Returns a random animal from the list of available animals, and removes it from the list."""
animal = random.choice(self.available_animals)
self.available_animals.remove(animal)
return animal
@staticmethod
def count_chameleon_votes(player_votes: list[dict]) -> str | None:
"""Counts the votes for each player."""
votes = [vote['voted_for_id'] for vote in player_votes]
freq = Counter(votes)
most_voted_player, number_of_votes = freq.most_common()[0]
# If one player has more than 50% of the votes, the herd accuses them of being the chameleon
if number_of_votes / len(player_votes) >= 0.5:
return most_voted_player
else:
return None
def format_animal_descriptions(self, exclude: Player = None) -> str:
"""Formats the animal description responses of the players into a single string."""
formatted_responses = ""
for response in self.round_animal_descriptions:
# Used to exclude the player who is currently responding, so they don't vote for themselves like a fool
if response["player_id"] != exclude.player_id:
player = self.player_from_id(response["player_id"])
formatted_responses += f" - {player.name}: {response['description']}\n"
return formatted_responses