扫雷游戏项目
扫雷游戏 —— 项目设计报告一、游戏简介扫雷是一款经典的单人益智游戏最早在 Windows 3.1 时代随系统附带而广为人知。游戏的目标很简单在不踩到地雷的前提下翻开棋盘上所有安全格子。基本规则如下棋盘由若干格子组成其中随机分布着一定数量的地雷。玩家每次点击输入一个坐标翻开该格子。如果翻开的是地雷游戏结束。如果翻开的不是地雷格子上会显示该位置周围 8 个相邻格子中地雷的总数帮助玩家推断哪些格子是安全的。当所有非雷格子都被翻开时玩家获胜。本项目实现的是一个基于控制台的 9×9 扫雷共 10 颗地雷玩家通过输入坐标来进行游戏。二、项目文件结构本项目采用 C 语言编写源文件一共三个文件名职责game.h头文件定义宏常量、全局变量声明、函数声明game.c核心逻辑实现棋盘初始化、布雷、翻开格子、游戏主循环test.c测试与入口包含main函数、菜单界面、游戏启动流程三个文件各司其职test.c负责程序入口和交互入口game.c负责游戏算法和核心逻辑game.h则把二者连接起来。三、数据结构设计3.1 双棋盘设计扫雷的核心设计思路是“双棋盘”——程序内部维护两块同样大小的二维数组charmine_board[SIZE][SIZE];// 底层地雷盘存放真实地雷分布charshow_board[SIZE][SIZE];// 玩家显示盘存放玩家能看到的信息mine_board是上帝视角的地雷分布图只有程序自己读写玩家看不到。初始化时所有格子设为0布雷时将地雷位置设为*。show_board是玩家实际看到的界面初始化时所有格子设为*代表未翻开随着游戏推进逐步被替换为具体数字或空格。这种分离有两个好处一是保证玩家无法通过查看内存作弊二是逻辑上把真实状态和显示状态解耦代码更清晰。3.2 为什么用char而不是int棋盘元素使用char类型而不是int原因是棋盘需要显示字符未翻开用*地雷用*翻开后用数字字符0~8。使用char可以直接存字符打印时无需额外转换代码更简洁。数字的转换利用了 ASCII 码的特性show[x][y]cnt0;// cnt 是 0~8 的整数加上 0 后变成对应字符这是一个很常用的技巧——字符0的 ASCII 码是 481是 49以此类推所以整数加0就能得到对应字符。四、核心功能实现4.1 棋盘初始化voidinit_board(charboard[SIZE][SIZE],charval){memset(board,val,SIZE*SIZE);}使用memset将整个棋盘填充为指定字符。mine_board初始化为0无雷show_board初始化为*全未翻开。memset按字节操作对于char数组来说刚好适用。4.2 随机布雷voidset_mine(charmine[SIZE][SIZE]){srand((unsignedint)time(NULL));intcount0;while(countMINE_NUM){intxrand()%SIZE;intyrand()%SIZE;if(mine[x][y]!*){mine[x][y]*;count;}}}这段代码的逻辑很直观用srand(time(NULL))以当前时间作为随机种子保证每次运行游戏地雷分布不同。循环生成随机坐标(x, y)范围是0到SIZE-1。如果该位置还没有布雷不是*就放置一颗地雷然后count加一。重复直到布够MINE_NUM10 颗地雷。这里有一个细节如果随机到的位置已经有雷就跳过不计入 count所以虽然rand()可能重复但最终一定会布够 10 颗雷。不过在雷数接近格子总数时效率会变低但对于 9×9 只有 10 颗雷的场景来说完全不是问题。4.3 翻开格子周围雷数计算voidopen_cell(charmine[SIZE][SIZE],charshow[SIZE][SIZE],intx,inty){if(x0||xSIZE||y0||ySIZE)return;if(show[x][y]!*)return;intcnt0;for(intix-1;ix1;i){for(intjy-1;jy1;j){if(i0iSIZEj0jSIZE){if(mine[i][j]*)cnt;}}}show[x][y]cnt0;}这是扫雷中最关键的算法之一。它的任务是给定一个坐标(x, y)统计它周围 3×3 范围内含自身有多少颗地雷然后把结果显示到show_board上。具体步骤边界检查如果坐标越界直接返回。重复检查如果该格子已经翻开不是*也不需要处理。遍历相邻格子用两层循环遍历(x-1, y-1)到(x1, y1)的 9 个位置跳过越界的位置统计其中地雷的数量。写入结果cnt 0将数字转为字符写入show_board。需要指出的是当前实现只翻开单个格子。标准扫雷中如果翻开的格子周围雷数为 0应该自动展开周围所有相邻格子递归展开 / 泛洪填充。本项目暂未实现这个功能后续可以在open_cell中加入递归调用来完善。4.4 游戏主循环voidgame_main(charmine[SIZE][SIZE],charshow[SIZE][SIZE],intx,inty){intnum0;while(SIZE*SIZE-MINE_NUM-num){printf(请输入坐标);scanf(%d %d,x,y);if(show[x][y]!*){printf(已经验证过了,试试别的值吧\n);}if(mine[x][y]*){printf(踩雷游戏结束\n);print_board(mine);exit(1);}else{open_cell(mine_board,show,x,y);print_board(show);num;}}printf(------恭喜你过关-----\n);}游戏主循环的逻辑循环条件是SIZE * SIZE - MINE_NUM - num即总格子数减去雷数再减去已翻开数只要不为 0 就继续。每次循环接收玩家输入的坐标。如果该格子已经翻开提示玩家重新选择。如果踩到雷打印地雷盘并退出程序。如果是安全格子调用open_cell处理并打印显示盘已翻开数加一。当所有安全格子都被翻开打印过关信息。4.5 菜单与入口intmain(){intnum;do{menu();printf(请输入你的选择);scanf(%d,num);switch(num){case1:game();break;case0:break;}}while(num);return0;}main函数通过do-while循环实现了一个简单的菜单系统显示菜单 → 接收选择 → 执行对应操作 → 循环直到选择退出。进入游戏时调用game()函数完成棋盘初始化、布雷和游戏循环的全部流程。五、程序运行流程整体运行流程可以用下面的流程图来概括程序启动 │ ▼ 显示菜单1. 开始游戏 / 0. 退出 │ ▼ 玩家选择 │ ├── 选择 0 → 退出程序 │ └── 选择 1 → 进入 game() │ ▼ 初始化两个棋盘 mine_board 全 0show_board 全 * │ ▼ 打印初始 show_board │ ▼ 随机布雷set_mine │ ▼ 进入游戏循环game_main │ ▼ 玩家输入坐标 │ ├── 踩雷 → 打印 mine_board游戏结束 │ └── 安全 → open_cell 计算周围雷数 更新 show_board 并打印 检查是否全部翻开 │ ├── 未完成 → 继续输入 └── 全部翻开 → 恭喜过关六、代码中的不足与改进方向在阅读和分析代码的过程中也发现了一些可以改进的地方6.1 全局变量的使用game.h中声明了全局变量intx;inty;charmine_board[SIZE][SIZE];charshow_board[SIZE][SIZE];全局变量会导致多个函数共享同一份数据虽然在这个小程序中问题不大但从工程规范上来说建议把这些变量移到main或game函数内部通过参数传递给各子函数这样代码的可维护性和可测试性会更好。6.2 缺少坐标合法性校验玩家输入坐标后代码没有检查x和y是否在合法范围内0到SIZE-1。如果输入了超出范围的值直接访问数组会导致越界可能产生不可预期的行为。建议在接收输入后加入范围校验。6.3 翻开逻辑的局限当前open_cell只处理单个格子没有实现零值自动展开。标准扫雷中当翻开的格子周围雷数为 0 时应该自动递归翻开所有相邻格子直到遇到雷数不为 0 的边界。这个功能可以用递归或 BFS 实现能大幅提升游戏体验。6.4 缺少插旗功能标准扫雷允许玩家标记疑似地雷的位置插旗帮助记忆和推理。当前版本没有这个功能可以通过在show_board中使用特殊字符如F来表示旗帜标记。6.5 胜利判定的潜在问题胜利条件while (SIZE * SIZE - MINE_NUM - num)依赖num的计数。但当前代码中如果玩家输入了已经翻开的坐标num不会增加循环条件仍然正确。不过更健壮的做法是直接统计show_board中未翻开且非雷的格子数而不是依赖计数器。七、总结本项目实现了一个基础但功能完整的控制台扫雷游戏。通过双棋盘设计将底层数据与显示分离利用memset初始化、rand随机布雷、双重循环统计周围雷数等手段用不到 200 行代码完成了扫雷的核心逻辑。

相关新闻

校园网环境下Windows系统定时开机、自动登录与联网认证全自动方案

校园网环境下Windows系统定时开机、自动登录与联网认证全自动方案

1. 项目缘起:一个“懒人”的桌面自动化构想作为一名在校园里摸爬滚打多年的“老油条”,我每天到实验室的第一件事,就是按下电脑的电源键,然后等待漫长的启动过程。开机后,手动输入Windows密码登录,接着还得…

2026/11/1 12:15:53 阅读更多 →
Unity游戏Mod开发入门:基于BepInEx框架的完整实践指南

Unity游戏Mod开发入门:基于BepInEx框架的完整实践指南

1. 项目概述:从玩家到创造者的蜕变 如果你和我一样,是个深度游戏玩家,玩久了总会冒出一些“要是能这样改一下就好了”的念头。从《星露谷物语》里想随时换农场布局,到《幻兽帕鲁》里琢磨着加个新物种,这种“动手改造”…

2026/11/6 9:48:31 阅读更多 →
Flutter MethodChannel 深度指南:从原理到实战的跨平台通信

Flutter MethodChannel 深度指南:从原理到实战的跨平台通信

在实际的跨平台应用开发中,我们经常面临一个核心挑战:如何让一套代码逻辑,在 iOS 和 Android 两个生态系统中都能稳定、高效地运行。Flutter 的出现,通过自绘引擎和统一的 Dart 运行时,理论上解决了 UI 一致性的问题。…

2026/11/6 0:19:19 阅读更多 →

最新新闻

Electron桌面应用开发实战:从环境搭建到打包发布全流程

Electron桌面应用开发实战:从环境搭建到打包发布全流程

做前端这些年,我大部分时间都在跟浏览器标签页打交道,直到第一次把桌面应用跑起来,才发现原来自己一直被限制在地址栏和刷新按钮围起来的框框里。Electron这个词,最开始是在某段时间频繁出现在同事的讨论里,一个用HTML…

2026/11/5 12:28:00 阅读更多 →
滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

简介:亚太天能T109滑盖智能锁产品说明书是一份PDF格式的官方技术文档,主要面向智能锁安装人员、终端用户及售后维护者,帮助快速掌握产品安装、配置与常见故障处理。全包仅1个PDF文件,大小4.37MB,内容结构清晰&#xff…

2026/11/6 9:29:27 阅读更多 →
PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

上个月我在调试一台高速AGV的驱动系统,整车要求空载跑到8m/s,电机转速已经拉到额定点,驱动器的输出电压也顶到了母线电压极限附近。再往上踩速度指令,转速纹丝不动,三相电流反而开始震荡。那会儿我的第一反应就是&…

2026/11/5 12:28:01 阅读更多 →
C++核心知识点:从内存管理到STL的实战指南

C++核心知识点:从内存管理到STL的实战指南

C 这门语言我前前后后折腾了十几年,用它写过嵌入式驱动、做过游戏服务端,也搭过基础组件库。每次看到新人一头扎进"C 核心知识点"这个搜索词,我其实挺感慨的——这门语言的难点从来不是语法多,而是知识点之间的耦合太深…

2026/11/5 12:28:02 阅读更多 →
T109滑盖智能锁怎么装?安装调试与故障排查全攻略

T109滑盖智能锁怎么装?安装调试与故障排查全攻略

简介:亚太天能T109滑盖智能锁产品说明书以PDF格式发布,适合智能锁用户、安装人员与售后维修人员阅读。内容围绕T109系列指纹密码刷卡锁展开,先介绍前面板、后面板、锁体、机械钥匙、电池盒盖等部件名称与功能,再给出完整安装步骤&…

2026/11/6 8:08:42 阅读更多 →
LLM知识降熵:从信息垄断到理解权下放

LLM知识降熵:从信息垄断到理解权下放

你有没有过这种经历:翻开源码注释、论文摘要或者一份专业报告,每个字都认识,连在一起却像天书。想找人聊聊,身边要么没人懂,要么懂的懒得跟你解释。后来我试着把同一段内容丢给一个大语言模型,几秒钟之后&a…

2026/11/5 19:38:59 阅读更多 →

日新闻

周新闻

月新闻