summaryrefslogtreecommitdiffstats
path: root/CLI/ruby/lib/game_of_life/cell.rb
blob: 97a4e05b24402f714ba5a1dafdc76046bbe796ce (plain) (blame)
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