项目地址

https://github.com/Fufffh/AI-Werewolf

项目目标与总体思路

AI 狼人杀,一个完整的可推进游戏系统:

  1. GameService 维护强规则的回合制状态机(白天/夜晚、胜负判断、技能触发、投票结算)。
  2. AIService 把“发言”和“决策”拆开,分别构建SKILL,让模型在不同阶段做不同任务。
  3. GameState 维护公开信息和私有信息(预言家查验、女巫记录、私信),实现视角隔离。
  4. 用前端 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 既保存公共状态,也保存角色私有信息:

  • 公共:roundphaseplayersmessagesvoteswinner
  • 扩展:lastNightKilledlastDayVoted、女巫药剂状态。
  • 私有:seerInspectionswitchActionsprivateMessages

这让“同一份世界状态”可以被不同角色看到不同信息。

游戏引擎:规则与阶段推进

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 采用观察台思路:

  1. 每 5 秒轮询 /api/game/state
  2. 消息分层展示:公开消息 vs 系统/私有消息。
  3. 玩家左右分栏 + 存活态 + 放逐结果 + 胜负状态。

工作流程(前后端 + LLM)

一局游戏里,前后端和 LLM 的协作链路基本是下面这样:

  1. 前端触发动作
    页面按钮(开始、下一阶段、加载)在 App.vue 里通过 axios 请求后端,如 POST /api/game/next
  2. 控制层接收请求
    GameController 接口接收请求后,调用 GameService,自身不做复杂业务逻辑。
  3. 规则引擎推进阶段
    GameService.nextPhase() 根据当前 phase 进入 startDayPhase()startNightPhase(),并在关键节点调用 checkGameEnd()
  4. 进入 AI 决策点
    当流程走到发言、投票、狼刀、查验、女巫用药时,GameService 调用 AIService 对应方法,如 generatePlayerSpeech()decideVoteTargetWithReasoning()
  5. AIService 组装上下文
    AIService 会把角色人格(MD)、人物设定(JSON)、历史消息、当前可见状态拼进 prompt;不同角色看到的信息不同(信息隔离)。
  6. 调用 LLM 接口
    callAIAPI() 按 OpenAI Chat Completions 兼容格式发送请求(model/messages/max_tokens/temperature)到配置的模型服务。
  7. 解析模型输出并做兜底
    后端解析模型返回文本,提取目标玩家、动作类型等;如果解析失败或结果非法,会回退到启发式逻辑,避免流程中断。
  8. 更新状态并持久化
    GameState 更新玩家生死、消息、投票和私有信息,随后落盘到 data/world_state.json
  9. 前端轮询刷新
    前端每 5 秒拉取 GET /api/game/state,把最新阶段、发言、放逐结果和系统消息渲染出来。

数据与角色塑造

项目把“角色规则人格”和“角色个体人格”分开:

  • backend/resources/characters/*.md:职业角色人格(狼人、预言家等)。
  • skills/*.json:具体角色人设(姓名、说话风格、背景等)。

可以在不修改代码,进修改本地文件的情况下进行设定更改。

  1. 运行态:data/world_state.json
    这份 JSON 存的是一局游戏当前进度。每次阶段推进完成,GameService.saveWorldState() 都会把 GameState 序列化到这里。
    里面除了 roundphase,还包含玩家存活状态、消息列表、投票历史、lastNightKilledlastDayVoted、女巫药剂状态,以及预言家查验记录这类私有字段。

  2. 配置态:skills/*.json
    这批 JSON 是“人物设定库”。初始化时每个玩家会绑定一个设定文件路径,后面 AIService.loadCharacterProfile() 会读出内容并拼进 prompt。
    所以想要修改某个角色说话风格,不需要改 Java 逻辑,只改对应 JSON 即可。

  3. 职业人格和个体人格是叠加的
    角色职业规则来自 characters/*.md,个体差异来自 skills/*.json
    同一个“村民”职业,因为人物 JSON 不同,发言语气和策略偏好会明显不一样,实现了人物代入角色。

特点

  1. 规则与生成解耦:引擎判规则,模型做策略,职责明确。
  2. 私有信息系统化建模:不是靠 prompt 临时拼接,而是进状态结构。
  3. 多级回退机制:API 失败不致命,游戏不会中断。
  4. 可观察性强:系统消息、阶段、投票路径都能在前端看到。

游戏截图