扫雷游戏详解

此篇文章的目的是在VS控制台上通过9*9的棋盘实现模拟扫雷游戏。

从本句话中可知:

1. 需要一个9*9的初级棋盘       

2. 布置雷的数量(初始化棋盘)   

3.开始排雷 (1) 是雷提示炸死(2)非雷显示此坐标周围雷的个数(3)除雷外的全部坐标都排查完则获胜

效果展示:

    


以上是网页版扫雷小游戏原型,相信大家都玩过。接下来将通过代码逐步讲解模拟实现游戏

一、思维再现

1. 歧义问题

首先需要一个9*9的棋盘,在上面布置10个雷;要将这些信息存储起来我想到创建一个9行9列的数组,并规定雷用1来表示,非雷用0表示

那问题就来了,当我们开始游戏,选择的坐标周围只有一个雷的时候也会显示周围雷的个数1,这时就产生了歧义:这个1是布置的雷还是周围雷的个数

于是想到创建两个数组,一个用来布置雷,另一个用来显示排查出周围雷的信息,这样就避免了歧义;也许有人会想用不同的字符“*#”表示也可避免歧义,但是在一个棋盘内多种符号的判断容易乱,不妨大家再接着往下看,看看我是如何分析

2. 坐标越界问题

  

以上是排雷的假设,1处就是布置的10个雷的位置。若我们输入坐标(2,5)这个坐标不是雷,这个坐标周围(黄圈)有1个雷;若输入坐标(8,6),这时就发现了问题:它周围有三个坐标超出了数组的范围,那这可咋整?我要排周围雷的个数,排着排着发现越界了。此时先不要着急,解决办法就是将数组扩大一圈成11*11,设置雷还是在9*9的棋盘格内,周围一圈不去布置雷就行了;为保持一致性,存放雷信息的数组也扩大一圈,这样就解决了排雷越界问题,如下图绿色是扩大一圈的棋盘

这样做是浪费了一定的内存空间,换来了更大的便利。我们要尽量节省内存空间,但有时避免不了浪费一定的空间。

3. 数组类型问题

上面提到要创建两个数组,一个用来布置雷,一个用来存放雷的信息。那这个数组要什么类型呢,int?还是 char?

                                                

我希望的效果是这样的:(1)在还没布置雷的时候棋盘全部初始化为0,这样布置10个雷就有10个1,更容易与不是雷的位置做区分,方便看出雷的位置(2)在还没开始游戏前先打印9*9的 '*' 营造一种神秘感

既然要打印字符‘*’,存放雷信息的数组当然得用字符数组;设置雷的数组为了保持一致性也用字符类型,那么它初始化的时候就不能用数字0来初始化而是字符‘0’,雷同样也不是数字1,而是字符‘1’

二、过程详解(编程思维)

为了方便分模块化管理代码,需要创建三个文件:

再次说明,整个游戏的呈现主要围绕这几点:棋盘(初始化)、布雷排雷(三种情况)

1. 初始化数组函数

我希望程序一运行起来就先打印一个简易菜单供玩家选择:1开始游戏、0退出游戏、其他提示重新输入,do_while嵌套switch循环可实现,玩家的选择为循环的结束判断条件,代码如下:

如是代码写完要测试一下是不是我们要的结果,结果无误就进行下一步。当玩家选1开始游戏,我把整个游戏的实现封装为一个函数。开始游戏我们要创建11*11的 mind数组来存放雷和11*11的 show数组来显示雷的信息;接着就是初始化两个数组(把数组传过去,行、列传过去)在game.h文件中声明函数,game.c中具体实现函数

在此过程中会遇到一个问题,如何使用InitBoard函数在初始化mind数组时是'0',show数组时又是'*'?

既然mind和show数组中都是字符,那干脆把要初始化的字符也传过去,这样InitBoard函数就可以根据不同需求初始化;又考虑到11*11的棋盘不一定是固定的,后期可以根据需求方便修改棋盘大小,所以将它行和列使用全局变量定义,方便后期更改;每个.c文件都要包含game.h头文件,那直接把stdio.h头文件放在我们自己定义的game.h头文件中声明,当包含自己头文件时也把其包含进去了

改进后代码如下:

2. 打印棋盘函数

初始化完棋盘,我们当然希望打印出来看看是否有什么地方需要优化或与预期不同,于是就涉及到打印棋盘函数

打印棋盘DisplayBoard函数是对mind和show数组进行操作所以要把数组传过去,但打印的时候我们还是希望画面呈现出来的是9*9的正常棋盘格而不是11*11;玩家只要看到扫雷游戏的标准页面就行,没必要把程序员如何设计排雷而扩大的数组打印出来,所以传过去的是9行9列,初级扫雷是9*9 中级可就不是了,所以也把9*9用全局变量表示易于后期修改

最后打印出来成果:

3. 随机布雷函数

布雷函数Setmind只对mind数组进行操作,只在9*9的格子中布雷

雷的个数可改变,也用全局变量定义,通过while循环10次将10个雷布置进去;布雷要随机得调用rand函数和初始化随机数种子srand,分别用到的头文件是stdlib.h 和 time.h(生成随机数为什么用这些个函数,要怎么个调用法,在我另一篇文章>分支和循环(下)有详细讲解,此处不再赘述)

最后可以运行下代码,每次10个字符'1'都出现在不同位置

4. 排雷函数

在排雷的时候两个数组都要用到,所以传参时两个数组都传过去,所有的结果都展现在9*9的棋盘上所以行列都传9过去,为了不容易混淆我们也用相同名字的数组接收(形参和实参同名没有问题)

首先在屏幕上提示玩家输入要排查雷的坐标,玩家输入坐标后要判断这个坐标是否合法,用if语句进行判断,输入坐标必须>1并且<=9

以上逻辑理清楚后,这一整个输入坐标再判断的过程并不是只执行一次,所以写成while循环,循环的条件先暂时让它死循环;坐标无误进入if内部,判断输入的坐标在mind数组中是否是雷(雷用字符'1'表示),是炸死游戏结束;不是就统计坐标周围一圈雷的总个数并打印在show数组中,然后把show数组打印出来给玩家;其中统计雷的个数再封装为GetMindCount函数,将返回的个数存放在r变量中,再将show数组对应的位置改为r值

接下来实现一下GetMindCount函数,统计坐标周围雷的个数;要排查的坐标是(x,y)它周围一圈用x,y表示的九宫格如图所示,从1-8全部相加起来就是坐标(x,y)周围雷个数;注意:GetMindCount函数返回个数是int类型,而我们一开始mind数组和show数组是字符类型,字符'1'要变成数字1只要减去字符‘0’即可,把8个坐标加起来-8*‘0’就转换为数值,符合该函数的返回类型。

我们要把r的值放到show字符数组中去,r是数值,让它+‘0’转换为字符存入show中

更改如下:

我们可以看到整个排雷函数基本完成,最后一个问题是如何跳出while循环

不难发现只有当你被炸死的时候才能终止循环,不过游戏中还有一种情况是,当你把所有非雷位置排完也可终止循环打印排雷成功;所以我们跳出循环的条件就是 :(1)炸死(2)排雷成功;定义个win变量,当win变量<9*9-10(71个待排雷)就一直循环,直到把全部非雷位置找到或炸死跳出循环

跳出循环后判断是被炸死跳出的还是排雷成功跳出,整个排雷函数结束。

最后祝各位排雷成功!

三、代码展示

game.h文件:

game.c文件:

test.c文件:

代码运行结果:

           


全剧终

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值