Eric Botti
commited on
Commit
·
c6c2b98
1
Parent(s):
7877562
added points system
Browse files- src/game.py +77 -74
- src/game_utils.py +4 -2
- src/player.py +8 -4
src/game.py
CHANGED
@@ -109,15 +109,20 @@ class Game:
|
|
109 |
if recipient.controller_type == "human":
|
110 |
print(message)
|
111 |
|
112 |
-
|
113 |
-
async def instructional_message(message: str, player: Player, output_format: Type[BaseModel]):
|
114 |
"""Sends a message to a specific player and gets their response."""
|
115 |
if player.controller_type == "human":
|
116 |
-
|
117 |
response = await player.respond_to(message, output_format)
|
118 |
return response
|
119 |
|
120 |
# The following methods are used to broadcast messages to a human.
|
|
|
|
|
|
|
|
|
|
|
|
|
121 |
def verbose_message(self, message: str):
|
122 |
"""Sends a message for the human player to read. No response is expected."""
|
123 |
if self.verbose:
|
@@ -135,11 +140,16 @@ class Game:
|
|
135 |
|
136 |
await self.run_round()
|
137 |
|
138 |
-
|
|
|
139 |
"game_id": self.game_id,
|
140 |
-
|
141 |
-
"
|
|
|
142 |
}
|
|
|
|
|
|
|
143 |
|
144 |
|
145 |
|
@@ -149,6 +159,7 @@ class Game:
|
|
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]
|
@@ -182,96 +193,88 @@ class Game:
|
|
182 |
|
183 |
self.game_message(f"{current_player.name}: {response.description}", current_player, exclude=True)
|
184 |
|
185 |
-
# Phase III: Chameleon
|
186 |
|
187 |
-
self.game_message("All players have spoken.
|
188 |
if self.human_index != self.chameleon_index:
|
189 |
self.verbose_message("The Chameleon is thinking...")
|
190 |
|
191 |
-
prompt =
|
192 |
-
response = await self.instructional_message(prompt, chameleon, ChameleonGuessDecisionModel)
|
193 |
|
194 |
-
|
195 |
-
chameleon_will_guess = True
|
196 |
-
else:
|
197 |
-
chameleon_will_guess = False
|
198 |
|
199 |
-
|
200 |
|
201 |
-
|
202 |
-
|
203 |
-
self.game_message(f"{chameleon.name} has revealed themselves to be the chameleon and is guessing the animal...", chameleon, exclude=True)
|
204 |
|
205 |
-
|
206 |
|
207 |
-
|
|
|
|
|
|
|
|
|
208 |
|
209 |
-
|
210 |
|
211 |
-
|
212 |
-
self.
|
213 |
-
winner = "chameleon"
|
214 |
-
else:
|
215 |
-
self.game_message(f"The Chameleon is incorrect, the true animal is a {herd_animal}. The Herd wins!")
|
216 |
-
winner = "herd"
|
217 |
|
218 |
-
|
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 |
-
|
223 |
-
|
224 |
-
if player.
|
225 |
-
|
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 |
-
|
231 |
-
|
|
|
232 |
|
233 |
-
|
|
|
234 |
|
235 |
-
|
236 |
-
player_votes.append(response.vote)
|
237 |
-
if player.is_ai():
|
238 |
-
self.debug_message(f"{player.name} for {response.vote}")
|
239 |
|
240 |
-
|
241 |
-
|
|
|
|
|
242 |
|
243 |
-
|
244 |
-
accused_player
|
|
|
|
|
245 |
|
246 |
-
|
247 |
-
|
248 |
-
|
249 |
-
|
250 |
-
|
251 |
-
|
252 |
-
|
253 |
-
|
254 |
-
|
255 |
-
|
256 |
-
|
257 |
-
|
|
|
258 |
|
|
|
|
|
|
|
259 |
|
260 |
-
#
|
261 |
-
|
262 |
-
# Herd Wins by Failed Chameleon Guess - 1 Point (each)
|
263 |
-
# Herd Wins by Correctly Guessing Chameleon - 2 points (each)
|
264 |
|
265 |
-
|
266 |
-
|
267 |
-
|
268 |
-
|
269 |
"herd_animal": herd_animal,
|
270 |
-
"
|
271 |
-
"
|
272 |
-
"
|
273 |
-
"winner": winner
|
274 |
}
|
275 |
-
game_log_path = os.path.join(self.log_dir, self.game_log_file.format(game_id=self.game_id))
|
276 |
-
|
277 |
-
log(game_log, game_log_path)
|
|
|
109 |
if recipient.controller_type == "human":
|
110 |
print(message)
|
111 |
|
112 |
+
async def instructional_message(self, message: str, player: Player, output_format: Type[BaseModel]):
|
|
|
113 |
"""Sends a message to a specific player and gets their response."""
|
114 |
if player.controller_type == "human":
|
115 |
+
self.human_message(message)
|
116 |
response = await player.respond_to(message, output_format)
|
117 |
return response
|
118 |
|
119 |
# The following methods are used to broadcast messages to a human.
|
120 |
+
# They are design so that they can be overridden by a subclass for a different player interface.
|
121 |
+
@staticmethod
|
122 |
+
def human_message(self, message: str):
|
123 |
+
"""Sends a message for the human player to read. No response is expected."""
|
124 |
+
print(message)
|
125 |
+
|
126 |
def verbose_message(self, message: str):
|
127 |
"""Sends a message for the human player to read. No response is expected."""
|
128 |
if self.verbose:
|
|
|
140 |
|
141 |
await self.run_round()
|
142 |
|
143 |
+
# Log Game Info
|
144 |
+
game_log = {
|
145 |
"game_id": self.game_id,
|
146 |
+
"start_time": self.start_time,
|
147 |
+
"number_of_players": len(self.players),
|
148 |
+
"human_player": self.players[self.human_index].id if self.human_index else "None",
|
149 |
}
|
150 |
+
game_log_path = os.path.join(self.log_dir, self.game_log_file.format(game_id=self.game_id))
|
151 |
+
|
152 |
+
log(game_log, game_log_path)
|
153 |
|
154 |
|
155 |
|
|
|
159 |
# Phase I: Choose Animal and Assign Roles
|
160 |
|
161 |
herd_animal = random_animal()
|
162 |
+
self.debug_message(f"The secret animal is {herd_animal}.")
|
163 |
|
164 |
chameleon_index = random_index(len(self.players))
|
165 |
chameleon = self.players[chameleon_index]
|
|
|
193 |
|
194 |
self.game_message(f"{current_player.name}: {response.description}", current_player, exclude=True)
|
195 |
|
196 |
+
# Phase III: Chameleon Guesses the Animal
|
197 |
|
198 |
+
self.game_message("All players have spoken. The Chameleon will now guess the secret animal...")
|
199 |
if self.human_index != self.chameleon_index:
|
200 |
self.verbose_message("The Chameleon is thinking...")
|
201 |
|
202 |
+
prompt = fetch_prompt("chameleon_guess_animal")
|
|
|
203 |
|
204 |
+
response = await self.instructional_message(prompt, chameleon, ChameleonGuessAnimalModel)
|
|
|
|
|
|
|
205 |
|
206 |
+
chameleon_animal_guess = response.animal
|
207 |
|
208 |
+
# Phase IV: The Herd Votes for who they think the Chameleon is
|
209 |
+
self.game_message("The Chameleon has guessed the animal. Now the Herd will vote on who they think the chameleon is.")
|
|
|
210 |
|
211 |
+
self.game_message("The Chameleon has decided not to guess the animal. Now all players will vote on who they think the chameleon is.")
|
212 |
|
213 |
+
player_votes = []
|
214 |
+
for player in self.players:
|
215 |
+
if player.role == "herd":
|
216 |
+
if player.is_ai():
|
217 |
+
self.verbose_message(f"{player.name} is thinking...")
|
218 |
|
219 |
+
prompt = format_prompt("vote", player_responses=self.format_responses(exclude=player.name))
|
220 |
|
221 |
+
# Get Player Vote
|
222 |
+
response = await self.instructional_message(prompt, player, VoteModel)
|
|
|
|
|
|
|
|
|
223 |
|
224 |
+
# check if a valid player was voted for...
|
|
|
|
|
225 |
|
226 |
+
# Add Vote to Player Votes
|
227 |
+
player_votes.append({"voter": player, "vote": response.vote})
|
228 |
+
if player.is_ai():
|
229 |
+
self.debug_message(f"{player.name} voted for {response.vote}")
|
|
|
230 |
|
|
|
231 |
|
232 |
+
self.game_message("All players have voted!")
|
233 |
+
formatted_votes = '\n'.join([f'{vote["voter"].name}: {vote["vote"]}' for vote in player_votes])
|
234 |
+
self.game_message(f"Votes:\n{formatted_votes}")
|
235 |
|
236 |
+
# Count Votes
|
237 |
+
accused_player = count_chameleon_votes(player_votes)
|
238 |
|
239 |
+
# Phase V: Assign Points
|
|
|
|
|
|
|
240 |
|
241 |
+
self.game_message(f"The round is over. Caclulating results...")
|
242 |
+
self.game_message(
|
243 |
+
f"The Chameleon was {chameleon.name}, and they guessed the secret animal was {chameleon_animal_guess}.")
|
244 |
+
self.game_message(f"The secret animal was actually was {herd_animal}.")
|
245 |
|
246 |
+
if accused_player:
|
247 |
+
self.game_message(f"The Herd voted for {accused_player} as the Chameleon.")
|
248 |
+
else:
|
249 |
+
self.game_message(f"The Herd could not come to a consensus.")
|
250 |
|
251 |
+
# Point Logic
|
252 |
+
# If the Chameleon guesses the correct animal = +1 Point to the Chameleon
|
253 |
+
if chameleon_animal_guess.lower() == herd_animal.lower():
|
254 |
+
chameleon.points += 1
|
255 |
+
# If the Chameleon guesses the incorrect animal = +1 Point to each Herd player
|
256 |
+
else:
|
257 |
+
for player in self.players:
|
258 |
+
if player.role == "herd":
|
259 |
+
player.points += 1
|
260 |
+
# If a Herd player votes for the Chameleon = +1 Point to that player
|
261 |
+
for vote in player_votes:
|
262 |
+
if vote["vote"] == chameleon.name:
|
263 |
+
vote['voter'].points += 1
|
264 |
|
265 |
+
# If the Herd fails to accuse the Chameleon = +1 Point to the Chameleon
|
266 |
+
if not accused_player or accused_player != chameleon.name:
|
267 |
+
chameleon.points += 1
|
268 |
|
269 |
+
# Check for a Winner
|
270 |
+
player_points = "\n".join([f"{player.name}: {player.points}" for player in self.players])
|
|
|
|
|
271 |
|
272 |
+
self.game_message(f"Current Game Score: {player_points}")
|
273 |
+
|
274 |
+
# Log Round Info
|
275 |
+
round_log = {
|
276 |
"herd_animal": herd_animal,
|
277 |
+
"chameleon_name": self.players[self.chameleon_index].name,
|
278 |
+
"chameleon_guess": chameleon_animal_guess,
|
279 |
+
"herd_votes": player_votes,
|
|
|
280 |
}
|
|
|
|
|
|
src/game_utils.py
CHANGED
@@ -35,9 +35,11 @@ def random_index(number_of_players : int) -> int:
|
|
35 |
return random.randint(0, number_of_players - 1)
|
36 |
|
37 |
|
38 |
-
def count_chameleon_votes(player_votes: list[
|
39 |
"""Counts the votes for each player."""
|
40 |
-
|
|
|
|
|
41 |
most_voted_player, number_of_votes = freq.most_common()[0]
|
42 |
|
43 |
# If one player has more than 50% of the votes, the herd accuses them of being the chameleon
|
|
|
35 |
return random.randint(0, number_of_players - 1)
|
36 |
|
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]
|
44 |
|
45 |
# If one player has more than 50% of the votes, the herd accuses them of being the chameleon
|
src/player.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1 |
import os
|
2 |
from typing import Type, Literal
|
|
|
3 |
|
4 |
from langchain_core.runnables import Runnable, RunnableParallel, RunnableLambda, chain
|
5 |
|
@@ -16,6 +17,7 @@ from controllers import controller_from_name
|
|
16 |
|
17 |
Role = Literal["chameleon", "herd"]
|
18 |
|
|
|
19 |
|
20 |
class Player:
|
21 |
|
@@ -87,13 +89,15 @@ class Player:
|
|
87 |
retries = 0
|
88 |
try:
|
89 |
output = await self.format_output.ainvoke({"output_format": output_format})
|
90 |
-
except
|
91 |
-
if 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 |
-
|
95 |
else:
|
96 |
-
|
97 |
raise e
|
98 |
else:
|
99 |
# Convert the human message to the pydantic object format
|
|
|
1 |
import os
|
2 |
from typing import Type, Literal
|
3 |
+
import logging
|
4 |
|
5 |
from langchain_core.runnables import Runnable, RunnableParallel, RunnableLambda, chain
|
6 |
|
|
|
17 |
|
18 |
Role = Literal["chameleon", "herd"]
|
19 |
|
20 |
+
logging.basicConfig(level=logging.WARNING)
|
21 |
|
22 |
class Player:
|
23 |
|
|
|
89 |
retries = 0
|
90 |
try:
|
91 |
output = await self.format_output.ainvoke({"output_format": output_format})
|
92 |
+
except OutputParserException as e:
|
93 |
+
if retries < max_retries:
|
94 |
+
retries += 1
|
95 |
+
logging.warning(f"Player {self.id} failed to format response: {output} due to an exception: {e} \n\n Retrying {retries}/{max_retries}")
|
96 |
self.add_to_history(HumanMessage(content=f"Error formatting response: {e} \n\n Please try again."))
|
97 |
output = await self.format_output.ainvoke({"output_format": output_format})
|
98 |
+
|
99 |
else:
|
100 |
+
logging.error(f"Max retries reached due to Error: {e}")
|
101 |
raise e
|
102 |
else:
|
103 |
# Convert the human message to the pydantic object format
|