当前位置: 首页 > news >正文

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// 实际游戏列数#defineROWSROW+2// 数组总行数#defineCOLSCOL+2// 数组总列数#defineEASY_COUNT10// 雷的数量

1.3 多文件工程拆分
按照模块化编程的思路,把代码拆成 3 个文件:
game.h:头文件,放宏定义、函数声明、需要的标准头文件
game.c:游戏所有功能函数的具体实现
test.c:主程序入口,写菜单、游戏主流程控制
二、核心功能函数实现拆解
2.1 棋盘初始化函数
因为两个棋盘都需要初始化,所以做成通用函数,通过参数 set 指定初始化的字符:
mine 数组初始化为全 ‘0’
show 数组初始化为全 ‘*’

voidInitBoard(charboard[ROWS][COLS],introws,intcols,charset){inti=0;for(i=0;i<rows;i++){intj=0;for(j=0;j<cols;j++){board[i][j]=set;}}}

2.2 打印棋盘函数
为了方便玩家输入坐标,打印时要带上行号和列号,且都从 1 开始显示,和玩家输入对齐。

voidDisplayBoard(charboard[ROWS][COLS],introw,intcol){inti=0;printf("--------扫雷游戏-------\n");// 打印列号for(i=0;i<=col;i++){printf("%d ",i);}printf("\n");// 打印每行数据和行号for(i=1;i<=row;i++){printf("%d ",i);intj=0;for(j=1;j<=col;j++){printf("%c ",board[i][j]);}printf("\n");}}

2.3 随机布置雷函数
生成随机坐标,在合法范围内布置雷,注意要判重,避免同一个位置重复放雷。

voidSetMine(charboard[ROWS][COLS],introw,intcol){intcount=EASY_COUNT;while(count){// 生成1~row、1~col的随机坐标intx=rand()%row+1;inty=rand()%col+1;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][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');}

2.5 排查雷主逻辑
游戏的核心循环,负责接收玩家输入、判断胜负、更新棋盘。

voidFindMine(charmine[ROWS][COLS],charshow[ROWS][COLS],introw,intcol){intx=0;inty=0;intwin=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{// 统计周围雷数,更新展示棋盘intcount=GetMineCount(mine,x,y);show[x][y]=count+'0';// 数字转字符存入DisplayBoard(show,ROW,COL);win++;}}else{printf("坐标非法,重新输入\n");}}// 排雷成功判断if(win==row*col-EASY_COUNT){printf("恭喜你,排雷成功\n");DisplayBoard(mine,ROW,COL);}}

三、复盘:我踩过的高频易错点
这部分是写项目最有价值的收获,很多语法点学的时候觉得懂了,一写代码就踩坑。

  1. 数组下标越界
    这是新手写扫雷最容易犯的错。
    一开始直接用 9×9 数组,统计边缘格子周围雷数时,x-1 或 y+1 会跑到下标 0 或者 9 的位置,越界访问内存,轻则输出乱码,重则程序直接崩溃。
    解决方式:扩大一圈数组做缓冲区,11×11 的设计从根源上避免越界判断。
  2. 字符 ‘0’ 和数字 0 混淆
    mine 数组存的是字符型的 ‘0’ 和 ‘1’,不是整型 0 和 1。
    字符 ‘0’ 的 ASCII 码是 48,直接相加得到的是 ASCII 值的和,不是雷的数量。一开始没注意,统计出来的雷数永远是几百,完全不对。
    解决方式:
    字符转数字:‘1’ - ‘0’ = 1
    数字转字符:count + ‘0’ 得到对应字符
  3. 随机数种子放置错误
    一开始把 srand((unsigned int)time(NULL)) 写在了 game() 函数里,每开一局就调用一次。快速连续开两局的话,时间戳几乎一样,生成的雷的位置完全相同。
    解决方式:srand 整个程序只调用一次,放在 main 函数开头即可。
  4. 布置雷没有判重
    如果随机生成了重复的坐标,不做判断就直接赋值,会导致实际雷的数量比设定值少。
    解决方式:布置雷前先判断当前位置是不是 ‘0’,确认没有雷再布置,只有布置成功才让计数器减一。
  5. 胜利条件逻辑写错
    总共有 ROW*COL 个格子,减去 EASY_COUNT 个雷,剩下的就是需要排查的非雷格子总数。
    一开始把胜利条件写反了,或者 win 计数器累加的位置放错了,导致排查完所有格子也不提示胜利。
    注意:只有排查非雷格子成功的时候,才让 win++。
  6. 多文件头文件重复包含
    拆文件初期,经常出现重复定义的报错。
    解决方式:头文件开头加上 #pragma once,防止头文件被重复包含;每个 .c 文件都包含对应的 .h 文件,不要漏写。
    四、可扩展的优化方向
    基础版写完之后,还可以继续拓展功能,练手更多语法:
    多难度选择:简单(9×9,10 雷)、中等(16×16,40 雷)、困难(30×16,99 雷),通过菜单选择,用变量控制数组尺寸和雷数
    空白区域自动展开:如果当前位置周围雷数为 0,递归 / 循环展开周围一圈的格子,直到遇到有数字的边界,还原经典扫雷体验
    标记雷功能:支持玩家输入指令标记疑似雷的位置
    计时功能:记录排雷用时,增加游戏性
    总结
    这次扫雷项目做下来,最大的收获不是写出了一个游戏,而是学会了怎么把一个完整的需求,拆成数据结构 + 一个个独立功能函数,再用多文件组织成工程。
    很多知识点比如数组传参、函数封装、边界处理,上课听的时候觉得很简单,真正动手写才会发现各种细节坑。尤其是字符和数字的转换、数组越界、逻辑边界判断,都是 C 语言编程里的高频易错点。纸上得来终觉浅,多动手写项目,踩坑再复盘,才是巩固知识最有效的方式。
http://www.cnnetsun.cn/news/3878321.html

相关文章:

  • 构建健壮的AI输出处理管道:从Prompt工程到生产部署
  • 开源对话模型实战:从选型部署到API集成全指南
  • 2024年厦门php网站建设全流程深度解析,从服务器搭建到上线运营的避坑指南,揭秘本地化服务的真实成本与性能优化策略,助你在跨境电商与中小企业数字化转型中站稳脚跟
  • 终极指南:用Godot引擎快速构建2D平台跳跃游戏
  • 单片机毕业设计-基于 STM32/51 单片机的水环境参数阈值报警控制系统设计 基于 STM32 的多模式水质监测硬件终端设计与实现(011002)
  • 2024年如何从零开始搭建一套高转化的商城类网站建设指南与避坑全解析
  • 4G显存畅玩AI视频创作:WanVideo_comfy轻量化方案完全指南
  • PCB打样板材受潮失效机理拆解:教你看懂底层逻辑
  • 3步掌握WanVideo_comfy:ComfyUI视频生成模型终极整合指南
  • 做网站不是搭积木!2024年避坑指南与定制平台网站建设方案深度解析
  • iOS上的Minecraft Java版终极指南:PojavLauncher完整使用教程
  • verilog HDLBits刷题[Building Larger Circuits]“Exams/review2015 fsmshi”---FSM :Enable shift register
  • 《2026 国内可擦写语音芯片厂商手册:32 位内核方案、存储选型、多语言产品避坑 FAQ》
  • scene-editor:基于vis-three框架的Web 3D场景编辑器架构深度解析
  • 从2D图像到3D人体网格:HybrIK混合逆运动学算法终极指南
  • 掌握AI学习捷径:Python机器学习完整知识体系指南 [特殊字符]
  • AMAT 0100-01321 数字输入 / 输出板
  • 免费开源针织设计工具:从创意到成品的完整数字化编织方案
  • Qwen3.8-Max 2.4万亿参数大模型部署与API调用实战指南
  • 2026年跑遍南昌必打卡火锅热门店,人均差出近一倍
  • Windows信号量:线程同步与资源管理实战指南
  • 电子商务网站建设规划书:从0到1打造高转化率商业帝国的实操指南与避坑手册
  • 网络诊断基础:Ping命令原理与专业使用技巧
  • 如何打造终极桌面AI伴侣:5分钟快速上手指南
  • 基于LangChain与Python构建多智能体协同办公系统实战指南
  • 如何撰写一份专业且落地的网站建设方案 filetype doc 文档详解与实战指南
  • Python网络小说分析系统:技术实现与优化策略
  • phpstan-strict-rules实战:解决10个常见的PHP代码问题
  • 网站建设后台管理到底难不难:一个老程序员的真心话与实战避坑指南
  • 简单实现惊人视觉效果:UIScrollView-Examples中的Parallax视差滚动教程