Module: GameOfLife

Defined in:
lib/game_of_life.rb,
lib/game_of_life/cell.rb,
lib/game_of_life/plane.rb,
lib/game_of_life/universe.rb,
lib/game_of_life/generators/seed.rb,
lib/game_of_life/generators/input.rb

Overview

Game of Life module entry point

Defined Under Namespace

Modules: Generators Classes: Cell, Error, Plane, Universe

Class Method Summary collapse

Class Method Details

.generate(options) ⇒ GameOfLife::Universe

Generate a universe based on the options (from an input file/URI) or using a random/passed seed This method also sets the Classes/Modules constants for the run defining the options for Cell (Dead/Live) state representations, and the delay introduced after rendering each universe/generation and the banner for the info/options

Parameters:

  • options (Hash)

    CLI options to generate initial conditions

Options Hash (options):

  • "input" (String | nil)

    path to a local file or URL to open

  • "seed" (Numeric)

    number to use if no "input" is provided (defaults to random)

  • "width" (Numeric)

    of the universe

  • "height" (Numeric)

    of the universe

  • "delay" (Numeric)

    introduced after each cycle

  • "live-cell" (String)

    representation

  • "dead-cell" (String)

    representation

Returns:



80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/game_of_life.rb', line 80

def generate(options)
  # Define the constants for Cell representation
  GameOfLife::Cell.const_set(:LIVE_CELL, options["live-cell"])
  GameOfLife::Cell.const_set(:DEAD_CELL, options["dead-cell"])
  # Define the constant used for delay
  GameOfLife.const_set(:DELAY, options["delay"].to_f)

  banner = options["input"] ? "Input: #{options['input']}" : "Seed: #{options['seed']}"
  # Define the constant used for banner info
  GameOfLife.const_set(:BANNER, banner)

  return GameOfLife::Generators::Input.new(options) if options["input"]
  GameOfLife::Generators::Seed.new(options)
end

.parsed_options(options) ⇒ Object

Validate and parse the CLI options and set defaults for the dynamic options. return [Hash] of the parsed options with dynamic defaults based on window size and kernel random

Parameters:

  • options (Hash)

    CLI options to generate initial conditions

Options Hash (options):

  • "input" (String | nil)

    path to a local file or URL to open

  • "seed" (Numeric | nil)

    number to use if no "input" is provided

  • "width" (Numeric)

    of the universe

  • "height" (Numeric)

    of the universe

  • "delay" (Numeric)

    introduced after each cycle

  • "live-cell" (String)

    representation

  • "dead-cell" (String)

    representation

Code smells:

  • AbcSize, MethodLenght, Cyclomatic and Perceived Complexity needed for parsing and validating options rubocop:disable Metrics/AbcSize rubocop:disable Metrics/MethodLength rubocop:disable Metrics/CyclomaticComplexity rubocop:disable Metrics/PerceivedComplexity



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
# File 'lib/game_of_life.rb', line 29

def parsed_options(options)
  max_height = IO.console.winsize[0] - 2
  max_width  = IO.console.winsize[1]

  if options["delay"].to_i.negative?
    fail GameOfLife::Error, "Invalid --delay value. must be positive value (sadly this is how time works -so far-)"
  end
  if (options["width"]&.< 1) || (options["width"]&.> max_width)
    fail GameOfLife::Error, "Invalid --width value. " \
                            "must be between 1 and #{max_width} (current terminal width)"
  end
  if (options["height"]&.< 1) || (options["height"]&.> max_height)
    fail GameOfLife::Error, "Invalid --height value. " \
                            "must be between 1 and #{max_height} (current terminal height)"
  end

  fail GameOfLife::Error, "Invalid --live-cell value. must be a single character" if options["live-cell"].length > 1

  fail GameOfLife::Error, "Invalid --dead-cell value. must be a single character" if options["dead-cell"].length > 1

  if options["dead-cell"] == options["live-cell"]
    fail GameOfLife::Error, "Invalid --dead-cell value. must be a different character than --live-cell"
  end

  {
    # Defaults the hight to less then the height of the terminal to allow banner info
    # and an extra emtpy line to avoid triggering terminal scroll while flushing
    "height" => max_height,
    "width" => max_width,
    "seed" => rand(100_000),
  }.merge(options)
end

.render(universe) ⇒ Object

render method to do the terminal screen/scrollback buffer cleaning and rendering of the current universe/banner info based on the delay/cell configured by the user



107
108
109
110
111
112
113
114
115
116
# File 'lib/game_of_life.rb', line 107

def render(universe)
  # Use Alternate Screen (\e[?1049h) and move cursor to top left (\e[H)
  # Ref: https://github.com/ruby/ruby/blob/ruby_2_7/lib/irb/easter-egg.rb#L112-L131
  # Ref: https://stackoverflow.com/questions/11023929/using-the-alternate-screen-in-a-bash-script
  # Ref: https://superuser.com/questions/122911/what-commands-can-i-use-to-reset-and-clear-my-terminal
  print "\e[?1049h\e[H"
  print universe
  print "\n #{BANNER}"
  sleep(DELAY / 1000.0)
end

.run(universe) ⇒ Object

The infinite running loop for rendering the current state of a universe and then evolving it and repeating.



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

def run(universe)
  Kernel.loop do
    GameOfLife.render(universe)
    universe.evolve!
  end
end