项目-贪吃蛇-代码逻辑梳理和实现(手搓无ai)

张开发
2026/4/13 14:46:06 15 分钟阅读

分享文章

项目-贪吃蛇-代码逻辑梳理和实现(手搓无ai)
目录一、游戏效果预览二、核心思路拆解三Win32API补充说明四、贪吃蛇游戏思路整理1.地图1.游戏菜单2.游戏界面2.游戏角色1.蛇的设计2.食物的设计3.游戏运行核心逻辑1.游戏开始2.游戏运行3.游戏结束4.详细代码实现1.蛇的初始化2.食物的初始化3.蛇与食物位置关系遇到与没遇到4.死亡原因撞墙和撞到自己5.蛇的移动核心做有意义的事过意义的人生欢迎大家一起讨论创作不易小博主求求赞啦一、游戏效果预览实现的功能方向控制 上 / 下 / 左 / 右随机生成食物蛇吃到食物变长、得分增加撞墙 / 撞自身游戏结束实时显示分数游戏速度随分数提升变快二、核心思路拆解蛇的表示用结构体存储坐标用数组存储蛇的每一节身体地图固定大小的矩形区域边界为围墙食物随机生成坐标不能出现在蛇身上移动逻辑蛇头向指定方向移动身体跟随前一节位置碰撞检测判断蛇头是否撞墙 / 撞自己键盘控制监听按键实时改变蛇的移动方向三Win32API补充说明1. COORD控制台屏幕上的坐标COORDCOORD 是Windows API中定义的⼀个结构体表⽰⼀个字符在控制台屏幕上的坐标typedef struct _COORD { SHORT X; SHORT Y; } COORD, *PCOORD;2.GetStdHandleHANDLE hOutput NULL; //获取标准输出的句柄(⽤来标识不同设备的数值) hOutput GetStdHandle(STD_OUTPUT_HANDLE);GetStdHandle是⼀个Windows API函数。它⽤于从⼀个特定的标准设备标准输⼊、标准输出或标准错误中取得⼀个句柄⽤来标识不同设备的数值使⽤这个句柄可以操作设备。3.GetConsoleCursorInfo检索有关指定控制台屏幕缓冲区的光标⼤⼩和可⻅性的信息。CONSOLE_CURSOR_INFO CursorInfo; GetConsoleCursorInfo(hOutput, CursorInfo);//获取控制台光标信息4.CONSOLE_CURSOR_INFO这个结构体包含有关控制台光标的信息。CursorInfo.bVisible false; //隐藏控制台光标5.SetConsoleCursorInfo设置指定控制台屏幕缓冲区的光标的⼤⼩和可⻅性。SetConsoleCursorInfo(hOutput, CursorInfo);//设置控制台光标状态6.SetConsoleCursorPosition设置指定控制台屏幕缓冲区中的光标位置我们将想要设置的坐标信息放在COORD类型的pos中调 ⽤SetConsoleCursorPosition函数将光标位置设置到指定的位置。COORD pos { 10, 5}; HANDLE hOutput NULL; //获取标准输出的句柄(⽤来标识不同设备的数值) hOutput GetStdHandle(STD_OUTPUT_HANDLE); //设置标准输出上光标的位置为pos SetConsoleCursorPosition(hOutput, pos);7.GetAsyncKeyState获取按键情况将键盘上每个键的虚拟键值传递给函数函数通过返回值来分辨按键的状态。GetAsyncKeyState的返回值是short类型在上⼀次调⽤GetAsyncKeyState函数后如果返回的16位的short数据中最⾼位是1说明按键的状态是按下如果最⾼是0说明按键的状态 是抬起如果最低位被置为1则说明该按键被按过否则为0。如果我们要判断⼀个键是否被按过可以检测GetAsyncKeyState返回值的最低值是否为1。#define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) 0x1) ? 1 : 0 )四、贪吃蛇游戏思路整理1.地图1.游戏菜单在贪吃蛇游戏地图中我们要创建两大部分 游戏菜单和游戏界面 我们先从游戏菜单说起。在这里我们用到SetPos函数它的功能是用来定义光标位置这样我们就能在控制台的不同位置打印我们需要的文字。void SetPos(short x, short y) { COORD pos { x,y }; HANDLE hOutput NULL; //获取标准输出的句柄(用来标识不同设备的数值) hOutput GetStdHandle(STD_OUTPUT_HANDLE); //设置标准输出上光标的位置为pos SetConsoleCursorPosition(hOutput, pos); }void WelcomeToGame() { SetPos(40, 15); printf(欢迎来到贪吃蛇小游戏); SetPos(40, 25);//让按任意键继续的出现的位置好看点 system(pause); system(cls); SetPos(25, 12); printf(用 ↑ . ↓ . ← . → 分别控制蛇的移动 F3为加速F4为减速\n); SetPos(25, 13); printf(加速将能得到更高的分数。\n); SetPos(40, 25);//让按任意键继续的出现的位置好看点 system(pause); system(cls); }2.游戏界面这⾥不得不讲⼀下控制台窗⼝的⼀些知识如果想在控制台的窗⼝中指定位置输出信息我们得知道该位置的坐标所以⾸先介绍⼀下控制台窗⼝的坐标知识。控制台窗⼝的坐标如下所⽰横向的是X轴从左向右依次增⻓纵向是Y轴从上到下依次增⻓。在游戏地图上我们打印墙体使⽤宽字符□打印蛇使⽤宽字符 ●打印⻝物使⽤宽字符★普通的字符是占⼀个字节的这类宽字符是占⽤2个字节。这⾥再简单的讲⼀下C语⾔的国际化特性相关的知识过去C语⾔并不适合⾮英语国家地区使⽤。C语⾔最初假定字符都是但⾃⼰的。但是这些假定并不是在世界的任何地⽅都适⽤。后来为了使C语⾔适应国际化C语⾔的标准中不断加⼊了国际化的⽀持。⽐如加⼊和宽字符的类型wchar_t和宽字符的输⼊和输出函数加⼊和locale.h头⽂件其中提供了允许程序员针对特定地区通常是国家或者说某种特定语⾔的地理区域调整程序⾏为的函数。setlocale函数char* setlocale (int category, const char* locale); 1 setlocale(LC_ALL, C);//切换到正常环境 2 setlocale(LC_ALL, );//切换到本地环境 当程序运⾏起来后想改变地区就只能显⽰调⽤setlocale函数。⽤ 作为第2个参数调⽤setlocale 函数就可以切换到本地模式这种模式下程序会适应本地环境。⽐如切换到我们的本地模式后就⽀ 持宽字符汉字的输出等。了解完宽字符的打印后看到这个页面时我们会思考如何打印游戏的边框可是宽字符和普通字符的区别在哪呢这里可以通过图解对比。我们假设实现⼀个棋盘27⾏58列的棋盘⾏和列可以根据⾃⼰的情况修改再围绕地图画出墙即可完成基本的游戏界面。void CreateMap() { int i 0; //上(0,0)-(56, 0) SetPos(0, 0); for (i 0; i 58; i 2) { SetPos(i,0); wprintf(L%c, WALL); } //下(0,26)-(56, 26) SetPos(0, 26); for (i 0; i 58; i 2) { SetPos(i, 26); wprintf(L%c, WALL); } //左 //x是0y从1开始增长 for (i 1; i 26; i) { SetPos(0, i); wprintf(L%c, WALL); } //x是56y从1开始增长 for (i 1; i 26; i) { SetPos(56, i); wprintf(L%c, WALL); } }2.游戏角色蛇和⻝物初始化状态假设蛇的⻓度是5蛇⾝的每个节点是●在固定的⼀个坐标处⽐如(24, 5)处开始出现 蛇连续5个节点。注意蛇的每个节点的x坐标必须是2个倍数否则可能会出现蛇的⼀个节点有⼀半出现在墙体中另外⼀般在墙外的现象坐标不好对⻬。关于⻝物就是在墙体内随机⽣成⼀个坐标x坐标必须是2的倍数坐标不能和蛇的⾝体重合然后打印★。1.蛇的设计在游戏运⾏的过程中蛇每次吃⼀个⻝物蛇的⾝体就会变⻓⼀节如果我们使⽤链表存储蛇的信息那么蛇的每⼀节其实就是链表的每个节点。每个节点只要记录好蛇⾝节点在地图上的坐标就⾏所以蛇节点结构如下typedef struct SnakeNode { int x; int y; struct SnakeNode* next; }SnakeNode, * pSnakeNode;要管理整条贪吃蛇我们再封装⼀个Snake的结构来维护整条贪吃蛇要管理整条贪吃蛇我们再封装⼀个Snake的结构来维护整条贪吃蛇。typedef struct Snake { pSnakeNode _pSnake;//维护整条蛇的指针 pSnakeNode _pFood;//维护⻝物的指针 enum DIRECTION _Dir;//蛇头的⽅向默认是向右 enum GAME_STATUS _Status;//游戏状态 int _Socre;//当前获得分数 int _foodWeight;//默认每个⻝物10分 int _SleepTime;//每⾛⼀步休眠时间 }Snake, * pSnake;蛇的⽅向可以一一列举使⽤枚举enum DIRECTION { UP 1, DOWN, LEFT, RIGHT };蛇的游戏状态可以一一 列举使⽤枚举enum GAME_STATUS { OK,//正常运⾏ KILL_BY_WALL,//撞墙 KILL_BY_SELF,//咬到⾃⼰ END_NOMAL//正常结束 };2.食物的设计对于食物的设计我们要思考它在游戏中扮演的角色作用蛇通过吃它增加身体长度对于由链表组成的蛇体食物必然也是单个节点然后遇到蛇头后被连接到链表中。对此我们首先要创建单节点充当食物的角色然后它会出现在哪呢是完全随机的吗我们思考下蛇头出现的位置对于宽字符x的坐标必然是偶数那么食物的x坐标也需要出现在偶数位才能与蛇头对齐其次它的位置也不能是位于墙体上我们要对x和y进行限制。void CreateFood(pSnake ps) { int x 0; int y 0; again: //产生的x坐标应该是2的倍数这样才可能和蛇头坐标对齐。 do { x rand() % 53 2; y rand() % 25 1; } while (x % 2 ! 0); pSnakeNode cur ps-_pSnake;//获取指向蛇头的指针 //食物不能和蛇身冲突 while (cur) { if (cur-x x cur-y y) { goto again; } cur cur-next; } pSnakeNode pFood (pSnakeNode)malloc(sizeof(SnakeNode)); //创建食物 if (pFood NULL) { perror(CreateFood::malloc()); return; } else { pFood-x x; pFood-y y; SetPos(pFood-x, pFood-y); wprintf(L%c, FOOD); ps-_pFood pFood; } }3.游戏运行核心逻辑我们先看下头文件中的函数代码定义进行思路的梳理。//游戏开始前的初始化 void GameStart(pSnake ps); //游戏运行过程 void GameRun(pSnake ps); //游戏结束 void GameEnd(pSnake ps); //设置光标的坐标 void SetPos(short x, short y); //欢迎界面 void WelcomeToGame(); //打印帮助信息 void PrintHelpInfo(); //创建地图 void CreateMap(); //初始化蛇 void InitSnake(pSnake ps); //创建食物 void CreateFood(pSnake ps); //暂停响应 void pause(); //下一个节点是食物 int NextIsFood(pSnakeNode psn, pSnake ps); //吃食物 void EatFood(pSnakeNode psn, pSnake ps); //不吃食物 void NoFood(pSnakeNode psn, pSnake ps); //撞墙检测 int KillByWall(pSnake ps); //撞自身检测 int KillBySelf(pSnake ps); //蛇的移动 void SnakeMove(pSnake ps); //游戏初始化 void GameStart(pSnake ps); //游戏运行 void GameRun(pSnake ps); //游戏结束 void GameEnd(pSnake ps);1. 蛇的本质 蛇是一串连续的点。 只有蛇头会主动动身体都是跟着前面一节走。 2. 移动规则最核心 从尾巴开始每一节身体都变成前一节的位置。 最后只需要移动蛇头整个蛇就动起来了。 一句话身体跟着走蛇头往前冲。 3. 方向控制 蛇只能上下左右四个方向移动。 不能直接掉头比如正在往右走不能立刻往左否则会瞬间撞死自己。 4. 食物机制 地图上随机出现一个食物。当蛇头碰到食物蛇长度加 1 分数增加游戏速度变快重新生成一个 5. 死亡判定 只有两种情况会死撞墙蛇头走出地图边界 撞自己蛇头碰到自己身体任意一节 6. 游戏主循环 整个游戏就是不断重复看有没有按键改方向。 蛇移动一步判断是否吃到食物 判断是否死亡 刷新面稍微停顿一下控制速度 回到第一步继续循环我们一个一个来梳理具体的代码实现我们先不管。1.游戏开始创建地图蛇食物。void GameStart(pSnake ps) { system(mode con lines30 cols100); system(title 贪吃蛇); HANDLE hOutput GetStdHandle(STD_OUTPUT_HANDLE);//获取标准输出的句柄(用来标识不同设备的数值 CONSOLE_CURSOR_INFO CursorInfo; //影藏光标操作 GetConsoleCursorInfo(hOutput, CursorInfo);//获取控制台光标信息 CursorInfo.bVisible false; //隐藏控制台光标 SetConsoleCursorInfo(hOutput, CursorInfo);//设置控制台光标状态 //打印欢迎界面 WelcomeToGame(); //打印地图 CreateMap(); //初始化蛇 InitSnake(ps); //创造第一个食物 CreateFood(ps); }2.游戏运行规则提示计算分数蛇的移动游戏的结束与暂停。void GameRun(pSnake ps) { PrintHelpInfo(); do { SetPos(64, 10); printf(得分%d分, ps-_Socre); printf( 每个食物得分%d分, ps-_Add); if (KEY_PRESS(VK_UP) ps-_Dir ! DOWN) { ps-_Dir UP; } else if (KEY_PRESS(VK_DOWN) ps-_Dir ! UP) { ps-_Dir DOWN; } else if (KEY_PRESS(VK_LEFT) ps-_Dir ! RIGHT) { ps-_Dir LEFT; } else if (KEY_PRESS(VK_RIGHT) ps-_Dir ! LEFT) { ps-_Dir RIGHT; } else if (KEY_PRESS(VK_SPACE)) { Sleep(3000); while (1) { Sleep(10); if (KEY_PRESS(VK_SPACE)) { break; } } } else if (KEY_PRESS(VK_ESCAPE)) { ps-_Status END_NOMAL; break; } else if (KEY_PRESS(VK_F3)) { if (ps-_SleepTime 50) { ps-_SleepTime - 30; ps-_Add 2; } } else if (KEY_PRESS(VK_F4)) { if (ps-_SleepTime 350) { ps-_SleepTime 30; ps-_Add - 2; if (ps-_SleepTime 350) { ps-_Add 1; } } } Sleep(ps-_SleepTime); SnakeMove(ps); } while (ps-_Status OK); }3.游戏结束归纳失败情况结束程序。void GameEnd(pSnake ps) { pSnakeNode cur ps-_pSnake; SetPos(64, 5); switch (ps-_Status) { case END_NOMAL: printf(您主动退出游戏\n); break; case KILL_BY_SELF: printf(您撞上自己了 ,游戏结束!\n); break; case KILL_BY_WALL: printf(您撞墙了,游戏结束!\n); break; } while (cur) { pSnakeNode del cur; cur cur-next; free(del); } }4.详细代码实现1.蛇的初始化void InitSnake(pSnake ps) { pSnakeNode cur NULL; int i 0; //创建蛇身节点并初始化坐标 //头插法 for (i 0; i 5; i) { //创建蛇身的节点 cur (pSnakeNode)malloc(sizeof(SnakeNode)); if (cur NULL) { perror(InitSnake()::malloc()); return; } //设置坐标 cur-next NULL; cur-x POS_X i * 2; cur-y POS_Y; //头插法 if (ps-_pSnake NULL) { ps-_pSnake cur; } else { cur-next ps-_pSnake; ps-_pSnake cur; } } //打印蛇的身体 cur ps-_pSnake; while (cur) { SetPos(cur-x, cur-y); wprintf(L%c, BODY); cur cur-next; } //初始化贪吃蛇数据 ps-_SleepTime 200; ps-_Socre 0; ps-_Status OK; ps-_Dir RIGHT; ps-_Add 10; }2.食物的初始化void CreateFood(pSnake ps) { int x 0; int y 0; again: //产生的x坐标应该是2的倍数这样才可能和蛇头坐标对齐。 do { x rand() % 53 2; y rand() % 25 1; } while (x % 2 ! 0); pSnakeNode cur ps-_pSnake;//获取指向蛇头的指针 //食物不能和蛇身冲突 while (cur) { if (cur-x x cur-y y) { goto again; } cur cur-next; } pSnakeNode pFood (pSnakeNode)malloc(sizeof(SnakeNode)); //创建食物 if (pFood NULL) { perror(CreateFood::malloc()); return; } else { pFood-x x; pFood-y y; SetPos(pFood-x, pFood-y); wprintf(L%c, FOOD); ps-_pFood pFood; } }3.蛇与食物位置关系遇到与没遇到int NextIsFood(pSnakeNode psn, pSnake ps) { return (psn-x ps-_pFood-x) (psn-y ps-_pFood-y); } void EatFood(pSnakeNode psn, pSnake ps) { //头插法 psn-next ps-_pSnake; ps-_pSnake psn; pSnakeNode cur ps-_pSnake; //打印蛇 while (cur) { SetPos(cur-x, cur-y); wprintf(L%c, BODY); cur cur-next; } ps-_Socre ps-_Add; free(ps-_pFood); CreateFood(ps); } void NoFood(pSnakeNode psn, pSnake ps) { //头插法 psn-next ps-_pSnake; ps-_pSnake psn; pSnakeNode cur ps-_pSnake; //打印蛇 while (cur-next-next) { SetPos(cur-x, cur-y); wprintf(L%c, BODY); cur cur-next; } //最后一个位置打印空格然后释放节点 SetPos(cur-next-x, cur-next-y); printf( ); free(cur-next); cur-next NULL; }4.死亡原因撞墙和撞到自己int KillByWall(pSnake ps) { if ((ps-_pSnake-x 0) || (ps-_pSnake-x 56) || (ps-_pSnake-y 0) || (ps-_pSnake-y 26)) { ps-_Status KILL_BY_WALL; return 1; } return 0; } int KillBySelf(pSnake ps) { pSnakeNode cur ps-_pSnake-next; while (cur) { if ((ps-_pSnake-x cur-x ) (ps-_pSnake-y cur-y)) { ps-_Status KILL_BY_SELF; return 1; } cur cur-next; } return 0; }5.蛇的移动核心void SnakeMove(pSnake ps) { //创建下一个节点 pSnakeNode pNextNode (pSnakeNode)malloc(sizeof(SnakeNode)); if (pNextNode NULL) { perror(SnakeMove()::malloc()); return; } //确定下一个节点的坐标下一个节点的坐标根据蛇头的坐标和方向确定 switch (ps-_Dir) { case UP: { pNextNode-x ps-_pSnake-x; pNextNode-y ps-_pSnake-y - 1; } break; case DOWN: { pNextNode-x ps-_pSnake-x; pNextNode-y ps-_pSnake-y 1; } break; case LEFT: { pNextNode-x ps-_pSnake-x - 2; pNextNode-y ps-_pSnake-y; } break; case RIGHT: { pNextNode-x ps-_pSnake-x 2; pNextNode-y ps-_pSnake-y; } break; } //如果下一个位置就是食物 if (NextIsFood(pNextNode, ps)) { EatFood(pNextNode, ps); } else//如果没有食物 { NoFood(pNextNode, ps); } KillByWall(ps); KillBySelf(ps);自取哦加油少年 (wxx547803_0) - Gitee.com做有意义的事过意义的人生欢迎大家一起讨论创作不易小博主求求赞啦

更多文章