JS/HTML5游戏常用算法之路径搜索算法 A*寻路算法完整实例


Posted in Javascript onDecember 14, 2018

本文实例讲述了JS/HTML5游戏常用算法之路径搜索算法 A*寻路算法。分享给大家供大家参考,具体如下:

原理可参考:https://3water.com/article/152744.htm

完整实例代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
  <meta charset="UTF-8">
  <title>A*寻路算法</title>
  <style>
    #stage {
      border: 1px solid lightgray;
    }
  </style>
</head>
<body>
<canvas id="stage"></canvas>
</body>
<script>
  window.onload = function () {
    var stage = document.querySelector('#stage'),
      ctx = stage.getContext('2d');
    stage.width = 600;
    stage.height = 600;
    var row = 7, column = 7, r = 40;
    //取区域随机数x>=min && x<max
    function randInt(min, max) {
      max = max || 0;
      min = min || 0;
      var step = Math.abs(max - min);
      var st = (arguments.length < 2) ? 0 : min;//参数只有一个的时候,st = 0;
      var result;
      result = st + (Math.ceil(Math.random() * step)) - 1;
      return result;
    }
    //普里姆算法生成连通图的二维数组 row 行 column 列
    function primMaze(r, c) {
      //初始化数组
      function init(r, c) {
        var a = new Array(2 * r + 1);
        //全部置1
        for (let i = 0, len = a.length; i < len; i++) {
          var cols = 2 * c + 1;
          a[i] = new Array(cols);
          for (let j = 0, len1 = a[i].length; j < len1; j++) {
            a[i][j] = 1;
          }
        }
        //中间格子为0
        for (let i = 0; i < r; i++)
          for (let j = 0; j < c; j++) {
            a[2 * i + 1][2 * j + 1] = 0;
          }
        return a;
      }
      //处理数组,产生最终的数组
      function process(arr) {
        //acc存放已访问队列,noacc存放没有访问队列
        var acc = [], noacc = [];
        var r = arr.length >> 1, c = arr[0].length >> 1;
        var count = r * c;
        for (var i = 0; i < count; i++) {
          noacc[i] = 0;
        }
        //定义空单元上下左右偏移
        var offs = [-c, c, -1, 1], offR = [-1, 1, 0, 0], offC = [0, 0, -1, 1];
        //随机从noacc取出一个位置
        var pos = randInt(count);
        noacc[pos] = 1;
        acc.push(pos);
        while (acc.length < count) {
          var ls = -1, offPos = -1;
          offPos = -1;
          //找出pos位置在二维数组中的坐标
          var pr = pos / c | 0, pc = pos % c, co = 0, o = 0;
          //随机取上下左右四个单元
          while (++co < 5) {
            o = randInt(0, 5);
            ls = offs[o] + pos;
            var tpr = pr + offR[o];
            var tpc = pc + offC[o];
            if (tpr >= 0 && tpc >= 0 && tpr <= r - 1 && tpc <= c - 1 && noacc[ls] == 0) {
              offPos = o;
              break;
            }
          }
          if (offPos < 0) {
            pos = acc[randInt(acc.length)];
          }
          else {
            pr = 2 * pr + 1;
            pc = 2 * pc + 1;
            //相邻空单元中间的位置置0
            arr[pr + offR[offPos]][pc + offC[offPos]] = 0;
            pos = ls;
            noacc[pos] = 1;
            acc.push(pos);
          }
        }
      }
      var a = init(r, c);
      process(a);
      return a;
      //返回一个二维数组,行的数据为2r+1个,列的数据为2c+1个
    }
    //栅格线条
    function drawGrid(context, color, stepx, stepy) {
      context.strokeStyle = color;
      context.lineWidth = 0.5;
      for (var i = stepx + 0.5; i < context.canvas.width; i += stepx) {
        context.beginPath();
        context.moveTo(i, 0);
        context.lineTo(i, context.canvas.height);
        context.stroke();
      }
      for (var i = stepy + 0.5; i < context.canvas.height; i += stepy) {
        context.beginPath();
        context.moveTo(0, i);
        context.lineTo(context.canvas.width, i);
        context.stroke();
      }
    }
    //方块创造方法
    function createRect(x, y, r, c) {
      ctx.beginPath();
      ctx.fillStyle = c;
      ctx.rect(x, y, r, r);
      ctx.fill();
    }
    //定义点对象【a*点对象】
    function Point(x, y) {
      this.x = x;
      this.y = y;
      this.parent = null;
      this.f = 0;
      this.g = 0;
      this.h = 0;
      //当前点状态,0:表示在openlist 1:表示closelist,-1表示还没处理
      this.state = -1;
      //flag表明该点是否可通过
      this.flag = 0;
    }
    //把普通二维数组(全部由1,0表示)的转换成a*所需要的点数组
    function convertArrToAS(arr) {
      var r = arr.length, c = arr[0].length;
      var a = new Array(r);
      for (var i = 0; i < r; i++) {
        a[i] = new Array(c);
        for (var j = 0; j < c; j++) {
          var pos = new Point(i, j);
          pos.flag = arr[i][j];
          a[i][j] = pos;
        }
      }
      return a;
    }
    //A*算法,pathArr表示最后返回的路径
    function findPathA(pathArr, start, end, row, col) {
      //添加数据到排序数组中
      function addArrSort(descSortedArr, element, compare) {
        var left = 0;
        var right = descSortedArr.length - 1;
        var mid = (left + right) >> 1;
        while (left <= right) {
          var mid = (left + right) >> 1;
          if (compare(descSortedArr[mid], element) == 1) {
            left = mid + 1;
          }
          else if (compare(descSortedArr[mid], element) == -1) {
            right = mid - 1;
          }
          else {
            break;
          }
        }
        for (var i = descSortedArr.length - 1; i >= left; i--) {
          descSortedArr[i + 1] = descSortedArr[i];
        }
        descSortedArr[left] = element;
      }
      //判断两个点是否相同
      function pEqual(p1, p2) {
        return p1.x == p2.x && p1.y == p2.y;
      }
      //获取两个点距离,采用曼哈顿方法
      function posDist(pos, pos1) {
        return (Math.abs(pos1.x - pos.x) + Math.abs(pos1.y - pos.y));
      }
      function between(val, min, max) {
        return (val >= min && val <= max)
      }
      //比较两个点f值大小
      function compPointF(pt1, pt2) {
        return pt1.f - pt2.f;
      }
      //处理当前节点
      function processCurrpoint(arr, openList, row, col, currPoint, destPoint) {
        //get up,down,left,right direct
        var ltx = currPoint.x - 1;
        var lty = currPoint.y - 1;
        for (var i = 0; i < 3; i++){
          for (var j = 0; j < 3; j++) {
            var cx = ltx + i;
            var cy = lty + j;
            if ((cx === currPoint.x || cy === currPoint.y) && between(ltx, 0, row - 1) && between(lty, 0, col - 1)) {
              var tp = arr[cx][cy];
              if (tp.flag === 0 && tp.state !== 1) {
                if (pEqual(tp, destPoint)) {
                  tp.parent = currPoint;
                  return true;
                }
                if (tp.state === -1) {
                  tp.parent = currPoint;
                  tp.g = 1 + currPoint.g;
                  tp.h = posDist(tp, destPoint);
                  tp.f = tp.h + tp.f;
                  tp.state = 0;
                  addArrSort(openList, tp, compPointF);
                }
                else {
                  var g = 1 + currPoint.g;
                  if (g < tp.g) {
                    tp.parent = currPoint;
                    tp.g = g;
                    tp.f = tp.g + tp.h;
                    openList.quickSort(compPointF);
                  }
                }
              }
            }
          }
        }
        return false;
      }
      //定义openList
      var openList = [];
      //定义closeList
      var closeList = [];
      start = pathArr[start[0]][start[1]];
      end = pathArr[end[0]][end[1]];
      //添加开始节点到openList;
      addArrSort(openList, start, compPointF);
      var finded = false;
      while ((openList.length > 0)) {
        var currPoint = openList.pop();
        currPoint.state = 1;
        closeList.push(currPoint);
        finded = processCurrpoint(pathArr, openList, row, col, currPoint, end);
        if (finded) {
          break;
        }
      }
      if (finded) {
        var farr = [];
        var tp = end.parent;
        farr.push(end);
        while (tp != null) {
          farr.push(tp);
          tp = tp.parent;
        }
        return farr;
      }
      else {
        return null;
      }
    }
    //定位屏幕坐标到数组位置
    function mapSCPos(i, j) {
      return [i / r | 0, j / r | 0];
    }
    //检测数组中的位置是否存在方块
    function mapHasRect(map, i, j) {
      return (map[i][j]);
    }
    var mapArr = primMaze(row, column);
    var startRect = {
      x: function () {
        for (var i = 0, len = mapArr.length; i < len; i++) {
          for (var j = 0, len1 = mapArr[i].length; j < len1; j++) {
            if (!mapArr[i][j]) {
              return j * r;
              break;
            }
          }
        }
      }(),
      y: function () {
        for (var i = 0, len = mapArr.length; i < len; i++) {
          for (var j = 0, len1 = mapArr[i].length; j < len1; j++) {
            if (!mapArr[i][j]) {
              return i * r;
              break;
            }
          }
        }
      }(),
      pos: function () {
        return [this.x, this.y];
      }
    },
      endRect = {
      hasCreate:false,
      x:null,
      y:null,
      pos: function () {
        return [this.x, this.y];
      }
    },
      startPoint = mapSCPos(startRect.pos()[1], startRect.pos()[0]),
      endPoint,
      path = null,
      next = null;
    //计算路经
    function update() {
      ctx.clearRect(0, 0, 600, 600);
      drawGrid(ctx, 'lightgray', r, r);
      //根据地图二维数组创建色块
      for (var i = 0, len = mapArr.length; i < len; i++) {
        for (var j = 0, len1 = mapArr[i].length; j < len1; j++) {
          if (mapArr[i][j]) {
            createRect(j * r, i * r, r, "black");
          }
        }
      }
      //绘制开始方块
      createRect(startRect.x, startRect.y, r, "red");
      if (endRect.hasCreate) {
        //绘制跟随方块
        createRect(endRect.pos()[0], endRect.pos()[1], r, "blue");
        endPoint = mapSCPos(endRect.pos()[1], endRect.pos()[0]);
        if(path === null){
          var ASmap = convertArrToAS(mapArr);
          path = findPathA(ASmap, startPoint, endPoint, ASmap.length, ASmap.length);
        }else{
          next = path.pop();
          startRect.y = next.x * r;
          startRect.x = next.y * r;
          if(path.length===0){
            startPoint = mapSCPos(startRect.pos()[1], startRect.pos()[0]);
            path = null;
            endRect.hasCreate = false;
          }
        }
      }
      requestAnimationFrame(update);
    }
    update();
    stage.addEventListener('click', function () {
      //标准的获取鼠标点击相对于canvas画布的坐标公式
      var x = event.clientX - stage.getBoundingClientRect().left,
        y = event.clientY - stage.getBoundingClientRect().top;
      var endRectPos = mapSCPos(y, x);//[i,j]
      endRect.x = endRectPos[1]*r;
      endRect.y = endRectPos[0]*r;
      if (mapHasRect(mapArr, endRectPos[0], endRectPos[1])) {
        console.log('这个位置已经有方块啦!');
      } else {
        endRect.pos();
        endRect.hasCreate = true;
      }
    })
  };
</script>
</html>

使用在线HTML/CSS/JavaScript代码运行工具:http://tools.3water.com/code/HtmlJsRun,测试运行上述代码,可得到如下运行效果:

JS/HTML5游戏常用算法之路径搜索算法 A*寻路算法完整实例

希望本文所述对大家JavaScript程序设计有所帮助。

Javascript 相关文章推荐
Jquery Ajax的Get方式时需要注意URL地方
Apr 07 Javascript
jQuery获取注册信息并提示实现代码
Apr 21 Javascript
node.js实现逐行读取文件内容的代码
Jun 27 Javascript
EasyUI,点击开启编辑框,并且编辑框获得焦点的方法
Mar 01 Javascript
JQuery实现鼠标移动图片显示描述层的方法
Jun 25 Javascript
javascript原型继承工作原理和实例详解
Apr 07 Javascript
利用node.js制作命令行工具方法教程(一)
Jun 22 Javascript
BootStrap入门学习第一篇
Aug 28 Javascript
微信小程序实现横向增长表格的方法
Jul 24 Javascript
Vue-router的使用和出现空白页,路由对象属性详解
Sep 03 Javascript
解决node终端下运行js文件不支持ES6语法
Apr 04 Javascript
javascript实现计算器功能详解流程
Nov 01 Javascript
JS实现的A*寻路算法详解
Dec 14 #Javascript
详解vue项目接入微信JSSDK的坑
Dec 14 #Javascript
微信小程序实现动态显示和隐藏某个控件功能示例
Dec 14 #Javascript
javascript中的event loop事件循环详解
Dec 14 #Javascript
如何在Vue中使用CleaveJS格式化你的输入内容
Dec 14 #Javascript
webpack配置proxyTable时pathRewrite无效的解决方法
Dec 13 #Javascript
node.js学习笔记之koa框架和简单爬虫练习
Dec 13 #Javascript
You might like
php2html php生成静态页函数
2008/12/08 PHP
浅析PHP编程中10个最常见的错误
2014/08/08 PHP
PHP date_default_timezone_set()设置时区操作实例分析
2020/05/16 PHP
一些Javascript的IE和Firefox(火狐)兼容性的问题总结及常用例子
2009/05/21 Javascript
bgsound 背景音乐 的一些常用方法及特殊用法小结
2010/05/11 Javascript
javascript各浏览器中option元素的表现差异
2011/04/07 Javascript
JS中的public和private对象,即static修饰符
2012/01/18 Javascript
JQuery中对Select的option项的添加、删除、取值
2013/08/25 Javascript
JavaScript实现图片轮播的方法
2015/07/31 Javascript
探讨:JavaScript ECAMScript5 新特性之get/set访问器
2016/05/05 Javascript
基于BootStrap Metronic开发框架经验小结【一】框架总览及菜单模块的处理
2016/05/12 Javascript
Easyui Tree获取当前选择节点的所有顶级父节点
2017/02/14 Javascript
JS与jQuery判断文本框还剩多少字符可以输入的方法
2018/09/01 jQuery
vue倒计时刷新页面不会从头开始的解决方法
2020/03/03 Javascript
[02:44]DOTA2英雄基础教程 魅惑魔女
2014/01/07 DOTA
[53:38]OG vs LGD 2018国际邀请赛淘汰赛BO3 第三场 8.26
2018/08/30 DOTA
python基础教程之元组操作使用详解
2014/03/25 Python
Python实现冒泡,插入,选择排序简单实例
2014/08/18 Python
flask中的wtforms使用方法
2018/07/21 Python
解决PySide+Python子线程更新UI线程的问题
2019/01/11 Python
python 对字典按照value进行排序的方法
2019/05/09 Python
python正则-re的用法详解
2019/07/28 Python
Python笔记之工厂模式
2019/11/20 Python
Python逐行读取文件内容的方法总结
2020/02/14 Python
python和opencv构建运动检测器的实现
2021/03/03 Python
Linux内核产生并发的原因
2012/07/13 面试题
查环查孕证明
2014/01/10 职场文书
构建高效课堂实施方案
2014/03/13 职场文书
毕业晚会主持词
2014/03/24 职场文书
财产公证书
2014/04/10 职场文书
遵纪守法演讲稿
2014/05/23 职场文书
酒后驾车标语
2014/06/30 职场文书
法定授权委托证明书
2014/09/27 职场文书
Golang 并发下的问题定位及解决方案
2022/03/16 Golang
Python使用华为API为图像设置多个锚点标签
2022/04/12 Python
Windows server 2012 R2 安装IIS服务器
2022/04/29 Servers