A*尋路演算法的lua實現,尋路演算法lua
前言:又好久沒寫blog了,感覺有點“頹廢”了,最近認識好多好多同齡人,也是大學剛畢業,覺得他們很優秀,認識到自己跟他們的差距,有點自愧不如。沒寫blog當然也有一部分原因是因為工作,本來經驗就有點欠缺,還要承擔起一個項目的壓力,原本國慶回去就要把這個尋路的功能改進一下,結果第一次去女朋友家了就沒碰電腦,回上海來的第一個夜晚滿腦子全是心事,早上淩晨四點就在床上輾轉睡不著了,這個月隨著項目的進行感覺壓力也越來越大,上班時期天天六點多就醒了睡不著,希望挺過這段適應期。關於尋路問題,在幾個月之前一次面試就碰到,面試官叫我上機寫出這個演算法,由於之前沒看過A*,只是有這個概念,所以只能憑著自己的思路來臨時編寫。最後憋出了一段後來主動跟面試官說名可以用A*演算法來實現,最後還是我的堅持打動了面試官。今天又碰到同樣的問題,也讓我糾結了一兩天,再次寫下尋路的學習心得。
一、問題概述遊戲中有敵我雙方,有四十個方格,當輪到我方武將行動的時候,要先顯示出我方武將可以行動的方位,這個就涉及到我方武將的行動力的大小來決定,預先做出路徑的預算。這裡還要考慮敵方以及地標(例如:炸彈、勢頭)的阻擋,以及特殊方格對武將行動力的消耗以及敵方的間隔阻擋規則。當碰到這個問題的時候,問老大選擇用什麼尋路演算法,他推薦的是Dijstra演算法,但我看了之後感覺還不是很適合我的需求,第一:我覺得Dijstra演算法是有向圖的最佳直接選取,節點之間路徑長度必須Crowdsourced Security Testing曉,但我這裡四十個方格如果要兩兩制定感覺稍微複雜,而且隨著武將的移動,地圖還是時時變化的,感覺更複雜。第二:Distra演算法沒有阻擋考慮,當然也可以記錄若干路徑然後考慮阻擋選擇最優路徑,我感覺也稍複雜。然而我的第一反應該需求A*演算法挺接近的,然後諮詢老大,老大說A*方格之間距離必須要均等不然比較複雜。然後再諮詢公司其他稍有經驗同事,當然各有個的說法,什麼深度、廣度遍曆,感覺沒一個確定的說法。讓我選擇就糾結了一天,不敢輕易選擇,如果稍有考慮不慎,說不定就要做幾天白用工,但最後我還是堅持自己的想法用A*來實現。二、關於A*
A*通用的計算公式F=G+HG:表示從起點A移動到網格上指定方格的移動耗費(我這裡沒考慮斜方向移動),也可理解為節點的權重H:表示從制定方格移動到終點B的預計耗費,這裡一般就是計算距離*係數三、尋路思想1.從起點A開始,把它添加到"開啟列表"2.尋找A點可到到的周圍四個點,這裡可到達是指沒有阻擋並且已經不再關閉列表中的節點,並把他們添加進開啟列表,並設定他們的父節點為A3.從開啟列表中刪除A點並添加進關閉列表中,關閉列表就是存放的不需要檢測的方格節點4.檢查它所有相鄰並且合一到達的方格,如果這些方格還不再開啟列表裡的話就把他們加入開啟列表,計算這些方格的GHF值並設定它的父節點5.如果某個相鄰方格 D 已經在 "開啟列表" 裡了, 檢查如果用新的路徑 (就是經過C 的路徑) 到達它的話, G值是否會更低一些, 如果新的G值更低, 那就把它的 "父方格" 改為目前選中的方格 C, 然後重新計算它的 F 度量和 G 值 (H 值不需要重新計算, 因為對於每個方塊, H 值是不變的). 如果新的 G 值比較高, 就說明經過 C 再到達 D 不是一個明智的選擇, 因為它需要更遠的路, 這時我們什麼也不做6.當發現開啟列表中有終點目標方格的時候則說明路徑已經找到。關於詳細的圖解可以參考其他網友的詳解,我這裡就不詳細寫了。四、找迴路徑
我們找到最後一個點的時候然後層層往之前找到他的父節點迭代到最後不為空白結束五、資料結構Point類
module('Point', package.seeall)-- require("script/battle/BattleCommon")--計算F值function CalcF( point )point.F = point.G + point.Hendfunction create( posId )local point = {}point.parentPoint = {}point.step = 1 --用於計算h值local x,y = BattleCommon.convertPosIdToCoord(posId)point.F = 0point.G = 0point.H = 0point.X = y --point.X範圍[1,5]point.Y = x --point.Y範圍[1,8]point.posId = posIdpoint.CalcF = CalcFreturn pointendreturn Point地形(Maze)結構
--根據一個table建立一個地形function create( tb )local maze = {}maze.step = 1 --格子與格子的基本距離,用於計算H值maze.mazeArray = tbmaze.openList = TabledArray.create() --開啟列表maze.closeList = TabledArray.create() --關閉列表maze.findPath = findPathreturn mazeend
六、主要代碼
module('Maze', package.seeall)require("script/battle/TabledArray")require("script/battle/BattleCommon")require ("script/battle/Point")-- --擷取列表中F值最小的點function getMinPoint( pointsList )local minPoint = pointsList.tbl[1]for i = 1,pointsList:getSize() doif minPoint.F > pointsList.tbl[i].F thenminPoint = pointsList.tbl[i]endendreturn minPointend-- --檢測是否有阻擋,沒有阻擋為truefunction checkBlock( maze,x,y,roleFlag) --x範圍[1,5] y範圍[1,8]if roleFlag == BattleCommon.BATTLE_GROUP_ALLIES then --我方陣營if maze.mazeArray[x][y][1] == 0 or maze.mazeArray[x][y][1] == 1 thenreturn true --沒有阻擋elsereturn false endelseif roleFlag == BattleCommon.BATTLE_GROUP_ENEMY thenif maze.mazeArray[x][y][1] == 0 or maze.mazeArray[x][y][1] == 2 thenreturn true --沒有阻擋elsereturn false endendend-- --列表中是否包含x,y的點function existsXY( list,x,y )if list:getSize()>0 thenfor i,point in pairs(list.tbl) doif point.X == x and point.Y == y thenreturn trueendendendreturn falseend--列表中是否包含某個點function existsPoint( list,point )for i, p in pairs(list.tbl) doif (p.X == point.X) and (p.Y == point.Y) thenreturn trueendendreturn falseend-- --檢測能達到的點function canReach( maze,startPoint,x,y,roleFlag)if (not checkBlock(maze,x,y,roleFlag)) or existsXY(maze.closeList,x,y) then --關閉列表中包含這個點或者有阻擋return falseelseif (math.abs(x-startPoint.X)+math.abs(y-startPoint.Y) == 1 ) thenreturn trueendendend-- --擷取相鄰的點function getSurroundPoints( maze,point,roleFlag )local surroundPoints = TabledArray.create()for i = point.X - 1 ,point.X + 1 dofor j=point.Y - 1,point.Y + 1 doif i>0 and i<6 and j > 0 and j < 9 then --排除超過表姐if BattleCommon.distanceFromTo(point.posId,BattleCommon.convertToPositionId(j-1,i-1)) < 2 thenif canReach(maze,point,i,j,roleFlag) thensurroundPoints:append(maze.mazeArray[i][j][2])endendendendendreturn surroundPoints --返回point點的集合end-- --計算G值function CalcG( point )local G = point.Glocal parentG = 0if point.parentPoint thenparentG = point.parentPoint.Gendreturn G + parentGendfunction foundPoint( tempStart,point )local G = CalcG(point)if G < point.G thenpoint.parentPoint = tempStartpoint.G = Gpoint:CalcF()endendfunction notFoundPoint( maze,tempStart,point )point.parentPoint = tempStartpoint.G = CalcG(point)point:CalcF()maze.openList:append(point)endfunction getPoint( list,data )for i,point in pairs(list.tbl) doif point.posId == data.posId thenreturn pointendendreturn nilend-- --尋找路徑(起始路徑)local function findPath( maze,startPoint,endPoint,roleFlag)maze.openList:append(startPoint)while maze.openList:getSize() ~= 0 do--找出F的最小值local tempStart = getMinPoint(maze.openList)maze.openList:removeById(1)maze.closeList:append(tempStart)--找出它相鄰的點local surroundPoints = getSurroundPoints(maze,tempStart,roleFlag)for i,point in pairs(surroundPoints.tbl) doif existsPoint(maze.openList,point) then--計算G值,如果比原來大,就什麼都不做,否則設定他的父節點為當前節點,並更新G和FfoundPoint(tempStart,point)else--如果他們不再開始列表裡面,就加入,並設定父節點,並計算GHFnotFoundPoint(maze,tempStart,point)endend--如果最後一個存在則返回if getPoint(maze.openList,endPoint) thenreturn getPoint(maze.openList,endPoint)endendreturn getPoint(maze.openList,endPoint)end--根據一個table建立一個地形function create( tb )local maze = {}maze.step = 1 --格子與格子的基本距離,用於計算H值maze.mazeArray = tbmaze.openList = TabledArray.create() --開啟列表maze.closeList = TabledArray.create() --關閉列表maze.findPath = findPathreturn mazeendreturn Maze
調用方法
function printPath( presentPoint )local pathArray = TabledArray.create()while presentPoint dopathArray:preppend(presentPoint.posId)presentPoint = presentPoint.parentPointendlocal startPoint = pathArray:get(2)local endPoint = pathArray:getLast()cclog(startPoint)cclog(endPoint)cclog("從"..startPoint.."到"..endPoint.."的路徑是:")for i,p in pairs(pathArray.tbl) docclog(p)endend
local array = battleBoard:createBoxPoints(cRole:getFlag(),40)local maze = Maze.create(array)local startPoint = Point.create(cRole:getPositionId())local endPoint = Point.create(40)local presentPoint = maze:findPath(startPoint,endPoint,cRole:getFlag())printPath(presentPoint)
七、運行效果
手機測試效果還可以,貌似在255*255規模的方格規模上採用A*還是不會有太大的效率影響。如果需要交流歡迎聯絡。
A* 尋路演算法
A*演算法
?? 啟發學習法搜尋
– 在搜尋中涉及到三個函數
?? g(n) = 從初始結點到結點n的耗費
?? h(n) = 從結點n到目的結點的耗費評估值,啟發函數
?? f(n)=g(n)+h(n) 從起始點到目的點的最佳評估值
– 每次都選擇f(n)值最小的結點作為下一個結點,
直到最終達到目的結點
– A*演算法的成功很大程度依賴於h(n)函數的構建
?? 在各種遊戲中廣泛應用 Open列表和Closed列表
– Open列表
?? 包含我們還沒有處理到的結點
?? 我們最開始將起始結點放入到Open列表中
– Closed列表
?? 包含我們已經處理過的結點
?? 在演算法啟動時,Closed列表為空白 A* 演算法虛擬碼初始化OPEN列表
初始化CLOSED列表
建立目的結點;稱為node_goal
建立起始結點;稱為node_start
將node_start添加到OPEN列表
while OPEN列表非空{
從OPEN列表中取出f(n)值最低的結點n
將結點n添加到CLOSED列表中
if 結點n與node_goal相等then 我們找到了路徑,程式返回n
else 產生結點n的每一個後繼結點n'
foreach 結點n的後繼結點n'{
將n’的父結點設定為n
計算啟發學習法評估函數h(n‘)值,評估從n‘到node_goal的費用
計算g(n‘) = g(n) + 從n’到n的開銷
計算f(n') = g(n') + h(n')
if n‘位於OPEN或者CLOSED列表and 現有f(n)較優then丟棄n’ ,處理後繼n’
將結點n‘從OPEN和CLOSED中刪除
添加結點n‘到OPEN列表
}
}
return failure (我們已經搜尋了所有的結點,但是仍然沒有找到一條路徑)
用C++實現A*尋路演算法代碼,要可以封裝成一個函數,函數原型是Cpoint* CGetPath(Cpoint start,Cpoint end
帶環K短路
const int maxn = 1001; //最大節點數
const int inf = 1<<20;
struct node //a星的節點
{
int v;
int len;
bool operator < (const node a) const
{
return len > a.len;
}
};
struct point //圖的節點
{
int v , w;
};
vector<point> g[maxn] ,reg[maxn]; //g為原圖鄰接表表示 reg為反圖臨界表表示
int dis[maxn] , visit[maxn] , out[maxn] , n , m , s ,t , K;
void init()
{
for(int i = 1;i<=n;i++)
{
g[i].clear(); reg[i].clear();
}
}
void dij()
{
memset(visit , 0 ,sizeof(visit));
for(int i = 1 ; i<=n ;i++)
dis[i] = inf;
dis[t] = 0;
while(1)
{
int temp = inf;
int p = -1;
for(int i = 1;i<=n;i++)
if(!visit[i]&&dis[i]<temp)
{
temp = dis[i];
p = i;
}
if(p==-1)
break;
visit[p] = 1;
for(int i = 0;i<reg[p].size();i++)
if(!visit[reg[p][i].v]&&dis[reg[p][i].v]>dis[p]+reg[p][i].w)
dis[reg[p][i].v] = dis[p] + reg[p][i].w;
}
}
int astar()
{
dij();
memset(out,0,sizeof(out)); //記錄節點出隊列的次數 out[t]的值為第i短路標記
priority_queue<node> q;
node temp;
temp.v = s; temp.len = dis[s];
q.push(temp);
while(!q.empty()&&out[t]<K)
{
temp = q.top();
q.pop();
if(out[temp.v]>=K) continue;
out[temp.v]++;
if(out[t]==K && temp.v==t)
return temp.len;
for(int i = 0;i<g[temp.v].size();i++......餘下全文>>