@ -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 < uint16_t > : : 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 = Path2N odes;
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 = pP ath ;
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 = pP ath ;
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 < int16_t > ( candidatePosition . x ) ;
dxdy . y = static_cast < int16_t > ( 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 < bool ( Point ) > & posOk , PATHNODE * pPath , Point destination )
bool GetPath ( const std : : function < bool ( Point ) > & 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 < bool ( Point ) > & posOk , Point startPosition , Point destinationPosition , int8_t path [ MAX_PATH_LENGTH ] )
int FindPath ( const std : : function < bool ( Point ) > & 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_n odes = NewStep ( ) ;
pnode_ptr = NewStep ( ) ;
Path2N odes = & 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 < int16_t > ( startPosition . x ) ;
pathStart . y = static_cast < int16_t > ( 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 ;
in t 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 ) {
in t i ;
if ( pathLength ! = MaxPathLength ) {
size_ t i ;
for ( i = 0 ; i < pathLength ; i + + )
path [ i ] = pnodeVals [ pathLength - i - 1 ] ;
return i ;
return static_cast < int > ( 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!