前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java实现扫雷小游戏二

Java实现扫雷小游戏二

作者头像
XG.孤梦
发布2022-05-27 09:52:42
4.2K0
发布2022-05-27 09:52:42
举报
文章被收录于专栏:日志随记

布雷

上一篇已经完成了界面UI的实现,接下来开始功能的实现。

定义布雷类(com.tools/LayMine.java)

(1)布雷

采用随机生成的布雷方式,玩家第一次点击小方格不应该是雷,故布雷功能设计在玩家第一次左键时开始布雷。参数row和col是第一次点击的鼠标坐标。

代码语言:javascript
复制
public class LayMine {
   /**
    * labels:存储方格的二维数据
    * row:当前鼠标点击的x值
    * col:当前鼠标点击的y值
    */
    public static void lay(MineLabel[][] labels, int row, int col) {
        int count = 0;
        Random random = new Random(); // 随机
        
        while (count<Tools.allcount) {
            int x = random.nextInt(Tools.rows);
            int y = random.nextInt(Tools.cols);
            
            if(!labels[x][y].isMineTag() && (x !=row && y!= col)) {
                // 布雷
                labels[x][y].setMineTag(true);
                count++;                
            }
        }
        countBomb(labels);
    }

(2)计算小方格周围雷的数量

分析当前方格(x,y)周围方格的坐标:设 x 和 y的最大值分别为 X 和 Y :

  1. ([0, x+1][0, y+1])
  2. ([x-1, x+1][0, y+1])
  3. ([x-1, X][0, y+1])
  4. ([0, x+1][y-1, y+1])
  5. ([x-1, x+1][y-1, y+1])
  6. ([x-1, X][y-1, y+1])
  7. ([0, x+1][y-1, Y])
  8. ([x-1, x+1][y-1, Y])
  9. ([x-1, X][y-1, Y])

当前点(x,y)的周围方格

  • x值最小值或者为0,或者为x-1,且不能小于0,
  • x的最小值取Math.max(0, x- 1),x的最大值x+1,或者为X,且x+1最大不能超过X,
  • 故x的范围:
    • Math.max(0, x - 1) 至 Math.min(Tools.rows - 1, x + 1)
  • 同理y的范围:
    • Math.max(0, y - 1) 至 Math.min(Tools.cols - 1, y + 1)

实现方法一

理解简单,通俗易懂,代码执行效率较低,不推荐使用

代码语言:javascript
复制
public static void countBomb(MineLabel[][] labels) {

int count = 0;
if (!mineLabel[i][j].isMine()) {
// 计算雷块周围八个方向雷数                                    
   /**
    * 上
    */
    if (i > 0) {
        if (labels[i - 1][j].isMine()) {
            count++;
        }
    }

   /**
    * 左上
    */
    if (i > 0 && j>0) {
        if (labels[i - 1][j-1].isMine()) {
            count++;
        }
    }    
                
   /**
    * 右上
    */
    if (i > 0&&j+1< Tools.cols) {
        if (labels[i - 1][j+1].isMine()) {
            count++;
        }
    }

   /**
    * 左
    */
    if (j>0) {
        if (labels[i][j-1].isMine()) {
            count++;
        }
    }

   /**
    * 右
    */
    if (j+1< Tools.cols) {
        if (labels[i][j+1].isMine()) {
            count++;
        }
    }

  /**
    * 左下
    */
    if (i+1< Tools.rows && j>0) {
        if (labels[i + 1][j-1].isMine()) {
            count++;
        }
    }
                    
   /**
    * 下
    */
    if (i+1< Tools.rows) {
        if (labels[i + 1][j].isMine()) {
            count++;
        }
    }

   /**
    * 右下
    */
    if (i+1< Tools.rows && j+1< Tools.cols) {
        if (labels[i + 1][j+1].isMine()) {
            count++;
        }
    }
  }
}

实现方法二

代码语言:javascript
复制
        /**
     * |计算周围雷数
     */
    public static void countBomb(MineLabel[][] labels) {
        int count = 0;
        for (int i = 0; i < Tools.rows; i++) {
            for (int j = 0; j < Tools.cols; j++) {
                count = 0;
                // 当前方格不是雷才计算周围雷的数量
                if (!labels[i][j].isMineTag()) {
                    for (int x = Math.max(i - 1, 0); x <= Math.min(i + 1, Tools.rows - 1); x++) {
                        for (int y = Math.max(j - 1, 0); y <= Math.min(j + 1, Tools.cols - 1); y++) {
                            if (labels[x][y].isMineTag()) {
                                count++;
                            }
                        }
                    }
                    labels[i][j].setCountAround(count);
                }        
            }
        }
        
    }
编写测试类(test/TestBomb.java)

测试布雷和计数是否准确

代码语言:javascript
复制
public class TestBomb {
    /**
     * 测试类
     */
    public static void main(String[] args) {
        MainFrame mainframe = new MainFrame();
        MineLabel[][] labels = mainframe.getBombJPanel().getLabels();
        LayMine.lay(labels, 3, 3); // 假设当前鼠标点击位置【3,3】
        LayMine.countBomb(labels);
        for (int i = 0; i < Tools.rows; i++) {
            for (int j = 0; j < Tools.cols; j++) {
                if (labels[i][j].isMineTag()) {
                        labels[i][j].setIcon(Tools.mine);
                }else {
                    int count = labels[i][j].getCountAround();
                    labels[i][j].setIcon(Tools.mineCount[count]);
                }
            }
        }
    }
}

运行效果

鼠标事件监听

添加鼠标事件监听(com.listener/MouListener)
代码语言:javascript
复制
public class MouListener implements MouseListener {

    MainFrame mainframe;
    MineLabel[][] labels;
    
    Boolean isDoublePress = false;;
    
    public MouListener(MineLabel[][] labels, MainFrame mainframe) {
        this.labels = labels;
        this.mainframe = mainframe;
    }

在 BombJPanel.java 中为雷区小方块添加事件监听

代码语言:javascript
复制
    public BombJPanel(MainFrame mainframe) {
        this.mainframe = mainframe;
        //定义布局方式,网格布局
        this.setLayout(new GridLayout(Tools.rows, Tools.cols));

        listener = new MouListener(labels,mainframe);
        
        init();
    }
    // 初始化
    private void init() {
        // 实例化小方格
        for(int i = 0;i<labels.length;i++) {
            for(int j = 0; j<labels[i].length;j++) {
                labels[i][j] = new MineLabel(i, j);
                labels[i][j].setIcon(Tools.blank);
                this.add(labels[i][j]);
                labels[i][j].addMouseListener(listener);
            }
        }
        
        // 实现边框效果
        Border lowerBorder = BorderFactory.createLoweredBevelBorder();
        Border emptyBorder = BorderFactory.createEmptyBorder(5, 5, 5, 5); //边框大小
        CompoundBorder compoundBorder = BorderFactory.createCompoundBorder(emptyBorder, lowerBorder)    ;
        this.setBorder(compoundBorder);
        this.setBackground(Color.LIGHT_GRAY);
    }
    
}

需求分析:鼠标操作包含:左键、右键、左右键同时按下、右键一次、右键两次、右键三次。

鼠标按下

鼠标左键按下时效果: (1)设置笑脸为惊叹 (2)左右键同时按下:设置鼠标所在位置周围小方格为背景效果

左键按下时效果: (1)设置笑脸为惊 (2)如果未被展开的,则显示鼠标所在位置的小方格的背景 (3)已被展开则不做处理

鼠标右键按下时效果: (1)第一次按下:标记插上红旗 (2)第二次按下:标记显示问号 (3)第三次按下:还原

代码语言:javascript
复制
    // 鼠标按下时
    @Override
    public void mousePressed(MouseEvent e) {
        MineLabel mineLabel = (MineLabel) e.getSource(); // 获取事件源
        int row = mineLabel.getRowx();
        int col = mineLabel.getColy();
        
            // 判断是否是鼠标双击(左右键)操作
        if (e.getModifiersEx() == InputEvent.BUTTON1_DOWN_MASK + InputEvent.BUTTON3_DOWN_MASK) {
                isDoublePress = true;
                doublePress(row, col);
            // 鼠标左键按下事件
        } else if (e.getModifiers() == InputEvent.BUTTON1_MASK && !mineLabel.isFlagTag()) {
                // 对没有被展开或标记的方格
            if (!mineLabel.isExpendTag()) {
                // 鼠标左键按下背景
                mineLabel.setIcon(Tools.mineCount[0]);
            }
                // 表情变惊讶
            mainframe.getFaceJPanel().getLabelFace().setIcon(Tools.face2);
            
            // 鼠标右键按下事件
        } else if (e.getModifiers() == InputEvent.BUTTON3_MASK && !mineLabel.isExpendTag()) {
                // 右键点击数为 0 时
            if (mineLabel.getRightClickCount() == 0) {
                mineLabel.setIcon(Tools.flag); // 设置旗子标记
                mineLabel.setRightClickCount(1);
                mineLabel.setFlagTag(true);
                Tools.bombCount--;
                mainframe.getFaceJPanel().setNumber(Tools.bombCount); // 改变计数区雷数图片
                
                // 右键点击数为 1 时
            } else if (mineLabel.getRightClickCount() == 1) {
                mineLabel.setIcon(Tools.ask); // 设置问号标记
                mineLabel.setRightClickCount(2);
                mineLabel.setFlagTag(false);
                Tools.bombCount++;
                mainframe.getFaceJPanel().setNumber(Tools.bombCount); // 改变计数区雷数图片
                
                // 第3次点击还原回  未标记状态
            } else {
                mineLabel.setIcon(Tools.blank);
                mineLabel.setRightClickCount(0);
                mineLabel.setFlagTag(false);
            }

        }
    }

FaceJPanel.java类中添加

代码语言:javascript
复制
// 计数器 根据当前旗子数计算剩余雷数
    public void setNumber(int count) {
        int b = 0;
        if (count < 0) {
            b = 10;
        } else {
            b = count / 100;
        }
        int g = Math.abs(count) % 10;
        int s = Math.abs(count) / 10 % 10;
        labelCountG.setIcon(Tools.timeCount[g]);
        labelCountS.setIcon(Tools.timeCount[s]);
        labelCountB.setIcon(Tools.timeCount[b]);
    }
鼠标释放

需求分析:包含左右键双击释放,左键释放,鼠标右键弹起没有任何动作。

鼠标左右键双击释放 (1)如果当前方格(被点击的方格)没有标记,且之前未被展开,则还原成点击前的状态(外观); (2)否则(已标记,或者已被展开),就判断方格周围雷的数量与周围被标记旗子的方格数是否相等,如果相等就展开周围的方格

A.相等的情况有标记存在两种情况:

  • 标记正确:如下图对圆圈所在方格双击释放时将打开其周围的方格
  • 标记错误:会有惩罚,相当于触雷,游戏结束。

即当前方格周围的雷全部并且正确标记,则会迅速打开当前方格周围未打开的方格,如果标记有错误,则进行惩罚

B.不相等:还原小方格状态

鼠标左键释放

(1)如果是第一次点击,则布雷,且点击的方格不布雷,确保第一次点击不触雷。 (2)如果踩到雷,则引发触雷,游戏结束 (3)否则展开方格

代码语言:javascript
复制
// 鼠标左右键同时按下
    private void doublePress(int row, int col) {
        for (int x = Math.max(0, row - 1); x <= Math.min(Tools.rows - 1, row + 1); x++) {
            for (int y = Math.max(0, col - 1); y <= Math.min(Tools.cols - 1, col + 1); y++) {
                if (!labels[x][y].isExpendTag() && !labels[x][y].isFlagTag()) {
                    int rightClickCount = labels[x][y].getRightClickCount();
                    // 对标记旗子或者展开的不做处理
                    if (rightClickCount == 1) {
                        labels[x][y].setIcon(Tools.flag);
                    } else {
                        labels[x][y].setIcon(Tools.mineCount[0]);
                    }
                }
            }
        }
    }
    
    
    // 左键展开方格
    private void expand(int x, int y) {
        int count = labels[x][y].getCountAround();
        if (!labels[x][y].isExpendTag() && !labels[x][y].isFlagTag()) {
            // 周围雷为0,递归调用继续展开
            if (count == 0) {
                labels[x][y].setIcon(Tools.mineCount[count]);
                labels[x][y].setExpendTag(true);
                for (int i = Math.max(0, x - 1); i <= Math.min(Tools.rows -1, x + 1); i++) {
                    for (int j = Math.max(0, y - 1); j <= Math.min(Tools.cols -1, y + 1); j++) {
                        expand(i, j);
                    }
                }
            } else {
                // 显示周围雷数
                labels[x][y].setIcon(Tools.mineCount[count]);
                labels[x][y].setExpendTag(true);
            }
        }
    }
    
    

    @Override
    public void mouseReleased(MouseEvent e) {
        MineLabel mineLabel= (MineLabel) e.getSource(); // 当前方格
        int row = mineLabel.getRowx();
        int col = mineLabel.getColy();
        
        // 鼠标双击释放(右键不做处理)
        if(isDoublePress) {
            isDoublePress = false;
            if (!mineLabel.isExpendTag() && !mineLabel.isFlagTag()) {
                backIcon(row, col);
            } else {
                boolean isEquals = isEquals(row, col);
                if (isEquals) {
                    doubleExpend(row, col);
                } else {
                    backIcon(row, col);
                }
            }
            mainframe.getFaceJPanel().getLabelFace().setIcon(Tools.face0);
        // 左键释放
        }else if (e.getModifiers() == InputEvent.BUTTON1_MASK && !mineLabel.isFlagTag()) {
            if(!Tools.isStart) {        // 第一次鼠标左键点击,在弹起事件中进行布雷
                LayMine.lay(labels, row, col); // 布雷
                Tools.isStart = true;   //设置isStart=true,表示不是第一次点击了
                mainframe.getTimer().start();  // 开启计时器
            }
            
            if (mineLabel.isMineTag()) {//判断是否踩到地雷
                bombAction(row, col); //如果踩到地雷,游戏结束,显示全部的地雷
                mineLabel.setIcon(Tools.blood);
                mainframe.getFaceJPanel().getLabelFace().setIcon(Tools.face3);
            } else {
                mainframe.getFaceJPanel().getLabelFace().setIcon(Tools.face0);
                expand(row, col);

            }

        }
        //判断雷是否已全被清除完
        isWin();
    }
    
    
    // 左右键双击展开
    private void doubleExpend(int i, int j) {
        for (int x = Math.max(0, i - 1); x <= Math.min(Tools.rows - 1, i + 1); x++) {
            for (int y = Math.max(0, j - 1); y <= Math.min(Tools.cols - 1, j + 1); y++) {
                if (labels[x][y].isMineTag()) { // 如果是雷
                    if (!labels[x][y].isFlagTag()) { // 没有旗子标记
                        bombAction(x, y);
                    }
                } else {  // 不是雷
                    if (!labels[x][y].isFlagTag()) { // 没有旗子标记
                        expand(x, y);
                    }
                }

            }
        }
    }
    
    // 触雷
    private void bombAction(int row, int col) {
        for (int i = 0; i < labels.length; i++) {
            for (int j = 0; j < labels[i].length; j++) {
                if (labels[i][j].isMineTag()) { // 是雷
                    if (!labels[i][j].isFlagTag()) { // 没有标记
                        labels[i][j].setIcon(Tools.mine0);
                    } else {
                        labels[i][j].setIcon(Tools.mine1);
                    }
                }
            }
        }
        // 修改踩雷状态
        Tools.isBoom = true;
        // 停止计时器
        mainframe.getTimer().stop();
        
        // 取消鼠标监听器
        for (int i = 0; i < labels.length; i++) {
            for (int j = 0; j < labels[i].length; j++) {
                labels[i][j].removeMouseListener(this);

            }
        }

    }
    
    //还原方格显示效果(因为鼠标按下时显示背景)
    private void backIcon(int i, int j) {
        for (int x = Math.max(0, i - 1); x <= Math.min(Tools.rows - 1, i + 1); x++) {
            for (int y = Math.max(0, j - 1); y <= Math.min(Tools.cols - 1, j + 1); y++) {
                if (!labels[x][y].isFlagTag() && !labels[x][y].isExpendTag()) {
                    
                    int rightClickCount = labels[x][y].getRightClickCount();
                    if (rightClickCount == 2) {
                        labels[x][y].setIcon(Tools.ask);
                    } else {
                        if(!labels[x][y].isFlagTag()){
                            labels[x][y].setIcon(Tools.blank);
                        }
                    }
                }
            }
        }

    }
计数区面板点击事件

(1)计时(com.timer/Timers) 在主窗体中添加计时器timer控件,并编写事件监听器

代码语言:javascript
复制
 // 计时器
public class Timers implements ActionListener {
    private int times;
    MainFrame mainfame;
    public Timers(MainFrame mainfame){
        this.mainfame = mainfame;
    }
    @Override
    public void actionPerformed(ActionEvent arg0) {
        // TODO Auto-generated method stub
        Tools.timecount++;
        if(Tools.timecount>999){
            Tools.timecount=999;
        }else{    
            mainfame.getFaceJPanel().setTime(Tools.timecount);
        }
    }
}

在FaceJPanel.java中添加:

代码语言:javascript
复制
// 计时器
    public void setTime(int count) {
        int b = 0;
        if (count < 0) {
            b = 10;
        } else {
            b = count / 100;
        }
        int g = Math.abs(count) % 10;
        int s = Math.abs(count) / 10 % 10;
        labelTimeG.setIcon(Tools.timeCount[g]);
        labelTimeS.setIcon(Tools.timeCount[s]);
        labelTimeB.setIcon(Tools.timeCount[b]);
    }

(2)“笑脸”事件处理(com.panel/FaceJPanel.java)

代码语言:javascript
复制
public class FacelabelListener extends MouseAdapter{
        @Override
        public void mousePressed(MouseEvent e) {
            if (e.getModifiers() == InputEvent.BUTTON1_MASK) {
                mainframe.getTimer().stop();
                labelFace.setIcon(Tools.face1);
            }
        }
    
        @Override
        public void mouseReleased(MouseEvent e) {
            if (e.getModifiers() == InputEvent.BUTTON1_MASK) {
                mainframe.getTimer().start();
                mainframe.reStartGame();
                labelFace.setIcon(Tools.face0);
            }
    }

(3)重新开始方法(com.main/mainFrame.java)

代码语言:javascript
复制
BombJPanel bombJPanel = new BombJPanel(this);    
FaceJPanel faceJPanel = new FaceJPanel(this);

public void reStartGame() {
        // 游戏重新开始方法
        this.remove(faceJPanel);
        this.remove(bombJPanel);

        Tools.bombCount = Tools.allcount;
        Tools.timecount = 0;
        Tools.isStart = false;
        Tools.isBoom = false;

        faceJPanel = new FaceJPanel(this);
        bombJPanel = new BombJPanel(this);
        this.add(faceJPanel, BorderLayout.NORTH);
        this.add(bombJPanel);
        this.pack();
        this.validate();

        getTimer().stop();

    }

在的init方法中添加

代码语言:javascript
复制
    private void init() {
        // 菜单栏
        this.setJMenuBar(menuBar);
        BorderLayout layout = new BorderLayout();
        
        this.setLayout(layout);
        // 计数区
        this.add(faceJPanel,layout.NORTH);
        
        // 雷区
        this.add(bombJPanel,layout.CENTER);
        
    }
扫雷成功

需求分析:把不是雷的方格全部展开,如果不是雷的方格全部展开了,但雷没被标记也算扫雷成功,以下等式成立即可。

被展开的方格数量 = 所有方格数量 - 雷的数量

代码语言:javascript
复制
private void isWin() {
        
        int expendCount = 0;
        for (int i = 0; i < labels.length; i++) {
            for (int j = 0; j < labels[i].length; j++) {
                if (labels[i][j].isExpendTag()) {
                    expendCount++;
                }
            }
        }
        if (Tools.rows * Tools.cols - expendCount == Tools.allcount) {

            for (int i = 0; i < Tools.rows; i++)
                for (int j = 0; j < Tools.cols; j++) {
                    if (mainframe.getBombJPanel().getLabels()[i][j].isMineTag()
                            && !mainframe.getBombJPanel().getLabels()[i][j].isFlagTag()) {
                        mainframe.getBombJPanel().getLabels()[i][j].setIcon(Tools.flag);
                    }
                    // 移除监听
                    mainframe.getBombJPanel().getLabels()[i][j]
                            .removeMouseListener(mainframe.getBombJPanel()
                                    .getListener());

                }
            mainframe.getFaceJPanel().getLabelFace().setIcon(Tools.face4);

            mainframe.getFaceJPanel().setNumber(0);

            mainframe.getTimer().stop();
            new Win(mainframe);
            

            //成功后弹出英雄记录版
             
            Tools.isStart = false;
        }
        
    }

    //判断方格周围雷的数量与周围被标记的方格数是否相等
    private boolean isEquals(int i, int j) {
        int count = labels[i][j].getCountAround();
        int flagCount = 0;
        for (int x = Math.max(0, i - 1); x <= Math.min(Tools.rows - 1, i + 1); x++) {
            for (int y = Math.max(0, j - 1); y <= Math.min(Tools.cols - 1, j + 1); y++) {
                if (labels[x][y].isFlagTag()) {
                    flagCount++;
                }
            }
        }
        if (count == flagCount) {
            return true;
        }else {
            return false;
        }
    }
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-05-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 布雷
    • 定义布雷类(com.tools/LayMine.java)
      • 编写测试类(test/TestBomb.java)
      • 鼠标事件监听
        • 添加鼠标事件监听(com.listener/MouListener)
          • 鼠标按下
            • 鼠标释放
              • 计数区面板点击事件
                • 扫雷成功
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档