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