扫雷基础与进阶(附有详细解析)

前言:对于基础版扫雷,你需要掌握的知识有:循环与分支、函数基础、二维数组以及随机数函数(不懂可以看看我这篇文章《随机数函数 和 猜数字游戏》,需要了解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♪(・ω・)ノ

  • 12
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值