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