【问题标题】:Connect 4 Diagonal Win Check连接 4 对角线获胜检查
【发布时间】:2019-05-10 10:29:58
【问题描述】:

我正在测试 Ruby Connect 4 游戏中的对角线胜利。我一直在使用硬编码的二维数组进行测试:

grid_array = [
["B", ".", ".", ".", ".", ".", ".", "."],
[".", "B", ".", ".", ".", ".", ".", "."],
[".", ".", "B", ".", ".", ".", ".", "."],
[".", ".", ".", "B", ".", ".", ".", "."],
[".", ".", ".", "X", "M", ".", ".", "."],
[".", ".", ".", ".", "X", "M", ".", "."],
[".", ".", ".", ".", ".", "X", "M", "."],
[".", ".", ".", ".", ".", ".", "X", "M"]
]

此方法的此内部循环正常工作(正确识别 'M''B' 分别是赢家,但是当我尝试移动对角线检查跨列或使用外部循环选择行时卡住了例如,将'X' 作为获胜值。

def nw_diagonal_win (playing_board)
  row = 7
  while row < playing_board.size && row >= 0

    row = 7
    column = 7
    piece_count = 0

    while (row < playing_board.size && column < playing_board[row].size && column >= 0)

      if playing_board[row][column] == 'M'
        piece_count += 1

        if piece_count == 4
          puts "Player is the winner in a diagonal!"
        end

        puts piece_count.inspect

      else
        piece_count = 0
        puts "No winner."
      end

      row += 1
      column += 1
    end

    row -= 1
  end
end

编辑添加: Connect 4 中的“获胜者”设置了 4 个相邻的棋子(水平、垂直或对角线)。在我的游戏中,这由'X''0' 表示。碎片从网格中列的顶部“掉落”到该列中最底部的可用空间。棋子可以堆成一列,但不能“漂浮”在棋盘中间。对角线可以从左上到右下或从右上到左下。只有当棋子在网格内不间断(没有环绕)时才会发生胜利。

考虑一个更大版本的井字游戏,其中必须首先在底行进行移动,然后才能在上面的行中进行移动,像盒子一样堆叠。连续四个(水平、垂直或对角线 \ /)获胜。

响应史蒂夫的回答建议,如下:

def top_left_diagonal (playing_board, player_piece)
  row = 0
  while row < playing_board.size - 3
    piece_count = 0
    column = 0
    while column < playing_board[row].size - 3 && playing_board[row][column] == player_piece
      if (playing_board[row][column] == playing_board[row + piece_count][column + piece_count])
        piece_count += 1
      else
        piece_count = 0
      end
      column += 1
    end
    if piece_count == 4
      puts "Diagonal winner!"
    end
    row += 1
  end
end

【问题讨论】:

  • 对于位置 i,j 的元素,让 t=i-j。那么位置 p,q 处的元素在同一主对角线上当且仅当 p-q=t。改变 t 会产生所有的主对角线。除了p+q=t 之外,每个对角线上的元素都具有相似的属性。请定义什么是“赢家”。许多读者,包括我在内,从未听说过这款游戏。
  • 您应该编写计算对角线上点的函数,然后为每个对角线调用该辅助函数(在 n*n 板上有 2*(n+n-1) 条对角线 - 两个方向和从第一行第一列的每个元素开始)
  • @CarySwoveland en.wikipedia.org/wiki/Connect_Four ...我希望它无处不在...或者至少是无限的纸质变体。
  • 感谢两位的帮助。如果我有任何澄清问题,我会回到 cmets。
  • @CarySwoveland 我理解这个概念(第 7 列 - 第 7 行)= 0,所以该对角线上的所有点都等于 0(4,4;5,5 等)我只是不知道'不明白如何实现。

标签: arrays ruby multidimensional-array


【解决方案1】:

我正在玩一个类似的网格,用于填字游戏,并想出了一种获得对角线的方法。我做了一些改变以适应连接 4。 这不是一个完整的答案,但我希望它可以提供帮助。

首先我想将网格映射为也有坐标:

grid_map = grid_array.map.with_index{ |line, y| line.map.map.with_index { |e, x| [e, x, y] } }

然后有几种方法可以恢复矩阵的所有对角线(无论如何应该有更好的解决方案)。

第一个只取一个方向的一半对角线:

def half_diagonals(matrix)
  max_y = matrix[0].size
  max_x = matrix.size
  diagonals = []
  (0...max_x).each do |x_start|
    x = x_start
    y = 0
    diagonal = [matrix[x][y]]
    while  x < max_x - 1 and y < max_y - 1 do
      x += 1
      y += 1
      diagonal << matrix[x][y]
    end
    diagonals << diagonal
  end
  # only in diagonals with at least four spots
  return diagonals.select{ |d| d.size >= 4 }
end

这会调用 half_diagonals(matrix) 对矩阵的变换来获得所有对角线:

def diagonals(matrix)
  half_diagonals(matrix) +
  half_diagonals(matrix.transpose)[1..-1] +
  half_diagonals(matrix.reverse) +
  half_diagonals(matrix.reverse.transpose)[1..-1]
end

# diagonals(grid_map).each { |e| p e } # to print the diagonals

现在,需要检查每个对角线是否有获胜者,因此定义一个方法来执行此操作:

def check_diagonal(diagonal, empty_spot = ".")
  check = diagonal.chunk_while { |s1, s2| s1[0] == s2[0] }.map { |e| [e.count, e[0][0], e.map{ |ee| ee[1..-1]}] }
  return detected = check.detect { |e| e[0] == 4 and e[1] != empty_spot }
  # it returns the first four detected
end

现在调用grid_map上的方法,获取相连四个的个数、颜色和坐标:

diagonals(grid_map).map { |diagonal| check_diagonal(diagonal) }.compact
#=> [[4, "B", [[0, 0], [1, 1], [2, 2], [3, 3]]], [4, "X", [[3, 4], [4, 5], [5, 6], [6, 7]]]]

四个"M"没有返回,因为check.detect

【讨论】:

  • 谢谢!我对这个解决方案非常感兴趣,并且会在这个项目之外更多地使用它!
【解决方案2】:

如果有获胜对角线,则起始位置必须在第[0-3]行和第[0-3]列的范围内。任何从左上角框外开始的对角线都没有足够的上下位置来使其连续四个。

所以你真的需要一个 while row &lt; 4while col &lt; 4 嵌套循环。

对于每一行col组合,假设值不是“.”然后您可以将您的piece_count 设置为1,然后将计数器1 设置为3,并检查playing_board[row + counter][col + counter] 是否等于playing_board[row][col] 的值,如果它增加了您的piece_count。

在计数器 1 到 3 循环之外,如果 piece_count 为 4,则您有赢家。

【讨论】:

  • 我有点糊涂,但我想我明白你的意思,史蒂夫。我写了一些代码并将其添加到我上面的问题的末尾。你介意看看吗?
  • 我在下面添加了我想出的答案。它从 r7、c7 开始,从右下角移动到左上角。你看到任何直接的问题吗?我已经测试了一点,我遇到了什么,我已经能够解决。
  • 我认为您添加到问题中的代码不起作用。如果件数增加,我认为不需要增加该列。但我看到你从 Cary 那里得到了答案,而且这在银行里通常很值钱。
【解决方案3】:

假设我们有

grid = [
  %w| . . . . . . |,
  %w| . . . w w . |,
  %w| . . . w b . |,
  %w| b . w . b . |,
  %w| w w . w b b |,
  %w| b w b b w b |
]
  #=> [[".", ".", ".", ".", ".", "."], 
  #    [".", ".", ".", "w", "w", "."],
  #    [".", ".", ".", "w", "b", "."],
  #    ["b", ".", "w", ".", "b", "."],
  #    ["w", "w", ".", "w", "b", "b"],
  #    ["b", "w", "b", "b", "w", "b"]]

没错,这只是 6x6,但解决方案没有什么不同。

首先,由于数组很小,我们不必担心计算效率,所以我们可以专注于代码效率。

我们先检查每行是否有四个。

检查行

def four_in_a_row_by_row(arr)
  arr.each do |row|
    a = row.each_cons(4).find { |a| a.uniq.size == 1 && a.first != '.' }
    return a.first unless a.nil?        
  end
  nil
end

如果连续有四个w,则此方法返回w,如果连续有四个b,则返回b,否则返回nil

对于arr = grid,我们发现没有一行连续包含四个'b''w'

four_in_a_row_by_row(grid)
  #=> nil

请注意,此方法不要求arr.size == grid.sizearr 的所有元素大小相同。它只是检查是否有任何元素连续有四个'w' 或四个'b'。这将在以后具有重要意义。

arr 的最后一个元素传递给块,例如,如下。

row =  ["b", "w", "b", "b", "w", "b"]

然后我们计算

enum0 = row.each_cons(4)
  #=> #<Enumerator: ["b", "w", "b", "b", "w", "b"]:each_cons(4)>

enum1 = enum0.find
  #=> #<Enumerator: #<Enumerator: ["b", "w", "b", "b", "w", "b"]:each_cons(4)>:find>

enum1 可以被认为是一个 compound 枚举器,尽管 Ruby 没有这样定义它。请参阅Enumerable#each_consEnumerable#find

我们可以将此枚举器转换为数组,以查看将传递给块的元素。

enum1.to_a
  #=> [["b", "w", "b", "b"],
  #    ["w", "b", "b", "w"],
  #    ["b", "b", "w", "b"]]

第一个元素被传递给块并进行以下计算。

    a = enum1.next
    u = a.uniq
    u.size == 1

因此我们不需要计算a.first != '.'enum1 的其余两个元素被传递到块中,并为每个元素计算 nil,表明在最后一行中没有连续四个 'w''b'

我们快完成了!

“等等”,你说,我们只检查了行!仍然有柱子和所有的对角线!敬请期待...

检查列

这个很简单。

four_in_a_row_by_row(grid.transpose)
  #=> nil

这里

 grid.transpose
   #=> [[".", ".", ".", "b", "w", "b"],
   #    [".", ".", ".", ".", "w", "w"],
   #    [".", ".", ".", "w", ".", "b"],
   #    [".", "w", "w", ".", "w", "b"],
   #    [".", "w", "b", "b", "b", "w"],
   #    [".", ".", ".", ".", "b", "b"]]

检查对角线(从左上到右下)

这里我们需要做的就是构造包含对角线的数组arr,然后应用four_in_a_row(arr)。首先确定包含第一列中长度为4 或更大的元素的对角线。这包括以下对角线

[grid[0][0], grid[1][1], grid[2][2], grid[3][3], grid[4][4],grid[5][5]] 
[grid[1][0], grid[2][1], grid[3][2], grid[4][3], grid[5][4]] 
[grid[2][0], grid[3][1], grid[4][2], grid[5][3]] 

没有必要考虑包含第一列元素的剩余对角线,因为它们包含的元素少于4

[grid[3][0], grid[4][1], grid[5][2]] 
[grid[4][0], grid[5][1]] 
[grid[5][0]] 

我们可以得到前三个对角线如下。

(0..grid.size-4).map { |i| (0..grid.size-1-i).map { |j| grid[i+j][j] } }
  #=> [[".", ".", ".", ".", "b", "b"],
  #    [".", ".", "w", "w", "w"],
  #    [".", ".", ".", "b"]]

同样,确定包含第一行中元素的对角线,除了grid[0][0],长度为4 或更大。这些是对角线

[grid[0][1], grid[1][2], grid[2][3], grid[3][4], grid[4][5]] 
[grid[0][2], grid[1][3], grid[2][4], grid[3][5]] 

包含第一行元素的剩余对角线(grid[0][0] 除外)包含少于 4 元素。我们得到这些对角线如下。

(1..grid.first.size-4).map do |j|
  (0..grid.size-j-1).map { |i| grid[i][j+i] }
end
  #=> [[".", ".", "w", "b", "b"],
  #    [".", "w", "b", "."]]

因此,我们可以得到一个由所有对角线组成的数组,如下所示。

def diagonals(grid)
  (0..grid.size-4).map do |i|
    (0..grid.size-1-i).map { |j| grid[i+j][j] }
  end.concat((1..grid.first.size-4).map do |j|
    (0..grid.size-j-1).map { |i| grid[i][j+i] }
  end)
end
arr = diagonals(grid)
  #=> [[".", ".", ".", ".", "b", "b"],
  #    [".", ".", "w", "w", "w"],
  #    [".", ".", ".", "b"],
  #    [".", ".", "w", "b", "b"],
  #    [".", "w", "b", "."]]

我们看到没有对角线连续包含四个。

four_in_a_row_by_row(arr)
  #=> nil

检查对角线(从左到右)

我们可以通过与计算对角线相同的推理,但由于此处计算效率并不重要,因此有一种更简单的方法:计算通过“旋转”grid 90 度获得的数组的对角线。

def rotate90(grid)
  ncols = grid.first.size
  grid.each_index.with_object([]) do |i,a|
    a << ncols.times.map { |j| grid[j][ncols-1-i] }
  end
end
arr = rotate90(grid)
  #=> [[".", ".", ".", ".", "b", "b"],
  #    [".", "w", "b", "b", "b", "w"],
  #    [".", "w", "w", ".", "w", "b"],
  #    [".", ".", ".", "w", ".", "b"],
  #    [".", ".", ".", ".", "w", "w"],
  #    [".", ".", ".", "b", "w", "b"]]
arr1 = diagonals(arr)
  #=> [[".", "w", "w", "w", "w", "b"], [".", "w", ".", ".", "w"],
  #    [".", ".", ".", "b"], [".", "b", ".", ".", "w"], [".", "b", "w", "b"]]

我们看到没有任何对角线连续包含四个。

four_in_a_row_by_row(arr1)
  #=> "w"

把它们放在一起

def four_in_a_row(grid)
  four_in_a_row_by_row(grid) ||
  four_in_a_row_by_row(grid.transpose) ||
  four_in_a_row_by_row(diagonals(grid)) ||
  four_in_a_row_by_row(diagonals(rotate90(grid)))
end
  
four_in_a_row_by_row(grid) 
  #=> "w"

four_in_a_row_by_row的替代计算

也可以写成four_in_a_row_by_row,如下所示。

def four_in_a_row_by_row(arr)
  row = arr.find { |row| four_in_a_row(row) }
  row.nil? ? nil : four_in_a_row(row)
end
def four_in_a_row(row)
  (0..row.size-5).find { |j| row[j,4].uniq.size == 1 && row[j] != '.' }
end
four_in_a_row_by_row(grid)
  #=> nil

如果愿意,row.nil? ? nil : four_in_a_row(row) 可以替换为

four_in_a_row(row) unless row.nil?

【讨论】:

  • 感谢super的详细讲解!我只是在学习 Ruby,所以其中一些我不太了解,但我能理解的内容帮助我想出了一个可行的 fingers crossed 解决方案。我添加了我的解决方案作为对这篇文章的回答。如有任何建议或挖洞,我将不胜感激。
  • 一如既往的出色回答。有关信息,物理玩具“Connect4”要求您将令牌插入顶部,然后重力将其降至最低位置。因此,位置 [4][2] 中的点和位置 [3][2] 中的令牌永远不会在物理上发生(除非,我猜,令牌在跌落时“卡住”,我从未见过这种情况发生)
  • @Christian,我进行了编辑以澄清我的答案。如果它回答了您的问题,请告诉我。
  • (0..2).map 返回一个包含三个元素的数组,映射为012。例如,考虑11 被传递给块{ |i| (0..grid.size-1-i).map { |j| grid[i+j][j] } },块变量i 被设置为1。因此i=1 的块计算为(0..grid.size-1-1).map { |j| grid[1+j][j] },它减少为(0..4).map { |j| grid[1+j][j] },等于[".", ".", "w", "w", "w"],所以i=1 映射到那个5 元素数组。对i=0i=2 进行了类似的计算。清除吗?
  • 太好了,感谢您抽出宝贵的时间来写这篇文章。在您的答案和 cmets 之间,我只是要掌握它:)
【解决方案4】:

我想出了一个似乎在测试中有效的解决方案。它从右下角到左上角,从我的 8x8 网格中的第 7 行第 7 列开始。我还创建了从左下角移动到右上角的对角线。

我已经测试了一段时间,没有发现错误,但很想知道是否有其他人在其中戳洞。

非常感谢你们所有人——你们的解决方案和建议让我走到了这一步!

def nw_diagonal_win (playing_board, player_piece)
    row = 7
    column = 7
    piece_count = 0
    while row < playing_board.size && row >= 0 && column < playing_board[row].size && column >= 0
      if playing_board[row][column] == player_piece
        piece_count += 1
        column -= 1
        row -= 1
      else
        piece_count = 0
        column -= 1
        if column < 3
          row -= 1
          column = 7
        end
      end
        if piece_count == 4
          puts "Player #{player_piece} is the winner in a diagonal!"
        end
    end
end

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-09-02
    • 1970-01-01
    • 2015-12-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多