diff --git a/examples/werewolf_game/actions/__init__.py b/examples/werewolf_game/actions/__init__.py index 16b9391b3..21a053980 100644 --- a/examples/werewolf_game/actions/__init__.py +++ b/examples/werewolf_game/actions/__init__.py @@ -1,5 +1,5 @@ from examples.werewolf_game.actions.moderator_actions import InstructSpeak -from examples.werewolf_game.actions.common_actions import Speak +from examples.werewolf_game.actions.common_actions import Speak, NighttimeWhispers from examples.werewolf_game.actions.werewolf_actions import Hunt, Impersonate from examples.werewolf_game.actions.guard_actions import Protect from examples.werewolf_game.actions.seer_actions import Verify diff --git a/examples/werewolf_game/actions/common_actions.py b/examples/werewolf_game/actions/common_actions.py index 84936ebde..42f2223ba 100644 --- a/examples/werewolf_game/actions/common_actions.py +++ b/examples/werewolf_game/actions/common_actions.py @@ -1,30 +1,174 @@ from metagpt.actions import Action +import json +from metagpt.const import WORKSPACE_ROOT + class Speak(Action): """Action: Any speak action in a game""" PROMPT_TEMPLATE = """ - ## BACKGROUND - It's a Werewolf game, you are {profile}, say whatever possible to increase your chance of win, - ## HISTORY - You have knowledge to the following conversation: - {context} - ## YOUR TURN - Please follow the moderator's latest instruction, FIGURE OUT if you need to speak your opinion or directly to vote, - 1. If the instruction is to speak, speak in 100 words; - 2. If the instruction is to vote, you MUST vote and ONLY say "I vote to eliminate PlayerX", where X is the player index, DO NOT include any other words. - Your will say: + { + "BACKGROUND": "It's a Werewolf game, you are __profile__, say whatever possible to increase your chance of win" + ,"HISTORY": "You have knowledge to the following conversation: __context__" + ,"ATTENTION": "You can NOT VOTE a player who is NOT ALIVE now!" + ,"STRATEGY": __strategy__ + ,"MODERATOR_INSTRUCTION": __latest_instruction__, + ,"RULE": "Please follow the moderator's latest instruction, figure out if you need to speak your opinion or directly to vote: + 1. If the instruction is to SPEAK, speak in 200 words. Remember the goal of your role and try to achieve it using your speech; + 2. If the instruction is to VOTE, you MUST vote and ONLY say 'I vote to eliminate PlayerX', replace PlayerX with the actual player name, DO NOT include any other words." + ,"OUTPUT_FORMAT": + { + "ROLE": "Your role, in this case, __profile__" + ,"PLAYER_NAME": "Your name, in this case, __name__" + ,"LIVING_PLAYERS": "List living players based on MODERATOR_INSTRUCTION. Return a LIST datatype." + ,"THOUGHTS": "Based on `MODERATOR_INSTRUCTION` and `RULE`, carefully think about what to say or vote so that your chance of win as __profile__ maximizes. Give your step-by-step thought process, you should think no more than 3 steps. For example: My step-by-step thought process:..." + ,"RESPONSE": "Based on `MODERATOR_INSTRUCTION`, `RULE`, and the 'THOUGHTS' you had, express your opinion or cast a vote." + } + } + """ + STRATEGY = """ + Decide whether to reveal your identity based on benefits vs. risks, provide useful information, and vote to eliminate the most suspicious. + If you have special abilities, pay attention to those who falsely claims your role, for they are probably werewolves. """ def __init__(self, name="Speak", context=None, llm=None): super().__init__(name, context, llm) - async def run(self, context: str, profile: str): + async def run(self, profile: str, name: str, context: str, latest_instruction: str): - prompt = self.PROMPT_TEMPLATE.format(context=context, profile=profile) + prompt = ( + self.PROMPT_TEMPLATE.replace("__context__", context).replace("__profile__", profile) + .replace("__name__", name).replace("__latest_instruction__", latest_instruction) + .replace("__strategy__", self.STRATEGY) + ) - rsp = await self._aask(prompt) + re_run = 2 + while re_run > 0: + rsp = await self._aask(prompt) + try: + rsp = rsp.replace("\n", " ") + rsp_json = json.loads(rsp) + break + except: + re_run -= 1 - return rsp + with open(WORKSPACE_ROOT / 'speak.txt', 'a') as f: + f.write(rsp) + return rsp_json['RESPONSE'] +class NighttimeWhispers(Action): + """ + + Action: nighttime whispers with thinking processes + + Usage Example: + + class Hunt(NighttimeWhispers): + def __init__(self, name="Hunt", context=None, llm=None): + super().__init__(name, context, llm) + + class Protect(NighttimeWhispers): + def __init__(self, name="Protect", context=None, llm=None): + super().__init__(name, context, llm) + + class Verify(NighttimeWhispers): + def __init__(self, name="Verify", context=None, llm=None): + super().__init__(name, context, llm) + + class Save(NighttimeWhispers): + def __init__(self, name="Save", context=None, llm=None): + super().__init__(name, context, llm) + + def _update_prompt_json(self, prompt_json: dict, profile: str, name: str, context: str, **kwargs): + del prompt_json['ACTION'] + del prompt_json['ATTENTION'] + prompt_json["OUTPUT_FORMAT"]["THOUGHTS"] = "It is night time. Return the thinking steps of your decision of whether to save the player JUST be killed at this night." + prompt_json["OUTPUT_FORMAT"]["RESPONSE"] = "Follow the Moderator's instruction, decide whether you want to save that person or not. Return SAVE or PASS." + return prompt_json + + class Poison(NighttimeWhispers): + def __init__(self, name="Poison", context=None, llm=None): + super().__init__(name, context, llm) + + def _update_prompt_json(self, prompt_json: dict, profile: str, name: str, context: str, **kwargs): + prompt_json["OUTPUT_FORMAT"]["RESPONSE"] += "Or if you want to PASS, return PASS." + return prompt_json + """ + + PROMPT_TEMPLATE = """ + { + "ROLE": "__profile__" + ,"ACTION": "Choose one living player to __action__." + ,"ATTENTION": "1. You can only __action__ a player who is alive this night! And you can not __action__ a player who is dead this night! 2. `HISTORY` is all the information you observed, DONT hallucinate other player actions!" + ,"STRATEGY": "__strategy__" + ,"BACKGROUND": "It's a werewolf game and you are a __profile__. Here's the game history: __context__." + ,"OUTPUT_FORMAT": + { + "ROLE": "Your role, in this case, __profile__" + ,"PLAYER_NAME": "Your name, in this case, __name__" + ,"LIVING_PLAYERS": "List the players who is alive based on moderator's latest instruction. Return a LIST datatype." + ,"THOUGHTS": "Choose one living player from `LIVING_PLAYERS` to __action__ this night. Return the reason why you choose to __action__ this player. If you observe nothing at first night, DONT imagine unexisting player actions! Give your step-by-step thought process, you should think no more than 3 steps. For example: My step-by-step thought process:..." + ,"RESPONSE": "As a __profile__, you should choose one living player from `LIVING_PLAYERS` to __action__ this night according to the THOUGHTS you have just now. Return the player name ONLY." + } + } + """ + STRATEGY = """ + Decide which player is most threatening to you or most needs your support, take your action correspondingly. + """ + + def __init__(self, name="NightTimeWhispers", context=None, llm=None): + super().__init__(name, context, llm) + + def _construct_prompt_json(self, role_profile: str, role_name: str, context: str, **kwargs): + prompt_template = self.PROMPT_TEMPLATE + + def replace_string(prompt_json: dict): + k: str + for k in prompt_json.keys(): + if isinstance(prompt_json[k], dict): + prompt_json[k] = replace_string(prompt_json[k]) + continue + prompt_json[k] = prompt_json[k].replace("__profile__", role_profile) + prompt_json[k] = prompt_json[k].replace("__name__", role_name) + prompt_json[k] = prompt_json[k].replace("__context__", context) + prompt_json[k] = prompt_json[k].replace("__action__", self.name) + prompt_json[k] = prompt_json[k].replace("__strategy__", self.STRATEGY) + + return prompt_json + + prompt_json: dict = json.loads(prompt_template) + + prompt_json = replace_string(prompt_json) + + prompt_json: dict = self._update_prompt_json(prompt_json, role_profile, role_name, context, **kwargs) + assert isinstance(prompt_json, dict) + + prompt: str = json.dumps(prompt_json, indent=4, separators=(',', ': '), ensure_ascii=False) + + return prompt + + def _update_prompt_json(self, prompt_json: dict, role_profile: str, role_name: str, context: str) -> dict: + # one can modify the prompt_json dictionary here + return prompt_json + + async def run(self, context: str, profile: str, name: str): + + final_prompt = self._construct_prompt_json( + role_profile=profile, role_name=name, context=context + ) + + re_run = 2 + while re_run > 0: + rsp_content = await self._aask(final_prompt) + try: + rsp_content = rsp_content.replace("\n", " ") + rsp = json.loads(rsp_content) + break + except: + re_run -= 1 + + with open(WORKSPACE_ROOT / f'{self.name}.txt', 'a') as f: + f.write(rsp_content) + + return f"{self.name} " + str(rsp["RESPONSE"]) diff --git a/examples/werewolf_game/actions/guard_actions.py b/examples/werewolf_game/actions/guard_actions.py index 5b98d7e0e..310d1b278 100644 --- a/examples/werewolf_game/actions/guard_actions.py +++ b/examples/werewolf_game/actions/guard_actions.py @@ -1,26 +1,7 @@ from metagpt.actions import Action +from examples.werewolf_game.actions import NighttimeWhispers -class Protect(Action): - """Action: choose a player to protect""" - - PROMPT_TEMPLATE = """ - It's a werewolf game and you are a guard, - you can choose to protect a player, including yourself, then the protected player will not be killed by the Werewolves this night. - this is game history: - {context}. - Attention: you can not protect the same player two nights in a row. - Format: "Protect PlayerX", where X is the player index. - Now, choose one to protect, you will: - """ +class Protect(NighttimeWhispers): def __init__(self, name="Protect", 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) - # rsp = "Protect Player 1" - - return rsp \ 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 fc897474e..638c3d658 100644 --- a/examples/werewolf_game/actions/moderator_actions.py +++ b/examples/werewolf_game/actions/moderator_actions.py @@ -42,7 +42,7 @@ STEP_INSTRUCTIONS = { "restricted_to": "Moderator,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 so, say "Poison PlayerX", where X is the player index, else, say "Pass".""", + If so, say ONLY "Poison PlayerX", replace PlayerX with the actual player name, else, say "Pass".""", "send_to": "Witch", "restricted_to": "Moderator,Witch"}, # 10: {"content": "Witch, close your eyes", @@ -65,13 +65,13 @@ STEP_INSTRUCTIONS = { 15: {"content": "{player_current_dead} was killed last night!", "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.""", + 16: {"content": """Living players: {living_players}, now freely talk about the current situation based on your observation and + reflection with a 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 ...""", + {living_players}. Say ONLY: I vote to eliminate ...""", "send_to": "", "restricted_to": ""}, 18: {"content": """{player_current_dead} was eliminated.""", @@ -91,12 +91,12 @@ class InstructSpeak(Action): }) 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)) + content = content.format(living_players=living_players, + werewolf_players=werewolf_players) if "{living_players}" in content: - content = content.format(living_players=",".join(living_players)) + content = content.format(living_players=living_players) if "{werewolf_players}" in content: - content = content.format(werewolf_players=",".join(werewolf_players)) + content = content.format(werewolf_players=werewolf_players) if "{player_hunted}" in content: content = content.format(player_hunted=player_hunted) if "{player_current_dead}" in content: @@ -140,8 +140,8 @@ class SummarizeDay(Action): class AnnounceGameResult(Action): - async def run(self, winner: str): - return f"Game over! The winner is the {winner}" + async def run(self, winner: str, win_reason: str): + return f"Game over! {win_reason}. The winner is the {winner}" async def main(): rst1 = await SummarizeDay().run({"Player1": 0, "Player2": 0, "Player3": 0, "Player4": 0}) diff --git a/examples/werewolf_game/actions/seer_actions.py b/examples/werewolf_game/actions/seer_actions.py index 4c54debe2..6318de85f 100644 --- a/examples/werewolf_game/actions/seer_actions.py +++ b/examples/werewolf_game/actions/seer_actions.py @@ -1,24 +1,6 @@ from metagpt.actions import Action +from examples.werewolf_game.actions import NighttimeWhispers - -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 - Format: "Verify PlayerX", where X is the player index. - You will: - """ - +class Verify(NighttimeWhispers): 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/actions/werewolf_actions.py b/examples/werewolf_game/actions/werewolf_actions.py index 410221cb0..24272c79e 100644 --- a/examples/werewolf_game/actions/werewolf_actions.py +++ b/examples/werewolf_game/actions/werewolf_actions.py @@ -1,46 +1,18 @@ from metagpt.actions import Action -from examples.werewolf_game.actions.common_actions import Speak +from examples.werewolf_game.actions.common_actions import Speak, NighttimeWhispers -class Hunt(Action): - """Action: choose a villager to kill""" - - PROMPT_TEMPLATE = """ - It's a werewolf game and you are a werewolf, - this is game history: - {context}. - Attention: if your previous werewolf has chosen, follow its choice. - Format: "Kill PlayerX", where X is the player index. - Now, choose one to kill, you will: - """ +class Hunt(NighttimeWhispers): def __init__(self, name="Hunt", 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) - # rsp = "Kill Player 1" - - return rsp - class Impersonate(Speak): """Action: werewolf impersonating a good guy in daytime speak""" - PROMPT_TEMPLATE = """ - ## BACKGROUND - It's a Werewolf game, you are {profile}, say whatever possible to increase your chance of win, - ## HISTORY - You have knowledge to the following conversation: - {context} - ## ATTENTION: Try continuously impersonating a role with special ability, such as a Seer or a Witch, in order to mislead - other players, make them trust you, and thus hiding your werewolf identity - ## YOUR TURN - Please follow the moderator's latest instruction, FIGURE OUT if you need to speak your opinion or directly to vote, - 1. If the instruction is to speak, speak in 100 words; - 2. If the instruction is to vote, you MUST vote and ONLY say "I vote to eliminate PlayerX", where X is the player index, DO NOT include any other words. - Your will say: + STRATEGY = """ + Try continuously impersonating a role with special ability, such as a Seer or a Witch, in order to mislead + other players, make them trust you, and thus hiding your werewolf identity. However, pay attention to what your werewolf partner said, + if your werewolf partner has claimed to be a Seer or Witch, DONT claim to be the same role. Remmber NOT to reveal your real identity as a werewolf! """ def __init__(self, name="Impersonate", context=None, llm=None): diff --git a/examples/werewolf_game/actions/witch_actions.py b/examples/werewolf_game/actions/witch_actions.py index dec39f466..af8032a42 100644 --- a/examples/werewolf_game/actions/witch_actions.py +++ b/examples/werewolf_game/actions/witch_actions.py @@ -1,45 +1,30 @@ from metagpt.actions import Action +from examples.werewolf_game.actions import NighttimeWhispers -class Save(Action): - """Action: choose a villager to Save""" - - PROMPT_TEMPLATE = """ - It's a werewolf game and you are a witch, - this is game history: - {context}. - Follow the Moderator's instruction, decide whether you want to save that person or not: - """ - +class Save(NighttimeWhispers): def __init__(self, name="Save", context=None, llm=None): super().__init__(name, context, llm) - async def run(self, context: str): + def _update_prompt_json(self, prompt_json: dict, profile: str, name: str, context: str, **kwargs): + del prompt_json['ACTION'] + del prompt_json['ATTENTION'] - prompt = self.PROMPT_TEMPLATE.format(context=context) + prompt_json["OUTPUT_FORMAT"]["THOUGHTS"] = "It is night time. Return the thinking steps of your decision of whether to save the player JUST be killed at this night." + prompt_json["OUTPUT_FORMAT"]["RESPONSE"] = "Follow the Moderator's instruction, decide whether you want to save that person or not. Return SAVE or PASS." - rsp = await self._aask(prompt) - # rsp = "Save Player 1" + return prompt_json - return rsp - -class Poison(Action): - """Action: choose a villager to Poison""" - - PROMPT_TEMPLATE = """ - It's a werewolf game and you are a witch, - this is game history: - {context}. - Follow the Moderator's instruction, decide whether you want to poison another person or not: +class Poison(NighttimeWhispers): + STRATEGY = """ + Only poison a player if you are confident he/she is a werewolf. Don't poison a player randomly or at first night. + If someone claims to be the witch, poison him/her, because you are the only witch, he/she can only be a werewolf. """ def __init__(self, name="Poison", context=None, llm=None): super().__init__(name, context, llm) - async def run(self, context: str): + def _update_prompt_json(self, prompt_json: dict, profile: str, name: str, context: str, **kwargs): - prompt = self.PROMPT_TEMPLATE.format(context=context) + prompt_json["OUTPUT_FORMAT"]["RESPONSE"] += "Or if you want to PASS, return PASS." - rsp = await self._aask(prompt) - # rsp = "Poison Player 1" - - return rsp + return prompt_json diff --git a/examples/werewolf_game/roles/base_player.py b/examples/werewolf_game/roles/base_player.py index df4cf48bf..1a044673d 100644 --- a/examples/werewolf_game/roles/base_player.py +++ b/examples/werewolf_game/roles/base_player.py @@ -15,8 +15,6 @@ class BasePlayer(Role): **kwargs, ): super().__init__(name, profile, **kwargs) - self._init_actions([Speak]) - self._watch([InstructSpeak]) # 通过 set_status() 更新状态。 self.status = 0 # 0代表活着,1代表死亡 @@ -60,6 +58,9 @@ class BasePlayer(Role): memories = [f"{m.sent_from}: {re.sub(time_stamp_pattern, '', m.content)}" for m in memories] # regex去掉时间戳 memories = "\n".join(memories) return memories + + def get_latest_instruction(self) -> str: + return self._rc.important_memory[-1].content # 角色监听着Moderator的InstructSpeak,是其重要记忆,直接获取即可 def set_status(self, new_status): self.status = new_status diff --git a/examples/werewolf_game/roles/guard.py b/examples/werewolf_game/roles/guard.py index 1f53dd795..580d16cd9 100644 --- a/examples/werewolf_game/roles/guard.py +++ b/examples/werewolf_game/roles/guard.py @@ -18,20 +18,21 @@ class Guard(BasePlayer): todo = self._rc.todo logger.info(f"{self._setting}: ready to {str(todo)}") - # 可以用这个函数获取该角色的全部记忆 + # 可以用这个函数获取该角色的全部记忆和最新的instruction memories = self.get_all_memories() + latest_instruction = self.get_latest_instruction() # print("*" * 10, f"{self._setting}'s current memories: {memories}", "*" * 10) # 根据自己定义的角色Action,对应地去run,run的入参可能不同 if isinstance(todo, Speak): - rsp = await todo.run(profile=self.profile, context=memories) + rsp = await todo.run(profile=self.profile, name=self.name, context=memories, latest_instruction=latest_instruction) msg = Message( content=rsp, role=self.profile, sent_from=self.name, cause_by=Speak, send_to="", restricted_to="", ) elif isinstance(todo, Protect): - rsp = await todo.run(context=memories) + rsp = await todo.run(profile=self.profile, name=self.name, context=memories) msg = Message( content=rsp, role=self.profile, sent_from=self.name, cause_by=Protect, send_to="", diff --git a/examples/werewolf_game/roles/human_player.py b/examples/werewolf_game/roles/human_player.py index 8b04ae821..fce90b05a 100644 --- a/examples/werewolf_game/roles/human_player.py +++ b/examples/werewolf_game/roles/human_player.py @@ -14,7 +14,7 @@ async def _act(self): ## You are {self.name}({self.profile}) ## Guidance: 1. If you are performing a special action or exercising a vote, - end your response with "PlayerX" where X is the player index, e.g., "..., kill/protect/poison/.../vote Player1". + end your response with "PlayerX", replace PlayerX with the actual player name, e.g., "..., kill/protect/poison/.../vote Player1". 2. If it is a daytime free speech, you can speak in whatever format. Now, please speak: """ diff --git a/examples/werewolf_game/roles/moderator.py b/examples/werewolf_game/roles/moderator.py index 95af332db..f4bad6c96 100644 --- a/examples/werewolf_game/roles/moderator.py +++ b/examples/werewolf_game/roles/moderator.py @@ -24,12 +24,15 @@ class Moderator(Role): self._watch([UserRequirement, InstructSpeak, ParseSpeak]) self._init_actions([InstructSpeak, ParseSpeak, AnnounceGameResult]) self.step_idx = 0 + self.eval_step_idx = [] # game states self.living_players = [] self.werewolf_players = [] - self.good_guys = [] + self.villager_players = [] + self.special_role_players = [] self.winner = None + self.win_reason = None self.witch_poison_left = 1 self.witch_antidote_left = 1 @@ -44,7 +47,10 @@ class Moderator(Role): self.living_players = re.findall(r"Player[0-9]+", game_setup) self.werewolf_players = re.findall(r"Player[0-9]+: Werewolf", game_setup) self.werewolf_players = [p.replace(": Werewolf", "") for p in self.werewolf_players] - self.good_guys = [p for p in self.living_players if p not in self.werewolf_players] + self.villager_players = re.findall(r"Player[0-9]+: Villager", game_setup) + self.villager_players = [p.replace(": Villager", "") for p in self.villager_players] + self.special_role_players = [p for p in self.living_players \ + if p not in self.werewolf_players + self.villager_players] def update_player_status(self, player_names: list[str]): if not player_names: @@ -114,8 +120,10 @@ class Moderator(Role): def _update_game_states(self, memories): step_idx = self.step_idx % len(STEP_INSTRUCTIONS) - if step_idx not in [15, 18]: # FIXME: hard code + if step_idx not in [15, 18] or self.step_idx in self.eval_step_idx: # FIXME: hard code return + else: + self.eval_step_idx.append(self.step_idx) # record evaluation, avoid repetitive evaluation at the same step if step_idx == 15: # FIXME: hard code # night ends: after all special roles acted, process the whole night @@ -135,6 +143,7 @@ class Moderator(Role): self.player_poisoned = None elif step_idx == 18: # FIXME: hard code + print("*" * 10, step_idx) # day ends: after all roles voted, process all votings voting_msgs = memories[-len(self.living_players):] voted_all = [] @@ -144,16 +153,20 @@ class Moderator(Role): continue voted_all.append(voted.group(0)) self.player_current_dead = [Counter(voted_all).most_common()[0][0]] # 平票时,杀序号小的 + # print("*" * 10, "dead", self.player_current_dead) self.living_players = [p for p in self.living_players if p not in self.player_current_dead] self.update_player_status(self.player_current_dead) # game's termination condition living_werewolf = [p for p in self.werewolf_players if p in self.living_players] - living_good_guys = [p for p in self.good_guys if p in self.living_players] + living_villagers = [p for p in self.villager_players if p in self.living_players] + living_special_roles = [p for p in self.special_role_players if p in self.living_players] if not living_werewolf: self.winner = "good guys" - elif not living_good_guys: + self.win_reason = "werewolves all dead" + elif not living_villagers or not living_special_roles: self.winner = "werewolf" + self.win_reason = "villagers all dead" if not living_villagers else "special roles all dead" def _record_game_history(self): if self.step_idx % len(STEP_INSTRUCTIONS) == 0 or self.winner is not None: @@ -210,7 +223,7 @@ class Moderator(Role): cause_by=ParseSpeak, send_to="", restricted_to=msg_restriced_to) elif isinstance(todo, AnnounceGameResult): - msg_content = await AnnounceGameResult().run(winner=self.winner) + msg_content = await AnnounceGameResult().run(winner=self.winner, win_reason=self.win_reason) msg = Message(content=msg_content, role=self.profile, sent_from=self.name, cause_by=AnnounceGameResult) logger.info(f"{self._setting}: {msg_content}") diff --git a/examples/werewolf_game/roles/seer.py b/examples/werewolf_game/roles/seer.py index 1d58b70bf..54a15689d 100644 --- a/examples/werewolf_game/roles/seer.py +++ b/examples/werewolf_game/roles/seer.py @@ -19,19 +19,21 @@ class Seer(BasePlayer): todo = self._rc.todo logger.info(f"{self._setting}: ready to {str(todo)}") + # 可以用这个函数获取该角色的全部记忆和最新的instruction memories = self.get_all_memories() + latest_instruction = self.get_latest_instruction() # 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) + rsp = await todo.run(profile=self.profile, name=self.name, context=memories, latest_instruction=latest_instruction) 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) + rsp = await todo.run(profile=self.profile, name=self.name, context=memories) msg = Message( content=rsp, role=self.profile, sent_from=self.name, cause_by=Verify, send_to="", diff --git a/examples/werewolf_game/roles/villager.py b/examples/werewolf_game/roles/villager.py index ececa96ea..e6e59a51e 100644 --- a/examples/werewolf_game/roles/villager.py +++ b/examples/werewolf_game/roles/villager.py @@ -19,12 +19,13 @@ class Villager(BasePlayer): todo = self._rc.todo logger.info(f"{self._setting}: ready to {todo}") - # 可以用这个函数获取该角色的全部记忆 + # 可以用这个函数获取该角色的全部记忆和最新的instruction memories = self.get_all_memories() + latest_instruction = self.get_latest_instruction() # print("*" * 10, f"{self._setting}'s current memories: {memories}", "*" * 10) # 根据自己定义的角色Action,对应地去run - rsp = await todo.run(profile=self.profile, context=memories) + rsp = await todo.run(profile=self.profile, name=self.name, context=memories, latest_instruction=latest_instruction) # 返回消息,注意给Moderator发送的加密消息需要用restricted_to="Moderator" msg = Message( diff --git a/examples/werewolf_game/roles/werewolf.py b/examples/werewolf_game/roles/werewolf.py index 5426ca7c4..786e37691 100644 --- a/examples/werewolf_game/roles/werewolf.py +++ b/examples/werewolf_game/roles/werewolf.py @@ -18,21 +18,22 @@ class Werewolf(BasePlayer): todo = self._rc.todo logger.info(f"{self._setting}: ready to {str(todo)}") - # 可以用这个函数获取该角色的全部记忆 + # 可以用这个函数获取该角色的全部记忆和最新的instruction memories = self.get_all_memories() + latest_instruction = self.get_latest_instruction() # print("*" * 10, f"{self._setting}'s current memories: {memories}", "*" * 10) # 根据自己定义的角色Action,对应地去run,run的入参可能不同 if isinstance(todo, Speak): - # rsp = await todo.run(profile=self.profile, context=memories) - rsp = await Impersonate().run(profile=self.profile, context=memories) + # rsp = await todo.run(profile=self.profile, name=self.name, context=memories, latest_instruction=latest_instruction) + rsp = await Impersonate().run(profile=self.profile, name=self.name, context=memories, latest_instruction=latest_instruction) msg = Message( content=rsp, role=self.profile, sent_from=self.name, cause_by=Speak, send_to="", restricted_to="", ) elif isinstance(todo, Hunt): - rsp = await todo.run(context=memories) + rsp = await todo.run(profile=self.profile, name=self.name, context=memories) msg = Message( content=rsp, role=self.profile, sent_from=self.name, cause_by=Hunt, send_to="", diff --git a/examples/werewolf_game/roles/witch.py b/examples/werewolf_game/roles/witch.py index 13b677d7e..a570677df 100644 --- a/examples/werewolf_game/roles/witch.py +++ b/examples/werewolf_game/roles/witch.py @@ -38,18 +38,19 @@ class Witch(BasePlayer): # 可以用这个函数获取该角色的全部记忆 memories = self.get_all_memories() + latest_instruction = self.get_latest_instruction() # print("*" * 10, f"{self._setting}'s current memories: {memories}", "*" * 10) # 根据自己定义的角色Action,对应地去run,run的入参可能不同 if isinstance(todo, Speak): - rsp = await todo.run(profile=self.profile, context=memories) + rsp = await todo.run(profile=self.profile, name=self.name, context=memories, latest_instruction=latest_instruction) msg = Message( content=rsp, role=self.profile, sent_from=self.name, cause_by=Speak, send_to="", restricted_to="", ) elif isinstance(todo, Save): - rsp = await todo.run(context=memories) + rsp = await todo.run(profile=self.profile, name=self.name, context=memories) msg = Message( content=rsp, role=self.profile, sent_from=self.name, cause_by=Save, send_to="", @@ -57,7 +58,7 @@ class Witch(BasePlayer): ) elif isinstance(todo, Poison): - rsp = await todo.run(context=memories) + rsp = await todo.run(profile=self.profile, name=self.name, context=memories) msg = Message( content=rsp, role=self.profile, sent_from=self.name, cause_by=Poison, send_to="",