conway 0.2.0 → 0.3.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.
- data/README.md +8 -5
- data/bin/conway +8 -1
- data/lib/conway.rb +2 -1
- data/lib/conway/cell_location_lookup.rb +2 -2
- data/lib/conway/cell_space.rb +9 -12
- data/lib/conway/driver/ascii.rb +8 -7
- data/lib/conway/generation.rb +7 -7
- data/lib/conway/memoized_cells.rb +14 -0
- data/lib/conway/point.rb +5 -2
- data/lib/conway/potential_cell_collection.rb +11 -24
- data/lib/conway/rules.rb +27 -0
- data/lib/conway/rules/default.rb +11 -0
- data/lib/conway/rules/live_count.rb +11 -0
- data/lib/conway/rules/over_population.rb +22 -0
- data/lib/conway/rules/reproduction.rb +21 -0
- data/lib/conway/rules/stability.rb +21 -0
- data/lib/conway/rules/under_population.rb +21 -0
- data/spec/conway/cell_location_lookup_spec.rb +9 -0
- data/spec/conway/cell_space_spec.rb +31 -5
- data/spec/conway/memoized_cells_spec.rb +25 -0
- data/spec/conway/point_spec.rb +13 -0
- data/spec/conway/potential_cell_collection_spec.rb +13 -47
- data/spec/conway/rules/default_spec.rb +13 -0
- data/spec/conway/rules/over_population_spec.rb +63 -0
- data/spec/conway/rules/reproduction_spec.rb +55 -0
- data/spec/conway/rules/stability_spec.rb +63 -0
- data/spec/conway/rules/under_population_spec.rb +55 -0
- data/spec/conway/rules_spec.rb +30 -0
- metadata +25 -6
- data/lib/conway/rule_set.rb +0 -33
- data/spec/conway/rule_set_spec.rb +0 -44
data/README.md
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
## Conway
|
2
|
-
A simple Game of Life
|
2
|
+
A simple [Game of Life](http://en.wikipedia.org/wiki/Conway's_Game_of_Life)
|
3
|
+
implementation with a priority focus on object design.
|
3
4
|
|
4
|
-
It's
|
5
|
-
|
5
|
+
It's probably a bit leaky of object references and hasn't had any go at
|
6
|
+
optimizing the efficiency of the algorithm.
|
6
7
|
|
7
8
|
Conway comes with a very simple ASCII visualizer. It can be invoked as
|
8
9
|
below, where the size argument determines the dimensions of the cell grid:
|
@@ -10,8 +11,10 @@ below, where the size argument determines the dimensions of the cell grid:
|
|
10
11
|
conway --size 25 --cells="10,2 9,3 8,3 9,4 10,4"
|
11
12
|
conway -s 30 -c 2,3:3,3:3,2:2,2:3,4
|
12
13
|
|
14
|
+
When playing with the code itself, there's a sanity check Rake task:
|
15
|
+
|
16
|
+
rake glider
|
17
|
+
|
13
18
|
Please feel free to send feedback via the
|
14
19
|
[GitHub project](https://github.com/mattyoho/conway) or via email at
|
15
20
|
mby _AT_ mattyoho _DOT_ com.
|
16
|
-
|
17
|
-
|
data/bin/conway
CHANGED
@@ -46,6 +46,13 @@ ConwayCli.suppress_warnings do
|
|
46
46
|
default 30
|
47
47
|
end
|
48
48
|
|
49
|
+
option :stats do
|
50
|
+
short '-S'
|
51
|
+
long '--stats'
|
52
|
+
desc 'Display object count and elapsed time'
|
53
|
+
default false
|
54
|
+
end
|
55
|
+
|
49
56
|
option :cells do
|
50
57
|
short '-c'
|
51
58
|
long '--cells=CELLS'
|
@@ -69,7 +76,7 @@ Signal.trap("INT") do
|
|
69
76
|
end
|
70
77
|
|
71
78
|
live_points = ConwayCli.parse_points Choice[:cells]
|
72
|
-
grid = Conway::Driver::Ascii.new(Choice[:size], live_points)
|
79
|
+
grid = Conway::Driver::Ascii.new(Choice[:size], live_points, :display_stats => Choice[:stats])
|
73
80
|
|
74
81
|
grid.loop do |step|
|
75
82
|
puts step
|
data/lib/conway.rb
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
require 'conway/generation'
|
2
2
|
require 'conway/live_cell'
|
3
3
|
require 'conway/dead_cell'
|
4
|
+
require 'conway/memoized_cells'
|
4
5
|
require 'conway/point'
|
5
6
|
require 'conway/cell_location'
|
6
7
|
require 'conway/cell_location_lookup'
|
7
8
|
require 'conway/potential_cell_collection'
|
8
|
-
require 'conway/
|
9
|
+
require 'conway/rules'
|
9
10
|
require 'conway/cell_space'
|
data/lib/conway/cell_space.rb
CHANGED
@@ -1,23 +1,20 @@
|
|
1
1
|
module Conway
|
2
2
|
class CellSpace
|
3
|
-
def initialize(
|
4
|
-
self.potential_cells = PotentialCellCollection.new(
|
3
|
+
def initialize(locations)
|
4
|
+
self.potential_cells = PotentialCellCollection.new(locations)
|
5
5
|
end
|
6
6
|
|
7
|
-
def apply(
|
8
|
-
potential_cells.
|
9
|
-
|
7
|
+
def apply(rules, live_cell_lookup = CellLocationLookup.new)
|
8
|
+
potential_cells.each_location do |location, neighbors|
|
9
|
+
cell = rules.apply(location.cell, neighbors.map{|n| n.cell })
|
10
|
+
if cell.alive?
|
11
|
+
live_cell_lookup.insert(CellLocation.new(cell, location.point))
|
12
|
+
end
|
10
13
|
end
|
11
|
-
|
14
|
+
live_cell_lookup
|
12
15
|
end
|
13
16
|
|
14
17
|
private
|
15
18
|
attr_accessor :potential_cells
|
16
|
-
|
17
|
-
def live_locations(points)
|
18
|
-
points.map do |point|
|
19
|
-
CellLocation.new(LiveCell.new, point)
|
20
|
-
end
|
21
|
-
end
|
22
19
|
end
|
23
20
|
end
|
data/lib/conway/driver/ascii.rb
CHANGED
@@ -3,10 +3,10 @@ require "conway"
|
|
3
3
|
module Conway
|
4
4
|
module Driver
|
5
5
|
class Ascii
|
6
|
-
def initialize(size, starting_cells,
|
6
|
+
def initialize(size, starting_cells, options={})
|
7
7
|
self.max_x = self.max_y = size
|
8
8
|
self.starting_cells = starting_cells
|
9
|
-
self.
|
9
|
+
self.options = {:loop_interval => 0.25}.merge(options)
|
10
10
|
end
|
11
11
|
|
12
12
|
def loop
|
@@ -20,9 +20,10 @@ module Conway
|
|
20
20
|
|
21
21
|
grid = generate_grid(lookup)
|
22
22
|
|
23
|
-
|
24
|
-
|
25
|
-
|
23
|
+
if options[:display_stats]
|
24
|
+
grid << current_stats(lookup)
|
25
|
+
grid << elapsed_time_since(start)
|
26
|
+
end
|
26
27
|
|
27
28
|
yield grid if block_given?
|
28
29
|
|
@@ -31,12 +32,12 @@ module Conway
|
|
31
32
|
break
|
32
33
|
end
|
33
34
|
|
34
|
-
sleep(loop_interval)
|
35
|
+
sleep(options[:loop_interval])
|
35
36
|
end while(generation = generation.next)
|
36
37
|
end
|
37
38
|
|
38
39
|
private
|
39
|
-
attr_accessor :max_x, :max_y, :starting_cells, :
|
40
|
+
attr_accessor :max_x, :max_y, :starting_cells, :options
|
40
41
|
|
41
42
|
def generate_grid(lookup)
|
42
43
|
grid = ""
|
data/lib/conway/generation.rb
CHANGED
@@ -2,15 +2,15 @@ module Conway
|
|
2
2
|
class Generation
|
3
3
|
attr_reader :location_lookup
|
4
4
|
|
5
|
-
def initialize(
|
6
|
-
self.location_lookup = normalize_to_lookup
|
7
|
-
self.
|
5
|
+
def initialize(points_or_lookup, rules=Rules.new)
|
6
|
+
self.location_lookup = normalize_to_lookup points_or_lookup
|
7
|
+
self.rules = rules
|
8
8
|
end
|
9
9
|
|
10
10
|
def next
|
11
|
-
cell_space = CellSpace.new(
|
12
|
-
cell_lookup = cell_space.apply(
|
13
|
-
Generation.new cell_lookup,
|
11
|
+
cell_space = CellSpace.new(location_lookup.locations)
|
12
|
+
cell_lookup = cell_space.apply(rules)
|
13
|
+
Generation.new cell_lookup, rules
|
14
14
|
end
|
15
15
|
|
16
16
|
def cell_coordinates
|
@@ -18,7 +18,7 @@ module Conway
|
|
18
18
|
end
|
19
19
|
|
20
20
|
private
|
21
|
-
attr_accessor :
|
21
|
+
attr_accessor :rules
|
22
22
|
attr_writer :location_lookup
|
23
23
|
|
24
24
|
def normalize_to_lookup(points)
|
data/lib/conway/point.rb
CHANGED
@@ -19,12 +19,15 @@ module Conway
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def update(x, y)
|
22
|
-
self.x
|
23
|
-
self.y
|
22
|
+
self.x = x
|
23
|
+
self.y = y
|
24
|
+
@adjacents = nil
|
25
|
+
|
24
26
|
self
|
25
27
|
end
|
26
28
|
|
27
29
|
def adjacents
|
30
|
+
@adjacents ||=
|
28
31
|
(-1..1).map do |j|
|
29
32
|
(-1..1).map do |i|
|
30
33
|
Point.new(x+i, y+j) unless i == 0 && j == 0
|
@@ -1,32 +1,27 @@
|
|
1
1
|
module Conway
|
2
2
|
class PotentialCellCollection
|
3
|
-
|
3
|
+
include Conway::MemoizedCells
|
4
4
|
|
5
5
|
def initialize(live_locations)
|
6
|
-
self.
|
7
|
-
|
8
|
-
|
6
|
+
self.potential_cell_lookup = CellLocationLookup.new do |point|
|
7
|
+
CellLocation.new(dead_cell, point)
|
8
|
+
end
|
9
9
|
|
10
|
-
|
10
|
+
insert_potential_locations(live_locations)
|
11
11
|
end
|
12
12
|
|
13
|
-
def
|
13
|
+
def each_location(&block)
|
14
14
|
potential_cell_lookup.each do |cell_location|
|
15
|
-
|
16
|
-
|
17
|
-
next_cell = block.call(cell_location.cell, neighbor_cells)
|
15
|
+
neighbor_locations = neighbors_for(cell_location)
|
18
16
|
|
19
|
-
|
20
|
-
live_cell_lookup.insert(cell_location)
|
21
|
-
end
|
17
|
+
block.call(cell_location, neighbor_locations)
|
22
18
|
end
|
23
19
|
end
|
24
20
|
|
25
21
|
private
|
26
|
-
attr_accessor :potential_cell_lookup
|
27
|
-
attr_writer :live_cell_lookup
|
22
|
+
attr_accessor :potential_cell_lookup
|
28
23
|
|
29
|
-
def
|
24
|
+
def insert_potential_locations(locations)
|
30
25
|
locations.each do |loc|
|
31
26
|
potential_cell_lookup.insert(loc)
|
32
27
|
neighbors_for(loc).each do |neighbor|
|
@@ -35,18 +30,10 @@ module Conway
|
|
35
30
|
end
|
36
31
|
end
|
37
32
|
|
38
|
-
def dead_cell_location(point)
|
39
|
-
CellLocation.new(default_dead_cell, point)
|
40
|
-
end
|
41
|
-
|
42
33
|
def neighbors_for(location)
|
43
34
|
location.adjacent_points.map do |point|
|
44
|
-
potential_cell_lookup.retrieve(point)
|
35
|
+
potential_cell_lookup.retrieve(point)
|
45
36
|
end
|
46
37
|
end
|
47
|
-
|
48
|
-
def neighbor_cells_for(location)
|
49
|
-
neighbors_for(location).map {|n| n.cell }
|
50
|
-
end
|
51
38
|
end
|
52
39
|
end
|
data/lib/conway/rules.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'conway/rules/under_population'
|
2
|
+
require 'conway/rules/stability'
|
3
|
+
require 'conway/rules/over_population'
|
4
|
+
require 'conway/rules/reproduction'
|
5
|
+
require 'conway/rules/default'
|
6
|
+
|
7
|
+
module Conway
|
8
|
+
class Rules
|
9
|
+
def initialize(rules=[UnderPopulation.new,
|
10
|
+
Stability.new,
|
11
|
+
OverPopulation.new,
|
12
|
+
Reproduction.new,
|
13
|
+
Default.new])
|
14
|
+
self.rules = rules
|
15
|
+
end
|
16
|
+
|
17
|
+
def apply(cell, neighbors)
|
18
|
+
rules.map do |rule|
|
19
|
+
next_cell = rule.apply(cell, neighbors)
|
20
|
+
break next_cell if next_cell
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
attr_accessor :rules
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'conway/rules/live_count'
|
2
|
+
|
3
|
+
module Conway
|
4
|
+
class Rules
|
5
|
+
class OverPopulation
|
6
|
+
include Conway::Rules::LiveCount
|
7
|
+
include Conway::MemoizedCells
|
8
|
+
|
9
|
+
def apply(cell, neighbors)
|
10
|
+
count = live_count(neighbors)
|
11
|
+
dead_cell if cell.alive? && greater_than_three?(count)
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def greater_than_three?(count)
|
17
|
+
count > 3
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'conway/rules/live_count'
|
2
|
+
|
3
|
+
module Conway
|
4
|
+
class Rules
|
5
|
+
class Reproduction
|
6
|
+
include Conway::Rules::LiveCount
|
7
|
+
include Conway::MemoizedCells
|
8
|
+
|
9
|
+
def apply(cell, neighbors)
|
10
|
+
count = live_count(neighbors)
|
11
|
+
live_cell unless cell.alive? || fewer_or_greater_than_three?(count)
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def fewer_or_greater_than_three?(count)
|
17
|
+
count != 3
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'conway/rules/live_count'
|
2
|
+
|
3
|
+
module Conway
|
4
|
+
class Rules
|
5
|
+
class Stability
|
6
|
+
include Conway::Rules::LiveCount
|
7
|
+
include Conway::MemoizedCells
|
8
|
+
|
9
|
+
def apply(cell, neighbors)
|
10
|
+
count = live_count(neighbors)
|
11
|
+
live_cell if cell.alive? && two_or_three?(count)
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def two_or_three?(count)
|
17
|
+
count == 2 || count == 3
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'conway/rules/live_count'
|
2
|
+
|
3
|
+
module Conway
|
4
|
+
class Rules
|
5
|
+
class UnderPopulation
|
6
|
+
include Conway::Rules::LiveCount
|
7
|
+
include Conway::MemoizedCells
|
8
|
+
|
9
|
+
def apply(cell, neighbors)
|
10
|
+
count = live_count(neighbors)
|
11
|
+
dead_cell if cell.alive? && less_than_two?(count)
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def less_than_two?(count)
|
17
|
+
count < 2
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -8,6 +8,15 @@ describe CellLocationLookup do
|
|
8
8
|
let(:location) { CellLocation.new cell, point }
|
9
9
|
let(:lookup) { CellLocationLookup.new }
|
10
10
|
|
11
|
+
describe "#initialize" do
|
12
|
+
context "given a block" do
|
13
|
+
it "uses the block for default returns from #retrieve" do
|
14
|
+
lookup = CellLocationLookup.new {|p| "Point: (#{p.x},#{p.y})" }
|
15
|
+
lookup.retrieve(point).should eql("Point: (1,1)")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
11
20
|
describe "#insert" do
|
12
21
|
it "accepts a CellLocation" do
|
13
22
|
lookup.insert location
|
@@ -3,16 +3,42 @@ require 'spec_helper'
|
|
3
3
|
include Conway
|
4
4
|
|
5
5
|
describe CellSpace do
|
6
|
-
it "accepts a collection of
|
7
|
-
CellSpace.new [Point.new(0,0)]
|
6
|
+
it "accepts a collection of CellLocations" do
|
7
|
+
CellSpace.new [CellLocation.new(LiveCell.new, Point.new(0,0))]
|
8
8
|
end
|
9
9
|
|
10
10
|
describe "#apply" do
|
11
|
-
let(:
|
12
|
-
let(:
|
11
|
+
let(:cell) { LiveCell.new }
|
12
|
+
let(:location) { CellLocation.new(cell, Point.new(0,0)) }
|
13
|
+
let(:space) { CellSpace.new [location] }
|
14
|
+
let(:rules) { Rules.new }
|
13
15
|
|
14
16
|
it "returns a CellLocationLookup" do
|
15
|
-
space.apply(
|
17
|
+
space.apply(rules).should be_a_kind_of(CellLocationLookup)
|
18
|
+
end
|
19
|
+
|
20
|
+
context "when the Rules gives a LiveCell" do
|
21
|
+
let(:rules) { stub(:apply => LiveCell.new) }
|
22
|
+
|
23
|
+
it "retains the cell" do
|
24
|
+
live_cell_lookup = space.apply(rules)
|
25
|
+
locations = live_cell_lookup.locations
|
26
|
+
|
27
|
+
locations.should have(9).locations
|
28
|
+
|
29
|
+
locations.all? {|loc| loc.cell.alive? }.should be_true
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context "when the Rules gives a DeadCell" do
|
34
|
+
let(:cell_location) { CellLocation.new(DeadCell.new, point) }
|
35
|
+
let(:rules) { stub(:apply => DeadCell.new) }
|
36
|
+
|
37
|
+
it "does not retain the cell" do
|
38
|
+
live_cell_lookup = space.apply(rules)
|
39
|
+
live_cell_lookup.should be_empty
|
40
|
+
end
|
16
41
|
end
|
17
42
|
end
|
43
|
+
|
18
44
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Conway::MemoizedCells do
|
4
|
+
let(:klass) { Class.new.tap {|c| c.send(:include, Conway::MemoizedCells) } }
|
5
|
+
let(:instance) { klass.new }
|
6
|
+
describe "#live_cell" do
|
7
|
+
it "is alive" do
|
8
|
+
instance.live_cell.should be_alive
|
9
|
+
end
|
10
|
+
|
11
|
+
it "returns the same object across invocations" do
|
12
|
+
instance.live_cell.should equal(instance.live_cell)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "#dead_cell" do
|
17
|
+
it "is not alive" do
|
18
|
+
instance.dead_cell.should_not be_alive
|
19
|
+
end
|
20
|
+
|
21
|
+
it "returns the same object across invocations" do
|
22
|
+
instance.dead_cell.should equal(instance.dead_cell)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/spec/conway/point_spec.rb
CHANGED
@@ -33,6 +33,12 @@ describe Point do
|
|
33
33
|
it "returns the eight adjacent points" do
|
34
34
|
point.adjacents.should == neighbors
|
35
35
|
end
|
36
|
+
|
37
|
+
it "memoizes the points" do
|
38
|
+
point.adjacents
|
39
|
+
Point.should_receive(:new).never
|
40
|
+
point.adjacents
|
41
|
+
end
|
36
42
|
end
|
37
43
|
|
38
44
|
describe "#update" do
|
@@ -46,6 +52,13 @@ describe Point do
|
|
46
52
|
it "returns self" do
|
47
53
|
point.update(4,2).should equal(point)
|
48
54
|
end
|
55
|
+
|
56
|
+
it "clears the adjacents cache" do
|
57
|
+
point.adjacents
|
58
|
+
point.instance_variable_get("@adjacents").should_not be_nil
|
59
|
+
point.update(0, 0)
|
60
|
+
point.instance_variable_get("@adjacents").should be_nil
|
61
|
+
end
|
49
62
|
end
|
50
63
|
end
|
51
64
|
|
@@ -10,84 +10,50 @@ describe PotentialCellCollection do
|
|
10
10
|
let(:collection) { PotentialCellCollection.new(initial_cells) }
|
11
11
|
|
12
12
|
describe "#each_cell" do
|
13
|
-
it "
|
13
|
+
it "yields the initial set of LiveCells" do
|
14
14
|
yielded_cells = []
|
15
|
-
collection.
|
15
|
+
collection.each_location do |cell, dont_care|
|
16
16
|
yielded_cells << cell
|
17
17
|
cell
|
18
18
|
end
|
19
|
-
yielded_cells.should include(
|
20
|
-
end
|
21
|
-
|
22
|
-
context "when the cell is a LiveCell" do
|
23
|
-
it "does not retain the cell if it becomes a DeadCell" do
|
24
|
-
collection.each_cell do |cell, neighbors|
|
25
|
-
DeadCell.new
|
26
|
-
end
|
27
|
-
collection.live_cell_lookup.should be_empty
|
28
|
-
end
|
29
|
-
|
30
|
-
it "retains the cell if it remains a LiveCell" do
|
31
|
-
collection.each_cell do |cell, neighbors|
|
32
|
-
LiveCell.new
|
33
|
-
end
|
34
|
-
collection.live_cell_lookup.locations.should include(cell_location)
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
context "when the cell is a DeadCell" do
|
39
|
-
let(:cell_location) { CellLocation.new(DeadCell.new, point) }
|
40
|
-
|
41
|
-
it "does not retain the cell if it remains a DeadCell" do
|
42
|
-
collection.each_cell do |cell, neighbors|
|
43
|
-
DeadCell.new
|
44
|
-
end
|
45
|
-
collection.live_cell_lookup.should be_empty
|
46
|
-
end
|
47
|
-
|
48
|
-
it "retains the cell if it remains a LiveCell" do
|
49
|
-
collection.each_cell do |cell, neighbors|
|
50
|
-
LiveCell.new
|
51
|
-
end
|
52
|
-
collection.live_cell_lookup.locations.should include(cell_location)
|
53
|
-
end
|
19
|
+
yielded_cells.should include(cell_location)
|
54
20
|
end
|
55
21
|
|
56
22
|
context "neighbors" do
|
57
|
-
let(:neighbor_cell)
|
23
|
+
let(:neighbor_cell) { LiveCell.new }
|
24
|
+
let(:neighbor_location) { CellLocation.new(neighbor_cell, Point.new(1,2)) }
|
58
25
|
let(:initial_cells) do
|
59
|
-
[CellLocation.new(live_cell, point),
|
60
|
-
CellLocation.new(neighbor_cell, Point.new(1,2))]
|
26
|
+
[CellLocation.new(live_cell, point), neighbor_location]
|
61
27
|
end
|
62
28
|
|
63
29
|
it "yields the neighboring LiveCells" do
|
64
30
|
yielded_neighbors = nil
|
65
|
-
collection.
|
31
|
+
collection.each_location do |cell, neighbors|
|
66
32
|
yielded_neighbors = neighbors
|
67
33
|
break
|
68
34
|
end
|
69
35
|
|
70
|
-
yielded_neighbors.should include(
|
36
|
+
yielded_neighbors.should include(neighbor_location)
|
71
37
|
end
|
72
38
|
|
73
|
-
it "yields the neighboring DeadCells" do
|
39
|
+
it "yields the neighboring locations with DeadCells" do
|
74
40
|
yielded_neighbors = nil
|
75
|
-
collection.
|
41
|
+
collection.each_location do |loc, neighbors|
|
76
42
|
yielded_neighbors = neighbors
|
77
43
|
break
|
78
44
|
end
|
79
45
|
|
80
|
-
(yielded_neighbors - [
|
46
|
+
(yielded_neighbors - [neighbor_location]).should have(7).neighbors
|
81
47
|
end
|
82
48
|
|
83
49
|
it "includes all neighboring DeadCells" do
|
84
50
|
yielded_neighbors = nil
|
85
|
-
collection.
|
51
|
+
collection.each_location do |cell, neighbors|
|
86
52
|
yielded_neighbors = neighbors
|
87
53
|
break
|
88
54
|
end
|
89
55
|
|
90
|
-
(yielded_neighbors - [
|
56
|
+
(yielded_neighbors - [neighbor_location]).any?{|c| c.kind_of?(LiveCell)}.should be_false
|
91
57
|
end
|
92
58
|
end
|
93
59
|
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
include Conway
|
4
|
+
|
5
|
+
describe Rules::OverPopulation do
|
6
|
+
let(:rule) { Rules::OverPopulation.new }
|
7
|
+
|
8
|
+
context "when a DeadCell" do
|
9
|
+
let(:cell) { DeadCell.new }
|
10
|
+
|
11
|
+
context "with a bunch of live neighbors" do
|
12
|
+
let(:neighbors) { [LiveCell.new] * 8 }
|
13
|
+
|
14
|
+
it "returns nil" do
|
15
|
+
rule.apply(cell, neighbors).should be_nil
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context "with a bunch of dead neighbors" do
|
20
|
+
let(:neighbors) { [DeadCell.new] * 8 }
|
21
|
+
|
22
|
+
it "returns nil" do
|
23
|
+
rule.apply(cell, neighbors).should be_nil
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context "when a LiveCell" do
|
29
|
+
let(:cell) { LiveCell.new }
|
30
|
+
|
31
|
+
context "with less than two live neighbors" do
|
32
|
+
let(:neighbors) { [DeadCell.new] * 7 + [LiveCell.new] }
|
33
|
+
|
34
|
+
it "returns a nil" do
|
35
|
+
rule.apply(cell, neighbors).should be_nil
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
context "with two live neighbors" do
|
40
|
+
let(:neighbors) { [DeadCell.new] * 6 + [LiveCell.new] * 2 }
|
41
|
+
|
42
|
+
it "returns nil" do
|
43
|
+
rule.apply(cell, neighbors).should be_nil
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context "with three live neighbors" do
|
48
|
+
let(:neighbors) { [DeadCell.new] * 5 + [LiveCell.new] * 3 }
|
49
|
+
|
50
|
+
it "returns nil" do
|
51
|
+
rule.apply(cell, neighbors).should be_nil
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context "with more than three live neighbors" do
|
56
|
+
let(:neighbors) { [DeadCell.new] * 4 + [LiveCell.new] * 4 }
|
57
|
+
|
58
|
+
it "returns a DeadCell" do
|
59
|
+
rule.apply(cell, neighbors).should_not be_alive
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
include Conway
|
4
|
+
|
5
|
+
describe Rules::Reproduction do
|
6
|
+
let(:rule) { Rules::Reproduction.new }
|
7
|
+
|
8
|
+
context "when a LiveCell" do
|
9
|
+
let(:cell) { LiveCell.new }
|
10
|
+
|
11
|
+
context "with a bunch of live neighbors" do
|
12
|
+
let(:neighbors) { [LiveCell.new] * 8 }
|
13
|
+
|
14
|
+
it "returns nil" do
|
15
|
+
rule.apply(cell, neighbors).should be_nil
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context "with a bunch of dead neighbors" do
|
20
|
+
let(:neighbors) { [DeadCell.new] * 8 }
|
21
|
+
|
22
|
+
it "returns nil" do
|
23
|
+
rule.apply(cell, neighbors).should be_nil
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context "when a DeadCell" do
|
29
|
+
let(:cell) { DeadCell.new }
|
30
|
+
|
31
|
+
context "with less than three live neighbors" do
|
32
|
+
let(:neighbors) { [DeadCell.new] * 6 + [LiveCell.new] * 2 }
|
33
|
+
|
34
|
+
it "returns nil" do
|
35
|
+
rule.apply(cell, neighbors).should be_nil
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
context "with three live neighbors" do
|
40
|
+
let(:neighbors) { [DeadCell.new] * 5 + [LiveCell.new] * 3 }
|
41
|
+
|
42
|
+
it "returns a LiveCell" do
|
43
|
+
rule.apply(cell, neighbors).should be_alive
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context "with more than three live neighbors" do
|
48
|
+
let(:neighbors) { [DeadCell.new] * 4 + [LiveCell.new] * 4 }
|
49
|
+
|
50
|
+
it "returns nil" do
|
51
|
+
rule.apply(cell, neighbors).should be_nil
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
include Conway
|
4
|
+
|
5
|
+
describe Rules::Stability do
|
6
|
+
let(:rule) { Rules::Stability.new }
|
7
|
+
|
8
|
+
context "when a DeadCell" do
|
9
|
+
let(:cell) { DeadCell.new }
|
10
|
+
|
11
|
+
context "with a bunch of live neighbors" do
|
12
|
+
let(:neighbors) { [LiveCell.new] * 8 }
|
13
|
+
|
14
|
+
it "returns nil" do
|
15
|
+
rule.apply(cell, neighbors).should be_nil
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context "with a bunch of dead neighbors" do
|
20
|
+
let(:neighbors) { [DeadCell.new] * 8 }
|
21
|
+
|
22
|
+
it "returns nil" do
|
23
|
+
rule.apply(cell, neighbors).should be_nil
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context "when a LiveCell" do
|
29
|
+
let(:cell) { LiveCell.new }
|
30
|
+
|
31
|
+
context "with less than two live neighbors" do
|
32
|
+
let(:neighbors) { [DeadCell.new] * 7 + [LiveCell.new] }
|
33
|
+
|
34
|
+
it "returns nil" do
|
35
|
+
rule.apply(cell, neighbors).should be_nil
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
context "with two live neighbors" do
|
40
|
+
let(:neighbors) { [DeadCell.new] * 6 + [LiveCell.new] * 2 }
|
41
|
+
|
42
|
+
it "returns a LiveCell" do
|
43
|
+
rule.apply(cell, neighbors).should be_alive
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context "with three live neighbors" do
|
48
|
+
let(:neighbors) { [DeadCell.new] * 5 + [LiveCell.new] * 3 }
|
49
|
+
|
50
|
+
it "returns a LiveCell" do
|
51
|
+
rule.apply(cell, neighbors).should be_alive
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context "with more than three live neighbors" do
|
56
|
+
let(:neighbors) { [DeadCell.new] * 4 + [LiveCell.new] * 4 }
|
57
|
+
|
58
|
+
it "returns nil" do
|
59
|
+
rule.apply(cell, neighbors).should be_nil
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
include Conway
|
4
|
+
|
5
|
+
describe Rules::UnderPopulation do
|
6
|
+
let(:rule) { Rules::UnderPopulation.new }
|
7
|
+
|
8
|
+
context "when a DeadCell" do
|
9
|
+
let(:cell) { DeadCell.new }
|
10
|
+
|
11
|
+
context "with a bunch of live neighbors" do
|
12
|
+
let(:neighbors) { [LiveCell.new] * 8 }
|
13
|
+
|
14
|
+
it "returns nil" do
|
15
|
+
rule.apply(cell, neighbors).should be_nil
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context "with a bunch of dead neighbors" do
|
20
|
+
let(:neighbors) { [DeadCell.new] * 8 }
|
21
|
+
|
22
|
+
it "returns nil" do
|
23
|
+
rule.apply(cell, neighbors).should be_nil
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context "when a LiveCell" do
|
29
|
+
let(:cell) { LiveCell.new }
|
30
|
+
|
31
|
+
context "with less than two live neighbors" do
|
32
|
+
let(:neighbors) { [DeadCell.new] * 7 + [LiveCell.new] }
|
33
|
+
|
34
|
+
it "returns a DeadCell" do
|
35
|
+
rule.apply(cell, neighbors).should_not be_alive
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
context "with two live neighbors" do
|
40
|
+
let(:neighbors) { [DeadCell.new] * 6 + [LiveCell.new] * 2 }
|
41
|
+
|
42
|
+
it "returns nil" do
|
43
|
+
rule.apply(cell, neighbors).should be_nil
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context "with more than two live neighbors" do
|
48
|
+
let(:neighbors) { [DeadCell.new] * 5 + [LiveCell.new] * 3 }
|
49
|
+
|
50
|
+
it "returns nil" do
|
51
|
+
rule.apply(cell, neighbors).should be_nil
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
include Conway
|
4
|
+
|
5
|
+
describe Rules do
|
6
|
+
describe "#apply" do
|
7
|
+
let(:cell) { LiveCell.new }
|
8
|
+
let(:neighbors) { [LiveCell.new] }
|
9
|
+
let(:a_rule) { stub }
|
10
|
+
let(:b_rule) { stub }
|
11
|
+
let(:c_rule) { stub }
|
12
|
+
let(:rule_set) { [a_rule, b_rule, c_rule] }
|
13
|
+
let(:rules) { Rules.new(rule_set) }
|
14
|
+
|
15
|
+
it "calls apply for each sub rule" do
|
16
|
+
rule_set.each {|r| r.should_receive(:apply).with(cell, neighbors) }
|
17
|
+
rules.apply(cell, neighbors)
|
18
|
+
end
|
19
|
+
|
20
|
+
context "return value" do
|
21
|
+
let(:a_rule) { stub(:apply => nil) }
|
22
|
+
let(:b_rule) { stub(:apply => :b) }
|
23
|
+
let(:c_rule) { stub(:apply => :c) }
|
24
|
+
|
25
|
+
it "returns the first non-nil result" do
|
26
|
+
rules.apply(cell, neighbors).should == :b
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
metadata
CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
|
|
4
4
|
prerelease: false
|
5
5
|
segments:
|
6
6
|
- 0
|
7
|
-
-
|
7
|
+
- 3
|
8
8
|
- 0
|
9
|
-
version: 0.
|
9
|
+
version: 0.3.0
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Matt Yoho
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2011-01-
|
17
|
+
date: 2011-01-24 00:00:00 -05:00
|
18
18
|
default_executable: conway
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -51,9 +51,16 @@ files:
|
|
51
51
|
- lib/conway/driver/ascii.rb
|
52
52
|
- lib/conway/generation.rb
|
53
53
|
- lib/conway/live_cell.rb
|
54
|
+
- lib/conway/memoized_cells.rb
|
54
55
|
- lib/conway/point.rb
|
55
56
|
- lib/conway/potential_cell_collection.rb
|
56
|
-
- lib/conway/
|
57
|
+
- lib/conway/rules/default.rb
|
58
|
+
- lib/conway/rules/live_count.rb
|
59
|
+
- lib/conway/rules/over_population.rb
|
60
|
+
- lib/conway/rules/reproduction.rb
|
61
|
+
- lib/conway/rules/stability.rb
|
62
|
+
- lib/conway/rules/under_population.rb
|
63
|
+
- lib/conway/rules.rb
|
57
64
|
- lib/conway.rb
|
58
65
|
- spec/conway/cell_location_lookup_spec.rb
|
59
66
|
- spec/conway/cell_location_spec.rb
|
@@ -61,9 +68,15 @@ files:
|
|
61
68
|
- spec/conway/dead_cell_spec.rb
|
62
69
|
- spec/conway/generation_spec.rb
|
63
70
|
- spec/conway/live_cell_spec.rb
|
71
|
+
- spec/conway/memoized_cells_spec.rb
|
64
72
|
- spec/conway/point_spec.rb
|
65
73
|
- spec/conway/potential_cell_collection_spec.rb
|
66
|
-
- spec/conway/
|
74
|
+
- spec/conway/rules/default_spec.rb
|
75
|
+
- spec/conway/rules/over_population_spec.rb
|
76
|
+
- spec/conway/rules/reproduction_spec.rb
|
77
|
+
- spec/conway/rules/stability_spec.rb
|
78
|
+
- spec/conway/rules/under_population_spec.rb
|
79
|
+
- spec/conway/rules_spec.rb
|
67
80
|
- spec/spec_helper.rb
|
68
81
|
- bin/conway
|
69
82
|
has_rdoc: true
|
@@ -105,7 +118,13 @@ test_files:
|
|
105
118
|
- spec/conway/dead_cell_spec.rb
|
106
119
|
- spec/conway/generation_spec.rb
|
107
120
|
- spec/conway/live_cell_spec.rb
|
121
|
+
- spec/conway/memoized_cells_spec.rb
|
108
122
|
- spec/conway/point_spec.rb
|
109
123
|
- spec/conway/potential_cell_collection_spec.rb
|
110
|
-
- spec/conway/
|
124
|
+
- spec/conway/rules/default_spec.rb
|
125
|
+
- spec/conway/rules/over_population_spec.rb
|
126
|
+
- spec/conway/rules/reproduction_spec.rb
|
127
|
+
- spec/conway/rules/stability_spec.rb
|
128
|
+
- spec/conway/rules/under_population_spec.rb
|
129
|
+
- spec/conway/rules_spec.rb
|
111
130
|
- spec/spec_helper.rb
|
data/lib/conway/rule_set.rb
DELETED
@@ -1,33 +0,0 @@
|
|
1
|
-
module Conway
|
2
|
-
class RuleSet
|
3
|
-
def apply(cell, neighbors)
|
4
|
-
live_neighbors_count = neighbors.count {|n| n.alive? }
|
5
|
-
case cell
|
6
|
-
when LiveCell
|
7
|
-
evaluate_live_cell(cell, live_neighbors_count)
|
8
|
-
when DeadCell
|
9
|
-
evaluate_dead_cell(cell, live_neighbors_count)
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
private
|
14
|
-
|
15
|
-
def evaluate_dead_cell(cell, live_neighbors_count)
|
16
|
-
if live_neighbors_count == 3
|
17
|
-
LiveCell.new
|
18
|
-
else
|
19
|
-
DeadCell.new
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
def evaluate_live_cell(cell, live_neighbors_count)
|
24
|
-
if live_neighbors_count < 2
|
25
|
-
DeadCell.new
|
26
|
-
elsif live_neighbors_count > 3
|
27
|
-
DeadCell.new
|
28
|
-
else
|
29
|
-
LiveCell.new
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
@@ -1,44 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
include Conway
|
4
|
-
|
5
|
-
describe RuleSet do
|
6
|
-
let(:rule_set) { RuleSet.new }
|
7
|
-
describe "#apply" do
|
8
|
-
context "when a DeadCell" do
|
9
|
-
let(:cell) { DeadCell.new }
|
10
|
-
|
11
|
-
it "returns a LiveCell when there are three live neighbors" do
|
12
|
-
new_cell = rule_set.apply(cell, [LiveCell.new] * 3)
|
13
|
-
new_cell.should be_alive
|
14
|
-
end
|
15
|
-
|
16
|
-
it "returns a DeadCell with fewer than three live neighbors" do
|
17
|
-
new_cell = rule_set.apply(cell, [LiveCell.new] * 2)
|
18
|
-
new_cell.should_not be_alive
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
context "when a LiveCell" do
|
23
|
-
let(:cell) { LiveCell.new }
|
24
|
-
it "returns a DeadCell when underpopulated" do
|
25
|
-
rule_set.apply(cell, [DeadCell.new] * 8).should_not be_alive
|
26
|
-
end
|
27
|
-
|
28
|
-
it "returns a LiveCell when sufficiently populated" do
|
29
|
-
new_cell = rule_set.apply(cell, [LiveCell.new] * 2)
|
30
|
-
new_cell.should be_alive
|
31
|
-
end
|
32
|
-
|
33
|
-
it "returns a LiveCell when sufficiently populated" do
|
34
|
-
new_cell = rule_set.apply(cell, [LiveCell.new] * 3)
|
35
|
-
new_cell.should be_alive
|
36
|
-
end
|
37
|
-
|
38
|
-
it "returns a DeadCell when over-populated" do
|
39
|
-
new_cell = rule_set.apply(cell, [LiveCell.new] * 4)
|
40
|
-
new_cell.should_not be_alive
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|