Micropather実装A*アルゴリズム
10147 ワード
MicroPather is a path finder and A* solver (astar or a-star) written in platform independent C++ that can be easily integrated into existing code. MicroPather focuses on being a path finding engine for video games but is a generic A* solver.
There is plenty of pathing code out there, but most if it seems to focus on teaching somewhat how to write an A* solver, rather than being utility code for pathing. MicroPather is firmly aimed at providing functionality, not at being a tutorial.
MicroPather's primary goal is to be easy to use: An easy API No requirements on the host program to change its data structures or objects No library or 'make' - just add 1 .cpp and 1 .h file to your project.
Enjoy, and thanks for checking out MicroPather!
公式サイトではsdkとdemoをダウンロードできます.
demo 1のコードは以下の通りです.
dungeon.cpp
There is plenty of pathing code out there, but most if it seems to focus on teaching somewhat how to write an A* solver, rather than being utility code for pathing. MicroPather is firmly aimed at providing functionality, not at being a tutorial.
MicroPather's primary goal is to be easy to use:
Enjoy, and thanks for checking out MicroPather!
公式サイトではsdkとdemoをダウンロードできます.
demo 1のコードは以下の通りです.
dungeon.cpp
#define USE_PATHER
#include <ctype.h>
#include <stdio.h>
#include <memory.h>
#include <math.h>
#include <vector>
#include <iostream>
#ifdef USE_PATHER
#include "micropather.h"
using namespace micropather;
#endif
const int MAPX = 30;
const int MAPY = 10;
const char gMap[MAPX*MAPY+1] =
//"012345678901234567890123456789"
" | | |"
" | |----+ | +"
"---+ +---DD-+ +--+--+ "
" | +-- +"
" +----+ +---+ "
"---+ + D D | "
" | | +----+ +----+ +--+"
" D | | | "
" | +-------+ +-+ |--+ "
"---+ | +";
class Dungeon
#ifdef USE_PATHER
: public Graph
#endif
{
private:
Dungeon( const Dungeon& );
void operator=( const Dungeon& );
//
int playerX, playerY;
//
std::vector<void*> path;
//
bool doorsOpen;
//
bool showConsidered;
MicroPather* pather;
public:
Dungeon() : playerX( 0 ), playerY( 0 ), doorsOpen( false ), showConsidered( false ), pather( 0 )
{
// this : The "map" that implements the Graph callbacks.
// 20 : How many states should be internally allocated at a time.
// This can be hard to get correct. The higher the value,
// the more memory MicroPather will use.
pather = new MicroPather( this, 20 ); // Use a very small memory block to stress the pather
}
virtual ~Dungeon()
{
delete pather;
}
int X() { return playerX; }
int Y() { return playerY; }
// , debug
unsigned Checksum() { return pather->Checksum(); }
void ClearPath()
{
#ifdef USE_PATHER
path.resize( 0 );
#endif
}
//
void ToggleTouched()
{
showConsidered = !showConsidered;
// Reset() Should be called whenever the cost between states or
// the connection between states changes.
// Also frees overhead memory used by MicroPather,
// and calling will free excess memory.
pather->Reset();
}
// Door
void ToggleDoor()
{
doorsOpen = !doorsOpen;
#ifdef USE_PATHER
pather->Reset();
#endif
}
// (nx, ny) (" " "D")
int Passable( int nx, int ny )
{
if ( nx >= 0 && nx < MAPX
&& ny >= 0 && ny < MAPY )
{
int index = ny * MAPX + nx;
char c = gMap[index];
if ( c == ' ' )
return 1;
else if ( c == 'D' )
return 2;
}
return 0;
}
// (nx,ny)
int SetPos( int nx, int ny )
{
int result = 0;
if ( Passable( nx, ny ) == 1 )
{
#ifdef USE_PATHER
float totalCost;
if ( showConsidered )
pather->Reset();
/*
int micropather::MicroPather::Solve
( void * startState,
void * endState,
std::vector< void * > * path,
float * totalCost
)
Solve for the path from start to end.
Parameters:
startState: Input, the starting state for the path.
endState: Input, the ending state for the path.
path: Output, a vector of states that define the path. Empty if not found.
totalCost: Output, the cost of the path, if found.
Returns:
Success or failure, expressed as SOLVED, NO_SOLUTION, or START_END_SAME.
*/
result = pather->Solve( XYToNode( playerX, playerY ), XYToNode( nx, ny ), &path, &totalCost );
if ( result == MicroPather::SOLVED )
{
// (nx, ny)
playerX = nx;
playerY = ny;
}
printf( "Pather returned %d
", result );
#else
playerX = nx;
playerY = ny;
#endif
}
return result;
}
void Print()
{
char buf[ MAPX + 1 ];
std::vector<void*> stateVec;
if ( showConsidered )
pather->StatesInPool(&stateVec);
printf(" doors %s
", doorsOpen ? "open" : "closed");
printf(" 0 10 20
");
printf(" 012345678901234567890123456789
");
for( int j=0; j<MAPY; ++j )
{
// ,
memcpy(buf, &gMap[MAPX * j], MAPX + 1);
buf[MAPX] = 0;
#ifdef USE_PATHER
unsigned k;
// Wildly inefficient demo code.
for( k=0; k<path.size(); ++k )
{
int x, y;
NodeToXY( path[k], &x, &y );
if ( y == j )
buf[x] = '0' + k % 10;
}
if ( showConsidered )
{
for( k=0; k<stateVec.size(); ++k )
{
int x, y;
NodeToXY( stateVec[k], &x, &y );
if ( y == j )
buf[x] = 'x';
}
}
#endif
// Insert the player
if ( j == playerY )
buf[playerX] = 'i';
printf( "%d%s
", j % 10, buf );
}
}
#ifdef USE_PATHER
void NodeToXY( void* node, int* x, int* y )
{
int index = (int)node;
*y = index / MAPX;
*x = index - *y * MAPX;
}
void* XYToNode( int x, int y )
{
return (void*) ( y*MAPX + x );
}
//
virtual float LeastCostEstimate( void* nodeStart, void* nodeEnd )
{
int xStart, yStart, xEnd, yEnd;
NodeToXY( nodeStart, &xStart, &yStart );
NodeToXY( nodeEnd, &xEnd, &yEnd );
/* Compute the minimum path cost using distance measurement. It is possible
to compute the exact minimum path using the fact that you can move only
on a straight line or on a diagonal, and this will yield a better result.
*/
int dx = xStart - xEnd;
int dy = yStart - yEnd;
return (float) sqrt( (double)(dx*dx) + (double)(dy*dy) );
}
// Return the exact cost from the given state to all its neighboring states.
// This may be called multiple times, or cached by the solver.
virtual void AdjacentCost( void* node, std::vector< StateCost > *neighbors )
{
int x, y;
// X,Y 8 E SE S SW W NW N NE
const int dx[8] = { 1, 1, 0, -1, -1, -1, 0, 1 };
const int dy[8] = { 0, 1, 1, 1, 0, -1, -1, -1 };
// X,Y 8
const float cost[8] = { 1.0f, 1.41f, 1.0f, 1.41f, 1.0f, 1.41f, 1.0f, 1.41f };
NodeToXY( node, &x, &y );
// 8 ; ; push vector
for( int i=0; i<8; ++i )
{
int nx = x + dx[i];
int ny = y + dy[i];
int pass = Passable( nx, ny );
if ( pass > 0 )
{
if ( pass == 1 || doorsOpen )
{
// Normal floor
StateCost nodeCost =
{
// (nx, ny)
XYToNode( nx, ny ),
// node
cost[i]
};
neighbors->push_back( nodeCost );
}
else
{
// pass FLT_MAX
StateCost nodeCost = { XYToNode( nx, ny ), FLT_MAX };
neighbors->push_back( nodeCost );
}
}
}
}
virtual void PrintStateInfo( void* node )
{
int x, y;
NodeToXY( node, &x, &y );
printf( "(%d,%d)", x, y );
}
#endif
};
int main( int /*argc*/, const char** /*argv*/ )
{
{
Dungeon test;
const int NUM_TEST = 5;
int tx[NUM_TEST] = { 24, 25, 10, 6, 0 }; // x of test
int ty[NUM_TEST] = { 9, 9, 5, 5, 0 }; // y of test
int door[NUM_TEST] = { 0, 0, 0, 1, 0 }; // toggle door? (before move)
unsigned check[NUM_TEST] = { 139640, 884, 0, 129313, 2914 };
for( int i=0; i<NUM_TEST; ++i )
{
// (25,9) (10, 5) , (10, 5) , 2 ,
// 2 , (25,9) (10, 5) !
// (10,5) (6,5) , ,
if ( door[i] )
test.ToggleDoor();
int _result = test.SetPos( tx[i], ty[i] );
if ( _result == MicroPather::SOLVED )
{
// Return the "checksum" of the last path returned by Solve().
// Useful for debugging, and a quick way to see if 2 paths are the same.
unsigned checkNum = test.Checksum();
if ( checkNum == check[i] )
printf( "Test %d to (%d,%d) ok
", i, tx[i], ty[i] );
else
printf( "Test %d to (%d,%d) BAD CHECKSUM
", i, tx[i], ty[i] );
}
else if (_result == MicroPather::NO_SOLUTION)
{
printf( "Test %d to (%d,%d) no solution
", i, tx[i], ty[i] );
}
else if (_result == MicroPather::START_END_SAME)
{
printf( "Test %d to (%d,%d) start end same
", i, tx[i], ty[i] );
}
}
}
Dungeon dungeon;
bool done = false;
char buf[ 256 ];
while ( !done )
{
dungeon.Print();
printf( "
# # to move, q to quit, r to redraw, d to toggle doors, t for touched
" );
std::cin.getline( buf, 256 );
if ( *buf )
{
if ( buf[0] == 'q' )
done = true;
else if ( buf[0] == 'd' )
{
dungeon.ToggleDoor();
dungeon.ClearPath();
}
else if ( buf[0] == 't' )
dungeon.ToggleTouched();
else if ( buf[0] == 'r' )
dungeon.ClearPath();
else if ( isdigit( buf[0] ) )
{
int x, y;
sscanf( buf, "%d %d", &x, &y ); // sleazy, I know
dungeon.SetPos( x, y );
}
}
else
dungeon.ClearPath();
}
return 0;
}