#pragma once #include #include "StarList.hpp" #include "StarMap.hpp" #include "StarSet.hpp" #include "StarLexicalCast.hpp" #include "StarMathCommon.hpp" #include "StarBlockAllocator.hpp" namespace Star { namespace AStar { struct Score { Score(); double gScore; double hScore; double fScore; }; // 'Edge' should be implemented as a class with public fields compatible with // these: // double cost; // Node source; // Node target; template using Path = List; template class Search { public: typedef function HeuristicFunction; typedef function& neighbors)> NeighborFunction; typedef function GoalFunction; typedef function CompareFunction; typedef function ValidateEndFunction; Search(HeuristicFunction heuristicCost, NeighborFunction getAdjacent, GoalFunction goalReached, bool returnBestIfFailed = false, // In returnBestIfFailed mode, validateEnd checks the end of the path // is valid, e.g. not floating in the air. Maybe validateEnd = {}, Maybe maxFScore = {}, Maybe maxNodesToSearch = {}); // Start a new exploration, resets result if it was found before. void start(Node startNode, Node goalNode); // Explore the given number of nodes in the search space. If // maxNodesToSearch is reached, or the search space is exhausted, will // return // false to signal failure. On success, will return true. If the given // maxExploreNodes is exhausted before success or failure, will return // nothing. Maybe explore(Maybe maxExploreNodes = {}); // Returns the result if it was found. Maybe> const& result() const; // Convenience, equivalent to calling start, then explore({}) and returns // result() Maybe> const& findPath(Node startNode, Node goalNode); private: struct ScoredNode { bool operator<(ScoredNode const& other) const { return score.fScore > other.score.fScore; } Score score; Node node; }; struct NodeMeta { Score score; Maybe cameFrom; }; Path reconstructPath(Node currentNode); HeuristicFunction m_heuristicCost; NeighborFunction m_getAdjacent; GoalFunction m_goalReached; bool m_returnBestIfFailed; Maybe m_validateEnd; Maybe m_maxFScore; Maybe m_maxNodesToSearch; Node m_goal; Map, BlockAllocator, 1024>> m_nodeMeta; std::priority_queue m_openQueue; Set, BlockAllocator> m_openSet; Set, BlockAllocator> m_closedSet; Maybe m_earlyExploration; bool m_finished; Maybe> m_result; }; inline Score::Score() : gScore(highest()), hScore(0), fScore(highest()) {} template Search::Search(HeuristicFunction heuristicCost, NeighborFunction getAdjacent, GoalFunction goalReached, bool returnBestIfFailed, Maybe validateEnd, Maybe maxFScore, Maybe maxNodesToSearch) : m_heuristicCost(heuristicCost), m_getAdjacent(getAdjacent), m_goalReached(goalReached), m_returnBestIfFailed(returnBestIfFailed), m_validateEnd(validateEnd), m_maxFScore(maxFScore), m_maxNodesToSearch(maxNodesToSearch) {} template void Search::start(Node startNode, Node goalNode) { m_goal = std::move(goalNode); m_nodeMeta.clear(); m_openQueue = std::priority_queue(); m_openSet.clear(); m_closedSet.clear(); m_earlyExploration = {}; m_finished = false; m_result.reset(); Score startScore; startScore.gScore = 0; startScore.hScore = m_heuristicCost(startNode, m_goal); startScore.fScore = startScore.hScore; m_nodeMeta[startNode].score = startScore; m_openSet.insert(startNode); m_openQueue.push(ScoredNode{startScore, std::move(startNode)}); } template Maybe Search::explore(Maybe maxExploreNodes) { if (m_finished) return m_result.isValid(); List neighbors; while (true) { if ((m_maxNodesToSearch && m_closedSet.size() > *m_maxNodesToSearch) || (m_openQueue.empty() && !m_earlyExploration)) { m_finished = true; // Search failed. Either return the path to the closest node to the // target, // or return nothing. if (m_returnBestIfFailed) { double bestScore = highest(); Maybe bestNode; for (Node node : m_closedSet) { NodeMeta const& nodeMeta = m_nodeMeta[node]; if (m_validateEnd && nodeMeta.cameFrom && !(*m_validateEnd)(*nodeMeta.cameFrom)) continue; if (nodeMeta.score.hScore < bestScore) { bestScore = nodeMeta.score.hScore; bestNode = node; } } if (bestNode) m_result = reconstructPath(*bestNode); } return false; } if (maxExploreNodes) { if (*maxExploreNodes == 0) return {}; --*maxExploreNodes; } ScoredNode currentScoredNode; if (m_earlyExploration) { currentScoredNode = m_earlyExploration.take(); } else { currentScoredNode = m_openQueue.top(); m_openQueue.pop(); if (!m_openSet.remove(currentScoredNode.node)) // Duplicate entry in the queue due to this node's score being // updated. // Just ignore this node; we've already searched it. continue; } Node const& current = currentScoredNode.node; Score const& currentScore = currentScoredNode.score; if (m_goalReached(current)) { m_finished = true; m_result = reconstructPath(current); return true; } m_closedSet.insert(current); neighbors.clear(); m_getAdjacent(current, neighbors); for (Edge const& edge : neighbors) { if (m_closedSet.find(edge.target) != m_closedSet.end()) // We've already visited this node. continue; double newGScore = currentScore.gScore + edge.cost; NodeMeta& targetMeta = m_nodeMeta[edge.target]; Score& targetScore = targetMeta.score; if (m_openSet.find(edge.target) == m_openSet.end() || newGScore < targetScore.gScore) { targetMeta.cameFrom = edge; targetScore.gScore = newGScore; targetScore.hScore = m_heuristicCost(edge.target, m_goal); targetScore.fScore = targetScore.gScore + targetScore.hScore; if (m_maxFScore && targetScore.fScore > *m_maxFScore) continue; // Early exploration optimization - no need to add things to the // openQueue/openSet // if they're at least as good as the current node. if (targetScore.fScore <= currentScore.fScore) { if (m_earlyExploration.isNothing()) { m_earlyExploration = ScoredNode{targetScore, edge.target}; continue; } else if (m_earlyExploration->score.fScore > targetScore.fScore) { m_openSet.insert(m_earlyExploration->node); m_openQueue.push(*m_earlyExploration); m_earlyExploration = ScoredNode{targetScore, edge.target}; continue; } } m_openSet.insert(edge.target); m_openQueue.push(ScoredNode{targetScore, edge.target}); } } } } template Maybe> const& Search::result() const { return m_result; } template Maybe> const& Search::findPath(Node startNode, Node goalNode) { start(std::move(startNode), std::move(goalNode)); explore(); return result(); } template Path Search::reconstructPath(Node currentNode) { Path res; // this will be backwards, we reverse it before returning it. while (m_nodeMeta.find(currentNode) != m_nodeMeta.end()) { Maybe currentEdge = m_nodeMeta[currentNode].cameFrom; if (currentEdge.isNothing()) break; res.append(*currentEdge); currentNode = currentEdge->source; } std::reverse(res.begin(), res.end()); return res; } } }