最后一次更新于 2019/07/08
效果演示图
Java 实现经典扫雷游戏
本扫雷游戏有以下功能:
如果点中炸弹会显示炸弹。
玩家左键点击方块能显示该方块周围会出现几个炸弹,如果不存在炸弹的话扫描范围会被放大。
满足各种行数,列数和炸弹个数要求。
对不同水平的玩家提供不同的游戏难度级别。
如果玩家单击鼠标右键会显示红旗。
如果玩家双击鼠标右键会显示问号。
如果玩家游戏挑战失败显示所有炸弹隐藏的地方以及玩家失误标记的地方。
如果玩家挑战成功显示所有的炸弹(原本炸弹的位置有可能已被玩家用小红旗标识了)。
源代码包括抽象类和接口。我将程序分为三个部分来介绍:GameDriver,Library,UserInterface。
游戏驱动
这部分相当简单(因为只有一个主函数)。Driver 类被用于作为启动游戏的接口。
代码如下所示:
package GameDriver;
import UserInterface.Menu;
/**
* @author Hephaest
* @since 3/21/2019 8:41 PM
* 此类是用来运行扫雷游戏程序的。
*/
public class Driver
{
public static void main(String[] Args)
{
// 游戏启动的时候会附带一个选项菜单窗口。
new Menu("Minesweeper");
}
}
工具库
包 Library 内只有两个文件。第一个是抽象类 Bomb,它用于存储和有关游戏窗口的物理信息。
代码如下所示:
package Library;
import UserInterface.GameBoard;
/**
* @author Hephaest
* @since 3/21/2019 8:41 PM
* 这个抽象类中的抽象方法会在被继承时实现。
*/
public abstract class Bomb
{
/** 游戏窗口实例 **/
protected GameBoard board;
/** 实例的高度 **/
protected int boardHeight;
/** 实例的宽度 **/
protected int boardWidth;
/**
* Create bombs, which can be placed on a GameBoard.
* @param board the GameBoard upon which user clicks on.
*/
public Bomb(GameBoard board)
{
this.board = board;
// 真正加入计算的高和宽去需要减去填充边距的长度。
boardHeight = (board.getHeight() - 20) / 20;
boardWidth = (board.getWidth() - 20) / 20;
}
/**
* 该方法将会被用于分布炸弹的位置。
*/
protected abstract void reproduceBomb();
}
第二个工具就是 TimeChecker 接口,它使用将毫秒时间转换成相对应的时间表达,将会被用于 SmartSquare 类。
代码如下所示:
package Library;
/**
* @author Hephaest
* @since 3/21/2019 8:41 PM
* 这个接口有个静态方法通过给定的毫秒时间换算成相对应的时间表达。
*/
public interface TimeChecker
{
/**
* 根据程序给定的运行时间返回程序运行时间的标准表达。
* @param time 在游戏开始和结束之间的时间。
* @return 总用时的文本描述。
*/
static String calculateTime(long time)
{
int CONVERT_TO_SEC = 1000;
int CONVERT_TO_OTHERS = 60;
int ms = (int) time;
int sec = ms / CONVERT_TO_SEC;
int min = sec / CONVERT_TO_OTHERS; // 把秒转换成分。
int hr = min / CONVERT_TO_OTHERS; // 把分转化成小时。
if (hr == 0)
{
if(min == 0)
{
if (sec == 0)
return ms + " ms";
else
return sec + " sec " + ms % 1000 + " ms";
} else
return min + " min " + sec % CONVERT_TO_OTHERS + " sec " + ms % CONVERT_TO_SEC + " ms";
} else
return hr + " hour " + min % CONVERT_TO_OTHERS + " min " + sec % CONVERT_TO_OTHERS + " sec " + ms % CONVERT_TO_SEC + " ms";
}
}
用户界面
下方的 UML 图 可以帮助您理解以下几个类之间的关系:
菜单
Menu 类为玩家提供了4种难度级别的选项:初级,中级,高级和自定义。尤其对于自定义来说,程序需要检验玩家的输入是否符合要求。如果玩家确定选择了以后,选择菜单消失,启动游戏窗口。
代码如下所示:
package UserInterface;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.regex.Pattern;
/**
* 该类继承 JFrame 类。
* 该类实现了 ActionListener 里对不用点击事件的反馈。
* 该类提供4个选项供玩家选择。
* 这4个选项分别是 "初级","中级","高级" 和 "自定义"。
* 在点击 "New Game" 按钮之后,菜单窗口自动关闭。
* @author Hephaest
* @since 3/21/2019 8:41 PM
*/
public class Menu extends JFrame implements ActionListener
{
private JButton start;
private JRadioButton beginner, intermediate, advanced, custom;
private JTextField width, height, mines;
/**
* 创建一个给定标题的菜单。
* @param title 菜单上的标题。
*/
public Menu(String title)
{
// 设置菜单标题。
setTitle(title);
// 创建菜单子标题。
JLabel subtitle = new JLabel("Difficulty");
subtitle.setBounds(100,10,100,20);
add(subtitle);
// 创建 "初级" 选择按钮。
beginner = new JRadioButton("Beginner");
beginner.setBounds(40,40,150,20);
add(beginner);
// 设置 "初级" 选择的描述。
JLabel bDescFirstLine = new JLabel("10 mines");
bDescFirstLine.setBounds(70,60,100,20);
JLabel bDescSecondLine = new JLabel("10 x 10 tile grid");
bDescSecondLine.setBounds(70,80,100,20);
add(bDescFirstLine);
add(bDescSecondLine);
// 创建 "中级" 选择按钮。
intermediate=new JRadioButton("Intermediate");
intermediate.setBounds(40,100,150,20);
add(intermediate);
// 设置 "中级" 选择的描述。
JLabel iDescFirstLine = new JLabel("40 mines");
iDescFirstLine.setBounds(70,120,100,20);
JLabel iDescSecondLine = new JLabel("16 x 16 tile grid");
iDescSecondLine.setBounds(70,140,100,20)