terminal_game_of_life 0.1.0 → 1.0.0

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: e653045a66a53af6c2b4c5b58f0330ec18d8125e7ce70b2b9c8803e8d7f67b2a
4
- data.tar.gz: 75ffbbd48c0b9cb883a2cd1aab1f0b7e7606856223a18e88c4dd0a9916a5afc6
3
+ metadata.gz: b949e4ff807eb374946cc0c2647f9aa5cf3f79b2427cab818b492ecdbb9a3b24
4
+ data.tar.gz: b0e82c40b3c53972f52382728281cc2b83219835ffe1ad65c89f9e446beadf02
5
5
  SHA512:
6
- metadata.gz: a4ae52be24a7bbadf45997c129b1e4b407f3b6f47ecc1cb6c6c63c002d57add5f6ac231581ef024458f366189555f73fad4995a279d4b9b92c0fc5e2d5721141
7
- data.tar.gz: b3f717f56369f87ed173a57ff9f79364e3400af441c543c58a64a244fa8b9a328304fd00a9de8d82593fbb6aeb0795f81d15ceb67b29a698d1b0ebe0133ff1ea
6
+ metadata.gz: 96e1fd3c329a3d775a7d332351303c53a2767857ccef421ad372ec8abbebae7bd4f7d07ae2ad23210cd360d889d21fa7ccafc6c9dc08388a6ffb967dd44afa52
7
+ data.tar.gz: 8674f279918813b3cf8c4ee1ed9a2d257c48755a3c06e687a2f5f17cdf6707e85509277042e3176d6eaa5e7c69ae143bc5d4e996572a55fa73900c2bc8e9ac1d
@@ -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,26 +9,32 @@ $ 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 +`
13
+
12
14
  Check `game-of-life --help` for usage info.
13
15
 
14
16
  ```
17
+ Commands:
15
18
  game-of-life --version, -v # Prints the Game of Life version information
16
19
  game-of-life help [COMMAND] # Describe available commands or one specific command
17
- game-of-life start [OPTIONS] # Start the Game of Life simulations (default command)
20
+ game-of-life start [OPTIONS] # Start the Game of Life simulations
18
21
 
19
22
  Options:
20
23
  -s, [--seed=SEED] # Specify the seed number to use as an initial state (default to random).
21
24
  -i, [--input=VALUE] # Specify the path/URL for the file to use as an initial state. (used instead of seed)
22
- -w, [--width=WIDTH] # Specify the width of generated universe. (default to terminal width)
23
- -h, [--height=HEIGHT] # Specify the hight of generated universe. (default to terminal height)
25
+ [--width=WIDTH] # Specify the width of generated universe. (default to terminal width)
26
+ [--height=HEIGHT] # Specify the hight of generated universe. (default to terminal height)
24
27
  [--dead-cell=CHAR] # Specify the dead-cell representation
25
- # Default:
28
+ # Default:
26
29
  [--live-cell=CHAR] # Specify the dead-cell representation
27
30
  # Default: █
28
31
  -d, [--delay=Milli-Seconds] # Specify the introduced delay between each generation
29
32
  # Default: 50
30
33
  ```
31
34
 
35
+ ## Demo
36
+ [![asciicast](https://asciinema.org/a/PsKzaWaNwUq3ZEfrFATqMw0yF.svg)](https://asciinema.org/a/PsKzaWaNwUq3ZEfrFATqMw0yF)
37
+
32
38
  ## Development
33
39
 
34
40
  - Clone the repo.
@@ -46,7 +52,16 @@ Run `bundle exec rspec` or `bundle exec rake spec`
46
52
  ## Documentation
47
53
  Run `bundle exec yard`
48
54
 
55
+ ## Release
56
+ - Update the [version](./lib/game_of_live/version.rb) number
57
+ - Run `bundle install` and commit changes
58
+ - Update the [CHANGELOG](./CHANGELOG.md)
59
+ - Create a git(lab) tag `ruby/v#{version_number}` ex: `ruby/v0.1.1-pre`
60
+
61
+ The tag will automatically trigger the release workflow after successful build/test
62
+ and release the changes to [rubygems.org](https://rubygems.org/gems/terminal_game_of_life)
63
+
49
64
  ## Extra information
50
- ### [Contributing](../../CONTRIBUTING.md)
51
- ### [License](../../LICENSE.md)
52
- ### [Code of Conduct](../../CODE_OF_CONDUCT.md)
65
+ ### [Contributing](https://gitlab.com/a14m/game-of-life/-/blob/master/CONTRIBUTING.md)
66
+ ### [License](https://gitlab.com/a14m/game-of-life/-/blob/master/LICENSE.md)
67
+ ### [Code of Conduct](https://gitlab.com/a14m/game-of-life/-/blob/master/CODE_OF_CONDUCT.md)
@@ -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
@@ -60,6 +73,7 @@ begin
60
73
  CLI.start(ARGV)
61
74
  rescue SystemExit, Interrupt
62
75
  "Do nothing when user exits with Interrupt (CMD/Ctrl + C)"
63
- rescue NotImplementedError
64
- puts "Seed option isn't implemented yet, please use --input option"
76
+ rescue NotImplementedError, GameOfLife::Error => e
77
+ puts e.message
78
+ exit(-1)
65
79
  end
@@ -12,15 +12,17 @@ Gem::Specification.new do |spec|
12
12
  spec.description = "Conway's game of life implementation as a CLI ruby gem."
13
13
  spec.homepage = "https://gitlab.com/a14m/game-of-life"
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
19
  spec.metadata["source_code_uri"] = "https://gitlab.com/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://gitlab.com/a14m/game-of-life/-/blob/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
 
@@ -7,6 +7,9 @@ require "game_of_life/cell"
7
7
 
8
8
  # Game of Life module entry point
9
9
  module GameOfLife
10
+ # Base Error class
11
+ class Error < StandardError; end
12
+
10
13
  class << self
11
14
  # Generate a universe based on the options (from an input file/URI) or using a random/passed seed
12
15
  # This method also sets the Classes/Modules constants for the run defining the options for
@@ -46,9 +49,9 @@ module GameOfLife
46
49
  end
47
50
  end
48
51
 
49
- # Private method to do the terminal screen/scrollback buffer cleaning
52
+ # render method to do the terminal screen/scrollback buffer cleaning
50
53
  # and rendering of the current universe/banner info
51
- # based on the delay configured by the user
54
+ # based on the delay/cell configured by the user
52
55
  def render(universe)
53
56
  puts "\33c\e[3J" # Clears the screen and scrollback buffer.
54
57
  puts universe.to_s
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "open-uri"
4
+ require "socket"
4
5
 
5
6
  module GameOfLife
6
7
  module Generators
@@ -13,28 +14,46 @@ module GameOfLife
13
14
  # @option options [Numeric] "width" (floored) and used as the width of the universe
14
15
  # @option options [Numeric] "height" (floored) and used as the height of the universe
15
16
  # @return [GameOfLife::Universe] populated with the parsed input
17
+ # @see ::GameOfLife::Generators::Input.generate_input_data
16
18
  # @see ::GameOfLife::Generators::Input.populate
17
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:)
18
32
  # By design, it's intentional that we use URI.open to to allow seamless file and url access
19
- raw = URI.open(options["input"]).read.split("\n") # rubocop:disable Security/Open
33
+ raw = URI.open(uri).read.split("\n") # rubocop:disable Security/Open
20
34
 
21
35
  # Parse the file and convert it to a boolean 2D array
22
36
  # where any alpha numeric character is considered a living cell (true)
23
37
  raw.map! { |row| row.chars.map { |char| char.match?(/[[:alnum:]]/) } }
24
- populate(parsed_input: raw, width: options["width"].to_i, height: options["height"].to_i)
38
+ rescue ::OpenURI::HTTPError, ::SocketError
39
+ raise GameOfLife::Error, "URL isn't avaialable, please check it again and make sure the URL is correct"
40
+ rescue ::Errno::ENOENT
41
+ raise GameOfLife::Error,
42
+ "File isn't avaialable, please check it again and make sure the path to the input file is correct"
25
43
  end
26
44
 
27
45
  # Populate the {GameOfLife::Universe} with reference to the parsed input 2D array (True/False)
28
- # @param parsed_input [Array<Array<True|False>>]
46
+ # @param input [Array<Array<True|False>>]
29
47
  # @param width [Integer] width of the generated cyclic universe
30
48
  # @param height [Integer] height of the generated cyclic universe
31
49
  # @return [GameOfLife::Universe] populated with the parsed input
32
- private def populate(parsed_input:, width:, height:)
50
+ # @see ::GameOfLife::Generators::Input.generate_input_data
51
+ private def populate(input:, width:, height:)
33
52
  # Generate a universe based on the parsed raw file and fill it with the cells
34
53
  universe = GameOfLife::Universe.new(height: height, width: width)
35
54
  (0...height).each do |i|
36
55
  (0...width).each do |j|
37
- 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) }
38
57
  universe[i][j] = ::GameOfLife::Cell.new(cell)
39
58
  end
40
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
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.0"
4
+ # Game of life version number
5
+ VERSION = "1.0.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.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - a14m
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-12-04 00:00:00.000000000 Z
11
+ date: 2020-12-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -130,6 +130,7 @@ executables:
130
130
  extensions: []
131
131
  extra_rdoc_files: []
132
132
  files:
133
+ - ".yardopts"
133
134
  - README.md
134
135
  - bin/game-of-life
135
136
  - game_of_life.gemspec
@@ -147,7 +148,9 @@ metadata:
147
148
  allowed_push_host: https://rubygems.org
148
149
  homepage_uri: https://gitlab.com/a14m/game-of-life
149
150
  source_code_uri: https://gitlab.com/a14m/game-of-life/-/tree/master/CLI/ruby/
150
- post_install_message:
151
+ documentation_uri: https://rubydoc.info/gems/terminal_game_of_life
152
+ changelog_uri: https://gitlab.com/a14m/game-of-life/-/blob/master/CLI/ruby/CHANGELOG.md
153
+ post_install_message:
151
154
  rdoc_options: []
152
155
  require_paths:
153
156
  - lib
@@ -155,15 +158,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
155
158
  requirements:
156
159
  - - ">="
157
160
  - !ruby/object:Gem::Version
158
- version: 2.4.0
161
+ version: 2.5.0
159
162
  required_rubygems_version: !ruby/object:Gem::Requirement
160
163
  requirements:
161
164
  - - ">="
162
165
  - !ruby/object:Gem::Version
163
166
  version: '0'
164
167
  requirements: []
165
- rubygems_version: 3.1.4
166
- signing_key:
168
+ rubygems_version: 3.2.0
169
+ signing_key:
167
170
  specification_version: 4
168
171
  summary: Game of Life CLI
169
172
  test_files: []