A星尋路算法

A星尋路算法html

1.準備一個close關閉列表(存放已被檢索的點),一個open開啓列表(存放未被檢索的點),一個當前點的對象curweb

2.將cur設成開始點算法

3.從cur起,將cur點放入close表中,而後將cur點四周不爲障礙物的點放入open表中,並計算四周點的F、G、H值,設置點的父級爲cur數組

4.依次檢索open表中F值最小的點,若是出現多個F值最小的點,取離cur最近的點,並更新cur爲最小的點,並將其從open表中刪除app

5.重複3-4步dom

 

結束條件:結束點被放入close表中,或者open表爲空動畫

尋路:this

從結束點開始,依次查找其父級,直至查找到起始點spa

 

<!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>
相關文章
相關標籤/搜索