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... 来统计周围雷的数量就可以了,谁说过一定要用01来代表非雷与雷。没错,这种办法也可以,博主也期待你们去尝试一下,但是这样做棋盘上既有雷和非雷的信息,还有排查出雷的个数信息,就比较混杂,但不够方便!



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);

总结

好啦!扫雷简易版的小游戏到这里就完了,注:永远不要害怕每一次失败,要勇于尝试,看过千遍万遍,不过于自己动手做几遍!
感谢您的观看,如果喜欢的话,请点个赞。如果文章有错误,你可以指出,我不胜感激,让我们共同学习和交流!