利用java开发简易版扫雷游戏
1.简介
学了几周的Java,闲来无事,写个乞丐版的扫雷,加强一下Java基础知识。
2.编写过程
编写这个游戏,一共经历了三个阶段,编写了三个版本的游戏代码。
第一版:完成了扫雷游戏的基本雏形,实现了游戏的基本功能,游戏运行在cmd黑窗口中,以字符绘制游戏界面,无图形化窗口,通过控制台输入字符完成游戏控制。代码放置在一个java文件中,代码的可读性以及可扩展性都比较差。
第二版:在第一版实现基本功能的基础之上,对游戏代码进行重构,根据各部分的功能创建多个类,增加代码注释,提高代码的可读性以及可扩展性。
第三版:在第二版重构代码的基础之上给游戏增加了图形化界面,将用户从控制台输入命令控制游戏变为通过鼠标左右键点击操作控制游戏。
3.游戏运行逻辑
游戏运行逻辑图(第一版代码):
游戏运行逻辑图(第二版代码):
以上两个游戏流程图的运行是建立在从控制台读取数据的基础之上的,两者的执行逻辑大体相同,其本质区别在于修改游戏数据的时机不相同。前者是在通关判断之前修改数据,后者实在通关判断之后。两者在运行期间并没有什么区别,但是当玩家完成扫雷之后最后的画面打印就会出现问题,即游戏画面中最后一个进行操作的坐标点的字符的显示状态,在发生改变之前就会终止程序。通过对修改游戏数据以及通关判断这两个操作的执行顺序进行调整,即可修正这一显示错误。
游戏运行逻辑图(第三版代码):
这个运行流程图是基于第三版加了图形化界面之后的游戏代码,游戏控制流程与控制台输入控制的流程基本相同,只是将从控制台读取用户输入变成了监听用户的鼠标左键与右键的点击事件。并且,从控制台读取数据时要保证游戏结束前一直进行读取,因此需要设置while(true)循环以进行实现,游戏结束使用break跳出循环。而在图形化界面中,使用的是事件监听器,事件监听器在游戏结束前持续监听用户的鼠标点击事件,在游戏结束的弹窗弹出的同时移除监听器,结束鼠标对游戏的控制。
4.游戏相关数据存储与读取
以10x10x3的三维数组存储每个坐标点的信息(包括行号、列号、是否是地雷、当前显示的符号(未操作?、插旗#、地雷*)、周围地雷数)。
一维数组的下标表示行号,二维数组的下标表示列号,三维数组中存的第一个数据表示的是该位置是否是地雷(0-不是,1-是),第二个数据表示该位置当前显示的符号(0-?,1-#,2-*,3-显示地雷数),第三个数据表示该位置周围的地雷数。例如:
array[0][9][0]=1;表示将第0行第9列设置为地雷
array[0][9][1]=1;表示将第0行第9列的显示字符设置为'#'
array[0][9][2]=0;表示将第0行第9列位置周围的地雷数设置为0
5.游戏代码
5.1 第一版
第一版的游戏代码写在一个类中,主要的作用是实现基本的游戏功能。代码如下:
import java.util.Random;
import java.util.Scanner;
public class MineSweeper {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
//初始化游戏数据
int[][][] gameData = init();
while (true) {
//打印游戏信息
showInfo();
//打印游戏框
showWin(gameData);
//踩中地雷结束游戏
//由于踩中地雷会把所有标记变成'*',所以只需要判断0行0列的显示标记是不是'*'就行了
if (gameData[0][0][1] == 2) {
System.out.println("踩中地雷,游戏结束!");
break;
}
//通关结束游戏
if (missionAccomplished(gameData)) {
System.out.println("恭喜通关!");
break;
}
//读取控制台数据并对游戏数据数组进行修改
gameData = readAndChangeData(input,gameData);
}
}
private static void showInfo() {
printBlank(25);
System.out.println("*******************************************************\n"
+ "\t\t 游戏信息\n"
+ "游戏名称:扫雷\n"
+ "游戏版本:1.0\n"
+ "游戏操作:1.输入行号及列号来选中要翻开的\'?\'进行操作,可\n"
+ "\t 以选择插旗(#)或者直接翻开.\n"
+ "\t 2.如果翻开'*'则表示地雷,则游戏结束;如果翻开\n"
+ "\t 的是数字,则表示该格周围的地雷数.\n"
+ "\t 3.标记出全部地雷,并且没有\'?\',则闯关成功,游戏\n"
+ "\t 结束.\n"
+ "*******************************************************\n\n");
}
private static void showWin(int[][][] gameData) {
System.out.println(" 0 1 2 3 4 5 6 7 8 9\n"
+ " ***********************");
//遍历游戏框中的每个坐标,读取并打印显示符号
for (int i = 0; i < 10; i++) {
System.out.print(i + " * ");
for (int j = 0; j < 10; j++) {
//读取展示的符号
char sign;
switch (gameData[i][j][1]) {
case 1:
sign = '#';
break;
case 2:
sign = '*';
break;
case 3:
sign = (char)(gameData[i][j][2] + 48);
break;
default:
sign = '?';
break;
}
//打印符号
System.out.print(sign + " ");
}
System.out.println("*");
}
System.out.println(" ***********************");
}
private static void printBlank(int blankNum) {
for (int i = 0; i < blankNum; i++) {
System.out.println("");
}
}
private static int[][] createMineCoord() {
//定义二维数组
int[][] mineCoordArray = new int[20][2];
Random random = new Random();
//将生成的随机坐标存入数组中
for (int i = 0; i < 20; i++) {
for (int j = 0; j < 2; j++) {
//生成0~9范围内的随机数
int randomNumber = random.nextInt(10);
mineCoordArray[i][j] = randomNumber;
}
}
return mineCoordArray;
}
private static int[][][] init(){
//创建大小为10*10*3的三维数组(默认初始值为0)
int[][][] gameData = new int[10][10][3];
//生成随机的地雷坐标,并将其存入游戏数据数组中
int[][] mineCoordArray = createMineCoord();
for (int[] mineCoord : mineCoordArray) {
int row = mineCoord[0];
int col = mineCoord[1];
gameData[row][col][0] = 1;
}
//计算每格周围地雷数并将其存入游戏数据数组中
//循环遍历每个坐标
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 10; j++) {
//遍历当前坐标周围的8个坐标
for (int aroundRow = i-1; aroundRow <= i+1; aroundRow++) {
//行号超范围则跳过
if (aroundRow < 0 || aroundRow > 9) {
continue;
}
for (int aroundCol = j-1; aroundCol <= j+1; aroundCol++) {
//列号超范围则跳过
if (aroundCol < 0 || aroundCol > 9) {
continue;
}
//排除本身坐标点
if ((gameData[aroundRow][aroundCol][0] == 1) && (!(aroundRow == i && aroundCol == j))) {
gameData[i][j][2] += 1;
}
}
}
}
}
return gameData;
}
private static int[][][] readAndChangeData(Scanner input,int[][][] gameData) {
//定义在循环外部,以方便后续使用
int row;
int col;
printBlank(12);
//读取输入
//设置循环来读取行号,当输入的行号不在范围内时,会一直提示玩家
while (true) {
System.out.print("请输入行号:");
row = input.nextInt();
if (row >= 0 && row <= 9) {
break;
} else {
System.out.println("输入的行号不符合规范!");
}
}
//设置循环来读取列号,当输入的行号不在范围内时,会一直提示玩家
while(true) {
System.out.print("请输入列号:");
col = input.nextInt();
if (col >= 0 && col <= 9) {
break;
} else {
System.out.println("输入的列号不符合规范!");
}
}
//设置循环,防止玩家输入不能识别的字符
while (true) {
System.out.print("标记(B)还是直接翻开(F):");
String sign = input.next();
//如果翻开的是炸弹,直接把所有标记变成'*',并返回结束游戏
if (sign.equalsIgnoreCase("f")) {
if (gameData[row][col][0] == 1) {
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 10; j++) {
gameData[i][j][1] =2;
}
}
break;
}
}
//修改数据
if (gameData[row][col][1] != 3) {//gameData[row][col][1] == 3 表示已被翻开,翻开的坐标点不能再被操作
if (sign.equalsIgnoreCase("b")) {
gameData[row][col][1] = 1;
} else if (sign.equalsIgnoreCase("f")) {
//如果翻开的不是炸弹,则显示其周围地雷数
if (gameData[row][col][0] != 1) {
gameData[row][col][1] = 3;
}
} else {
System.out.println("输入不符合要求,请重新输入!");
continue;
}
}
break;
}
return gameData;
}
private static boolean missionAccomplished(int[][][] gameDate) {
//坐标点总数
int totalSite = 10 * 10;
//统计地雷数与非地雷数
int mineSigned = 0;
int noMineOpen = 0;
//遍历游戏数据数组
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 10; j++) {
//通关条件
//1、翻开非地雷的位置
if (gameDate[i][j][0] == 0 && gameDate[i][j][1] == 3) {
noMineOpen++;
}
//2、地雷位置标记
if (gameDate[i][j][0] == 1 && gameDate[i][j][1] == 1) {
mineSigned++;
}
}
}
if (totalSite == (noMineOpen + mineSigned)) {
return true;
}
return false;
}
}
5.2 第二版
这一版的代码实在第一版的基础上对代码进行重构,根据功能对代码进行分类,将其放入不同的类中,增加代码的可读性与可维护性、可扩展性。代码一共分为五个类:主程序类、设置类、地雷类、控制类、显示类。各部分代码各司其职,共同作用,共同完成游戏运行。
主程序类,游戏程序入口:
import java.util.Scanner;
public class MineSweeper {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
//初始化游戏数据
int[][][] gameData = GameControl.init();
while (true) {
//打印游戏信息
Show.gameInfo();
//打印游戏框
Show.gameBoard(gameData);
//踩中地雷结束游戏
//由于踩中地雷会把所有标记变成'*',所以只需要判断0行0列的显示标记是不是'*'就行了
if (gameData[0][0][Settings.SIGN_DATA] == Settings.MINE_SIGN_DATA) {
System.out.println("踩中地雷,游戏结束!");
break;
}
//通关结束游戏
if (GameControl.missionAccomplished(gameData)) {
System.out.println("恭喜通关!");
break;
}
//读取控制台数据并对游戏数据数组进行修改
GameControl.readAndChangeData(input,gameData);
}
}
}
设置类,游戏相关设置数据:
public class Settings {
//定义游戏界面参数
public static final int ROW_SIZE = 10;
public static final int COL_SIZE = 10;
public static final int DEFAULT_BLANK = 20;
public static final int MINE_NUM = 20;
public static final int MINE_SITE_NUM = 2;
public static final int MINE_ROW_IN_ARRAY = 0;
public static final int MINE_COL_IN_ARRAY = 1;
public static final int DATA_SIZE = 3;
//定义每个坐标点中存储数据的数组中数值的含义
public static final int MINE_DATA = 0;
public static final int IS_NOT_MINE = 0;
public static final int IS_MINE = 1;
public static final int SIGN_DATA = 1;
public static final int INIT_SIGN_DATA = 0;
public static final int FLAG_SIGN_DATA = 1;
public static final int MINE_SIGN_DATA = 2;
public static final int MINE_NUM_SIGN_DATA = 3;
public static final int AROUND_MINE_DATA = 2;
//游戏符号
public static final char INIT_SIGN = '?';
public static final char FLAG_SIGN = '#';
public static final char MINE_SIGN = '*';
public static final int ASCII_ADD = 48;
//定义玩家在控制台输入的操纵符
public static final String OPEN_OPERATION = "F";
public static final String FLAG_OPERATION = "B";
public static final String INFORMATION = "**************************************************\n"
+ "\t\t 游戏信息\n"
+ "游戏名称:扫雷\n"
+ "游戏版本:2.0\n"
+ "游戏操作:1.输入行号及列号来选中要翻开的\'?\'进行操作,可\n"
+ "\t 以选择插旗(#)或者直接翻开.\n"
+ "\t 2.如果翻开'*'则表示地雷,则游戏结束;如果翻开\n"
+ "\t 的是数字,则表示该格周围的地雷数.\n"
+ "\t 3.标记出全部地雷,并且没有\'?\',则闯关成功,游戏\n"
+ "\t 结束.\n"
+ "**************************************************\n\n";
}
地雷类,生成随机的地雷坐标数据:
import java.util.Random;
public class Mine {
public static int[][] createMineCoord() {
//定义二维数组
int[][] mineCoordArray = new int[Settings.MINE_NUM][Settings.MINE_SITE_NUM];
Random random = new Random();
//将生成的随机坐标存入数组中
for (int i = 0; i < Settings.MINE_NUM; i++) {
for (int j = 0; j < Settings.MINE_SITE_NUM; j++) {
//生成行坐标随机数,并将其放入数组
if (j == Settings.MINE_ROW_IN_ARRAY) {
mineCoordArray[i][j] = random.nextInt(Settings.ROW_SIZE);
}
//生成列坐标随机数,并将其放入数组
if (j == Settings.MINE_COL_IN_ARRAY) {
mineCoordArray[i][j] = random.nextInt(Settings.COL_SIZE);
}
}
}
return mineCoordArray;
}
}
控制类,控制游戏进程,以及游戏数据:
import java.util.Scanner;
public class GameControl {
public static int[][][] init(){
//创建存储游戏相关数据的三维数组(默认初始值为0)
int[][][] gameData = new int[Settings.ROW_SIZE][Settings.COL_SIZE][Settings.DATA_SIZE];
//生成随机的地雷坐标,并将其存入游戏数据数组中
int[][] mineCoordArray = Mine.createMineCoord();
for (int[] mineCoord : mineCoordArray) {
int row = mineCoord[Settings.MINE_ROW_IN_ARRAY];
int col = mineCoord[Settings.MINE_COL_IN_ARRAY];
gameData[row][col][Settings.MINE_DATA] = Settings.IS_MINE;
}
//计算每格周围地雷数并将其存入游戏数据数组中
//循环遍历每个坐标
for (int i = 0; i < Settings.ROW_SIZE; i++) {
for (int j = 0; j < Settings.COL_SIZE; j++) {
//遍历当前坐标周围的8个坐标
for (int aroundRow = i-1; aroundRow <= i+1; aroundRow++) {
//行号超范围则跳过
if (aroundRow < 0 || aroundRow > Settings.ROW_SIZE-1) {
continue;
}
for (int aroundCol = j-1; aroundCol <= j+1; aroundCol++) {
//列号超范围则跳过
if (aroundCol < 0 || aroundCol > Settings.COL_SIZE-1) {
continue;
}
//排除本身坐标点
if ((gameData[aroundRow][aroundCol][Settings.MINE_DATA] == Settings.IS_MINE) && (!(aroundRow == i && aroundCol == j))) {
gameData[i][j][Settings.AROUND_MINE_DATA] += 1;
}
}
}
}
}
return gameData;
}
public static void readAndChangeData(Scanner input,int[][][] gameData) {
//定义在循环外部,以方便后续使用
int row;
int col;
//读取输入
//设置循环来读取行号,当输入的行号不在范围内时,会一直提示玩家
while (true) {
System.out.print("请输入行号:");
row = input.nextInt();
if (row >= 0 && row <= Settings.ROW_SIZE-1) {
break;
} else {
System.out.println("输入的行号不符合规范!");
}
}
//设置循环来读取列号,当输入的行号不在范围内时,会一直提示玩家
while(true) {
System.out.print("请输入列号:");
col = input.nextInt();
if (col >= 0 && col <= Settings.COL_SIZE-1) {
break;
} else {
System.out.println("输入的列号不符合规范!");
}
}
//设置循环,防止玩家输入不能识别的字符
while (true) {
System.out.print("标记(B)还是直接翻开(F):");
String sign = input.next();
//如果翻开的是炸弹,直接把所有标记变成'*',并返回结束游戏
if (sign.equalsIgnoreCase(Settings.OPEN_OPERATION)) {
if (gameData[row][col][Settings.MINE_DATA] == Settings.IS_MINE) {
for (int i = 0; i < Settings.ROW_SIZE; i++) {
for (int j = 0; j < Settings.COL_SIZE; j++) {
gameData[i][j][Settings.SIGN_DATA] =Settings.MINE_SIGN_DATA;
}
}
break;
}
}
//修改数据
if (gameData[row][col][Settings.SIGN_DATA] != Settings.MINE_NUM_SIGN_DATA) {//相等表示已被翻开,翻开的坐标点不能再被操作
if (sign.equalsIgnoreCase(Settings.FLAG_OPERATION)) {
gameData[row][col][Settings.SIGN_DATA] = Settings.FLAG_SIGN_DATA;
} else if (sign.equalsIgnoreCase(Settings.OPEN_OPERATION)) {
//如果翻开的不是炸弹,则显示其周围地雷数
if (gameData[row][col][Settings.MINE_DATA] == Settings.IS_NOT_MINE) {
gameData[row][col][Settings.SIGN_DATA] = Settings.MINE_NUM_SIGN_DATA;
}
} else {
System.out.println("输入不符合要求,请重新输入!");
continue;
}
}
break;
}
}
public static boolean missionAccomplished(int[][][] gameDate) {
//坐标点总数
int totalSite = Settings.ROW_SIZE * Settings.COL_SIZE;
//统计地雷数与非地雷数
int mineSigned = 0;
int noMineOpen = 0;
//遍历游戏数据数组
for (int i = 0; i < Settings.ROW_SIZE; i++) {
for (int j = 0; j < Settings.COL_SIZE; j++) {
//通关条件
//1、翻开非地雷的位置
if (gameDate[i][j][Settings.MINE_DATA] == Settings.IS_NOT_MINE && gameDate[i][j][Settings.SIGN_DATA] == Settings.MINE_NUM_SIGN_DATA) {
noMineOpen++;
}
//2、地雷位置标记
if (gameDate[i][j][Settings.MINE_DATA] == Settings.IS_MINE && gameDate[i][j][Settings.SIGN_DATA] == Settings.FLAG_SIGN_DATA) {
mineSigned++;
}
}
}
//当翻开的的坐标数加上标记的地雷数等于坐标点总数的时候,返回true表示可以结束游戏
if (totalSite == (noMineOpen + mineSigned)) {
return true;
}
//条件不满足,游戏继续
return false;
}
}
显示类,对游戏的相关画面进行打印:
public class Show {
public static void gameInfo() {
//打印空白行,作用是使展现在控制台的图形刷新
printSign(Settings.DEFAULT_BLANK,"\n");
System.out.println(Settings.INFORMATION);
}
public static void printSign(int num,String sign) {
for (int i = 0; i < num; i++) {
System.out.print(sign);
}
}
public static void gameBoard(int[][][] gameData) {
//打印游戏上边框
printSign(4, " ");
for (int i = 0; i < Settings.COL_SIZE; i++) {
printSign(1, i+" ");
}
printSign(1, "\n *");
printSign(Settings.COL_SIZE+1, "**");
printSign(1, "\n");
//遍历游戏框中的每个坐标,读取并打印显示符号
for (int i = 0; i < Settings.ROW_SIZE; i++) {
System.out.print(i + " * ");
for (int j = 0; j < Settings.COL_SIZE; j++) {
//读取展示的符号
char sign;
switch (gameData[i][j][Settings.SIGN_DATA]) {
case Settings.FLAG_SIGN_DATA:
sign = Settings.FLAG_SIGN;
break;
case Settings.MINE_SIGN_DATA:
sign = Settings.MINE_SIGN;
break;
case Settings.MINE_NUM_SIGN_DATA:
//将数组中存的整型数值通过ASCII码转为字符型表示
sign = (char)(gameData[i][j][Settings.AROUND_MINE_DATA] + Settings.ASCII_ADD);
break;
default:
sign = Settings.INIT_SIGN;
break;
}
//打印符号
System.out.print(sign + " ");
}
System.out.println("*");
}
//打印游戏下边框
printSign(2, " ");
printSign(Settings.COL_SIZE+1, "**");
printSign(1, "*\n");
}
}
5.3 第三版
在第二版的基础上,去除了显示类,增加了图片类、图形界面类、事件监听器类。
游戏运行主程序类:
public class MineSweeper {
public static void main(String[] args) {
int[][][] gameData = new int[Settings.ROW_SIZE][Settings.COL_SIZE][Settings.DATA_SIZE];
//创建游戏控制类对象
GameDataController controller = new GameDataController(gameData);
//初始化游戏数据
controller.init();
//绘制游戏界面
new Graphic(controller);
}
}
设置类,提供游戏运行相关这是数据:
public class Settings {
//定义游戏界面参数
public static final int ROW_SIZE = 10;
public static final int COL_SIZE = 10;
public static final int MINE_NUM = 20;
public static final int MINE_SITE_NUM = 2;
public static final int MINE_ROW_IN_ARRAY = 0;
public static final int MINE_COL_IN_ARRAY = 1;
public static final int DATA_SIZE = 3;
//定义每个坐标点中存储数据的数组中数值的含义
public static final int MINE_DATA = 0;
public static final int IS_NOT_MINE = 0;
public static final int IS_MINE = 1;
public static final int SIGN_DATA = 1;
public static final int INIT_SIGN_DATA = 0;
public static final int FLAG_SIGN_DATA = 1;
public static final int MINE_SIGN_DATA = 2;
public static final int MINE_NUM_SIGN_DATA = 3;
public static final int AROUND_MINE_DATA = 2;
//定义游戏框尺寸数据
public static final int IMAGE_SIZE = 60;
public static final int FRAME_X = 400;
public static final int FRAME_Y = 150;
public static final int FRAME_WIDTH = IMAGE_SIZE * ROW_SIZE;
public static final int FRAME_HEIGHT =IMAGE_SIZE * COL_SIZE;
public static final int BORDER_WIDTH = 1;
public static final int TITLE_HEIGHT = 23;
//弹窗数据
public static final String DIALOG_TITLE = "提示";
public static final String DIALOG_DEFEAT = "踩雷,游戏结束!";
public static final String DIALOG_VECTORY = "恭喜通关!";
}
图片类,提供游戏相关图片:
import javax.swing.ImageIcon;
public class Image{
public static final ImageIcon IMAGE_MINE = new ImageIcon("img\\mine.png");
public static final ImageIcon IMAGE_FLAG = new ImageIcon("img\\flag.png");
public static final ImageIcon IMAGE_DEFEAT = new ImageIcon("img\\defeat.png");
public static final ImageIcon IMAGE_VECTORY = new ImageIcon("img\\vectory.png");
public static ImageIcon getImageByNum(int mineNum) {
ImageIcon image = new ImageIcon("img\\"+mineNum+".png");
return image;
}
}
地雷类,生成游戏中的类的相关数据:
import java.util.Random;
public class Mine {
public static int[][] createMineCoord() {
//定义二维数组
int[][] mineCoordArray = new int[Settings.MINE_NUM][Settings.MINE_SITE_NUM];
Random random = new Random();
//将生成的随机坐标存入数组中
for (int i = 0; i < Settings.MINE_NUM; i++) {
for (int j = 0; j < Settings.MINE_SITE_NUM; j++) {
//生成行坐标随机数,并将其放入数组
if (j == Settings.MINE_ROW_IN_ARRAY) {
mineCoordArray[i][j] = random.nextInt(Settings.ROW_SIZE);
}
//生成列坐标随机数,并将其放入数组
if (j == Settings.MINE_COL_IN_ARRAY) {
mineCoordArray[i][j] = random.nextInt(Settings.COL_SIZE);
}
}
}
return mineCoordArray;
}
}
游戏控制类,提供用于游戏控制的相关方法:
import javax.swing.JLabel;
public class GameDataController {
private int[][][] gameData;
public GameDataController(int[][][] gameData) {
this.gameData = gameData;
}
public void init(){
//将地雷数据存入三维游戏数组中
int[][] mineCoordArray = Mine.createMineCoord();
for (int[] mineCoord : mineCoordArray) {
int row = mineCoord[Settings.MINE_ROW_IN_ARRAY];
int col = mineCoord[Settings.MINE_COL_IN_ARRAY];
gameData[row][col][Settings.MINE_DATA] = Settings.IS_MINE;
}
//计算每格周围地雷数并将其存入游戏数据数组中
calcAroundNum();
}
public boolean missionAccomplished() {
//坐标点总数
int totalSite = Settings.ROW_SIZE * Settings.COL_SIZE;
//统计地雷数与非地雷数
int mineSigned = 0;
int noMineOpen = 0;
//遍历游戏数据数组
for (int i = 0; i < Settings.ROW_SIZE; i++) {
for (int j = 0; j < Settings.COL_SIZE; j++) {
//通关条件
//1、翻开非地雷的位置
if (gameData[i][j][Settings.MINE_DATA] == Settings.IS_NOT_MINE && gameData[i][j][Settings.SIGN_DATA] == Settings.MINE_NUM_SIGN_DATA) {
noMineOpen++;
}
//2、地雷位置标记
if (gameData[i][j][Settings.MINE_DATA] == Settings.IS_MINE && gameData[i][j][Settings.SIGN_DATA] == Settings.FLAG_SIGN_DATA) {
mineSigned++;
}
}
}
//当翻开的的坐标数加上标记的地雷数等于坐标点总数的时候,返回true表示可以结束游戏
if (totalSite == (noMineOpen + mineSigned)) {
return true;
}
//条件不满足,游戏继续
return false;
}
public void leftClick(int x, int y, JLabel[][] labels) {
if (gameData[y][x][Settings.SIGN_DATA] == Settings.INIT_SIGN_DATA) {
if (gameData[y][x][Settings.MINE_DATA] == Settings.IS_MINE) {
//如果翻开的是地雷,显示弹窗提示结束游戏
labels[y][x].setIcon(Image.IMAGE_MINE);
Graphic.showDialog(false);
}else {
//如果当前位置未被翻开,则翻开当前位置,修改游戏数据及显示图片
gameData[y][x][Settings.SIGN_DATA] = Settings.MINE_NUM_SIGN_DATA;
int aroundMineNum = gameData[y][x][Settings.AROUND_MINE_DATA];
labels[y][x].setIcon(Image.getImageByNum(aroundMineNum));
}
}
}
public void rightClick(int x, int y, JLabel[][] labels) {
if (gameData[y][x][Settings.SIGN_DATA] == Settings.INIT_SIGN_DATA) {
//如果当前位置未被翻开,则修改相应数据,并将其显示为插旗
gameData[y][x][Settings.SIGN_DATA] = Settings.FLAG_SIGN_DATA;
labels[y][x].setIcon(Image.IMAGE_FLAG);
} else if (gameData[y][x][Settings.SIGN_DATA] == Settings.FLAG_SIGN_DATA) {
//如果该位置已被插旗,则修改相应数据,并将其恢复初始状态
gameData[y][x][Settings.SIGN_DATA] = Settings.INIT_SIGN_DATA;
labels[y][x].setIcon(null);
}
}
private void calcAroundNum() {
for (int i = 0; i < Settings.ROW_SIZE; i++) {
for (int j = 0; j < Settings.COL_SIZE; j++) {
//遍历当前坐标周围的8个坐标
for (int aroundRow = i-1; aroundRow <= i+1; aroundRow++) {
//行号超范围则跳过
if (aroundRow < 0 || aroundRow > Settings.ROW_SIZE-1) {
continue;
}
for (int aroundCol = j-1; aroundCol <= j+1; aroundCol++) {
//列号超范围则跳过
if (aroundCol < 0 || aroundCol > Settings.COL_SIZE-1) {
continue;
}
//排除本身坐标点
if ((gameData[aroundRow][aroundCol][Settings.MINE_DATA] == Settings.IS_MINE) && (!(aroundRow == i && aroundCol == j))) {
gameData[i][j][Settings.AROUND_MINE_DATA] += 1;
}
}
}
}
}
}
}
绘制图形化界面类,生成显示游戏的图形化界面:
import java.awt.Color;
import java.awt.GridLayout;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
public class Graphic {
private static JFrame frame;
private static GameListener gameListener;
private JLabel[][] labels = new JLabel[Settings.ROW_SIZE][Settings.COL_SIZE];
private JLabel label;
private GameDataController controller;
static {
frame = new JFrame("扫雷2.0");
//将frame的布局管理器设置为GridLayout
frame.setLayout(new GridLayout(Settings.ROW_SIZE, Settings.COL_SIZE));
//设置frame的位置、大小、可见性,设置窗体大小不可更改以及关闭按钮的功能
frame.setBounds(Settings.FRAME_X, Settings.FRAME_Y, Settings.FRAME_WIDTH, Settings.FRAME_HEIGHT);
frame.setVisible(true);
frame.setResizable(false);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public Graphic(GameDataController controller) {
this.controller = controller;
draw();
}
private void draw() {
//通过循环创建label,并将其加入到frame中
for (int i = 0; i < Settings.ROW_SIZE; i++) {
for (int j = 0; j < Settings.COL_SIZE; j++) {
frame.add(label = new JLabel());
labels[i][j] = label;
//设置label的边框属性
label.setBorder(BorderFactory.createLineBorder(Color.BLACK, Settings.BORDER_WIDTH));
}
}
//创建事件监听器,监听鼠标点击在frame上的位置,并将监听器添加到frame上
gameListener = new GameListener(labels, controller);
frame.addMouseListener(gameListener);
}
public static void showDialog(boolean result) {
int option;
String message;
ImageIcon image;
//判断游戏的结束原因,并进行相应的赋值操作
if (result) {
message = Settings.DIALOG_VECTORY;
image = Image.IMAGE_VECTORY;
} else {
message = Settings.DIALOG_DEFEAT;
image = Image.IMAGE_DEFEAT;
}
//弹窗出现表示游戏结束,此时应移除窗体上的事件监听器
frame.removeMouseListener(gameListener);
//根据相关参数绘制弹窗
option = JOptionPane.showConfirmDialog(null, message, Settings.DIALOG_TITLE, JOptionPane.CANCEL_OPTION,JOptionPane.INFORMATION_MESSAGE,image);
if (option != JOptionPane.CANCEL_OPTION) {
System.exit(0);
}
}
}
事件监听器类,用于提供监听鼠标点击事件的监听器:
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
public class GameListener extends MouseAdapter {
private JLabel[][] labels;
private GameDataController controller;
public GameListener(JLabel[][] labels, GameDataController controller) {
this.labels = labels;
this.controller = controller;
}
@Override
public void mouseClicked(MouseEvent e) {
//对鼠标点击点的坐标进行计算可得到label在数组中的下标
int x = (e.getX()-Settings.BORDER_WIDTH)/Settings.IMAGE_SIZE;
int y = (e.getY()-Settings.TITLE_HEIGHT)/Settings.IMAGE_SIZE;
//区分鼠标左击右击事件
if (e.getButton() == MouseEvent.BUTTON1) {//鼠标左击,进行的操作为翻开当前位置
controller.leftClick(x, y, labels);
} else if (e.getButton() == MouseEvent.BUTTON3) {//鼠标右击,进行的操作为插旗与取消插旗
controller.rightClick(x, y, labels);
}
//通关判断
if (controller.missionAccomplished()) {
//通关则显示弹窗并移除监听器
Graphic.showDialog(true);
}
}
}
6.部分代码思路
6.1 生成随机的地雷坐标
创建Random类的对象,使用相关方法,生成地雷行坐标与列坐标的随机数值,使用二维数组存储坐标点数据。由于没有做去重处理,因此有概率生成多个相同的坐标,所以地雷数最多为设置的生成数,最少为1(概率极低)。
生成随机坐标的代码如下:
public static int[][] createMineCoord() {
//定义二维数组
int[][] mineCoordArray = new int[Settings.MINE_NUM][Settings.MINE_SITE_NUM];
Random random = new Random();
//将生成的随机坐标存入数组中
for (int i = 0; i < Settings.MINE_NUM; i++) {
for (int j = 0; j < Settings.MINE_SITE_NUM; j++) {
//生成行坐标随机数,并将其放入数组
if (j == Settings.MINE_ROW_IN_ARRAY) {
mineCoordArray[i][j] = random.nextInt(Settings.ROW_SIZE);
}
//生成列坐标随机数,并将其放入数组
if (j == Settings.MINE_COL_IN_ARRAY) {
mineCoordArray[i][j] = random.nextInt(Settings.COL_SIZE);
}
}
}
return mineCoordArray;
}
6.2 测试地雷生成
代码如下:
import java.util.Random;
public class Test {
public static void main(String[] args) {
int[][][] gameData = init();
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 10; j++) {
System.out.print("(");
for (int k= 0; k < 3; k++) {
System.out.print(gameData[i][j][k]);
if (k < 2) {
System.out.print(",");
}
}
System.out.print(")");
}
System.out.println();
}
}
private static int[][][] init(){
//创建大小为10*10*3的三维数组,并赋初值(默认初始值为0)
int[][][] gameData = new int[10][10][3];
//生成随机的地雷坐标,并将其存入游戏数据数组中
int[][] mineCoordArray = createMineCoord();
for (int[] mineCoord : mineCoordArray) {
int row = mineCoord[0];
int col = mineCoord[1];
gameData[row][col][0] = 1;
}
//计算每格周围地雷数并将其存入游戏数据数组中
return gameData;
}
}
运行结果如下:
将其转化为图像形式就是:
6.3 计算每格周围的地雷数目
思路:遍历目标坐标点周围的8个坐标点,每当发现一个地雷,则目标坐标点的游戏数据数组中的统计地雷的数值加1。
实现代码:
private void calcAroundNum() {
for (int i = 0; i < Settings.ROW_SIZE; i++) {
for (int j = 0; j < Settings.COL_SIZE; j++) {
//遍历当前坐标周围的8个坐标
for (int aroundRow = i-1; aroundRow <= i+1; aroundRow++) {
//行号超范围则跳过
if (aroundRow < 0 || aroundRow > Settings.ROW_SIZE-1) {
continue;
}
for (int aroundCol = j-1; aroundCol <= j+1; aroundCol++) {
//列号超范围则跳过
if (aroundCol < 0 || aroundCol > Settings.COL_SIZE-1) {
continue;
}
//排除本身坐标点
if ((gameData[aroundRow][aroundCol][Settings.MINE_DATA] == Settings.IS_MINE) && (!(aroundRow == i && aroundCol == j))) {
gameData[i][j][Settings.AROUND_MINE_DATA] += 1;
}
}
}
}
}
}
测试运行结果如下:
将其转换为图像表示:
7.游戏运行画面
7.1 踩中地雷
第一、二版:
第三版:
7.2 通关游戏
第一、二版:
第三版:
到此这篇关于利用java开发丐版扫雷游戏的文章就介绍到这了,更多相关java开发扫雷游戏内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341