micropather實現A*演算法

weixin_34015860發表於2019-01-08

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:

  1. An easy API
  2. No requirements on the host program to change its data structures or objects
  3. No library or 'make' - just add 1 .cpp and 1 .h file to your project.

Enjoy, and thanks for checking out MicroPather!


官方網站可以下載其sdk和demo。

demo1的程式碼如下:

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\n", 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\n", doorsOpen ? "open" : "closed");
		printf(" 0         10        20\n");
		printf(" 012345678901234567890123456789\n");
		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\n", 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\n", i, tx[i], ty[i] );
				else
					printf( "Test %d to (%d,%d) BAD CHECKSUM\n", i, tx[i], ty[i] );
			}
			else if (_result == MicroPather::NO_SOLUTION)
			{
				printf( "Test %d to (%d,%d) no solution\n", i, tx[i], ty[i] );
			}
			else if (_result == MicroPather::START_END_SAME)
			{
				printf( "Test %d to (%d,%d) start end same\n", i, tx[i], ty[i] );
			}
		}
	}

	Dungeon dungeon;
	bool done = false;
	char buf[ 256 ];
	while ( !done ) 
	{
		dungeon.Print();

		printf( "\n# # to move, q to quit, r to redraw, d to toggle doors, t for touched\n" );
		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;
}

 

  


相關文章