C++/MFC扫雷完整程序:大作业从能跑到交得掉的避坑指南

发布时间:2026/10/25 13:30:41
C++/MFC扫雷完整程序:大作业从能跑到交得掉的避坑指南 简介这是一份面向C初学者与课程设计需求者的MFC扫雷完整工程适合作为C大作业、Windows桌面开发入门练手项目。资源以C语言结合MFC类库实现涵盖二维数组雷区建模、消息映射事件响应、周围雷数计算算法、踩雷判定与状态管理以及新游戏、标记等按钮交互逻辑并借助资源文件存放图标位图美化界面。压缩包共31个文件包含6个cpp源文件、8个h头文件与17个bmp图片资源整体约33KB源码与素材齐备可直接在Visual Studio中打开编译调试。目前已有1568人学习下载读者可据此理解MFC窗口、对话框与控件类的组织方式掌握事件驱动编程与调试排错思路并在此基础上扩展难度设置、计时与排行榜等功能对提升C工程实践能力有较高参考价值。1. 从一份 C/MFC 扫雷完整程序说起大作业为什么总卡在“能跑但交不掉”很多人做 C 大作业的第一反应是找一份“扫雷完整程序”改改交上去结果打开工程就翻车编译报一堆afxwin.h找不到链接提示mfc140ud.lib缺失运行起来窗口一闪就退。问题不在扫雷逻辑而在 MFC 这套桌面开发框架的工程配置和消息机制它和你在 VS Code 里写c stl小练习完全不是一回事。这份 C/MFC 扫雷完整程序要解决的核心问题很具体用 MFC 的文档/视图或对话框框架把雷区生成、左键翻开、右键插旗、空白区递归展开、胜负判定这几件事串成一个能编译、能演示、能讲清设计思路的桌面程序。它适合正在赶 C 大作业、需要一份结构清晰可讲解代码的在校生也适合想借扫雷这个经典案例重新捡起 MFC 消息映射和 GDI 绘图的开发者。下面按“先立住原理、再动手复现、最后避坑”的顺序拆开讲参数和代码都能直接抄。2. 扫雷的数据模型与 MFC 框架选型先想清楚再建工程2.1 雷区到底该用什么数据结构存扫雷看着简单数据模型设计错了后面全是补丁。常见做法是用一个二维数组存每个格子的状态但状态不止“有雷/无雷”两种还要区分“已翻开/未翻开/已插旗”以及记录周围雷数。我一般会拆成两个二维数组一个int存雷和计数一个int存显示状态。这样逻辑和渲染彻底分离调试时打印状态矩阵就能定位问题。// 雷区核心数据放在 CMyMineView 或对话框类里 const int ROWS 16; // 行数 const int COLS 16; // 列数 const int MINES 40; // 雷数 int m_cell[ROWS][COLS]; // -1 表示雷0~8 表示周围雷数 int m_state[ROWS][COLS]; // 0 未翻开1 已翻开2 已插旗 bool m_bGameOver; // 游戏是否结束 bool m_bFirstClick; // 是否还没点第一下m_cell用 -1 表示雷是个约定好处是周围雷数计算时可以直接判断 -1不用额外布尔数组。m_state单独存是为了右键插旗不影响雷数信息。m_bFirstClick这个标志很关键正规扫雷第一次点击不能踩雷需要在第一次点击后再布雷或者把雷挪开。很多人忽略这点演示时第一下就炸观感很差。参数上ROWS、COLS、MINES建议做成常量或从配置读方便改成初级 9×9/10 雷、中级 16×16/40 雷、高级 16×30/99 雷。大作业里能切换难度是加分项但别一上来就做先把固定难度跑通。2.2 MFC 用对话框还是文档/视图这是选型第一个岔路口。对话框程序Dialog-based上手快所有控件拖一拖消息处理直接写OnLButtonDown适合时间紧、只求交作业。文档/视图SDI结构更“正统”老师一看就知道你懂 MFC 框架但要多理解CDocument、CView、OnDraw的配合调试成本高。我的建议如果大作业要求里明确写了“体现 MFC 文档/视图架构”就选 SDI否则对话框程序足够把精力放在扫雷逻辑和界面上。两者在扫雷核心逻辑上完全一样区别只是绘制和消息入口。对话框程序里用OnPaint或自绘按钮SDI 里用OnDraw配合CDC。选对话框时别用一堆按钮控件拼雷区16×16 就是 256 个控件资源编辑器卡到怀疑人生消息映射也难维护。正确做法是整个客户区自绘在OnPaint里根据m_state逐格画矩形和数字鼠标点击时用坐标反算行列。这样代码量反而小性能也好。2.3 消息映射左键右键怎么落到具体格子MFC 的消息映射是新手最容易懵的地方。你要处理鼠标左键、右键还要区分是按下还是抬起。扫雷一般用WM_LBUTTONDOWN翻开、WM_RBUTTONDOWN插旗。在对话框类里加消息处理函数类向导会自动生成映射宏。// 头文件里声明 afx_msg void OnLButtonDown(UINT nFlags, CPoint point); afx_msg void OnRButtonDown(UINT nFlags, CPoint point); // 源文件消息映射 BEGIN_MESSAGE_MAP(CMyMineDlg, CDialogEx) ON_WM_LBUTTONDOWN() ON_WM_RBUTTONDOWN() END_MESSAGE_MAP() void CMyMineDlg::OnLButtonDown(UINT nFlags, CPoint point) { int row point.y / CELL_SIZE; // CELL_SIZE 是格子像素边长 int col point.x / CELL_SIZE; if (row 0 row ROWS col 0 col COLS) RevealCell(row, col); // 翻开逻辑 CDialogEx::OnLButtonDown(nFlags, point); }point是客户区坐标除以格子边长就得到行列。这里必须做边界判断否则点到窗口边缘会数组越界程序直接崩。CELL_SIZE建议设 24 或 30 像素太小数字看不清太大一屏放不下。RevealCell是核心翻开函数下一章展开讲。提示消息映射宏必须写在.cpp的BEGIN_MESSAGE_MAP和END_MESSAGE_MAP之间函数声明和定义签名要完全一致否则编译能过但点击没反应这是 MFC 最典型的“玄学”问题之一。3. 核心逻辑实现布雷、翻开、递归展开与胜负判定3.1 第一次点击后再布雷避开首点踩雷前面提到首点不能踩雷。实现方式有两种一是先随机布雷首点时如果正好是雷就把它和某个非雷格交换二是首点后再布雷且布雷时排除首点及其周围 8 格。第二种体验更好首点必定展开一片但实现稍复杂。大作业里用第一种就够代码简单不易错。void CMyMineDlg::InitBoard(int safeRow, int safeCol) { // 先全部清零 for (int r 0; r ROWS; r) for (int c 0; c COLS; c) { m_cell[r][c] 0; m_state[r][c] 0; } // 随机布雷避开首点 int placed 0; while (placed MINES) { int r rand() % ROWS; int c rand() % COLS; if (m_cell[r][c] -1) continue; if (r safeRow c safeCol) continue; m_cell[r][c] -1; placed; } // 计算周围雷数 for (int r 0; r ROWS; r) for (int c 0; c COLS; c) { if (m_cell[r][c] -1) continue; int cnt 0; for (int dr -1; dr 1; dr) for (int dc -1; dc 1; dc) { int nr r dr, nc c dc; if (nr 0 nr ROWS nc 0 nc COLS m_cell[nr][nc] -1) cnt; } m_cell[r][c] cnt; } }rand()前记得srand((unsigned)time(NULL))否则每次运行雷区一样演示时会被看出来。周围雷数计算用双重循环扫 8 邻域边界判断不能省。这段是 O(ROWS×COLS×9)16×16 规模下瞬间完成不用优化。3.2 递归展开空白格连锁翻开怎么写不爆栈点到一个周围雷数为 0 的格子要自动把相连的空白区全翻开这是扫雷的灵魂。用递归最直观但 16×16 最多 256 格递归深度有限不会爆栈。真正要注意的是别重复访问导致死循环靠m_state判断即可。void CMyMineDlg::RevealCell(int r, int c) { if (r 0 || r ROWS || c 0 || c COLS) return; if (m_state[r][c] ! 0) return; // 已翻开或已插旗直接返回 if (m_cell[r][c] -1) { // 踩雷 m_bGameOver true; m_state[r][c] 1; Invalidate(); // 触发重绘 MessageBox(_T(踩雷了)); return; } m_state[r][c] 1; // 标记翻开 if (m_cell[r][c] 0) { // 空白格递归展开 8 邻域 for (int dr -1; dr 1; dr) for (int dc -1; dc 1; dc) if (dr ! 0 || dc ! 0) RevealCell(r dr, c dc); } }关键判断是m_state[r][c] ! 0直接返回这既挡住了已翻开的格子也挡住了插旗的格子避免递归重复进入。踩雷后设m_bGameOver并弹提示同时Invalidate()让界面重绘显示所有雷。注意递归里对每个邻格都调RevealCell边界判断在函数开头统一做代码干净。3.3 右键插旗与胜负判定右键插旗逻辑简单未翻开的格子状态在 0 和 2 之间切换已翻开的不响应。插旗数量可以显示剩余雷数但别强制限制插旗数否则体验差。void CMyMineDlg::OnRButtonDown(UINT nFlags, CPoint point) { int row point.y / CELL_SIZE; int col point.x / CELL_SIZE; if (row 0 row ROWS col 0 col COLS m_state[row][col] ! 1) { m_state[row][col] (m_state[row][col] 2) ? 0 : 2; Invalidate(); } CDialogEx::OnRButtonDown(nFlags, point); }胜负判定放在每次翻开后统计已翻开格子数如果等于总格子数减雷数就赢了。也可以在插旗时判断所有雷是否都被正确标记但前者更可靠。bool CMyMineDlg::CheckWin() { int revealed 0; for (int r 0; r ROWS; r) for (int c 0; c COLS; c) if (m_state[r][c] 1) revealed; return revealed ROWS * COLS - MINES; }RevealCell末尾调用CheckWin()赢了就弹提示并禁用后续点击靠m_bGameOver判断。这套逻辑跑通扫雷的核心就完成了剩下的是绘制和界面美化。3.4 用 GDI 把雷区画出来绘制放在OnPaint里用CPaintDC。每个格子根据m_state和m_cell决定画什么未翻开画凸起灰色矩形已翻开画平面并显示数字插旗画个红旗或字母 F踩中的雷画红色背景。void CMyMineDlg::OnPaint() { CPaintDC dc(this); for (int r 0; r ROWS; r) for (int c 0; c COLS; c) { CRect rect(c * CELL_SIZE, r * CELL_SIZE, (c 1) * CELL_SIZE, (r 1) * CELL_SIZE); if (m_state[r][c] 0) { dc.FillSolidRect(rect, RGB(192, 192, 192)); dc.Draw3dRect(rect, RGB(255,255,255), RGB(128,128,128)); } else if (m_state[r][c] 2) { dc.FillSolidRect(rect, RGB(192, 192, 192)); dc.SetTextColor(RGB(255, 0, 0)); dc.DrawText(_T(F), rect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); } else { dc.FillSolidRect(rect, RGB(224, 224, 224)); if (m_cell[r][c] 0) { CString s; s.Format(_T(%d), m_cell[r][c]); dc.SetTextColor(GetNumColor(m_cell[r][c])); dc.DrawText(s, rect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); } } } }Draw3dRect画出立体边框未翻开格子有凸起感。数字颜色按经典扫雷规则1 蓝、2 绿、3 红、4 深蓝等写个GetNumColor辅助函数返回COLORREF。DrawText用DT_CENTER | DT_VCENTER | DT_SINGLELINE让数字居中。绘制代码不复杂但格子多时要注意Invalidate()别频繁调用一次重绘整屏即可。4. 工程配置与编译链接让程序在别人机器上也能跑起来4.1 字符集与 MFC 库的链接方式MFC 工程最常见的编译错误是字符集不匹配。VS 默认用 Unicode代码里字符串要用_T(...)或L...直接写...在CString赋值时可能报错。另一个坑是运行库链接方式/MDd调试多线程 DLL和/MTd调试多线程静态不能混用MFC 工程一般用“在共享 DLL 中使用 MFC”对应/MDd。在项目属性里检查三处配置属性 → 常规 → 字符集设为“使用 Unicode 字符集”配置属性 → 高级 → MFC 的使用设为“在共享 DLL 中使用 MFC”C/C → 代码生成 → 运行库设为“多线程调试 DLL (/MDd)”。这三处一致基本不会出链接错误。4.2 常见编译错误对照表错误现象原因解决找不到afxwin.h未安装 MFC 组件或未包含头文件VS Installer 勾选“用于最新 v143 生成工具的 C MFC”LNK2019无法解析外部符号运行库或 MFC 链接方式不一致统一改为共享 DLL 使用 MFCCString与const char*不兼容字符集为 Unicode 但用了窄字符串字符串加_T()宏窗口一闪就退对话框程序DoModal前出错或断言失败调试运行看输出窗口的断言信息点击无反应消息映射宏缺失或签名不匹配检查BEGIN_MESSAGE_MAP和函数声明这张表覆盖了大作业里 90% 的编译运行问题。遇到LNK错误先看完整错误信息里的库名再对照运行库设置。断言失败一般会在输出窗口打印文件和行号顺着看就能定位。4.3 发布时让别人也能运行交作业时老师可能直接双击 exe。如果你用“共享 DLL 使用 MFC”目标机器没装对应运行库就会报缺 DLL。稳妥做法是改成“在静态库中使用 MFC”对应运行库/MTd调试或/MT发布这样 exe 自带 MFC 代码不依赖外部 DLL代价是文件大几 MB。大作业场景下这点体积无所谓换来的是双击即开。改完静态链接后要重新生成整个工程清理旧的中间文件否则可能残留/MDd的 obj 导致链接冲突。生成 → 清理解决方案再重新生成这一步别省。5. 避坑与排查扫雷大作业里那些让人熬夜的细节5.1 递归展开时界面卡死或闪屏现象点一个空白格程序卡住几秒才刷新或者整个窗口闪个不停。原因通常是每翻开一格就调一次Invalidate()256 格触发 256 次重绘。解决递归过程中只改数据递归结束后统一调一次Invalidate()。把RevealCell里的Invalidate()挪到鼠标消息处理函数的末尾。5.2 首点踩雷导致演示翻车现象程序刚启动第一次点击就炸。原因布雷在初始化时完成没排除首点。解决把布雷推迟到第一次左键点击用m_bFirstClick标志判断首点时传入安全坐标调InitBoard。这个改动很小但演示效果天差地别。5.3 数组越界导致随机崩溃现象点窗口边缘或快速连点程序偶尔崩溃。原因坐标反算行列后没做边界判断或者递归展开时邻格坐标越界。解决所有访问m_cell、m_state的地方下标前必须判断0 r ROWS 0 c COLS。递归函数开头统一判断鼠标消息里也判断一次双保险。5.4 插旗后左键还能翻开现象右键插了旗左键点同一格还是翻开了。原因RevealCell只判断了m_state ! 0但插旗状态是 2应该被挡住。检查判断条件是否写成了m_state[r][c] 1才返回。正确写法是if (m_state[r][c] ! 0) return;这样 1 和 2 都挡住。5.5 静态链接后仍报缺 DLL现象改成静态库使用 MFC换台机器还是提示缺mfc140u.dll。原因只改了 MFC 使用方式没改运行库或者没清理重新生成。解决确认运行库是/MT发布或/MTd调试清理解决方案后重新生成。另外检查是否引用了其他动态库比如某些第三方控件。6. 进阶技巧把扫雷大作业做出“可讲解”的加分项大作业评分不只看能不能跑还看你能不能讲清设计。我一般会加两个小功能成本低但答辩时很能说。第一个是计时器和剩余雷数显示放在状态栏或对话框顶部。MFC 里用SetTimer每秒更新一次KillTimer在游戏结束时停掉。剩余雷数就是MINES减去已插旗数每次插旗后刷新。// 在对话框初始化里启动计时 SetTimer(1, 1000, NULL); // 消息映射 ON_WM_TIMER处理函数里 void CMyMineDlg::OnTimer(UINT_PTR nIDEvent) { if (nIDEvent 1 !m_bGameOver) { m_nSeconds; CString s; s.Format(_T(时间%d 秒), m_nSeconds); m_staticTime.SetWindowText(s); // 静态文本控件 } CDialogEx::OnTimer(nIDEvent); }第二个加分项是把难度切换做成菜单或按钮初级 9×9/10 雷、中级 16×16/40 雷、高级 16×30/99 雷。切换时重置所有状态并重新初始化注意CELL_SIZE要按难度调整高级模式格子得小一点才放得下。这个功能能体现你对参数化的理解答辩时老师问“怎么扩展”你就有话说。验证程序是否真的正确别只靠手点。我习惯写一个临时的调试函数把m_cell和m_state打印到输出窗口对照雷区图检查周围雷数算得对不对。特别是角落和边缘格子8 邻域边界处理最容易错。打印出来看一眼比点半天靠谱。最后一个血泪经验答辩前一定在老师那台机器上试运行或者至少在一台没装 VS 的电脑上双击 exe。我见过太多人本地跑得好好的换台机器就缺 DLL 或者字符集乱码。静态链接加 Unicode这两条守住基本不会翻车。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询