• 扫雷?拿来吧你(递归展开+坐标标记)


    前言

    如果聊起有什么让大家印象深刻的童年游戏,那么《 扫雷 》的地位应该是不可撼动的。这个搭载在 Windows 系统菜单里的小游戏在当时可谓大红大紫,算的上是我们实打实的童年回忆了。

    • 或许我们在一遍又一遍玩扫雷的同时,都会回忆起小学夏天的那节电脑课上,穿着鞋套,在硕大的白色电脑上,瞒着老师偷偷玩一把扫雷的简单快乐吧~

    • 如今我们用经典的C语言复刻扫雷,还能否勾起你对童年的回忆呢?

    Alt

    一、游戏规则

    扫雷游戏规则:玩家需要尽快找出雷区中的所有不是地雷的方块,而不许踩到地雷。

    众所周知。点开其中一个小方格之后,数字是几,就说明它周围的八个方位就有几个雷

    比如下图中就表示红框内存在2个雷

    二、效果演示

    🍑1、初级版

    🍑2、升级版

    三、游戏设计思路

    在介绍过三子棋的实现后,要实现扫雷是不是就会感觉就简单一些了呢?其实二者有很多相似的地方,扫雷游戏的实现主要是对下面两个模块的实现:

    一、游戏交互界面

    1. 创建游戏菜单
    2. 创建游戏逻辑主体

    二、扫雷游戏实现

    1. 创建扫雷棋盘
    2. 初始化扫雷棋盘
    3. 布置雷
    4. 打印扫雷棋盘
    5. 用户排查雷
    6. 判断输赢

    这里准备了一张图,便大家更直观的理清游戏实现原理与游戏实现的逻辑 :

    四、游戏交互界面

    🍑1、创建游戏菜单

    📝代码展示:

    void menu()
    {
    	printf("* * * * * * * * * * * * * * * * * *\n");
    	printf("* * * * * * 1.扫雷游戏  * * * * * *\n");
    	printf("* * * * * * 0.退出游戏  * * * * * *\n");
    	printf("* * * * * * * * * * * * * * * * * *\n");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    🍑2、创建游戏逻辑主体

    📝代码展示:

    int main()
    {
    	srand((unsigned int)time(NULL));
    	int input = 0;
    	do
    	{
    		menu();
    		printf("请选择:>");
    		scanf("%d",&input);
    		switch (input)
    		{case 1:
    			game();//扫雷游戏
    			break;
    		case 0:
    			printf("退出游戏\n");
    			break;
    		default:
    			printf("输入错误,请重新输入\n");
    			break;
    		}
    
    	} while (input);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    1、srand((unsigned int)time(NULL)) 表示随机数生成函数通过时间戳生成随机数,为下面的布置雷做准备。

    2、do-while() 因为游戏至少可以进入一次,至少让玩家输入一次玩或不玩,所以用do-while()循环–循环代码至少执行一次。

    观察上面这段代码,其中我们使用了一个game()函数来实现扫雷游戏,但是这个函数还未定义,下面将实现对game()函数的定义。

    五、扫雷游戏实现(初级版)

    🍑1、游戏框架搭建

    📝代码展示

    void game()
    {
    	//创建雷盘:
    	char mine[ROWS][COLS] = { 0 };//存放布置好的雷的信息
    	char show[ROWS][COLS] = { 0 };//存放排查出的雷的信息
    
    	//初始化雷盘:
    	InitBoard(mine, ROWS, COLS,'0');
    	InitBoard(show, ROWS, COLS,'*');
    
    	//布置雷:
    	SetMine(mine, ROW, COL);
    
    	//向玩家打印扫雷棋盘:
    	DisPlayBoard(show, ROW, COL);
    
    	//排查雷并判断输赢
    	FindMine(mine,show,ROW,COL);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    🍑2、游戏内函数实现

    🌳(1)创建雷盘

    📝代码展示:

    #define ROW 9
    #define COL 9
    #define ROWS ROW+2
    #define COLS COL+2
    
    char mine[ROWS][COLS] = { 0 };//存放布置好的雷的信息
    char show[ROWS][COLS] = { 0 };//存放排查出的雷的信息
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    问题1:这里为什么创建两个棋盘?

    假设这里只创建一个棋盘,那么这一个棋盘上就要放置3种信息。分别为雷、非雷、排查出周围雷的信息。虽然这种方法也没太大的问题,但是一个棋盘上放置3种信息会给之后打印棋盘时造成麻烦,不易打印。

    所以我们创建两个二维数组,一个数组存放布置好雷的信息;另一个数组存放排查出的雷的信息。这样创建不仅方便之后的打印,也可以将问题分解从而让问题层次更加分明,解题思路更加清晰。

    问题2:为什么使用宏常量?

    其实这个问题在前面的三子棋游戏中也有介绍。在这里使用宏常量主要是为了方便程序的修改,增加了程序的可塑性。

    就像我这里通过宏定义将行列通设定为9,也就是9*9的雷盘。假如后面我想要玩12*12的雷盘,我只需要将宏定义中的9改为12即可,这样就省去了在程序中大量修改的精力,使代码可塑性更高。

    问题3:为什么二位数组的行列选择ROWS、COLS而不是ROW、COL

    通过扫雷游戏规则我们已知,如果一个位置不是雷,我们要排查它周围8个坐标是不是雷,对于9*9的棋盘,当在排查边界时可能会出现越界的情况。因此我们创建11*11的棋盘这样就很好的解决了这个问题。

    那么怎么让用户看到9*9的棋盘呀?其实用户看到的只是程序员想让你看到的,后面我们只需要打印11*11中的9*9就ok了。
    在这里插入图片描述

    🌳(2)初始化雷盘

    📝代码展示:

    void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
    {
    	int i = 0;
    	int j = 0;
    	for (i = 0; i < rows; i++)
    	{
    		for (j = 0; j < cols; j++)
    		{
    			board[i][j] = set;
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    这里就是简单使用两个for循环遍历整个棋盘从而实现棋盘初始化,这里就不在过多赘述。

    补充:这里是将mine[ROWS][COLS]中的内容初始化为‘0’show[ROWS][COLS]中内容初始化为‘*’

    效果展示:

    🌳(3)打印扫雷棋盘

    📝代码展示:

    //打印棋盘
    DisPlayBoard(char board[ROWS][COLS], int row, int col)
    {
    	int i = 1;
    	int j = 1;
    	printf("------扫雷游戏------\n");
    	for (i = 1; i <= row; i++)
    	{
    
    		for (j = 1; j <= col; j++)
    		{
    			printf("%c ",board[i][j]);
    		}
    		printf("\n");
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    效果展示:

    简单观察不难发现,上述棋盘使用户查找坐标时变得困难,不易于用户输入排雷坐标 ,因此我们可以通过下面简单优化,为棋盘标号👇:

    📝优化后代码:

    //打印棋盘
    DisPlayBoard(char board[ROWS][COLS], int row, int col)
    {
    	int i = 1;
    	int j = 1;
    	printf("------扫雷游戏------\n");
    	//打印列号
    	for (i = 0; i <= col; i++)
    	{
    		printf("%d ",i);
    	}
    	printf("\n");
    
    	for (i = 1; i <= row; i++)
    	{//打印行号
    		printf("%d ",i);
    		for (j = 1; j <= col; j++)
    		{
    			printf("%c ",board[i][j]);
    		}
    		printf("\n");
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    优化后效果展示:

    这样以来雷盘坐标是不是就变得更加直观清楚了 !😊

    🌳(4)布置雷

    📝代码展示:

    #define EASY_COUNT 10
    
    • 1
    void SetMine(char mine[ROWS][COLS], int row, int col)
    {
    	//布置10个雷
    	int count = EASY_COUNT;
    	while (count)
    	{
    		//生产随机的下标
    		int x = rand()%row+1;
    		int y = rand()%col+1;
    		if (mine[x][y] == '0')
    		{
    			mine[x][y] = '1';
    			count--;
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    1、这里使用宏定义布置雷的个数,便于修改,增加代码的可塑性。

    2、rand()函数与srand((unsigned int)time(NULL))函数配合生成随机数,rand()%row+1rand()%col+1保证了生成了横纵坐标在合理范围之内。

    补充:这里规定雷为'1'

    🌳(5)扫雷并判断输赢

    1.排雷原理

    在扫雷的时候我们还需要定义一个函数,用来返回排查坐标周围地雷的个数。

    下面用一张图来解析排查原理:

    假设排查坐标为(x,y),我们可以如右图,依次返回其周围8个坐标下对应的值,由于我们上面规定,雷为‘1’,非雷为‘0’,则字符相加减对应ASCLL码值相加减。

    例如:(x,y)周围有1个雷,则7*'0'+'1'-8*'0'即表示‘1’的ASCLL码值减‘0’的ASCLL码值,返回整数1,即周围有1个雷

    方案一:逐个遍历
    📝代码展示:

    //返回排查坐标周围雷的数量
    int get_mine_count(char mine[ROWS][COLS], int x, int y)
    {
    	return mine[x - 1][y] +
    		mine[x - 1][y - 1] +
    		mine[x][y - 1] +
    		mine[x + 1][y - 1] +
    		mine[x + 1][y] +
    		mine[x + 1][y + 1] +
    		mine[x][y + 1] +
    		mine[x - 1][y + 1] - 8 * '0';
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    方案二:循环遍历
    📝代码展示:

    int get_mine_count2(char mine[ROWS][COLS], int x, int y)
    {
    	int i = 0;
    	int count = 0;
    	for (i = -1; i <= 1; i++)
    	{
    		int j = 0;
    		for (j = -1; j <= 1; j++)
    		{
    			if (mine[x + i][y + j] == '1')
    				count++;
    		}
    	}
    	return count;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    2.扫雷流程+判断输赢

    设计思路:

    1.输入排查的坐标

    2.判断坐标合法性
    (1)合法 -程序执行下一步
    (2)不合法 -重新输入

    3.检查坐标处是不是雷
    (1)是雷 - 很遗憾炸死了 - 游戏结束
    (2)不是雷 - 统计坐标周围有几个雷 - 存储排查雷的信息到show数组,游戏继续

    程序执行过程如下图:

    在这里插入图片描述

    📝代码展示:

    注意:row*col- EASY_COUNT表示所有非雷数量,所有非雷坐标全部排查则游戏正常结束,玩家胜利。

    void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
    {   //思路:
    	//1. 输入排查的坐标
        //2、判断坐标合法性
    	//3. 检查坐标处是不是雷
    	   // (1) 是雷   - 很遗憾炸死了 - 游戏结束
    	   // (2) 不是雷  - 统计坐标周围有几个雷 - 存储排查雷的信息到show数组,游戏继续
    
    	int x = 0;
    	int y = 0;
    	int win = 0;
    
    	while (win<row*col- EASY_COUNT)
    	{
    		printf("请输入要排查的坐标:>");
    		scanf("%d%d", &x, &y);//x--(1,9)  y--(1,9)
    
    		//判断坐标的合法性
    		if (x >= 1 && x <= row && y >= 1 && y <= col)
    		{
    			if (mine[x][y] == '1')
    			{
    				printf("很遗憾,你被炸死了\n");
    				DisplayBoard(mine, row, col);
    				break;
    			}
    			else
    			{
    				//不是雷情况下,统计x,y坐标周围有几个雷
    				int count = get_mine_count(mine, x, y);
    				show[x][y] = count+'0';
    				//显示排查出的信息
    				DisplayBoard(show, row, col);
    				win++;
    			}
    		}
    		else
    		{
    			printf("坐标不合法,请重新输入\n");
    		}
    	}
    
    	if (win == row * col - EASY_COUNT)
    	{
    		printf("恭喜你,排雷成功\n");
    		DisplayBoard(mine, row, col);//向玩家展示一下布雷棋盘
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    到这里初级扫雷游戏就搭建完成了,我们来试玩一把:

    六、游戏缺陷

    1.上述代码每次排雷只展开一个坐标,排雷效率较低.

    2.没有标记功能,缺少趣味性.

    七、扫雷升级版

    显然,初级扫雷虽然能实现扫雷游戏的基本功能,但是缺少展开功能和标记功能,扫雷仿佛没了灵魂,那么我们应该如何画龙点睛,让扫雷如虎添翼,更上一层呢?下面我们就针对游戏缺陷进一步改进!

    🍑1、展开功能

    如果没有思路,我们可以先观察一下游戏中的展开过程:

    聪明的你们一定会发现:当排雷坐标周围处地雷数量为0时,棋盘会向周围展开,并且将周围雷的数量显示到屏幕上,直到坐标周围地雷数不为0​​。​

    显然,这是一个递归问题,当排雷坐标周围地雷数为0,棋盘会继续遍历周围个坐标,直到坐标周围出现雷,递归停止。

    📝代码展示:

    static void Digit_boom(char show[ROWS][COLS], char mine[ROWS][COLS], int x, int y, int row, int col, int* win)
    {
    	if (x >= 1 && x <= row && y >= 1 && y <= col)//判断坐标合法性
    	{
    		int ret = Around_num2(mine, x, y);//接受坐标周围雷的数量
    
    		if (ret == 0)//递归条件--周围雷数为0
    		{
    			(*win)++;//每排查一个坐标,排查次数加1,为判断输赢做准备
    			show[x][y] = '0';//显示周围雷数
    			int i = 0;
    			int j = 0;
    			//用两个循环遍历周围8个坐标
    			for (i = -1; i <= 1; i++)
    			{
    				for (j = -1; j <= 1; j++)
    				{
    					if (show[x + i][y + j] == '*')//递归的坐标必须是未排查过的坐标,
                                                      //防止死递归
    					{
    						Digit_boom(show, mine, x + i, y + j, row, col,win);
    					}
    				}
    			}
    		}
    		else
    		{    //条件不满足退出递归
    			
    			(*win)++;//排查坐标,次数加1
    			show[x][y] = ret + '0';//显示周围雷数
    		}
    	}
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    1、递归参数win这里为传址调用,作用为计数器,统计排雷次数,方便之后判断输赢。

    2、注意此递归有两个条件:
    条件一:if (ret == 0)为大前提,只有周围坐标为0才可能进入递归
    条件二:if(show[x+i][y+j]=='*') ,为小前提,进入递归的坐标必须是未排查过的坐标,否则可能重复排查坐标,出现死递归

    效果展示:

    在这里插入图片描述

    🍑2、标记功能

    标记功能,作用是把玩家确定的雷坐标标记出来,或者是把不确定的雷坐标标记出来。它的作用只是做记号,相比于递归展开,标记功能显然更容易实现。

    由于是做标记,我们需要再引入一个标志符号,为了不与雷盘符号*冲突,易于玩家区分,这里使用#作为标记符号。

    📝代码展示:(标记雷)

    //标记雷
    static void flag(char show[ROWS][COLS], int row, int col)
    {
    
    	while (1)
    	{
    		int x = 0;
    		int y = 0;
    		printf("请输入标记坐标(输入:0 0退出):>");
    		scanf("%d %d",&x,&y);
    		if (x >= 1 && x <= row && y >= 0 && y <= col)
    		{
    			if (show[x][y] != '#')
    			{
    				show[x][y] = '#';
    				break;
    			}
    			else
    			{
    					printf("该坐标已经标记,请重新选择\n");
    			}
    
    		}
    		else
    		{
    			printf("坐标越界,请重新输入\n");
    		}
    		if (x == y && x == 0)
    			break;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    📝代码展示:(取消标记)

    static void cancel_flag(char show[ROWS][COLS], int row, int col)
    {
    	while (1)
    	{
    		int x = 0;
    		int y = 0;
    		printf("请输入取消标记坐标(输入:0 0退出):>");
    		scanf("%d %d", &x, &y);
    		if (x >= 1 && x <= row && y >= 0 && y <= col)
    		{
    			if (show[x][y] == '#')//比较用双等号!!!
    			{
    				show[x][y] = '*';
    				break;
    			}
    			else
    			{
    				printf("该坐标还未标记,请重新选择\n");
    			}
    		}
    		else
    		{
    			printf("坐标越界,请重新输入\n");
    		}
    		if (x == y && x == 0)
    			break;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    效果展示 :

    🍑3、搭建游戏菜单

    当我们添加了这些功能后,为了能够有更好的游戏体验我们可以为这些功能添加一个菜单,如下图:

    📝代码展示:

    static void menu2()
    {
    	printf("┌-----------------------------┐\n"); 
    	printf("├**********1.排雷*************┤\n");
    	printf("├**********2.标记*************┤\n");
    	printf("├**********3.取消标记*********┤\n");
    	printf("└-----------------------------┘\n");
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    🍑4、排雷函数封装

    📝在实现了上述功能后,我们可以重新封装排雷函数FindMine()

    void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
    {
    	int choice = -1;
    	int x = 0;
    	int y = 0;
    	int win = 0;
    	while (win < col * row - Easy_Count)
    	{
    		menu2();
    		printf("请选择:>");
    		scanf("%d", &choice);
            //标记
    		if (2 == choice)
    		{
    			flag(show, ROW, COL);
    			DisplayBoard(show, ROW, COL);
    
    		}
            //取消标记
    		else if (3 == choice)
    		{
    			cancel_flag(show, ROW, COL);
    			DisplayBoard(show, ROW, COL);
    		}
            //排雷
    		else if(1==choice)
    		{
    			printf("请输入排查坐标:>");
    			scanf("%d%d", &x, &y);
    			if (x >= 0 && x <= row && y >= 0 && y <= col)
    			{
    				if (mine[x][y] == '1')
    				{
    					DisplayBoard(mine, ROW, COL);
    					printf("很遗憾,你被炸死了!\n");
    					break;	
    				}
    				else if(show[x][y]=='*')
    				{
    					Digit_boom(show, mine, x, y, row, col, &win);
    					DisplayBoard(show, row, col);
    				}
    				else
    				{
    					printf("该坐标已排查,请重新选择\n");
    				}
    
    			}
    			else
    			{
    				printf("输入坐标错误,请重新输入\n");
    			}
    		}
    		else
    		{
    			printf("选择错误,请重新选择\n");
    		}
    	}
    	if (win == col * row - Easy_Count)//非雷坐标全部排查完毕
    	{
    		DisplayBoard(mine, ROW, COL);
    		printf("恭喜你,你赢了咯!\n");
    	}
    	
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66

    最终效果展示:

    为了让扫雷界面更简洁,在每次打印雷盘前增加了清屏操作:system("cls");

    七、完整代码

    🍑1、游戏交互主体-test.c

    #define _CRT_SECURE_NO_WARNINGS 1
    #include"game.h"
    //游戏菜单
    menu()
    {
    	printf("* * * * * * * * * * * * * * * * * *\n");
    	printf("* * * * * * 1.扫雷游戏  * * * * * *\n");
    	printf("* * * * * * 0.退出游戏  * * * * * *\n");
    	printf("* * * * * * * * * * * * * * * * * *\n");
    }
    //扫雷游戏
    void game()
    {
    	//创建扫雷棋盘
    	char mine[ROWS][COLS];
    	char show[ROWS][COLS];
    	//初始化扫雷棋盘
    	Init(mine, ROWS, COLS, '0');
    	Init(show, ROWS, COLS, '*');
    	//布置雷
    	SetMine(mine, ROW, COL);
    	//打印扫雷棋盘
    	DisplayBoard(show, ROW, COL);
    	//开始扫雷并判断输赢
    	FindMine(mine, show, ROW, COL);
    
    }
    //交互界面
    int main()
    {
    	srand((unsigned int)time(NULL));
    	int input = 0;
    	do {
    		menu();
    		printf("请选择:>");
    		scanf("%d", &input);
    		switch (input)
    		{
    		case 1:
    			game();
    			break;
    		case 0:
    			printf("退出游戏\n");
    			break;
    		default:
    			printf("输入错误,请重新输入\n");
    			break;
    		}
    	} while (input);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    🍑2、声明部分-game.h

    #pragma once
    //包含头文件
    #include 
    #include 
    #include
    #include
    #include
    //宏定义
    #define ROW 9
    #define COL 9
    #define ROWS ROW+2
    #define COLS COL+2
    #define Easy_Count 10
    //初始化雷盘
    void Init(char board[ROWS][COLS],int rows,int cols,char x);
    //打印雷盘
    void DisplayBoard(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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    🍑3、函数定义-game.c

     #define _CRT_SECURE_NO_WARNINGS 1
    #include"game.h"
    //初始化雷盘
    void Init(char board[ROWS][COLS], int rows, int cols, char x)
    {
    	int i = 0;
    	for (i=0;i<rows;i++)
    	{
    		int j = 0;
    		for (j=0;j<cols;j++)
    		{
    			board[i][j] = x;
    		}
    	}
    }
    //打印雷盘
    void DisplayBoard(char board[ROWS][COLS], int row, int col)
    {
    	system("cls");
    	printf("-----扫雷游戏-----\n");
    	int i = 0;
    	int j = 0;
    	for (j = 0; j <= col; j++)//打印列号
    	{
    		printf("%d ",j);
    	}
    	printf("\n");
    
    	for (i = 1; i <= row; i++)
    	{
    		printf("%d ",i);//打印行
    		for (j = 1; j <=col; j++)
    		{
    			printf("%c ",board[i][j]);
    		}
    		printf("\n");
    	}
    }
    //布置雷
    void SetMine(char mine[ROWS][COLS], int row, int col)
    {
    	int count = Easy_Count;
    	while (count)
    	{
    		int i = rand() % row + 1;
    		int j = rand() % col + 1;
    		if (mine[i][j] == '0')
    		{
    			mine[i][j] = '1';
    			count--;
    		}
    
    	}
    }
    //显示周围雷数
    //方案一
    static int Around_num1(char mine[ROWS][COLS], int x, int y)
    {
    	return mine[x - 1][y] + mine[x - 1][y - 1] +
    		mine[x - 1][y + 1] + mine[x][y - 1] +
    		mine[x][y + 1] + mine[x + 1][y] + 
    		mine[x + 1][y - 1] + mine[x + 1][y + 1]-8*'0';
    }
    //方案二
    static int Around_num2(char mine[ROWS][COLS], int x, int y)
    {
    	int i = 0;
    	int count = 0;
    	for (i = -1; i <= 1; i++)
    	{
    		int j = 0;
    		for (j = -1; j <= 1; j++)
    		{
    			if (mine[x + i][y + j] == '1')
    				count++;
    		}
    	}
    	return count;
    }
    //标记雷
    static void flag(char show[ROWS][COLS], int row, int col)
    {
    
    	while (1)
    	{
    		int x = 0;
    		int y = 0;
    		printf("请输入标记坐标(输入:0 0退出):>");
    		scanf("%d %d",&x,&y);
    		if (x >= 1 && x <= row && y >= 0 && y <= col)
    		{
    			if (show[x][y] != '#')
    			{
    				show[x][y] = '#';
    				break;
    			}
    			else
    			{
    					printf("该坐标已经标记,请重新选择\n");
    			}
    
    		}
    		else
    		{
    			printf("坐标越界,请重新输入\n");
    		}
    		if (x == y && x == 0)
    			break;
    	}
    }
    //取消标记
    static void cancel_flag(char show[ROWS][COLS], int row, int col)
    {
    	while (1)
    	{
    		int x = 0;
    		int y = 0;
    		printf("请输入取消标记坐标(输入:0 0退出):>");
    		scanf("%d %d", &x, &y);
    		if (x >= 1 && x <= row && y >= 0 && y <= col)
    		{
    			if (show[x][y] == '#')//比较用双等号!!!
    			{
    				show[x][y] = '*';
    				break;
    			}
    			else
    			{
    				printf("该坐标还未标记,请重新选择\n");
    			}
    		}
    		else
    		{
    			printf("坐标越界,请重新输入\n");
    		}
    		if (x == y && x == 0)
    			break;
    	}
    }
    //如果排雷坐标周围雷数为零,递归展开
    static void Digit_boom(char show[ROWS][COLS], char mine[ROWS][COLS], int x, int y, int row, int col, int* win)
    {
    	if (x >= 1 && x <= row && y >= 1 && y <= col)//判断坐标合法性
    	{
    		int ret = Around_num2(mine, x, y);//接受坐标周围雷的数量
    
    		if (ret == 0)//递归条件--周围雷数为0
    		{
    			(*win)++;//每排查一个坐标,排查次数加1,为判断输赢做准备
    			show[x][y] = '0';
    			int i = 0;
    			int j = 0;
    			//用两个循环遍历周围8个坐标
    			for (i = -1; i <= 1; i++)
    			{
    				for (j = -1; j <= 1; j++)
    				{
    					if (show[x + i][y + j] == '*')//递归的坐标必须是未排查过的坐标,防止死递归
    					{
    						Digit_boom(show, mine, x + i, y + j, row, col,win);
    					}
    				}
    			}
    		}
    		else
    		{
    			//条件不满足退出递归
    			(*win)++;//排查坐标,次数加1
    			show[x][y] = ret + '0';
    		}
    	}
    
    }
    //扫雷菜单
    static void menu2()
    {
    	printf("┌-----------------------------┐\n"); 
    	printf("├**********1.排雷*************┤\n");
    	printf("├**********2.标记*************┤\n");
    	printf("├**********3.取消标记*********┤\n");
    	printf("└-----------------------------┘\n");
    
    }
    //扫雷并判断输赢
    
    void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
    {
    	int choice = -1;
    	int x = 0;
    	int y = 0;
    	int win = 0;
    	while (win < col * row - Easy_Count)
    	{
    		menu2();
    		printf("请选择:>");
    		scanf("%d", &choice);
    		if (2 == choice)
    		{
    			flag(show, ROW, COL);
    			DisplayBoard(show, ROW, COL);
    
    		}
    		else if (3 == choice)
    		{
    			cancel_flag(show, ROW, COL);
    			DisplayBoard(show, ROW, COL);
    		}
    		else if(1==choice)
    		{
    			printf("请输入排查坐标:>");
    			scanf("%d%d", &x, &y);
    			if (x >= 0 && x <= row && y >= 0 && y <= col)
    			{
    				
    				if (mine[x][y] == '1')
    				{
    					
    						
    					DisplayBoard(mine, ROW, COL);
    					printf("很遗憾,你被炸死了!\n");
    					break;
    					
    				}
    				else if(show[x][y]=='*')
    				{
    					Digit_boom(show, mine, x, y, row, col, &win);
    					DisplayBoard(show, row, col);
    				}
    				else
    				{
    					printf("该坐标已排查,请重新选择\n");
    				}
    
    			}
    			else
    			{
    				printf("输入坐标错误,请重新输入\n");
    			}
    		}
    		else
    		{
    			printf("选择错误,请重新选择\n");
    		}
    	}
    	if (win == col * row - Easy_Count)
    	{
    		DisplayBoard(mine, ROW, COL);
    		printf("恭喜你,你赢了咯!\n");
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250

    总结

    由于作者水平有限,如笔下有误,敬请留言。

    如果本文对您有所帮助,请给博主点赞👍关注🙏哦,笔者会持续更新干货教程,期待与君共勉!

  • 相关阅读:
    【定向征文活动】2023年深圳1024开发者城市聚会活动参会感想征文
    【Elasticsearch<一>✈️✈️】简单安装使用以及各种踩坑
    【实验3:RW007联网实验】
    计算机网络第七章知识点回顾(自顶向下)
    VGG网络
    【HTML——旋转火焰】(效果+代码)
    助力AR眼镜轻量化,国内攻破二维扩瞳几何光波导量产
    alibaba dragonwell jdk
    03数据结构与算法刷题之【栈】篇
    Android Studio的Java项目种运行main()的方法
  • 原文地址:https://blog.csdn.net/LEE180501/article/details/126110416