summaryrefslogtreecommitdiff
path: root/libs/graph/example/astar_maze.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'libs/graph/example/astar_maze.cpp')
-rw-r--r--libs/graph/example/astar_maze.cpp311
1 files changed, 311 insertions, 0 deletions
diff --git a/libs/graph/example/astar_maze.cpp b/libs/graph/example/astar_maze.cpp
new file mode 100644
index 0000000000..20cff4fdd1
--- /dev/null
+++ b/libs/graph/example/astar_maze.cpp
@@ -0,0 +1,311 @@
+
+// Copyright W.P. McNeill 2010.
+// Distributed under the Boost Software License, Version 1.0.
+// (See accompanying file LICENSE_1_0.txt or copy at
+// http://www.boost.org/LICENSE_1_0.txt)
+
+
+// This program uses the A-star search algorithm in the Boost Graph Library to
+// solve a maze. It is an example of how to apply Boost Graph Library
+// algorithms to implicit graphs.
+//
+// This program generates a random maze and then tries to find the shortest
+// path from the lower left-hand corner to the upper right-hand corner. Mazes
+// are represented by two-dimensional grids where a cell in the grid may
+// contain a barrier. You may move up, down, right, or left to any adjacent
+// cell that does not contain a barrier.
+//
+// Once a maze solution has been attempted, the maze is printed. If a
+// solution was found it will be shown in the maze printout and its length
+// will be returned. Note that not all mazes have solutions.
+//
+// The default maze size is 20x10, though different dimensions may be
+// specified on the command line.
+
+
+#include <boost/graph/astar_search.hpp>
+#include <boost/graph/filtered_graph.hpp>
+#include <boost/graph/grid_graph.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/random/mersenne_twister.hpp>
+#include <boost/random/uniform_int.hpp>
+#include <boost/random/variate_generator.hpp>
+#include <boost/unordered_map.hpp>
+#include <boost/unordered_set.hpp>
+#include <ctime>
+#include <iostream>
+
+boost::mt19937 random_generator;
+
+// Distance traveled in the maze
+typedef double distance;
+
+#define GRID_RANK 2
+typedef boost::grid_graph<GRID_RANK> grid;
+typedef boost::graph_traits<grid>::vertex_descriptor vertex_descriptor;
+typedef boost::graph_traits<grid>::vertices_size_type vertices_size_type;
+
+// A hash function for vertices.
+struct vertex_hash:std::unary_function<vertex_descriptor, std::size_t> {
+ std::size_t operator()(vertex_descriptor const& u) const {
+ std::size_t seed = 0;
+ boost::hash_combine(seed, u[0]);
+ boost::hash_combine(seed, u[1]);
+ return seed;
+ }
+};
+
+typedef boost::unordered_set<vertex_descriptor, vertex_hash> vertex_set;
+typedef boost::vertex_subset_complement_filter<grid, vertex_set>::type
+ filtered_grid;
+
+// A searchable maze
+//
+// The maze is grid of locations which can either be empty or contain a
+// barrier. You can move to an adjacent location in the grid by going up,
+// down, left and right. Moving onto a barrier is not allowed. The maze can
+// be solved by finding a path from the lower-left-hand corner to the
+// upper-right-hand corner. If no open path exists between these two
+// locations, the maze is unsolvable.
+//
+// The maze is implemented as a filtered grid graph where locations are
+// vertices. Barrier vertices are filtered out of the graph.
+//
+// A-star search is used to find a path through the maze. Each edge has a
+// weight of one, so the total path length is equal to the number of edges
+// traversed.
+class maze {
+public:
+ friend std::ostream& operator<<(std::ostream&, const maze&);
+ friend maze random_maze(std::size_t, std::size_t);
+
+ maze():m_grid(create_grid(0, 0)),m_barrier_grid(create_barrier_grid()) {};
+ maze(std::size_t x, std::size_t y):m_grid(create_grid(x, y)),
+ m_barrier_grid(create_barrier_grid()) {};
+
+ // The length of the maze along the specified dimension.
+ vertices_size_type length(std::size_t d) const {return m_grid.length(d);}
+
+ bool has_barrier(vertex_descriptor u) const {
+ return m_barriers.find(u) != m_barriers.end();
+ }
+
+ // Try to find a path from the lower-left-hand corner source (0,0) to the
+ // upper-right-hand corner goal (x-1, y-1).
+ vertex_descriptor source() const {return vertex(0, m_grid);}
+ vertex_descriptor goal() const {
+ return vertex(num_vertices(m_grid)-1, m_grid);
+ }
+
+ bool solve();
+ bool solved() const {return !m_solution.empty();}
+ bool solution_contains(vertex_descriptor u) const {
+ return m_solution.find(u) != m_solution.end();
+ }
+
+private:
+ // Create the underlying rank-2 grid with the specified dimensions.
+ grid create_grid(std::size_t x, std::size_t y) {
+ boost::array<std::size_t, GRID_RANK> lengths = { {x, y} };
+ return grid(lengths);
+ }
+
+ // Filter the barrier vertices out of the underlying grid.
+ filtered_grid create_barrier_grid() {
+ return boost::make_vertex_subset_complement_filter(m_grid, m_barriers);
+ }
+
+ // The grid underlying the maze
+ grid m_grid;
+ // The underlying maze grid with barrier vertices filtered out
+ filtered_grid m_barrier_grid;
+ // The barriers in the maze
+ vertex_set m_barriers;
+ // The vertices on a solution path through the maze
+ vertex_set m_solution;
+ // The length of the solution path
+ distance m_solution_length;
+};
+
+
+// Euclidean heuristic for a grid
+//
+// This calculates the Euclidean distance between a vertex and a goal
+// vertex.
+class euclidean_heuristic:
+ public boost::astar_heuristic<filtered_grid, double>
+{
+public:
+ euclidean_heuristic(vertex_descriptor goal):m_goal(goal) {};
+
+ double operator()(vertex_descriptor v) {
+ return sqrt(pow(m_goal[0] - v[0], 2) + pow(m_goal[1] - v[1], 2));
+ }
+
+private:
+ vertex_descriptor m_goal;
+};
+
+// Exception thrown when the goal vertex is found
+struct found_goal {};
+
+// Visitor that terminates when we find the goal vertex
+struct astar_goal_visitor:public boost::default_astar_visitor {
+ astar_goal_visitor(vertex_descriptor goal):m_goal(goal) {};
+
+ void examine_vertex(vertex_descriptor u, const filtered_grid&) {
+ if (u == m_goal)
+ throw found_goal();
+ }
+
+private:
+ vertex_descriptor m_goal;
+};
+
+// Solve the maze using A-star search. Return true if a solution was found.
+bool maze::solve() {
+ boost::static_property_map<distance> weight(1);
+ // The predecessor map is a vertex-to-vertex mapping.
+ typedef boost::unordered_map<vertex_descriptor,
+ vertex_descriptor,
+ vertex_hash> pred_map;
+ pred_map predecessor;
+ boost::associative_property_map<pred_map> pred_pmap(predecessor);
+ // The distance map is a vertex-to-distance mapping.
+ typedef boost::unordered_map<vertex_descriptor,
+ distance,
+ vertex_hash> dist_map;
+ dist_map distance;
+ boost::associative_property_map<dist_map> dist_pmap(distance);
+
+ vertex_descriptor s = source();
+ vertex_descriptor g = goal();
+ euclidean_heuristic heuristic(g);
+ astar_goal_visitor visitor(g);
+
+ try {
+ astar_search(m_barrier_grid, s, heuristic,
+ boost::weight_map(weight).
+ predecessor_map(pred_pmap).
+ distance_map(dist_pmap).
+ visitor(visitor) );
+ } catch(found_goal fg) {
+ // Walk backwards from the goal through the predecessor chain adding
+ // vertices to the solution path.
+ for (vertex_descriptor u = g; u != s; u = predecessor[u])
+ m_solution.insert(u);
+ m_solution.insert(s);
+ m_solution_length = distance[g];
+ return true;
+ }
+
+ return false;
+}
+
+
+#define BARRIER "#"
+// Print the maze as an ASCII map.
+std::ostream& operator<<(std::ostream& output, const maze& m) {
+ // Header
+ for (vertices_size_type i = 0; i < m.length(0)+2; i++)
+ output << BARRIER;
+ output << std::endl;
+ // Body
+ for (int y = m.length(1)-1; y >= 0; y--) {
+ // Enumerate rows in reverse order and columns in regular order so that
+ // (0,0) appears in the lower left-hand corner. This requires that y be
+ // int and not the unsigned vertices_size_type because the loop exit
+ // condition is y==-1.
+ for (vertices_size_type x = 0; x < m.length(0); x++) {
+ // Put a barrier on the left-hand side.
+ if (x == 0)
+ output << BARRIER;
+ // Put the character representing this point in the maze grid.
+ vertex_descriptor u = {{x, y}};
+ if (m.solution_contains(u))
+ output << ".";
+ else if (m.has_barrier(u))
+ output << BARRIER;
+ else
+ output << " ";
+ // Put a barrier on the right-hand side.
+ if (x == m.length(0)-1)
+ output << BARRIER;
+ }
+ // Put a newline after every row except the last one.
+ output << std::endl;
+ }
+ // Footer
+ for (vertices_size_type i = 0; i < m.length(0)+2; i++)
+ output << BARRIER;
+ if (m.solved())
+ output << std::endl << "Solution length " << m.m_solution_length;
+ return output;
+}
+
+// Return a random integer in the interval [a, b].
+std::size_t random_int(std::size_t a, std::size_t b) {
+ if (b < a)
+ b = a;
+ boost::uniform_int<> dist(a, b);
+ boost::variate_generator<boost::mt19937&, boost::uniform_int<> >
+ generate(random_generator, dist);
+ return generate();
+}
+
+// Generate a maze with a random assignment of barriers.
+maze random_maze(std::size_t x, std::size_t y) {
+ maze m(x, y);
+ vertices_size_type n = num_vertices(m.m_grid);
+ vertex_descriptor s = m.source();
+ vertex_descriptor g = m.goal();
+ // One quarter of the cells in the maze should be barriers.
+ int barriers = n/4;
+ while (barriers > 0) {
+ // Choose horizontal or vertical direction.
+ std::size_t direction = random_int(0, 1);
+ // Walls range up to one quarter the dimension length in this direction.
+ vertices_size_type wall = random_int(1, m.length(direction)/4);
+ // Create the wall while decrementing the total barrier count.
+ vertex_descriptor u = vertex(random_int(0, n-1), m.m_grid);
+ while (wall) {
+ // Start and goal spaces should never be barriers.
+ if (u != s && u != g) {
+ wall--;
+ if (!m.has_barrier(u)) {
+ m.m_barriers.insert(u);
+ barriers--;
+ }
+ }
+ vertex_descriptor v = m.m_grid.next(u, direction);
+ // Stop creating this wall if we reached the maze's edge.
+ if (u == v)
+ break;
+ u = v;
+ }
+ }
+ return m;
+}
+
+
+int main (int argc, char const *argv[]) {
+ // The default maze size is 20x10. A different size may be specified on
+ // the command line.
+ std::size_t x = 20;
+ std::size_t y = 10;
+
+ if (argc == 3) {
+ x = boost::lexical_cast<std::size_t>(argv[1]);
+ y = boost::lexical_cast<std::size_t>(argv[2]);
+ }
+
+ random_generator.seed(std::time(0));
+ maze m = random_maze(x, y);
+
+ if (m.solve())
+ std::cout << "Solved the maze." << std::endl;
+ else
+ std::cout << "The maze is not solvable." << std::endl;
+ std::cout << m << std::endl;
+ return 0;
+}