A* Pathfinding
Thu Apr 04 2024 01:13:32 GMT+0000 (Coordinated Universal Time)
#include <pch.h>
#include "Projects/ProjectTwo.h"
#include "P2_Pathfinding.h"
#pragma region Extra Credit
std::list<AStarPather::Node*> list;
AStarPather::Node map[61][61];
bool ProjectTwo::implemented_floyd_warshall()
{
return false;
}
bool ProjectTwo::implemented_goal_bounding()
{
return false;
}
bool ProjectTwo::implemented_jps_plus()
{
return false;
}
#pragma endregion
bool AStarPather::initialize()
{
// handle any one-time setup requirements you have
/*
If you want to do any map-preprocessing, you'll need to listen
for the map change message. It'll look something like this:
Callback cb = std::bind(&AStarPather::your_function_name, this);
Messenger::listen_for_message(Messages::MAP_CHANGE, cb);
There are other alternatives to using std::bind, so feel free to mix it up.
Callback is just a typedef for std::function<void(void)>, so any std::invoke'able
object that std::function can wrap will suffice.
*/
return true; // return false if any errors actually occur, to stop engine initialization
}
void AStarPather::shutdown()
{
/*
Free any dynamically allocated memory or any other general house-
keeping you need to do during shutdown.
*/
}
/*
This is where you handle pathing requests, each request has several fields:
start/goal - start and goal world positions
path - where you will build the path upon completion, path should be
start to goal, not goal to start
heuristic - which heuristic calculation to use
weight - the heuristic weight to be applied
newRequest - whether this is the first request for this path, should generally
be true, unless single step is on
smoothing - whether to apply smoothing to the path
rubberBanding - whether to apply rubber banding
singleStep - whether to perform only a single A* step
debugColoring - whether to color the grid based on the A* state:
closed list nodes - yellow
open list nodes - blue
use terrain->set_color(row, col, Colors::YourColor);
also it can be helpful to temporarily use other colors for specific states
when you are testing your algorithms
method - which algorithm to use: A*, Floyd-Warshall, JPS+, or goal bounding,
will be A* generally, unless you implement extra credit features
The return values are:
PROCESSING - a path hasn't been found yet, should only be returned in
single step mode until a path is found
COMPLETE - a path to the goal was found and has been built in request.path
IMPOSSIBLE - a path from start to goal does not exist, do not add start position to path
*/
PathResult AStarPather::compute_path(PathRequest &request)
{
//start/goal - start and goal world positions
GridPos start = terrain->get_grid_position(request.start);
GridPos goal = terrain->get_grid_position(request.goal);
terrain->set_color(start, Colors::Red);
//set color to orange
terrain->set_color(goal, Colors::Red);
//request.path.push_back(request.start);
/***********************************A* SEARCH ALGORITHM*********************************/
//Push Start Node onto the Open List.
if (request.newRequest)
{
for (int i = 0; i <= 40; i++)
{
for (int j = 0; j <= 40; j++)
{
map[i][j].parent = NULL;
map[i][j].pos = GridPos{j, i};
map[i][j].onList_ = onList::NONE;
map[i][j].cost = 0.0f;
map[i][j].given = 0.0f;
}
}
list.clear();
list.push_back(&map[start.col][start.row]);
}
//While (Open List is not empty) {
while (!list.empty())
{
//Pop cheapest node off Open List (parent node).
Node* parentNode = findCheapest(list);
std::list<Node*>::iterator it;
it = list.begin();
std::advance(it, findNodeIndex(list, parentNode));
it = list.erase(it);
//request.path.push_back(terrain->get_world_position(parentNode->pos));
//If node is the Goal Node, then path found (RETURN �found�).
if (parentNode->pos == goal)
{
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
Node* cur = parentNode;
while (cur) {
//push request
request.path.push_front(terrain->get_world_position(cur->pos));
//go to next parent
cur = cur->parent;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
terrain->set_color(start, Colors::Orange);
terrain->set_color(goal, Colors::Orange);
return PathResult::COMPLETE;
}
bool NW = true;
bool NE = true;
bool SE = true;
bool SW = true;
//For (all neighboring child nodes)
for (int i = 1; i <= 8; i++)
{
//set parent to parent
GridPos childPos = getChild(parentNode->pos, i); //get child
//deleted line
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//Node* oldParent = map[childPos.col][childPos.row].parent;
if (childPos != parentNode->pos)
{
//set map's parent to new parent after getting position
//map[childNode->pos.col][childNode->pos.row].parent = &map[parentNode->pos.col][parentNode->pos.row];
//grid is on the map and isn't a wall
if (terrain->is_valid_grid_position(childPos) && !terrain->is_wall(childPos))
{
//i is non diagonals or is a valid diagonal
if (i <= 4 || (i == 5 && NE) || (i == 6 && SE) || (i == 7 && SW) || (i == 8 && NW))
{
//Compute its cost, f(x) = g(x) + h(x)
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
float given = parentNode->given;
if (i >= 4)
{
//tile is a diagonal
given += (float)std::sqrt(2);
//map[childPos.col][childPos.row].given = map[parentNode->pos.col][parentNode->pos.row].given + (float)std::sqrt(2);
}
else
{
//tile is N, S, W, E
given += 1;
//map[childPos.col][childPos.row].given = map[parentNode->pos.col][parentNode->pos.row].given + 1;
}
float h = getHeuristic(request.settings.heuristic, childPos, goal);
//map[childPos.col][childPos.row].cost = map[parentNode->pos.col][parentNode->pos.row].given + h * request.settings.weight;
float newCost = given + h * request.settings.weight;
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//find if child exists on curr list, and assign it
map[parentNode->pos.col][parentNode->pos.row].onList_ = assignList(list, map[parentNode->pos.col][parentNode->pos.row].pos);
//If child node isn't on Open or Closed list, put it on Open List.
if (map[childPos.col][childPos.row].onList_ == onList::NONE)
{
////////////////////////////////////////////////////////////////////////////////////////////////////////////
map[childPos.col][childPos.row].parent = parentNode;
map[childPos.col][childPos.row].given = given;
map[childPos.col][childPos.row].cost = newCost;
////////////////////////////////////////////////////////////////////////////////////////////////////////////
map[childPos.col][childPos.row].onList_ = onList::OPEN;
terrain->set_color(childPos, Colors::Blue);
list.push_back(&map[childPos.col][childPos.row]);
}
//Else if child node is on Open or Closed List,
else if (map[childPos.col][childPos.row].onList_ == onList::OPEN || map[childPos.col][childPos.row].onList_ == onList::CLOSE)
{
//AND this new one is cheaper,
//then take the old expensive one off both lists
//if oldCost == 0 then it's our first time setting it
if (map[childPos.col][childPos.row].cost > newCost)
{
//and put this new cheaper one on the Open List.
////////////////////////////////////////////////////////////////////////////////////////////////////////////
map[childPos.col][childPos.row].parent = parentNode;
map[childPos.col][childPos.row].given = given;
map[childPos.col][childPos.row].cost = newCost;
////////////////////////////////////////////////////////////////////////////////////////////////////////////
map[childPos.col][childPos.row].onList_ = onList::OPEN;
terrain->set_color(childPos, Colors::Blue);
list.push_back(&map[childPos.col][childPos.row]);
}
/*
else
{
map[childPos.col][childPos.row].cost = oldCost;
map[childPos.col][childPos.row].parent = oldParent;
}*/
}
}
}
//grid is valid but the non-diagonals is a wall, skip the diagonals
else if (terrain->is_valid_grid_position(childPos) && terrain->is_wall(childPos) && i <= 4)
{
if (i == 1) //NORTH
{
NE = false;
NW = false;
}
if (i == 2) //EAST
{
NE = false;
SE = false;
}
if (i == 3) //SOUTH
{
SE = false;
SW = false;
}
if (i == 4) //WEST
{
SW = false;
NW = false;
}
}
}
}
/***************************************************************************************************************/
//Place parent node on the Closed List (we're done with it).
parentNode->onList_ = onList::CLOSE;
map[parentNode->pos.col][parentNode->pos.row].onList_ = onList::CLOSE;
terrain->set_color(parentNode->pos, Colors::Yellow);
map[parentNode->pos.col][parentNode->pos.row] = *parentNode;
//If taken too much time this frame (or in single step mode),
if (request.settings.singleStep == true)
{
//abort search for now and resume next frame (RETURN �working�).
return PathResult::PROCESSING;
}
}
//Open List empty, thus no path possible (RETURN �fail�).
return PathResult::IMPOSSIBLE;
}
float AStarPather::getHeuristic(Heuristic method, GridPos position, GridPos goal)
{
float dx = (float)std::fabs(position.row - goal.row);
float dy = (float)std::fabs(position.col - goal.col);
if (method == Heuristic::OCTILE)
{
return 1 * (dx + dy) + (float)(sqrt(2) - 2 * 1) * std::min(dx, dy);
}
if (method == Heuristic::CHEBYSHEV)
{
return 1 * (dx + dy) + (1 - 2 * 1) * std::min(dx, dy);
}
if (method == Heuristic::MANHATTAN)
{
return dx + dy;
}
if (method == Heuristic::EUCLIDEAN)
{
return (float)sqrt(dx * dx + dy * dy);
}
return 0.0f;
}
AStarPather::onList AStarPather::assignList(std::list<Node*> list, GridPos position)
{
//go through list
for (const Node* node : list)
{
//if node exists in list
//and is labeled as open
if (node->pos == position && node->onList_ == onList::OPEN)
{
//return open
return onList::OPEN;
}
//and is labeled as closed
if (node->pos == position && node->onList_ == onList::CLOSE)
{
//return closed
return onList::CLOSE;
}
//and is labeled as none
if (node->pos == position && node->onList_ == onList::NONE)
{
return onList::NONE;
}
}
//else it's not on either list
return onList::NONE;
}
GridPos AStarPather::getChild(GridPos node, int i)
{
GridPos pos;
if (i == 1) //NORTH
{
pos = { node.row + 1, node.col};
}
else if (i == 5) //NE
{
pos = { node.row + 1, node.col + 1 };
}
else if (i == 2) //EAST
{
pos = { node.row, node.col + 1};
}
else if (i == 6) //SE
{
pos = { node.row - 1, node.col + 1 };
}
else if (i == 3) //SOUTH
{
pos = { node.row - 1, node.col };
}
else if (i == 7) //SW
{
pos = { node.row - 1, node.col - 1 };
}
else if (i == 4) //WEST
{
pos = { node.row, node.col - 1};
}
else if (i == 8) //NW
{
pos = { node.row + 1, node.col - 1};
}
return pos;
}
AStarPather::Node* AStarPather::findCheapest(std::list<Node*> list)
{
Node* cheapestNode;
float minCost = -1.0f;
for (Node* node : list)
{
if ((node->cost < minCost || minCost == -1.0f) && node->onList_ != onList::CLOSE)
{
//is a valid node
if (terrain->is_valid_grid_position(node->pos) && node->cost >= 0.0f)
{
cheapestNode = node;
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
minCost = cheapestNode->cost;
}
}
}
return cheapestNode;
}
int AStarPather::findNodeIndex(std::list<Node*> list, Node* node)
{
int i = 0;
int index = 0;
for (Node* node_ : list)
{
if (node_->pos == node->pos)
{
index = i;
}
i++;
}
return index;
}



Comments