astar_maze.cpp 10 KB

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