zoukankan      html  css  js  c++  java
  • A星寻路算法

    A星寻路算法

    1.准备一个close关闭列表(存放已被检索的点),一个open开启列表(存放未被检索的点),一个当前点的对象cur

    2.将cur设成开始点

    3.从cur起,将cur点放入close表中,然后将cur点四周不为障碍物的点放入open表中,并计算四周点的F、G、H值,设置点的父级为cur

    4.依次检索open表中F值最小的点,如果出现多个F值最小的点,取离cur最近的点,并更新cur为最小的点,并将其从open表中删除

    5.重复3-4步

    结束条件:结束点被放入close表中,或者open表为空

    寻路:

    从结束点开始,依次查找其父级,直至查找到起始点

    <!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 (/(|s)+block(|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|)+dashed(s|)+/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>
  • 相关阅读:
    HDU 2010 水仙花数
    HDU 2009 求数列的和
    HDU 2008 数值统计
    Nginx实现反向代理
    vue-router 实现分析
    区分slice,splice和split方法
    notify()和notifyAll()主要区别
    移动端页面兼容性问题解决方案整理
    clearfix的用法
    前端js面试题
  • 原文地址:https://www.cnblogs.com/baie/p/3243275.html
Copyright © 2011-2022 走看看