life_game_viewer 0.9.1

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