在游戲開發中,又一個很常見的需求,就是讓一角色從A點走到B點,而我們期望所走的路是最短的,最容易想到的就是兩點之間直線最短,我們可以通過勾股定理來求出兩點之間的距離,但這個情況只能用於兩點之間沒有障礙物的情況,如果兩點之間有很多不可避免無法穿過的障礙物的時候,怎么辦呢?因此,我們的需求就是:計算出兩點之間的最短路徑,而且能夠避開所有的障礙物
A-star javascript實現
第一種情況:
在這種情況下,所走路徑方向朝上
第二種情況:
當我們把上方的障礙物增多的時候,選擇走的路徑就往下走
第三種情況:
在障礙物中間打開一個口子,那么所選擇的路徑就會之間從中間穿過
前置知識,百度百科:
啟發式搜索:啟發式搜索(Heuristically Search)又稱為有信息搜索(Informed Search),它是利用問題擁有的啟發信息來引導搜索,達到減少搜索范圍、降低問題復雜度的目的,這種利用啟發信息的搜索過程稱為啟發式搜索。
A-Star(A*)算法的核心:將游戲背景分成一個又一個的格子,每個格子計算出一個估值,然后遍歷起點的格子去找格子周圍估值最小的節點作為下一步要走的路徑,然后遞歸遍歷,直到找到目標點
實現算法的關鍵點
1 將游戲背景划分成大小一樣的正方形格子 ,我們把這些格子作為算法的基本單元,大小自定義
2 創建open隊列(也就是一個數組),這個隊列里面放可能會走的單元格
3 創建close隊列(也是一個數組),這個隊列里面放不能走的單元格,這里會包括已經走過的單元格和所有的障礙物
4 對所有的單元格進行估值
5 找到當前單元格周圍的單元格,並且從這些單元格種找到估值最小的單元格
6 給每個走過的單元格加指針,記錄所走過的路徑,以便於打印最終路線
估價函數
在A*算法種用到的就是啟發式搜索,而啟發式搜索是利用擁有問題的啟發信息來引導搜索的,所以我們需要把當前單元格周圍的點都找出來進行估值,這個估值表示當前點到目標點的實際代價,如果到目標點的實際代價越高,那么說明要走的路就越長,因此,我們需要找到估值最低的作為下一步要走的路徑
估價函數公式:fn(n) = g(n)+h(n)
其中:fn(n) 表示節點n的估值,g(n) 表示初始點到當前節點的實際代價,h(n)表示當前節點到目標點的實際代價,這里的實際代價就是這他們之間的最短距離
具體代碼實現:
1 完成ui布局,這一步我們動態創建出一個 20 * 20 的正方形地圖,每個格子大小也為20
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> <style type="text/css"> #ul1{ margin: 30px auto; border: 1px solid black; border-bottom: none; border-right:none ; padding: 0; height: auto; overflow: hidden; } #ul1 li{ list-style: none; border: 1px solid black; border-top:none ; border-left:none ; float: left; } #ul1 li.style1{ background-color: red; } #ul1 li.style2{ background-color: black; } #ul1 li.style3{ background-color: orange; } #btn{ position: absolute; left: 50%; margin-left: -50px; } #btn:hover{ background-color: #E21918; color: white; border-radius: 4px; } </style> </head> <body> <ul id="ul1"> </ul> <input id="btn" type="button" value="開始尋路"/> <script type="text/javascript"> //1 找對象 var oUl = document.getElementById("ul1"); var aLi = document.getElementsByTagName("li"); var oBtn = document.getElementById("btn") //創建一個數組來表示地圖,這個地圖 20 * 20 其中 值為1 表示起始節點 值為2 表示障礙物 值為3 表示目標節點 var map = [ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,2,2,2,2,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,1,0,0,2,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0, 0,0,0,2,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ] /* * 定義初始化函數 */ function init(){ //在這個函數中需要動態創建出li 通過調用封裝好的函數實現 createMap() } //調用初始化函數 init(); /* * 定義創建地圖函數 */ function createMap(){ //定義li的大小 var liSize = 20; for(var i=0;i<map.length;i++){ //創建元素 var oLi = document.createElement("li"); //設置寬高 oLi.style.width = liSize +"px"; oLi.style.height = liSize + "px"; //添加到li中 oUl.appendChild(oLi); //判斷map元素的值來設置起始點以及障礙物 if(map[i]==1){ //如果map值為1 設置樣式1 背景為紅色 表示起始點 oLi.className = "style1"; }else if(map[i]==2){ //如果map值為2 設置樣式2 背景為黑色 表示障礙物 oLi.className = "style2"; }else if(map[i]==3){ //如果map值為3 設置樣式3 背景為橙色 表示結束點 oLi.className = "style3" } } oUl.style.width = 20*(liSize+1)+1+"px" } </script> </body> </html>
2 估價函數封裝
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> <style type="text/css"> #ul1{ margin: 30px auto; border: 1px solid black; border-bottom: none; border-right:none ; padding: 0; height: auto; overflow: hidden; } #ul1 li{ list-style: none; border: 1px solid black; border-top:none ; border-left:none ; float: left; } #ul1 li.style1{ background-color: red; } #ul1 li.style2{ background-color: black; } #ul1 li.style3{ background-color: orange; } #btn{ position: absolute; left: 50%; margin-left: -50px; } #btn:hover{ background-color: #E21918; color: white; border-radius: 4px; } </style> </head> <body> <ul id="ul1"> </ul> <input id="btn" type="button" value="開始尋路"/> <script type="text/javascript"> //1 找對象 var oUl = document.getElementById("ul1"); var aLi = document.getElementsByTagName("li"); var oBtn = document.getElementById("btn"); //找到起始點,因為我們給起始點設置了不同的樣式 所以可以通過樣式可以找到起始點 var beginLi = document.getElementsByClassName("style1"); var endLi = document.getElementsByClassName("style3"); //創建一個數組來表示地圖,這個地圖 20 * 20 其中 值為1 表示起始節點 值為2 表示障礙物 值為3 表示目標節點 var map = [ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,2,2,2,2,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,1,0,0,2,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0, 0,0,0,2,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ] /* * 定義初始化函數 */ function init(){ //在這個函數中需要動態創建出li 通過調用封裝好的函數實現 createMap() } //調用初始化函數 init(); /* * 定義創建地圖函數 */ function createMap(){ //定義li的大小 var liSize = 20; for(var i=0;i<map.length;i++){ //創建元素 var oLi = document.createElement("li"); //設置寬高 oLi.style.width = liSize +"px"; oLi.style.height = liSize + "px"; //添加到li中 oUl.appendChild(oLi); //判斷map元素的值來設置起始點以及障礙物 if(map[i]==1){ //如果map值為1 設置樣式1 背景為紅色 表示起始點 oLi.className = "style1"; }else if(map[i]==2){ //如果map值為2 設置樣式2 背景為黑色 表示障礙物 oLi.className = "style2"; }else if(map[i]==3){ //如果map值為3 設置樣式3 背景為橙色 表示結束點 oLi.className = "style3" } } //ul的寬帶等於 ul的左邊 1 + 20個節點的寬帶 20*(liSize+1) 其中 liSize+1 是因為 節點有1個像素的右邊框 oUl.style.width = 20*(liSize+1)+1+"px" } /** * 估價函數封裝,傳入一個節點給他一個估值 */ function fn(nowLi){ return g(nowLi)+h(nowLi) } //初始點到當前節點的實際代價 function g(nowLi){ //勾股定理 //橫坐標的差值 beginLi[0] 開始節點 nowLi表示當前正在被估值的節點 var a = nowLi.offsetLeft-beginLi[0].offsetLeft; //縱坐標的差值 var b = nowLi.offsetTop - beginLi[0].offsetTop; //勾股定理 開方得到兩點之間最短距離 return Math.sqrt(a*a+b*b) } //當前節點到目標點的實際代價 function h(nowLi){ //勾股定理,這里和g函數一樣的原理 endLi[0] 結束節點 nowLi表示當前正在被估值的節點 var a = nowLi.offsetLeft-endLi[0].offsetLeft; var b = nowLi.offsetTop - endLi[0].offsetTop; return Math.sqrt(a*a+b*b) } </script> </body> </html>
3 定義開始隊列和結束隊列數組 並且封裝函數實現節點添加
/* * 封裝函數 實現開始隊列中元素的添加 */ function openFn(){ //1 需要把openArr中的第一個元素刪除 並且返回給 nodeLi變量 var nodeLi = openArr.shift(); //2 把openArr中刪除的這個li節點 添加到closeArr中 這里調用closeFn函數實現 closeFn(nodeLi); } /* * 封裝函數 實現結束隊列中元素的添加 * */ function closeFn(nodeLi){ //open隊列中刪除的元素 被 push到close隊列中 closeArr.push(nodeLi); }
疑問?1openFn 什么時候執行? openArr 中什么時候有數據? 所以需要添加以下代碼:
1) 在初始化函數中添加按鈕的點擊事件
//初始化函數 function init(){ createMap() //點擊按鈕的時候 需要去收集可能走的路線 oBtn.onclick = function(){ openFn(); } }
2) 在創建地圖的時候,就要區分出 起始點和障礙物 並且把起點放在open隊列數組中,把障礙物和放在close隊列數組中
function createMap(){ //定義li的大小 var liSize = 20; for(var i=0;i<map.length;i++){ //創建元素 var oLi = document.createElement("li"); //設置寬高 oLi.style.width = liSize +"px"; oLi.style.height = liSize + "px"; //添加到li中 oUl.appendChild(oLi); //判斷map元素的值來設置起始點以及障礙物 if(map[i]==1){ //如果map值為1 設置樣式1 背景為紅色 表示起始點 oLi.className = "style1"; //當元素剛開始創建的時候,open隊列中的元素只有 起始節點 也就是說將紅色點都放到open隊列中 並且 剛開始的時候 起始點只有一個 openArr.push(oLi); //這里是新增加的代碼 }else if(map[i]==2){ //如果map值為2 設置樣式2 背景為黑色 表示障礙物 oLi.className = "style2"; //把黑色的點都放到close隊列中 這些作為障礙物 是不會走的 closeArr.push(oLi); //這里是新增加的代碼 }else if(map[i]==3){ //如果map值為3 設置樣式3 背景為橙色 表示結束點 oLi.className = "style3" } } //ul的寬帶等於 ul的左邊 1 + 20個節點的寬帶 20*(liSize+1) 其中 liSize+1 是因為 節點有1個像素的右邊框 oUl.style.width = 20*(liSize+1)+1+"px" }
經過上面兩步,完整代碼如下:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> <style type="text/css"> #ul1{ margin: 30px auto; border: 1px solid black; border-bottom: none; border-right:none ; padding: 0; height: auto; overflow: hidden; } #ul1 li{ list-style: none; border: 1px solid black; border-top:none ; border-left:none ; float: left; } #ul1 li.style1{ background-color: red; } #ul1 li.style2{ background-color: black; } #ul1 li.style3{ background-color: orange; } #btn{ position: absolute; left: 50%; margin-left: -50px; } #btn:hover{ background-color: #E21918; color: white; border-radius: 4px; } </style> </head> <body> <ul id="ul1"> </ul> <input id="btn" type="button" value="開始尋路"/> <script type="text/javascript"> //1 找對象 var oUl = document.getElementById("ul1"); var aLi = document.getElementsByTagName("li"); var oBtn = document.getElementById("btn"); //找到起始點,因為我們給起始點設置了不同的樣式 所以可以通過樣式可以找到起始點 var beginLi = document.getElementsByClassName("style1"); var endLi = document.getElementsByClassName("style3"); //定義開始隊列數組 open隊列: 收集可能會需要走的路線 要走的路線放在open隊列中 var openArr = [] //定義結束隊列數組 close隊列: 排除掉不能走的路線 不走的路線放在close隊列中 var closeArr = [] //創建一個數組來表示地圖,這個地圖 20 * 20 其中 值為1 表示起始節點 值為2 表示障礙物 值為3 表示目標節點 var map = [ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,2,2,2,2,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,1,0,0,2,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0, 0,0,0,2,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ] /* * 定義初始化函數 */ function init(){ //在這個函數中需要動態創建出li 通過調用封裝好的函數實現 createMap() } //調用初始化函數 init(); /* * 定義創建地圖函數 */ function createMap(){ //定義li的大小 var liSize = 20; for(var i=0;i<map.length;i++){ //創建元素 var oLi = document.createElement("li"); //設置寬高 oLi.style.width = liSize +"px"; oLi.style.height = liSize + "px"; //添加到li中 oUl.appendChild(oLi); //判斷map元素的值來設置起始點以及障礙物 if(map[i]==1){ //如果map值為1 設置樣式1 背景為紅色 表示起始點 oLi.className = "style1"; //當元素剛開始創建的時候,open隊列中的元素只有 起始節點 也就是說將紅色點都放到open隊列中 並且 剛開始的時候 起始點只有一個 openArr.push(oLi); }else if(map[i]==2){ //如果map值為2 設置樣式2 背景為黑色 表示障礙物 oLi.className = "style2"; //把黑色的點都放到close隊列中 這些作為障礙物 是不會走的 closeArr.push(oLi); }else if(map[i]==3){ //如果map值為3 設置樣式3 背景為橙色 表示結束點 oLi.className = "style3" } } oUl.style.width = 20*(liSize+1)+1+"px" } /** * 估價函數封裝,傳入一個節點給他一個估值 */ function fn(nowLi){ return g(nowLi)+h(nowLi) } //初始點到當前節點的實際代價 function g(nowLi){ //勾股定理 //橫坐標的差值 beginLi[0] 開始節點 nowLi表示當前正在被估值的節點 var a = nowLi.offsetLeft-beginLi[0].offsetLeft; //縱坐標的差值 var b = nowLi.offsetTop - beginLi[0].offsetTop; //勾股定理 開方得到兩點之間最短距離 return Math.sqrt(a*a+b*b) } //當前節點到目標點的實際代價 function h(nowLi){ //勾股定理,這里和g函數一樣的原理 endLi[0] 結束節點 nowLi表示當前正在被估值的節點 var a = nowLi.offsetLeft-endLi[0].offsetLeft; var b = nowLi.offsetTop - endLi[0].offsetTop; return Math.sqrt(a*a+b*b) } /* * 封裝函數 實現開始隊列中元素的添加 */ function openFn(){ //1 需要把openArr中的第一個元素刪除 並且返回給 nodeLi變量 var nodeLi = openArr.shift(); //2 把openArr中刪除的這個li節點 添加到closeArr中 這里調用closeFn函數實現 closeFn(nodeLi); } /* * 封裝函數 實現結束隊列中元素的添加 * */ function closeFn(nodeLi){ //open隊列中刪除的元素 被 push到close隊列中 closeArr.push(nodeLi); } </script> </body> </html>
4 上面的步驟已經把 開始節點放到了open隊列中 把障礙物放到了 close隊列中,接下來我們要做的就是 尋找下一個要走的節點,並且把這個節點添加到close隊列中
1) 封裝函數查找周圍的節點
/** * 封裝函數查找某個節點周圍的節點 */ function findLi(nodeLi){ //創建一個結果數組 把查找到的結果放到這個數組中 var result = []; //循環所有的li節點 進行查找 for(var i=0;i<aLi.length;i++){ //如果經過過濾 返回的是true 表示 這個節點不是障礙物 那么需要添加到result結果數組中 if(filter(aLi[i])){ result.push(aLi[i]); } } //接下來需要在沒有障礙物的結果中去找 和 當前節點相鄰的節點 //判斷條件是 他們的橫縱坐標的差值需要小於 等於 網格大小 for(var i=0;i<result.length;i++){ if(Math.abs(nodeLi.offsetLeft - result[i].offsetLeft)<=21 && Math.abs(nodeLi.offsetTop - result[i].offsetTop)<=20+1 ){ //這里的result[i]就是當前目標點相鄰的節點 把這些節點傳入到估價函數就能得到他們的估值,並且要把這些估值掛載到他們自身的一個自定義屬性上 result[i].num = fn(result[i]); //把已經經過估值的li添加到openArr中 openArr.push(result[i]); } } } /** * 封裝函數 實現過濾功能 * 這個函數的功能就是 接收到一個li 判斷是否是障礙物 如果是 就返回false 如果不是就返回true */ function filter(nodeLi){ //循環close隊列中的所有元素 與傳過來的節點進行比對 如果比對成功 返回false for(var i=0;i<closeArr.length;i++){ if(nodeLi == closeArr[i]){ return false; } } for(var i=0;i<openArr.length;i++){ if(nodeLi == openArr[i]){ return false; } } //如果循環完都沒有匹配上 那么證明當前傳過來的 li節點 並不是障礙物 return true; }
2) 上面函數封裝好以后 需要在openFn函數中進行調用
function openFn(){ //nodeLi 表示 當前open隊列中的元素 也就是說 先去除第一個起始節點 //shift 方法的作用: 把數組中的第一個元素刪除,並且返回這個被刪除的元素 var nodeLi = openArr.shift(); //如果nodeLi 和 endLi 一樣了 那么證明已經走到目標點了 ,這個時候需要停止調用 if(nodeLi == endLi[0]){ return; } //把open隊列中刪除的元素 添加到 close隊列中 closeFn(nodeLi) //接下來 需要找到 nodeLi 周圍的節點 findLi(nodeLi); }
3) 經過上面的函數調用 在openArr數組中就已經 添加了 當前路徑 周圍的8個點,我們要從這8個點中去尋找一個估值最小的作為下一步要走的點
4) 接下來需要對openArr中的點進行估值排序
function openFn(){ //nodeLi 表示 當前open隊列中的元素 也就是說 先去除第一個起始節點 //shift 方法的作用: 把數組中的第一個元素刪除,並且返回這個被刪除的元素 var nodeLi = openArr.shift(); //如果nodeLi 和 endLi 一樣了 那么證明已經走到目標點了 ,這個時候需要停止調用 if(nodeLi == endLi[0]){ showPath(); return; } //把open隊列中刪除的元素 添加到 close隊列中 closeFn(nodeLi) //接下來 需要找到 nodeLi 周圍的節點,並且對這些點進行估值 findLi(nodeLi); //經過上面的步驟 已經能夠找到相鄰的元素了 接下來需要對這些元素的估值進行排序 openArr.sort(function(li1,li2){ return li1.num - li2.num }) }
5) 經過上面的步驟 就已經能夠確定下一步要走的點了 接下來要做的就是同樣的操作 因此需要 遞歸調用openFn函數,在找到目標點的時候停止
function openFn(){ //nodeLi 表示 當前open隊列中的元素 也就是說 先去除第一個起始節點 //shift 方法的作用: 把數組中的第一個元素刪除,並且返回這個被刪除的元素 var nodeLi = openArr.shift(); //如果nodeLi 和 endLi 一樣了 那么證明已經走到目標點了 ,這個時候需要停止調用 if(nodeLi == endLi[0]){ showPath(); return; } //把open隊列中刪除的元素 添加到 close隊列中 closeFn(nodeLi) //接下來 需要找到 nodeLi 周圍的節點 findLi(nodeLi); //經過上面的步驟 已經能夠找到相鄰的元素了 接下來需要對這些元素的估值進行排序 openArr.sort(function(li1,li2){ return li1.num - li2.num }) //進行遞歸操作 找下一步需要走的節點 在這個過程中,也需要執行相同的步 // 那就是查找相鄰的節點 但是查找出來的結果可能和上一次的重復,也就是說上一次動作已經把這個元素添加到open隊列中了 //那么就沒有必要再進行push操作了 所以還需要在過濾函數中加一段代碼 openFn(); }
5 打印出所走的路徑
1) 給所走過的路徑設置一個父指針,例如: 當前的節點應該有一個屬性來存上一個節點,這樣我們就可以從最后一個節點倒推出上面所有節點
在找到下一個要走的點的時候,把上一個已經走過的點掛載到下一個要走的點身上
/** * 封裝函數查找某個節點周圍的節點 */ function findLi(nodeLi){ //創建一個結果數組 把查找到的結果放到這個數組中 var result = []; //循環所有的li節點 進行查找 for(var i=0;i<aLi.length;i++){ //如果經過過濾 返回的是true 表示 這個節點不是障礙物 那么需要添加到result結果數組中 if(filter(aLi[i])){ result.push(aLi[i]); } } //接下來需要在沒有障礙物的結果中去找 和 當前節點相鄰的節點 //判斷條件是 他們的橫縱坐標的差值需要小於 等於 網格大小 for(var i=0;i<result.length;i++){ if(Math.abs(nodeLi.offsetLeft - result[i].offsetLeft)<=21 && Math.abs(nodeLi.offsetTop - result[i].offsetTop)<=20+1 ){ //這里的result[i]就是當前目標點相鄰的節點 把這些節點傳入到估價函數就能得到他們的估值,並且要把這些估值掛載到他們自身的一個自定義屬性上 result[i].num = fn(result[i]); //nodeLi 是當前的位置 result[i] 是當前位置相鄰的點 下一次要走的位置就在這幾個點中,所以給result[i]定義一個parent屬性 //來存上一次的路徑 ,最終把這些路徑聯系起來就是完整的路徑 result[i].parent = nodeLi; //把已經經過估值的li添加到openArr中 openArr.push(result[i]); } } }
2) 封裝一個函數來找到所有已經走過的點
//最終線路數組 var resultParent = []; /** * 定義一個函數來找到上一次走過的節點 */ function findParent(li){ resultParent.unshift(li); if(li.parent == beginLi[0]){ return; } findParent(li.parent); }
3) 封裝函數來打印 路徑
/** * 打印出所走過的路徑 */ function showPath(){ //closeArr中最后一個 就是 找到目標點的前一個位置 因為走過的位置都會被存放在closeArr中 var lastLi = closeArr.pop(); var iNow = 0; //調用findParent函數 來找上一個節點 findParent(lastLi) var timer = setInterval(function(){ resultParent[iNow].style.background = "red"; iNow++; if(iNow == resultParent.length){ clearInterval(timer); } },500) }
4) 在找到最終目標點的時候 調用打印路徑函數
function openFn(){ //nodeLi 表示 當前open隊列中的元素 也就是說 先去除第一個起始節點 //shift 方法的作用: 把數組中的第一個元素刪除,並且返回這個被刪除的元素 var nodeLi = openArr.shift(); //如果nodeLi 和 endLi 一樣了 那么證明已經走到目標點了 ,這個時候需要停止調用 if(nodeLi == endLi[0]){ showPath(); return; } //把open隊列中刪除的元素 添加到 close隊列中 closeFn(nodeLi) //接下來 需要找到 nodeLi 周圍的節點 findLi(nodeLi); //經過上面的步驟 已經能夠找到相鄰的元素了 接下來需要對這些元素的估值進行排序 openArr.sort(function(li1,li2){ return li1.num - li2.num }) //進行遞歸操作 找下一步需要走的節點 在這個過程中,也需要執行相同的步驟 那就是查找相鄰的節點 //但是查找出來的結果可能和上一次的重復,也就是說上一次動作已經把這個元素添加到open隊列中了 //那么就沒有必要再進行push操作了 所以還需要在過濾函數中加一段代碼 openFn(); }
走到這里 我們的算法已經基本實現