不再依賴A*,利用C++編寫全新尋路算法


一,說在前面的話

大概在半年前,看見一到信息競賽題:在任意方格陣中設置障礙物,確定起始點后,求這兩點之間路徑。當時覺得蠻有意思的,但是沒有時間去做,今天花了兩個小時來實現它。據說有一個更高級的尋路算法叫做a*, 那我就把我的算法叫做W*。

這個算法主要用於解迷宮和實現戰棋游戲(SLG)的尋路。


首先講一講我的算法的思路:
我們先確定起始點,然后從起點出發,按一定順序判斷這個位置上下左右是否有可走的位置,如果發現有可走的位置,則遞歸進入該位置的判斷。在遞歸的同時記錄所走的路線。當發現某個位置無路可走,則刪除路線的最后一個位置並返回上級位置進行判斷。如此反復嘗試最終找到路線。


說了這么多,就來講解一下代碼吧。

二,講解部分

包含頭文件(全部都是stl中的):

#include <map>
#include <vector>
#include <iostream>

為幾個冗長的類型重命名,用來使后來的代碼更明了。

typedef        unsigned int                uint;
typedef        std::vector<int>            CRow;
//相當於把CLabyrinth定義成一個整型的二維數組
typedef        std::vector<CRow>            CLabyrinth;

定義一個類類型表示二維數組中的位置:

class CPoint
{

public:

    int    col;            //
    int    row;            //

public:

    //構造函數,接受行和列的初始化
    CPoint(int c = 0, int r = 0)
        : col(c)
        , row(r)
    {
        return;
    }

    //賦值操作
    CPoint& operator=(const CPoint& pt)
    {
        col = pt.col;
        row = pt.row;
        return *this;
    }

    //比較操作
    bool operator==(const CPoint& pt)
    {
        return col == pt.col && row == pt.row;
    }

    //判斷該位置是否合法
    bool allRight()
    {
        return col >= 0 && row >= 0;
    }

};

typedef        std::vector<CPoint>            CRoute;

然后到了核心類類型CLabyrinthAI

{

protected:

    //裝有迷宮數據的二維數組
    CLabyrinth        m_xLabyrinth;
    //起點位置
    CPoint            m_ptBeginning;
    //終點位置
    CPoint            m_ptEnding;
    //記錄路線的數組
    CRoute            m_vRoute;

public:

    //枚舉表示起點、終點的值
    enum{Beginning = -1, Ending = -2};
    //枚舉表示障礙物與可走區的值
    enum{CanntGo = 0, CanGo = 1};
    //枚舉是否找到終點
    enum{FoundEnding = 0, NotFoundEnding = 1};

protected:

    //判斷某個位置是否已在路線數組中,用於別走重復的路
    bool isRepeat(const CPoint& pt)
    {
        bool bRes = false;
        CRoute::iterator it = m_vRoute.begin();
        for(; it != m_vRoute.end(); it++){
            CPoint pt0 = *it;
            if(pt0 == pt){
                bRes = true;
                break;
            }
        }
        return bRes;
    }

    //將某一位置加入路線數組
    void advance(const CPoint& ptTo)
    {
        m_vRoute.push_back(ptTo);
    }

    //將路線數組最后一個位置彈出
    void back()
    {
        m_vRoute.pop_back();
    }

    //判斷某一位置是否是起點
    bool isBeginning(const CPoint& pt)
    {
        return m_ptBeginning == pt;
    }

    //判斷某一位置是否是終點
    bool isEnding(const CPoint& pt)
    {
        return m_ptEnding == pt;
    }

/*-----------------核心算法------------------------*/
    //判斷某一位置是否可以向上移動
    CPoint canUp(const CPoint& ptCurrent)    //接受當前位置
    {
        CPoint ptRes = CPoint(-1, -1);
        int col = ptCurrent.col;
        int row = ptCurrent.row;
        if(row > 0){
            CPoint ptNext = CPoint(col, row - 1);    //上移后位置
            //檢查上移后位置是否已經走過,以免尋路過程中繞圈子進入死循環
            if(!isRepeat(ptNext)){
                //獲得迷宮二維數組中上移后位置的屬性(起點、終點、可走、障礙)
                int nAttr = m_xLabyrinth[ptNext.row][ptNext.col];
                //如果上移后位置為可走或到達終點,則設定返回值為上移后的位置
                if(nAttr == CanGo || nAttr == Ending){
                    ptRes = ptNext;
                }
            }
        }
        return ptRes;    //如果上移后位置不可走則返回非法的位置
    }

    //以下判斷某一位置可否移動的原理大致與上相同,就不多說了

    //判斷某一位置是否可以向下移動
    CPoint canDown(const CPoint& ptCurrent)
    {
        CPoint ptRes = CPoint(-1, -1);
        int col = ptCurrent.col;
        int row = ptCurrent.row;
        if(row < m_xLabyrinth.size() - 1){
            CPoint ptNext = CPoint(col, row + 1);
            if(!isRepeat(ptNext)){
                int nAttr = m_xLabyrinth[ptNext.row][ptNext.col];
                if(nAttr == CanGo || nAttr == Ending){
                    ptRes = ptNext;
                }
            }
        }
        return ptRes;
    }

    //判斷某一位置是否可以向左移動
    CPoint canLeft(const CPoint& ptCurrent)
    {
        CPoint ptRes = CPoint(-1, -1);
        int col = ptCurrent.col;
        int row = ptCurrent.row;
        if(col > 0){
            CPoint ptNext = CPoint(col - 1, row);
            if(!isRepeat(ptNext)){
                int nAttr = m_xLabyrinth[ptNext.row][ptNext.col];
                if(nAttr == CanGo || nAttr == Ending){
                    ptRes = ptNext;
                }
            }
        }
        return ptRes;
    }

    //判斷某一位置是否可以向右移動
    CPoint canRight(const CPoint& ptCurrent)
    {
        CPoint ptRes = CPoint(-1, -1);
        int col = ptCurrent.col;
        int row = ptCurrent.row;
        if(col < m_xLabyrinth[0].size() - 1){
            CPoint ptNext = CPoint(col + 1, row);
            if(!isRepeat(ptNext)){
                int nAttr = m_xLabyrinth[ptNext.row][ptNext.col];
                if(nAttr == CanGo || nAttr == Ending){
                    ptRes = ptNext;
                }
            }
        }
        return ptRes;
    }

/*
*判斷某一位置是否可以向四周移動,如果判斷到某一位置可以移動,則遞歸進入該位置判斷。
*如果該位置沒有任何位置可移動,則返會上級位置並且調用back函數。如果走到終點,
*則立刻返回枚舉值FoundEnding,上級位置檢查到返回值為FoundEnding,也直接返回。
*/
    int findRoute(const CPoint& ptCurrent)
    {
        int nRes = NotFoundEnding;        //默認返回值為沒有找到終點
        CPoint ptNext = CPoint(-1, -1);

        advance(ptCurrent);            //將當前位置加入路線數組

        //判斷當前位置是否是終點,如果是終點則不進行下面的判斷,將返回值設置為找到終點
        if(isEnding(ptCurrent)){
            nRes = FoundEnding;
        }else{                    //按上左下右的順序判斷有無可走路徑
            //嘗試向上
            ptNext = canUp(ptCurrent);    //獲取向上走后的位置
            //判斷向上走后的位置是否是合法位置,若不合法,則表明上走到了迷宮的邊緣,或者上面沒有可走路徑
            if(ptNext.allRight()){
                //上述判斷成功,則將向上移動后的位置傳入給自己,進行遞歸。當該函數退出,查看返回值是否為找到終點。若找到終點則立刻返回FoundEnding
                if(findRoute(ptNext) == FoundEnding){
                    nRes = FoundEnding;
                    return nRes;
                }
            }
//下列嘗試四周位置是否可走的代碼與上述大體相同,就不多說了
            //嘗試向左
            ptNext = canLeft(ptCurrent);
            if(ptNext.allRight()){
                if(findRoute(ptNext) == FoundEnding){
                    nRes = FoundEnding;
                    return nRes;
                }
            }
            //嘗試向下
            ptNext = canDown(ptCurrent);
            if(ptNext.allRight()){
                if(findRoute(ptNext) == FoundEnding){
                    nRes = FoundEnding;
                    return nRes;
                }
            }
            //嘗試向右
            ptNext = canRight(ptCurrent);
            if(ptNext.allRight()){
                if(findRoute(ptNext) == FoundEnding){
                    nRes = FoundEnding;
                    return nRes;
                }
            }
        }

        //檢測是否到達終點,若沒有到達終點,則立刻從路線表中刪除該位置
        if(nRes != FoundEnding){
            back();
        }

        return nRes;
    }
/*-----------------核心算法------------------------*/

public:

    //構造函數
    CLabyrinthAI()
    {
        return;
    }

    //帶有初始化迷宮數組構造函數
    CLabyrinthAI(const CLabyrinth& vLabyrinth)
    {
        m_xLabyrinth = vLabyrinth;
        getBeginning();
        getEnding();
    }

    //初始化迷宮數組
    void setLabyrinth(const CLabyrinth& vLabyrinth)
    {
        m_xLabyrinth = vLabyrinth;
    }

    //查找起點
    void getBeginning()
    {
        uint nRow = 0;
        for(; nRow < m_xLabyrinth.size(); nRow++){
            CRow xRow = m_xLabyrinth[nRow];
            uint nCol = 0;
            for(; nCol < xRow.size(); nCol++){
                int n = xRow[nCol];
                if(n == Beginning){
                    m_ptBeginning = CPoint(nCol, nRow);
                    break;
                }
            }
        }
    }

    //查找終點
    void getEnding()
    {
        uint nRow = 0;
        for(; nRow < m_xLabyrinth.size(); nRow++){
            CRow xRow = m_xLabyrinth[nRow];
            uint nCol = 0;
            for(; nCol < xRow.size(); nCol++){
                int n = xRow[nCol];
                if(n == Ending){
                    m_ptEnding = CPoint(nCol, nRow);
                    break;
                }
            }
        }
    }

    //調用核心算法函數,輸出獲得的路線
    void AI()
    {
        findRoute(m_ptBeginning);
        if(!m_vRoute.empty()){
            CRoute::iterator it = m_vRoute.begin();
            for(; it != m_vRoute.end(); it++){
                CPoint pt = *it;
                std::cout << "(" << pt.row << ", " << pt.col << ")";
                if(it != m_vRoute.end() - 1){
                    std::cout << "->";
                }else{
                    std::cout << std::endl;
                }
            }
        }else{
            //如果沒有找到路線到達終點
            std::cout << "Sorry cannot file any ways to get ending." << std::endl;
        }
    }

};

代碼都加上了注釋,大家可以慢慢看。
如果上述過程把你攪暈了,那就用圖來為你解答吧。

 

然后來到main函數

//用VC 6.0貌似不需要給main傳參數,那我就偷一下懶
int main()
{
    //定義迷宮數組,定義成C風格的二維數組方便查看
    int vLabyrinthArray[][4] = {
        {1,0,-1,1}
        , {1,0,0,1}
        , {0,0,1,1}
        , {0,1,1,0}
        , {0,1,1,1}
        , {-2,1,0,0}
    };

    //以下代碼為將C風格的二維數組導入成C++風格的二維數組
    int nRowNum = sizeof(vLabyrinthArray) / sizeof(vLabyrinthArray[0]);
    int nColNum = sizeof(vLabyrinthArray[0]) / sizeof(int);

    CLabyrinth vLabyrinth;
    for(int row = 0; row < nRowNum; row++){
        CRow xRow;
        for(int col = 0; col < nColNum; col++){
            int n = vLabyrinthArray[row][col];
            xRow.push_back(n);
        }
        vLabyrinth.push_back(xRow);
    }

    //實例化CLabyrinthAI
    CLabyrinthAI xAI(vLabyrinth);
    //打出路線
    xAI.AI();

    //使程序暫停,方便查看數據
    system("Pause");

    return 0;
}

以上代碼同樣加了注釋,相信了解C++的同學都能看懂。

運行截圖:

(Dos的,有點丑……尷尬

三,Javascript版

順便我也把C++版的移植到了JavaScript上,代碼如下:

function CLabyrinthAI(){
    var s = this;
    s.m_xLabyrinth = new Array(new Array());
    s.m_ptBeginning = {};
    s.m_ptEnding = {};
    s.m_vRoute = new Array();
    s.Beginning = -1;
    s.Ending = -2;
    s.CannotGo = 0;
    s.CanGo = 1;
    s.FoundEnding = 0;
    s.NotFoundEnding = 1;
}
CLabyrinthAI.prototype.initAI = function(){
    var s = this;
    s.getBeginning();
    s.getEnding();
}
CLabyrinthAI.prototype.isRepeat = function(pt){
    var s = this;
    var bRes = false;
    for(var n = 0; n < s.m_vRoute.length; n++){
        var pt0 = s.m_vRoute[n];
        if(pt0.col == pt.col && pt0.row == pt.row){
            bRes = true;
            break;
        }
    }
    return bRes;
};
CLabyrinthAI.prototype.advance = function(ptTo){
    this.m_vRoute.push(ptTo);
};
CLabyrinthAI.prototype.back = function(){
    this.m_vRoute.splice(this.m_vRoute.length-1,1);
};
CLabyrinthAI.prototype.isBeginning = function(pt){
    if(this.m_ptBeginning.col == pt.col && this.m_ptBeginning.row == pt.row){
        return true;
    }else{
        return false;
    }
};
CLabyrinthAI.prototype.isEnding = function(pt){
    if(this.m_ptEnding.col == pt.col && this.m_ptEnding.row == pt.row){
        return true;
    }else{
        return false;
    }
};
CLabyrinthAI.prototype.canUp = function(ptCurrent){
    var s = this;
    var ptRes = {col:-1,row:-1};
    var col = ptCurrent.col;
    var row = ptCurrent.row;
    if(row > 0){
        var ptNext = {col:col,row:row - 1};
        if(!s.isRepeat(ptNext)){
            var nAttr = s.m_xLabyrinth[ptNext.row][ptNext.col];
            if(nAttr == s.CanGo || nAttr == s.Ending){
                ptRes = ptNext;
            }
        }
    }
    return ptRes;
};
CLabyrinthAI.prototype.canDown = function(ptCurrent){
    var s = this;
    var ptRes = {col:-1,row:-1};
    var col = ptCurrent.col;
    var row = ptCurrent.row;
    if(row < s.m_xLabyrinth.length - 1){
        var ptNext = {col:col,row:row + 1};
        if(!s.isRepeat(ptNext)){
            var nAttr = s.m_xLabyrinth[ptNext.row][ptNext.col];
            if(nAttr == s.CanGo || nAttr == s.Ending){
                ptRes = ptNext;
            }
        }
    }
    return ptRes;
};
CLabyrinthAI.prototype.canLeft = function(ptCurrent){
    var s = this;
    var ptRes = {col:-1,row:-1};
    var col = ptCurrent.col;
    var row = ptCurrent.row;
    if(col > 0){
        var ptNext = {col:col-1,row:row};
        if(!s.isRepeat(ptNext)){
            var nAttr = s.m_xLabyrinth[ptNext.row][ptNext.col];
            if(nAttr == s.CanGo || nAttr == s.Ending){
                ptRes = ptNext;
            }
        }
    }
    return ptRes;
};
CLabyrinthAI.prototype.canRight = function(ptCurrent){
    var s = this;
    var ptRes = {col:-1,row:-1};
    var col = ptCurrent.col;
    var row = ptCurrent.row;
    if(col < s.m_xLabyrinth[0].length - 1){
        var ptNext = {col:col+1,row:row};
        if(!s.isRepeat(ptNext)){
            var nAttr = s.m_xLabyrinth[ptNext.row][ptNext.col];
            if(nAttr == s.CanGo || nAttr == s.Ending){
                ptRes = ptNext;
            }
        }
    }
    return ptRes;
};
CLabyrinthAI.prototype.allRight = function(p){
    if(p.col >= 0 && p.row >= 0){
        return true;
    }else{
        return false;
    }
};
CLabyrinthAI.prototype.findRoute = function(ptCurrent){
    var s = this;
    var nRes = s.NotFoundEnding;
    var ptNext = {col:-1,row:-1};

    s.advance(ptCurrent);
    
    if(s.isEnding(ptCurrent)){
        nRes = s.FoundEnding;
    }else{
        ptNext = s.canUp(ptCurrent);
        if(s.allRight(ptNext)){
            if(s.findRoute(ptNext) == s.FoundEnding){
                nRes = s.FoundEnding;
                return nRes;
            }
        }
        
        ptNext = s.canLeft(ptCurrent);
        if(s.allRight(ptNext)){
            if(s.findRoute(ptNext) == s.FoundEnding){
                nRes = s.FoundEnding;
                return nRes;
            }
        }
        
        ptNext = s.canDown(ptCurrent);
        if(s.allRight(ptNext)){
            if(s.findRoute(ptNext) == s.FoundEnding){
                nRes = s.FoundEnding;
                return nRes;
            }
        }
        
        ptNext = s.canRight(ptCurrent);
        if(s.allRight(ptNext)){
            if(s.findRoute(ptNext) == s.FoundEnding){
                nRes = s.FoundEnding;
                return nRes;
            }
        }
    }
    if(nRes != s.FoundEnding){
        s.back();
    }
    
    return nRes;
};
CLabyrinthAI.prototype.getBeginning = function(){
    var s = this;
    for(var nRow = 0; nRow < s.m_xLabyrinth.length; nRow++){
        var xRow = s.m_xLabyrinth[nRow];
        for(var nCol = 0; nCol < xRow.length; nCol++){
            var n = xRow[nCol];
            if(n == s.Beginning){
                s.m_ptBeginning = {col:nCol,row:nRow};
                break;
            }
        }
    }
};
CLabyrinthAI.prototype.getEnding = function(){
    var s = this;
    for(var nRow = 0; nRow < s.m_xLabyrinth.length; nRow++){
        var xRow = s.m_xLabyrinth[nRow];
        for(var nCol = 0; nCol < xRow.length; nCol++){
            var n = xRow[nCol];
            if(n == s.Ending){
                s.m_ptEnding = {col:nCol,row:nRow};
                break;
            }
        }
    }
};
CLabyrinthAI.prototype.AI = function(data){
    var s = this;
    s.m_xLabyrinth = data;
    s.initAI();
    s.findRoute(s.m_ptBeginning);
    return s.m_vRoute;
};

設計原理和C++版差不多,只是沒有CPoint類而已。

 

雖然這套算法是研究出來了,但是還不能判斷是否為最近路線,因此有待更新。不過以現在的算法,開發一個SLG應該不是問題了。

※感謝我的哥哥與我一起討論其中的原理。

源代碼下載:

http://files.cnblogs.com/yorhom/findRoute.rar


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM