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 (/(\b|\s)+block(\b|\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|\b)+dashed(\s|\b)+/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>

 

你可能感兴趣的:(算法)