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,77 @@
|
|
1
|
+
|
2
|
+
class Amaze::Algorithm::RecursiveBacktracker < Amaze::Algorithm
|
3
|
+
|
4
|
+
def initialize
|
5
|
+
@implementation = :stack
|
6
|
+
end
|
7
|
+
|
8
|
+
def configure implementation
|
9
|
+
@implementation = implementation
|
10
|
+
end
|
11
|
+
|
12
|
+
def work grid, &block
|
13
|
+
case @implementation
|
14
|
+
when :recursion
|
15
|
+
carve [grid.random_cell], &block
|
16
|
+
when :stack
|
17
|
+
work_with_stack [grid.random_cell], &block
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# implementation with explicit stack
|
22
|
+
def work_with_stack stack, &block
|
23
|
+
while stack.any?
|
24
|
+
current = stack.last
|
25
|
+
unvisited_neighbors = current.neighbors.select {|c| c.links.empty? }
|
26
|
+
|
27
|
+
yield Stat.new( # visualize
|
28
|
+
current: stack, #
|
29
|
+
pause: segment?(unvisited_neighbors), #
|
30
|
+
info: "Path: #{stack.size}") if block_given? #
|
31
|
+
|
32
|
+
if unvisited_neighbors.any?
|
33
|
+
neighbor = unvisited_neighbors.sample
|
34
|
+
current.link neighbor
|
35
|
+
stack << neighbor
|
36
|
+
else
|
37
|
+
stack.pop
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# implementation with recursion
|
43
|
+
def carve path, &block
|
44
|
+
current = path.last
|
45
|
+
|
46
|
+
while current
|
47
|
+
unvisited_neighbors = current.neighbors.select {|c| c.links.empty? }
|
48
|
+
|
49
|
+
yield Stat.new( # visualize
|
50
|
+
current: path, #
|
51
|
+
pause: segment?(unvisited_neighbors), #
|
52
|
+
info: "Path: #{path.size}") if block_given? #
|
53
|
+
|
54
|
+
if unvisited_neighbors.any?
|
55
|
+
neighbor = unvisited_neighbors.sample
|
56
|
+
current.link neighbor
|
57
|
+
carve [*path, neighbor], &block
|
58
|
+
else
|
59
|
+
current = nil
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def segment? unvisited_neighbors
|
65
|
+
@forward = true if @forward.nil?
|
66
|
+
if unvisited_neighbors.empty? && @forward || unvisited_neighbors.any? && !@forward
|
67
|
+
@forward = !@forward
|
68
|
+
true
|
69
|
+
else
|
70
|
+
false
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def status
|
75
|
+
"Recursive Backtracker (#{@implementation}) algorithm: #{duration}s"
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
|
2
|
+
class Amaze::Algorithm::Sidewinder < Amaze::Algorithm
|
3
|
+
|
4
|
+
def work grid
|
5
|
+
run = []
|
6
|
+
grid.each_cell do |cell|
|
7
|
+
run << cell
|
8
|
+
|
9
|
+
dig_north = cell.north && (!cell.east || rand(2) == 0)
|
10
|
+
|
11
|
+
yield Stat.new( # visualize
|
12
|
+
current: run, #
|
13
|
+
pause: dig_north, #
|
14
|
+
info: "Run set: #{run.size}") if block_given? #
|
15
|
+
|
16
|
+
# dig north if there is a cell north AND
|
17
|
+
# you can't dig east OR
|
18
|
+
# you choose randomly
|
19
|
+
if dig_north
|
20
|
+
# dig north (choose one cell of the run set)
|
21
|
+
run.sample.tap {|c| c.link c.north }
|
22
|
+
|
23
|
+
# dig east if there is a cell east
|
24
|
+
elsif cell.east
|
25
|
+
# dig east (keep run set)
|
26
|
+
cell.link cell.east
|
27
|
+
next
|
28
|
+
end
|
29
|
+
|
30
|
+
run.clear
|
31
|
+
end
|
32
|
+
grid
|
33
|
+
end
|
34
|
+
|
35
|
+
def speed
|
36
|
+
0.1
|
37
|
+
end
|
38
|
+
|
39
|
+
def status
|
40
|
+
"Sidewinder algorithm: #{duration}s"
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
|
2
|
+
class Amaze::Algorithm::Wilson < Amaze::Algorithm
|
3
|
+
|
4
|
+
def work grid
|
5
|
+
|
6
|
+
# initialize
|
7
|
+
unvisited = []
|
8
|
+
grid.each_cell {|cell| unvisited << cell }
|
9
|
+
|
10
|
+
# pick randon cell and remove from unvisited
|
11
|
+
start = unvisited.sample
|
12
|
+
unvisited.delete start
|
13
|
+
|
14
|
+
while unvisited.any?
|
15
|
+
cell = unvisited.sample
|
16
|
+
path = [cell]
|
17
|
+
|
18
|
+
# loop erased walk
|
19
|
+
while unvisited.include? cell
|
20
|
+
cell = cell.neighbors.sample
|
21
|
+
|
22
|
+
if path.include? cell
|
23
|
+
path = path[0..path.index(cell)]
|
24
|
+
else
|
25
|
+
path << cell
|
26
|
+
end
|
27
|
+
|
28
|
+
yield Stat.new( # visualize
|
29
|
+
current: [start, *path].compact, #
|
30
|
+
pause: ! unvisited.include?(cell), #
|
31
|
+
info: "Path: #{path.size}") if block_given? #
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
# carve path
|
36
|
+
carved = 0 # visualize
|
37
|
+
path.each_cons(2) do |cell1, cell2|
|
38
|
+
cell1.link cell2
|
39
|
+
unvisited.delete cell1
|
40
|
+
|
41
|
+
yield Stat.new( # visualize
|
42
|
+
current: path, #
|
43
|
+
pause: cell2 == path.last, #
|
44
|
+
info: "Carved: #{carved += 1}") if block_given? #
|
45
|
+
|
46
|
+
end
|
47
|
+
start = nil # visualize
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def status
|
52
|
+
"Wilson algorithm: #{duration}s"
|
53
|
+
end
|
54
|
+
end
|
data/lib/amaze/cell.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
|
2
|
+
class Amaze::Cell
|
3
|
+
autoload :Square, 'amaze/cell/square'
|
4
|
+
autoload :Hex, 'amaze/cell/hex'
|
5
|
+
autoload :Octo, 'amaze/cell/octo'
|
6
|
+
autoload :Polar, 'amaze/cell/polar'
|
7
|
+
|
8
|
+
# The position of the cell in the grid
|
9
|
+
attr_reader :row, :column
|
10
|
+
|
11
|
+
def initialize row, column
|
12
|
+
@row = row
|
13
|
+
@column = column
|
14
|
+
|
15
|
+
@links = {}
|
16
|
+
end
|
17
|
+
|
18
|
+
def links
|
19
|
+
@links.keys
|
20
|
+
end
|
21
|
+
|
22
|
+
def link cell, bidi=true
|
23
|
+
@links[cell] = true
|
24
|
+
cell.link self, false if bidi
|
25
|
+
end
|
26
|
+
|
27
|
+
def linked? cell
|
28
|
+
@links.key? cell
|
29
|
+
end
|
30
|
+
|
31
|
+
def linked_to? direction
|
32
|
+
@links.key? self.send(direction)
|
33
|
+
end
|
34
|
+
|
35
|
+
def inspect
|
36
|
+
"cell(#{row},#{column})"
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_s
|
40
|
+
"(#{row},#{column})"
|
41
|
+
end
|
42
|
+
|
43
|
+
def distances
|
44
|
+
distances = Amaze::Distances.new self
|
45
|
+
frontier = [self]
|
46
|
+
|
47
|
+
while frontier.any?
|
48
|
+
new_frontier = []
|
49
|
+
|
50
|
+
frontier.each do |cell|
|
51
|
+
cell.links.each do |linked|
|
52
|
+
next if distances[linked]
|
53
|
+
distances[linked] = distances[cell] + 1
|
54
|
+
new_frontier << linked
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
frontier = new_frontier
|
59
|
+
end
|
60
|
+
|
61
|
+
distances
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
|
2
|
+
class Amaze::Cell::Octo < Amaze::Cell
|
3
|
+
|
4
|
+
# The neighbor cells
|
5
|
+
attr_accessor :north, :northeast, :east, :southeast, :south, :northwest, :west, :southwest
|
6
|
+
|
7
|
+
def neighbors
|
8
|
+
[north, northeast, east, southeast, south, northwest, west, southwest].compact
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
|
2
|
+
class Amaze::Cell::Polar < Amaze::Cell
|
3
|
+
|
4
|
+
# The neighbor cells
|
5
|
+
attr_accessor :cw, :ccw, :inward
|
6
|
+
attr_reader :outward
|
7
|
+
|
8
|
+
def initialize(row, column)
|
9
|
+
super
|
10
|
+
@outward = []
|
11
|
+
end
|
12
|
+
|
13
|
+
def neighbors
|
14
|
+
[cw, ccw, inward, *outward].compact
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
|
2
|
+
class Amaze::Distances
|
3
|
+
|
4
|
+
def initialize root
|
5
|
+
@root = root
|
6
|
+
@cells = {}
|
7
|
+
@cells[root] = 0
|
8
|
+
end
|
9
|
+
|
10
|
+
def [] cell
|
11
|
+
@cells[cell]
|
12
|
+
end
|
13
|
+
|
14
|
+
def []= cell, distance
|
15
|
+
@cells[cell] = distance
|
16
|
+
end
|
17
|
+
|
18
|
+
def cells
|
19
|
+
@cells.keys
|
20
|
+
end
|
21
|
+
|
22
|
+
def path_to goal
|
23
|
+
current = goal
|
24
|
+
breadcrumbs = Amaze::Distances.new @root
|
25
|
+
breadcrumbs[current] = @cells[current]
|
26
|
+
|
27
|
+
until current == @root
|
28
|
+
current.links.each do |neighbor|
|
29
|
+
if @cells[neighbor] < @cells[current]
|
30
|
+
breadcrumbs[neighbor] = @cells[neighbor]
|
31
|
+
current = neighbor
|
32
|
+
break
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
breadcrumbs
|
38
|
+
end
|
39
|
+
|
40
|
+
def max
|
41
|
+
max_cell = @root
|
42
|
+
max_distance = 0
|
43
|
+
|
44
|
+
@cells.each do |cell, distance|
|
45
|
+
if distance > max_distance
|
46
|
+
max_cell = cell
|
47
|
+
max_distance = distance
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
[max_cell, max_distance]
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
|
2
|
+
require 'gradient'
|
3
|
+
|
4
|
+
class Amaze::Factory
|
5
|
+
|
6
|
+
# All known maze types
|
7
|
+
def self.types
|
8
|
+
%i( delta ortho sigma upsilon polar )
|
9
|
+
end
|
10
|
+
|
11
|
+
# The type of the grid
|
12
|
+
attr_reader :type
|
13
|
+
|
14
|
+
def initialize type
|
15
|
+
raise "#{type} maze is not supported" unless self.class.types.include? type
|
16
|
+
@type = type
|
17
|
+
end
|
18
|
+
|
19
|
+
def create_grid *args
|
20
|
+
Amaze::Grid.const_get(type.to_s.capitalize).new *args
|
21
|
+
end
|
22
|
+
|
23
|
+
def create_masked_grid file
|
24
|
+
klass = Amaze::Grid.const_get(type.to_s.capitalize)
|
25
|
+
klass.prepend Amaze::MaskedGrid
|
26
|
+
klass.new create_mask(file)
|
27
|
+
end
|
28
|
+
|
29
|
+
def create_mask file
|
30
|
+
case File.extname file
|
31
|
+
when '.txt'
|
32
|
+
Amaze::Mask.from_txt file
|
33
|
+
when '.png'
|
34
|
+
Amaze::Mask.from_png file
|
35
|
+
else
|
36
|
+
raise "Mask file of type #{File.extname(file)} is not supported."
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# All known shapes
|
41
|
+
def self.shapes
|
42
|
+
%i( triangle diamond hexagon star )
|
43
|
+
end
|
44
|
+
|
45
|
+
def create_shaped_grid shape, *args
|
46
|
+
klass = Amaze::Grid.const_get(type.to_s.capitalize)
|
47
|
+
klass.prepend Amaze::MaskedGrid
|
48
|
+
klass.new Amaze::Shape.const_get(shape.to_s.capitalize).new(args.first).create_mask
|
49
|
+
end
|
50
|
+
|
51
|
+
def create_ascii_formatter *args
|
52
|
+
Amaze::Formatter::ASCII.const_get(type.to_s.capitalize).new *args
|
53
|
+
end
|
54
|
+
|
55
|
+
def create_image_formatter *args
|
56
|
+
Amaze::Formatter::Image.const_get(type.to_s.capitalize).new *args
|
57
|
+
end
|
58
|
+
|
59
|
+
# All known algorithms
|
60
|
+
def self.algorithms
|
61
|
+
%i( bt sw ab gt1 gt2 gt3 gt4 w hk rb1 rb2 )
|
62
|
+
end
|
63
|
+
|
64
|
+
def create_algorithm algorithm
|
65
|
+
raise "#{algorithm} is not supported" unless self.class.algorithms.include? algorithm
|
66
|
+
|
67
|
+
# Alogrithms for all mazes
|
68
|
+
select = {
|
69
|
+
ab: Amaze::Algorithm::AldousBorder,
|
70
|
+
gt1: Amaze::Algorithm::GrowingTree,
|
71
|
+
gt2: Amaze::Algorithm::GrowingTree,
|
72
|
+
gt3: Amaze::Algorithm::GrowingTree,
|
73
|
+
gt4: Amaze::Algorithm::GrowingTree,
|
74
|
+
w: Amaze::Algorithm::Wilson,
|
75
|
+
hk: Amaze::Algorithm::HuntAndKill,
|
76
|
+
rb1: Amaze::Algorithm::RecursiveBacktracker,
|
77
|
+
rb2: Amaze::Algorithm::RecursiveBacktracker,
|
78
|
+
}
|
79
|
+
|
80
|
+
# Algorithms only for ortho mazes
|
81
|
+
select.merge!(
|
82
|
+
bt: Amaze::Algorithm::BinaryTree,
|
83
|
+
sw: Amaze::Algorithm::Sidewinder
|
84
|
+
) if type == :ortho
|
85
|
+
|
86
|
+
raise "Alogrithm not supported on #{type} maze." unless select[algorithm]
|
87
|
+
instance = select[algorithm].new
|
88
|
+
|
89
|
+
case algorithm
|
90
|
+
when :gt1
|
91
|
+
instance.configure "last from list", proc {|active| active.last }
|
92
|
+
when :gt2
|
93
|
+
instance.configure "random from list", proc {|active| active.sample }
|
94
|
+
when :gt3
|
95
|
+
instance.configure "last/random 1/1 from list", proc {|active| (rand(2) > 0) ? active.last : active.sample }
|
96
|
+
when :gt4
|
97
|
+
instance.configure "last/random 2/1 from list", proc {|active| (rand(3) > 0) ? active.last : active.sample }
|
98
|
+
when :rb1
|
99
|
+
instance.configure :stack
|
100
|
+
when :rb2
|
101
|
+
instance.configure :recursion
|
102
|
+
end
|
103
|
+
|
104
|
+
instance
|
105
|
+
end
|
106
|
+
|
107
|
+
def self.gradient_maps
|
108
|
+
%i( red green blue monochrome cold warm gold )
|
109
|
+
end
|
110
|
+
|
111
|
+
def gradient_map name=:green
|
112
|
+
{ red:
|
113
|
+
Gradient::Map.new(
|
114
|
+
Gradient::Point.new(0, Color::RGB.new( 95, 0, 0), 1.0), # dark red
|
115
|
+
Gradient::Point.new(1, Color::RGB.new(255, 255, 255), 1.0), # white
|
116
|
+
),
|
117
|
+
green:
|
118
|
+
Gradient::Map.new(
|
119
|
+
Gradient::Point.new(0, Color::RGB.new( 0, 95, 0), 1.0), # dark green
|
120
|
+
Gradient::Point.new(1, Color::RGB.new(255, 255, 255), 1.0), # white
|
121
|
+
),
|
122
|
+
blue:
|
123
|
+
Gradient::Map.new(
|
124
|
+
Gradient::Point.new(0, Color::RGB.new( 0, 0, 95), 1.0), # dark blue
|
125
|
+
Gradient::Point.new(1, Color::RGB.new(255, 255, 255), 1.0), # white
|
126
|
+
),
|
127
|
+
monochrome:
|
128
|
+
Gradient::Map.new(
|
129
|
+
Gradient::Point.new(0, Color::RGB.new( 0, 0, 0), 1.0), # black
|
130
|
+
Gradient::Point.new(1, Color::RGB.new(255, 255, 255), 1.0), # white
|
131
|
+
),
|
132
|
+
cold:
|
133
|
+
Gradient::Map.new(
|
134
|
+
Gradient::Point.new(0, Color::RGB.new( 0, 0, 95), 1.0), # blue
|
135
|
+
Gradient::Point.new(0.65, Color::RGB.new( 0, 191, 255), 1.0), # cyan
|
136
|
+
Gradient::Point.new(1, Color::RGB.new(255, 255, 255), 1.0), # white
|
137
|
+
),
|
138
|
+
warm:
|
139
|
+
Gradient::Map.new(
|
140
|
+
Gradient::Point.new(0, Color::RGB.new( 95, 0, 0), 1.0), # dark red
|
141
|
+
Gradient::Point.new(0.65, Color::RGB.new(255, 191, 0), 1.0), # yellow
|
142
|
+
Gradient::Point.new(1, Color::RGB.new(255, 255, 255), 1.0), # white
|
143
|
+
),
|
144
|
+
gold:
|
145
|
+
Gradient::Map.new(
|
146
|
+
Gradient::Point.new(0, Color::RGB.new(127, 0, 0), 1.0), # dark red
|
147
|
+
Gradient::Point.new(0.5, Color::RGB.new(255, 127, 0), 1.0), # light red yellow
|
148
|
+
Gradient::Point.new(0.75, Color::RGB.new(255, 255, 0), 1.0), # yellow
|
149
|
+
Gradient::Point.new(1, Color::RGB.new(255, 255, 255), 1.0), # white
|
150
|
+
),
|
151
|
+
}[name]
|
152
|
+
end
|
153
|
+
end
|