write in front :
个人主页 : @啊森要自信的主页
作者寄语 : 小菜鸟的力量不在于它的体型,而在于它内心的勇气和无限的潜能,只要你有决心,就没有什么事情是不可能的。
欢迎大家关注点赞收藏⭐️留言>希望看完我的文章对你有小小的帮助,如有错误,可以指出,让我们一起探讨学习交流,一起加油鸭。
文章目录
- 一、扫雷游戏分析和设计
- 1.1 扫雷游戏的功能说明
- 1.2 游戏的分析和设计
- 1.2.1 数据结构的分析
- 1.2.2 ⽂件结构设计
- 二、扫雷游戏代码实现
- 总结
一、扫雷游戏分析和设计
1.1 扫雷游戏的功能说明
• 使⽤控制台实现经典的扫雷游戏
• 游戏可以通过菜单实现继续玩或者退出游戏
• 扫雷的棋盘是9*9
的格⼦
• 默认随机布置10
个雷
• 可以排查雷
◦ 如果位置不是雷,就显⽰周围有⼏个雷
◦ 如果位置是雷,就炸死游戏结束
◦ 把除10个雷之外的所有雷都找出来,排雷成功,游戏结束
游戏的界⾯:
初始游戏画面:
排雷画面:
排雷失败画面:
1.2 游戏的分析和设计
1.2.1 数据结构的分析
◦ 如果位置不是雷,就显⽰周围有⼏个雷
◦ 如果位置是雷,就炸死游戏结束
◦ 把除10个雷之外的所有雷都找出来,排雷成功,游戏结束
扫雷的过程中,布置的雷和排查出的雷的信息都需要存储,所以我们需要⼀定的数据结构来存储这些信息。
因为我们需要在9*9
的棋盘上布置雷的信息和排查雷,我们⾸先想到的就是创建⼀个9*9
的数组来存放信息。
那如果这个位置布置雷,我们就存放1,没有雷我们就存放0
假如排查的是(3,2)
这个坐标,我们就访问绿色一圈里面,除黄色方格的八个方位的雷的个数,然后统计周围的雷的个数。
但是问题来了,但当我们如果要排查
(8,1)
这个坐标的时候,当我们开始访问周围一圈8个位置,统计雷的个数时,最下面的三个位置由于没有开辟空间,会导致数组越界访问,越界访问,代码就执行不起来了呀?这怎么办。
既然他会数组越界,那我们不让他越界就好了呀!既然99访问四周边缘的时候会多访问一层空间,那我们就再包裹一圈,也就是创建1111的数组,我们在外层里面不放雷,到时候访问坐标(8,1)
的时候,直接就可以访问了,这样就解决了数组越界的问题。
此时此刻我们的雷阵大小的设计是不是创建好了呢,雷是1
,非雷是0
,如果我们要排查一个某一位置,这个坐标是不是雷,这个坐标周围有一个雷,我们将统计周围的雷数量打印在棋盘上的时候,这个雷如果打印在棋盘上面,那到底是雷的个数信息,还是雷的信息,这就产生了混淆和打印上的困难,怎么办呢?
如果我们加多一个棋盘,玩家点击该棋盘上的信息对应到埋雷的棋盘上的信息。将雷的信息放在一个棋盘上,而雷的个数信息通过专门给玩家猜雷的棋盘,而玩家猜雷的棋盘统计雷的棋盘并打印在玩家的棋盘上,而我们的埋雷的棋盘上的信息并没有发生相应的改变。
读到这里,哎,你可能会想到,我一个棋盘也可以搞定呀!非雷用#
,雷用&
,然后用数字1,2,3...
来统计周围雷的数量就可以了,谁说过一定要用0
和1
来代表非雷与雷。没错,这种办法也可以,博主也期待你们去尝试一下,但是这样做棋盘上既有雷和非雷的信息,还有排查出雷的个数信息,就比较混杂,但不够方便!
1.2.2 ⽂件结构设计
当我们去写一个游戏,设计一个游戏时,我们可以采用分文件的形式来写:
1 test.c //⽂件中写游戏的测试逻辑2 game.c //⽂件中写游戏中函数的实现等3 game.h //⽂件中写游戏需要的数据类型和函数声明等
举个例子:
当然这个代码也可以执行起来:
二、扫雷游戏代码实现
通过上面的形式,我们把文件分成三个文件管理
1. removal_of_mines.c 2. game.c 3. game.h
首先,我们一上来就要打印菜单给玩家看,我们可以选择do...while
循环,而菜单通过函数封装,用menu()
打印菜单,然后玩家输入,switch语句来辨认玩家的选择,玩与不玩,接下来,巧妙的地方来了,我们把玩家输入的选择作为do....while(intput)
的循环条件,因为这样可以让玩家选择继续玩,或者退出!点击(猜数字小游戏的一步一步实现)的步骤一样
# define _CRT_SECURE_NO_WARNINGS 1#include "game.h"void menu(){printf("******************************************\n");printf("******************************************\n");printf("*********** 1. 扫雷游戏启动!!*******\n");printf("************0. 不玩了,启动不了 ********\n");printf("******************************************\n");printf("******************************************\n");}int main(){int input = 0;do //因为我们一上来就是要打印菜单,因此我们选择do.....while语句{menu();//通过函数封装,用menu()打印菜单printf("请选择-->");scanf("%d", &input);//然后玩家输入switch (input)//用switch分支选择选择玩与不玩{case 1:printf("扫雷游戏启动!!\n");game();break;case 0:printf("不玩了,启动不了!\n");break;default:printf("输入错误,请重新输入\n");break;}} while (input);//选择1继续循环,0退出,这也是采用do.....while语句的好处return 0;}
接下来,我们要实现game(),我们刚才分析得出,创建两个数组,一个埋雷的棋盘,一个用来给用户看和猜的棋盘;
埋雷的棋盘:char mine[][]={0};猜雷的棋盘:char player[][]={0};
到这里,我们是不是只要把9放进去就好了呢?但是这样不利于后面代码的维护,如果要改为16*16的棋盘呢?就要把整个程序的char mine[9][9]里的9
改为16
,一个一个的改,也可以,不过有点费力。如果代码很长要改的数量很多,有几个漏改的,代码也就会出错,因此为了以后代码的修改和长远的考虑,我们可以#define定义ROW(行)和COL(列),
也就是
#define ROW 9#define COL 9char mine[ROW][COL]={0};char palyer[ROWS][COLS]={0};
但是这样九行就列真的可以了吗?哎,显然还不行,因为,前面我们分析了数组会越界,因此这里我们要定义11*11数组,那我们是可以加2:
#define ROWS ROW+2#define COLS COL+2
因此数组创建为:
char mine[ROWS][COLS]={0};char player[ROW]S[COLS]={0};
这个时候,数组的大小创建好了,接下来就是对数组初始化了,也就是给我们两个棋盘放我们的初始值,那我们就创建一个初始化函数吧!
InitBoard(player, ROWS, COLS);
void InitBoard(char board[ROWS][COLS], int rows, int cols){int i = 0;for (i = 0; i < rows; i++){int j = 0;for (j = 0; j < cols; j++){board[i][j] = ‘*’;}}}
这样写也可以,但是我们要放字符0
和*
,这样我们就要调用两个相同的函数,这样的话,就有点太繁琐了,如果我们调用一个函数,能同时打印;那就更好了,让代码来:
void InitBoard(char board[ROWS][COLS], int rows, int cols, int ret){int i = 0;for (i = 0; i < rows; i++){int j = 0;for (j = 0; j < cols; j++){board[i][j] = ret;}}}
InitBoard(mine, ROWS, COLS, '0');InitBoard(player, ROWS, COLS, '*');
把0
和*
一起传过去字符‘0’代表该位置没有雷,字符*
代表隐藏,让玩家来猜,初始化完了,我们得看看我们放的值有没有实现我们想要的效果,那我们来打印一下吧,打印我们得要printf函数吧!
void DisplayBoard(char board[ROWS][COLS], int row, int col){printf("******************************************\n");int i = 0;for (i = 0; i <= col; i++){printf(" %d ", i);}printf("\n");for (i = 1; i <= col; i++){printf(" %d ", i);int j = 0;for (j = 1; j <= col; j++){printf(" %c ", board[i][j]);}printf("\n");}printf("******************************************\n");}
接下来就是埋地雷了;使用rand()函数(猜数字小游戏的一步一步实现有讲解),随机生成坐标,但是我们的坐标要合法,x 的范围必须在row的范围内,y也同样如此,为了避免重复埋雷,我们要加上if判断语句;
void SetMine(char board[ROWS][COLS], int row, int col){//布置10个雷//⽣成随机的坐标,布置雷int count = EASY_COUNT;//EASY_COUNT是设置雷的个数while (count){int x = rand() % row + 1;//1到9int y = rand() % col + 1;if (board[x][y] == '0'){board[x][y] = '1';count--;}}}
打印雷盘看看是否埋好雷了:
此时此刻,我们的雷布置好了,接下来我们要排查雷了;排查雷我们要排查的位置是里面9*9的位置,也就是(x >= 1 && x = 1 && y <= col)
在这些合法的位置排查雷,
(一共10个雷,没排完了71个位置,继续循环,排完了71个位置,也排赢了)
如果mine[x][y] == '1'
,也就是点中雷了,游戏结束,然后打印雷的地图给玩家看。
如果该位置不是雷,那么就统计该坐标周围的雷,也就是八个方位。我们用
GetMineCount来统计周围雷的个数
void FindMine(char mine[ROWS][COLS], char player[ROWS][COLS], int row, int col){int x = 0;int y = 0;int win = 0;while (win < row * col - EASY_COUNT)//一共10个雷,没排完了71个位置,继续循环{printf("请输入要排查的坐标:>");scanf("%d%d", &x, &y);if (x >= 1 && x <= row && y >= 1 && y <= col){if (mine[x][y] == '1'){printf("很遗憾,你被炸死了\n");DisplayBoard(mine, ROW, COL);break;}else{//该位置不是雷,就统计这个坐标周围有⼏个雷int count = GetMineCount(mine, x, y);player[x][y] = count + '0';DisplayBoard(player, ROW, COL);win++;}}else{printf("坐标⾮法,重新输⼊\n");}}if (win == row * col - EASY_COUNT)//一共10个雷,排完了71个位置,也排赢了{printf("恭喜你,排雷成功\n");DisplayBoard(mine, ROW, COL);}}
利用GetMineCount统计雷的个数,怎么统计呢?由于我们用的是字符‘0’和‘1’来存储,怎么把字符统计起来,转换成整型数字呢?
小知识来啦:
字符:‘1’
– ‘0’
==1,
‘2’
– ‘1’
==1,‘3’
–‘2‘
==1
字符1
的ASCII是49,字符0
的ASCII是48,两相减就是数字1了,在C语言中,当字符‘0’
以%c字符的形式打印,打印出来的字符0和数字0一样的,以整形的形式打印出来的是48
,也就是他的ASCII值。
我们将八个方位的字符统计起来再减去字符’0‘
,算出来的值就是我们的雷的个数。
int GetMineCount(char mine[ROWS][COLS], int x, int y){return (mine[x - 1][y] + mine[x - 1][y - 1] + mine[x - 1][y + 1] + mine[x + 1][y] + mine[x + 1][y - 1] + mine[x + 1][y + 1]+ mine[x][y - 1] + mine[x][y + 1] - 8 * '0');}
源码如下:
removal_of_mines.c
# define _CRT_SECURE_NO_WARNINGS 1#include "game.h"void game(){char mine[ROWS][COLS];char player[ROWS][COLS];InitBoard(mine, ROWS, COLS, '0');InitBoard(player, ROWS, COLS, '*');SetMine(mine, ROW, COL);DisplayBoard(player, ROW, COL);FindMine(mine, player, ROW, COL);}void menu(){printf("******************************************\n");printf("******************************************\n");printf("*********** 1. 扫雷游戏启动!!*******\n");printf("************0. 不玩了,启动不了 ********\n");printf("******************************************\n");printf("******************************************\n");}int main(){int input = 0;srand((unsigned int)time(NULL));do //因为我们一上来就是要打印菜单,因此我们选择do.....while语句{menu();//通过函数封装,用menu()打印菜单printf("请选择-->");scanf("%d", &input);//然后玩家输入switch (input)//用switch分支选择选择玩与不玩{case 1:printf("扫雷游戏启动!!\n");game();break;case 0:printf("不玩了,启动不了!\n");break;default:printf("输入错误,请重新输入\n");break;}} while (input);//选择1继续循环,0退出,这也是采用do.....while语句的好处return 0;
game.c
# define _CRT_SECURE_NO_WARNINGS 1#include "game.h"void InitBoard(char board[ROWS][COLS], int rows, int cols, int ret){int i = 0;for (i = 0; i < rows; i++){int j = 0;for (j = 0; j < cols; j++){board[i][j] = ret;}}}void DisplayBoard(char board[ROWS][COLS], int row, int col){printf("******************************************\n");int i = 0;for (i = 0; i <= col; i++){printf(" %d ", i);}printf("\n");for (i = 1; i <= col; i++){printf(" %d ", i);int j = 0;for (j = 1; j <= col; j++){printf(" %c ", board[i][j]);}printf("\n");}printf("******************************************\n");}void SetMine(char board[ROWS][COLS], int row, int col){//布置10个雷//⽣成随机的坐标,布置雷int count = EASY_COUNT;//EASY_COUNT是设置雷的个数while (count){int x = rand() % row + 1;int y = rand() % col + 1;if (board[x][y] == '0'){board[x][y] = '1';count--;}}}int GetMineCount(char mine[ROWS][COLS], int x, int y){return (mine[x - 1][y] + mine[x - 1][y - 1] + mine[x - 1][y + 1] + mine[x + 1][y] + mine[x + 1][y - 1] + mine[x + 1][y + 1]+ mine[x][y - 1] + mine[x][y + 1] - 8 * '0');}void FindMine(char mine[ROWS][COLS], char player[ROWS][COLS], int row, int col){int x = 0;int y = 0;int win = 0;while (win < row * col - EASY_COUNT){printf("请输入要排查的坐标:>");scanf("%d%d", &x, &y);if (x >= 1 && x <= row && y >= 1 && y <= col){if (mine[x][y] == '1'){printf("很遗憾,你被炸死了\n");DisplayBoard(mine, ROW, COL);break;}else{//该位置不是雷,就统计这个坐标周围有⼏个雷int count = GetMineCount(mine, x, y);player[x][y] = count + '0';DisplayBoard(player, ROW, COL);win++;}}else{printf("坐标⾮法,重新输⼊\n");}}if (win == row * col - EASY_COUNT){printf("恭喜你,排雷成功\n");DisplayBoard(mine, ROW, COL);}}
game.h
# define _CRT_SECURE_NO_WARNINGS 1#include #include #include #define EASY_COUNT 10#define ROW 9 #define COL 9#define ROWS ROW+9#define COLS COL+9//初始化棋盘void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);//打印棋盘void DisplayBoard(char board[ROWS][COLS], int row, int col);//布置雷void SetMine(char board[ROWS][COLS], int row, int col);//排查雷void FindMine(char mine[ROWS][COLS], char player[ROWS][COLS], int row, int col);
总结
好啦!扫雷简易版的小游戏到这里就完了,注:永远不要害怕每一次失败,要勇于尝试,看过千遍万遍,不过于自己动手做几遍!
感谢您的观看,如果喜欢的话,请点个赞。如果文章有错误,你可以指出,我不胜感激,让我们共同学习和交流!