【问题标题】:board game win situation - searching algorithm棋盘游戏胜势 - 搜索算法
【发布时间】:2014-05-16 05:32:52
【问题描述】:

我正在寻找可能有效的算法来检测在 19x19 棋盘上玩的五子棋(五连胜)游戏中的“获胜”情况。当其中一名玩家连续获得五个且不超过五个“石头”(水平、对角或垂直)时,就会发生获胜情况。

我有以下数据可以轻松访问:

  • 两个玩家的先前移动(“石头”)存储在一个二维数组(也可以是 json 符号对象)中,使用变量“B”和“W”来区分玩家,
  • 传入移动的“坐标”(move.x,move.y),
  • 每位玩家的移动次数

我是用 javascript 做的,但任何不使用内存分配等低级东西或高级 (python) 数组操作的解决方案都很好。

我发现了类似的问题 (Detect winning game in nought and crosses),但那里给出的解决方案只涉及小板(5x5 等)。

【问题讨论】:

  • “Detect wins game in naught and crosss”问题的答案实际上非常适合您的问题。

标签: javascript arrays algorithm multidimensional-array


【解决方案1】:

一个简单易懂的解决方案,没有过多的循环(仅提供伪代码,如果您需要更多解释,请告诉我):

我假设你的二维数组是这样运行的:

board = [
   [...],
   [...],
   [...],
   ...
];

即内部数组代表棋盘的水平行。

我还假设数组由“b”、“w”和“x”填充,分别代表黑色块、白色块和空方格。

我的解决方案有点分而治之,所以我将其分为以下 3 种情况。请耐心等待,一开始它可能看起来比简单地运行多个嵌套循环更复杂,但这个概念很容易理解、阅读,并且使用正确的方法,编码非常简单。

水平线

让我们首先考虑仅当线是水平线时才检测获胜情况的情况 - 这是最简单的。首先,使用board[0].join("") 之类的东西将一行连接成一个字符串。对每一行执行此操作。你最终得到一个这样的数组:

rows = [
   "bxwwwbx...",
   "xxxwbxx...",
   "wwbbbbx...",
   ...
]

现在加入这个数组,但在元素之间插入一个“x”来分隔每一行:rows.join("x")

现在您有一个代表您的棋盘的长字符串,只需应用一个正则表达式即可找到长度正好为 5 的连续“w”或“b”:superString.test(/(b{5,5})|(w{5,5})/)。如果测试返回true,你就有了胜利的局面。如果没有,让我们继续讨论垂直线。

垂直线

你想重用上面的代码,所以为它创建一个函数testRows。测试垂直线的过程完全相同,但您要转置板,使行变为列,列变为行。然后你应用相同的testRows 函数。转置可以通过将值复制到一个新的二维数组中来完成,或者通过编写一个简单的 getCol 函数并在 testRows 中使用它来完成。

对角线

再一次,我们想重用“testRows”函数。像这样的对角线:

b x x x x
x b x x x
x x b x x
x x x b x
x x x x b

可以转换成这样的垂直:

b x x x x
b x x x
b x x
b x
b

通过将行i 移动i 位置。现在这是一个移调的问题,我们又回到了水平测试。您需要对相反方向的对角线执行相同操作,但这次将行 i 移动 length - 1 - i 位置,或者在您的情况下,移动 18 - i 位置。

函数式javascript

附带说明一下,我的解决方案非常适合函数式编程,这意味着如果你有函数式编程工具,它可以很容易地编码,尽管这不是必需的。我建议使用underscore.js,因为在许多不同的游戏算法中,您很可能需要mapreducefilter 等基本工具。例如,我关于测试水平线的部分可以使用 map 用一行 javascript 编写:

_(board).map(function (row) {return row.join("")}).join("x").test(/(b{5,5})|(w{5,5})/);

【讨论】:

    【解决方案2】:

    尽管这是一个非常老的问题,但我想提供我的答案,因为我今天更深入地研究了这个问题,并以一种(很多)更有效的方式解决了它。

    我使用的是位棋盘,由于效率高,大多数棋盘游戏和引擎(国际象棋引擎)都使用它来代表我的领域。

    你可以在这个游戏中使用按位运算来做你需要的一切。

    一个位只能有 2 个状态(0 和 1)但是我们需要的是 3 个状态,例如p1、p2 或为空。 为了解决这个问题,我们将改为使用 2 个板,每个玩家一个。

    另一个问题是五子棋有很多字段(19x19)并且没有数字类型有那么多位来表示字段。 我们将使用一个数字数组来表示每一行,并且只使用它的前 lsb 15 位。

    垂直行

    玩家 1 的简化棋盘可能如下所示

    000000
    101100
    001000
    011000
    000000
    

    假设我们要连续检测 3 个。我们取前 3 行 (0-2) 并取走它们。

    000000
    001100
    101000
    

    使用 & (AND) 运算符,您可以检查每行中是否有 1。

    var result = field[player][0] & field[player][1] & field[player][2];
    

    在这种情况下,结果将为 0,这意味着没有获胜者。让我们继续……下一步是取第 1-3 行

    101100
    001000
    011000
    

    再次应用AND,我们将得到001000。我们不必关心这是什么数字,只要它是否为 0。 (结果!= 0)

    水平行

    好的,现在我们可以检测垂直行了。为了检测水平行,我们需要保存另外 2 个板,每个玩家也有一个。但是我们需要反转 x 和 y 轴。然后我们可以再次做同样的检查来检测水平线。您的数组将是:

    //[player][hORv][rows]
    var field[2][2][19];
    

    对角线:)

    最棘手的部分当然是对角线,但是通过一个简单的技巧,您可以进行与上述相同的检查。一个简单的板子:

    000000
    010000
    001000
    000100
    000000
    

    基本上我们做的和上面一样,但在我们做之前我们需要移动行。假设我们在第 1-3 行。

    010000
    001000
    000100
    

    第一行保持原样。然后将第二行向左移动 1,将第三行向左移动 2。

    var r0 = field[0][0][i];
    var r1 = field[0][0][i+1] << 1;
    var r2 = field[0][0][i+2] << 2;
    

    你会得到的是:

    010000
    010000
    010000
    

    应用并且你可以有你的胜利检测。要获得另一个对角线方向,只需再做一次,但不是向左移动 >

    我希望这对某人有所帮助。

    【讨论】:

      【解决方案3】:

      未经测试:

      int left = max(0, move.x-5), right = min(width-1, move.x+5), top = max(0, move.y-5), bottom = min(width-1, move.y+5);    
      // check the primary diagonal (top-left to bottom-right)
      for (int x = left, y = top; x <= right && y <= bottom; x++, y++) {
          for (int count = 0; x <= right && y <= bottom && stones[x][y] == lastPlayer; x++, y++, count++) {
              if (count >= 5) return true;
          }
      }
      // check the secondary diagonal (top-right to bottom-left)
      // ...
      // check the horizontal
      // ...
      // check the vertical
      // ...
      return false;
      

      或者,如果您不喜欢嵌套循环(未经测试):

      // check the primary diagonal (top-left to bottom-right)
      int count = 0, maxCount = 0;
      for (int x = left, y = top; x <= right && y <= bottom; x++, y++) {
          if (count < 5) {
              count = stones[x][y] == lastPlayer ? count + 1 : 0;
          } else {
              return true;
          }
      }
      

      【讨论】:

      • 不过,这些不处理“不超过 5”的规则。
      • @Adam Norberg:添加这很容易。 “不超过 5”规则意味着它实际上是连续六颗石头,例如 B-B-B-B-B-(W/_) 或 W-W-W-W-W-(B/_)。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-27
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多