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)
8 // This program uses the A-star search algorithm in the Boost Graph Library to
9 // solve a maze. It is an example of how to apply Boost Graph Library
10 // algorithms to implicit graphs.
12 // This program generates a random maze and then tries to find the shortest
13 // path from the lower left-hand corner to the upper right-hand corner. Mazes
14 // are represented by two-dimensional grids where a cell in the grid may
15 // contain a barrier. You may move up, down, right, or left to any adjacent
16 // cell that does not contain a barrier.
18 // Once a maze solution has been attempted, the maze is printed. If a
19 // solution was found it will be shown in the maze printout and its length
20 // will be returned. Note that not all mazes have solutions.
22 // The default maze size is 20x10, though different dimensions may be
23 // specified on the command line.
29 This example appears to be broken and crashes at runtime, see https://github.com/boostorg/graph/issues/148
34 #include <boost/graph/astar_search.hpp>
35 #include <boost/graph/filtered_graph.hpp>
36 #include <boost/graph/grid_graph.hpp>
37 #include <boost/lexical_cast.hpp>
38 #include <boost/random/mersenne_twister.hpp>
39 #include <boost/random/uniform_int.hpp>
40 #include <boost/random/variate_generator.hpp>
41 #include <boost/unordered_map.hpp>
42 #include <boost/unordered_set.hpp>
46 boost::mt19937 random_generator
;
48 // Distance traveled in the maze
49 typedef double distance
;
52 typedef boost::grid_graph
<GRID_RANK
> grid
;
53 typedef boost::graph_traits
<grid
>::vertex_descriptor vertex_descriptor
;
54 typedef boost::graph_traits
<grid
>::vertices_size_type vertices_size_type
;
56 // A hash function for vertices.
58 typedef vertex_descriptor argument_type
;
59 typedef std::size_t result_type
;
60 std::size_t operator()(vertex_descriptor
const& u
) const {
62 boost::hash_combine(seed
, u
[0]);
63 boost::hash_combine(seed
, u
[1]);
68 typedef boost::unordered_set
<vertex_descriptor
, vertex_hash
> vertex_set
;
69 typedef boost::vertex_subset_complement_filter
<grid
, vertex_set
>::type
74 // The maze is grid of locations which can either be empty or contain a
75 // barrier. You can move to an adjacent location in the grid by going up,
76 // down, left and right. Moving onto a barrier is not allowed. The maze can
77 // be solved by finding a path from the lower-left-hand corner to the
78 // upper-right-hand corner. If no open path exists between these two
79 // locations, the maze is unsolvable.
81 // The maze is implemented as a filtered grid graph where locations are
82 // vertices. Barrier vertices are filtered out of the graph.
84 // A-star search is used to find a path through the maze. Each edge has a
85 // weight of one, so the total path length is equal to the number of edges
89 friend std::ostream
& operator<<(std::ostream
&, const maze
&);
90 friend void random_maze(maze
&);
92 maze():m_grid(create_grid(0, 0)),m_barrier_grid(create_barrier_grid()) {};
93 maze(std::size_t x
, std::size_t y
):m_grid(create_grid(x
, y
)),
94 m_barrier_grid(create_barrier_grid()) {};
96 // The length of the maze along the specified dimension.
97 vertices_size_type
length(std::size_t d
) const {return m_grid
.length(d
);}
99 bool has_barrier(vertex_descriptor u
) const {
100 return m_barriers
.find(u
) != m_barriers
.end();
103 // Try to find a path from the lower-left-hand corner source (0,0) to the
104 // upper-right-hand corner goal (x-1, y-1).
105 vertex_descriptor
source() const {return vertex(0, m_grid
);}
106 vertex_descriptor
goal() const {
107 return vertex(num_vertices(m_grid
)-1, m_grid
);
111 bool solved() const {return !m_solution
.empty();}
112 bool solution_contains(vertex_descriptor u
) const {
113 return m_solution
.find(u
) != m_solution
.end();
117 // Create the underlying rank-2 grid with the specified dimensions.
118 grid
create_grid(std::size_t x
, std::size_t y
) {
119 boost::array
<std::size_t, GRID_RANK
> lengths
= { {x
, y
} };
120 return grid(lengths
);
123 // Filter the barrier vertices out of the underlying grid.
124 filtered_grid
create_barrier_grid() {
125 return boost::make_vertex_subset_complement_filter(m_grid
, m_barriers
);
128 // The grid underlying the maze
130 // The barriers in the maze
131 vertex_set m_barriers
;
132 // The underlying maze grid with barrier vertices filtered out
133 filtered_grid m_barrier_grid
;
134 // The vertices on a solution path through the maze
135 vertex_set m_solution
;
136 // The length of the solution path
137 distance m_solution_length
;
141 // Euclidean heuristic for a grid
143 // This calculates the Euclidean distance between a vertex and a goal
145 class euclidean_heuristic
:
146 public boost::astar_heuristic
<filtered_grid
, double>
149 euclidean_heuristic(vertex_descriptor goal
):m_goal(goal
) {};
151 double operator()(vertex_descriptor v
) {
152 return sqrt(pow(double(m_goal
[0] - v
[0]), 2) + pow(double(m_goal
[1] - v
[1]), 2));
156 vertex_descriptor m_goal
;
159 // Exception thrown when the goal vertex is found
160 struct found_goal
{};
162 // Visitor that terminates when we find the goal vertex
163 struct astar_goal_visitor
:public boost::default_astar_visitor
{
164 astar_goal_visitor(vertex_descriptor goal
):m_goal(goal
) {};
166 void examine_vertex(vertex_descriptor u
, const filtered_grid
&) {
172 vertex_descriptor m_goal
;
175 // Solve the maze using A-star search. Return true if a solution was found.
177 boost::static_property_map
<distance
> weight(1);
178 // The predecessor map is a vertex-to-vertex mapping.
179 typedef boost::unordered_map
<vertex_descriptor
,
181 vertex_hash
> pred_map
;
182 pred_map predecessor
;
183 boost::associative_property_map
<pred_map
> pred_pmap(predecessor
);
184 // The distance map is a vertex-to-distance mapping.
185 typedef boost::unordered_map
<vertex_descriptor
,
187 vertex_hash
> dist_map
;
189 boost::associative_property_map
<dist_map
> dist_pmap(distance
);
191 vertex_descriptor s
= source();
192 vertex_descriptor g
= goal();
193 euclidean_heuristic
heuristic(g
);
194 astar_goal_visitor
visitor(g
);
197 astar_search(m_barrier_grid
, s
, heuristic
,
198 boost::weight_map(weight
).
199 predecessor_map(pred_pmap
).
200 distance_map(dist_pmap
).
202 } catch(found_goal fg
) {
203 // Walk backwards from the goal through the predecessor chain adding
204 // vertices to the solution path.
205 for (vertex_descriptor u
= g
; u
!= s
; u
= predecessor
[u
])
206 m_solution
.insert(u
);
207 m_solution
.insert(s
);
208 m_solution_length
= distance
[g
];
217 // Print the maze as an ASCII map.
218 std::ostream
& operator<<(std::ostream
& output
, const maze
& m
) {
220 for (vertices_size_type i
= 0; i
< m
.length(0)+2; i
++)
224 for (int y
= m
.length(1)-1; y
>= 0; y
--) {
225 // Enumerate rows in reverse order and columns in regular order so that
226 // (0,0) appears in the lower left-hand corner. This requires that y be
227 // int and not the unsigned vertices_size_type because the loop exit
228 // condition is y==-1.
229 for (vertices_size_type x
= 0; x
< m
.length(0); x
++) {
230 // Put a barrier on the left-hand side.
233 // Put the character representing this point in the maze grid.
234 vertex_descriptor u
= {{x
, vertices_size_type(y
)}};
235 if (m
.solution_contains(u
))
237 else if (m
.has_barrier(u
))
241 // Put a barrier on the right-hand side.
242 if (x
== m
.length(0)-1)
245 // Put a newline after every row except the last one.
249 for (vertices_size_type i
= 0; i
< m
.length(0)+2; i
++)
252 output
<< std::endl
<< "Solution length " << m
.m_solution_length
;
256 // Return a random integer in the interval [a, b].
257 std::size_t random_int(std::size_t a
, std::size_t b
) {
260 boost::uniform_int
<> dist(a
, b
);
261 boost::variate_generator
<boost::mt19937
&, boost::uniform_int
<> >
262 generate(random_generator
, dist
);
266 // Generate a maze with a random assignment of barriers.
267 void random_maze(maze
& m
) {
268 vertices_size_type n
= num_vertices(m
.m_grid
);
269 vertex_descriptor s
= m
.source();
270 vertex_descriptor g
= m
.goal();
271 // One quarter of the cells in the maze should be barriers.
273 while (barriers
> 0) {
274 // Choose horizontal or vertical direction.
275 std::size_t direction
= random_int(0, 1);
276 // Walls range up to one quarter the dimension length in this direction.
277 vertices_size_type wall
= random_int(1, m
.length(direction
)/4);
278 // Create the wall while decrementing the total barrier count.
279 vertex_descriptor u
= vertex(random_int(0, n
-1), m
.m_grid
);
281 // Start and goal spaces should never be barriers.
282 if (u
!= s
&& u
!= g
) {
284 if (!m
.has_barrier(u
)) {
285 m
.m_barriers
.insert(u
);
289 vertex_descriptor v
= m
.m_grid
.next(u
, direction
);
290 // Stop creating this wall if we reached the maze's edge.
298 int main (int argc
, char const *argv
[]) {
299 // The default maze size is 20x10. A different size may be specified on
305 x
= boost::lexical_cast
<std::size_t>(argv
[1]);
306 y
= boost::lexical_cast
<std::size_t>(argv
[2]);
309 random_generator
.seed(std::time(0));
313 std::cout
<< "Solved the maze." << std::endl
;
315 std::cout
<< "The maze is not solvable." << std::endl
;
316 std::cout
<< m
<< std::endl
;