随机迷宫
- 迷宫生成算法也有很多种,也正是由于结果随机,反过来讲就意味着该问题由无数种解,所以它完全可以利用回溯的思想。
- 常见算法由三种:
- 十字分割法
- 随机prim算法
- 深度优先算法
十字分割法
- 先画一个十字分成四个部分
- 在三面墙上打洞
- 再在每个子部分中重复这一步骤,直至空间不够分割(这个值需要我们自行设置)
十字分割法生成的迷宫会形成一个一个大小不一的房间,适合制作RPG游戏地图。
代码
import java.util.Random;
public class RandomMaze {
private int row, col;
private Random random;
private boolean[][] maze;
RandomMaze(int row, int col) {
this.row = row;
this.col = col;
this.random = new Random();
this.maze = initialMaze();
}
/**
* 根据给定的高和宽生成一个初始迷宫,初始迷宫只是在没有任何阻碍的迷宫外侧添加一圈墙壁
*/
private boolean[][] initialMaze() {
if (row % 2 == 0 || col % 2 == 0) {
System.out.println("必须为奇数");
return null;
}
boolean[][] m = new boolean[this.row][this.col];
for (int i = 0; i < this.col; i++) {
m[0][i] = true;
m[this.row - 1][i] = true;
}
for (int i = 0; i < this.row; i++) {
m[i][0] = true;
m[i][this.col - 1] = true;
}
return m;
}
public void makeMaze() {
drawMaze(1, 1, this.row - 2, this.col - 2);
}
/**
* 迷宫生成算法,采用递归方式实现,随机画横竖两条线,然后在线上随机开门
* 左上角为入口
* 右下角为出口
* 墙的坐标只能是偶数,路的坐标只能是奇数,如何保证奇偶是本算法的难点
*/
private void drawMaze(int row1, int col1, int row2, int col2) {
if (row1 == row2 || col1 == col2) return;
int row3, col3;
//横着画线,在偶数位置画线
row3 = row1 + random.nextInt((row2 - row1) / 2) * 2 + 1;
for (int i = col1; i <= col2; i++) {
maze[row3][i] = true;
}
//竖着画一条线,在偶数位置画线
col3 = col1 + random.nextInt((col2 - col1) / 2) * 2 + 1;
for (int i = row1; i <= row2; i++) {
maze[i][col3] = true;
}
//开门
switch (random.nextInt(4)) {
case 0:
//digging(row3, col1, row3, col3 - 1);//left
digging(row3 + 1, col3, row2, col3);//down
digging(row3, col3 + 1, row3, col2);//right
digging(row1, col3, row3, col3);//up
break;
case 1:
digging(row3, col1, row3, col3 - 1);//left
//digging(row3 + 1, col3, row2, col3);//down
digging(row3, col3 + 1, row3, col2);//right
digging(row1, col3, row3, col3);//up
break;
case 2:
digging(row3, col1, row3, col3 - 1);//left
digging(row3 + 1, col3, row2, col3);//down
//digging(row3, col3 + 1, row3, col2);//right
digging(row1, col3, row3, col3);//up
break;
case 3:
digging(row3, col1, row3, col3 - 1);//left
digging(row3 + 1, col3, row2, col3);//down
digging(row3, col3 + 1, row3, col2);//right
//digging(row1, col3, row3, col3);//up
break;
default:
break;
}
//递归
//左上角
drawMaze(row1, col1, row3 - 1, col3 - 1);
//右上角
drawMaze(row1, col3 + 1, row3 - 1, col2);
//右下角
drawMaze(row3 + 1, col3 + 1, row2, col2);
//左下角
drawMaze(row3 + 1, col1, row2, col3 - 1);
}
/**
* 从起点到终点随机打一个洞,道路坐标只能是奇数
*/
private void digging(int x1, int y1, int x2, int y2) {
int pos;
if (x1 == x2) {//横线打洞,坐标只能是奇数
pos = y1 + random.nextInt((y2 - y1) / 2 + 1) * 2;
maze[x1][pos] = false;
}
if (y1 == y2) {//竖线打洞
pos = x1 + random.nextInt((x2 - x1) / 2 + 1) * 2;
maze[pos][y1] = false;
}
}
public void showMaze() {
for (int i = 0; i < maze.length; i++) {
for (int j = 0; j < maze[i].length; j++) {
System.out.print((this.maze[i][j]) ? "X " : " ");
}
System.out.println();
}
}
public static void main(String[] args) {
RandomMaze rMaze = new RandomMaze(21, 23);//设置迷宫大小
if (rMaze.maze != null) {
rMaze.makeMaze();
rMaze.showMaze();
}
}
}
重点理解
- 十字分割法是递归算法,不是回溯算法,这里注意体会一下。
- 我们规定整个迷宫最外围由墙壁包裹,所以迷宫的长与宽必须是奇数,且最小的大小是3X3
- 墙的坐标只能是偶数,路的坐标只能是奇数,如何保证奇偶是本算法的难点
- 如果每次递归都只在三面墙上打洞,则迷宫走法只有一个解,这在实际应用中意义不大,如果想要有多种走法,降低迷宫难度,则可以考虑有时候在四面墙上打洞,使得迷宫存在回路。
结果
X X X X X X X X X X X X X X X X X X X X X X X
X X
X X X X X X X X X X X X X X X X X X X X
X X X
X X X X X X X X X X X X X X X X X X X X X X
X X X X X X
X X X X X X X X X X X X X X X X X
X X X
X X X X X X X X X X X X X X X X X X X X
X X X X X X X X X X
X X X X X X X X X X X X
X X X X X X X X X X X
X X X X X X X X X X X X X X
X X X X X X
X X X X X X X X X X X X X X X X X
X X X X X X X X
X X X X X X X X X X X X X X X X X
X X X X X X X
X X X X X X X X X X X X X X X X
X X X X X
X X X X X X X X X X X X X X X X X X X X X X X