用C++編寫簡易尋路算法

一,說在前面的話

大概在半年前,看見一到信息競賽題:在任意方格陣中設置障礙物,確定起始點後,求這兩點之間路徑。當時覺得蠻有意思的,但是沒有時間去做,今天花了兩個小時來實現它。據說有一個更高級的尋路算法叫做A*,可以啓發式尋路。

此算法主要用於解迷宮和實現戰棋遊戲(SLG)的尋路。


首先講一講我這個簡易算法的思路:

我們先確定起始點,然後從起點出發,按一定順序判斷這個位置上下左右是否有可走的位置,如果發現有可走的位置,則遞歸進入該位置的判斷。在遞歸的同時記錄所走的路線。當發現某個位置無路可走,則刪除路線的最後一個位置並返回上級位置進行判斷。如此反覆嘗試最終找到路線。



說了這麼多,就來講解一下代碼吧。


二,講解部分

包含頭文件(全部都是stl中的):

#include <map>
#include <vector>
#include <iostream>

爲幾個冗長的類型重命名,用來使後來的代碼更明瞭。

typedef		unsigned int				uint;
typedef		std::vector<int>			CRow;
//相當於把CLabyrinth定義成一個整型的二維數組
typedef		std::vector<CRow>			CLabyrinth;
定義一個類類型表示二維數組中的位置:

class CPoint
{

public:

	int	col;			//列
	int	row;			//行

public:

	//構造函數,接受行和列的初始化
	CPoint(int c = 0, int r = 0)
		: col(c)
		, row(r)
	{
		return;
	}

	//賦值操作
	CPoint& operator=(const CPoint& pt)
	{
		col = pt.col;
		row = pt.row;
		return *this;
	}

	//比較操作
	bool operator==(const CPoint& pt)
	{
		return col == pt.col && row == pt.row;
	}

	//判斷該位置是否合法
	bool allRight()
	{
		return col >= 0 && row >= 0;
	}

};

typedef		std::vector<CPoint>			CRoute;

然後到了核心類類型CLabyrinthAI

{

protected:

	//裝有迷宮數據的二維數組
	CLabyrinth		m_xLabyrinth;
	//起點位置
	CPoint			m_ptBeginning;
	//終點位置
	CPoint			m_ptEnding;
	//記錄路線的數組
	CRoute			m_vRoute;

public:

	//枚舉表示起點、終點的值
	enum{Beginning = -1, Ending = -2};
	//枚舉表示障礙物與可走區的值
	enum{CanntGo = 0, CanGo = 1};
	//枚舉是否找到終點
	enum{FoundEnding = 0, NotFoundEnding = 1};

protected:

	//判斷某個位置是否已在路線數組中,用於別走重複的路
	bool isRepeat(const CPoint& pt)
	{
		bool bRes = false;
		CRoute::iterator it = m_vRoute.begin();
		for(; it != m_vRoute.end(); it++){
			CPoint pt0 = *it;
			if(pt0 == pt){
				bRes = true;
				break;
			}
		}
		return bRes;
	}

	//將某一位置加入路線數組
	void advance(const CPoint& ptTo)
	{
		m_vRoute.push_back(ptTo);
	}

	//將路線數組最後一個位置彈出
	void back()
	{
		m_vRoute.pop_back();
	}

	//判斷某一位置是否是起點
	bool isBeginning(const CPoint& pt)
	{
		return m_ptBeginning == pt;
	}

	//判斷某一位置是否是終點
	bool isEnding(const CPoint& pt)
	{
		return m_ptEnding == pt;
	}

/*-----------------核心算法------------------------*/
	//判斷某一位置是否可以向上移動
	CPoint canUp(const CPoint& ptCurrent)	//接受當前位置
	{
		CPoint ptRes = CPoint(-1, -1);
		int col = ptCurrent.col;
		int row = ptCurrent.row;
		if(row > 0){
			CPoint ptNext = CPoint(col, row - 1);	//上移後位置
			//檢查上移後位置是否已經走過,以免尋路過程中繞圈子進入死循環
			if(!isRepeat(ptNext)){
				//獲得迷宮二維數組中上移後位置的屬性(起點、終點、可走、障礙)
				int nAttr = m_xLabyrinth[ptNext.row][ptNext.col];
				//如果上移後位置爲可走或到達終點,則設定返回值爲上移後的位置
				if(nAttr == CanGo || nAttr == Ending){
					ptRes = ptNext;
				}
			}
		}
		return ptRes;	//如果上移後位置不可走則返回非法的位置
	}

	//以下判斷某一位置可否移動的原理大致與上相同,就不多說了

	//判斷某一位置是否可以向下移動
	CPoint canDown(const CPoint& ptCurrent)
	{
		CPoint ptRes = CPoint(-1, -1);
		int col = ptCurrent.col;
		int row = ptCurrent.row;
		if(row < m_xLabyrinth.size() - 1){
			CPoint ptNext = CPoint(col, row + 1);
			if(!isRepeat(ptNext)){
				int nAttr = m_xLabyrinth[ptNext.row][ptNext.col];
				if(nAttr == CanGo || nAttr == Ending){
					ptRes = ptNext;
				}
			}
		}
		return ptRes;
	}

	//判斷某一位置是否可以向左移動
	CPoint canLeft(const CPoint& ptCurrent)
	{
		CPoint ptRes = CPoint(-1, -1);
		int col = ptCurrent.col;
		int row = ptCurrent.row;
		if(col > 0){
			CPoint ptNext = CPoint(col - 1, row);
			if(!isRepeat(ptNext)){
				int nAttr = m_xLabyrinth[ptNext.row][ptNext.col];
				if(nAttr == CanGo || nAttr == Ending){
					ptRes = ptNext;
				}
			}
		}
		return ptRes;
	}

	//判斷某一位置是否可以向右移動
	CPoint canRight(const CPoint& ptCurrent)
	{
		CPoint ptRes = CPoint(-1, -1);
		int col = ptCurrent.col;
		int row = ptCurrent.row;
		if(col < m_xLabyrinth[0].size() - 1){
			CPoint ptNext = CPoint(col + 1, row);
			if(!isRepeat(ptNext)){
				int nAttr = m_xLabyrinth[ptNext.row][ptNext.col];
				if(nAttr == CanGo || nAttr == Ending){
					ptRes = ptNext;
				}
			}
		}
		return ptRes;
	}

/*
*判斷某一位置是否可以向四周移動,如果判斷到某一位置可以移動,則遞歸進入該位置判斷。
*如果該位置沒有任何位置可移動,則返會上級位置並且調用back函數。如果走到終點,
*則立刻返回枚舉值FoundEnding,上級位置檢查到返回值爲FoundEnding,也直接返回。
*/
	int findRoute(const CPoint& ptCurrent)
	{
		int nRes = NotFoundEnding;		//默認返回值爲沒有找到終點
		CPoint ptNext = CPoint(-1, -1);

		advance(ptCurrent);			//將當前位置加入路線數組

		//判斷當前位置是否是終點,如果是終點則不進行下面的判斷,將返回值設置爲找到終點
		if(isEnding(ptCurrent)){
			nRes = FoundEnding;
		}else{					//按上左下右的順序判斷有無可走路徑
			//嘗試向上
			ptNext = canUp(ptCurrent);	//獲取向上走後的位置
			//判斷向上走後的位置是否是合法位置,若不合法,則表明上走到了迷宮的邊緣,或者上面沒有可走路徑
			if(ptNext.allRight()){
				//上述判斷成功,則將向上移動後的位置傳入給自己,進行遞歸。當該函數退出,查看返回值是否爲找到終點。若找到終點則立刻返回FoundEnding
				if(findRoute(ptNext) == FoundEnding){
					nRes = FoundEnding;
					return nRes;
				}
			}
//下列嘗試四周位置是否可走的代碼與上述大體相同,就不多說了
			//嘗試向左
			ptNext = canLeft(ptCurrent);
			if(ptNext.allRight()){
				if(findRoute(ptNext) == FoundEnding){
					nRes = FoundEnding;
					return nRes;
				}
			}
			//嘗試向下
			ptNext = canDown(ptCurrent);
			if(ptNext.allRight()){
				if(findRoute(ptNext) == FoundEnding){
					nRes = FoundEnding;
					return nRes;
				}
			}
			//嘗試向右
			ptNext = canRight(ptCurrent);
			if(ptNext.allRight()){
				if(findRoute(ptNext) == FoundEnding){
					nRes = FoundEnding;
					return nRes;
				}
			}
		}

		//檢測是否到達終點,若沒有到達終點,則立刻從路線表中刪除該位置
		if(nRes != FoundEnding){
			back();
		}

		return nRes;
	}
/*-----------------核心算法------------------------*/

public:

	//構造函數
	CLabyrinthAI()
	{
		return;
	}

	//帶有初始化迷宮數組構造函數
	CLabyrinthAI(const CLabyrinth& vLabyrinth)
	{
		m_xLabyrinth = vLabyrinth;
		getBeginning();
		getEnding();
	}

	//初始化迷宮數組
	void setLabyrinth(const CLabyrinth& vLabyrinth)
	{
		m_xLabyrinth = vLabyrinth;
	}

	//查找起點
	void getBeginning()
	{
		uint nRow = 0;
		for(; nRow < m_xLabyrinth.size(); nRow++){
			CRow xRow = m_xLabyrinth[nRow];
			uint nCol = 0;
			for(; nCol < xRow.size(); nCol++){
				int n = xRow[nCol];
				if(n == Beginning){
					m_ptBeginning = CPoint(nCol, nRow);
					break;
				}
			}
		}
	}

	//查找終點
	void getEnding()
	{
		uint nRow = 0;
		for(; nRow < m_xLabyrinth.size(); nRow++){
			CRow xRow = m_xLabyrinth[nRow];
			uint nCol = 0;
			for(; nCol < xRow.size(); nCol++){
				int n = xRow[nCol];
				if(n == Ending){
					m_ptEnding = CPoint(nCol, nRow);
					break;
				}
			}
		}
	}

	//調用核心算法函數,輸出獲得的路線
	void AI()
	{
		findRoute(m_ptBeginning);
		if(!m_vRoute.empty()){
			CRoute::iterator it = m_vRoute.begin();
			for(; it != m_vRoute.end(); it++){
				CPoint pt = *it;
				std::cout << "(" << pt.row << ", " << pt.col << ")";
				if(it != m_vRoute.end() - 1){
					std::cout << "->";
				}else{
					std::cout << std::endl;
				}
			}
		}else{
			//如果沒有找到路線到達終點
			std::cout << "Sorry cannot file any ways to get ending." << std::endl;
		}
	}

};
代碼都加上了註釋,大家可以慢慢看。
如果上述過程把你攪暈了,那就用圖來爲你解答吧。

然後來到main函數

//用VC 6.0貌似不需要給main傳參數,那我就偷一下懶
int main()
{
	//定義迷宮數組,定義成C風格的二維數組方便查看
	int vLabyrinthArray[][4] = {
		{1,0,-1,1}
		, {1,0,0,1}
		, {0,0,1,1}
		, {0,1,1,0}
		, {0,1,1,1}
		, {-2,1,0,0}
	};

	//以下代碼爲將C風格的二維數組導入成C++風格的二維數組
	int nRowNum = sizeof(vLabyrinthArray) / sizeof(vLabyrinthArray[0]);
	int nColNum = sizeof(vLabyrinthArray[0]) / sizeof(int);

	CLabyrinth vLabyrinth;
	for(int row = 0; row < nRowNum; row++){
		CRow xRow;
		for(int col = 0; col < nColNum; col++){
			int n = vLabyrinthArray[row][col];
			xRow.push_back(n);
		}
		vLabyrinth.push_back(xRow);
	}

	//實例化CLabyrinthAI
	CLabyrinthAI xAI(vLabyrinth);
	//打出路線
	xAI.AI();

	//使程序暫停,方便查看數據
	system("Pause");

	return 0;
}

以上代碼同樣加了註釋,相信瞭解C++的同學都能看懂。

運行截圖:


(Dos的,有點醜……尷尬

三,Javascript版

順便我也把C++版的移植到了Javascript上,代碼如下:

function CLabyrinthAI(){
	var s = this;
	s.m_xLabyrinth = new Array(new Array());
	s.m_ptBeginning = {};
	s.m_ptEnding = {};
	s.m_vRoute = new Array();
	s.Beginning = -1;
	s.Ending = -2;
	s.CannotGo = 0;
	s.CanGo = 1;
	s.FoundEnding = 0;
	s.NotFoundEnding = 1;
}
CLabyrinthAI.prototype.initAI = function(){
	var s = this;
	s.getBeginning();
	s.getEnding();
}
CLabyrinthAI.prototype.isRepeat = function(pt){
	var s = this;
	var bRes = false;
	for(var n = 0; n < s.m_vRoute.length; n++){
		var pt0 = s.m_vRoute[n];
		if(pt0.col == pt.col && pt0.row == pt.row){
			bRes = true;
			break;
		}
	}
	return bRes;
};
CLabyrinthAI.prototype.advance = function(ptTo){
	this.m_vRoute.push(ptTo);
};
CLabyrinthAI.prototype.back = function(){
	this.m_vRoute.splice(this.m_vRoute.length-1,1);
};
CLabyrinthAI.prototype.isBeginning = function(pt){
	if(this.m_ptBeginning.col == pt.col && this.m_ptBeginning.row == pt.row){
		return true;
	}else{
		return false;
	}
};
CLabyrinthAI.prototype.isEnding = function(pt){
	if(this.m_ptEnding.col == pt.col && this.m_ptEnding.row == pt.row){
		return true;
	}else{
		return false;
	}
};
CLabyrinthAI.prototype.canUp = function(ptCurrent){
	var s = this;
	var ptRes = {col:-1,row:-1};
	var col = ptCurrent.col;
	var row = ptCurrent.row;
	if(row > 0){
		var ptNext = {col:col,row:row - 1};
		if(!s.isRepeat(ptNext)){
			var nAttr = s.m_xLabyrinth[ptNext.row][ptNext.col];
			if(nAttr == s.CanGo || nAttr == s.Ending){
				ptRes = ptNext;
			}
		}
	}
	return ptRes;
};
CLabyrinthAI.prototype.canDown = function(ptCurrent){
	var s = this;
	var ptRes = {col:-1,row:-1};
	var col = ptCurrent.col;
	var row = ptCurrent.row;
	if(row < s.m_xLabyrinth.length - 1){
		var ptNext = {col:col,row:row + 1};
		if(!s.isRepeat(ptNext)){
			var nAttr = s.m_xLabyrinth[ptNext.row][ptNext.col];
			if(nAttr == s.CanGo || nAttr == s.Ending){
				ptRes = ptNext;
			}
		}
	}
	return ptRes;
};
CLabyrinthAI.prototype.canLeft = function(ptCurrent){
	var s = this;
	var ptRes = {col:-1,row:-1};
	var col = ptCurrent.col;
	var row = ptCurrent.row;
	if(col > 0){
		var ptNext = {col:col-1,row:row};
		if(!s.isRepeat(ptNext)){
			var nAttr = s.m_xLabyrinth[ptNext.row][ptNext.col];
			if(nAttr == s.CanGo || nAttr == s.Ending){
				ptRes = ptNext;
			}
		}
	}
	return ptRes;
};
CLabyrinthAI.prototype.canRight = function(ptCurrent){
	var s = this;
	var ptRes = {col:-1,row:-1};
	var col = ptCurrent.col;
	var row = ptCurrent.row;
	if(col < s.m_xLabyrinth[0].length - 1){
		var ptNext = {col:col+1,row:row};
		if(!s.isRepeat(ptNext)){
			var nAttr = s.m_xLabyrinth[ptNext.row][ptNext.col];
			if(nAttr == s.CanGo || nAttr == s.Ending){
				ptRes = ptNext;
			}
		}
	}
	return ptRes;
};
CLabyrinthAI.prototype.allRight = function(p){
	if(p.col >= 0 && p.row >= 0){
		return true;
	}else{
		return false;
	}
};
CLabyrinthAI.prototype.findRoute = function(ptCurrent){
	var s = this;
	var nRes = s.NotFoundEnding;
	var ptNext = {col:-1,row:-1};

	s.advance(ptCurrent);
	
	if(s.isEnding(ptCurrent)){
		nRes = s.FoundEnding;
	}else{
		ptNext = s.canUp(ptCurrent);
		if(s.allRight(ptNext)){
			if(s.findRoute(ptNext) == s.FoundEnding){
				nRes = s.FoundEnding;
				return nRes;
			}
		}
		
		ptNext = s.canLeft(ptCurrent);
		if(s.allRight(ptNext)){
			if(s.findRoute(ptNext) == s.FoundEnding){
				nRes = s.FoundEnding;
				return nRes;
			}
		}
		
		ptNext = s.canDown(ptCurrent);
		if(s.allRight(ptNext)){
			if(s.findRoute(ptNext) == s.FoundEnding){
				nRes = s.FoundEnding;
				return nRes;
			}
		}
		
		ptNext = s.canRight(ptCurrent);
		if(s.allRight(ptNext)){
			if(s.findRoute(ptNext) == s.FoundEnding){
				nRes = s.FoundEnding;
				return nRes;
			}
		}
	}
	if(nRes != s.FoundEnding){
		s.back();
	}
	
	return nRes;
};
CLabyrinthAI.prototype.getBeginning = function(){
	var s = this;
	for(var nRow = 0; nRow < s.m_xLabyrinth.length; nRow++){
		var xRow = s.m_xLabyrinth[nRow];
		for(var nCol = 0; nCol < xRow.length; nCol++){
			var n = xRow[nCol];
			if(n == s.Beginning){
				s.m_ptBeginning = {col:nCol,row:nRow};
				break;
			}
		}
	}
};
CLabyrinthAI.prototype.getEnding = function(){
	var s = this;
	for(var nRow = 0; nRow < s.m_xLabyrinth.length; nRow++){
		var xRow = s.m_xLabyrinth[nRow];
		for(var nCol = 0; nCol < xRow.length; nCol++){
			var n = xRow[nCol];
			if(n == s.Ending){
				s.m_ptEnding = {col:nCol,row:nRow};
				break;
			}
		}
	}
};
CLabyrinthAI.prototype.AI = function(data){
	var s = this;
	s.m_xLabyrinth = data;
	s.initAI();
	s.findRoute(s.m_ptBeginning);
	return s.m_vRoute;
};
設計原理和C++版差不多,只是沒有CPoint類而已。

雖然這套算法是研究出來了,但是還不能判斷是否爲最近路線,因此有待更新。不過以現在的算法,開發一個SLG應該不是問題了。

※感謝我的哥哥與我一起討論其中的原理。

源代碼下載:

http://files.cnblogs.com/yorhom/findRoute.rar


謝謝大家閱讀本文,支持就是最大的鼓勵。

----------------------------------------------------------------

歡迎大家轉載我的文章。

轉載請註明:轉自Yorhom's Game Box

http://blog.csdn.net/yorhomwang

歡迎繼續關注我的博客

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章