Eric Botti commited on
Commit
7877562
·
1 Parent(s): c68d320

updated game setup and messaging

Browse files
Files changed (5) hide show
  1. .gitignore +2 -1
  2. src/game.py +76 -51
  3. src/main.py +2 -2
  4. src/player.py +37 -10
  5. src/prompts.py +5 -6
.gitignore CHANGED
@@ -1,3 +1,4 @@
1
  /venv/
2
  /experiments/
3
- /test/
 
 
1
  /venv/
2
  /experiments/
3
+ /test/
4
+ /.idea/
src/game.py CHANGED
@@ -10,24 +10,26 @@ from player import Player
10
  from prompts import fetch_prompt, format_prompt
11
 
12
  # Default Values
13
- NUMBER_OF_PLAYERS = 5
14
-
15
 
16
  class Game:
17
  log_dir = os.path.join(os.pardir, "experiments")
18
  player_log_file = "{player_id}.jsonl"
19
  game_log_file = "{game_id}-game.jsonl"
 
 
 
 
 
 
20
 
21
  def __init__(
22
  self,
23
  number_of_players: int = NUMBER_OF_PLAYERS,
24
  human_name: str = None,
25
- verbose: bool = False # If there is a human player game will always be verbose
26
  ):
27
-
28
- # This function is used to broadcast messages to the human player.
29
- # They are purely informative and do not affect the game.
30
-
31
  # Game ID
32
  self.game_id = game_id()
33
  self.start_time = datetime.now().strftime('%Y-%m-%d_%H-%M-%S')
@@ -41,11 +43,11 @@ class Game:
41
  if human_name:
42
  ai_names = random_names(number_of_players - 1, human_name)
43
  self.human_index = random_index(number_of_players)
44
- self.verbose = True
45
  else:
46
  ai_names = random_names(number_of_players)
47
  self.human_index = None
48
- self.verbose = verbose
 
49
 
50
  # Add Players
51
  self.players = []
@@ -62,14 +64,14 @@ class Game:
62
  else:
63
  role = "herd"
64
 
65
- player_id = f"{self.game_id}-{i + 1}-{role}"
66
 
67
  log_path = os.path.join(
68
  self.log_dir,
69
  self.player_log_file.format(player_id=player_id)
70
  )
71
 
72
- self.players.append(Player(name, controller, role, player_id, log_filepath=log_path))
73
 
74
  # Game State
75
  self.player_responses = []
@@ -100,6 +102,8 @@ class Game:
100
  player.prompt_queue.append(message)
101
  if player.controller_type == "human":
102
  print(message)
 
 
103
  else:
104
  recipient.prompt_queue.append(message)
105
  if recipient.controller_type == "human":
@@ -119,38 +123,57 @@ class Game:
119
  if self.verbose:
120
  print(Fore.GREEN + message + Style.RESET_ALL)
121
 
122
- # def debug_message(self, message: str):
123
- # """Sends a message for a human observer. These messages contain secret information about the players such as their role."""
124
- # if self.debug:
125
- # print(Fore.YELLOW + message + Style.RESET_ALL)
126
-
127
- # def game_setup(self):
128
- # """Sets up the game. This includes assigning roles and gathering player names."""
129
- # self.verbose_message("Setting up the game...")
130
- #
131
- # for i, player in enumerate(self.players):
132
- # if player.controller_type != "human":
133
- # self.verbose_message(f"Player {i + 1}: {player.name} - {player.role}")
134
-
135
 
136
  async def start(self):
137
- """Starts the game."""
138
- self.verbose_message(("Welcome to Chameleon! This is a social deduction game powered by LLMs."))
139
  self.game_message(fetch_prompt("game_rules"))
140
 
141
- self.player_responses = []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
  herd_animal = random_animal()
143
 
144
- # Phase I: Collect Player Animal Descriptions
145
- self.game_message(f"Each player will now take turns describing themselves.")
146
- for current_player in self.players:
 
 
 
 
 
 
 
 
 
 
 
 
 
147
  if current_player.controller_type != "human":
148
  self.verbose_message(f"{current_player.name} is thinking...")
149
 
150
- if current_player.role == "chameleon":
151
- prompt = format_prompt("chameleon_animal", player_responses=self.format_responses())
152
  else:
153
- prompt = format_prompt("herd_animal", animal=herd_animal, player_responses=self.format_responses())
 
154
 
155
  # Get Player Animal Description
156
  response = await self.instructional_message(prompt, current_player, AnimalDescriptionModel)
@@ -159,13 +182,12 @@ class Game:
159
 
160
  self.game_message(f"{current_player.name}: {response.description}", current_player, exclude=True)
161
 
 
162
 
163
- # Phase II: Chameleon Decides if they want to guess the animal (secretly)
164
- self.game_message("All players have spoken. Now the chameleon will decide if they want to guess the animal or not.")
165
  if self.human_index != self.chameleon_index:
166
- self.verbose_message("The chameleon is thinking...")
167
 
168
- chameleon = self.players[self.chameleon_index]
169
  prompt = format_prompt("chameleon_guess_decision", player_responses=self.format_responses(exclude=chameleon.name))
170
  response = await self.instructional_message(prompt, chameleon, ChameleonGuessDecisionModel)
171
 
@@ -174,7 +196,8 @@ class Game:
174
  else:
175
  chameleon_will_guess = False
176
 
177
- # Phase III: Chameleon Guesses Animal or All Players Vote for Chameleon
 
178
  if chameleon_will_guess:
179
  # Chameleon Guesses Animal
180
  self.game_message(f"{chameleon.name} has revealed themselves to be the chameleon and is guessing the animal...", chameleon, exclude=True)
@@ -194,23 +217,25 @@ class Game:
194
 
195
  else:
196
  # All Players Vote for Chameleon
197
- print("vote time")
198
- self.game_message("The chameleon has decided not to guess the animal. Now all players will vote on who they think the chameleon is.")
199
 
200
  player_votes = []
201
  for player in self.players:
202
- if player.controller_type != "human":
203
- self.verbose_message(f"{player.name} is thinking...")
 
204
 
205
- prompt = format_prompt("vote", player_responses=self.format_responses(exclude=player.name))
206
 
207
- # Get Player Vote
208
- response = await self.instructional_message(prompt, player, VoteModel)
209
 
210
- # check if a valid player was voted for...
211
 
212
- # Add Vote to Player Votes
213
- player_votes.append(response.vote)
 
 
214
 
215
  self.game_message("All players have voted!")
216
  self.game_message(f"Votes: {player_votes}")
@@ -232,9 +257,9 @@ class Game:
232
  winner = "chameleon"
233
 
234
 
235
- # Assign Points
236
- # Chameleon Wins - 3 Points
237
- # Herd Wins by Failed Chameleon Guess - 1 Point (each)
238
  # Herd Wins by Correctly Guessing Chameleon - 2 points (each)
239
 
240
  # Log Game Info
 
10
  from prompts import fetch_prompt, format_prompt
11
 
12
  # Default Values
13
+ NUMBER_OF_PLAYERS = 6
14
+ WINNING_SCORE = 11
15
 
16
  class Game:
17
  log_dir = os.path.join(os.pardir, "experiments")
18
  player_log_file = "{player_id}.jsonl"
19
  game_log_file = "{game_id}-game.jsonl"
20
+ number_of_players = NUMBER_OF_PLAYERS
21
+ """The number of players in the game."""
22
+ winning_score = WINNING_SCORE
23
+ """The Number of points required to win the game."""
24
+ debug = True
25
+ """If True, the game will print debug messages to the console."""
26
 
27
  def __init__(
28
  self,
29
  number_of_players: int = NUMBER_OF_PLAYERS,
30
  human_name: str = None,
31
+ verbose = False
32
  ):
 
 
 
 
33
  # Game ID
34
  self.game_id = game_id()
35
  self.start_time = datetime.now().strftime('%Y-%m-%d_%H-%M-%S')
 
43
  if human_name:
44
  ai_names = random_names(number_of_players - 1, human_name)
45
  self.human_index = random_index(number_of_players)
 
46
  else:
47
  ai_names = random_names(number_of_players)
48
  self.human_index = None
49
+
50
+ self.verbose = verbose
51
 
52
  # Add Players
53
  self.players = []
 
64
  else:
65
  role = "herd"
66
 
67
+ player_id = f"{self.game_id}-{i + 1}"
68
 
69
  log_path = os.path.join(
70
  self.log_dir,
71
  self.player_log_file.format(player_id=player_id)
72
  )
73
 
74
+ self.players.append(Player(name, controller, player_id, log_filepath=log_path))
75
 
76
  # Game State
77
  self.player_responses = []
 
102
  player.prompt_queue.append(message)
103
  if player.controller_type == "human":
104
  print(message)
105
+ if self.verbose:
106
+ print(message)
107
  else:
108
  recipient.prompt_queue.append(message)
109
  if recipient.controller_type == "human":
 
123
  if self.verbose:
124
  print(Fore.GREEN + message + Style.RESET_ALL)
125
 
126
+ def debug_message(self, message: str):
127
+ """Sends a message for a human observer. These messages contain secret information about the players such as their role."""
128
+ if self.debug:
129
+ print(Fore.YELLOW + "DEBUG: " + message + Style.RESET_ALL)
 
 
 
 
 
 
 
 
 
130
 
131
  async def start(self):
132
+ """Sets up the game. This includes assigning roles and gathering player names."""
133
+ self.game_message(("Welcome to Chameleon! This is a social deduction game powered by LLMs."))
134
  self.game_message(fetch_prompt("game_rules"))
135
 
136
+ await self.run_round()
137
+
138
+ round_log = {
139
+ "game_id": self.game_id,
140
+ # "round_id": round_number,
141
+ "herd_animal": random_animal(),
142
+ }
143
+
144
+
145
+
146
+ async def run_round(self):
147
+ """Starts the round."""
148
+
149
+ # Phase I: Choose Animal and Assign Roles
150
+
151
  herd_animal = random_animal()
152
 
153
+ chameleon_index = random_index(len(self.players))
154
+ chameleon = self.players[chameleon_index]
155
+
156
+ for i, player in enumerate(self.players):
157
+ if i == chameleon_index:
158
+ player.assign_role("chameleon")
159
+ self.game_message("You are the **Chameleon**, remain undetected and guess what animal the others are pretending to be", player)
160
+ self.debug_message(f"{player.name} is the Chameleon!")
161
+ else:
162
+ player.assign_role("herd")
163
+ 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)
164
+
165
+ # Phase II: Collect Player Animal Descriptions
166
+
167
+ self.game_message(f"Each player will now take turns describing themselves:")
168
+ for i, current_player in enumerate(self.players):
169
  if current_player.controller_type != "human":
170
  self.verbose_message(f"{current_player.name} is thinking...")
171
 
172
+ if i == 0:
173
+ prompt = "Your Response:"
174
  else:
175
+ prompt = "It's your turn to describe yourself. Do not repeat responses from other players.\nYour Response:"
176
+
177
 
178
  # Get Player Animal Description
179
  response = await self.instructional_message(prompt, current_player, AnimalDescriptionModel)
 
182
 
183
  self.game_message(f"{current_player.name}: {response.description}", current_player, exclude=True)
184
 
185
+ # Phase III: Chameleon Decides if they want to guess the animal (secretly)
186
 
187
+ self.game_message("All players have spoken. Now the Chameleon will decide if they want to guess the animal or not.")
 
188
  if self.human_index != self.chameleon_index:
189
+ self.verbose_message("The Chameleon is thinking...")
190
 
 
191
  prompt = format_prompt("chameleon_guess_decision", player_responses=self.format_responses(exclude=chameleon.name))
192
  response = await self.instructional_message(prompt, chameleon, ChameleonGuessDecisionModel)
193
 
 
196
  else:
197
  chameleon_will_guess = False
198
 
199
+ # Phase IV: Chameleon Guesses Animal or All Players Vote for Chameleon
200
+
201
  if chameleon_will_guess:
202
  # Chameleon Guesses Animal
203
  self.game_message(f"{chameleon.name} has revealed themselves to be the chameleon and is guessing the animal...", chameleon, exclude=True)
 
217
 
218
  else:
219
  # All Players Vote for Chameleon
220
+ self.game_message("The Chameleon has decided not to guess the animal. Now all players will vote on who they think the chameleon is.")
 
221
 
222
  player_votes = []
223
  for player in self.players:
224
+ if player.role == "herd":
225
+ if player.is_ai():
226
+ self.verbose_message(f"{player.name} is thinking...")
227
 
228
+ prompt = format_prompt("vote", player_responses=self.format_responses(exclude=player.name))
229
 
230
+ # Get Player Vote
231
+ response = await self.instructional_message(prompt, player, VoteModel)
232
 
233
+ # check if a valid player was voted for...
234
 
235
+ # Add Vote to Player Votes
236
+ player_votes.append(response.vote)
237
+ if player.is_ai():
238
+ self.debug_message(f"{player.name} for {response.vote}")
239
 
240
  self.game_message("All players have voted!")
241
  self.game_message(f"Votes: {player_votes}")
 
257
  winner = "chameleon"
258
 
259
 
260
+ # Phase V: Assign Points
261
+ # Chameleon Wins by Correct Guess - 3 Points
262
+ # Herd Wins by Failed Chameleon Guess - 1 Point (each)
263
  # Herd Wins by Correctly Guessing Chameleon - 2 points (each)
264
 
265
  # Log Game Info
src/main.py CHANGED
@@ -8,9 +8,9 @@ def main():
8
  name = input()
9
 
10
  if name:
11
- game = Game(human_name=name)
12
  else:
13
- game = Game()
14
 
15
  asyncio.run(game.start())
16
 
 
8
  name = input()
9
 
10
  if name:
11
+ game = Game(human_name=name, verbose=True)
12
  else:
13
+ game = Game(verbose=True)
14
 
15
  asyncio.run(game.start())
16
 
src/player.py CHANGED
@@ -1,5 +1,5 @@
1
  import os
2
- from typing import Type
3
 
4
  from langchain_core.runnables import Runnable, RunnableParallel, RunnableLambda, chain
5
 
@@ -7,18 +7,35 @@ from langchain.output_parsers import PydanticOutputParser
7
  from langchain_core.prompts import PromptTemplate
8
  from langchain_core.messages import HumanMessage, AnyMessage
9
 
 
 
10
  from pydantic import BaseModel
11
 
12
  from game_utils import log
13
  from controllers import controller_from_name
14
 
 
 
15
 
16
  class Player:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  def __init__(
18
  self,
19
  name: str,
20
  controller: str,
21
- role: str,
22
  player_id: str = None,
23
  log_filepath: str = None
24
  ):
@@ -31,12 +48,6 @@ class Player:
31
  self.controller_type = "ai"
32
 
33
  self.controller = controller_from_name(controller)
34
-
35
- self.role = role
36
- self.messages = []
37
-
38
- self.prompt_queue = []
39
-
40
  self.log_filepath = log_filepath
41
 
42
  if log_filepath:
@@ -55,11 +66,20 @@ class Player:
55
  self.generate = RunnableLambda(self._generate)
56
  self.format_output = RunnableLambda(self._output_formatter)
57
 
 
 
 
 
 
 
 
58
  async def respond_to(self, prompt: str, output_format: Type[BaseModel], max_retries=3):
59
  """Makes the player respond to a prompt. Returns the response in the specified format."""
60
  if self.prompt_queue:
61
  # If there are prompts in the queue, add them to the current prompt
62
  prompt = "\n".join(self.prompt_queue + [prompt])
 
 
63
 
64
  message = HumanMessage(content=prompt)
65
  output = await self.generate.ainvoke(message)
@@ -67,12 +87,13 @@ class Player:
67
  retries = 0
68
  try:
69
  output = await self.format_output.ainvoke({"output_format": output_format})
70
- except ValueError as e:
71
- if retries < max_retries:
72
  self.add_to_history(HumanMessage(content=f"Error formatting response: {e} \n\n Please try again."))
73
  output = await self.format_output.ainvoke({"output_format": output_format})
74
  retries += 1
75
  else:
 
76
  raise e
77
  else:
78
  # Convert the human message to the pydantic object format
@@ -85,6 +106,12 @@ class Player:
85
  self.messages.append(message)
86
  log(message.dict(), self.log_filepath)
87
 
 
 
 
 
 
 
88
  def _generate(self, message: HumanMessage):
89
  """Entry point for the Runnable generating responses, automatically logs the message."""
90
  self.add_to_history(message)
 
1
  import os
2
+ from typing import Type, Literal
3
 
4
  from langchain_core.runnables import Runnable, RunnableParallel, RunnableLambda, chain
5
 
 
7
  from langchain_core.prompts import PromptTemplate
8
  from langchain_core.messages import HumanMessage, AnyMessage
9
 
10
+ from langchain_core.exceptions import OutputParserException
11
+
12
  from pydantic import BaseModel
13
 
14
  from game_utils import log
15
  from controllers import controller_from_name
16
 
17
+ Role = Literal["chameleon", "herd"]
18
+
19
 
20
  class Player:
21
+
22
+ role: Role | None = None
23
+ """The role of the player in the game. Can be "chameleon" or "herd"."""
24
+ rounds_played_as_chameleon: int = 0
25
+ """The number of times the player has been the chameleon."""
26
+ rounds_played_as_herd: int = 0
27
+ """The number of times the player has been in the herd."""
28
+ points: int = 0
29
+ """The number of points the player has."""
30
+ messages: list[AnyMessage] = []
31
+ """The messages the player has sent and received."""
32
+ prompt_queue: list[str] = []
33
+ """A queue of prompts to be added to the next prompt."""
34
+
35
  def __init__(
36
  self,
37
  name: str,
38
  controller: str,
 
39
  player_id: str = None,
40
  log_filepath: str = None
41
  ):
 
48
  self.controller_type = "ai"
49
 
50
  self.controller = controller_from_name(controller)
 
 
 
 
 
 
51
  self.log_filepath = log_filepath
52
 
53
  if log_filepath:
 
66
  self.generate = RunnableLambda(self._generate)
67
  self.format_output = RunnableLambda(self._output_formatter)
68
 
69
+ def assign_role(self, role: Role):
70
+ self.role = role
71
+ if role == "chameleon":
72
+ self.rounds_played_as_chameleon += 1
73
+ elif role == "herd":
74
+ self.rounds_played_as_herd += 1
75
+
76
  async def respond_to(self, prompt: str, output_format: Type[BaseModel], max_retries=3):
77
  """Makes the player respond to a prompt. Returns the response in the specified format."""
78
  if self.prompt_queue:
79
  # If there are prompts in the queue, add them to the current prompt
80
  prompt = "\n".join(self.prompt_queue + [prompt])
81
+ # Clear the prompt queue
82
+ self.prompt_queue = []
83
 
84
  message = HumanMessage(content=prompt)
85
  output = await self.generate.ainvoke(message)
 
87
  retries = 0
88
  try:
89
  output = await self.format_output.ainvoke({"output_format": output_format})
90
+ except Exception as e:
91
+ if retries > max_retries:
92
  self.add_to_history(HumanMessage(content=f"Error formatting response: {e} \n\n Please try again."))
93
  output = await self.format_output.ainvoke({"output_format": output_format})
94
  retries += 1
95
  else:
96
+ print(f"Max retries reached due to Error: {e}")
97
  raise e
98
  else:
99
  # Convert the human message to the pydantic object format
 
106
  self.messages.append(message)
107
  log(message.dict(), self.log_filepath)
108
 
109
+ def is_human(self):
110
+ return self.controller_type == "human"
111
+
112
+ def is_ai(self):
113
+ return not self.is_human()
114
+
115
  def _generate(self, message: HumanMessage):
116
  """Entry point for the Runnable generating responses, automatically logs the message."""
117
  self.add_to_history(message)
src/prompts.py CHANGED
@@ -73,18 +73,16 @@ After all players have spoken, they vote on who they think the Chameleon is. \
73
  _herd_animal = """\
74
  You are a {animal}, keep this a secret at all costs.
75
  In as few words as possible describe of yourself starting with "I". Your description should be vague but true, \
76
- since if the Chameleon can guess animal you are, you will LOSE. Do not repeat responses from other players.
77
- Previously Mentioned Descriptions:
78
- {player_responses}
79
- Your Response: """
80
 
81
  _chameleon_animal = """\
82
  You are the Chameleon, keep this a secret at all costs.
83
  You don't know what animal the other players are, your goal is to deduce it using the context they provide.
84
  Starting with "I" describe yourself in 10 words or less as if you are the same animal as the other players.
85
  If no one else has said anything try to say something generic that could be true of any animals.
86
- If the other players realize you are the Chameleon you will LOSE.
87
- Your Response: """
88
 
89
  _all_responses = """\
90
  Below are the responses from all the other players.
@@ -113,5 +111,6 @@ prompts = {
113
  "chameleon_animal": _chameleon_animal,
114
  "chameleon_guess_decision": _all_responses + _chameleon_guess_decision,
115
  "chameleon_guess_animal": _chameleon_guess_animal,
 
116
  "vote": _all_responses + _vote_prompt
117
  }
 
73
  _herd_animal = """\
74
  You are a {animal}, keep this a secret at all costs.
75
  In as few words as possible describe of yourself starting with "I". Your description should be vague but true, \
76
+ since if the Chameleon can guess animal you are, you will LOSE. Do not repeat responses from other players.\
77
+ """
 
 
78
 
79
  _chameleon_animal = """\
80
  You are the Chameleon, keep this a secret at all costs.
81
  You don't know what animal the other players are, your goal is to deduce it using the context they provide.
82
  Starting with "I" describe yourself in 10 words or less as if you are the same animal as the other players.
83
  If no one else has said anything try to say something generic that could be true of any animals.
84
+ If the other players realize you are the Chameleon you will LOSE.\
85
+ """
86
 
87
  _all_responses = """\
88
  Below are the responses from all the other players.
 
111
  "chameleon_animal": _chameleon_animal,
112
  "chameleon_guess_decision": _all_responses + _chameleon_guess_decision,
113
  "chameleon_guess_animal": _chameleon_guess_animal,
114
+ "response": "Your response:",
115
  "vote": _all_responses + _vote_prompt
116
  }