【扫雷】游戏设计——逻辑全分析和代码设计详解

扫雷游戏系列目录

第一章 扫雷游戏——基础功能设计和代码实现【本文】
第二章 扫雷游戏——功能扩展和部分逻辑优化【链接: link


前言

本次是C语言在函数部分的学习和阶段性的总结,就以扫雷的代码实现来展现吧~
在阅读后续内容前,没有接触过扫雷游戏的读者可以自行体验网页版的扫雷游戏,便于后续理解和设计。链接: link
笔者的C代码均在 Visual Studio 2022 环境中编译和运行。本次分享的扫雷代码是基础实现,不涉及功能延申,后续会继续分享增加的功能代码和思路。


第一章 知识储备

先来回顾自定义函数的格式,如示例1-1所示。

//示例 1-1
ret_type fun_name(形式参数)
{
}

ret_type:函数返回类型,无返回值可设为 void
fun_name:函数名字,自己随便取,但肯定是以简洁和“顾名思义”为优先考虑。
形式参数:可有可无,具体要看设计的功能是否需要形式参数。不需要形式参数时可设计为 void或直接省略 ,当设计为 void 时,对函数进行参数会在编译运行时会出现警告,如示例代码1-2所示。

//示例代码 1-2
#include <stdio.h>
void Print(void)	//明确表示不需要任何形式参数,也无返回值   
{					//设计为 void Print() 时,使用函数传参 Print(1);编译时无警告
	printf("haha\n");
}
int main()
{
	Print(1);		//非法对Print传参。 编译时出现警告:【“Print”:用“void"参数列表声明】
	return 0;
}

代码1-2中不设计形式参数的 void Print() 和 void Print(void) 的区别:后者已经明确声明过自己不需要 (void) 形式参数,不需要传参,硬要塞参数给 Print 时,它会告诉(编译时出现警告)咱们,自己不需要参数。前者并未明确声明自己是否需要传递参数,这里问题来了,默认状况下咱们知道 void Print() 的本意是它不需要传参,但设计函数的形参时没有任何声明,就算使用 Print 时传递了参数,如 “Print(1);” 这条表达式语句,在编译时也不会有警告出现,这个传参操作其实是不正确的使用方式。
总结:使用 void 形参的函数,错误使用时会有警告;没有 void 时,就算错误使用也不会出现任何提示或警告,它只会默默流眼泪,呜呜呜~~~

函数体:大括号里边的内容,函数具体实现的逻辑和代码实现部分。
注意函数使用时需要先声明后使用。函数定义后,在函数调用时,如果函数定义在 main() 之后,需要在 main() 之前声明函数,否则会有警告,如示例代码1-3所示。

//示例代码 1-3
#include <stdio.h>

int Add(int, int);//int Add(int x, int y)
				  //这两种写法等价,形式参数的名字可以省略
int main()
{
	int a = 0;
	int b = 0;
	printf("请输入a和b的值->  ");
	scanf("%d %d", &a, &b);
	int ret = Add(a, b);

	printf("a+b=%d\n", ret);
	return 0;
}

int Add(int x, int y)
{
	return x + y;
}

在多文件的项目中,进行函数声明时,可以将函数声明单独放在一个头文件 (.h) 中,将函数定义单独放在一个文件中,在主函数所在的文件,只负责项目的整体逻辑。如本次实现的扫雷代码,使用头文件 game.h 声明函数;使用 game.c 文件进行函数定义,这里也是函数功能和逻辑的代码实现部分;test.c 文件是主函数 main() 整体逻辑的主要实现部分,负责扫雷代码的整体逻辑。


第二章 游戏的逻辑分析

首先分析扫雷游戏要实现的基本效果,根据需要实现的效果分析出需要的设计的功能并完善设计的逻辑。

游戏刚开始,要显示一个游戏菜单,提示玩家是否进行游戏,然后玩家输入对应的指示,来进行游戏或是退出游戏。笔者采用输入数字 1 来进行游戏,输入数字 0 来退出游戏的方式来实现。玩家选择输入数字 1 ,开始进行游戏。扫雷游戏开始,肯定有一个棋盘,让玩家选择性输入坐标来进行排雷的操作,若该坐标不是地雷,会在该坐标位置处显示周围 8 个格子的总地雷数量;若是地雷,则玩家直接死亡,该局游戏结束。之后笔者的设计是不会直接退出游戏,而是继续提示玩家是否继续游戏或退出游戏。本次扫雷游戏要实现的基本效果大致就是这些,具体细节会在设计代码时进行添改。这里对主要的过程进行逻辑分析。

2.1 棋盘和二维数组

扫雷游戏,首先要打印出玩家能看到的棋盘,可以使用二维数组来实现,将二维数组的每个元素作为棋盘的格子。这里笔者采用字符型二维数组,其它类型的数组是否适用,笔者在后文中有详细论述。

有了棋盘,在玩家眼中,游戏刚开始的棋盘中暗藏了一定数量的地雷。随着扫雷的进行,棋盘上会逐渐显示出数字(严格来说是数字字符),用来提示其周围 8 个格子中的总地雷数量。为了显示效果,笔者将玩家看到的初始棋盘中的每个格子,用 * 填充,也就是二维数组的元素全部为 * 。初始棋盘是布满了 * 的区域,随着玩家排雷的正确操作,棋盘上会逐步显示出数字 (周围地雷数量的信息) 。理论上的设计效果如图2-1所示(5 * 5 棋盘)。

在这里插入图片描述图2-1 理论棋盘的设计效果

2.2 随机布置地雷

解决了棋盘刚开始的显示问题,再来分析下如何暗藏地雷。这个在棋盘随机布置地雷的操作,肯定要放在打印棋盘之前,然后玩家才看到充满了 * 的初始棋盘。随机布雷主要还是体现在随机上,计算机中不可能真正做到随机,但能利用随时都在变化的时间,得到随时都在变化的值,这个伪随机数也可以满足需求。随机数的生成和使用,可以参考笔者另外的文章,最后的猜数字小游戏中有详细的介绍和其使用方法: link

获得随机数之后,可以将棋盘中随机找到的元素,更改为字符 1 ,代表此处有地雷,这也是随机布雷的过程。从这里开始,出现了一个问题,我们设计代码肯定是得到棋盘后,先随机布雷,然后打印棋盘。但当我们将字符型二维数组中原来的 * 元素,更改为字符 1 后,打印棋盘时,棋盘中会直接显示出字符 1 ,这就是直接告诉玩家:这些位置有地雷 (下方图片中显示 1 的位置) !!!扫雷游戏也就失去了意义,如图2-2所示。

在这里插入图片描述图2-2 地雷信息直接被显示在棋盘中

如何解决这个问题呢?这里给出一个方案。在设计棋盘时,设计两个棋盘,也就是定义两个同样大小的字符型二维数组,其中一个数组取名mine ,用来存放随机布置的地雷信息,另一个数组取名 show ,作为展示给玩家的初始棋盘。show 数组的元素,刚开始还是全部初始化为 * ,玩家刚进入游戏时看到的棋盘和之后玩家排雷时输入的坐标信息,都存放在 show 数组。mine 数组初始化时的元素全部显示为字符 0 ,当随机布置地雷后,布置地雷的元素所在处,使用字符 1 加以区分,这个数组不需要让玩家看到。设计两个棋盘,这样就不会出现只有一个棋盘时,布雷数据直接覆盖初始化棋盘数据的状况。棋盘初始化和随机布雷后的理论效果如图2-3所示。

棋盘图2-3 初步改进后的棋盘设计效果

随机布雷结束后,布置的地雷信息存放在 mine 数组,打印出棋盘 show 数组让玩家观看和游戏(输入坐标进行排雷操作)。接下来就是扫雷游戏最关键的扫雷过程了。

2.3 棋盘设计和扫雷

先把扫雷这个游戏动作转化为代码的动作。扫雷,其实是玩家看到 show 数组(棋盘)后,选择一个位置的坐标并输入,代码接收到这个坐标后,在 mine 数组(存放有地雷的排布信息)对应的坐标处检查该元素是否为字符 1 ,若为字符 1 ,玩家踩到地雷,玩家游戏失败,否则在 show 数组该坐标处显示出周围 8 个格子的总地雷数量,玩家继续排雷操作。在设计棋盘时,提到了设计两个大小一致的棋盘 show 和 mine ,那么在 show 中某个位置的坐标,放在 mine 中时,该位置相对棋盘的位置没有任何变化;show 数组中某个坐标周围的地雷数量信息,其实是先在 mine 数组中统计之后,将这个数据送到了 show 数组该坐标处进行显示。

继续分析,如何在 show 数组中,玩家输入的某个坐标处,显示周围 8 个格子的总地雷数量。假设棋盘为 5 * 5,布置 5 个地雷(mine 中的字符 1 ),玩家看着打印出的 show 棋盘,输入排查坐标 (5,3) ,代码需要在 mine 数组同位置 (5,3) 处,统计该位置周围 8 个格子的总地雷数量,然后将数量信息送至 show 数组 (5,3) 位置处并显示。mine 数组中统计 (5,3) 周围的地雷数量时,有三个格子在 mine 数组外,以 (5,3) 处的元素为基准,遍历其周围八个元素,在 mine 数组中需要访问的位置肯定会超出 mine 数组的访问地址,产生越界,这肯定是不可以的。如何解决呢?笔者采用扩大棋盘的方式解决坐标访问越界的问题,既然 5 * 5 的棋盘不够用,那就给它增大一圈,变成 7 * 7 的棋盘,初始化时的 show 和 mine 数组都是 7 * 7 大小,但进行随机布雷、打印棋盘和排雷操作时,只使用中间的 5 * 5 棋盘,周围增加的棋盘位只是为了坐标访问时不产生越界问题,如图2-4所示。接下来就能愉快统计坐标周围的地雷数量了~

改进前:
地雷数量统计
改进后:
Alt
图2-4 棋盘的最终设计效果

2.4 棋盘设计再分析

完成扫雷代码必不可少的操作:统计 show 棋盘中玩家输入的坐标处,周围 8 个格子中总的地雷数量。还记得设计棋盘时笔者说要设计字符型二维数组吗?是时候谈谈这个话题了。

假如设计的是两个整型二维数组。简单设计一下,使用数字 1 表示 mine 数组中的地雷,数字 0 表示没有地雷,mine 数组初始化时全部设为数字 0 。鉴于设计的是整型数组,mine 中排查出地雷数量后,show 数组显示的某位置处周围 8 个格子的总地雷数量可能是0~8 中的任何一个数字,为了避免这些显示地雷数量的数字和 show 棋盘初始化后的初始化显示数字混淆,show 数组初始化显示的数字不能选取 0~8 中的任何一个数字。那 show 数组初始化显示的数字,使用正数的话,只能使用数字 9 或更大数字的话,初始化棋盘 show 密密麻麻显示数字 9;再想象一下使用负整数,如果读者愿意这样设计的话,也可以实现本次的基础扫雷代码,但后续会出现一些不可避免的麻烦。

假如后续要在原代码的基础上添加新功能,当玩家初次选择的排雷坐标处没有地雷,并且周围的八个格子也没有地雷,那就递归展开一大片没有地雷的位置,直到遇到周围有地雷的地方就停止展开,展开的位置在 show 棋盘中显示时肯定要有一个标志性的标记,以便和没有排查过的地方进行区分。假设使用整型数组,show 数组初始化使用数字 9 显示,此时展开棋盘的位置就使用正数 10 吧,读者可以自行脑补一下效果。假如设计字符型二维数组,完全可以使用 空格 或者其它符号 # 、& 等。
还有增加地雷标记的功能后,玩家肯定要在 show 数组中对确认是地雷的位置进行标记,这个标记要和 show 棋盘中的其它显示内容都要不同,继续上述整型数组的假设,咱们选择使用数字 11 标记玩家确认是地雷的元素(位置)。分析到这里,想必读者也能看到整型数组在本次代码设计中的局限性了,而使用字符型数组,不仅可以使用数字字符 ‘0’、‘1’ 等,还可以使用其它符号字符如 * 、 # 、~ 等,拥有了更多的选择,更有利于代码设计和后期完善。

2.5 地雷数量统计

分析完棋盘设计和字符型数组的问题,这里提一下在使用字符型数组统计某个坐标周围的地雷数量时,需要注意的问题。还是假设 5 * 5 的棋盘,我们在随机布雷和排雷操作时,只会使用 7 * 7 棋盘中间的 5 * 5 棋盘,只有在初始化时是按照 7 * 7 棋盘进行初始化。假设玩家选择排查的坐标位置 (5,3) ,统计周围的地雷数量时,mine 中 (4,3) 处有一个地雷,mine[4][3] - ‘0’ ,也就是’1’ - ‘0’ ,这个字符减法表达式的结果是数字 1,那么周围八个元素各自减去字符零的和,就是 (5,3) 处周围 8 个格子内的总地雷数量。当把地雷数量转换为字符放到 show 中时,需要将这个值加上字符0,转为数字字符,注意 show 是字符型数组。本例算出地雷数量为数字 3,当把数字 3 放进 show 中 show[5][3] 时,放入的是 3 + ‘0’ 这个表达式的结果,这个结果是字符3(‘3’ )。玩家看到 show 棋盘显示 3 ,就会明白周围八个格子共有总计 3 个地雷,如图2-5所示。

在这里插入图片描述
图2-5 地雷数量统计


第三章 代码设计

3.1 宏定义

宏定义的引入主要是为了方便修改棋盘大小和布置地雷数量。如果将棋盘大小和布置的地雷数量设计为某个确定的数字,后续修改棋盘大小和地雷数量时,需要逐步找到这些数字出现的位置和表达式语句再进行修改,比较繁琐,宏定义可以一键替换文本,即修改棋盘大小和随机布置的地雷数量。
没有参数的宏定义,使用格式如示例3-1所示。

//示例 3-1
#define 宏名称 替换文本  //惯例将宏名称每个字母采用大写,这有助于区分宏与一般的变量。

本次代码设计中,使用的对象式宏如示例3-2所示。

//示例 3-2
#define ROW 5 			 //设计的棋盘大小  行
#define COL 5 			//列

#define ROWS ROW + 2  //这里的行和列是根据基础棋盘的行 (ROW) 和列 (COL) 进行调整
#define COLS COL + 2

#define NumLei 23   //布置的地雷数量

3.2 主函数

扫雷游戏的主要逻辑和游戏菜单实现,如示例代码3-3所示。

//示例代码 3-3
#include "game.h"
void menu(void)
{
	printf("*************************\n");
	printf("*** 1.play     0.exit ***\n");
	printf("*************************\n");
}

void game(void)
{
	//有棋盘
	char mine[ROWS][COLS] = { 0 };
	char show[ROWS][COLS] = { 0 };

	//初始化棋盘
	InitBoard(mine, ROWS, COLS, '0');
	InitBoard(show, ROWS, COLS, '*');

	//随机布雷
	SetMine(mine, ROW, COL);
	DisPlay(mine, ROW, COL);

	//打印棋盘
	DisPlay(show, ROW, COL);

	//排雷
	FindMine(mine, show, ROW, COL);
}

void test(void)
{
	int input = 0;
	srand((unsigned int)time(NULL));
	do
	{
		menu();
		printf("游戏即将开始!请输入你的选择:->  ");
		scanf("%d", &input);
		printf("\n");

		switch (input)
		{
		case 1:
			game();//printf("开始游戏\n");
			break;
		case 0:
			printf("游戏结束!退出游戏\n\n");
			break;
		default:
			printf("输入错误!请重新输入\n\n");
		}
	} while (input);
}

int main()
{
	test();

	return 0;
}

3.3 棋盘元素

设计两个相同大小的字符型二维数组,将数组的元素作为游戏需要操作的对象,如示例3-4所示。

//示例 3-4
//有棋盘
char mine[ROWS][COLS] = { 0 };//还没进行初始化棋盘
char show[ROWS][COLS] = { 0 };

3.4 初始化棋盘内容

代码3-5中形式参数 char set 的设计,使 InitBoard() 对数组元素进行初始化时更加灵活,可随意设置初始化的内容。

//示例代码 3-5
void InitBoard(char arr[ROWS][COLS], int rows, int cols, char set)
{
	int i = 0;
	for (i = 0; i < rows; i++)
	{
		int j = 0;
		for (j = 0; j < cols; j++)
		{
			arr[i][j] = set;
		}
	}
}

3.5 随机布雷

假设需要设计 5 * 5 的棋盘。为了得到1~5 的随机数进行随机布雷,将 rand() 模上棋盘的行数 5 ,得到余数 0 ~ 4 ,将其加一,得到 1 ~ 5 的随机数,列数同理。如代码3-6所示。

//示例代码 3-6
void SetMine(char mine[ROWS][COLS], int row, int col)
{
	int count = 0;

	while (count < NumLei)
	{
		int x = rand() % row + 1;
		int y = rand() % col + 1;

		if (mine[x][y] == '0')
		{
			mine[x][y] = '1';
			count++;
		}
	}
}

3.6 显示棋盘

打印行号和列号是为了便于玩家游戏,写代码的人知道数组从零开始,但玩家不一定了解。打印出行号和列号,每次排雷时不必从零开始查坐标,可以方便查看某个位置的坐标,提高游戏体验,并规范排查的坐标范围。代码设计如3-7所示。

//示例代码 3-7
void DisPlay(char arr[ROWS][COLS], int row, int col)
{
	int i = 0;

	printf("-------扫雷游戏-------\n");
	for (i = 0; i <= row; i++)//打印列号,从 0 开始
	{
		printf("%d ", i);
	}
	printf("\n");

	for (i = 1; i <= row; i++)
	{
		int j = 0;
		printf("%d ", i);//打印行号,从 1 开始

		for (j = 1; j <= col; j++)
		{
			printf("%c ", arr[i][j]);
		}
		printf("\n");
	}
}

显示棋盘的 DisPlay() 函数,在设计时的实际顺序为初始化棋盘结束后。因为初始化棋盘后,可以使用 DisPlay() 显示函数来观察初始化棋盘 InitBoard() 函数设计的正确性。后续的随机布雷 SetMine() 函数设计完成后,也可使用 DisPlay() 函数来观察随机布雷函数 SetMine() 设计的正确性。

3.7 排查地雷(扫雷)

这是整个游戏设计中最为重要的一个功能,需要和玩家进行实时交互判断输入的坐标是否为地雷以及进行输赢的判断等。其中,统计地雷的数量时,要清楚数字字符和数字之间的关系,前文有叙述。

玩家游戏胜利的判断条件:
玩家每次排查一个位置,假设为 5 * 5 (ROW * COL)棋盘,布置地雷数量为 23 (NumLei)个,只要玩家正确地连续排查 2 个位置就能胜利,因为共有 5 * 5 = 25 个元素,不是地雷的元素有 25 - 23 = 2 (ROW * COL - NumLei) 个,只要不是地雷的元素都被玩家排查后,玩家肯定就是胜利了,剩下的元素都是分布的地雷。如代码3-8所示。

//示例代码 3-8
//获得 mine 中 (x,y) 处周围八个格子的总地雷数量,返回数字字符
static int GetMine(char mine[ROWS][COLS], int x, int y)
{
	int i = 0;
	int count = 0;

	for (i = x - 1; i <= x + 1; i++)
	{
		int j = 0;
		for (j = y - 1; j <= y + 1; j++)
		{
			count += (mine[i][j] - '0');
		}
	}

	return count;
}

void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)//排雷操作
{
	int x = 0;
	int y = 0;
	int win = 0;

	while (win < row * col - NumLei)
	{
		printf("\n请输入要排查的坐标:->   ");
		scanf("%d %d", &x, &y);

		if (x >= 1 && x <= row && y >= 1 && y <= col)//坐标有效?
		{
			if (show[x][y] == '*')//坐标重复?
			{
				if (mine[x][y] == '1')//直接死亡?
				{
					printf("很遗憾!你被炸死了\n");
					printf("来看下地雷分布吧~\n\n");
					DisPlay(mine, ROW, COL);

					printf("\n");
					break;
				}
				else
				{
					int count = GetMine(mine, x, y);
					show[x][y] = count + '0';//传入数字字符
					DisPlay(show, ROW, COL);
					win++;
				}
			}
			else
			{
				printf("该坐标已被排查过!请重新输入\n\n");
			}
		}
		else
		{
			printf("输入坐标错误!请重新输入\n\n");
		}
	}
	if (row * col - NumLei == win)
	{
		printf("\a恭喜你!排雷成功^ - ^\n\n");
		puts("来看下本次游戏的地雷分布吧~");
		DisPlay(mine, ROW, COL);
		printf("\n");
	}
}

第四章 源代码分享

4.1 主函数 test()

//示例代码 4-1         test.c
#include "game.h"

void menu(void)
{
	printf("*************************\n");
	printf("*** 1.play     0.exit ***\n");
	printf("*************************\n");
}

void game(void)
{
	//有棋盘
	char mine[ROWS][COLS] = { 0 };
	char show[ROWS][COLS] = { 0 };

	//初始化棋盘
	InitBoard(mine, ROWS, COLS, '0');
	InitBoard(show,	ROWS, COLS, '*');

	//随机布雷
	SetMine(mine, ROW, COL);
	DisPlay(mine, ROW, COL);//该行代码主要用于调试使用,游戏时请将该行代码注释掉。  否则就是开挂游戏了,哈哈哈^ - ^ 

	//打印棋盘
	DisPlay(show, ROW, COL);

	//排雷
	FindMine(mine, show, ROW, COL);
}

void test(void)
{
	int input = 0;
	srand((unsigned int)time(NULL));
	do
	{
		menu();
		printf("游戏即将开始!请输入你的选择:->  ");
		scanf("%d", &input);
		printf("\n");

		switch (input)
		{
		case 1:
			game();
			break;
		case 0:
			printf("游戏结束!退出游戏\n\n");
			break;
		default:
			printf("输入错误!请重新输入\n\n");
		}
	} while (input);
}

int main()
{
	test();

	return 0;
}

4.2 函数声明 game.h

//game.h
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define ROW 5    //棋盘行数,自行修改
#define COL 5	 //列数

#define ROWS ROW + 2
#define COLS COL + 2

#define NumLei 23  //布置的地雷数量,可自行修改
	
void InitBoard(char arr[ROWS][COLS], int rows, int cols, char set );

void SetMine(char mine[ROWS][COLS], int row, int col);

void DisPlay(char arr[ROWS][COLS], int row, int col);

void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);

4.3 函数定义 game.c

//game.c
#include "game.h"
void InitBoard(char arr[ROWS][COLS], int rows, int cols, char set)
{
	int i = 0;
	for (i = 0; i < rows; i++)
	{
		int j = 0;
		for (j = 0; j < cols; j++)
		{
			arr[i][j] = set;
		}
	}
}

void SetMine(char mine[ROWS][COLS], int row, int col)
{
	int count = 0;

	while (count < NumLei)
	{
		int x = rand() % row + 1;
		int y = rand() % col + 1;

		if (mine[x][y] == '0')
		{
			mine[x][y] = '1';
			count++;
		}
	}
}

void DisPlay(char arr[ROWS][COLS], int row, int col)
{
	int i = 0;

	printf("-------扫雷游戏-------\n");
	for (i = 0; i <= row; i++)
	{
		printf("%d ", i);
	}
	printf("\n");

	for (i = 1; i <= row; i++)
	{
		int j = 0;
		printf("%d ", i);

		for (j = 1; j <= col; j++)
		{
			printf("%c ", arr[i][j]);
		}
		printf("\n");
	}
}

static int GetMine(char mine[ROWS][COLS], int x, int y)
{
	int i = 0;
	int count = 0;

	for (i = x - 1; i <= x + 1; i++)
	{
		int j = 0;
		for (j = y - 1; j <= y + 1; j++)
		{
			count += (mine[i][j] - '0');
		}
	}

	return count;
}

void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	int x = 0;
	int y = 0;
	int win = 0;

	while (win < row * col - NumLei)
	{
		printf("\n请输入要排查的坐标:->   ");
		scanf("%d %d", &x, &y);

		if (x >= 1 && x <= row && y >= 1 && y <= col)//坐标有效?
		{
			if (show[x][y] == '*')//坐标重复?
			{
				if (mine[x][y] == '1')//直接死亡?
				{
					printf("很遗憾!你被炸死了\n");
					printf("来看下地雷分布吧~\n\n");
					DisPlay(mine, ROW, COL);

					printf("\n");
					break;
				}
				else
				{
					int count = GetMine(mine, x, y);
					show[x][y] = count + '0';
					DisPlay(show, ROW, COL);
					win++;
				}
			}
			else
			{
				printf("该坐标已被排查过!请重新输入\n\n");
			}
		}
		else
		{
			printf("输入坐标错误!请重新输入\n\n");
		}
	}
	if (row * col - NumLei == win)
	{
		printf("\a恭喜你!排雷成功^ - ^\n\n");
		puts("来看下本次游戏的地雷分布吧~");
		DisPlay(mine, ROW, COL);
		printf("\n");
	}
}

4.4 部分运行效果展示

在这里插入图片描述图4-1 代码运行效果


总结

本次扫雷代码的分享只涉及基础功能,后续会扩展新功能。自定义函数和模块化编程是整个项目的设计思路。感谢每位耐心观看的读者~

  • 29
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个简单的扫雷游戏的 JAVA 代码,实现了基本的游戏功能: ``` import java.util.Random; import java.util.Scanner; public class Minesweeper { private static final int ROWS = 10; private static final int COLS = 10; private static final int BOMBS = 10; private static final char COVERED = '-'; private static final char UNCOVERED = ' '; private static final char BOMB = '*'; private char[][] board; private boolean[][] bombPositions; public Minesweeper() { board = new char[ROWS][COLS]; bombPositions = new boolean[ROWS][COLS]; initBoard(); initBombs(); } private void initBoard() { for (int i = 0; i < ROWS; i++) { for (int j = 0; j < COLS; j++) { board[i][j] = COVERED; } } } private void initBombs() { Random random = new Random(); int count = 0; while (count < BOMBS) { int row = random.nextInt(ROWS); int col = random.nextInt(COLS); if (!bombPositions[row][col]) { bombPositions[row][col] = true; count++; } } } private int countAdjacentBombs(int row, int col) { int count = 0; for (int i = row - 1; i <= row + 1; i++) { for (int j = col - 1; j <= col + 1; j++) { if (i >= 0 && i < ROWS && j >= 0 && j < COLS && bombPositions[i][j]) { count++; } } } return count; } public boolean playMove(int row, int col) { if (bombPositions[row][col]) { board[row][col] = BOMB; return false; } int adjacentBombs = countAdjacentBombs(row, col); board[row][col] = (char) ('0' + adjacentBombs); if (adjacentBombs == 0) { for (int i = row - 1; i <= row + 1; i++) { for (int j = col - 1; j <= col + 1; j++) { if (i >= 0 && i < ROWS && j >= 0 && j < COLS && board[i][j] == COVERED) { playMove(i, j); } } } } return true; } public void playGame() { Scanner scanner = new Scanner(System.in); boolean gameOver = false; while (!gameOver) { printBoard(); System.out.print("Enter row and column (e.g. 1 2): "); int row = scanner.nextInt() - 1; int col = scanner.nextInt() - 1; if (row < 0 || row >= ROWS || col < 0 || col >= COLS) { System.out.println("Invalid row or column!"); continue; } gameOver = !playMove(row, col); if (checkWin()) { System.out.println("You win!"); gameOver = true; } } printBoard(); } private boolean checkWin() { for (int i = 0; i < ROWS; i++) { for (int j = 0; j < COLS; j++) { if (board[i][j] == COVERED && !bombPositions[i][j]) { return false; } } } return true; } private void printBoard() { System.out.print(" "); for (int j = 0; j < COLS; j++) { System.out.print((j + 1) + " "); } System.out.println(); for (int i = 0; i < ROWS; i++) { System.out.print((i + 1) + " "); for (int j = 0; j < COLS; j++) { System.out.print(board[i][j] == UNCOVERED && bombPositions[i][j] ? BOMB : board[i][j]); System.out.print(" "); } System.out.println(); } } public static void main(String[] args) { Minesweeper minesweeper = new Minesweeper(); minesweeper.playGame(); } } ``` 代码中定义了游戏的行数,列数和雷数,以及用来表示板块状态的字符常量。在 `Minesweeper` 类中,通过二维数组 `board` 和 `bombPositions` 来表示游戏面板和雷的位置。通过 `initBoard` 和 `initBombs` 方法初始化游戏面板和雷的位置。 在 `countAdjacentBombs` 方法中,根据当前位置的周围八个位置计算雷的个数。 在 `playMove` 方法中,根据当前位置是否有雷计算该位置的状态,并根据周围是否有雷递归更新周围的位置状态。 在 `playGame` 方法中,通过 `Scanner` 类读取玩家的输入,并根据输入更新游戏状态,直到游戏结束。 在 `checkWin` 方法中,检查游戏是否胜利。 在 `printBoard` 方法中,输出游戏面板。 在 `main` 方法中,创建 `Minesweeper` 对象并调用 `playGame` 方法开始游戏

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值