ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

狼人杀高手差距不在口才:状态管理驱动的智能体决策系统

狼人杀高手差距不在口才:状态管理驱动的智能体决策系统 一个人玩狼人杀最大的幻觉是觉得输赢取决于口才。实际上高手和普通玩家的差距更像软件工程里的状态管理差距。普通玩家记不住警徽流记不住第二天谁投了谁记不住女巫的解药是否已经用掉高手则会在脑内持续维护一张“当前世界状态表”谁发了言谁跳过身份谁在关键轮次投了谁谁之前的话和刚才的行动相互矛盾。如果某天一觉醒来全球狼人杀水平下降100倍你可以想象所有人突然集体丢掉的并不是表达能力而是对信息状态的管理能力。好人忘了自己昨天验过谁狼人忘了自己刀过谁发言里全是“我感觉你是狼”却交代不出任何依据投票像随机数一样乱飞。在这种世界里一个人如果仍然能记住自己投过谁、能事后复现自己的判断路径他几乎不需要更高级的话术就可以获得巨大优势。这篇文章不打算做社会学推论而是把这个脑洞当作一个系统实验来用当人类玩家各自的记忆、推理和决策能力同步退化哪些机制还能稳定保持水平。“状态外置”的游戏智能体是一种很自然的答案。下面会把狼人杀里高水平玩家的判断方式拆成状态模型、记忆库、推理模块和决策模块用一套可运行的最小 Python 示例说明。所谓“全球水平下降 100 倍”的世界在这套示例里会被模拟成三类退化玩家的集合。1. 狼人杀真正的门槛不在话术而在状态管理1.1 一觉醒来水平下降的玩家到底丢了什么狼人杀的高水平表现通常被描述成“会抿人”“口才好”“心理素质好”。但这些描述不可复现也不适合训练。若用一个软件工程的视角去看真正让一个玩家稳定的是几个被低估的基础能力记录能力。能记住每天晚上谁被刀、谁被救、谁被查验以及谁在什么轮次投了谁。回溯能力。当场上出现新的冲突信息时能快速回到之前某轮判断里找到矛盾和依据。建模能力。能在脑海里维护一张阵营概率表而不是把所有发言都当成平等的“感觉”。策略能力。投票时考虑的是行动后果而不是单纯表态度。抗干扰能力。面对强逻辑或强情绪施压时仍能按既有推理路径决策。“全球水平下降 100 倍”在这个框架里不是形容词而是一张精确的能力缺失清单。高水平玩家依赖的能力水平下降后的表现在技术上对应什么身份记录忘记自己验过谁警徽流断裂持久化存储与状态更新发言记忆对同一对象前后判断反复横跳事件记录与回放投票轨迹连上一轮自己投了谁都说不清操作日志矛盾追踪听到新发言后被带偏冲突检测和证据链阵营概率估计凭第一印象做最终决策置信度评分模型收益计算只投“最讨厌的人”而忽略轮次决策函数与效用评估一个连自己用没用过解药都记不住的女巫和一个每晚都把用药情况写入日志的女巫在十局游戏里的稳定性完全不同。这不是天赋差异而是状态管理差异。1.2 把高水平玩家拆成一套决策管线把高手行为拆开看会发现每轮决策背后都有一条相似的管线读取当前局面今天第几天轮到谁发言哪些人还活着。更新私人信息自己夜间行动的结果、技能使用情况、他人发言中新增的信息。抽取他人声明谁跳了预言家谁报了自己的身份谁暗示自己有刀。形成候选假设在这些声明之间寻找互相矛盾或互相支持的关系。更新阵营置信度对“某人是狼”的概率进行加减分。选择本次行动决定说哪段话决定投票给谁。留下行动痕迹让自己下一轮还能回溯这次判断的依据。这条管线里的每个环节都可以变成独立的程序模块。真正困难的不是设计某一条精巧发言而是让这些模块在几十个时间片段里保持连贯不出现记忆断层和逻辑裂缝。这正好是一个游戏智能体能稳定超越“退化人类”的地方。1.3 规则智能体为什么能保持稳定下限很多人以为智能体在狼人杀里必须接大模型能生成自然发言才算“有水平”。但如果目标是抵抗“全球水平下降 100 倍”决定稳定性的关键其实是状态管理能力而不是语言生成能力。一个不接大模型的规则智能体只要做到以下几点就已经能超过大量退化玩家它不会忘记自己昨天投过谁。它不会忘记预言家已经给出过哪几个结果。它不会因为被情绪攻击就临时改票。它的每一条判断都可以通过日志被重新检查。这里要强调一个边界规则智能体稳定的只是下限不是上限。它没有超自然上帝视角依然只能基于公共发言和自身身份信息决策。真正让它有效的是把高手的“脑内持久层”搬到了代码里让判断路径可以被复现、被回滚、被解释。2. 先建一个最小可运行的世界模型在写任何推理逻辑之前先让程序理解狼人杀的一轮发生了什么。如果不做这层基础后面无论是规则智能体还是 LLM 智能体都会变成没有记忆的聊天机器人。为便于演示下面的示例采用一套简化的教学规则4 名玩家1 狼人、1 预言家、2 普通村民。夜晚狼人可以刀人预言家可以查验一名存活玩家白天所有存活玩家发言并投票狼人被投出则好人阵营获胜。正式线上游戏必须按官方规则和房间配置扩展这个简化模型只用于理解状态管理架构。2.1 玩家、角色与游戏状态结构使用 Python 3.10 的 dataclass 描述玩家和游戏状态。from dataclasses import dataclass, field from enum import Enum from typing import Optional class Role(Enum): WEREWOLF werewolf SEER seer VILLAGER villager dataclass class Player: pid: int name: str role: Optional[Role] None alive: bool True property def is_wolf(self) - bool: return self.role Role.WEREWOLF dataclass class GameState: day: int 1 phase: str discuss players: dict[int, Player] field(default_factorydict) alive_pids: list[int] field(default_factorylist) last_night_deaths: list[int] field(default_factorylist) last_day_vote: Optional[int] None over: bool False这里的GameState是整个智能体共享的“世界真相”但它并不代表每个玩家都知道一切。每个智能体只能看到自己视角下的状态自己是不是狼人、自己夜间行动的结果、白天别人公开说的话。真正的智能体必须维护一份“我的视角”而不是直接读全局状态。字段含义需要特别关注的点day当前第几天轮次决定很多策略例如是否聊“轮次杀”phasediscuss / vote / night / end不同阶段只能执行不同动作players玩家字典角色字段在真实对局中不应全局可见alive_pids存活玩家 ID 列表决策时必须基于该列表过滤候选last_night_deaths上一晚死亡对象女巫救人与否会影响白天推理last_day_vote上一轮白天被投出的对象投票轨迹是重要证据在完整工程里需要负责仲裁游戏的服务端去维护这份全局状态再按每个玩家的可见范围派发事件。学习阶段可以用一份全局状态做单机调试但不能把这种做法直接搬到多人网络对战里。2.2 事件记录与记忆库游戏进程由事件推动。不要只存“最终状态”要把每一条动作按时间顺序记录下来这样智能体才能完成回溯和矛盾检测。dataclass class GameEvent: day: int phase: str # speech / vote / night / end source_pid: int event_type: str # speech / vote / check / kill content: dict created_at: int一个简单记忆库只需要做三件事追加事件、按玩家筛选、按轮次筛选。class MemoryStore: def __init__(self) - None: self.events: list[GameEvent] [] def append(self, event: GameEvent) - None: self.events.append(event) def by_player(self, pid: int) - list[GameEvent]: return [e for e in self.events if e.source_pid pid] def by_day(self, day: int) - list[GameEvent]: return [e for e in self.events if e.day day] def last_event_of_type(self, pid: int, event_type: str) - Optional[GameEvent]: for e in reversed(self.events): if e.source_pid pid and e.event_type event_type: return e return None实际项目里不会无限把事件堆在内存里而是要配合数据库或日志系统做持久化。更重要的是这里的“事件流”设计让一个智能体在第三天时还能回放第一天夜间发生过的查验事件这一点已经超越了大多数在脑内推理的真人玩家。2.3 明确简化边界这套示例不会实现完整狼人杀规则所以在落地前要明确简化边界只保留狼人、预言家、村民三种角色不包含女巫的救人和毒药、猎人开枪、守卫守护、骑士决斗等技能。夜间行动顺序简化为“狼人先刀预言家后查”。白天发言按固定顺序广播不处理插麦和警长竞选。票型结果作为事件写入记忆库但不处理平票 PK。胜负判断简化为狼人被投出则好人胜利狼刀光所有好人则狼人胜利。简化不是为了自欺欺人而是为了先验证最困难的部分当一个智能体只有少量证据时它如何保持推理连续。如果这套骨架在 4 人简化局里都跑不通接上 12 人板和大量角色技能后只会更不可控。3. 把推理过程拆成四个可编码模块高水平玩家的临场判断拆到代码层通常可以分成四个模块信息抽取、置信度更新、矛盾检测、投票决策。它们不负责生成漂亮的发言只负责让智能体“想得清楚”。3.1 信息抽取从发言中抽出身份声称和查验结果智能体每天能听到大量自然语言发言。第一步是把发言里的核心声明抽出来例如“我昨晚验了 2 号他是个狼”“我是女巫昨晚救了自己”。在原型阶段可以用正则表达式完成最粗粒度的抽取。import re from typing import Optional CLAIM_PATTERNS { Role.SEER: r(我|本人的底牌|我身份是)\s*(预言家)|我跳预言家, } def extract_seer_claim(speech: str) - bool: return bool(re.search(CLAIM_PATTERNS[Role.SEER], speech)) def extract_check_result(speech: str) - Optional[dict]: # 简单示例覆盖 “查验了 X 号X 是狼 / X 是好人” pattern re.compile(r查(验|过)?\s*(\d)\s*号?.*(狼|好人|金水|查杀)) match pattern.search(speech) if not match: return None target int(match.group(2)) result wolf if match.group(3) in (狼, 查杀) else good return {target: target, result: result}真实对局里正则非常不可靠因为玩家发言可能绕圈子、可能阴阳怪气、可能在被抗推时才半挡身份。工程上更合理的做法是先让大模型做信息抽取把输出约束成 JSON 结构再由规则层校验。这样既利用了语言理解能力又不会让大模型直接做阵营决策。3.2 置信度更新不追求严格贝叶斯但要保留贝叶斯直觉严格的贝叶斯推理需要维护所有玩家在全部角色组合上的联合分布这对一个夜间游戏而言计算复杂度过高。但在策略层面完全可以用“嫌疑分”来表达同样直觉预言家给出查杀命中目标的狼嫌疑大幅上升。预言家给出金水目标对象的狼嫌疑明显下降。某人宣称自己是预言家但如果当天对跳结果解释不清逻辑分下降。某人发言前后矛盾狼嫌疑上升。可以用一个简化的更新函数演示。class BeliefSystem: def __init__(self, alive_pids: list[int]) - None: # 初始狼嫌疑设置为 0.5代表未知 self.wolf_score {pid: 0.5 for pid in alive_pids} def add_check(self, target: int, result: str, confidence: float 0.9) - None: if result wolf: # 查杀提高目标嫌疑其余存活者少量下调 self.wolf_score[target] 0.5 * confidence else: # 金水降低目标嫌疑 self.wolf_score[target] - 0.5 * confidence self._clamp() def add_contradiction(self, pid: int, weight: float 0.3) - None: self.wolf_score[pid] weight self._clamp() def _clamp(self) - None: for pid in self.wolf_score: self.wolf_score[pid] min(1.0, max(0.0, self.wolf_score[pid]))这里强调一个非常容易错的地方置信度更新的前提是“信息来源的可信度”。如果宣布查验结果的人本身还没被证实是否真是预言家就不该把这个结果当成确定证据。更合理的做法是分别维护“这条信息的来源可信度”和“这条信息带来的阵营变化”先对来源做折扣再更新最终嫌疑。3.3 矛盾检测言行不一致是高价值特征狼人杀里最有信息量的往往是矛盾点一个玩家在第二天说“我听 3 号是好人”第三天却带头把 3 号投了出去中间没有给出任何新的查验或逻辑变化这种行为就需要被记录。在代码里最朴素的矛盾检测是身份声称变化检测。class ContradictionDetector: def __init__(self) - None: self.role_declared: dict[int, Optional[Role]] {} def observe_claim(self, pid: int, claimed_role: Optional[Role]): if claimed_role is None: return previous self.role_declared.get(pid) if previous is None: self.role_declared[pid] claimed_role return if previous ! claimed_role: # 同一局内声称自己底牌由 A 变为 B通常视为高风险逻辑 print(fplayer {pid}: claimed {previous} then {claimed_role}) def check_vote_consistency(self, records, pid: int, target: int) - bool: # 简单检查最近是否表达过 target 是金水却今天就投 target pass这段代码会误伤所有“藏身份”的策略玩家。真实狼人杀里一个平民可能一开始说自己是民被抗推到生死局时跳猎人求自保这在规则上本身问题很大但一个女巫可能为了藏药物第一轮不跳第二轮才跳身份。所以要区分两种声明“我现在的底牌是什么”这种显式身份声明改变必须给出强解释。“我暂时不想谈身份不要把我扛推出局”这种防守性表达不等同于固定底牌声明。因此真正落地时要在信息抽取层就明确字段语义不能把所有“我是女巫”的发言都无条件当成底牌承诺。3.4 投票决策从“谁最差”到“我做出这个选择的收益最大”很多新手投票的逻辑是“我觉得谁最像狼就投谁”但高手的投票会同时考虑投出这个人的好处是什么。如果他是好人并带走关键神职对轮次的损失有多大。这一票投出去后自己在场上暴露了哪些信息。如果不投 A还有什么备选方案能获得更长时间验证。可编码的简化公式如下def vote_score(belief_system, pid: int) - float: score belief_system.wolf_score[pid] # 来自预言家查验结果的权重 if pid in confirmed_good: score * 0.1 if pid in seer_check_wolf: score 0.8 # 来自矛盾的额外权重 if pid in contradiction_pids: score 0.3 return score def decide_vote(alive_pids, own_pid) - Optional[int]: candidates [pid for pid in alive_pids if pid ! own_pid] if not candidates: return None return max(candidates, keyvote_score)不能把票型做成纯模版。真实产品里还需要加入“平票 PK”“警徽流”“避免第一天票出神职”“狼队冲票”等场景这些都不是单一行数能解决的。4. 先用规则智能体跑通最小决策闭环在接入大模型之前应该先做一个不依赖任何外部能力的规则智能体。它能用一套简单的记忆和嫌疑分机制完成整局游戏验证状态模型与事件记录是否够用。4.1 智能体接口与调用顺序为了让规则智能体和之后的 LLM 智能体能互相替换先定义统一接口。class GameAgent: def __init__(self, pid: int, name: str) - None: self.pid pid self.name name self.memory MemoryStore() def observe(self, event: GameEvent) - None: self.memory.append(event) def night_action(self, state: GameState) - dict: raise NotImplementedError def daytime_speech(self, state: GameState) - str: raise NotImplementedError def vote_target(self, state: GameState, candidates) - Optional[int]: raise NotImplementedError引擎每产生一个事件都会调用所有存活代理的observe保证它们在决策前拿到完整事件流。这个“先同步事件再要求行动”的顺序很重要它可以避免智能体错过夜间信息也能统一调试日志。4.2 一个只靠查验记忆的预言家智能体下面这条规则智能体刻意写得“笨”它只会做四件确定的事保留预言家查验结果。发言时优先声明自己身份并报告夜间查验。如果自己被查杀或遭遇强踩则继续发验证结果。投票时以查验结果和基础嫌疑分为依据。class SeerRuleAgent(GameAgent): def __init__(self, pid: int, name: str) - None: super().__init__(pid, name) self.check_results: list[dict] [] def observe(self, event: GameEvent) - None: super().observe(event) if event.event_type check and event.source_pid self.pid: self.check_results.append(event.content) def daytime_speech(self, state: GameState) - str: if not self.check_results: return f{self.pid}号发言我是一张预言家牌昨晚没有验人先听发言。 last self.check_results[-1] result 狼人 if last[result] wolf else 好人 return f{self.pid}号发言我是预言家昨晚查验了{last[target]}号结果是{result}。 def vote_target(self, state: GameState, candidates) - Optional[int]: wolved_pids [ r[target] for r in self.check_results if r[result] wolf ] for pid in candidates: if pid in wolved_pids: return pid return candidates[0] if candidates else None这个智能体不会理解复杂心理博弈也不会组织特别强的逻辑但它的判断不会因为争论而漂移。在多智能体测试里它适合做“稳定样本”而不是“最强玩家”。4.3 先跑规则智能体的几个理由可测试。规则逻辑是确定性的同样的输入必定得到同样的输出问题容易复现。能快速验证状态模型。如果事件流缺了投票记录规则智能体跑几局就会暴露问题。不会产生幻觉。它不会说出自己并未掌握的查验结果因此行为可信度高。能作为 LLM 智能体的兜底。当大模型输出格式异常或决策不可用时可以让规则模块接管夜间行动和投票避免整局崩溃。接大模型并不是必须的第一步。一个没有大模型的稳定底座在后续接入语言能力时会轻松很多。5. 在规则骨架外接 LLM负责“说话”而不是负责“判断”只有规则智能体的狼人杀很呆它不会撒谎不会演不会组织有感染力的逻辑。要解决表达问题最常见的方式是接入大语言模型。但大模型在狼人杀里最危险的地方不是它说话不够自然而是它会为了补齐故事而“编造事实”明明没有查验过 3 号却说出“我昨晚查验了 3 号”明明 5 号已经出局却投给 5 号。所以架构上必须坚持一个原则LLM 只负责表达不负责事实生成。5.1 两段式架构决策与表达分离更安全的做法是先让规则系统计算“当前应该做出的行动”再把行动约束放进提示词让 LLM 生成合适的发言。def decide_and_speak(agent, state) - str: # 决策层产生结构化计划 action_plan { claim: seer, should_report: True, check_result: last_check, vote_target: agent.vote_target(state, state.alive_pids) } # 表达层LLM 只负责把决策翻译成自然语言 messages build_messages(state, action_plan, agent.role) speech call_llm
返回列表