C 语言项目复盘:用数组和函数实现扫雷游戏

📅 2026/8/6 23:42:56
C 语言项目复盘:用数组和函数实现扫雷游戏
C 语言项目复盘用数组和函数实现扫雷游戏学完二维数组和函数模块化编程之后终于动手完成了第一个完整的小项目 —— 控制台版扫雷游戏。整个过程把数组操作、函数拆分、多文件工程写法都串起来了也踩了不少细节坑。这篇博客完整复盘设计思路、核心实现和高频易错点既是内化总结也方便以后回头复习。一、游戏整体设计与思路1.1 基础功能需求我们实现的是经典初级扫雷核心功能如下控制台交互主菜单选择开始游戏 / 退出9×9 的游戏棋盘默认随机布置 10 个雷玩家输入坐标排查雷踩到雷游戏失败展示全部雷的位置没踩到雷显示该位置周围 8 格的雷数排查完所有非雷格子游戏胜利1.2 核心数据结构设计这是整个项目最关键的设计点一开始没想明白很容易写乱。两个独立棋盘我们用两个二维数组分别存储数据互不干扰mine 数组存放雷的布置信息字符 ‘0’ 表示非雷‘1’ 表示雷show 数组展示给玩家的棋盘初始全是 ‘*’排查后显示周围雷数用两个数组的好处是数据逻辑分离而且初始化、打印这类通用函数可以复用一套代码不用写两遍。为什么数组是 11×11不是 9×9这是新手最容易忽略的设计细节。如果只开 9×9 的数组当排查边缘格子比如第一行、最后一列时统计周围 8 个格子的雷数会出现数组下标越界导致程序异常。解决方案在 9×9 棋盘外围多扩一圈缓冲区数组大小变成 11×11。雷只布置在中间 1~9 行、1~9 列的区域外围一圈永远是 ‘0’。这样统计任何位置的周围雷数时都不会越界访问代码写起来也不用额外写边界判断。我们用宏定义统一管理尺寸改难度也方便#defineROW9// 实际游戏行数#defineCOL9// 实际游戏列数#defineROWSROW2// 数组总行数#defineCOLSCOL2// 数组总列数#defineEASY_COUNT10// 雷的数量1.3 多文件工程拆分按照模块化编程的思路把代码拆成 3 个文件game.h头文件放宏定义、函数声明、需要的标准头文件game.c游戏所有功能函数的具体实现test.c主程序入口写菜单、游戏主流程控制二、核心功能函数实现拆解2.1 棋盘初始化函数因为两个棋盘都需要初始化所以做成通用函数通过参数 set 指定初始化的字符mine 数组初始化为全 ‘0’show 数组初始化为全 ‘*’voidInitBoard(charboard[ROWS][COLS],introws,intcols,charset){inti0;for(i0;irows;i){intj0;for(j0;jcols;j){board[i][j]set;}}}2.2 打印棋盘函数为了方便玩家输入坐标打印时要带上行号和列号且都从 1 开始显示和玩家输入对齐。voidDisplayBoard(charboard[ROWS][COLS],introw,intcol){inti0;printf(--------扫雷游戏-------\n);// 打印列号for(i0;icol;i){printf(%d ,i);}printf(\n);// 打印每行数据和行号for(i1;irow;i){printf(%d ,i);intj0;for(j1;jcol;j){printf(%c ,board[i][j]);}printf(\n);}}2.3 随机布置雷函数生成随机坐标在合法范围内布置雷注意要判重避免同一个位置重复放雷。voidSetMine(charboard[ROWS][COLS],introw,intcol){intcountEASY_COUNT;while(count){// 生成1~row、1~col的随机坐标intxrand()%row1;intyrand()%col1;if(board[x][y]0){board[x][y]1;count--;}}}2.4 统计周围雷数函数这是最巧妙的一个小函数。因为 mine 数组存的是字符 ‘0’ 和 ‘1’我们把周围 8 个格子的字符值加起来再减去 8 个字符 ‘0’ 的 ASCII 值得到的就是数字形式的雷的总数。intGetMineCount(charmine[ROWS][COLS],intx,inty){return(mine[x-1][y-1]mine[x-1][y]mine[x-1][y1]mine[x][y-1]mine[x][y1]mine[x1][y-1]mine[x1][y]mine[x1][y1]-8*0);}2.5 排查雷主逻辑游戏的核心循环负责接收玩家输入、判断胜负、更新棋盘。voidFindMine(charmine[ROWS][COLS],charshow[ROWS][COLS],introw,intcol){intx0;inty0;intwin0;// 记录已排查的非雷格子数while(winrow*col-EASY_COUNT){printf(请输入要排查的坐标:);scanf(%d %d,x,y);// 坐标合法性校验if(x1xrowy1ycol){if(mine[x][y]1){printf(很遗憾你被炸死了\n);DisplayBoard(mine,ROW,COL);break;}else{// 统计周围雷数更新展示棋盘intcountGetMineCount(mine,x,y);show[x][y]count0;// 数字转字符存入DisplayBoard(show,ROW,COL);win;}}else{printf(坐标非法重新输入\n);}}// 排雷成功判断if(winrow*col-EASY_COUNT){printf(恭喜你排雷成功\n);DisplayBoard(mine,ROW,COL);}}三、复盘我踩过的高频易错点这部分是写项目最有价值的收获很多语法点学的时候觉得懂了一写代码就踩坑。数组下标越界这是新手写扫雷最容易犯的错。一开始直接用 9×9 数组统计边缘格子周围雷数时x-1 或 y1 会跑到下标 0 或者 9 的位置越界访问内存轻则输出乱码重则程序直接崩溃。解决方式扩大一圈数组做缓冲区11×11 的设计从根源上避免越界判断。字符 ‘0’ 和数字 0 混淆mine 数组存的是字符型的 ‘0’ 和 ‘1’不是整型 0 和 1。字符 ‘0’ 的 ASCII 码是 48直接相加得到的是 ASCII 值的和不是雷的数量。一开始没注意统计出来的雷数永远是几百完全不对。解决方式字符转数字‘1’ - ‘0’ 1数字转字符count ‘0’ 得到对应字符随机数种子放置错误一开始把 srand((unsigned int)time(NULL)) 写在了 game() 函数里每开一局就调用一次。快速连续开两局的话时间戳几乎一样生成的雷的位置完全相同。解决方式srand 整个程序只调用一次放在 main 函数开头即可。布置雷没有判重如果随机生成了重复的坐标不做判断就直接赋值会导致实际雷的数量比设定值少。解决方式布置雷前先判断当前位置是不是 ‘0’确认没有雷再布置只有布置成功才让计数器减一。胜利条件逻辑写错总共有 ROW*COL 个格子减去 EASY_COUNT 个雷剩下的就是需要排查的非雷格子总数。一开始把胜利条件写反了或者 win 计数器累加的位置放错了导致排查完所有格子也不提示胜利。注意只有排查非雷格子成功的时候才让 win。多文件头文件重复包含拆文件初期经常出现重复定义的报错。解决方式头文件开头加上 #pragma once防止头文件被重复包含每个 .c 文件都包含对应的 .h 文件不要漏写。四、可扩展的优化方向基础版写完之后还可以继续拓展功能练手更多语法多难度选择简单9×910 雷、中等16×1640 雷、困难30×1699 雷通过菜单选择用变量控制数组尺寸和雷数空白区域自动展开如果当前位置周围雷数为 0递归 / 循环展开周围一圈的格子直到遇到有数字的边界还原经典扫雷体验标记雷功能支持玩家输入指令标记疑似雷的位置计时功能记录排雷用时增加游戏性总结这次扫雷项目做下来最大的收获不是写出了一个游戏而是学会了怎么把一个完整的需求拆成数据结构 一个个独立功能函数再用多文件组织成工程。很多知识点比如数组传参、函数封装、边界处理上课听的时候觉得很简单真正动手写才会发现各种细节坑。尤其是字符和数字的转换、数组越界、逻辑边界判断都是 C 语言编程里的高频易错点。纸上得来终觉浅多动手写项目踩坑再复盘才是巩固知识最有效的方式。