Eric Botti
commited on
Commit
·
c6447fa
1
Parent(s):
7275c7f
streamlit game updates
Browse files- src/agent_interfaces.py +18 -10
- src/app.py +31 -54
- src/game.py +3 -0
- src/game_chameleon.py +40 -18
src/agent_interfaces.py
CHANGED
@@ -63,11 +63,15 @@ class BaseAgentInterface:
|
|
63 |
|
64 |
# Generate response methods - These do not take a message as input and only use the current message history
|
65 |
|
66 |
-
def generate_response(self) -> Message:
|
67 |
"""Generates a response based on the current messages in the history."""
|
68 |
-
|
69 |
-
|
70 |
-
|
|
|
|
|
|
|
|
|
71 |
|
72 |
def generate_formatted_response(
|
73 |
self,
|
@@ -158,14 +162,18 @@ class HumanAgentInterface(BaseAgentInterface):
|
|
158 |
output_format: Type[OutputFormatModel],
|
159 |
additional_fields: dict = None,
|
160 |
max_retries: int = 3
|
161 |
-
) -> OutputFormatModel:
|
162 |
"""For Human agents, we can trust them enough to format their own responses... for now"""
|
163 |
response = self.generate_response()
|
164 |
-
|
165 |
-
|
166 |
-
|
167 |
-
fields.
|
168 |
-
|
|
|
|
|
|
|
|
|
169 |
|
170 |
return output
|
171 |
|
|
|
63 |
|
64 |
# Generate response methods - These do not take a message as input and only use the current message history
|
65 |
|
66 |
+
def generate_response(self) -> Message | None:
|
67 |
"""Generates a response based on the current messages in the history."""
|
68 |
+
content = self._generate()
|
69 |
+
if content:
|
70 |
+
response = Message(type="agent", content=content)
|
71 |
+
self.add_message(response)
|
72 |
+
return response
|
73 |
+
else:
|
74 |
+
return None
|
75 |
|
76 |
def generate_formatted_response(
|
77 |
self,
|
|
|
162 |
output_format: Type[OutputFormatModel],
|
163 |
additional_fields: dict = None,
|
164 |
max_retries: int = 3
|
165 |
+
) -> OutputFormatModel | None:
|
166 |
"""For Human agents, we can trust them enough to format their own responses... for now"""
|
167 |
response = self.generate_response()
|
168 |
+
|
169 |
+
if response:
|
170 |
+
# only works because current outputs have only 1 field...
|
171 |
+
fields = {output_format.model_fields.copy().popitem()[0]: response.content}
|
172 |
+
if additional_fields:
|
173 |
+
fields.update(additional_fields)
|
174 |
+
output = output_format.model_validate(fields)
|
175 |
+
else:
|
176 |
+
output = None
|
177 |
|
178 |
return output
|
179 |
|
src/app.py
CHANGED
@@ -22,8 +22,7 @@ def display_message(message: Message):
|
|
22 |
|
23 |
if "messages" not in session_state:
|
24 |
session_state.messages = []
|
25 |
-
session_state.
|
26 |
-
session_state.game_state = "game_start"
|
27 |
|
28 |
|
29 |
class StreamlitInterface(HumanAgentInterface):
|
@@ -33,7 +32,9 @@ class StreamlitInterface(HumanAgentInterface):
|
|
33 |
display_message(message)
|
34 |
|
35 |
def _generate(self) -> str:
|
36 |
-
|
|
|
|
|
37 |
|
38 |
|
39 |
class StreamlitChameleonGame(ChameleonGame):
|
@@ -42,76 +43,50 @@ class StreamlitChameleonGame(ChameleonGame):
|
|
42 |
def run_game(self):
|
43 |
"""Starts the game."""
|
44 |
|
45 |
-
if
|
46 |
self.game_message(fetch_prompt("game_rules"))
|
47 |
-
|
48 |
-
if
|
49 |
self.setup_round()
|
50 |
-
|
51 |
-
if
|
52 |
self.run_round()
|
53 |
-
if
|
54 |
self.resolve_round()
|
55 |
-
|
56 |
|
57 |
def run_round(self):
|
58 |
"""Starts the round."""
|
59 |
|
60 |
# Phase I: Collect Player Animal Descriptions
|
61 |
-
if
|
62 |
for current_player in self.players:
|
63 |
if current_player.id not in [animal_description['player_id'] for animal_description in self.round_animal_descriptions]:
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
self.player_turn_animal_description(current_player)
|
71 |
-
session_state.awaiting_human_input = False
|
72 |
-
else:
|
73 |
-
self.game_message(fetch_prompt("player_describe_animal"), current_player)
|
74 |
-
self.player_turn_animal_description(current_player)
|
75 |
if len(self.round_animal_descriptions) == len(self.players):
|
76 |
-
|
77 |
-
session_state.awaiting_human_input = False
|
78 |
|
79 |
# Phase II: Chameleon Guesses the Animal
|
80 |
-
if
|
81 |
-
self.
|
82 |
-
player_responses = self.format_animal_descriptions(exclude=self.chameleon)
|
83 |
-
self.game_message(format_prompt("chameleon_guess_animal", player_responses=player_responses), self.chameleon)
|
84 |
-
if self.human_player().role == "chameleon":
|
85 |
-
if not session_state.awaiting_human_input:
|
86 |
-
session_state.awaiting_human_input = True
|
87 |
-
else:
|
88 |
-
self.player_turn_chameleon_guess(self.chameleon)
|
89 |
-
session_state.awaiting_human_input = False
|
90 |
-
else:
|
91 |
-
self.player_turn_chameleon_guess(self.chameleon)
|
92 |
-
session_state.awaiting_human_input = False
|
93 |
-
|
94 |
-
session_state.game_state = "herd_vote"
|
95 |
|
96 |
# Phase III: The Herd Votes for who they think the Chameleon is
|
97 |
-
if
|
98 |
for current_player in self.players:
|
99 |
if current_player.role == "herd" and current_player.id not in [vote['voter_id'] for vote in self.herd_vote_tally]:
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
-
break
|
106 |
-
else:
|
107 |
-
self.player_turn_herd_vote(current_player)
|
108 |
-
session_state.awaiting_human_input = False
|
109 |
-
else:
|
110 |
-
self.game_message(format_prompt("vote", player_responses=player_responses), current_player)
|
111 |
-
self.player_turn_herd_vote(current_player)
|
112 |
|
113 |
if len(self.herd_vote_tally) == len(self.players) - 1:
|
114 |
-
|
115 |
|
116 |
|
117 |
# Streamlit App
|
@@ -142,7 +117,9 @@ with center:
|
|
142 |
if user_input:
|
143 |
if "game" not in st.session_state:
|
144 |
st.session_state.game = StreamlitChameleonGame.from_human_name(user_input, StreamlitInterface)
|
145 |
-
|
|
|
|
|
146 |
st.session_state.game.run_game()
|
147 |
|
148 |
st.markdown("#")
|
|
|
22 |
|
23 |
if "messages" not in session_state:
|
24 |
session_state.messages = []
|
25 |
+
session_state.user_input = None
|
|
|
26 |
|
27 |
|
28 |
class StreamlitInterface(HumanAgentInterface):
|
|
|
32 |
display_message(message)
|
33 |
|
34 |
def _generate(self) -> str:
|
35 |
+
response = session_state.user_input
|
36 |
+
session_state.user_input = None
|
37 |
+
return response
|
38 |
|
39 |
|
40 |
class StreamlitChameleonGame(ChameleonGame):
|
|
|
43 |
def run_game(self):
|
44 |
"""Starts the game."""
|
45 |
|
46 |
+
if self.game_state == "game_start":
|
47 |
self.game_message(fetch_prompt("game_rules"))
|
48 |
+
self.game_state = "setup_round"
|
49 |
+
if self.game_state == "setup_round":
|
50 |
self.setup_round()
|
51 |
+
self.game_state = "animal_description"
|
52 |
+
if self.game_state in ["animal_description", "chameleon_guess", "herd_vote"]:
|
53 |
self.run_round()
|
54 |
+
if self.game_state == "resolve_round":
|
55 |
self.resolve_round()
|
56 |
+
self.game_state = "setup_round"
|
57 |
|
58 |
def run_round(self):
|
59 |
"""Starts the round."""
|
60 |
|
61 |
# Phase I: Collect Player Animal Descriptions
|
62 |
+
if self.game_state == "animal_description":
|
63 |
for current_player in self.players:
|
64 |
if current_player.id not in [animal_description['player_id'] for animal_description in self.round_animal_descriptions]:
|
65 |
+
|
66 |
+
response = self.player_turn_animal_description(current_player)
|
67 |
+
|
68 |
+
if not response:
|
69 |
+
break
|
70 |
+
|
|
|
|
|
|
|
|
|
|
|
71 |
if len(self.round_animal_descriptions) == len(self.players):
|
72 |
+
self.game_state = "chameleon_guess"
|
|
|
73 |
|
74 |
# Phase II: Chameleon Guesses the Animal
|
75 |
+
if self.game_state == "chameleon_guess":
|
76 |
+
self.player_turn_chameleon_guess(self.chameleon)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
77 |
|
78 |
# Phase III: The Herd Votes for who they think the Chameleon is
|
79 |
+
if self.game_state == "herd_vote":
|
80 |
for current_player in self.players:
|
81 |
if current_player.role == "herd" and current_player.id not in [vote['voter_id'] for vote in self.herd_vote_tally]:
|
82 |
+
|
83 |
+
response = self.player_turn_herd_vote(current_player)
|
84 |
+
|
85 |
+
if not response:
|
86 |
+
break
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
87 |
|
88 |
if len(self.herd_vote_tally) == len(self.players) - 1:
|
89 |
+
self.game_state = "resolve_round"
|
90 |
|
91 |
|
92 |
# Streamlit App
|
|
|
117 |
if user_input:
|
118 |
if "game" not in st.session_state:
|
119 |
st.session_state.game = StreamlitChameleonGame.from_human_name(user_input, StreamlitInterface)
|
120 |
+
else:
|
121 |
+
session_state.user_input = user_input
|
122 |
+
|
123 |
st.session_state.game.run_game()
|
124 |
|
125 |
st.markdown("#")
|
src/game.py
CHANGED
@@ -28,6 +28,9 @@ class Game:
|
|
28 |
|
29 |
self.winner_id: str | None = None
|
30 |
"""The id of the player who has won the game."""
|
|
|
|
|
|
|
31 |
|
32 |
def player_from_id(self, player_id: str) -> Player:
|
33 |
"""Returns a player from their ID."""
|
|
|
28 |
|
29 |
self.winner_id: str | None = None
|
30 |
"""The id of the player who has won the game."""
|
31 |
+
self.game_state: str = "game_start"
|
32 |
+
"""Keeps track of the current state of the game."""
|
33 |
+
self.awaiting_input: bool = False
|
34 |
|
35 |
def player_from_id(self, player_id: str) -> Player:
|
36 |
"""Returns a player from their ID."""
|
src/game_chameleon.py
CHANGED
@@ -152,45 +152,67 @@ class ChameleonGame(Game):
|
|
152 |
|
153 |
def player_turn_animal_description(self, player: Player):
|
154 |
"""Handles a player's turn to describe themselves."""
|
155 |
-
if
|
156 |
-
self.verbose_message(f"{player.name} is thinking...")
|
157 |
-
|
158 |
-
prompt = fetch_prompt("player_describe_animal")
|
159 |
|
160 |
# Get Player Animal Description
|
161 |
response = player.interface.generate_formatted_response(AnimalDescriptionFormat)
|
162 |
|
163 |
-
|
|
|
|
|
|
|
|
|
|
|
164 |
|
165 |
-
|
166 |
|
167 |
def player_turn_chameleon_guess(self, chameleon: Player):
|
168 |
"""Handles the Chameleon's turn to guess the secret animal."""
|
169 |
-
|
170 |
-
|
171 |
-
self.verbose_message("The Chameleon is thinking...")
|
|
|
|
|
|
|
172 |
|
173 |
response = chameleon.interface.generate_formatted_response(ChameleonGuessFormat)
|
174 |
|
175 |
-
|
176 |
-
|
177 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
178 |
|
179 |
def player_turn_herd_vote(self, player: Player):
|
180 |
"""Handles a player's turn to vote for the Chameleon."""
|
181 |
-
if
|
182 |
-
self.verbose_message(f"{player.name} is thinking...")
|
|
|
|
|
183 |
|
184 |
# Get Player Vote
|
185 |
additional_fields = {"player_names": [p.name for p in self.players if p != player]}
|
186 |
response = player.interface.generate_formatted_response(HerdVoteFormat, additional_fields=additional_fields)
|
187 |
|
188 |
-
|
|
|
|
|
|
|
189 |
|
190 |
-
|
|
|
|
|
|
|
|
|
|
|
191 |
|
192 |
-
|
193 |
-
self.herd_vote_tally.append({"voter_id": player.id, "voted_for_id": voted_for_player.id})
|
194 |
|
195 |
def resolve_round(self):
|
196 |
"""Resolves the round, assigns points, and prints the results."""
|
|
|
152 |
|
153 |
def player_turn_animal_description(self, player: Player):
|
154 |
"""Handles a player's turn to describe themselves."""
|
155 |
+
if not self.awaiting_input:
|
156 |
+
self.verbose_message(f"{player.name} is thinking...", recipient=player, exclude=True)
|
157 |
+
self.game_message(fetch_prompt("player_describe_animal"), player)
|
|
|
158 |
|
159 |
# Get Player Animal Description
|
160 |
response = player.interface.generate_formatted_response(AnimalDescriptionFormat)
|
161 |
|
162 |
+
if response:
|
163 |
+
self.round_animal_descriptions.append({"player_id": player.id, "description": response.description})
|
164 |
+
self.game_message(f"{player.name}: {response.description}", player, exclude=True)
|
165 |
+
self.awaiting_input = False
|
166 |
+
else:
|
167 |
+
self.awaiting_input = True
|
168 |
|
169 |
+
return response
|
170 |
|
171 |
def player_turn_chameleon_guess(self, chameleon: Player):
|
172 |
"""Handles the Chameleon's turn to guess the secret animal."""
|
173 |
+
if not self.awaiting_input:
|
174 |
+
self.game_message("All players have spoken. The Chameleon will now guess the secret animal...")
|
175 |
+
self.verbose_message("The Chameleon is thinking...", recipient=chameleon, exclude=True)
|
176 |
+
player_responses = self.format_animal_descriptions(exclude=self.chameleon)
|
177 |
+
self.game_message(format_prompt("chameleon_guess_animal", player_responses=player_responses),
|
178 |
+
self.chameleon)
|
179 |
|
180 |
response = chameleon.interface.generate_formatted_response(ChameleonGuessFormat)
|
181 |
|
182 |
+
if response:
|
183 |
+
self.chameleon_guesses.append(response.animal)
|
184 |
+
self.game_message(
|
185 |
+
"The Chameleon has guessed the animal. Now the Herd will vote on who they think the chameleon is.")
|
186 |
+
self.awaiting_input = False
|
187 |
+
self.game_state = "herd_vote"
|
188 |
+
else:
|
189 |
+
# Await input and do not proceed to the next phase
|
190 |
+
self.awaiting_input = True
|
191 |
|
192 |
def player_turn_herd_vote(self, player: Player):
|
193 |
"""Handles a player's turn to vote for the Chameleon."""
|
194 |
+
if not self.awaiting_input:
|
195 |
+
self.verbose_message(f"{player.name} is thinking...", recipient=player, exclude=True)
|
196 |
+
player_responses = self.format_animal_descriptions(exclude=player)
|
197 |
+
self.game_message(format_prompt("vote", player_responses=player_responses), player)
|
198 |
|
199 |
# Get Player Vote
|
200 |
additional_fields = {"player_names": [p.name for p in self.players if p != player]}
|
201 |
response = player.interface.generate_formatted_response(HerdVoteFormat, additional_fields=additional_fields)
|
202 |
|
203 |
+
if response:
|
204 |
+
self.debug_message(f"{player.name} voted for {response.vote}", recipient=player, exclude=True)
|
205 |
+
|
206 |
+
voted_for_player = self.player_from_name(response.vote)
|
207 |
|
208 |
+
player_vote = {"voter_id": player.id, "voted_for_id": voted_for_player.id}
|
209 |
+
|
210 |
+
self.herd_vote_tally.append(player_vote)
|
211 |
+
self.awaiting_input = False
|
212 |
+
else:
|
213 |
+
self.awaiting_input = True
|
214 |
|
215 |
+
return response
|
|
|
216 |
|
217 |
def resolve_round(self):
|
218 |
"""Resolves the round, assigns points, and prints the results."""
|