DND?跑团?
近年,随着博德之门3等CRPG游戏出圈,跑团游戏在中国逐渐被热门起来,特别是在年轻人群体中备受欢迎。跑团是桌游中最特别的一类,属于TRPG范畴,其与普通桌游不同,没有具体规则没有胜负条件,官方仅给出了参考框架,而玩家所作的则是“一群富有创造力的人聚在一起用语言和想象力共同书写自己的故事”。
在这里你可以自由探索世界的文化背景、决定角色的成长、撰写自己的传奇故事。遇见一扇门,玩家可以选择寻找钥匙、进行撬锁检定抑或是用爆炸物简单粗暴的轰开大门(如果你想让整个地下城的怪物知道有人送上门了的话)。如果你觉得你的角色魅力够高,那么大可魅惑眼前不太友好的兽人(即便它很可能对你不太感兴趣),也可以选择在战斗中消耗一个动作将手中的火把扔向一个熊地精。总之不管你的决策有多么让人“瞳孔地震”,DM(游戏主持人)都必须板着脸假装思索然后平静的回复“OK”,然后即兴进行一次效果描述和判定。
我们的目标
跑团主要包括侧重奇幻冒险和战斗的DND(龙与地下城)规则和侧重恐怖叙事和解谜的COC(克苏鲁的呼唤)规则,但两个游戏都具有较高的上手门槛,进行游戏之前有大量的规则书学习沉默成本,特别是DM需要对总体规则和世界观有很深的理解(而作为DM的人则失去了亲身体验游戏的机会)。我们团队都是桌游爱好者,自近年AI工具兴起后一直试图找到好用的软件帮助玩家车角色卡、帮助DM设计场景、协作判定,这样可以大大提高组织游戏的效率并让更多小白玩家能够低成本的参与到游戏中。但这样的软件往往是玩家自发制作的网页、会员制的平台又或者能完成部分任务如掷骰子的小工具。而想真正实现AI跑团还是需要前沿大模型的付费会员,可是即便这样仍然会有各个环节的错误出现。这有一部分原因是模型数据库包含干扰信息以及模型的上下文能力不足,但更多的原因是我们给了AI一个错误的参考文件。
跑团的规则书为了方便人们阅读多是PDF版的,然而PDF版为了美观常会运用双排版面,并且其中往往存在一些排版错误和非标准符号,这对于AI阅读是非常困难的一件事——我们人眼看到的是渲染后的图像,而AI则是通过在原始文本中抓取信息完成思考的,另外规则书过长的篇幅对于AI的上下文能力也是一项不小的挑战。与此同时我们还需要面对的问题是,广大玩家不会为了一个跑团功能购买一个昂贵的AI会员又或者购买企业级算力资源服务。因此我们计划从上游文字处理部分入手,将pdf转化成标准文字文档如md格式,将规则信息变成一个个易抓取的标签和单元。一番搜集信息之后,一款由合合信息开发的工具textin进入了我们的视野,据悉曾经大火的扫描全能王就是由他们公司开发的,可以说这家公司具有多年的文档处理和格式转化开发经验,这让我们看到了希望。
工作流
使用了腾讯的workbuddy内textin的skill调用模式,用户只需在textin官网注册申请试用额度,再下载workbuddy,在“专家·技能·连接器”界面找到textin登录并绑定即可。DND规则有版权限制,但官方发布了SRD版本CC-BY-4.0授权,允许玩家初步游玩、二次创作、开发相关辅助工具。
SRD规则书md文件的生成
只需用文字指令调用textin的解析功能,便可获得一个高保真度的SRD规则书“同款”md文件
workbuddy生成中译本md
目前的SRD文件仅有官网的英文版,玩家间流行的翻译基本是爱好者饭制版,我曾看过一些中文译本里面存在大量谐音错别字,并且很多翻译可能并不符合主流西幻术语翻译习惯。我本人就曾问过某AI一个战士可不可以在一个大失败后失衡进入倒地状态,AI跟我说“是的,你可以让其进入俯卧状态”说实话我还真没反应过来...无语中。因此为了便于玩家和AI模型同频交流,我们决定在md文件的原地进行翻译,对于不同内容我们利用AI制定了不同的标准。
在翻译过程中,我们首先从wiki百科和各大资源库整合了可能涉及的术语翻译习惯形成style.md。在实际翻译中模型将md文件分为了72个chuck,每一部分涉及的新术语都额外添加进了style文件中,有争议的点由模型提出进行人为调整。该style.md我将后续发布在资源中可见,它或许可以成为DND乃至西幻背景的AI电影游戏制作中的重要参考。以下是部分输出过程,可见模型在处理冰风暴和雪雨暴时废了不少脑筋。另外有几处人为修改,最记忆尤新的是游戏过程中获得的时尚小垃圾——一缕某人的头发、一只能吹出泡泡的烟斗等Trinket被译为小把戏,这与不需要法术位的“法术”戏法存在歧义,因此我们译作了饰品;而邪术师经典“平A当大招”戏法在最初的版本被译为邪能爆裂,我们勒令AI改成了魔能爆。Workbuddy也指出,md文件中错误最多的是数字1和i区分的问题,常出现等级I3(13)、属性DEX II(11)这样的错误。
md转化为pdf
此时我们还需要一个供人参考的版本,因此我们打算将中文版md再译回pdf,也通过这样的方式检测textin的转化质量,检测内容是否有失真。md变成pdf的过程是很复杂的,相关的软件体积庞大步骤繁琐,但考虑到md是html的简化表达形式,所以我们决定先以html网页呈现整个md内容,再储存成pdf文件。我们在这里为大家展示比较让人头疼的兼职施法者及其法术位问题,可以看到文字内容表述清晰,表格完整准确。当然不可避免的中文版pdf经过了md提取、翻译、转化成html内容再导出,标题层级会产生一些混乱,但基本不影响阅读,我们几乎通读了全文,没有明显错误。
中文版md文件拆分
如先前介绍,我们制作md格式是方便AI抓取阅读的,这时我们为了AI能更高效的输出相关内容、节省token和生成时间,利用workbuddy将中文版MD进行了基于内容的拆分,这同样有利于AI执行DM及玩家不同的职责。具体来说,根据我们对规则的理解,将整个SRD分段整合成了以下五个部分:
当我们这样做分割,其实玩家大部分时间仅需要角色创建部分和法术描述部分,规则部分仅需根据DM指示即可。而DM制作怪物时可以仅用到怪物大全部分,平时仅依靠简介及规则部分。
成果检验
接下来让我们打开大胖鱼-DeepSeek网页版,关闭联网搜索和深度思考,将md或分割好的版本提交给它,在提示词中额外强调“禁止联网,仅凭借SRD规则的md文件完成以下任务...”
1. 一个8级法师/5级牧师是几级施法者,几个法术位,可以准备哪些法术?
这是一个兼职施法者的问题,也是之前我比较困惑的地方,模型仅用了几秒钟便做出了回答:
法师和牧师都是完全施法者,等级全部计入二者加和13级,根据该等级按列表判断法术位,但只能用8级法师和5级牧师已解锁的法术,此时其实没有5级以上的法术,但可以放标示可升环的魔法,经典的如法师的火球。
2. 做一个1级战士角色卡,使用标准数组,按常见玩法选择种族和背景,最后进行数值和特性汇总
AI选择了装备A,加上人类给的共18gp,买了一面盾牌,用单手长剑保证容错。固AC为锁子甲16+盾牌2+防御专长1;职业技能和背景技能重复了,只有运动、生存、威吓、察觉;生命为10+1体质加值;标准数组15 14 13 8 10 12,主力量副敏捷,士兵背景调整值分别给力量和敏捷加一和加二;三个武器精通分别给长剑、矛、手斧,没有选择巨剑;人类种族提供足智多谋特性,英雄激励——2d20取最高。
另外,模型给出了当这位战士接受城主奖励直升2级时需要做出的属性和特性变化,此时战士会获得经典的行动如潮特性。
3. 利用非人生物为4位1级角色设计一场中等难度的战斗
SRD规则无怪物数量乘数的设定,DeepSeek很好的进行了区分,完全依赖于我们提供的规则书进行设计(乘数设定例如1只×1、2只×1.5、3~6只×2、7~10只×2.5、11~14只×3、15只以上×4)。
SRD规则书关于难度的描述如下(这与5e版规则书的表述和分级也不同):
接下来需要对照怪物的XP值花费预算,如熊地精战士(200XP),地精战士(50XP),固两只地精+一只熊地精即为合理的中等难度战斗。
结果复现
若大家想直接使用AI玩DND桌游,那么直接拿取我们的SRD_CC_v5.2.1_zh交给AI即可,原始及过程文件均会上传资源,其中也会有完整版的复现流程和供AI参考的脚本。生成md、翻译、转化成pdf每一步均可复现,只需架设好软件分步输入提示词,但需要注意全工作流程需要数百积分,使用个人模型则接近10块钱的tokens。各位如果对任何其他桌游感兴趣,该流程可套用任何桌游规则,只需在上传时替换为自己的pdf即可~
1.请将这个DND规则SRD版本的PDF文件调用Textin xParse转化为md格式。过程中你无需做出解释和校对。若文档超出大小范围,请拆分并命名为-1、-2、-3等文件分别输出 # 上传SRD_CC_v5.2.1.pdf 2.请将SRD_CC_v5.2.1.md分成适当数量模块段落(为了适应LLM模型的上下文限制,可参考_bak2_chunks文件夹),依次翻译成中文版md,每翻译完四个模块输出各模块内容概况和注意要点。翻译依照以下限制:法律声明页术语及专有名词保留英语;装备法术怪物等依照style.md内的西幻文学术语;规则性描述请翻译的规范且准确;每个模块严格校对html标签与行数,确保中文md与英文md一致,校验可使用verify.py。最终生成SRD_CC_v5.2.1_zh.md 3.将翻译完的md转化成pdf,边翻译边进行SRD_CC_v5.2.1.md、SRD_CC_v5.2.1_zh.md、SRD_CC_v5.2.1_zh.pdf三个文档的联合验证,有问题的地方可以直接同步修改。可以使用Markdown → HTML(打印 CSS)→ Edge headless 出 PDF 的路线。特别注意修改页码三位错误变四位的情况,如257变成2557;同时注意联合施法及各职业法术位表格是否完整。AI协助跑团demo
微软的AutoGen是一个开源的多智能体协作架构,而AI跑团这类问题则天然适配多智能体协作框架,试想我们可以做一个DM智能体,让他构思场景、剧情、战斗并进行各类检定;同时我们再布置一个协助智能体,它可以为玩家解答规则方面的问题同时协助玩家们对当前信息和成功率进行分析、润色玩家语言表述并调用脚本完成骰子投掷提交给DM。我们还可以进一步做一个网页或一个本地窗口,将智能体囊括进来,在页面不同的区域执行不同功能。接下来,我们为大家展示一个demo版本,该版本完全由AI编程实现,通过实际DND游玩经验调整。
以下是代码
import asyncio import os import random import threading import tkinter as tk from tkinter import ttk, scrolledtext, filedialog, messagebox from autogen_agentchat.agents import AssistantAgent from autogen_core.models import ModelInfo from autogen_ext.models.openai import OpenAIChatCompletionClient # ===================== 工具函数 ===================== def roll_d20(modifier: int = 0) -> dict: """标准 d20 投骰,数值由代码生成,AI 无法篡改""" raw = random.randint(1, 20) total = raw + modifier return { "raw": raw, "modifier": modifier, "total": total, "crit": raw == 20, "fumble": raw == 1 } # ===================== 主窗口类 ===================== class TRPGRunnerApp(tk.Tk): def __init__(self): super().__init__() self.title("D&D5e 双Agent跑团助手") self.geometry("1020x740") self.minsize(920, 620) # 运行状态 self.dm_agent = None self.advisor_agent = None self.deepseek_client = None self._build_layout() self._load_default_content() def _build_layout(self): main_pane = ttk.PanedWindow(self, orient=tk.HORIZONTAL) main_pane.pack(fill=tk.BOTH, expand=True, padx=8, pady=8) # ---------- 左侧:配置区 ---------- left_frame = ttk.Frame(main_pane, width=380) main_pane.add(left_frame, weight=1) # 设定区 ttk.Label(left_frame, text="DM 核心设定(世界观/主题/人物/限制,DM 会在此基础上自由创作)").pack(anchor="w", pady=(0, 4)) self.dm_setting_text = scrolledtext.ScrolledText(left_frame, height=16, wrap=tk.WORD) self.dm_setting_text.pack(fill=tk.BOTH, expand=True) ttk.Button(left_frame, text="加载本地 MD 文件", command=self._load_dm_setting_file).pack(fill=tk.X, pady=4) # 规则区 ttk.Label(left_frame, text="规则文档(所有检定、机制严格以此文件为准)").pack(anchor="w", pady=(8, 4)) self.rule_text = scrolledtext.ScrolledText(left_frame, height=10, wrap=tk.WORD) self.rule_text.pack(fill=tk.BOTH, expand=True) ttk.Button(left_frame, text="加载本地 MD 文件", command=self._load_rule_file).pack(fill=tk.X, pady=4) ttk.Separator(left_frame, orient=tk.HORIZONTAL).pack(fill=tk.X, pady=8) ttk.Label(left_frame, text="DeepSeek API Key").pack(anchor="w") self.deepseek_key_entry = ttk.Entry(left_frame, show="*") self.deepseek_key_entry.pack(fill=tk.X, pady=2) self.start_btn = ttk.Button(left_frame, text="启动游戏,DM 生成开场", command=self._start_game) self.start_btn.pack(fill=tk.X, pady=8) # ---------- 右侧:交互区 ---------- right_frame = ttk.Frame(main_pane) main_pane.add(right_frame, weight=2) ttk.Label(right_frame, text="游戏记录").pack(anchor="w", pady=(0, 4)) self.chat_box = scrolledtext.ScrolledText(right_frame, wrap=tk.WORD, state="disabled") self.chat_box.pack(fill=tk.BOTH, expand=True) ttk.Label(right_frame, text="你的行动描述(贿赂、知识回忆、对话内容写在这里)").pack(anchor="w", pady=(8, 4)) self.user_input = scrolledtext.ScrolledText(right_frame, height=4, wrap=tk.WORD) self.user_input.pack(fill=tk.X) # 检定参数栏 op_frame = ttk.Frame(right_frame) op_frame.pack(fill=tk.X, pady=6) ttk.Label(op_frame, text="检定技能:").pack(side=tk.LEFT) self.check_type_var = tk.StringVar(value="游说") skill_list = [ "纯叙事(无检定)", "杂技", "驯兽", "奥秘", "运动", "欺瞒", "历史", "洞悉", "威吓", "调查", "医药", "自然", "察觉", "表演", "游说", "宗教", "巧手", "隐匿", "生存" ] self.check_combo = ttk.Combobox( op_frame, textvariable=self.check_type_var, values=skill_list, state="readonly", width=16 ) self.check_combo.pack(side=tk.LEFT, padx=4) self.check_combo.bind("<<ComboboxSelected>>", self._on_check_type_change) ttk.Label(op_frame, text="修正值:").pack(side=tk.LEFT, padx=(12, 0)) self.mod_entry = ttk.Entry(op_frame, width=6) self.mod_entry.insert(0, "3") self.mod_entry.pack(side=tk.LEFT, padx=4) # 双功能按钮区 btn_frame = ttk.Frame(right_frame) btn_frame.pack(fill=tk.X, pady=4) self.analysis_btn = ttk.Button( btn_frame, text="咨询分析(仅答疑建议,不执行)", command=self._on_ask_analysis, state="disabled" ) self.analysis_btn.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=2) self.execute_btn = ttk.Button( btn_frame, text="执行行动(自动润色并发给DM)", command=self._on_execute_action, state="disabled" ) self.execute_btn.pack(side=tk.RIGHT, fill=tk.X, expand=True, padx=2) def _on_check_type_change(self, _event=None): """切换行动类型时,控制修正值输入框状态""" if self.check_type_var.get() == "纯叙事(无检定)": self.mod_entry.config(state="disabled") else: self.mod_entry.config(state="normal") def _load_default_content(self): """预置示例设定与通用规则模板""" default_setting = """# 核心设定 世界观:低魔奇幻中世纪边境小镇「灰泥镇」 基调:写实、略带悬疑,民风谨慎 当前任务:调查近日一名行商失踪事件 开场场景:镇中心的「破车轮」酒馆,傍晚,雨后潮湿 要求:NPC 立场中立偏谨慎,信息需要通过互动逐步解锁,禁止一次性全部给出。 """ self.dm_setting_text.insert(tk.END, default_setting) default_rule = """# D&D5e 技能检定规则 技能与对应属性(严格遵循) 杂技-敏捷|驯兽-感知|奥秘-智力|运动-力量|欺瞒-魅力|历史-智力|洞悉-感知 威吓-魅力|调查-智力|医药-感知|自然-智力|察觉-感知|表演-魅力|游说-魅力 宗教-智力|巧手-敏捷|隐匿-敏捷|生存-感知 1. 检定采用 d20 + 技能修正值机制,难度由主持人根据情境合理判定。 2. 自然20视为大成功,效果额外利好;自然1视为大失败,出现额外负面后果。 3. 社交类互动可通过不同方式达成目标,不同方式会影响NPC态度与后续剧情。 4. 知识类检定用来回忆相关学识;感知类检定用来捕捉线索、情绪、环境细节。 """ self.rule_text.insert(tk.END, default_rule) def _load_dm_setting_file(self): path = filedialog.askopenfilename(filetypes=[("Markdown 文件", "*.md"), ("所有文件", "*.*")]) if path: with open(path, "r", encoding="utf-8") as f: self.dm_setting_text.delete("1.0", tk.END) self.dm_setting_text.insert(tk.END, f.read()) def _load_rule_file(self): path = filedialog.askopenfilename(filetypes=[("Markdown 文件", "*.md"), ("所有文件", "*.*")]) if path: with open(path, "r", encoding="utf-8") as f: self.rule_text.delete("1.0", tk.END) self.rule_text.insert(tk.END, f.read()) def _append_chat(self, speaker: str, content: str): self.chat_box.config(state="normal") self.chat_box.insert(tk.END, f"\n【{speaker}】\n{content}\n") self.chat_box.insert(tk.END, "-" * 50 + "\n") self.chat_box.see(tk.END) self.chat_box.config(state="disabled") # ===================== 游戏核心逻辑 ===================== def _start_game(self): deepseek_key = self.deepseek_key_entry.get().strip() or os.getenv("DEEPSEEK_API_KEY") setting_content = self.dm_setting_text.get("1.0", tk.END).strip() rule_content = self.rule_text.get("1.0", tk.END).strip() if not deepseek_key: messagebox.showerror("错误", "请填写 DeepSeek API Key,或提前设置环境变量 DEEPSEEK_API_KEY") return if not setting_content: messagebox.showerror("错误", "DM 核心设定不能为空") return self.start_btn.config(state="disabled") threading.Thread( target=self._init_agents_thread, args=(deepseek_key, setting_content, rule_content), daemon=True ).start() def _init_agents_thread(self, deepseek_key, setting_content, rule_content): try: self.deepseek_client = OpenAIChatCompletionClient( model="deepseek-flash", base_url="https://api.deepseek.com", api_key=deepseek_key, model_info=ModelInfo( vision=False, function_calling=True, json_output=True, family="unknown", structured_output=True ) ) # DM Agent self.dm_agent = AssistantAgent( name="Game_Master", model_client=self.deepseek_client, system_message=f"""你是专业D&D5e主持人。 【核心设定(必须严格遵守,不可违背)】 {setting_content} 【规则标准(必须严格遵循)】 {rule_content} 你的职责: 1. 在核心设定框架内,自主创作场景细节、NPC形象性格、对话、检定DC与后果。 2. 描述环境、扮演NPC、判定玩家技能检定结果、推进剧情。 3. 信息逐步释放,禁止一次性全盘托出;NPC反应符合立场性格。 4. 绝不替玩家做决定,不给行动建议,不私自投骰。 5. 描述简洁有画面感,输出完毕停止,不主动追问下一步。""" ) # 行动顾问 Agent self.advisor_agent = AssistantAgent( name="Action_Advisor", model_client=self.deepseek_client, system_message=f"""你是玩家专属行动顾问,不是DM,不扮演NPC、不推进剧情。 【D&D5e规则依据】 {rule_content} 两种工作模式: 1. 分析模式:针对玩家问题,给风险分析、选项建议、规则解读,**不生成行动文本、不投骰**。 2. 执行模式: - 技能检定:结合骰子结果、玩家行动描述、所选5e标准技能,输出【行动分析】【骰子结果】【最终行动文本】 - 纯叙事:无检定,输出【行动优化说明】【最终行动文本】 约束: - 技能名称严格匹配5e标准:游说、欺瞒、洞悉、隐匿,不要自行别名替换。 - 不编造设定,不预判DM反应,不替玩家做决定。""" ) self.after(0, self._on_game_ready) except Exception as e: self.after(0, lambda: messagebox.showerror("初始化失败", str(e))) self.after(0, lambda: self.start_btn.config(state="normal")) def _on_game_ready(self): self.analysis_btn.config(state="normal") self.execute_btn.config(state="normal") self._append_chat("系统", "游戏启动成功,DM正在生成开场场景...") threading.Thread(target=self._get_intro_thread, daemon=True).start() def _get_intro_thread(self): try: result = asyncio.run(self.dm_agent.run( task="请生成游戏开场场景,描述环境氛围、当前可见NPC与互动契机,引导玩家行动。" )) self.after(0, lambda: self._append_chat("DM", result.messages[-1].content)) except Exception as e: self.after(0, lambda: messagebox.showerror("开场生成失败", str(e))) # ---------- 模式1:纯咨询分析 ---------- def _on_ask_analysis(self): user_text = self.user_input.get("1.0", tk.END).strip() if not user_text: messagebox.showwarning("提示", "请先输入你的问题或想法") return self.analysis_btn.config(state="disabled") self.execute_btn.config(state="disabled") threading.Thread( target=self._analysis_thread, args=(user_text,), daemon=True ).start() def _analysis_thread(self, user_text): try: task = f""" 【分析模式】 玩家的问题/想法:{user_text} 结合D&D5e技能规则给出分析、建议、风险提示,不要输出行动文本,不要投骰。 """ result = asyncio.run(self.advisor_agent.run(task=task)) self.after(0, lambda: self._append_chat("行动顾问", result.messages[-1].content)) except Exception as e: self.after(0, lambda: messagebox.showerror("调用失败", str(e))) finally: self.after(0, lambda: self.analysis_btn.config(state="normal")) self.after(0, lambda: self.execute_btn.config(state="normal")) # ---------- 模式2:执行行动(润色+发DM) ---------- def _on_execute_action(self): user_intent = self.user_input.get("1.0", tk.END).strip() if not user_intent: messagebox.showwarning("提示", "请先输入你的行动想法") return check_type = self.check_type_var.get() mod = 0 if check_type != "纯叙事(无检定)": try: mod = int(self.mod_entry.get()) except ValueError: messagebox.showerror("错误", "修正值必须是整数") return self.analysis_btn.config(state="disabled") self.execute_btn.config(state="disabled") threading.Thread( target=self._execute_thread, args=(user_intent, check_type, mod), daemon=True ).start() def _execute_thread(self, user_intent, check_type, mod): try: is_narrative = check_type == "纯叙事(无检定)" if is_narrative: advisor_task = f""" 【执行模式 · 纯叙事】 玩家原始意图:{user_intent} 润色为符合D&D叙事风格的行动描述,无检定、无骰子。 输出:【行动优化说明】【最终行动文本】 """ else: dice = roll_d20(mod) dice_text = f"d20 原始: {dice['raw']}, 修正: +{dice['modifier']}, 总和: {dice['total']}" if dice["crit"]: dice_text += " 【自然 20 · 大成功】" if dice["fumble"]: dice_text += " 【自然 1 · 大失败】" advisor_task = f""" 【执行模式 · 5e技能检定】 玩家行动描述:{user_intent} 选用技能:{check_type} 骰子结果:{dice_text} 输出:【行动分析】【骰子结果】【最终行动文本】 """ advisor_result = asyncio.run(self.advisor_agent.run(task=advisor_task)) advisor_reply = advisor_result.messages[-1].content self.after(0, lambda: self._append_chat("行动顾问", advisor_reply)) dm_task = f"玩家执行以下行动,请给出剧情反馈:\n{advisor_reply}" dm_result = asyncio.run(self.dm_agent.run(task=dm_task)) self.after(0, lambda: self._append_chat("DM", dm_result.messages[-1].content)) except Exception as e: self.after(0, lambda: messagebox.showerror("执行失败", str(e))) finally: self.after(0, lambda: self.analysis_btn.config(state="normal")) self.after(0, lambda: self.execute_btn.config(state="normal")) def destroy(self): if self.deepseek_client: asyncio.run(self.deepseek_client.close()) super().destroy() if __name__ == "__main__": app = TRPGRunnerApp() app.mainloop()测试者提交了我们自制的SRD中文版md文件作为参考规则,在窗口中进行了一个“酒馆中打听情报”的实际游玩过程。
根据规则书,模型回答了游戏货币GP的定义并说明了汇率
对酒馆吧台和老板的动作习惯进行一次成功的调查
对话获得情报
一次不太成功的察觉,未能发现在场角色对于行商失踪一事的立场是否统一,但酒馆老板对该事的态度至少是很谨慎
结语
从一份排版复杂的 PDF 规则书,到一份 AI 能读懂、玩家也能看懂的中文 SRD,再到一场真正跑起来的酒馆冒险,我们完成了一套并不复杂却独具匠心的链路:Textin把文档变成结构化的 md,WorkBuddy把术语统一成一份风格表完成中文翻译,进行人工校对,再把长文拆成适合上下文的小块,剩下的交给强悍的大语言模型(DeepSeek)。如今的AI模型百花齐放,能制作电影、能攻克数学难题,但没有人能为普通使用者回答它该怎么用、怎样发挥各软件的优势。我们从跑团这类小众桌游入手,拆解问题,先解决非标准文本格式的痛点,再根据经验手动调整,验证大模型的提取效率,最终再结合问题特点用开源大众化的软件构建出完整的工程流程,希望可以起到抛砖引玉的作用。另外,我们也希望以此推广跑团游戏,让它成为更多人的娱乐选择。我们将这套流程和全部过程文件都公布了出来,它不只适用于 DND,换成任何一本桌游规则书,只要替换掉那份 PDF,就能跑通同样的路径。希望它能帮你省下翻规则书的时间,把更多精力留给故事和游戏本身~