conway 0.1.1 → 0.2.0

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