自己训 RL vs 逆向官方公式——两种「会打牌」的复盘

游戏自动化做出牌决策,路子大致两条:

  • A 路:把官方游戏里那个”自动出牌推荐”逆向出来,照着它的公式抄一遍
  • B 路:自己把游戏建成 Gym 环境,让 RL agent 从零学

两条都试过。这篇不写哪条赢——两边都有自己的舒适区,搞清楚各自适合什么场景比争胜负有用得多。

A 路:逆向官方公式

游戏里其实有内置的自动打牌——你把画面挂着不动几秒,会有一张手牌闪橙色边框,那就是它推荐的。

社区里有大佬已经把逆向做了大半(Vibbit’s Blog 有篇详细解析),核心方法叫 ExamRuleCalculator.Evaluate。算法本身不复杂:

预先分别计算每张手牌打出后的 evaluation 值,哪个大就出哪张。

evaluation 由 19 个参数加权求和而成。比如:

1
r1 = v1 × ProduceExamAutoEvaluation.evaluation

其中 v1 是当前局面的某个状态值(比如剩余回合数、当前集中、当前体力等),权重从主数据库里查表得到,跟”主打效果””剩余回合””玩法类型”三个维度索引。

主数据库里大概几千条这样的权重 entry。游戏每次更新可能调整。

抄进去之后好用吗

挺好用的。

直接抄一遍核心评估函数,接上自己写的”枚举每张手牌→算 evaluation→出最大值”,立马就能跑出一个能打的 agent。该过线过线,该高分高分。

好处太明显

  • 不用训练,写完即可用
  • 行为可解释——任何一次决策都能输出”为什么是这张”,权重一拉出来就懂
  • 不会”抽风”——纯算式,输入相同输出必相同
  • 调试友好——出问题能精确定位是哪个权重不对

那为啥还想搞 RL

因为这套启发式有几个绕不开的天花板。

第一,它只看当前回合的局部最优。 evaluation 计算的是”打出这张牌之后的状态分”,本质是贪心。有些局面下你需要”忍一回合不出强卡,攒到下回合 buff 起效再出”,启发式做不到——它每一步都挑当前最优解,整盘可能不是最优。

第二,它是开发者写出来的,不是为玩家爽点优化的。 游戏里那个自动推荐主要是”帮新手别打太烂”,目标是”过线”,不是”打高分”。你想冲分?这套启发式天花板就摆在那。

第三,新机制出来要重新逆向。 每次游戏大版本更新,新增了卡片效果、状态、机制,得重新挖一遍主数据库的权重表。维护成本不低。

第四,没法适配自己的目标。 我想训一个”专门刷高排名 fan vote”的 agent,启发式的目标函数和我的目标不一致,怎么调权重都拧巴。

B 路:自己训 RL

另一条路是把游戏建成 Gym 环境,自己训。

我们做的是这套:

  • 把游戏的核心战斗逻辑用纯 Python 重写一遍(这步是大头,几千行)
  • 包成 Gymnasium 接口
  • 用 MaskablePPO 训练(动作空间离散且有非法动作,必须用 mask)
  • 从课程学习入手:先学初级关卡,能过线了再学高级

代码组织大概这样:

1
2
3
4
5
6
7
8
9
train/gakumas_rl/src/
├── simulation/
│ ├── exam/runtime.py # 考试关卡的纯 Python 仿真
│ └── produce/runtime.py # 育成流程的仿真
├── training/
│ ├── autopilot.py # 课程学习的总调度
│ ├── self_bootstrap.py # BC 蒸馏 + RL 微调的自举
│ └── model.py
└── ...

卡在哪儿

仿真环境复现游戏机制,这一步比想象中难十倍。

游戏里看似简单的”一张卡的效果”,背后可能有触发顺序、buff 叠加优先级、上下取整规则、随机种子处理等一堆细节。我们前后对了几个月,对着真实游戏对局抓包反复校准,才把仿真精度调到”打 10 局,分数和真实游戏差异 ≤ 5%”。

仿真不准,训出来的 agent 一上真实游戏就抓瞎——它学的策略是基于错误规则的最优解,到了真规则下面就是次优解。

训练流水线

为了避免冷启动太痛苦,我们走了一套”模仿先行→RL 微调”的路线:

  1. 用启发式(A 路那套)跑大量数据,收集 (状态, 启发式选择) 对
  2. BC(Behavior Cloning)预训练——让神经网络先学会”像启发式那样打”
  3. PPO 微调——在启发式策略基础上继续探索更优解

效果:

  • 单跑 PPO 从零探索,前几百万步基本不收敛
  • BC 预训练 + PPO 微调,几十万步就能稳定超过纯启发式

启发式在这里反而成了 RL 的”师父”——给它一个像样的起点,比从随机策略开始强太多。

课程学习

游戏里有好几种难度档位:初级 Regular → 初级 Master → NIA Pro → NIA Master。直接训最高难度,agent 探索半天连过线都困难,PPO 收敛极慢。

课程的做法是按难度阶梯训:

1
2
3
4
5
6
7
初级中间考试 → 初级最终考试

NIA 中间考试 → NIA 最终考试 → NIA 选拔

初级 Regular 全流程 → 初级 Master 全流程

NIA Pro 全流程 → NIA Master 全流程

每阶段从上一阶段的 checkpoint 热启动。听起来繁琐,但比”直接训最难”快了一个量级。

两条路实测对比

挑几个能量化的维度比一下:

维度启发式(A 路)RL(B 路)
开发成本低(抄公式即可)高(仿真环境最贵)
维护成本中(版本更新要重新逆向)中(仿真要跟版本同步)
决策可解释性极强弱(黑箱)
决策稳定性完全可复现训练 seed 不同会有差异
过线率(初级关卡)接近 100%接近 100%
高分率(顶级关卡)中等较高(特别是 fan vote 那种长链优化)
应对新机制要重写公式要更新仿真,但策略可微调适应
异常局面处理严格按规则,遇到没考虑的情况会很离谱见过类似的状态有泛化能力

我们最后怎么用的

实战部署里两个都没扔——按场景挑。

日常自动化场景(清日常任务、刷材料、过普通关卡):用启发式。

这种场景诉求是”稳、快、能解释”。一个 evaluation 函数就搞定,没必要请 RL 出马。RL 模型加载、推理都比纯函数慢,没意义。

冲分场景(高难度活动、排行榜挑战):用 RL。

这种场景诉求是”在已知规则下逼近最优”。RL 训出来的策略能学到一些启发式想不到的”反直觉操作”——比如某些回合故意不打最强牌,留给后面叠 buff 后翻倍输出。

中间的判断由上层调度做,模型 strategy 接口都一致(这块的接口设计另外写了一篇)。

一些个人观察

写完两套之后我反过来想,启发式和 RL 不是对立关系,是上下游关系

  • 启发式给你一个”能解释、能调试、能稳定运行”的基线
  • RL 在这个基线之上”找那些人写不出来的细微策略”

一上来就想训 RL、不写启发式基线的项目,多半会卡死在”训了半天还不如随便写个规则”的尴尬阶段。

反过来,认为”有启发式就够了,RL 是花架子”也偏激——你的目标函数和启发式作者的目标函数永远不会完全一致,那一点点 gap,RL 真能补上。

下次有人问”游戏 AI 该走规则还是 RL”,我大概会回:”你两个都该有,然后按场景挑。”

欢迎关注我的其它发布渠道