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判断iframe中元素是否存在的方法
May 11 Javascript
一个支付页面DEMO附截图
Jul 22 Javascript
jquery.validate使用时遇到的问题
May 25 Javascript
判断js的Array和Object的实现方法
Aug 29 Javascript
JavaScript用构造函数如何获取变量的类型名
Dec 23 Javascript
JS将unicode码转中文方法
May 08 Javascript
Mongoose实现虚拟字段查询的方法详解
Aug 15 Javascript
详解React Native 屏幕适配(炒鸡简单的方法)
Jun 11 Javascript
在AngularJs中设置请求头信息(headers)的方法及不同方法的比较
Sep 04 Javascript
一百行JS代码实现一个校验工具
Apr 30 Javascript
vue中渲染对象中属性时显示未定义的解决
Jul 31 Javascript
微信小程序使用前置摄像头拍照
Oct 22 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
使用array mutisort 实现按某字段对数据排序
2013/06/18 PHP
ASP Json Parser修正版
2009/12/06 Javascript
过虑特殊字符输入的js代码
2010/08/05 Javascript
SOSO地图API使用(一)在地图上画圆实现思路与代码
2013/01/15 Javascript
初始Nodejs
2014/11/08 NodeJs
javascript图片预加载实例分析
2015/07/16 Javascript
ES2015 Symbol 一种绝不重复的值
2016/12/25 Javascript
JavaScript评论点赞功能的实现方法
2017/03/13 Javascript
NodeJS实现微信公众号关注后自动回复功能
2017/05/31 NodeJs
使用微信小程序开发弹出框应用实例详解
2018/10/18 Javascript
element-ui带输入建议的input框踩坑(输入建议空白以及会闪出上一次的输入建议问题)
2019/01/15 Javascript
在Echarts图中给坐标轴加一个标识线markLine
2020/07/20 Javascript
js实现弹幕飞机效果
2020/08/27 Javascript
解决python os.mkdir创建目录失败的问题
2018/10/16 Python
python3的输入方式及多组输入方法
2018/10/17 Python
Python使用Pickle模块进行数据保存和读取的讲解
2019/04/09 Python
简单了解python单例模式的几种写法
2019/07/01 Python
opencv3/C++ 平面对象识别&amp;透视变换方式
2019/12/11 Python
如何表示python中的相对路径
2020/07/08 Python
html5自带表单验证体验优化及提示气泡修改功能
2017/09/12 HTML / CSS
HTML5 Canvas 起步(1) - 基本概念
2009/05/12 HTML / CSS
html5各种页面切换效果和模态对话框用法总结
2014/12/15 HTML / CSS
马来西亚和新加坡巴士票在线预订:CatchThatBus
2018/11/17 全球购物
实习鉴定范文
2013/12/19 职场文书
农民工创业典型事迹
2014/01/25 职场文书
列车长先进事迹材料
2014/01/25 职场文书
2014年宣传部工作总结
2014/11/12 职场文书
2014年学生资助工作总结
2014/12/18 职场文书
焦点访谈观后感
2015/06/11 职场文书
开学第一周总结
2015/07/16 职场文书
党校培训学习心得体会
2016/01/06 职场文书
《静夜思》教学反思
2016/02/17 职场文书
pycharm无法导入lxml的解决办法
2021/03/31 Python
python 机器学习的标准化、归一化、正则化、离散化和白化
2021/04/16 Python
MySQL 可扩展设计的基本原则
2021/05/14 MySQL
Navicat Premium自定义 sql 标签的创建方式
2022/09/23 数据库