1

我正在用 Ruby 实现生命游戏,这是我目前所拥有的:

class Cell

  attr_accessor :world, :x, :y

  def initialize(world=World.new, x=0, y=0)
    @world = world
    @world.cells << self
    @x = x
    @y = y
  end

  def neighbours
    @neighbours = []
    world.cells.each do |cell|

      # Detects neighbour to the north
      if self.x == cell.x && self.y == cell.y - 1
        @neighbours << cell
      end
      # Detects neighbour to the north-east
      if self.x == cell.x - 1 && self.y == cell.y - 1
        @neighbours << cell
      end
      # Detects neighbour to the east
      if self.x == cell.x - 1 && self.y == cell.y
        @neighbours << cell
      end
      # Detects neighbour to the south-east
      if self.x == cell.x - 1 && self.y == cell.y + 1
        @neighbours << cell
      end
      # Detects neighbour to the south
      if self.x == cell.x && self.y == cell.y + 1
        @neighbours << cell
      end
      # Detects neighbour to the south-west
      if self.x == cell.x + 1 && self.y == cell.y + 1
        @neighbours << cell
      end
      # Detects neighbour to the west
      if self.x == cell.x + 1 && self.y == cell.y
        @neighbours << cell
      end
      # Detects neighbour to the north-west
      if self.x == cell.x + 1 && self.y == cell.y - 1
        @neighbours << cell
      end

    end
    @neighbours
  end

  def alive?
    self.world.cells.include?(self)
  end

  def dead?
    !self.world.cells.include?(self)
  end

  def die!
    self.world.cells.delete(self)
  end

  def revive!
    self.world.cells << self
  end

end

class World
  attr_accessor :cells

  def initialize
    @cells = []
  end

  def tick!
    self.cells.each do |cell|
      # Rule 1
      if cell.neighbours.count < 2
        cell.die!
      end
    end
  end

end

我在 Rails 中编码已经有一段时间了,但我对如何做以下事情感到困惑:

  1. 验证并确保在 World 的一个字段上只能存在一个 Cell 对象?
  2. 如何将东西保存到数据库(例如 postgresql)?在这种情况下我是否必须这样做,或者我可以保留它并在内存中运行它吗?
  3. 如何使我的生命游戏的图形输出看起来像这样?

人生游戏.gif

我感到困惑的原因是因为 Rails 开箱即用,现在我只需要了解 Ruby 是如何做到这一点的帮助。

编辑:

我已经用验证方法更新了 Cell 类,但我只能在初始化对象后运行它。有没有办法在初始化时运行它?这是代码:

  5   def initialize(world=World.new, x=0, y=0)                                                           | 53       neighbour_cell = Cell.new(subject.world, -1, 0)                                                 
  6     @world = world                                                                                    | 54       subject.neighbours.count.should == 1                                                            
  7     @world.cells << self # if self.valid? < this after if doesn't work                                         | 55     end                                                                                               
  8     @x = x                                                                                            | 56                                                                                                       
  9     @y = y                                                                                            | 57     it 'Detects cell to the north-west' do                                                            
 10   end                                                                                                 | 58       neighbour_cell = Cell.new(subject.world, -1, 1)                                                 
 11                                                                                                       | 59       subject.neighbours.count.should == 1                                                            
 12   def valid?                                                                                          | 60     end                                                                                               
 13     @valid = true                                                                                     | 61                                                                                                       
 14     self.world.cells.each do |cell|                                                                   | 62     it 'Creates a live cell' do                                                                       
 15       if self.x == cell.x && self.y == cell.y                                                         | 63       cell.should be_alive                                                                            
 16         @valid = false                                                                                | 64     end                                                                                               
 17         self.world.cells.delete(self)                                                                 | 65                                                                                                       
 18       end                                                                                             | 66     it 'Kills a cell' do                                                                              
 19     end                                                                                               | 67       cell.die!                                                                                       
 20     @valid                                                                                            | 68       cell.should be_dead                                                                             
 21   end                                        
4

3 回答 3

1

脱离 Rails 并尝试不同的东西是件好事,尤其是像 Game of Life 这样的经典挑战。

至于您的问题,它们都取决于您选择的系统设计。但是对于一个简单的实现,这里有一些指针。

  1. 使用Set类怎么样?这是一个不允许重复的对象数组。
  2. 如果您想在应用程序的单独运行之间维护状态,您只需要一个 DB。如果您不需要它,请将其全部保存在内存中。
  3. 这是一个太大的问题,无法在这里回答,但您有很多选择。极端情况是:
    • 管理屏幕更新自己的像素渲染,在单元格更改时将字符打印到终端
    • 使用类似ruby​​game的东西,它有精灵、GUI 和绘图的概念
于 2013-03-12T14:57:44.777 回答
1

你在这里问了一些大的、一般的问题,每个问题都有很多答案。我将给出我的初步看法:

1:验证- 看起来(来自 Rails)你想要某种valid?可以调用对象的方法。但是,在纯 ruby​​ 中,您必须明确定义“有效”对象到底是什么。您还必须决定应该在哪里进行验证,即哪个对象正在执行验证以及哪个对象正在被验证

从上面提供的代码中,我建议您至少需要一个类,Game例如调用它,它控制世界和其中的所有单元格。目前,您已经在类中加载了很多逻辑Cell,您可能想查看单一职责原则,看看这是否是一个有效的(哈哈)设计选择。

2. 持久性——没有什么能阻止你在纯红宝石中使用 ActiveRecord 作为你的 ORM。您可以查看这个 SO 问题以获取示例实现:how-to-use-active-record-without-rails

3. 图形- 有许多用于 ruby​​ 的游戏库,它们具有适用于您所在平台的内置图形 API。我个人与他们合作的不多,但gosu似乎是一个不错的选择。

于 2013-03-12T15:11:53.803 回答
0

我已经做了!但不是这里采用的方法。下面是我的代码,剩下要做的就是实现一些图形输出。此外,这是 Github 上的完整代码,请查看并评论并帮助我改进它和其他东西。谢谢你们俩帮助我:)

我可以重构这个 'live_neighbours_around_cell(cell)' 方法,但不知道如何。你能帮忙吗?此外,将 'Cell.new' 立即添加到 'cells' 数组会很有用,而不是像我现在这样做的那样,我有单独的函数。

class Game
  attr_accessor :world

  def initialize(world=World.new, seeds=[[1, 1]])
    @world = world
    seeds.each do |seed|
      world.cell_board[seed[0]][seed[1]].alive = true
    end
  end

  def tick!
    next_round_live_cells = []
    next_round_dead_cells = []

    world.cells.each do |cell|
      # Rule 1: 
      # Any live cell with fewer than two live neighbours dies
      if world.live_neighbours_around_cell(cell).count < 2
        next_round_dead_cells << cell
      end
      # Rule 2:
      # Any live cell with two or three live neighbours lives on to the next generation
      if cell.alive? && world.live_neighbours_around_cell(cell).count == (2 || 3)
        next_round_live_cells << cell
      end
      # Rule 3:
      # Any live cell with more than three live neighbours dies
      if cell.alive? && world.live_neighbours_around_cell(cell).count > 3
        next_round_dead_cells << cell
      end
      # Rule 4:
      # Any dead cell with exactly three live neighbours becomes a live cell
      if cell.dead? && world.live_neighbours_around_cell(cell).count == 3
        next_round_live_cells << cell
      end
    end

    next_round_live_cells.each do |cell|
      cell.revive!
    end
    next_round_dead_cells.each do |cell|
      cell.die!
    end
  end
end

class World
  attr_accessor :rows, :cols, :cell_board, :cells

  # Scheme of default initialized world matrix
  #------------------------
  #     0     1     2
  # 0 [ dead, dead, dead ]
  # 1 [ dead, alive, dead ]
  # 2 [ dead, dead, dead ]
  #-----------------------

  def initialize(rows=3, cols=3)
    @rows = rows
    @cols = cols
    @cells = []

    @cell_board = Array.new(rows) do |row|
      Array.new(cols) do |col|
        Cell.new(row, col)
      end
    end

    cell_board.each do |row|
      row.each do |element|
        if element.is_a?(Cell)
          cells << element
        end
      end
    end
  end

  def live_cells
    cells.select { |cell| cell.alive }
  end

  def live_neighbours_around_cell(cell)
    live_neighbours = []
    live_cells.each do |live_cell|
      # Neighbour to the North
      if live_cell.x == cell.x - 1 && live_cell.y == cell.y
        live_neighbours << live_cell
      end
      # Neighbour to the North-East
      if live_cell.x == cell.x - 1 && live_cell.y == cell.y + 1
        live_neighbours << live_cell
      end
      # Neighbour to the East
      if live_cell.x == cell.x && live_cell.y == cell.y + 1
        live_neighbours << live_cell
      end
      # Neighbour to the South-East
      if live_cell.x == cell.x + 1 && live_cell.y == cell.y + 1
        live_neighbours << live_cell
      end
      # Neighbour to the South
      if live_cell.x == cell.x + 1 && live_cell.y == cell.y
        live_neighbours << live_cell
      end
      # Neighbour to the South-West
      if live_cell.x == cell.x + 1 && live_cell.y == cell.y - 1
        live_neighbours << live_cell
      end
      # Neighbour to the West
      if live_cell.x == cell.x && live_cell.y == cell.y - 1
        live_neighbours << live_cell
      end
      # Neighbour to the North-West
      if live_cell.x == cell.x - 1 && live_cell.y == cell.y - 1
        live_neighbours << live_cell
      end
    end
    live_neighbours
  end

end

class Cell
  attr_accessor :x, :y, :alive

  def initialize(x=0, y=0)
    @x = x
    @y = y
    @alive = false
  end

  def alive?
    alive
  end

  def dead?
    !alive
  end

  def die!
    @alive = false
  end

  def revive!
    @alive = true # same as > self.alive = true
  end
end
于 2013-03-14T13:45:03.930 回答