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