文章目录
一、扫雷游戏整体设计思路1.扫雷游戏功能说明2.游戏的分析和设计3.文件结构设计: 二 、主函数大致模型三、创建棋盘四、初始化棋盘五、打印棋盘六、布置雷七、排查雷八、源码九、如何把游戏分享给小伙伴十、扫雷进阶的一些思路
一、扫雷游戏整体设计思路
1.扫雷游戏功能说明
使⽤控制台实现经典的扫雷游戏
(1)游戏可以通过菜单实现继续玩或者退出游戏
(2)扫雷的棋盘是9*9的格⼦
(3) 默认随机布置10个雷
(4)可以排查雷
◦ 如果位置不是雷,就显⽰周围有⼏个雷
◦ 如果位置是雷,就炸死游戏结束
◦ 把除10个雷之外的所有⾮雷都找出来,排雷成功,游戏结束
2.游戏的分析和设计
扫雷的过程中,布置的雷和排查出的雷的信息都需要存储,所以我们需要⼀定的数据结构来存储这些信息
因为我们需要在99的棋盘上布置雷的信息和排查雷,我们⾸先想到的就是创建⼀个99的数组来存放信息
如图,创建一个9 * 9的棋盘,我们这样定义,在这样一个数组中,字符0表示不是雷,字符1表示雷,当我们排查2,4这个坐标时,如果不是雷,就算出它周围8个坐标内有几个雷,然后显示在2,4这个位置,这就是排查雷
但是我们来看另一个坐标,如0,0这个坐标,我们发现要排查的有一部分区域超出边界了,会产生越界访问的情况,但是也不能专门写一个代码解决,因为除了这个0,0还有0,1、0,2·····等等的坐标会出现越界访问,如果每一个都写一个代码来解决就太麻烦了,怎么做呢?
我们可以在创建棋盘的时候扩大一圈,变成11 * 11的棋盘,但是只显示内部9 * 9的棋盘,如:
这样就有效避免了越界访问
再继续分析,我们在棋盘上布置了雷,棋盘上雷的信息(1)和⾮雷的信息(0),假设我们排查了某⼀个位置后,这个坐标处不是雷,这个坐标的周围有1个雷,那我们需要将排查出的雷的数量信息记录存储,并打印出来,作为排雷的重要参考信息的。那这个雷的个数信息存放在哪⾥呢?如果存放在布置雷的数组中,这样雷的信息和雷的个数信息就可能或产⽣混淆和打印上的困难,比如周围有1个雷,就要显示1,但是1原本是表示雷的,就导致了混淆
解决办法就是:我们专⻔给⼀个棋盘(对应⼀个数组hide)存放布置好的雷的信息,再给另外⼀个棋盘(对应另外⼀个数组show)存放排查出的雷的信息。这样就互不⼲扰了,把雷布置到hide数组,在hide数组中排查雷,排查出的数据存放在show数组,并且打印show数组的信息给后期排查参考
同时为了保持神秘,show数组开始时初始化为字符 ‘*’,为了保持两个数组的类型⼀致,可以使⽤同⼀套函数处理,hide数组最开始也初始化为字符’0’,布置雷改成’1’,如图:
hide数组:
show数组:
3.文件结构设计:
我们之前学习了多⽂件的形式对函数的声明和定义,这⾥我们实践⼀下,我们设计三个⽂件:
test.c //⽂件中写游戏的测试逻辑 game.c //⽂件中写游戏中函数的实现等game.h //⽂件中写游戏需要的数据类型和函数声明等
二 、主函数大致模型
主函数中的大致模型就和之前讲过的猜数字游戏相似,这里就不再过多赘述,直接上代码,后面的游戏制作才是重点
//文件test.cvoid menu(){printf("********************\n");printf("**** 1.开始游戏 ****\n");printf("**** 0.退出游戏 ****\n");printf("********************\n\n");printf("提示:请输入1或0\n");}void game(){}int main(){srand((unsigned int)time(NULL));int input = 0;do{menu();printf("\n请输入:");scanf("%d", &input);switch (input){case 1:game();break;case 0:printf("游戏退出成功\n");break;default:printf("\n输入错误,请重新输入!\n\n");break;}} while (input);return 0;}
三、创建棋盘
经过前面的分析,我们知道了需要11 * 11的数组,并且要创建两个,一个作为表面显示的棋盘,用于排雷,一个不让用户看见,用来布置雷,并且类型为char,代码如下:
void game(){ //隐藏起来用于布置雷: char hide[11][11] = {0}; //展示出来用于排查雷: char show[11][11] = {0};}
四、初始化棋盘
经过最上面的思路分析,我们最好将hide数组全部初始化为字符0,表示全部都不是雷,然后后续再对其加入雷(也就是字符1),将show数组全部初始化为字符*,增加神秘感
由于game函数中可能会有很多的代码,所以为了简明大方,我们将初始化棋盘以及后面的步骤都封装为函数,然后再game.h中进行声明,在game.c中实现
我们开始分析:
//由于这是声明,我们写在game.h中//显示的棋盘大小#define Row 9#define Col 9//真正的棋盘大小#define Rows Row+2#define Cols Col+2
以后我们需要用到棋盘大小的地方就用定义的这些符号就行,当然,这些符号也是可以自行定义的,这里只是我的写法,然后如果以后想改棋盘大小就只需要把9改掉,就特别方便(记得把上面创建数组)
函数的声明:函数的声明要写在game.h中,由于很简单,这里只讲一次,后面讲到的函数就不会再讲到,自行推导就行,声明只需要写出函数的返回值,函数名,括号,形参,最后还有一个分号,如://声明初始化棋盘函数void initboard(char board[Rows][Cols], int rows, int cols, char x);
函数的实现:函数的实现我们就放在game.c中,思路就是通过函数传过来的行列进行遍历数组,把每一个元素初始化为传过来的字符,代码如下: //实现初始化棋盘函数:void initboard(char board[Rows][Cols], int rows, int cols, char x){int i = 0;int j = 0;for (i = 0; i < rows; i++){for (j = 0; j < cols; j++){board[i][j] = x;}}}
函数的使用:这个很简单,按照我们的形参,把实参写出来就行,如: //初始化棋盘:initboard(show, Rows, Cols, '0');initboard(hide, Rows, Cols, '*');
五、打印棋盘
经过上面的初始化,我们现在还看不到棋盘,我们就先写一个打印棋盘的函数,将它们打印出来,看看我们初始化成功没有,步骤也和刚刚一样
我们需要这个函数帮我们把棋盘中间9 * 9的大小打印出来,并且有一个标识,设计步骤如下:
//声明打印棋盘函数:void printboard(char board[Rows][Cols], int row, int col);
函数的实现:(1)在平常一样打印数组时,我们需要注意,每打印完一行就要进行一次换行
(2)最好标出行号和列号,这样玩家在玩的时候才方便输入坐标进行排雷
(3)我们可以在棋盘的上下做一点标识,让玩家更加醒目的看到棋盘
(4)虽然棋盘也就是数组的真正大小是11 * 11,但是真正显示的是中间9 * 9的部分,传参要传Row和Col而不是Rows和Cols
(5)代码:
//实现打印棋盘函数:void printboard(char board[Rows][Cols], int row, int col){int i = 1;int j = 1;//打印棋盘标志,让棋盘更显眼 printf("------ 扫雷 -------\n");//打印列号:for (i = 0; i <= col; i++){printf("%d ", i);}//列号打印完进行换行printf("\n");for (i = 1; i <= row; i++){//打印行号printf("%d ", i);for (j = 1; j <= col; j++){printf("%c ", board[i][j]);}//每打印完一行就要进行一次换行:printf("\n");}//打印棋盘标志,让棋盘更显眼 printf("------ 扫雷 -------\n");//棋盘打印完之后进行换行printf("\n");}
函数的使用:函数的一切都已经准备好了,这时我们只需要按照参数传参即可,我们游戏的要求只需要显示show数组,为了观察一下hide数组是否成功初始化可以使用一下,随后进行注释操作,如:
//打印棋盘:printboard(show, Row, Col);//printboard(hide, Row, Col);
六、布置雷
经过第一部分的分析,我们可以知道,字符0代表不是雷,字符1表示雷,并且布置雷的对象是数组hide,之前我们把hide数组全部初始化为了字符0,这时我们只需要随机地在整个棋盘上布置雷,本质上就是随机地将一些字符0改成字符1,而随机数的产生要依靠rand函数和srand函数,以及时间戳,这些在猜数字游戏中有将到,如果忘了可以自行复习,后面直接贴上代码
布置雷函数的命名:我将其命名为setboard,也可自行命名函数参数:首先要把hide数组传过去,随后我们会使用行和列,要将Row和Col传过去,然后就是要布置的雷的个数,由于雷的个数也是经常出现的常数,所以我们也可以用define定义一下,方便以后的更改,如://雷的个数:#define Count 10
函数的声明:此处不做赘述,代码如下: //声明布置雷函数:void setboard(char board[Rows][Cols],int row,int col, int count);
函数的实现:(1)首先我们需要产生1~Row的随机数,也就是1到雷的个数的随机数,只需要rand()%Row+1或者rand()%Col+1即可,rand如何产生随机数此处不在赘述,之前讲的猜数字游戏有,这里提醒一点就是记得将头文件放在game.h中
(2)随后就是布置雷,也就是将字符0改成字符1的过程,这个很简单,只需要用到一个if语句
(3)代码:
//实现布置雷函数:void setboard(char board[Rows][Cols],int row,int col, int count){while (count){int x = rand() % row + 1;int y = rand() % col + 1;if (board[x][y] != '1'){board[x][y] = '1';count--;}}}
函数的使用:只需要将hide函数和雷的个数(即Count)传给函数即可,如: //布置雷:setboard(hide, Count);
注意,在用的时候,要把布置雷这个步骤写在打印棋盘前,如图:七、排查雷
排查雷的重点就是,我们要看玩家输入的坐标位置是不是雷,也就是是不是字符1,如果是字符1,说明踩到雷了,游戏失败,如果是字符0,也就是不是雷,那么该处就显示为周围8个坐标的雷的个数,所以它既要涉及到show数组,又要涉及到hide数组,hide数组用来查看玩家输入的坐标是否是雷,show数组用于该坐标不是雷的时候,在该坐标处显示周围有几个雷
函数的命名:我将它命名为findboard,可以自行命名函数的参数:由于既要涉及到show数组,又要涉及到hide数组,所以前两个参数就是两个数组,由于我们要判断用户输入的值是否合法,也就是是否在1~Row或者Col,所以我们需要将函数的Row和Col传过去,又因为游戏胜负和雷的个数有关,所以我们要将雷的个数传给函数函数的声明:声明如下://声明排查雷函数void findboard(char show[Rows][Cols], char hide[Rows][Cols], int row,int col,int count);
4.函数的实现:
(1)首先我们需要让用户输入一个坐标,并且要确保坐标正确,否则显示提示信息
(2)如果坐标正确,那么就判断坐标处是否是雷,如果是雷就直接炸死,游戏失败,当然,为了让玩家知道自己怎么失败的,我们可以将含有雷信息的数组hide打印出来
(3)如果不是雷,那么我们就想办法获取周围雷的个数,我们可以再创建一个函数getcount来解决,这个函数就帮我们统计周围雷的个数
(4)由于代表是否是雷的元素是字符0或者字符1,无法像整型一样直接相加,因为字符0代表的ascll码值是48,与整型的0不同,怎么解决呢?我们可以这样,让玩家周围的所有字符相加然后全部减去字符0,如果一个位置是字符1,减去字符0就成了真正的1,字符0减去字符0就成了真正的0,我们可以让坐标周围的八个坐标的字符相加,再减去8个字符0,就得到了雷的个数
(5)坐标周围雷的个数也最多就8个,也就是最多返回一个8的整数,我们怎么把它变成字符,存放在show数组的相应位置呢?此时我们又可以加上一个’0’,这时雷的个数就变成了字符
(6)如果玩家排雷成功要如何结束游戏呢?我们可以使用一个新的变量win,每次用户排雷就对它进行自增1的操作,直到win= =row * col-count,也就是循环条件是win<row*col-count,如果win= =row * col-count也就说明了玩家排雷成功,这时就可以输出扫雷成功的信息
(7)getcount函数实现:将其放在findboard函数前定义就行,不用在game.h进行声明,代码如下:
//如果坐标不是雷,获取坐标周围雷的个数int getcount(char hide[Rows][Cols], int x, int y){return hide[x][y-1] + hide[x][y+1] + hide[x - 1][y] + hide[x-1][y-1] +hide[x-1][y+1]+ hide[x + 1][y]+ hide[x + 1][y-1] + hide[x + 1][y+1]-8*'0';}
这是我觉得比较简单的思路,当然还有另一种思路,如:
//如果不是雷,获取坐标周围雷的个数char getcount(char hide[Rows][Cols],int x,int y){char num = 0;int i = 0;int j = 0;for (i = -1; i <= 1; i++){for (j = -1; j <= 1; j++){num += hide[x + i][y + j];}}num = num - 8 * '0';return num;}
这样也可以直接返回字符,思路也不难,就是比较难写
(8)findboard函数实现:此处的代码使用的是第一个getcount的方法,代码如下:
//实现排查雷函数:void findboard(char show[Rows][Cols], char hide[Rows][Cols],int row,int col, int count){int x = 0;int y = 0;int win = 0;printf("请输入空格或英文逗号隔开坐标!\n\n");while (win<Row*Col-Count){printf("请输入要排查的坐标:");//这里需要用到之前学过的%*c,就是//scanf的赋值忽略符*//使用过后就可以输入空格或者英文逗号隔开坐标,但是不能用中文//这里中文和英文逗号不能都使用//所以我们最好在开始时给予一些提示scanf("%d%*c%d", &x, &y);printf("\n");if (x >= 1 && x <= row && y >= 1 && y <= col){if (hide[x][y] == '1'){printf("很遗憾,你踩到雷了,游戏失败!\n\n");printf("显示的0代表不是雷,1表示雷\n\n");printboard(hide, Row, Col);break;}else{win++;int ret = getcount(hide, x, y);show[x][y] = ret + '0';printboard(show, Row, Col);}}else{printf("输入有误,请重新输入!\n");}}if (win == Row * Col - Count){printf("恭喜你,扫雷成功!\n\n");printf("显示的0代表不是雷,1表示雷\n\n");printboard(hide, Row, Col);}}
八、源码
整个扫雷游戏的思路已经讲解完毕,接下来附上每个文件的源码,中间为了美观,可能会加了不少\n进行换行,但是不影响阅读代码,源码如下:
game.h:
//game.h#include <stdio.h>#include <stdlib.h>#include <time.h>//显示的棋盘大小#define Row 9#define Col 9//实际的棋盘大小#define Rows Row+2#define Cols Col+2//雷的个数:#define Count 10//声明初始化棋盘函数:void initboard(char board[Rows][Cols], int rows, int cols, char x);//声明打印棋盘函数:void printboard(char board[Rows][Cols], int row, int col);//声明布置雷函数:void setboard(char board[Rows][Cols], int count);//声明排查雷函数void findboard(char show[Rows][Cols], char hide[Rows][Cols], int row,int col,int count);
game.c:
//game.c#include "game.h"//实现初始化棋盘函数:void initboard(char board[Rows][Cols], int rows, int cols, char x){int i = 0;int j = 0;for (i = 0; i < rows; i++){for (j = 0; j < cols; j++){board[i][j] = x;}}}//实现打印棋盘函数:void printboard(char board[Rows][Cols], int row, int col){int i = 1;int j = 1;//打印棋盘标志,让棋盘更显眼printf("------ 扫雷 -------\n");//打印列号:for (i = 0; i <= col; i++){printf("%d ", i);}//列号打印完进行换行printf("\n");for (i = 1; i <= row; i++){//打印行号printf("%d ", i);for (j = 1; j <= col; j++){printf("%c ", board[i][j]);}//每打印完一行就要进行一次换行:printf("\n");}//打印棋盘标志,让棋盘更显眼printf("------ 扫雷 -------\n");//棋盘打印完之后进行换行printf("\n");}//实现布置雷函数:void setboard(char board[Rows][Cols], int count){while (count){int x = rand() % Row + 1;int y = rand() % Col + 1;if (board[x][y] != '1'){board[x][y] = '1';count--;}}}//如果坐标不是雷,获取坐标周围雷的个数int getcount(char hide[Rows][Cols], int x, int y){return (hide[x][y-1] + hide[x][y+1] + hide[x - 1][y] + hide[x-1][y-1] +hide[x-1][y+1]+ hide[x + 1][y]+ hide[x + 1][y-1] + hide[x + 1][y+1] - 8*'0');}//实现排查雷函数:void findboard(char show[Rows][Cols], char hide[Rows][Cols],int row,int col, int count){int x = 0;int y = 0;int win = 0;printf("请输入空格或英文逗号隔开坐标!\n\n");while (win<Row*Col-Count){printf("请输入要排查的坐标:");//这里需要用到之前学过的%*c,就是//scanf的赋值忽略符*//使用过后就可以输入空格或者英文逗号隔开坐标,但是不能用中文//这里中文和英文逗号不能都使用//所以我们最好在开始时给予一些提示scanf("%d%*c%d", &x, &y);printf("\n");if (x >= 1 && x <= row && y >= 1 && y <= col){if (hide[x][y] == '1'){printf("很遗憾,你踩到雷了,游戏失败!\n\n");printf("显示的0代表不是雷,1表示雷\n\n");printboard(hide, Row, Col);break;}else{win++;int ret = getcount(hide, x, y);show[x][y] = ret + '0';printboard(show, Row, Col);}}else{printf("输入有误,请重新输入!\n");}}if (win == Row * Col - Count){printf("恭喜你,扫雷成功!\n\n");printf("显示的0代表不是雷,1表示雷\n\n");printboard(hide, Row, Col);}}
test.c:
#include "game.h"void menu(){printf("********************\n");printf("**** 1.开始游戏 ****\n");printf("**** 0.退出游戏 ****\n");printf("********************\n\n");printf("提示:请输入1或0\n");}void game(){//不显示出来,用于布置雷char hide[Rows][Cols];//显示出来,用于排查雷char show[Rows][Cols];//初始化棋盘:initboard(hide, Rows, Cols, '0');initboard(show, Rows, Cols, '*');//布置雷:setboard(hide, Count);//打印棋盘:printboard(show, Row, Col);//printboard(hide, Row, Col);//排查雷:findboard(show, hide,Row,Col, Count);}int main(){srand((unsigned int)time(NULL));int input = 0;do{menu();printf("\n请选择:");scanf("%d", &input);switch (input){case 1:game();break;case 0:printf("游戏退出成功\n");break;default:printf("选择错误,请重新输入!\n");break;}} while (input);return 0;}
九、如何把游戏分享给小伙伴
相信你已经尝试玩了这个扫雷游戏,是不是特别想分享给朋友室友玩一玩呢?跟着我来学习吧
首先打开VS,将上方的debug模式改为release版本,随后将代码运行一次,如图:
然后找到存放该文件的文件夹,找到x64文件夹,然后找到release文件夹,打开后找到扩展名为exe的可执行文件,直接将其发送给朋友就可以了,是不是非常简单呢!
十、扫雷进阶的一些思路
• 是否可以选择游戏难度
◦ 简单 9 * 9 棋盘,10个雷
◦ 中等 16 * 16棋盘,40个雷
◦ 困难 30 * 16棋盘,99个雷
• 如果排查位置不是雷,周围也没有雷,可以展开周围的⼀⽚
• 是否可以标记雷
• 是否可以加上排雷的时间显⽰
如果小伙伴们有兴趣,后续还可以更新这些进阶扫雷需求,当然,如果排查位置不是雷,周围也没有雷,可以展开周围的⼀⽚,这个需要用到函数递归的知识点,后面讲到递归直接讲了,其它进阶需求出不出可以看小伙伴们的意愿,想看哪个直接评论区留言哦~
这篇关于扫雷的博客到此结束了,如果对你有帮助的话记得一键三连哦~