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

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# -*- 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())