扫雷小游戏-课程设计

该项目是一个扫雷小游戏,包括难度选择、用户登录注册功能。使用二维数组存储游戏状态,通过文件读取和存储实现用户数据持久化。游戏有初级、中级、高级三种难度,核心算法涉及雷区随机布雷及数字计算。玩家点击格子进行游戏,获胜条件是找出所有非雷格子且不触雷。项目还包括游戏状态的切换,如游戏胜利和失败的界面展示。
摘要由CSDN通过智能技术生成

一、项目介绍

扫雷是一款大众类的益智小游戏。根据点击格子出现的数字找出所有非雷格子,同时避免踩雷,踩到一个雷即全盘皆输。这款游戏有着很长的历史,从扫雷被开发出来到现在进行了无数次的优化,这款游戏通过简单的玩法,加上一个好看的游戏界面,每一处的细节都体现了扫雷的魅力。

项目采用技术

二维数组

文件读取存储

功能需求分析

完成 难度选择雷随机生成数字生成用户登录与注册等功能实现
游戏四种状态: 难度选择游戏状态游戏胜利游戏失败
游戏难度: 初级中级高级(不同难度对应不同的雷区大小和雷数量)
游戏核心: 二维数组 的相关操作
其他: 窗口绘制、界面规划、操作计数、重新开始。

项目亮点

1.登陆注册用到了文件的读取和存储

2.用二维数组来储存信息

3.将容器划分为网格,每个网格中可以放置一个组件,再对组件进行处理

系统演示图片

登录

注册图片


 切换难度

 游戏失败

游戏胜利

核心代码

文件的存储和读取

public void mouseClicked(MouseEvent t) {
        if (t.getSource() == submit) {
            //注册
            if (username.getText().length() == 0 || password.getText().length() == 0 || rePassword.getText().length() == 0) {
                showDialog("用户名和密码不能为空");
                return;
            }
            if (!password.getText().equals(rePassword.getText())) {
                showDialog("两次密码不一致");
                return;
            }
            if (!username.getText().matches("[a-zA-Z0-9]{3,16}")) {
                showDialog("用户名不符合规则");
                return;
            }
            if (!password.getText().matches("[a-zA-Z0-9]{3,16}")) {
                showDialog("密码不符合规则");
                return;
            }
            if (containsUsername(username.getText())) {
                showDialog("用户名已存在,重新输入");
                return;
            }
            //添加用户
            allUsers.add(new User(username.getText(), password.getText()));
            //写入文件
            BufferedWriter bw = null;
            try {
                //创建输出缓冲流对象
                bw = new BufferedWriter(new FileWriter("C:\\Users\\86151\\IdeaProjects\\untitled1\\42\\src\\user.txt"));
                for (User u : allUsers) {
                    bw.write(u.toString());
                    bw.newLine();
                    bw.flush();
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                //释放资源
                if (bw != null) {
                    try {
                        bw.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
            //提醒注册成功
            showDialog("注册成功");
            //关闭注册界面,打开登陆界面
            this.setVisible(false);
            new LoginJFrame();
        }
         else if (t.getSource() == reset) {
            //重置
            username.setText("");
            password.setText("");
            rePassword.setText("");
        }
    }
=====================================================================
private void readUser(){
        //读取数据
        // 创建了一个 BufferedReader 对象,并将其与一个 FileReader 对象关联。
                BufferedReader reader = null;
        // 创建了一个 List 集合,
                List<String> list = new ArrayList<>();
                try {
                    reader = new BufferedReader(new FileReader("C:\\Users\\86151\\IdeaProjects\\untitled1\\42\\src\\user.txt"));
                    String line;
                    // 使用 BufferedReader 类的 readLine() 方法来一行一行地读取文件中的内容。
                    while ((line = reader.readLine()) != null) {
                        // 对于每一行,我们都将其添加到 List 集合中
                        list.add(line);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    if (reader != null) {
                        try {
                            reader.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
                //遍历集合获取用户信息
        for(String str:list){
            //username=zhangsan & password=123
            String []userIn=str.split("&");
            String[] list1=userIn[0].split("=");
            String[] list2=userIn[1].split("=");
            User u=new User(list1[1],list2[1]);
            allUsers.add(u);
        }
    }

布雷

public void layMinesForBlock(Block [][] block,int mineCount){ //在雷区布置mineCount个雷
        initBlock(block);       //先都设置是无雷
        int row=block.length;
        int column=block[0].length;
        LinkedList<Block> list=new LinkedList<Block>();
        for(int i=0;i<row;i++) {
            for(int j=0;j<column;j++)
                list.add(block[i][j]);
        }
        while(mineCount>0){//开始布雷
            //它首先获取列表的大小,并生成一个随机索引值。
            int size=list.size();             // list返回节点的个数
            int randomIndex=(int)(Math.random()*size);
            // 然后,它使用这个索引值从列表中获取一个方块对象,并将其 isMine 属性设置为 true,
            Block b=list.get(randomIndex);
            b.setIsMine(true);
            // 将其名称设置为 "雷",并将其图标设置为 mineIcon。
            b.setName("雷");
            b.setMineIcon(mineIcon);
            // 最后,它从列表中删除这个方块对象,并将地雷计数器减一。
            list.remove(randomIndex);
            mineCount--;
            // 当地雷计数器减为零时,循环结束。
        }
        for(int i=0;i<row;i++){             //检查布雷情况,标记每个方块周围的雷的数目
            for(int j=0;j<column;j++){
                if(block[i][j].isMine()){
                    block[i][j].setIsOpen(false);
                    block[i][j].setIsMark(false);
                }
                else {
                    //计算一个二维数组 block 中某个元素周围的地雷数量。
                    // 它首先定义了一个变量 mineNumber 并将其初始值设置为 0。
                    int mineNumber=0;
                    // 然后,它使用两个嵌套循环遍历该元素周围的所有元素
                    for(int k=Math.max(i-1,0);k<=Math.min(i+1,row-1);k++) {
                        for(int t=Math.max(j-1,0);t<=Math.min(j+1,column-1);t++){
                            // 对于每一个元素,如果它是地雷(即 block[k][t].isMine() 返回 true),则将 mineNumber 的值加一。
                            if(block[k][t].isMine())
                                mineNumber++;
                        }
                    }
                    // 循环结束后,mineNumber 的值就是该元素周围的地雷数量。
                    block[i][j].setIsOpen(false);
                    block[i][j].setIsMark(false);
                    block[i][j].setName(""+mineNumber);
                    block[i][j].setAroundMineNumber(mineNumber); //设置该方块周围的雷数目
                }
            }
        }
    }

 得到所点块周围不是雷的方块

public Stack<Block> getNoMineAroundBlock(Block bk){//得到方块bk附近区域不是雷的方块
        // 清空栈 notMineBlock。
        notMineBlock.clear();
        // 然后,它使用两个嵌套循环遍历二维数组中的每一个元素,寻找与参数 bk 相同的元素。
        for(int i=0;i<row;i++) {   //寻找bk在雷区block中的位置索引
            for(int j=0;j<colum;j++) {
                // 如果找到了这样的元素,则将其索引值分别赋给类的成员变量 m 和 n。
                if(bk == block[i][j]){
                    m=i;
                    n=j;
                    break;
                }
            }
        }
        if(!bk.isMine()) {     //方块不是雷
            show(m,n);        //见后面的递归方法
        }
        return notMineBlock;
    }
    public void show(int m,int n) {
        //首先检查二维数组中索引为 (m, n) 的元素周围是否有地雷。
        if(block[m][n].getAroundMineNumber()>0&&block[m][n].getIsOpen()==false){
            block[m][n].setIsOpen(true);
            notMineBlock.push(block[m][n]); //将不是雷的方块压栈
            return;
        }
        else if(block[m][n].getAroundMineNumber()==0&&block[m][n].getIsOpen()==false){
            // 则将该元素的 isOpen 属性设置为 true
            block[m][n].setIsOpen(true);
            notMineBlock.push(block[m][n]);
            //将不是雷的方块压栈
            // 然后,使用两个嵌套循环遍历该元素周围的所有元素,并对每一个元素递归调用本方法。
            for(int k=Math.max(m-1,0);k<=Math.min(m+1,row-1);k++) {
                for(int t=Math.max(n-1,0);t<=Math.min(n+1,colum-1);t++)
                    show(k,t);
            }
        }
    }

胜利

public boolean verifyWin(){
        //这个方法检查玩家是否获胜。
        // 它首先定义了一个布尔变量 isOK 并将其初始值设置为 false
        // 然后,它定义了一个整数变量 number 并将其初始值设置为 0。
        boolean isOK = false;
        int number=0;
        // 接下来,它使用两个嵌套循环遍历二维数组中的每一个元素。
        for(int i=0;i<row;i++) {
            for(int j=0;j<colum;j++) {
                // 对于每一个元素,如果它的 isOpen 属性为 false,则将 number 的值加一。
                if(block[i][j].getIsOpen()==false)
                    number++;
            }
        }
        // 循环结束后,如果 number 的值等于地雷数量(即类的成员变量 mineCount 的值),
        // 则将 isOK 的值设置为 true。最后,返回 isOK 的值。
        if(number==mineCount){
            isOK =true;
        }
        return isOK;
    }

设置方块信息

public  BlockView(){
        card=new CardLayout();
        //设置卡片式为面板的布局管理器。
        setLayout(card);
        //第一行代码创建了一个新的JLabel对象。它的文本内容为空字符串,文本的水平对齐方式为居中。
        blockNameOrIcon=new JLabel("",JLabel.CENTER);
        //第二行代码设置了标签上文本的水平对齐方式。它将文本的水平对齐方式设置为居中。
        blockNameOrIcon.setHorizontalTextPosition(AbstractButton.CENTER);
        //第三行代码设置了标签上文本的垂直对齐方式。它将文本的垂直对齐方式设置为居中。
        blockNameOrIcon.setVerticalTextPosition(AbstractButton.CENTER);

        blockCover=new JButton();

        add("cover",blockCover);
        add("view",blockNameOrIcon);
    }
    public void acceptBlock(Block block){
        this.block = block;
    }
    public void setDataOnView(){
        //这个方法根据类的成员变量 block 的属性来设置标签的文本和图标。
        if(block.isMine()){
            // 将标签的文本设置为方块的名称,并将标签的图标设置为地雷图标。
            blockNameOrIcon.setText(block.getName());
            blockNameOrIcon.setIcon(block.getMineicon());
        }
        else {
            // 否则,如果方块周围有地雷,则将标签的文本设置为方块周围地雷数量的字符串表示形式。
            int n=block.getAroundMineNumber();
            if(n>=1)
                blockNameOrIcon.setText(""+n);
            else
                // 将标签的文本设置为空格。
                blockNameOrIcon.setText(" ");
        }
    }
    //当玩家点击一个方块时,游戏会调用类的成员方法 seeBlockNameOrIcon() 来显示标签,
    // 从而让玩家看到方块的信息。
    public void seeBlockNameOrIcon(){
        card.show(this,"view");
        validate();
    }
    //当玩家重新开始游戏时,游戏会调用类的成员方法 seeBlockCover() 来显示按钮,从而遮挡标签。
    public void seeBlockCover(){
        card.show(this,"cover");
        validate();
    }
    public JButton getBlockCover(){
        return blockCover;
    }

初始化雷区

public void initMineArea(int row,int colum,int mineCount,String grade){
        //它首先清空面板 pCenter 中的所有组件,并将类的成员变量的值设置为相应的参数值。
        pCenter.removeAll();
        spendTime=0;
        markMount=mineCount;
        this.row=row;
        this.colum=colum;
        this.mineCount=mineCount;
        this.grade=grade;
        // 然后,它创建了一个二维数组,并使用两个嵌套循环遍历这个数组中的每一个元素。
        block=new Block[row][colum];
        for(int i=0;i<row;i++){
            for(int j=0;j<colum;j++)
                // 对于每一个元素,它创建了一个新的 Block 对象并将其存储在数组中。
                block[i][j]=new Block();
        }
        // 接下来,它调用布雷者对象的成员方法来布置地雷,并调用扫雷者对象的成员方法来准备扫雷。
        lay.layMinesForBlock(block,mineCount);     //布雷
        peopleScoutMine.setBlock(block,mineCount); //准备扫雷
        blockView=new BlockView[row][colum];       //创建方块的视图
        //GridLayout布局管理器将容器划分为网格,每个网格中可以放置一个组件。
        // 指定网格的行数和列数。
        pCenter.setLayout(new GridLayout(row,colum));
        // 然后,它创建了一个二维数组,并使用两个嵌套循环遍历这个数组中的每一个元素。
        // 对于每一个元素,它创建了一个新的 BlockView 视图对象并将其存储在数组中。
        for(int i=0;i<row;i++) {
            for(int j=0;j<colum;j++) {
                blockView[i][j]=new BlockView();
                block[i][j].setBlockView(blockView[i][j]); //方块设置自己的视图
                // 调用视图对象的成员方法来将block[i][j]的数据放入视图中。
                blockView[i][j].setDataOnView();
                // 接下来,它将视图对象添加到面板 pCenter 中,
                // 并为视图对象上的按钮注册了一个动作监听器和一个鼠标监听器。
                pCenter.add(blockView[i][j]);
                blockView[i][j].getBlockCover().addActionListener(this);//注册监视器
                blockView[i][j].getBlockCover().addMouseListener(this);
                blockView[i][j].seeBlockCover(); //初始时盖住block[i][j]的数据信息
                //将按钮设置为可用状态。
                blockView[i][j].getBlockCover().setEnabled(true);
                //按钮将显示这个图标,而不是文本。
                blockView[i][j].getBlockCover().setIcon(null);
            }
        }
        //将探明的雷数添加到文本中
        showMarkedMineCount.setText(""+markMount);
        //重绘面板。
        repaint();
    }

点击方块

public void actionPerformed(ActionEvent e) {
        // 如果方块是地雷,则调用视图对象的成员方法来显示标签。
        //如果事件源是重新开始按钮,则调用类的成员方法来初始化雷区,并重绘面板。
        //如果事件源是计时器,则将用时增加 1,并更新文本字段 showTime 的文本。
        //最后,检查玩家是否获胜。如果获胜,则停止计时器并显示提示框。
        if(e.getSource()!=reStart&&e.getSource()!=time) {
            //开始计时
            time.start();
            int m=-1,n=-1;
            for(int i=0;i<row;i++) { //找到单击的方块以及它的位置索引
                for(int j=0;j<colum;j++) {
                    if(e.getSource()==blockView[i][j].getBlockCover()){
                        m=i;
                        n=j;
                        break;
                    }
                }
            }
            //检查该方块是否是地雷。
            if(block[m][n].isMine()) { //用户输掉游戏
                //遍历二维数组中的每一个元素,并将每一个方块的按钮设置为不可用状态。
                for(int i=0;i<row;i++) {
                    for(int j=0;j<colum;j++) {
                        blockView[i][j].getBlockCover().setEnabled(false);//用户单击无效了
                        if(block[i][j].isMine())
                            blockView[i][j].seeBlockNameOrIcon(); //视图显示方块上的数据信息
                    }
                }
                time.stop();
                spendTime=0;             //恢复初始值
                markMount=mineCount;      //恢复初始值
            }
            else {
                //遍历周围没有地雷的方块,并显示它们上面的数据信息。
                //首先调用peopleScoutMine.getNoMineAroundBlock(block[m][n])方法获取周围没有地雷的方块,并将它们存储在notMineBlock栈中。
                Stack<Block> notMineBlock =peopleScoutMine.getNoMineAroundBlock(block[m][n]);
                while(!notMineBlock.empty()){
                    //在每次循环中,它使用notMineBlock.pop()方法移除栈顶元素并返回它。。
                    Block bk = notMineBlock.pop();
                    //接下来,它调用bk.getBlockView()方法获取该方块的视图对象,并将其赋值给变量viewforBlock。
                    ViewForBlock viewforBlock = bk.getBlockView();
                    //最后,它调用viewforBlock.seeBlockNameOrIcon()方法显示方块上的数据信息。
                    viewforBlock.seeBlockNameOrIcon();
                }
            }
        }
        //如果事件源是重新开始按钮,则调用类的成员方法来初始化雷区,并重绘面板。
        if(e.getSource()==reStart) {
            initMineArea(row,colum,mineCount,grade);
            repaint();
            validate();
        }
        //如果点击计时器,则将用时增加 1,并更新文本字段 showTime 的文本。
        if(e.getSource()==time){
            spendTime++;
            showTime.setText(""+spendTime);
        }
        if(peopleScoutMine.verifyWin()) {  //判断用户是否扫雷成功
            //则停止计时器并显示提示框
            showDialog("你胜利了");

            spendTime=0;


        }
    }

右键标记

public void mousePressed(MouseEvent e){ //探雷:给方块上插一个小旗图标(再次单击取消)
        // 当用户按下鼠标右键时,这个方法会被调用。

        //e.getSource()方法返回了触发事件的组件。强转将其转换为JButton类型,并将其赋值给变量source
        JButton source=(JButton)e.getSource();
        for(int i=0;i<row;i++) {
            for(int j=0;j<colum;j++) {
                // 对于每一个元素,如果事件源与该元素上的按钮相同,则检查该元素是否已经被标记。
                //1.检查鼠标事件的修饰符是否为InputEvent.BUTTON3_MASK。如果是,则表示用户按下了鼠标右键。
                //2.检查事件源(即被单击的按钮)是否与blockView[i][j]元素上的按钮相同。
                if(e.getModifiers()==InputEvent.BUTTON3_MASK&&
                        source==blockView[i][j].getBlockCover()){
                    if(block[i][j].getIsMark()) {
                        //将按钮上的图标设置为null。这样,按钮上原先显示的图标就会消失。
                        source.setIcon(null);
                        //将方块的isMark属性设置为false。这样,该方块就不再被标记为有地雷。
                        block[i][j].setIsMark(false);
                        markMount=markMount+1;
                        //更新文本
                        showMarkedMineCount.setText(""+markMount);
                    }
                    else{
                        // 标记该元素
                        source.setIcon(mark);
                        block[i][j].setIsMark(true);
                        markMount=markMount-1;
                        showMarkedMineCount.setText(""+markMount);
                    }
                }
            }
        }
    }

成员负责模块
杨德旭雷的标记,方块点击,初始化雷区,界面搭建
郭延博雷的布置,胜利判断,显示扫雷游戏中的方块

github地址:

https://gitee.com/yaun_123/minesweeping-mini-program-1

github提交照片:



 

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值