扫雷游戏
- 一、data
- 1、Bolck
- 2、LayMines
- 3、PeopleScoutMine
- 4、ViewForBlock
- 5、RecordOrShowRecord
- 二、测试
- 三、View
- 1、BlockView
- 2、MineArea
- 3、Record读/写英雄榜的视图
- 4、ShowRecord
- 四、GUI程序
一、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();
}
}