本项目旨在打造一款高效、解耦、可扩展的《杀戮尖塔》AI 决策系统。通过无头渲染(Headless)技术绕开游戏引擎动画开销,利用云边端协同架构,实现从大规模并行训练到本地低延迟推理的全链路闭环。
项目采用 Monorepo 结构管理,核心逻辑遵循 “Protocol-First”(协议先行) 原则。
- Java(Mod)负责游戏状态拦截与指令注入,通过 Hook 游戏核心类(如
AbstractDungeon),在逻辑层直接提取数据。 - Python(RL)基于 Stable Baselines3 实现 PPO 强化学习算法,支持多实例并行数据采集,相比 UI 模式训练效率提升 200%–500%。
- C++/Qt(Client)作为轻量化决策客户端,集成 ONNX Runtime,实现单步推理延迟小于 5ms。
项目放弃传统 JSON 传输,统一使用 Google Protobuf 作为跨语言契约:
- 强类型校验:避免跨语言开发中的数据类型对齐问题。
- 高性能序列化:降低高频采样时的 CPU 开销。
- 防死锁机制:在协议中引入
sequence_id与is_waiting_for_input字段,保证指令执行的原子性。
当前使用的协议版本为 docs/protocols/sts_v1.proto,是所有跨语言通信的单一真理来源。
STS-AI-Master/
├── docs/ # 项目灵魂:协议定义与架构文档
│ ├── protocols/ # .proto 契约文件 (真理来源)
│ └── dev_logs/ # 每日开发日志与 Debug 记录
38→├── sts-bridge-mod/ # [Java] 游戏数据拦截 Mod
39→├── training-engine/ # [Python] 强化学习训练引擎 (Gym/PPO)
40→├── qt-decision-client/ # [C++] Qt/ONNX 落地推理客户端
41→├── cloud-backend/ # [Flask] 云端数据同步与策略中心
42→├── gym_sts/ # [Python] 独立 Gymnasium 环境封装包 (SlayTheSpireEnv)
43→└── tools/ # 自动化脚本工具箱 (一键编译/无头启动)
- Java 8+
- Python 3.10+(依赖见
training-engine/requirements.txt) - CMake 与 C++17 编译器
- protoc ≥ 3.13.0(已在本仓库开发环境中验证)
git clone https://github.com/YourUsername/STS-AI-Master.git
cd STS-AI-Master- Linux/WSL:
chmod +x tools/protobuf_compile.sh
./tools/protobuf_compile.sh- Windows(PowerShell 或 CMD):
cd C:\Projects\GitHub\STS-AI-Master
.\tools\protobuf_compile.bat执行成功后,将分别生成:
- Java 协议代码:
sts-bridge-mod/src/main/java/ - Python 协议代码:
training-engine/envs/sts_v1_pb2.py - C++ 协议代码:
qt-decision-client/include/sts_v1.pb.h
-
Phase 0:协议定义与 Java Mod Bridge —— 已完成
- 基于
docs/protocols/sts_v1.proto建立统一跨语言协议(包含GameState、ActionCommand、ProtocolMessage等核心结构)。 - 完成
sts-bridge-modMod 工程搭建,通过@SpireInitializer+@SpirePatch(AbstractDungeon.update)拦截游戏状态。 - 接入
protobuf-maven-plugin与独立的protobuf/sts_state.proto,实现结构化的玩家与怪物状态采样。 - 使用
maven-shade-plugin将protobuf-java打入 Mod 并通过 Relocation 将com.google.protobuf重定位到sts.ai.bridge.repackaged.protobuf,解决运行时依赖缺失与潜在类加载冲突。 - 在战斗中每 3 秒输出一条带有
[STS-AI-PROTO]前缀的GameState文本日志,验证数据实时性与完整性(详见docs/dev_logs/STAGE_1_SUMMARY.md)。 - 在
protobuf/sts_state.proto中扩展GameOutcome、GameState.master_deck等字段,并为卡牌 / 遗物 / 药水预留价格字段,满足后续训练对长期构筑和购买决策的需求(详见docs/dev_logs/ENVIRONMENT_AUDIT_REPORT.md)。 - Java 端 Socket 服务支持通过
-Dsts.ai.port=XXXX动态配置端口,解除 9999 硬编码限制,可在同一物理机上并行启动多个无头实例。 - 新增
RESET动作与对应处理逻辑(基于CardCrawlGame.startOver),Episode 结束后可自动重开,无需人工干预。 - 引入
tools/launch_headless.sh一键无头启动脚本,形成 “启动游戏 → 建立 Socket → 周期采样 → 接收动作 → 自动重开” 的完整训练闭环。
- 基于
-
Stage 1:数据结构化与感知打通 —— 已完成
- 将原始字符串日志升级为 Protobuf 协议对象
GameState的构造与打印。 - 数据覆盖范围:
- Player:HP、Max HP、Gold、Energy、Block、Floor。
- Monsters:ID、Name、HP、Max HP、Intent、Block。
- 在当前版本中进一步扩展观测空间,包含 Master Deck、游戏结局摘要(胜利 / 死亡、近似得分、进阶等级)以及商店与篝火的关键占位信息,满足强化学习对长程收益与经济策略的建模需求。
- 通过
[STS-AI-PROTO]与[STS-AI-ACTION]日志为 Python / Qt 侧提供稳定的外部观测与动作回放入口,为后续 Gym 环境与训练闭环打基础。
- 将原始字符串日志升级为 Protobuf 协议对象
-
Stage 2:Gymnasium 环境封装与训练就绪 —— 进行中
- 新增独立的
gym_sts包,基于 Gym (0.26+) 与 Stable Baselines3 封装标准化的SlayTheSpireEnv(Dict 观测空间 + Discrete 动作空间 + 动作掩码)。 - 提供
test_single_episode.py、train_ppo.py、validate_model.py三个脚本,分别用于单局调试、PPO 训练示例与可视化验证。 - 在 Python 侧固定关键依赖版本:
protobuf==3.20.x、numpy<2.0.0,确保与现有 Protobuf 生成代码及 Gym 生态兼容。 - 修正 Java 端出牌逻辑,使用
AbstractPlayer.useCard执行PLAY_CARD,保证能量消耗、手牌移除与遗物/力量触发与原版行为一致。
- 新增独立的
-
实际可落地的使用场景:
- 使用
tools/launch_headless.sh启动无头 Slay the Spire 实例,指定训练端口并自动加载basemod, stslib, sts-ai-bridge。 - 在 Python 侧通过 Socket(参考
python_ai/client_demo.py或后续 Gym 环境)接入,实时接收包含 Master Deck / GameOutcome / Shop 概要等字段的GameState,并下发动作指令。 - 通过
RESET动作实现 Episode 结束后的自动重开,支撑长时间连续采样与多进程并行训练。
- 使用
-
启动与并行示例:
- 单实例无头启动:
./tools/launch_headless.sh 10001
- 多实例并行(示意):
./tools/launch_headless.sh 10001./tools/launch_headless.sh 10002
- 对应地,在 Python 环境中为每个进程配置匹配的
host:port,即可形成 N 个独立的环境副本。
- 单实例无头启动:
-
使用须知:
- 当前版本的
GameOutcome.score为基于楼层与进阶等级的近似得分,用于训练阶段的相对奖励信号,并非严格还原游戏 UI 上的最终分数。 - 商店中卡牌 / 遗物 / 药水的价格字段已经通过 Java 反射从
ShopScreen内部结构中读取并写入 Protobuf,不再依赖占位实现。
- 当前版本的
在 Phase 0 的基础上,第一阶段完成了针对训练环境鲁棒性的系统加固,目前支持的核心特性包括:
-
反射驱动的数据采集:
- 通过 Java Reflection 访问
ShopScreen的私有字段,完整采集商店中的卡牌、遗物、药水列表及其价格,并映射到ShopState中的cards/relics/potions结构。 - 通过反射读取
CampfireUI的buttons列表,动态识别休息点可用选项(Rest / Smith / Dig / Lift / Toke 等),并映射到RestSiteState的布尔标记字段。
- 通过 Java Reflection 访问
-
地图自动导航(Map Node 跳转):
- 实现
CHOOSE_MAP_NODE动作:根据(x, y)坐标在AbstractDungeon.map中查找目标节点,设置AbstractDungeon.nextRoom,更新pathX/pathY并调用nextRoomTransitionStart()。 - 在无头模式下无需鼠标事件即可完成稳定的房间跳转,适配自动化训练与批量环境。
- 实现
-
自动重开(Reset)与并行适配:
- 通过
RESET动作设置CardCrawlGame.startOver = true,在 Episode 结束后由游戏主循环驱动新一局的创建,实现真正的“无人值守长时间训练”。 - Socket 端口由 JVM 参数
-Dsts.ai.port=XXXX动态配置,可在同一物理机上启动多实例无头环境,为后续多进程 Gymnasium 封装提供基础。
- 通过
-
Protobuf 采样优化:
- 在手牌序列化时调用
calculateCardDamage(null),同步导出当前上下文下的伤害 / 格挡等关键数值,为策略网络提供更贴近实时战局的观测。 CardState.is_playable字段综合考虑能量、卡牌自身限制与cardPlayable结果,可直接作为动作掩码(Action Mask)的基础。
- 在手牌序列化时调用
| Phase | 名称 | 说明 | 状态 |
|---|---|---|---|
| 0 | 协议定义与 Java Mod Bridge | 设计跨语言 Protobuf 协议,搭建 Mod 拦截层与结构化状态采样通路(含 Shade + Relocation) | 已完成 |
| 1 | Python 训练引擎 | 构建 Gymnasium 环境封装与 PPO 训练流水线,打通多进程采样到训练闭环 | 进行中 |
| 2 | Qt 决策客户端 | 基于 ONNX Runtime 的本地推理客户端与可视化面板 | 规划中 |
| 3 | 大规模训练与评估 | 多环境并行训练、策略评估与对比实验工具链 | 规划中 |
| 4 | 云端策略中心与数据同步 | 云端数据存储、策略管理与下发,支撑多终端共享策略 | 规划中 |
Phase 0 聚焦于协议定义与 Java Mod 工程,目前已完成基础协议、Bridge Mod 和结构化采样链路;后续阶段将依次推进 Python 训练引擎、Qt 客户端与云端策略中心。
-
Protobuf 相关错误:
- 现象:运行 Python 客户端时出现
TypeError: Descriptors cannot be created directly。 - 快速检查:
- 使用
pip show protobuf确认当前版本是否在<= 3.20.x区间。 - 确认
sts_state_pb2.py是由兼容版本的protoc重新生成(包含create_key=_descriptor._internal_create_key等字段)。 - 在 Python 客户端中,优先使用
always_print_fields_with_no_presence等兼容参数,而非较新文档中的替代参数名。 - 必要时设置环境变量:
PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python以提升兼容性。
- 使用
- 现象:运行 Python 客户端时出现
-
端口占用与 Socket 连接失败:
- 现象:
- Java 端日志中出现
Address already in use: JVM_Bind或无法看到Listening on port 9999。 - Python 客户端报错
ConnectionRefusedError,无法连接到127.0.0.1:9999。
- Java 端日志中出现
- 快速检查:
- 使用
netstat -ano | findstr 9999确认端口占用情况,如有残留进程,使用taskkill /PID <pid> /F清理。 - 确认 STS-AI Bridge Mod 已成功加载,并在控制台看到
[STS-AI-SOCKET] Listening on port 9999日志。 - 检查防火墙或杀毒软件是否拦截本地回环连接。
- 使用
- 现象:
-
Gym / NumPy 兼容性:
- 现象:
- 运行 Gym 环境时出现
module 'numpy' has no attribute 'bool8'或大量关于 NumPy 2.x 的兼容性警告。
- 运行 Gym 环境时出现
- 快速检查:
- 使用
pip show numpy查看版本,建议固定在< 2.0.0(如1.26.x)以保持与 Gym 0.26 兼容。 - 如需使用 Gymnasium,请参考官方迁移文档,将
import gym替换为import gymnasium as gym并适配 API 差异。
- 使用
- 现象:
本项目采用 MIT License,详情见 LICENSE 文件。