手把手教你用C语言写扫雷(包含递归,数组,随机数等知识点)

目录

 

 

我已经把我的代码上传到了我的Gitee的仓库中了,需要的可自取

仓库 - 流云luoxiao (liuyun-luo) - Gitee.com

准备工作

第一步:打印菜单

第二步:定义数组和初始化

第三步:布雷

第三步:打印show和mine数组

第四步:标记和取消标记

标记

取消标记

第五步:排雷

1.判断输入的坐标是否合法

2.判断坐标是否被排除过和该坐标是否是雷

3.递归排雷

递归结束的条件

递归函数参数的设计

递归函数的递归方式(我用的是十字递归(即递归该坐标的上下左右))

第六步:用switch语句将标记,取消标记和排雷放在一起

第七步:判断是否胜利

最后一步:将所有函数放在game函数中(就可以玩了)

玩一玩

 

我已经把我的代码上传到了我的Gitee的仓库中了,需要的可自取

仓库 - 流云luoxiao (liuyun-luo) - Gitee.com

准备工作

首先为了便于代码的维护与管理,我们将扫雷的所有代码分成三个部分

①game.h:用来存放扫雷函数声明、库函数的头文件、以及#define定义的宏常量等

②game.c:用来存放扫雷函数的实现的代码

③test.c:用来存放main函数,以及对扫雷函数的测试

ed657bdb4a124f5fa6bce9690807903e.png

包含在game.h中包含头文件stdio.h,并在game.c和test.c中包函我们自己的头文件game.h(注意包含自己的头文件的时候头文件不用<>,而用“”,即#include“game.h”)

第一步:打印菜单

①分装一个函数menu,用以下的代码(加这么多*是为了让菜单更好看一点捏)

②实现对菜单选项的响应,定义一个变量,接收从键盘传来的值,因为菜单至少得打印一次所以用do while循环,因为选项可能出现多种情况,所以使用switch语句

③根据菜单的情况,在switch语句中写对应的代码

手把手教你用C语言写扫雷(包含递归,数组,随机数等知识点)_第1张图片

 

手把手教你用C语言写扫雷(包含递归,数组,随机数等知识点)_第2张图片

 

第二步:定义数组和初始化

①定义两个二维数组,一个mine,一个show(为什么定义两个数组?因为扫雷的时候,总得给别人看一个要扫雷的区域吧,只有一个数组+printf会很麻烦)mine用来存放雷,show用来存储标记和一个坐标周围雷的个数。

②数组的行列,扫雷的时候,扫一个位置,该位置就会显示它周围一圏雷的个数,既然是周围的一圏,那么处理角/边缘上的位置扫的时候就要分类讨论,这样太麻烦,不如直接再给数组大一圏(例9x9的扫雷区域,就建11x11的数组[注意10X10的数组不能包围9x9的区域一圏])这样只要控制布雷的区域为9x9就可以。

扫雷区域是9x9

mine[11][11]

show[9][9](show也可以建11x11的行列,根据个人习惯)

③数组的行列都要用#define来定义,并把定义放在game.h中,这样以后的时候想要改变扫雷的区域的大小,就只需要改变#define后面的行,列的值就可以了

手把手教你用C语言写扫雷(包含递归,数组,随机数等知识点)_第3张图片

④mine数组全部出初始化为0/字符0/其他(可以根据自己的喜好。)

代码

void MineInit(char mine[NLIL][NCOL])
{
	int i = 0, j = 0;
	for (i = 0; i < NLIL; i++)
		for (j = 0; j < NCOL; j++)
			mine[i][j] = 0;
}

show数组全部初始化为*(或者自己的喜欢任意字符)

//初始化无雷数组
void ShowInit(char show[LIL][COL])
{
	int i = 0, j = 0;
	for (i = 0; i < LIL; i++)
		for (j = 0; j < COL; j++)
			show[i][j] = '*';
}

第三步:布雷

利用srand函数,rand函数,time函数制造随机数并限制随机数的范围,把随机数赋给X(二维数组的行)y(二维数组的列)

1.将srand函数放在main函数的最开头(一般这样)设置随机数的种子(代码为srand((unsigned int)time(NULL),利用时间戳随时间变化而变化的特点,让随机数的种子不断变化,以达到真随机的效果。

srand的()中的参数为随机数的种子,参数类型为unsigned int,如果种子固定为一个值(不论是任何一个unsigned int的值)那么rand生成的随机数只会生成一份,之后无论再运行程序多少次,rand生成的随机数还是和第一次生成的随机数相同.

2.把rand生成的随机数赋给X(二维数组的行)y(二维数组的列)

//布雷
void Mine(char mine[NLIL][NCOL])
{ 
	int count = 0;
	while (count < MINE)
	{
		int x = rand() % (NLIL - 2) + 1;
		int y = rand() % (NCOL - 2) + 1;
		if(mine[x][y]==0)//无雷的地方才布雷
		{
			mine[x][y] = 1;
		    count++; 
		}
	}
}

第三步:打印show和mine数组

mine数组的打印用于测试代码/玩家失败和胜利时让玩家知道雷在哪。

为了便于玩家的游玩,我们可以给show数组加上行号和列号。

show数组的打印

void ShowPrint(char show[LIL][COL])
{
	printf("—————————扫雷——————————————\n");
	int z = 0;
	for (z = 0; z <= COL; z++)//打印列号
		printf("%d|", z);
	printf("\n");
	int i = 0, j = 0;
	for (i = 0; i < LIL; i++)
	{
		for (j = 0; j < COL; j++)
		{
			if (j == 0)
			{
				printf("%d|", i + 1);//打印行号
				printf("%c ", show[i][j]);
			}
			else
			{
				printf("%c ", show[i][j]);
			}
		}
		printf("\n");
	}
	printf("—————————扫雷——————————————\n");
}

效果

手把手教你用C语言写扫雷(包含递归,数组,随机数等知识点)_第4张图片

mine数组的打印

//打印有雷数组
void MinePrint(char mine[NLIL][NCOL])
{
	int i = 0, j = 0;
	for (i = 1; i < NLIL - 1; i++)
	{
		for (j = 1; j < NCOL - 1; j++)
			printf("%d ", mine[i][j]);
		printf("\n");
	}

第四步:标记和取消标记

标记

1.判断输入的坐标是否合法(可以在排雷的函数中限制,也可以在排雷函数外,game函数中限制,我这里是在game函数中限制的)

手把手教你用C语言写扫雷(包含递归,数组,随机数等知识点)_第5张图片

2.判断坐标是否被排除过/被标记过

void mark(char show[LIL][COL],int lil,int col)
{
	int x = lil, y = col;
	while (show[x - 1][y - 1] == 'X')
	{
		printf("该坐标已经被标记过了,请换一个坐标吧:");
		scanf("%d %d", &x, &y);
	}
	while (show[x - 1][y - 1] != 'X'&&show[x - 1][y - 1] != '*')
	{
		printf("该坐标已经被排除过了,请换一个坐标吧:");
		scanf("%d %d", &x, &y);
	}
	show[x - 1][y - 1] = 'X';
}

取消标记

判断输入的坐标是否合法(可以在排雷的函数中限制,也可以在排雷函数外,game函数中限制,我这里是在game函数中限制的)

手把手教你用C语言写扫雷(包含递归,数组,随机数等知识点)_第6张图片

 

void unmark(char show[LIL][COL],int lil,int col)
{
	int x = lil, y = col;
	while (show[x - 1][y - 1] != 'X')
	{
		printf("该坐标没有被标记过,请换一个坐标吧:");
		scanf("%d %d", &x, &y);
	}
	show[x - 1][y - 1] = '*';
}

第五步:排雷

1.判断输入的坐标是否合法

(可以在排雷的函数中限制,也可以在排雷函数外,game函数中限制,我这里是在game函数中限制的)

手把手教你用C语言写扫雷(包含递归,数组,随机数等知识点)_第7张图片

2.判断坐标是否被排除过和该坐标是否是雷

如果踩到雷就返回1,被函数外的game函数的flag接受,结束游戏

int MineClearance(char mine[NLIL][NCOL], char show[LIL][COL],int lil,int col)
{
	int x = lil, y = col;
	while (show[x - 1][y - 1] != '*'&&show[x - 1][y - 1] != 'X')
	{
		printf("该坐标已经被排除过了,请换一个坐标吧:");
		scanf("%d %d", &x, &y);
	}
	if (mine[x][y] == 1)
	{
		printf("你踩到雷了,你失败了!\n");
		MinePrint(mine);
		return 1;
	}
	_MineClearance(mine, show, x, y,0);
	return 0;
}

3.递归排雷

递归结束的条件

1.递归到的坐标周围有雷

2.递归到的坐标已经被排除过了

递归函数参数的设计

void _MineClearance(char mine[NLIL][NCOL], char show[LIL][COL], int lil, int col,int flag)
{

我是这样设计的lil是行,col是列,flag是根据递归到的坐标是否被排除过,而是否递归

如果被排除过就要结束递归,flag的值为1;果没被排除过,就不需要递归,flag的值为0;

 

递归函数的递归方式(我用的是十字递归(即递归该坐标的上下左右))

//排雷的递归函数
void _MineClearance(char mine[NLIL][NCOL], char show[LIL][COL], int lil, int col,int flag)
{
	if (flag)//该坐标如果已经被排除过,就结束递归
		return;
	int flag1 = 0;
	int x = lil, y = col;
	int count = 0;
	int i = 0, j = 0;
	if (x - 1 < LIL && y - 1 < COL && x - 1 >= 0 && y - 1 >= 0)//防止坐标越界
	{
		for (i = x - 1; i <= x + 1; i++)//计算该坐标周围有多少个雷
		{
			for (j = y - 1; j <= y + 1; j++)
			{
				if (mine[i][j] == 1)
				{
					count++;
					flag1 = 1;//该坐标如果周围有雷就标记一下
				}
			}
		}
		if(flag1==1)
		    show[x - 1][y - 1] = count + '0';
		else
			show[x - 1][y - 1] = ' ';//如果没雷就放空格
	}
	if (flag1)//该坐标如果周围有雷就结束递归
	{
		return;
	}
	if (x - 1 < LIL && y - 1 < COL&&x-1>=0&&y-1>=0)  //防止坐标越界
	{
		if (('0'< show[x - 1-1][y-1] && show[x - 1-1][y-1] < '9') || show[x - 1-1][y-1] == ' ')//坐标已经被排除过了也结束递归
			_MineClearance(mine, show, x - 1, y,1);  //结束递归
		else
	     _MineClearance(mine, show, x - 1, y,0);  //递归该坐标的上
		if (('0' < show[x-1][y - 1-1] && show[x-1][y - 1-1] < '9') || show[x-1][y - 1-1] == ' ')
			_MineClearance(mine, show, x, y - 1,1);
		else
	     _MineClearance(mine, show, x, y - 1,0);  //递归该坐标的左
		if (('0' < show[x-1][y + 1-1] && show[x-1][y + 1-1] < '9') || show[x-1][y + 1-1] == ' ')
			_MineClearance(mine, show, x, y + 1,1);
		else
	     _MineClearance(mine, show, x, y+1,0);   //递归该坐标的右
		if (('0' < show[x + 1-1][y-1] && show[x + 1-1][y-1] < '9') || show[x + 1-1][y-1] == ' ')
			_MineClearance(mine, show, x + 1, y,1);
		else
	     _MineClearance(mine, show, x+1, y,0);  //递归该坐标的下
	}
	else  //坐标越界就结束递归
	{
		return;
	}
}

第六步:用switch语句将标记,取消标记和排雷放在一起

	while (1)
	{
		int flag = 0;
		int a = 0;
		ShowPrint(show);
		do
		{
			printf("1:标记|2:取消标记|3:排雷\n");
			scanf("%d", &a);
			switch (a)
			{
			case 1:
				printf("请输入你标记的坐标:");
				scanf("%d %d", &lil, &col);
				while (lil > LIL || lil<1 || col>COL || col < 1)
				{
					printf("坐标输入错误,请重新输入:");
					scanf("%d %d", &lil, &col);
				}
				mark(show, lil, col);
				ShowPrint(show);
				break;
			case 2:
				printf("请输入取消标记的坐标:");
				scanf("%d %d", &lil, &col);
				while (lil > LIL || lil<1 || col>COL || col < 1)
				{
					printf("坐标输入错误,请重新输入:");
					scanf("%d %d", &lil, &col);
				}
				unmark(show, lil, col);
				ShowPrint(show);
				break;
			case 3:
				printf("请输入您要排的坐标:");
				scanf("%d %d", &lil, &col);
				while (lil > LIL || lil<1 || col>COL || col < 1)
				{
					printf("坐标输入错误,请重新输入:");
					scanf("%d %d", &lil, &col);
				}
				flag = MineClearance(mine, show, lil, col);
				if (flag)
				{
					return;
				}
				ShowPrint(show);
				break;
			default:
				printf("选择错误\n");
				break;
			}
			if (iswin(show))
			{
				printf("\n恭喜你,你已经排除了所有雷!\n");
				MinePrint(mine);
				return;
			}
		} while (a);
	}
}

第七步:判断是否胜利

利用剩余的标记和*的个数之和是否等于雷的个数判断是否胜利,如果等于就胜利。

int iswin(char show[LIL][COL])
{
	int i = 0, j = 0;
	int count = 0;
	for (i = 0; i < LIL; i++)
	{
		for (j = 0; j < COL; j++)
		{
			if (show[i][j] != '*' &&show[i][j]!='X')
			    count++;
		}
	}
	if (count==LIL*COL-MINE)
	{
		return 1;
	}
	else
	{
		return 0;
	}
}

最后一步:将所有函数放在game函数中(就可以玩了)

玩一玩

手把手教你用C语言写扫雷(包含递归,数组,随机数等知识点)_第8张图片

手把手教你用C语言写扫雷(包含递归,数组,随机数等知识点)_第9张图片

 

以上就是C语言实现扫雷的全部内容了,如果对您有帮助的话,希望您给我点点赞!

 

 

你可能感兴趣的:(c语言)