[教你做小游戏] 车炮能移动17个位置,针对90种出发点,如何建立0-16和目标点的映射?
背景
兄弟们,之前我开发了支持联机对战的五子棋、斗地主、UNO。在大家的呼吁之下,我准备开发「象棋」啦!
😄 联机象棋马上就能跟大家见面了!
之前的进展:
- 《用SVG画一个象棋棋盘》。
- 《基于svg和ttf(字体文件),我仅用6kb就画完了象棋所有棋子》。
- 《我用43个字符,就存下了象棋的棋盘状态》。
- 《JS实现象棋移动规则》。
- 《一种记录象棋历史记录的方案:平均每步仅占10bit位》。
- 《用JS实现平均每步仅占10bit位的象棋历史记录保存方案(encode篇)》。
- 《用JS实现平均每步仅占10bit位的象棋历史记录保存方案(decode篇)》。
- 《极致压缩:用2至5位二进制表示17种可能性》。
今天给大家聊一个难题:
在文章《一种记录象棋历史记录的方案:平均每步仅占10bit位》中我提到:车和炮有17个可移动范围。但是车和炮可以存在于象棋棋盘上任意位置,也就是说有90个起点,在每个起点,都有17个可移动范围。如果我们存储时仅仅存下了0-16这17个数字及其出发点,该如何知道对应的目标点呢?
也就是说,我们需要建立一种一对一映射关系,把起点A至终点B的所有可能性映射到0-16上,当然,这种映射关系需要覆盖到起点A的90种可能。
方式一:语义化+映射表
理论知识
我们根据游戏规则,赋予0-16各自语义。
我们不妨把出发点叫做Before,目标点叫做After,0-16叫做Delta。
例如:规定Delta为0-8表示横向移动,Delta为9-16表示纵向移动。
然后,我们再绘制「映射表」。
例如,这是横向移动映射表:
第一列表示Before,即棋子的出发点的X坐标,第一行表示After,即棋子的目标点的X坐标。当然Before不可以等于After,所以斜线处都是空的。表格主体内容就是Delta(0-8)。
我们只要针对每个Before,把0-8分配给不同的After即可。需要有这样的限制:同一Before、不同After的Delta不同;同一After不同Before的Delta不同。
如果满足这个条件,Before、After、Delta三个数就有这样的效果:知道任意2项,就能推理出剩下一项。
只有这样,才是满足要求的。因为我们在生成象棋历史记录时,是通过Before+After计算Delta。在解析历史记录时,是通过After+Delta计算Before(或者通过Before+Delta计算After)。
开发
其实开发时,发现上面这个映射表设计的不太友好。
if (Before > After) {
Delta = Before - Afrer;
} else {
Delta = 9 + Before - After;
}
if (Delta === 8) Delta = 0;
主要是多了个if (Delta === 8) Delta = 0;
比较麻烦。
所以,我们稍加修改「映射表」,改为这样:
就可以这样写代码了:
if (Before > After) {
Delta = Before - Afrer - 1;
} else {
Delta = 8 + Before - After;
}
可见,采取这种方式时,一定要合理设计「映射表」,从而简化开发成本。可以在对角线下方统一设置为0,然后依次往下+1。
逆向推断Before
上面介绍了如何根据Before和After计算Delta,那么如何根据After和Delta计算Before呢?
就像解方程一样,我们移动变量即可得到2个等式:
After = Before - 1 - Delta;
After = 8 + Before - Delta;
二者一定有一个是对的。
简单验证即可得到选取条件:
if (Before > Delta) {
After = Before - 1 - Delta;
} else {
After = 8 + Before - Delta;
}
举一反三
上面给你介绍了横方向移动的「映射表」,那么纵方向怎么写「映射表」呢?
如下图:
计算的代码就略了。
方式二:「候选人」列表
理论知识
如果我们在生成历史记录时,根据After坐标计算一下「车」的所有可到达After的Before的位置(一个数组),并且保证它的顺序(即针对同一个目标点,生成的出发点数组是一模一样的),那么就可以很方便的映射了,因为可以把0-16直接映射到数组的位置。
在解析历史记录时,需要根据After坐标重新计算一下这个可移动位置数组,找到Delta对应的位置。
该方案效率不如方案一,但是开发者心智负担小。在效率可以忽略不计的情况下,建议采用方案二。
开发
根据After计算所有「候选人」列表的方法:
function getCarBeforeCandidates(after) {
const [x, y] = getPieceXY(after);
const candidates = [];
for (let i = x + 1; i <= 8; i++) {
candidates.push(y * 9 + i);
}
for (let i = x - 1; i >= 0; i--) {
candidates.push(y * 9 + i);
}
for (let i = y - 1; i >= 0; i--) {
candidates.push(i * 9 + x);
}
for (let i = y + 1; i <= 9; i++) {
candidates.push(i * 9 + x);
}
return candidates;
}
这是如何保证有序的呢?因为每次都按照固定顺序遍历「候选人」位置,所以针对各个After,生成的结果一定是固定的。
根据Before、After计算Delta的方法:
Delta = getCarBeforeCandidates(After).indexOf(Before)
根据Delta、After计算Before的方法:
Before = getCarBeforeCandidates(After)[Delta]
写在最后
我是HullQin,公众号线下聚会游戏的作者(欢迎关注公众号,联系我,交个朋友),转发本文前需获得作者HullQin授权。我独立开发了《联机桌游合集》,是个网页,可以很方便的跟朋友联机玩斗地主、五子棋等游戏,不收费无广告。还独立开发了《合成大西瓜重制版》。还开发了《Dice Crush》参加Game Jam 2022。喜欢可以关注我噢~我有空了会分享做游戏的相关技术,会在这2个专栏里分享:《教你做小游戏》、《极致用户体验》。
转载自:https://juejin.cn/post/7152537902423998494