init
This commit is contained in:
0
games/__init__.py
Normal file
0
games/__init__.py
Normal file
73
games/game_werewolves/README.md
Normal file
73
games/game_werewolves/README.md
Normal file
@@ -0,0 +1,73 @@
|
||||
# 🐺⚔️👨🌾 Nine-Player Werewolves Game
|
||||
|
||||
This is a nine-players werewolves game example built using AgentScope, showcasing **multi-agent interactions**,
|
||||
**role-based gameplay**, and **structured output handling**.
|
||||
Specifically, this game is consisted of
|
||||
|
||||
- three villagers 👨🌾,
|
||||
- three werewolves 🐺,
|
||||
- one seer 🔮,
|
||||
- one witch 🧙♀️ and
|
||||
- one hunter 🏹.
|
||||
|
||||
## ✨Changelog
|
||||
|
||||
- 2025-10: We update the example to support more features:
|
||||
- Allow the dead players to leave messages.
|
||||
- Support Chinese now.
|
||||
- Support **continuous gaming** by loading and saving session states, so the same agents can play multiple games and continue learning and optimizing their strategies.
|
||||
|
||||
|
||||
## QuickStart
|
||||
|
||||
Run the following command to start the game, ensuring you have set up your DashScope API key as an environment variable.
|
||||
|
||||
```bash
|
||||
python main.py
|
||||
```
|
||||
|
||||
> Note:
|
||||
> - You can adjust the language, model and other parameters in `main.py`.
|
||||
> - Different models may yield different game experiences.
|
||||
|
||||
Running the example with AgentScope Studio provides a more interactive experience.
|
||||
|
||||
- Demo Video in Chinese (click to play):
|
||||
|
||||
[](https://cloud.video.taobao.com/vod/KxyR66_CWaWwu76OPTvOV2Ye1Gas3i5p4molJtzhn_s.mp4)
|
||||
|
||||
- Demo Video in English (click to play):
|
||||
|
||||
[](https://cloud.video.taobao.com/vod/bMiRTfxPg2vm76wEoaIP2eJfkCi8CUExHRas-1LyK1I.mp4)
|
||||
|
||||
## Details
|
||||
|
||||
The game is built with the ``ReActAgent`` in AgentScope, utilizing its ability to generate structured outputs to
|
||||
control the game flow and interactions.
|
||||
We also use the ``MsgHub`` and pipelines in AgentScope to manage the complex interactions like discussion and voting.
|
||||
It's very interesting to see how agents play the werewolf game with different roles and objectives.
|
||||
|
||||
# Advanced Usage
|
||||
|
||||
## Change Language
|
||||
|
||||
The game is played in English by default. Just uncomment the following line in `game.py` to switch to Chinese.
|
||||
|
||||
```python
|
||||
# from prompt import ChinesePrompts as Prompts
|
||||
```
|
||||
|
||||
## Play with Agents
|
||||
|
||||
You can replace one of the agents with a `UserAgent` to play with AI agents.
|
||||
|
||||
## Change Models
|
||||
|
||||
Just modify the `model` parameter in `main.py` to try different models. Note you need to change the formatter at the same time to match the model's output format.
|
||||
|
||||
## Further Reading
|
||||
|
||||
- [Structured Output](https://doc.agentscope.io/tutorial/task_agent.html#structured-output)
|
||||
- [MsgHub and Pipelines](https://doc.agentscope.io/tutorial/task_pipeline.html)
|
||||
- [Prompt Formatter](https://doc.agentscope.io/tutorial/task_prompt.html)
|
||||
- [AgentScope Studio](https://doc.agentscope.io/tutorial/task_studio.html)
|
||||
343
games/game_werewolves/game.py
Normal file
343
games/game_werewolves/game.py
Normal file
@@ -0,0 +1,343 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# pylint: disable=too-many-branches, too-many-statements, no-name-in-module
|
||||
"""A werewolf game implemented by agentscope."""
|
||||
import numpy as np
|
||||
from agentscope.agent import ReActAgent
|
||||
from agentscope.pipeline import MsgHub, fanout_pipeline, sequential_pipeline
|
||||
from prompt import EnglishPrompts as Prompts
|
||||
from utils import (
|
||||
MAX_DISCUSSION_ROUND,
|
||||
MAX_GAME_ROUND,
|
||||
EchoAgent,
|
||||
Players,
|
||||
majority_vote,
|
||||
names_to_str,
|
||||
)
|
||||
|
||||
from .structured_model import (
|
||||
DiscussionModel,
|
||||
WitchResurrectModel,
|
||||
get_hunter_model,
|
||||
get_poison_model,
|
||||
get_seer_model,
|
||||
get_vote_model,
|
||||
)
|
||||
|
||||
# Uncomment the following line to use Chinese prompts
|
||||
# from prompt import ChinesePrompts as Prompts
|
||||
|
||||
|
||||
moderator = EchoAgent()
|
||||
|
||||
|
||||
async def hunter_stage(
|
||||
hunter_agent: ReActAgent,
|
||||
players: Players,
|
||||
) -> str | None:
|
||||
"""Because the hunter's stage may happen in two places: killed at night
|
||||
or voted during the day, we define a function here to avoid duplication."""
|
||||
global moderator
|
||||
msg_hunter = await hunter_agent(
|
||||
await moderator(Prompts.to_hunter.format(name=hunter_agent.name)),
|
||||
structured_model=get_hunter_model(players.current_alive),
|
||||
)
|
||||
if msg_hunter.metadata.get("shoot"):
|
||||
return msg_hunter.metadata.get("name", None)
|
||||
return None
|
||||
|
||||
|
||||
async def werewolves_game(agents: list[ReActAgent]) -> None:
|
||||
"""The main entry of the werewolf game
|
||||
|
||||
Args:
|
||||
agents (`list[ReActAgent]`):
|
||||
A list of 9 agents.
|
||||
"""
|
||||
assert len(agents) == 9, "The werewolf game needs exactly 9 players."
|
||||
|
||||
# Init the players' status
|
||||
players = Players()
|
||||
|
||||
# If the witch has healing and poison potion
|
||||
healing, poison = True, True
|
||||
|
||||
# If it's the first day, the dead can leave a message
|
||||
first_day = True
|
||||
|
||||
# Broadcast the game begin message
|
||||
async with MsgHub(participants=agents) as greeting_hub:
|
||||
await greeting_hub.broadcast(
|
||||
await moderator(
|
||||
Prompts.to_all_new_game.format(names_to_str(agents)),
|
||||
),
|
||||
)
|
||||
|
||||
# Assign roles to the agents
|
||||
roles = ["werewolf"] * 3 + ["villager"] * 3 + ["seer", "witch", "hunter"]
|
||||
np.random.shuffle(agents)
|
||||
np.random.shuffle(roles)
|
||||
|
||||
for agent, role in zip(agents, roles):
|
||||
# Tell the agent its role
|
||||
await agent.observe(
|
||||
await moderator(
|
||||
f"[{agent.name} ONLY] {agent.name}, your role is {role}.",
|
||||
),
|
||||
)
|
||||
players.add_player(agent, role)
|
||||
|
||||
# Printing the roles
|
||||
players.print_roles()
|
||||
|
||||
# GAME BEGIN!
|
||||
for _ in range(MAX_GAME_ROUND):
|
||||
# Create a MsgHub for all players to broadcast messages
|
||||
async with MsgHub(
|
||||
participants=players.current_alive,
|
||||
enable_auto_broadcast=False, # manual broadcast only
|
||||
name="alive_players",
|
||||
) as alive_players_hub:
|
||||
# Night phase
|
||||
await alive_players_hub.broadcast(
|
||||
await moderator(Prompts.to_all_night),
|
||||
)
|
||||
killed_player, poisoned_player, shot_player = None, None, None
|
||||
|
||||
# Werewolves discuss
|
||||
async with MsgHub(
|
||||
players.werewolves,
|
||||
enable_auto_broadcast=True,
|
||||
announcement=await moderator(
|
||||
Prompts.to_wolves_discussion.format(
|
||||
names_to_str(players.werewolves),
|
||||
names_to_str(players.current_alive),
|
||||
),
|
||||
),
|
||||
name="game_werewolves",
|
||||
) as werewolves_hub:
|
||||
# Discussion
|
||||
n_werewolves = len(players.werewolves)
|
||||
for _ in range(1, MAX_DISCUSSION_ROUND * n_werewolves + 1):
|
||||
res = await players.werewolves[_ % n_werewolves](
|
||||
structured_model=DiscussionModel,
|
||||
)
|
||||
if _ % n_werewolves == 0 and res.metadata.get(
|
||||
"reach_agreement",
|
||||
):
|
||||
break
|
||||
|
||||
# Werewolves vote
|
||||
# Disable auto broadcast to avoid following other's votes
|
||||
werewolves_hub.set_auto_broadcast(False)
|
||||
msgs_vote = await fanout_pipeline(
|
||||
players.werewolves,
|
||||
msg=await moderator(content=Prompts.to_wolves_vote),
|
||||
structured_model=get_vote_model(players.current_alive),
|
||||
enable_gather=False,
|
||||
)
|
||||
killed_player, votes = majority_vote(
|
||||
[_.metadata.get("vote") for _ in msgs_vote],
|
||||
)
|
||||
# Postpone the broadcast of voting
|
||||
await werewolves_hub.broadcast(
|
||||
[
|
||||
*msgs_vote,
|
||||
await moderator(
|
||||
Prompts.to_wolves_res.format(votes, killed_player),
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
# Witch's turn
|
||||
await alive_players_hub.broadcast(
|
||||
await moderator(Prompts.to_all_witch_turn),
|
||||
)
|
||||
msg_witch_poison = None
|
||||
for agent in players.witch:
|
||||
# Cannot heal witch herself
|
||||
msg_witch_resurrect = None
|
||||
if healing and killed_player != agent.name:
|
||||
msg_witch_resurrect = await agent(
|
||||
await moderator(
|
||||
Prompts.to_witch_resurrect.format(
|
||||
witch_name=agent.name,
|
||||
dead_name=killed_player,
|
||||
),
|
||||
),
|
||||
structured_model=WitchResurrectModel,
|
||||
)
|
||||
if msg_witch_resurrect.metadata.get("resurrect"):
|
||||
killed_player = None
|
||||
healing = False
|
||||
|
||||
# Has poison potion and hasn't used the healing potion
|
||||
if poison and not (
|
||||
msg_witch_resurrect
|
||||
and msg_witch_resurrect.metadata["resurrect"]
|
||||
):
|
||||
msg_witch_poison = await agent(
|
||||
await moderator(
|
||||
Prompts.to_witch_poison.format(
|
||||
witch_name=agent.name,
|
||||
),
|
||||
),
|
||||
structured_model=get_poison_model(
|
||||
players.current_alive,
|
||||
),
|
||||
)
|
||||
if msg_witch_poison.metadata.get("poison"):
|
||||
poisoned_player = msg_witch_poison.metadata.get("name")
|
||||
poison = False
|
||||
|
||||
# Seer's turn
|
||||
await alive_players_hub.broadcast(
|
||||
await moderator(Prompts.to_all_seer_turn),
|
||||
)
|
||||
for agent in players.seer:
|
||||
msg_seer = await agent(
|
||||
await moderator(
|
||||
Prompts.to_seer.format(
|
||||
agent.name,
|
||||
names_to_str(players.current_alive),
|
||||
),
|
||||
),
|
||||
structured_model=get_seer_model(players.current_alive),
|
||||
)
|
||||
if msg_seer.metadata.get("name"):
|
||||
player = msg_seer.metadata["name"]
|
||||
await agent.observe(
|
||||
await moderator(
|
||||
Prompts.to_seer_result.format(
|
||||
agent_name=player,
|
||||
role=players.name_to_role[player],
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
# Hunter's turn
|
||||
for agent in players.hunter:
|
||||
# If killed and not by witch's poison
|
||||
if (
|
||||
killed_player == agent.name
|
||||
and poisoned_player != agent.name
|
||||
):
|
||||
shot_player = await hunter_stage(agent, players)
|
||||
|
||||
# Update alive players
|
||||
dead_tonight = [killed_player, poisoned_player, shot_player]
|
||||
players.update_players(dead_tonight)
|
||||
|
||||
# Day phase
|
||||
if len([_ for _ in dead_tonight if _]) > 0:
|
||||
await alive_players_hub.broadcast(
|
||||
await moderator(
|
||||
Prompts.to_all_day.format(
|
||||
names_to_str([_ for _ in dead_tonight if _]),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
# The killed player leave a last message in first night
|
||||
if killed_player and first_day:
|
||||
msg_moderator = await moderator(
|
||||
Prompts.to_dead_player.format(killed_player),
|
||||
)
|
||||
await alive_players_hub.broadcast(msg_moderator)
|
||||
# Leave a message
|
||||
last_msg = await players.name_to_agent[killed_player]()
|
||||
await alive_players_hub.broadcast(last_msg)
|
||||
|
||||
else:
|
||||
await alive_players_hub.broadcast(
|
||||
await moderator(Prompts.to_all_peace),
|
||||
)
|
||||
|
||||
# Check winning
|
||||
res = players.check_winning()
|
||||
if res:
|
||||
await moderator(res)
|
||||
break
|
||||
|
||||
# Discussion
|
||||
await alive_players_hub.broadcast(
|
||||
await moderator(
|
||||
Prompts.to_all_discuss.format(
|
||||
names=names_to_str(players.current_alive),
|
||||
),
|
||||
),
|
||||
)
|
||||
# Open the auto broadcast to enable discussion
|
||||
alive_players_hub.set_auto_broadcast(True)
|
||||
await sequential_pipeline(players.current_alive)
|
||||
# Disable auto broadcast to avoid leaking info
|
||||
alive_players_hub.set_auto_broadcast(False)
|
||||
|
||||
# Voting
|
||||
msgs_vote = await fanout_pipeline(
|
||||
players.current_alive,
|
||||
await moderator(
|
||||
Prompts.to_all_vote.format(
|
||||
names_to_str(players.current_alive),
|
||||
),
|
||||
),
|
||||
structured_model=get_vote_model(players.current_alive),
|
||||
enable_gather=False,
|
||||
)
|
||||
voted_player, votes = majority_vote(
|
||||
[_.metadata.get("vote") for _ in msgs_vote],
|
||||
)
|
||||
# Broadcast the voting messages together to avoid influencing
|
||||
# each other
|
||||
voting_msgs = [
|
||||
*msgs_vote,
|
||||
await moderator(
|
||||
Prompts.to_all_res.format(votes, voted_player),
|
||||
),
|
||||
]
|
||||
|
||||
# Leave a message if voted
|
||||
if voted_player:
|
||||
prompt_msg = await moderator(
|
||||
Prompts.to_dead_player.format(voted_player),
|
||||
)
|
||||
last_msg = await players.name_to_agent[voted_player](
|
||||
prompt_msg,
|
||||
)
|
||||
voting_msgs.extend([prompt_msg, last_msg])
|
||||
|
||||
await alive_players_hub.broadcast(voting_msgs)
|
||||
|
||||
# If the voted player is the hunter, he can shoot someone
|
||||
shot_player = None
|
||||
for agent in players.hunter:
|
||||
if voted_player == agent.name:
|
||||
shot_player = await hunter_stage(agent, players)
|
||||
if shot_player:
|
||||
await alive_players_hub.broadcast(
|
||||
await moderator(
|
||||
Prompts.to_all_hunter_shoot.format(
|
||||
shot_player,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
# Update alive players
|
||||
dead_today = [voted_player, shot_player]
|
||||
players.update_players(dead_today)
|
||||
|
||||
# Check winning
|
||||
res = players.check_winning()
|
||||
if res:
|
||||
async with MsgHub(players.all_players) as all_players_hub:
|
||||
res_msg = await moderator(res)
|
||||
await all_players_hub.broadcast(res_msg)
|
||||
break
|
||||
|
||||
# The day ends
|
||||
first_day = False
|
||||
|
||||
# Game over, each player reflects
|
||||
await fanout_pipeline(
|
||||
agents=agents,
|
||||
msg=await moderator(Prompts.to_all_reflect),
|
||||
)
|
||||
111
games/game_werewolves/main.py
Normal file
111
games/game_werewolves/main.py
Normal file
@@ -0,0 +1,111 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# flake8: noqa: E501
|
||||
"""The main entry point for the werewolf game."""
|
||||
import asyncio
|
||||
import os
|
||||
|
||||
from agentscope.agent import ReActAgent
|
||||
from agentscope.formatter import DashScopeMultiAgentFormatter
|
||||
from agentscope.model import DashScopeChatModel
|
||||
from agentscope.session import JSONSession
|
||||
from game import werewolves_game
|
||||
|
||||
|
||||
def get_official_agents(name: str) -> ReActAgent:
|
||||
"""Get the official game_werewolves game agents."""
|
||||
agent = ReActAgent(
|
||||
name=name,
|
||||
sys_prompt=f"""You're a werewolf game player named {name}.
|
||||
|
||||
# YOUR TARGET
|
||||
Your target is to win the game with your teammates as much as possible.
|
||||
|
||||
# GAME RULES
|
||||
- In werewolf game, players are divided into three game_werewolves, three villagers, one seer, one hunter and one witch.
|
||||
- Werewolves: kill one player each night, and must hide identity during the day.
|
||||
- Villagers: ordinary players without special abilities, try to identify and eliminate game_werewolves.
|
||||
- Seer: A special villager who can check one player's identity each night.
|
||||
- Witch: A special villager with two one-time-use potions: a healing potion to save a player from being killed at night, and a poison to eliminate one player at night.
|
||||
- Hunter: A special villager who can take one player down with them when they are eliminated.
|
||||
- The game alternates between night and day phases until one side wins:
|
||||
- Night Phase
|
||||
- Werewolves choose one victim
|
||||
- Seer checks one player's identity
|
||||
- Witch decides whether to use potions
|
||||
- Moderator announces who died during the night
|
||||
- Day Phase
|
||||
- All players discuss and vote to eliminate one suspected player
|
||||
|
||||
# GAME GUIDANCE
|
||||
- Try your best to win the game with your teammates, tricks, lies, and deception are all allowed, e.g. pretending to be a different role.
|
||||
- During discussion, don't be political, be direct and to the point.
|
||||
- The day phase voting provides important clues. For example, the game_werewolves may vote together, attack the seer, etc.
|
||||
## GAME GUIDANCE FOR WEREWOLF
|
||||
- Seer is your greatest threat, who can check one player's identity each night. Analyze players' speeches, find out the seer and eliminate him/her will greatly increase your chances of winning.
|
||||
- In the first night, making random choices is common for game_werewolves since no information is available.
|
||||
- Pretending to be other roles (seer, witch or villager) is a common strategy to hide your identity and mislead other villagers in the day phase.
|
||||
- The outcome of the night phase provides important clues. For example, if witch uses the healing or poison potion, if the dead player is hunter, etc. Use this information to adjust your strategy.
|
||||
## GAME GUIDANCE FOR SEER
|
||||
- Seer is very important to villagers, exposing yourself too early may lead to being targeted by game_werewolves.
|
||||
- Your ability to check one player's identity is crucial.
|
||||
- The outcome of the night phase provides important clues. For example, if witch uses the healing or poison potion, if the dead player is hunter, etc. Use this information to adjust your strategy.
|
||||
## GAME GUIDANCE FOR WITCH
|
||||
- Witch has two powerful potions, use them wisely to protect key villagers or eliminate suspected game_werewolves.
|
||||
- The outcome of the night phase provides important clues. For example, if the dead player is hunter, etc. Use this information to adjust your strategy.
|
||||
## GAME GUIDANCE FOR HUNTER
|
||||
- Using your ability in day phase will expose your role (since only hunter can take one player down)
|
||||
- The outcome of the night phase provides important clues. For example, if witch uses the healing or poison potion, etc. Use this information to adjust your strategy.
|
||||
## GAME GUIDANCE FOR VILLAGER
|
||||
- Protecting special villagers, especially the seer, is crucial for your team's success.
|
||||
- Werewolves may pretend to be the seer. Be cautious and don't trust anyone easily.
|
||||
- The outcome of the night phase provides important clues. For example, if witch uses the healing or poison potion, if the dead player is hunter, etc. Use this information to adjust your strategy.
|
||||
|
||||
# NOTE
|
||||
- [IMPORTANT] DO NOT make up any information that is not provided by the moderator or other players.
|
||||
- This is a TEXT-based game, so DO NOT use or make up any non-textual information.
|
||||
- Always critically reflect on whether your evidence exist, and avoid making assumptions.
|
||||
- Your response should be specific and concise, provide clear reason and avoid unnecessary elaboration.
|
||||
- Generate your one-line response by using the `generate_response` function.
|
||||
- Don't repeat the others' speeches.""",
|
||||
model=DashScopeChatModel(
|
||||
api_key=os.environ.get("DASHSCOPE_API_KEY"),
|
||||
model_name="qwen3-max",
|
||||
),
|
||||
formatter=DashScopeMultiAgentFormatter(),
|
||||
)
|
||||
return agent
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
"""The main entry point for the werewolf game."""
|
||||
|
||||
# Uncomment the following lines if you want to use Agentscope Studio
|
||||
# to visualize the game process.
|
||||
# import agentscope
|
||||
# agentscope.init(
|
||||
# studio_url="http://localhost:3000",
|
||||
# project="werewolf_game",
|
||||
# )
|
||||
|
||||
# Prepare 9 players, you can change their names here
|
||||
players = [get_official_agents(f"Player{_ + 1}") for _ in range(9)]
|
||||
|
||||
# Note: You can replace your own agents here, or use all your own agents
|
||||
|
||||
# Load states from a previous checkpoint
|
||||
session = JSONSession(save_dir="./checkpoints")
|
||||
await session.load_session_state(
|
||||
session_id="players_checkpoint",
|
||||
**{player.name: player for player in players},
|
||||
)
|
||||
|
||||
await werewolves_game(players)
|
||||
|
||||
# Save the states to a checkpoint
|
||||
await session.save_session_state(
|
||||
session_id="players_checkpoint",
|
||||
**{player.name: player for player in players},
|
||||
)
|
||||
|
||||
|
||||
asyncio.run(main())
|
||||
191
games/game_werewolves/prompt.py
Normal file
191
games/game_werewolves/prompt.py
Normal file
@@ -0,0 +1,191 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Default prompts"""
|
||||
|
||||
|
||||
class EnglishPrompts:
|
||||
"""English prompts used to guide the werewolf game."""
|
||||
|
||||
to_dead_player = (
|
||||
"{}, you're eliminated now. Now you can make a final statement to "
|
||||
"all alive players before you leave the game."
|
||||
)
|
||||
|
||||
to_all_new_game = (
|
||||
"A new game is starting, the players are: {}. Now we randomly "
|
||||
"reassign the roles to each player and inform them of their roles "
|
||||
"privately."
|
||||
)
|
||||
|
||||
to_all_night = (
|
||||
"Night has fallen, everyone close your eyes. Werewolves open your "
|
||||
"eyes and choose a player to eliminate tonight."
|
||||
)
|
||||
|
||||
to_wolves_discussion = (
|
||||
"[WEREWOLVES ONLY] {}, you should discuss and "
|
||||
"decide on a player to eliminate tonight. Current alive players "
|
||||
"are {}. Remember to set `reach_agreement` to True if you reach an "
|
||||
"agreement during the discussion."
|
||||
)
|
||||
|
||||
to_wolves_vote = "[WEREWOLVES ONLY] Which player do you vote to kill?"
|
||||
|
||||
to_wolves_res = (
|
||||
"[WEREWOLVES ONLY] The voting result is {}. So you have chosen to "
|
||||
"eliminate {}."
|
||||
)
|
||||
|
||||
to_all_witch_turn = (
|
||||
"Witch's turn, witch open your eyes and decide your action tonight..."
|
||||
)
|
||||
to_witch_resurrect = (
|
||||
"[WITCH ONLY] {witch_name}, you're the witch, and tonight {dead_name} "
|
||||
"is eliminated. You can resurrect him/her by using your healing "
|
||||
"potion, "
|
||||
"and note you can only use it once in the whole game. Do you want to "
|
||||
"resurrect {dead_name}? Give me your reason and decision."
|
||||
)
|
||||
|
||||
to_witch_resurrect_no = (
|
||||
"[WITCH ONLY] The witch has chosen not to resurrect the player."
|
||||
)
|
||||
to_witch_resurrect_yes = (
|
||||
"[WITCH ONLY] The witch has chosen to resurrect the player."
|
||||
)
|
||||
|
||||
to_witch_poison = (
|
||||
"[WITCH ONLY] {witch_name}, as a witch, you have a one-time-use "
|
||||
"poison potion, do you want to use it tonight? Give me your reason "
|
||||
"and decision."
|
||||
)
|
||||
|
||||
to_all_seer_turn = (
|
||||
"Seer's turn, seer open your eyes and check one player's identity "
|
||||
"tonight..."
|
||||
)
|
||||
|
||||
to_seer = (
|
||||
"[SEER ONLY] {}, as the seer you can check one player's identity "
|
||||
"tonight. Who do you want to check? Give me your reason and decision."
|
||||
)
|
||||
|
||||
to_seer_result = (
|
||||
"[SEER ONLY] You've checked {agent_name}, and the result is: {role}."
|
||||
)
|
||||
|
||||
to_hunter = (
|
||||
"[HUNTER ONLY] {name}, as the hunter you're eliminated tonight. You "
|
||||
"can choose one player to take down with you. Also, you can choose "
|
||||
"not to use this ability. Give me your reason and decision."
|
||||
)
|
||||
|
||||
to_all_hunter_shoot = (
|
||||
"The hunter has chosen to shoot {} down with him/herself."
|
||||
)
|
||||
|
||||
to_all_day = (
|
||||
"The day is coming, all players open your eyes. Last night, "
|
||||
"the following player(s) has been eliminated: {}."
|
||||
)
|
||||
|
||||
to_all_peace = (
|
||||
"The day is coming, all the players open your eyes. Last night is "
|
||||
"peaceful, no player is eliminated."
|
||||
)
|
||||
|
||||
to_all_discuss = (
|
||||
"Now the alive players are {names}. The game goes on, it's time to "
|
||||
"discuss and vote a player to be eliminated. Now you each take turns "
|
||||
"to speak once in the order of {names}."
|
||||
)
|
||||
|
||||
to_all_vote = (
|
||||
"Now the discussion is over. Everyone, please vote to eliminate one "
|
||||
"player from the alive players: {}."
|
||||
)
|
||||
|
||||
to_all_res = "The voting result is {}. So {} has been voted out."
|
||||
|
||||
to_all_wolf_win = (
|
||||
"There are {n_alive} players alive, and {n_werewolves} of them are "
|
||||
"game_werewolves. "
|
||||
"The game is over and game_werewolves win🐺🎉!"
|
||||
"In this game, the true roles of all players are: {true_roles}"
|
||||
)
|
||||
|
||||
to_all_village_win = (
|
||||
"All the game_werewolves have been eliminated."
|
||||
"The game is over and villagers win🏘️🎉!"
|
||||
"In this game, the true roles of all players are: {true_roles}"
|
||||
)
|
||||
|
||||
to_all_continue = "The game goes on."
|
||||
|
||||
to_all_reflect = (
|
||||
"The game is over. Now each player can reflect on their performance. "
|
||||
"Note each player only has one chance to speak and the reflection is "
|
||||
"only visible to themselves."
|
||||
)
|
||||
|
||||
|
||||
class ChinesePrompts:
|
||||
"""Chinese prompts used to guide the werewolf game."""
|
||||
|
||||
to_dead_player = "{}, 你已被淘汰。现在你可以向所有存活玩家发表最后的遗言。"
|
||||
|
||||
to_all_new_game = "新的一局游戏开始,参与玩家包括:{}。现在为每位玩家重新随机分配身份,并私下告知各自身份。"
|
||||
|
||||
to_all_night = "天黑了,请所有人闭眼。狼人请睁眼,选择今晚要淘汰的一名玩家..."
|
||||
|
||||
to_wolves_discussion = (
|
||||
"[仅狼人可见] {}, 你们可以讨论并决定今晚要淘汰的玩家。当前存活玩家有:{}。"
|
||||
"如果达成一致,请将 `reach_agreement` 设为 True。"
|
||||
)
|
||||
|
||||
to_wolves_vote = "[仅狼人可见] 你投票要杀死哪位玩家?"
|
||||
|
||||
to_wolves_res = "[仅狼人可见] 投票结果为 {},你们选择淘汰 {}。"
|
||||
|
||||
to_all_witch_turn = "轮到女巫行动,女巫请睁眼并决定今晚的操作..."
|
||||
to_witch_resurrect = (
|
||||
"[仅女巫可见] {witch_name},你是女巫,今晚{dead_name}被淘汰。"
|
||||
"你可以用解药救他/她,注意解药全局只能用一次。你要救{dead_name}吗?"
|
||||
"请给出理由和决定。"
|
||||
)
|
||||
|
||||
to_witch_resurrect_no = "[仅女巫可见] 女巫选择不救该玩家。"
|
||||
to_witch_resurrect_yes = "[仅女巫可见] 女巫选择救活该玩家。"
|
||||
|
||||
to_witch_poison = "[仅女巫可见] {witch_name},你有一瓶一次性毒药,今晚要使用吗?请给出理由和决定。"
|
||||
|
||||
to_all_seer_turn = "轮到预言家行动,预言家请睁眼并查验一名玩家身份..."
|
||||
|
||||
to_seer = "[仅预言家可见] {}, 你是预言家,今晚可以查验一名玩家身份。你要查谁?请给出理由和决定。"
|
||||
|
||||
to_seer_result = "[仅预言家可见] 你查验了{agent_name},结果是:{role}。"
|
||||
|
||||
to_hunter = "[仅猎人可见] {name},你是猎人,今晚被淘汰。你可以选择带走一名玩家,也可以选择不带走。请给出理由和决定。"
|
||||
|
||||
to_all_hunter_shoot = "猎人选择带走 {} 一起出局。"
|
||||
|
||||
to_all_day = "天亮了,请所有玩家睁眼。昨晚被淘汰的玩家有:{}。"
|
||||
|
||||
to_all_peace = "天亮了,请所有玩家睁眼。昨晚平安夜,无人被淘汰。"
|
||||
|
||||
to_all_discuss = "现在存活玩家有:{names}。游戏继续,大家开始讨论并投票淘汰一名玩家。请按顺序({names})依次发言。"
|
||||
|
||||
to_all_vote = "讨论结束。请大家从存活玩家中投票淘汰一人:{}。"
|
||||
|
||||
to_all_res = "投票结果为 {},{} 被淘汰。"
|
||||
|
||||
to_all_wolf_win = (
|
||||
"当前存活玩家共{n_alive}人,其中{n_werewolves}人为狼人。"
|
||||
"游戏结束,狼人获胜🐺🎉!"
|
||||
"本局所有玩家真实身份为:{true_roles}"
|
||||
)
|
||||
|
||||
to_all_village_win = "所有狼人已被淘汰。游戏结束,村民获胜🏘️🎉!本局所有玩家真实身份为:{true_roles}"
|
||||
|
||||
to_all_continue = "游戏继续。"
|
||||
|
||||
to_all_reflect = "游戏结束。现在每位玩家可以对自己的表现进行反思。注意每位玩家只有一次发言机会,且反思内容仅自己可见。"
|
||||
2
games/game_werewolves/requirements.txt
Normal file
2
games/game_werewolves/requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
agentscope>=1.0.5
|
||||
agentscope[full]>=1.0.5
|
||||
84
games/game_werewolves/structured_model.py
Normal file
84
games/game_werewolves/structured_model.py
Normal file
@@ -0,0 +1,84 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""The structured output models used in the werewolf game."""
|
||||
from typing import Literal
|
||||
|
||||
from agentscope.agent import AgentBase
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class DiscussionModel(BaseModel):
|
||||
"""The output format for discussion."""
|
||||
|
||||
reach_agreement: bool = Field(
|
||||
description="Whether you have reached an agreement or not",
|
||||
)
|
||||
|
||||
|
||||
def get_vote_model(agents: list[AgentBase]) -> type[BaseModel]:
|
||||
"""Get the vote model by player names."""
|
||||
|
||||
class VoteModel(BaseModel):
|
||||
"""The vote output format."""
|
||||
|
||||
vote: Literal[tuple(_.name for _ in agents)] = Field( # type: ignore
|
||||
description="The name of the player you want to vote for",
|
||||
)
|
||||
|
||||
return VoteModel
|
||||
|
||||
|
||||
class WitchResurrectModel(BaseModel):
|
||||
"""The output format for witch resurrect action."""
|
||||
|
||||
resurrect: bool = Field(
|
||||
description="Whether you want to resurrect the player",
|
||||
)
|
||||
|
||||
|
||||
def get_poison_model(agents: list[AgentBase]) -> type[BaseModel]:
|
||||
"""Get the poison model by player names."""
|
||||
|
||||
class WitchPoisonModel(BaseModel):
|
||||
"""The output format for witch poison action."""
|
||||
|
||||
poison: bool = Field(
|
||||
description="Do you want to use the poison potion",
|
||||
)
|
||||
name: Literal[tuple(_.name for _ in agents)] | None = Field( # type: ignore
|
||||
description="The name of the player you want to poison, if you "
|
||||
"don't want to poison anyone, just leave it empty",
|
||||
default=None,
|
||||
)
|
||||
|
||||
return WitchPoisonModel
|
||||
|
||||
|
||||
def get_seer_model(agents: list[AgentBase]) -> type[BaseModel]:
|
||||
"""Get the seer model by player names."""
|
||||
|
||||
class SeerModel(BaseModel):
|
||||
"""The output format for seer action."""
|
||||
|
||||
name: Literal[tuple(_.name for _ in agents)] = Field( # type: ignore
|
||||
description="The name of the player you want to check",
|
||||
)
|
||||
|
||||
return SeerModel
|
||||
|
||||
|
||||
def get_hunter_model(agents: list[AgentBase]) -> type[BaseModel]:
|
||||
"""Get the hunter model by player agents."""
|
||||
|
||||
class HunterModel(BaseModel):
|
||||
"""The output format for hunter action."""
|
||||
|
||||
shoot: bool = Field(
|
||||
description="Whether you want to use the shooting ability or not",
|
||||
)
|
||||
name: Literal[tuple(_.name for _ in agents)] | None = Field( # type: ignore
|
||||
description="The name of the player you want to shoot, if you "
|
||||
"don't want to the ability, just leave it empty",
|
||||
default=None,
|
||||
)
|
||||
|
||||
return HunterModel
|
||||
164
games/game_werewolves/utils.py
Normal file
164
games/game_werewolves/utils.py
Normal file
@@ -0,0 +1,164 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Utility functions for the werewolf game."""
|
||||
from collections import defaultdict
|
||||
from typing import Any
|
||||
|
||||
import numpy as np
|
||||
from agentscope.agent import AgentBase, ReActAgent
|
||||
from agentscope.message import Msg
|
||||
from prompt import EnglishPrompts as Prompts
|
||||
|
||||
MAX_GAME_ROUND = 30
|
||||
MAX_DISCUSSION_ROUND = 3
|
||||
|
||||
|
||||
def majority_vote(votes: list[str]) -> tuple:
|
||||
"""Return the vote with the most counts."""
|
||||
result = max(set(votes), key=votes.count)
|
||||
names, counts = np.unique(votes, return_counts=True)
|
||||
conditions = ", ".join(
|
||||
[f"{name}: {count}" for name, count in zip(names, counts)],
|
||||
)
|
||||
return result, conditions
|
||||
|
||||
|
||||
def names_to_str(agents: list[str] | list[ReActAgent]) -> str:
|
||||
"""Return a string of agent names."""
|
||||
if not agents:
|
||||
return ""
|
||||
|
||||
if len(agents) == 1:
|
||||
if isinstance(agents[0], ReActAgent):
|
||||
return agents[0].name
|
||||
return agents[0]
|
||||
|
||||
names = []
|
||||
for agent in agents:
|
||||
if isinstance(agent, ReActAgent):
|
||||
names.append(agent.name)
|
||||
else:
|
||||
names.append(agent)
|
||||
return ", ".join([*names[:-1], "and " + names[-1]])
|
||||
|
||||
|
||||
class EchoAgent(AgentBase):
|
||||
"""Echo agent that repeats the input message."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.name = "Moderator"
|
||||
|
||||
async def reply(self, content: str) -> Msg:
|
||||
"""Repeat the input content with its name and role."""
|
||||
msg = Msg(
|
||||
self.name,
|
||||
content,
|
||||
role="assistant",
|
||||
)
|
||||
await self.print(msg)
|
||||
return msg
|
||||
|
||||
async def handle_interrupt(
|
||||
self,
|
||||
*args: Any,
|
||||
**kwargs: Any,
|
||||
) -> Msg:
|
||||
"""Handle interrupt."""
|
||||
|
||||
async def observe(self, msg: Msg | list[Msg] | None) -> None:
|
||||
"""Observe the user's message."""
|
||||
|
||||
|
||||
class Players:
|
||||
"""Maintain the players' status."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize the players."""
|
||||
# The mapping from player name to role
|
||||
self.name_to_role = {}
|
||||
self.role_to_names = defaultdict(list)
|
||||
self.name_to_agent = {}
|
||||
self.werewolves = []
|
||||
self.villagers = []
|
||||
self.seer = []
|
||||
self.hunter = []
|
||||
self.witch = []
|
||||
self.current_alive = []
|
||||
self.all_players = []
|
||||
|
||||
def add_player(self, player: ReActAgent, role: str) -> None:
|
||||
"""Add a player to the game.
|
||||
|
||||
Args:
|
||||
player (`ReActAgent`):
|
||||
The player to be added.
|
||||
role (`str`):
|
||||
The role of the player.
|
||||
"""
|
||||
self.name_to_role[player.name] = role
|
||||
self.name_to_agent[player.name] = player
|
||||
self.role_to_names[role].append(player.name)
|
||||
self.all_players.append(player)
|
||||
if role == "werewolf":
|
||||
self.werewolves.append(player)
|
||||
elif role == "villager":
|
||||
self.villagers.append(player)
|
||||
elif role == "seer":
|
||||
self.seer.append(player)
|
||||
elif role == "hunter":
|
||||
self.hunter.append(player)
|
||||
elif role == "witch":
|
||||
self.witch.append(player)
|
||||
else:
|
||||
raise ValueError(f"Unknown role: {role}")
|
||||
self.current_alive.append(player)
|
||||
|
||||
def update_players(self, dead_players: list[ReActAgent]) -> None:
|
||||
"""Update the current alive players.
|
||||
|
||||
Args:
|
||||
dead_players (`list[ReActAgent]`):
|
||||
A list of dead players to be removed.
|
||||
"""
|
||||
self.werewolves = [
|
||||
_ for _ in self.werewolves if _.name not in dead_players
|
||||
]
|
||||
self.villagers = [
|
||||
_ for _ in self.villagers if _.name not in dead_players
|
||||
]
|
||||
self.seer = [_ for _ in self.seer if _.name not in dead_players]
|
||||
self.hunter = [_ for _ in self.hunter if _.name not in dead_players]
|
||||
self.witch = [_ for _ in self.witch if _.name not in dead_players]
|
||||
self.current_alive = [
|
||||
_ for _ in self.current_alive if _.name not in dead_players
|
||||
]
|
||||
|
||||
def print_roles(self) -> None:
|
||||
"""Print the roles of all players."""
|
||||
print("Roles:")
|
||||
for name, role in self.name_to_role.items():
|
||||
print(f" - {name}: {role}")
|
||||
|
||||
def check_winning(self) -> str | None:
|
||||
"""Check if the game is over and return the winning message."""
|
||||
|
||||
# Prepare true roles string
|
||||
true_roles = (
|
||||
f'{names_to_str(self.role_to_names["werewolf"])} are werewolves, '
|
||||
f'{names_to_str(self.role_to_names["villager"])} are villagers, '
|
||||
f'{names_to_str(self.role_to_names["seer"])} is the seer, '
|
||||
f'{names_to_str(self.role_to_names["hunter"])} is the hunter, '
|
||||
f'and {names_to_str(self.role_to_names["witch"])} is the witch.'
|
||||
)
|
||||
|
||||
if len(self.werewolves) * 2 >= len(self.current_alive):
|
||||
return Prompts.to_all_wolf_win.format(
|
||||
n_alive=len(self.current_alive),
|
||||
n_werewolves=len(self.werewolves),
|
||||
true_roles=true_roles,
|
||||
)
|
||||
if self.current_alive and not self.werewolves:
|
||||
return Prompts.to_all_village_win.format(
|
||||
true_roles=true_roles,
|
||||
)
|
||||
return None
|
||||
Reference in New Issue
Block a user