A*尋路
阿新 • • 發佈:2018-12-26
/*
* 1.佈局
* 2.估計函式 f(n) = g(n) + h(n);
* 3.open佇列和close佇列
* 4.掛載估算值和父節點
* 5.繪製路線
* */
var oUl1 = document.getElementById('ul1');
var aLi = oUl1.getElementsByTagName('li');
var oInput1 = document.getElementById('input1');
// open佇列,裡面存放的是可以走的值
var openArr = [];
// close佇列,裡面存放的是不可以走的值,走過的 + 障礙物
var closeArr = [];
// 定義地圖
// 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,3,3,3,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,1,0,0,3,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,3,0,0,0,0,0,0,0,0,0,0,0,0,0,
3,3,3,3,3,3,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,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0 ,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
];
// 開方求正方形的邊長
var length = Math.sqrt(map.length);
// 設定格子的寬高
var wide = 20;
init();
// 起點和終點的查詢一定要放在init()之後,不然可能找不到
// 起點
var startLi = document.getElementsByClassName('sty1')[0];
// 終點
var endLi = document.getElementsByClassName('sty2')[0];
// 初始化
function init() {
createGrid();
// 當點選按鈕時,開始尋路
oInput1.onclick = function () {
openFn();
}
}
// 建立格子
function createGrid() {
// 設定oUl1的width
// 20個格子 * (格子的邊長 + 右側1px的邊框) + oUl1左側1px的邊框
oUl1.style.width = 20 * ( wide + 1 ) + 1 + 'px';
// 設定格子的樣式
for ( var i = 0; i < map.length; i++ ) {
var oGrid = document.createElement('li');
oGrid.style.width = wide + 'px';
oGrid.style.height = wide + 'px';
if(map[i] === 1){
oGrid.className = 'sty1';
// 把起始點放入open佇列中
openArr.push(oGrid);
} else if ( map[i] === 2 ) {
oGrid.className = 'sty2';
} else if ( map[i] === 3 ) {
oGrid.className = 'sty3';
// 把障礙物放入close佇列中
closeArr.push(oGrid);
}
oUl1.appendChild(oGrid);
}
}
// 估價函式
function f(nodeLi){
return g(nodeLi) + h(nodeLi);
}
// 起始點到n節點的距離
function g(nodeLi) {
var a = startLi.offsetLeft - nodeLi.offsetLeft;
var b = startLi.offsetTop - nodeLi.offsetTop;
return Math.sqrt( a*a + b*b );
}
// n節點到終點的距離
function h(nodeLi) {
var a = endLi.offsetLeft - nodeLi.offsetLeft;
var b = endLi.offsetTop - nodeLi.offsetTop;
return Math.sqrt( a*a + b*b );
}
// open佇列函式
function openFn(){
// 剔除open佇列陣列中的第一個值,將它新增到close佇列陣列中去
var nowLi = openArr.shift();
// 當nowLi等於終點的時候,停止遞迴
if( nowLi === endLi ){
// 顯示路線
showLine();
return;
}
closeFn(nowLi);
// 找到當前元素,周圍可以走的路
findLi(nowLi);
// 找出最小值
openArr.sort(function (li1, li2){
return li1.num - li2.num;
});
// 遞迴
arguments.callee();
}
// close佇列函式
function closeFn(nowLi){
closeArr.push(nowLi);
}
// 找到當前元素,周圍可以走的路,然後新增到open佇列陣列中去
/*
* 過濾條件
* 1.不能是close佇列陣列中的元素(障礙物+走過的路)
* 2.不能是open佇列陣列中已經存在的值
* 3.只能是當前元素鄰居(只相差一個單元格)
* */
function findLi(nowLi){
var result = [];
// 1.不能是close佇列陣列中的元素(障礙物+走過的路)
// 2.不能是open佇列陣列中已經存在的值
for ( var i = 0; i < aLi.length; i++ ) {
if( filter(closeArr, aLi[i]) && filter(openArr, aLi[i]) ) {
result.push(aLi[i]);
}
}
function filter(arr, li){
// 剔除open佇列陣列或者close佇列陣列有的值
for ( var i = 0; i < arr.length; i++ ) {
if ( arr[i] === li ){
return false;
}
}
return true;
}
// 3.只能是當前元素鄰居(只相差一個單元格)
for ( var i = 0; i < result.length; i++ ) {
if(Math.abs(nowLi.offsetLeft - result[i].offsetLeft) <= (wide + 1)
&& Math.abs(nowLi.offsetTop - result[i].offsetTop) <= (wide + 1)){
// 將估價值掛載到元素
result[i].num = f(result[i]);
// 掛載父節點,以便之後查詢
result[i].parent = nowLi;
openArr.push(result[i]);
//result[i].style.background = 'yellow';
}
}
}
// 展示最短的路徑
function showLine(){
// 其實close佇列裡就有最短的路徑
var result = [];
// 找到close佇列陣列最後一個值
var lastLi = closeArr.pop();
var iNow = 0;
findParent(lastLi);
/*
* 通過找父節點,當父節點等於起點的時候就停止遞迴
* */
function findParent(nodeLi){
result.unshift(nodeLi);
// 如果當前節點的父節點為起點,那麼停止遞迴
if(nodeLi === startLi ) {
return;
}
findParent(nodeLi.parent);
}
var timer = setInterval(function (){
result[iNow++].style.background = 'red';
if(iNow === result.length){
clearInterval(timer);
}
}, 500);
}