• 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