0
点赞
收藏
分享

微信扫一扫

[教你做小游戏] 用JS实现平均每步仅占10bit位的象棋历史记录保存方案(decode篇)

背景

兄弟们,之前我开发了支持联机对战的五子棋、斗地主、UNO。在大家的呼吁之下,我准备开发「象棋」啦!

😄 联机象棋马上就能跟大家见面了!

之前的进展:

  • 《用SVG画一个象棋棋盘》。
  • 《基于svg和ttf(字体文件),我仅用6kb就画完了象棋所有棋子》。
  • 《我用43个字符,就存下了象棋的棋盘状态》。
  • 《JS实现象棋移动规则》。
  • 《一种记录象棋历史记录的方案:平均每步仅占10bit位》。
  • 《用JS实现平均每步仅占10bit位的象棋历史记录保存方案(encode篇)》。

今天的进展:开发完毕了decode象棋历史记录的逻辑。

decode难点

相比encode,decode其实是更难的事情。

因为encode时,你只需要无脑往一个字节串后面补充就好。而decode需要你非常清楚,每一位的作用,并理解他们的含义。你需要有高超的位运算技巧,才能轻易完成。

如何知道一共有多少项目

设计数据结构时,我们没有把项目数作为一个变量,所以数组长度是未知的。

也就是说,我们必须不断循环,直到这个字节串没有内容了,我们就终止。

如何读取制定长度bit位的内容

我们封装一个函数​​readBits​​,用于读取某个字节串,从第x位开始、长度为n的内容。

因此,需要3个参数:

  • 字节串​​array​
  • 位偏移量​​bitsOffset​
  • 要读取的长度​​bitsLength​

返回值主要是对应的内容(可以用一个uint8来表示),此外,读取后还需要更新一下调用者的位偏移量​​bitsOffset​​​,方便持续调用,所以我们顺便把新的位偏移量​​bitsOffset​​返回,作为返回值第二项。

function readBits(array: Uint8Array, bitsOffset: number, bitsLength: number) {
const offset = bitsOffset % 8;
const index = Math.floor(bitsOffset / 8);
if ((offset + bitsLength > 8 && index + 1 >= array.length) || offset + bitsLength <= 8 && index >= array.length) {
throw new Error('readBitsError');
}
let number = offset + bitsLength <= 8 ? array[index] : (array[index] << 8) | array[index + 1];
const length = offset + bitsLength <= 8 ? 8 : 16;
number >>= (length - bitsLength - offset);
number &= ([0, 1, 3, 7, 15, 31, 63][bitsLength]);
return [number, bitsOffset + bitsLength];
}

解释

在本文场景下,要读取的长度​​bitsLength​​不超过8,所以我们要关注的数据量,只会来自1个uint8或者某连续2个uint8。

计算​​index​​就是为了判断第一个关键的uint8的位置。

计算​​offset​​​,知道应该从​​index​​的第几位开始算数。

然后通过比较​​offset + bitsLength​​​和​​8​​的大小,就知道我们需要关注1个uint8即可、还是需要关注连续2个uint8。

我们把需要关注的uint8赋值给​​number​​​,用​​length​​表示我们关注8位还是16位。

例如number二进制是​​10110000​​​,我们需要取从2开始的长度为2的内容(即​​11​​)。该怎么做呢?

只需要把它右移4位(用于删除不需要的后缀),再跟二进制​​11​​做个与操作(用于删除不需要的前缀),即可。

因此代码会这样写:​​number >>= (length - bitsLength - offset);​​​ ​​number &= ([0, 1, 3, 7, 15, 31, 63][bitsLength]);​​。

其中0 1 3 7 15 31 63,对应二进制分别是0 1 11 111 1111 11111 111111。都是为了删除前缀。

这里因为我需要的bitsLength有限,所以我用这种方式偷懒了。如果你要做的更通用,可能要这样写:​​2 ** bitsLength - 1​​,目的是获取位长度为bitsLength的全是1的数字,用于删除number不需要的前缀。

​readBits​​开发完毕,以后可以这样调用:

let current;
let bitsOffset = 0;
[current, bitsOffset] = readBits(array, bitsOffset, 4);

这会读取字节串array的从第0位开始、长度为4个bit位的内容,赋值给current变量。

写在最后

我是HullQin,独立开发了《联机桌游合集》,是个网页,可以很方便的跟朋友联机玩斗地主、五子棋等游戏,不收费无广告。还独立开发了《合成大西瓜重制版》。还开发了《Dice Crush》参加Game Jam 2022。喜欢可以关注我噢~我有空了会分享做游戏的相关技术,会在这2个专栏里分享:《教你做小游戏》、《极致用户体验》。


举报

相关推荐

0 条评论