今天用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数组它也是字符数组不能放数字,所以我们把算出来的数再转化成字符放进去