life_game_viewer 0.9.1
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/.gitignore +19 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +233 -0
- data/Rakefile +2 -0
- data/bin/life_view_sample +6 -0
- data/lib/life_game_viewer/model/life_calculator.rb +103 -0
- data/lib/life_game_viewer/model/life_visualizer.rb +19 -0
- data/lib/life_game_viewer/model/model_validation.rb +41 -0
- data/lib/life_game_viewer/model/my_life_model.rb +56 -0
- data/lib/life_game_viewer/model/sample_life_model.rb +132 -0
- data/lib/life_game_viewer/version.rb +3 -0
- data/lib/life_game_viewer/view/actions.rb +193 -0
- data/lib/life_game_viewer/view/clipboard_helper.rb +55 -0
- data/lib/life_game_viewer/view/generations.rb +67 -0
- data/lib/life_game_viewer/view/life_game_viewer_frame.rb +293 -0
- data/lib/life_game_viewer/view/life_table_model.rb +97 -0
- data/lib/life_game_viewer/view/main.rb +16 -0
- data/lib/life_game_viewer.rb +27 -0
- data/life_game_viewer.gemspec +17 -0
- data/resources/images/alfred-e-neuman.jpg +0 -0
- data/spec/model/life_calculator_spec.rb +110 -0
- data/spec/model/life_visualizer_spec.rb +20 -0
- data/spec/model/model_validation_spec.rb +51 -0
- data/spec/model/sample_life_model_spec.rb +108 -0
- data/spec/spec_helper.rb +1 -0
- data/spec/view/generations_spec.rb +19 -0
- metadata +79 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Keith Bennett
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,233 @@
|
|
1
|
+
Game of Life Viewer
|
2
|
+
===================
|
3
|
+
|
4
|
+
This is a JRuby application that calculates and displays generations
|
5
|
+
of Conway's Game of Life.
|
6
|
+
It uses Java's Swing UI library and is an entirely client side application,
|
7
|
+
so there is no need for a web server, browser, or even network connection.
|
8
|
+
|
9
|
+
The game itself (as opposed to the viewer) is often used as a programming
|
10
|
+
exercise. More information on the game is at
|
11
|
+
http://en.wikipedia.org/wiki/Conway%27s_Game_of_Life.
|
12
|
+
|
13
|
+
My intention in writing this was to provide a GUI player
|
14
|
+
with which developers could:
|
15
|
+
|
16
|
+
<ol>
|
17
|
+
<li> view and test their implementations of the Game of Life exercise</li>
|
18
|
+
<li> easily inspect the results of different data inputs into the game</li>
|
19
|
+
</ol>
|
20
|
+
|
21
|
+
(Note: The instructions below assume use of a Unix command line
|
22
|
+
(e.g. Linux, Mac OS) and rvm. If you're using Windows,
|
23
|
+
make the appropriate substitutions, such as '\' for '/', 'ren' for 'mv'.
|
24
|
+
Also, please see the troubleshooting section below if you have
|
25
|
+
problems running the program.)
|
26
|
+
|
27
|
+
|
28
|
+
JRuby and Java
|
29
|
+
==============
|
30
|
+
|
31
|
+
This program will only run in [JRuby] [3] (which needs the Java Runtime Environment),
|
32
|
+
so you'll need to make sure you have both installed.
|
33
|
+
The easiest way to install and use JRuby is with [rvm] [4], which you
|
34
|
+
can only do with a Unix-like shell. Linux or Mac OS will easily work; for Windows,
|
35
|
+
you might be able to get it to work with [Cygwin] [5].
|
36
|
+
|
37
|
+
|
38
|
+
1.9 Required
|
39
|
+
============
|
40
|
+
|
41
|
+
This program requires that JRuby be run in 1.9 mode. In JRuby versions 1.7
|
42
|
+
and above, this is the default setting, but for earlier versions
|
43
|
+
you'll have to specify this mode by passing the "--1.9" option to JRuby.
|
44
|
+
It's probably easiest to do this by putting the following into your startup
|
45
|
+
shell's initialization file (e.g. .bashrc or .zshrc):
|
46
|
+
|
47
|
+
```
|
48
|
+
export JRUBY_OPTS=--1.9
|
49
|
+
```
|
50
|
+
|
51
|
+
You could do this on your command line instead by preceding your JRuby commands with
|
52
|
+
the setting, as in:
|
53
|
+
|
54
|
+
```
|
55
|
+
JRUBY_OPTS=--1.9 jruby ...
|
56
|
+
```
|
57
|
+
|
58
|
+
|
59
|
+
Running With the Provided Sample Model
|
60
|
+
--------------------------------------
|
61
|
+
|
62
|
+
It's fine to use a downloaded copy of the source tree directly,
|
63
|
+
but using it as a gem will probably be simpler.
|
64
|
+
|
65
|
+
Here is how to run it with the provided model and provided sample data.
|
66
|
+
First, install the life_game_viewer gem. This installs a script that
|
67
|
+
you can then run on your command line:
|
68
|
+
|
69
|
+
```
|
70
|
+
life_view_sample
|
71
|
+
```
|
72
|
+
|
73
|
+
You can experiment with different data sets by:
|
74
|
+
|
75
|
+
1) using the clipboard copy and paste feature
|
76
|
+
(see _Reading and Writing Game Data Via the Clipboard_ below)
|
77
|
+
|
78
|
+
3) (of course) modifying the source code
|
79
|
+
|
80
|
+
|
81
|
+
|
82
|
+
Viewing Your Own Game of Life Model Implementation
|
83
|
+
--------------------------------------------------
|
84
|
+
|
85
|
+
In order to do the exercise, you will need to replace the
|
86
|
+
[SampleLifeModel] [6] implementation with your own. Your model will need to
|
87
|
+
respond appropriately to the SampleLifeModel's public method names, because
|
88
|
+
they are called by the viewer, but you can implement them any way you
|
89
|
+
want, even using the MyLifeModel as a minimal adapter to a completely
|
90
|
+
different design. (To take this to the extreme, the model could even
|
91
|
+
be implemented in Java, with a thin JRuby adapter around it; or, as
|
92
|
+
a RESTful web service in any arbitrary language with the adapter
|
93
|
+
making calls to it.)
|
94
|
+
|
95
|
+
A [MyLifeModel] [7] skeleton file is provided in the
|
96
|
+
lib/model directory as a convenient starting point for you.
|
97
|
+
You can copy this file into your own working area.
|
98
|
+
|
99
|
+
In your program, all you would need to do is to require life_game_viewer
|
100
|
+
and pass an instance of your model to the LifeGameViewer.view method.
|
101
|
+
For example:
|
102
|
+
|
103
|
+
```ruby
|
104
|
+
require 'life_game_viewer'
|
105
|
+
model = SampleLifeModel.create(5,5) { |r,c| r.even? } # as an example
|
106
|
+
LifeGameViewer.view(model)
|
107
|
+
```
|
108
|
+
|
109
|
+
|
110
|
+
Where to Find This Software
|
111
|
+
---------------------------
|
112
|
+
|
113
|
+
This software is located on GitHub at
|
114
|
+
https://github.com/keithrbennett/life_game_viewer.
|
115
|
+
There is also an [article] [1] about this application on my [blog] [2].
|
116
|
+
|
117
|
+
|
118
|
+
Reading and Writing Game Data Via the Clipboard
|
119
|
+
-----------------------------------------------
|
120
|
+
|
121
|
+
The application starts with a sample data set that can be easily modified in the code.
|
122
|
+
You can also use the provided buttons to use the system clipboard to load and save
|
123
|
+
game data. You use the same keys you would normally use for copying pasting,
|
124
|
+
that is, _Command_ c and v on a Mac, and _Ctrl_ c and v on other systems. (Note: there
|
125
|
+
are currently problems using these keystrokes on Linux and Windows; for now,
|
126
|
+
please click the buttons.)
|
127
|
+
|
128
|
+
Data is represented as follows:
|
129
|
+
|
130
|
+
* The data is a single string of lines, each line representing a row in the matrix
|
131
|
+
* Alive (true) values are represented as asterisks ('*'), and false values are hyphens.
|
132
|
+
|
133
|
+
For example, the two lines below:
|
134
|
+
|
135
|
+
```
|
136
|
+
*-
|
137
|
+
-*
|
138
|
+
```
|
139
|
+
|
140
|
+
...represent a 2 x 2 matrix in which only the upper left and
|
141
|
+
lower right cells are alive. The final row's new line is optional.
|
142
|
+
|
143
|
+
When you copy a new game's data into the application, it clears all other data and
|
144
|
+
uses that as generation #0.
|
145
|
+
|
146
|
+
The clipboard functionality enables you to edit game data by doing the following:
|
147
|
+
|
148
|
+
* copy the game's current data into the clipboard
|
149
|
+
* paste it into an editor window
|
150
|
+
* edit it
|
151
|
+
* 'select all' it
|
152
|
+
* copy it to your clipboard
|
153
|
+
* paste it back into the game
|
154
|
+
|
155
|
+
In many cases, it will be easier to generate the string programmatically,
|
156
|
+
either in the program itself, or in irb.
|
157
|
+
|
158
|
+
|
159
|
+
Navigating the Generations
|
160
|
+
--------------------------
|
161
|
+
|
162
|
+
There are buttons to help you navigate the generations:
|
163
|
+
_First_, _Previous_, _Next_, and _Last_.
|
164
|
+
There are keystroke equivalents for your convenience, all numbers
|
165
|
+
so that you can put your fingers on the number row of the
|
166
|
+
keyboard to do all your navigation. The numbers _1_, _4_, _7_, and _0_
|
167
|
+
correspond to _First_, _Previous_, _Next_, and _Last_, respectively.
|
168
|
+
|
169
|
+
The Game of Life next generation calculation algorithm
|
170
|
+
only considers the most recent generation as input, so
|
171
|
+
if there are two consecutive generations that are identical,
|
172
|
+
all subsequent ones will be identical as well. The viewer will
|
173
|
+
consider the first of consecutive identical generations to be
|
174
|
+
the end of the lineage.
|
175
|
+
|
176
|
+
You can have the viewer find this last generation for you
|
177
|
+
by pressing the _Last_ button. Be careful, though --
|
178
|
+
this operation runs on the main UI thread and I haven't
|
179
|
+
gotten around to enabling its interruption --
|
180
|
+
so be prepared to kill the application if it no longer responds.
|
181
|
+
You can normally do this by pressing _Ctrl-C_ on the command line
|
182
|
+
from which you started the program.
|
183
|
+
|
184
|
+
|
185
|
+
Troubleshooting
|
186
|
+
---------------
|
187
|
+
|
188
|
+
The most common problems will probably be related to the installation and use of JRuby,
|
189
|
+
and the unintentional use of MRI Ruby instead of JRuby.
|
190
|
+
|
191
|
+
To see which version of Ruby you're using, use the '-v' option for ruby or jruby:
|
192
|
+
|
193
|
+
```
|
194
|
+
>ruby -v
|
195
|
+
jruby 1.6.7 (ruby-1.9.2-p312) (2012-02-22 3e82bc8) (Java HotSpot(TM) 64-Bit Server VM 1.6.0_33) [darwin-x86_64-java]
|
196
|
+
```
|
197
|
+
|
198
|
+
Another test is to try to require 'java'. When you see the error in the last command below,
|
199
|
+
you know that you're not using JRuby:
|
200
|
+
|
201
|
+
```
|
202
|
+
>ruby -v
|
203
|
+
jruby 1.6.7 (ruby-1.9.2-p312) (2012-02-22 3e82bc8) (Java HotSpot(TM) 64-Bit Server VM 1.6.0_33) [darwin-x86_64-java]
|
204
|
+
|
205
|
+
>ruby -e "require 'java'"
|
206
|
+
|
207
|
+
>rvm 1.9
|
208
|
+
|
209
|
+
>ruby -e "require 'java'"
|
210
|
+
/Users/keithb/.rvm/rubies/ruby-1.9.3-p125/lib/ruby/site_ruby/1.9.1/rubygems/custom_require.rb:36:
|
211
|
+
in `require': cannot load such file -- java (LoadError)
|
212
|
+
```
|
213
|
+
|
214
|
+
|
215
|
+
Feedback
|
216
|
+
--------
|
217
|
+
|
218
|
+
Constructive feedback is always welcome, even for little things.
|
219
|
+
|
220
|
+
|
221
|
+
License
|
222
|
+
-------
|
223
|
+
|
224
|
+
This software is released under the MIT/X11 license.
|
225
|
+
|
226
|
+
|
227
|
+
[1]: http://www.bbs-software.com/blog/2012/09/05/conways-game-of-life-viewer/ "http://www.bbs-software.com/blog/2012/09/05/conways-game-of-life-viewer/"
|
228
|
+
[2]: http://www.bbs-software.com/blog/ "http://www.bbs-software.com/blog/"
|
229
|
+
[3]: http://jruby.org/ "http://jruby.org/"
|
230
|
+
[4]: https://rvm.io/rvm/install/ "https://rvm.io/rvm/install/"
|
231
|
+
[5]: http://www.cygwin.com/ "http://www.cygwin.com/"
|
232
|
+
[6]: https://github.com/keithrbennett/life_game_viewer/blob/master/lib/life_game_viewer/model/sample_life_model.rb "https://github.com/keithrbennett/life_game_viewer/blob/master/lib/life_game_viewer/model/sample_life_model.rb"
|
233
|
+
[7]: https://github.com/keithrbennett/life_game_viewer/blob/master/lib/life_game_viewer/model/my_life_model.rb "https://github.com/keithrbennett/life_game_viewer/blob/master/lib/life_game_viewer/model/my_life_model.rb"
|
data/Rakefile
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
# Performs calculations relating to determination of a cell's neighbors
|
2
|
+
# and the next generation. Generally, 'next_generation' will be the
|
3
|
+
# only method that will need to be called, but the others are provided
|
4
|
+
# publicly, since the Game of Life is all about experimentation.
|
5
|
+
#
|
6
|
+
# See Wikipedia for more information about Conway's Game of Life.
|
7
|
+
#
|
8
|
+
# Rules distilled:
|
9
|
+
#
|
10
|
+
# 1) Any live cell with fewer than two live neighbours dies, as if caused by under-population.
|
11
|
+
# 2) Any live cell with two or three live neighbours lives on to the next generation.
|
12
|
+
# 3) Any live cell with more than three live neighbours dies, as if by overcrowding.
|
13
|
+
# 4) Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction.
|
14
|
+
|
15
|
+
class LifeCalculator
|
16
|
+
|
17
|
+
# Returns a new model with the next generation's data.
|
18
|
+
def next_generation(old_model)
|
19
|
+
old_model.class.send(:create, old_model.row_count, old_model.column_count) do |row, col|
|
20
|
+
should_live(old_model, row, col)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Returns an array of [row, col] tuples corresponding to the cells
|
25
|
+
# neighboring the specified cell location. "Neighbor" is defined
|
26
|
+
# as a cell with up/down/left/right/diagonal adjacency to the specified cell.
|
27
|
+
def neighbors(model, row, col)
|
28
|
+
|
29
|
+
neighbors = []
|
30
|
+
|
31
|
+
at_left_edge = col == 0
|
32
|
+
at_right_edge = col == model.column_count - 1
|
33
|
+
at_top_edge = row == 0
|
34
|
+
at_bottom_edge = row == model.row_count - 1
|
35
|
+
|
36
|
+
col_to_left = col - 1
|
37
|
+
col_to_right = col + 1
|
38
|
+
row_above = row - 1
|
39
|
+
row_below = row + 1
|
40
|
+
|
41
|
+
# In its own row, return the cell to the left and right as possible.
|
42
|
+
unless at_left_edge
|
43
|
+
neighbors << [row, col_to_left]
|
44
|
+
end
|
45
|
+
unless at_right_edge
|
46
|
+
neighbors << [row, col_to_right]
|
47
|
+
end
|
48
|
+
|
49
|
+
# Process the row above
|
50
|
+
unless at_top_edge
|
51
|
+
unless at_left_edge
|
52
|
+
neighbors << [row_above, col_to_left]
|
53
|
+
end
|
54
|
+
neighbors << [row_above, col]
|
55
|
+
unless at_right_edge
|
56
|
+
neighbors << [row_above, col_to_right]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Process the row below
|
61
|
+
unless at_bottom_edge
|
62
|
+
unless at_left_edge
|
63
|
+
neighbors << [row_below, col_to_left]
|
64
|
+
end
|
65
|
+
neighbors << [row_below, col]
|
66
|
+
unless at_right_edge
|
67
|
+
neighbors << [row_below, col_to_right]
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
neighbors
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
# Returns an array of [row, col] tuples corresponding to those
|
77
|
+
# neighbor cells that are alive.
|
78
|
+
def num_living_neighbors(model, row, col)
|
79
|
+
neighbors(model, row, col).inject(0) do |num_living, neighbor|
|
80
|
+
neighbor_row, neighbor_column = neighbor
|
81
|
+
num_living += 1 if model.alive?(neighbor_row, neighbor_column)
|
82
|
+
num_living
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
# Returns whether or not (as true or false) the specified cell
|
88
|
+
# should continue to live in the next generation.
|
89
|
+
def should_live(model, row, col)
|
90
|
+
model.alive?(row, col) \
|
91
|
+
? live_cell_should_continue_to_live(model, row, col) \
|
92
|
+
: dead_cell_should_become_alive(model, row, col)
|
93
|
+
end
|
94
|
+
|
95
|
+
def live_cell_should_continue_to_live(model, row, col)
|
96
|
+
(2..3).include?(num_living_neighbors(model, row, col))
|
97
|
+
end
|
98
|
+
|
99
|
+
def dead_cell_should_become_alive(model, row, col)
|
100
|
+
num_living_neighbors(model, row, col) == 3
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# Manages the string display of a life model.
|
2
|
+
|
3
|
+
class LifeVisualizer
|
4
|
+
|
5
|
+
# Returns a string representation of a LifeModel.
|
6
|
+
def to_display_string(model)
|
7
|
+
output = ''
|
8
|
+
|
9
|
+
(0...model.row_count).each do |x|
|
10
|
+
(0...model.column_count).each do |y|
|
11
|
+
alive_as_string = model.alive?(x, y) ? '*' : '-'
|
12
|
+
output << alive_as_string
|
13
|
+
end
|
14
|
+
output << "\n"
|
15
|
+
end
|
16
|
+
|
17
|
+
output
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
class ModelValidation
|
2
|
+
|
3
|
+
def required_class_methods
|
4
|
+
[
|
5
|
+
:create_from_string
|
6
|
+
]
|
7
|
+
end
|
8
|
+
|
9
|
+
def required_instance_methods
|
10
|
+
[
|
11
|
+
:row_count,
|
12
|
+
:column_count,
|
13
|
+
:alive?,
|
14
|
+
:set_living_state,
|
15
|
+
:set_living_states,
|
16
|
+
:next_generation_model,
|
17
|
+
:number_living
|
18
|
+
]
|
19
|
+
end
|
20
|
+
|
21
|
+
def class_methods_missing(instance)
|
22
|
+
required_class_methods - instance.class.methods
|
23
|
+
end
|
24
|
+
|
25
|
+
def instance_methods_missing(instance)
|
26
|
+
required_instance_methods - instance.methods
|
27
|
+
end
|
28
|
+
|
29
|
+
def methods_missing(instance)
|
30
|
+
missing_class_method_display_names = class_methods_missing(instance).map { |m| "self.#{m}" }
|
31
|
+
missing_class_method_display_names + instance_methods_missing(instance)
|
32
|
+
end
|
33
|
+
|
34
|
+
def methods_missing_message(instance)
|
35
|
+
missing_methods = methods_missing(instance)
|
36
|
+
missing_methods.empty? \
|
37
|
+
? nil \
|
38
|
+
: "Model is missing the following required methods: #{missing_methods.join(", ")}."
|
39
|
+
end
|
40
|
+
private
|
41
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# Object that contains and serves (stores and retrieves)
|
2
|
+
# living/dead states in the matrix.
|
3
|
+
|
4
|
+
|
5
|
+
class MyLifeModel
|
6
|
+
|
7
|
+
# You may want to copy certain methods from SampleLifeModel that you don't
|
8
|
+
# feel the need to implement yourself. For example, the to_s and ==
|
9
|
+
# methods may be helpful to you in your development and testing.
|
10
|
+
# Also, set_living_states is a convenience method that merely calls
|
11
|
+
# your set_living_state method.
|
12
|
+
|
13
|
+
# Because this viewer is just a tool and should not drive your implementation,
|
14
|
+
# there are no assumptions about your constructor -- you are free to do whatever
|
15
|
+
# you want with it. Instead, we require a couple of static factory methods
|
16
|
+
# needed by the application.
|
17
|
+
|
18
|
+
# Creates and returns an instance whose size and values are specified
|
19
|
+
# in the passed string. Rows must be delimited by "\n". The '*'
|
20
|
+
# character represents true, and any other value will evaluate to false.
|
21
|
+
def self.create_from_string(string)
|
22
|
+
end
|
23
|
+
|
24
|
+
# This method will create a model with the specified number of rows and
|
25
|
+
# columns. If a block is passed it will be used to initialize the
|
26
|
+
# alive/dead values; the block should take params row and column number.
|
27
|
+
def self.create(row_count, column_count)
|
28
|
+
end
|
29
|
+
|
30
|
+
def row_count
|
31
|
+
end
|
32
|
+
|
33
|
+
def column_count
|
34
|
+
end
|
35
|
+
|
36
|
+
def alive?(row, col)
|
37
|
+
end
|
38
|
+
|
39
|
+
def set_living_state(row, col, alive)
|
40
|
+
end
|
41
|
+
|
42
|
+
def set_living_states(array_of_row_col_tuples, alive)
|
43
|
+
end
|
44
|
+
|
45
|
+
def next_generation_model
|
46
|
+
end
|
47
|
+
|
48
|
+
def number_living
|
49
|
+
end
|
50
|
+
|
51
|
+
def to_s
|
52
|
+
end
|
53
|
+
|
54
|
+
def ==(other)
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
require_relative "life_calculator"
|
2
|
+
|
3
|
+
# Object that contains and serves (stores and retrieves)
|
4
|
+
# living/dead states in the matrix.
|
5
|
+
#
|
6
|
+
# All public methods here should be responded to in alternate
|
7
|
+
# model implementations.
|
8
|
+
#
|
9
|
+
# See Wikipedia for more information about Conway's Game of Life.
|
10
|
+
#
|
11
|
+
# Rules distilled:
|
12
|
+
#
|
13
|
+
# 1) Any live cell with fewer than two live neighbours dies, as if caused by under-population.
|
14
|
+
# 2) Any live cell with two or three live neighbours lives on to the next generation.
|
15
|
+
# 3) Any live cell with more than three live neighbours dies, as if by overcrowding.
|
16
|
+
# 4) Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction.
|
17
|
+
|
18
|
+
class SampleLifeModel
|
19
|
+
|
20
|
+
attr_accessor :data
|
21
|
+
|
22
|
+
|
23
|
+
# Creates an instance with the specified number of rows and columns.
|
24
|
+
# All values are initialized to false.
|
25
|
+
def initialize(row_count, column_count)
|
26
|
+
@data = create_data(row_count, column_count)
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
# Creates a LifeModel instance whose size and values are specified
|
31
|
+
# in the passed string. Rows must be delimited by "\n". The '*'
|
32
|
+
# character represents alive, and the hyphen signifies dead.
|
33
|
+
def self.create_from_string(string)
|
34
|
+
row_count = string.chomp.count("\n") + 1
|
35
|
+
lines = string.split("\n")
|
36
|
+
col_count = lines.first.size
|
37
|
+
model = new(row_count, col_count)
|
38
|
+
|
39
|
+
(0...row_count).each do |row|
|
40
|
+
line = lines[row]
|
41
|
+
(0...(line.size)).each do |col|
|
42
|
+
ch = line[col]
|
43
|
+
alive = (ch == '*')
|
44
|
+
model.set_living_state(row, col, alive)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
model
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
# This method will create a model with the specified number of rows and
|
52
|
+
# columns. If a block is passed it will be used to initialize the
|
53
|
+
# alive/dead values; the block should take params row and column number.
|
54
|
+
def self.create(row_count, column_count)
|
55
|
+
model = new(row_count, column_count)
|
56
|
+
if block_given?
|
57
|
+
(0...row_count).each do |row|
|
58
|
+
(0...column_count).each do |col|
|
59
|
+
model.set_living_state(row, col, yield(row, col))
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
model
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
def row_count
|
68
|
+
@data.size
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
def column_count
|
73
|
+
@data[0].size
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
def alive?(row, col)
|
78
|
+
@data[row][col]
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
def set_living_state(row, col, alive)
|
83
|
+
@data[row][col] = alive
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
def set_living_states(array_of_row_col_tuples, alive)
|
88
|
+
array_of_row_col_tuples.each do |row_col_tuple|
|
89
|
+
row, col = row_col_tuple
|
90
|
+
set_living_state(row, col, alive)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
def next_generation_model
|
96
|
+
LifeCalculator.new.next_generation(self)
|
97
|
+
end
|
98
|
+
|
99
|
+
|
100
|
+
def to_s
|
101
|
+
super.to_s + ": #{row_count} rows, #{column_count} column_count, #{number_living} alive."
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
def ==(other)
|
106
|
+
other.is_a?(self.class) &&
|
107
|
+
other.row_count == row_count &&
|
108
|
+
other.column_count == column_count &&
|
109
|
+
other.data == data
|
110
|
+
end
|
111
|
+
|
112
|
+
|
113
|
+
def number_living
|
114
|
+
num = 0
|
115
|
+
(0...row_count).each do |row|
|
116
|
+
(0...column_count).each do |col|
|
117
|
+
num += 1 if alive?(row, col)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
num
|
121
|
+
end
|
122
|
+
|
123
|
+
|
124
|
+
private
|
125
|
+
|
126
|
+
def create_data(row_count, column_count)
|
127
|
+
data = []
|
128
|
+
row_count.times { |n| data << Array.new(column_count, false) }
|
129
|
+
data
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|