C 语言项目实战:扫雷游戏设计与实现(附完整代码)
C语言实现扫雷核心功能踩雷与排雷全解析融合分文件写法扫雷游戏作为经典的控制台小游戏核心逻辑围绕“踩雷”判断玩家点击是否命中地雷和“排雷”揭示非雷区域、计算周围雷数、展开区域展开。本文将结合分文件.h头文件.c源文件写法整合完整代码逐模块拆解代码功能清晰讲解每一部分的实现让新手既能掌握扫雷核心原理也能学会C语言分文件编程的规范用法代码可直接复制编译运行。扫雷游戏分析设计和功能说明① 随机生成地雷位置不重复② 玩家输入坐标“踩雷”命中则游戏结束③ 未踩雷则“排雷”显示当前位置周围雷数④ 排完所有非雷区域则游戏胜利。本次采用分文件写法将声明与实现分离代码结构更清晰、可维护性更强。本文所有代码基于C语言标准语法无复杂第三方库可直接在Dev-C、VS、MinGW等编译器中运行注释详细逐块解析可直接复制学习。一、前期准备分文件设计与头文件声明规范编程分文件编程是C语言的常用规范将函数声明、常量定义放在头文件game.h中函数实现放在源文件game.c中主函数放在另一个源文件main.c中避免代码冗余方便后续修改和扩展。以下拆解头文件的核心作用。模块功能解析#pragma once预处理指令防止头文件被重复包含比如main.c和game.c都包含game.h时避免函数声明重复报错是C语言分文件编程的必备指令。常量定义的核心设计ROWS ROW2、COLS COL2是关键玩家看到的是9x9棋盘但程序实际用11x11棋盘存储这样计算边缘位置比如第1行、第9列的周围雷数时无需额外判断边界直接遍历8个方向即可避免数组越界比单纯9x9棋盘更简洁。函数声明仅声明函数的返回值、参数类型和函数名不写具体实现相当于“函数接口”让main.c和game.c知道有这些函数可用实现“声明与实现分离”。头文件说明stdio.h用于输入输出printf/scanfstdlib.h用于rand()/srand()函数生成随机地雷time.h用于生成随机数种子保证每次地雷位置不同。二、核心模块1棋盘初始化game.c实现游戏开始前需要初始化两个棋盘后台地雷棋盘mine全部置为 0 表示无雷前台显示棋盘show全部置为 ‘ * ’表示未探索通过一个InitBoard函数实现通用初始化灵活且简洁。模块功能解析_CRT_SECURE_NO_WARNINGS仅用于VS编译器解决scanf函数的安全警告Dev-C、MinGW等编译器可省略不影响代码运行。通用初始化设计通过传入参数set一个函数实现两个棋盘的初始化——初始化地雷棋盘时传入0初始化显示棋盘时传入*无需写两个重复的初始化函数减少代码冗余。遍历赋值双重for循环遍历11x11的完整棋盘rowsROWS11colsCOLS11将每个位置赋值为set确保棋盘初始化彻底为后续布置地雷、显示区域做准备。三、核心模块2打印棋盘game.c实现仅展示9x9区域玩家操作后需要实时打印前台棋盘你的代码仅打印9x9的可见区域与玩家交互同时添加行列号方便玩家输入坐标界面简洁直观。模块功能解析仅打印9x9可见区域循环条件i从1到rowrow9、j从1到colcol9跳过0行0列和10行10列边界保护区域玩家看到的就是标准9x9棋盘避免暴露边界保护的多余内容。行列号设计列号打印0~9行号打印1~9玩家输入坐标时比如“3 5”可直接对应棋盘位置降低操作难度比无行列号的设计更人性化。界面优化开头打印“-----扫雷游戏-----”结尾换行让棋盘显示更整洁提升玩家体验。四、核心模块3布置地雷game.c实现随机生成10颗雷地雷需要随机分布在9x9可见区域且不能重复同一个位置不能有两颗地雷你的代码用rand()函数生成随机坐标结合边界保护简洁高效地实现了地雷布置。模块功能解析随机坐标生成rand()%row 1row9因此rand()%9得到0~81后刚好是1~9对应9x9可见区域的坐标避免生成0或10边界保护区域无需额外判断坐标合法性。地雷标记方式你用1标记地雷、0标记无雷与之前的*标记不同更简洁后续计算周围雷数时可直接通过ASCII码运算无需额外转换。去重逻辑if (board[x][y] 0)只有当前位置无雷时才布置地雷确保同一个位置不会重复布置最终刚好布置10颗雷。五、核心模块4计算周围雷数game.c实现踩雷/排雷的基础未踩雷时需要计算当前坐标周围8个方向的地雷总数你的代码用简洁的表达式实现利用ASCII码运算无需循环高效便捷。模块功能解析边界保护的优势因为实际棋盘是11x11ROWS11COLS11即使x、y是1或9边缘位置x-1、x1、y-1、y1也不会越界比如x1时x-10属于边界保护区域值为0不影响计算无需额外判断边界简化代码。ASCII码运算原理字符0的ASCII码是481的ASCII码是49。将周围8个位置的字符相加本质是它们的ASCII码相加减去8*488个0的ASCII码和最终得到的就是地雷数量0~8。比如周围有2颗雷就是2个49 6个48减去8*48结果为2。高效简洁无需双重for循环遍历8个方向直接用表达式计算代码更简洁执行效率更高。六、核心模块5踩雷与排雷逻辑game.c实现游戏核心FindMine函数是扫雷的核心整合了“玩家输入坐标→坐标校验→踩雷判断→排雷显示→胜利判断”的完整逻辑代码流程清晰适配9x9棋盘和10颗地雷的设定。模块功能解析重点1. 踩雷判断逻辑核心判断直接对比后台地雷棋盘mine[x][y]是否为1你定义的地雷标记因为mine棋盘存储的是真实的地雷位置这是“踩雷”的核心判断条件。踩雷处理踩雷后打印提示调用DisplayBoard显示mine棋盘所有地雷位置让玩家知道自己踩雷的位置然后退出循环游戏结束。2. 排雷逻辑排查状态判断show[x][y] *表示该位置未排查避免玩家重复排查同一个位置提升体验。雷数显示count是周围雷数0~8通过count 0将数字转换为字符比如count2转换为字符2因为show棋盘是char类型只能存储字符。实时反馈每次排雷后重新打印show棋盘让玩家看到最新的排查状态交互性更强。3. 胜利判断逻辑核心公式win col * row - 10col*row是9*981总区域数10是地雷数81-1071非雷区域数。当win已排查非雷区域数等于71时说明所有非雷区域都已排查完毕玩家胜利。胜利处理打印胜利提示显示所有地雷位置让玩家看到自己成功避开了所有地雷提升成就感。七、核心模块6菜单与游戏主逻辑main.c实现衔接所有功能main.c中包含菜单、test函数游戏循环、game函数游戏流程是整个程序的入口整合了你所有的功能模块实现“菜单选择→游戏初始化→布置地雷→排查地雷”的完整流程。模块功能解析菜单设计简洁明了玩家选择1开始游戏、0退出游戏符合控制台游戏的常规交互逻辑界面整洁。随机种子设置srand((unsigned int)time(NULL))放在test函数开头仅调用一次利用当前系统时间作为种子确保每次运行rand()生成的随机数不同地雷位置也不同避免每次游戏体验一致。游戏流程串联game函数中按“定义棋盘→初始化棋盘→布置地雷→打印初始棋盘→排查地雷”的顺序执行形成完整的游戏闭环衔接所有核心模块。循环菜单do-while循环玩家选择1后玩完一局游戏会回到菜单可继续选择1玩下一局选择0则退出游戏逻辑流畅体验更好。八、常见问题与注意事项头文件包含问题game.c和main.c都必须包含game.h否则会出现“函数未声明”的错误同时加上#pragma once防止头文件重复包含。随机种子问题srand()必须只调用一次放在test函数开头若多次调用比如放在SetMine函数中会导致随机数重复地雷位置异常。坐标输入问题玩家输入坐标时需输入两个整数比如“3 5”若输入字母、符号会导致scanf读取失败程序进入死循环可参考之前的输入校验逻辑。编译器适配问题VS编译器需添加#define _CRT_SECURE_NO_WARNINGS否则会报scanf安全警告Dev-C、MinGW可省略直接编译运行即可。棋盘边界问题ROWSROW2、COLSCOL2的设计是核心不要修改为ROW和COL否则计算边缘位置周围雷数时会数组越界程序崩溃。九、代码运行步骤与效果1. 运行步骤以VS 2022为例新建3个文件game.h头文件、game.c源文件、main.c源文件。将上述对应代码复制到3个文件中保存确保3个文件在同一文件夹下。编译运行main.c即可启动游戏。2. 运行效果演示十、总结整合了完整分文件扫雷代码核心逻辑围绕“踩雷”与“排雷”展开分文件设计声明与实现分离代码整洁可维护和11x11边界保护棋盘避免数组越界简化代码同时保留了对新手友好的的模块解析风格让每一段代码的功能都清晰易懂。代码逻辑完整、简洁高效get_mine_count函数的ASCII码运算、InitBoard函数的通用初始化都是非常实用的编程技巧。新手可以重点理解分文件写法和边界保护的设计再逐步优化比如添加输入校验、递归展开空白区域、插旗功能等。3个文件的代码可直接复制编译运行赶紧动手试试体验自己写的扫雷游戏吧后续可根据需求扩展功能进一步提升编程能力。十一、全部代码可直接复制运行game.h#pragma once#includestdio.h#includestdlib.h#includetime.h// 1. 定义棋盘核心常量#define ROW 9 //玩家可见的9x9棋盘行数#define COL 9 //玩家可见的9x9棋盘列数#define ROWS ROW2 // 实际存储的棋盘行数多2行避免计算周围雷数时越界#define COLS COL2 // 实际存储的棋盘列数多2列边界保护// 2. 函数声明所有功能函数的接口供源文件调用void InitBoard(char board[ROWS][COLS], int rows, int cols ,char set);//初始化棋盘void DisplayBoard(char board[ROWS][COLS],int row,int col);//打印棋盘仅展示9*9可见区域void SetMine(char board[ROWS][COLS], int row,int col );//布置雷void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);//排查地雷踩雷排雷核心game.c#define _CRT_SECURE_NO_WARNINGS#includegame.h // 包含头文件获取函数声明和常量定义// 初始化棋盘将棋盘所有位置设置为set通用函数可初始化两个棋盘void InitBoard(char board[ROWS][COLS], int rows, int cols,char set){for (int i 0; i rows; i){for (int j 0; j cols; j){board[i][j] set;// set为传入的字符0或*}}}// 打印棋盘仅展示玩家可见的9*9区域rowROW9colCOL9void DisplayBoard(char board[ROWS][COLS], int row, int col){printf(-----扫雷游戏-----\n);// 打印列号0~9方便玩家定位坐标列号从0开始与输入对应for (int i 0; i col; i){printf(%d , i);}printf(\n);//打印每一行行号 棋盘内容从第1行开始跳过0行对应可见区域for (int i 1; i row; i){printf(%d , i); //打印行号1~9for (int j 1; j col; j){printf(%c , board[i][j]);// 打印9x9可见区域的内容}printf(\n); // 每一行结束换行}printf(\n);}// 布置地雷在9x9可见区域随机放置10颗雷void SetMine(char board[ROWS][COLS], int row, int col){//布置十个雷随机找位置布置,随机生成坐标int count 10;while (count)// 循环布置地雷直到布置完10颗{// 生成随机坐标x1~9y1~9对应9x9可见区域int x rand() % row 1; //rand() % row得到0~8 1后变为1~9int y rand() % col 1; //rand() % col得到0~8 1后变为1~9if (board[x][y] 0)// 判断该位置是否无雷board[x][y] 0表示无雷避免重复布置{board[x][y] 1;count--;// 排1颗后地雷数量-1直到count0退出循环}}}// 计算当前坐标(x,y)周围8个方向的地雷总数int get_mine_count(char mine[ROWS][COLS], int x, int y){// 核心逻辑将周围8个位置的字符0或1相加再减去8个0的ASCII码得到雷数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 row, int col){int x, y;int win 0;// 记录已排查的非雷区域数量// 游戏循环直到排查完所有非雷区域win 9*9 - 10 71while (win col * row - 10){printf(请输入排查的坐标);scanf(%d%d, x, y);if (x 0 x row y 0 y col)//1. 坐标合法性校验确保输入在1~9范围内{if (mine[x][y] 1)//2. 踩雷判断mine[x][y] 1表示命中地雷{printf(很遗憾被炸死了);DisplayBoard(mine, ROW, COL);// 显示所有地雷位置方便玩家查看break;}else{//3. 判断该坐标是否已排查show[x][y] *表示未排查if (show[x][y] *){//1.统计坐标周围有几个雷int count get_mine_count(mine, x, y);//2.存到show数组中show[x][y] count 0;// 3.3 重新打印棋盘展示排查结果DisplayBoard(show, ROW, COL);win;// 排查成功非雷区域数量1}else{printf(该坐标被排查过无需重新排查);}}}else{// 坐标非法超出1~9范围提示重新输入printf(坐标非法重新输入);}}//4. 胜利判断排查的非雷区域数量 总非雷区域数量71if (win col * row - 10){printf(恭喜你排雷成功);// 显示所有地雷位置庆祝胜利DisplayBoard(mine, ROW, COL);}}test.c#define _CRT_SECURE_NO_WARNINGS#includegame.h// 包含头文件获取所有函数声明和常量// 菜单函数显示游戏选择界面void menu(){printf(************************\n);printf(******* 1、play ******\n);printf(******* 0、exit ******\n);printf(************************\n);}// 游戏流程函数整合所有核心功能执行一局扫雷游戏void game(){//扫雷游戏的过程char mine[ROWS][COLS] {0};//存放布置好的雷的信息char show[ROWS][COLS] { 0 };//存放排查出的雷的信息InitBoard(mine, ROWS, COLS,0);//初始化棋盘‘0’InitBoard(show, ROWS, COLS,*);//初始化棋盘‘*’//DisplayBoard(mine, ROW, COL);//打印是只展示9*9的棋盘//DisplayBoard(show, ROW, COL);//打印是只展示9*9的棋盘SetMine(mine, ROW, COL);//在9x9可见区域随机布置10颗//DisplayBoard(mine, ROW, COL);DisplayBoard(show, ROW, COL);//打印初始显示棋盘玩家看到的初始界面//排查雷FindMine(mine, show, ROW, COL);}void test(){int input;// 随机种子利用当前系统时间保证每次运行地雷位置不同仅调用一次srand((unsigned int)time(NULL));// 循环菜单直到玩家选择0退出才结束do{menu();//菜单printf(请选择);scanf(%d, input);switch(input){case 1:game();// 选择1开始一局游戏break;case 0:printf(退出游戏);break;default:printf(选择错误重新选择);}} while (input); // input为0时退出循环否则继续显示菜单}// 主函数程序入口仅调用test函数int main(){test();return 0;}