terminal_game_of_life 0.1.2 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
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