amaze 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +4 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +110 -0
  8. data/Rakefile +6 -0
  9. data/amaze.gemspec +30 -0
  10. data/bin/console +15 -0
  11. data/bin/setup +8 -0
  12. data/exe/amaze +5 -0
  13. data/lib/amaze.rb +17 -0
  14. data/lib/amaze/algorithm.rb +44 -0
  15. data/lib/amaze/algorithm/aldous_border.rb +36 -0
  16. data/lib/amaze/algorithm/binary_tree.rb +20 -0
  17. data/lib/amaze/algorithm/growing_tree.rb +52 -0
  18. data/lib/amaze/algorithm/hunt_and_kill.rb +53 -0
  19. data/lib/amaze/algorithm/recursive_backtracker.rb +77 -0
  20. data/lib/amaze/algorithm/sidewinder.rb +42 -0
  21. data/lib/amaze/algorithm/wilson.rb +54 -0
  22. data/lib/amaze/cell.rb +63 -0
  23. data/lib/amaze/cell/hex.rb +10 -0
  24. data/lib/amaze/cell/octo.rb +10 -0
  25. data/lib/amaze/cell/polar.rb +16 -0
  26. data/lib/amaze/cell/square.rb +10 -0
  27. data/lib/amaze/distances.rb +53 -0
  28. data/lib/amaze/factory.rb +153 -0
  29. data/lib/amaze/formatter.rb +5 -0
  30. data/lib/amaze/formatter/ascii.rb +91 -0
  31. data/lib/amaze/formatter/ascii/delta.rb +180 -0
  32. data/lib/amaze/formatter/ascii/ortho.rb +105 -0
  33. data/lib/amaze/formatter/ascii/polar.rb +199 -0
  34. data/lib/amaze/formatter/ascii/sigma.rb +213 -0
  35. data/lib/amaze/formatter/ascii/upsilon.rb +281 -0
  36. data/lib/amaze/formatter/image.rb +127 -0
  37. data/lib/amaze/formatter/image/delta.rb +123 -0
  38. data/lib/amaze/formatter/image/ortho.rb +103 -0
  39. data/lib/amaze/formatter/image/polar.rb +173 -0
  40. data/lib/amaze/formatter/image/sigma.rb +122 -0
  41. data/lib/amaze/formatter/image/upsilon.rb +135 -0
  42. data/lib/amaze/grid.rb +66 -0
  43. data/lib/amaze/grid/delta.rb +87 -0
  44. data/lib/amaze/grid/ortho.rb +38 -0
  45. data/lib/amaze/grid/polar.rb +61 -0
  46. data/lib/amaze/grid/sigma.rb +61 -0
  47. data/lib/amaze/grid/upsilon.rb +74 -0
  48. data/lib/amaze/mask.rb +75 -0
  49. data/lib/amaze/masked_grid.rb +29 -0
  50. data/lib/amaze/script.rb +361 -0
  51. data/lib/amaze/shape.rb +23 -0
  52. data/lib/amaze/shape/diamond.rb +26 -0
  53. data/lib/amaze/shape/hexagon.rb +79 -0
  54. data/lib/amaze/shape/star.rb +114 -0
  55. data/lib/amaze/shape/triangle.rb +25 -0
  56. data/lib/amaze/version.rb +3 -0
  57. data/support/characters.txt +17 -0
  58. data/support/mask/mask1.txt +10 -0
  59. data/support/mask/mask2.txt +12 -0
  60. data/support/mask/mask3.txt +15 -0
  61. metadata +203 -0
@@ -0,0 +1,61 @@
1
+
2
+ class Amaze::Grid::Polar < Amaze::Grid
3
+
4
+ def initialize rows
5
+ super rows, 1
6
+ end
7
+
8
+ def prepare_grid
9
+ @grid = Array.new(rows)
10
+ @grid[0] = [Amaze::Cell::Polar.new(0, 0)]
11
+
12
+ row_height = 1.0 / rows
13
+ (1...rows).each do |row|
14
+ radius = row.to_f / rows
15
+ circumference = 2 * Math::PI * radius
16
+
17
+ previous_count = @grid[row-1].size
18
+ estimated_cell_width = circumference / previous_count
19
+ ratio = (estimated_cell_width / row_height).round
20
+
21
+ cells = previous_count * ratio
22
+ @grid[row] = Array.new(cells) {|column| Amaze::Cell::Polar.new(row, column) }
23
+ end
24
+ end
25
+
26
+ def configure_cell
27
+ each_cell do |cell|
28
+ row, column = cell.row, cell.column
29
+ next if row == 0
30
+
31
+ cell.cw = self[row, column+1]
32
+ cell.ccw = self[row, column-1]
33
+
34
+ ratio = grid[row].size / grid[row-1].size
35
+ parent = self[row-1, column/ratio]
36
+ parent.outward << cell
37
+ cell.inward = parent
38
+ end
39
+ end
40
+
41
+ def columns row
42
+ grid[row]
43
+ end
44
+
45
+ def [](row, column)
46
+ return nil unless row.between?(0, rows-1)
47
+ grid[row][column % columns(row).size]
48
+ end
49
+
50
+ def random_cell
51
+ row = rand rows
52
+ column = rand grid[row].size
53
+ self[row, column]
54
+ end
55
+
56
+ def size
57
+ count = 0
58
+ each_row {|row| count += row.size }
59
+ count
60
+ end
61
+ end
@@ -0,0 +1,61 @@
1
+
2
+ class Amaze::Grid::Sigma < Amaze::Grid
3
+
4
+ def prepare_grid
5
+ @grid = Array.new(rows) do |row|
6
+ Array.new(columns) do |column|
7
+ Amaze::Cell::Hex.new row, column
8
+ end
9
+ end
10
+ end
11
+
12
+ def configure_cell
13
+ each_cell do |cell|
14
+ row, column = cell.row, cell.column
15
+
16
+ cell.north = self[row-1, column]
17
+ cell.south = self[row+1, column]
18
+
19
+ if column.even?
20
+ cell.northeast = self[row-1, column+1]
21
+ cell.southeast = self[row,column+1]
22
+ cell.northwest = self[row-1, column-1]
23
+ cell.southwest = self[row, column-1]
24
+ else
25
+ cell.northeast = self[row, column+1]
26
+ cell.southeast = self[row+1,column+1]
27
+ cell.northwest = self[row, column-1]
28
+ cell.southwest = self[row+1, column-1]
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ __END__
35
+
36
+ __ __ __
37
+ / \__/ \__/ \__
38
+ \__/ \__/ \__/ \
39
+ / \__/ \__/ \__/
40
+ \__/ \__/ \__/ \
41
+ / \__/ \__/ \__/
42
+ \__/ \__/ \__/ \
43
+ / \__/ \__/ \__/
44
+ \__/ \__/ \__/ \
45
+ \__/ \__/ \__/
46
+
47
+ ______ ______ ______
48
+ / \ / \ / \
49
+ / \______/ \______/ \______
50
+ \ / \ / \ / \
51
+ \______/ \______/ \______/ \
52
+ / \ / \ / \ /
53
+ / \______/ \______/ \______/
54
+ \ / \ / \ / \
55
+ \______/ \______/ \______/ \
56
+ / \ / \ / \ /
57
+ / \______/ \______/ \______/
58
+ \ / \ / \ / \
59
+ \______/ \______/ \______/ \
60
+ \ / \ / \ /
61
+ \______/ \______/ \______/
@@ -0,0 +1,74 @@
1
+ class Amaze::Grid::Upsilon < Amaze::Grid
2
+
3
+ def prepare_grid
4
+ @grid = Array.new(rows) do |row|
5
+ Array.new(columns) do |column|
6
+ if (row+column).even?
7
+ Amaze::Cell::Octo.new row, column
8
+ else
9
+ Amaze::Cell::Square.new row, column
10
+ end
11
+ end
12
+ end
13
+ end
14
+
15
+ def configure_cell
16
+ each_cell do |cell|
17
+ row, column = cell.row, cell.column
18
+
19
+ cell.north = self[row-1, column]
20
+ cell.east = self[row, column+1]
21
+ cell.south = self[row+1, column]
22
+ cell.west = self[row, column-1]
23
+
24
+ # Octo
25
+ if (cell.row+cell.column).even?
26
+ cell.northeast = self[row-1, column+1]
27
+ cell.southeast = self[row+1, column+1]
28
+ cell.southwest = self[row+1, column-1]
29
+ cell.northwest = self[row-1, column-1]
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ __END__
36
+
37
+ +---+ +---+ +---+
38
+ / \ / \ / \
39
+ + +---+ +---+ +---+
40
+ | | | | | | |
41
+ + +---+ +---+ +---+
42
+ \ / \ / \ / \
43
+ +---+ +---+ +---+ +
44
+ | | | | | | |
45
+ +---+ +---+ +---+ +
46
+ / \ / \ / \ /
47
+ + +---+ +---+ +---+
48
+ | | | | | | |
49
+ + +---+ +---+ +---+
50
+ \ / \ / \ / \
51
+ +---+ +---+ +---+ +
52
+ | | | | | | |
53
+ +---+ +---+ +---+ +
54
+ / \ / \ / \ /
55
+ + +---+ +---+ +---+
56
+ | | | | | | |
57
+ + +---+ +---+ +---+
58
+ \ / \ / \ /
59
+ +---+ +---+ +---+
60
+
61
+
62
+
63
+
64
+
65
+
66
+
67
+
68
+
69
+
70
+
71
+
72
+
73
+
74
+
@@ -0,0 +1,75 @@
1
+
2
+ require 'chunky_png'
3
+
4
+ class Amaze::Mask
5
+
6
+ # The rows and columns of the mask
7
+ attr_reader :rows, :columns
8
+
9
+ def initialize rows, columns
10
+ @rows, @columns = rows, columns
11
+ @bits = Array.new(@rows) { Array.new(@columns, true) }
12
+ end
13
+
14
+ def [](row, column)
15
+ if row.between?(0, rows - 1) && column.between?(0, columns - 1)
16
+ @bits[row][column]
17
+ else
18
+ false
19
+ end
20
+ end
21
+
22
+ def []=(row, column, is_on)
23
+ @bits[row][column] = is_on
24
+ end
25
+
26
+ def count
27
+ count = 0
28
+
29
+ rows.times do |row|
30
+ columns.times do |column|
31
+ count += 1 if @bits[row][column]
32
+ end
33
+ end
34
+ count
35
+ end
36
+
37
+ def random_location
38
+ loop do
39
+ row = rand(rows)
40
+ column = rand(columns)
41
+ return [row, column] if @bits[row][column]
42
+ end
43
+ end
44
+
45
+ def self.from_txt file
46
+ lines = File.readlines(file).map(&:strip)
47
+ lines.pop while lines.last.length < 1
48
+
49
+ rows = lines.length
50
+ columns = lines.first.length
51
+ mask = new rows, columns
52
+
53
+ mask.rows.times do |row|
54
+ mask.columns.times do |column|
55
+ mask[row,column] = lines[row][column] != 'X'
56
+ end
57
+ end
58
+
59
+ mask
60
+ end
61
+
62
+ def self.from_png file
63
+ image = ChunkyPNG::Image.from_file file
64
+ mask = new image.height, image.width
65
+
66
+ mask.rows.times do |row|
67
+ mask.columns.times do |column|
68
+ mask[row,column] = image[column, row] != ChunkyPNG::Color::BLACK
69
+ end
70
+ end
71
+
72
+ mask
73
+ end
74
+
75
+ end
@@ -0,0 +1,29 @@
1
+
2
+ module Amaze::MaskedGrid
3
+
4
+ # The mask
5
+ attr_reader :mask
6
+
7
+ def initialize mask
8
+ @mask = mask
9
+ super mask.rows, mask.columns
10
+ end
11
+
12
+ def prepare_grid
13
+ super
14
+
15
+ each_cell do |cell|
16
+ row, column = cell.row, cell.column
17
+ grid[row][column] = nil unless mask[row, column]
18
+ end
19
+ end
20
+
21
+ def random_cell
22
+ row, column = mask.random_location
23
+ self[row, column]
24
+ end
25
+
26
+ def size
27
+ mask.count
28
+ end
29
+ end
@@ -0,0 +1,361 @@
1
+
2
+ require 'optparse'
3
+ require 'io/console'
4
+ require 'rainbow/ext/string'
5
+ require 'rmagick'
6
+
7
+ class Amaze::Script
8
+
9
+ attr_reader :seed
10
+
11
+ attr_reader :options
12
+
13
+ def initialize
14
+ # default options
15
+ @options = {
16
+ type: :ortho,
17
+ distances: false,
18
+ formats: [:ascii],
19
+ algorithm: :gt1,
20
+ visualize: false,
21
+ }
22
+ end
23
+
24
+ def parser
25
+ OptionParser.new do |o|
26
+ o.banner = "\nMaze generator\n\nUsage: #{File.basename $0} [options]\n"
27
+ o.separator "\nGrid options:"
28
+
29
+ o.on('-t', '--type TYPE', Amaze::Factory.types, 'The type of the maze.', "One of #{Amaze::Factory.types.join(', ')}") do |type|
30
+ options[:type] = type
31
+ end
32
+ o.on('-g', '--grid-size ROWS[,COLUMNS]', Array, 'The size of the grid.') do |v|
33
+ options[:grid_size] = Array(v).map(&:to_i)
34
+ end
35
+ o.on('-m', '--mask MASKFILE', String, 'MASKFILE is either a ASCII file or a PNG file.') do |mask|
36
+ options[:mask] = mask
37
+ end
38
+ o.on('-s', '--shape SHAPE', Amaze::Factory.shapes, "One of #{Amaze::Factory.shapes.join(', ')}.", "Shapes won't work on polar mazes.") do |shape|
39
+ options[:shape] = shape
40
+ end
41
+
42
+ o.separator "\nAlgorithm options:"
43
+
44
+ o.on('-a', '--algorithm ALGORITHM', Amaze::Factory.algorithms, 'The algorithm to generate the maze.', "One of #{Amaze::Factory.algorithms.join(', ')}") do |algorithm|
45
+ options[:algorithm] = algorithm
46
+ end
47
+ o.on('-S', '--seed SEED', Integer, 'Set random seed') do |seed|
48
+ options[:seed] = seed
49
+ end
50
+ visualization_modes = [:run, :runsegment, :segment, :step]
51
+ o.on('-v', '--visualize [MODE]', visualization_modes, 'Visualize the progress of the algorithm', "One of #{visualization_modes.join(', ')}") do |mode|
52
+ options[:visualize] = mode || :run
53
+ end
54
+
55
+ o.separator "\nSolution options:"
56
+
57
+ o.on('--[no-]distances [ROW,COLUMN]', Array, 'Calculate the distances from cell(ROW/COLUMN) to all other cells of the grid.') do |distances|
58
+ options[:distances] = distances ? distances.map(&:to_i) : :auto
59
+ end
60
+ o.on('--[no-]solution [ROW,COLUMN]', Array, 'Find the shortest path to cell(ROW/COLUMN).') do |solution|
61
+ options[:solution] = solution ? solution.map(&:to_i) : :auto
62
+ end
63
+ o.on('--[no-]longest', 'Find the longest path of the maze.') do |longest|
64
+ options[:longest] = longest
65
+ end
66
+
67
+ o.separator "\nRender Options:"
68
+
69
+ o.on('-f', '--format [FORMAT,...]', Array, 'Render the maze on the given formats.') do |formats|
70
+ options[:formats] = formats.map(&:to_sym)
71
+ end
72
+
73
+ o.separator "\nASCII Options:"
74
+
75
+ o.on('-c', '--cell-size SIZE', Integer, 'The size of the cell') do |cell_size|
76
+ options[:cell_size] = cell_size
77
+ end
78
+ o.on('--grid-color NAME', Rainbow::X11ColorNames::NAMES.keys, 'The color of the grid.') do |color|
79
+ options[:ascii_grid_color] = color
80
+ end
81
+ o.on('--path-color NAME', Rainbow::X11ColorNames::NAMES.keys, 'The color of the path, when drawing the solution or longest path.') do |color|
82
+ options[:ascii_path_color] = color
83
+ end
84
+ o.on('--distances-color NAME', Rainbow::X11ColorNames::NAMES.keys, 'The color of the distances.') do |color|
85
+ options[:ascii_distances_color] = color
86
+ end
87
+ o.on('--all-ascii-colors', 'Print all the supported ascii colors.') do
88
+ puts Rainbow::X11ColorNames::NAMES.keys.map {|n| n.to_s.color(n) }.join(' ')
89
+ exit 0
90
+ end
91
+
92
+ o.separator "\nImage Options:"
93
+
94
+ o.on('--cell-width PIXEL', Integer, 'The width of a cell.') do |px|
95
+ options[:image_cell_width] = px
96
+ end
97
+ o.on('--wall-width PIXEL', Integer, 'The width of the walls.') do |px|
98
+ options[:image_wall_width] = px
99
+ end
100
+ o.on('--wall-color NAME', Magick.colors.map(&:name), 'The color of the walls.') do |color|
101
+ options[:image_wall_color] = color
102
+ end
103
+ o.on('--path-width PIXEL', Integer, 'The width of the path.') do |px|
104
+ options[:image_path_width] = px
105
+ end
106
+ o.on('--path-color NAME', Magick.colors.map(&:name), 'The color of the path.') do |color|
107
+ options[:image_path_color] = color
108
+ end
109
+ o.on('--border-width PIXEL', Integer, 'The width of the border around the maze.') do |px|
110
+ options[:image_border_width] = px
111
+ end
112
+ o.on('--background-color NAME', Magick.colors.map(&:name), 'The background color.') do |color|
113
+ options[:image_background_color] = color
114
+ end
115
+ o.on('--show-grid', 'Render the underlying grid.') do
116
+ options[:image_show_grid] = true
117
+ end
118
+ o.on('--hide-walls', "Don't render the walls.") do
119
+ options[:image_hide_walls] = true
120
+ end
121
+ o.on('--gradient-map NAME', Amaze::Factory.gradient_maps, 'The gradient map to use for the distances color.', "One of #{Amaze::Factory.gradient_maps.join(', ')}") do |map|
122
+ options[:gradient_map] = map
123
+ end
124
+ o.on('--all-image-colors', 'Print all the supported image colors.') do
125
+ puts Magick.colors.map(&:name).join(', ')
126
+ exit 0
127
+ end
128
+
129
+ o.separator ""
130
+ end
131
+ end
132
+
133
+ def run args
134
+ parser.parse!(args)
135
+
136
+ initialize_random_seed
137
+
138
+ # Run the algorithm on the grid
139
+ if visualize?
140
+ algorithm.on grid do |stat|
141
+ # print the maze
142
+ ascii = factory.create_ascii_formatter grid,
143
+ ascii_options(path_color: :blue, path_cells: stat.current)
144
+
145
+ puts ascii.render
146
+
147
+ puts stat.info if stat.info
148
+ sleep algorithm.speed
149
+ sleep 1 if options[:visualize] == :runsegment && stat.pause?
150
+
151
+ # wait for keystroke ?
152
+ if (options[:visualize] == :segment && stat.pause? || options[:visualize] == :step)
153
+ case read_char
154
+ when "\e"
155
+ break
156
+ when "r"
157
+ options[:visualize] = :run
158
+ end
159
+ end
160
+ end
161
+ else
162
+ algorithm.on grid
163
+ end
164
+
165
+ ascii_runtime_options = {}
166
+ image_runtime_options = {}
167
+
168
+ # Calculate the distances from a given start cell
169
+ if distances?
170
+ distances = start_cell.distances
171
+ ascii_runtime_options[:distances] = distances
172
+ image_runtime_options[:distances] = distances
173
+ end
174
+
175
+ # And the solution to a given end cell
176
+ if solution?
177
+ distances = start_cell.distances.path_to finish_cell
178
+ ascii_runtime_options[:path_cells] = distances.cells
179
+ image_runtime_options[:path_cells] = distances.cells
180
+ image_runtime_options[:path_start] = start_cell
181
+ image_runtime_options[:path_finish] = finish_cell
182
+ path_length = distances[finish_cell]
183
+ end
184
+
185
+ if longest?
186
+ new_start, distance = start_cell.distances.max
187
+ new_distances = new_start.distances
188
+ new_finish, distance = new_distances.max
189
+ distances = new_distances.path_to new_finish
190
+ image_runtime_options[:distances] = new_distances if distances?
191
+ ascii_runtime_options[:path_cells] = distances.cells
192
+ image_runtime_options[:path_cells] = distances.cells
193
+ image_runtime_options[:path_start] = new_start
194
+ image_runtime_options[:path_finish] = new_finish
195
+ path_length = distance
196
+ end
197
+
198
+ # Render the maze, set defaults for missing options
199
+ if ascii?
200
+ ascii = factory.create_ascii_formatter grid, ascii_options(ascii_runtime_options)
201
+ puts ascii.render
202
+ end
203
+
204
+ puts algorithm.status
205
+ puts "Dead ends: #{grid.deadends.size} of #{grid.size} (#{(100.to_f / grid.size * grid.deadends.size).to_i}%)"
206
+ puts "Path length: #{path_length}" if path_length
207
+ puts "Random seed: #{seed}"
208
+
209
+ if image?
210
+ image = factory.create_image_formatter grid,
211
+ image_options(image_runtime_options)
212
+ image.render
213
+
214
+ # TODO: write multiple images with solution and distances
215
+ # or a psd file with layers
216
+
217
+ image.write "maze.png"
218
+ puts "Maze 'maze.png' saved."
219
+ end
220
+ end
221
+
222
+ def ascii_options runtime_options={}
223
+ {
224
+ cell_size: options[:cell_size] || 1,
225
+ grid_color: options[:ascii_grid_color] || :white,
226
+ path_color: options[:ascii_path_color] || :red,
227
+ distances_color: options[:ascii_distances_color]
228
+ }.merge runtime_options
229
+ end
230
+
231
+ def image_options runtime_options={}
232
+ {
233
+ cell_width: options[:image_cell_width] || 100,
234
+ wall_width: options[:image_wall_width] || 6,
235
+ wall_color: options[:image_wall_color] || 'black',
236
+ path_width: options[:image_path_width] || 4,
237
+ path_color: options[:image_path_color] || 'red',
238
+ border_width: options[:image_border_width] || 0,
239
+ background_color: options[:image_background_color] || 'white',
240
+ show_grid: options[:image_show_grid] || false,
241
+ hide_walls: options[:image_hide_walls] || false,
242
+ gradient_map: factory.gradient_map(options[:gradient_map] || :warm),
243
+ }.merge runtime_options
244
+ end
245
+
246
+ def ascii?
247
+ @options[:formats].include? :ascii
248
+ end
249
+
250
+ def image?
251
+ @options[:formats].include? :image
252
+ end
253
+
254
+ def visualize?
255
+ ascii? && !!@options[:visualize]
256
+ end
257
+
258
+ def distances?
259
+ !!@options[:distances]
260
+ end
261
+
262
+ def solution?
263
+ !!@options[:solution]
264
+ end
265
+
266
+ def longest?
267
+ !!@options[:longest]
268
+ end
269
+
270
+ # TODO: specify a start cell should also work for polar grids
271
+
272
+ def start_cell
273
+ if @options[:type] == :polar
274
+ grid[grid.rows-1, 0]
275
+ else
276
+ if !@options[:distances] || @options[:distances] == :auto
277
+ column = grid.columns.times.find {|i| grid[0,i] }
278
+ return grid[0,column] if column
279
+ row = grid.rows.times.find {|i| grid[i,0] }
280
+ return grid[row,0]
281
+ else
282
+ grid[*@options[:distances]]
283
+ end
284
+ end
285
+ end
286
+
287
+ # TODO: specify a finish cell should also work for polar grids
288
+
289
+ def finish_cell
290
+ if @options[:type] == :polar
291
+ row = grid.rows-1
292
+ columns = grid.columns row
293
+ grid[row, columns.size / 2]
294
+ else
295
+ if !@options[:solution] || @options[:solution] == :auto
296
+ column = grid.columns.times.find {|i| grid[grid.rows-1,grid.columns-1-i] }
297
+ return grid[grid.rows-1,grid.columns-1-column] if column
298
+ row = grid.rows.times.find {|i| grid[grid.rows-1-i,grid.columns-1] }
299
+ return grid[grid.rows-1-row,grid.columns-1]
300
+ else
301
+ grid[*@options[:solution]]
302
+ end
303
+ end
304
+ end
305
+
306
+ def factory
307
+ @factory ||= Amaze::Factory.new options[:type]
308
+ end
309
+
310
+ def grid
311
+ @grid ||= if options[:mask]
312
+ factory.create_masked_grid options[:mask]
313
+ elsif options[:shape]
314
+ factory.create_shaped_grid options[:shape], *grid_size
315
+ else
316
+ factory.create_grid *grid_size
317
+ end
318
+ end
319
+
320
+ def grid_size
321
+ # double the columns for delta grids if not specified
322
+ if options[:grid_size]
323
+ size = options[:grid_size].first(2).map(&:to_i)
324
+ size[1] ||= (options[:type] == :delta ? options[:grid_size][0] * 2 : options[:grid_size][0])
325
+ else
326
+ size = [4, options[:type] == :delta ? 8 : 4]
327
+ end
328
+ size = size.first if options[:type] == :polar
329
+ size
330
+ end
331
+
332
+ def algorithm
333
+ @algorithm ||= factory.create_algorithm options[:algorithm]
334
+ end
335
+
336
+ def initialize_random_seed
337
+ if options[:seed]
338
+ @seed = options[:seed]
339
+ else
340
+ srand
341
+ @seed = srand
342
+ end
343
+ srand @seed
344
+ end
345
+
346
+ # Reads keypresses from the user including 2 and 3 escape character sequences.
347
+ def read_char
348
+ STDIN.echo = false
349
+ STDIN.raw!
350
+
351
+ input = STDIN.getc.chr
352
+ if input == "\e" then
353
+ input << STDIN.read_nonblock(3) rescue nil
354
+ input << STDIN.read_nonblock(2) rescue nil
355
+ end
356
+ ensure
357
+ STDIN.echo = true
358
+ STDIN.cooked!
359
+ return input
360
+ end
361
+ end