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 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