likes
comments
collection
share

[教你做小游戏] 五子棋 判断 四四禁手 算法

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

背景

之前我开发过五子棋,输出了一些文章:

之前的版本,五子棋不支持禁手。现在,我准备加一下禁手功能,还真没那么简单。

禁手介绍

[教你做小游戏] 五子棋 判断 四四禁手 算法

四四禁手

这个判断相对简单,我们判断下棋后,这个棋子是否同时组成了1个以上的4连就可以了(包括活四、冲四)。

  • 活四:就是有两个点可以成五的四。(见下方第一个图)
  • 冲四:有且只有一个点可以形成连五的四。(见下方第二个图,中间是连冲,两边是跳冲)

[教你做小游戏] 五子棋 判断 四四禁手 算法

[教你做小游戏] 五子棋 判断 四四禁手 算法

开发

我们需要兼顾跳冲四的场景。

以竖方向为例子,我们需要判断竖直方向,紧挨着当前黑棋的黑棋子有几个。随后继续往上遍历,如果碰到白棋结束遍历,如果碰到空白棋子,则跳过它,看看后面还有多少连续的黑色棋子。之后再往下遍历,如果碰到白棋结束遍历,如果碰到空白棋子,则跳过它,看看后面还有多少连续的黑色棋子。这样得到3个变量:middle before after

一个特殊情况

如果middle已经是4了,我们还需要存下相邻是否有白棋,如果两头都是白色,那么这不算活四冲四。例如,下图带标记的黑色棋子不是禁手:

[教你做小游戏] 五子棋 判断 四四禁手 算法

如果middle上方相邻是白棋,我们可以把before标为-1,类似的,如果下方相邻白棋,我们可以把after标记为-1。

另一个特殊情况

还有个特殊情况,下面带标记的黑色棋子,也不是禁手:

[教你做小游戏] 五子棋 判断 四四禁手 算法

因为左侧的空白,黑色不能落子,那里是长连禁手。此时黑棋只形成了1个跳冲四。

如何解决呢?我们判断beforemiddle相加大于等于5,则表示会触发长连禁手,那么这里就不算一个活四冲四。

外层逻辑

我们需要遍历4个方向,包括竖直、水平、左斜、右斜。都是一样的算法。我们没必要写四遍,可以通过参数dx dy表明每次遍历时,x和y的变化量。通过参数传入。传4遍就可以。例如这样:

function judgeFourFourBan(pieces: number[]) {
  const fourCount = getFourOnOneLine(pieces, 1, 0)
    + getFourOnOneLine(pieces, 0, 1)
    + getFourOnOneLine(pieces, -1, 1)
    + getFourOnOneLine(pieces, 1, 1);
  return fourCount > 1;
}

这完全是四四禁手的定义:是否存在1个以上的活四冲四。

注:同一行可能有2个冲四,即getFourOnOneLine返回值为0-2,也就是说,fourCount最大值为8,下面的情况,fourCount达到最大值,所以中间的黑棋就是禁手了。

[教你做小游戏] 五子棋 判断 四四禁手 算法

当然,如果棋局变成这样,由于长连禁手存在,fourCount应该是0,这时候中间的黑棋不是禁手:

[教你做小游戏] 五子棋 判断 四四禁手 算法

是不是很神奇!我作为开发者,已经懵了!!!佩服五子棋禁手的博大精深!

内层逻辑

下面我们编写核心逻辑getFourOnOneLine

function getFourOnOneLine(pieces: number[], dx: number, dy: number) {
  const piece = pieces[pieces.length - 1];
  const [x, y] = getPieceXY(piece);
  let before = 0;
  let middle = 1;
  let after = 0;
  let flag = false;
  for (let i = x - dx, j = y - dy; ; i -= dx, j -= dy) {
    if (!(i >= 0 && i <= 14 && j >= 0 && j <= 14) || isWhite(i, j, pieces)) {
      if (!flag) before = -1;
      break;
    }
    if (isBlack(i, j, pieces)) {
      if (flag) before += 1;
      else middle += 1;
    } else {
      if (flag) break;
      flag = true;
    }
  }
  flag = false;
  for (let i = x + dx, j = y + dy; ; i += dx, j += dy) {
    if (!(i >= 0 && i <= 14 && j >= 0 && j <= 14) || isWhite(i, j, pieces)) {
      if (!flag) after = -1;
      break;
    }
    if (isBlack(i, j, pieces)) {
      if (flag) after += 1;
      else middle += 1;
    } else {
      if (flag) break;
      flag = true;
    }
  }
  if (middle === 4) return before === 0 || after === 0 ? 1 : 0;
  return (before > 0 && before + middle === 4 ? 1 : 0) + (after > 0 && middle + after === 4 ? 1 : 0);
}

下面,解释一下if (middle === 4) return before === 0 || after === 0 ? 1 : 0;。这句话不是凭空写出来的,是经过严格推理论证的:

middlebeforeafter是否活四冲四
400活四
4-10(白棋点)冲四
40-1冲四(白棋点)
4正数0(长连禁手点)冲四
40正数冲四(长连禁手点)
4-1-1(白棋点)四(白棋点)
4-1正数(白棋点)四(长连禁手点)
4正数-1(长连禁手点)四(白棋点)
4正数正数(长连禁手点)四(长连禁手点)

可以看到,middle=4时,形成活四冲四的充要条件:before和after至少一个为0。

写在最后

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

转载自:https://juejin.cn/post/7155511034483048456
评论
请登录