1. 量化交易员生存游戏为什么用 JSON 驱动剧情分支文字冒险 RPG 最怕什么剧情分支一多代码里全是if-else嵌套改一句对白要翻三个文件加一个结局要重跑整个逻辑。我试过用纯 Python 硬编码写一个二十来个节点的剧情写到第十个节点就开始分不清哪个分支跳哪里了。这个项目的思路是把剧情和引擎彻底拆开Python 只负责读 JSON、管状态、做跳转所有剧情文本、选项、数值变化全部写在story.json里。你新增一个关卡只需要在 JSON 里加一个节点对象Python 代码一行都不用动。适合谁适合想学数据驱动设计、想做文字游戏原型、或者单纯想用 Cursor 快速搭一个可玩项目的开发者。核心检索词就三个Python 主循环、JSON 关卡配置、状态机跳转。量化交易题材的好处是数值联动天然合理——本金和压力值两个指标就能撑起整个生存压力。本金归零直接破产压力满 100% 直接猝死每个决策都在两者之间做权衡。这种设计让 JSON 里的effects字段有了真实的游戏意义而不是随便填的数字。整个项目两个文件story.json存剧情quant_rpg.py跑引擎。下面从目录骨架开始一步步搭起来。2. 前置准备TaoToken 接入与 Cursor 环境配置在开始写代码之前先把开发环境理顺。Cursor 本身是编辑器但如果你想让 AI 辅助生成剧情 JSON 或者帮你检查状态机逻辑接入一个稳定的模型 API 会让体验顺畅很多。TaoToken 在这里的角色是提供模型调用能力你可以在 Cursor 里配置它来辅助编码。2.1 获取 API Key打开 TaoToken 控制台进入 API Keys 页面创建一个新的密钥。建议给这个 key 起个名字比如cursor-rpg-dev方便后续管理。创建后复制保存页面关闭后就不再完整显示了。注意API Key 只显示一次建议创建后立即存入密码管理器或本地环境变量文件不要直接硬编码在代码里提交到 Git。2.2 在 Cursor 中配置模型接入Cursor 支持自定义模型端点。在设置中找到模型配置区域填入 TaoToken 的 API 地址https://taotoken.net/api粘贴刚才创建的 Key然后选择你需要的模型。配置完成后你可以在 Cursor 的对话窗口里让 AI 帮你生成剧情节点、检查 JSON 语法、或者解释状态机跳转逻辑。如果你更习惯用命令行工具做长期编码可以了解 Coding Plan 的配置方式它适合需要持续调用模型进行代码生成和审查的场景。日常调试阶段用模型对话页面做快速验证也够用。2.3 Python 环境确认项目本身不依赖第三方库只需要 Python 3.9 以上。确认一下版本python --version # 或 python3 --version如果版本低于 3.9建议用 pyenv 或直接去官网下载新版。终端需要支持 ANSI 颜色码macOS 和 Linux 默认支持Windows 建议用 Windows Terminal 而不是老版 cmd。3. 项目骨架与 JSON 关卡配置模板3.1 目录结构在任意位置新建一个文件夹结构如下quant_rpg/ ├── story.json # 剧情关卡配置 └── quant_rpg.py # Python 游戏引擎就两个文件不需要requirements.txt不需要虚拟环境。这种极简结构的好处是你随时可以复制到任何机器上跑。3.2 JSON 关卡字段设计每个节点是一个对象包含三个核心字段字段类型说明textstring当前场景的剧情描述文本choicesarray玩家可选的决策列表空数组表示终局choices[].textstring选项显示文字choices[].next_node_idstring跳转目标节点的 keychoices[].effectsobject数值变化含capital和stress顶层还需要一个start_node_id指定起始节点。整个 JSON 就是一个有向图节点是场景边是决策。3.3 完整 story.json 模板下面这份配置包含 12 个节点覆盖入职选择、黑天鹅事件、杠杆爆仓、稳健终局等多条路径。你可以直接复制使用也可以在此基础上增删节点。{ start_node_id: welcome, nodes: { welcome: { text: 【入职第一天】你通过了层层面试入职了一家量化私募基金担任 Junior Trader。初始资金 100 万美元压力值一旦达到 100% 将被强制辞退。组长给你两个方向, choices: [ { text: 选择高频统计套利组强度大赚钱快, next_node_id: high_frequency, effects: { capital: 0, stress: 20 } }, { text: 选择多因子选股组偏中长线强度适中, next_node_id: multi_factor, effects: { capital: 0, stress: 5 } } ] }, high_frequency: { text: 【高频日内争夺】你进入高频套利组。某天中午系统报警套利策略在股指期货上发生滑点偏差亏损以每秒 5 万美元扩大。组长去开会了你必须决策, choices: [ { text: 立即手动拔线强行平仓止损, next_node_id: cut_loss, effects: { capital: -20, stress: 30 } }, { text: 坚信数学模型坚守等待均值回归, next_node_id: wait_mean_reversion, effects: { capital: 0, stress: 50 } } ] }, multi_factor: { text: 【多因子黑天鹅】你进入多因子选股组。周一开盘市场突发黑天鹅大宗商品全线暴跌你重仓的多头因子组合严重超额亏损。配资中介询问是否加三倍杠杆抄底, choices: [ { text: 富贵险中求加三倍杠杆抄底反击, next_node_id: leverage_gamble, effects: { capital: 50, stress: 40 } }, { text: 理性克制主动降低暴露仓位, next_node_id: cut_exposure, effects: { capital: -10, stress: 10 } } ] }, cut_loss: { text: 【强平止损的代价】你果断强平账户亏损 20 万。虽然避免了更大灾难但本金缩水。组长回来后表扬了你的风控意识。市场稍微平静你准备, choices: [ { text: 下班去酒吧买醉发泄, next_node_id: pub_relax, effects: { capital: -1, stress: -20 } }, { text: 在工位继续加班复盘系统错误, next_node_id: overtime_study, effects: { capital: 0, stress: 25 } } ] }, wait_mean_reversion: { text: 【爆仓与奇迹】你选择死抗。幸运的是半小时后大盘奇迹般反弹均值回归策略生效你不仅挽回损失还帮公司多赚了 40 万。但半小时的爆仓惊魂让你心脏几乎停跳, choices: [ { text: 拿着奖金去申请休假调理身体, next_node_id: vacation, effects: { capital: 40, stress: -35 } } ] }, leverage_gamble: { text: 【杠杆的诅咒】大盘继续下跌你的配资账户发生严重爆仓预警强平线已被击穿你被配资公司强行平仓血本无归, choices: [ { text: 天亮了收拾东西离开华尔街, next_node_id: game_over_bankruptcy, effects: { capital: -150, stress: 60 } } ] }, cut_exposure: { text: 【稳健的步伐】虽然缩水了 10 万本金但你躲过了后续大崩盘。公司高层看中你的沉稳风控表现决定把你升职为高级投资经理掌管更大的底池资金, choices: [ { text: 迈向传奇基金经理之路, next_node_id: legendary_ending, effects: { capital: 200, stress: -10 } } ] }, pub_relax: { text: 【微醺之夜】酒精麻痹了神经你的压力得到极大释放。第二天开盘你神清气爽地重新坐在电脑前。此时你发现一个全新的套利期权机会出现..., choices: [ { text: 以沉稳的姿态开始新的套利, next_node_id: stable_ending, effects: { capital: 30, stress: 10 } } ] }, overtime_study: { text: 【过劳猝死边缘】你在工位上连续复盘 16 小时窗外天亮了。你眼前开始出现重影心脏发生剧烈刺痛, choices: [ { text: 支撑不住晕倒在键盘上..., next_node_id: game_over_stress, effects: { capital: 0, stress: 40 } } ] }, vacation: { text: 【海滩阳光】在蔚蓝的海滩上你看着账户里的盈余吹着海风。你成功挺过了 Junior 阶段正式进阶为合格的量化黑客, choices: [ { text: 结束旅行迎接更大的市场风暴, next_node_id: legendary_ending, effects: { capital: 50, stress: 0 } } ] }, game_over_bankruptcy: { text: 【终局爆仓出局】你破产了。你在金融海啸中彻底爆仓本金归零。华尔街的灯光璀璨但已经与你无关。你准备回老家转行种地。 (GAME OVER), choices: [] }, game_over_stress: { text: 【终局心梗猝死】你的压力值达到了 100%。你的身体无法承受长期的焦虑与高负荷加班在救护车的鸣笛声中被抬出了交易大厅。健康才是量化投资最大的本钱。 (GAME OVER), choices: [] }, stable_ending: { text: 【终局平淡才是真】你成为了一名合格的量化打工人。每天稳定套利赚取属于你的超额阿尔法收益虽然没有一夜暴富但成功在华尔街活了下来。 (HAPPY ENDING), choices: [] }, legendary_ending: { text: 【终局量化之王】恭喜你的本金达到了顶峰成功避开黑天鹅战胜了市场庄家。你创办了自己的对冲基金名字被刻在量化圣殿名人堂上 (LEGENDARY ENDING), choices: [] } } }这份 JSON 的关键设计点终局节点的choices是空数组引擎检测到空数组就结束循环。effects里的数值可正可负压力值最低会被引擎钳制到 0但上限 100 由主循环拦截。4. Python 引擎核心代码状态机与主循环4.1 玩家状态类先定义玩家数据结构负责持有本金和压力值并提供apply_effects方法执行数值变更。#!/usr/bin/env python # -*- coding: utf-8 -*- 文件名: quant_rpg.py 描述: 量化交易题材文字 RPG 游戏引擎基于 JSON 剧情有向图状态树驱动 import json import logging import os import sys import time logging.basicConfig( levellogging.INFO, format%(asctime)s [%(levelname)s] %(message)s, handlers[logging.StreamHandler(sys.stdout)] ) class PlayerStatus: def __init__(self, initial_capital100, initial_stress10): # 本金以“万”美元为单位初始 100 万 self.capital initial_capital # 压力值初始 10%达到或超过 100% 触发提前死亡 self.stress initial_stress def apply_effects(self, effects: dict): 根据决策选项改变玩家数值状态并进行边界限制 if not effects: return self.capital effects.get(capital, 0) self.stress effects.get(stress, 0) # 压力最低为 0% if self.stress 0: self.stress 04.2 游戏引擎类引擎类负责加载 JSON、打印状态栏、驱动主循环。核心逻辑是每轮循环先检查是否触发隐性死亡条件然后打印当前节点文本和选项等待玩家输入执行数值变更跳转下一节点。class GameEngine: def __init__(self, story_file_path: str): self.story_path story_file_path self.story_data {} self.player PlayerStatus() self.current_node_id def load_story(self): 读取并验证剧情 JSON 文件的完整性与语法合法性 if not os.path.exists(self.story_path): logging.error(f剧情配置文件 {self.story_path} 未找到) sys.exit(1) try: with open(self.story_path, r, encodingutf-8) as f: self.story_data json.load(f) self.current_node_id self.story_data.get(start_node_id) if not self.current_node_id or self.current_node_id not in self.story_data.get(nodes, {}): raise ValueError(JSON 配置中未找到合法的 start_node_id 起始场景) logging.info(【游戏引擎】剧情数据文件加载成功准备进入首场对局。) except json.JSONDecodeError as je: logging.error(f【语法错误】剧情 JSON 格式损坏: {str(je)}) sys.exit(1) except Exception as e: logging.error(f【引擎异常】装载关卡失败: {str(e)}) sys.exit(1) def print_status_bar(self): 在终端打印量化交易员数值面板 print(\n * 65) capital_color \033[91m if self.player.capital 40 else \033[92m if self.player.stress 90: stress_color \033[5;31m elif self.player.stress 70: stress_color \033[93m else: stress_color \033[92m print(f交易员当前状态 | 本金资金: {capital_color}{self.player.capital} 万美元\033[0m | 压力值: {stress_color}{self.player.stress}%\033[0m) print( * 65 \n) def run(self): 核心关卡跳转驱动循环 self.load_story() print(\n * * 22 量化生存华尔街黑客传奇 * * 22) print(提示生存的关键是控制你的本金不归零且保证压力值不冲破 100%。) nodes self.story_data[nodes] while True: # 1. 拦截提前触发的隐性死亡状态 if self.player.capital 0 and self.current_node_id not in [game_over_bankruptcy, game_over_stress]: print(\n【风控爆仓警告】你的本金低于或等于 0 元触发强制平仓出局流程...) self.current_node_id game_over_bankruptcy if self.player.stress 100 and self.current_node_id not in [game_over_bankruptcy, game_over_stress]: print(\n【生理机能预警】你的精神压力已经达到 100% 极限身体机能崩溃中...) self.current_node_id game_over_stress node nodes[self.current_node_id] # 2. 打印状态栏与剧情描述文本 self.print_status_bar() for char in node[text]: sys.stdout.write(char) sys.stdout.flush() time.sleep(0.01) print(\n) choices node.get(choices, []) # 3. 判定是否到达剧情终点 if not choices: print(* * 24 游戏结束 (THE END) * * 24 \n) break # 4. 展示玩家可行的选择分支 for i, choice in enumerate(choices): print(f [{i 1}] {choice[text]}) # 5. 阻塞等待并捕获玩家命令行输入 while True: try: user_input input(\n请输入你的选择数字 (1, 2...) ).strip() choice_idx int(user_input) - 1 if 0 choice_idx len(choices): selected_choice choices[choice_idx] break else: print(请输入列表里存在的数字序号) except ValueError: print(无效输入请输入正确的整数序号。) # 6. 执行数值变量加减法变动 effects selected_choice.get(effects, {}) self.player.apply_effects(effects) # 7. 跳转下一关卡路由指针 self.current_node_id selected_choice[next_node_id] if __name__ __main__: story_file story.json engine GameEngine(story_file) engine.run()4.3 主循环设计要点主循环里有两个关键设计。第一是死亡拦截放在循环头部每轮开始前检查本金和压力值如果触发条件就强制改写current_node_id到对应的终局节点。这样即使 JSON 里没有显式配置死亡跳转引擎也能兜底。第二是打字机效果用sys.stdout.write逐字符输出加time.sleep(0.01)每个字符延时 10 毫秒。如果你觉得太慢把0.01改成0.005或直接去掉time.sleep那行。5. 运行验证从启动到爆仓的完整流程5.1 启动游戏在终端进入项目目录执行python quant_rpg.py启动后你会看到首帧界面********************** 量化生存华尔街黑客传奇 ********************** 提示生存的关键是控制你的本金不归零且保证压力值不冲破 100%。 交易员当前状态 | 本金资金: 100 万美元 | 压力值: 10% 【入职第一天】你通过了层层面试入职了一家量化私募基金担任 Junior Trader... [1] 选择高频统计套利组强度大赚钱快 [2] 选择多因子选股组偏中长线强度适中 请输入你的选择数字 (1, 2...) 5.2 模拟一条爆仓路径输入1选择高频套利组。状态栏刷新本金 100 万压力上升到 30%。剧情输出滑点偏差场景两个选项手动平仓或死守。输入2选择死守均值回归。状态栏刷新本金 100 万压力瞬间攀升到 80%压力值显示为黄色警告。剧情判定均值回归胜利赚取 40 万本金攀升到 140 万。此时只有一个选项申请休假。如果你在上一关选择的是多因子组然后加三倍杠杆本金会遭受 -150 万的重创扣减后本金变为 -50 万。主循环运行到头部时风控拦截模块瞬间触发【风控爆仓警告】你的本金低于或等于 0 元触发强制平仓出局流程...然后跳转到game_over_bankruptcy节点打印终局文本后优雅结束进程。5.3 验证压力值死亡路径重新启动游戏选择高频组然后选择死守。压力到 80% 后选择不休假而是继续加班复盘压力再加 25% 达到 105%。下一轮循环头部检测到stress 100强制跳转到game_over_stress节点输出心梗猝死结局。这两条路径验证了引擎的兜底逻辑即使 JSON 配置里没有显式写死亡跳转数值越界也能被主循环拦截并导向正确的终局。6. 常见报错排查与 JSON 图健康检查6.1 KeyError: high_frequency这是最常见的报错。原因是在 JSON 里某个choices[].next_node_id指向了一个不存在的节点 key。玩家做出选择后路由指针指向虚无键名下一轮循环加载时直接 KeyError 闪退。排查方法在load_story函数里加一段静态拓扑校验遍历所有节点的所有 choices确认next_node_id都在nodes的 key 集合里。def validate_graph(self): 静态校验 JSON 有向图的拓扑有效性 nodes self.story_data.get(nodes, {}) node_keys set(nodes.keys()) errors [] for node_id, node in nodes.items(): for idx, choice in enumerate(node.get(choices, [])): target choice.get(next_node_id) if target not in node_keys: errors.append(f节点 [{node_id}] 的第 {idx1} 个选项指向不存在的节点 [{target}]) if errors: for err in errors: logging.error(f【拓扑错误】{err}) sys.exit(1) logging.info(【拓扑校验】所有节点跳转目标均有效。)在load_story成功后调用self.validate_graph()这样在游戏启动阶段就能发现断链而不是等玩家走到那个分支才闪退。6.2 JSONDecodeError: Expecting , delimiter这是 JSON 语法错误通常是漏了逗号、括号不匹配、或者字符串里用了未转义的双引号。Cursor 和 VS Code 都会在编辑器里用红线标出语法问题保存前看一眼就行。如果报错信息里给了行号和列号直接跳过去检查那个位置。6.3 环状死循环检测如果两个节点互相指向对方且环内没有数值流失玩家可以无限刷选择。比如cut_loss指向overtime_studyovertime_study又指回cut_loss两个节点的 effects 都不扣本金也不加压力就形成了死循环。防范手段是写一个 DFS 遍历脚本在游戏启动时扫描 JSON。如果发现一个环且环内所有边的 effects 都没有对 capital 或 stress 产生单向变化就红字警告。这个检测逻辑可以放在validate_graph里一起做用递归或栈实现有向图环检测。6.4 压力值显示异常如果压力值显示为负数检查apply_effects里的钳制逻辑是否生效。代码里if self.stress 0: self.stress 0保证了最低为 0。如果显示超过 100 但游戏没结束检查主循环头部的拦截条件是否写在了正确的位置——必须在获取当前节点之前执行。7. 接入文档与模型验证入口项目跑通之后如果你想继续扩展剧情节点可以让 Cursor 辅助生成新的 JSON 片段。在 Cursor 对话里描述你想要的场景和数值变化让它按现有格式输出节点对象你粘贴到story.json的nodes里即可。需要管理 API Key 或查看调用量去控制台页面操作。想快速验证模型输出质量用模型对话页面直接测试。长期做编码和 Agent 开发的话Coding Plan 的配置方式更适合持续调用场景。接入文档里有完整的端点和参数说明遇到配置问题可以先查那里。新增关卡的操作步骤就三步在story.json的nodes里加一个节点对象确保next_node_id指向已存在的 key然后从某个现有节点的 choices 里加一条指向新节点的选项。不需要改 Python 代码不需要重启引擎重新运行脚本即可加载新 JSON。
