友友们好呀~
昨天我们写了三子棋的小游戏,是不是觉得其实写一个小游戏也没有那么难呢?
是不是还跃跃欲试想尝试写出更多的有趣的小游戏呢?
那么今天我们就事不宜迟,写一个扫雷小游戏吧!
项目准备
首先,我们要创建一个新的项目,名为:mine clearance
并创建以下三个文件:
test.c - 用于测试游戏
game.c - 用于完成游戏函数的实现
game.h - 用于进行游戏函数的声明
首先我们还是先给游戏搭建一个完整的游戏打开方式,即打印菜单,用户选择是否进行游戏。
由于在之前的博客中已经详细解释过,(详情请参考:【手把手带你搞定】第一个C语言猜数字游戏(超详细教程,不仅学如何写代码,更学方法思路!))这里就不在赘述,直接上代码截图。
初步完成后,记得先测试运行一下噢~
扫雷游戏
接下来,完美就要真正开始扫雷游戏的实现了。
首先先给上一个game( )函数用于实现扫雷游戏。
游戏分析
那么首先我们要对扫雷游戏的逻辑进行分析。
首先打开一个扫雷小游戏看看,我们看到的是这样一个界面(初级版)。
界面有9*9个格子,点击其中一个格子,会给出一个数字,或者不给数字。
按照扫雷的逻辑,应该是如果我们点击的格子内有雷,那我们就被炸死了,游戏结束。
如果没有雷,那么就统计一下这个格子周围一圈一共有几个雷,并在这个格子上给出一个数字以告诉玩家。而图中没有展示数字的格子就表示周围一圈没有雷。
我们就可以通过游戏给出的数字来判断哪个格子有雷,哪个格子没有雷,逐渐把所有的雷找出来。这就是扫雷的过程。
当我们把游戏中的所有雷都找出来了,那么游戏就赢了。
当然,这里用VS2019来实现扫雷小游戏,界面就搭建得每那么美观了,但是游戏的逻辑是不变,接下来我们就真正开始实现。
我们把扫雷游戏先分为以下几个步骤:
- 布置雷
- 打印格子
- 排雷
- 判断结果
两层思考
一
首先我们要布置雷,假设在一个9*9的格子中,随机放一些雷,那么我们用1表示该格子有雷,用0表示无雷。
当我们随机地放入了10个完雷之后,我们要开始排雷。
那么我们统计了之后,这一个周围一共有1个雷。
但是这1个雷的信息如果我们用1来表示,并把它放到原来我们选择的格子中,那这个1就会和格子中原本表示有雷的1搞混。
那么这个时候我们应该怎么办呢?
两种解决方法:
-
有雷的地方用“*”表示,无雷的地方用“#”表示,这样就不会和排查出的雷的数量搞混了。
-
我们再复制一个一样的格子,把排查出来的雷的数量存放到复制的格子的对应位置中。
这两种方法都可行。
接下来我们要把格子打印出来。
首先,我们布置的雷的信息肯定是不能直接打印出来的,所以我们应该给一个简单的格子。假设用#表示,那么我们应该打印出的是9*9个井。
然后玩家输入坐标,根据坐标计算出周围的雷的数量,再把数据放到坐标对应的位置中。
所以,在这里我们可以创建两个二维数组,一个用来存放布置的雷的信息,一个用来打印并储存排查出来的雷的信息。(即考虑到打印的问题,使用上述问题中的第二种方法更好。)
二
当我们排雷的时候,我们要根据玩家给出的坐标像四周拓展来统计雷的个数。但如果这个坐标在表格的边缘时,我们再想四周拓展的时候就会造成越界。
那么这时候我们又该怎么办呢?
我们可以分情况讨论。
先判断玩家给出的坐标在哪个位置,如果在中间,就西周拓展计算雷的个数;如果在边缘处,就根据在哪个边缘分情况拓展计算。
但是这种方法听起来就好麻烦……除了上下左右四条边,还有角落处的四个位置都要另外分情况计算,虽然这种方法确实可行,但是这这这……也太麻烦了吧!
那有没有更好跟简单的方法呢?
答案是有的!!!!!
看图!
你get到了吗?
我们只要把布置雷的99的格子上下左右都向外拓展一行,即把格子设置成1111的,然后把雷布置在中间的9*9个位置上,问题不就迎刃而解了吗?
但是为了让存放雷的信息的位置坐标和布置雷的位置坐标一样,所以我们也要把存放雷信息的格子设计为11*11的数组。
当我们思考完这两个问题之后,我们就可以把代码敲起来啦!
答应我,敲代码之前先做一件事好吗?
那就是动动手指给文章点个一键三连吧~~~~
代码实现
数组的创建与初始化
接下来我们要在test.c文件中创建两个数组,考虑到我们打印的时候既要打印“#”,也要打印排查出的雷的个数,所以我们把数组的类型设置为char。
接下来我们要初始化一下我们的数组。
按照我们的想法,最开始还没有布置雷的时候,我们的mine数组中应该全部放‘0’,在存放雷的信息中应该全部放‘#’。
InitBoard函数代码如下:
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
int i = 0;
for (i = 0; i < rows; i++)
{
int j = 0;
for (j = 0; j < cols; j++)//遍历数组
{
board[i][j] = set;//放入我们希望初始化的字符
}
}
}
在初始化之后,我们想看一下初识化的情况,所以这里我们需要写一个打印函数。
注意:我们只需要看到中间的99个位置上的内容,所以在传参的时候,行和列传的是ROW和COL。
在头文件的声明中也要注意,因为我们传给函数的数组是一个1111的数组,所以第一个参数的类型中数组的行和列还是ROWS和COLS噢!千万不要搞错了!!
运行程序:
为了更美观和方便地观察每个位置的坐标, 我们给其加上行数列数,并用空格行隔开。
打印效果:
布置雷
接下来我们就要把雷布置进去。
首先,我们先确定放置的雷的个数。
这里我们把雷的个数像行数和列数一样定义在头文件中,这样如果以后想更改雷的个数,就可以直接在头文件中进行更改。
然后我们开始写布置雷的函数。
老规矩,先写函数的调用和声明。
这里,我们布置雷是在mine数组中布置,布置在9*9的格子中,所以我们将对应的参数传给函数。
接下来,我们在game.c中实现布置雷的函数。
首先,我们应该生成一个随机坐标。(关于随机数的生成,请参考【手把手带你搞定】第一个C语言猜数字游戏(超详细教程,不仅学如何写代码,更学方法思路!))
首先,我们要产生1-9的随机值。
让生成的随机数%行数(或列数),得到0~行数(或列数)-1的值,所以我们在得到的数后面加上1,得到的数就在我们想要的范围之间啦!
在main函数中写入随机数生成器。
别忘了在game.h中引用对应的头文件。
布置雷的代码如下:
void SetMine(char board[ROWS][COLS], int row, int col)
{
int count = COUNT;//要布置的雷的个数
while (count)//当count的值为0,完成布置
{
int x = rand() % row + 1;//产生1-9的随机值
int y = rand() % col + 1;
if (board[x][y] != '1')//检验该位置是否已经布置过雷
{
board[x][y] = '1';
count--;//每次布置完,雷的个数-1
}
}
}
布置完,我们再打印代码看一下布置的结果。
运行程序:
此时说明我们的雷已经布置好啦!
排查雷
接下来我们就要排查雷啦~
首先还是先进行函数的封装和声明。
注意:
- 我们是在mine数组中统计获取雷的信息的,然后再把信息放到show数组中,所以这两个数组都要传给函数。
- 由于操作范围是中间的9*9个格子,所以行数和列数传ROW和COL
在test.c文件中:
game.h文件中:
然后回到game.c文件中对实现排雷函数。
首先,我们需要玩家输入一个坐标,然后,我们要判断坐标的合法性,如果坐标不合法则重新输入;如果坐标合法,则要判断玩家输入的坐标处是否有雷,如果有雷,则玩家提示玩家被炸死,游戏结束。
现在我们测试一下程序是否按照我们的想法走的。
如果没踩到雷,则排查一下坐标周围的雷的个数,并展示出来。
那么这里有一个问题,我们统计雷的个数,得到的是一个数,而我们存放在show数组中的元素是一个字符。
所以这里就涉及到了字符和整型数字之间的转换:
我们知道,在ASCII码表中,每个字符对应一个整型数字,所以如果我们把两个字符相减,就能得到一个整形数字。如:‘9’ - ‘0’ = 9
同样地,把9+‘0’,就能得到字符‘9’。
测试一下代码吧~
当我们把没有雷的位置全部都排查出来后,说明玩家已经把雷排查完,游戏结束。
游戏结束后,我们找把布置的雷的情况打印出来给玩家看看。
这时候,我们要测试一下代码。但是有10个雷,我们要遍历71个位置,才能测试出排雷成功的状态。
这时候作为游戏编写者的我们,就要发挥一下身份优势啦!
把雷的个数设置成80个不就好了嘛~这样只需一步就能排雷成功!
运行代码:
这样我们游戏的逻辑就很好地实现啦!
现在,我们只需要把雷的个数改回10,并且把打印的数组改为show数组就行啦!
完整代码
由于代码较长,这里只贴上代码的截图,完整代码已上传至Gitee,需要自取噢~
https://gitee.com/fang-qiuhui/my-code/tree/my_code/blog_2021_8_23_mine%20clearance
game.h文件:
game.c文件:
test.c文件
总结
不到两百行代码,我们就完成了扫雷游戏的编写啦!太棒啦~你学会了吗?
总结一下,其实编写扫雷游戏的整体思路方法和之前写猜数字游戏和三子棋游戏很像,先分析后代码,怎么用就怎么写,这是一种非常重要的方式噢。
在进行扫雷游戏的代码设计时,我们首先思考到了两个问题,一个是数组中元素的存放问题,一个是统计数组时数组越界的问题。每个问题都有不止一种的解决方法,但是我们要善于思考,发散思路,找到最好的一种,把问题想明白了再开始代码,这样在代码的过程中才会思路清晰,不会卡壳。所以,一个好的程序员,在写出好的代码之前,一定是花了更多的时间在分析上的。
最后,我们对比网络上的扫雷小游戏,可以发现在排查雷的时候,有两个点可以改进:
- 如果排查到的位置周围都没有雷,排查就会继续向四周拓展,直到排查到这个位置周围的有雷为止。
- 玩家在确定某个位置上有雷的时候,可以选择把雷标记出来。我们可以据此思考一下如何升级我们的扫雷小游戏。
优化升级
经过一番思考和设计之后,博主终于写出了优化升级之后的扫雷小游戏啦!所以决定爆肝也要把升级版扫雷小游戏的代码过程分享给大家!!
所以看到这里的你一定一定要给博主点个赞啊啊啊啊!
创建升级版扫雷游戏
由于之前已经写了一个扫雷游戏,所以我们就可以在原来的游戏项目的基础上对代码进行更改啦!
所以这里我复制了一个扫雷游戏项目,命名为:mine clearance_upgraded
首先为了方便后面的观看,我把打印函数进行了更改:(以下更改博主我就直接贴图解释咯~相信聪明的你一定能看得明白的!)
运行代码:
这样是不是看着更舒服一些呢?
选择排查雷或标记雷
因为我们要增加标记雷的选项,所以在布置好雷并把格子打印出来之后,我们应该是给调用一个选择函数。
在头文件中进行声明:
接下来是选择函数的实现。首先,我们应该像打印游戏菜单一样把排雷和标雷两个选择打印出来,然后让玩家进行选择,逻辑和选择是否玩游戏相似。
代码如下:
由于之前的FineMine函数并没有设置返回值,所以我们在函数的实现和声明都要进行更改。
game.h文件:
对于FineMine函数的更改:
接下来写标记雷的函数。
在头文件中声明:
标记雷函数的实现:
为了方便测试,我们先打印一下存放雷信息格子。
测试运行:
Very Good!!!
拓展排雷范围
接下来是拓展排雷范围,如果排查得到的数是0,则以周围坐标为中心继续排雷,直到得到非0,所以这里我们可以用一个递归来完成。
而递归的条件是:
- 排查时没有踩雷
- 排查的位置周围的坐标也没有雷
- 排查的位置没有被排查过(避免死递归)
下面,我们就可以写代码了。
首先找到FindMine函数。
运行程序:
完美实现拓展!NICE!
最后,只需要把打印存放雷的数组的代码隐藏起来就行啦!
再次运行代码,一个升级版的扫雷小游戏就可以开玩啦!(运行结果太长就不贴图啦!大家可以自己运行一下看看噢~)
升级版游戏的源代码上传到Gitee啦!需要自取噢~
https://gitee.com/fang-qiuhui/my-code/tree/my_code/blog_2021_8_23_mine%20clearance_upgraded
最后,感谢大家看到这里啦!~
如果你喜欢这篇文章,记得点赞收藏加关注噢~
你的支持将是我继续码文的极大动力!!!!!!
关注我,一起精进C语言!