javascript實現A*尋路演算法
阿新 • • 發佈:2019-02-19
A*尋路演算法是遊戲中經常用到的一種自動路徑計算演算法,比如連連看、NPC自動巡邏等等。本文章預設使用者已經熟悉A*尋路演算法演算法,不熟悉的可參閱下面連結的文章:
先來看看效果圖:
Node
定義尋路過程中的點物件
var Node = function (x, y) {
this.x = x;
this.y = y;
this.g = 0; // 到起點的長度
this.h = 0; // 到終點的長度
this.p = null; // 父節點
}
heapMin
定義一個最小堆,便於後面尋路中用到的OpenSet。最小衡量值為F = G + H,即點的g+h和。
/**
* 實現一個最小堆,方便從OpenSet中選擇最小代價的點
*/
var heapMin = function () {
this.set = [];
}
heapMin.prototype.adjust = function (index) {
let len = this.set.length,
l = index * 2 + 1,
r = index * 2 + 2,
min = index,
node = null;
if (l <= len-1 && this.set[min].g + this .set[min].h > this.set[l].g + this.set[l].h) {
min = l;
}
if (r <= len-1 && this.set[min].g + this.set[min].h > this.set[r].g + this.set[r].h) {
min = r;
}
// 如果min發生改變,則需要進行交換,並繼續遞迴min子樹
if (min != index){
node = this.set[min];
this.set[min] = this .set[index];
this.set[index] = node;
this.adjust(min);
}
}
/**
* 向最小堆中新增一個元素
*/
heapMin.prototype.push = function(node) {
// 新增到陣列尾部
this.set.push(node);
// 調整堆
for (let i = Math.floor(this.set.length / 2) - 1; i >= 0 ; i--) {
this.adjust(i);
}
}
/**
* 從最小堆中移除頂部元素
*/
heapMin.prototype.pop = function () {
// 移除頂部元素,為最小元素
let node = this.set.shift();
// 調整堆
this.adjust(0);
return node;
}
/**
* 檢查堆是否為空
*/
heapMin.prototype.empty = function () {
return this.set.length > 0 ? false : true;
}
/**
* 檢查堆是否包含指定元素
*/
heapMin.prototype.contain = function (node) {
for (let len = this.set.length, i = 0; i < len; i++) {
if (this.set[i].x === node.x && this.set[i].y === node.y) return true;
}
return false;
}
Set
使用者CloseSet
/**
* Set類
*/
function Set(){
this.set = [];
}
Set.prototype.push = function (node) {
this.set.push(node);
}
Set.prototype.pop = function () {
return this.set.pop();
}
Set.prototype.contain = function (node) {
for (let len = this.set.length, i = 0; i < len; i++) {
if (this.set[i].x === node.x && this.set[i].y === node.y) return true;// 存在
}
return false;
}
AStarPathFinding
尋路演算法物件
/**
* AStarPathFinding
*
* W:地圖的寬度
* H:地圖的高度
* map:地圖陣列,0表示可以通過,1表示不可以通過
*
*/
function AStarPathFinding (W, H, map) {
this.W = W;// 地圖的寬度
this.H = H;// 地圖的高度
this.map = map;// 地圖
}
/**
* 計算距離
*
* 採用曼哈頓估量演算法
*/
function calcDistance (startNode, endNode) {
return (startNode && endNode) ?
Math.abs(startNode.x - endNode.x) + Math.abs(startNode.y - endNode.y)
:
-1;
}
/**
* 查詢鄰居點
*
* 返回鄰居點陣列
*/
AStarPathFinding.prototype.getNeighbors = function (node) {
let arr = [],
x,y;
for (let i = -1; i < 2; i++) {
for (let j = -1; j < 2; j++) {
if ((i == 0 && j == 0) || (i === j) || (i === -j)) continue;
x = node.x + i;
y = node.y + j;
if (x < this.W && x > -1 && y < this.H && y > -1) {
arr.push(new Node(x, y));
}
}
}
return arr;
}
/**
* 查詢路徑
*
* 1 初始化起始點,計算g,h
* 2 將起始點加入到OpenSet中
* 3 當OpenSet不為空的時候,進入迴圈
* 3.1 從OpenSet中取出最小點,設為當前點
* 3.2 迴圈遍歷當前點的鄰居點
* 3.2.1 如果鄰居點不可通過(為牆壁)或者已經在CloseSet中,就略過continue
* 3.2.2 如果不在OpenSet中,計算FGH數值,並加入到CloseSet的尾部
* 3.3 迴圈遍歷鄰居點結束
* 4 OpenSet迴圈結束
*/
AStarPathFinding.prototype.findPath = function (startNode, endNode){
let OpenSet = new heapMin(),
CloseSet = new Set(),
curNode = null;
OpenSet.push(startNode);
// 迴圈遍歷OpenSet直到為空
while (!OpenSet.empty()) {
curNode = OpenSet.pop();
CloseSet.push(curNode);
if (curNode.x === endNode.x && curNode.y === endNode.y) { return CloseSet.set;}
let arr = this.getNeighbors(curNode);
for (let i = arr.length - 1; i >= 0; i--) {
if (this.map[ arr[i].y ][ arr[i].x ] === 1 || CloseSet.contain(arr[i])) continue;
// 不存在,加入到OpenSet集合
if (!OpenSet.contain(arr[i])) {
arr[i].g = calcDistance(arr[i], startNode);
arr[i].h = calcDistance(arr[i], endNode);
// 更新父節點,便於之後路徑查詢
arr[i].p = curNode;
OpenSet.push(arr[i]);
}
}
}
return null;
}
/**
* 列印尋路結果地圖
*/
AStarPathFinding.prototype.printMap = function(s, e){
if (s.x < 0 || s.x > this.W-1 || s.y < 0 || s.y > this.H-1 || e.x < 0 || e.x > this.W-1 || e.y < 0 || e.y > this.H-1)
return;
let arr = this.findPath(s, e);
if (arr == null) {
console.log('Not found Path...');
return;
}
let map = this.map.slice(),
node = arr.pop();
while(node !== null) {
map[node.y][node.x] = ((s.x === node.x && s.y === node.y ) || (e.x === node.x && e.y === node.y )) ? '+' : '*';
node = node.p;
}
for (let i = 0; i < this.H; i++) {
let temp = [];
for (let j = 0; j < this.W; j++) {
temp[j] = map[i][j];
}
document.write(temp.join(' ') + '<br />');
}
}
測試
// 定義地圖陣列 0表示可以通過,1表示不可以通過
let map = [//0 1 2 3 4 5 6 7
[0,0,0,0,1,0,0,0],//0
[0,0,1,1,1,0,0,0],//1
[0,0,0,0,1,0,0,0],//2
[1,1,1,0,1,0,0,0],//3
[0,0,0,0,1,0,0,0],//4
[0,0,1,1,1,0,0,0],//5
[0,0,1,0,0,0,0,0],//6
[0,0,0,0,0,0,0,0],//7
];
let AStarPathFindingObj = new AStarPathFinding(8, 8, map);
AStarPathFindingObj.printMap(new Node(0, 0), new Node(6, 4));
測試結果
其中:0表示空地,1表示牆壁,+表示起始點,*表示路徑
覺得有用,就點個贊吧。如果有問題,歡迎下方留言諮詢!