nlife 0.0.1 → 0.1.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
  SHA1:
3
- metadata.gz: bb3319cf910fe04786b885de262b96e56f69e56d
4
- data.tar.gz: e4cc2675fc16bcfb6e5b33d7cd48f6acc33f99ac
3
+ metadata.gz: 9c6e3e08c39d85b040029e683f60d34729949db3
4
+ data.tar.gz: bd91c74cf8e82a5eee043f09fd978e88f4ac091f
5
5
  SHA512:
6
- metadata.gz: 49f7c216349321a856e2f9900bc00d9959ea5458d22cd2ffab0e8bf9ec3c69f7eecfa071268bc274549e93e7a18bd697085d0e03c1f0cabc2e2de0acb64f37d5
7
- data.tar.gz: 40705b57e94c5a6b14c1ba640b60a057d7d08ac995eddf97a2e16c32d93ddfa33861da2d7c03e18cf2fb9ccba0227178964dc84b9bda84e194a010ac18e74806
6
+ metadata.gz: 9dc92b0822b1364a375792da53deb4b51a9351aa7b945d1b4f1f96d30973ce62afb1b29ec8d6508134a49030431b5971ff2adbcd671534695d8426e3eb18f1b3
7
+ data.tar.gz: aaa56266f27f2cd2cfe44df5961fb8925291653c711fde9ea5a95cc417537bd561df3695446e77187eab0b07aba44e0c8bd679e6c542193ed2cdcc7f610dc8f7
@@ -0,0 +1,13 @@
1
+ # Change log
2
+
3
+ ##0.1.0
4
+ - tests added
5
+ - helper rule_from_golly modified
6
+ - game surround accessor added
7
+
8
+ ## 0.0.1
9
+ - first working version
10
+ - pause/play/seed/quit functionality
11
+
12
+ ## 0.0.0
13
+ - empty gem
data/TODO.md CHANGED
@@ -1,14 +1,15 @@
1
1
  TODO
2
2
  ====
3
3
 
4
- ### Tests
5
- - add rspec tests
6
-
7
4
  ### Game features
8
5
  - resizing grid
9
6
  - load/save file
10
7
  - set cell state
11
8
  - yank/paste/clear region
9
+ - set speed
10
+ - status line showing:
11
+ - iteration
12
+ - speed
12
13
 
13
14
  ### Render features
14
15
  - make render a separate class
data/bin/nlife CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require_relative "../lib/nlife"
3
+ require_relative '../lib/nlife'
4
4
 
5
5
  NLife::UI.new.start
@@ -1 +1,4 @@
1
- require_relative "nlife/ui"
1
+ require_relative 'nlife/game'
2
+ require_relative 'nlife/helper'
3
+ require_relative 'nlife/ui'
4
+ require_relative 'nlife/version'
@@ -1,30 +1,31 @@
1
- require "narray"
2
- require "numru/fftw3"
1
+ require 'narray'
2
+ require 'numru/fftw3'
3
3
  include NumRu
4
4
 
5
- require_relative "./helper.rb"
5
+ require_relative 'helper.rb'
6
6
 
7
7
  module NLife
8
+ # encapsulates game logic
8
9
  class Game
9
10
  attr_reader :state
10
- attr_accessor :density
11
+ attr_reader :density
11
12
 
12
13
  def initialize(rows, cols, surround = nil, rule = nil)
13
- @state = NArray.int(cols, rows)
14
- @density = NArray.int(cols, rows)
14
+ @state = NArray.float(cols, rows)
15
+ @density = NArray.float(cols, rows)
15
16
  surround ||= default_surround(rows, cols)
16
- @t_surround = FFTW3.fft(surround.to_f, -1) / surround.size
17
+ @fft_surround = FFTW3.fft(surround.to_f, -1) / surround.size
17
18
  @rule = rule || default_rule
18
19
  end
19
20
 
20
21
  def default_surround(rows, cols)
21
- return Helper.surround_from_block(rows, cols) do |row, col|
22
- [row.abs, col.abs].max == 1
22
+ Helper.surround_from_block(rows, cols) do |row, col|
23
+ [row.abs, col.abs].max == 1 ? 1 : 0
23
24
  end
24
25
  end
25
26
 
26
27
  def default_rule
27
- return Helper.rule_from_golly("B3/S23")
28
+ Helper.rule_from_golly('B3/S23')
28
29
  end
29
30
 
30
31
  def seed
@@ -35,14 +36,16 @@ module NLife
35
36
  end
36
37
  end
37
38
 
38
- def step
39
- calc_density
40
- calc_next_state
39
+ def step(count = 1)
40
+ count.times do
41
+ calc_density
42
+ calc_next_state
43
+ end
41
44
  end
42
45
 
43
46
  def calc_density
44
47
  t_state = FFTW3.fft(@state, -1)
45
- t_density = t_state * @t_surround
48
+ t_density = t_state * @fft_surround
46
49
  @density = FFTW3.fft(t_density, 1).real.round
47
50
  end
48
51
 
@@ -56,11 +59,20 @@ module NLife
56
59
 
57
60
  # use for debug only
58
61
  def print
59
- puts "#" * (@state.shape[0] + 2)
62
+ puts '#' * (@state.shape[0] + 2)
60
63
  @state.shape[1].times do |i|
61
- puts "#" + @state[true, i].each.map { |e| e > 0 ? "*" : " " }.join + "#"
64
+ print_row(i)
62
65
  end
63
- puts "#" * (@state.shape[0] + 2)
66
+ puts '#' * (@state.shape[0] + 2)
67
+ end
68
+
69
+ # use for debug only
70
+ def print_row(row)
71
+ puts '#' + @state[true, row].each.map { |e| e > 0 ? '*' : ' ' }.join + '#'
72
+ end
73
+
74
+ def surround
75
+ FFTW3.fft(@fft_surround, 1).real
64
76
  end
65
77
 
66
78
  def rows
@@ -70,5 +82,5 @@ module NLife
70
82
  def cols
71
83
  @state.shape[0]
72
84
  end
73
- end
74
- end
85
+ end # class Game
86
+ end # module NLife
@@ -1,34 +1,47 @@
1
- require "narray"
1
+ require 'narray'
2
2
 
3
3
  module NLife
4
+ # miscellaneous functions for state/surround gereration
4
5
  module Helper
5
- def self.surround_from_block(rows, cols)
6
- result = NArray.int(cols, rows)
6
+ module_function
7
+
8
+ # surround is calculated for rectangle centered at (0, 0)
9
+ def surround_from_block(rows, cols)
10
+ result = NArray.float(cols, rows)
7
11
  result.shape[1].times do |row|
8
12
  result.shape[0].times do |col|
9
13
  result[col, row] = yield((row + rows / 2) % rows - rows / 2,
10
- (col + cols / 2) % cols - cols / 2) ? 1 : 0
14
+ (col + cols / 2) % cols - cols / 2)
11
15
  end
12
16
  end
13
17
  result
14
18
  end
15
19
 
16
- # constructs rules from two arrays, birth/survival densities
17
- def self.rule_from_arrays(birth, survival)
20
+ # constructs rules from two arrays: birth, survival densities
21
+ def rule_from_arrays(birth, survival)
22
+ unless birth.size == survival.size
23
+ fail ArgumentError 'params sizes not match'
24
+ end
25
+ max_density = birth.size
18
26
  proc do |state, density|
19
- birth[density.round] || state > 0 && survival[density.round] ? 1 : 0
27
+ fail ArgumentError 'density too high' if density > max_density
28
+ result = (state == 0 ? birth : survival)[density.round]
29
+ result ? 1.0 : 0.0
20
30
  end
21
31
  end
22
32
 
23
- # constructs rules from string, describing birth/survival densities
24
- def self.rule_from_golly(string)
25
- match = /^B([0-8]*)\/S([0-8]*)$/.match(string)
26
- Raise ArgumentError "Wrong input format" unless match
27
- birth = Array.new(9, false)
33
+ def golly_to_array(string)
34
+ match = %r{^B([0-8]*)\/S([0-8]*)$}.match(string)
35
+ raise ArgumentError 'wrong input format' unless match
36
+ birth = (0..8).map { |i| match[1].include? i.to_s }
28
37
  survival = Array.new(9, false)
29
- match[1].split(//).map(&:to_i).each { |i| birth[i] = true }
30
38
  match[2].split(//).map(&:to_i).each { |i| survival[i] = true }
31
- return rule_from_arrays(birth, survival)
39
+ return birth, survival
40
+ end
41
+
42
+ # constructs rules from string, describing birth/survival densities
43
+ def rule_from_golly(string)
44
+ rule_from_arrays(*golly_to_array(string))
32
45
  end
33
46
  end
34
47
  end
@@ -1,10 +1,10 @@
1
1
  require 'curses'
2
2
 
3
- require_relative "./game.rb"
3
+ require_relative 'game.rb'
4
4
 
5
5
  module NLife
6
6
  class UI
7
- RENDER_DEAD = " "
7
+ RENDER_DEAD = ' '
8
8
  RENDER_LIVE = "\u2588"
9
9
 
10
10
  def initialize
@@ -20,7 +20,7 @@ module NLife
20
20
  @window_lines = Curses.lines
21
21
  @window_cols = Curses.cols
22
22
  @window = Curses::Window.new(@window_lines, @window_cols, 0, 0)
23
- @window.box("|", "-")
23
+ @window.box('|', '-')
24
24
  @window.timeout = 0
25
25
  end
26
26
 
@@ -45,22 +45,20 @@ module NLife
45
45
 
46
46
  def dispatch_key(key)
47
47
  case key
48
- when "p" then @pause = !@pause
49
- when "s" then @life.seed
50
- when "q" then return false
48
+ when 'p' then @pause = !@pause
49
+ when 's' then @life.seed
50
+ when 'q' then return false
51
51
  end
52
- return true
52
+ true
53
53
  end
54
54
 
55
55
  def step
56
- unless @pause
57
- @life.step
58
- end
56
+ @life.step unless @pause
59
57
  end
60
58
 
61
59
  def render
62
60
  @life.rows.times do |i|
63
- string = ""
61
+ string = ''
64
62
  @life.cols.times do |j|
65
63
  string += @life.state[j, i] > 0 ? RENDER_LIVE : RENDER_DEAD
66
64
  end
@@ -1,3 +1,3 @@
1
1
  module NLife
2
- VERSION = '0.0.1'
2
+ VERSION = '0.1.0'
3
3
  end
@@ -4,17 +4,19 @@ Gem::Specification.new do |s|
4
4
  s.name = 'nlife'
5
5
  s.version = NLife::VERSION
6
6
  s.licenses = ['LGPL-3.0']
7
- s.authors = ["Oleg Zubchenko"]
7
+ s.authors = ['Oleg Zubchenko']
8
8
  s.email = 'RedGreenBlueDiamond@gmail.com'
9
- s.homepage = 'http://rubygems.org/gems/nlife'
10
- s.summary = "Generalized Game of Life"
11
- s.description = "Customizable Game of Life with ncurses viewer"
12
- s.files = Dir["lib/**/*.rb"] + Dir["bin/*"] + Dir["*.md"] +
13
- Dir["*.gemspec"]
9
+ s.homepage = 'https://github.com/rgbd/ruby-nlife'
10
+ s.summary = 'Generalized Game of Life'
11
+ s.description = 'Game of Life with customizable rules on ncurses viewer'
12
+ s.files = Dir['**/*.rb'] + Dir['bin/*'] + Dir['*.md'] +
13
+ Dir['*.gemspec']
14
14
  s.executables << 'nlife'
15
15
 
16
16
  s.add_runtime_dependency 'curses', '~> 1.0', '>= 1.0.1'
17
- s.add_runtime_dependency 'narray', '~>0.6.1'
18
- s.add_runtime_dependency 'numru-misc', '~>0.1.2'
19
- s.add_runtime_dependency 'ruby-fftw3', '~>0.4.2'
17
+ s.add_runtime_dependency 'narray', '~> 0.6.1', '>= 0.6.1.1'
18
+ s.add_runtime_dependency 'numru-misc', '~> 0.1.2'
19
+ s.add_runtime_dependency 'ruby-fftw3', '~> 0.4.2'
20
+
21
+ s.add_development_dependency 'rspec', '~> 3.2', '>= 3.2.0'
20
22
  end
@@ -0,0 +1,93 @@
1
+ require 'narray'
2
+
3
+ describe NLife::Game do
4
+ before do
5
+ @rows = 8
6
+ @cols = 8
7
+ @game = described_class.new(@rows, @cols)
8
+ end
9
+
10
+ context 'classic defauts' do
11
+ before do
12
+ # classic surround
13
+ @surround = NArray.int(@rows, @cols)
14
+ @surround[(-1..1).to_a, (-1..1).to_a] = 1
15
+ @surround[0, 0] = 0
16
+ @rule_length = 8
17
+ # ruler------012345678
18
+ @birth = '...X.....'.split(//).map { |e| e == 'X' }
19
+ @survival = '..XX.....'.split(//).map { |e| e == 'X' }
20
+ @rule = proc do |s, d|
21
+ result = s == 0 ? @birth[d] : @survival[d]
22
+ result ? 1 : 0
23
+ end
24
+ end
25
+
26
+ it 'should have classic default surround' do
27
+ expect(@game.surround.round).to eq(@surround)
28
+ end
29
+
30
+ it 'should have classic default rules' do
31
+ game_rule = @game.instance_variable_get(:@rule)
32
+ [0, 1].each do |s|
33
+ 0.upto(8) do |d|
34
+ expect(@rule.call(s, d)).to eq(game_rule.call(s, d))
35
+ end
36
+ end
37
+ end
38
+ end # context 'classic defauts'
39
+
40
+ context 'stepping' do
41
+ before do
42
+ # glider
43
+ @state = NArray.to_na([
44
+ [0, 0, 0, 0, 0, 0, 0, 0],
45
+ [0, 0, 0, 0, 0, 0, 0, 0],
46
+ [0, 0, 0, 1, 0, 0, 0, 0],
47
+ [0, 0, 0, 0, 1, 0, 0, 0],
48
+ [0, 0, 1, 1, 1, 0, 0, 0],
49
+ [0, 0, 0, 0, 0, 0, 0, 0],
50
+ [0, 0, 0, 0, 0, 0, 0, 0],
51
+ [0, 0, 0, 0, 0, 0, 0, 0]
52
+ ]).to_f
53
+ @density = NArray.to_na([
54
+ [0, 0, 0, 0, 0, 0, 0, 0],
55
+ [0, 0, 1, 1, 1, 0, 0, 0],
56
+ [0, 0, 1, 1, 2, 1, 0, 0],
57
+ [0, 1, 3, 5, 3, 2, 0, 0],
58
+ [0, 1, 1, 3, 2, 2, 0, 0],
59
+ [0, 1, 2, 3, 2, 1, 0, 0],
60
+ [0, 0, 0, 0, 0, 0, 0, 0],
61
+ [0, 0, 0, 0, 0, 0, 0, 0]
62
+ ]).to_f
63
+ @next_state = NArray.to_na([
64
+ [0, 0, 0, 0, 0, 0, 0, 0],
65
+ [0, 0, 0, 0, 0, 0, 0, 0],
66
+ [0, 0, 0, 0, 0, 0, 0, 0],
67
+ [0, 0, 1, 0, 1, 0, 0, 0],
68
+ [0, 0, 0, 1, 1, 0, 0, 0],
69
+ [0, 0, 0, 1, 0, 0, 0, 0],
70
+ [0, 0, 0, 0, 0, 0, 0, 0],
71
+ [0, 0, 0, 0, 0, 0, 0, 0]
72
+ ]).to_f
73
+ @game.instance_variable_set(:@state, @state)
74
+ @game.instance_variable_set(:@density, NArray.float(@rows, @cols))
75
+ end # before
76
+
77
+ it '.calc_density' do
78
+ @game.send(:calc_density)
79
+ expect(@game.instance_variable_get(:@density)).to eq(@density)
80
+ end
81
+
82
+ it '.calc_nest_state' do
83
+ @game.instance_variable_set(:@density, @density)
84
+ @game.send(:calc_next_state)
85
+ expect(@game.instance_variable_get(:@state)).to eq(@next_state)
86
+ end
87
+
88
+ it '.step' do
89
+ @game.step
90
+ expect(@game.instance_variable_get(:@state)).to eq(@next_state)
91
+ end
92
+ end # context 'stepping'
93
+ end # describe NLife::Game
@@ -0,0 +1,51 @@
1
+ require 'narray'
2
+
3
+ describe NLife::Helper do
4
+ before do
5
+ @rows = 8
6
+ @cols = 8
7
+ end
8
+
9
+ context 'surround generation' do
10
+ before do
11
+ # classic surround
12
+ @surround = NArray.int(@rows, @cols)
13
+ @surround[(-1..1).to_a, (-1..1).to_a] = 1
14
+ @surround[0, 0] = 0
15
+ end
16
+
17
+ it '.surround_from_block' do
18
+ shape = @surround.shape.reverse
19
+ got_surround = described_class.surround_from_block(*shape) do |row, col|
20
+ [row, col].map(&:abs).max == 1 ? 1 : 0
21
+ end
22
+ expect(got_surround).to eq(@surround)
23
+ end
24
+ end
25
+
26
+ context 'rule generation' do
27
+ before do
28
+ @rule_length = 8
29
+ # ruler------012345678
30
+ @birth = '...X..X..'.split(//).map { |e| e == 'X' }
31
+ @survival = '..XX.....'.split(//).map { |e| e == 'X' }
32
+ @golly = 'B36/S23'
33
+ end
34
+
35
+ it '.golly_to_array' do
36
+ expect(described_class.golly_to_array(@golly)).to eq([@birth, @survival])
37
+ end
38
+
39
+ it '.rule_from_arrays' do
40
+ rule = described_class.rule_from_arrays(@birth, @survival)
41
+ 0.upto(@rule_length) do |i|
42
+ expect(rule.call(0, i) > 0).to eq(@birth[i])
43
+ expect(rule.call(1, i) > 0).to eq(@survival[i])
44
+ end
45
+ end
46
+
47
+ it '.rule_from_golly' do
48
+ expect(described_class).to respond_to(:rule_from_golly)
49
+ end
50
+ end
51
+ end
File without changes
@@ -0,0 +1,3 @@
1
+ describe NLife do
2
+ it { should be_const_defined(:VERSION) }
3
+ end
@@ -0,0 +1,11 @@
1
+ require_relative '../lib/nlife.rb'
2
+
3
+ RSpec.configure do |config|
4
+ config.expect_with :rspec do |expectations|
5
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
6
+ end
7
+
8
+ config.mock_with :rspec do |mocks|
9
+ mocks.verify_partial_doubles = true
10
+ end
11
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nlife
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Oleg Zubchenko
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-05-15 00:00:00.000000000 Z
11
+ date: 2015-05-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: curses
@@ -37,6 +37,9 @@ dependencies:
37
37
  - - "~>"
38
38
  - !ruby/object:Gem::Version
39
39
  version: 0.6.1
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: 0.6.1.1
40
43
  type: :runtime
41
44
  prerelease: false
42
45
  version_requirements: !ruby/object:Gem::Requirement
@@ -44,6 +47,9 @@ dependencies:
44
47
  - - "~>"
45
48
  - !ruby/object:Gem::Version
46
49
  version: 0.6.1
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: 0.6.1.1
47
53
  - !ruby/object:Gem::Dependency
48
54
  name: numru-misc
49
55
  requirement: !ruby/object:Gem::Requirement
@@ -72,13 +78,34 @@ dependencies:
72
78
  - - "~>"
73
79
  - !ruby/object:Gem::Version
74
80
  version: 0.4.2
75
- description: Customizable Game of Life with ncurses viewer
81
+ - !ruby/object:Gem::Dependency
82
+ name: rspec
83
+ requirement: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - "~>"
86
+ - !ruby/object:Gem::Version
87
+ version: '3.2'
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: 3.2.0
91
+ type: :development
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - "~>"
96
+ - !ruby/object:Gem::Version
97
+ version: '3.2'
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ version: 3.2.0
101
+ description: Game of Life with customizable rules on ncurses viewer
76
102
  email: RedGreenBlueDiamond@gmail.com
77
103
  executables:
78
104
  - nlife
79
105
  extensions: []
80
106
  extra_rdoc_files: []
81
107
  files:
108
+ - CHANGELOG.md
82
109
  - README.md
83
110
  - TODO.md
84
111
  - bin/nlife
@@ -88,7 +115,12 @@ files:
88
115
  - lib/nlife/ui.rb
89
116
  - lib/nlife/version.rb
90
117
  - nlife.gemspec
91
- homepage: http://rubygems.org/gems/nlife
118
+ - spec/nlife/game_spec.rb
119
+ - spec/nlife/helper_spec.rb
120
+ - spec/nlife/ui_spec.rb
121
+ - spec/nlife/version_spec.rb
122
+ - spec/spec_helper.rb
123
+ homepage: https://github.com/rgbd/ruby-nlife
92
124
  licenses:
93
125
  - LGPL-3.0
94
126
  metadata: {}