接下来一段时间,想要研究下随机迷宫生成算法,打算在有空可时候偶尔更新一下这方面的学习过程。随机迷宫的生成算法有很多种,比如递归回溯,递归分割,随机Prime等等。今天是第一次尝试随机迷宫生成,就先试一下用递归的方法通过深度优先搜索来生成随机迷宫。
首先我们来明确一下基本观念,迷宫可以通过一个二维数组来表示,二维数组中的元素就表示存在于迷宫中的位置,他们可能是可以行走的路,也有可能是不能进入的障碍物或者围栏。我们只要通过两种不同的字符就可以标记障碍物和通道,比如我们使用false来表示一个位置是障碍物,而使用true来表示位置可以通行。在下面的例子中我们就沿用这个规定,整个迷宫可以使用一个二维的布尔型数组来表示。
接下来我们确定一下对迷宫的看法,我们认为一个迷宫只能有唯一解,也就是说从起点到终点不会有两条不一样的路线。而遍历迷宫的过程可以被看成是一个拆墙的过程,如果拆了一个墙会导致两个已经被标记为通道的方块连接,那么拆这面墙就是不合法的,这个条件是递归回溯过程中最重要的判定条件。
最后来看一下递归回溯算法的过程:
以上就是深度优先遍历生成随机迷宫的基本步骤,接下来我们看代码实现:
定义一个迷宫类,它的私有数据成员保存着记录迷宫状态的二维数组,查找方向,迷宫尺寸,入口点等信息:
class Maze
{
public:
//构造函数,通过传入的参数创建并创建并初始化二维数组
Maze(int row, int column);
ostream& print();
//设置迷宫入口的内联函数,注意这个入口并不是遍历起点,而是在边界上挖出的一个洞
void setEntry(int x,int y)
{
if (isValidEntry(x,y)) {
mazePtr[x - 1][y - 1] = true;
if (x == 1) {
startX = 2;
startY = y;
}
if (x == row) {
startX = row-1;
startY = y;
}
if (y == 1) {
startX = x;
startY = 2;
}
if (y == column) {
startX = 2;
startY = column-1;
}
//cout << mazePtr[x - 1][y - 1];
}
}
//创建迷宫
bool createMaze();
private:
bool isInRange(int x, int y);
bool isValidEntry(int x, int y);
//递归回溯的算法的核心实现
bool dig(int x, int y);
int startX;
int startY;
int row;
int column;
vector<pair<int,int>> direction = { {1,0},{0,1},{-1,0},{0,-1} };
unique_ptr<bool*[]> mazePtr;
};
然后我们在类外完成对构造函数的定义,他接受行列两个参数来动态创建一个布尔型二维数组,并与此同时初始化为false,允许通过初始化列表来为动态分配的内存初始化是C++11引入的新特性,这里为了方便管理堆内存,采用了智能指针类型unique_ptr来管理动态分配的内存,这种智能指针类型也是C++11新增的:
Maze::Maze(int row, int column):row(row),column(column)
{
mazePtr.reset(new bool*[row]);
for (int i = 0; i < row; i++) {
mazePtr[i] = new bool[column]{false};
}
}
定义成员函数createMaze,这个函数用来执行迷宫生成算法,通过调用另一个私有成员dig来完成:
bool Maze::createMaze()
{
int x = startX;
int y = startY;
if (!dig(x, y)) {
return false;
}
return true;
}
然后就是实现dig函数,正如他的名字所显示的那样,该函数所做的就是从一个起点开始挖掘障碍物,只要条件允许(存在不邻接多个路径的新方块)他总是尽可能的去多挖掘新的方块:
bool Maze::dig(int x, int y)
{
if (!isInRange(x, y)) {
return false;
}
mazePtr[x - 1][y - 1] = true;
//随机改变方向
//random_shuffle(direction.begin(),direction.end());
for (int i = 0; i < 4; i++) {
int tmpX = x + 2 * direction[i].first;
int tmpY = y + 2 * direction[i].second;
if (!isInRange(tmpX, tmpY)) {
continue;
}
if (mazePtr[tmpX-1][tmpY-1] == false) {
mazePtr[x+direction[i].first-1][y+direction[i].second-1] = true;
//print() << endl;
if (!dig(tmpX, tmpY)) {
mazePtr[x + direction[i].first - 1][y + direction[i].second - 1] = false;
continue;
}
}
}
return true;
}
剩下的就是一些提供辅助功能的成员函数了,比如判断指定方块是否超出迷宫边界,计算入口是否合法,通过入口寻找遍历起点,打印迷宫状态等等,他们的实现如下:
bool Maze::isInRange(int x, int y)
{
if (x <= 0 or x >= row) {
return false;
}
if (y <= 0 or y >= column) {
return false;
}
return true;
}
bool Maze::isValidEntry(int x, int y)
{
if ((x == 1 and y == 1) ||
(x == 1 and y == column) ||
(x == row and y == 1) ||
(x == row and y == column)) {
return false;
}
if (x == 1 or x == row or y == 1 or y == column) {
return true;
}
return false;
}
ostream& Maze::print()
{
for (int i = 0; i < row; i++) {
for (int j = 0; j < column; j++) {
cout << mazePtr[i][j] << " ";
}
cout << endl;
}
return cout;
}
调用这段代码可以得到下图这种迷宫:
显然这样生成的迷宫不具备足够的随机性,接着在上面的递归回溯中添加一些小的改变,让每次遍历方向随机选择,这样就可以得到下面这样的代码:
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
class Maze
{
public:
Maze(int row, int column);
ostream& print();
void setEntry(int x,int y)
{
if (isValidEntry(x,y)) {
mazePtr[x - 1][y - 1] = true;
if (x == 1) {
startX = 2;
startY = y;
}
if (x == row) {
startX = row-1;
startY = y;
}
if (y == 1) {
startX = x;
startY = 2;
}
if (y == column) {
startX = 2;
startY = column-1;
}
//cout << mazePtr[x - 1][y - 1];
}
}
bool createMaze();
private:
bool isInRange(int x, int y);
bool isValidEntry(int x, int y);
bool dig(int x, int y);
int startX;
int startY;
int row;
int column;
vector<pair<int,int>> direction = { {1,0},{0,1},{-1,0},{0,-1} };
unique_ptr<bool*[]> mazePtr;
};
bool Maze::isInRange(int x, int y)
{
if (x <= 0 or x >= row) {
return false;
}
if (y <= 0 or y >= column) {
return false;
}
return true;
}
bool Maze::dig(int x, int y)
{
if (!isInRange(x, y)) {
return false;
}
mazePtr[x - 1][y - 1] = true;
//每次遍历四个方向之前,对方向进行一次随机洗牌
random_shuffle(direction.begin(),direction.end());
for (int i = 0; i < 4; i++) {
int tmpX = x + 2 * direction[i].first;
int tmpY = y + 2 * direction[i].second;
if (!isInRange(tmpX, tmpY)) {
continue;
}
if (mazePtr[tmpX-1][tmpY-1] == false) {
mazePtr[x+direction[i].first-1][y+direction[i].second-1] = true;
//print() << endl;
if (!dig(tmpX, tmpY)) {
mazePtr[x + direction[i].first - 1][y + direction[i].second - 1] = false;
continue;
}
}
}
return true;
}
bool Maze::createMaze()
{
int x = startX;
int y = startY;
if (!dig(x, y)) {
return false;
}
return true;
}
bool Maze::isValidEntry(int x, int y)
{
if ((x == 1 and y == 1) ||
(x == 1 and y == column) ||
(x == row and y == 1) ||
(x == row and y == column)) {
return false;
}
if (x == 1 or x == row or y == 1 or y == column) {
return true;
}
return false;
}
ostream& Maze::print()
{
for (int i = 0; i < row; i++) {
for (int j = 0; j < column; j++) {
cout << mazePtr[i][j] << " ";
}
cout << endl;
}
return cout;
}
Maze::Maze(int row, int column):row(row),column(column)
{
mazePtr.reset(new bool*[row]);
for (int i = 0; i < row; i++) {
mazePtr[i] = new bool[column]{false};
}
}
int main(int argc, char*argv[])
{
Maze maze(15,15);
maze.setEntry(1, 2);
//cout << "设置入口后:" << endl;
//maze.print();
maze.createMaze();
maze.print();
system("pause");
return 0;
}
这次结果如下,随机性增强了很多:
这次就先做着写,接下来在慢慢补充改进。