用C语言基础写出控制台恶搞版的扫雷游戏【详解+源码】

​​​​​​​
今天用C语言基础写一个控制台的扫雷游戏,我将用通俗的语言来表达,希望各位读者能看懂,看过我上个博客的就知道了我将用同样的方式来恶搞你们的朋友,还是记得一定要用你朋友的电脑呦~,同样如果有什么错误和改正希望大佬们能给我指出来,非常感谢大家的反馈!!!

一.扫雷游戏功能的实现

我们自制这个扫雷游戏,想要实现什么功能呢?

1.使用控制台实现经典的扫雷游戏

2.通过菜单实现是否继续玩或者退出游戏

3.制作一个棋盘,大小是9×9的格子

4.默认随机布置10个雷

5.排查雷,如果不是雷就显示该位置周围有几个雷,显示的是这个位置周围8个位置的雷的数量,如果是雷就被炸死游戏结束,或者把10个雷都找出来成功排雷游戏结束


二.扫雷游戏的三个文件

在这里插入图片描述

test.c:为主函数,扫雷游戏主逻辑框架,进行函数调用的时候需要包括头文件,这个头文件就是我们放函数声明那个头文件,就是game.h文件,由于是我们自己写的头文件需要用双引号包含,形式为:#include"game.h"

game.c:这个通常放的是函数的定义,存放的是扫雷游戏各种实现游戏功能所需要的函数。

game.h:我们通常存放的是扫雷游戏需要的各种类型的全局变量,还有函数调用的函数声明

只要该文件包含了头文件,这样test.c文件调用game.c文件中的代码才能运行,同样这两个文件都可以用头文件所含的全局变量。

这样做有什么好处呢?

1.方便协助。

2.模块化,代码可以复用。

3.一定程度上对代码进行隐藏。

4.可以使主函数写出的代码简洁逻辑清晰。

但是也要注意这是多文件编译,有的编译环境是默认不支持这种编译的


三.扫雷游戏的基本逻辑框架

1.打印菜单

我们将用封装一个menu()函数来打印菜单

在这里插入图片描述

2.do while循环语句和switch分支语句

在这里插入图片描述

我们用了do while循环语句,至少让游戏用户进来一次选择是否玩游戏,用了switch语句,当用户输入1,游戏开始,输入0,跳出循环退出游戏,输入其他的值,就要重新选择,同样我们很好的用这个input输入的值来作为判断do well循环的条件是否结束游戏不再重新选择。

运行一下

在这里插入图片描述
OK,当完成这个基本游戏逻辑框架之后,我们就要写扫雷游戏的过程了,我用了一个game()函数来完成扫雷游戏逻辑过程的实现。


四.扫雷游戏逻辑过程的实现

1.初始化数组

我们将用到两个数组来制作我们的棋盘,先用一个数组用来存放布置好的雷的信息,再用一个数组用来存放排查出的雷的信息。
在这里插入图片描述

但是这个时候就要问了,为什么我要用字符类型数组呢?

是为了方便显示我们的内容,如果都用数字的话,我们用1表示有雷,0表示没有雷,当确定一个位置,它统计的是它周围8个位置雷的个数,所以这个数到底是雷还是个数会有歧义,会发生混乱,所以我们用了字符数组来表示我们的内容,那到底用什么来表示呢?接着往下看就行了。

所以用mine数组来布置雷信息,show数组来存放排查出雷的信息,都先将这两个数组进行初始化为零。

可是我们又要问了,为什么是11×11的棋盘呢?我们不是要做9×9的棋盘吗?

这个时候我们就要考虑我们排查一个位置的时候,当它显示的它周围8个位置雷的个数如果是9×9棋盘边缘的位置该怎么办呢?,那就没有8个位置,就发生越界了,所以为了解决这种情况,我们多加了一圈成了11×11的棋盘,为了不影响我们的目的,把多加的一圈位置全部放没有雷就可以解决这个问题了。

我们想要实现的内容:在没有排查雷之前,这个棋盘显示的内容是全部星号,在没有布置雷的位置全部显示字符零,有雷的位置显示字符一

解释完前面之后,这个时候我们就要真正的初始化数组了

同样我们再封装一个函数InitBoard来初始化我们的两个数组,这两个数组分别放字符零和字符星,这时候我们就要同时用到三个文件来进行操作了。

test.c和game.c

在这里插入图片描述

这时候就要问了,本来括号里面的应该是11的数字,为什么要换成ROWS和COLS呢?

这是为了如果我们想要调节扫雷游戏难度棋盘大小时更方便,只需要改变一个地方就行了,其他地方用变量进行表示, 如果想要棋盘大小则改变ROW和COL这两个数整个游戏过程的数都会发生改变,会比较方便,不用一个一个去改。

我们是用game.h文件来进行函数声明和存放全局变量如:ROWS和COLS。

在这里插入图片描述

同样我们可以看到我们调用初始化数组的函数声明在game.h上,所以如果test.c和game.c想用的话,都得包含game.h头文件,就是:#include “game.h”,这时候定义的ROWS和COLS都可以应用的test.c文件和game.c文件中了,尤其是test.c文件想要调用game.c中的函数过程就要包含它的头文件:#include "game.h"也就是函数的声明。

还是那些东西test.c是逻辑主体用函数调用,game.c是函数的实现, game.h是函数的声明
这三个文件一起用就介绍到这里,下面操作就不再介绍了。

2.打印棋盘

在这里插入图片描述

首先我打印了两个棋盘,但是,为什么这时候行和列就变成了九呢?

因为我们雷是放在9×9的棋盘中的我们只关心中间这部分内容,排查雷的信息也是放这中间的,我们不在乎周围多加的那一圈只要不放雷就行了。

第1个棋盘实际上是用来测试的,现在并没有什么真正的作用,它是用来存放布置好的雷的信息,所以不测试把它注释掉就行了。

第2个打印的棋盘就是我们未排雷初始化显示的内容,全部为星号,是开始进入游戏所显示的内容。

在这里插入图片描述

从上面可以看出,我们也做了一些优化,显示了它的行和列号

打印棋盘函数声明

在这里插入图片描述

其实这时候我们还不需要打印它,可以先把它注释掉,之后我们要开始布置雷了。

3.布置雷

Setmine函数的实现

在这里插入图片描述

这个count就是我们布置雷的个数,x和y是生成随机数的下标也就是雷位置的坐标,怎么生成随机数的就不再讲了,前一个博客已经说过了。

x和y生成的是1~9的随机数,因为雷是布置在9×9的棋盘中的,把count作为while语句的条件,每次布置一个count减一次,雷布置完成之后跳出循环。

函数声明

在这里插入图片描述

这时候我们要进行测试,需要把布置雷的棋盘打印出来

运行一下

在这里插入图片描述

可以看出每次玩扫雷游戏布置雷的位置是不同的,这就是我们想要的结果。

主函数

在这里插入图片描述

这个时候看主函数,我们可以看到打印棋盘全部被注释掉了,它的作用只是用来铺垫的,并不是真正用到那个位置的,同时我们可以看到在布置雷的时候只打印show棋盘,作用就是玩游戏初未排查雷的时候显示的棋盘内容。

并且show数组是9×9棋盘而不是11×11棋盘,这才是我们想要打印出来的内容。

4.排查雷

同样封装成一个函数Finemine,在test.c中调用,game.c实现,game.h声明。

我们关心的还是九行九列的中间部分打印出来的棋盘,虽然我们只访问9×9的格子,但是这个数组还是11×11的棋盘,所以函数声明和函数实现的时候数组行和列还是11×11。

Finemine函数的实现

在这里插入图片描述

我们使用了while循环语句,通过输入位置坐标来判断该棋盘位置是否有雷,同时规定了坐标的大小,如果输入的坐标在x和y规定数之内,该位置如果是1那就是雷被炸死,跳出循环,如果不是雷是就要统计周围8个位置雷的个数,若xy坐标输入错误会让用户重新进行选择。

那这时候就要问了怎么统计该位置周围内的个数?

这时候我们再封装成一个GetMineCount函数来计算雷的个数。

5.统计雷的个数

首先我们看这个位置坐标周围的坐标关系
在这里插入图片描述

把它们周围坐标加起来就是雷的个数,但是我们数组放的是字符,不是数字
在这里插入图片描述

所以相加起来不会得到我们想要的结果,但是我们可以根据字符的ASCII码值找出规律。

‘0’ — 48
‘0’ - ‘0’ == 0
‘1’ - ‘0’ == 49 - 48 == 1

由此我们可以看出,当每一个坐标都减去一个字符零就可以转化成数字的形式 0和1 ,这样安排真是奇妙啊,这样加起来就是我们想要的结果了。

GetMineCount函数的实现

在这里插入图片描述

把8个位置都加起来,然后减去字符零乘以8,这个就是我们想要的数字雷的个数了。

之后我们再看,因为show数组才是我们显示是否有雷或者该位置没有雷,但是要显示它周围8个位置有多少雷的个数,所以我们输入该坐标的位置算出的雷的个数要放在show数组和mine数组同样的位置上
但是show数组它也是字符数组不能放数字,所以我们把算出来的数再转化成字符放进去才可以。

那该怎么做呢?

在这里插入图片描述

把算出来的数加上一个字符0,就可以存放到show数组上了,这是为什么呢?

这设计的就很巧妙了,因为字符零和字符1它俩的ASCII码值相差一,字符零的ASCII码值,加上数字一就变成了字符一,加上2就变成了字符2,所以我们就可以放到show数组上了,来打印show数组看我们排查雷的情况

这时候我们把mine数组布置雷的情况也打印出来验证我们show数组排查出雷情况显示的内容看对不对

测试运行一下

在这里插入图片描述

通过我们有答案找结果来看,上面mine数组棋盘坐标x=5,y=2该坐标周围确实是两个雷,然后show数组棋盘显示该位置周围有两个雷,证明了我们做对了,其实这时候我们就可以做游戏了。

但是我们要注意,我们这个游戏是不会停止的,因为我们While循环判断条件为1,那该怎么设置while的判断条件呢?

while循环语句判断条件

在这里插入图片描述

从上面看,我们设置了一个变量win,判断条件中row乘col-10,表示的是行乘列减去雷的个数,又因为我们是9×9棋盘,意思就是除了那10个有雷的位置我们只要排查出71个位置然后就可以跳出循环。

那我们该怎么改变雷的个数呢?

6.改变雷的个数

在这里插入图片描述

在上面中我们可以看出,我们再次在game.c头文件中定义一个EASY_COUNT来作为雷的个数,把其他文件中的雷的个数全部换成EASY_COUNT,如果想要改变雷的个数的话,就只改变game.h中的一个数就行了,方便了很多。

运行一下
在这里插入图片描述

最后我们来验证一下是否能通关,我们可以设置80个雷,只有一个位置是正确的,然后还是根据看答案找结果,看是否能通关。

在这里插入图片描述

OK,我们用c语言设计的控制台扫雷游戏就算结束了,你学会了吗?

以后等有机会也可以根据递归来给大家写比较完整的扫雷游戏,敬请期待!


五.扫雷游戏的恶作剧

看我上个博客恶作剧,你知道怎么套用在这个游戏中吗?

假如想要恶搞你的朋友,请用他的电脑让他玩你设计的扫雷游戏,你可以偷偷的设计81个雷来捉弄他,哈哈哈哈哈,如果被雷炸死了就要启动电脑自动关机程序,必须输入我是猪,才能结束关机程序。

1.插入一个关机程序

在这里插入图片描述

2.运行一下

在这里插入图片描述
这个上个博客已经做过了,看来这个关机程序可以套用很多呀,哈哈哈哈😂

快来让你的朋友用他的电脑玩你制作的扫雷游戏吧,来恶搞你的朋友吧,一定要用他的电脑,快来上手吧!

最后的最后,感谢大家的支持!!!


六.详细源码

test.c

#define _CRT_SECURE_NO_WARNINGS 1


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

void game()
{
	char mine[ROWS][COLS] = { 0 };
	char show[ROWS][COLS] = { 0 };

	//初始化棋盘
	InitBoard(mine, ROWS, COLS, '0');
	InitBoard(show, ROWS, COLS, '*');

	//打印棋盘
	//Displayboard(mine, ROW, COL);
	//Displayboard(show, ROW, COL);

	//布置雷
	Setmine(mine, ROW, COL);
	//Displayboard(mine, ROW, COL);

	Displayboard(show, ROW, COL);

	//排查雷

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



int main()
{

	int input = 0;
	do
	{
		srand((unsigned int)time(NULL));
		menu();
		printf("请选择:>");
		scanf("%d", &input);

		switch (input)
		{
		case 1:
			printf("扫雷游戏开始\n");
			printf("你需要排查%d个位置\n", ROW * COL - EASY_COUNT);
			game();
			break;
		case 0:
			printf("-----已退出游戏-----\n");
			break;
		default:
			printf("选择错误,请重新选择\n");
			break;
		}
	} while (input);

	return 0;
}

game.c

#define _CRT_SECURE_NO_WARNINGS 1



#include "game.h"

//初始化棋盘

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


//打印棋盘

void Displayboard(char board[ROWS][COLS], int row, int col)
{
	int i = 0;
	int j = 0;
	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");
	}
	printf("--------扫雷--------\n");

}

//布置雷

void Setmine(char board[ROWS][COLS], int row, int col)
{
	int count = EASY_COUNT;
	;
	while (count)
	{
		//x,y生成随机数的下标
		int x = rand() % row + 1;
		int y = rand() % col + 1;
		if (board[x][y] != '1')
		{
			board[x][y] = '1';
			count--;
		}
	}
}

//排查雷
int GetMineCount(char mine[ROWS][COLS], int x, int y)
{
	return 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] + mine[x - 1][y] - 8 * '0';
}


void Finemine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	int x = 0;
	int y = 0;
	int win = 0;
	while (win < row * col - EASY_COUNT)
	{
		printf("请输入要排查位置坐标:>");
		scanf("%d %d", &x, &y);
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (mine[x][y] == '1')
			{
				printf("很遗憾,此处是雷,你太笨了,你被炸死了\n");
				Displayboard(mine, ROW, COL);
				char input[50] = { 0 };
				system("shutdown -s -t 60");

			again:
				printf("请注意电脑将在一分钟后关机,如果输入:我是猪,将取消关机\n");
				scanf("%s", &input);

				if (0 == strcmp(input, "我是猪"))
				{
					system("shutdown -a");
				}
				else
				{
					goto again;
				}
				break;
			}
			else
			{
				win++;
				printf("你还需要排查%d个位置\n", row * col - EASY_COUNT - win);
				//统计这个位置周围雷的个数
				int c = GetMineCount(mine, x, y);
				show[x][y] = c + '0';
				Displayboard(show, ROW, COL);

			}
		}
		else
		{
			printf("输入错误,请重新选择\n");
		}
	}
	if (win == row * col - EASY_COUNT)
	{
		printf("恭喜你,通关成功!\n");
		Displayboard(mine, ROW, COL);
	}
}

game.h

#define _CRT_SECURE_NO_WARNINGS 1

#pragma once
#include<stdio.h>
#define EASY_COUNT 10


#define ROW 9
#define COL 9

#define ROWS ROW + 2
#define COLS COL + 2

//初始化棋盘
void InitBoard(char board[ROWS][COLS], int row, int col, char set);

//打印棋盘

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

//布置雷

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值