扫雷游戏-Java课程设计

一、data

1、Bolck

Block类:其实例是雷区中的方块。
Block的实例是雷区中的方块,方块可以是雷也可以不是雷。如果方块是雷,该方块的isMine属性值就是true, 否则是false。 当方块的isMine 属性值是false 时,该方块的aroundMineNumber属性值是和该方块相邻且是雷的方块数目(一个方块最多可以有8个相邻的方块)。当该方块的isMine属性值是true 时,minelcon 属性值是一个Imagelcon图标的实例(地雷的样子)。
比如说:aroundMineNumber=1,说明它周围的8个方块中,有一个是地雷。

用来获取、设置成员变量。

package ch8.data;
import javax.swing.ImageIcon;
//Block方块
public class Block {
     String name;                   //名字,比如"雷"或数字
     int aroundMineNumber;          //如果不是类,此数据是周围雷的数目。Mine地雷
     ImageIcon mineIcon;            //雷的图标
     public boolean isMine=false;   //是否是雷
     boolean isMark=false;          //是否被标记
     boolean isOpen=false;          //是否被挖开
     ViewForBlock  blockView;       //方块的视图
    //这里不是接口创建对象,是接口声明了一个引用。
     public void setName(String name) { 
         this.name=name; 
     }
    public String getName() {
        return name;
    }

     public void setAroundMineNumber(int n) { 
         aroundMineNumber=n;
     }
     public int getAroundMineNumber() {
         return aroundMineNumber;
     }

     public boolean isMine() {
         return isMine;
     } 
     public void setIsMine(boolean b) {
         isMine=b;
     }
     public void setMineIcon(ImageIcon icon){
         mineIcon=icon;
     }
     public ImageIcon getMineicon(){
         return mineIcon;
     }
     public boolean getIsOpen() {
         return isOpen;
     } 
     public void setIsOpen(boolean p) {
         isOpen=p;
     }
     public boolean getIsMark() {
         return isMark;
     } 
     public void setIsMark(boolean m) {
         isMark=m;
     }
     public void setBlockView(ViewForBlock view){
        blockView = view;
        blockView.acceptBlock(this);//确定是哪个方块的视图
     } 
     public ViewForBlock getBlockView(){
        return  blockView ;
     }
}

2、LayMines

LayMines类:其实例负责在雷区布雷。即随机设置某些方块是雷。

package ch8.data;
import java.util.LinkedList;
import javax.swing.ImageIcon;
//布置雷区的类
public class LayMines {   
     ImageIcon mineIcon;
//     ImageIcon类是图片图标类。根据图片绘制图标。
     public LayMines() {
//         这是一个构造函数
          mineIcon=new ImageIcon("扫雷图片/mine.gif");
     }
     public void initBlock(Block [][] block){//初始化雷区
         for(int i=0;i<block.length;i++) {
             for(int j=0;j<block[i].length;j++)
                block[i][j].setIsMine(false);
             //当方块的isMine 属性值是false时,该方块的aroundMineNumber属性值是和该方块相邻且是雷的方块数目
         } 
     }
     public void layMinesForBlock(Block [][] block,int mineCount){ //在雷区布置mineCount个雷
         initBlock(block);       //先都设置是无雷
         int row=block.length;
         int column=block[0].length;
//       LinkedList集合数据存储的结构是链表结构。方便元素添加、删除的集合。
//       是一个双向链表
//         这里创建了Block类型的LinkedList集合
         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);
            Block b=list.get(randomIndex);
            b.setIsMine(true);      //设置方块是雷
             b.setName("雷");
            b.setMineIcon(mineIcon);
            list.remove(randomIndex);        //list删除索引值为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 {
                 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++){
                          if(block[k][t].isMine())
                              mineNumber++; 
                       }
                 }
                 block[i][j].setIsOpen(false); //是否被挖开
                 block[i][j].setIsMark(false); //是否被标记
                 block[i][j].setName(""+mineNumber);
                 block[i][j].setAroundMineNumber(mineNumber); //设置该方块周围的雷数目
              }
           } 
        }    
    }
}

3、PeopleScoutMine

PeopleScoutMine类的实例负责在雷区扫雷。该实例使用方法StackgetNoMineAroundBlock(Block bk)寻找不是雷的方块,并将找到的方块压入堆栈,然后返回该堆栈。

如果参数bk不是雷,但bk相邻的方块中有方块是雷,那么找到的不是雷的方块就是bk。如果bk不是雷,但bk相邻的方块中没有任何一个方块是雷,那么就把相邻的方块作为getNoMineAroundBlock(Block bk)方法的参数继续调用该方法,即PeopleScoutMine类的实例用递归方法寻找一个方块周围区域内不是雷的方块,并将这些方块压入堆栈,返回该堆栈。该实例使用方法public boolean verifyWin()判断用户是否扫雷成功。如果剩余的、没有揭开的方块数目刚好等于雷区的总雷数,该方法返回true,否则返回false.

package ch8.data;
import java.util.Stack;

public class PeopleScoutMine {
   public Block [][] block;    //雷区的全部方块
   Stack<Block>  notMineBlock; //存放一个方块周围区域内不是雷的方块
   int m,n ;                   //方块的索引下标
   int row,colum;              //雷区的行和列
   int mineCount;              //雷的数目
//    构造方法,
   public PeopleScoutMine(){
       notMineBlock = new Stack<Block>();
//       寻找不是雷的方块,并将找到的方块压入堆栈
   }
   public void setBlock(Block [][] block,int mineCount){
       this.block = block;
       this.mineCount =  mineCount;
       row = block.length;
       colum = block[0].length;
   }
//   什么是bk?如果参数bk不是雷,但bk相邻的方块中有方块是雷,那么找到的不是雷的方块就是bk。
   public Stack<Block> getNoMineAroundBlock(Block bk){//得到方块bk附近区域不是雷的方块
       notMineBlock.clear();
       for(int i=0;i<row;i++) {   //寻找bk在雷区block中的位置索引
             for(int j=0;j<colum;j++) {
               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) {
//          如果周围雷的数目>0,并且没有被挖开过。
       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){
          /*
          如果bk不是雷,但bk相邻的方块中没有任何一个方块是雷,
          那么就把相邻的方块作为getNoMineAroundBlock(Block bk)方法的参数继续调用该方法,
          即PeopleScoutMine类的实例
          用递归方法寻找一个方块周围区域内不是雷的方块,
          并将这些方块压入堆栈,返回该堆栈。
           */
          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(){
       boolean isOK = false;
       int number=0;
       for(int i=0;i<row;i++) {
           for(int j=0;j<colum;j++) {
              if(block[i][j].getIsOpen()==false)
                 number++;
           }
       }
       /*
       使用方法public boolean verifyWin()判断用户是否扫雷成功。
       如果剩余的、没有揭开的方块数目刚好等于雷区的总雷数,胜利!
       该方法返回true,否则返回false.
        */
       if(number==mineCount){
           isOK =true;
       }
       return isOK;
   }
}

4、ViewForBlock

方块需要一个外观提供给游戏的玩家,以便玩家单击方块或标记方块进行扫雷。ViewForBlock接口封装了给出视图的方法,
例如void acceptBlock(Block block)方法确定该视图为哪个Block实例提供视图。
实现ViewFor接口的类将在视图(View) 设计部分给出,见稍后8.4节中的BlockView类。

package ch8.data;
public interface ViewForBlock {
    public void acceptBlock(Block block);   //确定是哪个方块的视图
    public void setDataOnView();            //设置视图上需要显示的数据
    public void seeBlockNameOrIcon();       //显示图标方块上的名字或图标
    public void seeBlockCover();            //显示视图上负责遮挡的组件
    public Object getBlockCover();          //得到视图上的遮挡组件
}

5、RecordOrShowRecord

使用内置Derby数据库record存放玩家的成绩(有关内置Derby数据库的知识点可参见本书的第3章)。数据库使用表存放成绩,即表示英雄榜。
表中的字段p_ name的值是玩家的名字,字段P_time 是玩家的用时。玩家只要排进前3名就可以进入英雄榜,英雄榜上原有的第3名就退居到第4名(英雄榜记录着曾经的扫雷英雄)。
RecordOrShowRecord类的实例可以向英雄榜插入记录或查看英雄榜。

有关知识:
try{Class.forName(“org.apache.derby.jdbc.EmbeddedDriver”);
derby是apache的一个开源数据库产品,有丰富的特性。它支持client/server模式外,也支持embedded模式,即只需一个包含embedded driver的jar包,就可以在代码内启动及关闭数据库。在小项目中使用嵌入式的数据库也是一个不错的选择。
这里使用jdbc来连接derby进行操作并无特别之处。只需要将embedded driver包derby.jar包含进class_path,将创建driver实例,使用通常jdbc代码即可访问。

顺便记一下jdbc访问数据库的过程,尽管以上代码可以演示说明:
(1)所有相关类及接口都在包java.sql中。另外还有javax.sql,都是扩展内容
(2)由DriverManager获得到要连接数据库的Connection
(3)Connection创建Statement或PreparedStatement
(4)Statement或PreparedStatement执行sql语句,有可能返回ResultSet结果集。可以对ResultSet进行遍历访问
(5)若有insert/update/delete等数据操作,需调用Connection的commit().(如果设置为不自动提交)
(6)conn.close()断开与数据库的连接。

package ch8.data;
import java.sql.*;
public class RecordOrShowRecord{
   Connection con;
//   Connection接口代表与特定的数据库的连接.要对数据表中的数据进行操作,首先要获取数据库连接.
   String tableName ;
   int heroNumber   = 3;   //英雄榜显示的最多英雄数目
   public  RecordOrShowRecord(){//构造方法
      try{Class.forName("org.apache.derby.jdbc.EmbeddedDriver");
      }
      catch(Exception e){}
   }
//   设置数据库表名,人名,时间
   public void setTable(String str){
      tableName = "t_"+str;
      connectDatabase();//连接数据库
     
      try {
          Statement sta = con.createStatement();
          String SQL="create table "+tableName+
         "(p_name varchar(50) ,p_time int)";
          sta.executeUpdate(SQL);//创建表
          con.close();
      }
      catch(SQLException e) {//如果表已经存在,将触发SQL异常,即不再创建该表
      }
   }
   public boolean addRecord(String name,int time){
       boolean ok  = true;
      if(tableName == null)
         ok = false;
      //检查time是否达到标准(进入前heroNumber名),见后面的verifyScore方法:
       int amount = verifyScore(time);
       //如果数量大于等于3名,不加入英雄榜
       if(amount >= heroNumber) {
          ok = false;  
       }
       else {
          connectDatabase();  //连接数据库
          try { 
             String SQL ="insert into "+tableName+" values(?,?)";
             PreparedStatement sta  = con.prepareStatement(SQL);
             sta.setString(1,name);
             sta.setInt(2,time);
             sta.executeUpdate();
             con.close();
             ok = true;
          }
          catch(SQLException e) {
             ok = false;
          }
       }
       return ok;
   }
//   查询数据库记录
   public String [][] queryRecord(){
      if(tableName == null)
         return null;
      String [][] record  = null;
      Statement sql; 
      ResultSet rs;
      try { 
        sql=
        con.createStatement
       (ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_READ_ONLY);
        String str = "select * from "+tableName+" order by p_time ";
        rs=sql.executeQuery(str);
        boolean boo =rs.last(); 
        if(boo == false)
           return null;
        int recordAmount =rs.getRow();//结果集中的全部记录
        record = new String[recordAmount][2];
        rs.beforeFirst();
        int i=0;
        while(rs.next()) { 
          record[i][0] = rs.getString(1);
          record[i][1] = rs.getString(2);
          i++;
        }
        con.close();
      }
      catch(SQLException e) {}
      return record;
   }
   //连接数据库
   private void connectDatabase(){
      try{  
         String uri ="jdbc:derby:record;create=true";
         con=DriverManager.getConnection(uri); //连接数据库,如果不存在就创建
         
      }
      catch(Exception e){} 
   }
   //判断英雄榜记录是不是已经超过3个了
   private int verifyScore(int time){
      if(tableName == null)
         return Integer.MAX_VALUE ;
      connectDatabase();    //连接数据库
      Statement sql; 
      ResultSet rs;
      int amount = 0;
      String str =
      "select * from "+tableName+" where p_time < "+time;
      try { 
        sql=con.createStatement();
        rs=sql.executeQuery(str);
        while(rs.next()){ 
           amount++;
        }                        
        con.close();
      }
      catch(SQLException e) {}
      return amount;
   }
}

二、测试

把8.2节给出的类看作一个小框架,下面用框架中的类编写一个简单的应用程序,测试扫雷,即在命令行表述对象的行为过程,如果表述成功(如果表述困难,说明数据模型不是很合理) ,那么就为以后的GUI程序设计提供了很好的对象功能测试,

在后续的GUI设计中,重要的工作仅仅是为某些对象提供视图界面,并处理相应的界面事件而已。

package ch8.test;
import ch8.data.*;
import java.util.Stack;
public class AppTest {
   public static void main(String [] args) {
       Block block[][] = new Block[5][10];  //雷区
       for(int i=0;i<block.length;i++) {   
           for(int j = 0;j<block[i].length;j++) {
             block[i][j] = new Block();
           }
       }
       LayMines layMines = new LayMines();                        //布雷者
       PeopleScoutMine peopleScoutMine  = new PeopleScoutMine();  //扫雷者
       layMines.layMinesForBlock(block,10); //在雷区布10个雷
       System.out.println("雷区情况:");
       intputShow(block);
       peopleScoutMine.setBlock(block,10);  //准备扫雷 
       Stack<Block> stack = peopleScoutMine.getNoMineAroundBlock(block[0][0]);//扫雷
       if(block[0][0].isMine()){
          System.out.println("我的天啊,踩着地雷了啊");
          return;
       }
       System.out.println("扫雷情况:");
       intputProcess(block,stack);
       System.out.println("成功了吗:"+peopleScoutMine.verifyWin());
       if(block[3][3].isMine()){
          System.out.println("我的天啊,踩着地雷了啊");
          return;
       }
       stack = peopleScoutMine.getNoMineAroundBlock(block[3][3]);//扫雷
       System.out.println("扫雷情况:");
       intputProcess(block,stack);
       System.out.println("成功了吗:"+peopleScoutMine.verifyWin());
   }
   static void intputProcess(Block [][] block,Stack<Block> stack){
       int k = 0;
       for(int i=0;i<block.length;i++) {
           for(int j = 0;j<block[i].length;j++){
              if(!stack.contains(block[i][j])&&block[i][j].getIsOpen()==false){
                 System.out.printf("%2s","■ "); //输出■表示未挖开方块
              }
              else {
                 int m = block[i][j].getAroundMineNumber();//显示周围雷的数目
                 System.out.printf("%2s","□"+m);
              }
           }
           System.out.println();
       }  
   }
   static void intputShow(Block [][] block){
       int k = 0;
       for(int i=0;i<block.length;i++) {
           for(int j = 0;j<block[i].length;j++){
              if(block[i][j].isMine()){
                 System.out.printf("%2s","#"); //输出#表示是地雷
              }
              else {
                 int m = block[i][j].getAroundMineNumber();//显示周围雷的数目
                 System.out.printf("%2s",m);
              }
           }
           System.out.println();
       }  
   }
}

三、View

设计GUI程序除了使用8.2节给出的类以外,需要使用javaxswing包提供的视图(也称Java Swing框架)以及处理视图上触发的界面事件。与8.3节中简单的测试相比,GUI 程序可以提供更好的用户界面,完成8.1 节提出的设计要求。

1、BlockView

BlockView类是javax.swing.JPanel 的子类,其实例为方块(Block)提供视图,以便用户通过该视图与Block对象交互。BlockView 对象使用一个标签和按钮为Block对象提供视图,标签和按钮按照卡片布局(CardLayout) 层叠在一起,

**在默认状态下按钮遮挡住标签,即标签在按钮的下面。**用户单击视图中的按钮后,如果Block对象是雷,BockView对象中的标签显示的是雷的图标;如果Block 对象不是雷,标签显示的是和当前方块相邻且是雷的方块总数。

BlockView类中的setDataOnView(方法设置视图中需要显示的数据。

例如,
如果Block对象的isMine属性为true (方块是雷),那么setDataOnView0方法就将blockNameOrIcon标签的文本设置为Block对象的name属性的值,同时将blockNameOrIcon标签的图标设置为Block对象的minelcon属性指定的图标。

如果Block对象的isMine属性为false(方块不是雷),.就将blockNameOrIcon标签的文本设置为Block对象的aroundMineNumber属性的值,即周围雷的数目(效果如图8.5所示)。
在这里插入图片描述

seeBlockNameOrIcon()方法让用户看见视图中的标签,无法看见按钮;
seeBlockCover()方法让用户看见视图中的按钮,无法看见标签。

在这里插入图片描述

package ch8.view;
import javax.swing.*;
import java.awt.*;
import ch8.data.*;
public class BlockView extends JPanel implements ViewForBlock{ 
     JLabel blockNameOrIcon; //用来显示Block对象的name、number和mineIcon属性
     JButton blockCover;     //用来遮挡blockNameOrIcon.
     CardLayout card;        //卡片式布局
     Block block ;           //被提供视图的方块
     BlockView(){//构造方法
//         设置卡片式布局
        card=new CardLayout();
        setLayout(card);
        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(){
        if(block.isMine()){
           blockNameOrIcon.setText(block.getName());
           blockNameOrIcon.setIcon(block.getMineicon());
        }
        else {
            //如果不是雷,此数据是周围雷的数目。Mine地雷
           int n=block.getAroundMineNumber();
           if(n>=1)
               //有雷的话,JLabel的text为n
             blockNameOrIcon.setText(""+n);
           else
               //没有雷的话,JLabel的text为空
             blockNameOrIcon.setText(" ");
        }
     }
     public void seeBlockNameOrIcon(){//让用户看见视图中的标签,无法看见按钮;
        card.show(this,"view");
        validate();
     }
     public void seeBlockCover(){//让用户看见视图中的按钮,无法看见标签。
        card.show(this,"cover");
        validate();
        //确保组件具有有效的布局。此类主要适用于在 Container 实例上进行操作。可以不写
        //当提交一个表单时,响应的baiactionfrom会先按照你写的validate方法对表单数du据进行验证.
         //如果不正确则会返回到提交页面,并且可以显示错误信息.
     }
     public JButton getBlockCover() {
        return blockCover;
     } 
}

2、MineArea

MineArea是javax swing.JPanel的子类,其实例是雷区(效果如图8.6所示),雷区同时
指定自己作为当前视图上界面事件的事件监视器。用户单击方块视图触发ActionEvent事件
后,如果方块是雷,用户就输掉了扫雷游戏,程序播放雷爆炸的声音;如果不是雷,雷区就
让扫雷者开始扫雷(找出周围不是雷的方块)。

用户在方块上右击,可以将某个方块标记(用小红旗)为是雷(但不一定真是雷,看探
雷水平),再次单击可取消所作的标记。

用户扫雷成功:剩余的、没有揭开的方块数目刚好等于雷区的总雷数,则由Record的实
例负责判断用户是否可上英雄榜。

package ch8.view;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import ch8.data.*;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Stack;
public class MineArea extends JPanel implements ActionListener,MouseListener{
    //先弄两个面板,然后通过边界布局(Border),布局到上面和中间。
     JButton reStart;                   //从新开始按钮
     Block [][] block;                  //雷区的方块
     BlockView [][] blockView;          //方块的视图
     LayMines lay;                      //负责布雷
     PeopleScoutMine peopleScoutMine;   //负责扫雷
     int row,colum,mineCount,markMount;//雷区的行数、列数以及地雷个数和用户给出的标记数
     ImageIcon mark;                   //探雷作的标记
     String grade;                     //游戏级别 
     JPanel pCenter,pNorth,pSouth;            //布局用的面板
     JTextField showTime,showMarkedMineCount; //显示用时和探雷作的标记数目(不一定是雷哦)
     Timer time;                          //计时器
     int spendTime=0;                     //扫雷的用时
     Record record;                       //负责记录到英雄榜
     PlayMusic playMusic;                 //负责播放雷爆炸的声音
     PlayMusic2 playMusic2;                 //负责点击没雷时的声音

    JLabel bjTimeLabel = new JLabel("00:00:00");
    JButton bjTimeButton = new JButton("兄弟看看时间,别玩的太晚!");

    public MineArea(int row,int colum,int mineCount,String grade) {
         record = new Record(); //负责保存成绩到英雄榜
         reStart=new JButton("重新开始");//JButton
         mark=new ImageIcon("扫雷图片/mark.png");  //探雷标记ImageIcon
         time=new Timer(1000,this);                //计时器,每个一秒触发ActionEvent事件一次

         showTime=new JTextField(5);//JTextField
         showMarkedMineCount=new JTextField(5);//JTextField。探雷作的标记数目
         showTime.setHorizontalAlignment(JTextField.CENTER);//设置居中显示
         showMarkedMineCount.setHorizontalAlignment(JTextField.CENTER);
         showMarkedMineCount.setFont(new Font("Arial",Font.BOLD,16));//设置字体
         showTime.setFont(new Font("Arial",Font.BOLD,16));

         pCenter=new JPanel();//中心面板
         pNorth=new JPanel();//上面面板
         pSouth=new JPanel();//下面面板
         lay=new LayMines();                      //创建布雷者
         peopleScoutMine = new PeopleScoutMine(); //创建扫雷者
         initMineArea(row,colum,mineCount,grade); //初始化雷区,见下面的initMineArea方法
         reStart.addActionListener(this);

         pNorth.add(new JLabel("剩余雷数:"));
         pNorth.add(showMarkedMineCount);
         pNorth.add(reStart);
         pNorth.add(new JLabel("用时:"));
         pNorth.add(showTime);
         pSouth.add(bjTimeLabel);
         pSouth.add(bjTimeButton);
         //lambda表达式是一个匿名函数,代替匿名的内部类
         bjTimeButton.addActionListener( (e)->{
            beiJingTime();
         });

         setLayout(new BorderLayout());//设置边界布局
         add(pNorth,BorderLayout.NORTH);//将面板加入到边界布局当中
         add(pCenter,BorderLayout.CENTER);
         add(pSouth,BorderLayout.SOUTH);

         playMusic = new PlayMusic();              //负责播放触雷爆炸的声音
         playMusic.setClipFile("扫雷图片/mine.wav");
        playMusic2 = new PlayMusic2();              //负责播放触雷爆炸的声音
        playMusic2.setClipFile("扫雷图片/真正技术.wav");
    }

    //这是一个方法,用来显示目前的北京时间
    public void beiJingTime()
    {
        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
        String timestr = sdf.format(new Date());
        bjTimeLabel.setText( timestr );
    }

    public void initMineArea(int row,int colum,int mineCount,String grade){//雷区的行数、列数以及地雷个数、游戏级别
       pCenter.removeAll();//移除中心面板东西
       spendTime=0;//设置时间为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[i][j]=new Block();
       }
       lay.layMinesForBlock(block,mineCount);     //布雷
       peopleScoutMine.setBlock(block,mineCount); //准备扫雷   
       blockView=new BlockView[row][colum];       //创建方块的视图
       pCenter.setLayout(new GridLayout(row,colum));//中心面板通过网格布局GridLayout来说实现。
       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]); //方块设置自己的视图
               blockView[i][j].setDataOnView();  //将block[i][j]的数据放入视图
               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();
      /*
      如果你需要某个部件刷新一下界面,记得调用repaint().
      它是在图形线程后追加一段重绘操作,是安全的!是系统真正调用的重绘!
       */
    }
   public void setRow(int row){
       this.row=row;
   }
   public void setColum(int colum){
       this.colum=colum;
   }
   public void setMineCount(int mineCount){
       this.mineCount=mineCount;
   }
   public void setGrade(String grade) {
       this.grade=grade;
   }
   //点击事件发生时,被系统自动调用
    /*
    (1)创建监听器对象listener
    (2)将监听器对象交给按钮
    (3)当按钮被点击时,Swing框架会调用监听器对象里的方法,进行事件处理。
     */
   public void actionPerformed(ActionEvent e) {
        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;      //恢复初始值
             playMusic.playMusic();    //播放类爆炸的声音
         }
         else {  //扫雷者得到block[m][n]周围区域不是雷的方块
             Stack<Block> notMineBlock =peopleScoutMine.getNoMineAroundBlock(block[m][n]);
             while(!notMineBlock.empty()){
                Block bk = notMineBlock.pop();
                ViewForBlock viewforBlock = bk.getBlockView();
                viewforBlock.seeBlockNameOrIcon();//视图显示方块上的数据信息
                System.out.println("ookk");
                 playMusic2.playMusic();//播放未点击到雷的声音
             }
         }
      }
      if(e.getSource()==reStart) {
         initMineArea(row,colum,mineCount,grade);
         repaint();
         validate();
      }
      if(e.getSource()==time){
         spendTime++;
         showTime.setText(""+spendTime);
      }
      if(peopleScoutMine.verifyWin()) {  //判断用户是否扫雷成功
         time.stop();
         record.setGrade(grade);         
         record.setTime(spendTime);
         record.setVisible(true);       //弹出录入到英雄榜对话框
      }
    }
    public void mousePressed(MouseEvent e){ //探雷:给方块上插一个小旗图标(再次单击取消)
        JButton source=(JButton)e.getSource();
        /*
        getSource():获得你目前这个事件的事件源,
        比如有一个按钮事件,你点击一个按钮,在处理事件中你用e.getSource(),就是获得这个按钮.
        JButton a=(JButton)e.getSource();把事件源转换成你点击的那个对象类。
        这样你的a就可以用JButton里面的变量与方法了。
         */
        for(int i=0;i<row;i++) {
            for(int j=0;j<colum;j++) {
              if(e.getModifiers()==InputEvent.BUTTON3_MASK&&
                      /*
                      JAVA 反射机制中,Field的getModifiers()方法返回int类型值表示该字段的修饰符。
                       */
                 source==blockView[i][j].getBlockCover()){
                 if(block[i][j].getIsMark()) {
                        source.setIcon(null);
                        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);
                 }
              }    
            }
        }
   }
   public void mouseReleased(MouseEvent e){}//假设鼠标在A点被按下,然后一直不松开,然后移动到B点松开,此时触发的是 mouseReleased 事件
   public void mouseEntered(MouseEvent e){}//是鼠标刚进入组件的时候调用(只调用一次)
   public void mouseExited(MouseEvent e){}//是鼠标刚进入组件的时候退出
   public void mouseClicked(MouseEvent e){}//假设鼠标一直停留在A点,往下按然后放开,此时触发的是 mouseClicked 事件
} 

3、Record读/写英雄榜的视图

Record类是javax .swing.JDialog的子类,其实例是对话框,提供用户输入姓名的界面。用户输入姓名后对话框自动获得用户的成绩,然后委托RecordOrShowRecord的实例检查用户是否可上英雄榜。如果可以上英雄榜,就将用户姓名和绩录入英雄榜,否则提示用户不能上榜(效果如图8.7所示)
在这里插入图片描述

package ch8.view;
import javax.swing.*;
import java.awt.event.*;
import ch8.data.RecordOrShowRecord;
public class Record extends JDialog implements ActionListener{
   int time=0;
   String grade=null;//等级
   String key=null;
   String message=null;
   JTextField textName; 
   JLabel label=null; 
   JButton confirm,cancel;
   public Record(){//这是一个构造方法
      setTitle("记录你的成绩");
      this.time=time; 
      this.grade=grade;
      setBounds(100,100,240,160);//设置弹出窗口的效果
      setResizable(false);
      //设置此窗体是否可由用户调整大小。
       // resizeable值为false时,表示生成的窗体大小是由程序员决定的,用户不可以自由改变该窗体的大小。
      setModal(true);
      //modal-指定 dialog是否阻止在显示的时候将内容输入其他窗口。
       //也就是说,“有模式”意味着该窗口打开时其他窗口都被屏蔽了,在此情况下,点击程序的其他窗口是不允许的。
      confirm=new JButton("确定");
      cancel=new JButton("取消");
      textName=new JTextField(8);
      textName.setText("匿名");
      confirm.addActionListener(this);
      cancel.addActionListener(this);
      setLayout(new java.awt.GridLayout(2,1));//设置网格布局
      label=new JLabel("输入您的大名看是否可上榜");
      add(label);
      JPanel p=new JPanel();//p面板
      p.add(textName);
      p.add(confirm);
      p.add(cancel);
      add(p);
      setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
      //设置用户在此窗体上发起 "close" 时默认执行的操作。
       //调用任意已注册的 WindowListener 对象后自动隐藏该窗体。
   }
  public void setGrade(String grade){
      this.grade=grade;
  }
  public void setTime(int time){
      this.time=time;
  }
    //点击事件发生时,被系统自动调用
    /*
    (1)创建监听器对象listener
    (2)将监听器对象交给按钮
    (3)当按钮被点击时,Swing框架会调用监听器对象里的方法,进行事件处理。
     */
  public void actionPerformed(ActionEvent e){
     if(e.getSource()==confirm){
         String name = textName.getText();//获取单行文本内容
         writeRecord(name,time);
         setVisible(false);
         //setVisible(true);
         //方法的意思是说数据模型已经构造好了,
         // 允许JVM可以根据数据模型执行paint方法开始画图并显示到屏幕上了,并不是显示图形,而是可以运行开始画图了
     }
     if(e.getSource()==cancel){
         setVisible(false);
     }  
  }
  public void  writeRecord(String name,int time){
     RecordOrShowRecord rd = new RecordOrShowRecord();
     //使用内置Derby数据库record存放玩家的成绩
      //数据库使用表存放成绩,即表示英雄榜。
     rd.setTable(grade);
      //将数据库等级作为参数传入方法中。设置数据库表名,人名,时间

      //传入名字和时间,增加记录
     boolean boo= rd.addRecord(name,time);
     if(boo){
       JOptionPane.showMessageDialog//JOptionPane是一种对话框的便捷使用形式
          (null,"恭喜您,上榜了","消息框", JOptionPane.WARNING_MESSAGE);
       //showMessageDialog 显示消息对话框
     }
     else {
       JOptionPane.showMessageDialog
          (null,"成绩不能上榜","消息框", JOptionPane.WARNING_MESSAGE); 
     } 
  }
}

4、ShowRecord

ShowRecord类是javax.swing.JDialog的子类,其实例是对话框,提供显示英雄榜的界面,可以按成绩从高到低显示曾经登上过英雄榜的英雄们(效果如图8.8所示)
在这里插入图片描述

package ch8.view;
import java.awt.*;
import javax.swing.*;
import ch8.data.RecordOrShowRecord;
public class ShowRecord extends JDialog {
   String [][] record;
   JTextArea showMess;//一个显示纯文本的多行区域
   RecordOrShowRecord rd;//负责查询数据库的对象
   public ShowRecord() {
      rd = new RecordOrShowRecord();
      showMess = new JTextArea();
      showMess.setFont(new Font("楷体",Font.BOLD,15));
      add(new JScrollPane(showMess));//JScrollPane 管理视口、可选的垂直和水平滚动条以及可选的行和列标题视口。
      setTitle("显示英雄榜");
      setBounds(400,200,400,300);
      setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);//调用任意已注册 WindowListener 的对象后自动隐藏并释放该窗体。
   }
   public void setGrade(String grade){
      rd.setTable(grade);
   }
   public void setRecord(String [][]record){
      this.record=record;
   }
   public void showRecord() {
       showMess.setText(null);
       record = rd.queryRecord();//RecordOrShowRecord负责查询数据库的对象,查询数据库记录
       if(record == null ) {
         JOptionPane.showMessageDialog
            (null,"没人上榜呢","消息框", JOptionPane.WARNING_MESSAGE);
       }  
       else {
         for(int i =0 ;i<record.length;i++){
              int m = i+1;
              showMess.append("\n英雄"+m+":"+record[i][0]+"         "+"成绩:"+record[i][1]);
              showMess.append("\n--------------------------------");
         }
         setVisible(true);
      }
   }
} 

四、GUI程序

Derby是一个纯Java实现、开源的数据库管理系统。
AppWindow窗口中有“扫雷游戏”菜单,该菜单中有“初级”“中级”和“高级”子菜
单,3个子菜单分别有查看当前级别英雄榜的菜单项。用户选择相应级别的菜单,窗口中呈
现相应级别的雷区,选择某级别下的英雄榜菜单项可以查看该级别的英雄榜。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述在这里插入图片描述

package ch8.gui;
import java.awt.*;
import javax.swing.*;
import javax.swing.event.*;
import java.awt.event.*;
import ch8.view.MineArea;
import ch8.view.ShowRecord;
public class AppWindow extends JFrame implements MenuListener,ActionListener{
     JMenuBar bar;//菜单栏
     JMenu fileMenu;//菜单

     JMenu gradeOne,gradeTwo,gradeThree,gradeFour;//扫雷级别
     JMenuItem gradeOneList,gradeTwoList,gradeThreeList;//初,中,高级英雄榜,菜单项目
     MineArea mineArea=null;                //扫雷区域
     ShowRecord showHeroRecord=null;        //查看英雄榜

     public AppWindow(){
         bar=new JMenuBar();//创建一个菜单栏
         fileMenu=new JMenu("扫雷游戏");//菜单栏上命名一个

         gradeOne=new JMenu("初级");
         gradeTwo=new JMenu("中级");
         gradeThree=new JMenu("高级");
         gradeFour=new JMenu("自定义难度");


         gradeOneList=new JMenuItem("初级英雄榜");
         gradeTwoList=new JMenuItem("中级英雄榜");
         gradeThreeList=new JMenuItem("高级英雄榜");

         gradeOne.add(gradeOneList);//将菜单项,加入到菜单中
         gradeTwo.add(gradeTwoList);
         gradeThree.add(gradeThreeList);

         fileMenu.add(gradeOne);
         fileMenu.add(gradeTwo);
         fileMenu.add(gradeThree);
         fileMenu.add(gradeFour);

         bar.add(fileMenu);

         setJMenuBar(bar);

         gradeOne.addMenuListener(this);
         gradeTwo.addMenuListener(this);
         gradeThree.addMenuListener(this);
//         gradeFour.addMenuListener(this);
    //点击事件发生时,被系统自动调用
    /*
    (1)创建监听器对象listener
    (2)将监听器对象交给菜单
    (3)当按钮被点击时,Swing框架会调用监听器对象里的方法,进行事件处理。
     */

    //当点击到自定义难度时
         gradeFour.addMenuListener(new MenuListener(){

             @Override
             public void menuSelected(MenuEvent menuEvent) {
                 test3();
             }

             @Override
             public void menuDeselected(MenuEvent menuEvent) {

             }

             @Override
             public void menuCanceled(MenuEvent menuEvent) {

             }

         });

         gradeOneList.addActionListener(this);
         gradeTwoList.addActionListener(this);
         gradeThreeList.addActionListener(this);

         //扫雷区域
         mineArea=new MineArea(9,9,10,gradeOne.getText());//创建初级扫雷区
         add(mineArea,BorderLayout.CENTER);//将扫雷区域放在,边界布局的中间
         showHeroRecord=new ShowRecord();//创建英雄榜对象。查看英雄榜
         setBounds(300,100,500,450);//设置窗口位置和大小
         setVisible(true);
         /*
         setVisible(true);方法的意思是说数据模型已经构造好了,
         允许JVM可以根据数据模型执行paint方法开始画图并显示到屏幕上了,
         并不是显示图形,而是可以运行开始画图了
          */
         setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
         //设置用户在此窗体上发起 "close" 时默认执行的操作。
         //使用 System exit 方法退出应用程序。仅在应用程序中使用。
         validate();
     }
     public void menuSelected(MenuEvent e){//在菜单上如果选择了,初级、中级、高级的情况
        if(e.getSource()==gradeOne){
              mineArea.initMineArea(9,9,10,gradeOne.getText());
              validate(); 
        } 
        else if(e.getSource()==gradeTwo){
              mineArea.initMineArea(13,13,30,gradeTwo.getText());
              validate();
        } 
        else if(e.getSource()==gradeThree){
              mineArea.initMineArea(15,15,40,gradeThree.getText());
              validate();
        }
     }

    // 自定义难度时的简单数据输入框
    private void test3()
    {
        String input1 = JOptionPane.showInputDialog(
                this,
                "111行行行",
                "18");
        String input2 = JOptionPane.showInputDialog(
                this,
                "222列列列",
                "18");
        String input3 = JOptionPane.showInputDialog(
                this,
                "333雷雷雷",
                "30");
        int a = Integer.parseInt(input1);
        int b = Integer.parseInt(input2);
        int c = Integer.parseInt(input3);

        mineArea.initMineArea(a, b, c, gradeThree.getText());
        validate();
    }

     public void menuCanceled(MenuEvent e){}//菜单取消
     public void menuDeselected(MenuEvent e){}//菜单取消选择

    /*
    获得你目前这个事件的事件源,比如有一个按钮事件,
    你点击一个按钮,在处理事件中你用e.getSource(),
    就是获得这个按钮,你可以这样写
    JButton a=(JButton)e.getSource();
    把事件源转换成你点击的那个对象类。这样你的a就可以用JButton里面的变量与方法了。
     */
     public void actionPerformed(ActionEvent e){
        if(e.getSource()==gradeOneList){
              showHeroRecord.setGrade(gradeOne.getText());//设置等级,获得英雄榜内容
              showHeroRecord.showRecord();
        }
        else if(e.getSource()==gradeTwoList){
              showHeroRecord.setGrade(gradeTwo.getText());
              showHeroRecord.showRecord();
        }
        else if(e.getSource()==gradeThreeList){
              showHeroRecord.setGrade(gradeThree.getText());
              showHeroRecord.showRecord();
        }
    }
    public static void main(String args[]){
        new AppWindow();
    }
}

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值