Implemented Dijkstra's algorithm

This commit is contained in:
Jan Mrna 2025-09-27 22:29:33 +02:00
parent 5cd3a68e6d
commit 2dd44ab169
4 changed files with 144 additions and 2 deletions

View File

@ -15,10 +15,10 @@ Map::Map(int rows, int cols) : m_Cols(cols), m_Rows(rows) {
if (sw) if (sw)
m_Tiles[row].push_back(&tile_types.at("Grass")); m_Tiles[row].push_back(&tile_types.at("Grass"));
else else
m_Tiles[row].push_back(&tile_types.at("Road")); m_Tiles[row].push_back(&tile_types.at("Water"));
sw = !sw; sw = !sw;
} }
sw = !sw; //sw = !sw;
} }
} }

View File

@ -34,6 +34,7 @@ public:
std::vector<TilePos> GetNeighbors(TilePos center) const; std::vector<TilePos> GetNeighbors(TilePos center) const;
float GetCost(TilePos pos) const { return GetTileAt(pos)->cost; }
template <typename T> double GetTileVelocityCoeff(T p) const { template <typename T> double GetTileVelocityCoeff(T p) const {
return 1.0 / GetTileAt(p)->cost; return 1.0 / GetTileAt(p)->cost;

View File

@ -72,6 +72,130 @@ Path BFS::CalculatePath(WorldPos start_world, WorldPos end_world) {
std::reverse(path.begin(), path.end()); std::reverse(path.begin(), path.end());
return path; return path;
} }
//
//Path Dijkstra::CalculatePath(WorldPos start_world, WorldPos end_world) {
// if (m_Map == nullptr) return {};
//
// const TilePos start = m_Map->WorldToTile(start_world);
// const TilePos end = m_Map->WorldToTile(end_world);
//
// if (!m_Map->IsTilePosValid(start) || !m_Map->IsTilePosValid(end))
// return {};
// if (start == end) {
// return {};
// }
// // clear previous run
// m_CameFrom.clear();
// m_Distance.clear();
//
// std::queue<TilePos> frontier;
// frontier.push(start);
// m_CameFrom[start] = start;
// m_Distance[start] = 0.0f;
//
// // ---------------- build flow-field ----------------
// bool early_exit = false;
// while (!frontier.empty() && !early_exit) {
// TilePos current = frontier.front();
// frontier.pop();
//
// for (TilePos next : m_Map->GetNeighbors(current)) {
// if (m_CameFrom.find(next) == m_CameFrom.end()) { // not visited
// frontier.push(next);
// m_Distance[next] = m_Distance[current] + 1.0f;
// m_CameFrom[next] = current;
//
// if (next == end) { // early exit
// early_exit = true;
// break;
// }
// }
// }
// }
//
// // --------------- reconstruct path -----------------
// if (m_CameFrom.find(end) == m_CameFrom.end())
// return {}; // end not reached
//
// Path path;
// TilePos cur = end;
// path.push_back(m_Map->TileToWorld(cur));
// while (cur != start) {
// cur = m_CameFrom[cur];
// path.push_back(m_Map->TileToWorld(cur));
// }
// std::reverse(path.begin(), path.end());
// return path;
//}
struct QueueEntry
{
float cost;
TilePos tile;
// min-heap -> smallest cost on top
bool operator>(const QueueEntry& o) const noexcept { return cost > o.cost; }
};
Path Dijkstra::CalculatePath(WorldPos start_world, WorldPos end_world)
{
if (!m_Map) return {};
const TilePos start = m_Map->WorldToTile(start_world);
const TilePos end = m_Map->WorldToTile(end_world);
if (!m_Map->IsTilePosValid(start) || !m_Map->IsTilePosValid(end))
return {};
if (start == end) return {};
// clear previous run
m_CameFrom.clear();
m_Cost.clear();
std::priority_queue<QueueEntry, std::vector<QueueEntry>, std::greater<>> frontier;
frontier.push({0.0f, start});
m_CameFrom[start] = start; // sentinel
m_Cost[start] = 0.0f;
while (!frontier.empty())
{
const QueueEntry current = frontier.top();
frontier.pop();
if (current.tile == end) // early exit
break;
for (TilePos next : m_Map->GetNeighbors(current.tile))
{
// cost of moving to neighbour (uniform 1.0 matches original BFS)
const float newCost = m_Cost[current.tile] + m_Map->GetCost(next);
if (!m_Cost.count(next) || newCost < m_Cost[next])
{
m_Cost[next] = newCost;
m_CameFrom[next] = current.tile;
frontier.push({newCost, next});
}
}
}
// reconstruct path
if (!m_CameFrom.count(end))
return {}; // goal never reached
Path path;
TilePos cur = end;
path.push_back(m_Map->TileToWorld(cur));
while (cur != start)
{
cur = m_CameFrom[cur];
path.push_back(m_Map->TileToWorld(cur));
}
std::reverse(path.begin(), path.end());
return path;
}
std::unique_ptr<PathFinderBase> create(PathFinderType type, const Map* map) { std::unique_ptr<PathFinderBase> create(PathFinderType type, const Map* map) {
switch (type) { switch (type) {
@ -79,6 +203,8 @@ std::unique_ptr<PathFinderBase> create(PathFinderType type, const Map* map) {
return std::move(std::make_unique<LinearPathFinder>(map)); return std::move(std::make_unique<LinearPathFinder>(map));
case PathFinderType::BFS: case PathFinderType::BFS:
return std::move(std::make_unique<BFS>(map)); return std::move(std::make_unique<BFS>(map));
case PathFinderType::DIJKSTRA:
return std::move(std::make_unique<Dijkstra>(map));
case PathFinderType::COUNT: case PathFinderType::COUNT:
LOG_WARNING("Incorrect pathfinder type"); LOG_WARNING("Incorrect pathfinder type");
return nullptr; return nullptr;

View File

@ -14,6 +14,7 @@ using Path = std::vector<WorldPos>;
enum class PathFinderType { enum class PathFinderType {
LINEAR = 1, LINEAR = 1,
BFS, BFS,
DIJKSTRA,
COUNT, COUNT,
}; };
@ -60,6 +61,20 @@ private:
std::unordered_map<TilePos, TilePos, TilePosHash> m_CameFrom; std::unordered_map<TilePos, TilePos, TilePosHash> m_CameFrom;
}; };
class Dijkstra: public PathFinderBase {
public:
Dijkstra(const Map* m): PathFinderBase(m) {}
Path CalculatePath(WorldPos start, WorldPos end) override;
const std::string_view& GetName() const override { return m_Name; }
private:
const std::string_view m_Name = "Dijkstra's Algorithm";
std::unordered_map<TilePos, double, TilePosHash> m_Cost;
std::unordered_map<TilePos, TilePos, TilePosHash> m_CameFrom;
};
std::unique_ptr<PathFinderBase> create(PathFinderType type, const Map* map); std::unique_ptr<PathFinderBase> create(PathFinderType type, const Map* map);
} // pathfinder namespace } // pathfinder namespace