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 ADDED
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ coverage
6
+ InstalledFiles
7
+ lib/bundler/man
8
+ pkg
9
+ rdoc
10
+ spec/reports
11
+ test/tmp
12
+ test/version_tmp
13
+ tmp
14
+
15
+ # YARD artifacts
16
+ .yardoc
17
+ _yardoc
18
+ doc/
19
+ .idea/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in life_game_viewer.gemspec
4
+ gemspec
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,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env jruby
2
+
3
+
4
+ require 'life_game_viewer'
5
+
6
+ LifeGameViewer::Main.view_sample
@@ -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
@@ -0,0 +1,3 @@
1
+ module LifeGameViewer
2
+ VERSION = "0.9.1"
3
+ end