为什么这个函数会产生混乱的结果而不是计算康威的生命游戏?

Why does this function produce chaotic results instead of calculating Conway's Game Of Life?

我正在 Ruby 写康威的人生游戏。该游戏基于一个二维布尔数组,其中 true 代表活细胞,false 代表死细胞。 (例如

arr = [
    [false, false, false, false, false, false, false],
    [false, false, false, false, false, false, false],
    [false, false, false, true, false,  false, false],
    [false, false, false, false, true,  false, false],
    [false, false, true,  true,  true,  false, false],
    [false, false, false, false, false, false, false],
    [false, false, false, false, false, false, false],
    [false, false, false, false, false, false, false],
    [false, false, false, false, false, false, false],
    [false, false, false, false, false, false, false],
    [false, false, false, false, false, false, false],
    [false, false, false, false, false, false, false],
    [false, false, false, false, false, false, false]
]

将用于制作滑翔机) 按照规则,我相信你们大多数人都知道,它应该创造一个反复向右和底部改革的模式。但是,当我 运行 这段代码时,它会产生一个爆炸性的混乱模式。 这是我的主要功能,它计算新的棋盘状态:

def iterate(arr) 
  arr = arr.dup
  new_arr = arr
  y_counter = 0
  arr.each do |elem|
    x_counter = 0
    elem.each do 
      count = 0
      neighbors = [
        (arr[y_counter-1] || [false, false, false])[x_counter-1], 
        (arr[y_counter-1] || [false, false, false])[x_counter], 
        (arr[y_counter-1]|| [false, false, false])[x_counter+1],
        (arr[y_counter]|| [false, false, false])[x_counter-1],
        (arr[y_counter]|| [false, false, false])[x_counter+1],
        (arr[y_counter+1]|| [false, false, false])[x_counter-1],
        (arr[y_counter+1]|| [false, false, false])[x_counter],
        (arr[y_counter+1]|| [false, false, false])[x_counter+1],
      ]
      neighbors.each do |elem|
        count += 1 if elem
      end
      new_arr[y_counter][x_counter] = (count == 2) || (count == 3 && arr[y_counter][x_counter])
      x_counter+=1
  
    end
    
    y_counter+=1
  end
  new_arr
end

为什么它不起作用,我该如何解决?

考虑使用几种方法编写代码来执行不同的任务。首先,我们需要在终端中显示网格图片。我们可能会这样做。

ALIVE = "" 
DEAD = ""
def display_grid(grid)
  last_col = grid.first.size
  system("clear")
  grid.each_index do |i|
    puts (0..last_col).map { |j| grid[i][j] ? ALIVE : DEAD }.join
  end
end
display_grid(grid)      














system("clear") 清除 OS X 和 Linux 中的终端。 system("cls") 在 Windows 中做同样的事情。我知道 Ruby v2.7 引入了一种跨平台的方式来做到这一点:

require 'io/console'
$stdout.clear_screen # or STDOUT.clear_screen

接下来,对于网格中的每个单元格,我们需要计算它的直接邻居的存活数。

def neighbors_alive(grid, (row, col))
   rows = ([row-1, 0].max..[row+1, arr.size-1].min).to_a
   cols = ([col-1, 0].max..[col+1, arr.first.size-1].min).to_a
   neighbors = rows.product(cols) - [cell]
   neighbors.count { |row, col| grid[row][col] }
end

例如:

neighbors_alive(grid, [0, 0])  #=> 0 
neighbors_alive(grid, [1, 1])  #=> 0
neighbors_alive(grid, [2, 2])  #=> 1
neighbors_alive(grid, [3, 3])  #=> 5
neighbors_alive(grid, [4, 4])  #=> 2
neighbors_alive(grid, [5, 5])  #=> 1
neighbors_alive(grid, [6, 6])  #=> 0

单元格[4, 3]的计算如下,它位于以下3x3子数组的中心:

[false, false, true],  #  
[true,  true,  true],  # 
[false, false, false]] # 

所以我们预计该数字为 3

row, col = [4, 3]
row
  #=> 4 
col
  #=> 3 
rows = ([row-1, 0].max..[row+1, arr.size-1].min).to_a
  #=> [3, 4, 5] 
cols = ([col-1, 0].max..[col+1, arr.first.size-1].min).to_a
  #=> [2, 3, 4] 
neighbors = rows.product(cols) - [[row, col]]
  #=> [[3, 2], [3, 3], [3, 4], [4, 2], [4, 4], [5, 2], [5, 3], [5, 4]]
  #                                                  
neighbors.count { |row, col| grid[row][col] }
  #=> 3 

在每次迭代中,我们需要构建一组将死亡的活细胞 (to_die) 和将复活的死细胞 (to_alive)。这可以按如下方式完成。

def transitions(grid)
  rows = 0..grid.size - 1
  cols = 0..grid.first.size - 1
  rows.each_with_object([]) do |i, cells_to_flip|
    cols.each do |j|
      cell = [i, j]
      n = neighbors_alive(grid, cell)
      if grid[i][j] # alive
        cells_to_flip << cell unless [2, 3].include?(cell)
      else          # dead
        cells_to_flip << cell if n == 3
      end
    end
  end
end
cells_to_flip = transitions(grid)
  #=> [[2, 3], [3, 2], [3, 4], [4, 2], [4, 3], [4, 4], [5, 3]] 

然后我们需要更新网格。

def update_grid(grid, cells_to_flip)
  cells_to_flip.each { |i, j| grid[i][j] = ! grid[i][j] }
end

让我们尝试使用 grid 的深层副本(因此我们不修改原始 grid,它将在下面的 conway(grid) 中使用),使用 to_dieto_alive 以上。

arr = grid.dup.map(&:dup)
update_grid(arr, cells_to_flip)
display_grid(arr)














我们看到所有活细胞死亡,[3, 2][5, 3] 处的死细胞复活。[​​=40=]


我们现在可以编写一个方法来玩游戏了。回想一下,在每次迭代中,如果活邻居少于两个或多于三个,活细胞就会死亡,而如果死细胞恰好有三个活邻居,它就会复活。

def conway(grid, delay, max_iterations)
   rows = 0..grid.size - 1
   cols = 0..grid.first.size - 1
   iterations = 0
   display_grid(grid)
   sleep(delay)
   loop do
     break if iterations == max_iterations
     iterations += 1
     cells_to_flip = transitions(grid)
     break if cells_to_flip.empty?
     update_grid(grid, cells_to_flip)
     display_grid(grid)
     sleep(delay)
   end
end

我们来试试吧。

conway(grid, 2, 1_000_000)

这显示网格三次,间隔两秒,然后退出,因为在最后一次迭代后没有进一步的更改。