一、扫雷游戏介绍
扫雷游戏是一款经典的单机小游戏,具体玩法就是点击任意一个坐标,若该坐标是雷则游戏结束,若不是雷则显示该坐标周围八个格子内有多少个雷,可以对你认为是雷的位置进行标记,若将除雷以外的所有坐标都排查完毕,则游戏成功。

如图所示,用红框标示的数字1则代表以它为中心的周围八个格子内共有一个雷,接着不断排雷,直到将所有坐标排查完为止。
二、设计思路
Ⅰ、游戏页面设计
首先,我们想设计的是一个能重复进行游玩的扫雷游戏,那么我们就需要有一个菜单界面来让玩家选择是否玩游戏,假设输入“1”则进入游戏,输入“0”则退出,我们就需要在运行的开始和每局游戏结束时打印菜单来让玩家选择。
Ⅱ、棋盘设计
如上所述,我们首先需要设计两个棋盘,一个用来显示未排查的区域以及排查坐标周围雷的个数,一个棋盘用来设定地雷的位置。假设我们设置的棋盘大小为9*9大小的棋盘,由于我们排查某一坐标时,需要检测其周围八个坐标的地雷分布情况,但这就出现了一个很明显的问题:如果排查的坐标在边界上怎么办?所以为了避免每次排查时都要重复地判断坐标是否在棋盘边界上,我们直接在每行/列旁都添加一个空行/列,这就意味着我们需要创造的是两个11*11大小的棋盘,在游戏中我们只使用中间的9*9的大小。大致如下图所示。

Ⅲ、地雷设计
棋盘设计好后,接着就是埋地雷,因为扫雷游戏不止能玩一次,我们想让它能够重复的玩,我们地雷的位置就不能设置的一模一样,所以我们就需要在每局游戏开始时,在棋盘中随机地分布地雷。假设我们要设置十个地雷,我们就要在游戏开始时,在我们用于设置地雷的棋盘上,随机选出十个坐标设为地雷,我们假设字符‘1’为地雷,字符‘0’为安全区。
Ⅳ、排雷设计
埋好地雷后,下一步要进行的就是排雷操作。由于我们设计的这个扫雷游戏是在控制台界面运行的,我们无法用鼠标点击,所以我们先要在棋盘的上边和侧边打印行和列的坐标,然后通过输入坐标的形式进行排雷。
当我们选择一个坐标后,需要检测该坐标是否为雷,若是则此局游戏结束,回到菜单界面,若不是则打印出周围八个坐标的地雷个数。除此之外,如果玩过扫雷游戏我们就会发现,当我们点击一个坐标的时候,往往会一次性显示一大片坐标的信息,如下所示,这该如何实现呢?

这时候,我们就可以想到:如果选择的那个坐标周围一个雷都没有,我们就继续检测以周围八个坐标每个坐标为中心的周围是否有雷,没有则继续发散出去,直到发现有雷为止。
此外,我们知道扫雷游戏里面是可以对你认为是地雷的位置进行标记的,我们在设计的时候也要加上这个功能。
三、代码实现
Ⅰ、菜单页面
由于我们需要在每次启动程序和每局游戏结束时打印一份菜单,很明显我们需要一个循环来实现这一功能,最好的选择就是do-while循环。

我们定义一个函数menu,每次循环的时候就进入menu并打印菜单信息,接着让玩家选择是否进行游戏,并用switch-case语句实现分支结构,这边我们假设游戏通过game函数运行,则选择1的时候我们跳转进game函数。
Ⅱ、棋盘
因为我们设计的棋盘是有行和列的,所以我们需要创建两个二维数组,分别为mine用于存储雷的信息,show用于显示未排查区域和地雷数量,为了方便,我们用define来设置四个常量rows,cols,row,col分别表示数组的行/列以及棋盘的行/列。

接下来,我们要对两个数组进行初始化,将mine内的全部元素设置为‘0’,show内的全部元素设置为‘*’,通过循环遍历数组的操作实现。

这样,我们的棋盘就设置好了。
Ⅲ、埋雷
埋雷的过程我们也通过一个函数实现。我们需要解决一个问题,就是如何让每次游戏开始时地雷的位置都是随机的呢?这时候我们就需要用到一个rand()函数。
rand()函数
rand()函数是C语言中的一个库函数,使用的时候要引入stdlib.h这个头文件主要作用是生成一个0~32767之间的一个任意数,但是注意rand()函数生成的数值并不是真正的随机数,而是一个伪随机数,为什么说是伪随机数呢?因为rand()函数在生成随机数时需要一个种子,就有点类似于我的世界创造世界时用的种子,当种子一样的时候,它生成的数都是一样的。所以我们需要使用一个和它配套的srand()函数来给它设置一个种子。
srand()函数的原型为void srand(unsigned int seed)。
但是我们怎么确保它每次生成的时候种子都是随机的呢?这时候我们就可以想到,时间是一直在变化的,每次开始游戏时的时间都不一样,我们只要将每次开始游戏时的时间设置为rand()的种子不久好了吗?这时候我们就需要用到time()函数。
time()函数
time()函数也是C语言的一个库函数,使用时需引入time.h头文件,time()函数的原型为time_t time(time_t *tlot)。
由于srand需要的数据类型为unsigned int类型,而time函数产生的数为time_t类型,所以我们在使用时就需要将其强制转化为unsigned int类型,具体写法如下:
srand((unsigned int)time(NULL))。
这样,我们就完成了初始随机数的设置。但是我们又不需要这么大的一个随机数,我们只要一个1~9范围内的随机数即可,这时我们就只需要将我们得到的随机数对9取余数再加1不就解决了吗?具体写法如下。

先在主函数内使用srand设置种子,用SetMine函数实现埋雷,接着设置两个1~9范围的随机变量x,y,注意,当某一位置以及设置为地雷时,则重新进行设置,并且地雷只能设置在中间9*9的棋盘上,设置完十个地雷后便结束。
Ⅳ、排雷
首先在进行排雷之前,我们应该先打印一份show函数给玩家,玩家才能选择想要排查的位置,所以我们设置一个DisplayMine函数。

实际输出效果如下:

这时候我们就可以添加排查雷的函数,GetMine。


这个函数是整个程序中最复杂、最核心的函数,我们一步步对这个函数进行分解。首先设置x,y用于接收玩家输入的坐标,win用来记录已被排查的坐标个数,当win=71我们还没被炸死时,证明我们游戏成功,Choice函数用于让玩家选择排雷或扫雷。玩家选择完并输入坐标后,我们需要检测这个位置是否被操作过,即玩家只能对显示为‘*’的坐标进行操作。
当玩家选择标记地雷的位置时,我们就将对应坐标的符号改为‘^’,如图所示:

当玩家选择排雷时,就要进入我们排雷函数中最核心的部分,通过递归函数CheckMine来实现一片无地雷区域的打开,具体代码如下:

首先,我们设置了一个变量m来存储地雷的个数,对坐标周围八个格子进行检测,发现一颗雷则m+1,并在坐标上显示对应的数字。
若m为零,则进入下方循环依次对周围格子进行检测,实行依次递归,为了防止对已经检测过的格子进行二次检测导致进入死递归,我们每进入一次if语句就将该坐标改为一个空格,并在循环时进行判断,若坐标不为‘*’,则不进行递归。
除此之外,为了防止将周围边框的格子也计入导致win值高于实际值,我们也需要检测坐标是否在1~9内,若不在则不进行递归。最后将打开的格子个数传回win内。实际演示效果如下:

如此,我们就完成了该游戏所有功能的实现!
四、总结
根据对网页版扫雷的模仿,我们从零开始对该游戏进行了预定功能的构思、设计,并将它们一一通过代码进行实现,拓展了日常不常用的函数rand、srand、time,加深了我们对二维数组,自定义函数以及递归函数的理解。总的来说,写一个扫雷游戏锻炼了我们综合使用所学知识的能力。
此外,这个程序还有着比较大的进步空间,如我们可以在棋盘的头上加上我们扫雷所用的时间,对不同难度的选择等,这些都留给大家自己去拓展(绝不是因为我懒得写)。
这是我扫雷游戏的源代码,有需要的可以看一看,https://gitee.com/button_c/button_protect/tree/master/Project_12_15(2)
最后,给各位初学者一点建议,千万不要对着代码直接从上到下地抄,一定要按照思路一步步来,先写什么,再写什么,需要什么功能再进行添加,不自己去思考写一个程序地逻辑而是直接抄代码地话是不会进步的!

1万+

被折叠的 条评论
为什么被折叠?



