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