Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

solver documentation

  • Loading branch information...
commit f374ff8e5cb8659872d8a7f6be4ed9cbdca6f1e2 1 parent d417a0d
Jamis Buck authored December 19, 2010
1  .gitignore
... ...
@@ -0,0 +1 @@
  1
+html
2  bin/theseus
@@ -216,7 +216,7 @@ if animate
216 216
       solver = maze.new_solver(type: solution)
217 217
 
218 218
       while solver.step
219  
-        path = solver.path(color: png_opts[:solution_color])
  219
+        path = solver.to_path(color: png_opts[:solution_color])
220 220
 
221 221
         step += 1
222 222
         f = "%s-%04d.png" % [output, step]
2  lib/theseus/formatters/png.rb
@@ -76,7 +76,7 @@ def initialize(maze, options)
76 76
         @paths = @options[:paths] || []
77 77
 
78 78
         if @options[:solution]
79  
-          path = maze.new_solver(type: @options[:solution]).solve.path(color: @options[:solution_color])
  79
+          path = maze.new_solver(type: @options[:solution]).solve.to_path(color: @options[:solution_color])
80 80
           @paths = [path, *@paths]
81 81
         end
82 82
       end
56  lib/theseus/solvers/astar.rb
@@ -2,37 +2,75 @@
2 2
 
3 3
 module Theseus
4 4
   module Solvers
  5
+    # An implementation of the A* search algorithm. Although this can be used to
  6
+    # search "perfect" mazes (those without loops), the recursive backtracker is
  7
+    # more efficient in that case.
  8
+    #
  9
+    # The A* algorithm really shines, though, with multiply-connected mazes
  10
+    # (those with non-zero braid values, or some symmetrical mazes). In this case,
  11
+    # it is guaranteed to return the shortest path through the maze between the
  12
+    # two points.
5 13
     class Astar < Base
  14
+
  15
+      # This is the data structure used by the Astar solver to keep track of the
  16
+      # current cost of each examined cell and its associated history (path back
  17
+      # to the start).
  18
+      #
  19
+      # Although you will rarely need to use this class, it is documented because
  20
+      # applications that wish to visualize the A* algorithm can use the open set
  21
+      # of Node instances to draw paths through the maze as the algorithm runs.
6 22
       class Node
7 23
         include Comparable
8 24
 
9  
-        attr_accessor :point, :under, :path_cost, :estimate, :cost, :next
  25
+        # The point in the maze associated with this node.
  26
+        attr_accessor :point
  27
+
  28
+        # Whether the node is on the primary plane (+false+) or the under plane (+true+)
  29
+        attr_accessor :under
  30
+
  31
+        # The path cost of this node (the distance from the start to this cell,
  32
+        # through the maze)
  33
+        attr_accessor :path_cost
  34
+        
  35
+        # The (optimistic) estimate for how much further the exit is from this node.
  36
+        attr_accessor :estimate
  37
+        
  38
+        # The total cost associated with this node (path_cost + estimate)
  39
+        attr_accessor :cost
  40
+        
  41
+        # The next node in the linked list for the set that this node belongs to.
  42
+        attr_accessor :next
  43
+
  44
+        # The array of points leading from the starting point, to this node.
10 45
         attr_reader :history
11 46
 
12  
-        def initialize(point, under, path_cost, estimate, history)
  47
+        def initialize(point, under, path_cost, estimate, history) #:nodoc:
13 48
           @point, @under, @path_cost, @estimate = point, under, path_cost, estimate
14 49
           @history = history
15 50
           @cost = path_cost + estimate
16 51
         end
17 52
 
18  
-        def <=>(node)
  53
+        def <=>(node) #:nodoc:
19 54
           cost <=> node.cost
20 55
         end
21 56
       end
22 57
 
  58
+      # The open set. This is a linked list of Node instances, used by the A*
  59
+      # algorithm to determine which nodes remain to be considered. It is always
  60
+      # in sorted order, with the most likely candidate at the head of the list.
23 61
       attr_reader :open
24 62
 
25  
-      def initialize(maze, a=maze.start, b=maze.finish)
  63
+      def initialize(maze, a=maze.start, b=maze.finish) #:nodoc:
26 64
         super
27 65
         @open = Node.new(@a, false, 0, estimate(@a), [])
28 66
         @visits = Array.new(@maze.height) { Array.new(@maze.width, 0) }
29 67
       end
30 68
 
31  
-      def current_solution
  69
+      def current_solution #:nodoc:
32 70
         @open.history + [@open.point]
33 71
       end
34 72
 
35  
-      def step
  73
+      def step #:nodoc:
36 74
         return false unless @open
37 75
 
38 76
         current = @open
@@ -64,11 +102,11 @@ def step
64 102
 
65 103
       private
66 104
 
67  
-      def estimate(pt)
  105
+      def estimate(pt) #:nodoc:
68 106
         Math.sqrt((@b[0] - pt[0])**2 + (@b[1] - pt[1])**2)
69 107
       end
70 108
 
71  
-      def add_node(pt, under, path_cost, history)
  109
+      def add_node(pt, under, path_cost, history) #:nodoc:
72 110
         return if @visits[pt[1]][pt[0]] & (under ? 2 : 1) != 0
73 111
 
74 112
         node = Node.new(pt, under, path_cost, estimate(pt), history)
@@ -98,7 +136,7 @@ def add_node(pt, under, path_cost, history)
98 136
         end
99 137
       end
100 138
 
101  
-      def move(pt, direction)
  139
+      def move(pt, direction) #:nodoc:
102 140
         [pt[0] + @maze.dx(direction), pt[1] + @maze.dy(direction)]
103 141
       end
104 142
     end
12  lib/theseus/solvers/backtracker.rb
@@ -2,8 +2,14 @@
2 2
 
3 3
 module Theseus
4 4
   module Solvers
  5
+    # An implementation of a recursive backtracker for solving a maze. Although it will
  6
+    # work (eventually) for multiply-connected mazes, it will almost certainly not
  7
+    # return an optimal solution in that case. Thus, this solver is best suited only
  8
+    # for "perfect" mazes (those with no loops).
  9
+    #
  10
+    # For mazes that contain loops, see the Theseus::Solvers::Astar class.
5 11
     class Backtracker < Base
6  
-      def initialize(maze, a=maze.start, b=maze.finish)
  12
+      def initialize(maze, a=maze.start, b=maze.finish) #:nodoc:
7 13
         super
8 14
         @visits = Array.new(@maze.height) { Array.new(@maze.width, 0) }
9 15
         @stack = []
@@ -11,11 +17,11 @@ def initialize(maze, a=maze.start, b=maze.finish)
11 17
 
12 18
       VISIT_MASK = { false => 1, true => 2 }
13 19
 
14  
-      def current_solution
  20
+      def current_solution #:nodoc:
15 21
         @stack[1..-1].map { |item| item[0] }
16 22
       end
17 23
 
18  
-      def step
  24
+      def step #:nodoc:
19 25
         if @stack == [:fail]
20 26
           return false
21 27
         elsif @stack.empty?
42  lib/theseus/solvers/base.rb
@@ -2,9 +2,21 @@
2 2
 
3 3
 module Theseus
4 4
   module Solvers
  5
+    # The abstract superclass for solver implementations. It simply provides
  6
+    # some helper methods that implementations would otherwise have to duplicate.
5 7
     class Base
6  
-      attr_reader :maze, :a, :b
  8
+      # The maze object that this solver will provide a solution for.
  9
+      attr_reader :maze
7 10
 
  11
+      # The point (2-tuple array) at which the solution path should begin.
  12
+      attr_reader :a
  13
+
  14
+      # The point (2-tuple array) at which the solution path should end.
  15
+      attr_reader :b
  16
+
  17
+      # Create a new solver instance for the given maze, using the given
  18
+      # start (+a+) and finish (+b+) points. The solution will not be immediately
  19
+      # generated; to do so, use the #step or #solve methods.
8 20
       def initialize(maze, a=maze.start, b=maze.finish)
9 21
         @maze = maze
10 22
         @a = a
@@ -12,15 +24,21 @@ def initialize(maze, a=maze.start, b=maze.finish)
12 24
         @solution = nil
13 25
       end
14 26
 
  27
+      # Returns +true+ if the solution has been generated.
15 28
       def solved?
16 29
         @solution != nil
17 30
       end
18 31
 
  32
+      # Returns the solution path as an array of 2-tuples, beginning with #a and
  33
+      # ending with #b. If the solution has not yet been generated, this will
  34
+      # generate the solution first, and then return it.
19 35
       def solution
20 36
         solve unless solved?
21 37
         @solution
22 38
       end
23 39
 
  40
+      # Generates the solution to the maze, and returns +self+. If the solution
  41
+      # has already been generated, this does nothing.
24 42
       def solve
25 43
         while !solved?
26 44
           step
@@ -29,6 +47,10 @@ def solve
29 47
         self
30 48
       end
31 49
 
  50
+      # If the maze is solved, this yields each point in the solution, in order.
  51
+      #
  52
+      # If the maze has not yet been solved, this yields the result of calling
  53
+      # #step, until the maze has been solved.
32 54
       def each
33 55
         if solved?
34 56
           solution.each { |s| yield s }
@@ -37,7 +59,9 @@ def each
37 59
         end
38 60
       end
39 61
 
40  
-      def path(options={})
  62
+      # Returns the solution (or, if the solution is not yet fully generated,
  63
+      # the current_solution) as a Theseus::Path object.
  64
+      def to_path(options={})
41 65
         path = @maze.new_path(options)
42 66
         prev = @maze.entrance
43 67
 
@@ -52,6 +76,20 @@ def path(options={})
52 76
 
53 77
         path
54 78
       end
  79
+
  80
+      # Returns the current (potentially partial) solution to the maze. This
  81
+      # is for use while the algorithm is running, so that the current best-solution
  82
+      # may be inspected (or displayed).
  83
+      def current_solution
  84
+        raise NotImplementedError, "solver subclasses must implement #current_solution"
  85
+      end
  86
+
  87
+      # Runs a single iteration of the solution algorithm. Returns +false+ if the
  88
+      # algorithm has completed, and non-nil otherwise. The return value is
  89
+      # algorithm-dependent.
  90
+      def step
  91
+        raise NotImplementedError, "solver subclasses must implement #step"
  92
+      end
55 93
     end
56 94
   end
57 95
 end

0 notes on commit f374ff8

Please sign in to comment.
Something went wrong with that request. Please try again.