1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
|
# frozen_string_literal: true
module GameOfLife
# Class representing a cell in the GemeOfLife universe
# @!attribute [r] alive
# @return [True | False] the living state of the cell
# @!attribute [r] x
# @return [Integer] the X-axis position of the cell in the universe
# @!attribute [r] y
# @return [Integer] the Y-axis position of the cell in the universe
class Cell
attr_reader :alive, :x, :y
# Initialize a cell in the game of life universe
# @param alive [True|False] boolean indicating if the cell is considered alive
# @param x [Integer] the x position on the cyclic universe plane
# @param y [Integer] the y position on the cyclic universe plane
def initialize(alive:, x:, y:)
@alive = alive
@x = x
@y = y
end
# Representation of a cell
# Both LIVE_CELL, and DEAD_CELL constant are defined on the application start from the options
# @return [String] if a cell is alive or an empty space
def to_s
alive? ? LIVE_CELL : DEAD_CELL
end
# Evolve a cell in the game of life to the new state in the next generation
# @param neighbors [Array<GameOfLife::Cell>] that are neighboring the current cell (size of 8)
# @return [GameOfLife::Cell] representing the new state in the next generation
#
# @note There are some optimizations of returning a new object only when the state changes, otherwise returning self
# This optimization is done to limit the spacial complexity to `O(2n^2) = O(2*witdht*height)`
# in the worst case scenario, and to `O(n^2) = O(width * hight)` in the best case scenario
# (when the universe is stale and don't evolve anymore).
# @see #living_neighbors
def evolve!(neighbors)
return repopulate! if dead? && living_neighbors(neighbors) == 3
return survive! if alive? && [2, 3].include?(living_neighbors(neighbors))
die!
end
# Helper method to define the language used in GameOfLife
# @return [True | False] cell living state
def alive?
@alive
end
# Helper method to define the language used in GameOfLife
# @return [True | False] cell dead state
# @see #alive?
def dead?
!alive?
end
# Count the living neighbor cells relatively to the current cell in the {GameOfLife::Universe}
# @param neighbors [Array<GameOfLife::Cell>] that are neighboring the current cell (size of 8)
# @return [Integer] the number of living neighbors
# @see GameOfLive::Universe#neighbors
private def living_neighbors(neighbors)
neighbors.select(&:alive?).size
end
# Helper method to define the language used in GameOfLife
# @return [GameOfLife::Cell] new living cell
# @see #survive!
# @see #die!
private def repopulate!
self.class.new(x: x, y: y, alive: true)
end
# Helper method to define the language used in GameOfLife
# @return [GameOfLife::Cell] self (since no change in state needed)
# @see #repopulate!
# @see #die!
private def survive!
self
end
# Helper method to define the language used in GameOfLife
# When a cell is dead returns self otherwise return a new dead cell
# @return [GameOfLife::Cell] self (if already dead) or new dead cell
# @see #repopulate!
# @see #survive!
private def die!
dead? ? self : self.class.new(x: x, y: y, alive: false)
end
end
end
|