第十篇 扫雷游戏 下(初版·思路)
本文Gitee链接:2025.11.13https://gitee.com/donghua-wanli/blog-code/tree/master/2025.11.13
https://gitee.com/donghua-wanli/blog-code/tree/master/2025.11.13
书接上回,我们已经成功布置了十个雷,并将布雷后的方阵打印出来:

下一步就是排查雷,这里我们遇到了两个致命问题,
首先,所谓的排雷,就是两步:
- 判断目标坐标是否为雷(是则游戏结束,否则进入下一步);
- 计算目标坐标周围的雷数并展示。在这里,却是遇到了下面三种情况:
- 坐标位置是在中间区域,如下图红点,要排查周围的8个坐标
- 坐标位置是在“边”上,如下图蓝点,要排查周围的5个坐标
- 坐标位置是在“边”上,如下图黄点,要排查周围的3个坐标
也就是说,我们不仅要写三个排查雷的函数,这三个函数的条件还不同,还得想办法让程序识别不同的情况,进而选择不同的函数,这就太太麻烦了
那有没有办法解决呢?很简单,这个麻烦产生的原因是有三种情况,即“中间”,“边上非点”,四个顶点”这三个情况,我们只需保证不管要排查这9×9的方阵的那一个坐标,所面对的情况就相同就行了,即:“中间”

将这9×9的方阵向外拓展一圈,变成一个11×11的方阵,而我们只在中间9×9的方阵布置雷,在新拓展的位置我们不布置雷,这样的话,所有的排查的坐标都是中间区域,如上图红点,要排查周围的8个坐标,因为在新拓展的位置我们不布置雷,这就并不会改变我们排查的结果,这也正是我之前在第九篇中说将方阵的行和列设置为9和9是笨拙的原因
因为我们是使用define关键字定义的行和列,所以要将9×9的方阵改为11×11的方阵,只需要在game.h头文件中,修改一下define关键字对行和列的定义就行了
game.h:
#define _CRT_SECURE_NO_WARNINGS#include<stdio.h>
#include<stdlib.h>
#include<time.h>#define Hang 11 //之前是#define Hang 9
#define Lie 11 //之前是#define Lie 9void chushihua(int landmine[Hang][Lie]);//将数组初始化void zhanshi(int landmine[Hang][Lie]);//打印数组来展示void setmine(int landmine[Hang][Lie]);
接着我们对game.c 中的函数进行修改:首先是初始化函数:
void chushihua(int landmine[Hang][Lie])//将数组初始化
{int a;for (a = 0; a < Hang; a++){int b;for (b = 0; b < Lie; b++){landmine[a][b] = 0;}}
}
因为是用0代表不是雷,1代表是雷,我们之前说:我们只在中间9×9的方阵布置雷,在新拓展的位置我们不布置雷,所以实际上我们不需要对初始化函数进行任何的修改
接着是用来打印的展示函数:
void zhanshi(int landmine[Hang][Lie])
{int a;for (a = 0; a < Hang; a++){int b;for (b = 0; b < Lie; b++){printf("%d", landmine[a][b]);}printf("\n");}
}
如今方阵修改为了11×11的方阵,但是我们要打印的还是之前9×9的方阵,所以我们需要打印的是2~10行,2~10列中间的元素,也就是说,数组行索引是1到9,则修改如下:
void zhanshi(int landmine[Hang][Lie])
{int a;for (a = 1; a < Hang-1; a++){int b;for (b =1; b < Lie-1; b++){printf("%d", landmine[a][b]);}printf("\n");}
}
接下来看布置雷的函数:
void setmine(int landmine[Hang][Lie])
{int count = 10;while (count){int x = rand() % Hang;int y = rand() % Lie;if (landmine[x][y] == 0){landmine[x][y] = 1;count--;}}
}
之前的打印的展示函数的数组行索引是1到9,同理,随机数的生成也是1到9,则修改如下:
void setmine(int landmine[Hang][Lie])
{int count = 10;while (count){int x = rand() % (Hang-2)+1;int y = rand() % (Lie-2)+1;if (landmine[x][y] == 0){landmine[x][y] = 1;count--;}}
}
如果你不放心的话可以让它打印一下坐标,
void setmine(int landmine[Hang][Lie])
{int count = 10;while (count){int x = rand() % (Hang-2)+1;int y = rand() % (Lie-2)+1;if (landmine[x][y] == 0){landmine[x][y] = 1;printf("%d", x);printf("%d\n", y);count--;}}
}
运行结果:

显然正确
那么问题一就解决了,可是还有问题二:
我们现在要排查这个坐标位置是不是雷,是雷就炸死,不是就排查这个坐标位置附近的几个位置有几个雷,那根据游戏规则,你排查完之后要将排查的结果储存并打印出来,可是现在的方阵已经布满了数字0和1来表示是否是雷,排查的结果又是整形数字,用来表示这个坐标位置附近的几个位置有几个雷,其他非0和1的数字还好,你怎么区分方阵中的0和1表示的是雷还是排查完雷的个数呢?
你可能会说,将这个二维整形数组改为二维字符数组,然后用非数字的字符来表示是不是雷,而用字符数字来表示排查后雷的个数。这样确实不会有歧义了,但是之前我们在布置雷的时候,为了保证布置的雷不会和之前的雷位置重复,使用了一个if语句:
void setmine(int landmine[Hang][Lie])
{int count = 10;while (count){int x = rand() % (Hang - 2) + 1;int y = rand() % (Lie - 2) + 1;if (landmine[x][y] == 0){landmine[x][y] = 1;count--;}}
}
那这里也需要改,但是这些还都是小问题,最关键的是排查后表示累的个数的字符数字还会对接下来的排查造成干扰,导致排查的时候不仅要看这个坐标位置自身是不是雷以及坐标周围有几个雷,还要排除周围已经排查过的位置的干扰,这就太麻烦了。
所以我们采取一个简单至极的办法:再创建一个数组(同样大小的方阵),用来存放排查后的结果,并打印这个数组作为每次排查后的显示
直接在11.13.c 的main函数之前写一句:
int showmine[Hang][Lie];//制作存放排查结果的方阵:
但是这样的话,在showmine这个数组中又出现问题了,数字0表示的到底是排查的结果是0还是表示没有排查?其实我们可以将其中的元素全部初始化为9,因为在这个游戏中,一个不是雷的坐标周围最多只有八个元素与之相邻,所以排查的结果最大就是8,因此使用9来初始化就不会有歧义了,但是这样的方阵看着也太乱了,感觉不干净
所以我们不放制作一个字符数组,同样是11×11,将其所有元素初始化为字符 ' * ' ,用来表示未被排查:
char showmine[Hang][Lie];//制作存放排查结果的方阵:
而排查的结果是雷的个数,这也简单,我们用字符数字来表示就是了,例如:用 '2'表示排查的结果是两个雷。
接下来就是排查了,让我们在game.c中来写一个排查并存储排查结果的函数:
分析一下,函数不需要返回值,参数为两个二维数组,(一个是布置完雷用来排查的,一个是用来存放排查结果的)
先在game.h 中声明该函数:game.h:
#define _CRT_SECURE_NO_WARNINGS#include<stdio.h>
#include<stdlib.h>
#include<time.h>#define Hang 11
#define Lie 11void chushihua(int landmine[Hang][Lie]);//将数组初始化void zhanshi(int landmine[Hang][Lie]);//打印数组来展示void setmine(int landmine[Hang][Lie]);void paicha(int landmine[Hang][Lie], char showmine[Hang][Lie]);
在game.c 中编写函数体:
void paicha(int landmine[Hang][Lie], char showmine[Hang][Lie])
{printf("请输入排查的坐标:");scanf();}
排查的步骤:
- 先排查这个坐标位置是不是雷,是雷就炸死,不是就要进行下一步;
- 排查这个坐标位置附近的几个位置有几个雷;
第一步很好解决,用if语句就行:
void paicha(int landmine[Hang][Lie], char showmine[Hang][Lie])
{printf("请输入排查的坐标:");scanf("%d%d",&a,&b);if (landmine[a][b] == 1){printf("你踩到雷了,炸死,游戏结束");}else{showmine[a][b]=}
}
在第二步中,我们要将排查的结果储存到showmine数组中去,那这个排查的结果怎么计算呢?
其实非常简单,因为我们在landmine数组中用0来表示没有雷,而用1来表示有雷,那么其实只要将这个坐标位置的周围8个坐标的数值相加,就是这个坐标周围的雷的个数,(对于在9×9边缘和顶点的坐标也是如此,因为我们之前将这个方阵扩大了一圈嘛,这外围的一圈全部都是0,不影响最终的结果)
那我们要将这八个一个一个相加嘛?不是不行,知识有更简洁的方法:你要知道,我们之所以排查这个坐标周围雷的个数,就是因为这个坐标位置不是雷,即这个坐标上的数字是0,因此,我们将这九个的坐标(以输入的坐标为中心的3×3小方阵)相加,和八个坐标相加结果是一样的,而这只用一个简单循环就可以了:
void paicha(int landmine[Hang][Lie], char showmine[Hang][Lie])
{printf("请输入排查的坐标:");scanf("%d%d",&a,&b);if (landmine[a][b] == 1){printf("你踩到雷了,炸死,游戏结束");}else{int c;int i;for (i=a-1;i<=a+1;i++){int j;for (j = b - 1; j <= b + 1; j++){c += landmine[i][j];}}showmine[a][b] = ;}
}
这个时候又出现了一个小问题,排查的结果是c,但是我们并不能将c直接赋值给showmine[a][b],因为showmine是一个字符数组,我们需要将整形数字转化为字符数字,这也很简单,不用去查ASCII码表,我们随便写个main函数就能看出来了:
int main()
{printf("%d\n", 0);//打印的是数字0printf("%d\n", '0');//打印的是字符'0'对应的ASCII值return 0;
}
如图:
- printf("%d\n", 0);//打印的是数字0
- printf("%d\n", '0');//打印的是字符'0'对应的ASCII值
运行结果:

因此,当我们想要打印字符 ' 0 ' ,只需用printf("%c\n", 0 + 48);就可以了,如图:
int main()
{printf("%d\n", 0);//打印的是数字0printf("%d\n", '0');//打印的是字符'0'对应的ASCII值printf("%c\n", 0 + 48);//打印的是 数字0对应的ASCII值加上48,获得的新ASCII值对应的字符return 0;
}

因此,我们很轻松就完成了整形数字到字符数字的转换,只要加上48,利用ASCII码来打印就好了:
void paicha(int landmine[Hang][Lie], char showmine[Hang][Lie])
{int a; int b;printf("请输入排查的坐标:");scanf("%d%d",&a,&b);if (landmine[a][b] == 1){printf("你踩到雷了,炸死,游戏结束");return;}else{int c=0;int i;for (i=a-1;i<=a+1;i++){int j;for (j = b - 1; j <= b + 1; j++){c += landmine[i][j];}}showmine[a][b] = c+48;}
}
但是这里还是有一个逻辑漏洞:游戏的玩家可能会有意无意的输入不符合格式的坐标,现在我们用一个if 语句讲这个漏洞补上:
void paicha(int landmine[Hang][Lie], char showmine[Hang][Lie])
{int a;int b;printf("请输入排查的坐标:");chonglai :scanf("%d%d", &a, &b);if (a >= 1 && a <= 9 && b >= 1 && b <= 9){if (landmine[a][b] == 1){printf("你踩到雷了,炸死,游戏结束");return;}else{int c = 0;int i;for (i = a - 1; i <= a + 1; i++){int j;for (j = b - 1; j <= b + 1; j++){c += landmine[i][j];}}showmine[a][b] = c + 48;}}else{printf("输入错误,请输入符合格式的坐标:");goto chonglai;}}
这里我们使用了goto语句,这是我们在第五篇第七部分讲解过的知识:
第五篇:C 语言分支与循环(上)
https://blog.csdn.net/2401_86187158/article/details/148318099?spm=1
你会发现我好像刻意的使用之前的知识,每错,这正是我有意的安排,因为我认为:在后面的学习练习中不断的使用前面的知识,这是最好的复习方式
那么,我们需要将用来打印的展示函数更改一下(因为这个游戏不需要打印布置雷的方阵,只打印排查结果的方阵,所以不需要写一个新的打印函数,只要将之前的改一改就可以用了):
void zhanshi(char showmine[Hang][Lie])
{int a;for (a = 1; a < Hang - 1; a++){int b;for (b = 1; b < Lie - 1; b++){printf("%c", showmine[a][b]);}printf("\n");}
}
但是初始化函数就不一样了,因为整形数组和字符数组都要初始化,随意需要给字符数组单独写一个初始化函数:(说是在写一个,其实只要复制粘贴一下再随便改改就行了)
void chushihua2(int landmine[Hang][Lie])//将字符数组初始化
{int a;for (a = 0; a < Hang; a++){int b;for (b = 0; b < Lie; b++){landmine[a][b] = '*';}}
}
同时不要忘了在game.h中修改和添加新函数的声明:game.h:
#define _CRT_SECURE_NO_WARNINGS#include<stdio.h>
#include<stdlib.h>
#include<time.h>#define Hang 11
#define Lie 11void chushihua(int landmine[Hang][Lie]);//将数组初始化void setmine(int landmine[Hang][Lie]);//布置雷的函数void paicha(int landmine[Hang][Lie], char showmine[Hang][Lie]);//排查雷并存储排查结果的函数,int a,int bvoid chushihua2(char landmine[Hang][Lie]);//将字符数组初始化void zhanshi(char showmine[Hang][Lie]);//打印字符数组来展示
OK,现在我们来看看game.c:
#include"game.h"void chushihua(int landmine[Hang][Lie])//将整形数组初始化
{int a;for (a = 0; a < Hang; a++){int b;for (b = 0; b < Lie; b++){landmine[a][b] = 0;}}
}void zhanshi(char showmine[Hang][Lie])
{int a;for (a = 1; a < Hang - 1; a++){int b;for (b = 1; b < Lie - 1; b++){printf("%c", showmine[a][b]);}printf("\n");}
}void setmine(int landmine[Hang][Lie])
{int count = 10;while (count){int x = rand() % (Hang - 2) + 1;int y = rand() % (Lie - 2) + 1;if (landmine[x][y] == 0){landmine[x][y] = 1;count--;}}
}void paicha(int landmine[Hang][Lie], char showmine[Hang][Lie])
{int a;int b;printf("请输入排查的坐标:");
chonglai:scanf("%d%d", &a, &b);if (a >= 1 && a <= 9 && b >= 1 && b <= 9){if (landmine[a][b] == 1){printf("你踩到雷了,炸死,游戏结束");return;}else{int c = 0;int i;for (i = a - 1; i <= a + 1; i++){int j;for (j = a - 1; j <= a + 1; j++){c += landmine[i][j];}}showmine[a][b] = c + 48;}}else{printf("输入错误,请输入符合格式的坐标:");goto chonglai;}}void chushihua2(char landmine[Hang][Lie])//将字符数组初始化
{int a;for (a = 0; a < Hang; a++){int b;for (b = 0; b < Lie; b++){landmine[a][b] = '*';}}
}
我们简单的写个main函数试这运行一下:
11.13.c:
#include"game.h"int x;
int landmine[Hang][Lie];//制作存放雷的方阵:
char showmine[Hang][Lie];//制作存放排查结果的方阵:int main()
{srand((unsigned int)time(NULL));chushihua(landmine);chushihua2(showmine);//初始化两方阵setmine(landmine);//布置雷paicha(landmine, showmine);//排雷zhanshi(showmine);//打印排查结果return 0;
}
运行结果:

显然,我们写的是对的。
现在来看一下我们之前所写的main函数:
#include"game.h"int x;
int landmine[Hang][Lie];//制作存放雷的方阵:
char showmine[Hang][Lie];//制作存放排查结果的方阵:int main()
{printf("%s\n","*******************************************");//设计扫雷游戏目录和选项:printf("%s\n","***************扫雷游戏 * *****************");printf("%s\n","***请选择:1.开始游玩 2.退出游戏 * *******");printf("%s\n","*******************************************");printf("%s\n","*******************************************");printf("%s\n","*******************************************");scanf("%d",&x);switch (x){case 1:printf("%s\n", "*************开始游戏***************");game();break;case 2:printf("%s\n", "*************退出游戏***************");break;default:printf("%s\n", "*************输入错误***************");break;}
}
这么看来,我们只剩最后一步:将这些的函数组合拼装成游戏主体game()函数:
分析:这个game函数,不需要返回值和参数,直接在game.h中加上声明:
void game();
在game.c中组装:
game函数的前体跟刚才随便写的用来检验的main函数中的逻辑一样,都是初始化、布置雷、排雷,所以直接拷贝过来:
void game()
{chushihua(landmine);chushihua2(showmine);//初始化两方阵setmine(landmine);//布置雷paicha(landmine, showmine);//排雷zhanshi(showmine);//打印排查结果}
但是到这里,编译器报错了:
简单的说,
你可以把 landmine 和 showmine 想象成两个 “工具箱子”:
你在 game 函数里想让这两个 “箱子” 干活(调用函数操作它们),但编译器压根没见过这两个 “箱子” 长啥样,所以它就懵了 ——“你说的 landmine 和 showmine 是啥?我不认识啊!”
解决这个问题很简单,就像你得先跟别人 “介绍清楚这两个箱子存在” :
还记得我们之前在第八篇中学习的extern关键字吗?第八篇
https://blog.csdn.net/2401_86187158/article/details/148318099?spm=1
现在就用上了,这两个数组我们在11.13.c中定义过了,现在我们就只需要用关键字extern在game.c开头声明一下就行了:
extern int landmine[Hang][Lie];//制作存放雷的方阵:
extern char showmine[Hang][Lie];//制作存放排查结果的方阵:
排雷游戏中将所有的雷排完就是胜利,那么就是排了81-10=71次雷就胜利,因此:
extern int landmine[Hang][Lie];//制作存放雷的方阵:
extern char showmine[Hang][Lie];//制作存放排查结果的方阵:void game()
{chushihua(landmine);chushihua2(showmine);//初始化两方阵setmine(landmine);//布置雷paicha(landmine, showmine);//排雷int a = 0;while (a<71){zhanshi(showmine);//打印排查结果a++;}if (a == 71){printf("恭喜你排雷成功,游戏结束");}}
但是,现在还是有一个漏洞:若玩家重复输入已排查坐标,会被误计为 “有效排雷”,导致未排完 71 个非雷区域就提示胜利。
修复方法:在paicha函数中判断坐标是否已排查(showmine[a][b] != '*'),若已排查则提示并重新输入,仅有效新排查才触发计数增加。
即:
void paicha(int landmine[Hang][Lie], char showmine[Hang][Lie])
{int a;int b;printf("请输入排查的坐标:");
chonglai:scanf("%d%d", &a, &b);if (a >= 1 && a <= 9 && b >= 1 && b <= 9){if (landmine[a][b] == 1){printf("你踩到雷了,炸死,游戏结束");return;}else{// 新增:判断坐标是否已排查if (showmine[a][b] != '*'){printf("该坐标已排查,请重新输入:");goto chonglai;}int c = 0; // 存储周围雷数// 后续雷数计算逻辑不变...int i;for (i = a - 1; i <= a + 1; i++){int j;for (j = a - 1; j <= a + 1; j++){c += landmine[i][j];}}showmine[a][b] = c + 48;}}else{printf("输入错误,请输入符合格式的坐标:");goto chonglai;}}
至此,所有的代码写完,美化一下格式:
game.h:
#define _CRT_SECURE_NO_WARNINGS#include<stdio.h>
#include<stdlib.h>
#include<time.h>#define Hang 11
#define Lie 11void chushihua(int landmine[Hang][Lie]);//将数组初始化void setmine(int landmine[Hang][Lie]);//布置雷的函数void paicha(int landmine[Hang][Lie], char showmine[Hang][Lie]);//排查雷并存储排查结果的函数,int a,int bvoid chushihua2(char landmine[Hang][Lie]);//将字符数组初始化void zhanshi(char showmine[Hang][Lie]);//打印字符数组来展示void game();
game.c:
extern int landmine[Hang][Lie];//制作存放雷的方阵:
extern char showmine[Hang][Lie];//制作存放排查结果的方阵:#include"game.h"void chushihua(int landmine[Hang][Lie])//将整形数组初始化
{int a;for (a = 0; a < Hang; a++){int b;for (b = 0; b < Lie; b++){landmine[a][b] = 0;}}
}void zhanshi(char showmine[Hang][Lie])
{int a;for (a = 1; a < Hang - 1; a++){int b;for (b = 1; b < Lie - 1; b++){printf("%c", showmine[a][b]);}printf("\n");}
}void setmine(int landmine[Hang][Lie])
{int count = 10;while (count){int x = rand() % (Hang - 2) + 1;int y = rand() % (Lie - 2) + 1;if (landmine[x][y] == 0){landmine[x][y] = 1;count--;}}
}void paicha(int landmine[Hang][Lie], char showmine[Hang][Lie])
{int a;int b;printf("请输入排查的坐标:");
chonglai:scanf("%d%d", &a, &b);if (a >= 1 && a <= 9 && b >= 1 && b <= 9){if (landmine[a][b] == 1){printf("你踩到雷了,炸死,游戏结束");return;}else{// 新增:判断坐标是否已排查if (showmine[a][b] != '*'){printf("该坐标已排查,请重新输入:");goto chonglai;}int c = 0; // 存储周围雷数// 后续雷数计算逻辑不变...int i;for (i = a - 1; i <= a + 1; i++){int j;for (j = b - 1; j <= b + 1; j++){c += landmine[i][j];}}showmine[a][b] = c + 48;}}else{printf("输入错误,请输入符合格式的坐标:");goto chonglai;}}void chushihua2(char landmine[Hang][Lie])//将字符数组初始化
{int a;for (a = 0; a < Hang; a++){int b;for (b = 0; b < Lie; b++){landmine[a][b] = '*';}}
}void game()
{chushihua(landmine);chushihua2(showmine);//初始化两方阵setmine(landmine);//布置雷paicha(landmine, showmine);//排雷int a = 0;while (a<71){zhanshi(showmine);//打印排查结果a++;}if (a == 71){printf("恭喜你排雷成功,游戏结束");}}
11.13.c :
#include"game.h"int x;
int landmine[Hang][Lie];//制作存放雷的方阵:
char showmine[Hang][Lie];//制作存放排查结果的方阵:int main()
{printf("%s\n","*******************************************");//设计扫雷游戏目录和选项:printf("%s\n","***************扫雷游戏 * *****************");printf("%s\n","***请选择:1.开始游玩 2.退出游戏 * *******");printf("%s\n","*******************************************");printf("%s\n","*******************************************");printf("%s\n","*******************************************");scanf("%d",&x);switch (x){case 1:printf("%s\n", "*************开始游戏***************");game();break;case 2:printf("%s\n", "*************退出游戏***************");break;default:printf("%s\n", "*************输入错误***************");break;}
}
这就是扫雷游戏全篇
OK,还是经典结尾:
嗯,希望能够得到你的关注,希望我的内容能够给你带来帮助,希望有幸能够和你一起成长。
写这篇博客的时候天气有点凉,冷风吹过阳台,裹着深夜的孤独。我走到阳台拍下了一张宿舍对面的照片作为本文的封面。
