diff --git a/examples/werewolf_game/actions/__init__.py b/examples/werewolf_game/actions/__init__.py index 7c5af46df..5568f50f8 100644 --- a/examples/werewolf_game/actions/__init__.py +++ b/examples/werewolf_game/actions/__init__.py @@ -1,8 +1,10 @@ from examples.werewolf_game.actions.moderator_actions import InstructSpeak from examples.werewolf_game.actions.common_actions import Speak from examples.werewolf_game.actions.werewolf_actions import Hunt +from examples.werewolf_game.actions.seer_actions import Verify ACTIONS = { "Speak": Speak, "Hunt": Hunt, + "Verify": Verify, } \ No newline at end of file diff --git a/examples/werewolf_game/actions/moderator_actions.py b/examples/werewolf_game/actions/moderator_actions.py index 7850ab478..6a79e307d 100644 --- a/examples/werewolf_game/actions/moderator_actions.py +++ b/examples/werewolf_game/actions/moderator_actions.py @@ -1,49 +1,267 @@ +import asyncio +import collections +from random import random + from metagpt.actions import Action -STAGE_INSTRUCTIONS = { +STEP_INSTRUCTIONS = { # 上帝需要介入的全部步骤和对应指令 # The 1-st night 0: {"content": "It’s dark, everyone close your eyes. I will talk with you/your team secretly at night.", - "send_to": "Moderator", # for moderator to continuen speaking + "send_to": "Moderator", # for moderator to continuen speaking "restricted_to": ""}, - 1: {"content": "Werewolves, please open your eyes!", - "send_to": "Moderator", # for moderator to continuen speaking + 1: {"content": "Guard, please open your eyes!", + "send_to": "Moderator", # for moderator to continuen speaking "restricted_to": ""}, - 2: {"content": """Werewolves, I secretly tell you that Player 3 and Player 4 are + 2: {"content": """Guard, now tell me who you protect tonight? + You only choose one from the following living options please: {living_players}. Or you can pass. For example: I protect ...""", + "send_to": "Guard", + "restricted_to": "Guard"}, + 3: {"content": "Guard, close your eyes", + "send_to": "Moderator", + "restricted_to": ""}, + 4: {"content": "Werewolves, please open your eyes!", + "send_to": "Moderator", + "restricted_to": ""}, + 5: {"content": """Werewolves, I secretly tell you that {werewolf_players} are all of the 2 werewolves! Keep in mind you are teammates. The rest players are not werewolves. choose one from the following living options please: - [Player 1, Player2]. """, # send to werewolf restrictedly for a response + {living_players}. For example: I kill ...""", "send_to": "Werewolf", "restricted_to": "Werewolf"}, - 3: {"content": "Werewolves, close your eyes", - "send_to": "Moderator", # for moderator to continuen speaking + 6: {"content": "Werewolves, close your eyes", + "send_to": "Moderator", "restricted_to": ""}, - 4: {"content": """It's daytime. No one dies last night. Now freely talk about roles of other players with each other based on your observation and reflection - with few sentences. Decide whether to reveal your identity based on your reflection.""", - "send_to": "", # send to all to speak in daytime - "restricted_to": ""} + 7: {"content": "Witch, please open your eyes!", + "send_to": "Moderator", + "restricted_to": ""}, + 8: {"content": """Witch, tonight {killed_player} has been killed by the werewolves. + You have a bottle of antidote, would you like to save him/her? If not, simply Pass.""", + "send_to": "Witch", + "restricted_to": "Witch"}, # 要先判断女巫是否有解药,再去询问女巫是否使用解药救人 + 9: {"content": """Witch, you also have a bottle of poison, would you like to use it to kill one of the living players? + Choose one from the following living options: {living_players}. If not, simply Pass.""", + "send_to": "Witch", + "restricted_to": "Witch"}, # + 10: {"content": "Witch, close your eyes", + "send_to": "Moderator", + "restricted_to": ""}, + 11: {"content": "Seer, please open your eyes!", + "send_to": "Moderator", + "restricted_to": ""}, + 12: {"content": """Seer, you can check one player's identity. Who are you going to verify its identity tonight? + Choose only one from the following living options:{living_players}.""", + "send_to": "Seer", + "restricted_to": "Seer"}, + 13: {"content": "Seer, close your eyes", + "send_to": "Moderator", + "restricted_to": ""}, + # The 1-st daytime + 14: {"content": """It's daytime. Everyone woke up except those who had been killed.""", + "send_to": "Moderator", + "restricted_to": ""}, + 15: {"content": "{killed_player} was killed last night. Or, it was a peaceful night and no one died!", + "send_to": "Moderator", + "restricted_to": ""}, + 16: {"content": """Now freely talk about roles of other players with each other based on your observation and + reflection with few sentences. Decide whether to reveal your identity based on your reflection.""", + "send_to": "", # send to all to speak in daytime + "restricted_to": ""}, + 17: {"content": """Now vote and tell me who you think is the werewolf. Don’t mention your role. + You only choose one from the following living options please: + {living_players}. Or you can pass. For example: I vote to kill ...""", + "send_to": "", + "restricted_to": ""}, + 18: {"content": """{voted_out_player} was eliminated.""", + "send_to": "Moderator", + "restricted_to": ""}, } +ROLE_STATES = { + # 存活状态 + 0: "Alive", # 开场 + 1: "Dead", # 结束 + 2: "Protected", # 被保护 + 3: "Poisoned", # 被毒 + 4: "Saved", # 被救 + 5: "Killed" # 被刀 +} + +VOTE_PROMPT = """ + Welcome to the daytime discussion phase in the Werewolf game. + + During the day, players discuss and share information about who they suspect might be a werewolf. + Players can also cast their votes to eliminate a player they believe is a werewolf. + + Here are the conversations from the daytime: + + {vote_message} + + Now it's time to cast your votes. + + You can vote for a player by typing their name. + Example: "Vote for Player2" + + Here are the voting options: +""" + +PARSE_INSTRUCTIONS = { + 0: "Now it's time to vote", + 1: "The {winner} have won! They successfully eliminated all the {loser}." + "The game has ended. Thank you for playing Werewolf!", + 2: "The night has ended, and it's time to reveal the casualties." + "During the night, the Werewolves made their move. Unfortunately, they targeted {PlayerName}, who is now dead." +} + + class InstructSpeak(Action): def __init__(self, name="InstructSpeak", context=None, llm=None): super().__init__(name, context, llm) - async def run(self, context, stage_idx): - return STAGE_INSTRUCTIONS[stage_idx] + async def run(self, step_idx, living_players, werewolf_players, killed_player, voted_out_player): + instruction_info = STEP_INSTRUCTIONS.get(step_idx, { + "content": "Unknown instruction.", + "send_to": "", + "restricted_to": "" + }) + content = instruction_info["content"] + if "{living_players}" in content and "{werewolf_players}" in content: + content = content.format(living_players=",".join(living_players), + werewolf_players=",".join(werewolf_players)) + if "{living_players}" in content: + content = content.format(living_players=",".join(living_players)) + if "{werewolf_players}" in content: + content = content.format(werewolf_players=",".join(werewolf_players)) + if "{killed_player}" in content: + content = content.format(killed_player=killed_player) + if "{voted_out_player}" in content: + content = content.format(voted_out_player=voted_out_player) + + return content, instruction_info["send_to"], instruction_info["restricted_to"] + class ParseSpeak(Action): - async def run(self): - return "" + def __init__(self, name="ParseSpeak", context=None, llm=None): + super().__init__(name, context, llm) + self.daytime_info = collections.defaultdict(list) + self.night_info = collections.defaultdict(list) + self.vote_message = [] + + async def run(self, dead_history, context, env): + + for m in env.memory.get(): + role = m.sent_from if hasattr(m, 'sent_from') else "" + content = m.content if hasattr(m, 'content') else "" + target = m.sent_to if hasattr(m, 'sent_to') else "" + restricted = m.restricted_to if hasattr(m, 'restricted_to') else "" + if target == 'all': + self.daytime_info[role] = [content, target, restricted] + else: + self.night_info[role] = [content, target, restricted] + + # collect info from the night and identify the dead player + for role in self.night_info: + if "kill" in self.night_info[role][0] and self.night_info[role][1]: + target = self.night_info[role][1] + print("env.get_roles[target]", env, env.env.roles) + env.env.roles[target].set_status(ROLE_STATES[5]) + for role in self.night_info: + if ("save" or "guard") in self.night_info[role][0]: + save_target = self.night_info[role][1] + if save_target == target: + env.env.roles[target].set_status(ROLE_STATES[0]) + else: + dead_history.append(target) + + # collect message from the daytime and identify the vote player + for role in self.daytime_info: + self.vote_message += f"\n{self.daytime_info[role][0]}" + + vote_player = await self.llm.aask(VOTE_PROMPT.format(vote_message=self.vote_message)) + dead_history.append(vote_player) + + return dead_history, vote_player, PARSE_INSTRUCTIONS + class SummarizeNight(Action): """consider all events at night, conclude which player dies (can be a peaceful night)""" - pass + + def __init__(self, name="SummarizeNight", context=None, llm=None): + super().__init__(name, context, llm) + + async def run(self, events): + # 假设events是一个字典,代表夜晚发生的多个事件,key是事件类型,value是该事件对应的玩家 + # 例如被狼人杀的玩家:{"killed_by_werewolves": "Player1"} + # 被守卫守护的玩家:{"protected_by_guard": "Player2"} + # 被女巫救的玩家:{"saved_by_witch": "Player3"} + # 被女巫毒的玩家:{"poisoned_by_witch": "Player4"} + # 被预言家查验的玩家:{"verified_by_seer": "Player5"} + # 若没有事件发生,则events为空字典 + killed_by_werewolves = events.get("killed_by_werewolves", "") + protected_by_guard = events.get("protected_by_guard", "") + saved_by_witch = events.get("saved_by_witch", "") + poisoned_by_witch = events.get("poisoned_by_witch", "") + + # 若狼人杀的人和守卫守的人是同一个人,那么该人就会活着; + if protected_by_guard and killed_by_werewolves and protected_by_guard == killed_by_werewolves: + return "It was a peaceful night. No one was killed." + + # 若守卫和女巫都救了同一个人,那么该人就会死 + if protected_by_guard and saved_by_witch and protected_by_guard == saved_by_witch: + return f"{protected_by_guard} was killed by the werewolves." + + if saved_by_witch: + return f"{saved_by_witch} was saved by the witch." + + if poisoned_by_witch: + return f"{poisoned_by_witch} was poisoned by the witch." + + if killed_by_werewolves: + return f"{killed_by_werewolves} was killed by the werewolves." + class SummarizeDay(Action): """consider all votes at day, conclude which player dies""" - pass + + def __init__(self, name="SummarizeDay", context=None, llm=None): + super().__init__(name, context, llm) + + async def run(self, votes): + # 假设votes是一个字典,代表白天投票的结果,key是被投票的玩家,value是得票数 + # 例如:{"Player1": 2, "Player2": 1, "Player3": 1, "Player4": 0} + # 表示Player1得到2票,Player2和Player3各得到1票,Player4得到0票 + # 若平票,则随机选一个人出局 + if not votes: + return "No votes were cast. No one was killed." + + max_votes = max(votes.values()) + players_with_max_votes = [player for player, vote_count in votes.items() if vote_count == max_votes] + + if len(players_with_max_votes) == 1: + eliminated_player = players_with_max_votes[0] + return f"{eliminated_player} was voted out and eliminated." + else: + # 若平票,则随机选一个人出局 + eliminated_player = players_with_max_votes[int(random() * len(players_with_max_votes))] + return f"There was a tie in the votes. {eliminated_player} was randomly chosen and eliminated." + class AnnounceGameResult(Action): async def run(self, winner: str): return f"Game over! The winner is {winner}" + + +async def main(): + rst1 = await SummarizeDay().run({"Player1": 0, "Player2": 0, "Player3": 0, "Player4": 0}) + rst2 = await SummarizeNight().run({"killed_by_werewolves": "Player1"}) + # 表示第2个步骤的指令,living_players是所有活着的玩家,werewolf_players是所有狼人,killed_player是被杀的玩家,voted_out_player是被投票出局的玩家 + rst3 = await InstructSpeak().run(2, ["Player1", "Player2", "Player3", "Player4"], + ["Player3", "Player4"], "Player4", "Player3") + print(rst1) + print(rst2) + print(rst3) + + +if __name__ == '__main__': + asyncio.run(main()) diff --git a/examples/werewolf_game/actions/seer_actions.py b/examples/werewolf_game/actions/seer_actions.py new file mode 100644 index 000000000..b4cb55b15 --- /dev/null +++ b/examples/werewolf_game/actions/seer_actions.py @@ -0,0 +1,22 @@ +from metagpt.actions import Action + + +class Verify(Action): + """Action: Seer verifies a player's identity at night""" + + PROMPT_TEMPLATE = """ + It's a werewolf game and you are a seer. + You can choose to verify the identity of a player. + Here's the game history: + {context}. + Now, choose one player to verify: + """ + + def __init__(self, name="Verify", context=None, llm=None): + super().__init__(name, context, llm) + + async def run(self, context: str): + prompt = self.PROMPT_TEMPLATE.format(context=context) + rsp = await self._aask(prompt) + + return rsp diff --git a/examples/werewolf_game/prompts/prompts.py b/examples/werewolf_game/prompts/prompts.py new file mode 100644 index 000000000..5f618798d --- /dev/null +++ b/examples/werewolf_game/prompts/prompts.py @@ -0,0 +1,85 @@ +# 论文中出现的提示语,利用了思维链 +# 1. 基于游戏规则和对话内容,选择5个问题 +# 2. 生成2个问题 +# 3. 生成可能的回答 +# 4. 进行反思 +# 5. 生成最终回复 + +GAME_RULE = '''You are playing a game called the Werewolf with some other players. This game is based on text conversations. Here are +the game rules: Roles: The moderator is also host, he organised this game and you need to answer his instructions correctly. +Don’t talk with the moderator. There are five roles in the game, werewolf, villager, seer, guard and witch. There are two +alternate phases in this game, daytime and dark. When it’s dark: Your talking content with moderator is confidential. You +needn’t worry about other players and moderator knowing what you say and do. No need to worry about suspicions from +others during the night. If you are werewolf, you can know what your teammates want to kill and you should vote one player +to kill based on your analysis. Player who receives the most votes after all werewolves voting will be killed. No one will be +killed if there is no consensus! If you are witch, you have a bottle of antidote that can save a player targeted by werewolves +after dark, and a bottle of poison that can poison a player after dark. Both poison and antidote can be used only once. If you +are seer, you can verify whether a player is a werewolf every night, which is a very important thing. If you are guard, you +can protect a player every night to prevent the player from being killed by werewolves, but guard cannot resist the witch’s +poison and guard cannot protect the same player on two consecutive nights. Villagers can’t do anything at night. During the +daytime: you discuss with all players including your enemies. At the end of the discussion, players vote to eliminate one +player they suspect of being a werewolf. The player with the most votes will be eliminated. The moderator will tell who is +killed, otherwise there is no one killed. Note that villager, seer, guard and witch are all in villager side, they have the same +objective. Objectives: If you are werewolf, your goal is to cooperate with other werewolves to kill all players who are not +werewolves at last. If you are not werewolf, you need to kill all werewolves with your partner once you find out that certain +players are suspicious to be werewolves. This could greatly improve your chances of winning, although it is somewhat risky.If +one player is killed, he can’t do anything anymore and will be out of the game. Tips: To complete the objective: During +night, you should analyze and use your ability correctly. During daytime, you need to reason carefully about the roles of other +players and be careful not to reveal your own role casually unless you’re cheating other players. Only give the player’s name +when making a decision/voting, and don’t generate other players’ conversation.Reasoning based on facts you have observed +and you cannot perceive information (such as acoustic info) other than text. You are Player {name}, the {profile}. +You’re playing with 6 other players. Do not pretend you are other players or the moderator. +''' + +SELECT_QUESTIONS = ''' +Now its the {t}-th {day_or_night}. Given the game rules and conversations above, assuming you are {agent_name}, the +{role}, and to complete the instructions of the moderator, you need to think about a few questions clearly first, so that you can +make an accurate decision on the next step. Choose only five that you think are the most important in the current situation +from the list of questions below: {questions_prepared_for_specific_role} Please repeat the five important questions of your +choice, separating them with ‘##’. +''' + +# 为特定的角色,准备的问题 +questions_prepared_for_specific_role_sample = ''' +1. What is my player name and what is my role? What is my final objective in this game? +2. Based on the chat history, can you guess what some players’ role might be? +3. What is the current phase, daytime or night? what should I do at this phase according to the game rules? +4. Based on the conversation and my inference, who is most likely to be an alive werewolf? +5. I want to know who the most suspicious player, and why? +6. I also want to know if any player’s behavior has changed suspiciously compared to the previous days, and if so, who and why? +7. What is the best strategy I should use right now to uncover werewolves without revealing my own role? Should I accuse someone directly, ask probing questions, or stay silent for now? +8. Have any players claimed specific roles that can be verified or disputed? +''' + +ASK_QUESTIONS = ''' +Now its the {t}-th {day_or_night}. Given the game rules and conversations above, assuming you are {agent_name}, the +{role}, and to complete the instructions of the moderator, you need to think about a few questions clearly first, so that you can +make an accurate decision on the next step. {selected_questions} Do not answer these queations. In addition to the above +questions, please make a bold guess, what else do you want to know about the current situation? Please ask two important +questions in first person, separating them with ‘##’. +''' + +GENERATE_POSSIBLE_ANSWER = ''' +Now its the {t}-th {day_or_night}. Given the game rules and conversations above, assuming you are {agent_name}, the +{role}, for question: {question} There are some possible answers: {candidate_answers} Generate the correct answer +based on the context. If there is not direct answer, you should think and generate the answer based on the context. No need to +give options. The answer should in first person using no more than 2 sentences and without any analysis and item numbers. +''' + +REFLECTION = ''' +Now its the {t}-th {day_or_night}. Assuming you are {agent_name}, the {role}, what insights can you summarize +with few sentences based on the above conversations and {At} in heart for helping continue the talking and achieving your +objective? For example: As the {role}, I observed that... I think that... But I am... So... +''' + +# 得到最终的回复,再抽取出最终的content +GENERATE_FINAL_RESPONSE = ''' +Now its the {t}-th {day_or_night}. Think about what to say based on the game rules and context, especially the just now +reflection {R}. +Give your step-by-step thought process and your derived consise talking content (no more than 2 sentences) at last, separating them with ‘##’. +For example: +## Thought process +My step-by-step thought process:... +## Content +My concise talking content: ... +''' diff --git a/examples/werewolf_game/roles/__init__.py b/examples/werewolf_game/roles/__init__.py index 464563344..ac79f07d7 100644 --- a/examples/werewolf_game/roles/__init__.py +++ b/examples/werewolf_game/roles/__init__.py @@ -2,3 +2,4 @@ from examples.werewolf_game.roles.base_player import BasePlayer from examples.werewolf_game.roles.moderator import Moderator from examples.werewolf_game.roles.villager import Villager from examples.werewolf_game.roles.werewolf import Werewolf +from examples.werewolf_game.roles.seer import Seer diff --git a/examples/werewolf_game/roles/base_player.py b/examples/werewolf_game/roles/base_player.py index 05daa9797..c3e60372c 100644 --- a/examples/werewolf_game/roles/base_player.py +++ b/examples/werewolf_game/roles/base_player.py @@ -3,14 +3,6 @@ from metagpt.schema import Message from metagpt.logs import logger from examples.werewolf_game.actions import ACTIONS, Speak, InstructSpeak -ROLE_STATES = { - # 存活状态 - 0: "Alive", # 开场 - 1: "Dead", # 结束 - 2: "Protected", # 被保护 - 3: "Poisoned", # 被毒 - 4: "Saved", # 被救 -} class BasePlayer(Role): def __init__( @@ -24,7 +16,7 @@ class BasePlayer(Role): super().__init__(name, profile, **kwargs) self._init_actions([Speak]) self._watch([InstructSpeak]) - self.team = team + self.team = team # 调用 get_status() 来检查存活状态,并通过 set_status() 更新状态。 self.status = 0 # 初始状态为活着 diff --git a/examples/werewolf_game/roles/moderator.py b/examples/werewolf_game/roles/moderator.py index e5a61e6c1..49380919e 100644 --- a/examples/werewolf_game/roles/moderator.py +++ b/examples/werewolf_game/roles/moderator.py @@ -4,50 +4,66 @@ from metagpt.roles import Role from metagpt.schema import Message from metagpt.logs import logger from examples.werewolf_game.actions.moderator_actions import ( - InstructSpeak, ParseSpeak, AnnounceGameResult, STAGE_INSTRUCTIONS + InstructSpeak, ParseSpeak, AnnounceGameResult, STEP_INSTRUCTIONS ) from metagpt.actions import BossRequirement as UserRequirement -class Moderator(Role): +class Moderator(Role): # 游戏状态属性 is_game_over = False winner = None def __init__( - self, - name: str = "Moderator", - profile: str = "Moderator", - **kwargs, + self, + name: str = "Moderator", + profile: str = "Moderator", + **kwargs, ): super().__init__(name, profile, **kwargs) self._watch([UserRequirement, InstructSpeak, ParseSpeak]) self._init_actions([InstructSpeak, ParseSpeak, AnnounceGameResult]) - self.stage_idx = 0 - + self.step_idx = 0 + self.living_players = ["Player1", "Player2", "Player3", "Player4", "Player5"] + self.werewolf_players = ["Player1", "Player2"] + self.good_guys = ["Player3", "Player4", "Player5"] + self.dead_players = [] # 夜晚阶段,死掉的玩家 + # 假设votes代表白天投票的结果,key是被投票的玩家,value是得票数 + self.votes = {"Player1": 1, "Player2": 2, "Player3": 1, "Player4": 0, "Player5": 0} + async def _instruct_speak(self): - stage_idx = self.stage_idx % len(STAGE_INSTRUCTIONS) + step_idx = self.step_idx % len(STEP_INSTRUCTIONS) + self.step_idx += 1 + return await InstructSpeak().run(step_idx, + living_players=self.living_players, + werewolf_players=self.werewolf_players, + killed_player=self.dead_players, + voted_out_player="Player3") - stage_info = await InstructSpeak().run(context="", stage_idx=stage_idx) + async def _parse_speak(self, memories, env): - self.stage_idx += 1 + self.dead_players, vote_player, parse_info = await ParseSpeak().run(dead_history=self.dead_players, + context=memories, env=env) - return stage_info["content"], stage_info["send_to"], stage_info["restricted_to"] - - async def _parse_speak(self): - # 解析玩家消息并返回结果 - parse_result = await ParseSpeak().run() - - # 理解结果,更新各角色状态、游戏状态 - - return "Player message processed" + # decide to move the game into the next phase + if not vote_player: + msg_content, send_to = parse_info[0], self.profile + # game's termination condition + elif all(item in self.dead_players for item in self.werewolf_players) or all( + item in self.dead_players for item in self.good_guys): + self.is_game_over = True + msg_content, send_to = parse_info[1], "all" + else: + # game's termination condition + msg_content, send_to = parse_info[2], "" + return msg_content, send_to async def _think(self): if self.is_game_over: self._rc.todo = AnnounceGameResult() return - + # 确定当前是需要InstructSpeak还是ParseSpeak. 通过判断当前流程状态变量,以及消息的cause_by属性 # 0: InstructSpeak, 1: ParseSpeak,且需要判断消息的cause_by属性 if self._rc.memory.get()[-1].role in ["User", self.profile]: @@ -55,7 +71,7 @@ class Moderator(Role): # 2. 上一轮消息是Moderator自己的指令,继续发出指令,一个事情可以分几条消息来说 # 3. 上一轮消息是Moderator自己的解析消息,一个阶段结束,发出新一个阶段的指令 self._rc.todo = InstructSpeak() - + else: # 上一轮消息是游戏角色的发言,解析角色的发言 self._rc.todo = ParseSpeak() @@ -71,20 +87,21 @@ class Moderator(Role): if isinstance(todo, InstructSpeak): msg_content, msg_to_send_to, msg_restriced_to = await self._instruct_speak() msg = Message(content=msg_content, role=self.profile, sent_from=self.name, - cause_by=InstructSpeak, send_to=msg_to_send_to, restricted_to=msg_restriced_to) - + cause_by=InstructSpeak, send_to=msg_to_send_to, restricted_to=msg_restriced_to) + elif isinstance(todo, ParseSpeak): - msg_content = await self._parse_speak() - msg = Message(content=msg_content, role=self.profile, sent_from=self.name, cause_by=ParseSpeak) - + msg_content, send_to = await self._parse_speak(memories, self._rc) + msg = Message(content=msg_content, role=self.profile, sent_from=self.name, cause_by=ParseSpeak, + send_to=send_to) + elif isinstance(todo, AnnounceGameResult): msg_content = await AnnounceGameResult().run(winner=self.winner) msg = Message(content=msg_content, role=self.profile, sent_from=self.name, cause_by=AnnounceGameResult) - + logger.info(f"{self._setting}: {msg_content}") - + return msg - + def get_all_memories(self) -> str: memories = self._rc.memory.get() memories = [str(m) for m in memories] diff --git a/examples/werewolf_game/roles/seer.py b/examples/werewolf_game/roles/seer.py new file mode 100644 index 000000000..b4ff41c8c --- /dev/null +++ b/examples/werewolf_game/roles/seer.py @@ -0,0 +1,44 @@ +from examples.werewolf_game.actions.seer_actions import Verify +from examples.werewolf_game.roles.base_player import BasePlayer +from examples.werewolf_game.actions import Speak +from metagpt.schema import Message +from metagpt.logs import logger + + +class Seer(BasePlayer): + def __init__( + self, + name: str = "", + profile: str = "Seer", + team: str = "good guys", + special_action_names: list[str] = ["Verify"], + **kwargs, + ): + super().__init__(name, profile, team, special_action_names, **kwargs) + + async def _act(self): + todo = self._rc.todo + logger.info(f"{self._setting}: ready to {str(todo)}") + + memories = self.get_all_memories() + print("*" * 10, f"{self._setting}'s current memories: {memories}", "*" * 10) + + # 基于todo的类型,调用不同的action + if isinstance(todo, Speak): + rsp = await todo.run(profile=self.profile, context=memories) + msg = Message( + content=rsp, role=self.profile, sent_from=self.name, + cause_by=Speak, send_to="", restricted_to="", + ) + + elif isinstance(todo, Verify): + rsp = await todo.run(context=memories) + msg = Message( + content=rsp, role=self.profile, sent_from=self.name, + cause_by=Verify, send_to="", + restricted_to="Moderator", + ) + + logger.info(f"{self._setting}: {rsp}") + + return msg diff --git a/examples/werewolf_game/start_game.py b/examples/werewolf_game/start_game.py index 98d76aa9d..b272dc13b 100644 --- a/examples/werewolf_game/start_game.py +++ b/examples/werewolf_game/start_game.py @@ -3,14 +3,15 @@ import platform import fire from examples.werewolf_game.werewolf_game import WerewolfGame -from examples.werewolf_game.roles import Moderator, Villager, Werewolf +from examples.werewolf_game.roles import Moderator, Villager, Werewolf, Seer DEFAULT_PLAYER_SETUP = """ Game setup: Player1: Villager, Player2: Villager, Player3: Werewolf, -Player4: Werewolf. +Player4: Werewolf, +Player5: Seer. """ async def start_game(idea: str = DEFAULT_PLAYER_SETUP, investment: float = 3.0, n_round: int = 5): @@ -21,6 +22,7 @@ async def start_game(idea: str = DEFAULT_PLAYER_SETUP, investment: float = 3.0, Villager(name="Player2"), Werewolf(name="Player3"), Werewolf(name="Player4"), + Seer(name="Player5"), ]) game.invest(investment) game.start_project(idea)