主要功能
通过java代码实现3D迷宫的随机生成, 目前200阶需要1分钟左右, 可以先参考之前的文章2D迷宫生成.
代码实现
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.*;
/**
* 3D迷宫生成, 参照2D迷宫生成
* 由一面面很薄的墙组成, 拆除最少的墙, 使所有的单元格连通
* 阶数 需要拆的墙 总墙数
* 1 1 + 1 1*6 = 6
* 2 8 + 1 4*6+4*3*(2-1) = 24+12=36
* 3 27 + 1 9*6+9*3*(3-1) = 54 + 54 = 108
* 4 64 + 1 16*6+16*3*(4-1) = 96+144 = 240
* ...
* n n^3 + 1 n^2*6 + n^2*3*(n-1) = 3n^2(1+n)
*/
public class MazeGenerate3D {
// 迷宫的阶数
private int sqrt = 2;
// 迷宫的大小
private int size = sqrt * sqrt * sqrt;
// 用于存储每个单元格所属的集合
private int[] array = new int[size];
// 用于存储已经合并过的单元格
private HashSet<Integer> allCell = new HashSet<Integer>();
/**
* 返回 i 所在集合的最大值
*
* @param i 单元格编号
* @return
*/
public int find(int i) {
int result = i;
while (array[result] != -1) {
result = array[result];
}
return result;
}
/**
* 将 i 和 j 所在集合进行合并
*
* @param i 单元格编号
* @param j 单元格编号
*/
public void union(int i, int j) {
int result1 = find(i);
int result2 = find(j);
if (result1 == result2) {
return;
}
if (result1 > result2) {
array[result2] = result1;
allCell.add(result2);
} else {
array[result1] = result2;
allCell.add(result1);
}
}
/**
* 判断是否需要继续拆墙
*
* @return
*/
public boolean isContinue() {
// 第一个单元格和最后一个单元格是否连通
if (find(0) != (size - 1)) {
return true;
}
// 是否所有的单元格都连通
if (allCell.size() < size - 1) {
return true;
}
return false;
}
/**
* 获取所有的可拆的墙, 迷宫由一面面很薄的墙组成, 除了表面的墙, 都是可拆的墙
* 表面的墙都不可以拆除, 除了起始两面墙
* 阶数 需要拆的墙 可拆墙数
* 1 1 + 1 0
* 2 8 + 1 4*3*(2-1) = 12
* 3 27 + 1 9*3*(3-1) = 54
* 4 64 + 1 16*3*(4-1) = 144
* n n^3 + 1 n^2*3*(n-1) = 3*(n-1)*n^2
*/
public List<Wall> getAllWalls() {
// 保存所有可拆的墙
ArrayList<Wall> allWalls = new ArrayList<>();
// 迷宫单元格的标记, 从俯视图的左上角开始, 从左往右, 从上往下, 从前往后依次标记
// 二阶迷宫一共8个单元格, 依次为0,1,2,3, 4,5,6,7
// 一共size个元素
for (int i = 0; i < (size - 1); i++) {
// 右边的单元格
int k = i + 1;
// 下面的单元格
int l = i + sqrt;
// 后面的单元格
int m = i + sqrt*sqrt;
// 排除掉最右边的墙
if (((i + 1) % sqrt) != 0) {
allWalls.add(new MazeGenerate3D.Wall(i, k));
}
// 排除掉最下面的墙
if ((sqrt*sqrt - sqrt) > (i%(sqrt*sqrt))) {
allWalls.add(new MazeGenerate3D.Wall(i, l));
}
// 排除掉最后面的墙
if (i < (size - sqrt*sqrt)) {
allWalls.add(new MazeGenerate3D.Wall(i, m));
}
}
return allWalls;
}
/**
* 随机生成迷宫
*
* @param allWalls list
* @return List<Wall>
*/
public Set<String> generateMaze(List<Wall> allWalls) {
Random random = new Random();
// 用于存储待拆除的墙
HashSet<String> toDelWalls = new HashSet<String>();
// 拆除首尾节点的墙, 这是固定的, 进入迷宫的墙和离开迷宫的墙
toDelWalls.add("1,1,0");
toDelWalls.add(sqrt + "," + (2 * sqrt-1) + "," + (2 * sqrt));
// 初始化各个单元格所属的集合
for (int j = 0; j < size; j++) {
array[j] = -1;
}
int count = 0;
while (isContinue()) {
count++;
// 随机获取一面墙
int currentWall = random.nextInt(allWalls.size());
Wall wall = allWalls.get(currentWall);
int firstCellCode = wall.getFirstCellCode();
int secondCellCode = wall.getSecondCellCode();
// 判断墙对应的两个单元格是否是连通的
if (find(firstCellCode) == find(secondCellCode)) {
// 如果连通,墙不需要拆除
continue;
} else {
// 如果不连通,拆除墙
int maxIndex = allWalls.size() - 1;
allWalls.set(currentWall, allWalls.get(maxIndex));
allWalls.remove(maxIndex);
// 将两个单元格连通
union(firstCellCode, secondCellCode);
// 添加到需要拆除的墙集合里面
toDelWalls.add(wall.getCoordinate());
}
}
System.out.println("count: " + count);
return toDelWalls;
}
/**
* 打印迷宫到控制台
*
* 由于迷宫是3D的, 打印时需要像3D打印机那样一层一层的打印
* 俯视图, 从上往下依次打印
* 有两类墙, 一种与地面平行, 一种与地面垂直
* 例:
* 二阶平行墙俯视图
* _ _
* |X|_|
* |_|_|
* 表示4块平行于地面的墙拼在一起, 打叉的表示被拆掉, 还剩3块墙
*
* 二阶垂直墙俯视图
* _ _
* |_ _|
* |_|_|
* 表示12块垂直于地面的墙拼在一起, 中间上方丢失的那一竖表示被拆掉, 还剩11块墙
*
* @param toDelWalls
*/
public void printMaze(Set<String> toDelWalls) {
// z轴
for (int k = 0; k < 2 * sqrt + 1; k++) {
// 行
for (int i = 0; i < (sqrt + 1); i++) {
// 列
for (int j = 0; j < (2 * sqrt + 1); j++) {
String toPrintStr = "";
String temp = i + "," + j + "," + k;
if (i % 2 == 0) { // 奇数行
if (j % 2 == 0) { //奇数列
if (i == 0) { // 第一行
toPrintStr = " ";
} else {
toPrintStr = "|";
}
} else { // 偶数列
toPrintStr = "_";
}
} else { // 偶数行
if (j % 2 == 0) { //奇数列
toPrintStr = "|";
} else { // 偶数列
toPrintStr = "_";
}
}
if (toDelWalls.contains(temp)) {
if (k % 2 == 0) {
toPrintStr = "X";
} else {
toPrintStr = " ";
}
toDelWalls.remove(temp);
}
System.out.print(toPrintStr);
}
System.out.println();
}
}
}
/**
* 将迷宫保存到TXT文本中
* 由于迷宫是3D的, 打印时需要像3D打印机那样一层一层的打印
* 俯视图, 从上往下依次打印
* 有两类墙, 一种与地面平行, 一种与地面垂直
* 例:
* 二阶平行墙俯视图
* _ _
* |X|_|
* |_|_|
* 表示4块平行于地面的墙拼在一起, 打叉的表示被拆掉, 还剩3块墙
*
* 二阶垂直墙俯视图
* _ _
* |_ _|
* |_|_|
* 表示12块垂直于地面的墙拼在一起, 中间上方丢失的那一竖表示被拆掉, 还剩11块墙
*
* @param toDelWalls
* @param fileName
*/
public void saveToText(Set<String> toDelWalls, String fileName) {
File file = new File(fileName);
try {
Writer writer = new OutputStreamWriter(new FileOutputStream(file, true), "UTF-8");
StringBuilder builder = new StringBuilder();
// z轴
for (int k = 0; k < 2 * sqrt + 1; k++) {
// 行
for (int i = 0; i < (sqrt + 1); i++) {
// 列
for (int j = 0; j < (2 * sqrt + 1); j++) {
String toPrintStr = "";
String temp = i + "," + j + "," + k;
if (i % 2 == 0) { // 奇数行
if (j % 2 == 0) { //奇数列
if (i == 0) { // 第一行
toPrintStr = " ";
} else {
toPrintStr = "|";
}
} else { // 偶数列
toPrintStr = "_";
}
} else { // 偶数行
if (j % 2 == 0) { //奇数列
toPrintStr = "|";
} else { // 偶数列
toPrintStr = "_";
}
}
if (toDelWalls.contains(temp)) {
// 3D迷宫的打印分为两类墙, 一种是与地面平行的墙, 一种是与地面垂直的墙
if (k % 2 == 0) {
toPrintStr = "X";
} else {
toPrintStr = " ";
}
toDelWalls.remove(temp);
}
builder.append(toPrintStr);
}
builder.append("\r\n");
}
}
writer.write(builder.toString());
writer.close();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 保存关于墙所有的信息
*/
public class Wall {
// 墙对应的第一个单元格
private int firstCellCode = 0;
// 墙对应的第二个单元格
private int secondCellCode = 0;
// 这边的坐标以迷宫俯视图的左上角为起点, 向下为x轴, 向右为y轴, 向里为z轴
// 坐标在后面画图会用到
// 横坐标
private int x = 0;
// 纵坐标
private int y = 0;
// 竖坐标
private int z = 0;
// x,y,z组成的坐标系
private String coordinate = "";
public Wall(int firstCellCode, int secondCellCode) {
this.firstCellCode = firstCellCode;
this.secondCellCode = secondCellCode;
if (sqrt * sqrt == (secondCellCode - firstCellCode)) {
this.y = ((secondCellCode % (sqrt * sqrt)) % sqrt) * 2 + 1;
this.z = ((secondCellCode / (sqrt * sqrt)) % sqrt) * 2;
} else if (sqrt == (secondCellCode - firstCellCode)) {
this.y = ((secondCellCode % (sqrt * sqrt)) % sqrt) * 2 + 1;
this.z = (secondCellCode / (sqrt * sqrt)) * 2 + 1;
} else if (1 == (secondCellCode - firstCellCode)) {
this.y = ((secondCellCode % (sqrt * sqrt)) % sqrt) * 2;
this.z = (secondCellCode / (sqrt * sqrt)) * 2 + 1;
}
this.x = (firstCellCode % (sqrt * sqrt)) / sqrt + 1;
this.coordinate = x + "," + y + "," + z;
}
public int getFirstCellCode() {
return firstCellCode;
}
public int getSecondCellCode() {
return secondCellCode;
}
public String getCoordinate() {
return coordinate;
}
}
@Test
public void test() {
long startTime = System.currentTimeMillis();
// 1.获取所有可拆卸的墙的信息
List<Wall> allWalls = getAllWalls();
long getWallTime = System.currentTimeMillis();
System.out.println("getWallTime: " + (getWallTime - startTime) + "ms");
// 2.随机生成迷宫,并记录需要被拆除的墙
Set<String> toDelWalls = generateMaze(allWalls);
long generateMazeTime = System.currentTimeMillis();
System.out.println("generateMazeTime: " + (generateMazeTime - getWallTime) + "ms");
// 3.打印迷宫到控制台
printMaze(toDelWalls);
// 输出迷宫到TXT文本
//saveToText(toDelWalls, "d:" + File.separator + "10.txt");
}
}
打印结果
效果图(2阶立体迷宫)
俯视图, 从上往下, 水平墙,垂直墙依次打印, 一共5层
图片解释
表示4块平行于地面的墙拼在一起, 打叉的表示被拆掉, 还剩3块墙
表示12块垂直于地面的墙拼在一起, 中间上方丢失的那一竖表示被拆掉, 还剩11块墙
另附一百阶3D迷宫某一层效果图, 感觉进去就别想出来了
PS.以后星际时代的游乐园里面肯定会有这个娱乐项目