JAVA实现简单扫雷游戏

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

完整项目已经放到github上,可以随意下载并直接运行,只求点个星github链接

因为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);		//设置该点的背景颜色作为没有打开的点
	}	
}
  • 20
    点赞
  • 87
    收藏
    觉得还不错? 一键收藏
  • 15
    评论
下面是一个简单Java 扫雷游戏实现,你可以参考一下: ```java import java.util.Random; import java.util.Scanner; public class MineSweeperGame { private static final int ROWS = 10; // 行数 private static final int COLS = 10; // 列数 private static final int MINES = 10; // 雷数 private static final int MINE = -1; // 雷的标记 private static final int COVERED = 0; // 覆盖的标记 private static final int UNCOVERED = 1; // 已经被翻开的标记 private int[][] board; // 存储游戏状态的二维数组 private boolean gameOver; // 游戏是否结束 private int remaining; // 剩余未翻开的格子数 public MineSweeperGame() { board = new int[ROWS][COLS]; gameOver = false; remaining = ROWS * COLS - MINES; initBoard(); placeMines(); } // 初始化游戏面板 private void initBoard() { for (int i = 0; i < ROWS; i++) { for (int j = 0; j < COLS; j++) { board[i][j] = COVERED; } } } // 随机放置雷 private void placeMines() { Random random = new Random(); int count = 0; while (count < MINES) { int row = random.nextInt(ROWS); int col = random.nextInt(COLS); if (board[row][col] != MINE) { board[row][col] = MINE; count++; } } } // 显示游戏面板 private void displayBoard() { System.out.println(" 0 1 2 3 4 5 6 7 8 9"); System.out.println(" ---------------------"); for (int i = 0; i < ROWS; i++) { System.out.print(i + "| "); for (int j = 0; j < COLS; j++) { if (board[i][j] == COVERED) { System.out.print(". "); } else if (board[i][j] == UNCOVERED) { // 如果是已经被翻开的格子,显示数字 int count = countAdjacentMines(i, j); if (count > 0) { System.out.print(count + " "); } else { System.out.print(" "); } } else { System.out.print("* "); } } System.out.println("|" + i); } System.out.println(" ---------------------"); System.out.println(" 0 1 2 3 4 5 6 7 8 9"); } // 获取相邻的格子中雷的数量 private int countAdjacentMines(int row, int col) { int count = 0; for (int i = Math.max(0, row - 1); i <= Math.min(ROWS - 1, row + 1); i++) { for (int j = Math.max(0, col - 1); j <= Math.min(COLS - 1, col + 1); j++) { if (board[i][j] == MINE) { count++; } } } return count; } // 翻开格子 private void uncover(int row, int col) { if (board[row][col] == UNCOVERED) { return; } if (board[row][col] == MINE) { gameOver = true; return; } board[row][col] = UNCOVERED; remaining--; if (countAdjacentMines(row, col) == 0) { // 如果相邻的格子中没有雷,就翻开它们 for (int i = Math.max(0, row - 1); i <= Math.min(ROWS - 1, row + 1); i++) { for (int j = Math.max(0, col - 1); j <= Math.min(COLS - 1, col + 1); j++) { if (board[i][j] == COVERED) { uncover(i, j); } } } } } // 运行游戏 public void run() { Scanner scanner = new Scanner(System.in); while (!gameOver) { displayBoard(); System.out.print("Enter row and column (separated by a space): "); int row = scanner.nextInt(); int col = scanner.nextInt(); if (row < 0 || row >= ROWS || col < 0 || col >= COLS) { System.out.println("Invalid row or column!"); continue; } if (board[row][col] == UNCOVERED) { System.out.println("The cell has already been uncovered!"); continue; } uncover(row, col); if (remaining == 0) { System.out.println("Congratulations! You have won!"); break; } } displayBoard(); System.out.println("Game over!"); } public static void main(String[] args) { MineSweeperGame game = new MineSweeperGame(); game.run(); } } ``` 这个实现比较简单,只有基本的游戏逻辑,有些细节可能还需要完善。你可以根据自己的需求进行修改和扩展。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值