File size: 11,185 Bytes
0c31321 f596e58 46ba8c8 975158e c2392fe 975158e 5de0b8a 485a836 975158e 3ea5035 7877562 0c31321 dd5c856 0c31321 172af0f 7877562 0c31321 172af0f c2392fe 172af0f 7877562 172af0f 0c31321 f596e58 0c31321 c2392fe 3ea5035 1a8a579 3ea5035 172af0f 7877562 3ea5035 5de0b8a 3ea5035 f596e58 3ea5035 7877562 0c31321 172af0f 7877562 3ea5035 975158e 3ea5035 f596e58 5de0b8a f596e58 3ea5035 c2392fe 46ba8c8 3ac8e2a 7877562 3ac8e2a 46ba8c8 3ac8e2a c2392fe c6c2b98 c2392fe 46ba8c8 c6c2b98 c2392fe c6c2b98 c2392fe 7877562 5de0b8a 975158e 7877562 c2392fe 5de0b8a 7877562 c6c2b98 7877562 c6c2b98 7877562 c6c2b98 7877562 ab29f8e c6c2b98 5de0b8a 7877562 46ba8c8 5de0b8a 7877562 46ba8c8 7877562 c2392fe 46ba8c8 5de0b8a 46ba8c8 5de0b8a 46ba8c8 dd5c856 c6c2b98 dd5c856 c6c2b98 46ba8c8 7877562 3ea5035 c6c2b98 c2392fe c6c2b98 dd5c856 c6c2b98 7877562 c6c2b98 dd5c856 c6c2b98 c2392fe c6c2b98 dd5c856 c6c2b98 c2392fe c6c2b98 c2392fe c6c2b98 c2392fe c6c2b98 3ea5035 975158e c6c2b98 878e472 c6c2b98 878e472 c6c2b98 878e472 c6c2b98 3ea5035 c6c2b98 46ba8c8 c6c2b98 975158e c6c2b98 3ea5035 c6c2b98 3ea5035 c6c2b98 172af0f c6c2b98 172af0f |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 |
import os
from datetime import datetime
from typing import Optional, Type
from colorama import Fore, Style
from game_utils import *
from models import *
from player import Player
from prompts import fetch_prompt, format_prompt
# Default Values
NUMBER_OF_PLAYERS = 6
WINNING_SCORE = 11
class Game:
log_dir = os.path.join(os.pardir, "experiments")
player_log_file = "{player_id}.jsonl"
game_log_file = "{game_id}-game.jsonl"
number_of_players = NUMBER_OF_PLAYERS
"""The number of players in the game."""
winning_score = WINNING_SCORE
"""The Number of points required to win the game."""
debug = True
"""If True, the game will print debug messages to the console."""
def __init__(
self,
number_of_players: int = NUMBER_OF_PLAYERS,
human_name: str = None,
verbose = False
):
# Game ID
self.game_id = game_id()
self.start_time = datetime.now().strftime('%Y-%m-%d_%H-%M-%S')
self.log_dir = os.path.join(self.log_dir, f"{self.start_time}-{self.game_id}")
os.makedirs(self.log_dir, exist_ok=True)
# Choose Chameleon
self.chameleon_index = random_index(number_of_players)
# Gather Player Names
if human_name:
ai_names = random_names(number_of_players - 1, human_name)
self.human_index = random_index(number_of_players)
else:
ai_names = random_names(number_of_players)
self.human_index = None
self.verbose = verbose
# Add Players
self.players = []
for i in range(0, number_of_players):
if self.human_index == i:
name = human_name
controller = "human"
else:
name = ai_names.pop()
controller = "openai"
if self.chameleon_index == i:
role = "chameleon"
else:
role = "herd"
player_id = f"{self.game_id}-{i + 1}"
log_path = os.path.join(
self.log_dir,
self.player_log_file.format(player_id=player_id)
)
self.players.append(Player(name, controller, player_id, log_filepath=log_path))
# Game State
self.player_responses = []
def format_responses(self, exclude: str = None) -> str:
"""Formats the responses of the players into a single string."""
if len(self.player_responses) == 0:
return "None, you are the first player!"
else:
formatted_responses = ""
for response in self.player_responses:
# Used to exclude the player who is currently responding, so they don't vote for themselves like a fool
if response["sender"] != exclude:
formatted_responses += f" - {response['sender']}: {response['response']}\n"
return formatted_responses
def game_message(
self, message: str,
recipient: Optional[Player] = None, # If None, message is broadcast to all players
exclude: bool = False # If True, the message is broadcast to all players except the chosen player
):
"""Sends a message to a player. No response is expected, however it will be included next time the player is prompted"""
if exclude or not recipient:
for player in self.players:
if player != recipient:
player.prompt_queue.append(message)
if player.controller_type == "human":
self.human_message(message)
if self.verbose:
self.human_message(message)
else:
recipient.prompt_queue.append(message)
if recipient.controller_type == "human":
self.human_message(message)
async def instructional_message(self, message: str, player: Player, output_format: Type[BaseModel]):
"""Sends a message to a specific player and gets their response."""
if player.controller_type == "human":
self.human_message(message)
response = await player.respond_to(message, output_format)
return response
# The following methods are used to broadcast messages to a human.
# They are design so that they can be overridden by a subclass for a different player interface.
@staticmethod
def human_message(self, message: str):
"""Sends a message for the human player to read. No response is expected."""
print(message)
def verbose_message(self, message: str):
"""Sends a message for the human player to read. No response is expected."""
if self.verbose:
print(Fore.GREEN + message + Style.RESET_ALL)
def debug_message(self, message: str):
"""Sends a message for a human observer. These messages contain secret information about the players such as their role."""
if self.debug:
print(Fore.YELLOW + "DEBUG: " + message + Style.RESET_ALL)
async def start(self):
"""Sets up the game. This includes assigning roles and gathering player names."""
self.game_message(fetch_prompt("game_rules"))
await self.run_round()
# Log Game Info
game_log = {
"game_id": self.game_id,
"start_time": self.start_time,
"number_of_players": len(self.players),
"human_player": self.players[self.human_index].id if self.human_index else "None",
}
game_log_path = os.path.join(self.log_dir, self.game_log_file.format(game_id=self.game_id))
log(game_log, game_log_path)
async def run_round(self):
"""Starts the round."""
# Phase I: Choose Animal and Assign Roles
herd_animal = random_animal()
self.debug_message(f"The secret animal is {herd_animal}.")
chameleon_index = random_index(len(self.players))
chameleon = self.players[chameleon_index]
for i, player in enumerate(self.players):
if i == chameleon_index:
player.assign_role("chameleon")
self.game_message("You are the **Chameleon**, remain undetected and guess what animal the others are pretending to be", player)
self.debug_message(f"{player.name} is the Chameleon!")
else:
player.assign_role("herd")
self.game_message(f"You are a **{herd_animal}**, keep this secret at all costs and figure which player is not really a {herd_animal}", player)
# Phase II: Collect Player Animal Descriptions
self.game_message(f"Each player will now take turns describing themselves:")
for i, current_player in enumerate(self.players):
if current_player.controller_type != "human":
self.verbose_message(f"{current_player.name} is thinking...")
if i == 0:
prompt = "Your Response:"
else:
prompt = "It's your turn to describe yourself. Do not repeat responses from other players.\nYour Response:"
# Get Player Animal Description
response = await self.instructional_message(prompt, current_player, AnimalDescriptionModel)
self.player_responses.append({"sender": current_player.name, "response": response.description})
self.game_message(f"{current_player.name}: {response.description}", current_player, exclude=True)
# Phase III: Chameleon Guesses the Animal
self.game_message("All players have spoken. The Chameleon will now guess the secret animal...")
if self.human_index != self.chameleon_index:
self.verbose_message("The Chameleon is thinking...")
prompt = fetch_prompt("chameleon_guess_animal")
response = await self.instructional_message(prompt, chameleon, ChameleonGuessAnimalModel)
chameleon_animal_guess = response.animal
# Phase IV: The Herd Votes for who they think the Chameleon is
self.game_message("The Chameleon has guessed the animal. Now the Herd will vote on who they think the chameleon is.")
self.game_message("The Chameleon has decided not to guess the animal. Now all players will vote on who they think the chameleon is.")
player_votes = []
for player in self.players:
if player.role == "herd":
if player.is_ai():
self.verbose_message(f"{player.name} is thinking...")
prompt = format_prompt("vote", player_responses=self.format_responses(exclude=player.name))
# Get Player Vote
response = await self.instructional_message(prompt, player, VoteModel)
# check if a valid player was voted for...
# Add Vote to Player Votes
player_votes.append({"voter": player, "vote": response.vote})
if player.is_ai():
self.debug_message(f"{player.name} voted for {response.vote}")
self.game_message("All players have voted!")
formatted_votes = '\n'.join([f'{vote["voter"].name}: {vote["vote"]}' for vote in player_votes])
self.game_message(f"Votes:\n{formatted_votes}")
# Count Votes
accused_player = count_chameleon_votes(player_votes)
# Phase V: Assign Points
self.game_message(f"The round is over. Caclulating results...")
self.game_message(
f"The Chameleon was {chameleon.name}, and they guessed the secret animal was {chameleon_animal_guess}.")
self.game_message(f"The secret animal was actually was {herd_animal}.")
if accused_player:
self.game_message(f"The Herd voted for {accused_player} 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 chameleon_animal_guess.lower() == herd_animal.lower():
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 player_votes:
if vote["vote"] == chameleon.name:
vote['voter'].points += 1
# If the Herd fails to accuse the Chameleon = +1 Point to the Chameleon
if not accused_player or accused_player != chameleon.name:
chameleon.points += 1
# Check for a Winner
player_points = "\n".join([f"{player.name}: {player.points}" for player in self.players])
self.game_message(f"Current Game Score: {player_points}")
# Log Round Info
round_log = {
"herd_animal": herd_animal,
"chameleon_name": self.players[self.chameleon_index].name,
"chameleon_guess": chameleon_animal_guess,
"herd_votes": player_votes,
}
|