扫雷 - 并不精简的精简剖析

前言:本人为C语言初学者,学识尚浅,研究程度存在很大的局限性,眼界很窄。以下所有观点仅代表个人见解和思路,各位游刃有余的前辈可以给予批评和指正!各位与鄙人同路的学子可相互探讨、发表看法,交换观点!

注意:本篇以上篇 《三子棋 - 全思路剖析》 为基础而作,因其过程具有很大的相似性,一些思考方式也大同小异,所以本篇篇幅相对较小(确实少了一丢丢),在三子棋的基础上可以对本篇可以有快速理解。若想明晰逐过程分析,请先移步:https://blog.csdn.net/Thepale2022/article/details/126222228?spm=1001.2014.3001.5501

整体框架

1.main.c - 主函数功能实现,负责调用和游戏逻辑实现

2.game.c - 游戏功能实现函数

3.game.h - 函数的声明、头文件的包括、定义常量等

MAIN.C

首先,如果大伙儿玩过扫雷,应该多少对扫雷有一些印象,空间是 x * y 格的:

 这一次,我们以 9 * 9 的空间为例,来讲解扫雷。

对于C语言来说,我们必须先给用户交互选项,所以我们还是照常要打印一个菜单,并且实现能让用户反复游玩游戏的功能:

#define _CRT_SECURE_NO_WARNINGS 1

#include <stdio.h>

void menu()
{
	printf("*********************\n");
	printf("****    1.play   ****\n");
	printf("****    0.exit   ****\n");
	printf("*********************\n");
}

int main()
{
	int input = 0;

	do
	{
		menu();
		printf("请输入您的选择:");
		scanf("%d", &input);

		switch (input)
		{
		case 1:
			printf("扫雷游戏开始!\n");
			break;

		case 0:
			printf("游戏退出!\n");
			break;
		default:
			printf("数字输入非法,请重新输入!\n");
			break;
		}
	} while (input);
	
	return 0;
}

 这一步和三子棋是一模一样的。

然后,我们如果要让用户扫雷,那必须有数组来存储雷的信息和非雷的信息,毋庸置疑,我们肯定是用一个二维数组来搞定。于是很多小伙伴直接就咬定了:char arr[9][9]; 但其实如果我们稍加思考,扫雷每点一下,会提示周围一圈有几个雷,所以我们必然有一个函数负责遍历周围一圈的数据。如果刚好用户选择了顶点坐标,那么必然会出现数组越界,于是,为了避免这一情况发生,我们不如把数组整个扩大一圈,避免越界的问题。如果你无法理解,请见图:

 所以我们应该定义这样的数组:char arr[11][11] 当然,为了保证通用性,这里的11会用define定义,这是为了代码的通用性

这个游戏对于用户而言,一开始的信息全是未知的,换句话说,我们给用户看到的数组和我们实际布雷的数组肯定是不一样的,为了方便,我们可以开辟两个数组一个用来存放雷的信息(mine)一个用来存放用户看到的信息(show),那么我扫雷的时候,在mine中读取信息,然后反馈到show中,只要把这两个数组关联,这个问题就解决了。

所以一旦开始游戏,我们就应该定义两个数组,这里我用0当作无雷,用1当作雷,用户展示界面用*代替。当然,这里完全是自定义的,你想改成什么就改成什么。

细心的小伙伴可能发现,我之前定义数组的时候就制定了char类型,char类型可以直接存储我要展示的字符,你总不能用int存储 * 然后再强制转换为char类型输入吧....(如果你有这闲工夫,当我没说)。我们完全可以先看一眼效果:

 我们打印出来的肯定是 9 * 9 的格子,11 * 11是我们后台所制定的一种规定,不需要用户可以看到。所以,说到这儿,常量起码要定义4个了...(其实是5个)

打印数组,先初始化,这样,第一个函数终于要来了:初始化数组函数

GAME.C

初始化数组函数,不需要返回值,只是初始化就好了,所以定义是这样的:

void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);

(偷偷说一句,这里我已经在game.h里定义了如下内容:)

#include <stdio.h>

#define ROWS 11
#define COLS 11

#define ROW 9
#define COL 9

#define MINE 10

这里我传过去的数组是11*11的数组,初始化肯定是每个都要初始化,这和我把雷设置成1把非雷设置成0是相互应和的,我把数组全初始化为‘0’,只对中间的9*9布雷,这样我在检索顶点坐标时,周围伪数组越界的部分默认判断是没有雷的,懂了不~

初始化也就是两个for循环,这里不赘述了:

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

初始化完成,我得打印出来瞅瞅,但是这里注意,我初始化的是11*11的,但是我要打印的实际上是9*9的,这里的函数应该这样声明:

void DisplayBoard(char board[ROWS][COLS], int row, int col);

这里传过来的数组是11*11的,但是我们也把要打印的行列信息9*9传过来了,为了方便打印:

void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
	int i = 0, j = 0;
	for (i = 1; i <= row; i++)
	{
		for (j = 1; j <= col; j++)
		{
			printf("%c ", board[i][j]);
		}
		putchar('\n');
	}
}

这样打印出来的结果实在是令人唏嘘......

我们少了哪些东西?对比成品:1.分割线 2.行号列号

两者实现都非常简单,我不赘述了,直接看代码:

void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
	int i = 0, j = 0;
	printf("------扫雷游戏------\n");
	for (j = 0; j <= row; j++) //打印列号
	{
		printf("%d ", j);
	}
	putchar('\n');

	for (i = 1; i <= row; i++)
	{
		printf("%d ", i); //打印行号
		for (j = 1; j <= col; j++)
		{
			printf("%c ", board[i][j]);
		}
		putchar('\n');
	}
}

 这样就舒服多了:

下一步,我们需要在mine区布雷,雷肯定是随机的,所以我们这里又要取随机值,srand((unsigned int)time(NULL));放在主函数里,然后用rand取随机值,这个我就不多说了,以及game.h里又要来几个新伙伴:

#include <stdlib.h>
#include <time.h>

设置雷函数,实际上是改变数组数据,也不需要返回值,且实际操作的是9*9空间:

void SetMine(char board[ROWS][COLS], int row, int col);

如何让随机数取到1~9之间的值呢?这里可以先思考一下再看代码:

void SetMine(char board[ROWS][COLS], int row, int col)
{
	int count = MINE; //记录雷数,布一个减一个

	while (count != 0)
	{
		int x = rand() % row + 1; //行数是1~9
		int y = rand() % col + 1; //列数是1~9

		if (board[x][y] == '0')
		{
			board[x][y] = '1';
				count--;
		}
	}
}

这样就可以随机布置10个雷辣!

 

 

最关键的时刻要来了...

我们需要实现的功能是:用户给我们一个坐标,然后我们判断这个坐标是否是雷,如果是雷,则游戏结束。如果不是雷,我们需要计算这个坐标周围八个坐标的雷数然后反馈到show数组里打印一次告诉用户周围有几个雷,且游戏继续。

中间还有些许细节问题:

1.用户坐标输入必须合法

2.判断扫雷次数,达到最大row*col-MINE要结束游戏并判断用户胜利

所以这个函数肯定是要挂钩两个数组的,由于判断8个坐标的部分比较繁琐,我们可以单独拿出来实现,所以这个函数也可以定义成void,如果我们把这个函数定义成int,那相关判断就要在主函数内了,不是很方便!在此函数内部实现最好!

void FindMine (char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);

void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	while (1)
	{
		int x = 0;
		int y = 0;
		int ret = 0;
		printf("请输入坐标:");
		scanf("%d %d", &x, &y);

		if (x >= 1 && x <= 9 && y >= 1 && y <= 9) //检测坐标合法性
		{
			if (mine[x][y] != '1') //判断该坐标是不是雷
			{
				ret = Search(mine, x, y);
				show[x][y] = ret + '0';
				DisplayBoard(show, row, col);
			}
			else
			{
				printf("您踩到了雷!游戏结束!\n");
				DisplayBoard(mine, row, col);
                break;
			}
		}
		else
		{
			printf("坐标输入非法!请重新输入!\n");
		}
	}
}

这里定义了Search函数来负责遍历8个坐标,方法很多,你甚至可以用8个if来判断,但是我这里用了for循环遍历,仅供大家参考的一种思路:

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

	return sum - 9 * '0';
}

最后只剩输赢判断了:

void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	int win = row * col - MINE;
	while (win > 0)
	{
		int x = 0;
		int y = 0;
		int ret = 0;
		printf("请输入坐标:");
		scanf("%d %d", &x, &y);

		if (x >= 1 && x <= 9 && y >= 1 && y <= 9) //检测坐标合法性
		{
			if (mine[x][y] != '1') //判断该坐标是不是雷
			{
				ret = Search(mine, x, y);
				show[x][y] = ret + '0';
				DisplayBoard(show, row, col);
				win--;
			}
			else
			{
				printf("您踩到了雷!游戏结束!\n");
				DisplayBoard(mine, row, col);
                break;
			}
		}
		else
		{
			printf("坐标输入非法!请重新输入!\n");
		}
	}
	if (win == 0)
	{
		printf("恭喜您!获胜了!\n");
		DisplayBoard(mine, row, col);
	}
}

改掉while的循环条件,加上一个变量记录我们行走的步数,走完最后一步的时候自然就获胜了!

最后加上了if判断,其实是为了防止踩到雷游戏结束跳出的时候触发获胜的判断

MAIN.C

main.c :终于想起我了吗?

现在是main中的逻辑实现环节

首先,我们定义数组然后初始化是毋庸置疑的;然后,只需要打印出show数组,mine数组对于用户来讲必须是隐藏的;其次,调用一次FindMine函数即可,因为循环已经在函数内部实现了;然后,然后什么,然后写完了!(突然感觉在写论证思路题)

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

	DisplayBoard(show, ROW, COL);

	SetMine(mine, ROW, COL);

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

其实还有很多功能可以加装:比如每次要求输入坐标的时候给一个选择,是否标记雷,可以让程序具有标记雷功能(就是在show填一个其他的符号);或者如果用户走的第一步就是雷,能不能尝试改变这个雷的位置不要让用户一开始就死呢?很多可以优化的地方,大家可以打开脑洞勇敢尝试!

本代码地址:https://gitee.com/Thepale2022/c-for-thinking/tree/master/%E6%89%AB%E9%9B%B7%20-%20%E5%8D%9A%E5%AE%A2%E6%90%AD%E5%BB%BA%E7%89%88

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Thepale2022

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

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

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

打赏作者

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

抵扣说明:

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

余额充值