扫雷游戏 高度还原

目录

基本思路: 

开始部分:

开始游玩:

棋盘所需数组的定义:

初始化棋盘:

制作棋盘:

布置雷:

排查雷:

排雷函数:

查看周围是否有雷函数:

能向外扩展函数:

插旗和撤旗函数:

插旗函数:

撤旗函数: 

总览:

game.h:

game.c:


 

基本思路: 

首先 如果按照上次我们写三子棋设置game.h game.c work.c 这一次扫雷游戏的编写因为 要用到全局变量 而 为了保证一局游戏结束后 我们再次选择玩游戏这些变量能够被初始化 所以这次我们就只有 game.h 和 game.c了


来看我们的开始操作部分:

开始部分:

我们首先要给玩家在界面上显示一个 选择界面 所以要编写一个menu函数

menu()
{
	printf("**********************n");
	printf("**********1.play******n");
	printf("**********0.exit******n");
	printf("*********请 选 择*****n");
	printf("**********************n");
}

然后开始:

int main()
{
	srand((unsigned int)time(NULL));//因为后面我们布置雷采用的是一种随机的方式 所以这里要先用
	int input;                      //一下srand函数 因为我们的主函数是一定会执行的 所以我们把
	do                              //这个放在了主函数里面
	{
		memset(arr, 0, sizeof arr);
		int ret = 0;
		int A;
		int B = 1;//这一部分的变量 都是我们后面要用的 放在这里是为了我们完成一次游戏后还能把这些
		menu();   //变量变成开始的样子
		printf("---------------------------n");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			play(); 
			break;
		case 0:
			printf("您已经退出游戏n");
			break;
		default:
			printf(" 对不起你选择的选项不存在 请重新输入!n");
		}
	} while (input);
	return 0;
}

因为 1 为真 这里我们选择了玩游戏(1)时 玩完游戏 play()返回来执行break 这时break 跳出的是 switch选择语句 这样程序就会来到do-while的判断部分 我们选的是 1 就会循环 让我们再次下选择 选择的 0 的话 到了do-while的判断部分就会跳出来 结束游戏啦

开始游玩:

我们来理一下思路:

因为 我们后面编写的 插旗(表示你判断这个位置是雷)函数 和 撤旗函数 涉及到了字符的变化

如果把所有的棋盘信息全部塞到一个二维数组里面 这样后面编写起来肯定是更麻烦的

所以 我们就需要增加一个来分担我们的压力:

还有一个要说的点因为我们扫雷嘛 肯定会去排查你选的坐标周围一圈是否有雷 如果我们只是定义的9×9的数组的话 那我们在排查我们的边界时 寻找周围有没有雷这样不就越界了嘛


所以我们就要在行和列上各多加两行 这个时候就需要定义11×11的数组 我们这里用到的ROWS COWS都是11

#define ROW 9
#define COW 9//因为我们后面还有用到行和列的数字所以这个也要define一下

#define ROWS ROW+2//改变数组大小的时候就只需要改变一下ROW COW就可以了
#define COWS COW+2 

棋盘所需数组的定义:

    //初始化数组
	char mine[ROWS][COWS] = { 0 };//这个数组用于存放我们布置雷的信息
	char show[ROWS][COWS] = { 0 };//这个数组用于给我们玩家展示玩家选择一些操作后棋盘的变化

之所以采用字符型数组 因为我们后面会用到多种符号 如果用数字来区别的话 会不方便要不好区别出来所以我们就用字符型数组 遇到要用数字的时候也可以用字符数字来表示 只不过要了解字符数字 和 整型数字之间怎样转换 后面我们会讲到的 嘻嘻

数组定义好了 我们肯定就要开始制作棋盘了 在这里首先要说一下这个:

  1. mine数组里面 我们用字符 '0' 来表示 不是雷 字符 '1'表示 是雷(当然也是可以用其他符号的我们这里说一下这种怎么做)
  2. show数组里面 我们首先展示给玩家是一个未知的界面 我们用字符 '*'来实现

这就是我们一开始要展示给玩家的界面

初始化棋盘:

在我们初始化棋盘前 我们还是要在我们创建的game.h文件里面声明一下这个函数

void make_borad(char arr[ROWS][COWS], int row, int cow, char ret);//初始化棋盘

void make_borad(char arr[ROWS][COWS], int row, int cow, char ret)//初始化棋盘
{                      //我们传进去的是一个11×11的数组 肯定也是用11×11的数组去接收
	int i;
	int j;
	for (i = 0; i < row; i++)
	{
		for (j = 0; j < cow; j++)
		{
			arr[i][j] = ret;
		}
	}
}


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

这里之所以要用一个ret这个形参 是因为我们本身这两个数组初始化后 里面的内容就是不同的 show函数里面是 '*' mine函数里面是 '0' 所以这个时候我们就只需用不同形参 ret 传进去 这样就可以实现一个函数实现两个数组的初始化  


制作棋盘:

我们还是要先在我们创建的game.h里面声明这个函数

void dis_playborad(char mine[ROWS][COWS], int row, int cow);//制作并打印棋盘

然后在我们的game.c里面引用

dis_playborad(show, ROW, COW);//ROW和COW等于9

大家注意到了 那个棋盘的上面还有横坐标和竖坐标这样写是为了方便玩家找坐标 

void dis_playborad(char show[ROWS][COWS], int row, int cow)//制作并打印棋盘
{
	int i;
	int j;
	printf("-------------------------n");
	for (i = 0; i <= row; i++)
	{
		printf("%d ", i);//打印横坐标
	}printf("n");      //打印完记得换行
	for (i = 1; i <= row; i++)
	{
		printf("%d ", i);每一行开始打印这一行的竖坐标
		for (j = 1; j <= row; j++)
		{
			printf("%c ", show[i][j]);//打印%c后面要加一个 空格 不然打印出来会很紧凑
		}
		printf("n");//一行打印完记得换行
	}
	printf("-------------------------n");
}

这样我们给玩家展示的界面就打印完了 然后就是给我们的 mine数组 里面布置雷


布置雷:

首先在我们的game.h里面声明

void put_mine(char mine[ROWS][COWS], int row, int cow);//布置雷

然后引用它:

put_mine(mine, ROW, COW);//布置雷

大家来看:

void put_mine(char mine[ROWS][COWS], int row, int cow)//布置雷
{
	ret = 0;
	for (int i = 0; i < MINE; )     //这里的MINE是雷的意思 我们在game.h里面给他定义的是10
	{                                //#define	MINE 10
		int x = rand() % row + 1;
		int y = rand() % cow + 1;
		if (mine[x][y] == '0')
		{
			mine[x][y] = '1';
			i++;
		}
	}
}

我们设置一个循环 每一次创建的随机值 只要布置成功 i 就加一 直到10个雷全部布置完

这里随机数的设定我给大家说一下是怎么回事:


rand函数 是用来生成一个随机数  头文件是<stdio.h> 返回类型是整型

返回 0~Rand_Max(32767)中的一个数

srand函数相当于是给rand函数生成一个起始点 就是让rang函数不再是从0开始到Rand_max 如果你给rand函数设置的起始点是固定的那么好几次生成的随机数

    srand(1);
	int x = rand();
	for (int i = 0; i < 5; i++)//这里五次输出 都是 41 
	{                          //如果说你在使用rand函数之前没有使用srand函数 那么结果和srand(1)
		printf("%dn", x);     //是一样的
	}

所以我们要给srand函数里传入 一个变化的值 这个时候就要用到 time函数


time函数:

time函数这样定义的 这个函数的返回的类型是 time_t (就好比是int char)这个类型是一个无符号整型  但是不同平台这个类型的定义是不同的 我用的是 vs 而在vs里面是一个有符号的长整型 

所以这里用的时候记得 给time函数进行强制转换 (如果你用的也是vs的话) 

因为srand函数要求的是 我们给它传入的数要是一个无符号的数

其中time_t它返回从1970年1月1日零时零分零秒到目前为止所经过的时间

最后有一点 在我们使用这个函数之前 我们还要引用


srand函数:

这个函数他是这样的 void srand(unsigned seed)

他要求传入一个无符号的数 

因为我们要让这个随机数是变化的 而rand函数 srand函数 time函数通常结合在一起使用

最终拿出来就是这样的 srand((unsign int)time(NULL) )  NULL的意思就是表示空的意思

你可以写 0 


因为我们的主函数是一定会执行的所以我们就把 srand函数放到主函数里面去 

对了 在使用它们之前出了 引头文件<time.h>还有一个<stdlib.h>哦! 


这里的讲解我就照搬我上一篇文章说的了(转载 昶102的三子棋篇)


这样我们的雷就布置好了 嘻嘻 然后就要开始我们的重头戏 排查雷 了

排查雷:

一样的 我们还是要在我们创建的game.h里面进行声明我们的排雷函数

void Insit_mine(char show[ROWS][COWS], char mine[ROWS][COWS]); //排查雷

然后引用它:

Insit_mine(show, mine);

到这里我们的 play函数 就写完了 给大家看看是什么样子的:>

void play()
{

    //初始化数组
	char mine[ROWS][COWS] = { 0 };
	char show[ROWS][COWS] = { 0 };
	
	//初始化棋盘
	make_borad(show, ROWS, COWS, '*');
	make_borad(mine, ROWS, COWS, '0');

	put_mine(mine, ROW, COW);//布置雷
	printf("您已经开始游戏!n");

	//开始界面
	dis_playborad(show, ROW, COW);

	dis_playborad(mine, ROW, COW);//这里是之前调试写得 平常大家写代码时 也建议大家写完一些步骤
	Insit_mine(show, mine);       //就可以调试一下 免得出错 尤其像这种要写很多的项目
}

OK 让我们开始编写排雷函数吧:

因为我们在排雷有这样的几个选择 1.排雷 2.插旗 3.撤旗  所以我们还要再写一个目录

menu2()
{
	printf("*****************n");
	printf("*****1.排雷******n");
	printf("*****2.插棋******n");
	printf("*****3.撤销插旗**n");
	printf("*****************n");
}

排雷函数:

void Insit_mine(char show[ROWS][COWS], char mine[ROWS][COWS])//排查雷
{
	while (find_num(show) < ROW * COW - MINE)//find_num函数是用来查看一个坐标周围是否有雷的
	{
		int x = 0;
		int y = 0;
		int input;
		menu2();
		printf("请选择你想要进行的操作:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
		{
			printf("请选择你要排查的位置:>");
			scanf("%d %d", &x, &y);
			if ((1 <= x && x <= 9) && (1 <= y && y <= 9))//判断棋子的有效性
			{
				if (show[x][y] == '$')       //查看输入的坐标是否已经有旗
				{
					printf("该坐标现在已经插旗了 不能排查!n");
					printf("你可以选择撤旗 来继续排查该坐标n");
					break;                        //跳出switch选择语句就会再次循环 询问你想 
				}                                //要的操作

				if (mine[x][y] == '1')       //查看输入的坐标是否是雷
				{
					printf("很遗憾你踩雷了 游戏结束!n");
					return;    //像游戏结束这种就直接return 不要执行后面的程序了
				}
				else
				{
					if (show[x][y] == '*')//坐标只有是'*'才能排查
					{
						if (!find_mine(mine, x, y))
						{
							arr[A] = x;
							A += 2;
							arr[B] = y;
							B += 2;       //第一次选的坐标也要记录 因为后面循环会循环到这个坐
							show[x][y] = find_mine(mine, x, y) + '0';      //标
							prove_show(show, mine, ROW, COW, x, y);
							dis_playborad(show, ROW, COW);
						}
						else
						{
							show[x][y] = find_mine(mine, x, y) + '0';
							dis_playborad(show, ROW, COW);
						}
					}
					else
					{
						printf("对不起 当前坐标你已经输入过了或已经排查过n");
					}
				}
			}
			else
			{
				printf("对不起你输入的坐标无效 请重新输入n");
			}
			break;   //每一次排查完 到这里跳出来 进行下一次选择
		}
		case 2://插旗
		{
			FLAG(show, mine);
			if (ret == MINE)
			{
				printf("恭喜你 完成游戏!n");
				return;
			}
			break;
		}

		case 3://撤旗
		{
			take_flag(show);
			break;
		}
		default:
		{
			printf("您选择的选项不存在请重选!n");
			break;
		}
		}
	}

		printf("恭喜你 完成游戏!n");
		return;

}

大家看完了 我来每一部分分析一下是为什么这样写得:

首先case 1是我们的排查雷部分   我们的思路是如果我们排查的那个坐标周围没有雷那么我们就要像扫雷游戏那样往周边扩开直到那个排查的位置有雷 把它周围雷的数目放进去这个是我们上面的prove_show函数

而如果 我们一开始填写的坐标有雷那就先把那个坐标周围对应的雷的数目放进去 再break出来进行下一次要选择的操作 就是这一部分:

            if (show[x][y] == '*')//坐标只有是'*'才能排查
			{
    			if (!find_mine(mine, x, y))
				{
					arr[A] = x;
					A += 2;
					arr[B] = y;
					B += 2;       //第一次选的坐标也要记录 因为后面循环会循环到这个坐标
					show[x][y] = find_mine(mine, x, y) + '0';      
					prove_show(show, mine, ROW, COW, x, y); 					 
                    dis_playborad(show, ROW, COW);  //因为当你这个函数执行完回来时我们肯定还是
				}                                   //要给我们的玩家展示一下效果 所以打印棋盘
	   			else
				{
					show[x][y] = find_mine(mine, x, y) + '0';
					dis_playborad(show, ROW, COW);
				}
            }
            else
			{
				printf("对不起 当前坐标你已经输入过了或该坐标已经被排查n");
			}

注意这里就用到了我们说的全局变量 我说一下这个是为什么:

首先这个数组是用来装坐标 A表示记录横坐标 B表示记录竖坐标 一开始定义的 A=0,B=1意思就是  A记录的横坐标放在偶数位上面   B记录的竖坐标放在奇数位上面   每一次记录完就+=2 这样就可以进入下一组的记录

因为文件定义的是.c文件 不能用vector 如果大家是用c++来写的话这里记录坐标就可以这样写  首先  typedef pair<int, int>PII; 一个这样的类型然后 vector<PII>tmp; 这样就可以了  pair能够存两个变量  我们结合vector后 就可以存下多个 两个 变量(两个为一组)刚学c++我只知道可以这样  具体是为什么 其中的细节 就麻烦大家去看那些大牛们的讲解了 嘻嘻

为什么要去记录这种坐标内? 因为啊 首先我们要记录的是我们排查过并且这个位置周围没有雷 也就是这个位置应该放 '0' 的坐标  所以我们设置的是 不排查 那种 已经排查过或者已经插旗 的坐标 所以我们要把这些坐标记录下来 查过旗的坐标可以用 if语句去排除 但是排查过的坐标是一定要去记录的   所以我们创建了一个很大的数组 来保证我们存数据不会出粗

上面代码段说的 第一次你点击的坐标也要记录是为什么呢?  因为啊 我是把记录坐标放在了

if (!find_mine(mine, x, y))的里面  意思就是当我点击的这个位置周围没有雷   才记录   而我点击了它不就代表这个位置已经被排查过了嘛 它又进入了if语句 没有雷  为什么我不去记录内

如果我没有去记录它  因为prove_show去排查坐标的方法就是去循环周围的坐标  我们之前设置的什么   我们之前设置的是这个位置没有排查过 并且这个位置没有雷就去执行prove_show   我们一开始点击的坐标  不就是没有雷嘛 才进入的 prove_show函数 里面而我们不去记录一开始点击的那个坐标话  不久又回来了   又从我们一开始点击的那个位置开始  执行prove_show函数   虽然我设置的prove_show函数也是周围没有雷就记录  但是  是1.我们一开始点击就记录坐标  还是2.我们点击后执行了一次prove_show函数进行第二次prove_show时才记录坐标  很明显第一种方法更节省时间也不容易出错  不知道大家听懂我说的意思了没有  没有的话 只用在下面评论  我会好好看的 


这里又是另外一部分 是上面部分的前面那点:

  printf("请选择你要排查的位置:>");
  scanf("%d %d", &x, &y);
  if ((1 <= x && x <= 9) && (1 <= y && y <= 9))//查看输入的坐标的有效性
	{
		if (show[x][y] == '$')       //查看输入的坐标是否已经有旗
		{
			printf("该坐标现在已经插旗了 不能排查!n");
			printf("你可以选择撤旗 来继续排查该坐标n");
			break;
		}
		if (mine[x][y] == '1')       //查看输入的坐标是否是雷
		{
			printf("很遗憾你踩雷了 游戏结束!n");
			return;
		}
	else
	{
    这里就是上面讲过的那一部分 

    }

我们设置插旗使用的 '$' 肯定插旗的地方是不能排查的 所以要排除掉 排除的时候用的是break方便我们进行第二次要选择的操作  但如果直接游戏结束那种就直接return就可以了  毕竟游戏结束了嘛 后面就不执行了呗

查看周围是否有雷函数:

在写函数之前 我们首先要了解 字符数字 和 整型数字 之间的转换 首先来看一下ASCLL码表

 因为字符'0'的的ASCLL码是48  字符'1'的ASCLL码是49 我们设置的是字符 '1' 是雷 所以我们只需要把周围八个字符加起来减去八个字符'0'就是我们这个坐标周围雷的数目了 但这个返回的是整型数字

int find_mine(char mine[ROWS][COWS], int x, int y)//排查一个坐标周围是否有雷
{
	return mine[x - 1][y - 1] +
		mine[x - 1][y] +
		mine[x - 1][y + 1] +
		mine[x][y - 1] +
		mine[x][y + 1] +
		mine[x + 1][y + 1] +
		mine[x + 1][y] +
		mine[x + 1][y - 1] - 8 * '0';
}

所以在上面这个函数返回来后还要加上一个字符'0'才能转化为字符数字然后才能放进我们创建的show数组里面


while (find_num(show) < ROW * COW - MINE)
{
}

大家看我上面写的while循环的  我说一下这个find_num函数是什么

int find_num(char show[ROWS][COWS])//查看棋盘上还有多少没有访问的位置
{
	int count = 0;
	for (int i = 1; i <= ROW; i++)
	{
		for (int j = 1; j <= COW; j++)
		{
			if (show[i][j] != '*' && show[i][j] != '$')
				count++;
		}
	}
	return count;
}

这里面查看有多少地方没有访问 意思就是数这个show数组里面字符数字有多少  

我们设置的是9×9的棋盘  也就是有81个格子  布置了10个雷  也就是剩下的71个应该是数字 所以当我们去排查棋盘上面的数字等于71时 就说明这个棋盘上面剩下的10个格子一定是雷这样我们不久赢了然后循环就结束嘛

我们每次的排查完跳出switch选择语句都会来到 while的判断部分 这样也就保证我们每一次操作后都会来判断是否游戏成功

上面我们设置插旗的地方也不计数 是因为如果我们的旗插错了 插到了本来应该是数字的格子上那我们总的统计的数目就会比71小就不会赢得胜利了  如果计数了 那我插错的地方不是雷你还判断为胜利不就出错了嘛


只要这个循环能够跳出那我们棋盘上的数字是一定等于71的 就直接在循环外面写上 游戏胜利  还有另一种获得胜利的方法我们放在后面来讲


能向外扩展函数:

因为这个函数是为我们排雷函数设计的就不用去game.h里面声明了

给大家看一下循环一周 这个for循环怎么写

for (int i = -1; i <= 1; i++)
	{
		for (int j = -1; j <= 1; j++)
		{
         }
    }

然后拿一个坐标去加上这些偏移量 就可以访问到周围的所有坐标了 


在写这个函数我们要明确有哪些地方是不用去扩展的 哪些地方应该排除

  1. 中间的位置
  2. 插旗的位置
  3. 是雷的位置

这些都是我们应该排除的 我说一下是为什么

1.中间的位置是因为我们本身这个排查中间位置的周围的坐标是否有雷  就是因为中间的这个位置没有雷才去排查的  中间这个位置已经被认定为是没有雷的了  为什么还要去排查它呢

2.我往一个地方插了旗 如果去排查这个地方的周围恰好有雷  那么我数字就会填进去  我之前的'$'符号不就改变了嘛

3.有雷的位置如果排查不就违背了之前我们说的棋盘只有71个格子是数字了嘛  排查雷不就多了嘛

void prove_show(char show[ROWS][COWS], char mine[ROWS][COWS], int row, int cow, int x, int y)//往外面进行扩展
{
	for (int i = -1; i <= 1; i++)
	{
		for (int j = -1; j <= 1; j++)
		{
			if (!i && !j || show[x + i][y + j] == '$')//排除雷也可以写在这里
			{
				continue;//中间的位置 插旗的位置 不再判断
			}
			else
			{
				if (x + i >= 1 && x + i <= 9 && y + j >= 1 && y + j <= 9 && mine[x + i][y + j] != '1')//雷不能扩展
				{
					if (!check(x + i, y + j))//查看这个坐标是否被用过
					{
						continue;
					}
					int ret = find_mine(mine, x + i, y + j);
					if (!ret)
					{
						arr[A] = x + i;
						A += 2;
						arr[B] = y + j;
						B += 2;
	
							show[x + i][y + j] = ret + '0';
							prove_show(show, mine, row, cow, x + i, y + j);
					}
					else
					{
							show[x + i][y + j] = ret + '0';
					}
				}
			}
		}
	}
	return;//当每个坐标都循环完后才return
}

这就是我们的扩展函数

要注意我们我们坐标加上这些偏移量后有可能会访问越界的 所以还需要排除一下不在范围内的坐标

只要我们的find_mine返回的是0 就要进行扩展 并且记录当前的坐标再进入下一层函数


插旗和撤旗函数:

其实插旗和撤旗函数内容差不多就放在一起讲了  因为插旗和撤旗函数都是为排雷函数设定的就不用在game.h里面声明了

插旗函数:

void FLAG(char show[ROWS][COWS], char mine[ROWS][COWS])//插旗
{
	int x, y;
	while (1)
	{
		printf("请选择你要插旗的地方:>");
		scanf("%d %d", &x, &y);
		if (x >= 1 && x <= 9 && y >= 1 && y <= 9)
		{
			if (show[x][y] == '*')//让插旗的地方不能是数字
			{
				show[x][y] = '$';
				if (mine[x][y] == '1')//判断插旗的地方是否是雷 方便提前跳出
				{
					ret++;
				}
				dis_playborad(show, ROW, COW);//插完旗还是要给我们的玩家看一下插得对不对
				break;
			}
			else
			{
				printf("该位置已经被开扩 不能再插旗了n");
				return;
			}
		}
		else
		{
			printf("您输入的坐标无效哦! 请重新输入:>");
		}
	}
}

有些时候可能玩家插错地方了 所以我们return 来给玩家再一次选择操作的机会 不至于强制别人选择插旗

撤旗函数: 

void take_flag(char show[ROWS][COWS])//撤旗
{
	int x, y;
	while (1)
	{
		printf("选择你要撤旗的地方:>n");
		scanf("%d %d", &x, &y);
		if (x >= 1 && x <= 9 && y >= 1 && y <= 9)
		{
			if (show[x][y] == '$')//有旗的地方才能撤旗
			{
				show[x][y] = '*';
				dis_playborad(show, ROW, COW);
				break;
			}
			else
			{
				printf("当前位置并没有旗 不能撤旗!n");
				return;
			}
		}
		else
		{
			printf("----------------------------------n");
			printf("对不起你输入的坐标无效请重新输入!n");
		}
	}
}

两个函数没有什么好说的  基本都差不多


总览:

game.h:

#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <string.h>
#define	MINE 10

#define ROW 9
#define COW 9

#define ROWS ROW+2
#define COWS COW+2 


void make_borad(char arr[ROWS][COWS], int row, int cow, char ret);//初始化棋盘

void dis_playborad(char mine[ROWS][COWS], int row, int cow);//制作并打印棋盘

void put_mine(char mine[ROWS][COWS], int row, int cow);//布置雷

void Insit_mine(char show[ROWS][COWS], char mine[ROWS][COWS]); //排查雷

game.c:

#include "game.h"
int arr[10001];
int ret = 0;
int A;
int B=1;
	
menu()
{
	printf("**********************n");
	printf("**********1.play******n");
	printf("**********0.exit******n");
	printf("*********请 选 择*****n");
	printf("**********************n");
}

void play()
{

    //初始化数组
	char mine[ROWS][COWS] = { 0 };
	char show[ROWS][COWS] = { 0 };
	
	//初始化棋盘
	make_borad(show, ROWS, COWS, '*');
	make_borad(mine, ROWS, COWS, '0');

	put_mine(mine, ROW, COW);//布置雷
	printf("您已经开始游戏!n");

	//开始界面
	dis_playborad(show, ROW, COW);

	//dis_playborad(mine, ROW, COW);
	Insit_mine(show, mine);
}




menu2()
{
	printf("*****************n");
	printf("*****1.排雷******n");
	printf("*****2.插棋******n");
	printf("*****3.撤销插旗**n");
	printf("*****************n");
}
void make_borad(char arr[ROWS][COWS], int row, int cow, char ret)//制作棋盘
{
	int i;
	int j;
	for (i = 0; i < row; i++)
	{
		for (j = 0; j < cow; j++)
		{
			arr[i][j] = ret;
		}
	}
}


void dis_playborad(char show[ROWS][COWS], int row, int cow)//制作并打印棋盘
{
	int i;
	int j;
	printf("-------------------------n");
	for (i = 0; i <= row; i++)
	{
		printf("%d ", i);
	}printf("n");
	for (i = 1; i <= row; i++)
	{
		printf("%d ", i);
		for (j = 1; j <= row; j++)
		{
			printf("%c ", show[i][j]);
		}
		printf("n");
	}
	printf("-------------------------n");
}


void put_mine(char mine[ROWS][COWS], int row, int cow)//布置雷
{
	
	for (int i = 0; i < MINE; )
	{
		int x = rand() % row + 1;
		int y = rand() % cow + 1;
		if (mine[x][y] == '0')
		{
			mine[x][y] = '1';
			i++;
		}
	}
}


int find_mine(char mine[ROWS][COWS], int x, int y)//排查一个坐标周围是否有雷
{
	return mine[x - 1][y - 1] +
		mine[x - 1][y] +
		mine[x - 1][y + 1] +
		mine[x][y - 1] +
		mine[x][y + 1] +
		mine[x + 1][y + 1] +
		mine[x + 1][y] +
		mine[x + 1][y - 1] - 8 * '0';
}


int find_num(char show[ROWS][COWS])//查看棋盘上还有多少没有访问的位置
{
	int count = 0;
	for (int i = 1; i <= ROW; i++)
	{
		for (int j = 1; j <= COW; j++)
		{
			if (show[i][j] != '*' && show[i][j] != '$')
				count++;
		}
	}
	return count;
}


int check(int x, int y)//判断当前循环坐标是否被用过
{
	for (int i = 0, j = 1; i < A && j < B; i += 2, j += 2)
	{
		if (x == arr[i] && y == arr[j])
		{
			return 0;
		}
	}
	return 1;
}


void prove_show(char show[ROWS][COWS], char mine[ROWS][COWS], int row, int cow, int x, int y)//往外面进行扩展
{
	for (int i = -1; i <= 1; i++)
	{
		for (int j = -1; j <= 1; j++)
		{
			if (!i && !j || show[x + i][y + j] == '$')
			{
				continue;//中间的位置 插旗的位置 不再判断
			}
			else
			{
				if (x + i >= 1 && x + i <= 9 && y + j >= 1 && y + j <= 9 && mine[x + i][y + j] != '1')//雷不能扩展
				{
					if (!check(x + i, y + j))
					{
						continue;
					}
					int ret = find_mine(mine, x + i, y + j);
					if (!ret)
					{
						arr[A] = x + i;
						A += 2;
						arr[B] = y + j;
						B += 2;
	
							show[x + i][y + j] = ret + '0';
							prove_show(show, mine, row, cow, x + i, y + j);
					}
					else
					{
							show[x + i][y + j] = ret + '0';
					}
				}
			}
		}
	}
	return;
}
void FLAG(char show[ROWS][COWS], char mine[ROWS][COWS])//插旗
{
	int x, y;
	while (1)
	{
		printf("请选择你要插旗的地方:>");
		scanf("%d %d", &x, &y);
		if (x >= 1 && x <= 9 && y >= 1 && y <= 9)
		{
			if (show[x][y] == '*')//让插旗的地方不能是数字
			{
				show[x][y] = '$';
				if (mine[x][y] == '1')//判断插旗的地方是否是雷 方便提前跳出
				{
					ret++;
				}
				dis_playborad(show, ROW, COW);
				break;
			}
			else
			{
				printf("该位置已经被开扩 不能再插旗了n");
				return;
			}
		}
		else
		{
			printf("您输入的坐标无效哦! 请重新输入:>");
		}
	}
}

void take_flag(char show[ROWS][COWS])//撤旗
{
	int x, y;
	while (1)
	{
		printf("选择你要撤旗的地方:>n");
		scanf("%d %d", &x, &y);
		if (x >= 1 && x <= 9 && y >= 1 && y <= 9)
		{
			if (show[x][y] == '$')//有旗的地方才能撤旗
			{
				show[x][y] = '*';
				dis_playborad(show, ROW, COW);
				break;
			}
			else
			{
				printf("当前位置并没有旗 不能撤旗!n");
				return;
			}
		}
		else
		{
			printf("----------------------------------n");
			printf("对不起你输入的坐标无效请重新输入!n");
		}
	}
}

void Insit_mine(char show[ROWS][COWS], char mine[ROWS][COWS])//排查雷
{
	while (find_num(show) < ROW * COW - MINE)
	{
		int x = 0;
		int y = 0;
		int input;
		menu2();
		printf("请选择你想要进行的操作:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
		{
			printf("请选择你要排查的位置:>");
			scanf("%d %d", &x, &y);
			if ((1 <= x && x <= 9) && (1 <= y && y <= 9))
			{
				if (show[x][y] == '$')       //查看输入的坐标是否已经有旗
				{
					printf("该坐标现在已经插旗了 不能排查!n");
					printf("你可以选择撤旗 来继续排查该坐标n");
					break;
				}

				if (mine[x][y] == '1')       //查看输入的坐标是否是雷
				{
					printf("很遗憾你踩雷了 游戏结束!n");
					return;
				}
				else
				{
					if (show[x][y] == '*')
					{
						if (!find_mine(mine, x, y))
						{
							arr[A] = x;
							A += 2;
							arr[B] = y;
							B += 2;       //第一次选的坐标也要记录 因为后面循环会循环到这个坐标
							show[x][y] = find_mine(mine, x, y) + '0';

							prove_show(show, mine, ROW, COW, x, y);
							dis_playborad(show, ROW, COW);
						}
						else
						{
							show[x][y] = find_mine(mine, x, y) + '0';
							dis_playborad(show, ROW, COW);
						}
					}
					else
					{
						printf("对不起 当前坐标你已经输入过了或该坐标已经被排查n");
					}
				}
			}
			else
			{
				printf("对不起你输入的坐标无效 请重新输入!n");
			}
			break;
		}
		case 2://插旗
		{
			FLAG(show, mine);
			if (ret == MINE)
			{
				printf("恭喜你 完成游戏!n");
				return;
			}
			break;
		}

		case 3://撤旗
		{
			take_flag(show);
			break;
		}
		default:
		{
			printf("您选择的选项不存在请重选!n");
			break;
		}
		}
	}

		printf("恭喜你 完成游戏!n");
		return;
	
}


int main()
{
	srand((unsigned int)time(NULL));
	int input;
	
	do
	{
		memset(arr, 0, sizeof arr);
		ret = 0;
		A=0;
		B = 1;
		menu();
		printf("---------------------------n");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			play();
			break;
		case 0:
			printf("您已经退出游戏n");
			break;
		default:
			printf("对不起 您输入得选择不存在 请重新输入n");
		}
	} while (input);
	return 0;
}

 这样 我们的扫雷游戏就编写完啦  新的一年祝大家 身体健康 万事如意 都能收到心仪的大厂的offer 谢谢大家看到这里 嘻嘻

 

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>