您的位置:首页 > 其它

三种迷宫生成算法

2016-01-07 17:02 453 查看
三种迷宫生成算法

递归回溯:

static char block = 'x';
static char way = ' ';

enum DIRECTION {
UP, DOWN, LEFT, RIGHT
};

static List<String> history = new ArrayList<String>();

static Random ran = new Random(System.currentTimeMillis());

/**
*
* 递归回溯(核心思想为打洞)
* 在迷宫中任意选择一个点
* 从这个点朝任意一个方向挖洞,打通下一个点(已走过的点不能被打通)
* 从下个点继续打洞,直到无法某个点无法打通到下个点时,回退到上个点
* 直到回退到初始点时,迷宫完成
*
*/
public static char[][] createMaze() {

int size = 20;
char[][] maze = new char[size * 2 - 1][size * 2 - 1];
for (int i = 0; i < size * 2 - 1; i++) {
for (int j = 0; j < size * 2 - 1; j++) {
if (i % 2 == 0 && j % 2 == 0) {
maze[i][j] = way;
} else {
maze[i][j] = block;
}
}
}

Random ran = new Random(System.currentTimeMillis());

int x = ran.nextInt(size), y = ran.nextInt(size);

history.clear();
digHole(maze, x, y);

return maze;
}

private static void digHole(char[][] maze, int x, int y) {
int size = (maze.length + 1) / 2;

if (x < 0 || y < 0 || x >= size || y >= size) {
return;
}

Set<DIRECTION> dirList = new HashSet<DIRECTION>();

int dir = ran.nextInt(4);

history.add(x + "," + y);

while (dirList.size() < 4) {
// 选择一个方向
while (dirList.contains(DIRECTION.values()[dir])) {
dir = (dir >= 3 ? 0 : dir + 1);
}

int nextX = -1, nextY = -1;

// 定位下个点
DIRECTION direction = DIRECTION.values()[dir];
switch (direction) {
case UP:
nextX = x - 1;
nextY = y;
break;
case DOWN:
nextX = x + 1;
nextY = y;
break;
case LEFT:
nextX = x;
nextY = y - 1;
break;
case RIGHT:
nextX = x;
nextY = y + 1;
break;
}

// 挖洞
if (nextX >= 0 && nextX < size && nextY >= 0 && nextY < size
&& !history.contains(nextX + "," + nextY)) {
maze[nextX * 2][nextY * 2] = way;
try {
switch (direction) {
case UP:
maze[nextX * 2 + 1][nextY * 2] = way;
break;
case DOWN:
maze[nextX * 2 - 1][nextY * 2] = way;
break;
case LEFT:
maze[nextX * 2][nextY * 2 + 1] = way;
break;
case RIGHT:
maze[nextX * 2][nextY * 2 - 1] = way;
break;
}
} catch (Exception e) {

}

// 去下个点继续挖洞
digHole(maze, nextX, nextY);
}

dirList.add(direction);
}

return;
}


运行效果:



递归分割:

static class Rect {
int x1, y1;
int x2, y2;
}

/**
*
* 递归分割(核心思想为筑墙)
* 在一个空白的区域中,随意选择横向或纵向筑一道墙,同时在墙上任意一个位置打一个洞
* 继续在被分隔出来的两个区域筑墙
* 直到所有区域无法筑墙(即在某个方向上,该区域两个边界之间只剩一条通道的宽度)为止
*
*/
public static char[][] createMaze2() {
int size = 20;
char[][] maze = new char[size * 2 - 1][size * 2 - 1];
for (int i = 0; i < size * 2 - 1; i++) {
for (int j = 0; j < size * 2 - 1; j++) {
maze[i][j] = way;
}
}

Rect rect = new Rect();
rect.x1 = 0;
rect.y1 = 0;
rect.x2 = maze.length - 1;
rect.y2 = maze.length - 1;
divide(maze, rect);

return maze;
}

private static void divide(char[][] maze, Rect rect) {
if (rect.x1 == rect.x2 || rect.y1 == rect.y2) {
return;
}

// 取方向:0为横向,1为纵向
int dir = ran.nextInt(2);

// 取分割线
int line = 0;

// 开口
int hole = 0;

// 分离出两个区域
Rect first = new Rect();
Rect second = new Rect();

first.x1 = rect.x1;
first.y1 = rect.y1;

second.x2 = rect.x2;
second.y2 = rect.y2;

if (dir == 0) {
// 随机选择分割线和线上的开口
line = ran.nextInt((rect.x2 - rect.x1) / 2);
hole = ran.nextInt((rect.y2 - rect.y1) / 2 + 1);

// 筑墙
for (int i = rect.y1; i <= rect.y2; i++) {
maze[rect.x1 + line * 2 + 1][i] = block;
}
maze[rect.x1 + line * 2 + 1][rect.y1 + hole * 2] = way;

// 定位两个区域
first.x2 = rect.x1 + line * 2;
first.y2 = rect.y2;

second.x1 = rect.x1 + (line + 1) * 2;
second.y1 = rect.y1;
} else {
line = ran.nextInt((rect.y2 - rect.y1) / 2);
hole = ran.nextInt((rect.x2 - rect.x1) /2 + 1);

for (int i = rect.x1; i <= rect.x2; i++) {
maze[i][rect.y1 + line * 2 + 1] = block;
}
maze[rect.x1 + hole * 2][rect.y1 + line * 2 + 1] = way;

first.x2 = rect.x2;
first.y2 = rect.y1 + line * 2;

second.x1 = rect.x1;
second.y1 = rect.y1 + (line + 1) * 2;
}

// 递归
divide(maze, first);
divide(maze, second);
}


运行效果:



Eller算法:

/**
*
* Eller算法(核心思想为打洞)
* 从第一行开始,横向随机打洞(即为打洞与不打洞二选一),若两个格子为同个区域,则不打洞
* 将连通的格子合并为一个区域
* 接着纵向打洞,保证每个区域都有一个格子与下一行连通
* 一直向下,到达最后一行横向打洞时,需要将所有区域都打通
*
*/
public static char[][] createMaze3(int size) {
char[][] maze = new char[size * 2 - 1][size * 2 - 1];
for (int i = 0; i < size * 2 - 1; i++) {
for (int j = 0; j < size * 2 - 1; j++) {
if (i % 2 == 0 && j % 2 == 0) {
maze[i][j] = way;
} else {
maze[i][j] = block;
}
}
}

// 由于所有区域都与下一行连通,所以只需存储两行的信息即可保证所有区域最终能够通到底部
int[][] check = new int[2][size];
Map<Integer, Boolean> downMap = new HashMap<Integer, Boolean>();
Map<Integer, Integer> downMapIndex = new HashMap<Integer, Integer>();

int index = 0;
int area = 0;
while (index < size) {
// 判断格子是否同一区域,随机打通格子
// 最后一行需要打通所有区域
for (int i = 0; i < size - 1; i++) {
int a = check[index % 2][i];
int b = check[index % 2][i + 1];

// 两个格子属于同一区域
if (a != 0 && b != 0 && a == b) {
continue;
}

// 0:打洞,1:不打洞
if (ran.nextInt(2) == 0) {
maze[index * 2][i * 2 + 1] = way;
if (a == 0) {
check[index % 2][i]  = ++area;
}
check[index % 2][i + 1] = check[index % 2][i];
} else {
// 该格子与其他区域不连通,作为单独一个区域
if (index == size - 1) {
maze[index * 2][i * 2 + 1] = way;
} else if (b == 0) {
check[index % 2][i + 1] = ++area;
}
}
}

// 向下打洞,每个区域至少有一个格子打通下一层
for (int i = 0; i < size; i++) {
if (ran.nextInt(2) == 0) {
try {
// 打洞,将格子合并到区域
maze[index * 2 + 1][i * 2] = way;
check[(index + 1) % 2][i] = check[index % 2][i];
downMap.put(check[index % 2][i], true);
} catch (Exception e) {

}
} else {
// 不打洞,记录该区域未连通下行时的最后一个格子
check[(index + 1) % 2][i] = 0;
if (!downMap.containsKey(check[index % 2][i]) || !downMap.get(check[index % 2][i])) {
downMap.put(check[index % 2][i], false);
downMapIndex.put(check[index % 2][i], i);
}
}
}

// 打通未与下一层连通的区域
for (Map.Entry<Integer, Boolean> entity : downMap.entrySet()) {
if (!entity.getValue()) {
int col = downMapIndex.get(entity.getKey());
try {
maze[index * 2 + 1][col * 2] = way;
check[(index + 1) % 2][col] = check[index % 2][col];
} catch (Exception e) {

}
}
}

downMap.clear();
downMapIndex.clear();

index++;
}

return maze;
}


运行效果:



打印迷宫:

public static void printMaze(char[][] maze) {
int size = (maze.length + 1) / 2;
for (int i = 0; i <= size * 2; i++) {
if (i == 1) {
System.out.print("  ");
} else {
System.out.print("x ");
}
}
System.out.println();
for (int i = 0; i < size * 2 - 1; i++) {
for (int j = 0; j < size * 2 - 1; j++) {
if (j == 0) {
System.out.print("x ");
}
System.out.print(maze[i][j] + " ");
if (j == size * 2 - 2) {
System.out.print("x ");
}
}
System.out.println();
}
for (int i = 0; i <= size * 2; i++) {
if (i == size * 2 - 1) {
System.out.print("  ");
} else {
System.out.print("x ");
}
}
System.out.println();
}


递归回溯法使用递归方式实现容易造成栈溢出,所以重新实现了递归回溯:

public static char[][] createMazeX(int size) {
char[][] maze = new char[size * 2 - 1][size * 2 - 1];
for (int i = 0; i < size * 2 - 1; i++) {
for (int j = 0; j < size * 2 - 1; j++) {
if (i % 2 == 0 && j % 2 == 0) {
maze[i][j] = way;
} else {
maze[i][j] = block;
}
}
}

Random ran = new Random(System.currentTimeMillis());

int sx = ran.nextInt(size), sy = ran.nextInt(size);
int x = sx, y = sy;

Set<String> history = new HashSet<String>();
Set<DIRECTION> dirList = new HashSet<DIRECTION>();
Deque<String> step = new ArrayDeque<String>();
while (true) {
while (dirList.size() < 4) {
history.add(x + "," + y);
// 选择一个方向
int dir = ran.nextInt(4);
while (dirList.contains(DIRECTION.values()[dir])) {
dir = (dir >= 3 ? 0 : dir + 1);
}

int nextX = -1, nextY = -1;

// 定位下个点
DIRECTION direction = DIRECTION.values()[dir];
switch (direction) {
case UP:
nextX = x - 1;
nextY = y;
break;
case DOWN:
nextX = x + 1;
nextY = y;
break;
case LEFT:
nextX = x;
nextY = y - 1;
break;
case RIGHT:
nextX = x;
nextY = y + 1;
break;
}

// 挖洞
if (nextX >= 0 && nextX < size && nextY >= 0 && nextY < size
&& !history.contains(nextX + "," + nextY)) {
try {
switch (direction) {
case UP:
maze[nextX * 2 + 1][nextY * 2] = way;
break;
case DOWN:
maze[nextX * 2 - 1][nextY * 2] = way;
break;
case LEFT:
maze[nextX * 2][nextY * 2 + 1] = way;
break;
case RIGHT:
maze[nextX * 2][nextY * 2 - 1] = way;
break;
}
} catch (Exception e) {

}

// 去下个点继续挖洞
step.push(x + "," + y);

x = nextX;
y = nextY;

dirList.clear();

continue;
} else {
dirList.add(direction);
}
}

String pre = step.pop();
x = Integer.parseInt(pre.split(",")[0]);
y = Integer.parseInt(pre.split(",")[1]);

dirList.clear();

if (x == sx && y == sy) {
break;
}
}

return maze;
}


参考:
http://weblog.jamisbuck.org/2010/12/27/maze-generation-recursive-backtracking
http://weblog.jamisbuck.org/2011/1/12/maze-generation-recursive-division-algorithm
http://weblog.jamisbuck.org/2010/12/29/maze-generation-eller-s-algorithm
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: