javascript实现A*寻路算法

96
李伯特
2018.03.23 13:44* 字数 220

A*寻路算法是游戏中经常用到的一种自动路径计算算法,比如连连看、NPC自动巡逻等等。本文章默认用户已经熟悉A*寻路算法算法,不熟悉的可参阅下面链接的文章:

我见过的最容易读懂的 a*算法(A*寻路初探)

先来看看效果图:

测试结果:0表示空地,1表示墙壁,+表示起点和终点,*表示路径

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表示墙壁,+表示起始点,*表示路径

测试结果:0表示空地,1表示墙壁,+表示起点和终点,*表示路径

觉得有用,就点个赞吧。如果有问题,欢迎下方留言咨询!

计算机算法
Web note ad 1