main_cn.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  1. # -*- coding: utf-8 -*-
  2. """
  3. 三国狼人杀 - 基于AgentScope的中文版狼人杀游戏
  4. 融合三国演义角色和传统狼人杀玩法
  5. """
  6. import asyncio
  7. import os
  8. import random
  9. from typing import List, Dict, Optional
  10. from agentscope.agent import ReActAgent
  11. from agentscope.model import DashScopeChatModel
  12. from agentscope.pipeline import MsgHub, sequential_pipeline, fanout_pipeline
  13. from agentscope.formatter import DashScopeMultiAgentFormatter
  14. from prompt_cn import ChinesePrompts
  15. from game_roles import GameRoles
  16. from structured_output_cn import (
  17. DiscussionModelCN,
  18. get_vote_model_cn,
  19. WitchActionModelCN,
  20. get_seer_model_cn,
  21. get_hunter_model_cn,
  22. WerewolfKillModelCN
  23. )
  24. from utils_cn import (
  25. check_winning_cn,
  26. majority_vote_cn,
  27. get_chinese_name,
  28. format_player_list,
  29. GameModerator,
  30. MAX_GAME_ROUND,
  31. MAX_DISCUSSION_ROUND,
  32. )
  33. class ThreeKingdomsWerewolfGame:
  34. """三国狼人杀游戏主类"""
  35. def __init__(self):
  36. self.players: Dict[str, ReActAgent] = {}
  37. self.roles: Dict[str, str] = {}
  38. self.moderator = GameModerator()
  39. self.alive_players: List[ReActAgent] = []
  40. self.werewolves: List[ReActAgent] = []
  41. self.villagers: List[ReActAgent] = []
  42. self.seer: List[ReActAgent] = []
  43. self.witch: List[ReActAgent] = []
  44. self.hunter: List[ReActAgent] = []
  45. # 女巫道具状态
  46. self.witch_has_antidote = True
  47. self.witch_has_poison = True
  48. async def create_player(self, role: str, character: str) -> ReActAgent:
  49. """创建具有三国背景的玩家"""
  50. name = get_chinese_name(character)
  51. self.roles[name] = role
  52. agent = ReActAgent(
  53. name=name,
  54. sys_prompt=ChinesePrompts.get_role_prompt(role, character),
  55. model=DashScopeChatModel(
  56. model_name="qwen-max",
  57. api_key=os.environ["DASHSCOPE_API_KEY"],
  58. enable_thinking=True,
  59. ),
  60. formatter=DashScopeMultiAgentFormatter(),
  61. )
  62. # 角色身份确认
  63. await agent.observe(
  64. await self.moderator.announce(
  65. f"【{name}】你在这场三国狼人杀中扮演{GameRoles.get_role_desc(role)},"
  66. f"你的角色是{character}。{GameRoles.get_role_ability(role)}"
  67. )
  68. )
  69. self.players[name] = agent
  70. return agent
  71. async def setup_game(self, player_count: int = 6):
  72. """设置游戏"""
  73. print("🎮 开始设置三国狼人杀游戏...")
  74. # 获取角色配置
  75. roles = GameRoles.get_standard_setup(player_count)
  76. characters = random.sample([
  77. "刘备", "关羽", "张飞", "诸葛亮", "赵云",
  78. "曹操", "司马懿", "周瑜", "孙权"
  79. ], player_count)
  80. # 创建玩家
  81. for i, (role, character) in enumerate(zip(roles, characters)):
  82. agent = await self.create_player(role, character)
  83. self.alive_players.append(agent)
  84. # 分配到对应阵营
  85. if role == "狼人":
  86. self.werewolves.append(agent)
  87. elif role == "预言家":
  88. self.seer.append(agent)
  89. elif role == "女巫":
  90. self.witch.append(agent)
  91. elif role == "猎人":
  92. self.hunter.append(agent)
  93. else:
  94. self.villagers.append(agent)
  95. # 游戏开始公告
  96. await self.moderator.announce(
  97. f"三国狼人杀游戏开始!参与者:{format_player_list(self.alive_players)}"
  98. )
  99. print(f"✅ 游戏设置完成,共{len(self.alive_players)}名玩家")
  100. async def werewolf_phase(self, round_num: int):
  101. """狼人阶段"""
  102. if not self.werewolves:
  103. return None
  104. await self.moderator.announce(f"🐺 狼人请睁眼,选择今晚要击杀的目标...")
  105. # 狼人讨论
  106. async with MsgHub(
  107. self.werewolves,
  108. enable_auto_broadcast=True,
  109. announcement=await self.moderator.announce(
  110. f"狼人们,请讨论今晚的击杀目标。存活玩家:{format_player_list(self.alive_players)}"
  111. ),
  112. ) as werewolves_hub:
  113. # 讨论阶段
  114. for _ in range(MAX_DISCUSSION_ROUND):
  115. for wolf in self.werewolves:
  116. await wolf(structured_model=DiscussionModelCN)
  117. # 投票击杀
  118. werewolves_hub.set_auto_broadcast(False)
  119. kill_votes = await fanout_pipeline(
  120. self.werewolves,
  121. msg=await self.moderator.announce("请选择击杀目标"),
  122. structured_model=WerewolfKillModelCN,
  123. enable_gather=False,
  124. )
  125. # 统计投票
  126. votes = {}
  127. for i, vote_msg in enumerate(kill_votes):
  128. # 检查vote_msg是否为None或metadata是否存在
  129. if vote_msg is not None and hasattr(vote_msg, 'metadata') and vote_msg.metadata is not None:
  130. votes[self.werewolves[i].name] = vote_msg.metadata.get("target")
  131. else:
  132. # 如果返回无效,随机选择一个目标
  133. print(f"⚠️ {self.werewolves[i].name} 的击杀投票无效,随机选择目标")
  134. import random
  135. valid_targets = [p.name for p in self.alive_players if p.name not in [w.name for w in self.werewolves]]
  136. votes[self.werewolves[i].name] = random.choice(valid_targets) if valid_targets else None
  137. killed_player, _ = majority_vote_cn(votes)
  138. return killed_player
  139. async def seer_phase(self):
  140. """预言家阶段"""
  141. if not self.seer:
  142. return
  143. seer_agent = self.seer[0]
  144. await self.moderator.announce("🔮 预言家请睁眼,选择要查验的玩家...")
  145. check_result = await seer_agent(
  146. structured_model=get_seer_model_cn(self.alive_players)
  147. )
  148. # 检查返回结果是否有效
  149. if check_result is None or not hasattr(check_result, 'metadata') or check_result.metadata is None:
  150. print(f"⚠️ 预言家查验失败,跳过此阶段")
  151. return
  152. target_name = check_result.metadata.get("target")
  153. if not target_name:
  154. print(f"⚠️ 预言家未选择查验目标,跳过此阶段")
  155. return
  156. target_role = self.roles.get(target_name, "村民")
  157. # 告知预言家结果
  158. result_msg = f"查验结果:{target_name}是{'狼人' if target_role == '狼人' else '好人'}"
  159. await seer_agent.observe(await self.moderator.announce(result_msg))
  160. async def witch_phase(self, killed_player: str):
  161. """女巫阶段"""
  162. if not self.witch:
  163. return killed_player, None
  164. witch_agent = self.witch[0]
  165. await self.moderator.announce("🧙‍♀️ 女巫请睁眼...")
  166. # 告知女巫死亡信息
  167. death_info = f"今晚{killed_player}被狼人击杀" if killed_player else "今晚平安无事"
  168. await witch_agent.observe(await self.moderator.announce(death_info))
  169. # 女巫行动
  170. witch_action = await witch_agent(structured_model=WitchActionModelCN)
  171. saved_player = None
  172. poisoned_player = None
  173. # 检查返回结果是否有效
  174. if witch_action is None or not hasattr(witch_action, 'metadata') or witch_action.metadata is None:
  175. print(f"⚠️ 女巫行动失败,视为不使用技能")
  176. else:
  177. if witch_action.metadata.get("use_antidote") and self.witch_has_antidote:
  178. if killed_player:
  179. saved_player = killed_player
  180. self.witch_has_antidote = False
  181. await witch_agent.observe(await self.moderator.announce(f"你使用解药救了{killed_player}"))
  182. if witch_action.metadata.get("use_poison") and self.witch_has_poison:
  183. poisoned_player = witch_action.metadata.get("target_name")
  184. if poisoned_player:
  185. self.witch_has_poison = False
  186. await witch_agent.observe(await self.moderator.announce(f"你使用毒药毒杀了{poisoned_player}"))
  187. # 确定最终死亡玩家
  188. final_killed = killed_player if not saved_player else None
  189. return final_killed, poisoned_player
  190. async def hunter_phase(self, shot_by_hunter: str):
  191. """猎人阶段"""
  192. if not self.hunter:
  193. return None
  194. hunter_agent = self.hunter[0]
  195. if hunter_agent.name == shot_by_hunter:
  196. await self.moderator.announce("🏹 猎人发动技能,可以带走一名玩家...")
  197. hunter_action = await hunter_agent(
  198. structured_model=get_hunter_model_cn(self.alive_players)
  199. )
  200. # 检查返回结果是否有效
  201. if hunter_action is None or not hasattr(hunter_action, 'metadata') or hunter_action.metadata is None:
  202. print(f"⚠️ 猎人技能使用失败,视为放弃开枪")
  203. return None
  204. if hunter_action.metadata.get("shoot"):
  205. target = hunter_action.metadata.get("target")
  206. if target:
  207. await self.moderator.announce(f"猎人{hunter_agent.name}开枪带走了{target}")
  208. return target
  209. else:
  210. print(f"⚠️ 猎人选择开枪但未指定目标,视为放弃")
  211. return None
  212. return None
  213. def update_alive_players(self, dead_players: List[str]):
  214. """更新存活玩家列表"""
  215. for dead_name in dead_players:
  216. if dead_name:
  217. # 从存活列表移除
  218. self.alive_players = [p for p in self.alive_players if p.name != dead_name]
  219. # 从各阵营移除
  220. self.werewolves = [p for p in self.werewolves if p.name != dead_name]
  221. self.villagers = [p for p in self.villagers if p.name != dead_name]
  222. self.seer = [p for p in self.seer if p.name != dead_name]
  223. self.witch = [p for p in self.witch if p.name != dead_name]
  224. self.hunter = [p for p in self.hunter if p.name != dead_name]
  225. async def day_phase(self, round_num: int):
  226. """白天阶段"""
  227. await self.moderator.day_announcement(round_num)
  228. # 讨论阶段
  229. async with MsgHub(
  230. self.alive_players,
  231. enable_auto_broadcast=True,
  232. announcement=await self.moderator.announce(
  233. f"现在开始自由讨论。存活玩家:{format_player_list(self.alive_players)}"
  234. ),
  235. ) as all_hub:
  236. # 每人发言一轮
  237. await sequential_pipeline(self.alive_players)
  238. # 投票阶段
  239. all_hub.set_auto_broadcast(False)
  240. vote_msgs = await fanout_pipeline(
  241. self.alive_players,
  242. await self.moderator.announce("请投票选择要淘汰的玩家"),
  243. structured_model=get_vote_model_cn(self.alive_players),
  244. enable_gather=False,
  245. )
  246. # 统计投票
  247. votes = {}
  248. for i, vote_msg in enumerate(vote_msgs):
  249. # 检查vote_msg是否为None或metadata是否存在
  250. if vote_msg is not None and hasattr(vote_msg, 'metadata') and vote_msg.metadata is not None:
  251. votes[self.alive_players[i].name] = vote_msg.metadata.get("vote")
  252. else:
  253. # 如果返回无效,默认弃票
  254. print(f"⚠️ {self.alive_players[i].name} 的投票无效,视为弃票")
  255. votes[self.alive_players[i].name] = None
  256. voted_out, vote_count = majority_vote_cn(votes)
  257. await self.moderator.vote_result_announcement(voted_out, vote_count)
  258. return voted_out
  259. async def run_game(self):
  260. """运行游戏主循环"""
  261. try:
  262. await self.setup_game()
  263. for round_num in range(1, MAX_GAME_ROUND + 1):
  264. print(f"\n🌙 === 第{round_num}轮游戏开始 ===")
  265. # 夜晚阶段
  266. await self.moderator.night_announcement(round_num)
  267. # 狼人击杀
  268. killed_player = await self.werewolf_phase(round_num)
  269. # 预言家查验
  270. await self.seer_phase()
  271. # 女巫行动
  272. final_killed, poisoned_player = await self.witch_phase(killed_player)
  273. # 更新死亡玩家
  274. night_deaths = [p for p in [final_killed, poisoned_player] if p]
  275. self.update_alive_players(night_deaths)
  276. # 死亡公告
  277. await self.moderator.death_announcement(night_deaths)
  278. # 检查胜利条件
  279. winner = check_winning_cn(self.alive_players, self.roles)
  280. if winner:
  281. await self.moderator.game_over_announcement(winner)
  282. return
  283. # 白天阶段
  284. voted_out = await self.day_phase(round_num)
  285. # 猎人技能
  286. hunter_shot = await self.hunter_phase(voted_out)
  287. # 更新死亡玩家
  288. day_deaths = [p for p in [voted_out, hunter_shot] if p]
  289. self.update_alive_players(day_deaths)
  290. # 检查胜利条件
  291. winner = check_winning_cn(self.alive_players, self.roles)
  292. if winner:
  293. await self.moderator.game_over_announcement(winner)
  294. return
  295. print(f"第{round_num}轮结束,存活玩家:{format_player_list(self.alive_players)}")
  296. except Exception as e:
  297. print(f"❌ 游戏运行出错:{e}")
  298. import traceback
  299. traceback.print_exc()
  300. async def main():
  301. """主函数"""
  302. # 检查环境变量
  303. if "DASHSCOPE_API_KEY" not in os.environ:
  304. print("❌ 请设置环境变量 DASHSCOPE_API_KEY")
  305. return
  306. print("🎮 欢迎来到三国狼人杀!")
  307. # 创建并运行游戏
  308. game = ThreeKingdomsWerewolfGame()
  309. await game.run_game()
  310. if __name__ == "__main__":
  311. asyncio.run(main())