大学项目(ORG 2026.1)。一个完全用RISC-V汇编编写的RPG战斗模拟器和蒙特卡洛引擎。
背景
我从零开始用纯RISC-V汇编构建了一个RPG战斗引擎。目标是编写三种不同的AI状态机,将它们放入一个无头模拟器中,运行10,000场自动对战,看看哪种策略能胜出。
战斗围绕严格的行动经济系统展开。MP恢复缓慢,但可以通过特定技能进行过载充能。
| 技能 | 消耗 | 效果 |
|---|---|---|
| 攻击 | 免费 | 投掷1d20。<10未命中,10+命中,20暴击造成双倍伤害。 |
| 防御 | 免费 | 格挡受到的伤害。高掷骰结果触发反击。 |
| 绝对意志 | 20 MP | 绕过骰子,保证暴击。 |
| 灵魂吸取 | 免费 | 施法者承受1-12点反噬伤害,从敌人MP中吸取等量数值,并为自己获得4倍该数值。唯一能突破100 MP上限的方式。 |
| 最终处决 | 150 MP | 800%伤害的核弹。若目标生命值高于50%则失败。 |
| 镜面护盾 | 30 MP | 将下一次受到的攻击反射回攻击者。 |
参赛选手
我编写了三个机器人,每个都有完全不同的思维方式:
Flowey(混沌): 纯随机数生成器。他不做任何评估,只是随机选一个数字来决定下一步行动。
decision_random:
li a0, 6
call randomizer # 选一个1到6之间的数字,这就是全部策略
j decision_end
Chara(努力型): 她优化了一套连招:不断使用灵魂吸取直到MP达到150,将敌人生命值削减到50%以下,然后释放最终处决。在代码中这个策略也被命名为"智能",但实际并非如此。
decision_smart:
# 我写了这个策略
# 它的思路是使用灵魂吸取直到可以使用处决
# 但为此我们还需要存活下来并将敌人生命值降到50%
# 同时不能死亡
...
li t6, 150 # 处决的消耗
blt t4, t6, decision_smart_my_mana_is_low # mp < 150?去刷MP
bge a2, t6, decision_smart_enemy_hp_high # 敌人生命值 > 50?去压制
j decision_smart_i_can_kill # 否则,执行处决
decision_smart_my_mana_is_low:
li a0, 4 # 灵魂吸取
decision_smart_enemy_hp_high:
li a0, 2 # 绝对意志
decision_smart_i_can_kill:
li a0, 5 # 最终处决
Toby(硬克制型): 专门为克制Chara而构建。他同时关注自己的生命值和敌人的MP条。如果自己生命值低于50且敌人MP达到150,他就举起镜面护盾让核弹反弹回去。除此之外,他混合使用攻击和灵魂吸取,条件满足时也能自己释放最终处决。
decision_troll_checks:
li t6, 50
ble t5, t6, decision_troll_check_enemy_mp # 我的生命值够低需要担心吗?
j decision_troll_not_execute
decision_troll_check_enemy_mp:
li t6, 150
bge a3, t6, decision_troll_prepare_against_execute # 敌人MP接近150了吗?
j decision_troll_not_execute
decision_troll_prepare_against_execute:
li a0, 6 # 镜面护盾
ret
基准测试结果
在终端中使用rars.jar(基于RISC-V的Java模拟器RARS的jar包)运行10,000场对战后,结果如下:
| 角色 | 胜率 |
|---|---|
| Flowey | ~52% |
| Toby | ~29% |
| Chara | ~17% |
最愚蠢的AI赢得了绝大多数比赛。
Chara的17%胜率暴露了僵化贪婪连招的问题。为了达到150 MP,她必须承受灵魂吸取的反噬伤害。而Toby经常举起护盾,Chara的脚本迫使她不断消耗自己的生命值,直到在释放终极技能前把自己打死。
Toby的29%胜率来自成功引诱Chara。但面对Flowey时情况不同:护盾触发条件要求敌人MP接近150,而Flowey从不刻意积累MP。这个反制从未触发,所以Toby只能执行默认的攻击和灵魂吸取策略,这让他对混沌型对手没有任何优势。
Flowey以52%的胜率获胜,因为没有策略反而无法被持续反制。他从不为了设置大招而承受反噬伤害;他只是偶然打出高价值动作。事实证明,如果你的整个代码库依赖于预测敌人行为,那么面对一个毫无理由行动的敌人,你自动就会输。
这真的令人惊讶吗?
可能并不。随机智能体主导确定性智能体是策略模拟中一个被充分记录的结果。
我在学习小组中遇到的一个Citadel模拟产生了相同的模式:某些策略稳定地击败随机,某些被随机碾压,还有一些能击败特定对手但输给其他对手。石头剪刀布的动态关系确实存在,但随机仍然在大多数对局中站稳了脚跟。
区别在于,在更丰富的环境中(更多策略、更多决策变量、智能体更多利用随机的方式),结果往往更加分散。如果行动空间足够大,一个精心调整的确定性策略可以建立起可靠的领先优势。
在这里,可能并非如此。只有六种可能的行动,加上一个单次幸运暴击就能结束比赛的战斗系统,Chara的精心策划和Flowey的随机按键之间的差距并没有那么大。Chara的连招需要几个回合的铺垫,而随机数生成器有充足的机会在她达成目标前杀死她。行动空间可能太小、波动太大,以至于贪婪策略无法持续超越混沌。
换句话说:Flowey获胜与其说是一个发现,不如说是一个设计约束。一个更聪明的Chara需要一个更聪明的游戏来证明自己。
为什么这些数字不完全可靠
在从基准测试得出结论之前,有几个结构性偏差值得承认。
对战并非独立进行。 每场比赛双方都会随机分配一个策略,这意味着胜率是所有可能配对的总和,而不是干净的1v1统计数据。Flowey的52%包括Flowey对战另一个Flowey的比赛,而其中一方必须获胜。要真正知道Chara是否击败Toby,或者Flowey是否平等地击败所有人,你需要固定对战组合并分别运行每个配对。
回合顺序从未轮换。 玩家1总是先行动。在单次暴击就能结束比赛的系统中,先手是一个真正的优势。结果完全没有考虑这一点。
随机数生成器是一个自定义的xorshift,以系统时间为种子。 对于大学项目来说已经足够好,但它不是一个经过统计验证的生成器。如果6种可能行动的输出分布不均匀,Flowey的结果会直接受到影响,因为他的整个策略就是调用这个函数。
行动空间非常小。 六种可能的行动意味着任何策略都很难将自己与混沌区分开来。在更深的系统中,Chara的连招可能更难被打断,或者可能有恢复选项来减少她设置的代价。在这里,游戏恰好足够惩罚性,以至于她的反噬伤害经常在收益到来之前就结束了比赛。
Chara可能设计得不够好。 她的策略没有防御性的后备方案。如果连招条件不满足且她正在承受大量伤害,她仍然继续刷MP。一个更健壮的版本会在生命值低于某个阈值时切换到生存模式,这很可能会显著提高她的数据。
这些结果在方向上很有趣,但应被视为这个特定配置的快照,而不是关于策略与随机性的普遍性陈述。
未来研究
显而易见的下一步是完全脱离RARS。这个版本运行在基于Java的RISC-V模拟器上,这给模拟速度设置了硬性上限。第二个版本针对真实的编译型RISC-V,使用Linux系统调用而不是RARS ecalls,应该会快得多,一旦比赛数量开始攀升到数百万,这一点就非常重要。
除了性能之外,更有趣的方向是在设计方面:
每场比赛更多玩家。 目前总是1v1。加入第三个或第四个参与者会完全改变战略格局。突然间,反制策略必须考虑同时从两个方向受到攻击,纯随机性变得更难维持。
汇编中的机器学习。 想法是直接在RISC-V中实现一个最小的ML算法,使用矩阵运算让机器人根据比赛结果更新自己的权重。没有外部库,没有高级运行时,只有寄存器中的整数矩阵运算。这是否实用,或者只是一个有趣的痛苦实现过程,正是其吸引力的一部分。
更多策略和更深的引擎。 当前的行动空间太小,任何策略都无法有意义地超越随机。从《魔兽世界》中汲取更多直接灵感(冷却时间、资源权衡、定位效果、更多样化的技能互动)将为精心设计的策略提供实际空间来证明自己优于混沌。
基准测试基础设施已经就位。瓶颈在于游戏是否足够深入,使结果具有意义。
运行方式
要编译汇编代码并在本地运行引擎:
make simulate
