• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 
2 //          Copyright W.P. McNeill 2010.
3 // Distributed under the Boost Software License, Version 1.0.
4 //    (See accompanying file LICENSE_1_0.txt or copy at
5 //          http://www.boost.org/LICENSE_1_0.txt)
6 
7 // This program uses the A-star search algorithm in the Boost Graph Library to
8 // solve a maze.  It is an example of how to apply Boost Graph Library
9 // algorithms to implicit graphs.
10 //
11 // This program generates a random maze and then tries to find the shortest
12 // path from the lower left-hand corner to the upper right-hand corner.  Mazes
13 // are represented by two-dimensional grids where a cell in the grid may
14 // contain a barrier.  You may move up, down, right, or left to any adjacent
15 // cell that does not contain a barrier.
16 //
17 // Once a maze solution has been attempted, the maze is printed.  If a
18 // solution was found it will be shown in the maze printout and its length
19 // will be returned.  Note that not all mazes have solutions.
20 //
21 // The default maze size is 20x10, though different dimensions may be
22 // specified on the command line.
23 
24 /*
25    IMPORTANT:
26    ~~~~~~~~~~
27 
28    This example appears to be broken and crashes at runtime, see
29    https://github.com/boostorg/graph/issues/148
30 
31 */
32 
33 #include <boost/graph/astar_search.hpp>
34 #include <boost/graph/filtered_graph.hpp>
35 #include <boost/graph/grid_graph.hpp>
36 #include <boost/lexical_cast.hpp>
37 #include <boost/random/mersenne_twister.hpp>
38 #include <boost/random/uniform_int.hpp>
39 #include <boost/random/variate_generator.hpp>
40 #include <boost/unordered_map.hpp>
41 #include <boost/unordered_set.hpp>
42 #include <ctime>
43 #include <iostream>
44 
45 boost::mt19937 random_generator;
46 
47 // Distance traveled in the maze
48 typedef double distance;
49 
50 #define GRID_RANK 2
51 typedef boost::grid_graph< GRID_RANK > grid;
52 typedef boost::graph_traits< grid >::vertex_descriptor vertex_descriptor;
53 typedef boost::graph_traits< grid >::vertices_size_type vertices_size_type;
54 
55 // A hash function for vertices.
56 struct vertex_hash
57 {
58     typedef vertex_descriptor argument_type;
59     typedef std::size_t result_type;
operator ()vertex_hash60     std::size_t operator()(vertex_descriptor const& u) const
61     {
62         std::size_t seed = 0;
63         boost::hash_combine(seed, u[0]);
64         boost::hash_combine(seed, u[1]);
65         return seed;
66     }
67 };
68 
69 typedef boost::unordered_set< vertex_descriptor, vertex_hash > vertex_set;
70 typedef boost::vertex_subset_complement_filter< grid, vertex_set >::type
71     filtered_grid;
72 
73 // A searchable maze
74 //
75 // The maze is grid of locations which can either be empty or contain a
76 // barrier.  You can move to an adjacent location in the grid by going up,
77 // down, left and right.  Moving onto a barrier is not allowed.  The maze can
78 // be solved by finding a path from the lower-left-hand corner to the
79 // upper-right-hand corner.  If no open path exists between these two
80 // locations, the maze is unsolvable.
81 //
82 // The maze is implemented as a filtered grid graph where locations are
83 // vertices.  Barrier vertices are filtered out of the graph.
84 //
85 // A-star search is used to find a path through the maze. Each edge has a
86 // weight of one, so the total path length is equal to the number of edges
87 // traversed.
88 class maze
89 {
90 public:
91     friend std::ostream& operator<<(std::ostream&, const maze&);
92     friend void random_maze(maze&);
93 
maze()94     maze()
95     : m_grid(create_grid(0, 0)), m_barrier_grid(create_barrier_grid()) {};
maze(std::size_t x,std::size_t y)96     maze(std::size_t x, std::size_t y)
97     : m_grid(create_grid(x, y)), m_barrier_grid(create_barrier_grid()) {};
98 
99     // The length of the maze along the specified dimension.
length(std::size_t d) const100     vertices_size_type length(std::size_t d) const { return m_grid.length(d); }
101 
has_barrier(vertex_descriptor u) const102     bool has_barrier(vertex_descriptor u) const
103     {
104         return m_barriers.find(u) != m_barriers.end();
105     }
106 
107     // Try to find a path from the lower-left-hand corner source (0,0) to the
108     // upper-right-hand corner goal (x-1, y-1).
source() const109     vertex_descriptor source() const { return vertex(0, m_grid); }
goal() const110     vertex_descriptor goal() const
111     {
112         return vertex(num_vertices(m_grid) - 1, m_grid);
113     }
114 
115     bool solve();
solved() const116     bool solved() const { return !m_solution.empty(); }
solution_contains(vertex_descriptor u) const117     bool solution_contains(vertex_descriptor u) const
118     {
119         return m_solution.find(u) != m_solution.end();
120     }
121 
122 private:
123     // Create the underlying rank-2 grid with the specified dimensions.
create_grid(std::size_t x,std::size_t y)124     grid create_grid(std::size_t x, std::size_t y)
125     {
126         boost::array< std::size_t, GRID_RANK > lengths = { { x, y } };
127         return grid(lengths);
128     }
129 
130     // Filter the barrier vertices out of the underlying grid.
create_barrier_grid()131     filtered_grid create_barrier_grid()
132     {
133         return boost::make_vertex_subset_complement_filter(m_grid, m_barriers);
134     }
135 
136     // The grid underlying the maze
137     grid m_grid;
138     // The barriers in the maze
139     vertex_set m_barriers;
140     // The underlying maze grid with barrier vertices filtered out
141     filtered_grid m_barrier_grid;
142     // The vertices on a solution path through the maze
143     vertex_set m_solution;
144     // The length of the solution path
145     distance m_solution_length;
146 };
147 
148 // Euclidean heuristic for a grid
149 //
150 // This calculates the Euclidean distance between a vertex and a goal
151 // vertex.
152 class euclidean_heuristic
153 : public boost::astar_heuristic< filtered_grid, double >
154 {
155 public:
euclidean_heuristic(vertex_descriptor goal)156     euclidean_heuristic(vertex_descriptor goal) : m_goal(goal) {};
157 
operator ()(vertex_descriptor v)158     double operator()(vertex_descriptor v)
159     {
160         return sqrt(pow(double(m_goal[0] - v[0]), 2)
161             + pow(double(m_goal[1] - v[1]), 2));
162     }
163 
164 private:
165     vertex_descriptor m_goal;
166 };
167 
168 // Exception thrown when the goal vertex is found
169 struct found_goal
170 {
171 };
172 
173 // Visitor that terminates when we find the goal vertex
174 struct astar_goal_visitor : public boost::default_astar_visitor
175 {
astar_goal_visitorastar_goal_visitor176     astar_goal_visitor(vertex_descriptor goal) : m_goal(goal) {};
177 
examine_vertexastar_goal_visitor178     void examine_vertex(vertex_descriptor u, const filtered_grid&)
179     {
180         if (u == m_goal)
181             throw found_goal();
182     }
183 
184 private:
185     vertex_descriptor m_goal;
186 };
187 
188 // Solve the maze using A-star search.  Return true if a solution was found.
solve()189 bool maze::solve()
190 {
191     boost::static_property_map< distance > weight(1);
192     // The predecessor map is a vertex-to-vertex mapping.
193     typedef boost::unordered_map< vertex_descriptor, vertex_descriptor,
194         vertex_hash >
195         pred_map;
196     pred_map predecessor;
197     boost::associative_property_map< pred_map > pred_pmap(predecessor);
198     // The distance map is a vertex-to-distance mapping.
199     typedef boost::unordered_map< vertex_descriptor, distance, vertex_hash >
200         dist_map;
201     dist_map distance;
202     boost::associative_property_map< dist_map > dist_pmap(distance);
203 
204     vertex_descriptor s = source();
205     vertex_descriptor g = goal();
206     euclidean_heuristic heuristic(g);
207     astar_goal_visitor visitor(g);
208 
209     try
210     {
211         astar_search(m_barrier_grid, s, heuristic,
212             boost::weight_map(weight)
213                 .predecessor_map(pred_pmap)
214                 .distance_map(dist_pmap)
215                 .visitor(visitor));
216     }
217     catch (found_goal fg)
218     {
219         // Walk backwards from the goal through the predecessor chain adding
220         // vertices to the solution path.
221         for (vertex_descriptor u = g; u != s; u = predecessor[u])
222             m_solution.insert(u);
223         m_solution.insert(s);
224         m_solution_length = distance[g];
225         return true;
226     }
227 
228     return false;
229 }
230 
231 #define BARRIER "#"
232 // Print the maze as an ASCII map.
operator <<(std::ostream & output,const maze & m)233 std::ostream& operator<<(std::ostream& output, const maze& m)
234 {
235     // Header
236     for (vertices_size_type i = 0; i < m.length(0) + 2; i++)
237         output << BARRIER;
238     output << std::endl;
239     // Body
240     for (int y = m.length(1) - 1; y >= 0; y--)
241     {
242         // Enumerate rows in reverse order and columns in regular order so that
243         // (0,0) appears in the lower left-hand corner.  This requires that y be
244         // int and not the unsigned vertices_size_type because the loop exit
245         // condition is y==-1.
246         for (vertices_size_type x = 0; x < m.length(0); x++)
247         {
248             // Put a barrier on the left-hand side.
249             if (x == 0)
250                 output << BARRIER;
251             // Put the character representing this point in the maze grid.
252             vertex_descriptor u = { { x, vertices_size_type(y) } };
253             if (m.solution_contains(u))
254                 output << ".";
255             else if (m.has_barrier(u))
256                 output << BARRIER;
257             else
258                 output << " ";
259             // Put a barrier on the right-hand side.
260             if (x == m.length(0) - 1)
261                 output << BARRIER;
262         }
263         // Put a newline after every row except the last one.
264         output << std::endl;
265     }
266     // Footer
267     for (vertices_size_type i = 0; i < m.length(0) + 2; i++)
268         output << BARRIER;
269     if (m.solved())
270         output << std::endl << "Solution length " << m.m_solution_length;
271     return output;
272 }
273 
274 // Return a random integer in the interval [a, b].
random_int(std::size_t a,std::size_t b)275 std::size_t random_int(std::size_t a, std::size_t b)
276 {
277     if (b < a)
278         b = a;
279     boost::uniform_int<> dist(a, b);
280     boost::variate_generator< boost::mt19937&, boost::uniform_int<> > generate(
281         random_generator, dist);
282     return generate();
283 }
284 
285 // Generate a maze with a random assignment of barriers.
random_maze(maze & m)286 void random_maze(maze& m)
287 {
288     vertices_size_type n = num_vertices(m.m_grid);
289     vertex_descriptor s = m.source();
290     vertex_descriptor g = m.goal();
291     // One quarter of the cells in the maze should be barriers.
292     int barriers = n / 4;
293     while (barriers > 0)
294     {
295         // Choose horizontal or vertical direction.
296         std::size_t direction = random_int(0, 1);
297         // Walls range up to one quarter the dimension length in this direction.
298         vertices_size_type wall = random_int(1, m.length(direction) / 4);
299         // Create the wall while decrementing the total barrier count.
300         vertex_descriptor u = vertex(random_int(0, n - 1), m.m_grid);
301         while (wall)
302         {
303             // Start and goal spaces should never be barriers.
304             if (u != s && u != g)
305             {
306                 wall--;
307                 if (!m.has_barrier(u))
308                 {
309                     m.m_barriers.insert(u);
310                     barriers--;
311                 }
312             }
313             vertex_descriptor v = m.m_grid.next(u, direction);
314             // Stop creating this wall if we reached the maze's edge.
315             if (u == v)
316                 break;
317             u = v;
318         }
319     }
320 }
321 
main(int argc,char const * argv[])322 int main(int argc, char const* argv[])
323 {
324     // The default maze size is 20x10.  A different size may be specified on
325     // the command line.
326     std::size_t x = 20;
327     std::size_t y = 10;
328 
329     if (argc == 3)
330     {
331         x = boost::lexical_cast< std::size_t >(argv[1]);
332         y = boost::lexical_cast< std::size_t >(argv[2]);
333     }
334 
335     random_generator.seed(std::time(0));
336     maze m(x, y);
337     random_maze(m);
338     if (m.solve())
339         std::cout << "Solved the maze." << std::endl;
340     else
341         std::cout << "The maze is not solvable." << std::endl;
342     std::cout << m << std::endl;
343     return 0;
344 }
345