mirror of
https://github.com/FoundationAgents/MetaGPT.git
synced 2026-05-21 14:05:17 +02:00
Merge branch 'geekan:werewolf_game' into werewolf_game
This commit is contained in:
commit
48043f4f9d
15 changed files with 237 additions and 153 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"])
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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})
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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="",
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -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}")
|
||||
|
|
|
|||
|
|
@ -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="",
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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="",
|
||||
|
|
|
|||
|
|
@ -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="",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue