持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第3天,点击查看活动详情。
背景
兄弟们,之前我开发了支持联机对战的五子棋、斗地主、UNO。在大家的呼吁之下,我准备开发「象棋」啦!
😄 不出意外,国庆假期,联机象棋就能跟大家见面了!
上次进展:
继续给大家同步进展:今天,定义了象棋棋盘状态的模型,实现了序列化象棋棋盘状态和反序列化的方法。
象棋棋盘状态模型
方案很多,列举常见的如下:
- 只存两个玩家每步的操作。当前棋局可以通过初始布局+操作步骤,迭代复现。支持悔棋。
- 只存当前的棋局、该谁下棋。不支持悔棋。
- 存储当前的棋局、双方历史操作,支持悔棋。
我的思考如下:
- 当前棋局应该是能够尽快展现出来的,而不是还要通过计算得出。
- 历史记录是必要的,悔棋非常重要,但是不一定要悔棋到最开始,所以保留最近100步的历史记录也足够了。
最终,我选择了方案三。
此外,如果要支持清除一部分历史记录,还需要多保存一份「该谁下棋」,因为清除一部分历史记录后无法通过历史记录的奇偶来判断该谁下棋了。
如何存储当前棋局
方案有3种:
- 象棋一共32个棋子,每个棋子有91种状态:死亡或位于0-89中任一位置。所以用长度为32的列表即可,每个数的值域是0-90,其中90代表死亡。
- 死亡的棋子不再占用空间,使用类似map的结构,key是棋子id,value是棋子位置(0-89)。
- 压缩空间的方案:将帅个子有9个可能在的位置,只需要0-9即可表示,需要5位二进制。士有5种位置,每个士只需要3位二进制。以此类推……占用空间相对最少。
其实方案三占用空间相对较少(其实还有占用空间更少的方案,但是计算难度过高,pass了),但是开发成本也较高,需要开发者去拼接二进制位。而方案二在棋子多的时候,占用空间较多,所以存储空间的大小不太稳定。
最终,我选择了方案一。
为什么要序列化
玩过我做的「五子棋」的人知道,里面有个核心功能:支持单人演练,即双方联机对战激战正酣时,允许一方fork一份当前棋局到一个本地对战房间,去模拟后续的棋局。极大降低了心算负担,提高了中级玩家的水平上限。
我做「象棋」时,也要实现「单机演练」功能,所以必须有一种办法,把棋盘状态存到URL中。这就是序列化的必要性。
如何序列化
本质就是将一个长度为32的数组(每项范围是0-90)序列化为字符串。
直接JSON.stringify是一种思路,但是长度大概有32*3=96这么长(每个项目包含逗号、2个数字)。
URL太长,不是个好事情。所以,我准备使用base64来序列化:
export function encodeSafeBase64(array: Uint8Array) {
return window.btoa(String.fromCharCode(...array)).replace(/\//g, '_').replace(/+/g, '-').replace(/=/g, '');
}
export function decodeSafeBase64(value: string) {
if (value.length % 4 === 1) return null;
if (!/^[a-zA-Z0-9_-]+$/.test(value)) return null;
let newValue = value.replace(/_/g, '/').replace(/-/g, '+');
if (newValue.length % 4 === 3) {
newValue += '=';
} else if (newValue.length % 4 === 2) {
newValue += '==';
}
return new Uint8Array(Array.from(window.atob(newValue)).map((i) => i.charCodeAt(0)));
}
最终,这样的初始棋盘[85, 84, 86, 83, 87, 82, 88, 81, 89, 64, 70, 54, 56, 58, 60, 62, 4, 3, 5, 2, 6, 1, 7, 0, 8, 19, 25, 27, 29, 31, 33, 35]
,可以被序列化为VVRWU1dSWFFZQEY2ODo8PkQDBQIGAQcACBMZGx0fISM
,只有43个字符。
并且这种序列化方法非常稳定,无论棋局怎么变,都只需要43或44个字符来表示了。
Base64原理
很多人误以为base64可以对字符串加密、解密。其实非也,base64只是跟JSON相似,是一种序列化算法,一种序列化二进制串的算法:可以把uin8数组(数组每个项目是8个二进制位),映射为字符串A至Z、a至z、0至9、/、+
(这里有64个字符,所以每个字符可以代表6位二进制)。
字符串也属于二进制串,即ASCII编码组成的数组,因此base64也能序列化字符串,但其实序列化字符串的意义不大。它只能通过一堆乱码骗骗普通用户,骗不了黑客。
最终,32位数组,有32*8=256位,256/6约等于43。也就是说,我们用43个字符,就记录了象棋的棋盘状态。
写在最后
我是HullQin,独立开发了《联机桌游合集》,是个网页,可以很方便的跟朋友联机玩斗地主、五子棋等游戏,不收费无广告。还独立开发了《合成大西瓜重制版》。还开发了《Dice Crush》参加Game Jam 2022。喜欢可以关注我噢~我有空了会分享做游戏的相关技术,会在这2个专栏里分享:《教你做小游戏》、《极致用户体验》。