自动生成迷宫

知识点

①基于rank优化的并查集,用来检测两点是否相连,提高根节点查找效率

②最小生成树,通过随机权值,生成一个随机迷宫

设计思想

可以初始化一个网格地图,然后逐渐打通其中的墙,这里以5*5网格为例,“ ”表示结点,“#”表示墙;

打通前:

可以看出,这里一共有25个结点,如果要形成迷宫,需要打破24堵结点之间的墙;

创建边Edge类,存储边的起点和终点以及权值,每个结点只和自己在网格地图里上下左右相邻结点之间存在带权边,所以这里一共有5*4*2条带权边,权值随机在0~99999之间,然后存储根据Kruskal算法得到最小生成树(每个结点都与其他结点连通并且树的总权值最小)里的每一条边;根据边集合可以知道需要打通那些结点之间的墙,打通完24堵墙,迷宫生成完毕。

打通后:

具体实现

边Edge类:

主要用来存储结点与结点之间的权值,start起点,end终点,value边的权值

扫描二维码关注公众号,回复: 5020527 查看本文章
package 自动生成迷宫;

public class Edge{

	int start;
	int end;
	int value;
	
	public Edge(){
		
	}
	
	public Edge(int start,int end,int value){
		this.start = start;
		this.end = end;
		this.value = value;
	}

	
}

并查集Union类:

主要用来检测两个结点是否连通,连接两个结点,查找指定结点的根节点

package 自动生成迷宫;

public class Union {

	int count;
	int[] parent;//记录根节点
	int[] rank;//层数
	
	public Union(int count){
		this.count = count;
		parent = new int[count];
		rank = new int[count];
		for(int i=0;i<count;i++){
			parent[i] = i;
			rank[i] = 1;
		}
	}
	
	//返回根节点的同时进行路径压缩
	public int find(int p){
		while(parent[p]!=p){
			p = parent[p];
			parent[p] = parent[parent[p]];
		}
		return parent[p];
	}
	
	//返回两元素是否相连
	public boolean isConnected(int p,int q){
		return find(p)==find(q);
	}
	
	//连接两元素
	public void union(int p,int q){
		int pRoot = find(p);
		int qRoot = find(q);
		if(pRoot == qRoot){
			return ;
		}
		if(rank[pRoot]<rank[qRoot]){
			parent[pRoot] = qRoot;
		}else if(rank[pRoot]>rank[qRoot]){
			parent[qRoot] = pRoot;			
		}else{
			parent[pRoot] = qRoot;
			rank[qRoot]++;
		}
	}
	
}

地图工具MapUtil类 

用来生成迷宫数组,实现过程略复杂,简单的划分可以分为以下几步;

①先将网格地图中最上面横边和最左边纵边涉及到的边添加到list中;

②遍历网格中剩下的结点,将其与相邻左边和相邻上面两个结点相连的两条边添加到list中,完成对网格所有结点带权边的信息存储;

③利用Kruskal得到最小生成树,将树中的每条边添加到map中;

④声明大小为(2*row+1)*(2*col+1)的二维数组,用来存储迷宫信息,值为1代表墙,值为-1代表通道;先对每个结点所在位置留空(网格结点分布在奇数行、奇数列上);然后从网格数组的第2列开始对每个结点右侧的墙进行初始化(最左侧的墙先不管);再对网格地图横向铺满的墙(偶数行)进行初始化;最后对最左侧的纵向墙初始化,完成网格墙效果(未连通状态)

⑤对网格墙进行打通:遍历map,得到需要打通的边edge,通过edge可以得到边起点和边终点的结点编号;如果两点相差1,说明左右连通,否则上下连通;start/列数col得到start在纯结点构成的网格中是第x行,start%列数col得到start在纯结点构成的网格中是第y列;在这里保证了start编号一定比end小,如果是左右连通,array1[2*x+1][2*y+2]置空,如果是上下连通,array1[2*x+2][2*y+1]置空(原因:array1在第④步结束时,已经构成了未连通网格地图,在这里只需要对墙进行打通操作就可以了);返回数组array1得到迷宫信息。

package 自动生成迷宫;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;

public class MapUtil {
	Union unionUtil;
	ArrayList<Edge> list;
	ArrayList<Edge> map;
	int row;
	int col;
	
	public MapUtil(int row,int col){
		this.row = row;
		this.col = col;
		unionUtil = new Union(row*col);
		list = new ArrayList<Edge>();
		map = new ArrayList<Edge>();
		init();		
	}
	
	public void init(){
		for(int i=0;i<col-1;i++){
			list.add(new Edge(i,i+1,(int) (Math.random()*100000)));
		}
		
		for(int i=0;i<row-1;i++){
			list.add(new Edge(i*col,(i+1)*col,(int) (Math.random()*100000)));
		}
		
		for(int i=1;i<row;i++){
			for(int j=1;j<col;j++){
				list.add(new Edge(i*col+j-1,i*col+j,(int) (Math.random()*100000)));
				list.add(new Edge((i-1)*col+j,i*col+j,(int) (Math.random()*100000)));
			}
		}
		
		Collections.sort(list,new Comparator<Edge>() {
			 @Override
				public int compare(Edge o1, Edge o2) {
					if(o1.value<o2.value)
						return -1;
					else
						return 1;
				}
		});//排序
		
		int count = 0;
		//最小生成树
		for(Edge edge:list){
			int start = edge.start;
			int end = edge.end;
			if(!unionUtil.isConnected(start, end)){//如果还没相连
				unionUtil.union(start, end);//连接
				map.add(edge);//将打通的通道存储
				count++;
				if(count==col*row-1)//生成迷宫完毕
					break;
			}
		}
		
	}
	
	public int[][] getMap(){
		int[][] array1 = new int[2*row+1][2*col+1];
		//网格留空
		for(int i=1;i<2*row+1;i+=2){
			for(int j=1;j<2*col+1;j+=2){
				array1[i][j] = -1;
			}
		}
		
		//网格纵向画墙
		for(int i=1;i<2*row+1;i+=2){
			for(int j=2;j<2*col+1;j+=2){
				array1[i][j] = 1;
			}
		}
		
		//网格横向画墙
		for(int i=0;i<2*row+1;i+=2){
			for(int j=1;j<2*col+1;j++){
				array1[i][j] = 1;
			}
		}
		
		
		//边界最左边纵向画墙
		for(int i=0;i<2*row+1;i++){
			array1[i][0] = 1;
		}
		
//		打印连通前迷宫视图		
		for(int i=0;i<2*row+1;i++){
			for(int j=0;j<2*col+1;j++){
				if(array1[i][j]==-1)
					System.out.print(" ");
				else
					System.out.print("#");
			}
			System.out.println();
		}

		
		for(Edge edge:map){
			int start = edge.start;
			int end = edge.end;
			if(Math.abs(start-end)==1){//左右连通
				int x = start/col;
				int y = start%col;
				array1[2*x+1][2*y+2] = -1;
			}else{//上下连通
				int x = start/col;
				int y = start%col;
				array1[2*x+2][2*y+1] = -1;
			}	
		}

		
		//打印连通后迷宫视图		
		for(int i=0;i<2*row+1;i++){
			for(int j=0;j<2*col+1;j++){
				if(array1[i][j]==-1)
					System.out.print(" ");
				else
					System.out.print("#");
			}
			System.out.println();
		}
		
		return array1;
	}
	
	public void print(){
		int index = 0;
		for(Edge edge:map){
			if(index%10==0)
				System.out.println();
			System.out.print("("+edge.start+","+edge.end+","+edge.value+")");
			index ++;
		}
	}
	
}

面板GamePanel类:

获取图片并显示迷宫

package 自动生成迷宫;

import java.awt.*;

import javax.swing.*;

public class GamePanel extends JPanel{

	int[][] map;
	MapUtil mapUtil;
	int row,col;
	int leftX,leftY;
	
	public GamePanel(int row,int col){
		this.row = row;
		this.col = col;
		leftX = 0;
		leftY = 0;
		mapUtil = new MapUtil(row, col);
		map = mapUtil.getMap();
		setPreferredSize(new Dimension((col*2+1)*20,(row*2+1)*20));
	}
	
	public void paint(Graphics g){
		Image image = Toolkit.getDefaultToolkit().getImage("D:/Game/pic1.png");
		for(int i=0;i<2*row+1;i++){
			for(int j=0;j<2*col+1;j++){
				if(map[i][j]!=-1)
					g.drawImage(image, leftX+20*j, leftY+20*i, 20, 20,this);
			}
		}
		
	}
	
}

窗体Main类:

package 自动生成迷宫;

import javax.swing.JFrame;

public class Main extends JFrame{

	public Main(){
		this.getContentPane().add(new GamePanel(20, 30));
		pack();
		setVisible(true);
	}
	
	public static void main(String[] args) {
		new Main();
	}

}

运行效果:

生成一个20行30列的迷宫

补充说明

墙的素材应该放置在 D://Game路径下,命名为pic1.png;

迷宫可以根据自己需要,在自己指定位置添加入口和出口,修改数组指定位置的值就可以。

猜你喜欢

转载自blog.csdn.net/A1344714150/article/details/86587307