conway 0.1.1 → 0.2.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,13 +1,13 @@
1
1
  ## Conway
2
2
  A simple Game of Life implementation with foremost focus on object design.
3
3
 
4
- It's currently very much leaky of object references and has no mind for
5
- efficiency.
4
+ It's currently a bit leaky of object references and has not had efficiency
5
+ optimization.
6
6
 
7
7
  Conway comes with a very simple ASCII visualizer. It can be invoked as
8
8
  below, where the size argument determines the dimensions of the cell grid:
9
9
 
10
- conway --size 25 --cells="2,3 3,3 2,2 5,4 5,8 9,8, 7,6"
10
+ conway --size 25 --cells="10,2 9,3 8,3 9,4 10,4"
11
11
  conway -s 30 -c 2,3:3,3:3,2:2,2:3,4
12
12
 
13
13
  Please feel free to send feedback via the
data/bin/conway CHANGED
@@ -5,7 +5,7 @@ lib_dir = File.join(File.dirname(__FILE__), '..', 'lib')
5
5
  $LOAD_PATH.unshift lib_dir if File.directory?(lib_dir)
6
6
 
7
7
  require 'choice'
8
- require 'conway/visualizer/ascii'
8
+ require 'conway/driver/ascii'
9
9
 
10
10
  PROGRAM_VERSION = 1
11
11
 
@@ -69,7 +69,7 @@ Signal.trap("INT") do
69
69
  end
70
70
 
71
71
  live_points = ConwayCli.parse_points Choice[:cells]
72
- grid = Conway::Visualizer::Ascii.new(Choice[:size], live_points)
72
+ grid = Conway::Driver::Ascii.new(Choice[:size], live_points)
73
73
 
74
74
  grid.loop do |step|
75
75
  puts step
@@ -3,6 +3,7 @@ require 'conway/live_cell'
3
3
  require 'conway/dead_cell'
4
4
  require 'conway/point'
5
5
  require 'conway/cell_location'
6
+ require 'conway/cell_location_lookup'
6
7
  require 'conway/potential_cell_collection'
7
8
  require 'conway/rule_set'
8
9
  require 'conway/cell_space'
@@ -0,0 +1,35 @@
1
+ module Conway
2
+ class CellLocationLookup
3
+ def initialize
4
+ @hash ||= {}
5
+ @point ||= Point.new(0,0)
6
+ end
7
+
8
+ def insert(cell_location)
9
+ @hash[cell_location.point] = cell_location
10
+ end
11
+
12
+ def retrieve(point_or_x, y=nil)
13
+ if y
14
+ point_or_x = @point.update(point_or_x, y)
15
+ end
16
+ @hash[point_or_x]
17
+ end
18
+
19
+ def locations
20
+ @hash.values
21
+ end
22
+
23
+ def count
24
+ locations.count
25
+ end
26
+
27
+ def empty?
28
+ !(count > 0)
29
+ end
30
+
31
+ def each(&block)
32
+ locations.each(&block)
33
+ end
34
+ end
35
+ end
@@ -8,7 +8,7 @@ module Conway
8
8
  potential_cells.each_cell do |cell, neighbors|
9
9
  rule_set.apply(cell, neighbors)
10
10
  end
11
- potential_cells.live_cell_locations.map {|cell_loc| cell_loc.point }
11
+ potential_cells.live_cell_lookup
12
12
  end
13
13
 
14
14
  private
@@ -0,0 +1,78 @@
1
+ require "conway"
2
+
3
+ module Conway
4
+ module Driver
5
+ class Ascii
6
+ def initialize(size, starting_cells, loop_interval=0.25)
7
+ self.max_x = self.max_y = size
8
+ self.starting_cells = starting_cells
9
+ self.loop_interval = loop_interval
10
+ end
11
+
12
+ def loop
13
+ generation = Generation.new(starting_cells)
14
+ start = Time.now
15
+
16
+ puts "They live!!\n\n"
17
+
18
+ begin
19
+ lookup = generation.location_lookup
20
+
21
+ grid = generate_grid(lookup)
22
+
23
+ grid << current_stats(lookup)
24
+ grid << elapsed_time_since(start)
25
+
26
+
27
+ yield grid if block_given?
28
+
29
+ if lookup.empty?
30
+ puts "\nThey have all perished! D-:"
31
+ break
32
+ end
33
+
34
+ sleep(loop_interval)
35
+ end while(generation = generation.next)
36
+ end
37
+
38
+ private
39
+ attr_accessor :max_x, :max_y, :starting_cells, :loop_interval
40
+
41
+ def generate_grid(lookup)
42
+ grid = ""
43
+ (1..max_y).each do |y|
44
+ (1..max_x).each do |x|
45
+ cell_char = cell_content_for(lookup, x, y)
46
+ grid << "|#{cell_char}"
47
+ end
48
+ grid << "|\n"
49
+ end
50
+ grid
51
+ end
52
+
53
+ def cell_content_for(lookup, x,y)
54
+ lookup.retrieve(x,y) ? "X" : " "
55
+ end
56
+
57
+ def current_stats(lookup)
58
+ stats = "Total objects: #{live_object_count} "
59
+ stats << "Total living cells: #{lookup.count}\n"
60
+ end
61
+
62
+ def elapsed_time_since(start)
63
+ elapsed_minutes, elapsed_seconds = ((Time.now - start).to_i).divmod 60
64
+ "Elapsed time: #{elapsed_minutes} min, #{elapsed_seconds} secs\n"
65
+ end
66
+
67
+ def live_object_count
68
+ if ObjectSpace.respond_to?(:count_objects)
69
+ # Ruby 1.9.2
70
+ ObjectSpace.count_objects[:TOTAL]
71
+ else
72
+ # Ruby 1.8.7
73
+ ObjectSpace.live_objects
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -1,16 +1,41 @@
1
1
  module Conway
2
2
  class Generation
3
- attr_reader :cell_coordinates, :rule_set
3
+ attr_reader :location_lookup
4
4
 
5
- def initialize(cell_coordinates=[], rule_set=RuleSet.new)
6
- @cell_coordinates = cell_coordinates
7
- @rule_set = rule_set
5
+ def initialize(cell_locations, rule_set=RuleSet.new)
6
+ self.location_lookup = normalize_to_lookup cell_locations
7
+ self.rule_set = rule_set
8
8
  end
9
9
 
10
10
  def next
11
- cell_space = CellSpace.new(cell_coordinates)
12
- new_coords = cell_space.apply(rule_set)
13
- Generation.new new_coords, rule_set
11
+ cell_space = CellSpace.new(cell_coordinates)
12
+ cell_lookup = cell_space.apply(rule_set)
13
+ Generation.new cell_lookup, rule_set
14
+ end
15
+
16
+ def cell_coordinates
17
+ location_lookup.locations.map {|loc| loc.point }
18
+ end
19
+
20
+ private
21
+ attr_accessor :rule_set
22
+ attr_writer :location_lookup
23
+
24
+ def normalize_to_lookup(points)
25
+ return points unless points.kind_of?(Array)
26
+
27
+ lookup = CellLocationLookup.new
28
+ points.each do |point|
29
+ loc = point_to_location(point)
30
+ lookup.insert(loc)
31
+ end
32
+
33
+ lookup
34
+ end
35
+
36
+ def point_to_location(point)
37
+ @live_cell ||= LiveCell.new
38
+ CellLocation.new(@live_cell, point)
14
39
  end
15
40
  end
16
41
  end
@@ -21,6 +21,7 @@ module Conway
21
21
  def update(x, y)
22
22
  self.x = x
23
23
  self.y = y
24
+ self
24
25
  end
25
26
 
26
27
  def adjacents
@@ -1,45 +1,52 @@
1
1
  module Conway
2
2
  class PotentialCellCollection
3
+ attr_reader :live_cell_lookup
4
+
3
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
9
+
4
10
  identify_potential_locations(live_locations)
5
11
  end
6
12
 
7
13
  def each_cell(&block)
8
- potential_cell_locations.each do |point, cell_loc|
9
- neighbors = neighbors_for(cell_loc)
10
- neighbor_cells = neighbors.map {|n| n.cell }
14
+ potential_cell_lookup.each do |cell_location|
15
+ neighbor_cells = neighbor_cells_for(cell_location)
11
16
 
12
- new_cell = block.call(cell_loc.cell, neighbor_cells)
17
+ next_cell = block.call(cell_location.cell, neighbor_cells)
13
18
 
14
- new_location = CellLocation.new(new_cell, cell_loc.point)
15
- potential_cell_locations[cell_loc.point] = new_location
19
+ if next_cell.alive?
20
+ live_cell_lookup.insert(cell_location)
21
+ end
16
22
  end
17
23
  end
18
24
 
19
- def live_cell_locations
20
- potential_cell_locations.values.select{|loc| loc.cell.kind_of?(LiveCell) }
21
- end
22
-
23
25
  private
24
- def potential_cell_locations
25
- @potential_cell_locations ||= Hash.new do |hash, point|
26
- CellLocation.new(DeadCell.new, point)
27
- end
28
- end
26
+ attr_accessor :potential_cell_lookup, :default_dead_cell
27
+ attr_writer :live_cell_lookup
29
28
 
30
29
  def identify_potential_locations(locations)
31
30
  locations.each do |loc|
32
- potential_cell_locations[loc.point] = loc
31
+ potential_cell_lookup.insert(loc)
33
32
  neighbors_for(loc).each do |neighbor|
34
- potential_cell_locations[neighbor.point] = neighbor
33
+ potential_cell_lookup.insert(neighbor)
35
34
  end
36
35
  end
37
36
  end
38
37
 
38
+ def dead_cell_location(point)
39
+ CellLocation.new(default_dead_cell, point)
40
+ end
41
+
39
42
  def neighbors_for(location)
40
43
  location.adjacent_points.map do |point|
41
- potential_cell_locations[point]
44
+ potential_cell_lookup.retrieve(point) || dead_cell_location(point)
42
45
  end
43
46
  end
47
+
48
+ def neighbor_cells_for(location)
49
+ neighbors_for(location).map {|n| n.cell }
50
+ end
44
51
  end
45
52
  end
@@ -0,0 +1,90 @@
1
+ require 'spec_helper'
2
+
3
+ include Conway
4
+
5
+ describe CellLocationLookup do
6
+ let(:point) { Point.new 1,1 }
7
+ let(:cell) { LiveCell.new }
8
+ let(:location) { CellLocation.new cell, point }
9
+ let(:lookup) { CellLocationLookup.new }
10
+
11
+ describe "#insert" do
12
+ it "accepts a CellLocation" do
13
+ lookup.insert location
14
+ end
15
+ end
16
+
17
+ describe "#retrieve" do
18
+ before { lookup.insert location }
19
+
20
+ it "looks up based on a point" do
21
+ lookup.retrieve(point).should eql(location)
22
+ end
23
+
24
+ it "looks up based on x,y" do
25
+ lookup.retrieve(1,1).should eql(location)
26
+ end
27
+ end
28
+
29
+ describe "#each" do
30
+ let(:point2) { Point.new 2,2 }
31
+ let(:location2) { CellLocation.new cell, point2 }
32
+
33
+ before do
34
+ lookup.insert location
35
+ lookup.insert location2
36
+ end
37
+
38
+ it "yields each location once" do
39
+ locations = []
40
+ lookup.each do |loc|
41
+ locations << loc
42
+ end
43
+
44
+ locations.should have(2).locations
45
+ locations.should include(location)
46
+ locations.should include(location2)
47
+ end
48
+ end
49
+
50
+ describe "#locations" do
51
+ let(:point2) { Point.new 2,2 }
52
+ let(:location2) { CellLocation.new cell, point2 }
53
+
54
+ before do
55
+ lookup.insert location
56
+ lookup.insert location2
57
+ end
58
+
59
+ it "is all the contained locations" do
60
+ lookup.locations.should have(2).locations
61
+ lookup.locations.should include(location)
62
+ lookup.locations.should include(location2)
63
+ end
64
+ end
65
+
66
+ describe "#count" do
67
+ let(:point2) { Point.new 2,2 }
68
+ let(:location2) { CellLocation.new cell, point2 }
69
+
70
+ before do
71
+ lookup.insert location
72
+ lookup.insert location2
73
+ end
74
+
75
+ it "is the number of contained locations" do
76
+ lookup.count.should == 2
77
+ end
78
+ end
79
+
80
+ describe "#empty?" do
81
+ context "when the count is 0" do
82
+ it { should be_empty }
83
+ end
84
+
85
+ context "when the count is 1" do
86
+ before { subject.insert location }
87
+ it { should_not be_empty }
88
+ end
89
+ end
90
+ end
@@ -11,8 +11,8 @@ describe CellSpace do
11
11
  let(:space) { CellSpace.new [Point.new(0,0)] }
12
12
  let(:rule_set) { RuleSet.new }
13
13
 
14
- it "returns a set of Points" do
15
- space.apply(rule_set).all?{|p| p.kind_of?(Point)}.should be_true
14
+ it "returns a CellLocationLookup" do
15
+ space.apply(rule_set).should be_a_kind_of(CellLocationLookup)
16
16
  end
17
17
  end
18
18
  end
@@ -6,6 +6,17 @@ describe Generation do
6
6
  let(:coordinates) { [Point.new(0,0), Point.new(1,1)] }
7
7
  let(:generation) { Generation.new(coordinates) }
8
8
 
9
+ describe "#initialize" do
10
+ it "accepts an array of Points" do
11
+ expect { generation.next }.to_not raise_error
12
+ end
13
+
14
+ it "accepts a CellLocationLookup" do
15
+ generation = Generation.new(CellLocationLookup.new)
16
+ expect { generation.next }.to_not raise_error
17
+ end
18
+ end
19
+
9
20
  describe "#cell_coordinates" do
10
21
  it "returns points of current live cells" do
11
22
  generation.cell_coordinates.should have(2).points
@@ -42,6 +42,10 @@ describe Point do
42
42
  point.x.should eql(1)
43
43
  point.y.should eql(3)
44
44
  end
45
+
46
+ it "returns self" do
47
+ point.update(4,2).should equal(point)
48
+ end
45
49
  end
46
50
  end
47
51
 
@@ -5,7 +5,8 @@ include Conway
5
5
  describe PotentialCellCollection do
6
6
  let(:live_cell) { LiveCell.new }
7
7
  let(:point) { Point.new(1,1) }
8
- let(:initial_cells) { [CellLocation.new(live_cell, point)] }
8
+ let(:cell_location) { CellLocation.new(live_cell, point) }
9
+ let(:initial_cells) { [cell_location] }
9
10
  let(:collection) { PotentialCellCollection.new(initial_cells) }
10
11
 
11
12
  describe "#each_cell" do
@@ -18,11 +19,38 @@ describe PotentialCellCollection do
18
19
  yielded_cells.should include(live_cell)
19
20
  end
20
21
 
21
- it "removes LiveCells that become DeadCells" do
22
- collection.each_cell do |cell, neighbors|
23
- DeadCell.new
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)
24
53
  end
25
- collection.live_cell_locations.should be_empty
26
54
  end
27
55
 
28
56
  context "neighbors" do
@@ -53,7 +81,13 @@ describe PotentialCellCollection do
53
81
  end
54
82
 
55
83
  it "includes all neighboring DeadCells" do
84
+ yielded_neighbors = nil
85
+ collection.each_cell do |cell, neighbors|
86
+ yielded_neighbors = neighbors
87
+ break
88
+ end
56
89
 
90
+ (yielded_neighbors - [neighbor_cell]).any?{|c| c.alive?}.should be_false
57
91
  end
58
92
  end
59
93
  end
metadata CHANGED
@@ -1,13 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: conway
3
3
  version: !ruby/object:Gem::Version
4
- hash: 25
5
4
  prerelease: false
6
5
  segments:
7
6
  - 0
8
- - 1
9
- - 1
10
- version: 0.1.1
7
+ - 2
8
+ - 0
9
+ version: 0.2.0
11
10
  platform: ruby
12
11
  authors:
13
12
  - Matt Yoho
@@ -15,7 +14,7 @@ autorequire:
15
14
  bindir: bin
16
15
  cert_chain: []
17
16
 
18
- date: 2011-01-20 00:00:00 -05:00
17
+ date: 2011-01-21 00:00:00 -05:00
19
18
  default_executable: conway
20
19
  dependencies:
21
20
  - !ruby/object:Gem::Dependency
@@ -26,7 +25,6 @@ dependencies:
26
25
  requirements:
27
26
  - - "="
28
27
  - !ruby/object:Gem::Version
29
- hash: 19
30
28
  segments:
31
29
  - 0
32
30
  - 1
@@ -47,15 +45,17 @@ files:
47
45
  - MIT-LICENSE
48
46
  - README.md
49
47
  - lib/conway/cell_location.rb
48
+ - lib/conway/cell_location_lookup.rb
50
49
  - lib/conway/cell_space.rb
51
50
  - lib/conway/dead_cell.rb
51
+ - lib/conway/driver/ascii.rb
52
52
  - lib/conway/generation.rb
53
53
  - lib/conway/live_cell.rb
54
54
  - lib/conway/point.rb
55
55
  - lib/conway/potential_cell_collection.rb
56
56
  - lib/conway/rule_set.rb
57
- - lib/conway/visualizer/ascii.rb
58
57
  - lib/conway.rb
58
+ - spec/conway/cell_location_lookup_spec.rb
59
59
  - spec/conway/cell_location_spec.rb
60
60
  - spec/conway/cell_space_spec.rb
61
61
  - spec/conway/dead_cell_spec.rb
@@ -80,7 +80,6 @@ required_ruby_version: !ruby/object:Gem::Requirement
80
80
  requirements:
81
81
  - - ">="
82
82
  - !ruby/object:Gem::Version
83
- hash: 3
84
83
  segments:
85
84
  - 0
86
85
  version: "0"
@@ -89,7 +88,6 @@ required_rubygems_version: !ruby/object:Gem::Requirement
89
88
  requirements:
90
89
  - - ">="
91
90
  - !ruby/object:Gem::Version
92
- hash: 3
93
91
  segments:
94
92
  - 0
95
93
  version: "0"
@@ -99,8 +97,9 @@ rubyforge_project:
99
97
  rubygems_version: 1.3.7
100
98
  signing_key:
101
99
  specification_version: 3
102
- summary: A simple implementation of Conway's Game of Life with an ASCII visualizer . This version has significant reference leaks.
100
+ summary: A simple implementation of Conway's Game of Life with an ASCII visualizer . This version has fewer significant reference leaks.
103
101
  test_files:
102
+ - spec/conway/cell_location_lookup_spec.rb
104
103
  - spec/conway/cell_location_spec.rb
105
104
  - spec/conway/cell_space_spec.rb
106
105
  - spec/conway/dead_cell_spec.rb
@@ -1,69 +0,0 @@
1
- require "conway"
2
-
3
- module Conway
4
- module Visualizer
5
- class Ascii
6
- def initialize(size, starting_cells, loop_interval=0.25)
7
- self.max_x = self.max_y = size
8
- self.starting_cells = starting_cells
9
- self.loop_interval = loop_interval
10
- end
11
-
12
- def loop
13
- generation = Generation.new(starting_cells)
14
- start = Time.now
15
-
16
- puts "They live!!\n\n"
17
-
18
- begin
19
- live_cells = generation.cell_coordinates
20
-
21
- if live_cells.count == 0
22
- puts "\nThey have all perished! D-:"
23
- break
24
- end
25
-
26
- grid = ""
27
- (1..max_y).each do |y|
28
- (1..max_x).each do |x|
29
- cell_char = cell_content_for(live_cells, x,y)
30
- grid << "|#{cell_char}"
31
- end
32
- grid << "|\n"
33
- end
34
-
35
- grid << "\n"
36
-
37
- grid << "Total objects: #{live_object_count} "
38
- grid << "Total living cells: #{live_cells.count}\n"
39
-
40
- elapsed_minutes, elapsed_seconds = ((Time.now - start).to_i).divmod 60
41
- grid << "Elapsed time: #{elapsed_minutes} min, #{elapsed_seconds} secs\n"
42
-
43
- yield grid if block_given?
44
-
45
- sleep(loop_interval)
46
- end while(generation = generation.next)
47
- end
48
-
49
- private
50
- attr_accessor :max_x, :max_y, :starting_cells, :loop_interval
51
-
52
- def cell_content_for(points, x,y)
53
- @comparison_point ||= Point.new(x,y)
54
- @comparison_point.update(x,y)
55
- points.detect {|p| p == @comparison_point } ? "X" : " "
56
- end
57
-
58
- def live_object_count
59
- if ObjectSpace.respond_to?(:count_objects)
60
- # Ruby 1.9.2
61
- ObjectSpace.count_objects[:TOTAL]
62
- else
63
- # Ruby 1.8.7
64
- ObjectSpace.live_objects
65
- end
66
- end
67
- end
68
- end
69
- end