diff --git a/Source/controls/plrctrls.cpp b/Source/controls/plrctrls.cpp index 3c946634c..9497ac0ad 100644 --- a/Source/controls/plrctrls.cpp +++ b/Source/controls/plrctrls.cpp @@ -120,7 +120,7 @@ int GetDistance(Point destination, int maxDistance) return 0; } - int8_t walkpath[MAX_PATH_LENGTH]; + int8_t walkpath[MaxPathLength]; Player &myPlayer = *MyPlayer; int steps = FindPath([&myPlayer](Point position) { return PosOkPlayer(myPlayer, position); }, myPlayer.position.future, destination, walkpath); if (steps > maxDistance) diff --git a/Source/monster.cpp b/Source/monster.cpp index 19758c77c..285939f17 100644 --- a/Source/monster.cpp +++ b/Source/monster.cpp @@ -1922,7 +1922,7 @@ bool IsTileAccessible(const Monster &monster, Point position) bool AiPlanWalk(int i) { - int8_t path[MAX_PATH_LENGTH]; + int8_t path[MaxPathLength]; /** Maps from walking path step to facing direction. */ const Direction plr2monst[9] = { Direction::South, Direction::NorthEast, Direction::NorthWest, Direction::SouthEast, Direction::SouthWest, Direction::North, Direction::East, Direction::South, Direction::West }; diff --git a/Source/path.cpp b/Source/path.cpp index 583d60af8..744a8cddd 100644 --- a/Source/path.cpp +++ b/Source/path.cpp @@ -11,108 +11,137 @@ #include "objects.h" namespace devilution { - namespace { +constexpr size_t MaxPathNodes = 300; + +struct PathNode { + static constexpr uint16_t InvalidIndex = std::numeric_limits::max(); + static constexpr size_t MaxChildren = 8; + + int16_t x = 0; + int16_t y = 0; + uint16_t parentIndex = InvalidIndex; + uint16_t childIndices[MaxChildren] = { InvalidIndex, InvalidIndex, InvalidIndex, InvalidIndex, InvalidIndex, InvalidIndex, InvalidIndex, InvalidIndex }; + uint16_t nextNodeIndex = InvalidIndex; + uint8_t f = 0; + uint8_t h = 0; + uint8_t g = 0; + + [[nodiscard]] Point position() const + { + return Point { x, y }; + } + + void addChild(uint16_t childIndex) + { + size_t index = 0; + for (; index < MaxChildren; ++index) { + if (childIndices[index] == InvalidIndex) + break; + } + assert(index < MaxChildren); + childIndices[index] = childIndex; + } +}; + +PathNode PathNodes[MaxPathNodes]; + /** A linked list of the A* frontier, sorted by distance */ -PATHNODE *path_2_nodes; +PathNode *Path2Nodes; + /** * @brief return a node for a position on the frontier, or NULL if not found */ -PATHNODE *GetNode1(Point targetPosition) +uint16_t GetNode1(Point targetPosition) { - PATHNODE *result = path_2_nodes->NextNode; - while (result != nullptr) { - if (result->position == targetPosition) + uint16_t result = Path2Nodes->nextNodeIndex; + while (result != PathNode::InvalidIndex) { + if (PathNodes[result].position() == targetPosition) return result; - result = result->NextNode; + result = PathNodes[result].nextNodeIndex; } - return nullptr; + return PathNode::InvalidIndex; } /** - * @brief insert pPath into the frontier (keeping the frontier sorted by total distance) + * @brief insert `front` node into the frontier (keeping the frontier sorted by total distance) */ -void NextNode(PATHNODE *pPath) +void NextNode(uint16_t front) { - if (path_2_nodes->NextNode == nullptr) { - path_2_nodes->NextNode = pPath; + if (Path2Nodes->nextNodeIndex == PathNode::InvalidIndex) { + Path2Nodes->nextNodeIndex = front; return; } - PATHNODE *current = path_2_nodes; - PATHNODE *next = path_2_nodes->NextNode; - int f = pPath->f; - while (next != nullptr && next->f < f) { - current = next; - next = next->NextNode; + PathNode *current = Path2Nodes; + uint16_t nextIndex = Path2Nodes->nextNodeIndex; + const uint8_t maxF = PathNodes[front].f; + while (nextIndex != PathNode::InvalidIndex && PathNodes[nextIndex].f < maxF) { + current = &PathNodes[nextIndex]; + nextIndex = current->nextNodeIndex; } - pPath->NextNode = next; - current->NextNode = pPath; + PathNodes[front].nextNodeIndex = nextIndex; + current->nextNodeIndex = front; } /** A linked list of all visited nodes */ -PATHNODE *pnode_ptr; +PathNode *VisitedNodes; + /** * @brief return a node for this position if it was visited, or NULL if not found */ -PATHNODE *GetNode2(Point targetPosition) +uint16_t GetNode2(Point targetPosition) { - PATHNODE *result = pnode_ptr->NextNode; - while (result != nullptr) { - if (result->position == targetPosition) + uint16_t result = VisitedNodes->nextNodeIndex; + while (result != PathNode::InvalidIndex) { + if (PathNodes[result].position() == targetPosition) return result; - result = result->NextNode; + result = PathNodes[result].nextNodeIndex; } - return nullptr; + return result; } /** * @brief get the next node on the A* frontier to explore (estimated to be closest to the goal), mark it as visited, and return it */ -PATHNODE *GetNextPath() +uint16_t GetNextPath() { - PATHNODE *result = path_2_nodes->NextNode; - if (result == nullptr) { + uint16_t result = Path2Nodes->nextNodeIndex; + if (result == PathNode::InvalidIndex) { return result; } - path_2_nodes->NextNode = result->NextNode; - result->NextNode = pnode_ptr->NextNode; - pnode_ptr->NextNode = result; + Path2Nodes->nextNodeIndex = PathNodes[result].nextNodeIndex; + PathNodes[result].nextNodeIndex = VisitedNodes->nextNodeIndex; + VisitedNodes->nextNodeIndex = result; return result; } -constexpr size_t MAXPATHNODES = 300; - -/** Notes visisted by the path finding algorithm. */ -PATHNODE path_nodes[MAXPATHNODES]; /** the number of in-use nodes in path_nodes */ uint32_t gdwCurNodes; /** * @brief zero one of the preallocated nodes and return a pointer to it, or NULL if none are available */ -PATHNODE *NewStep() +uint16_t NewStep() { - if (gdwCurNodes >= MAXPATHNODES) - return nullptr; + if (gdwCurNodes >= MaxPathNodes) + return PathNode::InvalidIndex; - PATHNODE *newNode = &path_nodes[gdwCurNodes]; - gdwCurNodes++; - memset(newNode, 0, sizeof(PATHNODE)); - return newNode; + PathNodes[gdwCurNodes] = {}; + return gdwCurNodes++; } /** A stack for recursively searching nodes */ -PATHNODE *pnode_tblptr[MAXPATHNODES]; +uint16_t pnode_tblptr[MaxPathNodes]; /** size of the pnode_tblptr stack */ uint32_t gdwCurPathStep; /** * @brief push pPath onto the pnode_tblptr stack */ -void PushActiveStep(PATHNODE *pPath) +void PushActiveStep(uint16_t pPath) { - assert(gdwCurPathStep < MAXPATHNODES); + assert(gdwCurPathStep < MaxPathNodes); pnode_tblptr[gdwCurPathStep] = pPath; gdwCurPathStep++; } @@ -120,7 +149,7 @@ void PushActiveStep(PATHNODE *pPath) /** * @brief pop and return a node from the pnode_tblptr stack */ -PATHNODE *PopActiveStep() +uint16_t PopActiveStep() { gdwCurPathStep--; return pnode_tblptr[gdwCurPathStep]; @@ -144,22 +173,24 @@ int CheckEqual(Point startPosition, Point destinationPosition) /** * @brief update all path costs using depth-first search starting at pPath */ -void SetCoords(PATHNODE *pPath) +void SetCoords(uint16_t pPath) { PushActiveStep(pPath); // while there are path nodes to check while (gdwCurPathStep > 0) { - PATHNODE *pathOld = PopActiveStep(); - for (auto *pathAct : pathOld->Child) { - if (pathAct == nullptr) + uint16_t pathOldIndex = PopActiveStep(); + const PathNode &pathOld = PathNodes[pathOldIndex]; + for (uint16_t childIndex : pathOld.childIndices) { + if (childIndex == PathNode::InvalidIndex) break; - - if (pathOld->g + CheckEqual(pathOld->position, pathAct->position) < pathAct->g) { - if (path_solid_pieces(pathOld->position, pathAct->position)) { - pathAct->Parent = pathOld; - pathAct->g = pathOld->g + CheckEqual(pathOld->position, pathAct->position); - pathAct->f = pathAct->g + pathAct->h; - PushActiveStep(pathAct); + PathNode &pathAct = PathNodes[childIndex]; + + if (pathOld.g + CheckEqual(pathOld.position(), pathAct.position()) < pathAct.g) { + if (path_solid_pieces(pathOld.position(), pathAct.position())) { + pathAct.parentIndex = pathOldIndex; + pathAct.g = pathOld.g + CheckEqual(pathOld.position(), pathAct.position()); + pathAct.f = pathAct.g + pathAct.h; + PushActiveStep(childIndex); } } } @@ -195,70 +226,59 @@ int GetHeuristicCost(Point startPosition, Point destinationPosition) /** * @brief add a step from pPath to destination, return 1 if successful, and update the frontier/visited nodes accordingly * - * @param pPath pointer to the current path node + * @param pathIndex index of the current path node * @param candidatePosition expected to be a neighbour of the current path node position * @param destinationPosition where we hope to end up * @return true if step successfully added, false if we ran out of nodes to use */ -bool ParentPath(PATHNODE *pPath, Point candidatePosition, Point destinationPosition) +bool ParentPath(uint16_t pathIndex, Point candidatePosition, Point destinationPosition) { - int nextG = pPath->g + CheckEqual(pPath->position, candidatePosition); + PathNode &path = PathNodes[pathIndex]; + int nextG = path.g + CheckEqual(path.position(), candidatePosition); // 3 cases to consider // case 1: (dx,dy) is already on the frontier - PATHNODE *dxdy = GetNode1(candidatePosition); - if (dxdy != nullptr) { - int i; - for (i = 0; i < 8; i++) { - if (pPath->Child[i] == nullptr) - break; - } - pPath->Child[i] = dxdy; - if (nextG < dxdy->g) { - if (path_solid_pieces(pPath->position, candidatePosition)) { + uint16_t dxdyIndex = GetNode1(candidatePosition); + if (dxdyIndex != PathNode::InvalidIndex) { + path.addChild(dxdyIndex); + PathNode &dxdy = PathNodes[dxdyIndex]; + if (nextG < dxdy.g) { + if (path_solid_pieces(path.position(), candidatePosition)) { // we'll explore it later, just update - dxdy->Parent = pPath; - dxdy->g = nextG; - dxdy->f = nextG + dxdy->h; + dxdy.parentIndex = pathIndex; + dxdy.g = nextG; + dxdy.f = nextG + dxdy.h; } } } else { // case 2: (dx,dy) was already visited - dxdy = GetNode2(candidatePosition); - if (dxdy != nullptr) { - int i; - for (i = 0; i < 8; i++) { - if (pPath->Child[i] == nullptr) - break; - } - pPath->Child[i] = dxdy; - if (nextG < dxdy->g && path_solid_pieces(pPath->position, candidatePosition)) { + dxdyIndex = GetNode2(candidatePosition); + if (dxdyIndex != PathNode::InvalidIndex) { + path.addChild(dxdyIndex); + PathNode &dxdy = PathNodes[dxdyIndex]; + if (nextG < dxdy.g && path_solid_pieces(path.position(), candidatePosition)) { // update the node - dxdy->Parent = pPath; - dxdy->g = nextG; - dxdy->f = nextG + dxdy->h; + dxdy.parentIndex = pathIndex; + dxdy.g = nextG; + dxdy.f = nextG + dxdy.h; // already explored, so re-update others starting from that node - SetCoords(dxdy); + SetCoords(dxdyIndex); } } else { // case 3: (dx,dy) is totally new - dxdy = NewStep(); - if (dxdy == nullptr) + dxdyIndex = NewStep(); + if (dxdyIndex == PathNode::InvalidIndex) return false; - dxdy->Parent = pPath; - dxdy->g = nextG; - dxdy->h = GetHeuristicCost(candidatePosition, destinationPosition); - dxdy->f = nextG + dxdy->h; - dxdy->position = candidatePosition; + PathNode &dxdy = PathNodes[dxdyIndex]; + dxdy.parentIndex = pathIndex; + dxdy.g = nextG; + dxdy.h = GetHeuristicCost(candidatePosition, destinationPosition); + dxdy.f = nextG + dxdy.h; + dxdy.x = static_cast(candidatePosition.x); + dxdy.y = static_cast(candidatePosition.y); // add it to the frontier - NextNode(dxdy); - - int i; - for (i = 0; i < 8; i++) { - if (pPath->Child[i] == nullptr) - break; - } - pPath->Child[i] = dxdy; + NextNode(dxdyIndex); + path.addChild(dxdyIndex); } } return true; @@ -269,13 +289,14 @@ bool ParentPath(PATHNODE *pPath, Point candidatePosition, Point destinationPosit * * @return false if we ran out of preallocated nodes to use, else true */ -bool GetPath(const std::function &posOk, PATHNODE *pPath, Point destination) +bool GetPath(const std::function &posOk, uint16_t pathIndex, Point destination) { - for (auto dir : PathDirs) { - Point tile = pPath->position + dir; - bool ok = posOk(tile); - if ((ok && path_solid_pieces(pPath->position, tile)) || (!ok && tile == destination)) { - if (!ParentPath(pPath, tile, destination)) + for (Displacement dir : PathDirs) { + const PathNode &path = PathNodes[pathIndex]; + const Point tile = path.position() + dir; + const bool ok = posOk(tile); + if ((ok && path_solid_pieces(path.position(), tile)) || (!ok && tile == destination)) { + if (!ParentPath(pathIndex, tile, destination)) return false; } } @@ -340,48 +361,50 @@ bool IsTileOccupied(Point position) return false; } -int FindPath(const std::function &posOk, Point startPosition, Point destinationPosition, int8_t path[MAX_PATH_LENGTH]) +int FindPath(const std::function &posOk, Point startPosition, Point destinationPosition, int8_t path[MaxPathLength]) { /** * for reconstructing the path after the A* search is done. The longest * possible path is actually 24 steps, even though we can fit 25 */ - static int8_t pnodeVals[MAX_PATH_LENGTH]; + static int8_t pnodeVals[MaxPathLength]; // clear all nodes, create root nodes for the visited/frontier linked lists gdwCurNodes = 0; - path_2_nodes = NewStep(); - pnode_ptr = NewStep(); + Path2Nodes = &PathNodes[NewStep()]; + VisitedNodes = &PathNodes[NewStep()]; gdwCurPathStep = 0; - PATHNODE *pathStart = NewStep(); - pathStart->g = 0; - pathStart->h = GetHeuristicCost(startPosition, destinationPosition); - pathStart->f = pathStart->h + pathStart->g; - pathStart->position = startPosition; - path_2_nodes->NextNode = pathStart; + const uint16_t pathStartIndex = NewStep(); + PathNode &pathStart = PathNodes[pathStartIndex]; + pathStart.x = static_cast(startPosition.x); + pathStart.y = static_cast(startPosition.y); + pathStart.f = pathStart.h + pathStart.g; + pathStart.h = GetHeuristicCost(startPosition, destinationPosition); + pathStart.g = 0; + Path2Nodes->nextNodeIndex = pathStartIndex; // A* search until we find (dx,dy) or fail - PATHNODE *nextNode; - while ((nextNode = GetNextPath()) != nullptr) { + uint16_t nextNodeIndex; + while ((nextNodeIndex = GetNextPath()) != PathNode::InvalidIndex) { // reached the end, success! - if (nextNode->position == destinationPosition) { - PATHNODE *current = nextNode; - int pathLength = 0; - while (current->Parent != nullptr) { - if (pathLength >= MAX_PATH_LENGTH) + if (PathNodes[nextNodeIndex].position() == destinationPosition) { + const PathNode *current = &PathNodes[nextNodeIndex]; + size_t pathLength = 0; + while (current->parentIndex != PathNode::InvalidIndex) { + if (pathLength >= MaxPathLength) break; - pnodeVals[pathLength++] = GetPathDirection(current->Parent->position, current->position); - current = current->Parent; + pnodeVals[pathLength++] = GetPathDirection(PathNodes[current->parentIndex].position(), current->position()); + current = &PathNodes[current->parentIndex]; } - if (pathLength != MAX_PATH_LENGTH) { - int i; + if (pathLength != MaxPathLength) { + size_t i; for (i = 0; i < pathLength; i++) path[i] = pnodeVals[pathLength - i - 1]; - return i; + return static_cast(i); } return 0; } // ran out of nodes, abort! - if (!GetPath(posOk, nextNode, destinationPosition)) + if (!GetPath(posOk, nextNodeIndex, destinationPosition)) return 0; } // frontier is empty, no path! diff --git a/Source/path.h b/Source/path.h index dee98d822..4a4a1618a 100644 --- a/Source/path.h +++ b/Source/path.h @@ -5,7 +5,9 @@ */ #pragma once +#include #include +#include #include @@ -15,17 +17,7 @@ namespace devilution { -#define MAX_PATH_LENGTH 25 - -struct PATHNODE { - uint8_t f; - uint8_t h; - uint8_t g; - Point position; - struct PATHNODE *Parent; - struct PATHNODE *Child[8]; - struct PATHNODE *NextNode; -}; +constexpr size_t MaxPathLength = 25; bool IsTileNotSolid(Point position); bool IsTileSolid(Point position); @@ -44,7 +36,7 @@ bool IsTileOccupied(Point position); * @brief Find the shortest path from startPosition to destinationPosition, using PosOk(Point) to check that each step is a valid position. * Store the step directions (corresponds to an index in PathDirs) in path, which must have room for 24 steps */ -int FindPath(const std::function &posOk, Point startPosition, Point destinationPosition, int8_t path[MAX_PATH_LENGTH]); +int FindPath(const std::function &posOk, Point startPosition, Point destinationPosition, int8_t path[MaxPathLength]); /** * @brief check if stepping from a given position to a neighbouring tile cuts a corner. diff --git a/Source/platform/locale.cpp b/Source/platform/locale.cpp index 3167e9658..e105df575 100644 --- a/Source/platform/locale.cpp +++ b/Source/platform/locale.cpp @@ -11,6 +11,9 @@ #elif defined(__3DS__) #include "platform/ctr/locale.hpp" #elif defined(_WIN32) +// Suppress definitions of `min` and `max` macros by : +#define NOMINMAX 1 +#define WIN32_LEAN_AND_MEAN // clang-format off // Suppress definitions of `min` and `max` macros by : #define NOMINMAX 1 diff --git a/Source/player.cpp b/Source/player.cpp index dc29030d8..b5c71b42b 100644 --- a/Source/player.cpp +++ b/Source/player.cpp @@ -1561,11 +1561,11 @@ void CheckNewPath(int pnum, bool pmWillBeCalled) break; } - for (int j = 1; j < MAX_PATH_LENGTH; j++) { + for (size_t j = 1; j < MaxPathLength; j++) { player.walkpath[j - 1] = player.walkpath[j]; } - player.walkpath[MAX_PATH_LENGTH - 1] = WALK_NONE; + player.walkpath[MaxPathLength - 1] = WALK_NONE; if (player._pmode == PM_STAND) { StartStand(pnum, player._pdir); @@ -2158,7 +2158,7 @@ void Player::UpdatePreviewCelSprite(_cmd_id cmdId, Point point, uint16_t wParam1 } if (minimalWalkDistance >= 0 && position.future != point) { - int8_t testWalkPath[MAX_PATH_LENGTH]; + int8_t testWalkPath[MaxPathLength]; int steps = FindPath([this](Point position) { return PosOkPlayer(*this, position); }, position.future, point, testWalkPath); if (steps == 0) { // Can't walk to desired location => stand still diff --git a/Source/player.h b/Source/player.h index 9425d7b40..f2c228488 100644 --- a/Source/player.h +++ b/Source/player.h @@ -215,7 +215,7 @@ struct Player { Player &operator=(Player &&) noexcept = default; PLR_MODE _pmode; - int8_t walkpath[MAX_PATH_LENGTH]; + int8_t walkpath[MaxPathLength]; bool plractive; action_id destAction; int destParam1; diff --git a/appveyor.yml b/appveyor.yml index 162357107..662bd4a4d 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: 1.0.{build} +version: 1.1.{build} pull_requests: do_not_increment_build_number: true diff --git a/test/path_test.cpp b/test/path_test.cpp index ca6644aae..47d2bc27f 100644 --- a/test/path_test.cpp +++ b/test/path_test.cpp @@ -103,7 +103,7 @@ TEST(PathTest, SolidPieces) void CheckPath(Point startPosition, Point destinationPosition, std::vector expectedSteps) { - static int8_t pathSteps[MAX_PATH_LENGTH]; + static int8_t pathSteps[MaxPathLength]; auto pathLength = FindPath([](Point) { return true; }, startPosition, destinationPosition, pathSteps); EXPECT_EQ(pathLength, expectedSteps.size()) << "Wrong path length for a path from " << startPosition << " to " << destinationPosition; diff --git a/test/player_test.cpp b/test/player_test.cpp index fa2fd8fab..9fa18cd08 100644 --- a/test/player_test.cpp +++ b/test/player_test.cpp @@ -136,7 +136,7 @@ static void AssertPlayer(Player &player) ASSERT_EQ(player.pDamAcFlags, ItemSpecialEffectHf::None); ASSERT_EQ(player._pmode, 0); - ASSERT_EQ(Count8(player.walkpath, MAX_PATH_LENGTH), 0); + ASSERT_EQ(Count8(player.walkpath, MaxPathLength), 0); ASSERT_EQ(player._pSpell, 0); ASSERT_EQ(player._pSplType, 0); ASSERT_EQ(player._pSplFrom, 0); diff --git a/test/writehero_test.cpp b/test/writehero_test.cpp index 558388a2b..38924c6e7 100644 --- a/test/writehero_test.cpp +++ b/test/writehero_test.cpp @@ -262,7 +262,7 @@ static void AssertPlayer(Player &player) ASSERT_EQ(player.pDamAcFlags, ItemSpecialEffectHf::None); ASSERT_EQ(player._pmode, 0); - ASSERT_EQ(Count8(player.walkpath, MAX_PATH_LENGTH), 25); + ASSERT_EQ(Count8(player.walkpath, MaxPathLength), 25); ASSERT_EQ(player._pgfxnum, 36); ASSERT_EQ(player.AnimInfo.TicksPerFrame, 4); ASSERT_EQ(player.AnimInfo.TickCounterOfCurrentFrame, 1);