(C语言)扫雷-递归与可调

前言

这是我写的第二个小游戏,前一个:
https://blog.csdn.net/m0_71914032/article/details/131730775?spm=1001.2014.3001.5501

二者都是棋盘格式的小游戏,有很多地方相同,诸如二维数组的运用以及棋盘表格的打印,这里就不多赘述了。

#1 结构体变量

对于棋盘的每一个格子,都需要储存独属于自身的数据,包括:
1.该格子是否有雷
2.该格子周围八个格子有多少雷
3.该格子是否被探查过

自带的数据类型不足以满足我们的需求,于是选用结构体类型。

typedef struct Grid
{
	int mine_or_not;
	int num_of_mine_around;
	int search_or_not;
}Grid;

实际使用中几乎可以把结构体类型当作一个普通的类型,便于理解与使用。

#2 自定义常量

为了提升代码的灵活性和可调性,使用自定义常量。

#define ROW 9
#define COL 9
#define Mine 10

这样做的好处是可以方便的任意修改棋盘的大小,以及雷的数量。
只需在此处修改数字,方便快捷。

#3 所需函数

#3.1 棋盘初始化 InitBoard;

代码如下:

void InitBoard(Grid Board[ROW][COL])
{
	int i = 0;
	for (i = 0; i < ROW; i++)
	{
		int j = 0;
		for (j = 0; j < COL; j++)
		{
			Board[i][j].mine_or_not = 0;
			Board[i][j].num_of_mine_around = 0;
			Board[i][j].search_or_not = 0;
		}
	}
}

将结构体内三个成员初始化为 0 ;
意味着一开始任何一个格子,都没有安放雷,周围八个格子都没有雷,以及都没有被探查过。

#3.2 随机生成雷 PlantMine;

代码如下:

void PlantMine(Grid Board[ROW][COL])
{
	int i = 0;
	int j = 0;
	int num = Mine;
	while (num)
	{
		i = rand() % 9;
		j = rand() % 9;
		if (Board[i][j].mine_or_not == 0)
		{
			Board[i][j].mine_or_not = 1;
			// 上边
			if (i - 1 >= 0)
			{
				Board[i - 1][j].num_of_mine_around += 1;
				// 左上角
				if (j - 1 >= 0)
				{
					Board[i - 1][j - 1].num_of_mine_around += 1;
				}
				// 右上角
				if (j + 1 <= 8)
				{
					Board[i - 1][j + 1].num_of_mine_around += 1;
				}
			}
			// 下边
			if (i + 1 <= 8)
			{
				Board[i + 1][j].num_of_mine_around += 1;
				// 左下角
				if (j - 1 >= 0)
				{
					Board[i + 1][j - 1].num_of_mine_around += 1;
				}
				// 右下角
				if (j + 1 <= 8)
				{
					Board[i + 1][j + 1].num_of_mine_around += 1;
				}
			}
			//左边
			if (j - 1 >= 0)
			{
				Board[i][j - 1].num_of_mine_around += 1;
			}
			//右边
			if (j + 1 <= 8)
			{
				Board[i][j + 1].num_of_mine_around += 1;
			}
			num--;
		}
	}
}

此处不单单只是修改格子的 mine_or_not 至 1,表示该格子有雷;
还有顺便修改周围八个格子的 num_of_mine_around;
虽然麻烦,但是这一步是为了后续的方便。

#3.3 打印棋盘 ShowBoard;

代码如下:

void ShowBoard(Grid Board[ROW][COL])
{
	// 第一行数字和分隔符
	int i = 0;
	for (i = 0; i <= ROW; i++)
	{
		printf(" %d ", i);
		if (i < COL)
		{
			printf("|");
		}
	}
	printf("\n");
	for (i = 0; i <= ROW; i++)
	{
		printf("---");
		if (i < COL)
		{
			printf("|");
		}
	}
	printf("\n");
	// 打印棋盘主体
	for (i = 0; i < ROW; i++)
	{
		// 打印第一列数字
		printf(" %d |", i + 1);

		int j = 0;
		for (j = 0; j < COL; j++)
		{
			//如果该格子未被探查过,则打印空格
			if (Board[i][j].search_or_not == 0)
			{
				printf("   ");
			}
			else
			{
				//若该格子被探查过,而且不是雷,就打印出该格子周围八格的雷的数量
				if (Board[i][j].mine_or_not == 0)
				{
					printf(" %d ", Board[i][j].num_of_mine_around);
				}
				else//否则说明踩到雷了,打印 ! 示意一下
				{
					printf(" ! ");
				}
			}
			//分隔符
			if (j < COL - 1)
			{
				printf("|");
			}
		}
		printf("\n");
		if (i < ROW - 1)
		{
			int j = 0;
			for (j = 0; j <= COL; j++)
			{
				printf("---");
				if (j < COL)
				{
					printf("|");
				}
			}
			printf("\n");
		}
	}
}

该函数仅仅只是打印棋盘。
即根据每个格子目前的情况(是否被探查,探查后是否有雷)来打印棋盘。
并不对格子内的数据做修改。
修改部分放在下一个函数。

#3.4 探查坐标 SearchMine;

void SearchMine(Grid Board[ROW][COL], int row, int col, int* pcount)
{
	//这个if语句用于判断所想要探查的坐标符不符合棋盘要求
	//若不符合,相当于不执行该函数
	if (row >= 1 && row <= 9 && col >= 1 && col <= 9)
	{
		//探查到已经探查过的格子时,直接返回
		//如果没有这一步,将会导致死循环
		if (Board[row - 1][col - 1].search_or_not == 1)
		{
			return;
		}
		else
		{
			//对于没有探查过的格子,先标记为探查过
			Board[row - 1][col - 1].search_or_not = 1;
			//如果没有雷,说明成功的探查到一个安全的格子,让count--
			if (Board[row - 1][col - 1].mine_or_not == 0)
			{
				(*pcount)--;
			}
			else
			//否则说明踩到雷了,修改count为 0,并且结束函数
			{
				*pcount = 0;
				return;
			}
		}
		//我们知道扫雷游戏种。如果你点到了周围没有雷的格子,会自动把相邻所有没有雷的格子全部揭示。
		//接下来实现这一步
		//这部分用到递归的思想,对于没有雷的格子,如果该格子周围一个雷都没有,则将周围八格全部探查
		if (Board[row - 1][col - 1].num_of_mine_around == 0)
		{
			//这里不再需要考虑周围八格会不会越界,因为函数开头会检测
			SearchMine(Board, row - 1, col - 1, pcount);
			SearchMine(Board, row - 1, col, pcount);
			SearchMine(Board, row - 1,col + 1, pcount);

			SearchMine(Board, row,col - 1, pcount);
			SearchMine(Board, row,col + 1, pcount);

			SearchMine(Board, row + 1,col - 1, pcount);
			SearchMine(Board, row + 1,col, pcount);
			SearchMine(Board, row + 1,col + 1, pcount);
		}
	}
}

在该函数中,如果踩到雷了,count的值就会为 0;
反之,count的值将会-1,直到与雷的数量相等;
这两种count的最终值,用于区分胜负。

#3.5 胜负判断 Win_or_Lose;

游戏结束有以下两种情况:

#3.5.1 踩到雷了,游戏失败

当count = 0 的时候,说明此时游戏结束是因为踩到雷了;

#3.5.2 探查完所有安全的格子,扫雷成功

当count = Mine 的时候,说明此时游戏结束是因为所有雷的格子都已经被确定了

代码如下:

void Win_or_Lose(int* pcount)
{
	if (*pcount == 0)
	{
		printf("你踩到雷啦,游戏结束\n");
		exit(0);
	}
	if (*pcount == Mine)
	{
		printf("恭喜你,扫雷成功!\n");
		exit(0);
	}
}

这个函数实际上可以作为不一个函数存在,能够直接加在打印棋盘的函数最后;
但是为了每个函数功能分明单一,所以单独把这部分代码摘出来,另写一个函数。

结尾

总的来说,我认为扫雷游戏的难度是在N子棋之上的,前提是N子棋不涉及电脑下棋时的智能提升。
扫雷游戏的编写,用到了结构体二维数组,深度优先的递归探查等等没那么初级的知识点,
这些方面的要求自然是比N子棋高。

不过实际上我在编写扫雷代码上花的时间却比N子棋更少,这点还是让我觉得满欣慰的。
再接再厉。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值