|
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 |
|
|
|
|
|
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.""" |
|
|
|
|
|
|
|
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.""" |
|
|
|
|
|
|
|
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.""" |
|
|
|
|
|
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" |
|
|
|
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.""" |
|
|
|
|
|
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" |
|
|
|
|
|
if self.game_state == "chameleon_guess": |
|
self.player_turn_chameleon_guess(self.chameleon) |
|
|
|
|
|
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.""" |
|
|
|
herd_animal = self.random_animal() |
|
self.herd_animals.append(herd_animal) |
|
self.debug_message(f"The secret animal is {herd_animal}.") |
|
|
|
|
|
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") |
|
|
|
|
|
self.all_animal_descriptions.append([]) |
|
|
|
|
|
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) |
|
|
|
|
|
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: |
|
|
|
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) |
|
|
|
|
|
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.") |
|
|
|
|
|
|
|
if self.chameleon_guess.lower() == self.herd_animal.lower(): |
|
self.chameleon.points += 1 |
|
|
|
|
|
else: |
|
for player in self.players: |
|
if player.role == "herd": |
|
player.points += 1 |
|
|
|
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 not accused_player_id or accused_player_id != self.chameleon.player_id: |
|
self.chameleon.points += 1 |
|
|
|
|
|
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 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: |
|
|
|
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 |