diff --git a/Source/path.cpp b/Source/path.cpp index e9d7e71db..d99981dc9 100644 --- a/Source/path.cpp +++ b/Source/path.cpp @@ -2,123 +2,162 @@ #include "../types.h" +// preallocated nodes, search is terminated after 300 nodes are visited PATHNODE path_nodes[300]; +// size of the pnode_tblptr stack int gdwCurPathStep; +// the number of in-use nodes in path_nodes int gdwCurNodes; +/* for reconstructing the path after the A* search is done. The longest + * possible path is actually 24 steps, even though we can fit 25 + */ int pnode_vals[25]; +// a linked list of all visited nodes PATHNODE *pnode_ptr; +// a stack for recursively searching nodes PATHNODE *pnode_tblptr[300]; +// a linked list of the A* frontier, sorted by distance PATHNODE *path_2_nodes; +// for iterating over the 8 possible movement directions char pathxdir[8] = { -1, -1, 1, 1, -1, 0, 1, 0 }; char pathydir[8] = { -1, 1, -1, 1, 0, -1, 0, 1 }; /* rdata */ +/* each step direction is assigned a number like this: + * dx + * -1 0 1 + * +----- + * -1|5 1 6 + * dy 0|2 0 3 + * 1|8 4 7 + */ char path_directions[9] = { 5, 1, 6, 2, 0, 3, 8, 4, 7 }; +/* find the shortest path from (sx,sy) to (dx,dy), using PosOk(PosOkArg,x,y) to + * check that each step is a valid position. Store the step directions (see + * path_directions) in path, which must have room for 24 steps + */ int __fastcall FindPath(bool (__fastcall *PosOk)(int, int, int), int PosOkArg, int sx, int sy, int dx, int dy, char *path) { - PATHNODE *v8; // esi - char v9; // al - PATHNODE *v11; // eax + PATHNODE *path_start; // esi + char initial_h; // al + PATHNODE *next_node; // eax int result; // eax - PATHNODE *v13; // edx - PATHNODE **v14; // eax - int v15; // edi - bool v16; // zf - int *v17; // ecx - char v18; // dl - + PATHNODE *current; // edx + PATHNODE **previous; // eax + int path_length; // edi + bool path_is_full; // zf + int *step_ptr; // ecx + char step; // dl + + // clear all nodes, create root nodes for the visited/frontier linked lists gdwCurNodes = 0; path_2_nodes = path_new_step(); gdwCurPathStep = 0; pnode_ptr = path_new_step(); - v8 = path_new_step(); - v8->g = 0; - v9 = path_get_h_cost(sx, sy, dx, dy); - v8->h = v9; - v8->x = sx; - v8->f = v9 + v8->g; - v8->y = sy; - path_2_nodes->NextNode = v8; + path_start = path_new_step(); + path_start->g = 0; + initial_h = path_get_h_cost(sx, sy, dx, dy); + path_start->h = initial_h; + path_start->x = sx; + path_start->f = initial_h + path_start->g; + path_start->y = sy; + path_2_nodes->NextNode = path_start; + // A* search until we find (dx,dy) or fail while ( 1 ) { - v11 = GetNextPath(); - if ( !v11 ) + next_node = GetNextPath(); + // frontier is empty, no path! + if ( !next_node ) return 0; - if ( v11->x == dx && v11->y == dy ) + // reached the end, success! + if ( next_node->x == dx && next_node->y == dy ) break; - if ( !path_get_path(PosOk, PosOkArg, v11, dx, dy) ) + // ran out of nodes, abort! + if ( !path_get_path(PosOk, PosOkArg, next_node, dx, dy) ) return 0; } - v13 = v11; - v14 = &v11->Parent; - v15 = 0; - if ( *v14 ) + current = next_node; + previous = &next_node->Parent; + path_length = 0; + if ( *previous ) { while ( 1 ) { - v16 = v15 == 25; - if ( v15 >= 25 ) + path_is_full = path_length == 25; + if ( path_length >= 25 ) break; - pnode_vals[++v15-1] = path_directions[3 * (v13->y - (*v14)->y) - (*v14)->x + 4 + v13->x]; - v13 = *v14; - v14 = &(*v14)->Parent; - if ( !*v14 ) + pnode_vals[++path_length-1] = path_directions[3 * (current->y - (*previous)->y) - (*previous)->x + 4 + current->x]; + current = *previous; + previous = &(*previous)->Parent; + if ( !*previous ) { - v16 = v15 == 25; + path_is_full = path_length == 25; break; } } - if ( v16 ) + if ( path_is_full ) return 0; } result = 0; - if ( v15 > 0 ) + if ( path_length > 0 ) { - v17 = &pnode_vals[v15-1]; + step_ptr = &pnode_vals[path_length-1]; do { - v18 = *(_BYTE *)v17; - --v17; - path[result++] = v18; + step = *(_BYTE *)step_ptr; + --step_ptr; + path[result++] = step; } - while ( result < v15 ); + while ( result < path_length ); } return result; } +/* heuristic, estimated cost from (sx,sy) to (dx,dy) */ int __fastcall path_get_h_cost(int sx, int sy, int dx, int dy) { - int v4; // esi - int v5; // edi - int v6; // eax - int v7; // ecx - - v4 = sy; - v5 = abs(sx - dx); - v6 = abs(v4 - dy); - v7 = v5; - if ( v5 >= v6 ) + int y; // esi + int delta_x; // edi + int delta_y; // eax + int min_delta; // ecx + + y = sy; + delta_x = abs(sx - dx); + delta_y = abs(y - dy); + // this is a pointless swap, it's just 2(delta_x+delta_y) + min_delta = delta_x; + if ( delta_x >= delta_y ) { - v7 = v6; - if ( v5 > v6 ) - v6 = v5; + min_delta = delta_y; + if ( delta_x > delta_y ) + delta_y = delta_x; } - return 2 * (v7 + v6); + // see path_check_equal for why this is times 2 + return 2 * (min_delta + delta_y); } +/* return 2 if pPath is horizontally/vertically aligned with (dx,dy), else 3 + * + * This approximates that diagonal movement on a square grid should have a cost + * of sqrt(2). That's approximately 1.5, so they multiply all step costs by 2, + * except diagonal steps which are times 3 + */ int __fastcall path_check_equal(PATHNODE *pPath, int dx, int dy) { - int v4; // [esp-4h] [ebp-4h] + int result; // [esp-4h] [ebp-4h] if ( pPath->x == dx || pPath->y == dy ) - v4 = 2; + result = 2; else - v4 = 3; - return v4; + result = 3; + return result; } +/* 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 *__cdecl GetNextPath() { PATHNODE *result; // eax @@ -133,46 +172,56 @@ PATHNODE *__cdecl GetNextPath() return result; } +/* check if stepping from pPath to (dx,dy) cuts a corner. If you step from A to + * B, both Xs need to be clear: + * + * AX + * XB + * + * return true if step is allowed + */ bool __fastcall path_solid_pieces(PATHNODE *pPath, int dx, int dy) { bool result; // eax int dir; // ecx - int v8; // ecx - int v10; // edx + int tile1; // ecx + int tile2; // edx result = 1; + // this maps the four corner directions to 0,1,2,3 dir = path_directions[3 * (dy - pPath->y) - pPath->x + 4 + dx] - 5; - if ( !dir ) + // and this is basically a switch + if ( !dir ) // (-1,-1)->0 { result = 0; if ( nSolidTable[dPiece[dx][dy + 1]] ) return result; - v8 = dPiece[dx + 1][dy]; + tile1 = dPiece[dx + 1][dy]; goto LABEL_13; } - if ( !--dir ) + if ( !--dir ) // (1,-1)->1 { - v10 = dPiece[dx][dy + 1]; + tile2 = dPiece[dx][dy + 1]; goto LABEL_9; } - if ( !--dir ) + if ( !--dir ) // (1,1)->2 { - v10 = dPiece[dx][dy-1]; /* check */ + tile2 = dPiece[dx][dy-1]; /* check */ LABEL_9: result = 0; - if ( nSolidTable[v10] ) + if ( nSolidTable[tile2] ) return result; - v8 = dPiece[dx-1][dy]; /* check */ + tile1 = dPiece[dx-1][dy]; /* check */ goto LABEL_13; } - if ( dir == 1 ) + if ( dir == 1 ) // (-1,1)->3 { result = 0; if ( !nSolidTable[dPiece[dx + 1][dy]] ) { - v8 = dPiece[dx][dy-1]; /* check */ + tile1 = dPiece[dx][dy-1]; /* check */ LABEL_13: - if ( !nSolidTable[v8] ) + if ( !nSolidTable[tile1] ) result = 1; return result; } @@ -180,18 +229,23 @@ LABEL_13: return result; } +/* perform a single step of A* bread-first search by trying to step in every + * possible direction from pPath with goal (x,y). Check each step with PosOk + * + * return 0 if we ran out of preallocated nodes to use, else 1 + */ int __fastcall path_get_path(bool (__fastcall *PosOk)(int, int, int), int PosOkArg, PATHNODE *pPath, int x, int y) { - int v5; // eax + int dir; // eax int dx; // esi int dy; // edi int i; // [esp+14h] [ebp-4h] - v5 = 0; - for ( i = 0; ; v5 = i ) + dir = 0; + for ( i = 0; ; dir = i ) { - dx = pPath->x + pathxdir[v5]; - dy = pPath->y + pathydir[v5]; + dx = pPath->x + pathxdir[dir]; + dy = pPath->y + pathydir[dir]; if ( !PosOk(PosOkArg, dx, dy) ) break; if ( path_solid_pieces(pPath, dx, dy) ) @@ -208,108 +262,127 @@ LABEL_8: return 0; } +/* add a step from pPath to (dx,dy), return 1 if successful, and update the + * frontier/visited nodes accordingly + * + * return 1 if step successfully added, 0 if we ran out of nodes to use + */ int __fastcall path_parent_path(PATHNODE *pPath, int dx, int dy, int sx, int sy) { - PATHNODE *v5; // edi - int v6; // ebx - PATHNODE *v7; // esi - signed int v8; // eax - struct PATHNODE **v9; // ecx - char v10; // al - PATHNODE *v11; // esi - signed int v12; // eax - struct PATHNODE **v13; // ecx - char v14; // al + PATHNODE *pPath2; // edi, pointless copy + int next_g; // ebx + + PATHNODE *dxdy_frontier; // esi + signed int empty_slot1; // eax + struct PATHNODE **pPath_child_ptr1; // ecx + char dxdy_h; // al + + PATHNODE *dxdy_visited; // esi + signed int empty_slot2; // eax + struct PATHNODE **pPath_child_ptr2; // ecx + char dxdy_f; // al + PATHNODE *result; // eax - PATHNODE *v16; // esi - char v17; // al - signed int v18; // ecx - struct PATHNODE **v19; // eax - int a1; // [esp+Ch] [ebp-4h] - - a1 = dx; - v5 = pPath; - v6 = pPath->g + path_check_equal(pPath, dx, dy); - v7 = path_get_node1(a1, dy); - if ( v7 ) + PATHNODE *dxdy_new; // esi + char h_new; // al + signed int empty_slot3; // ecx + struct PATHNODE **pPath_child_ptr3; // eax + + int dx2; // [esp+Ch] [ebp-4h], pointless copy + + dx2 = dx; + pPath2 = pPath; + next_g = pPath->g + path_check_equal(pPath, dx, dy); + + // 3 cases to consider + // case 1: (dx,dy) is already on the frontier + dxdy_frontier = path_get_node1(dx2, dy); + if ( dxdy_frontier ) { - v8 = 0; - v9 = v5->Child; + empty_slot1 = 0; + pPath_child_ptr1 = pPath2->Child; do { - if ( !*v9 ) + if ( !*pPath_child_ptr1 ) break; - ++v8; - ++v9; + ++empty_slot1; + ++pPath_child_ptr1; } - while ( v8 < 8 ); - v5->Child[v8] = v7; - if ( v6 < v7->g ) + while ( empty_slot1 < 8 ); + pPath2->Child[empty_slot1] = dxdy_frontier; + if ( next_g < dxdy_frontier->g ) { - if ( path_solid_pieces(v5, a1, dy) ) + if ( path_solid_pieces(pPath2, dx2, dy) ) { - v10 = v7->h; - v7->Parent = v5; - v7->g = v6; - v7->f = v6 + v10; + // we'll explore it later, just update + dxdy_h = dxdy_frontier->h; + dxdy_frontier->Parent = pPath2; + dxdy_frontier->g = next_g; + dxdy_frontier->f = next_g + dxdy_h; } } } else { - v11 = path_get_node2(a1, dy); - if ( v11 ) + // case 2: (dx,dy) was already visited + dxdy_visited = path_get_node2(dx2, dy); + if ( dxdy_visited ) { - v12 = 0; - v13 = v5->Child; + empty_slot2 = 0; + pPath_child_ptr2 = pPath2->Child; do { - if ( !*v13 ) + if ( !*pPath_child_ptr2 ) break; - ++v12; - ++v13; + ++empty_slot2; + ++pPath_child_ptr2; } - while ( v12 < 8 ); - v5->Child[v12] = v11; - if ( v6 < v11->g && path_solid_pieces(v5, a1, dy) ) + while ( empty_slot2 < 8 ); + pPath2->Child[empty_slot2] = dxdy_visited; + if ( next_g < dxdy_visited->g && path_solid_pieces(pPath2, dx2, dy) ) { - v14 = v6 + v11->h; - v11->Parent = v5; - v11->g = v6; - v11->f = v14; - path_set_coords(v11); + // update the node + dxdy_f = next_g + dxdy_visited->h; + dxdy_visited->Parent = pPath2; + dxdy_visited->g = next_g; + dxdy_visited->f = dxdy_f; + // already explored, so re-update others starting from that node + path_set_coords(dxdy_visited); } } else { + // case 3: (dx,dy) is totally new result = path_new_step(); - v16 = result; + dxdy_new = result; if ( !result ) return 0; - result->Parent = v5; - result->g = v6; - v17 = path_get_h_cost(a1, dy, sx, sy); - v16->h = v17; - v16->f = v6 + v17; - v16->x = a1; - v16->y = dy; - path_next_node(v16); - v18 = 0; - v19 = v5->Child; + result->Parent = pPath2; + result->g = next_g; + h_new = path_get_h_cost(dx2, dy, sx, sy); + dxdy_new->h = h_new; + dxdy_new->f = next_g + h_new; + dxdy_new->x = dx2; + dxdy_new->y = dy; + // add it to the frontier + path_next_node(dxdy_new); + empty_slot3 = 0; + pPath_child_ptr3 = pPath2->Child; do { - if ( !*v19 ) + if ( !*pPath_child_ptr3 ) break; - ++v18; - ++v19; + ++empty_slot3; + ++pPath_child_ptr3; } - while ( v18 < 8 ); - v5->Child[v18] = v16; + while ( empty_slot3 < 8 ); + pPath2->Child[empty_slot3] = dxdy_new; } } return 1; } +/* return a node for (dx,dy) on the frontier, or NULL if not found */ PATHNODE *__fastcall path_get_node1(int dx, int dy) { PATHNODE *result; // eax @@ -321,6 +394,7 @@ PATHNODE *__fastcall path_get_node1(int dx, int dy) return result; } +/* return a node for (dx,dy) if it was visited, or NULL if not found */ PATHNODE *__fastcall path_get_node2(int dx, int dy) { PATHNODE *result; // eax @@ -332,45 +406,48 @@ PATHNODE *__fastcall path_get_node2(int dx, int dy) return result; } +/* insert pPath into the frontier (keeping the frontier sorted by total + * distance) */ void __fastcall path_next_node(PATHNODE *pPath) { - PATHNODE *v1; // edx - PATHNODE *v2; // eax + PATHNODE *current; // edx + PATHNODE *next; // eax - v1 = path_2_nodes; - v2 = path_2_nodes->NextNode; - if ( v2 ) + current = path_2_nodes; + next = path_2_nodes->NextNode; + if ( next ) { do { - if ( v2->f >= pPath->f ) + if ( next->f >= pPath->f ) break; - v1 = v2; - v2 = v2->NextNode; + current = next; + next = next->NextNode; } - while ( v2 ); - pPath->NextNode = v2; + while ( next ); + pPath->NextNode = next; } - v1->NextNode = pPath; + current->NextNode = pPath; } +/* update all path costs using depth-first search starting at pPath */ void __fastcall path_set_coords(PATHNODE *pPath) { PATHNODE *PathOld; // edi PATHNODE *PathAct; // esi - char v6; // al + char next_g; // al int i; // [esp+0h] [ebp-8h] - PATHNODE **v9; // [esp+4h] [ebp-4h] + PATHNODE **child_ptr; // [esp+4h] [ebp-4h] path_push_active_step(pPath); while ( gdwCurPathStep ) { PathOld = path_pop_active_step(); - v9 = PathOld->Child; + child_ptr = PathOld->Child; for(i = 0; i < 8; i++) { - PathAct = *v9; - if ( !*v9 ) + PathAct = *child_ptr; + if ( !*child_ptr ) break; if ( PathOld->g + path_check_equal(PathOld, PathAct->x, PathAct->y) < PathAct->g ) @@ -378,37 +455,41 @@ void __fastcall path_set_coords(PATHNODE *pPath) if ( path_solid_pieces(PathOld, PathAct->x, PathAct->y) ) { PathAct->Parent = PathOld; - v6 = PathOld->g + path_check_equal(PathOld, PathAct->x, PathAct->y); - PathAct->g = v6; - PathAct->f = v6 + PathAct->h; + next_g = PathOld->g + path_check_equal(PathOld, PathAct->x, PathAct->y); + PathAct->g = next_g; + PathAct->f = next_g + PathAct->h; path_push_active_step(PathAct); } } - ++v9; + ++child_ptr; } } } +/* push pPath onto the pnode_tblptr stack */ void __fastcall path_push_active_step(PATHNODE *pPath) { - int v1; // eax + int stack_index; // eax - v1 = gdwCurPathStep++; - pnode_tblptr[v1] = pPath; + stack_index = gdwCurPathStep++; + pnode_tblptr[stack_index] = pPath; } +/* pop and return a node from the pnode_tblptr stack */ PATHNODE *__cdecl path_pop_active_step() { return pnode_tblptr[--gdwCurPathStep]; } +/* zero one of the preallocated nodes and return a pointer to it, or NULL if + * none are available */ PATHNODE *__cdecl path_new_step() { - PATHNODE *v1; // esi + PATHNODE *new_node; // esi if ( gdwCurNodes == 300 ) return 0; - v1 = &path_nodes[gdwCurNodes++]; - memset(v1, 0, 0x34u); - return v1; + new_node = &path_nodes[gdwCurNodes++]; + memset(new_node, 0, 0x34u); + return new_node; }