ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

6个C++控制台小游戏代码:Dev-C++可运行可复制练手项目

6个C++控制台小游戏代码:Dev-C++可运行可复制练手项目 玩C的人大概都有这么一段经历语法书翻了一本又一本指针、引用、虚函数背得滚瓜烂熟可真让你写点东西脑子里却一片空白。我当年也是这样直到有人甩给我一句话——别啃书了写几个小游戏一个星期顶你三个月。这话听着夸张但真上手之后我发现确实有道理游戏这个东西天生自带“反馈回路”你写的每一行逻辑都会立刻体现在屏幕上对错一眼就能看出来这种即时反馈对编程入门的人来说太重要了。这篇内容就是围绕“6个C游戏代码Dev-C都可以运行可复制”这个主题展开的我会把每个游戏的完整代码贴出来更重要的是把背后的思路、踩过的坑、以及为什么这么写讲透。如果你手上正好装了Dev-C或者小熊猫Dev-C这类衍生版本想找点能直接跑起来的练手项目那接下来的内容应该对你有用。难度从初学到中等依次递进零基础能看懂前几个有点基础的可以直接挑战后面的。1. 为什么选C写小游戏来练手1.1 C做控制台小游戏的天然优势很多人一提游戏开发就想到Unity、想到虚幻引擎觉得C写游戏是不是太重了。其实完全不是一回事。我们要写的是控制台小游戏也就是黑框框里用字符拼出来的那种。这类项目不依赖任何图形库不用配OpenGL不用装SDL一个编译器加一个标准库就能跑这正是C最舒服的使用场景。C本身就是从C语言发展来的它保留了C语言直接操作内存、贴近硬件的特性同时又多了类、模板、STL这些高级工具。写小游戏的时候你会自然而然地用到数组、循环、条件判断、函数封装、随机数、字符串处理这些最核心的语法点。这些东西你在语法书里看一百遍都不如自己写一遍猜数字游戏记得牢。比如随机数种子这个概念书上说“要用当前时间初始化种子否则每次运行结果一样”你可能看了没感觉。但当你写完猜数字游戏发现每次启动答案都是42你就永远记住srand((unsigned)time(0))这行了。另外一点C的控制台输出特别直观。cout一打字符就出现在屏幕上你不用去管渲染管线、不用管资源加载、不用管坐标变换注意力能100%放在逻辑设计上。这对初学者来说非常关键因为刚入门时最容易被打消积极性的就是“环境配置”和“上手门槛”。1.2 为什么推荐Dev-C而不是VS或者VSCode这个问题我被问过无数次。先说结论入门阶段Dev-C确实够用而且省心。Visual Studio功能强大但它动辄几个G的安装体积新建项目时一堆选项对新手来说容易劝退。VSCode本身只是个编辑器要跑C得自己配编译器、配c_cpp_properties.json、配tasks.json光是让“Hello World”跑起来就能折腾一下午。很多人搜“vscode配置c/c环境”搜到崩溃最后放弃了。Dev-C本质上是一个打包好的集成环境里面自带MinGW编译器装完就能用点击“编译运行”直接出结果。它的界面朴素但该有的都有语法高亮、编译错误提示、调试器虽然不太好用。对于写控制台小游戏这种纯逻辑项目它完全胜任。当然如果你后面要做大项目还是得换VS或者CLion但那是后话。注意Dev-C有多个版本老版Orwell Dev-C 5.11已经停止维护编译时默认标准可能比较老。如果代码里用到了C11的新特性报错记得在“工具-编译器选项”里勾选“编译时加入以下命令”填上-stdc11。1.3 这6个游戏的难度梯度设计选游戏不是随便挑的得按照语法点的覆盖面来排。我挑的这6个覆盖了从最基础的输入输出到稍微复杂的数组操作和状态管理序号游戏名称核心知识点难度建议用时1猜数字随机数、循环、条件判断入门20分钟2井字棋二维数组、函数封装、胜负判定入门偏上40分钟3打字练习字符串、计时、字符比较中等50分钟421点扑克枚举、随机发牌、状态管理中等60分钟5贪吃蛇简化版数组队列、方向控制、按键检测中等偏上90分钟6俄罗斯方块简化版矩阵操作、碰撞检测、消行逻辑进阶120分钟这个顺序的好处是知识点是叠加的。你写完猜数字已经会随机数和循环了写井字棋的时候二维数组的遍历和胜负判定又能复用循环到了后面的贪吃蛇和俄罗斯方块前面练出来的手感直接派上用场。整个过程像爬楼梯每一步都不算陡。2. 上手前的环境准备与避坑要点2.1 Dev-C的下载安装与编译器检查装Dev-C这件事本身没什么技术含量但有几个细节值得说。网上搜“dev-c下载”会出来一堆站点建议直接找官方发布页或者GitHub上的小熊猫Dev-C这是国内维护的分支版本更新更勤快对C11/14/17支持更好。下载下来的安装包一般几十兆一路下一步就行。装完之后先做一件事新建一个源文件写个Hello World编译运行看看能不能出结果。这一步能跑通说明编译器没问题。如果报错说找不到g那就是编译器路径没配好去“工具-编译器选项-目录”里检查一下bin目录是不是指向了安装目录下的MinGW64\bin。这里插一句很多人装完Dev-C后写代码时发现“编译”按钮是灰的或者点“编译运行”没反应。这种情况通常是没有保存文件。Dev-C和很多IDE不一样它要求你必须先把文件保存成.cpp后缀才允许编译不然它不知道该编译哪个文件。所以养成习惯新建源文件后先CtrlS保存起个有意义的名字比如guess_number.cpp。2.2 新建项目的正确姿势Dev-C里有两个入口容易搞混一个是“文件-新建-源代码”一个是“文件-新建-项目”。写单个小游戏的话用“源代码”就够了。项目模式适合多文件工程会在文件夹里生成.dev工程文件、.o中间文件反而显得乱。用源代码模式一个.cpp文件从头写到尾清清爽爽也方便你复制给别人。还有个小细节默认新建的源文件编码可能是GBK而有些代码里如果带了中文注释加特殊符号保存时可能乱码。稳妥起见在“工具-编辑器选项-显示”里看一下字体设置中文用等宽字体显示更整齐。不过说实话就算注释乱码了代码本身还是能跑的因为编译器只关心代码部分注释会被忽略。2.3 新手最容易踩的三个坑第一个坑是分号。C里语句结束必须加分号漏一个就会引发一连串莫名其妙的报错因为编译器会把下一行也当成上一句的一部分。我看到过不少新手一个分号找了半小时。第二个坑是中文标点。写代码时输入法忘了切换把;打成把()打成编译器报的错稀奇古怪。这个坑几乎人人踩过解决办法很简单写代码时锁定英文输入法。第三个坑是忘记清屏。控制台游戏如果每轮都往下打印屏幕很快就被刷满了。Windows下用system(cls)清屏记得包含cstdlib头文件。不过system调用会稍微慢一点对游戏帧率有影响的话后面贪吃蛇那节我会给出替代思路。3. 六个可直接运行的C游戏代码实现3.1 猜数字游戏——随机数与循环的入门课这是最经典的练手项目代码短逻辑清晰适合刚学完基本语法的人。#include iostream #include cstdlib #include ctime using namespace std; int main() { srand((unsigned)time(0)); // 用当前时间做随机种子 int target rand() % 100 1; // 生成1-100的随机数 int guess, count 0; cout 我已经想好了一个1到100之间的数字来猜猜看 endl; while (true) { cin guess; count; if (guess target) { cout 太大了再试试 endl; } else if (guess target) { cout 太小了再试试 endl; } else { cout 恭喜答对一共猜了 count 次 endl; break; } } return 0; }这段代码的核心就三个点随机数种子、取模运算限定范围、while(true)死循环加break退出。rand() % 100得到的是0到99加1变成1到100这个1的位置新手经常搞错写成rand() % 100 1是对的写成(rand() % 100) 1也一样但写成rand() % (100 1)就变成了0到100多了一个0。值得展开说的是为什么必须用时间做种子。计算机里的随机数是“伪随机”它其实是根据一个种子值算出来的一串数。如果你不调用srand默认种子是1那么每次程序启动算出来的序列都一模一样。用了srand((unsigned)time(0))种子就变成了当前时间戳每秒都在变所以每次运行结果都不同。提示如果不想用时间也可以让用户自己输入一个种子值做成“同样的数字生成同样的题目”的效果这在做测试的时候挺方便。这个游戏可以立刻加点花样比如限制猜的次数超过10次就失败或者把范围改成1到1000难度立马上去。改动很小但能让你体会到“修参数就能改玩法”的乐趣。3.2 井字棋——二维数组与胜负判定井字棋是练二维数组的绝佳项目。棋盘是3乘3的正好对应一个char board[3][3]。#include iostream using namespace std; char board[3][3]; int currentPlayer 1; void initBoard() { for (int i 0; i 3; i) for (int j 0; j 3; j) board[i][j] ; } void printBoard() { cout 1 2 3 endl; for (int i 0; i 3; i) { cout i 1 ; for (int j 0; j 3; j) { cout board[i][j]; if (j 2) cout | ; } cout endl; if (i 2) cout --------- endl; } } char checkWin() { // 检查横竖斜八条线 for (int i 0; i 3; i) { if (board[i][0] ! board[i][0] board[i][1] board[i][1] board[i][2]) return board[i][0]; if (board[0][i] ! board[0][i] board[1][i] board[1][i] board[2][i]) return board[0][i]; } if (board[0][0] ! board[0][0] board[1][1] board[1][1] board[2][2]) return board[0][0]; if (board[0][2] ! board[0][2] board[1][1] board[1][1] board[2][0]) return board[0][2]; return ; } int main() { initBoard(); int moves 0; while (true) { printBoard(); cout 玩家 currentPlayer 请落子行 列; int r, c; cin r c; r--; c--; // 用户输入从1开始转成0开始的下标 if (r 0 || r 2 || c 0 || c 2 || board[r][c] ! ) { cout 这个位置不能下重来 endl; continue; } board[r][c] (currentPlayer 1) ? X : O; moves; char winner checkWin(); if (winner ! ) { printBoard(); cout 玩家 currentPlayer 获胜 endl; break; } if (moves 9) { printBoard(); cout 平局 endl; break; } currentPlayer 3 - currentPlayer; // 1和2互相切换 } return 0; }这里有个小技巧值得单独说currentPlayer 3 - currentPlayer。1变22变1一行搞定比写if-else优雅。这种“用算术代替分支”的写法在游戏逻辑里很常见比如后面贪吃蛇方向反转也能用到类似思路。胜负判定是这段代码的核心。横竖斜一共8条线都要检查很多人写的时候只检查了横竖忘了对角线结果游戏出现“斜着连成三个却判不出胜负”的bug。我建议把8条线的检查都写全宁可啰嗦也别漏。落子时做合法性检查也很重要坐标必须在0到2之间且格子必须是空的。如果用户输入了已经占用的位置直接continue跳过本次循环重新提示。这里用continue比用else嵌套清爽得多。提示想让它变成人机对战可以在玩家落子后加一段电脑逻辑。最简单的做法是遍历所有空位找出能立刻获胜的位置没有就随便下一个。再进一步就是经典的“上下左右看”拦截玩家的连子。这个改造我强烈推荐做一遍写完之后你对游戏AI的基本思路就入门了。3.3 打字练习游戏——字符串处理与计时这个游戏的玩法是屏幕上随机显示一个单词你在规定时间内把它敲出来敲对了加分敲错了扣分。核心用到了字符串比较和计时函数。#include iostream #include string #include cstdlib #include ctime #include ctime using namespace std; int main() { string words[] {apple, banana, cherry, dragon, engine, forest, garden, hunter, island, jacket}; int total 10; srand((unsigned)time(0)); int score 0; clock_t startAll clock(); for (int i 0; i total; i) { string target words[rand() % 10]; cout 第 i 1 个词 target endl; string input; clock_t t1 clock(); cin input; clock_t t2 clock(); double used double(t2 - t1) / CLOCKS_PER_SEC; if (input target) { score 10; cout 正确用时 used 秒 endl; } else { score - 5; cout 错了正确答案是 target endl; } cout 当前得分 score endl endl; } double totalTime double(clock() - startAll) / CLOCKS_PER_SEC; cout 全部完成总用时 totalTime 秒最终得分 score endl; return 0; }这里的重点是clock()函数。它返回的是程序运行以来的CPU时钟数单位是CLOCKS_PER_SEC分之一秒所以(t2 - t1) / CLOCKS_PER_SEC就是秒数。要注意它统计的是CPU时间而不是真实的墙上时间如果程序在等待用户输入这段时间不会被计入。对打字游戏来说这其实是好事——你思考的时间不算真正敲键盘的时间才算。字符串比较用就行C的string类重载了运算符直接比内容是否相同。如果用的是C风格的char[]就得用strcmp麻烦得多。这也是我一直推荐大家用std::string而不是字符数组的原因之一。提示words[rand() % 10]这里数组有10个元素取模10正好。如果你改成20个单词记得把10改成20否则永远取不到后面10个。这个bug很难发现因为程序不报错只是有些单词从来不出现。想让这个游戏更有意思可以加个倒计时限制比如每个词只有5秒钟。实现方法是在读取输入前记录时间读完后判断是否超时。不过cin是阻塞的用户不输入程序就一直等所以严格的超时得用多线程或者非阻塞输入那就不适合在Dev-C这种入门环境里折腾了。初学者先做简单的版本就好。3.4 21点扑克——枚举与状态管理21点是练习枚举类型和状态管理的好项目。规则简单你和电脑各拿牌谁的点数接近21又不爆谁赢。#include iostream #include cstdlib #include ctime using namespace std; enum Suit { HEART, DIAMOND, CLUB, SPADE }; int drawCard() { int card rand() % 13 1; // 1到13 if (card 10) card 10; // J Q K 都算10 return card; } int main() { srand((unsigned)time(0)); char again y; while (again y) { int playerTotal drawCard() drawCard(); int dealerTotal drawCard() drawCard(); cout 你的初始点数是 playerTotal endl; cout 电脑明牌点数 dealerTotal / 2 endl; char choice; while (playerTotal 21) { cout 要牌(h) 停牌(s); cin choice; if (choice h) { int c drawCard(); playerTotal c; cout 你抽到 c 当前点数 playerTotal endl; } else { break; } } if (playerTotal 21) { cout 爆牌你输了 endl; } else { while (dealerTotal 17) { dealerTotal drawCard(); } cout 电脑最终点数 dealerTotal endl; if (dealerTotal 21 || playerTotal dealerTotal) { cout 你赢了 endl; } else if (playerTotal dealerTotal) { cout 平局 endl; } else { cout 你输了 endl; } } cout 再来一局(y/n); cin again; } return 0; }enum枚举在这里让代码可读性好很多。虽然这个简化版没真正用到花色只用了点数但保留Suit的定义可以让你扩展时有个起点。真正的21点里A可以当1也可以当11这里为了简化只当1处理这是刻意做的取舍——初学者别一上来就追求完整规则先把主流程跑通细节慢慢补。电脑的操作逻辑用了经典的“庄家17点停牌”规则点数小于17就必须继续要牌。这个规则简单却合理能保证电脑不会太蠢。你会发现程序里while (dealerTotal 17)一行就把这个策略实现了这就是逻辑清晰的威力。提示这个游戏有个隐藏bug如果玩家一直要牌最后爆了电脑其实不用再抽牌了。代码里是先判断玩家爆没爆再决定要不要让电脑操作这个顺序很重要。如果写反了即使玩家爆牌电脑还会“表演”一番抽牌看起来很奇怪。3.5 贪吃蛇简化版——数组队列与方向控制到了这个阶段可以挑战稍微复杂一点的了。控制台版贪吃蛇需要用到conio.h里的非阻塞键盘检测这在Windows下的Dev-C是可以直接用的。#include iostream #include conio.h #include windows.h #include cstdlib #include ctime using namespace std; const int W 20, H 15; int snakeX[100], snakeY[100]; int len 3; int dir 0; // 0上 1右 2下 3左 int foodX, foodY; int score 0; void gotoxy(int x, int y) { COORD pos {(SHORT)x, (SHORT)y}; SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), pos); } void placeFood() { foodX rand() % W; foodY rand() % H; } void init() { srand((unsigned)time(0)); for (int i 0; i len; i) { snakeX[i] W / 2 - i; snakeY[i] H / 2; } placeFood(); } void draw() { gotoxy(0, 0); for (int y 0; y H; y) { for (int x 0; x W; x) { bool isSnake false; for (int i 0; i len; i) if (snakeX[i] x snakeY[i] y) isSnake true; if (x foodX y foodY) cout *; else if (isSnake) cout o; else cout ; } cout endl; } cout 得分 score endl; } bool hitSelf(int nx, int ny) { for (int i 0; i len; i) if (snakeX[i] nx snakeY[i] ny) return true; return false; } int main() { init(); while (true) { if (_kbhit()) { int key _getch(); if (key 224) { key _getch(); if (key 72 dir ! 2) dir 0; if (key 77 dir ! 3) dir 1; if (key 80 dir ! 0) dir 2; if (key 75 dir ! 1) dir 3; } } int nx snakeX[0], ny snakeY[0]; if (dir 0) ny--; if (dir 1) nx; if (dir 2) ny; if (dir 3) nx--; if (nx 0 || nx W || ny 0 || ny H || hitSelf(nx, ny)) { cout 游戏结束最终得分 score endl; break; } if (nx foodX ny foodY) { len; score 10; placeFood(); } else { // 身体后移 for (int i len - 1; i 0; i--) { snakeX[i] snakeX[i - 1]; snakeY[i] snakeY[i - 1]; } } snakeX[0] nx; snakeY[0] ny; draw(); Sleep(150); } return 0; }这段代码里有几个关键点值得讲。蛇身的存储用的是两个并行数组snakeX[]和snakeY[]下标0是蛇头。每次移动时如果没吃到食物就把数组整体往后挪一位然后更新蛇头如果吃到了食物长度加一身体不用挪相当于尾巴长了一节。这是最朴素的实现方式好处是不用动态内存缺点是移动一次就要循环搬一次数组效率不算高但对20乘15的棋盘完全够用。方向控制这里有个经典陷阱不能允许蛇反向掉头否则下一秒就会咬到自己。代码里if (key 72 dir ! 2) dir 0;就是这个意思——当前方向是下2时按上键无效。这个判断如果用反了游戏立刻变成“自杀模拟器”。**_kbhit和_getch**是Windows专属函数。_kbhit()检测有没有按键有的话返回非零_getch()读取一个字符且不显示。方向键比较特殊它会发送两个字节第一个是224第二个才是真正的方向码72上、77右、80下、75左。忘了处理224这个前缀的话按方向键会没反应。提示Sleep(150)控制游戏速度单位是毫秒。数字越小蛇跑得越快调到50就比较刺激了。如果你发现蛇“瞬移”检查一下Sleep是不是漏了。gotoxy用的是SetConsoleCursorPosition比system(cls)更高效因为它只移动光标而不清屏闪烁会少很多。这个技巧在做所有控制台动画时都用得上。3.6 俄罗斯方块简化版——矩阵操作与碰撞检测俄罗斯方块是这6个里最复杂的但拆开看核心无非是消行和碰撞两件事。这里给一个可运行的骨架。#include iostream #include conio.h #include windows.h #include cstdlib #include ctime using namespace std; const int W 10, H 18; int board[H][W]; // 7种方块用4x4矩阵表示 int shapes[7][4][4] { {{0,0,0,0},{1,1,1,1},{0,0,0,0},{0,0,0,0}}, // I {{0,0,0,0},{0,1,1,0},{0,1,1,0},{0,0,0,0}}, // O {{0,0,0,0},{0,1,0,0},{1,1,1,0},{0,0,0,0}}, // T {{0,0,0,0},{1,0,0,0},{1,1,1,0},{0,0,0,0}}, // L {{0,0,0,0},{0,0,1,0},{1,1,1,0},{0,0,0,0}}, // J {{0,0,0,0},{0,1,1,0},{1,1,0,0},{0,0,0,0}}, // S {{0,0,0,0},{1,1,0,0},{0,1,1,0},{0,0,0,0}} // Z }; int curShape, curX, curY; bool checkCollision(int shape[4][4], int px, int py) { for (int i 0; i 4; i) for (int j 0; j 4; j) if (shape[i][j]) { int nx px j, ny py i; if (nx 0 || nx W || ny H) return true; if (ny 0 board[ny][nx]) return true; } return false; } void lockPiece() { for (int i 0; i 4; i) for (int j 0; j 4; j) if (shapes[curShape][i][j] curY i 0) board[curY i][curX j] 1; } void clearLines() { for (int i H - 1; i 0; i--) { bool full true; for (int j 0; j W; j) if (!board[i][j]) { full false; break; } if (full) { for (int k i; k 0; k--) for (int j 0; j W; j) board[k][j] board[k - 1][j]; for (int j 0; j W; j) board[0][j] 0; i; // 重新检查这一行现在已经是上一行内容 } } } void newPiece() { curShape rand() % 7; curX W / 2 - 2; curY 0; } void gotoxy(int x, int y) { COORD pos {(SHORT)x, (SHORT)y}; SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), pos); } void draw() { gotoxy(0, 0); for (int i 0; i H; i) { cout |; for (int j 0; j W; j) { bool filled board[i][j]; for (int r 0; r 4; r) for (int c 0; c 4; c) if (shapes[curShape][r][c] curY r i curX c j) filled true; cout (filled ? # : ); } cout | endl; } } int main() { srand((unsigned)time(0)); for (int i 0; i H; i) for (int j 0; j W; j) board[i][j] 0; newPiece(); int tick 0; while (true) { if (_kbhit()) { int key _getch(); if (key 224) { key _getch(); if (key 75 !checkCollision(shapes[curShape], curX - 1, curY)) curX--; if (key 77 !checkCollision(shapes[curShape], curX 1, curY)) curX; if (key 80 !checkCollision(shapes[curShape], curX, curY 1)) curY; } else if (key ) { while (!checkCollision(shapes[curShape], curX, curY 1)) curY; } } if (tick % 5 0) { if (!checkCollision(shapes[curShape], curX, curY 1)) { curY; } else { lockPiece(); clearLines(); newPiece(); if (checkCollision(shapes[curShape], curX, curY)) { draw(); cout 游戏结束 endl; break; } } } draw(); Sleep(60); tick; } return 0; }碰撞检测是整个游戏的心脏。checkCollision函数做两件事检查方块是否出界检查方块是否撞到已经固定的格子。任何一个成立就返回true表示不能往那个方向移动。有了这个函数左右移动、下移、快速落底的逻辑都能复用同一套判断。消行的逻辑是从下往上扫描发现满行就把上面所有行整体下移一格同时把最上面一行清零。这里的i配合循环的i--是为了让当前行重新检查一次因为下移之后原来这一行的内容变了。这个小细节如果写漏一次消两行的时候就会出bug。旋转在这个版本里我省略了因为矩阵旋转写起来会让代码变长不少。旋转的本质是把4x4矩阵顺时针转90度思路是newMatrix[i][j] oldMatrix[3 - j][i]。你可以自己试着补上加一个上方向键触发旋转配合碰撞检测判断能不能转。注意方块的初始位置curY 0如果方块第一行是空的比如I型方块的前两行都是0那么实际显示会在靠下的位置这是正常的。但游戏结束时判断checkCollision(curShape, curX, curY)要准确否则会误判。4. 常见问题与排查技巧实录4.1 编译报错速查表新手遇到的报错大部分都能归到几类我整理了一张表看到报错先对号入座报错信息关键词常见原因解决办法expected ; before上一行漏了分号检查报错行的上一行xxx was not declared变量没定义或拼错检查变量名注意大小写stray \ in program用了中文标点切换到英文输入法no matching function for call函数参数类型不对检查调用时传的参数类型undefined reference to函数只声明没定义或拼写不一致检查函数定义cannot open output file程序还在运行文件被占用关掉之前的运行窗口再编译其中最容易让人抓狂的是第一个。因为编译器报错的行号往往在真正出错行的下一行你要养成“往上找一行”的习惯。4.2 运行时异常与逻辑bug编译通过了不代表没问题游戏跑起来的行为可能和你想的完全不同。常见的运行时问题有这几类数组越界是最危险的因为它不一定立刻崩溃。比如贪吃蛇里snakeX[100]如果你的蛇超过了100节就会写到数组外面去破坏别的变量。表现可能是“游戏突然变得很奇怪”但不会报错。解决办法是把数组开大一点同时在长度增加时加个上限判断。随机数不随机前面说过是因为忘了初始化种子。每次都出一样的结果八成是rand()没配srand。死循环通常是while条件写错了或者循环里忘了改变量。比如写猜数字时如果cin失败用户输入了字母cin会进入错误状态guess保持不变循环就转个没完。健壮的写法是在cin后加if (cin.fail()) { cin.clear(); cin.ignore(10000, \n); }不过初学者可以先不管这个。画面闪烁控制台游戏用system(cls)清屏时特别明显。解决办法就是前面提的gotoxy配合局部刷新或者用双缓冲——先在内存里画好一帧再一次性输出。4.3 代码可复制性相关的隐藏问题标题里说“可复制”这里面其实有个坑。从网页上复制代码经常会带上全角空格或者特殊字符粘到Dev-C里编译就报错。我的建议是复制之后先全选用“格式-去除行尾空格”之类的功能清理一下或者干脆重新敲一遍关键部分。另一个问题是换行符。Windows、Linux、Mac的换行符不一样从某些地方复制来的代码可能带着不兼容的换行Dev-C显示可能正常但编译异常。这种情况用记事本打开再另存为一下就好。还有代码里的中文注释乱码。如果源文件保存成了UTF-8带BOM有些老版本Dev-C显示会出问题。稳妥的做法是把源文件保存为ANSIGBK编码中文注释就不会乱码了。提示如果你把代码发给别人最保险的是直接发.cpp文件而不是复制粘贴文本。这样编码、换行都是原样的。5. 从抄代码到改代码的进阶路径5.1 给游戏加功能的三个思路抄代码只是第一步真正长本事的是改代码。我给你三个方向第一是改参数。猜数字把范围从100改成1000贪吃蛇把速度调快俄罗斯方块的棋盘加宽。改参数最简单但能让你迅速理解哪些数字控制着什么。第二是加功能。猜数字加个“显示历史猜测”井字棋加个人机对战打字游戏加个排行榜用数组存最高分。加功能会逼着你去理解原有代码的结构知道在哪里插新代码。第三是换实现。比如把贪吃蛇的数组实现换成deque把井字棋的胜负判定写成查表法。同一种功能用不同方式实现是加深理解的最佳途径。5.2 从控制台走向图形界面的时机控制台玩溜了之后很多人会想搞图形界面。这里给个建议别急着上OpenGL或SDL。可以先用Windows自带的GDI也就是windows.h里的绘图函数画个窗口、画几个方块难度适中。等你能用GDI画出会动的贪吃蛇再去学SDL或者更高级的引擎会顺畅很多。需要提醒的是图形库的配置本身就是个门槛。Dev-C配SDL需要手动加链接库容易出错。如果这一步卡住了可以用Visual Studio来做图形项目它的包管理器省事很多。工具是为目的服务的没必要死磕某一个IDE。我个人实际操作的体会是把这6个游戏从头到尾写一遍、改一遍比看十本教程都管用。你会在过程中反复遇到数组越界、死循环、方向判断错误这些典型问题解决它们的过程就是成长。写完贪吃蛇之后再去碰指针和内存管理你会发现那些抽象概念突然变得具体了——因为你知道蛇的每一节数据在内存里是怎么排布的。最后一个建议把改好的代码上传到自己的代码仓库哪怕只有几十行坚持下去一年后回头看那个文件夹就是你最好的学习记录。
返回列表