C语言入门 | “扫雷”小游戏 保姆级详解

📅 2026/8/10 10:41:50
C语言入门 | “扫雷”小游戏 保姆级详解
目录一、什么是“扫雷”游戏1、原版Windows扫雷2、控制台自制版二、逻辑分析三、代码实现1、游戏的进入与退出2、核心game()函数的实现2.1、数组初始化2.2、打印棋盘2.3、埋雷2.4、排雷附完整代码供参考前言这篇文章将探讨如何使用C语言在控制台实现经典的扫雷游戏。对于初学者来说会有稍许难度但笔者会尽我所能让文章通俗易懂。当然在学习这个小游戏之前你需要先掌握这些有关C语言的内容C语言数据类型和变量、分支和循环、数组、函数。行文过程中遇到这些内容将不做过多的讲解。如果你已经掌握那么我们就开始吧一、什么是“扫雷”游戏1、原版Windows扫雷为了防止有些小伙伴之前没体验过这个游戏笔者就是在学习编写这个游戏程序的时候才学会玩的这里先对游戏进行简单的介绍。毕竟我们现在是要亲手实现这个游戏的因此不说要玩得有多厉害至少要会玩这样你才懂游戏的基本逻辑。如图所示这是一个99的棋盘里面埋了10个雷。玩家第一次可先随机点开一个格子系统自动设置第一次点开的一定不会是雷。你会发现展开了一大片代表这一大片格子都是没有雷的其中边上的一些格子中出现了数字找找规律会发现只有与未排查的格子相邻的格子才出现数字。数字代表这一格周围的8个格子中有几个雷。玩家需根据这个数字继续把不是雷的格子点开。如果判断失误或者不小心点开了有雷的格子那么GAME OVER!并且你会发现它最后会把每个雷的位置都告诉你让你死得明白哈哈当然如果你成功将所有不是雷的格子都点开了那么恭喜你游戏成功附扫雷游戏网页版入口https://www.minesweeper.cn/https://www.minesweeper.cn/2、控制台自制版我们真的能用C语言做出上面这种图形界面交互式扫雷游戏吗答不能C语言并没有画图、鼠标点击、窗口按钮的标准语法。那我们是做什么样的“扫雷”小游戏重点来了自制扫雷游戏功能说明1.游戏可以通过菜单实现继续玩或者退出游戏2.扫雷的棋盘是99的格子3.随机布置10个雷4.玩家在控制台输入坐标排查雷4.1 如果位置是雷就炸死游戏结束4.2 如果位置不是雷就显示周围有几个雷4.3 把除10个雷之外的非雷都找出来排雷成功游戏结束界面展示PS:这里暂时不涉及展开一片的功能也就是说玩家输入哪个坐标程序就只排查哪个坐标也不涉及标记雷的功能。我们先把最基础的功能实现好。二、逻辑分析好了搞清楚我们的目标之后现在我们需要大致疏理一下思路不妨把自己想像成是已经进入游戏的玩家。游戏开始后首先映入眼帘的应该是99的棋盘不过此时棋盘的每个位置都是 “ * ”用来保持神秘感。当然行和列最好要有数字标记方便我们输入排查坐标。接着我们在控制台输入坐标进行排查例如输入“1 1”。按下回车后如果该坐标位置是雷则游戏失败被炸死如果不是雷棋盘会相应更新在1, 1位置显示一个数字该数字表示其周围八个格子中雷的数量我们根据显示的周围雷的数量进行推理把所有不是雷的位置都找出来。那么问题来了程序是怎么知道我们排查的坐标上到底是不是雷的说明程序早就已经随机生成了10个雷 怎么区分是不是雷呢不妨用“0”和“1”来表示“0”代表不是雷“1”代表是雷。程序根据我们输入的坐标去比对和计算最终再将雷的个数打印在我们所看到的棋盘上。根据以上分析我们可以想到用两个数组去存储有关雷的信息或许是比较方便。一个隐藏起来专门存放每个位置是否是雷另一个展示给玩家看一开始全是 “ * ” 根据玩家输入的坐标对应输出周围雷的信息。为了方便解释这里称前者为mine数组后者为show数组。这时候细心的朋友可能又有问题了那边上的雷怎么计算呢它们周围似乎只有5个位置难道要特别为它们设置一套算法吗那也太麻烦了。聪明的你一定想到了在99的棋盘外面套一圈0变成1111不就能顺利解决这个问题了吗那么问题又来了mine数组的行列变了变成了1111如果show数组继续保持99会不会有问题呢其实不会只需要在记得同样的坐标mine数组的行和列都要1就行。但是这样就不利于函数的复用了笔者接下来的代码中几乎所有的过程都是用自定义函数来完成的比如初始化棋盘、打印棋盘等。如果两个数组的行列不统一就无法复用函数代码的效率变低。因此我们不妨将show数组也变成1111。最后一个问题考虑两个数组采用什么数据类型。因为涉及到函数复用所以必须采用同一种数据类型。是int吗当然不是show数组里面有 * 看来字符型char一定是不二之选。有了上面的初步思考接下来我们终于可以开始写代码了。三、代码实现1、游戏的进入与退出这一步不做过多讲解直接上代码逻辑在我的另一篇博客“猜数字”小游戏中有详细说明感兴趣的朋友可以去看看。//这是在test.c文件中 #include stdio.h int main() { int input 0; do { printf(-----------------------\n); printf(-------- 1.game -------\n); printf(-------- 0.exit -------\n); printf(-----------------------\n); printf(请选择); scanf(%d, input); switch (input) { case 1: game(); //这个函数是接下来要着重设计的是整个“扫雷”游戏的核心 break; case 0: printf(退出游戏\n); break; default: printf(选择错误请重新选择\n); break; } } while (input); return 0; }2、核心game()函数的实现下面我们就来聚焦game()这个函数。我们先设想一下game()里面一定会有非常多的功能要去实现包括初始化数组、打印棋盘、埋雷、排雷等。或许你刚开始的时候还不能一下子想得那么清晰但是你一定知道game()里面逻辑的复杂性和代码的数量之多。因此把这些功能打包成函数放进去一定是个好方法这样最终代码的可读性也更强。不如我们就创建另外一个game.c文件专门用来定义函数然后再创建一个game.h的头文件用来申明函数、包含头文件、宏定义。为了方便后面的代码展示还有一个操作要提前进行这个操作会非常方便地改变游戏难度。虽然我们决定实现的是经典的99棋盘但是如果你想玩1630的困难模式只需稍稍改动2个数字即可。这个操作就是上面提到的宏定义。在game.h文件中用宏定义#define统一规定数组和棋盘尺寸在.c文件需要用到的地方直接用这里定义的符号代表大小//这是game.h文件 #pragma once //宏定义 #define ROW 9 //棋盘行数 #define COL 9 //棋盘列数 #define ROWS ROW2 //数组行数 #define COLS COL2 //数组列数特别说明为了使文章看起来更简洁下面每一次展示的代码只对应当前正在讲解的步骤全部代码会附在文末。2.1、数组初始化首先我们要初始化上文提到的两个数组mine和show因为后面的每一次操作都是针对数组的操作。比如埋雷就是产生2个随机数作为数组的下标进而去定位某一个元素。如果你数组掌握的够好初始化这一步一定难不倒你。当然我们希望用函数来实现因为函数具有复用性调用2次就可以完成mine和show两个数组的初始化。下面我们就来“解剖”这个函数。功能实现任意一个二维字符数组的初始化函数名InitBoard函数原型void InitBoard ( char board[ROWS][COLS] , int r , int c , char set )返回值不需要返回任何值只要能顺利完成初始化的任务参数二维数组数组行数数组列数初始化字符//这是game.h文件 //将2个.c文件需要包含的头文件都写在game.h中这样.c文件只需包含game.h头文件即可 #include stdio.h //初始化数组函数的申明 void InitBoard(char board[ROWS][COLS], int r, int c, char set);//这是game.c文件 #include game.h //这样就包含了所有需要的头文件 //初始化数组函数的定义 void InitBoard(char board[ROWS][COLS], int r, int c, char set) { for (int i 0;i r;i) { for (int j 0;j c;j) { board[i][j] set; } } }//这是test.c文件 #include game.h //这样就包含了所有需要的头文件和函数的定义 void game() { char mine[ROWS][COLS]; char show[ROWS][COLS]; //初始化棋盘 InitBoard(mine, ROWS, COLS, 0); InitBoard(show, ROWS, COLS, *); }我们怎么知道写的对不对呢不如我们来调试一下设置好断点F5走起调试的结果证明我们的初始化过程是没问题的。那么接下来就应该是打印棋盘了毕竟玩家开始游戏后首先看到的就是99的 * 棋盘。2.2、打印棋盘这一步我们依旧使用自定义函数而且这步操作可以说是最常见的数组操作如果数组的知识掌握到位那么一定难不倒你。不过有一点还是要注意的我们创建的2个数组都是11行11列的但是最终打印的时候不管是mine数组还是show数组都只要打印9行9列。搞清楚这一点我们就可以开始动手了。功能实现棋盘打印功能注意数组的最外面一圈不打印函数名DisplayBoard函数原型void DisplayBoard ( char board[ROWS][COLS] , int r , int c )返回值不需要返回任何值只要能顺利完成棋盘打印的任务参数show数组打印的行数打印的列数//这是game.h文件 //打印棋盘函数的申明 void DisplayBoard(char board[ROWS][COLS], int r, int c);//这是game.c文件 //打印棋盘函数的定义 void DisplayBoard(char board[ROWS][COLS], int r, int c) { for (int i 1;i r;i) { for (int j 1;j c;j) { printf(%c , board[i][j]); } printf(\n); //打印完一行要换行 } printf(\n); //打印完一个棋盘空一行 }//这是test.c文件 void game() { char mine[ROWS][COLS]; char show[ROWS][COLS]; //初始化数组 InitBoard(mine, ROWS, COLS, 0); InitBoard(show, ROWS, COLS, *); //打印棋盘 DisplayBoard(show, ROW, COL); }总感觉还缺少什么没错玩家需要输入坐标如果没有相应的标记那么将非常影响游戏体验感。那么打印相应的标记呢很简单只要在刚才的自定义函数中稍稍动一下就好啦。//这是game.c文件 //打印棋盘函数的定义 void DisplayBoard(char board[ROWS][COLS], int r, int c) { for (int i 0;i r;i) //打印第一行坐标 { printf(%d , i); } printf(\n); //打印完第一行坐标换行 for (int i 1;i r;i) { printf(%d , i); //在打印每行*之前先打印该行坐标 for (int j 1;j c;j) { printf(%c , board[i][j]); } printf(\n); //打印完一行要换行 } printf(\n); //打印完一个棋盘空一行 }好了打印棋盘也完成了接下来就要埋雷了也就是随机生成10个雷。2.3、埋雷这一步是对数组mine进行的操作依旧是自定义函数。根据前文的分析我们是计划用字符0和字符1来区别是不是雷的。那么如何埋下一颗雷呢很容易想到的是生成2个随机数来作为雷的坐标随机数的生成这里不细讲需要的小伙伴可以去看我之前的一篇博客“猜数字”小游戏。功能在一个棋盘中随机存入一定数量的字符1函数名SetMine函数原型void SetMine ( char board[ROWS][COLS] , int r , int c )返回值不需要返回任何值只要能顺利完成“埋雷”的任务参数mine数组可埋的行数可埋的列数//这是game.h文件 //埋雷函数的申明 void SetMine(char board[ROWS][COLS], int r, int c);//这是game.c文件 //埋雷函数的定义 void SetMine(char board[ROWS][COLS], int r, int c) { int count EASY_COUNT; //EASY_COUNT在game.h文件中有定义 while (count) { int x rand() % r 1; int y rand() % c 1; if (board[x][y] 0) { board[x][y] 1; count--; } } }//这是test.c文件 void game() { char mine[ROWS][COLS]; char show[ROWS][COLS]; //初始化数组 InitBoard(mine, ROWS, COLS, 0); InitBoard(show, ROWS, COLS, *); //打印棋盘 DisplayBoard(show, ROW, COL); //埋雷 SetMine(mine, ROW, COL); }那这一步敲得到底对不对呢这们不妨先用DisplayBoard函数验证一下//这是test.c文件 void game() { char mine[ROWS][COLS]; char show[ROWS][COLS]; //初始化数组 InitBoard(mine, ROWS, COLS, 0); InitBoard(show, ROWS, COLS, *); //打印棋盘 DisplayBoard(show, ROW, COL); //埋雷 SetMine(mine, ROW, COL); DisplayBoard(mine, ROW, COL); }看来这一步成功了那么就只剩最后一步排雷了。2.4、排雷这一步是整个game()函数中的核心也就是整个程序核心中的核心逻辑最为复杂。不过我一大段文字终究抵不过一张简单的示意图下面我们根据上面这张图的逻辑来敲代码功能排雷函数名FindMine函数原型void FindMine (char mine[ROWS][COLS] ,char show[ROWS][COLS], int r , int c)返回值不需要返回任何值只要能顺利完成“排雷”的任务参数mine数组show数组可排雷的行数可排雷的列数//这是game.c文件 //排雷函数的定义 void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int r, int c) { int x 0; int y 0; int win 0; //这个变量用来计数每排雷成功一次就加1 while (winr*c-EASY_COUNT) { printf(请输入你要排查的坐标); scanf(%d%d, x, y); if (x 1 x r y 1 y c) { if (show[x][y] *) { if (mine[x][y] 1) { printf(很遗憾你被炸死了游戏结束\n); DisplayBoard(mine, ROW, COL); break; } else { //这里需要一个函数计算周围8个坐标雷的个数 DisplayBoard(show, ROW, COL); win; } } else { printf(该坐标已被排查过请不要重复排查\n); } } else { printf(输入错误请重新输入\n); } } if (win r * c - EASY_COUNT) //这里判断是因为跳出循环有2种可能第一种是踩雷了第二种是雷排完了 printf(恭喜你排雷成功\n); }很明显这里还需要一个自定义函数来计算排查坐标周围8个坐标雷的个数。数组里面存的都是字符这该如何计算呢其实道理很简单字符存储的本质是ASCII码值而mine数组中无非就是字符‘0’和字符‘1’甚至无需知道它们具体的ASCII码值因为它们之间相差1。因此很容易得到公式雷的个数8个字符相加-8*‘0’如图所示功能计算某个坐标周围8个雷的个数函数名GetMineCount函数原型int GetMineCount (char mine[ROWS][COLS] , int x , int y)返回值雷的个数参数mine数组行坐标列坐标由于这个函数是排雷函数内部的辅助函数就不再.h文件中申明了并且可以用static修饰起来使其仅在game.c文件中使用别的文件调用不了这一步其实不是必须只是笔者想借此复习一下所学知识。//这是game.c文件 static int GetMineCount(char mine[ROWS][COLS], int x, int y) { return mine[x - 1][y - 1] mine[x - 1][y] mine[x - 1][y 1] mine[x][y - 1] mine[x][y 1] mine[x 1][y - 1] mine[x 1][y] mine[x 1][y 1] - 8 * 0; } //排雷函数的定义 void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int r, int c) { int x 0; int y 0; int win 0; //这个变量用来计数每排雷成功一次就加1 while (winr*c-EASY_COUNT) { printf(请输入你要排查的坐标); scanf(%d%d, x, y); if (x 1 x r y 1 y c) { if (show[x][y] *) { if (mine[x][y] 1) { printf(很遗憾你被炸死了游戏结束\n); DisplayBoard(mine, ROW, COL); break; } else { int count GetMineCount(mine, x, y); show[x][y] count 0; DisplayBoard(show, ROW, COL); win; } } else { printf(该坐标已被排查过请不要重复排查\n); } } else { printf(输入错误请重新输入\n); } } if (win r * c - EASY_COUNT) //这里判断是因为跳出循环有2种可能第一种是踩雷了第二种是雷排完了 printf(恭喜你排雷成功\n); }到这里“扫雷游戏”就制作完成啦当然还有别的拓展功能比如展开一片、标记雷、计算游戏时间等留给小伙标们自己去探索啦。附完整代码供参考//这是game.h文件 #pragma once //宏定义 #define ROW 9 #define COL 9 #define ROWS ROW2 #define COLS COL2 #define EASY_COUNT 10 //难度等级即埋雷的个数 //将2个.c文件需要包含的头文件都写在game.h中这样.c文件只需包含game.h头文件即可 #include stdio.h #include stdlib.h #include time.h //初始化数组函数的申明 void InitBoard(char board[ROWS][COLS], int r, int c, char set); //打印棋盘函数的申明 void DisplayBoard(char board[ROWS][COLS], int r, int c); //埋雷函数的申明 void SetMine(char board[ROWS][COLS], int r, int c); //排雷函数的申明 void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int r, int c);//这是game.c文件 #include game.h //初始化数组函数的定义 void InitBoard(char board[ROWS][COLS], int r, int c, char set) { for (int i 0;i r;i) { for (int j 0;j c;j) { board[i][j] set; } } } //打印棋盘函数的定义 void DisplayBoard(char board[ROWS][COLS], int r, int c) { for (int i 0;i r;i) //打印第一行坐标 { printf(%d , i); } printf(\n); //打印完第一行坐标换行 for (int i 1;i r;i) { printf(%d , i); //在打印每行*之前先打印该行坐标 for (int j 1;j c;j) { printf(%c , board[i][j]); } printf(\n); //打印完一行要换行 } printf(\n); //打印完一个棋盘空一行 } //埋雷函数的定义 void SetMine(char board[ROWS][COLS], int r, int c) { int count EASY_COUNT; //EASY_COUNT在game.h文件中有定义 while (count) { int x rand() % r 1; int y rand() % c 1; if (board[x][y] 0) { board[x][y] 1; count--; } } } static int GetMineCount(char mine[ROWS][COLS], int x, int y) { return mine[x - 1][y - 1] mine[x - 1][y] mine[x - 1][y 1] mine[x][y - 1] mine[x][y 1] mine[x 1][y - 1] mine[x 1][y] mine[x 1][y 1] - 8 * 0; } //排雷函数的定义 void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int r, int c) { int x 0; int y 0; int win 0; //这个变量用来计数每排雷成功一次就加1 while (winr*c-EASY_COUNT) { printf(请输入你要排查的坐标); scanf(%d%d, x, y); if (x 1 x r y 1 y c) { if (show[x][y] *) { if (mine[x][y] 1) { printf(很遗憾你被炸死了游戏结束\n); DisplayBoard(mine, ROW, COL); //把mine棋盘打印出来是为了玩家“死”而无憾 break; } else { int count GetMineCount(mine, x, y); show[x][y] count 0; DisplayBoard(show, ROW, COL); win; } } else { printf(该坐标已被排查过请不要重复排查\n); } } else { printf(输入错误请重新输入\n); } } if (win r * c - EASY_COUNT) //这里判断是因为跳出循环有2种可能第一种是踩雷了第二种是雷排完了 printf(恭喜你排雷成功\n); }//这是test.c文件 #include game.h void game() { char mine[ROWS][COLS]; char show[ROWS][COLS]; //初始化数组 InitBoard(mine, ROWS, COLS, 0); InitBoard(show, ROWS, COLS, *); //打印棋盘 DisplayBoard(show, ROW, COL); //埋雷 SetMine(mine, ROW, COL); //排雷 FindMine(mine, show, ROW, COL); } int main() { int input 0; srand((unsigned int)time(NULL)); do { printf(-----------------------\n); printf(-------- 1.game -------\n); printf(-------- 0.exit -------\n); printf(-----------------------\n); printf(请选择); scanf(%d, input); switch (input) { case 1: game(); break; case 0: printf(退出游戏\n); break; default: printf(选择错误请重新选择\n); break; } } while (input); return 0; }感谢观看有问题欢迎在评论区留言或私信我们一起交流讨论。完