terminal_game_of_life 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: eac2c8c38b2299e6eb97af42e51429af726ad8b3e71707a77ab9af261a736e7c
4
- data.tar.gz: 9d733ec099d7494fe862efc6c8cac5609a82e775c12523bbf7acfe70deca0147
3
+ metadata.gz: bf5383033cdfa94863a1ffed02d61b746689d927821350042ada2bd3727ac031
4
+ data.tar.gz: 9e5c119a3d6b70ef61fc80db8fdc740f3233624e4284da35539fbd1c06e61ba5
5
5
  SHA512:
6
- metadata.gz: 25a07b67279a9843072a1667dfc8be1ba73ef8ae6e6c38d6ccd837e5f77adbf9685d2a6693083fe0b0cb21ea5ccd4723f008e1615f9a666483efdf029cff6921
7
- data.tar.gz: 5062f9fbc8ce2d3c74af9692b7639cda2f3ff98f3a77e8430bc453130bd313c9ba27eef34f4573ee41a6d9a5cdce8a08ca27a24a882045f16295ea1cf7da7e48
6
+ metadata.gz: 94b939ec472b19c6d7d38ec0f38ada21d24756c752fba4bb99da96c79c4e2b5a170a46884611ae57eb83d9a6093d81edaa529cfe58feffdee8d7d4d2f609faa4
7
+ data.tar.gz: bdc9a52b4f4040c22c72a7583ecb2c0685a031804ad5cb28abadd89465f60a3bd431b3ba8aa424f959e75884c69d2f370865be6d918d52823685a68a8c6eb160
@@ -20,10 +20,10 @@ class CLI < Thor
20
20
  class_option "input", aliases: "-i", type: :string, banner: :VALUE,
21
21
  desc: "Specify the path/URL for the file to use as an initial state. (used instead of seed)"
22
22
 
23
- class_option "width", aliases: "-w", type: :numeric, banner: :WIDTH,
23
+ class_option "width", type: :numeric, banner: :WIDTH,
24
24
  desc: "Specify the width of generated universe. (default to terminal width)"
25
25
 
26
- class_option "height", aliases: "-h", type: :numeric, banner: :HEIGHT,
26
+ class_option "height", type: :numeric, banner: :HEIGHT,
27
27
  desc: "Specify the hight of generated universe. (default to terminal height)"
28
28
 
29
29
  class_option "dead-cell", type: :string, banner: :CHAR, default: "\s",
@@ -34,18 +34,31 @@ class CLI < Thor
34
34
 
35
35
  class_option "delay", aliases: "-d", type: :numeric, banner: "Milli-Seconds", default: 50,
36
36
  desc: "Specify the introduced delay between each generation"
37
+ # rubocop:disable Metrics/AbcSize
38
+ # rubocop:disable Metrics/MethodLength
37
39
  def start
40
+ max_height = IO.console.winsize[0] - 2
41
+ max_width = IO.console.winsize[1]
42
+ if options["width"]&.> max_width
43
+ fail GameOfLife::Error, "Invalid --width value, must not exceed current terminal width: #{max_width}"
44
+ end
45
+ if options["height"]&.> max_height
46
+ fail GameOfLife::Error, "Invalid --height value, must not exceed current terminal height: #{max_height}"
47
+ end
48
+
38
49
  options_with_console_defaults = {
39
50
  # Defaults the hight to less then the height of the terminal to allow banner info
40
51
  # and an extra emtpy line to avoid triggering terminal scroll while flushing
41
- "height" => IO.console.winsize[0] - 3,
42
- "width" => IO.console.winsize[1],
52
+ "height" => max_height,
53
+ "width" => max_width,
43
54
  "seed" => rand(100_000),
44
55
  }.merge(options)
45
56
 
46
57
  universe = GameOfLife.generate(options_with_console_defaults)
47
58
  GameOfLife.run(universe)
48
59
  end
60
+ # rubocop:enable Metrics/AbcSize
61
+ # rubocop:enable Metrics/MethodLength
49
62
 
50
63
  class << self
51
64
  # Allow exit with status 1 on failure
@@ -14,15 +14,27 @@ module GameOfLife
14
14
  # @option options [Numeric] "width" (floored) and used as the width of the universe
15
15
  # @option options [Numeric] "height" (floored) and used as the height of the universe
16
16
  # @return [GameOfLife::Universe] populated with the parsed input
17
+ # @see ::GameOfLife::Generators::Input.generate_input_data
17
18
  # @see ::GameOfLife::Generators::Input.populate
18
19
  def new(options)
20
+ width = options["width"].to_i
21
+ height = options["height"].to_i
22
+ uri = options["input"]
23
+ input = generate_input_data(uri: uri)
24
+ populate(input: input, width: width, height: height)
25
+ end
26
+
27
+ # Generate the seeding data for populating the {GameOfLife::Universe} from input URI
28
+ # @param uri [String] inputh path to local file or URL to open
29
+ # @return [Array<Array<True|False>>] covering the defined size of universe
30
+ # @see ::GameOfLife::Generators::Input.populate
31
+ private def generate_input_data(uri:)
19
32
  # By design, it's intentional that we use URI.open to to allow seamless file and url access
20
- raw = URI.open(options["input"]).read.split("\n") # rubocop:disable Security/Open
33
+ raw = URI.open(uri).read.split("\n") # rubocop:disable Security/Open
21
34
 
22
35
  # Parse the file and convert it to a boolean 2D array
23
36
  # where any alpha numeric character is considered a living cell (true)
24
37
  raw.map! { |row| row.chars.map { |char| char.match?(/[[:alnum:]]/) } }
25
- populate(parsed_input: raw, width: options["width"].to_i, height: options["height"].to_i)
26
38
  rescue ::OpenURI::HTTPError, ::SocketError
27
39
  raise GameOfLife::Error, "URL isn't avaialable, please check it again and make sure the URL is correct"
28
40
  rescue ::Errno::ENOENT
@@ -31,16 +43,17 @@ module GameOfLife
31
43
  end
32
44
 
33
45
  # Populate the {GameOfLife::Universe} with reference to the parsed input 2D array (True/False)
34
- # @param parsed_input [Array<Array<True|False>>]
46
+ # @param input [Array<Array<True|False>>]
35
47
  # @param width [Integer] width of the generated cyclic universe
36
48
  # @param height [Integer] height of the generated cyclic universe
37
49
  # @return [GameOfLife::Universe] populated with the parsed input
38
- private def populate(parsed_input:, width:, height:)
50
+ # @see ::GameOfLife::Generators::Input.generate_input_data
51
+ private def populate(input:, width:, height:)
39
52
  # Generate a universe based on the parsed raw file and fill it with the cells
40
53
  universe = GameOfLife::Universe.new(height: height, width: width)
41
54
  (0...height).each do |i|
42
55
  (0...width).each do |j|
43
- cell = { x: j, y: i, alive: parsed_input.fetch(i, nil)&.fetch(j, nil) }
56
+ cell = { x: j, y: i, alive: input.fetch(i, nil)&.fetch(j, nil) }
44
57
  universe[i][j] = ::GameOfLife::Cell.new(cell)
45
58
  end
46
59
  end
@@ -11,8 +11,47 @@ module GameOfLife
11
11
  # @option options [Numeric] "width" (floored) and used as the width of the universe
12
12
  # @option options [Numeric] "height" (floored) and used as the height of the universe
13
13
  # @return [GameOfLife::Universe] populated with the parsed input
14
+ # @see ::GameOfLife::Generators::Seed.generate_seed_data
15
+ # @see ::GameOfLife::Generators::Seed.populate
14
16
  def new(options)
15
- fail ::NotImplementedError, "Seed option isn't implemented yet, please use --input option"
17
+ width = options["width"].to_i
18
+ height = options["height"].to_i
19
+ seed = options["seed"].to_i
20
+ input = generate_seed_data(seed: seed, width: width, height: height)
21
+ populate(input: input, width: width, height: height)
22
+ end
23
+
24
+ # Generate the seeding data for populating the {GameOfLife::Universe}
25
+ # @param seed [Integer] used to generate {Array<"0"|"1">} using Std ruby Random
26
+ # @param width [Integer] width of the generated cyclic universe
27
+ # @param height [Integer] height of the generated cyclic universe
28
+ # @return [Array<Array<True|False>>] covering the defined size of universe
29
+ # @see ::GameOfLife::Generators::Seed.populate
30
+ # @see https://ruby-doc.org/core-2.4.0/Random.html
31
+ private def generate_seed_data(seed:, width:, height:)
32
+ # Create a pseudo(stable) random generator from the seed
33
+ # Generate a sequence of bytes to cover the Game of Life Universe plane
34
+ # each byte can be unpacked into binary data string (ex. "01101011")
35
+ raw = Random.new(seed).bytes(width * height).unpack1("B*").split("").each_slice(width)
36
+ raw.map { |row| row.map { |char| char.eql?("1") } }
37
+ end
38
+
39
+ # Populate the {GameOfLife::Universe} with reference to the parsed input 2D array (True/False)
40
+ # @param input [Array<Array<True|False>>]
41
+ # @param width [Integer] width of the generated cyclic universe
42
+ # @param height [Integer] height of the generated cyclic universe
43
+ # @return [GameOfLife::Universe] populated with the parsed input
44
+ # @see ::GameOfLife::Generators::Seed.generate_seed_data
45
+ private def populate(input:, width:, height:)
46
+ # Generate a universe based on the parsed raw file and fill it with the cells
47
+ universe = GameOfLife::Universe.new(height: height, width: width)
48
+ (0...height).each do |i|
49
+ (0...width).each do |j|
50
+ cell = { x: j, y: i, alive: input.fetch(i, nil)&.fetch(j, nil) }
51
+ universe[i][j] = ::GameOfLife::Cell.new(cell)
52
+ end
53
+ end
54
+ universe
16
55
  end
17
56
  end
18
57
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GameOfLife
4
- VERSION = "0.1.2"
4
+ # Game of life version number
5
+ VERSION = "0.2.0"
5
6
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: terminal_game_of_life
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - a14m
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-12-09 00:00:00.000000000 Z
11
+ date: 2020-12-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor