手把手教你写个扫雷(插旗,及展开图解与实现)

学习了二维数组以后,不仅可以写个三子棋,我们也可以更近一步,来写个扫雷玩玩?

编写程序的时候,一定要先做好构思与大体思路步骤,扫雷的基本运行逻辑其实并不算非常复杂,我们只需要把每一步捋顺即可。

本人能力有限,难免有叙述错误或者不详细之处!希望读者在阅读时可以反馈一下错误以及不够好的地方!感激不尽!

目录

菜单的创建与变量的创建

雷区的打印与初始化

游戏主体逻辑

插旗效果实现

展开的实现

插旗排雷

 


1.做一个简单可互动的菜单,准备好需要用到的变量。

2.创建两个二维数组,一个用于存放真实的雷区,另一个存放显示用的雷区,其中真实的雷区用随机数随机填入地雷,真实雷区范围设置 要设置成11 11,显示雷区范围在9 9内,否则数组将会越界。
3.也是打印雷区地图,雷子用1与0,外面用于遮盖的用*。

4.编写可以随机在雷区内埋雷的函数与对雷区初始化的函数。

5.玩家行动的编写,失败的触发条件,玩家行动时的选择,是排查还是插旗子。

6.游戏主体逻辑的编写:包涵检测胜利,以及最终难点,扫雷的展开。

菜单的创建与变量的创建

 变量的创建

我们先在头文件内创建一个新文件,在其中定义我们所需要的变量以及所需库函数的调用。

我们的扫雷棋盘设置为9X9,所以我们定义两个变量:ROW,COL分别为其赋值为9,但是由于涉及到需要遍历格子旁边8格的地雷数量函数的编写,如果选择计算最左上角的格子附近8格的地雷数量,9X9的数组元素个数完全不够,会出现数组越界的情况,我们不妨为其新建另外两个真实的控制二维数组大小变量的值,让ROW与COL只显示9X9的雷区,而真实的二维数组大小应是11X11,我们创建两个变量ROWS,ROLS为其赋值为11。

一旦设置成9X9,我们编写的寻找附近地雷的函数就会越界,因为它找不着附近的元素了。

#include  <stdio.h>
#include <stdlib.h>
#include <time.h >
#define ROW 9
#define COL 9
#define ROWS 11
#define COLS 11

 接下来我们在main函数里创建二维数组,一个用于存放真实的雷区,另一个用于显示外面的覆盖雷区。

int main()
{
	char mine[ROWS][COLS] = { 0 };//真实雷区
	char showmine[ROWS][COLS] = { 0 };//显示雷区

	return 0;
}

   目标实现的效果如下:                        

              真雷区                          覆盖后雷区

 


menu()

这个并不算困难,首先是打印基本的UI界面,只需要创建一个函数放入几个printf即可,个人代码如下,想创建成其他样式请随意。

void menu()
{
	printf("\n");
	printf("======|      扫雷      |======\n");
	printf("======|     1.play     |======\n");
	printf("======|     0.exit     |======\n");
}

菜单的互动

我们使用case语句来编写菜单的互动,非常简单,玩家输入1进入游戏,输入0退出,输入错误重试,代码如下:

int main()
{

	char mine[ROWS][COLS] = { 0 };
	char showmine[ROWS][COLS] = { 0 };
	int c = 1;

	menu();

	do
	{
		scanf("%d", &c);

		switch (c)

		{

		case 1:

			printf("游戏开始!祝你好运!\n");
			game();
			break;

		case 0:

			printf("退出游戏!\n");
			break;

		default:
			printf("输入错误,请重新选择输入!\n");

		}

	} while (c);

	return 0;
}

雷区的打印与初始化

雷区初始化

这一部分的代码请注意作用于真实雷区,即mine而非showmine,我们创建另一个源文件function来实现这个函数

由于我们有两个雷区都涉及到初始化以及打印的问题,那么我们不妨集成一下功能,让这个函数不仅可以打印还可以初始化雷区,设置形参的时候我们多创建一个char类型的变量n,当我们需要初始化真实雷区的时候n传参0,反之,显示雷区则传递*,若是直接打印当前雷区情况则输入其他字符都可以。

9X9的二维数组,两个for循环为其赋值字符0即可完成初始化

//头文件内的设置:

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

void DrawMineField(char mine[ROWS][COLS], int row, int col, char n)
{
	int i = 0;
	int j = 0;

	printf("________OwO________\n");



	for (j = 0; j < col+1; j++)//纵向打印坐标数字,方便知道坐标
	{
		printf("%d ", j);
	} 

	printf("\n");

	for (i = 1; i <=row; i++)
	{
		printf("%d ", i);   //横向打印坐标数字
		for (j = 1; j <= col; j++)
		{
			if (n == '0' || n == '*')//如果输入字符0或者*,则初始化,不输入就纯打印
			{
				mine[i][j] = n;//之后若是需要让其发挥初始化功能从这下手
			}
			printf("%c ", mine[i][j]);
		}

		printf("\n");

	}
	printf("===================\n");

}
DrawMineField(showmine, ROW, COL, '*');初始化显示雷区
DrawMineField(showmine, ROW, COL, 'k');打印雷区,打印哪一个雷区只需要换传入的参数即可
DrawMineField(mine, ROW, COL, '0');初始化并显示真实雷区

在这里我们可以发现,虽然它可以实现初始化的功能,但是会打印出来,更改其中的逻辑会使得这个函数变得过于冗长,不妨直接再编写一个初始化的函数方便我们初始化。当然想要更正实现这一功能也很简单,在这里就不做优化了。

单纯的初始化真实雷区,不打印
void IntMineField(char mine[ROWS][COLS], int row, int col)
{
	int i = 0;
	int j = 0;

	for (i = 0; i < row; i++)
	{
		for (j = 0; j < col; j++)
		{
			mine[i][j] = '0';
		}
	}
}

坐标数字的打印已经在代码段内用注释标出。

随机埋雷:

而为了达成随机条件,则需要生成随机数。随机数的生成我们可以借助rand函数来进行生成。


rand函数在生成随机数的时候,会随机生成一个种子值,然后rand函数会借由这个种子值生成随机数,但是每一次只产生一次种子值rand函数所产生的随机数不是完全随机的,所以我们在使用rand函数之前还需要借助srand函数来使得rand函数的种子值不断变化以达成真正的随机数生成。而一直不断变化的值,那就是系统时间了。

srand(unsigned seed)通过参数seed改变系统提供的种子值,从而可以使得每次调用rand函数生成的伪随机数序列不同,从而实现真正意义上的“随机”。通常可以利用系统时间来改变系统的种子值,即srand(time(NULL)),可以为rand函数提供不同的种子值,进而产生不同的随机数序列。


rand函数位于#include <stdlib.h>的头文件内。

为了使用time函数,也需要使用<time.h>的头文件。

我们先在main函数中先实现随机数

int main()
{
	srand((unsigned int)time(NULL));
	return 0;
}

然后编写一个函数,用于随机埋雷,代码如下:

void ReadyForMine(char  mine[ROWS][COLS], int row, int col)
{
	int x = 0;
	int y = 0;
	int i = 0;
	int count = 0;
	
	while (count < 10)
	{
		x = rand() % row+1;//控制随机数的范围
		y = rand() % col+1;

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

}

效果如下:

游戏主体逻辑

以下为逻辑概述:

首先是提醒玩家行动,分为:1.插旗排雷 2.排查地雷

进入插旗排雷行动后,每一次插旗进行一次判断,当成功插在地雷上则雷的总数-1,当雷的总数计数为0时,跳出循环胜利。

排查时,获取玩家输入的坐标,排查过的区域不能反复排查,将获得的坐标与真实雷区的坐标比对,等于字符1则游戏失败,等于字符0则继续游戏并且进行展开的判断

插旗效果实现

首先我们先创建一个用于检测是否胜利的全局变量地雷数量minenumber,当minenumber等于0的时候游戏胜利。

我们需要实时更新被排掉的雷,若是在函数内进行创建每一次函数在被调用的时候minenumber会被重新赋值一次,所以创建全局变量。

int minenumber = 10;//地雷数量

int  Flag(char  mine[ROWS][COLS], char  showmine[ROWS][COLS], int x, int y)
{
	if (showmine[x][y] == '*')
	{
		showmine[x][y] = '@';//插上旗帜的坐标位置用@替代
		DrawMineField(showmine, ROW, COL, 'k');

		if (mine[x][y] == '1')
		{
			minenumber--;
			printf("%d\n", minenumber);
			if (minenumber == 0)
			{
				return 2;
			}
			return 1;
		}
		return 1;
	}
	else
	{
		printf("坐标非法,请重新输入!");
	}

展开的实现

展开的实现需要先满足以下3种情况:


该坐标不是雷,且没有被排查过

该坐标周围没有雷
该坐标的附近一旦有雷,则不要进入展开函数,遍历附近格子数得到附近8格地雷数量


首先则是实现对输入坐标附近8格进行地雷查找

思路是将当前坐标附近8格真实雷区里的元素值相加再减去字符0的值,可以得到附近8格里有几个1了。简单来说,收集附近8格的值,有雷就+1。

代码如下:

//获得坐标处旁的地雷数量
int  countmine(char  board[ROWS][COLS], int x,int y)
{
	int count = board[x - 1][y - 1] + board[x - 1][y] + board[x - 1][y + 1] +
				board[ x ]  [y - 1] + board[x][y + 1] +
				board[x + 1][y - 1] + board[x + 1][y] + board[x + 1][y + 1] - 8 * '0';
	return count;
}

接下来则是展开,代码如下:

void Spread(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
{
	int offset_x = 0;
	int offset_y = 0;
	int count = 0;
	//坐标合法
	if (x >= 1 && x <= 9 && y >= 1 && y <= 9)
	{
		//遍历周围坐标
		for (offset_x = -1; offset_x <= 1; offset_x++)
		{
			for (offset_y = -1; offset_y <= 1; offset_y++)
			{
				//如果这个坐标不是雷
				if (mine[x + offset_x][y + offset_y] == '0')
				{
					//统计周围雷的个数
					count = countmine(mine, x + offset_x, y + offset_y);
					if (count == 0)
					{
						if (show[x + offset_x][y + offset_y] == '*')
						{
							show[x + offset_x][y + offset_y] = ' ';
							Spread(mine, show, x + offset_x, y + offset_y);
						}
					}
					else
					{
						show[x + offset_x][y + offset_y] = count + '0';
					}
				}
			}
		}
	}
}

展开的代码的逻辑是有点绕的,为了更好的方便我们去理解这一串套娃,我决定画图来分析。



效果如下:

插旗排雷

插旗排雷的代码逻辑并不困难,需要注意的是为了在函数里成功判断的地雷数量是否达成胜利条件,地雷数量的变量我在声明时使用了全局变量。

代码如下:

//插旗子:

int  Flag(char  mine[ROWS][COLS], char  showmine[ROWS][COLS], int x, int y)
{
	if (showmine[x][y] == '*')
	{
		showmine[x][y] = '@';//为插上棋子的地方放上一个@当旗子

		DrawMineField(showmine, ROW, COL, 'k');
        //插完旗子后显示雷区,让玩家知道自己的棋子插在了哪里

		if (mine[x][y] == '1')
		{
			minenumber--;//如果插下的棋子底下真的是雷,地雷数量减一
			printf("%d\n", minenumber);//调试时用于检测函数是否生效
			if (minenumber == 0)//每一次插完旗子检测一次是否胜利,胜利返回2
			{
				return 2;
			}
			return 1;//还有地雷就返回1,继续
		}
		return 1;
	}
	else
	{
		printf("坐标非法,请重新输入!");
	}
}

 至此,我们大部分所需要的函数都写好了,把它们放到game函数里整理逻辑即可。

代码如下:

//游戏主体逻辑
void game(char  mine[ROWS][COLS], char  showmine[ROWS][COLS], int row, int col, int rows, int cols)
{
	int x = 0;
	int y = 0;
	int minenumber = 0;
	int ret = 1;
	int ret2 = 1;
	int c = 0;

	IntMineField(mine, ROWS, COLS);//初始化雷区
	DrawMineField(showmine, ROW, COL, '*');//初始化雷区后打印外面的隐藏雷区

	ReadyForMine(mine, ROW, COL);//埋雷
	//DrawMineField(mine, ROW, COL, 'k');//此处解除注释即可在初始化的时候看见雷的部署情况

	do
	{
		printf("请选择接下来的动作:\n1.探查地雷 2.为确认的地雷插下旗帜\n");
		scanf("%d", &c);
		if (2 == c)
		{
			printf("请输入需要 插下旗帜 的坐标->.");
			scanf("%d %d", &x, &y);
			ret2 = Flag(mine, showmine, x, y);
			if (ret2 == 2)//当Flag函数返回2的时候跳出循环,进行胜利的判断
				break;

		}
		else if (1 == c)
		{
			printf("请输入需要 排查 的坐标->.");
			scanf("%d %d", &x, &y);

			ret = Check(mine, x, y);//判断玩家是否踩到了雷

			if (showmine[x][y] != '*')
			{
				printf("该坐标被排查过了,不能重复排查\n");
			}

			else if (ret == 1)
			{
				Spread(mine, showmine, x, y);//展开函数
			}
			DrawMineField(showmine, ROW, COL, 'k');//打印排查完毕后的雷区状况
		}
		else
		{
			printf("输入错误,请重新输入!\n");

		}
	

	} while (ret);

	if (ret == 0)
	{
		printf("寄!你被炸死了,下辈子小心!XD\n");
		DrawMineField(mine, ROW, COL, 'k');//玩家失败,展示真实雷区情况
	}
	if (ret2 == 2)
	{
		printf("NICE!你排完了所有的地雷,高手!");
	}

	
	//DrawMineField(showmine, ROW, COL, 'k');
}

当然main函数里也要加上一些其他的逻辑:

int main()
{
	srand((unsigned int)time(NULL));
	char mine[ROWS][COLS] = { 0 };
	char showmine[ROWS][COLS] = { 0 };
	int c = 1;

	menu();

	do
	{
		scanf("%d", &c);

		switch (c)

		{

		case 1:

			printf("游戏开始!祝你好运!\n");
			game(mine, showmine, ROW, COL, ROWS, COLS);
			menu();
			IntMineField(mine, ROWS, COLS);
			break;

		case 0:

			printf("退出游戏!\n");
			break;

		default:
			printf("输入错误,请重新选择输入!\n");

		}

	} while (c);

	return 0;
}

完整代码地址:C语言刷题仓库: 装刷过的题目代码 - Gitee.com

 感谢阅读!希望能对你有一些帮助!

猜你喜欢

转载自blog.csdn.net/m0_53607711/article/details/124691520