目录
棋盘创建
1.对于扫雷游戏,我们首先应该思考一下扫雷可能是怎么实现的,我们在网页上玩的扫雷,我们对某一个坐标进行排查,如果这个坐标后面是雷,那么游戏失败,而如果不是雷,那么就会展开。那么,我们就可以想到,这里可能会用到两个棋盘,一个是展示给我们看的游戏棋盘,一个是棋盘背后的雷区。这里我们就可以想到,这个游戏我们最基本的应该创建两个棋盘,一个用来展示,一个用来判断是否触发了雷。
2.然后,我们要想,我们应该怎么创建这两个棋盘,其次,创建完这两个棋盘之后要做什么。怎么创建这两个棋盘? 我们回过头去看网页版扫雷,附图:
从这里我们大概可以想到,或许可以使用二维数组来实现棋盘。我们不妨假设就使用二维数组来进行棋盘的实现。这里是我创建的两个棋盘。
这里应使用宏常量。便于后续的改进与调试。而且这里创建的两个棋盘都是11 * 11的棋盘,但实际上,棋盘周围的一圈都只是为了进行排查时方便;我们真正的棋盘只是排除周围一圈后中间的部分。这些后续会讲,这里不做赘述;
布置雷盘
那么,棋盘创建完毕后,我们做什么呢。那么,雷盘是不是应该有雷。而我们的棋盘创建的是11 * 11的,去掉周围一圈就是9 * 9的棋盘,我们需要对中间的那部分雷盘进行布置雷。而布置雷是随机的。这就要用到rand函数。布置如图:
菜单创建
布置完雷后,游戏自身基本实现。接下来将目标转向用户。我们首先需要一个菜单,供用户进行操作。如图:
只要用户选择1,那么我们就开始游戏。用户选择0,那么就退出游戏。而用户如果输入了一些其他的数字,那么输入就是不符合规矩的。是非法的。这里显然是类似于开关的操作。如果if进行判断,就显得冗余。而用户每进行完毕一场游戏后,不应该程序自己退出,应该返回菜单界面,用户自己来选择是否退出游戏。那么这里就需要用到循环。这里的逻辑关系应该是:用户输入0-->退出循环;用户输入1-->开始游戏-->游戏结束-->进入循环;用户输入非法-->重新进入循环;则大致应该产生以下的程序段:
int input = 1;
while(input != 0)
{
scanf("%d", input);
switch(input)
{
........//游戏,推出,或者非法输入;
}
}
下图为本人创建的菜单:
打印棋盘
开始游戏后,首先第一件事情应该是打印棋盘;打印棋盘同样的,我们只打印中间的部分,而不是打印全部的棋盘。如图:
现在我们来看一下运行效果:
排查雷区
大体实现
打印实现之后就是进行排雷;这里我们要思考一个问题:假如,我们排查的坐标已经被排查过,那么,这一次的输入就应该无效。而且,我们应该怎么判定游戏获胜。判断游戏获胜,我们可以创建一个类似于计时器的变量。这个变量的作用是代表非雷的坐标的个数,当个数为零时,游戏成功。而且这个过程还是个循环;所以,这里分为这么几种情况:
坐标已经被排查过-->输入非法,重新输入;
坐标没有被排查过-->触发雷-->游戏失败;
坐标没有被排查过-->没有触发雷-->对周围的雷进行统计,将雷的个数赋值给当前坐标;并且非雷个数减一;
统计周围雷的个数
现在我们回过头去看开始定义棋盘时为什么要多定义一圈。我们再看对周围的雷进行统计的过程。假如,我们没有多定义这一圈,我们在对棋盘边缘的坐标进行周围雷的个数排查时,就会发生越界访问。我们的程序就可能崩溃。但是,我们可能会想到,进行判断不行吗?当坐标是棋盘边缘时,我们就让它不要将周围的坐标全部遍历访问。这个问题如果仔细想的话,是很难实现的,因为如果要去进行判断的话,那样情况太多。不谈四个棋盘角落的地方。单单只考虑棋盘的四个边,就要用到四个不同的判断语句,而考虑上四个角落的地方,那样情况就会更多。过于复杂。所以不可取。那么我们就可以多定义这么一圈。让我们更好的进行雷的统计。下图为我的雷的个数统计的实现:
整体实现
到这里,扫雷游戏基本就实现了。下面来看一下效果:
扫雷就基本完成了。
扩展
递归展开
接下来我们来讨论扩展,首先是递归展开。我们平常玩的扫雷游戏,经常遇到第一下就展开许多坐标的情况。如图
这里的规则就是,假如我们排查的坐标周围没有,那么游戏就会自动展开周围的坐标。直到遇到一个坐标且该坐标周围有雷 。那么就会停止展开。我们在上面已经设计了函数计算坐标周围雷的个数。也就是说,只要我们该函数的计算结果为0。那么程序自己就会排查周围的坐标。这个过程需要用到递归。这里我将我的代码重新改动,将"坐标没有被排查过-->没有触发雷-->对周围的雷进行统计,将雷的个数赋值给当前坐标;并且非雷个数减一;"这种情况封装成为了一个函数如图:
需要思考的是这里的递归的限制条件一定要选择正确,并且注意什么时候进行非雷个数减一。先说递归条件。需要明确的是,在递归的过程中不应该出现对同意坐标多次排查的情况。我们假设在一次完整递归过程中多进行了n次多余的排查。那么计数器由于只要发现不是雷就减一,只要发现不是雷就减一,最后就会导致计数器不准,出现问题。所以。递归的过程是不能重复的。判断*就可以避免出现重复的状况。因为只要我们或者递归排查了该坐标。那么这个坐标就会被改变为数字,或者空格。
第一下一定不是雷
对于这个处理。我们需要做到的是:假如玩家第一下碰到了雷,那么这个雷就会消失,转移到其他非雷的坐标处。这里需要思考判断条件。怎么来判断玩家是第一次排查坐标?怎么来判断被转移坐标不是雷?第二个问题比较简单,只要雷盘上被转移坐标不是雷,就埋雷;否则就重新判断。然后分析第一个问题,我们是不是同样可以看*,可以,但很麻烦。需要遍历整个内部棋盘。统计*的个数,假如是第一下,那么*的个数就 == row * col; 这里有一个更简单的方法,那就是使用计数器;假如玩家进行排雷时计数器为 row * col - 雷的个数; 那么就是第一次排查。这此排查玩家不能排查到雷(如果是雷,将雷转移)下面是代码的实现;
接下来看一下效果:
插旗操作
插旗操作只要指定特定的操做作为开始插旗的标志即可。值得注意的是插完棋子需要重新开始一轮循环。下面为代码图: