扫雷案例练习

1.启动类

public class StartApp {
	public static void main(String[] args) {
		new UI();
	}
}

2.界面类

import java.awt.Color;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;

import javax.swing.ImageIcon;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextArea;

public class UI {
	
	private int[][] corrdinatesUI ;//雷的坐标
	public static JPanel pane;//棋盘容器
	public static JButton[][] listBtn ;//按钮的数组储存按钮
	
	private JFrame mainWindow ;//主窗口
	/**
	 * 雷的个数
	 */
	public static int numMine;
	/**
	 * 棋盘的行数
	 */
	private int numRow;
	
	private Color bgColor = new Color(238,233,233);//背景的颜色
	private Color color;//方格的颜色
	
	
	/*
	 * 设置游戏模式的属性
	 */
	private JList<String> selList;
	private JFrame selFrame;
	private JTextArea txt;
	
	/*
	 * 底部调用多线程不断写入雷的个数和时间
	 */
	public static JLabel dateMine;
	
	/*
	 * 判断失败时候的值
	 */
	public static int loseNum = -1;
	
	Icon ic = new ImageIcon("旗子 (1).png");//右键控制插旗的图标
	
	public UI() {
		selRowMine();
	}
	
	/**
	 * 
	 * @Description 设置界面的方法,在选择完游戏难度以后运行
	 * @Author Mr.chen
	 * @Date 2020年11月2日上午10:45:55
	 * return:
	 */
	public void setUI(int numRow,int numMine) {
		this.numRow = numRow;
		UI.numMine = numMine;
		setWindow();
		UI.listBtn = new JButton[numRow][numRow];
		setPanel();
		setMine(numRow,numMine);
		corrdinatesUI = Service.corrdinates;//先将坐标拿过来
		setButton();
		new WinGameThread().start();
		setOtherPane();
		mainWindow.setVisible(true);
	}
	
	/**
	 * 
	 * @Description 设置选择游戏模式
	 * @Author Mr.chen
	 * @Date 2020年11月2日上午9:30:46
	 * return:
	 */
	public void selRowMine() {
		selFrame = new JFrame();//设置游戏模式窗口
		selFrame.setTitle("选择游戏模式!");
		selFrame.setAlwaysOnTop(true);//置顶
		JPanel selPane = new JPanel();//设置内部的容器
		selPane.setBackground(new Color(253,206,218));
		selFrame.setBounds(350, 350, 400, 300);
		selList = new JList<>(new String[] {"初级游戏模式-10*10","高级游戏模式-15*15"});//设置选择模式的列表
		selList.setFont(new Font("宋体",0,30));
		txt = new JTextArea();//设置自定义雷的个数
		txt.setBounds(178, 90, 150, 30);
		txt.setFont(new Font("宋体",0,25));
		JLabel txtMine = new JLabel();
		txtMine.setText("雷的个数");
		txtMine.setFont(new Font("宋体",0,25));
		txtMine.setBounds(55, 90, 100, 30);
		
		//设置选择确定时传入游戏的模式对应的棋盘大小和雷的个数
		JButton selBtn = new JButton("确定");//确定按钮
		selBtn.setBounds(100, 150, 180, 80);
		selBtn.setBackground(new Color(173,196,251));
		selBtn.addActionListener(new ActionListener() {
			
			@Override
			public void actionPerformed(ActionEvent e) {
				int num = selList.getSelectedIndex();
				if(num == 0) {
					int mine = 20;//默认20个
					try {
						mine = Integer.parseInt(txt.getText());
					} catch (NumberFormatException e1) {
						
					}
					setUI(10,mine);
					selFrame.dispose();
				}else if(num == 1) {
					int mine = 40;//默认40个
					try {
						mine = Integer.parseInt(txt.getText());
					} catch (NumberFormatException e1) {
						
					}
					setUI(15,mine);
					selFrame.dispose();
				}
			}
		});
		
		selPane.add(selList);
		selFrame.add(txtMine);
		selFrame.add(txt);
		selFrame.add(selBtn);
		selFrame.add(selPane);
		selFrame.setVisible(true);
	}
	
	/**
	 * 
	 * @Description 设置棋盘大小和雷的个数
	 * @Author Mr.chen
	 * @Date 2020年11月2日上午10:46:27
	 * return:
	 */
	public void setMine(int row,int mine) {
		Service s = new Service();
		s.setMine(mine,row);
	}
	
	/**
	 * 
	 * @Description 设置窗口
	 * @Author Mr.chen
	 * @Date 2020年10月30日下午4:22:37
	 * return:
	 */
	public void setWindow() {
		mainWindow = new JFrame();
		mainWindow.setBounds(100, 100, numRow*50 + 19, numRow*50 + 100);
		mainWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		mainWindow.setTitle("这是一个扫雷游戏");
		mainWindow.setLayout(null);
	}
	
	/**
	 * 
	 * @Description 设置容器显示时间和剩余雷的个数
	 * @Author Mr.chen
	 * @Date 2020年11月2日上午10:59:31
	 * return:
	 */
	public void setOtherPane() {
		dateMine = new JLabel();
		dateMine.setBounds(0, numRow*50 - 30, numRow*50, 100);
		dateMine.setFont(new Font("宋体",0,30));
		dateMine.setVisible(true);
		mainWindow.add(dateMine);
		new DateMineThread().start();
	}
	
	/**
	 * 
	 * @Description 设置容器
	 * @Author Mr.chen
	 * @Date 2020年10月30日下午4:31:14
	 * return:
	 */
	public void setPanel() {
		pane = new JPanel();
		pane.setLayout(new GridLayout(numRow,numRow));
		pane.setBounds(0, 0, numRow*50, numRow*50);
		mainWindow.add(pane);
	}
	
	/**
	 * 
	 * @Description 设置按钮并且将数组中的数字写入
	 * @Author Mr.chen
	 * @Date 2020年10月30日下午4:23:20
	 * return:
	 */
	public void setButton() {
		color = new Color(135,206,250);
		for (int i = 0; i < corrdinatesUI.length; i++) {
			for (int j = 0; j < corrdinatesUI.length; j++) {
						JButton btn = new JButton();
						String msg = String.valueOf(corrdinatesUI[i][j]);
						btn.setText(msg);//记录按钮是否有雷
						btn.setForeground(color);//设置字体的颜色和背景相同
						btn.setName(i + "," + j);//记录按钮的坐标
						btn.setBackground(color);
						btn.addMouseListener(new MouseListener() {//添加鼠标监听事件
							
							@Override
							public void mouseReleased(MouseEvent e) {
							}
							
							@Override
							public void mousePressed(MouseEvent e) {
							}
							
							@Override
							public void mouseExited(MouseEvent e) {
							}
							
							@Override
							public void mouseEntered(MouseEvent e) {
							}
							
							@Override
							public void mouseClicked(MouseEvent e) {
								if(e.getButton() == MouseEvent.BUTTON1) {//左键
									/*
									 * 如果点击到雷 -1 
									 */
									if(btn.getText().equals("-1")) {
										new Thread(new Runnable() {

											@Override
											public void run() {
												btnMine();
												loseNum = 1;  
												Integer choice = JOptionPane.showConfirmDialog(pane, "游戏结束!");//选择是否继续重新开始游戏
												if(choice == 0) {
													mainWindow.dispose();
													new UI();
												}else {
													
												}
											}
										}).start();
									}
									/*
									 * 点击的是  0
									 */
									else if(btn.getText().equals("0")){
										btnZero(btn,-1,-1);
									}
									/*
									 * 点击的不是 0 
									 */
									else {
										btnNotMine(btn);
									}
								}
								else if(e.getButton() == MouseEvent.BUTTON3) {//右键
									if(btn.isSelected() == false) {
										if(btn.getIcon() == null) {
											numMine--;
											btn.setIcon(ic);
										}else {
											numMine++;
											btn.setIcon(null);
										}
									}
								}
							}
						});
						listBtn[i][j] = btn;
						pane.add(btn);
			}
		}
	}
	
	/**
	 * 
	 * @Description 如果点击的是雷
	 * @Author Mr.chen
	 * @Date 2020年11月1日下午2:13:09
	 * return:
	 */
	public void btnMine() {
		for (int i = 0; i < listBtn.length; i++) {
			for (int j = 0; j < listBtn.length; j++) {
				if(listBtn[i][j].getText().equals("-1")) {
					listBtn[i][j].setBackground(Color.RED);
					listBtn[i][j].setSelected(true);
					listBtn[i][j].setEnabled(false);
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		}
		
	}
	
	/**
	 * 
	 * @Description 如果点击的不是雷 且不为0
	 * @Author Mr.chen
	 * @Date 2020年11月1日下午2:13:19
	 * return:
	 */
	public void btnNotMine(JButton btn) {
		btn.setBackground(bgColor);
		btn.setSelected(true);
		if(btn.getText().equals("0")) {//设置为0时按钮不可见
			btn.setForeground(bgColor);
		}
	}
	
	/**
	 * 
	 * @Description 如果选择到为0的,应该向四周递归打开
	 * @Author Mr.chen
	 * @Date 2020年11月1日下午2:47:12
	 * return:
	 * x-1,y-1  x,y-1  x-1,y+1
	 * x-1,y    x,y  x+1,y
	 */
	public void btnZero(JButton btn,int x,int y) {
		btnNotMine(btn);
		if(btn.getText().equals("-1")) {
			return;
		}
		if(!(btn.getText().equals("0"))) {
			return;
		}
		if(x==-1 && y==-1) {
			String num = btn.getName();//选中按钮的坐标  1,1
			String[] str = num.split(",");
			x = Integer.parseInt(str[0]);
			y = Integer.parseInt(str[1]);
		}
		for (int i = x - 1; i <= x + 1; i++) {
			for (int j = y - 1; j <= y + 1; j++) {
				if(i == x && j == y) {
					continue;
				}
				try {
					//周围是0打开并递归
					if(listBtn[i][j].getText().equals("0") && listBtn[i][j].isSelected() == false) {
						btnNotMine(listBtn[i][j]);
						btnZero(listBtn[i][j],i,j);
					}
					//周围打开是-1,则是雷不打开直接跳出当前循环
					else if(listBtn[i][j].getText().equals("-1") && listBtn[i][j].isSelected() == false){
						continue;
					}
					//周围打开不是雷,也不是0
					else if(listBtn[i][j].isSelected() == false){
						btnNotMine(listBtn[i][j]);
					}
				} catch (Exception e) {
					
				}
			}
		}
		
	}
}

3.逻辑类

/**
 * 
 * @Description 扫雷的逻辑类
 * @Author Mr.chen
 * @Date 2020年10月30日下午2:33:35
 * 功能:生成雷的坐标,生成棋盘并将期盼中是否有雷和周围雷的个数写入  此处方格是雷 -1 周围没有雷 0 周围有n个雷
 * 点击时为0递归  不为0则停止 
 */
public class Service {
	
	
	/**
	 * 储存的坐标
	 * 用来保存对应坐标的中是否有雷,或周围有几颗雷
	 * key:坐标   val:是否有雷或雷的个数
	 */
	public static int[][] corrdinates ;
	
	/**
	 * 
	 * @Description 生成传入值个数的雷, 并确定每个坐标处的数字
	 * @Author Mr.chen
	 * @Date 2020年10月30日下午2:38:50
	 * return:
	 */
	public void setMine(int numMine,int num) {
		setArray(num);
		int x;
		int y;
		for(int i = 0 ;i < numMine;i++) {
			x = (int)(Math.random() * num);
			y = (int)(Math.random() * num);
			if(corrdinates[x][y] != -1) {
				corrdinates[x][y] = -1;
			}else {
				i--;
				continue;
			}
			aroundMine(x,y);
		}
	}
	
	/**
	 * 
	 * @Description 判断周围有几个雷 并写入数字
	 * @Author Mr.chen
	 * @Date 2020年10月30日下午2:42:17
	 * return:
	 */
	private void aroundMine(int x, int y) {
		for (int i = x - 1; i <= x + 1; i++) {
			for (int j = y - 1; j <= y + 1; j++) {
				if(x == 0 && y == 0) {
					continue;
				}
				try {
					if(corrdinates[i][j] != -1) {
						corrdinates[i][j]++;
					}
				} catch (Exception e) {
					
				}
			}
		}
	}
	
	/**.
	 * 
	 * @Description 生成一个指定大小的数组,作为棋盘的大小
	 * @Author Mr.chen
	 * @Date 2020年10月30日下午3:05:10
	 * return:
	 */
	public void setArray(int num) {
		corrdinates = new int[num][num];
	}
}

4.判断胜利线程类

import javax.swing.JButton;
import javax.swing.JOptionPane;

public class WinGameThread extends Thread{
	
	private JButton[][] listBtn;//按钮的数组
	private int numMine;//雷的个数
	public static int winNum = -1;//胜利以后停止时间和雷计数线程
	
	@Override
	public void run() {
		this.numMine = UI.numMine;//获取总雷数
		while(true) {
			this.listBtn = UI.listBtn;//不断监听按钮状态
			int index = 0;
			try {
				for (JButton[] jButtons : this.listBtn) {
					for (JButton jButton : jButtons) {
						if(jButton.isSelected() == false) {
							index++;
						}
					}
				}
			} catch (Exception e1) {
				
			}
			if(index == this.numMine) {//当剩余未选择按钮为雷的总数时获胜
				JOptionPane.showMessageDialog(UI.pane, "获得胜利!");
				winNum = 1;
				break;
			}
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

5.底部时间和雷数线程类

import javax.swing.JLabel;
/**
 * 
 * @Description 底部时间和显示雷的个数的线程类
 * @Author Mr.chen
 * @Date 2020年11月4日上午9:21:45
 */
public class DateMineThread extends Thread{
	
	private JLabel dateMine;//显示时间和雷的个数的容器
	private int numMine;//雷的个数
	public static int gameTime;//游戏已用时间
	
	@Override
	public void run() {
		this.dateMine = UI.dateMine;
		int index = 0;
		while(true) {
			this.numMine = UI.numMine;//监听雷的个数
			String str = "已用时间:" + index + "   剩余雷的个数为:" + this.numMine;
			dateMine.setText(str);
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			index++;
			if(WinGameThread.winNum == 1) {//胜利时结束该线程
				gameTime = index;
				break;
			}
			if(UI.loseNum == 1) {//失败时结束该线程
				gameTime = index;
				break;
			}
		}
	}
	
	
}