Eric Botti commited on
Commit
270b042
·
1 Parent(s): 6bc86db

broke down round functions into components

Browse files
Files changed (3) hide show
  1. src/game.py +132 -98
  2. src/game_utils.py +1 -1
  3. src/prompts.py +2 -8
src/game.py CHANGED
@@ -9,7 +9,7 @@ from output_formats import *
9
  from player import Player
10
  from prompts import fetch_prompt, format_prompt
11
  from message import Message
12
- from agent_interfaces import HumanAgentCLI, OpenAIAgentInterface
13
 
14
  # Default Values
15
  NUMBER_OF_PLAYERS = 6
@@ -17,22 +17,26 @@ WINNING_SCORE = 3
17
 
18
 
19
  class Game:
 
20
 
21
- log_dir = os.path.join(os.pardir, "experiments")
22
- """The directory where the logs will be saved."""
23
- player_log_file_template = "{player_id}.jsonl"
24
- """Template for the name of the log file for each player."""
25
- game_log_file_template = "{game_id}-game.jsonl"
26
- """Template for the name of the log file for the game."""
27
  number_of_players = NUMBER_OF_PLAYERS
28
  """The number of players in the game."""
29
  winning_score = WINNING_SCORE
30
  """The Number of points required to win the game."""
 
 
 
 
 
 
 
 
31
 
32
  def __init__(
33
  self,
34
  number_of_players: int = NUMBER_OF_PLAYERS,
35
  human_name: str = None,
 
36
  verbose: bool = False,
37
  debug: bool = False
38
  ):
@@ -48,46 +52,59 @@ class Game:
48
  ai_names = random_names(number_of_players)
49
  self.human_index = None
50
 
51
- self.verbose = verbose
52
- """If True, the game will display verbose messages to the player."""
53
- self.debug = debug
54
- """If True, the game will display debug messages to the player."""
55
-
56
  # Add Players
57
  self.players = []
 
58
  for i in range(0, number_of_players):
59
  player_id = f"{self.game_id}-{i + 1}"
60
 
61
  if self.human_index == i:
62
  name = human_name
63
- interface = HumanAgentCLI(player_id)
64
  else:
65
  name = ai_names.pop()
66
  interface = OpenAIAgentInterface(player_id)
67
 
68
  self.players.append(Player(name, player_id, interface))
69
 
 
 
 
 
 
70
  # Add Observer - an Agent who can see all the messages, but doesn't actually play
71
  if (self.verbose or self.debug) and not self.human_index:
72
  self.observer = HumanAgentCLI("{self.game_id}-observer")
73
  else:
74
  self.observer = None
75
 
76
- # Game State
77
- self.player_responses = []
 
78
 
79
- def format_responses(self, exclude: str = None) -> str:
80
- """Formats the responses of the players into a single string."""
81
- if len(self.player_responses) == 0:
82
- return "None, you are the first player!"
83
- else:
84
- formatted_responses = ""
85
- for response in self.player_responses:
86
- # Used to exclude the player who is currently responding, so they don't vote for themselves like a fool
87
- if response["sender"] != exclude:
88
- formatted_responses += f" - {response['sender']}: {response['response']}\n"
 
 
 
 
 
 
 
 
89
 
90
- return formatted_responses
 
 
 
91
 
92
  def observer_message(self, message: Message):
93
  """Sends a message to the observer if there is one."""
@@ -134,36 +151,39 @@ class Game:
134
  winner = None
135
  round_number = 0
136
 
137
- while not winner:
138
- round_results = await self.run_round()
139
- round_number += 1
140
 
141
- # Check for a Winner
142
- for player in self.players:
143
- if player.points >= self.winning_score:
144
- winner = player # ignoring the possibility of a tie for now
145
 
146
- # Log Game Info
147
- game_log = {
148
- "game_id": self.game_id,
149
- "start_time": self.start_time,
150
- "number_of_players": len(self.players),
151
- "human_player": self.players[self.human_index].id if self.human_index else "None",
152
- }
153
- game_log_path = os.path.join(self.log_dir, self.game_log_file_template.format(game_id=self.game_id))
154
 
155
- log(game_log, game_log_path)
156
 
157
- async def run_round(self) -> dict:
158
- """Starts the round."""
159
 
160
- # Phase I: Choose Animal and Assign Roles
 
 
 
 
 
 
161
 
 
 
 
162
  herd_animal = random_animal()
 
163
  self.debug_message(f"The secret animal is {herd_animal}.")
164
 
 
165
  chameleon_index = random_index(len(self.players))
166
- chameleon = self.players[chameleon_index]
167
 
168
  for i, player in enumerate(self.players):
169
  if i == chameleon_index:
@@ -174,25 +194,44 @@ class Game:
174
  player.assign_role("herd")
175
  self.game_message(format_prompt("assign_herd", herd_animal=herd_animal), player)
176
 
177
- # Phase II: Collect Player Animal Descriptions
 
 
178
 
179
  self.game_message(f"Each player will now take turns describing themselves:")
180
  for i, current_player in enumerate(self.players):
181
- if current_player.interface.is_ai:
182
- self.verbose_message(f"{current_player.name} is thinking...")
 
 
 
 
 
 
 
 
 
 
 
183
 
184
- prompt = fetch_prompt("player_describe_animal")
185
 
186
- # Get Player Animal Description
187
- message = Message(type="prompt", content=prompt)
188
- response = current_player.interface.respond_to_formatted(message, AnimalDescriptionFormat)
189
 
190
- self.player_responses.append({"sender": current_player.name, "response": response.description})
191
 
192
- self.game_message(f"{current_player.name}: {response.description}", current_player, exclude=True)
 
 
 
193
 
194
- # Phase III: Chameleon Guesses the Animal
195
 
 
 
 
 
 
 
 
 
196
  self.game_message("All players have spoken. The Chameleon will now guess the secret animal...")
197
  if chameleon.interface.is_ai or self.observer:
198
  self.verbose_message("The Chameleon is thinking...")
@@ -202,75 +241,70 @@ class Game:
202
  message = Message(type="prompt", content=prompt)
203
  response = chameleon.interface.respond_to_formatted(message, ChameleonGuessFormat)
204
 
205
- chameleon_animal_guess = response.animal
206
 
207
- # Phase IV: The Herd Votes for who they think the Chameleon is
208
- self.game_message("The Chameleon has guessed the animal. Now the Herd will vote on who they think the chameleon is.")
 
 
209
 
210
- player_votes = []
211
- for player in self.players:
212
- if player.role == "herd":
213
- if player.interface.is_ai:
214
- self.verbose_message(f"{player.name} is thinking...")
215
 
216
- prompt = format_prompt("vote", player_responses=self.format_responses(exclude=player.name))
 
 
 
217
 
218
- # Get Player Vote
219
- message = Message(type="prompt", content=prompt)
220
- player_names = [p.name for p in self.players]
221
- response = player.interface.respond_to_formatted(message, HerdVoteFormat, player_names=player_names)
222
 
223
- # Add Vote to Player Votes
224
- player_votes.append({"voter": player, "vote": response.vote})
225
- if player.interface.is_ai:
226
- self.debug_message(f"{player.name} voted for {response.vote}")
227
 
228
- self.game_message("All players have voted!")
229
- formatted_votes = '\n'.join([f'{vote["voter"].name}: {vote["vote"]}' for vote in player_votes])
230
- self.game_message(f"Votes:\n{formatted_votes}")
231
 
232
- # Count Votes
233
- accused_player = count_chameleon_votes(player_votes)
 
 
 
 
 
234
 
235
- # Phase V: Assign Points
236
 
237
  self.game_message(f"The round is over. Calculating results...")
238
  self.game_message(
239
- f"The Chameleon was {chameleon.name}, and they guessed the secret animal was {chameleon_animal_guess}.")
240
- self.game_message(f"The secret animal was actually was {herd_animal}.")
241
 
242
- if accused_player:
243
- self.game_message(f"The Herd voted for {accused_player} as the Chameleon.")
 
244
  else:
245
  self.game_message(f"The Herd could not come to a consensus.")
246
 
247
  # Point Logic
248
  # If the Chameleon guesses the correct animal = +1 Point to the Chameleon
249
- if chameleon_animal_guess.lower() == herd_animal.lower():
 
250
 
251
- chameleon.points += 1
252
  # If the Chameleon guesses the incorrect animal = +1 Point to each Herd player
253
  else:
254
  for player in self.players:
255
  if player.role == "herd":
256
  player.points += 1
257
  # If a Herd player votes for the Chameleon = +1 Point to that player
258
- for vote in player_votes:
259
- if vote["vote"] == chameleon.name:
260
- vote['voter'].points += 1
261
 
262
  # If the Herd fails to accuse the Chameleon = +1 Point to the Chameleon
263
- if not accused_player or accused_player != chameleon.name:
264
- chameleon.points += 1
265
 
266
- # Check for a Winner
267
  player_points = "\n".join([f"{player.name}: {player.points}" for player in self.players])
 
268
 
269
- self.game_message(f"Current Game Score: {player_points}")
270
 
271
- return {
272
- "herd_animal": herd_animal,
273
- "chameleon_name": chameleon.name,
274
- "chameleon_guess": chameleon_animal_guess,
275
- "herd_votes": player_votes,
276
- }
 
9
  from player import Player
10
  from prompts import fetch_prompt, format_prompt
11
  from message import Message
12
+ from agent_interfaces import HumanAgentCLI, OpenAIAgentInterface, HumanAgentInterface
13
 
14
  # Default Values
15
  NUMBER_OF_PLAYERS = 6
 
17
 
18
 
19
  class Game:
20
+ """The main game class, handles the game logic and player interactions."""
21
 
 
 
 
 
 
 
22
  number_of_players = NUMBER_OF_PLAYERS
23
  """The number of players in the game."""
24
  winning_score = WINNING_SCORE
25
  """The Number of points required to win the game."""
26
+ chameleon_ids: List[str] = []
27
+ """Record of which player was the chameleon for each round."""
28
+ herd_animals: List[str] = []
29
+ """Record of what animal was the herd animal for each round."""
30
+ chameleon_guesses: List[str] = []
31
+ """Record of what animal the chameleon guessed for each round."""
32
+ herd_vote_tallies: List[List[dict]] = []
33
+ """Record of the votes of each herd member for the chameleon for each round."""
34
 
35
  def __init__(
36
  self,
37
  number_of_players: int = NUMBER_OF_PLAYERS,
38
  human_name: str = None,
39
+ human_interface: Type[HumanAgentInterface] = HumanAgentCLI,
40
  verbose: bool = False,
41
  debug: bool = False
42
  ):
 
52
  ai_names = random_names(number_of_players)
53
  self.human_index = None
54
 
 
 
 
 
 
55
  # Add Players
56
  self.players = []
57
+
58
  for i in range(0, number_of_players):
59
  player_id = f"{self.game_id}-{i + 1}"
60
 
61
  if self.human_index == i:
62
  name = human_name
63
+ interface = human_interface(player_id)
64
  else:
65
  name = ai_names.pop()
66
  interface = OpenAIAgentInterface(player_id)
67
 
68
  self.players.append(Player(name, player_id, interface))
69
 
70
+ self.verbose = verbose
71
+ """If True, the game will display verbose messages to the player."""
72
+ self.debug = debug
73
+ """If True, the game will display debug messages to the player."""
74
+
75
  # Add Observer - an Agent who can see all the messages, but doesn't actually play
76
  if (self.verbose or self.debug) and not self.human_index:
77
  self.observer = HumanAgentCLI("{self.game_id}-observer")
78
  else:
79
  self.observer = None
80
 
81
+ def player_from_id(self, player_id: str) -> Player:
82
+ """Returns a player from their ID."""
83
+ return next((player for player in self.players if player.id == player_id), None)
84
 
85
+ def player_from_name(self, name: str) -> Player:
86
+ """Returns a player from their name."""
87
+ return next((player for player in self.players if player.name == name), None)
88
+
89
+ @property
90
+ def chameleon(self) -> Player:
91
+ """Returns the current chameleon."""
92
+ return self.player_from_id(self.chameleon_ids[-1])
93
+
94
+ @property
95
+ def herd_animal(self) -> str:
96
+ """Returns the current herd animal."""
97
+ return self.herd_animals[-1]
98
+
99
+ @property
100
+ def chameleon_guess(self) -> str:
101
+ """Returns the current chameleon guess."""
102
+ return self.chameleon_guesses[-1]
103
 
104
+ @property
105
+ def herd_vote_tally(self) -> List[dict]:
106
+ """Returns the current herd vote tally."""
107
+ return self.herd_vote_tallies[-1]
108
 
109
  def observer_message(self, message: Message):
110
  """Sends a message to the observer if there is one."""
 
151
  winner = None
152
  round_number = 0
153
 
154
+ # while not winner:
155
+ # round_results = await self.run_round()
156
+ # round_number += 1
157
 
158
+ # # Check for a Winner
159
+ # for player in self.players:
160
+ # if player.points >= self.winning_score:
161
+ # winner = player # ignoring the possibility of a tie for now
162
 
163
+ self.setup_round()
 
 
 
 
 
 
 
164
 
165
+ self.run_round()
166
 
167
+ self.resolve_round()
 
168
 
169
+ # # Log Game Info
170
+ # game_log = {
171
+ # "game_id": self.game_id,
172
+ # "start_time": self.start_time,
173
+ # "number_of_players": len(self.players),
174
+ # "human_player": self.players[self.human_index].id if self.human_index else "None",
175
+ # }
176
 
177
+ def setup_round(self):
178
+ """Sets up the round. This includes assigning roles and gathering player names."""
179
+ # Choose Animal
180
  herd_animal = random_animal()
181
+ self.herd_animals.append(herd_animal)
182
  self.debug_message(f"The secret animal is {herd_animal}.")
183
 
184
+ # Assign Roles
185
  chameleon_index = random_index(len(self.players))
186
+ self.chameleon_ids.append(self.players[chameleon_index].id)
187
 
188
  for i, player in enumerate(self.players):
189
  if i == chameleon_index:
 
194
  player.assign_role("herd")
195
  self.game_message(format_prompt("assign_herd", herd_animal=herd_animal), player)
196
 
197
+ def run_round(self):
198
+ """Starts the round."""
199
+ # Phase I: Collect Player Animal Descriptions
200
 
201
  self.game_message(f"Each player will now take turns describing themselves:")
202
  for i, current_player in enumerate(self.players):
203
+ self.player_turn_animal_description(current_player)
204
+
205
+ # Phase II: Chameleon Guesses the Animal
206
+
207
+ self.player_turn_chameleon_guess(self.chameleon)
208
+
209
+ # Phase III: The Herd Votes for who they think the Chameleon is
210
+ self.game_message("The Chameleon has guessed the animal. Now the Herd will vote on who they think the chameleon is.")
211
+
212
+ self.herd_vote_tallies.append([])
213
+ for current_player in self.players:
214
+ if current_player.role == "herd":
215
+ self.player_turn_herd_vote(current_player)
216
 
 
217
 
 
 
 
218
 
 
219
 
220
+ def player_turn_animal_description(self, player: Player):
221
+ """Handles a player's turn to describe themselves."""
222
+ if player.interface.is_ai:
223
+ self.verbose_message(f"{player.name} is thinking...")
224
 
225
+ prompt = fetch_prompt("player_describe_animal")
226
 
227
+ # Get Player Animal Description
228
+ message = Message(type="prompt", content=prompt)
229
+ response = player.interface.respond_to_formatted(message, AnimalDescriptionFormat)
230
+
231
+ self.game_message(f"{player.name}: {response.description}", player, exclude=True)
232
+
233
+ def player_turn_chameleon_guess(self, chameleon: Player):
234
+ """Handles the Chameleon's turn to guess the secret animal."""
235
  self.game_message("All players have spoken. The Chameleon will now guess the secret animal...")
236
  if chameleon.interface.is_ai or self.observer:
237
  self.verbose_message("The Chameleon is thinking...")
 
241
  message = Message(type="prompt", content=prompt)
242
  response = chameleon.interface.respond_to_formatted(message, ChameleonGuessFormat)
243
 
244
+ self.chameleon_guesses.append(response.animal)
245
 
246
+ def player_turn_herd_vote(self, player: Player):
247
+ """Handles a player's turn to vote for the Chameleon."""
248
+ if player.interface.is_ai:
249
+ self.verbose_message(f"{player.name} is thinking...")
250
 
251
+ prompt = fetch_prompt("vote")
 
 
 
 
252
 
253
+ # Get Player Vote
254
+ message = Message(type="prompt", content=prompt)
255
+ other_player_names = [p.name for p in self.players if p != player]
256
+ response = player.interface.respond_to_formatted(message, HerdVoteFormat, player_names=other_player_names)
257
 
258
+ if player.interface.is_ai:
259
+ self.debug_message(f"{player.name} voted for {response.vote}")
 
 
260
 
261
+ voted_for_player = self.player_from_name(response.vote)
 
 
 
262
 
263
+ # Add Vote to Player Votes
264
+ self.herd_vote_tally.append({"voter": player.id, "voted_for": voted_for_player.id})
 
265
 
266
+ def resolve_round(self):
267
+ """Resolves the round, assigns points, and prints the results."""
268
+ self.game_message("All players have voted!")
269
+ for vote in self.herd_vote_tally:
270
+ voter = self.player_from_id(vote["voter"])
271
+ voted_for = self.player_from_id(vote["voted_for"])
272
+ self.game_message(f"{voter.name} voted for {voted_for.name}")
273
 
274
+ accused_player_id = count_chameleon_votes(self.herd_vote_tally)
275
 
276
  self.game_message(f"The round is over. Calculating results...")
277
  self.game_message(
278
+ f"The Chameleon was {self.chameleon.name}, and they guessed the secret animal was {self.chameleon_guess}.")
279
+ self.game_message(f"The secret animal was actually was {self.herd_animal}.")
280
 
281
+ if accused_player_id:
282
+ accused_name = self.player_from_id(accused_player_id).name
283
+ self.game_message(f"The Herd voted for {accused_name} as the Chameleon.")
284
  else:
285
  self.game_message(f"The Herd could not come to a consensus.")
286
 
287
  # Point Logic
288
  # If the Chameleon guesses the correct animal = +1 Point to the Chameleon
289
+ if self.chameleon_guess.lower() == self.herd_animal.lower():
290
+ self.chameleon.points += 1
291
 
 
292
  # If the Chameleon guesses the incorrect animal = +1 Point to each Herd player
293
  else:
294
  for player in self.players:
295
  if player.role == "herd":
296
  player.points += 1
297
  # If a Herd player votes for the Chameleon = +1 Point to that player
298
+ for vote in self.herd_vote_tally:
299
+ if vote["voted_for"] == self.chameleon.id:
300
+ self.player_from_id(vote['voter']).points += 1
301
 
302
  # If the Herd fails to accuse the Chameleon = +1 Point to the Chameleon
303
+ if not accused_player_id or accused_player_id != self.chameleon.id:
304
+ self.chameleon.points += 1
305
 
306
+ # Print Scores
307
  player_points = "\n".join([f"{player.name}: {player.points}" for player in self.players])
308
+ self.game_message(f"Current Game Score:\n{player_points}")
309
 
 
310
 
 
 
 
 
 
 
src/game_utils.py CHANGED
@@ -37,7 +37,7 @@ def random_index(number_of_players : int) -> int:
37
 
38
  def count_chameleon_votes(player_votes: list[dict]) -> str | None:
39
  """Counts the votes for each player."""
40
- votes = [vote['vote'] for vote in player_votes]
41
 
42
  freq = Counter(votes)
43
  most_voted_player, number_of_votes = freq.most_common()[0]
 
37
 
38
  def count_chameleon_votes(player_votes: list[dict]) -> str | None:
39
  """Counts the votes for each player."""
40
+ votes = [vote['voted_for'] for vote in player_votes]
41
 
42
  freq = Counter(votes)
43
  most_voted_player, number_of_votes = freq.most_common()[0]
src/prompts.py CHANGED
@@ -32,18 +32,12 @@ _player_describe_animal = """It's your turn to describe yourself. Remember:
32
 
33
  Your Response:"""
34
 
35
- _all_responses = """\
36
- Below are the responses from all the other players.
37
- {player_responses}
38
- """
39
-
40
  _chameleon_guess_animal = """\
41
  What animal do you think the other players are pretending to be?
42
- Guess the name of the animal not it's plural form e.g. guess "animal" not "animals"
43
  """
44
 
45
  _vote_prompt = """\
46
- Now it is time to vote. Choose from the players above who you think the Chameleon is.
47
  """
48
 
49
  prompts = {
@@ -53,5 +47,5 @@ prompts = {
53
  "player_describe_animal": _player_describe_animal,
54
  "chameleon_guess_animal": _chameleon_guess_animal,
55
  "response": "Your response:",
56
- "vote": _all_responses + _vote_prompt
57
  }
 
32
 
33
  Your Response:"""
34
 
 
 
 
 
 
35
  _chameleon_guess_animal = """\
36
  What animal do you think the other players are pretending to be?
 
37
  """
38
 
39
  _vote_prompt = """\
40
+ Now it is time to vote. Choose from the other players who you think the Chameleon is.
41
  """
42
 
43
  prompts = {
 
47
  "player_describe_animal": _player_describe_animal,
48
  "chameleon_guess_animal": _chameleon_guess_animal,
49
  "response": "Your response:",
50
+ "vote": _vote_prompt
51
  }