AI狼人杀
项目地址
https://github.com/Fufffh/AI-Werewolf
项目目标与总体思路
AI 狼人杀,一个完整的可推进游戏系统:
- 用
GameService维护强规则的回合制状态机(白天/夜晚、胜负判断、技能触发、投票结算)。 - 用
AIService把“发言”和“决策”拆开,分别构建SKILL,让模型在不同阶段做不同任务。 - 用
GameState维护公开信息和私有信息(预言家查验、女巫记录、私信),实现视角隔离。 - 用前端
Vue3 + Axios做“上帝视角”观察面板,持续轮询后端展示进展。
后端架构(SpringBoot)
1. 控制层:GameController
REST 接口很清晰,核心是:
POST /api/game/start:初始化新对局。POST /api/game/next:推进到下一阶段。GET /api/game/state:获取完整状态。GET /api/game/messages:读取消息。POST /api/game/load:从data/world_state.json恢复。POST /api/game/restart:重开。
接口保持薄,业务全部下沉到 service,便于后续换 UI 或做自动化测试。
2. 状态模型:GameState
这里对信息进行分离,如果不进行分离,那么系统信息(预言家查验结果,女巫用药结果)会被AI Agent读取,进而导致特殊信息公开,所以对信息进行分离:
GameState 既保存公共状态,也保存角色私有信息:
- 公共:
round、phase、players、messages、votes、winner。 - 扩展:
lastNightKilled、lastDayVoted、女巫药剂状态。 - 私有:
seerInspections、witchActions、privateMessages。
这让“同一份世界状态”可以被不同角色看到不同信息。
游戏引擎:规则与阶段推进
GameService 是项目核心。
1. 初始化
初始化时固定 10 人模板(3 狼 + 4 民 + 预言家 + 女巫 + 猎人),随机分配角色,再加载 10 份人物 JSON 设定。
2. 时序
阶段推进是:
startDayPhase():白天公告、首日自我介绍、讨论、(非首日)投票。startNightPhase():狼人行动、预言家查验、女巫决策。nextPhase():在 day/night 间切换并在夜转昼时增加天数。
考虑到AI幻觉问题,很多AI会在第一天白天(未进行预言家查验、女巫用药等操作)就自爆自己,比如第一天白天就会有人跳预言家说自己查验了谁谁谁,故添加首日约束。
首日约束:第一天只做“自我介绍 + 讨论”,不放逐。
3. 投票与平票
投票先走“AI 推理目标”,失败再回退到启发式目标。若平票会触发二轮流程:平票玩家再发言、全员重投;二轮仍平则无人出局。
4. 终局判断
终局条件是标准狼人杀:
- 场上无狼:好人胜。
- 狼人数 >= 好人数:狼人胜。
并且在关键节点提前检查,避免“已经结束还继续投票”的流程错误。
AI Service设计
1. 能力拆分
- 发言:
generatePlayerSpeech()。 - 投票:
decideVoteTargetWithReasoning()。 - 狼刀:
decideWerewolfTarget()。 - 查验:
decideSeerInspection()。 - 女巫:
decideWitchAction()。 - 首日自介:
generateSelfIntroduction()。
每类行为都有独立 prompt 模板,避免一个混合 prompt 负责所有任务导致漂移。
2. 视角隔离
buildGameStatusDescriptionForPlayer() + getDisplayRoleForPlayer() 会按“观察者身份”裁剪状态:
- 狼人能看到狼队友。
- 预言家能看到自己查验过的阵营结果。
- 普通玩家只能看到有限公开信息。
3. 提示词工程
提示词不仅喂“最近消息”,还构建全局统计上下文(发言数、投票行为、可疑度分)。
此外有首日硬约束:禁止引用不存在的昨夜信息,禁止首日乱跳身份,显著降低 LLM 幻觉产生的问题。
4. API 调用与鲁棒性
模型调用兼容 OpenAI Chat Completions 结构:model + messages + temperature + max_tokens。失败后会启用 fallback(默认发言/启发式决策),保证游戏可继续推进。
不推荐Deepseek,因为上下文过多时Deppseek会忽略部分信息,导致Agent决策随机性变大。
前端实现(Vue3)
App.vue 采用观察台思路:
- 每 5 秒轮询
/api/game/state。 - 消息分层展示:公开消息 vs 系统/私有消息。
- 玩家左右分栏 + 存活态 + 放逐结果 + 胜负状态。
工作流程(前后端 + LLM)
一局游戏里,前后端和 LLM 的协作链路基本是下面这样:
- 前端触发动作
页面按钮(开始、下一阶段、加载)在App.vue里通过axios请求后端,如POST /api/game/next。 - 控制层接收请求
GameController接口接收请求后,调用GameService,自身不做复杂业务逻辑。 - 规则引擎推进阶段
GameService.nextPhase()根据当前phase进入startDayPhase()或startNightPhase(),并在关键节点调用checkGameEnd()。 - 进入 AI 决策点
当流程走到发言、投票、狼刀、查验、女巫用药时,GameService调用AIService对应方法,如generatePlayerSpeech()、decideVoteTargetWithReasoning()。 - AIService 组装上下文
AIService会把角色人格(MD)、人物设定(JSON)、历史消息、当前可见状态拼进 prompt;不同角色看到的信息不同(信息隔离)。 - 调用 LLM 接口
callAIAPI()按 OpenAI Chat Completions 兼容格式发送请求(model/messages/max_tokens/temperature)到配置的模型服务。 - 解析模型输出并做兜底
后端解析模型返回文本,提取目标玩家、动作类型等;如果解析失败或结果非法,会回退到启发式逻辑,避免流程中断。 - 更新状态并持久化
GameState更新玩家生死、消息、投票和私有信息,随后落盘到data/world_state.json。 - 前端轮询刷新
前端每 5 秒拉取GET /api/game/state,把最新阶段、发言、放逐结果和系统消息渲染出来。
数据与角色塑造
项目把“角色规则人格”和“角色个体人格”分开:
backend/resources/characters/*.md:职业角色人格(狼人、预言家等)。skills/*.json:具体角色人设(姓名、说话风格、背景等)。
可以在不修改代码,进修改本地文件的情况下进行设定更改。
-
运行态:
data/world_state.json
这份 JSON 存的是一局游戏当前进度。每次阶段推进完成,GameService.saveWorldState()都会把GameState序列化到这里。
里面除了round、phase,还包含玩家存活状态、消息列表、投票历史、lastNightKilled、lastDayVoted、女巫药剂状态,以及预言家查验记录这类私有字段。 -
配置态:
skills/*.json
这批 JSON 是“人物设定库”。初始化时每个玩家会绑定一个设定文件路径,后面AIService.loadCharacterProfile()会读出内容并拼进 prompt。
所以想要修改某个角色说话风格,不需要改 Java 逻辑,只改对应 JSON 即可。 -
职业人格和个体人格是叠加的
角色职业规则来自characters/*.md,个体差异来自skills/*.json。
同一个“村民”职业,因为人物 JSON 不同,发言语气和策略偏好会明显不一样,实现了人物代入角色。
特点
- 规则与生成解耦:引擎判规则,模型做策略,职责明确。
- 私有信息系统化建模:不是靠 prompt 临时拼接,而是进状态结构。
- 多级回退机制:API 失败不致命,游戏不会中断。
- 可观察性强:系统消息、阶段、投票路径都能在前端看到。
游戏截图

