File size: 10,818 Bytes
0c31321
f596e58
46ba8c8
975158e
c2392fe
 
975158e
250cc97
5de0b8a
485a836
dfdde45
 
5401171
3ea5035
7877562
 
0c31321
dfdde45
dd5c856
5401171
0c31321
5401171
 
 
 
 
7877562
 
 
 
0c31321
172af0f
 
c2392fe
172af0f
5401171
 
172af0f
0c31321
 
758a706
c2392fe
3ea5035
 
1a8a579
3ea5035
 
 
172af0f
7877562
 
5401171
 
 
3ea5035
 
5de0b8a
3ea5035
dfdde45
 
3ea5035
 
dfdde45
3ea5035
 
dfdde45
3ea5035
dfdde45
0c31321
dfdde45
bee27cc
dfdde45
 
 
3ea5035
975158e
3ea5035
 
f596e58
5de0b8a
f596e58
 
 
 
 
 
 
 
 
 
3ea5035
dfdde45
 
 
 
c2392fe
46ba8c8
dfdde45
46ba8c8
 
 
 
dfdde45
 
46ba8c8
 
 
dfdde45
 
46ba8c8
dfdde45
c6c2b98
dfdde45
c2392fe
 
dfdde45
 
 
 
c2392fe
dfdde45
7877562
 
dfdde45
 
 
 
 
5de0b8a
975158e
7877562
c2392fe
5de0b8a
7877562
 
c6c2b98
 
7877562
c6c2b98
 
 
7877562
5401171
c6c2b98
 
7877562
 
 
 
 
 
ab29f8e
c6c2b98
5de0b8a
7877562
 
 
 
 
 
5401171
7877562
 
 
5401171
7877562
 
 
 
 
dfdde45
46ba8c8
5de0b8a
5401171
c2392fe
46ba8c8
dfdde45
250cc97
5de0b8a
46ba8c8
5de0b8a
46ba8c8
dd5c856
c6c2b98
dd5c856
c6c2b98
dfdde45
7877562
3ea5035
c6c2b98
c2392fe
dfdde45
250cc97
dd5c856
c6c2b98
7877562
c6c2b98
 
dd5c856
c6c2b98
 
 
dfdde45
c6c2b98
dd5c856
c6c2b98
c2392fe
c6c2b98
dfdde45
250cc97
 
c2392fe
c6c2b98
 
dfdde45
c6c2b98
3ea5035
975158e
c6c2b98
 
 
878e472
c6c2b98
 
878e472
c6c2b98
878e472
c6c2b98
 
 
 
3ea5035
c6c2b98
 
 
 
46ba8c8
c6c2b98
 
 
bee27cc
c6c2b98
 
 
 
 
 
 
 
 
 
975158e
c6c2b98
 
 
3ea5035
c6c2b98
 
3ea5035
c6c2b98
 
 
 
172af0f
dfdde45
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
import os
from datetime import datetime
from typing import Optional, Type

from colorama import Fore, Style

from game_utils import *
from output_formats import *
from player import Player
from prompts import fetch_prompt, format_prompt
from message import Message
from agent_interfaces import HumanAgentCLI, OpenAIAgentInterface

# Default Values
NUMBER_OF_PLAYERS = 6
WINNING_SCORE = 11


class Game:

    log_dir = os.path.join(os.pardir, "experiments")
    """The directory where the logs will be saved."""
    player_log_file_template = "{player_id}.jsonl"
    """Template for the name of the log file for each player."""
    game_log_file_template = "{game_id}-game.jsonl"
    """Template for the name of the log file for the game."""
    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."""

    def __init__(
            self,
            number_of_players: int = NUMBER_OF_PLAYERS,
            human_name: str = None,
            verbose: bool = False,
            debug: bool = False
    ):
        # Game ID
        self.game_id = game_id()
        self.start_time = datetime.now().strftime('%y%m%d-%H%M%S')

        # 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
        """If True, the game will display verbose messages to the player."""
        self.debug = debug
        """If True, the game will display debug messages to the player."""

        # Add Players
        self.players = []
        for i in range(0, number_of_players):
            player_id = f"{self.game_id}-{i + 1}"

            if self.human_index == i:
                name = human_name
                interface = HumanAgentCLI(player_id)
            else:
                name = ai_names.pop()
                interface = OpenAIAgentInterface(player_id)

            self.players.append(Player(name, player_id, interface))

        # Add Observer - an Agent who can see all the messages, but doesn't actually play
        if (self.verbose or self.debug) and not self.human_index:
            self.observer = HumanAgentCLI("{self.game_id}-observer")
        else:
            self.observer = None

        # 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 observer_message(self, message: Message):
        """Sends a message to the observer if there is one."""
        if self.observer:
            self.observer.add_message(message)

    def game_message(
            self, content: 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"""
        message = Message(type="info", content=content)

        if exclude or not recipient:
            for player in self.players:
                if player != recipient:
                    player.interface.add_message(message)
            self.observer_message(message)
        else:
            recipient.interface.add_message(message)

    def verbose_message(self, content: str):
        """Sends a message for the human player to read. No response is expected."""
        if self.verbose:
            message = Message(type="verbose", content=content)
            if self.human_index:
                self.players[self.human_index].interface.add_message(message)
            self.observer_message(message)

    def debug_message(self, content: str):
        """Sends a message for a human observer. These messages contain secret information about the players such as their role."""
        if self.debug:
            message = Message(type="debug", content=content)
            if self.human_index:
                self.players[self.human_index].interface.add_message(message)
            self.observer_message(message)


    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_template.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(fetch_prompt("assign_chameleon"), player)
                self.debug_message(f"{player.name} is the Chameleon!")
            else:
                player.assign_role("herd")
                self.game_message(format_prompt("assign_herd", herd_animal=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.interface.is_ai:
                self.verbose_message(f"{current_player.name} is thinking...")

            prompt = fetch_prompt("player_describe_animal")

            # Get Player Animal Description
            message = Message(type="prompt", content=prompt)
            response = current_player.interface.respond_to_formatted(message, OutputFormat(AnimalDescriptionFormat))

            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 chameleon.interface.is_ai or self.observer:
            self.verbose_message("The Chameleon is thinking...")

        prompt = fetch_prompt("chameleon_guess_animal")

        message = Message(type="prompt", content=prompt)
        response = chameleon.interface.respond_to_formatted(message, OutputFormat(ChameleonGuessFormat))

        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.")

        player_votes = []
        for player in self.players:
            if player.role == "herd":
                if player.interface.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
                message = Message(type="prompt", content=prompt)
                player_names = [p.name for p in self.players]
                response = player.interface.respond_to_formatted(message, OutputFormat(HerdVoteFormat, player_names))

                # Add Vote to Player Votes
                player_votes.append({"voter": player, "vote": response.vote})
                if player.interface.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": chameleon.name,
            "chameleon_guess": chameleon_animal_guess,
            "herd_votes": player_votes,
        }