amaze 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +4 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +110 -0
  8. data/Rakefile +6 -0
  9. data/amaze.gemspec +30 -0
  10. data/bin/console +15 -0
  11. data/bin/setup +8 -0
  12. data/exe/amaze +5 -0
  13. data/lib/amaze.rb +17 -0
  14. data/lib/amaze/algorithm.rb +44 -0
  15. data/lib/amaze/algorithm/aldous_border.rb +36 -0
  16. data/lib/amaze/algorithm/binary_tree.rb +20 -0
  17. data/lib/amaze/algorithm/growing_tree.rb +52 -0
  18. data/lib/amaze/algorithm/hunt_and_kill.rb +53 -0
  19. data/lib/amaze/algorithm/recursive_backtracker.rb +77 -0
  20. data/lib/amaze/algorithm/sidewinder.rb +42 -0
  21. data/lib/amaze/algorithm/wilson.rb +54 -0
  22. data/lib/amaze/cell.rb +63 -0
  23. data/lib/amaze/cell/hex.rb +10 -0
  24. data/lib/amaze/cell/octo.rb +10 -0
  25. data/lib/amaze/cell/polar.rb +16 -0
  26. data/lib/amaze/cell/square.rb +10 -0
  27. data/lib/amaze/distances.rb +53 -0
  28. data/lib/amaze/factory.rb +153 -0
  29. data/lib/amaze/formatter.rb +5 -0
  30. data/lib/amaze/formatter/ascii.rb +91 -0
  31. data/lib/amaze/formatter/ascii/delta.rb +180 -0
  32. data/lib/amaze/formatter/ascii/ortho.rb +105 -0
  33. data/lib/amaze/formatter/ascii/polar.rb +199 -0
  34. data/lib/amaze/formatter/ascii/sigma.rb +213 -0
  35. data/lib/amaze/formatter/ascii/upsilon.rb +281 -0
  36. data/lib/amaze/formatter/image.rb +127 -0
  37. data/lib/amaze/formatter/image/delta.rb +123 -0
  38. data/lib/amaze/formatter/image/ortho.rb +103 -0
  39. data/lib/amaze/formatter/image/polar.rb +173 -0
  40. data/lib/amaze/formatter/image/sigma.rb +122 -0
  41. data/lib/amaze/formatter/image/upsilon.rb +135 -0
  42. data/lib/amaze/grid.rb +66 -0
  43. data/lib/amaze/grid/delta.rb +87 -0
  44. data/lib/amaze/grid/ortho.rb +38 -0
  45. data/lib/amaze/grid/polar.rb +61 -0
  46. data/lib/amaze/grid/sigma.rb +61 -0
  47. data/lib/amaze/grid/upsilon.rb +74 -0
  48. data/lib/amaze/mask.rb +75 -0
  49. data/lib/amaze/masked_grid.rb +29 -0
  50. data/lib/amaze/script.rb +361 -0
  51. data/lib/amaze/shape.rb +23 -0
  52. data/lib/amaze/shape/diamond.rb +26 -0
  53. data/lib/amaze/shape/hexagon.rb +79 -0
  54. data/lib/amaze/shape/star.rb +114 -0
  55. data/lib/amaze/shape/triangle.rb +25 -0
  56. data/lib/amaze/version.rb +3 -0
  57. data/support/characters.txt +17 -0
  58. data/support/mask/mask1.txt +10 -0
  59. data/support/mask/mask2.txt +12 -0
  60. data/support/mask/mask3.txt +15 -0
  61. 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
@@ -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::Hex < Amaze::Cell
3
+
4
+ # The neighbor cells
5
+ attr_accessor :north, :northeast, :southeast, :south, :northwest, :southwest
6
+
7
+ def neighbors
8
+ [north, northeast, southeast, south, northwest, southwest].compact
9
+ end
10
+ 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,10 @@
1
+
2
+ class Amaze::Cell::Square < Amaze::Cell
3
+
4
+ # The neighbor cells
5
+ attr_accessor :north, :east, :south, :west
6
+
7
+ def neighbors
8
+ [north, east, south, west].compact
9
+ end
10
+ 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