目录
一、扫雷游戏介绍
二、实现游戏的前期工作
1. 游戏的原理&逻辑
2. 初始化雷区
三、代码实现游戏
1. 实现 test.c 文件
2. 实现 Mine_Sweeper.c 文件
2.1 雷区初始化函数
2.2 埋地雷函数
2.3 展示雷区函数
2.4 排查地雷函数
2.4.1 排查地雷子函数
2.4.2 标记地雷子函数
2.4.3 取消标记子函数
3. 完整代码
四、游戏展示
《扫雷》是一款大众类的益智小游戏,于1992年发行。游戏目标是在最短的时间内根据点击格子出现的数字找出所有非地雷的格子,同时避免踩到地雷,踩到一个地雷全盘皆输。
玩家需要在雷区中,将所有地雷一一排查出来,同时扫雷游戏提供了插旗标记地雷和取消插旗标记地雷的功能。
本篇博客将会与大家一起学习,如何运用我们所学的C语言知识,来实现这一经典而富有娱乐性的小游戏,冲🐛🐛🐛!!!
※ ( 本篇博客实现的是 9×9 的扫雷游戏,在雷区放置 10 个地雷 )
※ ( 本篇博客采用多文件的方式来实现扫雷游戏 )
test.c - - - - 测试游戏的逻辑
MineSweeper.c - - - - 游戏代码的实现
MineSweeper.h - - - - 游戏代码的声明 ( 函数声明,符号定义 )
让我们把目光聚焦到上面的GIF图,首先映入眼帘的一定是 9×9 的雷区,这 81 个被遮盖的格子,等待被我们翻开。
当我们随机的点击其中的格子时,会出现以下三种情况:
① 当翻开的格子是地雷时,玩家被炸“死”,游戏结束;
② 当翻开的格子不是地雷时,该格子会显示周围的 8 个格子存在的地雷的个数;
③ 当翻开的格子不是地雷,且周围 8 个格子不存在地雷时,雷区会一下子翻开一片区域;
如此反复,直到玩家把雷区所有的地雷都排查出来,排雷成功,玩家获胜,游戏结束。
游戏的原理和逻辑捋的差不多了,一个新的问题也随之被抛出。我们如何实现这样一个可以将每个格子都一一翻转的雷区呢?
首先我们会将 9×9 的雷区与二维数组联想起来,能不能用二维数组实现那?一个二维数组能实现像游戏中那样每个格子都能翻转的雷区吗?细想好像一个二维数组实现起来比较困难。
一个二维数组不行,那两个呢?
答案是肯定的,本篇博客我们将使用两个二维数组来实现游戏里的雷区。
一个二维数组来设置雷区,即存放地雷,另外一个二维数组展示给玩家看,即实现翻转格子功能,达到与游戏相同的效果。
| 原理&逻辑 |
首先我们要先创建两个二维数组,一个用来设置地雷,另一个用来展示给玩家看。
对于设置地雷的二维数组,我们规定:用字符 ‘0’ 和 ‘1’ 来填埋地雷,字符 ‘0’ 代表无地雷,相对应的,字符 ‘1’ 代表有地雷。
对于展示于玩家的二维数组,我们规定:在格子未被翻转前,用字符 ‘*’ 来对其进行遮盖,而格子翻转以后,该格子则显示周围的 8 个格子存在的地雷的个数。
讲到这里,又引出了一个新的问题。二维数组设置成几行几列合适?
有的小兄弟可能会说,这篇博客开头不是说要实现 9×9 的扫雷游戏吗,那就设置成 9 行 9 列!
但 9×9 的二维数组真的万无一失吗?这就是我们接下来要和各位一起探讨的问题。
我们上面规定了,若翻转的格子不是地雷,则显示该格子周围 8 个格子存在地雷的个数,这一操作对雷区中间区域的格子可能不会有问题,但对雷区边界的格子执行这一操作可能会导致数组的越界访问,有图有真相,请看下面的图例。
| 图例 |
由图例可以得出结论,实现 9×9 的扫雷游戏,创建一个 9 行 9 列的二维数组并不合适。
既然对 9 行 9 列的二维数组的边界元素进行操作时,会导致数组越界访问,那我们干脆就直接将二维数组扩大一圈,将那些会导致越界访问的范围包括在数组内,从源头上解决问题,这是一个非常巧妙的办法!
| 图例 |
弄明白了两个二维数组创建的逻辑后,接下来就是用代码一步一步的实现我们的游戏了。
当玩家进入游戏后,程序会向玩家展示一个菜单,供玩家选择程序 ( 1.玩游戏 0.退出游戏 ),玩家做出选择后,可进入相应的程序。当一把游戏结束后,玩家不过瘾,还能继续选择玩游戏,直到玩家不想玩时,可选择退出。
① 对于 “玩家做出选择后,可进入相应的程序” 这句话,我们会情不自禁的联想 switch 语句。
② 对于 “当一把游戏结束后,玩家不过瘾,还能继续选择玩游戏,直到玩家不想玩时,可选择退出” 这句话,我们联想到了 do while 语句,玩家进入程序就先展示菜单,待玩家做出选择后再判断是继续程序,还是结束程序。
| test.c 代码 |
眼神比较犀利的小兄弟可能会发现,上面的代码段中 Game 函数里存在 “ROW,COL,ROWS,COLS”,那这些代表什么意义呢?再仔细观察,我们可以发现 test.c 文件只包含了一个头文件 "Mine_Sweeper.h",而 “ROWS,COLS,ROW,COL” 就是在头文件中用 #define 定义的常量名。( ROW 代表常量 9,COL 代表常量 9,ROWS 代表 ROW + 2,即常量 11,COLS 代表 COL + 2,即常量 11 )
| MineSweeper.h 代码 |
| 代码 |
本篇博客在讲解初始化雷区的原理和逻辑处已经规定了,用 ‘0’ 和 ‘1’ 对二维数组 mine 进行初始化,用 ‘*’ 对二维数组 show 进行初始化。
在调用此函数的同时将 数组名 数组行数 数组列数 初始化字符 作为函数参数进行传参。该函数对目标数组进行遍历,且将每个元素赋值,从而达到雷区初始化的效果。
| 代码 |
该代码需要获取随机数,以作为埋地雷的 行坐标 和 列坐标,获取随机数则需要调用库函数 rand 函数,注意,在调用 rand 函数前还要调用库函数 srand 函数,而程序中获取随机数只需要调用一次 srand 函数即可,所以我们把 srand 函数的调用置于 test.c 文件中的 主函数 的开头位置,我们使用时间戳作为 srand 函数的参数。
※ srand 函数和 rand 函数都需要程序包含头文件 <stdlib.h>
※ time 函数则需要程序包含 <time.h>
| 代码 |
在向玩家展示雷区时,可以将雷区的行坐标与列坐标预先打印出来,给予玩家更好的游戏体验。
该函数通过对二维数组的遍历打印,向玩家展示雷区,值得注意的是,本篇博客实现的是 9×9 的扫雷游戏,也就是说玩家看到的是 9 行 9 列的雷区,所以我们使用代码遍历二维数组时,要注意行和列都应该从 1 开始打印 ( i = 1 j = 1 ),以打印到 ROW 和 COL 为截至条件 ( i <= ROW j <= COL )。
※ ( 该函数中 i <= row j <= col,并不是书写错误,row 和 col 是函数的形参名,分别接收调用函数时传来的 ROW 和 COL )
| 效果展示 |
做完以上准备工作后,从这里开始才是真正意义上的开始扫雷。
当玩家选择玩游戏时,程序会再次向玩家展示一个菜单,供玩家选择程序 ( 1.排查地雷 2.标记地雷 3.取消标记 ),我们分别用三个函数去实现这三个功能。
※ ( 该函数由多个子函数嵌套调用而成 )
| 代码 |
| 讲解点 |
| 代码 |
| 讲解点 |
在扫雷游戏中,当我们点击的方格不是地雷,且周围一片区域都没有地雷时,会直接展开一片雷区,具体效果如下图
如何用代码实现这一功能呢?
还是按照惯例,先弄明白其中的原理和逻辑。
| 原理&逻辑 |
扫雷游戏中,当玩家翻转一个方格时,若该方格不是地雷则会显示该方格周围 8 个方格存在的地雷个数。如果该方格周围 8 个坐标都不存在地雷时会将这 9 个方格都展开,以此类推直到遇到一个方格的周围 8 个方格存在地雷时停止展开,两种情况如下图所
为实现这一功能,则需要我们遍历玩家输入的坐标的周围 8 个坐标,统计该坐标周围所存在的地雷个数。
| 代码 |
展开一片雷区,是一个重复的过程,这片区域没有地雷,则继续展开下一片区域,直至遇到一片区域周围存在地雷时,停止展开。
每次展开都要判断是否遇到地雷,每次的判断都是类似的,所以这一功能可以用我们所学的函数递归实现。
接下来我们继续用图例的形式捋清展开一片雷区的逻辑。
| 图例 |
| 代码 |
在扫雷游戏中,如果我们已经推断出一个坐标必定是地雷时,我们可以通过插旗子的方式去标记地雷,如下图。
接下来我们就要学习如何用代码实现这一标记地雷的功能。
| 代码 |
如果玩家因判断失误而错误的标记了地雷,则此时我们还需要实现一个取消标记的功能,这样才不至于让玩家只能标记地雷而不能取消标记。
| 代码 |
| 代码 |
| 头文件 >> Mine_Sweeper.h |
| 源文件 >> Mine_Sweeper.c |
| 源文件 >> test.c |
到这里本篇博客就接近尾声了,希望看完本篇博客对你有所帮助,期待下次与你相遇。
< 你的关注,点赞,评论,收藏都是对我创作最大的鼓励 >
( 若本篇博客存在错误,望指出,感谢! )
有话要说...