JAVA实现简单扫雷游戏

这是我第一次写博客,初衷是想把我学到的东西展示出来,通过写博客的方式再捋一遍自己的思路。希望自己的一点点想法能够给其他人启发,我也要把自己存在的问题提出来,以此文为起点,树立写博客的习惯,在之后的日子里不断见证自己的成长。

因为win10系统没有自带的扫雷游戏我很难受,就决定自己要写一个扫雷出来。
在这里插入图片描述
在这里插入图片描述

需求:

  • 懂得一定的JAVA图形化界面知识
  • 懂得一定的搜索算法知识(如果不懂的最好先学习一下广度优先搜索)

实现扫雷的要点:

  1. 如何随机生成雷
  2. 如何自动打开空地
  3. 如何在空地添加附近雷的个数

思路:

  • 使用网格布局,每一个单元格代表一个点,每个点可能是空地或者是雷。
  • 鼠标左键单击打开一个点,是雷就结束,是空地就显示周围8个点雷的个数,右键单击在一个点上插旗,双击左键可自动打开符合条件的所有空地,所有雷都插旗并且剩下的所有点都被打开就胜利。
  • 每个点用一个继承了JButton类的类的对象代表,则可用一个二维的对象数组对应网格布局生成每一个点。
  • 通过鼠标事件监控每一个点从而进行游戏的操作。

随机生成雷&空地添加附近雷的个数:
使用两个随机数得到坐标,若该坐标上的点没有埋雷就在该点埋一个雷。一直循环该过程直到雷的个数够需求为止。
原来设想的是在点开空地后再搜索附近8个点雷的个数,但感觉不是很方便就换个思路,在一个点埋雷的时候就把这个雷周围8个点的附近雷的个数加一,在这个过程中需要注意不能越界。

while(flag<mineNum){				//随机添加雷
			int i,j;
			i=(int)(Math.random()*x-1);		//随机数乘小于x-1控制范围在0~x-1,不会超过数组范围
			j=(int)(Math.random()*y-1);		//随机数乘小于y-1控制范围在0~y-1,不会超过数组范围
			if(!mine[i][j].ismine){			//该点没有雷就埋雷
				flag++;
				mine[i][j].ismine=true;		//该点是雷
				int [][]next= {{-1,0},{-1,1},{0,1},{1,1},{1,0},{1,-1},{0,-1},{-1,-1}};	//周围8个坐标
				for(int k=0;k<8;k++) {				//循环雷周围8个点为每个点周围的雷数加一
					int tx=i+next[k][0];
					int ty=j+next[k][1];
					if(tx<0||tx>x-1||ty<0||ty>y-1)	//越界则直接进入下一个
						continue;
					mine[tx][ty].aroundMine++;		//该点的周围雷数加一
				}
			}
		}

自动打开空地:
在打开了空地之后,分为两类:附近有雷的空地和附近没雷的空地。附近有雷的空地不能直接双击打开周围8不是雷的空地,需要先把周围8个点中是雷的所有点都插上旗才能双击自动打开,若插错旗则直接结束游戏;附近没有雷的空地可直接打开周围8个空地。在打开了周围的空地之后,新打开的空地继续判断:若附近有雷则不继续打开其周围的空地,若附近没有雷则直接打开周围8个空地,然后继续重复该操纵直到所有的点附近都有雷为止。
为实现该操作需要用到广度优先搜索。通过广搜将符合上述条件的所有点都入队,然后逐一打开其附近的空地,并且要注意越界问题,在打开的点中如果存在符合上述条件的点,就将该点也入队。不断重复该操作直到该队列结束。

if(e.getClickCount()==2&&e.getButton()==e.BUTTON1&&t.isfound&&!t.ismine) {//双击打开的点可自动打开周围没有雷的点
			int [][]map=new int[x*y][];		//用于存放于队列中每个点的坐标
			for(int i=0;i<x*y;i++)
				map[i]=new int[2];
			map[0][0]=t.x;					//首先将被双击的点进入队列以其为中心向四周进行搜索
			map[0][1]=t.y;
			int head=0,tail=1;
			int [][]next= {{-1,0},{-1,1},{0,1},{1,1},{1,0},{1,-1},{0,-1},{-1,-1}};	//周围8个坐标
			int tx,ty,flag=0;
			while(head<tail) {
				for(int w=0;w<8;w++) {
					tx=map[head][0]+next[w][0];
					ty=map[head][1]+next[w][1];
					if(tx<0||tx>x-1||ty<0||ty>y-1)		//越界则进入下一个
						continue;
					if(!mine[tx][ty].canSearch)			//周围雷的数量要和插的旗数量一样才能进行自动打开
						flag++;
				}
				if(flag==mine[map[head][0]][map[head][1]].aroundMine) {
					for(int k=0;k<8;k++) {				//循环周围8个方向
						tx=map[head][0]+next[k][0];
						ty=map[head][1]+next[k][1];
						if(tx<0||tx>x-1||ty<0||ty>y-1)	//越界则进入下一个
							continue;
						if(mine[tx][ty].ismine&&mine[tx][ty].canSearch) {	//差错旗直接结束
							for(int i=0;i<x;i++) {
								for(int j=0;j<y;j++) {
									if(mine[i][j].ismine) {
										ImageIcon icon=new ImageIcon(".//photo//雷.jpg");
										icon.setImage(icon.getImage().getScaledInstance(mine[i][j].getWidth(), mine[i][j].getHeight(), Image.SCALE_AREA_AVERAGING));
										mine[i][j].setIcon(icon);
									}
								}
							}
							time.stop();
							JOptionPane.showMessageDialog(this,"不好意思 您输了 下次好运");
							game.main(null);
						}
						if(!mine[tx][ty].isfound&&!mine[tx][ty].ismine&&mine[tx][ty].canSearch) {//将没有被打开并且没插旗的空地打开
							mine[tx][ty].setBackground(Color.LIGHT_GRAY);		//当被打开后变色
							mine[tx][ty].isfound=true;							//已经被点开
							if(mine[tx][ty].aroundMine>0&&!mine[tx][ty].ismine) {//周围有雷就标出周围有雷数
								mine[tx][ty].setText(""+mine[tx][ty].aroundMine);
								mine[tx][ty].setFont(new java.awt.Font("24",20,20)) ;
							}
							if(mine[tx][ty].aroundMine==0) {//周围8个点都没雷就将该点入队之后以该点为中心搜
								map[tail][0]=tx;
								map[tail][1]=ty;
								tail++;
							}
						}
					}
				}
				head++;		//每搜完一个点后该就出队
			}
		}

空地&雷类:
创建一个继承了JButton类的子类,用于表示每个单元格中的点,该类需要判断是雷还是空地、是否被打开、是否插了旗、记录该点的坐标、记录周围8个点雷的个数。
在游戏开始前应该默认该点没有被打开,默认该点不是雷(因为大多数的点都是空地),默认该点可以被打开(因为没有插旗)

public class Mine extends JButton { 	//空地&雷类
	boolean isfound;					//是否被打开
	boolean ismine;						//是否藏雷
	boolean canSearch;					//是否插旗,插旗则该点不会被搜索
	int x,y;							//确定坐标
	int aroundMine=0;					//周围的雷数
	Mine(int x,int y) {
		this.x=x;
		this.y=y;
		isfound=false;
		ismine=false;	
		canSearch=true;
		setBackground(Color.pink);		//设置该点的背景颜色作为没有打开的点
	}	
}

游戏面板类:
创建一个继承JPanel类的子类,用于创建游戏画面并在此面板上进行游戏的操作。可通过传入游戏的行、列、雷数创建相应难度的游戏。

public class Jpanel extends JPanel implements MouseListener,ActionListener {
	int x, y ;												//行列
	Mine [][]mine;											//每个点
	JLabel flagPhoto;										//雷图片
	JLabel blank1;											//空白占网格
	JLabel blank2;											//空白占网格
	JLabel blank3;											//空白占网格
	JLabel blank4;											//空白占网格
	JLabel flagNum;											//记录剩余雷数标签
	JLabel timeShow;										//显示用时标签
	Timer time;												//计时
	int mineNum=0;											//剩余雷数
	int second=0;											//用时
	Jpanel(int x, int y, int mineNum){						//传入网格的行列和雷数
		this.x = x ;
		this.y = y ;
		this.mineNum = mineNum;
		GridLayout grid=new GridLayout(x+1,y);				//网格布局每一格是一个点
		setLayout(grid);
		setFocusable(true);
		mine=new Mine[x][y];
		for(int i=0;i<x;i++){
			for(int j=0;j<y;j++){
				mine[i][j]=new Mine(i,j);					//创建每一个点
				add(mine[i][j]);
				mine[i][j].addMouseListener(this);			//为每一个点都添加鼠标事件
			}
		}
		blank1=new JLabel();		//空白标签用于占一个网格便于计时计数不会挤在一起影响美观
		add(blank1);
		flagPhoto=new JLabel();		
		add(flagPhoto);
		ImageIcon icon=new ImageIcon(".//photo//雷.jpg");
		icon.setImage(icon.getImage().getScaledInstance(40, 40, Image.SCALE_AREA_AVERAGING));
		flagPhoto.setIcon(icon);
		flagNum=new JLabel(":"+mineNum);
		flagNum.setFont(new java.awt.Font("24",20,23));
		add(flagNum);
		blank2=new JLabel();
		add(blank2);
		blank3=new JLabel();
		add(blank3);
		blank4=new JLabel();
		add(blank4);
		time=new Timer(1000,this);			//每1秒发生一次事件
		timeShow=new JLabel(""+second);
		add(timeShow);
		timeShow.setFont(new java.awt.Font("24",20,30));
		int flag=0;							//用于标记雷的数量是否够
		while(flag<mineNum){				//随机添加雷
			int i,j;
			i=(int)(Math.random()*x-1);		//随机数乘小于x-1控制范围在0~x-1,不会超过数组范围
			j=(int)(Math.random()*y-1);		//随机数乘小于y-1控制范围在0~y-1,不会超过数组范围
			if(!mine[i][j].ismine){			//该点没有雷就埋雷
				flag++;
				mine[i][j].ismine=true;		//该点是雷
				int [][]next= {{-1,0},{-1,1},{0,1},{1,1},{1,0},{1,-1},{0,-1},{-1,-1}};	//周围8个坐标
				for(int k=0;k<8;k++) {				//循环雷周围8个点为每个点周围的雷数加一
					int tx=i+next[k][0];
					int ty=j+next[k][1];
					if(tx<0||tx>x-1||ty<0||ty>y-1)	//越界则直接进入下一个
						continue;
					mine[tx][ty].aroundMine++;		//该点的周围雷数加一
				}
			}
		}
	}
	public void mouseClicked(MouseEvent e) {
		time.start();
		Mine t=new Mine(0,0);						//用于寻找当前点击的点是哪一个
		for(int i=0;i<x;i++) {						//循环整个对象数组,找到被点击的那个点 
			for(int j=0;j<y;j++) {
				if(e.getSource()==mine[i][j]) {
					t=(Mine) e.getSource();			//获得点击到的点的地址
					break;
				}
			}
		}
		
		if(e.getButton()==e.BUTTON3){				//点击右键插旗排雷
			if(t.canSearch&&!t.isfound){			//该点未被打开并且没有插着旗就有资格插旗
				t.canSearch=false;					//插旗后点击左键不可对该点进行搜索
				ImageIcon icon=new ImageIcon(".\\photo\\旗.jpg");		
				icon.setImage(icon.getImage().getScaledInstance(t.getWidth(), t.getHeight(), Image.SCALE_DEFAULT));
				t.setIcon(icon);
				mineNum--;
				flagNum.setText(": "+mineNum);		//插旗后显示的雷数减一
			}
			else if(!t.canSearch&&!t.isfound){		///该点未被打开并且插着旗就有资格拆掉旗							
				t.canSearch=true;					//插旗后再点一次右键可左键搜索
				t.setIcon(null);					//撤掉旗就删掉图标
				mineNum++;	
				flagNum.setText(": "+mineNum);		//拆旗后显示的雷数加一
			}		
		}
		
		if(e.getClickCount()==1&&e.getButton()==e.BUTTON1){	//单击左键打开该点
			if(t.canSearch) {						//该点没插旗就可以打开该点
				t.setBackground(Color.LIGHT_GRAY);	//当被点开后变色
				t.isfound=true;						//已经被打开
				if(t.aroundMine>0&&!t.ismine) {		//不是雷且周围有雷就标出周围有雷数
					t.setText(""+t.aroundMine);
					t.setFont(new java.awt.Font("24",20,20));
				}
			}	
			if(t.ismine&&t.canSearch){				//若该点有雷且没有插旗打开则结束游戏
				for(int i=0;i<x;i++) {
					for(int j=0;j<y;j++) {			
						if(mine[i][j].ismine) {		//将所有是雷的点都打开
							ImageIcon icon=new ImageIcon(".//photo//雷.jpg");
							icon.setImage(icon.getImage().getScaledInstance(mine[i][j].getWidth(), mine[i][j].getHeight(), Image.SCALE_AREA_AVERAGING));
							mine[i][j].setIcon(icon);
							
						}
					}
				}
				time.stop();						//停止计时
				JOptionPane.showMessageDialog(this,"不好意思 您输了 下次好运");	//弹出消息对话框提示游戏结束
				game.main(null);					//重新开始回到选择模式界面
			}
		}
		
		if(e.getClickCount()==2&&e.getButton()==e.BUTTON1&&t.isfound&&!t.ismine) {//双击打开的点可自动打开周围没有雷的点
			int [][]map=new int[x*y][];		//用于存放于队列中每个点的坐标
			for(int i=0;i<x*y;i++)
				map[i]=new int[2];
			map[0][0]=t.x;					//首先将被双击的点进入队列以其为中心向四周进行搜索
			map[0][1]=t.y;
			int head=0,tail=1;
			int [][]next= {{-1,0},{-1,1},{0,1},{1,1},{1,0},{1,-1},{0,-1},{-1,-1}};	//周围8个坐标
			int tx,ty,flag=0;
			while(head<tail) {
				for(int w=0;w<8;w++) {
					tx=map[head][0]+next[w][0];
					ty=map[head][1]+next[w][1];
					if(tx<0||tx>x-1||ty<0||ty>y-1)		//越界则进入下一个
						continue;
					if(!mine[tx][ty].canSearch)			//周围雷的数量要和插的旗数量一样才能进行自动打开
						flag++;
				}
				if(flag==mine[map[head][0]][map[head][1]].aroundMine) {
					for(int k=0;k<8;k++) {				//循环周围8个方向
						tx=map[head][0]+next[k][0];
						ty=map[head][1]+next[k][1];
						if(tx<0||tx>x-1||ty<0||ty>y-1)	//越界则进入下一个
							continue;
						if(mine[tx][ty].ismine&&mine[tx][ty].canSearch) {	//差错旗直接结束
							for(int i=0;i<x;i++) {
								for(int j=0;j<y;j++) {
									if(mine[i][j].ismine) {
										ImageIcon icon=new ImageIcon(".//photo//雷.jpg");
										icon.setImage(icon.getImage().getScaledInstance(mine[i][j].getWidth(), mine[i][j].getHeight(), Image.SCALE_AREA_AVERAGING));
										mine[i][j].setIcon(icon);
									}
								}
							}
							time.stop();
							JOptionPane.showMessageDialog(this,"不好意思 您输了 下次好运");
							game.main(null);
						}
						if(!mine[tx][ty].isfound&&!mine[tx][ty].ismine&&mine[tx][ty].canSearch) {//将没有被打开并且没插旗的空地打开
							mine[tx][ty].setBackground(Color.LIGHT_GRAY);		//当被打开后变色
							mine[tx][ty].isfound=true;							//已经被点开
							if(mine[tx][ty].aroundMine>0&&!mine[tx][ty].ismine) {//周围有雷就标出周围有雷数
								mine[tx][ty].setText(""+mine[tx][ty].aroundMine);
								mine[tx][ty].setFont(new java.awt.Font("24",20,20)) ;
							}
							if(mine[tx][ty].aroundMine==0) {//周围8个点都没雷就将该点入队之后以该点为中心搜
								map[tail][0]=tx;
								map[tail][1]=ty;
								tail++;
							}
						}
					}
				}
				head++;		//每搜完一个点后该就出队
			}
		}
		
		if(mineNum==0) {	//插的旗数和雷的数量相同时就开始判断是否胜利
			boolean a=false;			//记录是否插错旗
			for(int i=0;i<x;i++) {
				for(int j=0;j<y;j++) {	//循环整个对象数组
					if(!mine[i][j].isfound&&!mine[i][j].ismine&&mine[i][j].canSearch) {//该点不是雷并且没有被打开
						a=true;
						break;									//游戏继续
					}
					if(mine[i][j].ismine&&mine[i][j].canSearch){//该点是雷并且么有插旗
						a=true;
						break;									//游戏继续	
					}
				}
			}
			if(!a) {											//游戏胜利
				time.stop();									//停止计时
				JOptionPane.showMessageDialog(this,"胜利!");	//弹出消息对话框提示游戏结束
				game.main(null);								//重新开始回到选择模式界面
			}
		}
	}
	public void mouseEntered(MouseEvent e) {}
	public void mouseExited(MouseEvent e) {}
	public void mousePressed(MouseEvent e) {}	
	public void mouseReleased(MouseEvent e) {}
	public void actionPerformed(ActionEvent e) {
		if(e.getSource()==time) {					//计时
			second++;									//每秒加一
			timeShow.setText(""+second);
		}
	}
}

地图类
创建一个继承了JFrame类的子类,用于选择游戏的难易程度并进入游戏画面。分为简单(9x9 10个雷)、困难 (25x25 120个雷)、自定义。

public class Map extends JFrame implements ActionListener{
	Jpanel panel;						//游戏画面
	JPanel home ;						//初始界面
	JButton easy, difficult, diy;		//选择模式
	Map() {
		home = new JPanel() ;
		add(home) ;
		home.setLayout(null) ;			//设置布局
		easy = new JButton("简单  (9x9  10个雷)") ;
		difficult = new JButton("困难  (25x25   120个雷)") ;
		diy = new JButton("自定义") ;
		home.add(easy) ;							
		home.add(difficult) ;
		home.add(diy);
		easy.setBounds(100, 50, 190, 70);				//设置按钮大小
		difficult.setBounds(100, 170, 190, 70);
		diy.setBounds(100, 290, 190, 70);
		easy.addActionListener(this) ;					//按钮添加监听器
		difficult.addActionListener(this) ;
		diy.addActionListener(this);
		easy.setBackground(Color.GRAY);					//按钮设置背景颜色
		difficult.setBackground(Color.gray);
		diy.setBackground(Color.gray);
		easy.setFont(new java.awt.Font("24",20,15)) ;	//按钮设置字大小
		difficult.setFont(new java.awt.Font("24",20,15)) ;
		diy.setFont(new java.awt.Font("24",20,15)) ;
		setVisible(true) ;
		setBounds(400,100,400,450) ;
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE) ;
	}
	public void actionPerformed(ActionEvent e) {
		if(e.getSource()==easy) {			//简单模式
			setBounds(300,100,800,800) ;	//重新设置游戏界面大小
			panel=new Jpanel(9, 9, 10);
			home.setVisible(false) ;		//先将当前的面板不可视否则不会跳转到游戏画面
			this.remove(home) ;				//将当前的面板从该窗口移除并加入游戏面板
			this.add(panel) ;
		}
		if(e.getSource()==difficult) {		//困难模式
			setBounds(100,0,1600,1000) ;	
			panel=new Jpanel(25, 25, 120);
			home.setVisible(false) ;
			this.remove(home) ;
			this.add(panel) ;
		}
		if(e.getSource()==diy) {	//自定义模式通过三个输入对话框输入自定义的地图大小和雷数
			String x = JOptionPane.showInputDialog(this, "请输入行数:", null, JOptionPane.INFORMATION_MESSAGE);
			String y = JOptionPane.showInputDialog(this, "请输入列数:", null, JOptionPane.INFORMATION_MESSAGE);
			String num = JOptionPane.showInputDialog(this, "请输入雷数:", null, JOptionPane.INFORMATION_MESSAGE);
			setBounds(100,0,1600,1000) ;
			panel=new Jpanel(Integer.parseInt(x),  Integer.parseInt(y),  Integer.parseInt(num));	
			home.setVisible(false) ;
			this.remove(home) ;
			this.add(panel) ;
		}
	}
}

完整代码:

public class game {
	public static void main(String[] args) 
	{
		Map a=new Map();
	}
}

public class Map extends JFrame implements ActionListener{
	Jpanel panel;						//游戏画面
	JPanel home ;						//初始界面
	JButton easy, difficult, diy;		//选择模式
	Map() {
		home = new JPanel() ;
		add(home) ;
		home.setLayout(null) ;			//设置布局
		easy = new JButton("简单  (9x9  10个雷)") ;
		difficult = new JButton("困难  (25x25   120个雷)") ;
		diy = new JButton("自定义") ;
		home.add(easy) ;							
		home.add(difficult) ;
		home.add(diy);
		easy.setBounds(100, 50, 190, 70);				//设置按钮大小
		difficult.setBounds(100, 170, 190, 70);
		diy.setBounds(100, 290, 190, 70);
		easy.addActionListener(this) ;					//按钮添加监听器
		difficult.addActionListener(this) ;
		diy.addActionListener(this);
		easy.setBackground(Color.GRAY);					//按钮设置背景颜色
		difficult.setBackground(Color.gray);
		diy.setBackground(Color.gray);
		easy.setFont(new java.awt.Font("24",20,15)) ;	//按钮设置字大小
		difficult.setFont(new java.awt.Font("24",20,15)) ;
		diy.setFont(new java.awt.Font("24",20,15)) ;
		setVisible(true) ;
		setBounds(400,100,400,450) ;
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE) ;
	}
	public void actionPerformed(ActionEvent e) {
		if(e.getSource()==easy) {			//简单模式
			setBounds(300,100,800,800) ;	//重新设置游戏界面大小
			panel=new Jpanel(9, 9, 10);
			home.setVisible(false) ;		//先将当前的面板不可视否则不会跳转到游戏画面
			this.remove(home) ;				//将当前的面板从该窗口移除并加入游戏面板
			this.add(panel) ;
		}
		if(e.getSource()==difficult) {		//困难模式
			setBounds(100,0,1600,1000) ;	
			panel=new Jpanel(25, 25, 120);
			home.setVisible(false) ;
			this.remove(home) ;
			this.add(panel) ;
		}
		if(e.getSource()==diy) {	//自定义模式通过三个输入对话框输入自定义的地图大小和雷数
			String x = JOptionPane.showInputDialog(this, "请输入行数:", null, JOptionPane.INFORMATION_MESSAGE);
			String y = JOptionPane.showInputDialog(this, "请输入列数:", null, JOptionPane.INFORMATION_MESSAGE);
			String num = JOptionPane.showInputDialog(this, "请输入雷数:", null, JOptionPane.INFORMATION_MESSAGE);
			setBounds(100,0,1600,1000) ;
			panel=new Jpanel(Integer.parseInt(x),  Integer.parseInt(y),  Integer.parseInt(num));	
			home.setVisible(false) ;
			this.remove(home) ;
			this.add(panel) ;
		}
	}
}

public class Jpanel extends JPanel implements MouseListener,ActionListener {
	int x, y ;												//行列
	Mine [][]mine;											//每个点
	JLabel flagPhoto;										//雷图片
	JLabel blank1;											//空白占网格
	JLabel blank2;											//空白占网格
	JLabel blank3;											//空白占网格
	JLabel blank4;											//空白占网格
	JLabel flagNum;											//记录剩余雷数标签
	JLabel timeShow;										//显示用时标签
	Timer time;												//计时
	int mineNum=0;											//剩余雷数
	int second=0;											//用时
	Jpanel(int x, int y, int mineNum){						//传入网格的行列和雷数
		this.x = x ;
		this.y = y ;
		this.mineNum = mineNum;
		GridLayout grid=new GridLayout(x+1,y);				//网格布局每一格是一个点
		setLayout(grid);
		setFocusable(true);
		mine=new Mine[x][y];
		for(int i=0;i<x;i++){
			for(int j=0;j<y;j++){
				mine[i][j]=new Mine(i,j);					//创建每一个点
				add(mine[i][j]);
				mine[i][j].addMouseListener(this);			//为每一个点都添加鼠标事件
			}
		}
		blank1=new JLabel();		//空白标签用于占一个网格便于计时计数不会挤在一起影响美观
		add(blank1);
		flagPhoto=new JLabel();		
		add(flagPhoto);
		ImageIcon icon=new ImageIcon(".//photo//雷.jpg");
		icon.setImage(icon.getImage().getScaledInstance(40, 40, Image.SCALE_AREA_AVERAGING));
		flagPhoto.setIcon(icon);
		flagNum=new JLabel(":"+mineNum);
		flagNum.setFont(new java.awt.Font("24",20,23));
		add(flagNum);
		blank2=new JLabel();
		add(blank2);
		blank3=new JLabel();
		add(blank3);
		blank4=new JLabel();
		add(blank4);
		time=new Timer(1000,this);			//每1秒发生一次事件
		timeShow=new JLabel(""+second);
		add(timeShow);
		timeShow.setFont(new java.awt.Font("24",20,30));
		int flag=0;							//用于标记雷的数量是否够
		while(flag<mineNum){				//随机添加雷
			int i,j;
			i=(int)(Math.random()*x-1);		//随机数乘小于x-1控制范围在0~x-1,不会超过数组范围
			j=(int)(Math.random()*y-1);		//随机数乘小于y-1控制范围在0~y-1,不会超过数组范围
			if(!mine[i][j].ismine){			//该点没有雷就埋雷
				flag++;
				mine[i][j].ismine=true;		//该点是雷
				int [][]next= {{-1,0},{-1,1},{0,1},{1,1},{1,0},{1,-1},{0,-1},{-1,-1}};	//周围8个坐标
				for(int k=0;k<8;k++) {				//循环雷周围8个点为每个点周围的雷数加一
					int tx=i+next[k][0];
					int ty=j+next[k][1];
					if(tx<0||tx>x-1||ty<0||ty>y-1)	//越界则直接进入下一个
						continue;
					mine[tx][ty].aroundMine++;		//该点的周围雷数加一
				}
			}
		}
	}
	public void mouseClicked(MouseEvent e) {
		time.start();
		Mine t=new Mine(0,0);						//用于寻找当前点击的点是哪一个
		for(int i=0;i<x;i++) {						//循环整个对象数组,找到被点击的那个点 
			for(int j=0;j<y;j++) {
				if(e.getSource()==mine[i][j]) {
					t=(Mine) e.getSource();			//获得点击到的点的地址
					break;
				}
			}
		}
		
		if(e.getButton()==e.BUTTON3){				//点击右键插旗排雷
			if(t.canSearch&&!t.isfound){			//该点未被打开并且没有插着旗就有资格插旗
				t.canSearch=false;					//插旗后点击左键不可对该点进行搜索
				ImageIcon icon=new ImageIcon(".\\photo\\旗.jpg");		
				icon.setImage(icon.getImage().getScaledInstance(t.getWidth(), t.getHeight(), Image.SCALE_DEFAULT));
				t.setIcon(icon);
				mineNum--;
				flagNum.setText(": "+mineNum);		//插旗后显示的雷数减一
			}
			else if(!t.canSearch&&!t.isfound){		///该点未被打开并且插着旗就有资格拆掉旗							
				t.canSearch=true;					//插旗后再点一次右键可左键搜索
				t.setIcon(null);					//撤掉旗就删掉图标
				mineNum++;	
				flagNum.setText(": "+mineNum);		//拆旗后显示的雷数加一
			}		
		}
		
		if(e.getClickCount()==1&&e.getButton()==e.BUTTON1){	//单击左键打开该点
			if(t.canSearch) {						//该点没插旗就可以打开该点
				t.setBackground(Color.LIGHT_GRAY);	//当被点开后变色
				t.isfound=true;						//已经被打开
				if(t.aroundMine>0&&!t.ismine) {		//不是雷且周围有雷就标出周围有雷数
					t.setText(""+t.aroundMine);
					t.setFont(new java.awt.Font("24",20,20));
				}
			}	
			if(t.ismine&&t.canSearch){				//若该点有雷且没有插旗打开则结束游戏
				for(int i=0;i<x;i++) {
					for(int j=0;j<y;j++) {			
						if(mine[i][j].ismine) {		//将所有是雷的点都打开
							ImageIcon icon=new ImageIcon(".//photo//雷.jpg");
							icon.setImage(icon.getImage().getScaledInstance(mine[i][j].getWidth(), mine[i][j].getHeight(), Image.SCALE_AREA_AVERAGING));
							mine[i][j].setIcon(icon);
							
						}
					}
				}
				time.stop();						//停止计时
				JOptionPane.showMessageDialog(this,"不好意思 您输了 下次好运");	//弹出消息对话框提示游戏结束
				game.main(null);					//重新开始回到选择模式界面
			}
		}
		
		if(e.getClickCount()==2&&e.getButton()==e.BUTTON1&&t.isfound&&!t.ismine) {//双击打开的点可自动打开周围没有雷的点
			int [][]map=new int[x*y][];		//用于存放于队列中每个点的坐标
			for(int i=0;i<x*y;i++)
				map[i]=new int[2];
			map[0][0]=t.x;					//首先将被双击的点进入队列以其为中心向四周进行搜索
			map[0][1]=t.y;
			int head=0,tail=1;
			int [][]next= {{-1,0},{-1,1},{0,1},{1,1},{1,0},{1,-1},{0,-1},{-1,-1}};	//周围8个坐标
			int tx,ty,flag=0;
			while(head<tail) {
				for(int w=0;w<8;w++) {
					tx=map[head][0]+next[w][0];
					ty=map[head][1]+next[w][1];
					if(tx<0||tx>x-1||ty<0||ty>y-1)		//越界则进入下一个
						continue;
					if(!mine[tx][ty].canSearch)			//周围雷的数量要和插的旗数量一样才能进行自动打开
						flag++;
				}
				if(flag==mine[map[head][0]][map[head][1]].aroundMine) {
					for(int k=0;k<8;k++) {				//循环周围8个方向
						tx=map[head][0]+next[k][0];
						ty=map[head][1]+next[k][1];
						if(tx<0||tx>x-1||ty<0||ty>y-1)	//越界则进入下一个
							continue;
						if(mine[tx][ty].ismine&&mine[tx][ty].canSearch) {	//差错旗直接结束
							for(int i=0;i<x;i++) {
								for(int j=0;j<y;j++) {
									if(mine[i][j].ismine) {
										ImageIcon icon=new ImageIcon(".//photo//雷.jpg");
										icon.setImage(icon.getImage().getScaledInstance(mine[i][j].getWidth(), mine[i][j].getHeight(), Image.SCALE_AREA_AVERAGING));
										mine[i][j].setIcon(icon);
									}
								}
							}
							time.stop();
							JOptionPane.showMessageDialog(this,"不好意思 您输了 下次好运");
							game.main(null);
						}
						if(!mine[tx][ty].isfound&&!mine[tx][ty].ismine&&mine[tx][ty].canSearch) {//将没有被打开并且没插旗的空地打开
							mine[tx][ty].setBackground(Color.LIGHT_GRAY);		//当被打开后变色
							mine[tx][ty].isfound=true;							//已经被点开
							if(mine[tx][ty].aroundMine>0&&!mine[tx][ty].ismine) {//周围有雷就标出周围有雷数
								mine[tx][ty].setText(""+mine[tx][ty].aroundMine);
								mine[tx][ty].setFont(new java.awt.Font("24",20,20)) ;
							}
							if(mine[tx][ty].aroundMine==0) {//周围8个点都没雷就将该点入队之后以该点为中心搜
								map[tail][0]=tx;
								map[tail][1]=ty;
								tail++;
							}
						}
					}
				}
				head++;		//每搜完一个点后该就出队
			}
		}
		
		if(mineNum==0) {	//插的旗数和雷的数量相同时就开始判断是否胜利
			boolean a=false;			//记录是否插错旗
			for(int i=0;i<x;i++) {
				for(int j=0;j<y;j++) {	//循环整个对象数组
					if(!mine[i][j].isfound&&!mine[i][j].ismine&&mine[i][j].canSearch) {//该点不是雷并且没有被打开
						a=true;
						break;									//游戏继续
					}
					if(mine[i][j].ismine&&mine[i][j].canSearch){//该点是雷并且么有插旗
						a=true;
						break;									//游戏继续	
					}
				}
			}
			if(!a) {											//游戏胜利
				time.stop();									//停止计时
				JOptionPane.showMessageDialog(this,"胜利!");	//弹出消息对话框提示游戏结束
				game.main(null);								//重新开始回到选择模式界面
			}
		}
	}
	public void mouseEntered(MouseEvent e) {}
	public void mouseExited(MouseEvent e) {}
	public void mousePressed(MouseEvent e) {}	
	public void mouseReleased(MouseEvent e) {}
	public void actionPerformed(ActionEvent e) {
		if(e.getSource()==time) {					//计时
			second++;									//每秒加一
			timeShow.setText(""+second);
		}
	}
}

public class Mine extends JButton { 	//空地&雷类
	boolean isfound;					//是否被打开
	boolean ismine;						//是否藏雷
	boolean canSearch;					//是否插旗,插旗则该点不会被搜索
	int x,y;							//确定坐标
	int aroundMine=0;					//周围的雷数
	Mine(int x,int y) {
		this.x=x;
		this.y=y;
		isfound=false;
		ismine=false;	
		canSearch=true;
		setBackground(Color.pink);		//设置该点的背景颜色作为没有打开的点
	}	
}

这个扫雷只是完成了简单的游戏工程,许多东西还可以进行改进美化,如果有人想简借我的代码我会很开心但希望能够在评论里和我说一下。

发布了21 篇原创文章 · 获赞 51 · 访问量 3206

猜你喜欢

转载自blog.csdn.net/weixin_44689154/article/details/96605637