A星尋路算法html
1.準備一個close關閉列表(存放已被檢索的點),一個open開啓列表(存放未被檢索的點),一個當前點的對象curweb
2.將cur設成開始點算法
3.從cur起,將cur點放入close表中,而後將cur點四周不爲障礙物的點放入open表中,並計算四周點的F、G、H值,設置點的父級爲cur數組
4.依次檢索open表中F值最小的點,若是出現多個F值最小的點,取離cur最近的點,並更新cur爲最小的點,並將其從open表中刪除app
5.重複3-4步dom
結束條件:結束點被放入close表中,或者open表爲空動畫
尋路:this
從結束點開始,依次查找其父級,直至查找到起始點spa
<!doctype html> <html> <head> <meta charset="utf-8"> <title>A*尋路算法</title> <style> body, dl, dd { margin:0; padding:0; } html { font-size:12px; background:#F5F5F5; } .block { border:1px solid #eaeaea; width:30px; background:#FFF; } .menu { position:fixed; left:0; bottom:0; width:100%; background:#00aeff; background:-webkit-linear-gradient(rgba(0, 186, 255, .8), rgba(0, 130, 255, .8)); border-top:1px solid #009cff; padding:20px 0; box-shadow:2px -2px 12px rgba(167, 213, 255, .8); } .menu dd { float:left; padding:0 10px; } .menu span { padding:0 10px; color:#FFF; } .menu input { border-radius:4px; } .menu .txt { width:100px; border:1px solid #0085ff; padding:0 5px; width:60px; height:20px; color:#0085ff; background:#ffffff; } .menu .btn { border:1px solid #0085ff; color:#FFF; background:#0071d1; background:-webkit-linear-gradient(rgba(80, 170, 255, .8), rgba(0, 132, 255, .8)); height:22px; cursor:pointer; } .menu .btn:hover { background-color:#32a1ff; background:-webkit-linear-gradient(rgba(0, 132, 255, .8), rgba(80, 170, 255, .8)); border-color:#1988ff; } .menu .btn.dashed, .menu .btn.dashed:hover { background:#1988ff; color:#b2dcff; cursor:default; } .start, .start.path { background-color:#007de7; } .end, .end.path { background-color:#333; } .fence { background-color:#d1d1d1; } .path { background-color:#57e700; -webkit-transition:.5s all ease; } </style> <script> window.onload = function () { var oBox = document.getElementById('box'); // 主容器 var oBtnStart = document.getElementById('setStart'); // 設置起始點按鈕 var oBtnEnd = document.getElementById('setEnd'); // 設置結束點按鈕 var oBtnCalcu = document.getElementById('calcuPath'); // 計算路徑按鈕 var oBtnReplay = document.getElementById('calcuReplay'); // 從新計算 var oFrag = document.createDocumentFragment(); // 存放臨時文檔碎片 var oStart = null; // 起始點 var oEnd = null; // 結束點 var aPoint = oBox.getElementsByTagName('div'); // 存放地圖元素 var aMaps = []; // 存放地圖數據 var maps = [ [1, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 1, 1, 0, 0, 0, 1, 0, 0, 0], [0, 0, 0, 0, 1, 0, 0, 1, 1, 1], [0, 0, 0, 0, 1, 1, 0, 1, 0, 1], [0, 0, 0, 0, 0, 0, 0, 1, 0, 0], [0, 0, 0, 0, 0, 0, 1, 0, 0, 1], [0, 0, 0, 0, 0, 0, 0, 0, 0, 1], [0, 1, 0, 0, 0, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 1, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0, 0, 0, 0] ]; // 此maps調試用 var maps = []; var iRnd = parseInt(document.getElementById('setRnd').value); // 隨機障礙 var iRow = parseInt(document.getElementById('setRow').value); // 存放行數 var iCol = parseInt(document.getElementById('setCol').value); // 存放列數 var iWidth = parseInt(document.getElementById('setWidth').value); // 單元格寬 var iHeight = parseInt(document.getElementById('setHeight').value); // 單元格高 var iTime = parseInt(document.getElementById('setTime').value); // 動畫延時 function render() { var n = 0; oBox.innerHTML = ''; for (var i = 0; i < iRow; i++) { for (var j = 0; j < iCol; j++ ) { var oBlock = document.createElement('div'); oBlock.className = 'block'; oBlock.row = i; oBlock.col = j; oBlock.style.position = 'absolute'; oBlock.style.left = iWidth * j + 'px'; oBlock.style.top = iHeight * i + 'px'; oBlock.style.width = iWidth - 1 + 'px'; oBlock.style.height = iHeight - 1 + 'px'; oFrag.appendChild(oBlock); } } oBox.appendChild(oFrag); oBox.style.width = iWidth * iCol + 'px'; oBox.style.height = iHeight * iRow + 'px'; oBox.style.position = 'absolute'; oBox.style.left = '50%'; oBox.style.top = '50%'; oBox.style.marginLeft = -oBox.offsetWidth / 2 + 'px'; oBox.style.marginTop = -oBox.offsetHeight / 2 + 'px'; } function module() { aMaps = []; aPoint = oBox.getElementsByTagName('div'); for (var i = 0; i < iRow; i++) { aMaps[i] = []; for (var j = 0; j < iCol; j++) { aMaps[i][j] = aPoint[i * iCol + j]; } } } render(); module(); rndFence(aMaps, iRnd, maps); /* // 此處起始點調試用 oStart = aMaps[0][1]; oStart.className += ' start' oEnd = aMaps[3][8]; oEnd.className += ' end'; findway(aMaps, oStart, oEnd);*/ oBox.onclick = function (ev) { var ev = ev || window.event; var target = ev.target || ev.srcElement; if (oBtnCalcu.disabled) return; // 設置起點、設置終點、設置障礙物 if (/(\b|\s)+block(\b|\s)+/i.test(target.className)) { if (!oStart && target.val != 1) target.start = true, target.className += ' start', oStart = target; else if (oStart && !oEnd) { if (!target.start && target.val != 1) target.end = true, target.className += ' end', oEnd = target; else if (target.start) return alert('起止點不能相同點'); } else if (oStart && oEnd) { if (!target.start && !target.end && target.val != 1) { target.val = 1; target.className += ' fence'; } } } module(); }; oBtnCalcu.onclick = function () { if (oStart && oEnd) { var path = findway(aMaps, oStart, oEnd, 0); if (!path) alert('無路可走'); else { for (var i = 0; i < path.length; i++) { ~function (i) { var timer = null; timer = setTimeout(function () { path[i].className += ' path'; clearTimeout(timer); }, i * iTime)}(i); } } } if (!oStart && !oEnd) { alert('請選擇起始點'); } else if (oStart && !oEnd) { alert('請選擇結束點'); } else { this.disabled = true; this.className += ' dashed'; } } oBtnReplay.onclick = function () { oStart = null; oEnd = null; oBtnCalcu.disabled = false; oBtnCalcu.className = oBtnCalcu.className.replace(/(\s|\b)+dashed(\s|\b)+/ig, ''); iWidth = parseInt(document.getElementById('setWidth').value); iHeight = parseInt(document.getElementById('setHeight').value); iRow = parseInt(document.getElementById('setRow').value); iCol = parseInt(document.getElementById('setCol').value); iRnd = parseInt(document.getElementById('setRnd').value); iTime = parseInt(document.getElementById('setTime').value); render(); module(); rndFence(aMaps, iRnd); }; }; // 隨機生成障礙物 function rndFence(points, num, maps) { var total = points.length * points[0].length; var index = 0; var col = 0; var row = 0; var n = 0; var arr = []; if (!maps || !maps.length) { while (n < num) { index = rnd(0, total); row = parseInt(index / points[0].length); col = index % points[0].length; if (!points[row][col].val) { points[row][col].val = 1; points[row][col].className += ' fence'; n++; } else { continue; } } } else { for (var i = 0; i < maps.length; i++) { for (var j = 0; j < maps[0].length; j++) { if (maps[i][j] == 1) { points[i][j].val = 1; points[i][j].className += ' fence'; } } } } } // 生成隨機數 function rnd(begin, end){ return Math.floor(Math.random() * (end - begin)) + begin; } // 獲取四周點 function getRounds(points, current) { var u = null; var l = null; var d = null; var r = null; var rounds = []; // 上 if (current.row - 1 >= 0) { u = points[current.row - 1][current.col]; rounds.push(u); } // 左 if (current.col - 1 >= 0) { l = points[current.row][current.col - 1]; rounds.push(l); } // 下 if (current.row + 1 < points.length) { d = points[current.row + 1][current.col]; rounds.push(d); } // 右 if (current.col + 1 < points[0].length) { r = points[current.row][current.col + 1]; rounds.push(r); } return rounds; } // 檢測是否在列表中 function inList(list, current) { for (var i = 0, len = list.length; i < len; i++) { if ((current.row == list[i].row && current.col == list[i].col) || (current == list[i])) return true; } return false; } function findway(points, start, end) { var opens = []; // 存放可檢索的方塊 var closes = []; // 存放已檢索的方塊 var cur = null; // 當前指針 var bFind = true; // 是否檢索 var n = 0; // 設置開始點的F、G爲0並放入opens列表 start.F = 0; start.G = 0; start.H = 0; // 將起點壓入closes數組,並設置cur指向起始點 closes.push(start); cur = start; // 若是起始點緊鄰結束點則不計算路徑直接將起始點和結束點壓入closes數組 if (Math.abs(start.row - end.row) + Math.abs(start.col - end.col) == 1) { end.P = start; closes.push(end); bFind = false; } // 計算路徑 while (cur && bFind) { //while (n < 19) { // 先把當前點加入closes中 //if (n == 10) console.log(cur); if (!inList(closes, cur)) closes.push(cur); //alert(n + '次:運行'); // 而後獲取當前點四周點 var rounds = getRounds(points, cur); //if (n == 18) console.log(rounds); // 調試用 // 當四周點不在opens數組中而且可移動,設置G、H、F和父級P,並壓入opens數組 for (var i = 0; i < rounds.length; i++) { if (rounds[i].val == 1 || inList(closes, rounds[i]) || inList(opens, rounds[i])) continue; else if (!inList(opens, rounds[i]) && rounds[i].val != 1) { rounds[i].G = cur.G + 1; rounds[i].H = Math.abs(rounds[i].col - end.col) + Math.abs(rounds[i].row - end.row); rounds[i].F = rounds[i].G + rounds[i].H; rounds[i].P = cur; opens.push(rounds[i]); //rounds[i].style.backgroundColor = 'yellow'; //rounds[i].innerHTML = 'F=' + rounds[i].F + '<br />G=' + rounds[i].G + '<br />H=' + rounds[i].H + '<br />N=' + n; } } /* // 此for調試用 for (var i = 0; i < opens.length; i++) { opens[i].style.backgroundColor = 'yellow'; opens[i].innerHTML = 'F=' + opens[i].F + '<br />G=' + opens[i].G + '<br />H=' + opens[i].H + '<br />N=' + n; }*/ //alert(n + '次:計算open數組後'); // 若是獲取完四周點後opens列表爲空,則表明無路可走,此時推出循環 if (!opens.length) { cur = null; opens = []; closes = []; break; } // 按照F值由小到達將opens數組排序 opens.sort(function (a, b) { return a.F - b.F; }); // 取出opens數組中F值最小的元素,即opens數組中的第一個元素 var oMinF = opens[0]; var aMinF = []; // 存放opens數組中F值最小的元素集合 // 循環opens數組,查找F值和cur的F值同樣的元素,並壓入aMinF數組。即找出和最小F值相同的元素有多少 for (var i = 0; i < opens.length; i++) { if (opens[i].F == oMinF.F) aMinF.push(opens[i]); } // 若是最小F值有多個元素 if (aMinF.length > 1) { // 計算元素與cur的曼哈頓距離 for (var i = 0; i < aMinF.length; i++) { //aMinF[i].D = Math.abs(aMinF[i].row - cur.row) + Math.abs(aMinF[i].col - cur.col); aMinF[i].D = Math.abs(aMinF[i].row - cur.row) + Math.abs(aMinF[i].col - cur.col); } // 將aMinF按照D由小到大排序 aMinF.sort(function (a, b) { return a.D - b.D; }); // 將cur指向D值最小的元素 oMinF = aMinF[0]; } cur = oMinF; // 將cur壓入closes數組 if (!inList(closes, cur)) closes.push(cur); // 將cur從opens數組中刪除 for (var i = 0; i < opens.length; i++) { if (opens[i] == cur) { opens.splice(i, 1); break; } } /* // 此處樣式調試用 cur.style.backgroundColor = 'green'; cur.innerHTML = 'F=' + cur.F + '<br />G=' + cur.G + '<br />H=' + cur.H + '<br />N=' + n;*/ //alert(n + '次:選取cur後'); // 找到最後一點,並將結束點壓入closes數組 if (cur.H == 1) { end.P = cur; closes.push(end); cur = null; } n++; } if (closes.length) { // 從結尾開始往前找 var dotCur = closes[closes.length - 1]; var path = []; // 存放最終路徑 while (dotCur) { path.unshift(dotCur); // 將當前點壓入path數組 dotCur = dotCur.P; // 設置當前點指向父級 // 找到第一個點後把起點添加進路徑 if (!dotCur.P) { path.unshift(start); dotCur = null; } } return path; } else { return false; } } </script> </head> <body> <div id="box" class="box"></div> <dl class="menu"> <dd><span>動畫延時(毫秒)</span><input type="text" id="setTime" value="30" class="txt" /></dd> <dd><span>隨機障礙(整數)</span><input type="text" id="setRnd" value="20" class="txt" /></dd> <dd><span>設置行數(整數)</span><input type="text" id="setRow" value="10" class="txt" /></dd> <dd><span>設置列數(整數)</span><input type="text" id="setCol" value="10" class="txt" /></dd> <dd><span>設置寬度(像素)</span><input type="text" id="setWidth" value="50" class="txt" /></dd> <dd><span>設置高度(像素)</span><input type="text" id="setHeight" value="50" class="txt" /></dd> <dd><input type="button" id="calcuPath" value="計算路徑" class="btn" /></dd> <dd><input type="button" id="calcuReplay" value="從新計算" class="btn" /></dd> </dl> </body> </html>