TypechoJoeTheme

日志随记

统计
登录
用户名
密码

扫码登录
/
注册
用户名

此用户名将作为登录时所用的账号

邮箱

XG.孤梦

随风而动,随遇而安......

Java实现扫雷小游戏二

XG.孤梦博 主大佬
2022-05-26
/
0 评论
/
924 阅读
/
1373 个字
/
百度已收录
05/26
本文最后更新于 2022年08月13日,已超过 620天没有更新。如果文章内容或图片资源失效,请留言反馈,我会及时处理,谢谢!

布雷

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

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

(1)布雷

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

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)

实现方法一

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

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++;
        }
    }
  }
}

实现方法二

        /**
     * |计算周围雷数
     */
    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)

测试布雷和计数是否准确

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)

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 中为雷区小方块添加事件监听

    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)第三次按下:还原

    // 鼠标按下时
    @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类中添加

// 计数器 根据当前旗子数计算剩余雷数
    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)否则展开方格

// 鼠标左右键同时按下
    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控件,并编写事件监听器

 // 计时器
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中添加:

// 计时器
    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)

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)


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方法中添加

    private void init() {
        // 菜单栏
        this.setJMenuBar(menuBar);
        BorderLayout layout = new BorderLayout();
        
        this.setLayout(layout);
        // 计数区
        this.add(faceJPanel,layout.NORTH);
        
        // 雷区
        this.add(bombJPanel,layout.CENTER);
        
    }

扫雷成功

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

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

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;
        }
    }
    

Java扫雷
朗读
赞(1)
赞赏
感谢您的支持,我会继续努力哒!
版权属于:

日志随记

本文链接:

http://www.xggm.top/archives/903.html(转载时请注明本文出处及文章链接)

评论 (0)
 
XG.孤梦博 主大佬

随风而动,随遇而安......


访问 110851 ℃
51 文章数
115 评论量

标签云

登录
X
用户名
密码