WPE兵棋框架
为了花掉充的ds,于是搓了一个新项目——WPE,全称 Wargame Pipeline Engine,一台”吃兵棋规则”的通用引擎。一句话总结就是,通过写配置规则和素材就能让新项目跑起来。当然,这种个人项目,肯定得是纯粹的vibe coding了,有空就弄一点。
项目链接:
https://github.com/hwang041/WPE
启动:
交给ai
不交给ai的话,根下放了一个简单的bat,点击以后选择路径下已有的项就行了。
前
本人断断续续入坑了也有个十来年了,虽说也没推过什么大棋,但怎么说也算是个兵棋佬了。这次想搓新项目,目光就放在兵棋上了。刚开始是希望通过ai能力,把一些棋的规则和素材电子化,搞成html之类的静态游戏玩。但是后来发现电子化需要录入贴图的数据,这太麻烦了,需要能工智人(也就是本人)的深度参与,有违初衷。于是,决定直接搓一个兵棋引擎,让ai能够快速批量出简单的棋(一般来说质量比较低)来消遣。
一个游戏包 = 一堆表
在 WPE 里,一款棋不是一段代码,而是一个目录,里面全是 JSON:
1
2
3
4
5
6
7
8
games/<名字>/
game.json 主规则:规则选择 + 阶段/行动/触发/胜利 + 势力配色
map.json 地图:网格/地形/河流/胜利点
movement.json 移动表:地形移动费 + 防御修正 + 河流费
combat.json 战斗表:战力比 × 骰子 → 结果码
units.json 算子目录:键 → 基础属性
scenario.json 剧本:选算子/势力/部署/援军
cards.json 卡牌(可选)
战斗表、移动表、地图、算子、剧本全部是数据。想调平衡性?改表。想做新棋?复制模板、填表、verify 通过,就能跑。
这是我觉得整个项目里最值得一说的设计,配置和编排化。这个其实已经是成熟的软件工程设计范式了,给编排器提供大量的低耦合正交单元去编排。兵棋规则看似千差万别,拆开看,大多是若干相互独立(正交)的机制轴:地图用什么几何、移动怎么算、战斗怎么结算、有没有控制区、有没有补给线……
所以 WPE 把规则拆成一个个变体,由能力来组合,规则自己声明”提供什么、依赖什么”,平台按能力装配。
当前放了一些简单的棋,美术就不要指望了,如果不是我要试玩,完全可以在cli里完成调试。(乐,能工智人限制了效率说是)
game.json 里到底写了啥
前面那些表都是”料”,game.json 才是”谱”——它决定这盘棋用哪套规则、怎么分阶段、玩家能做什么、怎样算赢。拿徐州之战举例,主干长这样:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
"id": "xuzhou",
"name": "徐州之战",
"playerCount": 2,
"family": "default", // 家族预设:一口气装齐常用能力
"rules": { // 逐能力指定变体 + 对应读哪张表
"map": { "variant": "hexGrid", "file": "map.json" },
"counter": { "variant": "generic", "file": "units.json" },
"movement": { "variant": "movePoints", "file": "movement.json" },
"combat": { "variant": "crTable", "file": "combat.json" },
"dice": { "variant": "d6" }
},
"phaseOrder": ["action", "turnEnd"], // 一个回合里阶段的先后
"turnReset": [ // 回合开始要重置什么
{ "attr": "acted", "value": "0" },
{ "attr": "moveLeft", "value": "expr:effMove(counter)" }
],
"moves": { /* 见下 */ },
"endConditions": [ // 胜利条件,可多条叠加
{ "when": "vpHeld(me) >= 2 && me == 0", "message": "曹操连克两城,徐州易主!", "winner": 0 },
{ "when": "turn >= 10 && me == 1", "message": "曹操粮尽退兵,陶谦守住徐州!", "winner": 1 }
]
}
rules 那一栏就是前面说的”选规则”:左边是能力,右边指定用哪个变体、读哪张表。想换玩法基本就在这动手——比如把 combat 从 crTable 换成 oddsShift,表跟着换一份,引擎代码一行不动。
真正有意思的是 moves:玩家的每一个动作,都被拆成一条”校验 + 结算”的配置。还是徐州之战,这是”移动”:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
"move": {
"kind": "movement",
"phase": "action",
"counterFilter": "counter.owner == me", // 只能动自己的算子
"validators": [ // 先过校验,才允许执行
"notacted(counter)", // 本回合还没动过
"dist(counter, pos) == 1", // 目标是相邻格
"moveCost(counter, pos) <= counter.moveLeft", // 移动力够
"not(occupied(pos))" // 目标格没人
],
"effects": [ // 校验通过后依次结算
{ "effect": "move", "counter": "counter", "to": "pos" },
{ "effect": "setattr", "counter": "counter", "key": "moveLeft",
"value": "counter.moveLeft - moveCost(counter, pos)" },
{ "effect": "log", "text": "'进入' + terrainAt(pos) + '地形,剩余移动力' + counter.moveLeft" }
]
}
这里的 notacted / dist / moveCost / terrainAt 就是引擎的表达式语言:规则变体把自己的能力暴露成函数,游戏作者在 validators(能不能做)和 effects(做完发生什么)里把它们拼起来。小众规则基本都能在这一层表达;实在表达不了的,game.json 还留了个 hookType 后门,指向一段 C# 兜底。攻击那条同理,只是多一个 roll 掷骰、一个 combat 接口,以及一张按结果码分派的 resultEffects 表(后撤 / 翻面 / 消灭各算一套)。
所以期望上,做一款新棋 = 写一份这样的 game.json,把用到的表填好,verify 一过就能跑。这也是我一开始想要的:让 AI 批量出棋,人只负责拍板和试玩。
不过坦诚地说,期望很美好,已经实现的还不多。也只是经典六角格下的一些主要规则,补充了一些点对点和卡驱的内容。更多的还要慢慢完善,就是进度没有排期呢,毕竟是个人项目嘛..