amaze 0.2.0

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.
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