terminal_game_of_life 0.1.2 → 1.0.2

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: 2c26cd26c1b4aa435be502ab5d4490f9aac0c9b7213349daa3c7c74ff284a62b
4
+ data.tar.gz: bb7b96d7d61c2d77bb0d23fc900abe6484553f8f8aff384033fa9cba3b2b9d6e
5
5
  SHA512:
6
- metadata.gz: 25a07b67279a9843072a1667dfc8be1ba73ef8ae6e6c38d6ccd837e5f77adbf9685d2a6693083fe0b0cb21ea5ccd4723f008e1615f9a666483efdf029cff6921
7
- data.tar.gz: 5062f9fbc8ce2d3c74af9692b7639cda2f3ff98f3a77e8430bc453130bd313c9ba27eef34f4573ee41a6d9a5cdce8a08ca27a24a882045f16295ea1cf7da7e48
6
+ metadata.gz: 400bfb3d4e758c4b610bbbee25d839969ea9ee2c1e68c5c0d3a29e51d90e3e4cd42d113c2d8f80ab2c89e539c7091220abdbd8cc70fe66c9c9209f163fc8cdbb
7
+ data.tar.gz: af2edea622e9325201e4c9380b9fea8ebeedb749d6b0cbaaa6b59d154568c5049dac14033c6843cb18242f472a272904eaaa18db7de175e070341e86b2a6db46
@@ -0,0 +1,8 @@
1
+ lib/**/*.rb
2
+
3
+ --readme README.md
4
+ --markup markdown
5
+ --markup-provider redcarpet
6
+ --protected
7
+ --private
8
+ --tag smell:"Code smells"
data/README.md CHANGED
@@ -9,44 +9,50 @@ $ gem install terminal_game_of_life
9
9
 
10
10
  ## Usage
11
11
 
12
- Run `$game-of-life -i https://gitlab.com/a14m --live-cell +`
12
+ ```bash
13
+ $game-of-life -i https://meta.sr.ht/~a14m.keys --live-cell +
14
+ $game-of-life -i /path/to/file.txt --height 35 --width 35
15
+ $game-of-life -s 1337 --delay 250
16
+ ```
13
17
 
14
18
  Check `game-of-life --help` for usage info.
15
19
 
16
20
  ```
21
+ Commands:
17
22
  game-of-life --version, -v # Prints the Game of Life version information
18
23
  game-of-life help [COMMAND] # Describe available commands or one specific command
19
- game-of-life start [OPTIONS] # Start the Game of Life simulations (default command)
24
+ game-of-life start [OPTIONS] # Start the Game of Life simulations
20
25
 
21
26
  Options:
22
27
  -s, [--seed=SEED] # Specify the seed number to use as an initial state (default to random).
23
28
  -i, [--input=VALUE] # Specify the path/URL for the file to use as an initial state. (used instead of seed)
24
- -w, [--width=WIDTH] # Specify the width of generated universe. (default to terminal width)
25
- -h, [--height=HEIGHT] # Specify the hight of generated universe. (default to terminal height)
29
+ [--width=WIDTH] # Specify the width of generated universe. (default to terminal width)
30
+ [--height=HEIGHT] # Specify the hight of generated universe. (default to terminal height)
26
31
  [--dead-cell=CHAR] # Specify the dead-cell representation
27
- # Default:
28
- [--live-cell=CHAR] # Specify the dead-cell representation
32
+ # Default:
33
+ [--live-cell=CHAR] # Specify the live-cell representation
29
34
  # Default: █
30
35
  -d, [--delay=Milli-Seconds] # Specify the introduced delay between each generation
31
36
  # Default: 50
32
37
  ```
33
38
 
34
39
  ## Demo
35
- [![asciicast](https://asciinema.org/a/h7G2EUAXRfwlBVLZjQuXjoTnp.svg)](https://asciinema.org/a/h7G2EUAXRfwlBVLZjQuXjoTnp)
40
+ [![asciicast](https://asciinema.org/a/PsKzaWaNwUq3ZEfrFATqMw0yF.svg)](https://asciinema.org/a/PsKzaWaNwUq3ZEfrFATqMw0yF)
36
41
 
37
42
  ## Development
38
43
 
39
44
  - Clone the repo.
40
45
  - Navigate to the ruby CLI implementation `cd game-of-life/CLI/ruby`.
41
46
  - run `bundle install` to install dependencies.
42
- - run `bundle rake build` to build the gem/CLI into the `pkg` directory.
43
- - run `bundle rake install` to build and install gem/CLI into system gems.
47
+ - you can run `bundle rake build` to build the gem/CLI into the `pkg` directory.
48
+ - you can run `bundle rake install` to build and install gem/CLI into system gems.
49
+ - you can run `bundle exec ./bin/game-of-life` to run the code for development/testing purposes
44
50
 
45
51
  ## Linting
46
52
  Run `bundle exec rubocop`
47
53
 
48
54
  ## Testing
49
- Run `bundle exec rspec` or `bundle exec rake spec`
55
+ Run `bundle exec rspec`
50
56
 
51
57
  ## Documentation
52
58
  Run `bundle exec yard`
@@ -3,7 +3,6 @@
3
3
 
4
4
  require "game_of_life"
5
5
  require "thor"
6
- require "io/console"
7
6
 
8
7
  class CLI < Thor
9
8
  map %w[-v --version] => :version
@@ -20,30 +19,22 @@ class CLI < Thor
20
19
  class_option "input", aliases: "-i", type: :string, banner: :VALUE,
21
20
  desc: "Specify the path/URL for the file to use as an initial state. (used instead of seed)"
22
21
 
23
- class_option "width", aliases: "-w", type: :numeric, banner: :WIDTH,
22
+ class_option "width", type: :numeric, banner: :WIDTH,
24
23
  desc: "Specify the width of generated universe. (default to terminal width)"
25
24
 
26
- class_option "height", aliases: "-h", type: :numeric, banner: :HEIGHT,
25
+ class_option "height", type: :numeric, banner: :HEIGHT,
27
26
  desc: "Specify the hight of generated universe. (default to terminal height)"
28
27
 
29
28
  class_option "dead-cell", type: :string, banner: :CHAR, default: "\s",
30
29
  desc: "Specify the dead-cell representation"
31
30
 
32
31
  class_option "live-cell", type: :string, banner: :CHAR, default: "\u2588",
33
- desc: "Specify the dead-cell representation"
32
+ desc: "Specify the live-cell representation"
34
33
 
35
34
  class_option "delay", aliases: "-d", type: :numeric, banner: "Milli-Seconds", default: 50,
36
35
  desc: "Specify the introduced delay between each generation"
37
36
  def start
38
- options_with_console_defaults = {
39
- # Defaults the hight to less then the height of the terminal to allow banner info
40
- # 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],
43
- "seed" => rand(100_000),
44
- }.merge(options)
45
-
46
- universe = GameOfLife.generate(options_with_console_defaults)
37
+ universe = GameOfLife.generate(GameOfLife.parsed_options(options))
47
38
  GameOfLife.run(universe)
48
39
  end
49
40
 
@@ -10,17 +10,19 @@ Gem::Specification.new do |spec|
10
10
 
11
11
  spec.summary = "Game of Life CLI"
12
12
  spec.description = "Conway's game of life implementation as a CLI ruby gem."
13
- spec.homepage = "https://gitlab.com/a14m/game-of-life"
13
+ spec.homepage = "https://git.sr.ht/~a14m/game-of-life/tree/master/CLI/ruby/README.md"
14
14
  spec.license = "MIT"
15
- spec.required_ruby_version = ">= 2.4.0"
15
+ spec.required_ruby_version = ">= 2.5.0"
16
16
 
17
17
  spec.metadata["allowed_push_host"] = "https://rubygems.org"
18
18
  spec.metadata["homepage_uri"] = spec.homepage
19
- spec.metadata["source_code_uri"] = "https://gitlab.com/a14m/game-of-life/-/tree/master/CLI/ruby/"
19
+ spec.metadata["source_code_uri"] = "https://git.sr.ht/~a14m/game-of-life/tree/master/CLI/ruby"
20
+ spec.metadata["documentation_uri"] = "https://rubydoc.info/gems/terminal_game_of_life"
21
+ spec.metadata["changelog_uri"] = "https://git.sr.ht/~a14m/game-of-life/tree/master/CLI/ruby/CHANGELOG.md"
20
22
 
21
23
  spec.executables = ["game-of-life"]
22
24
  spec.require_paths = ["lib"]
23
- spec.files = Dir["README.md", "game_of_life.gemspec", "bin/game-of-life", "lib/**/*.rb"]
25
+ spec.files = Dir["README.md", "game_of_life.gemspec", "bin/game-of-life", "lib/**/*.rb", ".yardopts"]
24
26
 
25
27
  spec.add_dependency "thor", "~> 1.0"
26
28
 
@@ -29,6 +31,10 @@ Gem::Specification.new do |spec|
29
31
  spec.add_development_dependency "rake"
30
32
  spec.add_development_dependency "rspec"
31
33
  spec.add_development_dependency "rubocop"
34
+ spec.add_development_dependency "rubocop-rspec"
35
+ # Code Coverage dependencies
36
+ spec.add_development_dependency "codecov"
37
+ spec.add_development_dependency "simplecov"
32
38
  # Documentation dependencies
33
39
  spec.add_development_dependency "redcarpet"
34
40
  spec.add_development_dependency "yard"
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "io/console"
3
4
  require "game_of_life/version"
4
5
  require "game_of_life/plane"
5
6
  require "game_of_life/universe"
@@ -11,6 +12,50 @@ module GameOfLife
11
12
  class Error < StandardError; end
12
13
 
13
14
  class << self
15
+ # Validate and parse the CLI options and set defaults for the dynamic options.
16
+ # @param options [Hash] CLI options to generate initial conditions
17
+ # @option options [String | nil] "input" path to a local file or URL to open
18
+ # @option options [Numeric | nil] "seed" number to use if no "input" is provided
19
+ # @option options [Numeric] "width" of the universe
20
+ # @option options [Numeric] "height" of the universe
21
+ # @option options [Numeric] "delay" introduced after each cycle
22
+ # @option options [String] "live-cell" representation
23
+ # @option options [String] "dead-cell" representation
24
+ # @smell AbcSize, MethodLenght, Cyclomatic and Perceived Complexity needed for parsing and validating options
25
+ # rubocop:disable Metrics/AbcSize
26
+ # rubocop:disable Metrics/MethodLength
27
+ # rubocop:disable Metrics/CyclomaticComplexity
28
+ # rubocop:disable Metrics/PerceivedComplexity
29
+ # return [Hash] of the parsed options with dynamic defaults based on window size and kernel random
30
+ def parsed_options(options)
31
+ max_height = IO.console.winsize[0] - 2
32
+ max_width = IO.console.winsize[1]
33
+
34
+ if options["delay"].to_i.negative?
35
+ fail GameOfLife::Error, "Invalid --delay value. must be positive value (sadly this is how time works -so far-)"
36
+ end
37
+ if (options["width"]&.< 1) || (options["width"]&.> max_width)
38
+ fail GameOfLife::Error, "Invalid --width value. " \
39
+ "must be between 1 and #{max_width} (current terminal width)"
40
+ end
41
+ if (options["height"]&.< 1) || (options["height"]&.> max_height)
42
+ fail GameOfLife::Error, "Invalid --height value. " \
43
+ "must be between 1 and #{max_height} (current terminal height)"
44
+ end
45
+
46
+ {
47
+ # Defaults the hight to less then the height of the terminal to allow banner info
48
+ # and an extra emtpy line to avoid triggering terminal scroll while flushing
49
+ "height" => max_height,
50
+ "width" => max_width,
51
+ "seed" => rand(100_000),
52
+ }.merge(options)
53
+ end
54
+ # rubocop:enable Metrics/AbcSize
55
+ # rubocop:enable Metrics/MethodLength
56
+ # rubocop:enable Metrics/CyclomaticComplexity
57
+ # rubocop:enable Metrics/PerceivedComplexity
58
+
14
59
  # Generate a universe based on the options (from an input file/URI) or using a random/passed seed
15
60
  # This method also sets the Classes/Modules constants for the run defining the options for
16
61
  # Cell (Dead/Live) state representations, and the delay introduced after rendering each universe/generation
@@ -49,9 +94,9 @@ module GameOfLife
49
94
  end
50
95
  end
51
96
 
52
- # Private method to do the terminal screen/scrollback buffer cleaning
97
+ # render method to do the terminal screen/scrollback buffer cleaning
53
98
  # and rendering of the current universe/banner info
54
- # based on the delay configured by the user
99
+ # based on the delay/cell configured by the user
55
100
  def render(universe)
56
101
  puts "\33c\e[3J" # Clears the screen and scrollback buffer.
57
102
  puts universe.to_s
@@ -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 = "1.0.2"
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: 1.0.2
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-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -94,6 +94,48 @@ dependencies:
94
94
  - - ">="
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rubocop-rspec
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: codecov
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: simplecov
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
97
139
  - !ruby/object:Gem::Dependency
98
140
  name: redcarpet
99
141
  requirement: !ruby/object:Gem::Requirement
@@ -130,6 +172,7 @@ executables:
130
172
  extensions: []
131
173
  extra_rdoc_files: []
132
174
  files:
175
+ - ".yardopts"
133
176
  - README.md
134
177
  - bin/game-of-life
135
178
  - game_of_life.gemspec
@@ -140,13 +183,15 @@ files:
140
183
  - lib/game_of_life/plane.rb
141
184
  - lib/game_of_life/universe.rb
142
185
  - lib/game_of_life/version.rb
143
- homepage: https://gitlab.com/a14m/game-of-life
186
+ homepage: https://git.sr.ht/~a14m/game-of-life/tree/master/CLI/ruby/README.md
144
187
  licenses:
145
188
  - MIT
146
189
  metadata:
147
190
  allowed_push_host: https://rubygems.org
148
- homepage_uri: https://gitlab.com/a14m/game-of-life
149
- source_code_uri: https://gitlab.com/a14m/game-of-life/-/tree/master/CLI/ruby/
191
+ homepage_uri: https://git.sr.ht/~a14m/game-of-life/tree/master/CLI/ruby/README.md
192
+ source_code_uri: https://git.sr.ht/~a14m/game-of-life/tree/master/CLI/ruby
193
+ documentation_uri: https://rubydoc.info/gems/terminal_game_of_life
194
+ changelog_uri: https://git.sr.ht/~a14m/game-of-life/tree/master/CLI/ruby/CHANGELOG.md
150
195
  post_install_message:
151
196
  rdoc_options: []
152
197
  require_paths:
@@ -155,14 +200,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
155
200
  requirements:
156
201
  - - ">="
157
202
  - !ruby/object:Gem::Version
158
- version: 2.4.0
203
+ version: 2.5.0
159
204
  required_rubygems_version: !ruby/object:Gem::Requirement
160
205
  requirements:
161
206
  - - ">="
162
207
  - !ruby/object:Gem::Version
163
208
  version: '0'
164
209
  requirements: []
165
- rubygems_version: 3.1.4
210
+ rubygems_version: 3.1.2
166
211
  signing_key:
167
212
  specification_version: 4
168
213
  summary: Game of Life CLI