我正在尝试创建一个特殊的完美迷宫生成器。
我要处理的不是由房间和墙壁组成的标准案例,而是要处理由单元格填充的网格,在那里我可以从某些单元格中删除模块:
我使用DFS算法来挖掘路径迷宫,但找不到确保两个给定单元已连接的方法。
正常情况从这里开始
+-+-+
| | |
+-+-+
| | |
+-+-+
到这里
+-+-+
| | |
+ + +
| |
+-+-+
就我而言,我试图将左上角的单元格连接到右下角的单元格:
##
##
到这里
.#
..
或这里
..
#.
但不在这里(因为右下角的单元格被阻止了)
..
.#
而不是这里(两个单元未连接)
.#
#.
而不是这里(迷宫不是完美的,细胞通过多条路径连接)
..
..
还有两个8x8的示例:
好人(完美的迷宫,并且从左上角的单元格到右下角的单元格都有一条路径):
..#.....
.#.#.##.
.......#
.#.#.##.
.##...#.
..#.#...
.##.#.#.
...###..
一个错误(完美的迷宫,但是从左上角的单元格到右下角的单元格没有路径):
...#....
.##..#.#
....##..
#.#...##
#..##...
..#..#.#
#...#...
##.###.#
答案 0 :(得分:3)
使用两步过程来生成符合您条件的迷宫看起来是相当合理的:
生成一个随机迷宫,而不考虑是否有可能从左上角到达右下角。
重复步骤(1),直到找到右下角的路径。
我已经使用两种策略对此进行了编码,一种基于随机深度优先搜索,另一种基于随机宽度优先搜索。在大小为100×100的网格上进行的随机深度优先搜索会生成迷宫,其中迷离右下角可在82%的时间内从左上角到达。通过随机的广度优先搜索,在100×100网格上的成功率约为70%。因此,该策略确实确实可行;您将平均需要使用DFS生成约1.2迷宫和使用BFS生成约1.4迷宫,然后才能找到有效的迷宫。
我用来生成无循环迷宫的机制是基于对常规BFS和DFS想法的概括。在这两种算法中,我们选择的位置是(1)我们尚未访问过但(2)与我们所拥有的位置相邻,然后将新位置添加到其中,并将先前的位置作为其父级。即,新添加的位置最终恰好与先前访问的单元之一相邻。我通过使用以下规则适应了这个想法:
如果一个完整的单元格与一个以上的空单元格相邻,请勿将其转换为一个空单元格。
此规则可确保我们永远不会得到任何循环(如果某个物体与两个或多个空位置相邻并且我们将其清空了,我们将通过到达第一个位置,然后移至新清空的正方形,然后再创建一个循环),移至第二个位置。
以下是使用DFS方法生成的30×30迷宫样本:
.#.........#...#.#....#.#..##.
.#.#.#.#.#.#.#.....##....#....
..#...#..#.#.##.#.#.####.#.#.#
#..#.##.##.#...#..#......#.#..
.#..#...#..####..#.#.####..##.
...#..##..#.....#..#....##..#.
.##.#.#.#...####..#.###...#.#.
..#.#.#.###.#....#..#.#.#..##.
#.#...#....#..#.###....###....
...#.###.#.#.#...#..##..#..#.#
.#....#..#.#.#.#.#.#..#..#.#..
..####..#..###.#.#...###..#.#.
.#.....#.#.....#.########...#.
#..#.##..#######.....#####.##.
..##...#........####..###..#..
.#..##..####.#.#...##..#..#..#
..#.#.#.#....#.###...#...#..#.
.#....#.#.####....#.##.#.#.#..
.#.#.#..#.#...#.#...#..#.#...#
.#..##.#..#.#..#.##..##..###..
.#.#...##....#....#.#...#...#.
...#.##...##.####..#..##..##..
#.#..#.#.#.......#..#...#..#.#
..#.#.....#.####..#...##..##..
##..###.#..#....#.#.#....#..#.
...#...#..##.#.#...#####...#..
.###.#.#.#...#.#.#..#...#.#..#
.#...#.##..##..###.##.#.#.#.##
.#.###..#.##.#....#...#.##...#
......#.......#.#...#.#....#..
这是使用BFS生成的30×30迷宫样本:
.#..#..#...#......#..##.#.....
..#.#.#.#.#..#.##...#....#.#.#
#...#.......###.####..##...#.#
.#.#..#.#.##.#.......#.#.#..#.
.....#..#......#.#.#.#..#..##.
#.#.#.###.#.##..#.#....#.#....
..##.....##..#.##...##.#...#.#
#....#.#...#..##.##...#.#.##..
.#.#..##.##..##...#.#...##...#
....#...#..#....#.#.#.##..##..
#.##..#.##.##.##...#..#..##..#
....#.##.#..#...#.####.#...#..
.#.##......#..##.#.#.....#..#.
#....#.#.#..#........#.#.#.##.
.#.###..#..#.#.##.#.#...####..
.#.#...#.#...#..#..###.#.#...#
....##.#.##.#..#.####.....#.#.
.#.#.......###.#.#.#.##.##....
#..#.#.#.##.#.#........###.#.#
.#..#..#........##.#.####..#..
...#.#.#.#.#.##.#.###..#.##..#
#.#..#.##..#.#.#...#.#.....#..
....#...##.#.....#.....##.#..#
#.#.#.##...#.#.#.#.#.##..#.##.
...#..#..##..#..#...#..#.#....
#.#.#.##...#.##..##...#....#.#
..#..#...##....##...#...#.##..
#...#..#...#.#..#.#.#.#..#...#
..#..##..##..#.#..#..#.##.##..
#.#.#...#...#...#..#........#.
有趣的是,这是我用来生成这些数字和迷宫的代码。首先,DFS代码:
#include <iostream>
#include <algorithm>
#include <set>
#include <vector>
#include <string>
#include <random>
using namespace std;
/* World Dimensions */
const size_t kNumRows = 30;
const size_t kNumCols = 30;
/* Location. */
using Location = pair<size_t, size_t>; // (row, col)
/* Adds the given point to the frontier, assuming it's legal to do so. */
void updateFrontier(const Location& loc, vector<string>& maze, vector<Location>& frontier,
set<Location>& usedFrontier) {
/* Make sure we're in bounds. */
if (loc.first >= maze.size() || loc.second >= maze[0].size()) return;
/* Make sure this is still a wall. */
if (maze[loc.first][loc.second] != '#') return;
/* Make sure we haven't added this before. */
if (usedFrontier.count(loc)) return;
/* All good! Add it in. */
frontier.push_back(loc);
usedFrontier.insert(loc);
}
/* Given a location, adds that location to the maze and expands the frontier. */
void expandAt(const Location& loc, vector<string>& maze, vector<Location>& frontier,
set<Location>& usedFrontier) {
/* Mark the location as in use. */
maze[loc.first][loc.second] = '.';
/* Handle each neighbor. */
updateFrontier(Location(loc.first, loc.second + 1), maze, frontier, usedFrontier);
updateFrontier(Location(loc.first, loc.second - 1), maze, frontier, usedFrontier);
updateFrontier(Location(loc.first + 1, loc.second), maze, frontier, usedFrontier);
updateFrontier(Location(loc.first - 1, loc.second), maze, frontier, usedFrontier);
}
/* Chooses and removes a random element of the frontier. */
Location sampleFrom(vector<Location>& frontier, mt19937& generator) {
uniform_int_distribution<size_t> dist(0, frontier.size() - 1);
/* Pick our spot. */
size_t index = dist(generator);
/* Move it to the end and remove it. */
swap(frontier[index], frontier.back());
auto result = frontier.back();
frontier.pop_back();
return result;
}
/* Returns whether a location is empty. */
bool isEmpty(const Location& loc, const vector<string>& maze) {
return loc.first < maze.size() && loc.second < maze[0].size() && maze[loc.first][loc.second] == '.';
}
/* Counts the number of empty neighbors of a given location. */
size_t neighborsOf(const Location& loc, const vector<string>& maze) {
return !!isEmpty(Location(loc.first - 1, loc.second), maze) +
!!isEmpty(Location(loc.first + 1, loc.second), maze) +
!!isEmpty(Location(loc.first, loc.second - 1), maze) +
!!isEmpty(Location(loc.first, loc.second + 1), maze);
}
/* Returns whether a location is in bounds. */
bool inBounds(const Location& loc, const vector<string>& world) {
return loc.first < world.size() && loc.second < world[0].size();
}
/* Runs a recursive DFS to fill in the maze. */
void dfsFrom(const Location& loc, vector<string>& world, mt19937& generator) {
/* Base cases: out of bounds? Been here before? Adjacent to too many existing cells? */
if (!inBounds(loc, world) || world[loc.first][loc.second] == '.' ||
neighborsOf(loc, world) > 1) return;
/* All next places. */
vector<Location> next = {
{ loc.first - 1, loc.second },
{ loc.first + 1, loc.second },
{ loc.first, loc.second - 1 },
{ loc.first, loc.second + 1 }
};
shuffle(next.begin(), next.end(), generator);
/* Mark us as filled. */
world[loc.first][loc.second] = '.';
/* Explore! */
for (const Location& nextStep: next) {
dfsFrom(nextStep, world, generator);
}
}
/* Generates a random maze. */
vector<string> generateMaze(size_t numRows, size_t numCols, mt19937& generator) {
/* Create the maze. */
vector<string> result(numRows, string(numCols, '#'));
/* Build the maze! */
dfsFrom(Location(0, 0), result, generator);
return result;
}
int main() {
random_device rd;
mt19937 generator(rd());
/* Run some trials. */
size_t numTrials = 0;
size_t numSuccesses = 0;
for (size_t i = 0; i < 10000; i++) {
numTrials++;
auto world = generateMaze(kNumRows, kNumCols, generator);
/* Can we get to the bottom? */
if (world[kNumRows - 1][kNumCols - 1] == '.') {
numSuccesses++;
/* Print the first maze that works. */
if (numSuccesses == 1) {
for (const auto& row: world) {
cout << row << endl;
}
cout << endl;
}
}
}
cout << "Trials: " << numTrials << endl;
cout << "Successes: " << numSuccesses << endl;
cout << "Percent: " << (100.0 * numSuccesses) / numTrials << "%" << endl;
cout << endl;
return 0;
}
接下来,BFS代码:
#include <iostream>
#include <algorithm>
#include <set>
#include <vector>
#include <string>
#include <random>
using namespace std;
/* World Dimensions */
const size_t kNumRows = 30;
const size_t kNumCols = 30;
/* Location. */
using Location = pair<size_t, size_t>; // (row, col)
/* Adds the given point to the frontier, assuming it's legal to do so. */
void updateFrontier(const Location& loc, vector<string>& maze, vector<Location>& frontier,
set<Location>& usedFrontier) {
/* Make sure we're in bounds. */
if (loc.first >= maze.size() || loc.second >= maze[0].size()) return;
/* Make sure this is still a wall. */
if (maze[loc.first][loc.second] != '#') return;
/* Make sure we haven't added this before. */
if (usedFrontier.count(loc)) return;
/* All good! Add it in. */
frontier.push_back(loc);
usedFrontier.insert(loc);
}
/* Given a location, adds that location to the maze and expands the frontier. */
void expandAt(const Location& loc, vector<string>& maze, vector<Location>& frontier,
set<Location>& usedFrontier) {
/* Mark the location as in use. */
maze[loc.first][loc.second] = '.';
/* Handle each neighbor. */
updateFrontier(Location(loc.first, loc.second + 1), maze, frontier, usedFrontier);
updateFrontier(Location(loc.first, loc.second - 1), maze, frontier, usedFrontier);
updateFrontier(Location(loc.first + 1, loc.second), maze, frontier, usedFrontier);
updateFrontier(Location(loc.first - 1, loc.second), maze, frontier, usedFrontier);
}
/* Chooses and removes a random element of the frontier. */
Location sampleFrom(vector<Location>& frontier, mt19937& generator) {
uniform_int_distribution<size_t> dist(0, frontier.size() - 1);
/* Pick our spot. */
size_t index = dist(generator);
/* Move it to the end and remove it. */
swap(frontier[index], frontier.back());
auto result = frontier.back();
frontier.pop_back();
return result;
}
/* Returns whether a location is empty. */
bool isEmpty(const Location& loc, const vector<string>& maze) {
return loc.first < maze.size() && loc.second < maze[0].size() && maze[loc.first][loc.second] == '.';
}
/* Counts the number of empty neighbors of a given location. */
size_t neighborsOf(const Location& loc, const vector<string>& maze) {
return !!isEmpty(Location(loc.first - 1, loc.second), maze) +
!!isEmpty(Location(loc.first + 1, loc.second), maze) +
!!isEmpty(Location(loc.first, loc.second - 1), maze) +
!!isEmpty(Location(loc.first, loc.second + 1), maze);
}
/* Generates a random maze. */
vector<string> generateMaze(size_t numRows, size_t numCols, mt19937& generator) {
/* Create the maze. */
vector<string> result(numRows, string(numCols, '#'));
/* Worklist of free locations. */
vector<Location> frontier;
/* Set of used frontier sites. */
set<Location> usedFrontier;
/* Seed the starting location. */
expandAt(Location(0, 0), result, frontier, usedFrontier);
/* Loop until there's nothing left to expand. */
while (!frontier.empty()) {
/* Select a random frontier location to expand at. */
Location next = sampleFrom(frontier, generator);
/* If this spot has exactly one used neighbor, add it. */
if (neighborsOf(next, result) == 1) {
expandAt(next, result, frontier, usedFrontier);
}
}
return result;
}
int main() {
random_device rd;
mt19937 generator(rd());
/* Run some trials. */
size_t numTrials = 0;
size_t numSuccesses = 0;
for (size_t i = 0; i < 10000; i++) {
numTrials++;
auto world = generateMaze(kNumRows, kNumCols, generator);
/* Can we get to the bottom? */
if (world[kNumRows - 1][kNumCols - 1] == '.') {
numSuccesses++;
/* Print the first maze that works. */
if (numSuccesses == 1) {
for (const auto& row: world) {
cout << row << endl;
}
cout << endl;
}
}
}
cout << "Trials: " << numTrials << endl;
cout << "Successes: " << numSuccesses << endl;
cout << "Percent: " << (100.0 * numSuccesses) / numTrials << "%" << endl;
cout << endl;
return 0;
}
希望这会有所帮助!
答案 1 :(得分:0)
下面,我描述一种构建完美迷宫的简单方法。
这个想法是,您拥有三种类型的单元:封闭单元,开放单元和边界单元。
此图显示了开放式,封闭式和边界单元。
+--+--+--+--+--+--+--+--+--+--+
|** **|FF| | | | | | | |
+--+ +--+--+--+--+--+--+--+--+
|FF|**|FF| | | | | | | |
+--+ +--+--+--+--+--+--+--+--+
|** **|FF| | | | | | | |
+ +--+--+--+--+--+--+--+--+--+
|**|FF|FF|FF| | | | | | |
+ +--+--+--+--+--+--+--+--+--+
|** ** ** **|FF| | | | | |
+--+--+--+ +--+--+--+--+--+--+
|FF|FF|FF|**|FF| | | | | |
+--+--+--+--+--+--+--+--+--+--+
| | |FF|FF| | | | | | |
+--+--+--+--+--+--+--+--+--+--+
| | | | | | | | | | |
+--+--+--+--+--+--+--+--+--+--+
| | | | | | | | | | |
+--+--+--+--+--+--+--+--+--+--+
| | | | | | | | | | |
+--+--+--+--+--+--+--+--+--+--+
其中带有“ **”的单元格处于打开状态。其中带有“ FF”的单元格是边界单元格。空白单元格是封闭的单元格。
这个想法是从网格中每个封闭的单元格开始。
然后,创建一个最初为空的单元格列表。那是你的边境。
打开起始单元格和相邻单元格之一,并将与这两个单元格相邻的所有单元格添加到边界。因此,如果左上角是起始单元格,则前两行是
。+--+--+--+--+--+--+--+--+--+--+
|** **| | | | | | | | |
+--+--+--+--+--+--+--+--+--+--+
| | | | | | | | | | |
您的边界数组包含{[0,2],[1,0],[1,1]}
。
现在,执行以下循环直到边界数组为空:
保证创建一个从头到尾都有一条路径的迷宫。
如果您不想打开图中的所有单元格,请修改程序以在从边界中选择并打开Finish单元格时停止。
时间复杂度为O(高度*宽度)。我记得,边界数组将达到的最大大小为(2*height*width)/3
。在实践中,我从未见过它这么大。