【C语言】扫雷游戏(递归实现展开一片)8000字详细教学

🧸🧸🧸各位巨佬大家好,我是猪皮兄弟
在这里插入图片描述

这里是下面要讲的知识内容🥳🥳🥳

🚒前言

    今天我们学习的内容是扫雷游戏(可炸开一片和可标记)。是不是发现现在电脑上没有系统自带的扫雷游戏,那有问题吗?没有问题,今天手把手得带大家来实现一下扫雷游戏🐷,废话不多说,我们直接开始


一、🚀头文件部分(头文件的包含和一些函数接口)

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


#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define SET_NUM 10
//初始化雷盘
void InitBoard(char board[ROWS][COLS], int rows, int cols, char init);

//随机埋雷操作
void SetMine(char board[ROWS][COLS], int row, int col, int num);

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

//排雷操作
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS],char state[ROWS][COLS]);

//数该雷块的周围八个雷块埋了多少个累
int get_mine_count(char board[ROWS][COLS], int x, int y);

//重点!!递归实现展开一片
void Boom(char show[ROWS][COLS],char mine[ROWS][COLS],char state[ROWS][COL], int x, int y);

二、🚀主函数部分(main)

首先说下为什么使用9x9的雷盘要初始化化成11x11呢?
1.首先,找周围的八个雷会简单很多。因为只在需要的9x9的二维数组里进行埋雷,所以多出来的这两行两列(雷盘上下,左右各多一行)是没有雷的,所以不影响。
2.其次,方便打印棋盘的行号和列号,如图:
在这里插入图片描述

下面是主函数和菜单的实现

void menu()
{
	printf("*******************\n");
	printf("*******0.EXIT******\n");
	printf("*******1.PLAY******\n");
	printf("*******2.RULE******\n");
	printf("*******************\n");
}
void Rule_Menu()
{
	printf("1.输入坐标进行雷的排查\n");
	printf("2.输入b进行雷的标记\n");
	printf("3.显示数字表示包括自己在内的九宫格中雷的数目\n");
	system("pause");
}
void game()
{
	char mine[ROWS][COLS] = { 0 };
	char show[ROWS][COLS] = { 0 };
	char state[ROWS][COLS] = { 0 };
	InitBoard(mine, ROWS, COLS, '0');
	InitBoard(show, ROWS, COLS, '*');
	InitBoard(state, ROWS, COLS, '0');
	SetMine(mine, ROW, COL, SET_NUM);
	DisplayBoard(show, ROW, COL);
	/*DisplayBoard(mine, ROW, COL);*/
	FindMine(mine, show,state);

}
int main()
{
	int input;
	srand((unsigned int)time(NULL));
	do
	{
		menu();
		printf("\n");
		printf("请选择:> ");
		rewind(stdin);
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("进入游戏......\n");
			game();
			break;
		case 0:
			printf("退出游戏......\n");
			exit(-1);
			break;
		case 2:
			printf("查看规则......\n");
			Rule_Menu();
			break;
		default:
			printf("输入错误,请重新输入......\n");
			break;
		}
	} while (1);
	return 0;
}

三、🚀自定义函数的实现

声明一下
show是玩家可以看到的数组
mine是用于埋雷和排雷的数组
state是用于后面递归展开时定义是否递归的状态的数组

一、​⛄初始化雷盘

void  InitBoard(char board[ROWS][COLS], int rows, int cols, char init)
{
	//char init说明你想把数组里面定义成什么内容
	for (int i = 0; i < rows; i++)
	{
		for (int j = 0; j < cols; j++)
		{
			board[i][j] = init;
		}
	}
}

二、⛄随机埋雷

注意:要使用rand函数,要在主函数中加入
srand(unsigned int)time(NULL);

     注意srand写的位置,你比如说要写一个才随机数字大小的游戏,那么久只能写在主函数里,不然每次猜数字都会生成一个随机值,这不是拿阳寿猜答案嘛。

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

三、⛄打印雷盘

void DisplayBoard(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");
	}
}

效果展示
在这里插入图片描述


四、⛄获取周围雷的数目

这里需要说一下字符和数字之间的相互转换
在这里插入图片描述

int get_mine_count(char board[ROWS][COLS], int x, int y)
{
	return(board[x - 1][y] +
			board[x - 1][y - 1] +
			board[x][y - 1] +
			board[x + 1][y - 1] +
			board[x + 1][y] +
			board[x + 1][y + 1] +
			board[x][y + 1] +
			board[x - 1][y + 1] 
			-8 * '0');

}

五、⛄自定义Boom函数递归实现炸开一片(重点!)

注意!!!
如果不进行任何条件的约束,只是往周围进行递归的话,就会死循环

这是哇网页版的扫雷,我们来看卡看它的边界条件,是不是遇到数字就停止这一块雷块的递归,好,我们下面来模仿实现一下在这里插入图片描述

蓝色圈代表排雷点,黑色线代表向周围八个展开,如下图,演示了展开方向在这里插入图片描述

     大家想象一下,约束条件也就当某一个雷块周围有雷时,会调用get_mine_count函数数周围的雷,然后在show数组中会显示出来,当遇到这一块显示数字了的,就停止递归,而且不能超过边界,也就是说x>=1&&x<=ROW;y>=1&&y<=COL

下面是对Boom的实现

void Boom(char show[ROWS][COLS], char mine[ROWS][COLS], char state[ROWS][COLS],int x, int y)

{
	state[x][y] = '1';
	if (x<1 || x>ROW || y<1 || y>COL)
		return;
	if (show[x][y] != ' ')
		return;
	else
	{
		/*if (get_mine_count(mine, x, y) == 0)
			show[x][y] = ' ';
		else
			show[x][y] = get_mine_count(mine, x, y) + '0';*/
			//将周围8个先show出来,然后递归boom
		if (get_mine_count(mine, x - 1, y) == 0)
			show[x - 1][y] = ' ';
		else
			show[x - 1][y] = get_mine_count(mine, x - 1, y) + '0';
		if (get_mine_count(mine, x - 1, y - 1) == 0)
			show[x - 1][y - 1] = ' ';
		else
			show[x - 1][y - 1] = get_mine_count(mine, x - 1, y - 1) + '0';
		if (get_mine_count(mine, x, y - 1) == 0)
			show[x][y - 1] = ' ';
		else
			show[x][y - 1] = get_mine_count(mine, x, y - 1) + '0';
		if (get_mine_count(mine, x + 1, y - 1) == 0)
			show[x + 1][y - 1] = ' ';
		else
			show[x + 1][y - 1] = get_mine_count(mine, x + 1, y - 1) + '0';
		if (get_mine_count(mine, x + 1, y) == 0)
			show[x + 1][y] = ' ';
		else
			show[x + 1][y] = get_mine_count(mine, x + 1, y) + '0';
		if (get_mine_count(mine, x + 1, y + 1) == 0)
			show[x + 1][y + 1] = ' ';
		else
			show[x + 1][y + 1] = get_mine_count(mine, x + 1, y + 1) + '0';

		if (get_mine_count(mine, x, y + 1) == 0)
			show[x][y + 1] = ' ';
		else
			show[x][y + 1] = get_mine_count(mine, x, y + 1) + '0';
		if (get_mine_count(mine, x - 1, y + 1) == 0)
			show[x - 1][y + 1] = ' ';
		else
			show[x - 1][y + 1] = get_mine_count(mine, x - 1, y + 1) + '0';

	
		if(show[x-1][y]==' '&& state[x - 1][y] != '1')
		Boom(show, mine,state, x - 1, y);
		if (show[x - 1][y-1] == ' ' && state[x - 1][y-1] != '1')
		Boom(show, mine,state, x - 1, y - 1);
		if (show[x][y-1] == ' ' && state[x ][y-1] != '1')
		Boom(show, mine, state,x, y - 1);
		if (show[x + 1][y-1] == ' ' && state[x + 1][y-1] != '1')
		Boom(show, mine,state, x + 1, y - 1);
		if (show[x + 1][y] == ' '&& state[x + 1][y] != '1')
		Boom(show, mine,state, x + 1, y);
		if (show[x + 1][y+1] == ' '&& state[x + 1][y+1] != '1')
		Boom(show, mine,state, x + 1, y + 1);
		if (show[x][y+1] == ' '&& state[x][y+1] != '1')
		Boom(show, mine, state,x, y + 1);
		if (show[x - 1][y+1] == ' '&& state[x - 1][y+1] != '1')
		Boom(show, mine,state ,x - 1, y + 1);
	}
}

特别注意,不能够去递归 递归过的雷块,因为我弟归你,你递归我,我再递归你,是不是就死循环了啊,所以我们重新定义了一个state数组表示这一块是否被递归过。


六、⛄排雷和标记雷

     这里用到的排雷的思想是,有雷的雷块在mine数组上是1.遇到1则炸死了,打印mine数组(让玩家死得瞑目),这里会用到Boom函数进行递归,可以先看看上面讲得递归


void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS],char state[ROWS][COLS])
{
	int x, y;
	int input;
	while (1)
	{
		printf("1.排雷\n");
		printf("2.标记雷\n");
		printf("3.取消标记\n");
		printf("请输入操作选项:>");
		scanf("%d",&input);
		switch (input)
		{
		case 1:
				{printf("请输入排雷坐标:>");
					rewind(stdin);
					scanf("%d", &x);
					scanf("%d", &y);
					if (x >= 1 && x <= ROW && y >= 1 && y <= COL)
					{
						if (show[x][y] != '*')
						{
							printf("该坐标已查找过,请重新输入.....\n");
						}
						else
						{
							if (mine[x][y] == '1')
							{
								printf("你被炸死了,loser\n");
								DisplayBoard(mine, ROW, COL);
								system("pause");
								exit(-1);
							}
							else
							{
								int count = get_mine_count(mine, x, y);
								if (count == 0)
								{
									show[x][y] = ' ';
									Boom(show, mine, state, x, y);
									DisplayBoard(show, ROW, COL);
								}
								else
								{
									show[x][y] = count + '0';
									
									DisplayBoard(show, ROW, COL);
								}
							}
						}
					}
					else
					{
						printf("error position\n");
					}
					break;
				}
		case 2:
		{
			printf("请输入标记雷坐标;>");
			rewind(stdin);
			scanf("%d", &x);
			scanf("%d", &y);
			show[x][y] = '#';
			DisplayBoard(show, ROW, COL);
			break;
		}
		case 3:
		{
			printf("请输入取消标记坐标;>");
			rewind(stdin);
			scanf("%d", &x);
			scanf("%d", &y);
			show[x][y] = '*';
			DisplayBoard(show, ROW, COL);
			break;
		}
		default:
			printf("输入错误,请重新输入.....\n");
			break;
		}
		

	}

}

好了,今天的扫雷游戏就到这里,感谢大家对猪皮的支持!!!


、🚀

        当你看到这里,相信上面的内容已经倒背如流了吧😶‍🌫️😶‍🌫️😶‍🌫️。各位巨佬如果觉得有帮助的话,还望各位父老乡亲动动小手指👈👇👉点点点。一键三连有问题吗?没有问题,这都是什么?人情世故
在这里插入图片描述

  • 62
    点赞
  • 44
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 47
    评论
评论 47
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猪皮兄弟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值