在桌面游戏的世界里,规则的复杂性和玩家人数的限制常常成为阻碍体验的门槛。为了解决这一痛点,我们设计了 Imaginary Friend,旨在通过大语言模型的强大理解与推理能力,打造一个能够自动解析规则书、主持游戏并模拟对手的智能跨桌游通用 Agent。
本文将深入剖析项目的核心架构、技术亮点以及其带来的创新体验。

# 一、项目定位与核心理念
我曾在年初的文章(做了个桌游:时空拍卖行)的结尾这么写:
这个桌游会保持开源,如果任何人想玩的话,只需要打印出来,召集朋友(最难的一步)就能组起来一局。
现在,你就算没有朋友也能把这个游戏玩起来了。
Imaginary Friend 是一个基于 Python 和 FastAPI 构建的单人桌游 AI 对战系统。其核心设计理念可以概括为:"代码只做存储,GM 决定一切"。
传统的电子化桌游往往需要开发者为每一款游戏硬编码状态机、规则逻辑和卡牌效果。而 Imaginary Friend 采用了一种截然不同的文档驱动架构。用户只需上传一份 PDF、DOCX 或 Markdown 格式的规则书,系统便能自动解析规则,生成游戏定义,并由一个中央 GM Agent 作为 "大脑" 负责所有的流程推进和规则裁定。底层代码仅提供原子化的数据存储和操作工具,极大地提升了系统的通用性和扩展性。
# 二、技术模块选型设计
# 模块一:状态存储层
在最新的架构重构中,项目彻底摒弃了硬编码的强类型数据模型,转而采用基于 TinyDB 的文档型数据库。这种设计使得系统不再预设任何游戏特定的字段,而是通过四个核心表来管理所有状态。
| 表名 | 作用说明 |
|---|---|
| global | 存储全局变量,如当前回合、当前阶段、公共资源池等。通常只包含一个文档。 |
| players | 存储玩家实体,包含其私有状态(如金币、手牌、得分等)。每个玩家一个文档。 |
| zones | 存储游戏区域和对象容器(如牌库、弃牌堆、拍卖区等)。 |
| logs | 存储不可变的结构化游戏日志,用于追溯和展示。 |
这种灵活的 JSON 文档型状态管理,使得系统能够轻松适配任意桌游的数据结构,真正实现了 "通用" 的目标。值得一提的是,DocStore 还实现了 snapshot_for_player 方法,在向前端广播状态时,会自动过滤其他玩家的 hand 和 private_ 前缀字段,保证了信息隔离。
def snapshot_for_player(self, player_id: str) -> dict: | |
result = self.snapshot() | |
filtered_players = [] | |
for p in result.get("players", []): | |
if p.get("_id") == player_id: | |
filtered_players.append(p) | |
else: | |
public = {k: v for k, v in p.items() | |
if not k.startswith("private_") and k != "hand"} | |
filtered_players.append(public) | |
result["players"] = filtered_players | |
return result |
# 模块二:规则提取层
规则提取是实现 "上传即玩" 的关键。系统支持 PDF、DOCX、Markdown 三种格式的规则书解析,分别使用 PyMuPDF 、 python-docx 和原生文件读取。
提取出的原始文本会经过 RuleCleaner 的两轮 LLM 处理流水线:
- 第一轮(文本清洗):修复换行断层,识别并格式化表格和列表,生成高保真的完整 Markdown 规则手册。
- 第二轮(元数据提取):从清洗后的规则书中提取极简元数据(游戏名称、玩家人数范围、一句话简介),用于游戏大厅展示。
清洗结果会以文件哈希为 key 缓存到本地,避免重复消耗 API Token。

上图展示了游戏管理页面,用户可以在此上传规则书文件,系统会自动解析并展示已有的游戏定义列表。

点击游戏卡片后,可以查看和编辑游戏的元数据(名称、简介、玩家人数)以及完整的 Markdown 规则手册。
# 模块三:工具层
为了让 GM Agent 能够操作游戏状态,系统提供了一组精简而强大的 6 个固定工具。这些工具被定义为 Anthropic API 的 Tool Schema,GM Agent 只能通过这些工具与游戏世界交互:
| 工具名 | 类别 | 说明 |
|---|---|---|
| db_find | 数据库操作 | 查询 DocStore 中符合条件的文档。修改前必须先查询。 |
| db_insert | 数据库操作 | 插入新文档(初始化玩家、区域等)。 |
| db_update | 数据库操作 | 更新文档,支持 inc、pull 操作符。 |
| db_delete | 数据库操作 | 删除文档(谨慎使用)。 |
| request_player_action | 交互工具 | 挂起 GM 线程,向指定玩家请求行动输入。 |
| broadcast_message | 交互工具 | 向所有玩家广播文本消息。 |
GM Agent 必须严格遵循 "先查询再修改" 的原则,通过这些原子操作来管理整个游戏的生命周期。 db_update 支持 MongoDB 风格的操作符,例如 {"$inc": {"gold": -5}, "$push": {"hand": {"name": "新卡牌"}}} 可以在一次调用中同时扣除金币并向手牌添加卡牌。
# 模块四:Agent 逻辑层
系统的核心大脑是 GM Agent,由 Claude 大语言模型驱动。它负责接收用户输入,结合当前的系统状态和规则书,决定下一步的行动。
# Prompt Caching 优化
为了优化长文本规则书带来的 Token 成本和延迟,项目巧妙地利用了 Anthropic 的 Prompt Caching 机制。系统将 System Prompt 分为两层:
- 静态层:角色定义、工具使用规范(不变)
- 规则手册层:完整的 rules.md(不变,设置 cache_control 断点)
动态的游戏状态(TinyDB 快照)则作为每次请求的 User 消息附加,绝不混入静态缓存区。这种设计确保了规则书的 KV Cache 能够被 100% 复用,大幅降低了 API 成本。
def _build_system_messages(self) -> list[dict]: | |
return [ | |
{"type": "text", "text": _GM_SYSTEM_INSTRUCTIONS}, | |
{ | |
"type": "text", | |
"text": f"# 游戏规则手册\n\n{self.rules_md}", | |
"cache_control": {"type": "ephemeral"}, | |
}, | |
] |
# AI 对手性格系统
系统还实现了 Player Agent,为 AI 对手赋予了不同的性格,使得单人对战体验更加丰富:
- 策略家:深思熟虑,善于制定长期计划
- 冒险家:高风险高回报,直觉敏锐
- 外交家:圆滑得体,善于谈判
- 收藏家:对高价值物品情有独钟
- 搅局者:喜欢出其不意,破坏对手计划
# 三、Web 界面与交互体验
Imaginary Friend 提供了精心设计的三栏式 Web 游戏界面,通过 WebSocket 实时推送游戏状态和日志。

上图展示了游戏进行中的主界面:左栏显示游戏总览(全局资源)、玩家状态和 API 统计;中栏是 GM 实时播报区,以 Markdown 渲染 GM 的叙述和 AI 玩家的对话;右栏是玩家的背包,展示手牌和持有的文物。

底部的公共区域展示了所有游戏 Zone 的状态,包括文物牌库、事件牌库、拍卖区、弃牌堆等,前端根据 TinyDB 的 JSON 树动态渲染,无需硬编码任何字段。
# 四、总结
Imaginary Friend 展示了 LLM 在复杂逻辑推理和状态管理方面的巨大潜力。通过文档驱动的架构、灵活的文档型数据库以及精巧的 Prompt Caching 策略,它成功地证明了静态的桌游可以被低成本地转化为动态的、可交互的游戏体验。
这个项目最核心的创新在于:它不是在 "教" 计算机如何玩某一款游戏,而是在 "教" 计算机如何理解任意一款游戏的规则,并自主地成为那款游戏的裁判和对手。你可以在这里获取到这个项目的源码,更多的玩法演示也会贴在这里。
项目地址:github.com/Tritium0041/imaginary-friend