经典小游戏扫雷
在学习C语言的基本语法之后,我们可以来实现一款经典游戏 扫雷
一,游戏介绍
在固定的区域方格中排查雷,若不为雷统计周围的雷数并显示,若为雷游戏结束。
胜利条件:将所有不是雷的方格找出。
二,文件创建
写扫雷游戏我们需要创建多个文件,通过分模块来实现功能的划分。我们可以创建三个文件分别为:test.c;game.c;game.h。test.c文件用来存放main函数;game.c文件用来存放实现游戏各个功能的函数,game.h文件用来存放函数的声明和头文件,这样编写程序的时候test,c文件和game.c文件只需要包含game.h文件就可以了。
三,游戏程序设计
3.1游戏菜单
首先我们需要设计一个游戏菜单,来选择是否开始游戏,可以使用do…while循环和switch来实现,
要打印一个菜单,可以编写一个menu函数来实现,
//菜单
void menu()
{
printf("* * * * * * * * * * * * * *\n");
printf("* 1.开始游戏 0.退出游戏 *\n");
printf("* * * * * * * * * * * * * *\n");
}
根据玩家的选择来决定是否开始游戏,若开始我们则进入一个包含游戏程序主体的函数game来完成扫雷游戏
3.2游戏棋盘
开始游戏时我们首先需要一个展示信息的棋盘,上面可以显示已经排查完的方块和某个方块周围的雷数,同时在没有排查前不显示任何东西:
我们可以设计一个9×9大小的棋盘,我使用的是VS2022编译器,并不支持变长数组,为了让棋盘的大小方便改变,我们可以通过定义宏来实现如果使用其他支持变长数组的编译器则直接使用变长数组就行。
我们将宏的定义放在头文件game.h中。
我们在思考一下,在排雷后棋盘的展示信息会根据玩家的排雷产生变化,我们该如何实现这种动态变化?思考一下,可以想到在游戏的开始雷的位置是已经确定了的,并不会随着游戏的进行发生位置的变化,而棋盘展示的数字表示周围的雷数。从而我们可以想到可以用两个棋盘来存储不同的信息,一个棋盘mine用来存储雷的位置,一个棋盘show用来展示变化,这样我们只需将玩家所需要排查的位置信息在mine棋盘中统计之后在传到show棋盘中相同的位置就行了:
但是这样还有一个问题,我们需要统计某个格子周围一圈的雷数,但是当我们要排查的格子位于边界的时候周围一圈并没有全部都是格子,为了解决这种情况我们可以将mine棋盘设置成11×11,但是我们布置雷只在中间9×9的区域布置,这样当排查的格子位于9×9的边界时既可以按照其他格子一样排查周围一圈,因为最外的一层一定不会有雷,所以统计的时候并不会影响结果,这样可以减少工作量。所以我们在头文件中再一次定义两个宏
然后我们在game函数中先设置两个二位字符串数组作为我们的棋盘:
void game()
{
char mine[ROWS][COLS]={0};//埋雷的棋盘
char show[ROWS][COLS]={0};//展示的棋盘
}
在生成完棋盘后,我们需要对其进行初始化,在展示的棋盘中,我们可以将为排查的方块用‘*’来表示,而在布置雷的棋盘中我们可以先将其所有位置设置为字符‘0’,这样后期我们布置雷时可以将雷的位置标记为‘1’,统计数量时只需将周围八格作加法即可,我们使用Initboard函数来初始化棋盘:
注意:这里因为对两个棋盘进行初始化的值不同,所以我们可以将我们想要对其进行初始化的值也传给函数,用一个变量接收,这样我们就只要编写一个函数就能对多个棋盘进行初始化。
记得将函数的声明写在头文件中
然后我们就可以在game.c文件中编写函数Initboard了
为了查看我们是否初始化成功以及后期玩游戏时显示信息,我们还可以定义一个Displayboard来打印棋盘,同样在头文件中声明和定义:
==注意,因为我们只需要查看中间9×9范围格子的信息,所以我们传参的时候使用ROW和COL,下面就是打印棋盘的代码:
其运行的效果为:
当然为了美观和方便玩家排雷,我们可以将页面优化一下使其更加明显,还可以加上坐标方便玩家确认要排查的位置,可以增加一个循环用来打印坐标,优化后的代码如下:
效果为:
这样就比之前清楚多了,玩家对坐标的选择也能更准确了。当然游戏开始时,我们并不需要将埋雷的棋盘也打印出来。
3.3布置地雷
在生成完棋盘后,我们就需要对其进行地雷的布置
目前我们用以布置地雷的棋盘全是前面初始化棋盘时设置的字符0;假设一句有10个地雷,我们就需要标记10个位置,又因为后面需要统计地雷的数量,所以我们可以将地雷的位置用字符1来标记,这样后面只需将一圈内所有的字符1加起来,得出来的值就是地雷的数量了。
因为地雷是随机生成的,我们可以使用C语言中的rand函数来进行随机数的生成,这里我们设置一个函数Setmine函数来进行地雷的布置,同样记得在头文件中进行声明
注意,因为我们布置地雷的范围同样是中间9×9的范围,所以我们进行传参时使用的时ROW和COL(值为9),而不是ROWS,COLS(值为11)。
rand函数生成随即数需要调用srand函数来初始化“种子”的值,rand函数和srand函数的用法这里不再赘述,可以在我之前的文章: https://blog.csdn.net/gresio/article/details/135102205?spm=1001.2014.3001.5501中学习。
注意因为我们布置地雷的范围是中间9×9的范围也就是ROW和COL的大小,所以生成的随机数大小应该为1~9,设坐标为(x,y)
然后我们就可以将对应的坐标设置为地雷也就是标记为字符‘1’。
但是,我们还要注意几个问题:
第一:同个位置不能布置两次地雷;
第二:当布置完地雷后循环要结束;
要如何解决这两个问题?
很简单,我们只需定义一个变量来表示我们要设置的地雷数量,在布置地雷的时候在判断一下该坐标是否已经布置过地雷,若布置过则该次不布置,地雷数量不变化;若没有布置过则布置地雷并减少地雷数量;当地雷数量为0时表示地雷已经布置完毕,循环结束。首先我们在头文件中定义一个宏 Easy_Count 表示地雷数量
然后在Setmine函数中利用while循环来布置地雷,代码如下:
每次成功布置地雷,count的值都会减小1,当布置十次地雷后循环结束。我们打印棋盘看一下效果:
可以看到地雷布置成功。
3.4排查地雷并统计周围的雷数
在布置完地雷后,我们就可以进入排查雷的阶段,这也是这个游戏中玩家操作的重要一步,我们设置一个函数Findmine完成排查地雷的步骤,前面说过排查雷实在mine棋盘中进行,将排查后的信息发到show棋盘进行展示
所以,我们在给Findmine函数传参时要将两个棋盘都传过去,同样记得在头文件中进行声明
然后我们在game.c文件中完成函数的实现,因为要进行多次排查,可以在while循环中实现:
当玩家排查的坐标不是雷时,还需要统计周围的雷数并显示出来,我们设置一个函数Getminecount来完成
这一步是整个游戏中的关键步骤,在实际的游戏开发中,我们可以将这种关键步骤的函数就放在game.c中完成,以避免被他人破解
那我们要如何统计周围地雷的数量呢?前面说过,mine棋盘的值分别为字符‘1’和字符‘0’,然后我们需要统计周围一圈的地雷数量,因为要统计多个方块,所以我们可以使用for循环来完成,但是,如果统计周围一圈的方块那还需要添加额外的判断条件,所以为了方便,我们可以统计以玩家选择排查的方块为中心,周围3×3大小的方块,因为只有当玩家排查的方块不为地雷时才统计地雷数量,所以玩家排查的方块值一定为0,加上该方块的值并不会影响最终的结果,代码如下:
这样就解决了吗?并没有,这样的代码是错误的,前面说了用来该棋盘中的数据为字符而不是整形,所以当我们用整形count来统计数时系统会将字符‘0’和字符‘1’按照ASCII码表转化为对应的值,所以最终的结果并不正确。解决这个问题也很容易,我们只需在统计数量那一步中将每个方块的值减去字符‘0’的值,在将所有的差加起来就可以了在ASCII码表中‘0’的值为48,'1’的ASCII码值为49,所以,‘0’和‘0‘的差为0,’1‘和’0‘的差为1
而所有的差加起来的值就为地雷的数量,当然,我们在show棋盘上展示时使用的也是字符,所以,需要将最终计算出的count值加上0的ASCII码值,因为数字09的ASCII码值是连续的,所以这样计算最终显示的数字就是周围地雷的数量按照ASCII码表转化的值,也就是09,
更改后代码如下:
我们运行后的效果为:
可以看到程序成功的统计了雷的数量并且展示;
3.4胜利条件
在完成上述几步后,扫雷游戏已经完成了大部分,但在上述扫雷的代码中海存在一个问题,那就是如何在当玩家排查完所有非雷方块后宣布游戏结束,玩家胜利;也就是当玩家胜利时,循环结束,我们先看看目前我们排查雷部分的代码:
可以看到现在我们while循环的条件为1,当我们碰到地雷时游戏失败,跳出循环游戏结束,但我们并没有设置好当玩家排查完后游戏胜利时如何结束。我们先来看一下网页版的扫雷:
可以看当玩家找出所有非雷方块后游戏结束,玩家胜利。那我们要如何实现呢?
我们可以知道在9×9的棋盘中有十个地雷,那不是地雷的格子就有71个,玩家的胜利条件找出这71个格子。所以我们可以设置一个计数器,玩家没找出一个非地雷格子,计数器就减少一个,当计数器为0时,循环停止,玩家胜利,又因为非地雷的格子数量为格子总数减去地雷数量,所以我们可以设置计数器的初始值等于棋盘长和宽的积减去地雷数量,代码如下:
当然不要忘记在循环结束后判断玩家是否胜利
为了验证我们代码是否能正常运行,我们可以开个挂(开发者权限)试一下,将地雷数量设为80,将两个棋盘都打印出来:
可以看到游戏成功运行,这样我们就完成了基础扫雷的全部代码。
3.5递归展开
在上面的扫雷基础代码完成后我们可以发现和网页版相比,处理界面外还有一个明显的区别就是我们所编写的代码一次排查只能翻开一个格子,当棋盘变大后一局游戏的时间会非常长,而网页却能展开一片,节省玩家时间。
而要解决这个问题,就需要用到递归的知识。
我们先来分析一下递归展开的条件和结果是怎样的:
1,当玩家排查的方块周围不存在地雷;
2,该次排查的方块周围存在为排查的方块;
3,当出现排查到的某个方块周围存在地雷时停止展开;
4,已经排查过的方块不会再次被递归展开排查;
综上所述我们可以知道只有当玩家该次排查的方块周围不存在地雷也就是显示的数字为0且周围存在为排查的方块时才会进入递归展开,知道出现数字才停止。
那我们得先明白,因为我们要不停的对周围未排查的格子进行递归,也就是统计周围格子一圈范围内的地雷数量,所以是要对下面这部分进行统计地雷数量并显示的代码进行更改,
再更改的时候要注意,每次递归展开的时候计数器的值也要变小,所以我们更改后的代码要将计数器的地址传到函数当中,同样记得用一个地址接收;更改后的代码如下:
我们测试一下,
可以成功的展开一片,这就表示我们成功了。如果想实验一下玩家是否能正常胜利,可以按照上面的说法对地雷数量进行改变测试,这里我就不在展示了(已经测试过,没有问题)。
四,扩展部分,将游戏发给朋友游玩(使用VS2022)
在完成扫雷的程序后,我们该如何发给朋友让其方便快速的游玩呢?
很简单,在VS2022软件中的编程界面的最上方项目的正下方有个Debug
我们点击Debug,j将其切换为Release,
然后我们将程序运行一遍,不用通过游戏,可以在运行后直接关闭界面。
然后我们找到保存该文件的文件夹,
进入到X64中,找到Debug文件,在进入,找到exe文件,将exe文件发给朋友就行了。
以上,就是所有的内容了,谢谢各位的支持!!