You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

348 lines
13 KiB
Python

6 months ago
# -*- coding: utf-8 -*-
"""
三国狼人杀 - 基于AgentScope的中文版狼人杀游戏
融合三国演义角色和传统狼人杀玩法
"""
import asyncio
import os
import random
from typing import List, Dict, Optional
from agentscope.agent import ReActAgent
from agentscope.model import DashScopeChatModel
from agentscope.pipeline import MsgHub, sequential_pipeline, fanout_pipeline
from agentscope.formatter import DashScopeMultiAgentFormatter
from prompt_cn import ChinesePrompts
from game_roles import GameRoles
from structured_output_cn import (
DiscussionModelCN,
get_vote_model_cn,
WitchActionModelCN,
get_seer_model_cn,
get_hunter_model_cn,
WerewolfKillModelCN
)
from utils_cn import (
check_winning_cn,
majority_vote_cn,
get_chinese_name,
format_player_list,
GameModerator,
MAX_GAME_ROUND,
MAX_DISCUSSION_ROUND,
)
class ThreeKingdomsWerewolfGame:
"""三国狼人杀游戏主类"""
def __init__(self):
self.players: Dict[str, ReActAgent] = {}
self.roles: Dict[str, str] = {}
self.moderator = GameModerator()
self.alive_players: List[ReActAgent] = []
self.werewolves: List[ReActAgent] = []
self.villagers: List[ReActAgent] = []
self.seer: List[ReActAgent] = []
self.witch: List[ReActAgent] = []
self.hunter: List[ReActAgent] = []
# 女巫道具状态
self.witch_has_antidote = True
self.witch_has_poison = True
async def create_player(self, role: str, character: str) -> ReActAgent:
"""创建具有三国背景的玩家"""
name = get_chinese_name(character)
self.roles[name] = role
agent = ReActAgent(
name=name,
sys_prompt=ChinesePrompts.get_role_prompt(role, character),
model=DashScopeChatModel(
model_name="qwen-max",
api_key=os.environ["DASHSCOPE_API_KEY"],
enable_thinking=True,
),
formatter=DashScopeMultiAgentFormatter(),
)
# 角色身份确认
await agent.observe(
await self.moderator.announce(
f"{name}】你在这场三国狼人杀中扮演{GameRoles.get_role_desc(role)}"
f"你的角色是{character}{GameRoles.get_role_ability(role)}"
)
)
self.players[name] = agent
return agent
async def setup_game(self, player_count: int = 6):
"""设置游戏"""
print("🎮 开始设置三国狼人杀游戏...")
# 获取角色配置
roles = GameRoles.get_standard_setup(player_count)
characters = random.sample([
"刘备", "关羽", "张飞", "诸葛亮", "赵云",
"曹操", "司马懿", "周瑜", "孙权"
], player_count)
# 创建玩家
for i, (role, character) in enumerate(zip(roles, characters)):
agent = await self.create_player(role, character)
self.alive_players.append(agent)
# 分配到对应阵营
if role == "狼人":
self.werewolves.append(agent)
elif role == "预言家":
self.seer.append(agent)
elif role == "女巫":
self.witch.append(agent)
elif role == "猎人":
self.hunter.append(agent)
else:
self.villagers.append(agent)
# 游戏开始公告
await self.moderator.announce(
f"三国狼人杀游戏开始!参与者:{format_player_list(self.alive_players)}"
)
print(f"✅ 游戏设置完成,共{len(self.alive_players)}名玩家")
async def werewolf_phase(self, round_num: int):
"""狼人阶段"""
if not self.werewolves:
return None
await self.moderator.announce(f"🐺 狼人请睁眼,选择今晚要击杀的目标...")
# 狼人讨论
async with MsgHub(
self.werewolves,
enable_auto_broadcast=True,
announcement=await self.moderator.announce(
f"狼人们,请讨论今晚的击杀目标。存活玩家:{format_player_list(self.alive_players)}"
),
) as werewolves_hub:
# 讨论阶段
for _ in range(MAX_DISCUSSION_ROUND):
for wolf in self.werewolves:
await wolf(structured_model=DiscussionModelCN)
# 投票击杀
werewolves_hub.set_auto_broadcast(False)
kill_votes = await fanout_pipeline(
self.werewolves,
msg=await self.moderator.announce("请选择击杀目标"),
structured_model=WerewolfKillModelCN,
enable_gather=False,
)
# 统计投票
votes = {}
for i, vote_msg in enumerate(kill_votes):
votes[self.werewolves[i].name] = vote_msg.metadata.get("target")
killed_player, _ = majority_vote_cn(votes)
return killed_player
async def seer_phase(self):
"""预言家阶段"""
if not self.seer:
return
seer_agent = self.seer[0]
await self.moderator.announce("🔮 预言家请睁眼,选择要查验的玩家...")
check_result = await seer_agent(
structured_model=get_seer_model_cn(self.alive_players)
)
target_name = check_result.metadata.get("target")
target_role = self.roles.get(target_name, "村民")
# 告知预言家结果
result_msg = f"查验结果:{target_name}{'狼人' if target_role == '狼人' else '好人'}"
await seer_agent.observe(await self.moderator.announce(result_msg))
async def witch_phase(self, killed_player: str):
"""女巫阶段"""
if not self.witch:
return killed_player, None
witch_agent = self.witch[0]
await self.moderator.announce("🧙‍♀️ 女巫请睁眼...")
# 告知女巫死亡信息
death_info = f"今晚{killed_player}被狼人击杀" if killed_player else "今晚平安无事"
await witch_agent.observe(await self.moderator.announce(death_info))
# 女巫行动
witch_action = await witch_agent(structured_model=WitchActionModelCN)
saved_player = None
poisoned_player = None
if witch_action.metadata.get("use_antidote") and self.witch_has_antidote:
if killed_player:
saved_player = killed_player
self.witch_has_antidote = False
await witch_agent.observe(await self.moderator.announce(f"你使用解药救了{killed_player}"))
if witch_action.metadata.get("use_poison") and self.witch_has_poison:
poisoned_player = witch_action.metadata.get("target_name")
if poisoned_player:
self.witch_has_poison = False
await witch_agent.observe(await self.moderator.announce(f"你使用毒药毒杀了{poisoned_player}"))
# 确定最终死亡玩家
final_killed = killed_player if not saved_player else None
return final_killed, poisoned_player
async def hunter_phase(self, shot_by_hunter: str):
"""猎人阶段"""
if not self.hunter:
return None
hunter_agent = self.hunter[0]
if hunter_agent.name == shot_by_hunter:
await self.moderator.announce("🏹 猎人发动技能,可以带走一名玩家...")
hunter_action = await hunter_agent(
structured_model=get_hunter_model_cn(self.alive_players)
)
if hunter_action.metadata.get("shoot"):
target = hunter_action.metadata.get("target")
await self.moderator.announce(f"猎人{hunter_agent.name}开枪带走了{target}")
return target
return None
def update_alive_players(self, dead_players: List[str]):
"""更新存活玩家列表"""
for dead_name in dead_players:
if dead_name:
# 从存活列表移除
self.alive_players = [p for p in self.alive_players if p.name != dead_name]
# 从各阵营移除
self.werewolves = [p for p in self.werewolves if p.name != dead_name]
self.villagers = [p for p in self.villagers if p.name != dead_name]
self.seer = [p for p in self.seer if p.name != dead_name]
self.witch = [p for p in self.witch if p.name != dead_name]
self.hunter = [p for p in self.hunter if p.name != dead_name]
async def day_phase(self, round_num: int):
"""白天阶段"""
await self.moderator.day_announcement(round_num)
# 讨论阶段
async with MsgHub(
self.alive_players,
enable_auto_broadcast=True,
announcement=await self.moderator.announce(
f"现在开始自由讨论。存活玩家:{format_player_list(self.alive_players)}"
),
) as all_hub:
# 每人发言一轮
await sequential_pipeline(self.alive_players)
# 投票阶段
all_hub.set_auto_broadcast(False)
vote_msgs = await fanout_pipeline(
self.alive_players,
await self.moderator.announce("请投票选择要淘汰的玩家"),
structured_model=get_vote_model_cn(self.alive_players),
enable_gather=False,
)
# 统计投票
votes = {}
for i, vote_msg in enumerate(vote_msgs):
votes[self.alive_players[i].name] = vote_msg.metadata.get("vote")
voted_out, vote_count = majority_vote_cn(votes)
await self.moderator.vote_result_announcement(voted_out, vote_count)
return voted_out
async def run_game(self):
"""运行游戏主循环"""
try:
await self.setup_game()
for round_num in range(1, MAX_GAME_ROUND + 1):
print(f"\n🌙 === 第{round_num}轮游戏开始 ===")
# 夜晚阶段
await self.moderator.night_announcement(round_num)
# 狼人击杀
killed_player = await self.werewolf_phase(round_num)
# 预言家查验
await self.seer_phase()
# 女巫行动
final_killed, poisoned_player = await self.witch_phase(killed_player)
# 更新死亡玩家
night_deaths = [p for p in [final_killed, poisoned_player] if p]
self.update_alive_players(night_deaths)
# 死亡公告
await self.moderator.death_announcement(night_deaths)
# 检查胜利条件
winner = check_winning_cn(self.alive_players, self.roles)
if winner:
await self.moderator.game_over_announcement(winner)
return
# 白天阶段
voted_out = await self.day_phase(round_num)
# 猎人技能
hunter_shot = await self.hunter_phase(voted_out)
# 更新死亡玩家
day_deaths = [p for p in [voted_out, hunter_shot] if p]
self.update_alive_players(day_deaths)
# 检查胜利条件
winner = check_winning_cn(self.alive_players, self.roles)
if winner:
await self.moderator.game_over_announcement(winner)
return
print(f"{round_num}轮结束,存活玩家:{format_player_list(self.alive_players)}")
except Exception as e:
print(f"❌ 游戏运行出错:{e}")
import traceback
traceback.print_exc()
async def main():
"""主函数"""
# 检查环境变量
if "DASHSCOPE_API_KEY" not in os.environ:
print("❌ 请设置环境变量 DASHSCOPE_API_KEY")
return
print("🎮 欢迎来到三国狼人杀!")
# 创建并运行游戏
game = ThreeKingdomsWerewolfGame()
await game.run_game()
if __name__ == "__main__":
asyncio.run(main())