diff --git a/src/include/pathfinder.h b/src/include/pathfinder.h index fedc81043..3fbe44707 100644 --- a/src/include/pathfinder.h +++ b/src/include/pathfinder.h @@ -47,6 +47,12 @@ enum _move_return_ { extern unsigned char Matrix[(MaxMapWidth+1)*(MaxMapHeight+1)]; /// Path matrix +extern int AStarOn; /// are we using a* or the old path finder +extern int AStarFixedUnitCrossingCost; /// cost associated to move on a tile + /// occupied by a fixed unit +extern int AStarMovingUnitCrossingCost; /// cost associated to move on a tile + /// occupied by a moving unit + /*---------------------------------------------------------------------------- -- Functions @@ -64,6 +70,12 @@ extern int UnitReachable(Unit* unit,Unit* dest); /// Returns the next element of the path extern int NextPathElement(Unit*,int* xdp,int* ydp); +// +// in ccl_pathfinder.c +// + /// register ccl features +extern void PathfinderCclRegister(void); + //@} #endif // !__PATH_FINDER_H__ diff --git a/src/pathfinder/Makefile b/src/pathfinder/Makefile index 09989911e..13efc75e9 100644 --- a/src/pathfinder/Makefile +++ b/src/pathfinder/Makefile @@ -14,6 +14,6 @@ include $(TOPDIR)/Rules.make MODULE = pathfinder -OBJS = pathfinder.$(OE) astar.$(OE) +OBJS = pathfinder.$(OE) astar.$(OE) ccl_pathfinder.$(OE) include $(TOPDIR)/Common.mk diff --git a/src/pathfinder/astar.cpp b/src/pathfinder/astar.cpp index e49ed0e09..2026774b6 100644 --- a/src/pathfinder/astar.cpp +++ b/src/pathfinder/astar.cpp @@ -1,13 +1,23 @@ -/* -** A clone of a famous game. -*/ +// ___________ _________ _____ __ +// \_ _____/______ ____ ____ \_ ___ \____________ _/ ____\/ |_ +// | __) \_ __ \_/ __ \_/ __ \/ \ \/\_ __ \__ \\ __\\ __\ +// | \ | | \/\ ___/\ ___/\ \____| | \// __ \| | | | +// \___ / |__| \___ >\___ >\______ /|__| (____ /__| |__| +// \/ \/ \/ \/ \/ +// ______________________ ______________________ +// T H E W A R B E G I N S +// FreeCraft - A free fantasy real time strategy game engine +// /**@name astar.c - The a* path finder routines. */ /* -** (c) Copyright 1999 by Lutz Sammer +** (c) Copyright 1999-2000 by Lutz Sammer and Fabrice Rossi ** ** $Id$ */ +//FIXME: I don't fully understand the range parameter in the Unit +// structure. Something might be broken because of this misunderstanding + //@{ #include @@ -19,7 +29,8 @@ #include "pathfinder.h" typedef struct _node_ { - int Direction; /// Direction for trace back + short Direction; /// Direction for trace back + short InGoal; /// is this point in the goal int CostFromStart; /// Real costs to reach this point int CostToGoal; /// Aproximated costs until goal } Node; @@ -31,9 +42,19 @@ typedef struct _open_ { int Costs; /// complete costs to goal } Open; +/// heuristic cost fonction for a star #define AStarCosts(sx,sy,ex,ey) max(abs(sx-ex),abs(sy-ey)) +/// cost matrix local Node AStarMatrix[(MaxMapWidth+1)*(MaxMapHeight+1)]; +/// a list of close nodes, helps to speed up the matrix cleaning +local int CloseSet[(MaxMapWidth+1)*(MaxMapHeight+1)]; +local int Threshold=(MaxMapWidth+1)*(MaxMapHeight+1)/4; + +/// see pathfinder.h +global int AStarFixedUnitCrossingCost=MaxMapWidth*MaxMapHeight; +global int AStarMovingUnitCrossingCost=2; +global int AStarOn=0; /** ** Prepare path finder. @@ -43,66 +64,287 @@ local void AStarPrepare(void) memset(AStarMatrix,0,sizeof(AStarMatrix)); } +/** + ** Clean up the AStarMatrix + */ +local void AStarCleanUp(int num_in_close) +{ + int i; + if(num_in_close>Threshold) { + AStarPrepare(); + } else { + for(i=0;i>1)-1; + while(i<=end) { + j=(i<<1)+1; + if(j=OpenSet[j+1].Costs) + j++; + if(OpenSet[i].Costs>OpenSet[j].Costs) { + swap=OpenSet[i]; + OpenSet[i]=OpenSet[j]; + OpenSet[j]=swap; + i=j; + } else { + break; + } + } + } +} + +/** + ** Add a new node to the open set (and update the heap structure) + */ +local void AStarAddNode(int x,int y,int o,int costs) +{ + int i=OpenSetSize; + int j; + Open swap; + OpenSet[i].X=x; + OpenSet[i].Y=y; + OpenSet[i].O=o; + OpenSet[i].Costs=costs; + OpenSetSize++; + while(i>0) { + j=(i-1)>>1; + if(OpenSet[i].Costs0) { + j=(i-1)>>1; + if(OpenSet[i].Costs impossible to cross + ** 0 -> no induced cost, except move + ** >0 -> costly tile + */ +local int CostMoveTo(int ex,int ey,int mask,int current_cost) { + int j; + Unit* goal; + + j=TheMap.Fields[ex+ey*TheMap.Width].Flags&mask; + if (j) { + if(j&~(MapFieldLandUnit|MapFieldAirUnit|MapFieldSeaUnit)) { + // we can't cross fixed units and other unpassable things + return -1; + } + if(current_cost>=AStarFixedUnitCrossingCost) { + // we are already crossing a fixed unit. We don't need details + return AStarMovingUnitCrossingCost; + } else { + goal=UnitOnMapTile(ex,ey); + if( !goal ) { + return -1;//FIXME: is this a bug? + } + if( goal->Moving ) { + // moving unit are crossable + return AStarMovingUnitCrossingCost; + } + // for non moving unit + return AStarFixedUnitCrossingCost; + } + } + // empty tile + return 0; +} + /** ** Find path. */ local int AStarFindPath(Unit* unit,int* pxd,int* pyd) { - int i; + int i,j; int o; int x; int y; int ex; int ey; - int num_in_open; + int dx,dy; + int eo,gx,gy,cx,cy,sx; + int b_x,b_y,b_d,b_c; + int r; int shortest; int counter; - Open OpenSet[MaxMapWidth*MaxMapHeight/64]; + int new_cost; + int last_dir; + int path_length; + int num_in_close=0; + int mask=UnitMovementMask(unit); + int base_mask=mask&~(MapFieldLandUnit|MapFieldAirUnit|MapFieldSeaUnit); + Unit* goal; static int xoffset[]={ 0,-1,+1, 0, -1,+1,-1,+1 }; static int yoffset[]={ -1, 0, 0,+1, -1,-1,+1,+1 }; - DebugLevel0(__FUNCTION__": %Zd %d,%d->%d,%d\n", + DebugLevel3(__FUNCTION__": %Zd %d,%d->%d,%d\n", UnitNumber(unit), unit->X,unit->Y, unit->Command.Data.Move.DX,unit->Command.Data.Move.DY); - - AStarPrepare(); + + OpenSetSize=0; + /* AStarPrepare();*/ x=unit->X; y=unit->Y; - ex=unit->Command.Data.Move.DX; - ey=unit->Command.Data.Move.DY; - - OpenSet[0].X=x; // place start point in open - OpenSet[0].Y=y; - OpenSet[0].O=x*TheMap.Width+y; - OpenSet[0].Costs=AStarCosts(x,y,ex,ey); - - AStarMatrix[OpenSet[0].O].CostFromStart=0; // mark in matrix - AStarMatrix[OpenSet[0].O].CostToGoal=OpenSet[0].Costs; - - num_in_open=1; + r=unit->Command.Data.Move.Range; + i=0; + // Let's first mark goal + if(unit->Command.Data.Move.Goal) { + goal=unit->Command.Data.Move.Goal; + j=goal->Type->Type; + cx=goal->X; + cy=goal->Y; + ey=UnitTypes[j].TileHeight+r-1; + sx=UnitTypes[j].TileWidth+r-1; + // approximate goal for A* + gx=goal->X+UnitTypes[j].TileHeight/2; + gy=goal->Y+UnitTypes[j].TileWidth/2; + } else { + cx=gx=unit->Command.Data.Move.DX; + cy=gy=unit->Command.Data.Move.DY; + ey=r; + sx=r; + r=0; + } + for(;ey>=-r;ey--){ + dy=cy+ey; + if( dy<0 || dy>=TheMap.Height ) { + continue; + } + for(ex=sx;ex>=-r;ex--) { + dx=cx+ex; + if( dx<0 || dx>=TheMap.Width ) { + continue; + } + if(CostMoveTo(dx,dy,mask,AStarFixedUnitCrossingCost)>=0) { + eo=dx*TheMap.Width+dy; + AStarMatrix[eo].InGoal=1; + CloseSet[num_in_close]=eo; + num_in_close++; + i=1; + } + } + } + if(i) { + eo=x*TheMap.Width+y; + AStarMatrix[eo].CostToGoal=AStarCosts(x,y,gx,gy); + // it is quite important to start from 1 rather than 0, because we use + // 0 as a way to represent nodes that we have not visited yet. + AStarMatrix[eo].CostFromStart=1; + // place start point in open + AStarAddNode(x,y,eo,AStarMatrix[eo].CostFromStart+1); + CloseSet[num_in_close]=OpenSet[0].O; + num_in_close++; + b_c=1; + b_d=AStarMatrix[eo].CostToGoal; + b_x=x; + b_y=y; + } else { + AStarCleanUp(num_in_close); + return -2; + } counter=MaxMapWidth*MaxMapHeight; // how many tries for( ;; ) { // // Find the best node of from the open set // - for( i=shortest=0; i=TheMap.Height ) { continue; } + // if the point is "move to"-able an + // if we have not reached this point before, + // or if we have a better path to it, we add it to open set + new_cost=CostMoveTo(ex,ey,mask,AStarMatrix[o].CostFromStart); + if(new_cost==-1) { + // uncrossable tile + continue; + } + eo=ex*TheMap.Width+ey; + new_cost+=AStarMatrix[o].CostFromStart+1; + if(AStarMatrix[eo].CostFromStart==0) { + // we are sure the current node has not been already visited + AStarMatrix[eo].CostFromStart=new_cost; + AStarMatrix[eo].CostToGoal=AStarCosts(ex,ey,gx,gy); + AStarMatrix[eo].Direction=i; + AStarAddNode(ex,ey,eo, + AStarMatrix[eo].CostFromStart+ + AStarMatrix[eo].CostToGoal); + // we add the point to the close set + CloseSet[num_in_close++]=eo; + } else if(new_costX; + y=unit->Y; + DebugLevel3("%d %d %d %d\n",x,y,ex,ey); + while(ex != x || ey !=y) { + eo=ex*TheMap.Width+ey; + i=AStarMatrix[eo].Direction; + ex-=xoffset[i]; + ey-=yoffset[i]; + DebugLevel3("%d %d %d %d\n",x,y,ex,ey); + path_length++; + } + *pxd=xoffset[i]; + *pyd=yoffset[i]; + j=CostMoveTo(ex+*pxd,ey+*pyd,mask,0); + if(j!=0) { + if(j==AStarMovingUnitCrossingCost) { + // we should wait, we are blocked by a moving unit + //FIXME: this might lead to a deadlock, or something similar + DebugLevel3("Unit %Zd waiting. Proposed move: %d %d\n", + UnitNumber(unit),*pxd,*pyd); + path_length=0; + } else { + // j==-1 is a bug, so we should have only + // j==AStarFixedUnitCrossingCost, which means + // the way is blocked by a non moving unit. Waiting is here + // pointless. + path_length=-2; + } + } + // let's clean up the matrix now + AStarCleanUp(num_in_close); + DebugLevel3(__FUNCTION__": %Zd\n",UnitNumber(unit)); + DebugLevel3(__FUNCTION__": proposed move: %d %d (%d)\n",*pxd,*pyd,path_length); + return path_length; } /** @@ -165,16 +490,55 @@ local int AStarFindPath(Unit* unit,int* pxd,int* pyd) global int AStarNextPathElement(Unit* unit,int* pxd,int *pyd) { // FIXME: Cache for often used pathes, like peons to goldmine. - AStarFindPath(unit,pxd,pyd); - - switch( NewPath(unit,pxd,pyd) ) { - case 0: - return 999; - case 1: + // FIXME: (fabrice) I've copied here the code from NewPath. Is it really + // needed? + int x; + int y; + int r=unit->Command.Data.Move.Range; + Unit* goal=unit->Command.Data.Move.Goal; + UnitType* type; + + x=unit->X; + y=unit->Y; + if( goal ) { // goal unit + type=goal->Type; + DebugLevel3(__FUNCTION__": Unit %d,%d Goal %d,%d - %d,%d\n" + ,x,y + ,goal->X-r,goal->Y-r + ,goal->X+type->TileWidth+r + ,goal->Y+type->TileHeight+r); + if( x>=goal->X-r && xX+type->TileWidth+r + && y>=goal->Y-r && yY+type->TileHeight+r ) { + DebugLevel3(__FUNCTION__": Goal reached\n"); + *pxd=*pyd=0; return -1; - default: - return -2; + } + } else { // goal map field + if( x>=unit->Command.Data.Move.DX + && x<=unit->Command.Data.Move.DX+r + && y>=unit->Command.Data.Move.DY + && y<=unit->Command.Data.Move.DY+r ) { + DebugLevel3(__FUNCTION__": Field reached\n"); + *pxd=*pyd=0; + return -1; + } + // This reduces the processor use, + // If target isn't reachable and were beside it + if( r==0 && x>=unit->Command.Data.Move.DX-1 + && x<=unit->Command.Data.Move.DX+1 + && y>=unit->Command.Data.Move.DY-1 + && y<=unit->Command.Data.Move.DY+1 ) { + if( !CheckedCanMoveToMask(unit->Command.Data.Move.DX + ,unit->Command.Data.Move.DY + ,UnitMovementMask(unit)) ) { // blocked + DebugLevel3(__FUNCTION__": Field unreached\n"); + *pxd=*pyd=0; + return -2; + } + } } + + return AStarFindPath(unit,pxd,pyd); } /** @@ -190,7 +554,9 @@ global int AStarNextPathElement(Unit* unit,int* pxd,int *pyd) global int NextPathElement(Unit* unit,int* pxd,int *pyd) { // Convert old version to new version - if( 1 || unit!=Selected[0] ) { + if(AStarOn) { + return AStarNextPathElement(unit,pxd,pyd); + } else { switch( NewPath(unit,pxd,pyd) ) { case 0: return 999; @@ -200,10 +566,6 @@ global int NextPathElement(Unit* unit,int* pxd,int *pyd) return -2; } } - - DebugLevel0(__FUNCTION__": %Zd#%s\n",UnitNumber(unit),unit->Type->Ident); - - return AStarNextPathElement(unit,pxd,pyd); } //@} diff --git a/src/pathfinder/script_pathfinder.cpp b/src/pathfinder/script_pathfinder.cpp new file mode 100644 index 000000000..c6bef1476 --- /dev/null +++ b/src/pathfinder/script_pathfinder.cpp @@ -0,0 +1,117 @@ +// ___________ _________ _____ __ +// \_ _____/______ ____ ____ \_ ___ \____________ _/ ____\/ |_ +// | __) \_ __ \_/ __ \_/ __ \/ \ \/\_ __ \__ \\ __\\ __\ +// | \ | | \/\ ___/\ ___/\ \____| | \// __ \| | | | +// \___ / |__| \___ >\___ >\______ /|__| (____ /__| |__| +// \/ \/ \/ \/ \/ +// ______________________ ______________________ +// T H E W A R B E G I N S +// FreeCraft - A free fantasy real time strategy game engine +// +/**@name ccl_pathfinder.c - pathfinder ccl functions. */ +/* +** (c) Copyright 2000 by Lutz Sammer and Fabrice Rossi +** +** $Id$ +*/ + +//@{ + +/*---------------------------------------------------------------------------- +-- Includes +----------------------------------------------------------------------------*/ + +#include +#include +#include + +#include "clone.h" + +#if defined(USE_CCL) || defined(USE_CCL2) // { + +#include "video.h" +#include "tileset.h" +#include "map.h" +#include "sound_id.h" +#include "unitsound.h" +#include "unittype.h" +#include "player.h" +#include "unit.h" +#include "ccl.h" +#include "pathfinder.h" + +/*---------------------------------------------------------------------------- +-- Functions +----------------------------------------------------------------------------*/ + +/** + ** Enable a*. +*/ +local SCM CclAStar(void) +{ + AStarOn=1; + + return SCM_UNSPECIFIED; +} + +/** +** Disable a*. +*/ +local SCM CclNoAStar(void) +{ + AStarOn=0; + + return SCM_UNSPECIFIED; +} + +/** +** Set a* parameter (cost of FIXED unit tile crossing). +*/ +local SCM CclAStarSetFixedUCC(SCM cost) +{ + int i; + + i=gh_scm2int(cost); + if( i<=0) { + fprintf(stderr,__FUNCTION__": Fixed unit crossing cost must be strictly positive\n"); + i=MaxMapWidth*MaxMapHeight; + } + AStarFixedUnitCrossingCost=i; + + return SCM_UNSPECIFIED; +} + +/** +** Set a* parameter (cost of MOVING unit tile crossing). +*/ +local SCM CclAStarSetMovingUCC(SCM cost) +{ + int i; + + i=gh_scm2int(cost); + if( i<=0) { + fprintf(stderr,__FUNCTION__": Moving unit crossing cost must be strictly positive\n"); + i=1; + } + AStarMovingUnitCrossingCost=i; + + return SCM_UNSPECIFIED; +} + + +/** +** Register CCL features for pathfinder. +*/ +global void PathfinderCclRegister(void) +{ + gh_new_procedure0_0("a-star",CclAStar); + gh_new_procedure0_0("no-a-star",CclNoAStar); + gh_new_procedure1_0("a-star-fixed-unit-cost",CclAStarSetFixedUCC); + gh_new_procedure1_0("a-star-moving-unit-cost",CclAStarSetMovingUCC); +} + + + +#endif // } defined(USE_CCL) || defined(USE_CCL2) + +//@} diff --git a/src/stratagus/script.cpp b/src/stratagus/script.cpp index 61a5f7b48..f374d2c7d 100644 --- a/src/stratagus/script.cpp +++ b/src/stratagus/script.cpp @@ -47,6 +47,7 @@ #include "ccl_sound.h" #include "ccl.h" #include "font.h" +#include "pathfinder.h" #include // I use guile for a quick hack @@ -79,6 +80,7 @@ extern void sgtk_init_gtk_gdk_glue(); #include "ccl_sound.h" #include "ui.h" #include "font.h" +#include "pathfinder.h" #include "ai.h" #endif // USE_CCL2 @@ -702,6 +704,7 @@ local void gh_main_prog(int argc,char* argv[]) gh_new_procedureN("missile-type",CclMissileType); MapCclRegister(); + PathfinderCclRegister(); UnitButtonCclRegister(); UnitTypeCclRegister(); SoundCclRegister(); @@ -812,6 +815,7 @@ global void CclInit(void) init_lsubr("missile-type",CclMissileType); MapCclRegister(); + PathfinderCclRegister(); UnitButtonCclRegister(); UnitTypeCclRegister(); UpgradesCclRegister();