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 CHANGED
@@ -1,8 +1,9 @@
1
1
  ## Conway
2
- A simple Game of Life implementation with foremost focus on object design.
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 currently a bit leaky of object references and has not had efficiency
5
- optimization.
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
@@ -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/rule_set'
9
+ require 'conway/rules'
9
10
  require 'conway/cell_space'
@@ -1,7 +1,7 @@
1
1
  module Conway
2
2
  class CellLocationLookup
3
- def initialize
4
- @hash ||= {}
3
+ def initialize(&block)
4
+ @hash ||= block_given? ? Hash.new {|h,p| yield p } : {}
5
5
  @point ||= Point.new(0,0)
6
6
  end
7
7
 
@@ -1,23 +1,20 @@
1
1
  module Conway
2
2
  class CellSpace
3
- def initialize(points)
4
- self.potential_cells = PotentialCellCollection.new(live_locations(points))
3
+ def initialize(locations)
4
+ self.potential_cells = PotentialCellCollection.new(locations)
5
5
  end
6
6
 
7
- def apply(rule_set)
8
- potential_cells.each_cell do |cell, neighbors|
9
- rule_set.apply(cell, neighbors)
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
- potential_cells.live_cell_lookup
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
@@ -3,10 +3,10 @@ require "conway"
3
3
  module Conway
4
4
  module Driver
5
5
  class Ascii
6
- def initialize(size, starting_cells, loop_interval=0.25)
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.loop_interval = loop_interval
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
- grid << current_stats(lookup)
24
- grid << elapsed_time_since(start)
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, :loop_interval
40
+ attr_accessor :max_x, :max_y, :starting_cells, :options
40
41
 
41
42
  def generate_grid(lookup)
42
43
  grid = ""
@@ -2,15 +2,15 @@ module Conway
2
2
  class Generation
3
3
  attr_reader :location_lookup
4
4
 
5
- def initialize(cell_locations, rule_set=RuleSet.new)
6
- self.location_lookup = normalize_to_lookup cell_locations
7
- self.rule_set = rule_set
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(cell_coordinates)
12
- cell_lookup = cell_space.apply(rule_set)
13
- Generation.new cell_lookup, rule_set
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 :rule_set
21
+ attr_accessor :rules
22
22
  attr_writer :location_lookup
23
23
 
24
24
  def normalize_to_lookup(points)
@@ -0,0 +1,14 @@
1
+ require 'conway/live_cell'
2
+ require 'conway/dead_cell'
3
+
4
+ module Conway
5
+ module MemoizedCells
6
+ def live_cell
7
+ @live_cell ||= LiveCell.new
8
+ end
9
+
10
+ def dead_cell
11
+ @dead_cell ||= DeadCell.new
12
+ end
13
+ end
14
+ end
@@ -19,12 +19,15 @@ module Conway
19
19
  end
20
20
 
21
21
  def update(x, y)
22
- self.x = x
23
- self.y = 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
- attr_reader :live_cell_lookup
3
+ include Conway::MemoizedCells
4
4
 
5
5
  def initialize(live_locations)
6
- self.live_cell_lookup = CellLocationLookup.new
7
- self.potential_cell_lookup = CellLocationLookup.new
8
- self.default_dead_cell = DeadCell.new
6
+ self.potential_cell_lookup = CellLocationLookup.new do |point|
7
+ CellLocation.new(dead_cell, point)
8
+ end
9
9
 
10
- identify_potential_locations(live_locations)
10
+ insert_potential_locations(live_locations)
11
11
  end
12
12
 
13
- def each_cell(&block)
13
+ def each_location(&block)
14
14
  potential_cell_lookup.each do |cell_location|
15
- neighbor_cells = neighbor_cells_for(cell_location)
16
-
17
- next_cell = block.call(cell_location.cell, neighbor_cells)
15
+ neighbor_locations = neighbors_for(cell_location)
18
16
 
19
- if next_cell.alive?
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, :default_dead_cell
27
- attr_writer :live_cell_lookup
22
+ attr_accessor :potential_cell_lookup
28
23
 
29
- def identify_potential_locations(locations)
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) || dead_cell_location(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
@@ -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,11 @@
1
+ module Conway
2
+ class Rules
3
+ class Default
4
+ include Conway::MemoizedCells
5
+
6
+ def apply(cell, neighbors)
7
+ dead_cell
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module Conway
2
+ class Rules
3
+ module LiveCount
4
+ private
5
+
6
+ def live_count(neighbors)
7
+ neighbors.count {|n| n.alive? }
8
+ end
9
+ end
10
+ end
11
+ 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 points" do
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(:space) { CellSpace.new [Point.new(0,0)] }
12
- let(:rule_set) { RuleSet.new }
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(rule_set).should be_a_kind_of(CellLocationLookup)
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
@@ -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 "contains the initial set of LiveCells" do
13
+ it "yields the initial set of LiveCells" do
14
14
  yielded_cells = []
15
- collection.each_cell do |cell, dont_care|
15
+ collection.each_location do |cell, dont_care|
16
16
  yielded_cells << cell
17
17
  cell
18
18
  end
19
- yielded_cells.should include(live_cell)
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) { LiveCell.new }
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.each_cell do |cell, neighbors|
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(neighbor_cell)
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.each_cell do |cell, neighbors|
41
+ collection.each_location do |loc, neighbors|
76
42
  yielded_neighbors = neighbors
77
43
  break
78
44
  end
79
45
 
80
- (yielded_neighbors - [neighbor_cell]).should have(7).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.each_cell do |cell, neighbors|
51
+ collection.each_location do |cell, neighbors|
86
52
  yielded_neighbors = neighbors
87
53
  break
88
54
  end
89
55
 
90
- (yielded_neighbors - [neighbor_cell]).any?{|c| c.alive?}.should be_false
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,13 @@
1
+ require 'spec_helper'
2
+
3
+ include Conway
4
+
5
+ describe Rules::Default do
6
+ let(:rule) { Rules::Default.new }
7
+ let(:cell) { }
8
+ let(:neighbors) { }
9
+
10
+ it "returns nil" do
11
+ rule.apply(cell, neighbors).should_not be_alive
12
+ end
13
+ 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
- - 2
7
+ - 3
8
8
  - 0
9
- version: 0.2.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-21 00:00:00 -05:00
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/rule_set.rb
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/rule_set_spec.rb
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/rule_set_spec.rb
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
@@ -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