VC6.0迷宫小游戏开发实战:递归回溯算法与MFC界面实现

发布时间:2026/10/4 1:37:53
VC6.0迷宫小游戏开发实战:递归回溯算法与MFC界面实现 简介一份基于VC6.0与MFC的迷宫小游戏完整工程面向想通过实际项目掌握C桌面程序开发的初学者也适合需要回顾MFC消息映射与控件用法的开发者。压缩包共65个文件、153KB其中17个bmp用于小地图/大地图及角色贴图17个h与16个cpp构成游戏逻辑和界面代码另有2个ico图标及工程配置文件结构清晰可直接打开编译。已有213人学习浏览。项目实现了随机迷宫生成深度优先搜索/Prim算法、小地图全局预览与大地图探索、方向键控制兔子移动并检测碰撞覆盖C类设计、MFC窗口框架、键盘事件处理、GDI绘图等关键技能。下载后对照源码与说明文件可逐段理解迷宫建模、角色移动和界面刷新的实现思路适合作为课程设计参考或MFC入门练手项目。1. 用 VC6.0 编写迷宫小游戏老编译器是入门游戏开发最近的那条路一个标题为“migong.rar_vc6.0编写游戏_vc6.0编小”的压缩包在不少论坛和网盘里躺了很多年打开后通常就是一套用 VC6.0 写的迷宫游戏源码。它不是冷门收藏而是无数课程设计、毕业设计和自学者的共同起点用 1998 年的编译器亲手完成迷宫生成、窗口绘制、键盘移动、胜利判定这一整条游戏开发闭环。VC6.0 虽然老但正因为没有现代引擎帮你包办一切窗口、画笔、坐标、消息循环都必须自己面对反而是理解游戏底层逻辑最直接的工具。这篇笔记把从生成算法到 MFC 界面、从避坑到验证的完整路径写透新手可以照着跑通熟手可以拿避坑清单自查。2. 迷宫生成算法选型为什么递归回溯比 Prim 更好讲、更好改2.1 两种主流算法的思路对比与选型理由迷宫生成算法分两类一类是“深度优先打通墙”以递归回溯为代表一类是“随机拆墙”以随机 Prim 为代表。递归回溯的思路是从起点出发每到一个格子就随机选一个还没访问过的邻居拆掉中间的墙把新格子压入栈如果当前格子四周都走过了就弹栈回溯到上一个岔路口。这个过程生成的迷宫通道长、岔路少天然只有一条从起点到任意点的唯一路径视觉上像洞穴体系很适合课程设计里“附带最短路径演示”的后续要求。随机 Prim 的路线完全不同维护一个候选墙的集合每次随机抽一堵墙如果墙的另一侧还没被访问就拆墙并把新格子加入集合。它的生成结果分支多、分布均匀更像树状森林。两种算法都能生成完美迷宫但我做这个题目时一定会选递归回溯理由有三个代码量三十多行逻辑闭环容易讲明白栈这个数据结构有明确的“推进—回溯”过程评委一听就懂生成出来的迷宫路径相对细长画在窗口里不会出现大块空场观感更接近“地道迷宫”。2.2 在 VC6.0 控制台程序里先跑通生成逻辑先别急着开 MFC 工程。迷宫生成的本质是二维数组操作和窗口、画笔没有任何关系。我一般先把算法写成控制台程序在漆黑的命令行里用“##”和空格把迷宫打印出来验证通过后再套界面。这一步能帮你省掉至少一个晚上查坐标换算的时间。下面这段代码在 VC6.0 里可以独立编译运行。// maze_gen.cpp : 在 VC6.0 控制台下验证递归回溯迷宫生成 #define _CRT_SECURE_NO_WARNINGS #include stdio.h #include stdlib.h #include windows.h const int ROWS 21; // 迷宫行数必须为奇数保证外墙完整 const int COLS 21; // 迷宫列数必须为奇数 const int WALL 1; // 墙 const int PATH 0; // 通路 int maze[ROWS][COLS]; typedef struct { int row, col; } Point; Point stack[ROWS * COLS]; // 用数组模拟栈容量按格子总数估算 int top -1; void Push(Point p) { stack[top] p; } Point Pop() { return stack[top--]; } // 判断一个格子能否被扩展为通路 int CanVisit(int row, int col) { return row 1 row ROWS - 1 col 1 col COLS - 1 maze[row][col] WALL; } void InitMaze() { int r, c; for (r 0; r ROWS; r) for (c 0; c COLS; c) maze[r][c] WALL; } void GenerateMaze() { int d, count, pick; InitMaze(); top -1; // 种子用系统运行时间避免每次都生成同一个迷宫 srand(GetTickCount()); Point start { 1, 1 }; maze[start.row][start.col] PATH; Push(start); while (top 0) { Point cur stack[top]; // 方向上、下、左、右 int dr[4] { -1, 1, 0, 0 }; int dc[4] { 0, 0, -1, 1 }; int candidate[4]; // 收集所有“隔一堵墙”的目标格子 count 0; for (d 0; d 4; d) { int nr cur.row dr[d] * 2; int nc cur.col dc[d] * 2; if (CanVisit(nr, nc)) candidate[count] d; } if (count 0) { pick candidate[rand() % count]; int midRow cur.row dr[pick]; int midCol cur.col dc[pick]; int nextRow cur.row dr[pick] * 2; int nextCol cur.col dc[pick] * 2; maze[midRow][midCol] PATH; // 拆掉中间的墙 maze[nextRow][nextCol] PATH; // 新格子设为通路 Point next { nextRow, nextCol }; Push(next); } else { Pop(); // 四周走不通回溯 } } } void PrintMaze() { int r, c; for (r 0; r ROWS; r) { for (c 0; c COLS; c) { if (r 1 c 1) printf(S ); // 起点 S else if (r ROWS - 2 c COLS - 2) printf(E ); // 终点 E else printf(%s, maze[r][c] WALL ? ## : ); } printf(\n); } } int main() { GenerateMaze(); PrintMaze(); return 0; }这段代码的逻辑核心是“隔二取一”的扩展方式。每个通路格子只检查row 2*方向和col 2*方向的位置中间那一格天然是墙打通后就成为两个通路之间的连接。这样生成的迷宫墙体和通道各占一个单元格宽高一致画到窗口里非常整齐。ROWS和COLS必须取奇数因为起点在坐标 (1,1)如果行数是偶数终点(ROWS-2, COLS-2)会落到边界外外墙就不完整了。参数说明stack数组长度取ROWS*COLS是上限估算DFS 回溯栈深度不会超过格子总数对 21×21 的迷宫绰绰有余。srand(GetTickCount())是让每次运行生成不同迷宫的关键GetTickCount()返回系统开机以来的毫秒数比time(NULL)的秒级精度更适合当随机种子。注意在 VC6.0 里编译时所有 for 循环变量我都提到了函数开头声明这是故意绕开 VC6.0 的 for 作用域坑后面避坑章会专门说。2.3 控制台版本的核心参数与调试输出在 VC6.0 里新建一个“Win32 Console Application”工程把代码粘贴进去CtrlF5 就能跑。命令行里会出现一张用##表示墙、空格表示通路的迷宫S在左上角附近E在右下。这是最原始的“黑匣子输出”但它能立刻暴露两类问题迷宫没完全连通存在大片封闭区域或者路径笔直穿过整张地图说明随机性不够。如果生成结果是大片空白或者大片墙先检查CanVisit的边界条件和midRow/midCol是否把墙打通到数组外。如果maze[midRow][midCol] PATH把边界格子也当通路打通了打印时就会看到外墙“漏风”。定位这种问题不需要调试器直接把maze数组按行列输出到文本文件用编辑器打开检查四个边是否全为 1 就能确定外墙完整性。从控制台一寸一寸验证过的算法搬到 MFC 后只剩坐标换算一件事。这也是我坚持先做控制台验证的原因把逻辑错误和界面错误分开处理你会更快锁定真正的问题。3. 把迷宫搬进 MFC 窗口从 AppWizard 到键盘交互的完整改造3.1 用 MFC AppWizard 创建单文档工程先关掉多余零件在 VC6.0 里通过 File → New → Projects → MFC AppWizard(exe) 新建工程应用类型选“单文档”这一步生成的是 SDI 框架。对迷宫这类单窗口小游戏SDI 比对话框合适CView 天然带重绘机制和消息循环后续画迷宫、响应键盘只需覆盖 CView 的几个虚函数不用自己造轮子。工程生成后打开资源编辑器你会看到一排工具栏按钮和状态栏。这些在迷宫游戏里用不到直接处理掉在 ResourceView 里展开 Toolbar 资源把多余按钮拖走状态栏可以保留后面用来显示步数和用时。主窗口标题在 MainFrame 的PreCreateWindow里改m_strTitle或者直接改资源文件字符串表里 IDR_MAINFRAME 的标题改成“迷宫小游戏 - VC6.0”让窗口栏和任务栏都显示项目名。这一步花不了两分钟但对“作品感”的提升很明显。资源编辑器还需要加一个“新迷宫”按钮。如果你不想动工具栏更省事的做法是给菜单加一项“游戏 → 新迷宫”把命令 ID 映射到视图类的NewGame()成员函数。菜单项在 VC6.0 的资源编辑器里是所见即所得双击生成消息映射具体代码放到 4.1 一起写。3.2 在 CView 里绘制迷宫坐标换算与墙体画法把迷宫数据从二维数组变成窗口里的方块核心是坐标换算。定义CELL_SIZE 20第r行第c列的格子在窗口中的左上角坐标就是(c * CELL_SIZE, r * CELL_SIZE)右下角是((c1) * CELL_SIZE, (r1) * CELL_SIZE)。行对应 Y 轴列对应 X 轴写反了就是迷宫上下颠倒。这是新手最容易翻车的地方我见过有人调了一晚上最后发现是Row和Col在绘制函数里互换。// CMazeView.cpp : 迷宫绘制逻辑 // 成员变量约定 // int m_maze[21][21]; // 0 通路1 墙 // int m_playerRow; // 玩家所在行 // int m_playerCol; // 玩家所在列 void CMazeView::OnDraw(CDC* pDC) { int r, c; const int CELL_SIZE 20; // 先画墙体逐格填充逻辑直观 for (r 0; r ROWS; r) { for (c 0; c COLS; c) { if (m_maze[r][c] WALL) { CBrush wallBrush(RGB(70, 80, 100)); // 深蓝灰色墙体 CBrush* oldBrush pDC-SelectObject(wallBrush); pDC-Rectangle(c * CELL_SIZE, r * CELL_SIZE, (c 1) * CELL_SIZE, (r 1) * CELL_SIZE); pDC-SelectObject(oldBrush); } } } // 画起点和终点标记 CBrush startBrush(RGB(60, 180, 80)); // 绿色起点 CBrush* oldStart pDC-SelectObject(startBrush); pDC-Rectangle(1 * CELL_SIZE, 1 * CELL_SIZE, 2 * CELL_SIZE, 2 * CELL_SIZE); pDC-SelectObject(oldStart); CBrush endBrush(RGB(200, 60, 60)); // 红色终点 CBrush* oldEnd pDC-SelectObject(endBrush); pDC-Rectangle((ROWS - 2) * CELL_SIZE, (COLS - 2) * CELL_SIZE, (ROWS - 1) * CELL_SIZE, (COLS - 1) * CELL_SIZE); pDC-SelectObject(oldEnd); // 最后画玩家橙色圆块保证不被墙体盖住 CBrush playerBrush(RGB(220, 120, 40)); CBrush* oldPlayer pDC-SelectObject(playerBrush); pDC-Ellipse(m_playerCol * CELL_SIZE 3, m_playerRow * CELL_SIZE 3, (m_playerCol 1) * CELL_SIZE - 3, (m_playerRow 1) * CELL_SIZE - 3); pDC-SelectObject(oldPlayer); }这里的绘制顺序是“先墙体、再起点终点、再玩家”。玩家为什么放最后因为玩家是一个动态元素每移动一步都要重绘如果先画玩家再画墙玩家就会被新画出来的墙体盖住看起来像“走进墙里消失了”。终点用红色标记起点用绿色这样迷宫生成后玩家一眼就能判断目标在哪。SelectObject后必须保存旧对象并在用完立刻恢复这是 GDI 编程的铁律。恢复后 MFC 的CBrush析构函数才能正确释放 GDI 句柄否则任务管理器里的 GDI 对象数会一路涨窗口越跑越卡避坑章 5.5 还会再提。InvalidateRect(NULL, FALSE)的第二个参数传FALSE表示不要擦除背景能显著减少重绘闪烁。3.3 键盘控制玩家移动与碰撞检测MFC 的 CView 默认把方向键当作“改变焦点”的用户界面消息不会下发给OnKeyDown。想用方向键控制游戏最稳妥的做法是重写PreTranslateMessage在消息被翻译成WM_CHAR之前拦截WM_KEYDOWN。这样做的另一个好处是Tab 键和 Enter 键不会意外抢走焦点。BOOL CMazeView::PreTranslateMessage(MSG* pMsg) { int key, nr, nc; if (pMsg-message WM_KEYDOWN m_gameState GAME_PLAYING) { key (int)pMsg-wParam; nr m_playerRow; nc m_playerCol; switch (key) { case VK_UP: nr--; break; case VK_DOWN: nr; break; case VK_LEFT: nc--; break; case VK_RIGHT: nc; break; default: return CView::PreTranslateMessage(pMsg); } // 防御式边界检查防止数组越界访问 if (nr 0 nr ROWS nc 0 nc COLS m_maze[nr][nc] PATH) { m_playerRow nr; m_playerCol nc; m_stepCount; InvalidateRect(NULL, FALSE); } return TRUE; // 消息已被处理不再往下传递 } return CView::PreTranslateMessage(pMsg); }先做边界检查再判断目标格是否为通路。虽然生成算法保证最外圈是墙玩家理论上来不到边界但防御式检查成本几乎为零还能防止未来改动算法后出现数组越界导致程序崩溃。m_stepCount只在成功移动时增加撞墙不算步数这是做计步器的基本原则。return TRUE非常关键。MFC 的消息传递顺序是PreTranslateMessage → TranslateMessage → DispatchMessage返回 FALSE 时这条WM_KEYDOWN会被当作普通消息继续分发可能触发焦点跳转或系统默认行为。返回 TRUE 等于告诉 MFC“这条消息我已经消化掉了你们别碰”。如果你发现方向键按下后迷宫没反应但窗口标题栏在闪多半就是这里返回了 FALSE。4. 让迷宫真正“可玩”步数计时、胜利判定与状态机管理4.1 起点终点初始化与胜利判定迷宫生成完之后起点固定在(1,1)终点固定在(ROWS-2, COLS-2)。因为递归回溯生成的是完美迷宫这两个格子之间必然有且仅有一条路径。把NewGame函数写好菜单和按钮都映射到它一劳永逸void CMazeView::NewGame() { GenerateMaze(); // 复用第 2 章的生成函数 m_playerRow 1; m_playerCol 1; m_gameState GAME_PLAYING; m_stepCount 0; m_startTick GetTickCount(); SetTimer(1, 1000, NULL); // 每秒刷新一次计时显示 InvalidateRect(NULL, FALSE); } void CMazeView::CheckWin() { CString msg; DWORD elapsed; if (m_playerRow m_exitRow m_playerCol m_exitCol) { m_gameState GAME_WIN; elapsed (GetTickCount() - m_startTick) / 1000; msg.Format(_T(恭喜通关\n步数%d\n用时%d 秒), m_stepCount, elapsed); MessageBox(msg, _T(迷宫小游戏), MB_OK | MB_ICONINFORMATION); } }m_exitRow和m_exitCol在GenerateMaze()结束后赋值为ROWS-2和COLS-2。CheckWin在 3.3 的移动成功分支里调用玩家踩到终点格就弹窗。这里用_T()宏包住所有字符串常量是为了绕开 VC6.0 的字符集坑避坑章 5.3 会详细解释。4.2 用一个枚举管理游戏状态而不是用一堆 bool移动逻辑写完后你可能会顺手加m_bStart、m_bWin、m_bMoving三个布尔变量。三个 bool 之间会出现非法组合比如“游戏还没开始但玩家已经赢了”这种状态会让代码到处都是特判。我习惯用一个枚举把游戏生命周期钉死enum GameState { GAME_READY 0, // 还没生成迷宫界面上只有空白 GAME_PLAYING, // 迷宫已生成玩家正在走 GAME_WIN // 已到达终点键盘不再响应 };在这套状态下3.3 的PreTranslateMessage里m_gameState GAME_PLAYING的判断就变成了状态保证而不是规则判断。你不需要再想“赢的时候能不能按方向键”枚举已经保证了只有 PLAYING 状态才处理移动。菜单“新迷宫”只需要把状态重新置为GAME_PLAYING所有键盘响应、计时器逻辑自动恢复。VC6.0 的枚举在 Debug 下类型检查比较弱不要依赖枚举默认值做运算。比如不要写m_gameState来切换状态枚举的递增在现代 C 里合法但在 VC6.0 的调试器里容易被当成整数处理带来不可预测的边界行为。显式赋值GAME_READY 0是双保险。4.3 计时显示SetTimer 与 GetTickCount 怎么选迷宫游戏的耗时有两个展示方案。方案一是在玩家每次移动时用GetTickCount()算差值只在移动瞬间刷新标题方案二是挂一个SetTimer(1, 1000, NULL)每秒主动刷新窗口标题。前者零额外开销但玩家站着不动时计时不会跳少了“作品感”。我推荐方案二void CMazeView::OnTimer(UINT nIDEvent) { DWORD elapsed; CString title; if (nIDEvent 1 m_gameState GAME_PLAYING) { elapsed (GetTickCount() - m_startTick) / 1000; title.Format(_T(迷宫小游戏 - 步数%d 用时%d 秒), m_stepCount, elapsed); GetParentFrame()-SetWindowText(title); } CView::OnTimer(nIDEvent); }窗口标题实时刷新步数和用时玩家不用低头看状态栏。这里GetParentFrame()拿的是主框架窗口句柄在 SDI 里就是顶层窗口如果你用的是对话框框架要改成SetDlgItemText或者SetWindowText加GetSafeHwnd()。两种框架的定时器逻辑完全一样区别只在怎么找到那个“显示文字的窗口”。一个必须处理的尾巴是OnDestroy。在CMazeView::OnDestroy里调用KillTimer(1)否则窗口关闭后 Timer 消息仍可能继续进入消息队列访问已经销毁的视图对象VC6.0 里这会造成“关闭程序时弹出一个无标题错误框”的经典翻车现场。5. VC6.0 编译迷宫源码的避坑手册5 条血泪经验5.1 for(int i…) 诡异报错C2065 与变量作用域陷阱现象代码里写for (int r 0; r ROWS; r)VC6.0 报错error C2065: r : undeclared identifier而且报错位置经常不在 for 那一行而是在循环结束后的第一次使用处。原因VC6.0 的编译器发布于 C 标准刚定稿的年代它把 for 循环中声明的变量当作整个函数作用域的变量而不是只属于循环体。如果你在同一函数里后面又写了int r就会报重定义反过来循环结束后使用r在某些情况下又能“碰巧”通过编译让你误以为变量仍然可用。行为介于标准 C 和旧 C 风格之间非常别扭。解决把循环变量全部提到函数开头声明整个函数复用同一个变量名。我在这篇文章的所有代码里已经这样做了你复制到 VC6.0 可以直接编译。如果你接手的是别人的源码看到for(int i...)报错不要犹豫直接改成前置声明。注意VC6.0 的 for 作用域坑不止导致编译错误还会让同一份代码在 Debug 与 Release 下表现不一致。全用前置变量是最省心的规避方式。5.2 每次运行迷宫长得一模一样srand 种子粒度问题现象迷宫生成函数没问题但不管怎么运行打印出来的迷宫完全相同。重启电脑后也还是同一张。原因习惯性写srand(time(NULL))时time(NULL)的精度是秒。你在 F5 调试后马上 CtrlF5 再跑一次两次时间戳落在同一秒内种子相同随机序列完全一致。即便不同秒VC6.0 的rand()是线性同余生成器相邻种子产生的前几个伪随机数往往差异很小第一个候选方向可能撞车。解决把种子改成毫秒级并掺入迷宫尺寸信息srand(GetTickCount() ^ (ROWS * 31 COLS));如果这样仍然稳定复现同形迷宫在生成前多调几次rand()打散初始状态比如写rand(); rand();。另外VC6.0 的rand()是全局共享序列如果你在迷宫生成前还调用了其他库函数触发随机数序列位置会变化这类“只要你一加功能迷宫就变”的玄学现象多半也跟种子序列没控制好有关。5.3 字符串常量编译不过char* 与 wchar_t 的纠缠现象MessageBox(通关, 游戏, MB_OK)编译报错提示类似cannot convert parameter 1 from const char [5] to const unsigned short *。原因MFC 工程的字符集设置被配置为 Unicode窗口类 API 被宏映射到宽字符版本比如MessageBoxW而你传进去的是窄字符串常量char*。VC6.0 里这种映射是隐式的但常量字符串不会自动转换于是编译器在参数类型上直接报错。解决不要在“多字节字符集”和“Unicode 字符集”之间反复横跳统一用_T()宏包裹所有字符串常量。_T在 ANSI 工程下展开为char[]在 Unicode 工程下展开为wchar_t[]一套代码两种工程都能编译。这是 VC6.0 时代开发 MFC 应用的基本功也是你从网上抄源码时最先要扫描替换的地方。5.4 F9 断点不生效调试信息与优化冲突现象在GenerateMaze()里打了断点运行后断点变成空心圆圈永远不进中断仿佛程序绕过了那行代码。原因VC6.0 的工程默认 Debug Info 配置不完整同时如果编译设置带了优化哪怕是 Debug 下的 O1编译器会重排指令源码行号和机器码对应不上调试器无法把断点映射到实际指令。解决进入 Project → Settings → C/C在 Category 下拉框里选 General把 Debug Info 设为 “Program Database for Edit and Continue (/ZI)”再把 Optimization 选为 Disable。Link 页面勾选 Generate Debug Info。改完这些后执行一次 Rebuild All断点就能正常命中。这套配置对迷宫这种几十行逻辑的小工程来说完全够用。5.5 窗口越走越闪GDI 对象狂涨现象迷宫正常显示但每移动一步窗口明显闪烁运行几分钟后整个程序重绘变慢任务管理器里进程的“GDI 对象”列数值涨到几百甚至上千。原因初学绘制时把CBrush、CPen直接声明在OnPaint里用完不恢复旧对象。MFC 的CBrush析构时会调用DeleteObject但前提是它当前没有被选进设备上下文。如果你连续SelectObject却没有保存并恢复旧对象旧的画刷句柄一直留在 DC 里析构时无法释放GDI 句柄就这样一点点泄漏。解决每次SelectObject都保存返回的旧指针用完立即SelectObject(oldBrush)恢复。迷宫规模不大逐格Rectangle时用一个成员画刷、一个成员画笔效果最好。如果窗口还是闪再加双缓冲在OnDraw里创建内存CDC和CBitmap先画到内存再一次性BitBlt到窗口。课程设计能把这个优化讲出来至少能加两分。6. 先验证再扩展把迷宫从作业做成能演示的小作品6.1 用文本矩阵做基准验证别只盯着窗口迷宫生成完最容易被忽略的是验证。窗口里看到的只是重绘后的像素数组搬运过程中坐标错位很难用肉眼发现。我习惯在生成后把maze数组逐行写到一个文本文件里FILE* fp fopen(maze_dump.txt, w); int r, c; for (r 0; r ROWS; r) { for (c 0; c COLS; c) fprintf(fp, %d, m_maze[r][c]); fprintf(fp, \n); } fclose(fp);打开这个文本矩阵检查三点外圈是否全为 1起点到终点是否只有一条 0 通路有没有出现 2×2 的 0 方块。出现 2×2 的 0 方块说明相邻格子被打通过度迷宫失去“墙厚一致”的观感生成算法里CanVisit没过关。这个文本矩阵是后续所有扩展的基准改一次算法就跑一次对比比在窗口里走迷宫判断快得多。6.2 三个低成本的扩展方向寻路、关卡、算法拆封装第一个扩展是自动寻路演示。在GAME_PLAYING状态下按某个键触发 BFS实时画出从玩家当前位置到终点的路径。BFS 用队列实现三四十行代码课程设计里这一招几乎必杀而且能复用迷宫生成时打的PATH/WALL标记。第二个扩展是关卡系统把ROWS、COLS从常量改成成员变量按关卡递增为 21、31、41同时调整CELL_SIZE让窗口保持合适大小计时器继续复用。第三个扩展是把迷宫生成、寻路算法封装成独立的MazeCore类完全隔离 MFC 依赖以后换 DirectX 或者控制台渲染只改一个接口。做这三个扩展不需要你重写任何核心算法只是在现有代码外面包一层。我当年做这个题目时直接拿网上源码改结果花了整晚调一个“看起来正常但终点永远不可达”的 Bug最后发现是导入别人代码时把行列尺寸换错了。后来我养成的习惯是不管多简单的算法先跑一个文本输出再谈画界面。这次写迷宫文本矩阵永远在窗口之前给出正确答案。希望这个顺序也能帮到正在和 VC6.0 搏斗的你。本文还有配套的精品资源点击获取