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