Class: GameOfLife::Board

Inherits:
Object
  • Object
show all
Defined in:
lib/game_of_life/board.rb,
lib/game_of_life/cell.rb

Overview

The board used in the game. Holds the Cells.

Defined Under Namespace

Classes: Cell

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(seed_data) ⇒ Board

Creates the board

Examples:

seed_data looks like

the output of SimpleStringInputter#parse

Parameters:

  • seed_data (2D Array<Symbol>)

    the data for the Cells in the board, as an 2D array.

Raises:

  • (InvalidBoardError)

    if the seed_data is not in the shape of a square, or if all elements are not present



23
24
25
26
27
28
29
30
31
32
# File 'lib/game_of_life/board.rb', line 23

def initialize(seed_data)
  @cells = Array.new(Array.new)
  seed_with!(seed_data)
  begin
    validate
  rescue InvalidBoardError => ex
    # Add the seed_data into the error message, so the caller gets a clue
    raise InvalidBoardError, ex.message + " [seed data was: #{seed_data.inspect}]"
  end
end

Instance Attribute Details

#cellsObject (readonly)

Note:

Use #each_cell, #each_row etc. methods to access the cells individually.

The Cells in this board,internally maintained as a 2D Array of Cells. The x-coordinate increases horizontally and is always positive. The y-coordinate increases vertically and is always positive. Internally, the cells are arranged as a 2D array. The first-level Array indexed with the y-coordinates. It contains an Array of Cells, whose position is the x-coordinate.



15
16
17
# File 'lib/game_of_life/board.rb', line 15

def cells
  @cells
end

Instance Method Details

#cell_at(x, y) ⇒ Cell

Find the cell at a given pair of co-ordinates. Following Array symantics, this method returns nil if nothing exists at that location or if the location is out of the board. To avoid Array’s negative index symantics it returns nill if a negative index is passed.

Parameters:

  • x (Integer)

    the x-coordinate

  • y (Integer)

    the y-coordinate

Returns:



58
59
60
61
# File 'lib/game_of_life/board.rb', line 58

def cell_at(x, y)
  return nil if (x < 0 || y < 0)
  @cells[y][x] if @cells[y]
end

#coords_of_neighbors(x, y) ⇒ Array<Integer, Integer> (private)

Note:

This method returns all possible co-ordinate pairs of neighbors, so it can contain coordinates of cells not in the board, or negative ones.

Calculates the co-ordinates of neighbors of a given pair of co-ordinates.

Examples:

coords_of_neighbors(1,1) =>
  [
    [0, 0], [0, 1], [0, 2],
    [1, 0],         [1, 2],
    [2, 0], [2, 1], [2, 2],
  ]

Parameters:

  • x (Integer)

    the x-coordinate

  • y (Integer)

    the y-coordinate

Returns:

  • (Array<Integer, Integer>)

    the list of neighboring co-ordinates

See Also:



179
180
181
182
183
184
185
186
187
188
# File 'lib/game_of_life/board.rb', line 179

def coords_of_neighbors(x, y)
  coords_of_neighbors = []
  (x - 1).upto(x + 1).each do |neighbors_x|
    (y - 1).upto(y + 1).each do |neighbors_y|
      next if (x == neighbors_x) && (y == neighbors_y)
      coords_of_neighbors << [neighbors_x, neighbors_y]
    end
  end
  coords_of_neighbors
end

#each_cell(&block) ⇒ Object



47
48
49
# File 'lib/game_of_life/board.rb', line 47

def each_cell(&block)
  @cells.flatten.each { |cell| yield cell }
end

#each_row(&block) ⇒ Object



39
40
41
# File 'lib/game_of_life/board.rb', line 39

def each_row(&block)
  @cells.each { |row| yield row }
end

#each_row_with_index(&block) ⇒ Object



43
44
45
# File 'lib/game_of_life/board.rb', line 43

def each_row_with_index(&block)
  @cells.each_with_index { |row, i| yield row, i }
end

#mark_and_sweep_for_next_generation!Object

Goes through each Cell and marks it (using Rules) to signal it’s state for the next generation. This prevents modifying any Cell in-place as each generation is a pure function of the previous. Once all Cells are marked, it sweeps across them gets them to change their state if they were marked to.



98
99
100
101
# File 'lib/game_of_life/board.rb', line 98

def mark_and_sweep_for_next_generation!
  mark_changes_for_next_generation
  sweep_changes_for_next_generation!
end

#mark_changes_for_next_generationObject (private)



152
153
154
155
156
157
158
159
# File 'lib/game_of_life/board.rb', line 152

def mark_changes_for_next_generation
  self.each_row_with_index do |cells, y|
    cells.each_with_index do |cell, x|
      cell.should_live_in_next_generation =
        Rules.should_cell_live?(self, cell, x, y)
    end
  end
end

#neighbors_of_cell_at(x, y) ⇒ Array<Cell>

Finds the neighbors of a given Cell‘s co-ordinates. The neighbors are the eight cells that surround the given one.

Parameters:

  • x (Integer)

    the x-coordinate of the cell you find the neighbors of

  • y (Integer)

    the y-coordinate of the cell you find the neighbors of

Returns:



68
69
70
71
# File 'lib/game_of_life/board.rb', line 68

def neighbors_of_cell_at(x, y)
  neighbors = coords_of_neighbors(x, y).map { |x, y| self.cell_at(x, y) }
  neighbors.reject {|n| n.nil?}
end

#reformat_for_next_generation!Object

This is the first stage in a Game’s #tick.

See Also:



75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/game_of_life/board.rb', line 75

def reformat_for_next_generation!
  # create an array of dead cells and insert it as the first and last row of cells
  dead_cells = (1..@cells.first.size).map { Cell.new }
  # don't forget to deep copy the dead_cells
  @cells.unshift Marshal.load(Marshal.dump(dead_cells))
  @cells.push Marshal.load(Marshal.dump(dead_cells))

  # also insert a dead cell at the left and right of each row
  @cells.each do |row|
    row.unshift Cell.new
    row.push Cell.new
  end

  # validate to see if we broke the board
  validate
end

#seed_with!(data) ⇒ Object (private)



143
144
145
146
147
148
149
150
# File 'lib/game_of_life/board.rb', line 143

def seed_with!(data)
  data.each_with_index do |row, y|
    @cells << []
    row.each_with_index do |state, x|
      @cells[y] << Cell.new(state)
    end
  end
end

#shed_dead_weight!Object

This is the third and last stage in a Game’s #tick.

See Also:



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/game_of_life/board.rb', line 105

def shed_dead_weight!
  # Remove the first and last rows if all cells are dead
  @cells.shift if @cells.first.all? { |cell| cell.dead? }
  @cells.pop if @cells.last.all? { |cell| cell.dead? }

  # Remove the first cell of every row, if they are all dead
  first_columns = @cells.map { |row| row.first }
  if first_columns.all? { |cell| cell.dead? }
    @cells.each { |row| row.shift }
  end

  # Remove the last cell of every row, if they are all dead
  last_columns = @cells.map { |row| row.last }
  if last_columns.all? { |cell| cell.dead? }
    @cells.each { |row| row.pop }
  end

  validate
end

#sweep_changes_for_next_generation!Object (private)



161
162
163
# File 'lib/game_of_life/board.rb', line 161

def sweep_changes_for_next_generation!
  self.each_cell { |cell| cell.change_state_if_needed! }
end

#validateObject (private)



126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/game_of_life/board.rb', line 126

def validate
  num_o_rows = @cells.size
  columns_in_each_row = @cells.map(&:size)
  unless columns_in_each_row.uniq.size == 1
    msg = "Unequal number of columns, #{columns_in_each_row.inspect} in different rows found"
    raise InvalidBoardError, msg
  end

  num_o_columns = columns_in_each_row.uniq.first
  num_o_elements = @cells.flatten.reject {|d| d.nil? }.size
  unless (num_o_rows * num_o_columns) == num_o_elements
    msg = "Not a rectangular shape: " +
      "rows(#{num_o_rows}) x columns(#{num_o_columns}) != total elements(#{num_o_elements})]. "
    raise InvalidBoardError, msg
  end
end

#view(outputter = Outputters::SimpleStringOutputter.new) ⇒ Object

Parameters:



35
36
37
# File 'lib/game_of_life/board.rb', line 35

def view(outputter = Outputters::SimpleStringOutputter.new)
  outputter.render(self)
end