扫雷(C语言)

目录 ​​​​​​​

前言

一、前提知识

二、扫雷游戏编写

[2.2 test文件基本逻辑](#2.2 test文件基本逻辑)

2.2.1菜单编写

2.2.2game函数的逻辑

2.2.2.1定义两个数组

2.2.2.2两个数组数组的初始化

2.2.2.3打印棋盘

2.2.2.4布置雷

2.2.2.5排查雷

2.2.2.6获取坐标附近雷的数量

2.2.2.7什么时候截止

2.2.2.8如何实现点击后展开一片

总结


前言

本篇文章基于前面的学习内容,有一些部分与前一篇的三子棋一样,可能会比较快,但是写逻辑的和游戏实现的时候会详细讲解,来动手写一个简单的扫雷。


一、前提知识

我们知道扫雷一个地图上有固定的炸弹,当选到炸弹的时候,游戏就会结束,没选到的时候,这个格子就会说明周围有多少个炸弹,扫雷棋盘的原理还是和上一篇的一样,同样可以用一个二维数组进行编写,我们只需要实现上述的逻辑就可以完成这个扫雷的小游戏,

我们还是使用VS2022创建一个空项目,同样的创建三个文件,一个game.h头文件,一个game.c文件,test.c文件,头文件在头文件里面,源文件在源文件里面创建。

我们通过test.c文件来编写逻辑的实现,之后game.h和game.c来编写游戏的实现,通过测试的逻辑来调用游戏的实现。

二、扫雷游戏编写

2.2 test文件基本逻辑

2.2.1菜单编写

我们还是用简易的菜单,与上一篇文章一样,可以用一堆星号以及文字来代表一个菜单,同时上面有两个选择,1.选择游戏开始,0.选择游戏结束,直接结束进程。

编写上述模块,menu( ),这个函数在test( )游戏逻辑模块内进行调用。

这里还是用一个do-while循环语句来进行游戏持续的进行,同样的限制条件就是出入的内容,如果是0,那么就是为假,循环就退出,当1或者其他数字的时候,循环不结束。

循环语句里面套用一个switch-case语句,通过这个语句来进行不同选择从而实现不同的效果。

这样就基本实现了菜单选择的功能。

2.2.2game函数的逻辑

1.需要存放好雷的信息,存放排查出雷的信息,我们需要2个二维数组
2.排查坐标的之后,为了防止坐标越界,我们给数组的行增加了两行,列增加了两行。

3.我们希望show数组内都是 * ,希望我们的mine数组都是字符'0',这样认为雷都是没有排。

2.2.2.1定义两个数组

我们首先在game.h里定义行和列,以及多两行和多两列的定义。

之后再test.c中包含一下这个头文件,这样这里的定义的行和列就可以使用了。

接下来首先定义两个数组,一个是mine数组作为还没有进行排查的时候,并且里面布置好了雷的信息,接下来在定义一个排查出雷的信息,命名为show,将两个数组进行初始化。

2.2.2.2两个数组数组的初始化

我们用名字命名为InitBoard的函数来进行数组的初始化,通过传入三个参数,数组,行,列,来进行数组。

初始化函数还是在头文件进行声明,在game.c里面进行定义。由于这个函数既有可能是对mine函数进行初始化,又有是对show进行初始化,所以这里的参数数组形参就命名为board就可以。

声明之后在game.c里面进行定义,通过遍历就可以实现初始化,但是这里有一个问题,就是我们想要mine数组里面全是0,而show数组里面全是 * ,但它们俩是使用一个函数的,所以会产生冲突,这时候我们就可以进行多传入一个参数,这就可以解决这个问题。所以改成下面:

我们用set作为这个字符形参,用来接收这个数组里面都是什么字符,好实现数组的初始化。接下来进行定义:

这样就可以初始化了。

2.2.2.3打印棋盘

我们用一个命名为DisplayBoard的函数来进行棋盘的打印。我们创建的是11*11,但是我们只需要打印中间的9*9,外面的一行就是为了考虑数组越界而设置的,所以对于外面这一圈是不需要打印出来的。这时候传入的参数行和列就使用ROW和COL就可以。

首先在头文件中进行声明:

这里注意一下,我们传入参数依然是11*11的数组,因为它本来就是一个11*11的二维数组,如果想直接传入中间的9*9数组是不可能的。

接下来进行在game.c里面实现这个函数:

这里通过一个遍历循环,打印完一行之后打印换行符就可以了,这样就可以打印出行和列都是什么,也就可以实现初始化之后打印棋盘的目的。

我们这里把game函数放到case1中去,这样就就可以调用这个函数了。下面看运行结果:

通过以上代码运行,最后就输出了两个二维数组,一个是9*9的全是0的mine数组,这里的0是字符'0',如果直接写数字,那么打印出来什么都看不见。另一个则是全是 * 的show数组。但这样看着并不怎么美观,就可以打印一下行号和列号。通过字符后面加上空格,以及打印行数列数,即可实现。

把打印这个函数写成下面的形式:

运行结果如下:

这样就非常的美观了。

但实际上我们不会打印布置好的雷的,我们只会显示排查雷的信息就可以了,所以只需要打印一个show的棋盘就可以。接下来布置雷,布置完雷之后才开始排雷。

2.2.2.4布置雷

我们这里用SetMine这个名字来代表布置雷的的函数,在game函数中调用这个函数就可以实现布置雷,而这个雷是在9*9的二维数组里面进行布置,所以这个函数的参数有三个,一个是要布置雷的数组,还有行和类。

同样的还是在头文件中进行声明:

传入的时候还是传入11*11的mine数组。

game.c中进行定义实现 :

我们要随机的布置雷,同时因为扫雷有不同的难易程度,这里假设9*9的棋盘上有十个雷,所以先在头文件中定义一个EASY_COUNT来代表雷的个数有十个。

之后在布置雷的函数中用count来接收这个EASY_COUNT的值,这里写一个循环,只要count还存在,就执行循环内容,这样就可以实现了一个一个布置雷的效果,知道雷全布置完这个循环就结束了。

雷的生成有限制,因为这个数组棋盘是11*11的,我们只用到了中间的9*9,所以行数是从1到9,列是1到9。但随机数的生成该怎么实现行和列的随机值而且还符合这个限制呢?我们就可以让随机生成的值去模上row,col,之后再加1,这样就可以实现1-9。但做这些之前首先要给出一个随机数生成的标志。

要用到上述函数需要包含头文件,所以在game.h里面包含上:

接下来布置雷:

如果要布置的地方不是雷,我们就要布置雷进去,所以这里是一个if语句用来实现判断以及布置了的实现:

如果这个坐标位置里面是'0'这个字符,那么就把它变为'1',这就相当于布置成功了,这时候count就减去1,说明布置的雷少了一个。

我们可以在test.c中来看看效果,把打印mine棋盘这个函数写在布置雷之后,编译运行:

这样就实现的催记布置十个雷在mine数组里面,注意==和=号的区别,我在写的时候就写错了,看半天哈哈哈。

2.2.2.5排查雷

我们这里用命名为FindMine的函数来进行排雷。排查雷其实就是看一下这个是不是雷,如果是就被炸了,如果不是,就统计一下周围有几个雷,而且把这个值放到选择的这个坐标。所以FindMine函数是从mine这个数组里面查找雷的信息之后放到show数组里面,而且依旧是9*9的格子,所以里面的参数有两个数组,还有行和列。

还是一样的我们在头文件里面进行声明,之后game.c文件里面进行排雷函数的实现编写。

这里就是实现的排查雷的过程,我们这里用到一个while循环来代表游戏一直进行不间断,直到当我输入的坐标这个位置的字符为1,那么就是踩到了炸弹,就是接游戏结束,打印出mine数组的棋盘,如果没有遇到炸弹,就用一个自定义函数来获取这个坐标附近一圈的炸弹的数量,并且把这个坐标的字符改为附近炸弹的多少,由于这里数组里面都是字符,所以我们需要想一想字符所对应的ASCLL码,因为0字符对应的是48,所以只要加上n就可以实现ASCLL码转数字字符了。接下来再打印show数组。如果输入的坐标不是在这个范围之内,就直接输出"坐标非法,请重新输入",由于是while循环,所以循环一直进行。

注意这里那个提示输入排查坐标那句代码应该写在循环里面,因为如果写在外面,它就只提示一次,下一次就不会再提示了。

2.2.2.6获取坐标附近雷的数量

这里用命名为get_mine_count的函数来代表获取雷附近的数量,就直接在game.c里面写就可以:

一个格子附近有八个格子,我们只需要把这八个格子的字符的ASCLL码值加在一起,之后减去8个'0'就可以得出来周围有几个雷,因为如果一个坐标没有雷的话就是'0',只有有雷的时候才是'1'。

完成这些之后运行一下:

我们可以看见,在mine数组里面第二行第四列放置了一个雷,当我们选中1 4之后,它就会显示这个坐标周围有一个雷,这样这个排雷就可以运行起来了。

实际上我们玩的时候,我们只输出show这个数组,mine数组是不打印出来的,所以我们就可以把输出mine的数组的代码注释掉。

写到这里的时候,就可以进行游玩了,但是什么时候截止就会变成一个问题。

2.2.2.7什么时候截止

因为这个棋盘是9*9的棋盘,也就是81个格子,只要我们找到71个不是雷的格子,就可以结束运行,这样就代表了扫雷都扫到了。

这里就简单的实现一下,通过命名一个win的变量来代表排除雷的数量,当win的值小于71的时候,循环一直进行,当不满足这个条件的时候,还需要判断一下雷是否真的等于71,如果不等于,那么就是被炸了,如果等于,就输出排雷成功。

我们可以修改以下define定义的雷的数量,我们修改成80个之后,只有一个坐标不是雷,那么输入进去后看输出的内容,就可以判断自己是否写对了。当我们排雷成功后,再打印一下mine数组,让我们看一下最后的棋盘。

但这里还有一个问题:

当我们排查过这个坐标后,我们输入这个坐标后我们不想让它继续排查,所以应该看一下被没被排查然后再进行判断是否为雷:

利用continue就可以实现这一功能,如果被排查过它这个坐标应该是这个坐标周围的雷的数量,如果没被排查过这个就是星号,通过这个逻辑就可以解决这一问题。

2.2.2.8如何实现点击后展开一片

我们再玩的时候,正常是选择一个坐标后它可以展开一片,这个坐标的周围的八个坐标分别判断,依次类推,所以就可以实现这一样式。这里我们就可以通过一个递归来解决,但这个递归可以自己想一想,这里只讲一些大致的逻辑和思路。


总结

这里给出源代码:扫雷游戏: C语言版本扫雷游戏 (gitee.com)

大家可以参考上述代码进行自己的学习。

本篇文章基于上一篇的内容很容易实现,知识都是连贯的,只要肯有耐心就可以一直会下去。

相关推荐
Scott9999HH7 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
码智社8 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海8 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖9 小时前
JDK 26 新特性详解
java·开发语言
马优晨9 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区11 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大12 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai12 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
এ慕ོ冬℘゜12 小时前
前端基础:什么是时间戳?JS获取时间戳三种方法与实战用途
开发语言·前端·javascript
执明wa12 小时前
LayoutInflater详解: XML是如何变成View的?
android·xml·开发语言·android studio