likes
comments
collection
share

[教你做小游戏] 车炮能移动17个位置,针对90种出发点,如何建立0-16和目标点的映射?

作者站长头像
站长
· 阅读数 37

背景

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

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

之前的进展:

今天给大家聊一个难题:

在文章《一种记录象棋历史记录的方案:平均每步仅占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表示纵向移动。

然后,我们再绘制「映射表」。

例如,这是横向移动映射表:

[教你做小游戏] 车炮能移动17个位置,针对90种出发点,如何建立0-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;比较麻烦。

所以,我们稍加修改「映射表」,改为这样:

[教你做小游戏] 车炮能移动17个位置,针对90种出发点,如何建立0-16和目标点的映射?

就可以这样写代码了:

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;
}

举一反三

上面给你介绍了横方向移动的「映射表」,那么纵方向怎么写「映射表」呢?

如下图:

[教你做小游戏] 车炮能移动17个位置,针对90种出发点,如何建立0-16和目标点的映射?

计算的代码就略了。

方式二:「候选人」列表

理论知识

如果我们在生成历史记录时,根据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
评论
请登录