恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

VC++ MFC迷宫游戏开发:随机地图生成与DFS栈回溯实现

  • 首页
  • 资讯中心
  • /
  • VC++ MFC迷宫游戏开发:随机地图生成与DFS栈回溯实现

相关资讯

MoE时代AI算力体系重构:分层树形架构实战指南 2026/10/1 17:53:47
从离职到上线,我用 TaoToken 统一 Key 打通出海产品 AI 能力 2026/10/1 17:53:47
从MapReduce原理到实战:核心机制、Shuffle与排序应用详解 2026/10/1 17:53:47

最新资讯

Python3数据类型转换避坑指南:字符串拼接、Decimal精度与pandas批量转换实战
WorkBuddy接入自定义MCP连接器:SSE长连接实战与排查指南
[光学原理与应用-651]:低频电磁波走电路介质,超高频电磁波走光学介质,所谓光电差异,只是频率跨越了多个数量级、换了一套传输介质,底层物理体系完全统一。
杭州前端工程师如何度过职业发展的瓶颈期?
Agent记忆系统实战:从存储选型到混合检索与安全防护
Java向上转型与向下转型的本质与实战避坑指南

今日推荐

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)

本周热门

从像素到笔画:srt-whiteboard-animation骨架笔迹追踪实现(Zhang-Suen细化+8邻接追踪)
网站建设的英语怎么说?别只背单词,看完这套安全完整流程才敢上线
新手入门看这篇:建设网站加盟避坑指南与SEO实操

本月精选

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)

VC++ MFC迷宫游戏开发:随机地图生成与DFS栈回溯实现

发布时间:2026/10/1 17:53:47
VC++ MFC迷宫游戏开发:随机地图生成与DFS栈回溯实现 简介一套VC迷宫游戏源码支持随机生成迷宫地图玩家通过键盘方向键控制红色方块在迷宫中移动直至出口。资源面向初学C与Windows界面编程的读者非常适合作为理解简单游戏循环与用户交互的练手项目。资源以rar压缩包发布共11个文件主要由5个头文件、1个cpp源文件和dsp/dsw工程文件构成另含rc、aps、hm等Windows资源与辅助文件压缩包仅14KB整体结构清晰精简便于快速导入使用目前已有487人浏览学习说明其具备一定参考价值。对于自学开发者阅读源码可以重点学习随机迷宫地图的生成方式、方向键消息处理与碰撞判断、基于GDI的窗口绘制流程还可借助resource.h与rc文件理解Windows资源定义方式。工程文件齐全可在VC环境中直接打开编译便于逐行调试也方便在此基础上改进寻路算法或增加计时、步数统计等新功能。1. VC迷宫游戏的起点随机地图才是这个项目的灵魂很多刚接触VC的开发者想找个“有点图形界面、又不太复杂”的练手项目迷宫游戏几乎是默认选项。但难点从来不是画一个迷宫而是“地图怎么来”手写二维数组只能玩固定关卡玩两遍就腻了。真正让项目立住的是随机生成迷宫地图——每次开新局地图不一样玩家的探索感才会保留。这个标题落地的目标就是把随机生成算法、MFC窗口和玩家移动串成一套完整可运行的程序。不用QT不用Unity就用VC和Win32/MFC那套传统工具链适合C初学者、计算机专业做课程设计的人以及想在Windows桌面端找回手感的老开发。2. 技术选型VC做迷宫先定界面框架和生成算法2.1 界面框架选MFC还是Win32VC本身是工具链真正决定开发体验的是界面框架。纯Win32 API写迷宫不是不行但消息循环、窗口类注册、绘图代码全要手写光一个窗口骨架就要几十行。MFC把窗口封装成CView/CDialog绘图重写OnDraw即可更贴近“把精力花在迷宫本身”。我的习惯是Win32适合做几十行的工具窗口迷宫这种需要地图绘制、键盘响应、状态管理的项目MFC的文档视图结构更顺手。一个容易被忽略的点VC安装时要勾选“适用于桌面的VC运行库”和MFC组件。只装核心编译器新建项目时根本没有MFC模板网上很多“VS2017 vc安装”的报错根因都在这里。项目创建时选“MFC应用程序”应用程序类型选“单个文档”项目风格选“经典菜单”生成的框架足够干净。2.2 生成算法选型四选一按风格决定随机迷宫生成主流算法有四类深度优先搜索DFS、随机Prim、递归分割Recursive Division、Kruskal并查集。它们的核心差别是“随机性的分布方式”。算法生成风格实现难度是否需要额外空间DFS单一长路径通道窄回头路多低栈随机Prim分支均匀更像自然树状中集合/堆递归分割横平竖直房间感强中递归Kruskal通道均匀混乱无明显主干高并查集如果是课程设计DFS最稳——代码短逻辑直观生成速度快和“ds堆栈-迷宫求解”正好呼应生成时用栈回溯求解时也用栈探路一套数据结构吃两遍。如果想要地图好看选递归分割它生成的迷宫墙更整有“房间”感。我一般默认DFS后面第四章会给出完整代码。2.3 迷宫的数据结构二维数组表示墙和路迷宫本质是一张网格图。我用一个二维整型数组存储每个格子的状态0表示路1表示墙。生成时先把所有格子设为墙再挖出通路。还有一种常见做法是“单元格墙边”即把迷宫看成N×N个格子每个格子记录四面墙是否存在这种方式适合做寻路演算但绘图时要把墙转化成像素坐标多一步换算。我选用二维数组方案原因是绘图循环直接按行列映射像素矩形代码最直白。一个典型定义如下// maze_data.h #ifndef MAZE_DATA_H #define MAZE_DATA_H #define MAZE_MAX_W 41 #define MAZE_MAX_H 41 class MazeData { public: MazeData(int w, int h); ~MazeData(); bool isValid(int x, int y) const; // 判断坐标是否越界 bool isWall(int x, int y) const; // 是否为墙 void setCell(int x, int y, int val); // 设置格子值 int getWidth() const { return width_; } int getHeight() const { return height_; } private: int width_; int height_; int cells_[MAZE_MAX_W][MAZE_MAX_H]; // 0路, 1墙 }; #endif宽高限制为奇数个格子因为迷宫的外墙和通道需要对称宽度为奇数时路径规划更规整。isValid在生成和移动时都要调用后面避坑章节会讲它为什么是重灾区。3. 随机生成迷宫地图从DS堆栈回溯到随机化参数3.1 核心思路先把所有格子当墙再用栈挖路DFS生成迷宫的步骤并不神秘从起点格子开始把它设为路然后看它上下左右四个方向“隔一格的邻居”是否还是墙——如果是就打通当前格子与邻居之间的墙把邻居设为路再以邻居为当前点继续如果四个方向都走过了就从栈里退回上一个格子继续。这个过程叫“回溯”也是“ds堆栈-迷宫求解”里栈结构在生成侧的应用。用栈而不是递归的好处是避免深层递归导致栈溢出。迷宫越大递归调用越深41×41的迷宫最坏情况下递归深度接近800层Windows默认线程栈1MB虽然一般够用但换成显式栈后你把迷宫扩展到201×201也不会心里发慌。3.2 完整生成代码随机洗牌加显式栈// maze_gen.cpp #include vector #include stack #include random #include maze_data.h void generateByDFS(MazeData* maze, unsigned int seed) { std::mt19937 rng(seed); // 方向数组上下左右 const int dx[4] { 0, 0, -1, 1 }; const int dy[4] { -1, 1, 0, 0 }; int w maze-getWidth(); int h maze-getHeight(); // 先把所有格子设为墙 for (int y 0; y h; y) { for (int x 0; x w; x) { maze-setCell(x, y, 1); } } struct Pos { int x, y; }; std::stackPos st; Pos start { 1, 1 }; maze-setCell(start.x, start.y, 0); st.push(start); while (!st.empty()) { Pos cur st.top(); // 收集当前格子四周“隔一格”的可挖邻居 std::vectorint dirs; for (int i 0; i 4; i) { int nx cur.x dx[i] * 2; int ny cur.y dy[i] * 2; if (maze-isValid(nx, ny) maze-isWall(nx, ny)) { dirs.push_back(i); } } if (!dirs.empty()) { // 随机打乱方向保证每次生成的迷宫不一样 std::shuffle(dirs.begin(), dirs.end(), rng); int dir dirs.front(); int nx cur.x dx[dir] * 2; int ny cur.y dy[dir] * 2; // 打通当前格与邻居之间的墙 int wallX cur.x dx[dir]; int wallY cur.y dy[dir]; maze-setCell(wallX, wallY, 0); maze-setCell(nx, ny, 0); st.push({ nx, ny }); } else { // 四周没有可扩展的邻居回溯 st.pop(); } } }代码逻辑按三步走初始化全墙 → 从(1,1)开始挖路 → 无路可走时弹出栈顶。关键参数有两个一个是dx/dy方向数组里的*2步长这是隔一格挖路的精髓另一个是seed种子值它直接决定整张地图的分布形态。std::shuffle把四个方向的顺序打乱随机性全部来源于rng而rng又由外部传入的seed决定——这就是“随机生成唯一值”落到代码里的方式同一种子永远生成同一张图不同种子大概率不同图。setCell(wallX, wallY, 0)那一步最容易漏只把邻居格子设为路却忘记打通中间的墙生成出来的是悬空的点而不是通路。排查时看生成的迷宫图上有没有大量“格子是路但连不通”的孤岛基本就是这一步写漏了。3.3 地图尺寸与种子参数的取值建议迷宫宽高最好控制在21~61之间。小于15迷宫一下就走到头大于101MFC的绘制刷新和玩家视觉都会变吃力课程设计演示效果反而变差。宽高必须是奇数否则起点(1,1)和边界墙体对齐会出现半格墙画出来锯齿感明显。种子参数我建议直接取系统时间unsigned int seed static_castunsigned int(time(nullptr));如果嫌单次时间种子不够随机可以叠加一个递增计数器seed static_castunsigned int(time(nullptr)) gameCount_。这样即使用户在同一个秒级时间戳连续重建地图也能保证前后两张不一样。迷宫生成后需要做一个简单的连通性验证从起点(1,1)再做一次DFS统计访问到的格子数如果等于所有路的数量说明没有孤岛。这个验证不复杂但能挡住绝大多数算法边界错误。4. 把迷宫跑起来MFC地图绘制与玩家移动4.1 OnDraw绘图把数组格子映射成像素矩形MFC单文档工程的绘制核心是视图类的OnDraw你在里面用CDC把内存中的MazeData画到窗口上。我的做法是先算出每个格子对应的像素边长然后逐行扫描二维数组遇到墙就填深色遇到路就填浅色。// MazeView.cpp void CMazeView::OnDraw(CDC* pDC) { if (!maze_) return; CRect clientRect; GetClientRect(clientRect); int mazeW maze_-getWidth(); int mazeH maze_-getHeight(); int cellSize min(clientRect.Width() / mazeW, clientRect.Height() / mazeH); if (cellSize 2) cellSize 2; // 先把所有格子涂成背景色避免残留 pDC-FillSolidRect(clientRect, RGB(240, 240, 240)); for (int y 0; y mazeH; y) { for (int x 0; x mazeW; x) { COLORREF color maze_-isWall(x, y) ? RGB(50, 50, 50) : RGB(255, 255, 255); CRect cellRect( x * cellSize, y * cellSize, (x 1) * cellSize, (y 1) * cellSize ); pDC-FillSolidRect(cellRect, color); } } // 单独画出入口和出口 pDC-FillSolidRect( CRect(1 * cellSize, 1 * cellSize, 2 * cellSize, 2 * cellSize), RGB(120, 200, 120) ); }cellSize min(winW/mazeW, winH/mazeH)保证整张地图完整显示不会因窗口宽高比和迷宫宽高比不一致而截断。每格单独FillSolidRect在迷宫尺寸小于61×61时性能没问题但如果把迷宫放大到101以上整帧重绘会开始掉帧。解决办法是双缓冲先把所有格子画到内存CBitmap上再一次BitBlt到窗口DC。双缓冲的代码在MFC里这样搭// 双缓冲绘制 void CMazeView::OnDraw(CDC* pDC) { CRect rc; GetClientRect(rc); CDC memDC; memDC.CreateCompatibleDC(pDC); CBitmap bmp; bmp.CreateCompatibleBitmap(pDC, rc.Width(), rc.Height()); CBitmap* old memDC.SelectObject(bmp); // 所有绘制操作改用 memDC // 结束时一次性贴回窗口 pDC-BitBlt(0, 0, rc.Width(), rc.Height(), memDC, 0, 0, SRCCOPY); memDC.SelectObject(old); }注意SelectObject要保存旧位图并在结束后恢复否则CBitmap析构时内存DC还挂着它程序退出时容易触发GDI对象泄漏。这个坑在VC的MFC项目里常年出现。4.2 玩家移动键盘响应与一步一动移动逻辑放在视图类的OnKeyDown里。先定义玩家当前位置收到方向键后计算目标格如果目标格是路且不越界就更新玩家坐标并触发重绘。// MazeView.cpp void CMazeView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) { int dx 0, dy 0; switch (nChar) { case VK_UP: dy -1; break; case VK_DOWN: dy 1; break; case VK_LEFT: dx -1; break; case VK_RIGHT: dx 1; break; default: CView::OnKeyDown(nChar, nRepCnt, nFlags); return; } int nx playerX_ dx; int ny playerY_ dy; if (maze_ maze_-isValid(nx, ny) !maze_-isWall(nx, ny)) { playerX_ nx; playerY_ ny; steps_; Invalidate(FALSE); // 只刷新客户区不擦背景 } CView::OnKeyDown(nChar, nRepCnt, nFlags); }Invalidate(FALSE)的FALSE参数表示背景不擦除配合双缓冲能明显减少闪烁。steps_是玩家累计步数把它显示在窗口标题栏算是课程设计里最容易加分的小细节。走到出口的判定逻辑在移动后检查(playerX_ exitX_ playerY_ exitY_)弹出一个MessageBox显示总步数并询问是否重新生成基本就是一个完整的可玩迷宫游戏了。5. 避坑记录随机生成迷宫最常见的五个坑5.1 递归生成导致栈溢出程序直接崩溃现象迷宫尺寸调到101×101时生成函数一执行就崩报错位置在std::stack或函数递归调用处。原因有些写法用递归调用自己来挖路迷宫尺寸大、路径长递归深度可达网格格数默认线程栈被耗尽。解决把递归调用改成显式栈也就是第三章代码里的std::stackPos写法。显式栈分配在堆上撑得住更大的地图。如果坚持用递归则在项目属性中调大线程栈链接器 → 系统 → 栈保留大小设为4MB以上。但我的建议是显式栈代码只多几行可扩展性完全不同。5.2 连续两次生成地图一模一样现象点击“重新生成”按钮发现新地图和旧地图完全一样关掉程序重开才变化。原因种子用了time(nullptr)而两次生成的间隔不到1秒时间戳相同随机序列相同地图自然相同。解决把种子改成时间加计数器混合。更彻底的办法是用std::random_device产生首个种子再用mt19937做伪随机展开。注意random_device在MinGW环境下可能退化为伪随机MSVC下则可靠得多VC用户直接用即可。5.3 画面闪烁严重移动和刷新时像闪光灯现象按方向键移动玩家时窗口大片区域闪烁。原因OnDraw里直接往pDC画系统每次先擦背景再重绘形成了明显的闪烁感。手动调用Invalidate(TRUE)会加重这个问题。解决双缓冲是唯一根治方案第四章已经有完整代码框架。本质上是在内存里画完一帧再一次性把整帧位图拷贝到窗口。可以在OnEraseBkgnd里直接返回TRUE禁止背景擦除配合双缓冲后闪烁基本消失。5.4 迷宫边缘出现“半格路”入口不在墙上现象生成的迷宫最外圈路越界有些格子画在了窗口外入口的绿色块不见了一部分。原因起点写在(1,1)但迷宫宽高不是奇数或者绘制循环里没有用isValid保护最后一列/行把数组外的数据当成了路。解决生成前强制把宽高修正为奇数if (w % 2 0) w--;绘制循环里也统一走isValid判断。这个检查在setCell内部做也行但最稳妥的是生成函数和绘图函数两处都做防御。5.5 运行库缺失拷到别的电脑上提示“找不到VCRUNTIME140.dll”现象在开发机运行好好的把Debug版exe复制到同学电脑上双击报缺少运行库。原因Debug版本依赖调试运行库目标机器没装Visual Studio。解决切到Release x86配置重新编译并在项目设置中把“C/C → 代码生成 → 运行库”改为“多线程(/MT)”这样把运行时静态链入exe单文件即可运行。这也是课程设计交作业前必做的一步不然演示现场翻车概率极高。6. 进阶技巧用种子存档、自动求解与在线地图一个被很多人忽略的优势是因为整张迷宫由种子完全决定你不需要保存迷宫地图文件只需要保存seed和玩家坐标就能恢复任意一局游戏。存档格式用一行文本就够了// 存档核心逻辑 CString SaveContent; SaveContent.Format(L%d %d %d %d, seed_, playerX_, playerY_, steps_); WritePrivateProfileString(LMazeGame, LSaveData, SaveContent, L.\\save.ini);读取时用GetPrivateProfileString读回四个数值重新调用generateByDFS(seed_)再把玩家坐标直接恢复整局游戏就回来了。这比存储一张几百格的数组地图要简洁得多也让学生项目看起来有“存档系统”的完整感。自动求解方面你在生成迷宫时用的std::stack反过来就是求解器从玩家位置出发做DFS直到出口路径记录在另一个栈里最后把路径上的格子用高亮颜色绘出。想做得更聪明可以把“启发式搜索”的思路引进来估价函数f(n)g(n)h(n)h(n)取曼哈顿距离用优先队列代替普通栈。这样能演示出A*和DFS在搜索范围上的差异项目汇报时是个不错的加分点。如果你还想把迷宫推到更远一步可以输出PNG图片用CImage::Save把内存DC里的迷宫位图保存为图片这样一张地图既是游戏关卡也能当题图素材。至于“vc访问http的服务端api接口”这类目的一般是把每天生成的种子同步到服务器做每日挑战地图让所有玩家玩同一张图并上传排行榜步数这属于网络编程的下一步等迷宫本身跑顺了再接入不迟。我经手过的迷宫项目教训是先把种子和绘图这两条主链跑通再谈扩展功能。种子不唯一后面存档和排行榜全是空中楼阁绘图闪烁不解决玩家体验直接归零。按本文顺序从生成到绘制再到存档每一步都留了可验证的出口希望帮到你。本文还有配套的精品资源点击获取

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号