效果展示
一、开发思路
首先在地图上随机生成一定数量的炸弹,这样每个格子都有一个确定的数字代表周围的炸弹数。当鼠标左键点击一个格子时,加载出数字对应的图片;右键点击时,加载出旗子等图片,并在表示地图的二维数组上做上相应标记。当左键点到炸弹则输掉游戏,全部点开且旗子位置正确则胜利。
二、UI界面类
在UI界面类中设置窗体、按钮属性,添加鼠标、动作监听器。
import javax.swing.*;
import java.awt.*;
public class BombUI extends JFrame implements Data {
private BombUtil bombUtil ;
private BombListener bl = new BombListener();
public void showUI() {
//设置窗体属性
setTitle("扫雷");
setSize(600, 500);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLayout(null);
//设置开始按钮
JButton startBtn = new JButton("开始游戏");
add(startBtn);
startBtn.setBounds(450,40,100,30);
startBtn.addActionListener(bl);
//设置不同模式
ButtonGroup mode = new ButtonGroup();
//设置按钮属性
JRadioButton btn1 = new JRadioButton("10✖10");
add(btn1);
btn1.setBounds(450,100,100,20);
mode.add(btn1);
JRadioButton btn2 = new JRadioButton("15✖15");
add(btn2);
btn2.setBounds(450,140,100,20);
mode.add(btn2);
JRadioButton btn3 = new JRadioButton("20✖20");
add(btn3);
btn3.setBounds(450,180,100,20);
mode.add(btn3);
radio_btn[0] = btn1;
radio_btn[1] = btn2;
radio_btn[2] = btn3;
//创建提示框
JLabel notice = new JLabel();
bl.setNotice(notice);
notice.setBounds(440,220,100,20);
add(notice);
setVisible(true);
//添加工具、监听器、画笔
bombUtil = bl.getBombUtil();
addMouseListener(bl);
bombUtil.setGraphics(this.getGraphics());
}
public static void main(String[] args) {
BombUI bombUI = new BombUI();
bombUI.showUI();
}
@Override
public void paint(Graphics g) {//刷新窗体时重绘
super.paint(g);
bombUtil.flush();
bombUtil.drawMap();
bombUtil.drawVisit();
}
}
三、工具类
此类中封装了众多功能,包括生成炸弹、游戏初始化、点开格子、插旗子、空格扩展等。其中空格扩展用了广度优先搜索算法,实现了当点开一个空格能够将周围所有能确定不是雷的格子自动点开的功能。
import javax.swing.*;
import java.awt.*;
import java.util.*;
public class BombUtil implements Data {
private Graphics g;
private int length=20;//地图大小
private int bombNum;//炸弹数量
private int flagNum;//旗子数量
private boolean isStart=false;// true 开始 false 结束
private int[][] arr;//用二维数组表示炸弹和数字
private int[][] visit;//0表示未打开,1表示已打开,2表示旗子,3表示问号,
private Queue<int[]> queue = new LinkedList<>();//用于点击空格扩展时BFS算法中
public int[][] getArr(){
return arr;
}
public int[][] getVisit(){
return visit;
}
public boolean visitAll(){//是否全部访问
for (int i = 0; i < length; i++) {
for (int j = 0; j < length; j++) {
if (visit[i][j]==0 || visit[i][j]==3) {
return false;
}
}
}
return true;
}
public void printArr(){//用于调试,无实际用途
for (int m = 0; m < length; m++) {
System.out.print("\n");
for (int n = 0; n < length; n++) {
System.out.print(arr[m][n]);
}
}
System.out.print("\n");
}
public boolean getStart() {
return isStart;
}
public void setStart(boolean start){
isStart = start;
}
public void setGraphics(Graphics g){
this.g=g;
}
public void setLength(int n){//设置棋盘大小
length = n;
}
public void setBombNum(int n){//设置炸弹数量
bombNum = n;
}
public int getBombNum(){//获取炸弹数量
return bombNum;
}
public int getFlagNum(){//获取旗子数量
return this.flagNum;
}
public void setFlagNum(int n){
flagNum = n;
}
public void generateBomb(){
Random random = new Random();
int count = 0;
while (count < bombNum) {//随机产生炸弹,直到达到目标数
count = 0;
//在length-1的范围中随机生成炸弹,在数组中标记为9
arr[random.nextInt(length-1)][random.nextInt(length-1)] = 9;
//遍历数组,数出炸弹数量
for (int i = 0; i < length; i++) {
for (int j = 0; j < length; j++) {
if (arr[i][j] == 9) {
count++;
}
}
}
}
//当一个格子有炸弹,那么周围八个格子如果不是炸弹数值都会加1,算出所有地方的数值并付给数组
for (int i = 0; i < length; i++) {
for (int j = 0; j < length; j++) {
if(arr[i][j] == 9){//当检测到炸弹
if(i>0 && arr[i-1][j]!=9){//向上
arr[i-1][j]++;
}
if(i<length-1 && arr[i+1][j]!=9){//向下
arr[i+1][j]++;
}
if(j>0 && arr[i][j-1]!=9){//向左
arr[i][j-1]++;
}
if(j<length-1 && arr[i][j+1]!=9){//向右
arr[i][j+1]++;
}
if(i>0 && j<length-1 && arr[i-1][j+1]!=9){//右上
arr[i-1][j+1]++;
}
if(i<length-1 && j<length-1 && arr[i+1][j+1]!=9){//右下
arr[i+1][j+1]++;
}
if(i>0 && j>0 && arr[i-1][j-1]!=9){//左上
arr[i-1][j-1]++;
}
if(i<length-1 && j>0 && arr[i+1][j-1]!=9){//左下
arr[i+1][j-1]++;
}
}
}
}
}
public boolean checkout(int x,int y){//检查是否点击在屏幕外,防止数组越界
return x < X || x > X + length * SIZE || y < Y || y > Y + length * SIZE;
}
public void drawMap(){//根据地图大小画相应数量的线
g.setColor(Color.BLACK);
for (int i = 0 ; i <= length ; i++) {
g.drawLine(X, Y + i * SIZE, X + length * SIZE, Y + i * SIZE);
g.drawLine(X + i * SIZE, Y, X + i * SIZE, Y + length * SIZE);
}
}
public int[] getPos(int x,int y){//根据点击位置确定打开的坐标
int[] pos = new int[2];
pos[0] = (y-Y)/SIZE;
pos[1] = (x-X)/SIZE;
return pos;
}
public void initGame() {//初始化游戏
if(radio_btn[0].isSelected()){//如果某个按钮被选,就设置相应数量的大小和炸弹数
setLength(10);
setBombNum(15);
}
else if(radio_btn[1].isSelected()){
setLength(15);
setBombNum(35);
}
else if(radio_btn[2].isSelected()){
setLength(20);
setBombNum(60);
}
else{
JOptionPane.showMessageDialog(null, "请选择难度并点击开始游戏");
}
flush();//将地图变成全白
drawMap();//划线
arr = new int[length][length];//重新生成数组和炸弹
generateBomb();
visit = new int[length][length];
}
public void open(int r,int c){//左键打开一个格子并标记已访问
int i = arr[r][c];//获取这个格子的值
ImageIcon icon = new ImageIcon("images/BomImage/" + i + ".jpg");//画出值对应的照片
g.drawImage(icon.getImage(),X+c*SIZE,Y+r*SIZE,null);
visit[r][c] = 1;//标记已访问
}
public void flush(){//刷新界面
ImageIcon icon = new ImageIcon("images/BomImage/0.jpg");
for (int i = 0; i < length; i++) {
for (int j = 0; j < length; j++) {
g.drawImage(icon.getImage(),X+j*SIZE,Y+i*SIZE,null);
}
}
}
public void drawVisit(){//画出已经点开过的格子
for (int i = 0; i < length; i++) {
for (int j = 0; j < length; j++) {
if(visit!= null && visit[i][j]==1){
ImageIcon icon = new ImageIcon("images/BomImage/"+Integer.toString(arr[i][j])+".jpg");
g.drawImage(icon.getImage(),X+j*SIZE,Y+i*SIZE,null);
}
}
}
}
public void addFlag(int r, int c) {//画一个旗子
ImageIcon icon = new ImageIcon("images/BomImage/11.jpg");
g.drawImage(icon.getImage(),X+c*SIZE,Y+r*SIZE,null);
}
public void addQuestion(int r,int c) {//画一个问号
ImageIcon icon = new ImageIcon("images/BomImage/12.jpg");
g.drawImage(icon.getImage(),X+c*SIZE,Y+r*SIZE,null);
}
public void addBlank(int r,int c){//问好变成空白
ImageIcon icon = new ImageIcon("images/BomImage/0.jpg");
g.drawImage(icon.getImage(),X+c*SIZE,Y+r*SIZE,null);
}
public boolean flagRight(){//检查旗帜是否正确,每个旗子下都有炸弹则赢得游戏
for (int i = 0; i < length; i++) {
for (int j = 0; j < length; j++) {
if(visit[i][j]==3){
if(arr[i][j]!=9){
return false;
}
}
}
}
return true;
}
public void expand(int r,int c){
//当检测到空格时,将周围八个格子全部打开(如果已访问或者是炸弹则不动),将探测到的新空格入队,自己出队
//当队列中没有元素是代表已经扩展完毕
//显示最开始点击的格子入队
int[] start = new int[2];
start[0] = r;
start[1] = c;
queue.add(start);
while(!queue.isEmpty()){
r = queue.peek()[0];
c = queue.peek()[1];
visit[r][c] = 1;
//向上
if(r>0 && visit[r-1][c]==0 && arr[r-1][c]<9){
open(r-1,c);
if(arr[r-1][c]==0 ){
int[] pos = new int[2];
pos[0] = r-1;
pos[1] = c;
queue.add(pos);
}
}
//向下
if(r<length-1 && visit[r+1][c]==0 && arr[r+1][c]<9){
open(r+1,c);
if(arr[r+1][c]==0){
int[] pos = new int[2];
pos[0] = r+1;
pos[1] = c;
queue.add(pos);
}
}
//向左
if(c>0 && visit[r][c-1]==0 && arr[r][c-1]<9){
open(r,c-1);
if(arr[r][c-1]==0){
int[] pos = new int[2];
pos[0] = r;
pos[1] = c-1;
queue.add(pos);
}
}
//向右
if(c<length-1 && visit[r][c+1]==0 && arr[r][c+1]<9) {
open(r, c + 1);
if (arr[r][c + 1] == 0) {
int[] pos = new int[2];
pos[0] = r;
pos[1] = c + 1;
queue.add(pos);
}
}
//左上
if(r>0 && c>0 && visit[r-1][c-1]==0 && arr[r-1][c-1]<9){
open(r-1,c-1);
if(arr[r-1][c-1]==0){
int[] pos = new int[2];
pos[0] = r-1;
pos[1] = c-1;
queue.add(pos);
}
}
//右上
if(r>0 && c<length-1 && visit[r-1][c+1]==0 && arr[r-1][c+1]<9){
open(r-1,c+1);
if(arr[r-1][c+1]==0){
int[] pos = new int[2];
pos[0] = r-1;
pos[1] = c+1;
queue.add(pos);
}
}
//右下
if(r<length-1 && c<length-1 && visit[r+1][c+1]==0 && arr[r+1][c+1]<9){
open(r+1,c+1);
if(arr[r+1][c+1]==0){
int[] pos = new int[2];
pos[0] = r+1;
pos[1] = c+1;
queue.add(pos);
}
}
//左下
if(r<length-1 && c>0 && visit[r+1][c-1]==0 && arr[r+1][c-1]<9){
open(r+1,c-1);
if(arr[r+1][c-1]==0){
int[] pos = new int[2];
pos[0] = r+1;
pos[1] = c-1;
queue.add(pos);
}
}
//一轮探索完毕
queue.remove();//将这一轮扩展完的移出队列
}
}
}
四、数据接口
接口中包含窗体位置、大小,和按钮。
mport javax.swing.*;
public interface Data {
int X=20,Y=40,SIZE=20;//窗体位置和格子大小
JRadioButton[] radio_btn = new JRadioButton[3];
}
五、监听器类
对鼠标左键和右键的点击在地图上加载相应的图片。
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
public class BombListener implements MouseListener, ActionListener,Data{
private BombUtil bombUtil = new BombUtil();
private JLabel notice;//提示框
public BombUtil getBombUtil(){
return bombUtil;
}
public void setNotice(JLabel notice){
this.notice = notice;
}
@Override
public void actionPerformed(ActionEvent e) {
String text = e.getActionCommand();
JButton btn = (JButton) e.getSource();
if(text.equals("开始游戏")){//点击开始游戏后需要做一些初始化操作
bombUtil.flush();
bombUtil.initGame();
bombUtil.setFlagNum(0);
bombUtil.setStart(true);
notice.setText("剩余炸弹数:"+Integer.toString(bombUtil.getBombNum()));
btn.setText("结束游戏");
}
if(text.equals("结束游戏")){
btn.setText("开始游戏");
bombUtil.flush();
bombUtil.setStart(false);
}
}
@Override
public void mouseClicked(MouseEvent e) {
}
@Override
public void mousePressed(MouseEvent e) {
int btn = e.getButton();//记录点击的按钮
int r,c;//行列值
if(bombUtil.checkout(e.getX(),e.getY())){//检查是否点在了地图外
return;
}
else if(bombUtil.getStart()){
if(btn==MouseEvent.BUTTON1){//点到鼠标左键
//计算行列值
r = bombUtil.getPos(e.getX(),e.getY())[0];
c = bombUtil.getPos(e.getX(),e.getY())[1];
if(bombUtil.getVisit()[r][c]==0 && bombUtil.getArr()[r][c]!=0) {//打开未访问的格子
bombUtil.open(r,c);
bombUtil.getVisit()[r][c] = 1;
}
else if(bombUtil.getVisit()[r][c]==0 && bombUtil.getArr()[r][c]==0){//点到空格则扩展
bombUtil.open(r,c);
bombUtil.expand(r,c);
}
if(bombUtil.getArr()[r][c]==9){//左键点到炸弹直接输
JOptionPane.showMessageDialog(null,"你输掉了游戏!");
}
}
else if(btn==MouseEvent.BUTTON3){//点到鼠标右键
r = bombUtil.getPos(e.getX(),e.getY())[0];
c = bombUtil.getPos(e.getX(),e.getY())[1];
if(bombUtil.getVisit()[r][c]==0){//插棋子
if(bombUtil.getFlagNum()==bombUtil.getBombNum()){
JOptionPane.showMessageDialog(null,"没有更多炸弹了!");
return;
}
bombUtil.addFlag(r,c);
bombUtil.getVisit()[r][c] = 2;
bombUtil.setFlagNum(bombUtil.getFlagNum()+1);
notice.setText("剩余炸弹数:"+Integer.toString(bombUtil.getBombNum()-bombUtil.getFlagNum()));
}
else if(bombUtil.getVisit()[r][c] == 2){//棋子变问号
bombUtil.addQuestion(r,c);
bombUtil.getVisit()[r][c] = 3;
bombUtil.setFlagNum(bombUtil.getFlagNum()-1);
notice.setText("剩余炸弹数:"+Integer.toString(bombUtil.getBombNum()-bombUtil.getFlagNum()));
}
else if(bombUtil.getVisit()[r][c] == 3){//问号变空白
bombUtil.addBlank(r,c);
bombUtil.getVisit()[r][c] = 0;
}
}
}
if(bombUtil.getStart() && bombUtil.visitAll() && bombUtil.flagRight()){//全部访问且旗帜正确则胜利
JOptionPane.showMessageDialog(null,"你赢得了游戏!");
}
}
@Override
public void mouseReleased(MouseEvent e) {
}
@Override
public void mouseEntered(MouseEvent e) {
}
@Override
public void mouseExited(MouseEvent e) {
}
}