mirror of
https://github.com/FoundationAgents/MetaGPT.git
synced 2026-06-14 15:25:17 +02:00
add seer role and action, and update moderator role
This commit is contained in:
parent
3c01534964
commit
c6a5b46ddd
7 changed files with 195 additions and 60 deletions
|
|
@ -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,
|
||||
}
|
||||
|
|
@ -1,6 +1,9 @@
|
|||
import asyncio
|
||||
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.",
|
||||
|
|
@ -19,7 +22,7 @@ STAGE_INSTRUCTIONS = {
|
|||
4: {"content": "Werewolves, please open your eyes!",
|
||||
"send_to": "Moderator",
|
||||
"restricted_to": ""},
|
||||
5: {"content": """Werewolves, I secretly tell you that xxx and xxx are
|
||||
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:
|
||||
{living_players}. """, # send to werewolf restrictedly for a response
|
||||
|
|
@ -57,7 +60,7 @@ STAGE_INSTRUCTIONS = {
|
|||
14: {"content": """It's daytime. Everyone woke up except those who had been killed.""",
|
||||
"send_to": "Moderator",
|
||||
"restricted_to": ""},
|
||||
15: {"content": "xxxx was killed last night. Or, it was a peaceful night and no one died!",
|
||||
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
|
||||
|
|
@ -66,49 +69,39 @@ STAGE_INSTRUCTIONS = {
|
|||
"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 xxxx""",
|
||||
{living_players}. Or you can pass. For example: I vote to kill ...""",
|
||||
"send_to": "Moderator",
|
||||
"restricted_to": ""},
|
||||
18: {"content": """xxxx was eliminated.""",
|
||||
18: {"content": """{voted_out_player} was eliminated.""",
|
||||
"send_to": "Moderator",
|
||||
"restricted_to": ""},
|
||||
}
|
||||
|
||||
INSTRUCT_SPEAK_TEMPLATE = """
|
||||
## BACKGROUND
|
||||
It's a Werewolf game, you are moderator.
|
||||
|
||||
## STAGE
|
||||
The current stage of the game is:
|
||||
{}
|
||||
|
||||
## CONTEXT
|
||||
Here's the current context:
|
||||
{}
|
||||
|
||||
What would you like to instruct?
|
||||
"""
|
||||
class InstructSpeak(Action):
|
||||
def __init__(self, name="InstructSpeak", context=None, llm=None):
|
||||
super().__init__(name, context, llm)
|
||||
|
||||
async def run(self, context,living_players, 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)
|
||||
|
||||
instruction_info = STAGE_INSTRUCTIONS.get(stage_idx, "Unknown instruction.")
|
||||
# 利用上下文信息context和所处阶段的信息
|
||||
if "{living_players}" in instruction_info["content"]:
|
||||
content = instruction_info["content"].format(living_players)
|
||||
else:
|
||||
content = instruction_info["content"]
|
||||
|
||||
prompt = INSTRUCT_SPEAK_TEMPLATE.format(content, context)
|
||||
|
||||
rsp = await self._aask(prompt)
|
||||
|
||||
send_to = instruction_info["send_to"]
|
||||
restricted_to = instruction_info["restricted_to"]
|
||||
|
||||
return rsp, send_to, restricted_to
|
||||
return content, instruction_info["send_to"], instruction_info["restricted_to"]
|
||||
|
||||
|
||||
class ParseSpeak(Action):
|
||||
|
|
@ -118,15 +111,77 @@ class ParseSpeak(Action):
|
|||
|
||||
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"}
|
||||
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())
|
||||
|
|
|
|||
22
examples/werewolf_game/actions/seer_actions.py
Normal file
22
examples/werewolf_game/actions/seer_actions.py
Normal file
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -4,37 +4,46 @@ 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.living_players = "[Player 1, Player 2, Player 3, Player 4]"
|
||||
|
||||
async def _instruct_speak(self,context):
|
||||
stage_idx = self.stage_idx % len(STAGE_INSTRUCTIONS)
|
||||
self.stage_idx += 1
|
||||
return await InstructSpeak().run(context=context, stage_idx=stage_idx, living_players=self.living_players)
|
||||
self.step_idx = 0
|
||||
self.living_players = ["Player1", "Player2", "Player3", "Player4", "Player5"]
|
||||
self.werewolf_players = ["Player1", "Player2"]
|
||||
self.killed_player = "Player 4" # 夜晚阶段,死掉的玩家
|
||||
self.voted_out_player = "Player 3" # 白天阶段,被投票出局的玩家
|
||||
# 假设votes代表白天投票的结果,key是被投票的玩家,value是得票数
|
||||
self.votes = {"Player1": 1, "Player2": 2, "Player3": 1, "Player4": 0, "Player5": 0}
|
||||
|
||||
async def _instruct_speak(self, context):
|
||||
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.killed_player,
|
||||
voted_out_player=self.voted_out_player)
|
||||
|
||||
async def _parse_speak(self):
|
||||
# 解析玩家消息并返回结果
|
||||
parse_result = await ParseSpeak().run()
|
||||
|
||||
|
||||
# 理解结果,更新各角色状态、游戏状态
|
||||
|
||||
return "Player message processed"
|
||||
|
|
@ -44,7 +53,7 @@ class Moderator(Role):
|
|||
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]:
|
||||
|
|
@ -52,7 +61,7 @@ class Moderator(Role):
|
|||
# 2. 上一轮消息是Moderator自己的指令,继续发出指令,一个事情可以分几条消息来说
|
||||
# 3. 上一轮消息是Moderator自己的解析消息,一个阶段结束,发出新一个阶段的指令
|
||||
self._rc.todo = InstructSpeak()
|
||||
|
||||
|
||||
else:
|
||||
# 上一轮消息是游戏角色的发言,解析角色的发言
|
||||
self._rc.todo = ParseSpeak()
|
||||
|
|
@ -68,20 +77,20 @@ class Moderator(Role):
|
|||
if isinstance(todo, InstructSpeak):
|
||||
msg_content, msg_to_send_to, msg_restriced_to = await self._instruct_speak(memories)
|
||||
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)
|
||||
|
||||
|
||||
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]
|
||||
|
|
|
|||
44
examples/werewolf_game/roles/seer.py
Normal file
44
examples/werewolf_game/roles/seer.py
Normal file
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue