| 1 | // Copyright W.P. McNeill 2010.
|
|---|
| 2 | // Copyright Georg Gast 2014
|
|---|
| 3 | //
|
|---|
| 4 | // Distributed under the Boost Software License, Version 1.0.
|
|---|
| 5 | // (See accompanying file LICENSE_1_0.txt or copy at
|
|---|
| 6 | // http://www.boost.org/LICENSE_1_0.txt)
|
|---|
| 7 |
|
|---|
| 8 |
|
|---|
| 9 | // This program uses the A-star search algorithm in the Boost Graph Library to
|
|---|
| 10 | // solve a maze. It is an example of how to apply Boost Graph Library
|
|---|
| 11 | // algorithms to filtered graphs.
|
|---|
| 12 | //
|
|---|
| 13 | // This program generates a random maze and then tries to find the shortest
|
|---|
| 14 | // path from the lower left-hand corner to the upper right-hand corner. Mazes
|
|---|
| 15 | // are represented by two-dimensional grids where a cell in the grid may
|
|---|
| 16 | // contain a barrier. You may move up, down, right, or left to any adjacent
|
|---|
| 17 | // cell that does not contain a barrier.
|
|---|
| 18 | //
|
|---|
| 19 | // Once a maze solution has been attempted, the maze is printed. If a
|
|---|
| 20 | // solution was found it will be shown in the maze printout.
|
|---|
| 21 | // Note that not all mazes have solutions.
|
|---|
| 22 | //
|
|---|
| 23 | // The default maze size is 20x10, though different dimensions may be
|
|---|
| 24 | // specified on the command line.
|
|---|
| 25 |
|
|---|
| 26 | // Boost
|
|---|
| 27 | #include <boost/array.hpp>
|
|---|
| 28 | #include <boost/graph/grid_graph.hpp>
|
|---|
| 29 | #include <boost/graph/filtered_graph.hpp>
|
|---|
| 30 | #include <boost/graph/astar_search.hpp>
|
|---|
| 31 |
|
|---|
| 32 | #include <boost/random/mersenne_twister.hpp>
|
|---|
| 33 | #include <boost/random/uniform_int_distribution.hpp>
|
|---|
| 34 |
|
|---|
| 35 | #include <boost/lexical_cast.hpp>
|
|---|
| 36 |
|
|---|
| 37 | // STL
|
|---|
| 38 | #include <iostream>
|
|---|
| 39 | #include <vector>
|
|---|
| 40 | #include <cmath>
|
|---|
| 41 | #include <ctime>
|
|---|
| 42 |
|
|---|
| 43 | // ------------------------------------------------------------------------------
|
|---|
| 44 | // Types and filters
|
|---|
| 45 | // ------------------------------------------------------------------------------
|
|---|
| 46 |
|
|---|
| 47 | #define DIMS 2
|
|---|
| 48 |
|
|---|
| 49 | // Distance traveled in the maze
|
|---|
| 50 | using distance = double;
|
|---|
| 51 |
|
|---|
| 52 | // the graph types
|
|---|
| 53 | using graph_t = boost::grid_graph < DIMS >;
|
|---|
| 54 | using traits_t = boost::graph_traits < graph_t >;
|
|---|
| 55 |
|
|---|
| 56 | using vertex_t = traits_t::vertex_descriptor;
|
|---|
| 57 | using edge_t = traits_t::edge_descriptor;
|
|---|
| 58 |
|
|---|
| 59 | using vertex_path = std::vector < vertex_t > ;
|
|---|
| 60 |
|
|---|
| 61 | // the property types
|
|---|
| 62 | using index_map_type_t = boost::property_map<graph_t, boost::vertex_index_t>::const_type;
|
|---|
| 63 | using barrier_map_t = boost::vector_property_map < bool, index_map_type_t >;
|
|---|
| 64 | using predessor_map_t = boost::vector_property_map < vertex_t, index_map_type_t >;
|
|---|
| 65 | using distance_map_t = boost::vector_property_map < distance, index_map_type_t >;
|
|---|
| 66 |
|
|---|
| 67 | // the filter
|
|---|
| 68 | template <typename BarrierMap>
|
|---|
| 69 | struct vertex_filter_no_barrier
|
|---|
| 70 | {
|
|---|
| 71 | vertex_filter_no_barrier() {}
|
|---|
| 72 | vertex_filter_no_barrier(const BarrierMap& b) : m_barrier(b) {}
|
|---|
| 73 |
|
|---|
| 74 | template <typename Vertex>
|
|---|
| 75 | bool operator()(const Vertex& v) const
|
|---|
| 76 | {
|
|---|
| 77 | return boost::get(m_barrier, v) == false;
|
|---|
| 78 | }
|
|---|
| 79 |
|
|---|
| 80 | BarrierMap m_barrier;
|
|---|
| 81 | };
|
|---|
| 82 | struct edge_filter_dummy
|
|---|
| 83 | {
|
|---|
| 84 | edge_filter_dummy() {}
|
|---|
| 85 |
|
|---|
| 86 | template <typename Edge>
|
|---|
| 87 | bool operator()(const Edge& e) const
|
|---|
| 88 | {
|
|---|
| 89 | return true;
|
|---|
| 90 | }
|
|---|
| 91 | };
|
|---|
| 92 |
|
|---|
| 93 | // the fitered grid
|
|---|
| 94 | using filtered_graph_t = boost::filtered_graph < graph_t, edge_filter_dummy, vertex_filter_no_barrier<barrier_map_t>>;
|
|---|
| 95 |
|
|---|
| 96 | // ------------------------------------------------------------------------------
|
|---|
| 97 | // Helper
|
|---|
| 98 | // ------------------------------------------------------------------------------
|
|---|
| 99 | vertex_t start_vertex(const graph_t& /*graph*/)
|
|---|
| 100 | {
|
|---|
| 101 | /*
|
|---|
| 102 | size_t size_x = graph.length(0);
|
|---|
| 103 | size_t size_y = graph.length(1);
|
|---|
| 104 | */
|
|---|
| 105 | vertex_t v = { 0, 0 };
|
|---|
| 106 | return v;
|
|---|
| 107 | }
|
|---|
| 108 | vertex_t goal_vertex(const graph_t& graph)
|
|---|
| 109 | {
|
|---|
| 110 | size_t size_x = graph.length(0);
|
|---|
| 111 | size_t size_y = graph.length(1);
|
|---|
| 112 |
|
|---|
| 113 | vertex_t v = { size_x - 1, size_y - 1 };
|
|---|
| 114 | return v;
|
|---|
| 115 | }
|
|---|
| 116 |
|
|---|
| 117 | #define BARRIER "#"
|
|---|
| 118 | void print_maze(const graph_t& graph, const barrier_map_t& barrier, const vertex_path& solution)
|
|---|
| 119 | {
|
|---|
| 120 | size_t size_x = graph.length(0);
|
|---|
| 121 | size_t size_y = graph.length(1);
|
|---|
| 122 |
|
|---|
| 123 | // Header
|
|---|
| 124 | for (size_t i = 0; i < size_x + 2; i++)
|
|---|
| 125 | std::cout << BARRIER;
|
|---|
| 126 | std::cout << std::endl;
|
|---|
| 127 |
|
|---|
| 128 | vertex_t s = start_vertex(graph);
|
|---|
| 129 | vertex_t g = goal_vertex(graph);
|
|---|
| 130 |
|
|---|
| 131 | // body
|
|---|
| 132 | // x: left to right
|
|---|
| 133 | // y: bottom to top (0 bottom, growing upwards)
|
|---|
| 134 | for (size_t y = size_y - 1; y < size_y; --y) // underflow intended
|
|---|
| 135 | {
|
|---|
| 136 | for (size_t x = 0; x < size_x; ++x)
|
|---|
| 137 | {
|
|---|
| 138 | // barrier on the left side
|
|---|
| 139 | if (x == 0)
|
|---|
| 140 | {
|
|---|
| 141 | std::cout << BARRIER;
|
|---|
| 142 | }
|
|---|
| 143 |
|
|---|
| 144 | vertex_t v = { x, y };
|
|---|
| 145 | bool is_solution_vtx =
|
|---|
| 146 | (std::find(solution.begin(), solution.end(), v) != solution.end());
|
|---|
| 147 |
|
|---|
| 148 | if (get(barrier, v))
|
|---|
| 149 | {
|
|---|
| 150 | std::cout << "#";
|
|---|
| 151 | }
|
|---|
| 152 | else if (is_solution_vtx)
|
|---|
| 153 | {
|
|---|
| 154 | std::cout << ".";
|
|---|
| 155 | }
|
|---|
| 156 | else if (v == s)
|
|---|
| 157 | {
|
|---|
| 158 | std::cout << "S";
|
|---|
| 159 | }
|
|---|
| 160 | else if (v == g)
|
|---|
| 161 | {
|
|---|
| 162 | std::cout << "G";
|
|---|
| 163 | }
|
|---|
| 164 | else
|
|---|
| 165 | {
|
|---|
| 166 | std::cout << " ";
|
|---|
| 167 | }
|
|---|
| 168 |
|
|---|
| 169 | // barrier on the right side
|
|---|
| 170 | if (x == size_x-1)
|
|---|
| 171 | {
|
|---|
| 172 | std::cout << BARRIER;
|
|---|
| 173 | }
|
|---|
| 174 |
|
|---|
| 175 | }
|
|---|
| 176 | std::cout << std::endl;
|
|---|
| 177 | }
|
|---|
| 178 |
|
|---|
| 179 | // footer
|
|---|
| 180 | for (size_t i = 0; i < size_x + 2; i++)
|
|---|
| 181 | std::cout << BARRIER;
|
|---|
| 182 | std::cout << std::endl;
|
|---|
| 183 |
|
|---|
| 184 | }
|
|---|
| 185 |
|
|---|
| 186 | void create_maze(const graph_t& graph, barrier_map_t& barrier)
|
|---|
| 187 | {
|
|---|
| 188 | std::cout << "Creating Barriers ..." << std::endl;
|
|---|
| 189 |
|
|---|
| 190 | size_t size_x = graph.length(0);
|
|---|
| 191 | size_t size_y = graph.length(1);
|
|---|
| 192 | size_t total_space = size_x * size_y;
|
|---|
| 193 |
|
|---|
| 194 | size_t barriers_left = total_space / 4;
|
|---|
| 195 | boost::random::mt19937 gen;
|
|---|
| 196 | gen.seed(std::time(0));
|
|---|
| 197 |
|
|---|
| 198 | boost::random::uniform_int_distribution<> start_x(0, size_x - 1);
|
|---|
| 199 | boost::random::uniform_int_distribution<> start_y(0, size_y - 1);
|
|---|
| 200 | boost::random::uniform_int_distribution<> direction(0, 1);
|
|---|
| 201 | boost::random::uniform_int_distribution<> wall_length_x(1, size_x/4);
|
|---|
| 202 | boost::random::uniform_int_distribution<> wall_length_y(1, size_y/4);
|
|---|
| 203 |
|
|---|
| 204 | // start and goal vertex
|
|---|
| 205 | vertex_t s = start_vertex(graph);
|
|---|
| 206 | vertex_t g = goal_vertex(graph);
|
|---|
| 207 |
|
|---|
| 208 | while (barriers_left)
|
|---|
| 209 | {
|
|---|
| 210 | size_t sx = start_x(gen);
|
|---|
| 211 | size_t sy = start_y(gen);
|
|---|
| 212 |
|
|---|
| 213 | int dx, dy, wall;
|
|---|
| 214 | if (direction(gen) == 0)
|
|---|
| 215 | {
|
|---|
| 216 | // x
|
|---|
| 217 | dx = 1; dy = 0;
|
|---|
| 218 | wall = wall_length_x(gen);
|
|---|
| 219 | }
|
|---|
| 220 | else
|
|---|
| 221 | {
|
|---|
| 222 | // y
|
|---|
| 223 | dx = 0; dy = 1;
|
|---|
| 224 | wall = wall_length_y(gen);
|
|---|
| 225 | }
|
|---|
| 226 |
|
|---|
| 227 | // limit wall length to 20
|
|---|
| 228 | if (wall > 10) wall = 10;
|
|---|
| 229 |
|
|---|
| 230 | while (wall > 0 && barriers_left > 0)
|
|---|
| 231 | {
|
|---|
| 232 | vertex_t v = { sx, sy };
|
|---|
| 233 |
|
|---|
| 234 | // we dont put a barrier on start or goal
|
|---|
| 235 | if (v == s || v == g)
|
|---|
| 236 | break;
|
|---|
| 237 |
|
|---|
| 238 | bool current_value = boost::get(barrier, v);
|
|---|
| 239 | if (current_value == false)
|
|---|
| 240 | {
|
|---|
| 241 | boost::put(barrier, v, true);
|
|---|
| 242 | barriers_left--;
|
|---|
| 243 | wall--;
|
|---|
| 244 | }
|
|---|
| 245 | else
|
|---|
| 246 | {
|
|---|
| 247 | // stop this wall on this obstacle
|
|---|
| 248 | break;
|
|---|
| 249 | }
|
|---|
| 250 | // next vertex
|
|---|
| 251 | sx += dx; sy += dy;
|
|---|
| 252 |
|
|---|
| 253 | // we dont go outside of our grid
|
|---|
| 254 | if (sx >= size_x || sy >= size_y)
|
|---|
| 255 | break;
|
|---|
| 256 | if (barriers_left % 100 == 0)
|
|---|
| 257 | {
|
|---|
| 258 | std::cout << barriers_left << "...";
|
|---|
| 259 | }
|
|---|
| 260 | }
|
|---|
| 261 | }
|
|---|
| 262 | std::cout << std::endl;
|
|---|
| 263 | }
|
|---|
| 264 | // ------------------------------------------------------------------------------
|
|---|
| 265 | // AStar
|
|---|
| 266 | // ------------------------------------------------------------------------------
|
|---|
| 267 | // Visitor that terminates when we find the goal vertex
|
|---|
| 268 | struct found_goal {}; // exception which is thrown when we find a solution
|
|---|
| 269 | struct astar_goal_visitor :public boost::default_astar_visitor
|
|---|
| 270 | {
|
|---|
| 271 | astar_goal_visitor(vertex_t goal) :m_goal(goal) {};
|
|---|
| 272 |
|
|---|
| 273 | void examine_vertex(vertex_t u, const filtered_graph_t&)
|
|---|
| 274 | {
|
|---|
| 275 | if (u == m_goal)
|
|---|
| 276 | throw found_goal();
|
|---|
| 277 | }
|
|---|
| 278 |
|
|---|
| 279 | private:
|
|---|
| 280 | vertex_t m_goal;
|
|---|
| 281 | };
|
|---|
| 282 |
|
|---|
| 283 | // This calculates the Euclidean distance between a vertex and a goal vertex.
|
|---|
| 284 | class euclidean_heuristic :
|
|---|
| 285 | public boost::astar_heuristic<filtered_graph_t, double>
|
|---|
| 286 | {
|
|---|
| 287 | public:
|
|---|
| 288 | euclidean_heuristic(vertex_t goal) :m_goal(goal) {};
|
|---|
| 289 |
|
|---|
| 290 | double operator()(vertex_t v) {
|
|---|
| 291 | return sqrt(pow(double(m_goal[0] - v[0]), 2) + pow(double(m_goal[1] - v[1]), 2));
|
|---|
| 292 | }
|
|---|
| 293 |
|
|---|
| 294 | private:
|
|---|
| 295 | vertex_t m_goal;
|
|---|
| 296 | };
|
|---|
| 297 |
|
|---|
| 298 | void solve(vertex_t s, vertex_t g, filtered_graph_t& fg, vertex_path& solution)
|
|---|
| 299 | {
|
|---|
| 300 | // get the index map for the associated properties
|
|---|
| 301 | index_map_type_t index_map(get(boost::vertex_index, fg));
|
|---|
| 302 |
|
|---|
| 303 | // The predecessor map is a vertex-to-vertex mapping.
|
|---|
| 304 | predessor_map_t predecessor(num_vertices(fg), index_map);
|
|---|
| 305 |
|
|---|
| 306 | // The distance map is a vertex-to-distance mapping.
|
|---|
| 307 | distance_map_t distances(num_vertices(fg), index_map);
|
|---|
| 308 |
|
|---|
| 309 | // finally the weight map
|
|---|
| 310 | boost::static_property_map<distance> weight(1);
|
|---|
| 311 |
|
|---|
| 312 | euclidean_heuristic heuristic(g);
|
|---|
| 313 | astar_goal_visitor visitor(g);
|
|---|
| 314 |
|
|---|
| 315 | try {
|
|---|
| 316 | astar_search(fg, s, heuristic,
|
|---|
| 317 | boost::weight_map(weight).
|
|---|
| 318 | predecessor_map(predecessor).
|
|---|
| 319 | distance_map(distances).
|
|---|
| 320 | visitor(visitor));
|
|---|
| 321 | }
|
|---|
| 322 | catch (const found_goal& /*fg*/) {
|
|---|
| 323 | // Walk backwards from the goal through the predecessor chain adding
|
|---|
| 324 | // vertices to the solution path.
|
|---|
| 325 | for (vertex_t u = g; u != s; u = predecessor[u])
|
|---|
| 326 | {
|
|---|
| 327 | if (u != s && u != g) solution.push_back(u);
|
|---|
| 328 | }
|
|---|
| 329 | }
|
|---|
| 330 | }
|
|---|
| 331 | // ------------------------------------------------------------------------------
|
|---|
| 332 | // main
|
|---|
| 333 | // ------------------------------------------------------------------------------
|
|---|
| 334 | int main(int argc, char** argv)
|
|---|
| 335 | {
|
|---|
| 336 | // The default maze size is 20x10. A different size may be specified on
|
|---|
| 337 | // the command line.
|
|---|
| 338 | std::size_t x = 20;
|
|---|
| 339 | std::size_t y = 10;
|
|---|
| 340 |
|
|---|
| 341 | if (argc == 3) {
|
|---|
| 342 | x = boost::lexical_cast<std::size_t>(argv[1]);
|
|---|
| 343 | y = boost::lexical_cast<std::size_t>(argv[2]);
|
|---|
| 344 | }
|
|---|
| 345 |
|
|---|
| 346 | // Define a grid which doesnt wrap
|
|---|
| 347 | boost::array<std::size_t, DIMS> lengths = { x, y };
|
|---|
| 348 | boost::array<bool, DIMS> wrapped = { false, false };
|
|---|
| 349 | graph_t graph(lengths, wrapped);
|
|---|
| 350 |
|
|---|
| 351 | // Get the index map of the grid graph
|
|---|
| 352 | index_map_type_t index_map(get(boost::vertex_index, graph));
|
|---|
| 353 |
|
|---|
| 354 | // create the property for the vertexes: Barrier
|
|---|
| 355 | barrier_map_t barrier_map(num_vertices(graph), index_map);
|
|---|
| 356 |
|
|---|
| 357 | // create the walls
|
|---|
| 358 | create_maze(graph, barrier_map);
|
|---|
| 359 |
|
|---|
| 360 | // filter the graph
|
|---|
| 361 | filtered_graph_t fg(graph, edge_filter_dummy(), vertex_filter_no_barrier<barrier_map_t>(barrier_map));
|
|---|
| 362 |
|
|---|
| 363 | vertex_path solution;
|
|---|
| 364 | vertex_t s = start_vertex(graph);
|
|---|
| 365 | vertex_t g = goal_vertex(graph);
|
|---|
| 366 | solve(s,g,fg, solution);
|
|---|
| 367 | print_maze(graph, barrier_map, solution);
|
|---|
| 368 |
|
|---|
| 369 | if (solution.size())
|
|---|
| 370 | std::cout << "Maze solved" << std::endl;
|
|---|
| 371 | else
|
|---|
| 372 | std::cout << "Maze _NOT_ solved" << std::endl;
|
|---|
| 373 | }
|
|---|