【C++】控制台贪吃蛇小游戏(Windows API)完整代码与详解

📅 发布时间:2026/7/11 16:41:06 👁️ 浏览次数:
【C++】控制台贪吃蛇小游戏(Windows API)完整代码与详解
一、前言贪吃蛇是经典的益智游戏也是很多编程初学者的练手项目。本文将用C在Windows控制台下实现一个简单的贪吃蛇涉及键盘控制、动态绘制、碰撞检测等知识点。代码使用了Windows APIwindows.h来控制光标位置、隐藏光标以及conio.h进行非阻塞键盘输入。适合有一定C基础、想了解控制台游戏开发的读者。二、运行环境操作系统Windows因为使用了Windows API和conio.h编译器Visual Studio、MinGW等支持Windows的C编译器字符编码建议使用UTF-8或GBK避免控制台中文乱码三、整体设计游戏的基本逻辑绘制固定大小的地图边框。初始化蛇长度3水平放置。随机生成食物且不生成在蛇身上。通过键盘WASD或方向键控制蛇的移动方向不能反向。蛇按固定速度自动前进吃到食物后身体增长并生成新食物。若蛇头撞到墙壁或自己的身体游戏结束。四、数据结构定义1. 坐标结构 Pos用于表示地图上的一个点x为列y为行。cpp struct Pos { int x; int y; bool operator(const Pos other) const { return x other.x y other.y; } };2. 方块类型 BlockType地图上的每个格子可以是空的或食物。cpp enum BlockType { EMPTY 0, FOOD 1, };3. 地图 Map存储每个格子的类型以及当前是否有食物用于快速判断是否需要生成新食物。cpp struct Map { BlockType data[H][W]; bool hasFood; };4. 蛇 Snake用数组存储蛇身各节的坐标蛇头在下标0当前方向、长度、上次移动时间和移动间隔毫秒。cpp struct Snake { Pos snake[H * W]; int snakeDir; // 0上 1右 2下 3左 int snakeLength; int lastMoveTime; int moveFrequency; };五、核心函数解析1. 控制台辅助函数hideCursor()通过Windows API隐藏控制台光标避免闪烁。drawUnit(Pos p, const char unit[])将光标移动到指定位置并输出字符如蛇身■、食物●。2. 初始化initSnake()设置蛇的初始坐标、方向、长度、移动速度。initMap()清空地图设置无食物。3. 绘制drawMap()绘制地图边框用制表符画出┌─┐等中间区域全部打印空格。注意蛇和食物不在此函数绘制而是单独用drawUnit绘制。drawSnake()遍历蛇身调用drawUnit绘制■。4. 键盘输入与方向控制checkChangeDir()使用_kbhit()检测按键_getch()读取键值支持WASD和方向键并防止蛇直接掉头不能从向上变成向下等。5. 移动逻辑moveSnake()将蛇身从尾部到头部依次向前移动一节然后根据方向更新蛇头坐标。doMove()先擦除原蛇尾画空格再移动蛇然后判断是否撞墙或撞身最后检查是否吃到食物吃到则把原蛇尾追加到尾部实现增长。checkSnakeMove()基于时间控制移动速度通过GetTickCount()获取当前毫秒数与上次移动时间比较并调用方向检查和doMove。6. 食物生成checkFoodGenerate()如果地图没有食物则随机生成一个坐标确保不落在蛇身上然后在地图数组标记为食物并用drawUnit绘制●。7. 游戏主循环cpp while (1) { if (!checkSnakeMove(snk, map)) break; // 死亡则退出循环 checkFoodGenerate(snk, map); // 生成食物若无 // 注意这里没有 Sleep因为 checkSnakeMove 已经通过时间差控制移动频率 }死亡后显示Game Over和分数可由长度计算等待玩家选择重新开始或退出。六、代码完整版附注释cpp #include windows.h #include iostream #include conio.h using namespace std; const int W 60; // 地图宽度 const int H 27; // 地图高度 // 隐藏光标 void hideCursor() { HANDLE hOutput GetStdHandle(STD_OUTPUT_HANDLE); CONSOLE_CURSOR_INFO curInfo { 1, FALSE }; SetConsoleCursorInfo(hOutput, curInfo); } // 方向向量上、右、下、左 const int dir[4][2] { {-1,0}, {0,1}, {1,0}, {0,-1} }; enum BlockType { EMPTY 0, FOOD 1 }; struct Map { BlockType data[H][W]; bool hasFood; }; struct Pos { int x, y; bool operator(const Pos other) const { return x other.x y other.y; } }; struct Snake { Pos snake[H * W]; int snakeDir; // 0上 1右 2下 3左 int snakeLength; int lastMoveTime; int moveFrequency; // 移动间隔毫秒 }; void initSnake(Snake* snk) { snk-snakeLength 3; snk-snakeDir 1; snk-snake[0] { W / 2, H / 2 }; snk-snake[1] { W / 2 - 1, H / 2 }; snk-snake[2] { W / 2 - 2, H / 2 }; snk-lastMoveTime 0; snk-moveFrequency 150; } void initMap(Map* map) { for (int y 0; y H; y) for (int x 0; x W; x) map-data[y][x] EMPTY; map-hasFood false; } // 在指定位置输出字符 void drawUnit(Pos p, const char unit[]) { HANDLE hOutput GetStdHandle(STD_OUTPUT_HANDLE); COORD coord { (SHORT)(p.x 1), (SHORT)(p.y 1) }; // 1 让边框不占游戏区 SetConsoleCursorPosition(hOutput, coord); cout unit; } void drawMap(Map* map) { system(cls); // 上边框 cout ┌; for (int i 0; i W; i) cout ─; cout ┐ endl; // 中间区域 for (int y 0; y H; y) { cout │; for (int x 0; x W; x) cout ; cout │ endl; } // 下边框 cout └; for (int i 0; i W; i) cout ─; cout ┘ endl; } void drawSnake(Snake* snk) { for (int i 0; i snk-snakeLength; i) drawUnit(snk-snake[i], ■); } bool isBody(Pos p, Snake* snk) { for (int i 1; i snk-snakeLength; i) if (p snk-snake[i]) return true; return false; } bool checkOutOfBound(Pos p, Snake* snk) { if (p.x 0 || p.x W || p.y 0 || p.y H) return true; if (isBody(p, snk)) return true; return false; } void checkEatFood(Snake* snk, const Pos tail, Map* map) { Pos head snk-snake[0]; if (map-data[head.y][head.x] FOOD) { // 将原蛇尾追加到尾部增长 snk-snake[snk-snakeLength] tail; map-data[head.y][head.x] EMPTY; map-hasFood false; drawUnit(tail, ■); // 新增长的蛇身立即显示 } } void moveSnake(Snake* snk) { for (int i snk-snakeLength - 1; i 1; i--) snk-snake[i] snk-snake[i - 1]; snk-snake[0].y dir[snk-snakeDir][0]; snk-snake[0].x dir[snk-snakeDir][1]; } bool doMove(Snake* snk, Map* map) { Pos tail snk-snake[snk-snakeLength - 1]; drawUnit(tail, ); // 擦除尾部 moveSnake(snk); if (checkOutOfBound(snk-snake[0], snk)) return false; // 撞墙或撞身游戏结束 checkEatFood(snk, tail, map); // 检查是否吃到食物 drawUnit(snk-snake[0], ■); // 绘制新蛇头 return true; } void checkChangeDir(Snake* snk) { if (_kbhit()) { int ch _getch(); // 普通按键 WASD if (ch w || ch W) { if (snk-snakeDir ! 2) snk-snakeDir 0; } else if (ch s || ch S) { if (snk-snakeDir ! 0) snk-snakeDir 2; } else if (ch a || ch A) { if (snk-snakeDir ! 1) snk-snakeDir 3; } else if (ch d || ch D) { if (snk-snakeDir ! 3) snk-snakeDir 1; } // 方向键扩展键 else if (ch 0 || ch 224) { ch _getch(); switch (ch) { case 72: if (snk-snakeDir ! 2) snk-snakeDir 0; break; case 80: if (snk-snakeDir ! 0) snk-snakeDir 2; break; case 75: if (snk-snakeDir ! 1) snk-snakeDir 3; break; case 77: if (snk-snakeDir ! 3) snk-snakeDir 1; break; } } } } bool checkSnakeMove(Snake* snk, Map* map) { int curTime GetTickCount(); if (curTime - snk-lastMoveTime snk-moveFrequency) { checkChangeDir(snk); if (!doMove(snk, map)) return false; snk-lastMoveTime curTime; } return true; } void checkFoodGenerate(Snake* snk, Map* map) { if (!map-hasFood) { while (true) { int x rand() % W; int y rand() % H; int i 0; while (i snk-snakeLength !(x snk-snake[i].x y snk-snake[i].y)) i; if (i snk-snakeLength) { // 不在蛇身上 map-data[y][x] FOOD; map-hasFood true; drawUnit({ x, y }, ●); return; } } } } int main() { // 初始界面显示“1,开始游戏” Map map; initMap(map); drawMap(map); drawUnit({ W / 2 - 4, H / 2 }, 1,开始游戏); while (true) { if (_kbhit()) { int ch _getch(); if (ch 1) { bool restart false; do { Map map; Snake snk; hideCursor(); initMap(map); initSnake(snk); drawMap(map); drawSnake(snk); // 游戏主循环 while (true) { if (!checkSnakeMove(snk, map)) break; checkFoodGenerate(snk, map); } // 游戏结束 drawUnit({ W / 2 - 4, H / 2 }, Game Over); drawUnit({ W / 2 - 4, H / 2 1 }, 1, 重新开始); drawUnit({ W / 2 - 4, H / 2 2 }, 2, 结束游戏); while (true) { if (_kbhit()) { switch (_getch()) { case 1: restart true; break; case 2: restart false; break; } break; } } } while (restart); break; // 结束整个程序 } } } return 0; }七、关键点说明1. 非阻塞输入与方向限制使用_kbhit()实现非阻塞检测按键避免游戏卡在等待输入。方向限制保证了蛇不会直接反向撞到自己。2. 移动速度控制利用GetTickCount()获取系统时间毫秒与上次移动时间比较达到设定间隔才移动而不是依赖sleep这样键盘响应更灵敏。3. 蛇身增长实现当蛇头吃到食物时食物格子变为空并且将移动前保存的蛇尾坐标追加到蛇数组末尾蛇长度1。注意此时原蛇尾已经被擦除追加后需要立即绘制新的蛇身。4. 地图绘制优化只绘制边框一次之后仅通过drawUnit局部更新蛇和食物避免了全屏重绘的闪烁。5. 随机食物生成循环随机坐标直到找到一个不在蛇身上的空位简单但效率足够地图不大。八、运行效果九、总结本文实现了一个功能完整的贪吃蛇游戏涵盖了控制台编程的基本技巧光标定位、隐藏光标、非阻塞键盘输入、时间差控制移动等。读者可以在此基础上扩展功能如计分系统、加速减速、障碍物等。代码在Windows平台下编译运行良好适合作为C课程设计或入门小项目。