
DevC 和 C 小游戏听起来像是大学时代最熟悉的几样东西。我读书那会儿机房电脑清一色蓝底白字的 DevC 4.9.9.2一节课的任务就是把书上的控制台程序敲出来看它在黑框里跑出几行文字。后来我慢慢发现光跑数学题和输出星星太没劲了真正能让人把语法记住的是把这些黑框程序做成能玩的小游戏。这篇文章就把我积攒的一套 DevC 能直接编译运行的 C 小游戏合集拆开讲一遍包含完整的设计思路、关键代码、调试经验和踩坑记录适合刚学完 C 基础语法、想通过项目练手的人也适合期末课程设计不知道做什么的同学。1. 项目思路与选型解析1.1 为什么是小游戏不是管理系统很多初学 C 的人第一次做项目选择的是“图书管理系统”或“学生成绩管理系统”。这类项目确实覆盖了结构体、数组、文件读写但说实话菜单选项一多大部分人写着写着就变成堆代码缺少一种即时反馈带来的兴奋感。小游戏不一样。做出来的东西能玩、能互动界面会跳、数字会变每次运行结果都不同。对一个新手来说“让电脑猜出我心里想的数字”这种交互性带来的满足感是打印几十行成绩单比不上的。游戏项目天然要求你处理循环、分支、输入输出、状态转换这些恰恰是 C 入门时必须啃掉的核心语法练一遍比看十遍视频都管用。1.2 为什么绑定 DevC选择 DevC不是因为它多先进而是因为它省事。体量小安装包几十 MB解压即用不需要配环境变量也不需要安装 Visual Studio 那种动辄几个 G 的开发套件。对机房电脑、学校电脑、还在用老笔记本的学生来说这是最现实的选择。更重要的是DevC 内置的是 MinGW 编译器对 C 标准支持足够带 graphics.h 和 windows.h 支持控制台游戏里需要的延时、光标定位、按键捕获都能用。下面所有游戏的代码我都在 DevC 5.11 和 5.6.3 的 32 位环境下重新编译测试过确保教材级代码可以直接跑。1.3 合集中的游戏清单与难度梯度整个合集按难度分成三条线方便不同基础的人选择游戏名称核心知识点代码量适合阶段猜数字随机数、循环、分支约 60 行入门第一章后井字棋二维数组、逻辑判断约 150 行学完数组后贪吃蛇结构体、链表思想、按键事件约 220 行学完指针之前扫雷二维数组、递归、状态标记约 300 行进阶综合训练迷宫生成深度优先、回溯、打印技巧约 180 行学了函数递归后这些游戏的共同点是不依赖第三方库全部用 C 标准库加 Windows 控制台 API 实现复制进 DevC 新建的源文件就能编译。下一节我先说环境配置因为这一步有不少人卡住。2. 环境准备与常见配置2.1 安装之后首先改什么装好 DevC 后第一件事不是写代码而是确认编译器设置。打开菜单栏的“工具 → 编译选项”在“编译器”标签页里确认编译器类型是 TDM-GCC 4.9.2然后在“代码生成/优化”里把语言标准设置为-stdc11。如果不改这一步有些现代写法比如auto、基于范围的循环会报错新手很容易被这种报错劝退。另外如果电脑是 Win10 或 Win11建议右键 DevC 图标在兼容性里勾选“以兼容模式运行Windows 7”并把“替代高 DPI 缩放行为”设置为“应用程序”。这一步能避免代码编辑区字体发虚和菜单显示不全很影响体验。2.2 中文乱码问题的根治方法搜过 DevC 中文乱码的人都知道这是个高频问题。根源在于 DevC 编辑器默认用 GBK 编码保存源文件而 Windows 控制台默认代码页是 936也对应 GBK理论上不会乱但一旦复制粘贴过程中把编码搞成 UTF-8控制台就不认识了。我推荐的处理方式有两个根治优先级从高到低第一源码文件统一用 GBK 编码。在 DevC 里点击“文件 → 另存为”编码格式选“ANSI”。每次新建文件就在弹出的对话框里检查编码别让它默认存成 UTF-8。第二在代码开头加一行强制转换控制台编码的指令核心是调用 Windows 的 API#include windows.h // 在 main 函数第一行加上 SetConsoleOutputCP(GBK);有些教程让你用SetConsoleOutputCP(65001)切到 UTF-8但如果你的源码是 GBK 保存的这会越切越乱。记住一个原则源码用什么编码控制台就切到什么编码两者不匹配才是乱码的根源。提示如果用了windows.h请在 DevC 的“工程 → 工程属性 → 参数”里把编译器链接选项加上-mwindows某些环境下不链接会出现两个窗口或者控制台闪烁问题。不过这里会牺牲一个黑窗口的显示效果做控制台游戏一般用不到反而可以不勾选。2.3 编译报错 msvcp140.dll 之类的问题热搜词里经常出现microsoft visual c redistributable的报错这其实不是 DevC 本身的问题而是系统缺少运行时库。虽然 DevC 用 MinGW 编译器但一些插件和老版本程序会依赖微软的 VC 运行库。解决方案就是去微软官网下载vcredist_x86.exe和vcredist_x64.exe分别安装一遍。印象中 DevC 的调试器组件就要用这个运行库不装的话按 F8 调试时会弹错误框。3. 合集中的核心游戏实现3.1 猜数字吹响集合号的第一声猜数字是众多小游戏里最简单的一个核心思路是计算机生成一个 1 到 100 之间的随机数玩家循环输入猜测程序给出“大了”或“小了”的提示直到猜中。这里最容易被新手忽略的是随机种子。很多人直接用rand() % 100 1结果每次重新运行游戏随机数都不变因为 rand 默认以固定值作为种子。正确做法是在程序开始用当前时间设置种子#include iostream #include cstdlib #include ctime int main() { srand((unsigned)time(NULL)); int target rand() % 100 1; int guess 0; int count 0; while (true) { std::cout 请输入你猜的数1-100; std::cin guess; count; if (guess target) { std::cout 大了 std::endl; } else if (guess target) { std::cout 小了 std::endl; } else { std::cout 恭喜你用了 count 次猜对了。 std::endl; break; } } return 0; }time(NULL)返回当前秒数把它转成 unsigned 传给 srand种子每秒钟都不一样生成的数列也就不同。如果玩家猜得太快同一秒内启动两次还是会一样对游戏影响不大。如果想更细腻一点可以用std::chrono纳秒级时间戳做种子但新手阶段没必要。这个小项目能顺带练到输入合法性判断。可以加一个判断if (std::cin.fail())表示输入了非数字这时候要清空缓冲区重新输入否则程序会死循环。这是个很好玩的小坑很多人在控制台游戏里都被 scanf 和 cin 的缓冲区坑过。3.2 贪吃蛇控制台游戏入门的经典考验贪吃蛇是控制台游戏里最典型的练手项目因为它的代码量不大但覆盖了地图坐标管理、蛇身的数据结构、按键输入捕获、游戏结束判定这些几乎是一个完整游戏的所有关键模块。我用的是结构体加数组的方式实现蛇身#include iostream #include conio.h #include windows.h const int W 20; const int H 15; int map[H][W]; struct Point { int x, y; }; Point snake[400]; // 蛇身最多 400 格 int len 3; int dir 1; // 0上 1右 2下 3左 int fx, fy; // 食物坐标 // 光标定位函数来自 windows.h void gotoxy(int x, int y) { COORD c; c.X x; c.Y y; SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), c); } void draw() { system(cls); for (int i 0; i W 2; i) std::cout #; std::cout std::endl; for (int i 0; i H; i) { std::cout #; for (int j 0; j W; j) { if (map[i][j] 1) std::cout O; else if (map[i][j] 2) std::cout *; else std::cout ; } std::cout # std::endl; } for (int i 0; i W 2; i) std::cout #; }蛇身的移动思想是把旧蛇尾从 map 中清除然后把整个蛇数组往前挪一位新的蛇头根据方向设定坐标并在地图上标记。这不涉及链表用数组就能实现理解起来比链表直观得多。按键捕获上用conio.h的_kbhit()做非阻塞检测没有按键时游戏按原方向继续跑有按键时改变方向。这里有个关键细节不能直接把方向改成用户按的键要防止玩家按“上”时直接把方向从“下”改成“上”这样蛇会直接回头撞上自己。所以需要加一个反转检查if (key w dir ! 2) dir 0; if (key s dir ! 0) dir 2; if (key a dir ! 1) dir 3; if (key d dir ! 3) dir 1;游戏循环里用Sleep(100)控制速度每次循环代表一帧。初学者可以试着改 Sleep 的数值数值越小蛇跑得越快难度越大。我实测过Sleep(50) 时蛇的速度已经比较刺激新手会操作不过来所以建议默认 100。贪吃蛇最容易出的 bug 是“食物突然消失”和“蛇越走越长但质量糟糕”。前者通常是食物生成到了蛇身上解决办法是生成后检查一遍蛇数组如果冲突就重新生成后者是因为 e 数组更新顺序不对要先判断“这一帧吃没吃到食物”决定 len 是否加 1再移动蛇身顺序反了会导致蛇身少一节或多一节整个游戏就乱了。3.3 扫雷二维数组与递归的黄金搭档扫雷是合集里最能提升代码水平的项目它的核心难度在两个地方如何优雅地显示未知区域如何递归展开空白区域我先展示一个干净的二维数组初始化方式#include iostream #include cstdlib #include ctime const int ROWS 9; const int COLS 9; int mine[ROWS][COLS]; // -1表示地雷其他数字表示周围地雷数量 int show[ROWS][COLS]; // 0 未翻开1 翻开2 标旗 void init() { memset(mine, 0, sizeof(mine)); memset(show, 0, sizeof(show)); int cnt 10; // 10颗雷 while (cnt--) { int x rand() % ROWS; int y rand() % COLS; if (mine[x][y] -1) { cnt; continue; } mine[x][y] -1; } // 计算每个格子周围雷数 for (int i 0; i ROWS; i) { for (int j 0; j COLS; j) { if (mine[i][j] -1) continue; int cnt 0; for (int di -1; di 1; di) for (int dj -1; dj 1; dj) { int ni i di; int nj j dj; if (ni 0 ni ROWS nj 0 nj COLS mine[ni][nj] -1) cnt; } mine[i][j] cnt; } } }这里我特别想强调下标合法性检查。刚写扫雷的时候我常常忘了判断ni和nj是否越界结果数组下标访问到负值或超过数组大小轻则计算结果错误重则直接内存越界程序崩溃。以后你在任何项目里处理二维坐标的时候都要把边界检查当成习惯。递归展开是扫雷的点睛之笔。当玩家翻开一个周围没有雷的格子时应该自动展开一整片区域直到遇到数字格。实现核心是一个递归函数void reveal(int x, int y) { if (x 0 || x ROWS || y 0 || y COLS) return; if (show[x][y] ! 0) return; if (mine[x][y] -1) return; show[x][y] 1; if (mine[x][y] 0) return; // 数字格停止展开 // 周围没有雷继续展开八个邻居 for (int di -1; di 1; di) for (int dj -1; dj 1; dj) { if (di 0 dj 0) continue; reveal(x di, y dj); } }很多人第一次写这个递归会栈溢出原因是没有先判断当前格子是否已经翻开。如果忘了if (show[x][y] ! 0) return;函数会在两个相邻的空格之间无限互相调用最终把栈踩爆。我在代码注释里特意标注了这三种 return 顺序这是扫雷游戏里最容易出 bug 的地方也是最值得反复打磨的地方。我记得初版扫雷整个界面只有文字坐标没有图形框玩起来很不直观。后来加了简单的边框绘制用-和|画网格。虽然简陋但是对于控制台程序来说已经足够清晰。如果想要图形化界面很多人会想到用graphics.h配合 EasyX但 DevC 默认不自带这一头文件安装和配置都要额外折腾。合集里这些游戏保持“零额外依赖”的原则拿到就能跑才是最高优先级。3.4 迷宫生成与自动寻路秀肌肉的进阶玩法迷宫生成这个项目比较适合已经把前面几个项目做完、想挑战一下的人。它用到的是一个经典的深度优先搜索配合回溯算法。思路并不复杂把迷宫看成一个二维格点图每个格点初始值为 1墙然后从起点出发随机选择一个相邻的、距离两格的点如果那个点还在边界内且是墙就打通中间的那一格同时把新点加入路径继续递归。如果四面都不通路就回溯到上一个格点。这种算法生成的迷宫天然保证任意两点之间都有一条通道。核心代码框架如下#include iostream #include cstdlib #include ctime #include vector const int W 21; // 必须是奇数便于打通通道 const int H 21; int maze[H][W]; void generate(int x, int y) { maze[x][y] 0; // 随机方向打乱保证生成的迷宫不同 int dirs[4][2] {{0,2},{0,-2},{2,0},{-2,0}}; for (int i 3; i 0; i--) { std::swap(dirs[i], dirs[rand() % (i 1)]); } for (int i 0; i 4; i) { int nx x dirs[i][0]; int ny y dirs[i][1]; if (nx 0 nx H ny 0 ny W maze[nx][ny] 1) { maze[x dirs[i][0] / 2][y dirs[i][1] / 2] 0; // 打通中间 generate(nx, ny); } } }这个方向数组dirs是祖传技巧用/2来定位两个格点之间的墙的位置非常巧妙。第一次看可能有点懵你把坐标写在本子上画一画就明白了。迷宫生成之后还可以加一个自动寻路功能用队列做广度优先搜索把最短路线的轨迹打出来。如果还想再酷一点可以录一段“DFS 回溯过程演示”每秒刷一格效果相当带感。这个项目做出来的迷宫是文本界面的#和空格直接复制到博客里也能展示。4. 常见问题与调试技巧实录4.1 运行闪退和无响应的排查思路新手最常见的抱怨是写好的游戏一运行窗口一晃就没了什么都没看到。这通常不是程序写错而是控制台程序执行完后自动关闭。解决方法有很多最朴素的是在 main 函数 return 0 之前加一行std::cin.get();或者在程序开头加std::system(pause);不过system(pause)在部分环境下会提示pause 不是内部或外部命令而且调用系统命令本身就有点暴力我更推荐用std::cin.get()它是纯标准库兼容性最好。但要注意如果前面用 cin 输入过数字回车还留在缓冲区里直接 get() 会立刻读到回车然后跳过等待。这时需要先用std::cin.ignore()清掉残留字符再调用 get()。4.2 DevC 调试器的正确打开方式不少人的 DevC 只是用来写代码从没用过调试器。其实 DevC 自带 GDB 调试器虽然界面简陋但功能不缺。要调试先把编译器优化关掉。在“编译选项”里把-O2或-O3去掉改成-O0否则调试时变量值可能被优化得看不出来。然后按 F8 在行首设置断点F5 运行到断点F7 单步进入F8 单步跳过。右侧的面板可以监视变量值也可以手动添加表达式。我在调试贪吃蛇时经常用的技巧是在移动函数里加临时的输出把蛇头的坐标、方向、得分打印出来跑几帧观察数据变化比单纯靠眼睛盯黑窗格有效得多。调试完再把临时输出删掉就行。这个“日志插桩”的习惯在后来的项目里帮我节省了无数时间。4.3 代码风格与变量命名的习惯养成做小游戏项目除了练语法更重要的是养成写代码的好习惯。我自己的代码规范很简单变量名用英文单词不用拼音缩写比如用foodX而不是spX全局常量用大写比如MAX_SNAKE_LEN函数内部变量用camelCase函数名用下划线或大写开头都行只要全项目统一。还有一个容易被忽略的点控制台游戏里大量使用system(cls)刷屏运行时间长了会有屏闪用了一次就回不去了。改进的办法是用gotoxy定位到窗口左上角重绘那些需要变化的格子而不是整个屏幕重新输出一次。扫雷和贪吃蛇到了后期我都会改成局部重绘这一下运行效果立刻有了质的提升。5. 从复制到原创下一步怎么走这里分享一个我自己带人的方法先抄再改最后扔。“抄”不是让你复制粘贴就完事。我的要求是拿到代码后先自己敲一遍每敲到一行想一想这行在做什么。敲完后运行起来然后光看代码讲一遍游戏过程哪里讲不出来就算没理解重新回去研究。“改”是给写好的游戏加功能这一步动作幅度要大。比如给猜数字加一个计时器、给贪吃蛇加分值和吃到食物时加速、给扫雷加一个排行榜。加功能过程中你会主动把原来写好但看不懂的代码翻出来研究这时候学到的东西比看十遍教程都牢固。“扔”是要求你遮住原代码从零开始自己写一个新游戏只用刚才学到的知识点。能这样做出来的东西才是真正长在你身上的能力。6. 写在最后的几句大实话这套合集我前前后后带着几届新手跑过能坚持做完的人对循环和数组的运用都扎实了很多。贪吃蛇看着简单但把它改出“长度超过 20 个格子后每吃一个食物就加速”的效果比刷一晚上力扣有意思也更接近真实的游戏开发逻辑。如果你卡在某个环节先别急着问“这段代码哪里错了”试试把代码拆成几个小部分每部分单独跑一遍。比如贪吃蛇先只画蛇不加移动再手动改蛇头坐标看能不能动起来最后再加按键和食物。拆小分组之后很多你以为是玄学的 bug其实都是数组越界、方向反转检查漏了这些“很不玄学”的原因。这些游戏也能往很多方向扩展C 的算法功底练扎实之后你可以考虑把扫雷的界面搬到 EasyX 图形库上或者给贪吃蛇加上声音和计分板。我一直觉得编程入门最好的状态是“玩着学”DevC 的控制台虽然简陋但它能承载的创意一点都不少。