首页 > 其他分享 >扫雷基础与进阶(全面解析)

扫雷基础与进阶(全面解析)

时间:2024-08-20 23:52:33浏览次数:13  
标签:ROWS 进阶 show int COLS mine char 扫雷 解析

前言:对于基础版扫雷,你需要掌握的知识有:循环与分支、函数基础、二维数组以及随机数函数(不懂可以看看我这篇文章《随机数函数 和 猜数字游戏》,需要了解rand,srand,time这三个函数);对于进阶版扫雷,你还得了解函数递归调用的思想。

注意:如果想不看解析只看代码,可以直接阅读 “省略注释的全部参考代码(基础版)” 和 “最终参考代码(全)”的内容。

目录

1. 扫雷——基础版

1.1 题目要求(基础版)

1.2 前期分析与设计

1.2.1 问题与解答(问答递进式分析)

1.2.2 头文件 —— 工程函数目录 

1.3 代码实现讲解

1. 棋盘初始化函数:InitBoard

2. 棋盘打印函数:PrintBoard

3. 设置地雷函数:SetBoard

4. 统计地雷函数CountMine 与 排查地雷函数FindMine

5. 菜单函数menu 与 主函数main()

1.3 省略注释的全部参考代码(基础版)

2. 扫雷——进阶版

2.1题目更改(进阶版)

2.2 代码优化更新

2.2.1 修改部分宏定义

2.2.2 PrintBoard棋盘修饰优化

2.2.3 递归扩展函数CheckAndExtend 【重点】

2.2.4 FindMine函数的优化

2.3 最终参考代码(全)


1. 扫雷——基础版

1.1 题目要求(基础版)

1. 游戏可以通过菜单实现继续玩或者退出游戏

2. 扫雷的棋盘是5*5的格⼦

3. 随机布置5个雷

4. 通过坐标排查雷:

    ◦ 如果该坐标不是雷,就显⽰周围有几个雷

    ◦ 如果该坐标是雷就炸死,游戏失败

    ◦ 把除10个雷之外的所有雷都找出来,排雷成功 

1.2 前期分析与设计

①. 扫雷的过程中,布置的雷和排查出的雷的信息都需要存储,又因为我们需要在5*5的棋盘上布置雷的信息和排查雷,我们⾸先想到的就是创建⼀个5*5的数组来存放信息。

②. 为了区分坐标处有没有雷,我们把没有雷的地方设置成0,有雷的地方设置成1。

类似这样:(白色块数字是数组下标)

首次分析结果:(1)使用一个5*5的二维数组。(2)有雷设为1,无雷设为0。

1.2.1 问题与解答(问答递进式分析)

问题一:排查坐标时,如果该坐标无雷,那我们要怎样显示该坐标周围雷的个数?

答:我们可以遍历统计周围8个坐标,然后把该坐标下的数组元素的 '0' 改成 雷的个数


问题二:如果我们要统计贴边位置的周围雷的个数( 如下图(1,5)位置 ),我们还能用遍历周围8个坐标的方法吗?

答:如果不做出处理就直接遍历坐标会导致数组越界访问,但我们仍然使用这种方法。处理方法就是:把原来的数组扩展多2行和2列,变成7*7的数组,而我们让玩家看到和操作的只有中间5*5的空间,这样即使是四角处都能正常遍历周围8格。

这样做还有个好处:当玩家输入(1,1)时,就是访问二维数组下标为[1][1]的元素,即输入的坐标与数组下标 一 一对应。


问题三:情景假设:某坐标周围有1个雷,统计完后,该坐标对应的数组元素从0改成了1;可是1又表示此处有雷,这就造成了意义混乱,我们应该如何区分混淆的信息呢?

答:解决方法是我们使用两个二维数组。我们专门给⼀个棋盘(对应⼀个数组mine)存放布置好的雷的信息,再给另外⼀个棋盘(对应另外⼀个数组show)存放排查出的雷的信息,这样就互不干扰了。

两个二维数组的具体作用:

mine棋盘中:0是安全格,1是地雷格。

show棋盘中:“ * ”是未揭开的区域(可能有有雷);“数字”是已知区域,数字的大小反映着周围地雷的个数。


问答分析总结:(1)数组大小不是5*5,而是7*7。(2)我们需要创建两个数组,1个叫mine,1个叫show。(具体作用看上方)

1.2.2 头文件 —— 工程函数目录 

我们要创建3个文件:game.h存放宏定义和函数声明;game.c存放游戏函数定义;test.c 写游戏的测试逻辑。

game.h装有所有函数的声明,我们要做的就是实现这些函数,所以我称之为工程目录。如下:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>		//函数rand、srand需要用到
#include <time.h>		//函数time需要用到

#define mine_num 5		//放置5个地雷

#define ROW 5			//可视棋盘大小
#define COL 5

#define ROWS ROW+2		//真实数组大小
#define COLS COL+2

//初始化棋盘
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);

//打印棋盘
void PrintBoard(char board[ROWS][COLS], int row, int col);

//放置地雷
void SetMine(char mine[ROWS][COLS], int row, int col);

//排查地雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);

//计算周围8格有多少地雷
int CountMine(char mine[ROWS][COLS], int x, int y);

//游戏前菜单
void menu();

一些说明:

1. 参数表中的“board”:说明该函数适用于mine和show两个数组

2. 含“rows 和 cols” :说明传参的值是数组真实边长对应的宏定义,即ROWS和COLS。

3. 含“row 和 col”:说明传参的值是可视棋盘边长对应的宏定义,即ROW与COL。

下面我们来实现头文件中的函数。

1.3 代码实现讲解

1. 棋盘初始化函数:InitBoard

void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
	for (int i = 0; i < rows; i++)
	{
		for (int j = 0; j < cols; j++)
		{
			board[i][j] = set;			//注意数值与字符
		}
	}
}

该函数的具体作用是:用set赋值给二维数组的每一个元素。

注意:把mine数组初始化为0时,不是把数值0赋值给元素,而是把 '0' 的数值赋给元素。正确的使用格式是“ InitBoard ( mine, ROWS, COLS, '0' ); ”。

2. 棋盘打印函数:PrintBoard

void PrintBoard(char board[ROWS][COLS], int row, int col)
{
	printf("--------扫雷-------\n");
	for (int i = 0; i <= col; i++)			//细节1
	{
		printf("%d ", i);
	}
	printf("\n");
	for (int i = 1; i <= row; i++)			
	{
		printf("%d ", i);				    //细节2
		for (int j = 1; j <= col; j++)
		{
			printf("%c ", board[i][j]);
		}
		printf("\n");
	}
}

这个函数是用来打印可视棋盘的,第一个printf的内容是用来装饰的。

细节分析——2个:

 细节1 —— 打印横坐标:

  1. 变量 i 从0开始打印,可以把真实二维数组中的board[0][0]剔除出可操作区域。
  2.  因为横坐标的坐标数是与数组的列数对应的,所以i到col结束,而不是到row。(如果想弄成   长方形的可视棋盘,必须弄明白这里的对应关系)
  3. 可视棋盘的第一行用来打印横坐标,把 board[0][0] ~ board[0][7] 都剔除出可操作区域。

 细节2 —— 打印纵坐标:

  1. 变量i会产生行号,而行数对应的是纵坐标,所以我们在每行打印可视区域之前,都打印1个纵坐标数。
  2. 可视棋盘的第一列用来打印纵坐标,把 board[1][0] ~ board[7][0] 都剔除出可操作区域。

让我们把初始化后的mine和show打印出来看看:

1.    InitBoard(mine, ROWS, COLS, '0');
2.    InitBoard(show, ROWS, COLS, '*');
3.    PrintBoard(mine, ROW, COL);
4.    PrintBoard(show, ROW, COL);

3. 设置地雷函数:SetBoard

void SetMine(char board[ROWS][COLS], int row, int col)
{
	int count = mine_num;				
	while (count)						
	{
		//⽣成随机的坐标,布置雷
		int x = rand() % row + 1;		//要点Ⅰ
		int y = rand() % col + 1;
		if (board[x][y] == '0')			//要点Ⅱ
		{
			board[x][y] = '1';
			count--;
		}
	}
}

count为还要放置的地雷数,每放置一颗就减减,当count为0时说明放置完成。

(不会rand的要看看这个《随机数函数 和 猜数字游戏》

要点分析——2个:

要点Ⅰ—— 随机坐标的范围控制:

  • 我们要求地雷要放置在5*5的格子里,所以下标要控制在1~5(不能是0~5)。此时row和col的值是5,而 rand() % 5 的结果是 0~4,我们只需要再加上1,结果就变成了1~5。

要点Ⅱ—— 防止重复放雷:

为了防止重复放雷,我们放置地雷之前要检查该格是否已经有雷。没有雷就放置,剩余雷数count要减减;有雷就不能放置,剩余雷数count不能变。

我们看一下放置地雷后的mine棋盘:

1. SetMine(mine, ROW, COL);

2. PrintBoard(mine, ROW, COL);

4. 统计地雷函数CountMine 与 排查地雷函数FindMine

先说统计地雷函数CountMine :

int CountMine(char mine[ROWS][COLS], int x, int y)
{
	int cnt = 0;
	for (int i = x - 1; i <= x + 1; i++)
	{
		for (int j = y - 1; j <= y + 1; j++)
		{
			cnt += mine[i][j] - '0';		//要点Ⅰ
		}
	}
	return cnt;
}

用cnt统计,然后返回周围的地雷个数。

要点Ⅰ:

  • 由于创建的二维数组的元素类型是char,mine棋盘中的信息1和0都是acsii码的值,不能像这样统计:“ cnt += mine[i][j] ”。这样增加的是'0'和'1'的值,远大于数值0和1。
  • 而'0' - '0'就等于数值0,'1' - '0'就等于数值1,所以我们用mine[i][j] - '0'来统计。

再看看FindMine函数怎么实现:

void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	int surplus = ROW * COL - mine_num; //剩余的非雷区域
	int x = 0, y = 0;
	printf("请按坐标输入你想排除的位置:");

	while (surplus)									     //要点Ⅰ-↓↓
	{
		scanf("%d %d", &x, &y);		
		if (x >= 1 && x <= ROW && y >= 1 && y <= COL)
		{//mine棋盘(x,y)的位置为1时,说明踩到地雷游戏结束
			if (mine[x][y] == '1')					
			{
				break;								     //细节❶-↓↓
			}
			else		
			{//1.如果没炸,show棋盘要显示(x,y)周围的地雷数
				int cnt = CountMine(mine, x, y);
				show[x][y] = cnt + '0';				     //要点Ⅱ
			 //2.把统计完的个数更新到show棋盘上
				PrintBoard(show, ROW, COL);
			 //3.这里不是雷,已经排查了,剩余非雷的区域要-1
				surplus--;
			 //4.提示玩家还有多少的地方要排查
				if(surplus != 0)					     //细节❷
				{
					printf("还有%d个坐标没排除,请继续输入:", surplus); 
				}
			}
		}
		else                                             //要点Ⅲ
		{
			printf("输入错误,请重新输入:");
		}
	}
	if (surplus == 0)							         //要点Ⅰ-↑↑
	{
		printf("\n游戏胜利!!!\n");
	}
	else											     //细节❶-↑↑
	{
		printf("\n游戏失败,你被炸死了!!答案揭晓,数字1的位置是雷:\n");
		PrintBoard(mine, ROW, COL);
	}
}

分析——3个要点,2个细节:(↓↓与↑↑表示内容相通,合在一起讲解)

要点Ⅰ:surplus停止循环

  • 排除雷的形式是把所有不是雷的格子都揭开,变量surplus表示未揭开的非雷格子数。当surplus为0时,说明剩下的5个格子都是雷,已经排除成功了,要结束排查循环
  • surplus等于0是正常的循环结束,说明游戏胜利了,记得告诉玩家。

细节❶:break停止循环

  • 玩家输入x和y,如果mine[x][y]的为'1'时,说明踩到地雷游戏结束。break是异常的循环结束,记得告诉玩家游戏失败。

要点Ⅱ:注意数值与字符的区别

  • show的元素类型是char,如果"show[x][y] = cnt",那相当于装了int型的数值,值为cnt。正确的是“cnt + '0' ”,这样才是对应char型的数值,值为 'cnt' 。

细节❷:终端装饰优化

  • 如果没有这个if条件限制,当surplus为0时,终端先打印 “还有0个坐标没排除,请继续输入:”,再打印“ 游戏胜利!!!”。这样在玩家的角度看会有点奇怪:明明胜利了,都不用再排除了,为什么还要我继续输入坐标呢?

要点Ⅲ:防止越界输入

玩家的可操作区域只有数组下标的1~row, 1~col。所以要先对输入的x和y进行范围检查,再对board[x][y]进行访问;如果超出范围要提示玩家重新输入。

5. 菜单函数menu 与 主函数main()

menu很简单:

void menu()
{
	printf("———————————— 扫雷游戏:选择菜单 ————————————\n");
	printf("************** 1. 开始游戏 ****************\n");
	printf("************** 0. 离开游戏 ****************\n");
	printf("———————————(9X9大小,共%d个雷)———————————————\n", mine_num);
	printf("请选择操作指令:");
}

(这里用个占位符,方便以后提示放置了雷的个数)

主函数:

int main()
{
	int choice = 0;
	srand((unsigned int)time(NULL));
	do
	{
		menu();
		scanf("%d", &choice);
		switch (choice)
		{
		case 1:
			game();
			break;
		case 0:
			printf("---》游戏结束《---\n");
			break;
		default:
			printf("输入无效指令,请重新输入\n");
		}
	} while (choice != 0);

	return 0;
}

这里用switch分支语句,1是进入游戏,2是正常的退出游戏;如果输入了其他数字,要提醒用户重新输入。

1.3 省略注释的全部参考代码(基础版)

game.h

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>		
#include<string.h>
#include <time.h>		

#define mine_num 5		
#define ROW 5			
#define COL 5
#define ROWS ROW+2		
#define COLS COL+2
//初始化棋盘
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);
//打印棋盘
void PrintBoard(char board[ROWS][COLS], int row, int col);
//放置地雷
void SetMine(char mine[ROWS][COLS], int row, int col);
//排查地雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
//计算周围8格有多少地雷
int CountMine(char mine[ROWS][COLS], int x, int y);
//游戏前菜单
void menu();

game.c

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

void PrintBoard(char board[ROWS][COLS], int row, int col)
{
	printf("--------扫雷-------\n");
	for (int i = 0; i <= col; i++)			
	{
		printf("%d ", i);
	}
	printf("\n");
	for (int i = 1; i <= row; i++)			
	{
		printf("%d ", i);				
		for (int j = 1; j <= col; j++)
		{
			printf("%c ", board[i][j]);
		}
		printf("\n");
	}
}

void SetMine(char board[ROWS][COLS], int row, int col)
{
	int count = mine_num;				
	while (count)						
	{
		int x = rand() % row + 1;		
		int y = rand() % col + 1;
		if (board[x][y] == '0')			
		{
			board[x][y] = '1';
			count--;
		}
	}
}

int CountMine(char mine[ROWS][COLS], int x, int y)
{
	int cnt = 0;
	for (int i = x - 1; i <= x + 1; i++)
	{
		for (int j = y - 1; j <= y + 1; j++)
		{
			cnt += mine[i][j] - '0';		
		}
	}
	return cnt;
}

void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	int surplus = ROW * COL - mine_num; 
	int x = 0, y = 0;
	printf("请按坐标输入你想排除的位置:");
	while (surplus)									   
	{
		scanf("%d %d", &x, &y);		
		if (x >= 1 && x <= ROW && y >= 1 && y <= COL)
		{
			if (mine[x][y] == '1')					
			{
				break;								  
			}
			else		
			{
				int cnt = CountMine(mine, x, y);
				show[x][y] = cnt + '0';				   			
				PrintBoard(show, ROW, COL);			
				surplus--;			
				if(surplus != 0)					  
				{
					printf("还有%d个坐标没排除,请继续输入:", surplus); 
				}
			}
		}
		else                                           
		{
			printf("输入错误,请重新输入:");
		}
	}
	if (surplus == 0)							       
	{
		printf("\n游戏胜利!!!\n");
	}
	else											   
	{
		printf("\n游戏失败,你被炸死了!!答案揭晓,数字1的位置是雷:\n");
		PrintBoard(mine, ROW, COL);
	}
}

void menu()
{
	printf("———————————— 扫雷游戏:选择菜单 ————————————\n");
	printf("************** 1. 开始游戏 ****************\n");
	printf("************** 0. 离开游戏 ****************\n");
	printf("———————————(9X9大小,共%d个雷)———————————————\n", mine_num);
	printf("请选择操作指令:");
}

test.c

#include"project.h"

void game()
{
	char mine[ROWS][COLS];
	char show[ROWS][COLS];
	InitBoard(mine, ROWS, COLS, '0');
	InitBoard(show, ROWS, COLS, '*');
	SetMine(mine, ROW, COL);
	PrintBoard(show, ROW, COL);
	FindMine(mine, show, ROW, COL);
}

int main()
{
	int choice = 0;
	srand((unsigned int)time(NULL));
	do
	{
		menu();
		scanf("%d", &choice);
		switch (choice)
		{
		case 1:
			game();
			break;
		case 0:
			printf("---》游戏结束《---\n");
			break;
		default:
			printf("输入无效指令,请重新输入\n");
		}
	} while (choice != 0);
	return 0;
}

2. 扫雷——进阶版

2.1题目更改(进阶版)

更改1:扫雷的棋盘是9*9的格⼦

更改2:默认随机布置10个雷

新增要求:排除雷时,可以自动扩展排除的范围;扩展停止的条件:排查到某个坐标,该坐标下没有雷,但它的周围有地雷,则不再扩展。

2.2 代码优化更新

(注意:没有更新的部分我会把它注释掉)

2.2.1 修改部分宏定义

#define mine_num 10		//放置10个地雷
#define ROW 9			
#define COL 9
//#define ROWS ROW+2		
//#define COLS COL+2

因为题目规则中,棋盘大小和放置地雷数都变了,所以mine_num要改成10,ROW和COL要改成9。其他不用修改。

2.2.2 PrintBoard棋盘修饰优化

我们原本的棋盘样子太素了,我给出一个装饰好看的版本:

void PrintBoard(char board[ROWS][COLS], int row, int col) 
{
	//printf("———————————— ↓↓扫雷↓↓ ————————————\n");
    
	printf("\t ╭");                    第二行打印纵坐标
	for (int i = 1; i <= col; i++)		
		printf("%d-", i);
	printf("\b╮\n");
	
	for (int i = 1; i <= row; i++)     打印横坐标和棋盘
	{
		printf("\t");
		printf("%d|", i);
		//for (int j = 1; j <= col; j++)
		//{
			//printf("%c ", board[i][j]);
		//}
		printf("\b|\n");
	}

	printf("———————————— ↑↑游戏↑↑ ————————————\n\n");  最后一行增加边界
}

优化后棋盘的样子:

2.2.3 递归扩展函数CheckAndExtend 【重点】

功能实现的推理分析:

1. 新增要求分析:题目要求可以自动扩展排除的范围,而且还有停止扩展的条件,我们可以想到采用递归的思想。(题目做多了就能有这种直觉)

2. 停止条件分析:条件是“该坐标的周围8格有雷就停止”,这前提是我们知道该格子周围有没有雷,所以该扩展函数的前面部分会运用到CountMine函数统计周围地雷数。

3. 思路延伸①:既然扩展函数内部也把周围地雷数统计,那也可以顺便把该坐标对应的show数组元素的值改成周围地雷数。

4. 思路延伸②:既然对该坐标统计了,那剩余非地雷格子数surplus就得减一。因为是surplus是在排除函数FindMine中创建的,所以在扩展函数需要用指针来操作。

5. 写上递归出口:用cnt接收CountMine函数的返回值,当cnt 不等于0时,需要return出去。

6. 递归出口分析:我们可以采用遍历的方式,对周围的8个格子依次递归进去。(迭代嵌套递归)

7. 思路延伸③ —— 防止重复递归:

每次递归会更换方圈的中心点,中心点一换,新的方圈会与旧的方圈有重合(如下图所示),要防止重合的地方再次统计。但是只看mine棋盘的话只知道这里是无雷的,不知道有没有访问过,这时候就得看这里的show棋盘有没有被改过了,如果show棋盘这里已经不是“*”了,那么说明这里访问过,就不再递归统计。(这里还要对坐标进行判断,防止越界访问)

根据上述的推理,我们可以写出下面这个函数:

void CheckAndExtend(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y, int* sur)
{
	int cnt = CountMine(mine, x, y);
	show[x][y] = cnt + '0';
	(*sur)--;
	if(show[x][y]=='0')
		show[x][y] = ' ';

	if (cnt != 0)			//迭代出口						
		return;
	else											
	{
		for (int i = x - 1; i <= x + 1; i++)   //迭代嵌套递归
		{
			for (int j = y - 1; j <= y + 1; j++)
			{
				if(i >= 1 && i <= ROW && j >= 1 && j <= COL) //防止越界
				{
					if (show[i][j] == '*')   //防止重复迭代
						CheckAndExtend(mine, show, i, j, sur);  //迭代入口
				}
			}
		}
	}
}

为了玩家的界面好看,我把show棋盘上的0换成了空格。

2.2.4 FindMine函数的优化

既然扩展函数CheckAndExtend里面又能统计地雷数,又能改变show棋盘,还能让surplus减减。所以要把旧FineMine函数的重复部分删去,如下:

//void FindMine_new(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
//{
//	int surplus = ROW * COL - mine_num; //剩余的非雷区域
//	int x = 0, y = 0;
//	printf("请按坐标输入你想排除的位置:");
//
//	while(surplus) //当非雷区域剩余0个时,说明已经赢了
//	{
//		scanf("%d %d", &x, &y);
//		if (x >= 1 && x <= ROW && y >= 1 && y <= COL)
//		{
//			if (mine[x][y] == '1')
//			{
//				break;
//			}
			else		//扩展棋盘后才能打印
			{
				CheckAndExtend(mine, show, x, y, &surplus); 重复的部分被替换成扩展函数
				PrintBoard(show, ROW, COL);
				if(surplus==0)
				{
					break;
				}
				printf("还有%d个坐标没排除,请继续输入:", surplus);
			}
//		}
//		else
//		{
//			printf("输入错误,请重新输入:");
//		}
//	}
//	if (surplus == 0)
//	{
//		printf("\n游戏胜利!!!\n");
//	}
//	else
//	{
//		printf("\n游戏失败,你被炸死了!!");
//		printf("答案揭晓,数字1的位置是雷:\n");
		for (int i = 1; i <= row; i++)
		{
			for (int j = 1; j <= col; j++)
			{
				if (mine[i][j] == '0')
					mine[i][j] = ' ';
			}
		}
		PrintBoard(mine, ROW, COL);
//	}
//	printf("再接再厉\n\n");
//}

该函数下面的修改是为了让用户界面更好看

2.3 最终参考代码(全)

game.h

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<time.h>
#include<stdlib.h>

#define ROW 9     //可操作的棋盘大小是9X9
#define COL 9
#define ROWS ROW+2		//真实大小是11X11
#define COLS COL+2
#define mine_num 10		//放置地雷数是10

//初始化棋盘
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);
//打印棋盘
void PrintBoard(char board[ROWS][COLS], int row, int col);
//放置地雷
void SetMine(char mine[ROWS][COLS], int row, int col);
//排查地雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
//计算周围8格有多少地雷
int CountMine(char mine[ROWS][COLS], int x, int y);
//菜单函数
void menu();
//递归扩展函数
void CheckAndExtend(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y, int* sur);

game.c

void menu()
{
	printf("———————————— 扫雷游戏:选择菜单 ————————————\n");
	printf("************** 1. 开始游戏 ****************\n");
	printf("************** 0. 离开游戏 ****************\n");
	printf("———————————(9X9大小,共%d个雷)———————————————\n", mine_num);
	printf("请选择操作指令:");
}

void InitBoard(char board[ROWS][COLS], int rows, int cols, char set) 
{
	for (int i = 0; i < rows; i++)
	{
		for (int j = 0; j < cols; j++)
		{
			board[i][j] = set;
		}
	}
}

void PrintBoard(char board[ROWS][COLS], int row, int col)  
{
	printf("———————————— ↓↓扫雷↓↓ ————————————\n");
	
	printf("\t ╭");
	for (int i = 1; i <= col; i++)		
		printf("%d-", i);
	printf("\b╮\n");
	
	for (int i = 1; i <= row; i++)
	{
		printf("\t");
		printf("%d|", i);
		for (int j = 1; j <= col; j++)
		{
			printf("%c ", board[i][j]);
		}
		printf("\b|\n");
	}

	printf("———————————— ↑↑游戏↑↑ ————————————\n\n");
}

void SetMine(char mine[ROWS][COLS], int row, int col)		
{
	int count = mine_num;   
	while(count)
	{
		//布雷的坐标要随机
		int x = rand() % row + 1;
		int y = rand() % col + 1;
		//防止同一个坐标重复布雷
		if (mine[x][y] == '0')
		{
			mine[x][y] = '1';
			count--;
		}
	}
}

int CountMine(char mine[ROWS][COLS], int x, int y)
{
	int cnt = 0;
	for (int i = x - 1; i <= x + 1; i++)
	{
		for (int j = y - 1; j <= y + 1; j++)
		{
			cnt += mine[i][j] - '0';
		}
	}
	return cnt;
}

void CheckAndExtend(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y, int* sur)
{
	int cnt = CountMine(mine, x, y);
	show[x][y] = cnt + '0';
	(*sur)--;
	if (show[x][y] == '0')
		show[x][y] = ' ';

	if (cnt != 0)									
		return;
	else											
	{
		//show[x][y] = ' ';
		for (int i = x - 1; i <= x + 1; i++)
		{
			for (int j = y - 1; j <= y + 1; j++)
			{
				if (i >= 1 && i <= ROW && j >= 1 && j <= COL)
				{
					if (show[i][j] == '*')
						CheckAndExtend(mine, show, i, j, sur);
				}
			}
		}
	}
}

void FindMine_new(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	int surplus = ROW * COL - mine_num; 
	int x = 0, y = 0;
	printf("请按坐标输入你想排除的位置:");
	while(surplus) 
	{
		scanf("%d %d", &x, &y);
		if (x >= 1 && x <= ROW && y >= 1 && y <= COL)
		{
			if (mine[x][y] == '1')
			{
				break;
			}
			else		
			{
				CheckAndExtend(mine, show, x, y, &surplus); 
				PrintBoard(show, ROW, COL);
				if(surplus==0)
				{
					break;
				}
				printf("还有%d个坐标没排除,请继续输入:", surplus);
			}
		}
		else
		{
			printf("输入错误,请重新输入:");
		}
	}
	if (surplus == 0)
	{
		printf("\n游戏胜利!!!\n");
	}
	else
	{
		printf("\n游戏失败,你被炸死了!!");
		printf("答案揭晓,数字1的位置是雷:\n");
		for (int i = 1; i <= row; i++)
		{
			for (int j = 1; j <= col; j++)
			{
				if (mine[i][j] == '0')
					mine[i][j] = ' ';
			}
		}
		PrintBoard(mine, ROW, COL);
	}
	printf("再接再厉\n\n");
}

test.c

void game()
{
	char mine[ROWS][COLS];
	char show[ROWS][COLS];
	InitBoard(mine, ROWS, COLS, '0');
	InitBoard(show, ROWS, COLS, '*');

	SetMine(mine, ROW, COL);
	PrintBoard(show, ROW, COL);

	FindMine_new(mine, show, ROW, COL);
}

int main()
{
	int choice = 0;	
	srand((unsigned int)time(NULL));
	do
	{
		menu();
		scanf("%d", &choice);
		switch (choice)
		{
		case 1:
			game();
			break;
		case 0:
			printf("---》游戏结束《---\n\a");
			break;
		default:
			printf("输入无效指令,请重新输入\n");
		}
	} while (choice != 0);
	
	return 0;

本期分享就到这里,感谢您的支持Thanks♪(・ω・)ノ

标签:ROWS,进阶,show,int,COLS,mine,char,扫雷,解析
From: https://blog.csdn.net/2301_80030290/article/details/141281292

相关文章

  • leetcode 热题思路解析-最长连续序列
    题目给定一个未排序的整数数组nums,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。请你设计并实现时间复杂度为O(n)的算法解决此问题。示例1:输入:nums=[100,4,200,1,3,2]输出:4解释:最长数字连续序列是[1,2,3,4]。它的长度为4。示例2:输入......
  • Windows 隐蔽 DNS 隧道是一种利用 DNS 协议在网络上进行隐蔽数据传输的技术。DNS(域名
    Windows隐蔽DNS隧道是一种利用DNS协议在网络上进行隐蔽数据传输的技术。DNS(域名系统)通常用于将域名解析为IP地址,但其协议本身并不限制传输的数据内容。因此,攻击者或信息安全专家可能利用这一点,通过DNS请求和响应传输未经授权的数据流量。工作原理数据编码:首先,将要传......
  • ViT 原理解析 (Transformers for Image Recognition at Scale)
    ViT原理解析(TransformersforImageRecognitionatScale)原创 小白 小白研究室 2024年06月10日21:09 北京如何将transformer应用到图像领域Transformer模型最开始是用于自然语言处理(NLP)领域的,NLP主要处理的是文本、句子、段落等,即序列数据。视觉领域处理的......
  • Android开发 - BleConnectOptions 类设置蓝牙连接选项解析
    BleConnectOptions是什么BleConnectOptions类是与蓝牙设备连接相关的一个配置类。它主要用于设置蓝牙连接的选项,确保与蓝牙设备的连接能够根据需求进行调整和优化。常用于配置蓝牙设备的连接参数,例如连接超时时间、是否自动连接等。这些配置可以帮助你更好地控制蓝牙连接过程,......
  • OFtutorial10_transportEquation解析
    组成OFtutorial10.C源码头文件#include"fvCFD.H"主函数intmain(intargc,char*argv[]){头文件 //Setupthecase,parsecommandlineoptionsandcreatethegrid#include"setRootCase.H"#include"createTime.H"#inclu......
  • OFtutorial09_runtimePostprocessingUtility解析
    组成pipeCalc.H源码头文件#ifndefpipeCalc_H#definepipeCalc_H#include"volFieldsFwd.H"#include"Switch.H"#include"fvc.H"#include"fvMeshFunctionObject.H"#include"logFiles.H"#include"addToRunTi......
  • 汇编语言的神秘面纱:指令前缀的深度解析
    标题:汇编语言的神秘面纱:指令前缀的深度解析在计算机编程的底层世界中,汇编语言以其接近硬件的特性,扮演着至关重要的角色。指令前缀是汇编语言中一个关键的概念,它为指令提供了额外的信息,使得程序能够执行更加复杂和灵活的操作。本文将深入探讨指令前缀的作用、类型以及如何在......
  • 深入解析CDN(内容分发网络):架构、优势与实现
    摘要内容分发网络(CDN)是一种通过在多个地理位置部署服务器节点来提高网站加载速度和数据传输效率的网络技术。随着互联网内容的日益丰富和用户对访问速度的高要求,CDN已经成为现代网站和应用不可或缺的一部分。本文将详细介绍CDN的基本概念、工作原理、优势以及如何在Web开发......
  • 网络丢包深度解析:影响、原因及优化策略
    摘要网络丢包是数据在传输过程中未能成功到达目的地的现象,它对网络通信的性能有着显著的影响。本文将深入探讨网络丢包的定义、原因、对性能的影响,以及如何通过技术手段进行检测和优化。1.网络丢包的定义网络丢包发生在数据包在源和目的地之间的传输过程中,由于各种原因......
  • Python爬虫进阶技巧
    在掌握了基本的网页数据提取与解析技能后,我们将进一步探讨Python爬虫的进阶技巧,以应对更加复杂的网络环境和数据抓取需求。动态网页爬取动态网页是指那些通过JavaScript动态生成内容的网页。这类网页的内容在初次加载时并不包含在HTML源代码中,因此无法直接使用传统的爬虫方法......