热门标签 | HotTags
当前位置:  开发笔记 > 运维 > 正文

利用java开发简易版扫雷游戏

这篇文章主要介绍了利用java开发一个丐版扫雷游戏,喜欢玩扫雷的小伙伴们一定要试试哦,对正在学习java开发的小伙伴们也有一定帮助,需要的朋友可以参考下

1.简介

学了几周的Java,闲来无事,写个乞丐版的扫雷,加强一下Java基础知识。

2.编写过程

编写这个游戏,一共经历了三个阶段,编写了三个版本的游戏代码。

第一版:完成了扫雷游戏的基本雏形,实现了游戏的基本功能,游戏运行在cmd黑窗口中,以字符绘制游戏界面,无图形化窗口,通过控制台输入字符完成游戏控制。代码放置在一个java文件中,代码的可读性以及可扩展性都比较差。

第二版:在第一版实现基本功能的基础之上,对游戏代码进行重构,根据各部分的功能创建多个类,增加代码注释,提高代码的可读性以及可扩展性。

第三版:在第二版重构代码的基础之上给游戏增加了图形化界面,将用户从控制台输入命令控制游戏变为通过鼠标左右键点击操作控制游戏。

3.游戏运行逻辑

游戏运行逻辑图(第一版代码):

流程图1

游戏运行逻辑图(第二版代码):

流程图2

以上两个游戏流程图的运行是建立在从控制台读取数据的基础之上的,两者的执行逻辑大体相同,其本质区别在于修改游戏数据的时机不相同。前者是在通关判断之前修改数据,后者实在通关判断之后。两者在运行期间并没有什么区别,但是当玩家完成扫雷之后最后的画面打印就会出现问题,即游戏画面中最后一个进行操作的坐标点的字符的显示状态,在发生改变之前就会终止程序。通过对修改游戏数据以及通关判断这两个操作的执行顺序进行调整,即可修正这一显示错误。

游戏运行逻辑图(第三版代码):

流程图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;
/**
 * 扫雷游戏
 * @author zjl
 *
 */
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 = '&#63;';
					break;
				}	
				//打印符号
				System.out.print(sign + " ");
			}	
			System.out.println("*");
		}	
		System.out.println("  ***********************");
	}
	/**
	 * 打印空白行
	 */
	private static void printBlank(int blankNum) {
		for (int i = 0; i  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;
	}
	/**
	 * 从控制台读取数据,并对游戏的数据数组进行修改
	 * @param input
	 */
	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;
	}
	/**
	 * 通关判断
	 * @return
	 */
	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;
/**
 * 扫雷游戏
 * @author zjl
 *
 */
public class MineSweeper {
	/**
	 * 游戏运行主程序
	 * @param args
	 */
	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);		
		}
	}
}

设置类,游戏相关设置数据:

/**
 * 定义游戏初始数据的类
 * @author zjl
 *
 */
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;
	/**
	 * 确定地雷位置所需要的坐标数,由于是在平面内,所以只需要设置横纵坐标,值为2
	 */
	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;
	/**
	 * 表示初始符号,即'&#63;'
	 */
	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;
	//游戏符号
	/**
	 * 初始符号'&#63;'
	 */
	public static final char INIT_SIGN = '&#63;';
	/**
	 * 插旗符号'#'
	 */
	public static final char FLAG_SIGN = '#';
	/**
	 * 地雷符号'*'
	 */
	public static final char MINE_SIGN = '*';
	/**
	 * 在ASCII码表中整数48~57代表字符0~9,设置一个增量值,将数字转换为字符
	 */
	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.输入行号及列号来选中要翻开的\'&#63;\'进行操作,可\n"
											 + "\t  以选择插旗(#)或者直接翻开.\n"
											 + "\t 2.如果翻开'*'则表示地雷,则游戏结束;如果翻开\n"
											 + "\t  的是数字,则表示该格周围的地雷数.\n"
											 + "\t 3.标记出全部地雷,并且没有\'&#63;\',则闯关成功,游戏\n"
											 + "\t  结束.\n"
											 + "**************************************************\n\n";	
}

地雷类,生成随机的地雷坐标数据:

import java.util.Random;
/**
 * 有关地雷的类
 * @author zjl
 *
 */
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 

控制类,控制游戏进程,以及游戏数据:

import java.util.Scanner;

/**
 * 关于游戏相关控制的类
 * @author zjl
 *
 */
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-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;
	}
	/**
	 * 从控制台读取数据,并对游戏的数据数组进行修改
	 * @param input
	 */
	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 

显示类,对游戏的相关画面进行打印:

/**
 * 展示游戏相关画面的类
 * @author zjl
 *
 */
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 

5.3 第三版

在第二版的基础上,去除了显示类,增加了图片类、图形界面类、事件监听器类。

游戏运行主程序类:

/**
 * 扫雷游戏主程序类
 * @author zjl
 *
 */
public class MineSweeper {
	/**
	 * 游戏运行主程序
	 * @param args
	 */
	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);
	}
}

设置类,提供游戏运行相关这是数据:

/**
 * 定义游戏初始数据的类
 * @author zjl
 *
 */
public class Settings {
	//定义游戏界面参数
	/**
	 * 游戏界面的行数
	 */
	public static final int ROW_SIZE = 10;
	/**
	 * 游戏界面的列数
	 */
	public static final int COL_SIZE = 10;
	
	/**
	 * 地雷数
	 */
	public static final int MINE_NUM = 20;
	/**
	 * 确定地雷位置所需要的坐标数,由于是在平面内,所以只需要设置横纵坐标,值为2
	 */
	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;
	/**
	 * 游戏窗口在屏幕上的x位置
	 */
	public static final int FRAME_X = 400;
	/**
	 * 游戏窗口在屏幕上的y位置
	 */
	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;

/**
 * 这是一个用于提供游戏所需图片的类,在游戏运行时,将相关的图片加载到图形界面上
 * @author zjl
 *
 */
public class Image{
	/**
	 * ImageIcon类型的常量,表示地雷图片
	 */
	public static final ImageIcon IMAGE_MINE = new ImageIcon("img\\mine.png");
	/**
	 * ImageIcon类型的常量,表示旗帜图片
	 */
	public static final ImageIcon IMAGE_FLAG = new ImageIcon("img\\flag.png");
	/**
	 * ImageIcon类型的常量,表示失败时的表情图片
	 */
	public static final ImageIcon IMAGE_DEFEAT = new ImageIcon("img\\defeat.png");
	/**
	 * ImageIcon类型的常量,表示通关时的表情图片
	 */
	public static final ImageIcon IMAGE_VECTORY = new ImageIcon("img\\vectory.png");
	
	/**
	 * 这是一个用于返回数字图片的静态方法,
	 * 通过传入的参数来获取表示对应数字的图片,
	 * 返回的图片上的数字表示某位置周围存在的地雷数
	 * @param mineNum-int类型,表示传入地雷数量的参数
	 * @return image-ImageIcon类型的返回值,返回的图片上的数字与参数mineNum对应
	 */
	public static ImageIcon getImageByNum(int mineNum) {
		ImageIcon image = new ImageIcon("img\\"+mineNum+".png");
		return image;
	}
}

地雷类,生成游戏中的类的相关数据:

import java.util.Random;

/**
 * 这是一个用于生成随机地雷坐标的类。
 * 由于生成的是伪随机数,因此生成的地雷坐标可能重复,所以实际游戏中的地雷数量并不固定。
 * @author zjl
 *
 */
public class Mine {
	
	/**
	 * 随机生成坐标数据,为游戏提供随机的地雷坐标数据。
	 * @return mineCoordArray-int类型的二维数组,存储的是生成的地雷的坐标数据
	 */
	public static int[][] createMineCoord() {
		//定义二维数组
		int[][] mineCoordArray = new int[Settings.MINE_NUM][Settings.MINE_SITE_NUM];
		Random random = new Random();
		//将生成的随机坐标存入数组中
		for (int i = 0; i 

游戏控制类,提供用于游戏控制的相关方法:

import javax.swing.JLabel;

/**
 * 这是一个用于游戏数据控制的类,
 * @author zjl
 *
 */
public class GameDataController {
	/**
	 * 私有的成员变量,用于存储在构造方法中接收到的游戏数据
	 */
	private int[][][] gameData;
	/**
	 * 这是本类的一个有参构造方法,通过传入游戏数据来构造一个游戏数据控制器
	 * @param 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();
	}
	/**
	 * 游戏通关判断
	 * @return 返回boolean类型的true或false,true表示游戏通关
	 */
	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-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;

/**
 * 绘制扫雷的图形界面的类,提供用于绘制界面以及用于获取相关对象的方法
 * @author zjl
 *
 */
public class Graphic {
	/**
	 * 定义JFrame类型的的静态属性frame
	 */
	private static JFrame frame;
	/**
	 * 定义GameListener类型的游戏事件监听器gameListener
	 */
	private static GameListener gameListener;
	/**
	 * 定义用于存储JLabel类型数据的二维数组labels
	 */
	private JLabel[][] labels = new JLabel[Settings.ROW_SIZE][Settings.COL_SIZE];
	/**
	 * 定义JLabel类型的属性label
	 */
	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);
	}
	/**
	 * 这是该类的一个有参构造方法,用于创建Graphic对象。
	 * 该构造方法调用本类中的draw()方法,绘制游戏的图形化界面
	 * @param controller-接收主函数中传入的游戏参数控制器对象,后续将其传递到事件监听器中,使监听器能够修改游戏数据
	 */
	public Graphic(GameDataController controller) {
		this.cOntroller= controller;
		draw();
	}
	/**
	 * 绘制游戏的图形化界面,并在游戏窗口上添加事件监听器
	 */
	private void draw() {
		//通过循环创建label,并将其加入到frame中
		for (int i = 0; i 

事件监听器类,用于提供监听鼠标点击事件的监听器:

import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import javax.swing.ImageIcon;
import javax.swing.JLabel;

/**
 * 定义游戏相关的事件监听器
 * @author zjl
 *
 */
public class GameListener extends MouseAdapter {
	/**
	 * 私有的成员变量,用以接收构造事件监听器时传入的JLabel数组
	 */
	private JLabel[][] labels;
	/**
	 * 私有的成员变量,用以接收构造事件监听器时传入的游戏数据控制器对象
	 */
	private GameDataController controller;
	/**
	 * 这是本类一个有参构造方法,用于根据传入的参数构造事件监听器对象
	 * @param labels-这是一个JLabel数组,为后续改变相应的图像显示提供容纳图片的JLabel组件
	 * @param controller-游戏数据控制器对象,用于改变相应游戏数据
	 */
	public GameListener(JLabel[][] labels, GameDataController controller) {
		this.labels = labels;
		this.cOntroller= controller;
	}
	/**
	 * 重写MouseAdapter类中的mouseClicked方法,添加响应鼠标操作的逻辑代码
	 */
	 @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 

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();
		}
	}
	
	/**
	 * 初始化游戏数据
	 * @return
	 */
	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-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 踩中地雷

第一、二版:

踩雷

第三版:

踩雷2.0

7.2 通关游戏

第一、二版:

通关

第三版:

通关2.0

到此这篇关于利用java开发丐版扫雷游戏的文章就介绍到这了,更多相关java开发扫雷游戏内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!


推荐阅读
  • 本文详细探讨了Netty中Future及其子类的设计与实现,包括其在并发编程中的作用和具体应用场景。我们将介绍Future的继承体系、关键方法的实现细节,并讨论如何通过监听器和回调机制来处理异步任务的结果。 ... [详细]
  • 2023年京东Android面试真题解析与经验分享
    本文由一位拥有6年Android开发经验的工程师撰写,详细解析了京东面试中常见的技术问题。涵盖引用传递、Handler机制、ListView优化、多线程控制及ANR处理等核心知识点。 ... [详细]
  • 深入解析 Apache Shiro 安全框架架构
    本文详细介绍了 Apache Shiro,一个强大且灵活的开源安全框架。Shiro 专注于简化身份验证、授权、会话管理和加密等复杂的安全操作,使开发者能够更轻松地保护应用程序。其核心目标是提供易于使用和理解的API,同时确保高度的安全性和灵活性。 ... [详细]
  • 本文探讨了在Linux系统上使用Docker时,通过volume将主机上的HTML5文件挂载到容器内部指定目录时遇到的403错误,并提供了解决方案和详细的操作步骤。 ... [详细]
  • 探讨如何真正掌握Java EE,包括所需技能、工具和实践经验。资深软件教学总监李刚分享了对毕业生简历中常见问题的看法,并提供了详尽的标准。 ... [详细]
  • 作为一名专业的Web前端工程师,掌握HTML和CSS的命名规范是至关重要的。良好的命名习惯不仅有助于提高代码的可读性和维护性,还能促进团队协作。本文将详细介绍Web前端开发中常用的HTML和CSS命名规范,并提供实用的建议。 ... [详细]
  • 本文探讨了在 ASP.NET MVC 5 中实现松耦合组件的方法。通过分离关注点,应用程序的各个组件可以更加独立且易于维护和测试。文中详细介绍了依赖项注入(DI)及其在实现松耦合中的作用。 ... [详细]
  • Startup 类配置服务和应用的请求管道。Startup类ASP.NETCore应用使用 Startup 类,按照约定命名为 Startup。 Startup 类:可选择性地包括 ... [详细]
  • 网易严选Java开发面试:MySQL索引深度解析
    本文详细记录了网易严选Java开发岗位的面试经验,特别针对MySQL索引相关的技术问题进行了深入探讨。通过本文,读者可以了解面试官常问的索引问题及其背后的原理。 ... [详细]
  • 自己用过的一些比较有用的css3新属性【HTML】
    web前端|html教程自己用过的一些比较用的css3新属性web前端-html教程css3刚推出不久,虽然大多数的css3属性在很多流行的浏览器中不支持,但我个人觉得还是要尽量开 ... [详细]
  • 本文将深入探讨如何在不依赖第三方库的情况下,使用 React 处理表单输入和验证。我们将介绍一种高效且灵活的方法,涵盖表单提交、输入验证及错误处理等关键功能。 ... [详细]
  • 本文探讨了如何在日常工作中通过优化效率和深入研究核心技术,将技术和知识转化为实际收益。文章结合个人经验,分享了提高工作效率、掌握高价值技能以及选择合适工作环境的方法,帮助读者更好地实现技术变现。 ... [详细]
  • 探索电路与系统的起源与发展
    本文回顾了电路与系统的发展历程,从电的早期发现到现代电子器件的应用。文章不仅涵盖了基础理论和关键发明,还探讨了这一学科对计算机、人工智能及物联网等领域的深远影响。 ... [详细]
  • 科研单位信息系统中的DevOps实践与优化
    本文探讨了某科研单位通过引入云原生平台实现DevOps开发和运维一体化,显著提升了项目交付效率和产品质量。详细介绍了如何在实际项目中应用DevOps理念,解决了传统开发模式下的诸多痛点。 ... [详细]
  • 本文详细介绍了 Flink 和 YARN 的交互机制。YARN 是 Hadoop 生态系统中的资源管理组件,类似于 Spark on YARN 的配置方式。我们将基于官方文档,深入探讨如何在 YARN 上部署和运行 Flink 任务。 ... [详细]
author-avatar
小小的梦想123
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有