amaze 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +110 -0
- data/Rakefile +6 -0
- data/amaze.gemspec +30 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/exe/amaze +5 -0
- data/lib/amaze.rb +17 -0
- data/lib/amaze/algorithm.rb +44 -0
- data/lib/amaze/algorithm/aldous_border.rb +36 -0
- data/lib/amaze/algorithm/binary_tree.rb +20 -0
- data/lib/amaze/algorithm/growing_tree.rb +52 -0
- data/lib/amaze/algorithm/hunt_and_kill.rb +53 -0
- data/lib/amaze/algorithm/recursive_backtracker.rb +77 -0
- data/lib/amaze/algorithm/sidewinder.rb +42 -0
- data/lib/amaze/algorithm/wilson.rb +54 -0
- data/lib/amaze/cell.rb +63 -0
- data/lib/amaze/cell/hex.rb +10 -0
- data/lib/amaze/cell/octo.rb +10 -0
- data/lib/amaze/cell/polar.rb +16 -0
- data/lib/amaze/cell/square.rb +10 -0
- data/lib/amaze/distances.rb +53 -0
- data/lib/amaze/factory.rb +153 -0
- data/lib/amaze/formatter.rb +5 -0
- data/lib/amaze/formatter/ascii.rb +91 -0
- data/lib/amaze/formatter/ascii/delta.rb +180 -0
- data/lib/amaze/formatter/ascii/ortho.rb +105 -0
- data/lib/amaze/formatter/ascii/polar.rb +199 -0
- data/lib/amaze/formatter/ascii/sigma.rb +213 -0
- data/lib/amaze/formatter/ascii/upsilon.rb +281 -0
- data/lib/amaze/formatter/image.rb +127 -0
- data/lib/amaze/formatter/image/delta.rb +123 -0
- data/lib/amaze/formatter/image/ortho.rb +103 -0
- data/lib/amaze/formatter/image/polar.rb +173 -0
- data/lib/amaze/formatter/image/sigma.rb +122 -0
- data/lib/amaze/formatter/image/upsilon.rb +135 -0
- data/lib/amaze/grid.rb +66 -0
- data/lib/amaze/grid/delta.rb +87 -0
- data/lib/amaze/grid/ortho.rb +38 -0
- data/lib/amaze/grid/polar.rb +61 -0
- data/lib/amaze/grid/sigma.rb +61 -0
- data/lib/amaze/grid/upsilon.rb +74 -0
- data/lib/amaze/mask.rb +75 -0
- data/lib/amaze/masked_grid.rb +29 -0
- data/lib/amaze/script.rb +361 -0
- data/lib/amaze/shape.rb +23 -0
- data/lib/amaze/shape/diamond.rb +26 -0
- data/lib/amaze/shape/hexagon.rb +79 -0
- data/lib/amaze/shape/star.rb +114 -0
- data/lib/amaze/shape/triangle.rb +25 -0
- data/lib/amaze/version.rb +3 -0
- data/support/characters.txt +17 -0
- data/support/mask/mask1.txt +10 -0
- data/support/mask/mask2.txt +12 -0
- data/support/mask/mask3.txt +15 -0
- 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
|
+
|
data/lib/amaze/mask.rb
ADDED
@@ -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
|
data/lib/amaze/script.rb
ADDED
@@ -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
|