I'm working on a school project where I need to implement the pathfinding algorithm. The requirements are that it needs to find the path in at most a few seconds and ideally in less than 1 second. I've been trying to optimize my code to make the algorithm faster but it keeps taking a few minutes to find the path and I can't find what the cause it is..
I'll provide my code below and I hope someone will be able to help and/or point to things that can be done more efficiently which would be greatly appreciated.
Header of Pathfinder:
#ifndef PATHFINDER_H
#define PATHFINDER_H
//#include "world.h"
#include "lib/world.h"
#include "node.h"
#include <iostream>
#include <QSet>
#include <QStack>
#include <QHash>
#include <cmath>
#include <queue>
class Pathfinder
{
public:
Pathfinder(std::vector<std::shared_ptr<Tile>> tiles, int columns, int rows);
struct compareDistance {
bool operator()(const std::shared_ptr<Node> a , const std::shared_ptr<Node> b){
return (b->getF() < a->getF());
}
};
std::vector<std::shared_ptr<Node>> nodeList;
std::priority_queue<std::shared_ptr<Node>, std::vector<std::shared_ptr<Node>>,
compareDistance> openList;
std::vector<std::shared_ptr<Node>> closedList;
QStack<std::shared_ptr<Tile>> path;
QStack<std::shared_ptr<Tile>> findPath(int xDestination, int yDestination);
std::vector<std::shared_ptr<Enemy>> enemyList;
std::vector<std::shared_ptr<PEnemy>> pEnemyList;
//std::vector<std::shared_ptr<HealthPack>> healthpacks;
float getWeight() const;
void setWeight(float value);
float getHeuristicCost(int current_x, int current_y, int destination_x, int destination_y);
int getHeight() const;
void setHeight(int value);
int getWidth() const;
void setWidth(int value);
float getMoveCost() const;
void setMoveCost(float value);
bool solveAlgorithm(int xDestination, int yDestination);
void startingPoint(int xStart, int yStart);
private:
std::vector<std::shared_ptr<Tile>> tiles;
float weight = 1;
int width = 0;
int height = 0;
bool isValid(int x, int y);
void addNeighbors(int index, std::shared_ptr<Node> pre, std::shared_ptr<Node> finalNode);
std::shared_ptr<Tile> getTile(int x,int y);
std::vector<int> specialTiles;
};
#endif // PATHFINDER_H
Function to iterate over all the nodes and update their distance:
bool Pathfinder::solveAlgorithm(int xDestination, int yDestination)
{
std::cout<<"Solve Algorithm starting"<<std::endl;
auto destinationIndex = xDestination + yDestination*width;
auto destinationTile = std::make_shared<Tile>(xDestination,yDestination,tiles[(uint)destinationIndex]->getValue());
std::shared_ptr<Node> destinationNode = std::make_shared<Node>(destinationTile,nullptr);
nodeList[destinationIndex] = destinationNode; //nodeList is std::vector<std::shared_ptr<Node>>
std::cout<<"Width: "<<width<<std::endl;
std::cout<<"Height: "<<height<<std::endl;
//openList is priorityQueue
while(!openList.empty()){
std::shared_ptr<Node> currentNode = openList.top();
openList.pop(); //remove currentNode from openList
closedList.push_back(currentNode); //closedList is std::vector<shared_ptr<Node>>
//int index = currentNode->getTile()->getXPos()+currentNode->getTile()->getYPos()*width;
//closed.insert(index,currentNode);
auto currentTile = currentNode->getTile();//Tile the current node points to
int currentX = currentTile->getXPos();
int currentY = currentTile->getYPos();
std::cout<<"Start position: "<<currentTile->getXPos()<<", "<<currentTile->getYPos()<<std::endl;
if(currentNode == destinationNode){
return true;
}
for (int newX = -1; newX <= 1; newX++) {
for (int newY = -1; newY <= 1; newY++) {
if(isValid(currentX+newX,currentY+newY) && !(newX == 0 && newY == 0)){
int newIndex = (currentX+newX) + (currentY+newY)*width;
addNeighbors(newIndex,currentNode,destinationNode);
}
}
}
}
return false;
}
Function to check or add neighbours:
void Pathfinder::addNeighbors(int index, std::shared_ptr<Node> parent, std::shared_ptr<Node> final)
{
//tiles is std::vector<std::shared_ptr<Tile>>
auto tilePointer = tiles[(uint)index];//convert tile this node is associated with from unique ptr to shared ptr
std::shared_ptr<Node> neighbor;
//Neighbor not yet discovered
if(nodeList[index] == nullptr){
neighbor = std::make_shared<Node>(tilePointer, nullptr);
nodeList[index] = neighbor;
}
else{
neighbor = nodeList[index];
}
if(neighbor->getTile()->getValue() <= 1){
//if neighbor is not in closed list
if(std::find(closedList.begin(), closedList.end(), neighbor) == closedList.end()){
auto tempG = parent->getG() + sqrt(pow(neighbor->getTile()->getXPos()-parent->getTile()->getXPos(),2)+pow(neighbor->getTile()->getYPos()-parent->getTile()->getYPos(),2));
tempG = (tempG + (1-neighbor->getTile()->getValue()));//*weight;
auto tempH = getHeuristicCost(neighbor->getTile()->getXPos(),neighbor->getTile()->getYPos(),final->getTile()->getXPos(),final->getTile()->getYPos());
tempH *= weight;
double tempF = tempG + tempH;
//The neighbor was newly made node - also possible to check the G cost of neighbor
//If G = 0 ==> not yet updated and hence new node
if(neighbor->getParent() == nullptr){
neighbor->setParent(parent);
neighbor->setG(tempG);
neighbor->setH(tempH);
openList.push(neighbor);
}
else {
//We found better path ==> update cost and parent
if(neighbor->getF() > tempF){
neighbor->setParent(parent);
neighbor->setG(tempG);
neighbor->setH(tempH);
}
}
}
}
}
The header of the Node Class:
#ifndef NODE_H
#define NODE_H
#include "world.h"
#include "lib/world.h"
#include <memory>
#include <limits>
class Node
{
public:
Node(std::shared_ptr<Tile> tile,std::shared_ptr<Node> previousNode);
Node();
std::shared_ptr<Tile> getTile() const;
void setTile(const std::shared_ptr<Tile> &value);
std::shared_ptr<Node> getParent() const;
void setParent(std::shared_ptr<Node> value);
double getG() const;
void setG(double value);
double getF() const;
double getH() const;
void setH(double value);
private:
std::shared_ptr<Node> parent;
std::shared_ptr<Tile> tile;
double g;
double h;
};
My heuristic function is the Manhattan distance: return abs(destination_x - current_x)+abs(destination_y - current_y);
For the backtracing I use a Stack so I can simply pop() as long as it is not empty to draw the tiles being part of the path found:
QStack<std::shared_ptr<Tile>> Pathfinder::findPath(int xDestination, int yDestination)
{
if(solveAlgorithm(xDestination,yDestination) == true){
std::shared_ptr<Node> endNode = closedList.back(); //endNode or destination is the last added node to the closedList
while(endNode->getParent() != nullptr){
path.push(endNode->getTile());//push the tile of parent to top of stack
endNode = endNode->getParent();
}
return path;
}
else{
return path;
}
}
Pathfinderclass also \$\endgroup\$void addNeighbors(int index, Node* pre, Node* finalNode);in the header, but the implementation usesshared_ptr<Node>. \$\endgroup\$Node*instead ofstd::shared_ptr<Node>. I edited the header, it should be fine now \$\endgroup\$