conway 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2011 Matt Yoho
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
@@ -0,0 +1,15 @@
1
+ ## Conway
2
+ A simple Game of Live implementation with formost focus on object design.
3
+
4
+ Currently very much leaky of object references.
5
+
6
+ Conway comes with a very simple ASCII visualize. It can be invoked like so,
7
+ where the size argument determines the dimensions of the square cell grid:
8
+
9
+ conway --size 25 --cells="2,3 3,3 2,2 5,4 5,8 9,8, 7,6"
10
+ conway -s 30 -c 2,3:3,3:3,2:2,2:3,4
11
+
12
+ Please feel free to send feedback via the GitHub project or via email at
13
+ mby _AT_ mattyoho _dot_ com.
14
+
15
+ https://github.com/mattyoho/conway
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Useful for running the script from the project directory
4
+ lib_dir = File.join(File.dirname(__FILE__), '..', 'lib')
5
+ $LOAD_PATH.unshift lib_dir if File.directory?(lib_dir)
6
+
7
+ require 'choice'
8
+ require 'conway/visualizer/ascii'
9
+
10
+ PROGRAM_VERSION = 1
11
+
12
+ module ConwayAscii
13
+ def self.suppress_warnings
14
+ original_verbosity = $VERBOSE
15
+ begin
16
+ $VERBOSE = nil
17
+ yield
18
+ ensure
19
+ $VERBOSE = original_verbosity
20
+ end
21
+ end
22
+
23
+ def self.parse_points(point_str)
24
+ point_str.split(/\s|;|:/).map do |pt|
25
+ x,y = pt.split(',').map {|d| d.to_i }
26
+ Conway::Point.new x,y
27
+ end
28
+ end
29
+
30
+ def self.exit
31
+ puts "\nExiting...\n"
32
+ Kernel::exit
33
+ end
34
+ end
35
+
36
+ ConwayAscii.suppress_warnings do
37
+ Choice.options do
38
+ header 'Conway Game of Life ASCII visualizer'
39
+ header 'Available options:'
40
+
41
+ option :size do
42
+ short '-s'
43
+ long '--size=SIZE'
44
+ desc 'The size of the game grid, specified as the length of one side'
45
+ cast Integer
46
+ default 30
47
+ end
48
+
49
+ option :cells do
50
+ short '-c'
51
+ long '--cells=CELLS'
52
+ desc 'Starting points for live cells, comma-seperated x,y pairs, seperated by spaces, colons, or semicolons'
53
+ default '1,2 2,1'
54
+ end
55
+
56
+ option :help do
57
+ long '--help'
58
+ desc 'Print this help message'
59
+ end
60
+ end
61
+ end
62
+
63
+ Signal.trap("TERM") do
64
+ ConwayAscii.exit
65
+ end
66
+
67
+ Signal.trap("INT") do
68
+ ConwayAscii.exit
69
+ end
70
+
71
+ live_points = ConwayAscii.parse_points Choice[:cells]
72
+ grid = Conway::Visualizer::Ascii.new(Choice[:size], live_points)
73
+
74
+ grid.loop do |step|
75
+ puts step
76
+ end
@@ -0,0 +1,8 @@
1
+ require 'conway/generation'
2
+ require 'conway/live_cell'
3
+ require 'conway/dead_cell'
4
+ require 'conway/point'
5
+ require 'conway/cell_location'
6
+ require 'conway/potential_cell_collection'
7
+ require 'conway/rule_set'
8
+ require 'conway/cell_space'
@@ -0,0 +1,14 @@
1
+ module Conway
2
+ class CellLocation
3
+ attr_reader :cell, :point
4
+
5
+ def initialize(cell, point)
6
+ @cell = cell
7
+ @point = point
8
+ end
9
+
10
+ def adjacent_points
11
+ point.adjacents
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,23 @@
1
+ module Conway
2
+ class CellSpace
3
+ def initialize(points)
4
+ self.potential_cells = PotentialCellCollection.new(live_locations(points))
5
+ end
6
+
7
+ def apply(rule_set)
8
+ potential_cells.each_cell do |cell, neighbors|
9
+ rule_set.apply(cell, neighbors)
10
+ end
11
+ potential_cells.live_cell_locations.map {|cell_loc| cell_loc.point }
12
+ end
13
+
14
+ private
15
+ 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
+ end
23
+ end
@@ -0,0 +1,9 @@
1
+ module Conway
2
+ class DeadCell
3
+
4
+ def alive?
5
+ false
6
+ end
7
+
8
+ end
9
+ end
@@ -0,0 +1,16 @@
1
+ module Conway
2
+ class Generation
3
+ attr_reader :cell_coordinates, :rule_set
4
+
5
+ def initialize(cell_coordinates=[], rule_set=RuleSet.new)
6
+ @cell_coordinates = cell_coordinates
7
+ @rule_set = rule_set
8
+ end
9
+
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
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,9 @@
1
+ module Conway
2
+ class LiveCell
3
+
4
+ def alive?
5
+ true
6
+ end
7
+
8
+ end
9
+ end
@@ -0,0 +1,29 @@
1
+ module Conway
2
+ class Point
3
+ attr_reader :x, :y
4
+
5
+ def initialize(x=0,y=0)
6
+ @x,@y = x,y
7
+ end
8
+
9
+ def eql?(other)
10
+ self.x == other.x && self.y == other.y
11
+ end
12
+
13
+ def ==(other)
14
+ eql? other
15
+ end
16
+
17
+ def hash
18
+ :"#{x}-#{y}".object_id
19
+ end
20
+
21
+ def adjacents
22
+ grid = (-1..1).map do |j|
23
+ (-1..1).map do |i|
24
+ Point.new(x+i, y+j) unless i == 0 && j == 0
25
+ end
26
+ end.flatten.compact
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,45 @@
1
+ module Conway
2
+ class PotentialCellCollection
3
+ def initialize(live_locations)
4
+ identify_potential_locations(live_locations)
5
+ end
6
+
7
+ 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 }
11
+
12
+ new_cell = block.call(cell_loc.cell, neighbor_cells)
13
+
14
+ new_location = CellLocation.new(new_cell, cell_loc.point)
15
+ potential_cell_locations[cell_loc.point] = new_location
16
+ end
17
+ end
18
+
19
+ def live_cell_locations
20
+ potential_cell_locations.values.select{|loc| loc.cell.kind_of?(LiveCell) }
21
+ end
22
+
23
+ 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
29
+
30
+ def identify_potential_locations(locations)
31
+ locations.each do |loc|
32
+ potential_cell_locations[loc.point] = loc
33
+ neighbors_for(loc).each do |neighbor|
34
+ potential_cell_locations[neighbor.point] = neighbor
35
+ end
36
+ end
37
+ end
38
+
39
+ def neighbors_for(location)
40
+ location.adjacent_points.map do |point|
41
+ potential_cell_locations[point]
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,33 @@
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
@@ -0,0 +1,57 @@
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
+ new_points = generation.cell_coordinates
20
+
21
+ if new_points.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(new_points, x,y)
30
+ grid << "|#{cell_char}"
31
+ end
32
+ grid << "|\n"
33
+ end
34
+
35
+ grid << "\n"
36
+
37
+ grid << "Total objects: #{ObjectSpace.count_objects[:TOTAL]} "
38
+ grid << "Total LiveCells: #{new_points.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
+ points.detect {|p| p.x == x && p.y == y } ? "X" : " "
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,21 @@
1
+ require 'spec_helper'
2
+
3
+ include Conway
4
+
5
+ describe CellLocation do
6
+ let(:cell_location) { CellLocation.new(LiveCell.new, Point.new(1,1)) }
7
+
8
+ it "has a cell" do
9
+ cell_location.cell.should be_a_kind_of(LiveCell)
10
+ end
11
+
12
+ it "has a point" do
13
+ cell_location.point.should be_a_kind_of(Point)
14
+ end
15
+
16
+ describe "#adjacent_points" do
17
+ it "is returns its point's adjacents" do
18
+ cell_location.adjacent_points.should == Point.new(1,1).adjacents
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,18 @@
1
+ require 'spec_helper'
2
+
3
+ include Conway
4
+
5
+ describe CellSpace do
6
+ it "accepts a collection of points" do
7
+ CellSpace.new [Point.new(0,0)]
8
+ end
9
+
10
+ describe "#apply" do
11
+ let(:space) { CellSpace.new [Point.new(0,0)] }
12
+ let(:rule_set) { RuleSet.new }
13
+
14
+ it "returns a set of Points" do
15
+ space.apply(rule_set).all?{|p| p.kind_of?(Point)}.should be_true
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,9 @@
1
+ require 'spec_helper'
2
+
3
+ describe Conway::DeadCell do
4
+
5
+ it "lives not" do
6
+ Conway::DeadCell.new.should_not be_alive
7
+ end
8
+
9
+ end
@@ -0,0 +1,26 @@
1
+ require 'spec_helper'
2
+
3
+ include Conway
4
+
5
+ describe Generation do
6
+ let(:coordinates) { [Point.new(0,0), Point.new(1,1)] }
7
+ let(:generation) { Generation.new(coordinates) }
8
+
9
+ describe "#cell_coordinates" do
10
+ it "returns points of current live cells" do
11
+ generation.cell_coordinates.should have(2).points
12
+ generation.cell_coordinates.should == coordinates
13
+ end
14
+ end
15
+
16
+ describe '#next' do
17
+ it 'returns the next Generation' do
18
+ generation.next.should be_a_kind_of(Generation)
19
+ end
20
+
21
+ it "that generation doesn't blow up" do
22
+ next_generation = generation.next
23
+ expect { next_generation.next }.to_not raise_error
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,8 @@
1
+ require 'spec_helper'
2
+
3
+ describe Conway::LiveCell do
4
+
5
+ it "lives" do
6
+ Conway::LiveCell.new.should be_alive
7
+ end
8
+ end
@@ -0,0 +1,38 @@
1
+ require 'spec_helper'
2
+
3
+ include Conway
4
+
5
+ describe Point do
6
+ let(:point) { Point.new 1,2 }
7
+
8
+ it "has x,y coordinates" do
9
+ point.x.should eql(1)
10
+ point.y.should eql(2)
11
+ end
12
+
13
+ describe "equality" do
14
+ it "should equal another point with the same coordinates" do
15
+ Point.new(1,2).should eql(Point.new(1,2))
16
+ Point.new(1,2).should == Point.new(1,2)
17
+ end
18
+ end
19
+
20
+ describe "#adjacents" do
21
+ let(:point) { Point.new(2,2) }
22
+ let(:neighbors) do
23
+ [Point.new(1,1),
24
+ Point.new(2,1),
25
+ Point.new(3,1),
26
+ Point.new(1,2),
27
+ Point.new(3,2),
28
+ Point.new(1,3),
29
+ Point.new(2,3),
30
+ Point.new(3,3)]
31
+ end
32
+
33
+ it "returns the eight adjacent points" do
34
+ point.adjacents.should == neighbors
35
+ end
36
+ end
37
+ end
38
+
@@ -0,0 +1,60 @@
1
+ require 'spec_helper'
2
+
3
+ include Conway
4
+
5
+ describe PotentialCellCollection do
6
+ let(:live_cell) { LiveCell.new }
7
+ let(:point) { Point.new(1,1) }
8
+ let(:initial_cells) { [CellLocation.new(live_cell, point)] }
9
+ let(:collection) { PotentialCellCollection.new(initial_cells) }
10
+
11
+ describe "#each_cell" do
12
+ it "contains the initial set of LiveCells" do
13
+ yielded_cells = []
14
+ collection.each_cell do |cell, dont_care|
15
+ yielded_cells << cell
16
+ cell
17
+ end
18
+ yielded_cells.should include(live_cell)
19
+ end
20
+
21
+ it "removes LiveCells that become DeadCells" do
22
+ collection.each_cell do |cell, neighbors|
23
+ DeadCell.new
24
+ end
25
+ collection.live_cell_locations.should be_empty
26
+ end
27
+
28
+ context "neighbors" do
29
+ let(:neighbor_cell) { LiveCell.new }
30
+ let(:initial_cells) do
31
+ [CellLocation.new(live_cell, point),
32
+ CellLocation.new(neighbor_cell, Point.new(1,2))]
33
+ end
34
+
35
+ it "yields the neighboring LiveCells" do
36
+ yielded_neighbors = nil
37
+ collection.each_cell do |cell, neighbors|
38
+ yielded_neighbors = neighbors
39
+ break
40
+ end
41
+
42
+ yielded_neighbors.should include(neighbor_cell)
43
+ end
44
+
45
+ it "yields the neighboring DeadCells" do
46
+ yielded_neighbors = nil
47
+ collection.each_cell do |cell, neighbors|
48
+ yielded_neighbors = neighbors
49
+ break
50
+ end
51
+
52
+ (yielded_neighbors - [neighbor_cell]).should have(7).neighbors
53
+ end
54
+
55
+ it "includes all neighboring DeadCells" do
56
+
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,44 @@
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
@@ -0,0 +1,5 @@
1
+ require 'conway'
2
+
3
+ RSpec.configure do |config|
4
+ config.mock_with :rspec
5
+ end
metadata ADDED
@@ -0,0 +1,108 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: conway
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 0
9
+ version: 0.1.0
10
+ platform: ruby
11
+ authors:
12
+ - Matt Yoho
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2011-01-19 00:00:00 -05:00
18
+ default_executable: conway
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: choice
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - "="
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 0
30
+ - 1
31
+ - 4
32
+ version: 0.1.4
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ description: "\n A simple implementation of Conway's Game of Life with an ASCII visualizer\n "
36
+ email: mby@mattyoho.com
37
+ executables:
38
+ - conway
39
+ extensions: []
40
+
41
+ extra_rdoc_files:
42
+ - MIT-LICENSE
43
+ - README.md
44
+ files:
45
+ - MIT-LICENSE
46
+ - README.md
47
+ - lib/conway/cell_location.rb
48
+ - lib/conway/cell_space.rb
49
+ - lib/conway/dead_cell.rb
50
+ - lib/conway/generation.rb
51
+ - lib/conway/live_cell.rb
52
+ - lib/conway/point.rb
53
+ - lib/conway/potential_cell_collection.rb
54
+ - lib/conway/rule_set.rb
55
+ - lib/conway/visualizer/ascii.rb
56
+ - lib/conway.rb
57
+ - spec/conway/cell_location_spec.rb
58
+ - spec/conway/cell_space_spec.rb
59
+ - spec/conway/dead_cell_spec.rb
60
+ - spec/conway/generation_spec.rb
61
+ - spec/conway/live_cell_spec.rb
62
+ - spec/conway/point_spec.rb
63
+ - spec/conway/potential_cell_collection_spec.rb
64
+ - spec/conway/rule_set_spec.rb
65
+ - spec/spec_helper.rb
66
+ - bin/conway
67
+ has_rdoc: true
68
+ homepage: http://github.com/mattyoho/conway
69
+ licenses: []
70
+
71
+ post_install_message:
72
+ rdoc_options:
73
+ - --charset=UTF-8
74
+ require_paths:
75
+ - lib
76
+ required_ruby_version: !ruby/object:Gem::Requirement
77
+ none: false
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ segments:
82
+ - 0
83
+ version: "0"
84
+ required_rubygems_version: !ruby/object:Gem::Requirement
85
+ none: false
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ segments:
90
+ - 0
91
+ version: "0"
92
+ requirements: []
93
+
94
+ rubyforge_project:
95
+ rubygems_version: 1.3.7
96
+ signing_key:
97
+ specification_version: 3
98
+ summary: A simple implementation of Conway's Game of Life with an ASCII visualizer . This version has significant reference leaks.
99
+ test_files:
100
+ - spec/conway/cell_location_spec.rb
101
+ - spec/conway/cell_space_spec.rb
102
+ - spec/conway/dead_cell_spec.rb
103
+ - spec/conway/generation_spec.rb
104
+ - spec/conway/live_cell_spec.rb
105
+ - spec/conway/point_spec.rb
106
+ - spec/conway/potential_cell_collection_spec.rb
107
+ - spec/conway/rule_set_spec.rb
108
+ - spec/spec_helper.rb