This is my A-Star implementation for a 2D space of nodes but allowing to specifically set connections between nodes because my usecase does not have points tied to a grid (however for clarification reasons, the sample data down there does form a 3x3 grid with non-diagonal connections).
So basically it is A-Star on a graph with using euclidean distance as weights for moving between nodes, I believe.
#include <iostream>
#include <algorithm>
#include <vector>
#include <set>
#include <cmath>
#include <string>
#include <limits>
struct Point {
Point(int x = 0, int y = 0) : x(x), y(y) {};
bool operator ==(const Point& o) const { return o.x == x && o.y == y; }
int DistTo(Point & other) const { return(std::sqrt(std::pow(other.x - x, 2) + std::pow(other.y - y, 2))); };
int x, y;
void Print(std::string name) const
{
std::cout << name << " x: " << x << " y: " << y << std::endl;
}
};
struct Node
{
Node(Point position = Point(), Node * parent = nullptr) : position(position), parent(parent) {};
Point position;
int dist_to_start = 0;
int total_dist = INT_MAX;
std::shared_ptr<Node> parent;
std::shared_ptr< std::vector<Node> > connections{ new std::vector<Node> };
bool operator ==(const Node& o) const { return (o.position.x == position.x) && (o.position.y == position.y); }
bool operator <(const Node& o) const { return (total_dist < o.total_dist); }
void AddConnection(Node subnode)
{
connections->push_back(subnode);
}
};
std::vector<Point> Astar(Point & start_pos, Point & end_pos, std::vector<Node> all_nodes)
{
// Find the start and end nodes
Node start_node;
Node end_node;
for (Node & node : all_nodes)
{
if (node.position == end_pos) // start and end swaped here because A-Star traces from end to start
start_node = node;
if (node.position == start_pos)
end_node = node;
}
std::vector<Point> found_path; // Output path
std::set<Node> open_list; // Using set to be ordered
std::vector<Point> closed_list;
open_list.emplace(start_node);
while (!open_list.empty())
{
// Use node with lowest total_dist (the set is always sorted)
Node current_node = *open_list.begin();
// Remove smallest and add to closed list
open_list.erase(open_list.begin());
closed_list.push_back(current_node.position);
// Found the goal
if (current_node.position == end_node.position)
{
// Found the goal
Node * parent = ¤t_node;
// Jump from parent to parent to trace back
while (parent != nullptr)
{
found_path.push_back(parent->position);
parent = parent->parent.get();
}
return(found_path);
}
for (Node& node : *current_node.connections)
{
Node child(node);
child.parent.reset(new Node(current_node));
// Child is on the closed list
if (std::find_if(closed_list.begin(), closed_list.end(), [&child](Point & pt) { return(pt == child.position); }) != closed_list.end())
continue;
// Create the total_dist, dist_to_start, and heurist_dist_to_end values for the new subnode
// Calculated the total distance to the start by adding the distance from the subnode to the current node.
// Note: This distance could be precomputed and just held together with the "connections"
child.dist_to_start = current_node.dist_to_start + child.position.DistTo(current_node.position);
// Total cost is the sum of past distance and the heuristic (which assumes just line-of-flight)
child.total_dist = child.dist_to_start + child.position.SqrDistTo(end_node.position);
// Child is already in the open list
if (std::find_if(open_list.begin(), open_list.end(), [&child](const Node & node) { return((node == child) && (child.dist_to_start > node.dist_to_start)); }) != open_list.end())
continue;
// Add the child to the open list
open_list.emplace(child);
}
}
return(found_path);
}
Main method with test data (forms a 3x3 grid with non-diagonal connections):
int main()
{
std::vector<Node> all_nodes;
// Nodes
Node nd_0_0{ Point{0,0} };
Node nd_1_0{ Point{1,0} };
Node nd_2_0{ Point{2,0} };
Node nd_0_1{ Point{0,1} };
Node nd_1_1{ Point{1,1} };
Node nd_2_1{ Point{2,1} };
Node nd_0_2{ Point{0,2} };
Node nd_1_2{ Point{1,2} };
Node nd_2_2{ Point{2,2} };
// Connections
nd_0_0.AddConnection(nd_1_0);
nd_0_0.AddConnection(nd_0_1);
nd_1_0.AddConnection(nd_0_0);
nd_1_0.AddConnection(nd_2_0);
nd_1_0.AddConnection(nd_1_1);
nd_2_0.AddConnection(nd_1_0);
nd_2_0.AddConnection(nd_2_1);
nd_0_1.AddConnection(nd_0_0);
nd_0_1.AddConnection(nd_1_1);
nd_0_1.AddConnection(nd_0_2);
nd_1_1.AddConnection(nd_0_1);
nd_1_1.AddConnection(nd_1_0);
nd_1_1.AddConnection(nd_2_1);
nd_1_1.AddConnection(nd_1_2);
nd_2_1.AddConnection(nd_2_0);
nd_2_1.AddConnection(nd_1_1);
nd_2_1.AddConnection(nd_2_2);
nd_0_2.AddConnection(nd_0_1);
nd_0_2.AddConnection(nd_1_2);
nd_1_2.AddConnection(nd_0_2);
nd_1_2.AddConnection(nd_2_2);
nd_1_2.AddConnection(nd_1_1);
nd_2_2.AddConnection(nd_1_2);
nd_2_2.AddConnection(nd_2_1);
all_nodes.push_back(nd_0_0);
all_nodes.push_back(nd_1_0);
all_nodes.push_back(nd_2_0);
all_nodes.push_back(nd_0_1);
all_nodes.push_back(nd_1_1);
all_nodes.push_back(nd_2_1);
all_nodes.push_back(nd_0_2);
all_nodes.push_back(nd_1_2);
all_nodes.push_back(nd_2_2);
Point start_pos{ 0, 0 };
Point end_pos{ 2, 2 };
auto path = Astar(start_pos, end_pos, all_nodes);
if (path.empty())
std::cout << "No path :( \n";
else
{
std::cout << "Path found :) \n";
std::for_each(path.begin(), path.end(), [](const Point & pt) { pt.Print("Move to: "); });
}
}
Output:
Path found :)
Move to: x: 0 y: 0
Move to: x: 0 y: 1
Move to: x: 0 y: 2
Move to: x: 1 y: 2
Move to: x: 2 y: 2
What would you suggest for improvements? Huge thanks in advance!
#include "pch.h"
is irrelevant. That only works with the MSVC compiler. \$\endgroup\$ – πάντα ῥεῖ Feb 22 '20 at 8:43