Class: Amaze::Script

Inherits:
Object
  • Object
show all
Defined in:
lib/amaze/script.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeScript

Returns a new instance of Script.



13
14
15
16
17
18
19
20
21
22
# File 'lib/amaze/script.rb', line 13

def initialize
  # default options
  @options = {
    type: :ortho, 
    distances: false,
    formats: [:ascii],
    algorithm: :gt1,
    visualize: false,
  }
end

Instance Attribute Details

#optionsObject (readonly)

Returns the value of attribute options.



11
12
13
# File 'lib/amaze/script.rb', line 11

def options
  @options
end

#seedObject (readonly)

Returns the value of attribute seed.



9
10
11
# File 'lib/amaze/script.rb', line 9

def seed
  @seed
end

Instance Method Details

#algorithmObject



332
333
334
# File 'lib/amaze/script.rb', line 332

def algorithm
  @algorithm ||= factory.create_algorithm options[:algorithm]
end

#ascii?Boolean

Returns:

  • (Boolean)


246
247
248
# File 'lib/amaze/script.rb', line 246

def ascii?
  @options[:formats].include? :ascii
end

#ascii_options(runtime_options = {}) ⇒ Object



222
223
224
225
226
227
228
229
# File 'lib/amaze/script.rb', line 222

def ascii_options runtime_options={}
  { 
    cell_size: options[:cell_size] || 1,
    grid_color: options[:ascii_grid_color] || :white,
    path_color: options[:ascii_path_color] || :red,
    distances_color: options[:ascii_distances_color]
  }.merge runtime_options
end

#distances?Boolean

Returns:

  • (Boolean)


258
259
260
# File 'lib/amaze/script.rb', line 258

def distances?
  !!@options[:distances]
end

#factoryObject



306
307
308
# File 'lib/amaze/script.rb', line 306

def factory
  @factory ||= Amaze::Factory.new options[:type]
end

#finish_cellObject

TODO: specify a finish cell should also work for polar grids



289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
# File 'lib/amaze/script.rb', line 289

def finish_cell
  if @options[:type] == :polar
    row = grid.rows-1
    columns = grid.columns row
    grid[row, columns.size / 2]
  else
    if !@options[:solution] || @options[:solution] == :auto
      column = grid.columns.times.find {|i| grid[grid.rows-1,grid.columns-1-i] }
      return grid[grid.rows-1,grid.columns-1-column] if column
      row = grid.rows.times.find {|i| grid[grid.rows-1-i,grid.columns-1] }
      return grid[grid.rows-1-row,grid.columns-1]
    else
      grid[*@options[:solution]]
    end
  end
end

#gridObject



310
311
312
313
314
315
316
317
318
# File 'lib/amaze/script.rb', line 310

def grid
  @grid ||= if options[:mask]
    factory.create_masked_grid options[:mask]
  elsif options[:shape]
    factory.create_shaped_grid options[:shape], *grid_size
  else
    factory.create_grid *grid_size
  end
end

#grid_sizeObject



320
321
322
323
324
325
326
327
328
329
330
# File 'lib/amaze/script.rb', line 320

def grid_size
  # double the columns for delta grids if not specified
  if options[:grid_size]
    size = options[:grid_size].first(2).map(&:to_i)
    size[1] ||= (options[:type] == :delta ? options[:grid_size][0] * 2 : options[:grid_size][0])
  else
    size = [4, options[:type] == :delta ? 8 : 4]
  end
  size = size.first if options[:type] == :polar
  size
end

#image?Boolean

Returns:

  • (Boolean)


250
251
252
# File 'lib/amaze/script.rb', line 250

def image?
  @options[:formats].include? :image
end

#image_options(runtime_options = {}) ⇒ Object



231
232
233
234
235
236
237
238
239
240
241
242
243
244
# File 'lib/amaze/script.rb', line 231

def image_options runtime_options={}
  {
    cell_width: options[:image_cell_width] || 100,
    wall_width: options[:image_wall_width] || 6,
    wall_color: options[:image_wall_color] || 'black',
    path_width: options[:image_path_width] || 4,
    path_color: options[:image_path_color] || 'red',
    border_width: options[:image_border_width] || 0,
    background_color: options[:image_background_color] || 'white',
    show_grid: options[:image_show_grid] || false,
    hide_walls: options[:image_hide_walls] || false,
    gradient_map: factory.gradient_map(options[:gradient_map] || :warm),
  }.merge runtime_options
end

#initialize_random_seedObject



336
337
338
339
340
341
342
343
344
# File 'lib/amaze/script.rb', line 336

def initialize_random_seed
  if options[:seed]
    @seed = options[:seed]
  else
    srand
    @seed = srand
  end
  srand @seed
end

#longest?Boolean

Returns:

  • (Boolean)


266
267
268
# File 'lib/amaze/script.rb', line 266

def longest?
  !!@options[:longest]
end

#parserObject



24
25
26
27
28
29
30
31
32
33
34
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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/amaze/script.rb', line 24

def parser
  OptionParser.new do |o|
    o.banner = "\nMaze generator\n\nUsage: #{File.basename $0} [options]\n"
    o.separator "\nGrid options:"

    o.on('-t', '--type TYPE', Amaze::Factory.types, 'The type of the maze.', "One of #{Amaze::Factory.types.join(', ')}") do |type|
      options[:type] = type
    end
    o.on('-g', '--grid-size ROWS[,COLUMNS]', Array, 'The size of the grid.') do |v|
      options[:grid_size] = Array(v).map(&:to_i)
    end
    o.on('-m', '--mask MASKFILE', String, 'MASKFILE is either a ASCII file or a PNG file.') do |mask|
      options[:mask] = mask
    end
    o.on('-s', '--shape SHAPE', Amaze::Factory.shapes, "One of #{Amaze::Factory.shapes.join(', ')}.", "Shapes won't work on polar mazes.") do |shape|
      options[:shape] = shape
    end

    o.separator "\nAlgorithm options:"

    o.on('-a', '--algorithm ALGORITHM', Amaze::Factory.algorithms, 'The algorithm to generate the maze.', "One of #{Amaze::Factory.algorithms.join(', ')}") do |algorithm|
      options[:algorithm] = algorithm
    end
    o.on('-S', '--seed SEED', Integer, 'Set random seed') do |seed|
      options[:seed] = seed
    end
    visualization_modes = %i( run autopause pause step )
    o.on('-v', '--visualize [MODE]', visualization_modes, 'Visualize the progress of the algorithm', "One of #{visualization_modes.join(', ')}") do |mode|
      options[:visualize] = mode || :run
    end

    o.separator "\nSolution options:"

    o.on('--[no-]distances [ROW,COLUMN]', Array, 'Calculate the distances from cell(ROW/COLUMN) to all other cells of the grid.') do |distances|
      options[:distances] = distances ? distances.map(&:to_i) : :auto
    end
    o.on('--[no-]solution [ROW,COLUMN]', Array, 'Find the shortest path to cell(ROW/COLUMN).') do |solution|
      options[:solution] = solution ? solution.map(&:to_i) : :auto
    end
    o.on('--[no-]longest', 'Find the longest path of the maze.') do |longest|
      options[:longest] = longest
    end

    o.separator "\nRender Options:"

    o.on('-f', '--format [FORMAT,...]', Array, 'Render the maze on the given formats.') do |formats|
      options[:formats] = formats.map(&:to_sym)
    end

    o.separator "\nASCII Options:"

    o.on('-c', '--cell-size SIZE', Integer, 'The size of the cell') do |cell_size|
      options[:cell_size] = cell_size
    end
    o.on('--grid-color NAME', Rainbow::X11ColorNames::NAMES.keys, 'The color of the grid.') do |color|
      options[:ascii_grid_color] = color
    end
    o.on('--path-color NAME', Rainbow::X11ColorNames::NAMES.keys, 'The color of the path, when drawing the solution or longest path.') do |color|
      options[:ascii_path_color] = color
    end
    o.on('--distances-color NAME', Rainbow::X11ColorNames::NAMES.keys, 'The color of the distances.') do |color|
      options[:ascii_distances_color] = color
    end
    o.on('--all-ascii-colors', 'Print all the supported ascii colors.') do
      puts Rainbow::X11ColorNames::NAMES.keys.map {|n| n.to_s.color(n) }.join(' ')
      exit 0
    end

    o.separator "\nImage Options:"

    o.on('--cell-width PIXEL', Integer, 'The width of a cell.') do |px|
      options[:image_cell_width] = px
    end
    o.on('--wall-width PIXEL', Integer, 'The width of the walls.') do |px|
      options[:image_wall_width] = px
    end
    o.on('--wall-color NAME', Magick.colors.map(&:name), 'The color of the walls.') do |color|
      options[:image_wall_color] = color
    end
    o.on('--path-width PIXEL', Integer, 'The width of the path.') do |px|
      options[:image_path_width] = px
    end
    o.on('--path-color NAME', Magick.colors.map(&:name), 'The color of the path.') do |color|
      options[:image_path_color] = color
    end
    o.on('--border-width PIXEL', Integer, 'The width of the border around the maze.') do |px|
      options[:image_border_width] = px
    end
    o.on('--background-color NAME', Magick.colors.map(&:name), 'The background color.') do |color|
      options[:image_background_color] = color
    end
    o.on('--show-grid', 'Render the underlying grid.') do
      options[:image_show_grid] = true
    end
    o.on('--hide-walls', "Don't render the walls.") do
      options[:image_hide_walls] = true
    end
    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|
      options[:gradient_map] = map
    end
    o.on('--all-image-colors', 'Print all the supported image colors.') do
      puts Magick.colors.map(&:name).join(', ')
      exit 0
    end

    o.separator ""
  end
end

#read_charObject

Reads keypresses from the user including 2 and 3 escape character sequences.



347
348
349
350
351
352
353
354
355
356
357
358
359
360
# File 'lib/amaze/script.rb', line 347

def read_char
  STDIN.echo = false
  STDIN.raw!

  input = STDIN.getc.chr
  if input == "\e" then
    input << STDIN.read_nonblock(3) rescue nil
    input << STDIN.read_nonblock(2) rescue nil
  end
ensure
  STDIN.echo = true
  STDIN.cooked!
  return input
end

#run(args) ⇒ Object



133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
# File 'lib/amaze/script.rb', line 133

def run args
  parser.parse!(args)
  
  initialize_random_seed
  
  # Run the algorithm on the grid
  if visualize?
    algorithm.on grid do |stat|
      # print the maze
      ascii = factory.create_ascii_formatter grid,
        ascii_options(path_color: :blue, path_cells: stat.current)
        
      puts ascii.render
      
      puts stat.info if stat.info
      sleep algorithm.speed
      sleep 1 if options[:visualize] == :autopause && stat.pause?

      # wait for keystroke ?
      if (options[:visualize] == :pause && stat.pause? || options[:visualize] == :step)
        case read_char
        when "\e"
          break
        when "r"
          options[:visualize] = :run
        end
      end
    end
  else
    algorithm.on grid
  end
  
  ascii_runtime_options = {}
  image_runtime_options = {}
  
  # Calculate the distances from a given start cell
  if distances?
    distances = start_cell.distances
    ascii_runtime_options[:distances] = distances
    image_runtime_options[:distances] = distances
  end

  # And the solution to a given end cell
  if solution?
    distances = start_cell.distances.path_to finish_cell
    ascii_runtime_options[:path_cells] = distances.cells
    image_runtime_options[:path_cells] = distances.cells
    image_runtime_options[:path_start] = start_cell
    image_runtime_options[:path_finish] = finish_cell
    path_length = distances[finish_cell]
  end
  
  if longest?
    new_start, distance = start_cell.distances.max
    new_distances = new_start.distances
    new_finish, distance = new_distances.max
    distances = new_distances.path_to new_finish
    image_runtime_options[:distances] = new_distances if distances?
    ascii_runtime_options[:path_cells] = distances.cells
    image_runtime_options[:path_cells] = distances.cells
    image_runtime_options[:path_start] = new_start
    image_runtime_options[:path_finish] = new_finish
    path_length = distance
  end

  # Render the maze, set defaults for missing options
  if ascii?
    ascii = factory.create_ascii_formatter grid, ascii_options(ascii_runtime_options)
    puts ascii.render
  end
  
  puts algorithm.status
  puts "Dead ends: #{grid.deadends.size} of #{grid.size} (#{(100.to_f / grid.size * grid.deadends.size).to_i}%)"
  puts "Path length: #{path_length}" if path_length
  puts "Random seed: #{seed}"

  if image?
    image = factory.create_image_formatter grid,
      image_options(image_runtime_options)
    image.render
    
    # TODO: write multiple images with solution and distances
    #       or a psd file with layers
    
    image.write "maze.png"
    puts "Maze 'maze.png' saved."
  end
end

#solution?Boolean

Returns:

  • (Boolean)


262
263
264
# File 'lib/amaze/script.rb', line 262

def solution?
  !!@options[:solution]
end

#start_cellObject

TODO: specify a start cell should also work for polar grids



272
273
274
275
276
277
278
279
280
281
282
283
284
285
# File 'lib/amaze/script.rb', line 272

def start_cell
  if @options[:type] == :polar
    grid[grid.rows-1, 0]
  else
    if !@options[:distances] || @options[:distances] == :auto
      column = grid.columns.times.find {|i| grid[0,i] }
      return grid[0,column] if column
      row = grid.rows.times.find {|i| grid[i,0] }
      return grid[row,0]
    else
      grid[*@options[:distances]]
    end
  end
end

#visualize?Boolean

Returns:

  • (Boolean)


254
255
256
# File 'lib/amaze/script.rb', line 254

def visualize?
  ascii? && !!@options[:visualize]
end