确定性游戏引擎:为什么纯函数加种子随机数让多人联机几乎白送
原文:https://dev.to/mighty840/a-pure-seeded-game-engine-gives-you-multiplayer-almost-for-free-4412(作者 @mighty840)
这套思路的实战案例是一款浏览器桌游(Samudra Manthana):给它加在线多人模式的过程顺利得有点反高潮——不是因为网络代码(netcode)好写,而是一个早期定下的设计约束干完了大部分活。
这条约束是:引擎必须是一个纯的、确定性的、可序列化的函数。
export function applyAction(state: GameState, action: Action): GameState {
const s: GameState = structuredClone(state); // 永远不要修改传入的输入
// ... 校验并应用,全部只依赖 s 和 action ...
return s;
}
没有 Date.now(),没有 Math.random(),没有 I/O,不向外部伸手。同样的输入,永远得到同样的输出。每一分随机性都来自一个带种子的 PRNG,它的状态就存在 GameState 内部。
随机数生成器只是一个 uint32
它就是 mulberry32——一个完整的 PRNG 只用一个 32 位整数当状态,由字符串哈希播种:
// 字符串种子 -> uint32 初始状态
function hashSeed(seed: string): number {
let h = 1779033703 ^ seed.length;
for (let i = 0; i < seed.length; i++) {
h = Math.imul(h ^ seed.charCodeAt(i), 3432918353);
h = (h << 13) | (h >>> 19);
}
return (h ^ (h >>> 16)) >>> 0;
}
class Rng {
state: number;
next(): number {
let t = (this.state = (this.state + 0x6d2b79f5) | 0);
t = Math.imul(t ^ (t >>> 15), t | 1);
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
}
int(maxExclusive: number) { return Math.floor(this.next() * maxExclusive); }
}
由于 RNG 状态会随其他所有内容一起被序列化,一局游戏可以被「种子 + 已执行的动作列表」完整描述。单凭这一条性质,就能换来一大堆好处。
多人联机是顺手白送的
服务器不需要重新实现任何规则。它持有权威的 GameState,跑的是和客户端一模一样的 applyAction。客户端发送动作,服务器通过一层薄适配器校验并应用,然后广播新状态:
interface GameAdapter {
currentSeat(state): string | null;
isTerminal(state): boolean;
winner(state): string | null;
// 校验并应用来自某个座位的动作;返回新状态,非法则返回 null。
apply(state, seat, action): unknown | null;
botAction(state, seat, persona): unknown | null;
}
不需要调和任何客户端预测(client-side prediction),不存在「不同步(desync)」这一整类 bug,回放功能也是白送的(种子 + 动作列表)。这个适配器甚至是游戏无关的,一个服务器进程就能同时服务两款不同的游戏。
唯一值得提醒的坑
有意思的故障出在动作规范化(action canonicalisation)上。客户端经常省略可选字段(比如「生成一个单位」却不说生成在哪里,默认落在老家)。服务器用一个 key() 函数把收到的动作与合法动作集做匹配——如果 key() 解析默认值的方式和引擎不一致,一个完全合法的走法就会被当成非法操作拒收。在 key() 按照 at ?? homeHex 以和引擎完全相同的方式补全默认值之前,非雇佣兵氏族一直无法在线生成单位。
经验教训:客户端和服务器必须以完全相同的方式规范化动作。 确定性让正确性在各处都是白送的,除了一个边界——一个欠明确的动作在这里变成具体动作。把这个边界钉死,剩下的部分真的几乎是免费的。
可以在浏览器里试玩这款游戏(热座、人机或在线模式):<https://mighty840.itch.io/samudra-manthan>
原文:https://dev.to/mighty840/a-pure-seeded-game-engine-gives-you-multiplayer-almost-for-free-4412(作者 @mighty840)
