最近做課設時,有一個部分需要用到迷宮的生成算法. 在這裏介紹一種使用深度優先搜索生成迷宮的算法.
最終的效果
先上幾張效果圖,圖中綠色的表示障礙,灰色表示道路(我的世界既視感).
如何描述迷宮
迷宮其實就是一個複雜的地形圖,在這個地形中有基本的障礙和通道,當然也可以有其他元素。
我們這裏用最簡單的方式描述迷宮——矩陣。迷宮中的地形也只有障礙和通道兩種元素。可以用0和1表示這兩種元素。
因此我們用一個存儲着0和1,M*N大小的矩陣就可以描述迷宮啦!
迷宮的特點
- 從設定的起點到終點必須是連通的(
否則,還能不能好好玩耍了) - 從起點到終點只有一條通路.(其實也可以有多條,根據實際需要設定)
思路
從迷宮特點的描述有沒有想到什麼?
迷宮就是一個圖,要求任意設定的起點和終點之間是連通的,就是一個 全連通圖.但是如果這個圖的連通度太高,迷宮就沒有難度了,所以我們要求圖中任意頂點之間只有一條路.
什麼樣的圖只有一條路, 無環圖.
所以我們需要的是無環的連通圖,這是什麼? 樹
我們的迷宮就是一個樹,因此迷宮的生成算法就是樹的生成算法,樹的生成算法有深度優先遍歷和廣度優先遍歷, 在這裏使用深度優先.
迷宮的生成過程
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;
}