迷宮生成算法

最近做課設時,有一個部分需要用到迷宮的生成算法. 在這裏介紹一種使用深度優先搜索生成迷宮的算法.

個人博客地址

最終的效果

先上幾張效果圖,圖中綠色的表示障礙,灰色表示道路(我的世界既視感).




如何描述迷宮

迷宮其實就是一個複雜的地形圖,在這個地形中有基本的障礙和通道,當然也可以有其他元素。

我們這裏用最簡單的方式描述迷宮——矩陣。迷宮中的地形也只有障礙和通道兩種元素。可以用0和1表示這兩種元素。

因此我們用一個存儲着0和1,M*N大小的矩陣就可以描述迷宮啦!

迷宮的特點

  1. 從設定的起點到終點必須是連通的(否則,還能不能好好玩耍了)
  2. 從起點到終點只有一條通路.(其實也可以有多條,根據實際需要設定)

思路

從迷宮特點的描述有沒有想到什麼?

迷宮就是一個圖,要求任意設定的起點和終點之間是連通的,就是一個 全連通圖.但是如果這個圖的連通度太高,迷宮就沒有難度了,所以我們要求圖中任意頂點之間只有一條路.

什麼樣的圖只有一條路, 無環圖.

所以我們需要的是無環的連通圖,這是什麼?

我們的迷宮就是一個樹,因此迷宮的生成算法就是樹的生成算法,樹的生成算法有深度優先遍歷和廣度優先遍歷, 在這裏使用深度優先.

迷宮的生成過程

1 初始狀態

圖中綠色的表示障礙,灰色表示道路(空白)

由於迷宮四周都是障礙, 圖的寬和高都必須是奇數.

2 迷宮的生成

1 任意選擇一個空白塊, 將該空白塊作爲樹的根結點.

2 從根節點出發隔一個元素塊查找四周(上,下,左,右,四個方向,不包括對角線方向)其他的空白塊.

從該結點出發,四周只有兩個空白塊

從該結點出發,四周有四個空白塊

3 隨機選取其中一個空白塊, 將道路沿該方向拓展, 即把夾在這兩個空白塊之間的障礙塊去掉, 改成空白塊.

把夾在這兩個空白塊之間的障礙塊去掉

4 更新當前結點, 然後從當前結點出發,重複步驟2,3.

5 當遇到一個結點周圍沒有空白塊時, 即沒有可拓展道路的方向時, 回退並更新當前結點, 直至當前結點四周有空白塊, 重複步驟2,3.

該結點周圍沒有空白塊

6 當回退到根節點沒有任何可以拓展的道路時, 算法結束, 迷宮也就生成了.

3 設定起點和終點

選取迷宮中的兩個空白塊作爲迷宮的起點和終點,一個完整的迷宮就誕生了.

核心算法–深度優先

由於這部分算法是程序的一部分,不能完整運行,僅供參考.

在程序中用到了Qt中的容器QVector,可以用STL中的std::vector代替; 用到的qsrand()和qrand()生成隨機數,可以使用C標準庫中的srand()和rand()函數代替.

/*
@ 生成迷宮
*/
void GenerateMaze::Maze(int width, int height)
{
    //初始化矩陣, 申請內存
    maze_matrix_ = new int*[height];
    for(int i=0; i<height; i++)
    {
        maze_matrix_[i] = new int[width];
    }

    for(int i=0; i<height; i++)
    {
        for(int j=0; j<width;j++)
        {
            if(i % 2 == 0 || j % 2==0)
            {
                maze_matrix_[i][j] = 1;  //障礙
            }
            else
            {
                maze_matrix_[i][j] = 0;  //道路(空白)
            }
        }
    }

    qsrand(QTime(0,0,0).secsTo(QTime::currentTime())); //設置隨機數種子

    maze_matrix_[1][1] = 2; //選取(1,1)作爲根節點, 並將根節點的狀態設置成2

    this->generateMaze(1, 1);   //深度優先遍歷

    for(int i=0; i<height; i++)
    {
        for(int j=0; j<width;j++)
        {
            if(maze_matrix_[i][j] == 2)
            {
                maze_matrix_[i][j] = 0; //將狀態爲2的結點重新設置爲0, 表示可通行道路
            }
        }
    }
}


/*
@ brief:深度優先生成迷宮(遞歸實現)
*/
void Maze::generateMaze(int pos_i, int pos_j)
{
    //到達邊界, 返回
    if(pos_j < 0 || pos_j >= width || pos_i < 0 || pos_i >= height)
    {
        return;
    }

    QVector<int> vec = existedRoad((const int**)maze_matrix_, pos_i, pos_j); //查找當前結點四周空白塊

    //四周沒有空白塊, 返回
    if(vec.size() == 0)
    {
        return;
    }

    for(int i=0; i < vec.size();)
    {
        int index = qrand()%vec.size(); //隨機選擇其中一個空白塊

        switch(vec[index])
        {
        case D_LEFT:    //左
            if(maze_matrix_[pos_i][pos_j-2] != 2)
            {
                maze_matrix_[pos_i][pos_j-1] = 2;    //將走過的路徑設爲2, 防止重複經過
                maze_matrix_[pos_i][pos_j-2] = 2;
                this->generateMaze(pos_i, pos_j-2); //更新結點, 遞歸
            }
            break;
        case D_RIGHT:   //右
            if(maze_matrix_[pos_i][pos_j+2] != 2)
            {
                maze_matrix_[pos_i][pos_j+1] = 2;
                maze_matrix_[pos_i][pos_j+2] = 2;
                this->generateMaze(pos_i, pos_j+2); //更新結點, 遞歸
            }
            break;  
        case D_UP:  //上
            if(maze_matrix_[pos_i-2][pos_j] != 2)
            {
                maze_matrix_[pos_i-1][pos_j] = 2;
                maze_matrix_[pos_i-2][pos_j] = 2;
                this->generateMaze(pos_i-2, pos_j); //更新結點, 遞歸
            }
            break;
        case D_DOWN:    //下
            if(maze_matrix_[pos_i+2][pos_j] != 2)
            {
                maze_matrix_[pos_i+1][pos_j] = 2;
                maze_matrix_[pos_i+2][pos_j] = 2;
                this->generateMaze(pos_i+2, pos_j); //更新結點, 遞歸
            }
            break;
        }

        vec.remove(index);  //清空vec
    }
}

/*
@brief: 查找結點周圍的空白塊
*/

const QVector<int> Maze::existedRoad(const int **mat, int i, int j)
{
    QVector<int> vec;

    if(j-2 >= 0 && mat[i][j-2] == 0)
    {
        vec.push_back(D_LEFT);  //左邊有空白塊
    }

    if(j+2 < width && mat[i][j+2] == 0)
    {
        vec.push_back(D_RIGHT); //右邊有空白塊
    }

    if(i-2 >= 0 && mat[i-2][j] == 0)
    {
        vec.push_back(D_UP);    //上邊有空白塊
    }

    if(i+2 < height && mat[i+2][j] == 0)
    {
        vec.push_back(D_DOWN);  //下邊有空白塊
    }

    return vec;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章