购物网站最重要的功能wordpress域名空间
- 作者: 五速梦信息网
- 时间: 2026年04月20日 11:07
当前位置: 首页 > news >正文
购物网站最重要的功能,wordpress域名空间,wordpress vip会员系统,小型网站开发需要什么步骤该项目需要的技术要点 C语言函数、枚举、结构体、动态内存管理、预处理指令、链表、Win32API等。 由于篇幅限制 和 使知识模块化#xff0c; 若想了解 使用到的 Win32API 的知识#xff1a;请点击跳转#xff1a;【Win32API】贪吃蛇会使用到的 Win32API 目录
- 贪吃蛇游…该项目需要的技术要点 C语言函数、枚举、结构体、动态内存管理、预处理指令、链表、Win32API等。 由于篇幅限制 和 使知识模块化 若想了解 使用到的 Win32API 的知识请点击跳转【Win32API】贪吃蛇会使用到的 Win32API 目录
- 贪吃蛇游戏设计与分析 1.0 贪吃蛇页面大纲 1.1 地图 1.1.1 控制台窗口的坐标知识 1.1.2 宽字符 1.1.3 地图坐标 1.2 蛇身和食物 1.3 数据结构设计 1.4 整个游戏流程设计
- 核⼼逻辑实现分析 2.1 游戏主逻辑 2.2 游戏开始 2.3 游戏运行 2.4 游戏结束 3.总代码概览 Snake.c Snake.h test.c 准备工作创建三个文件 1. 贪吃蛇游戏设计与分析 1.0 贪吃蛇页面大纲 我们最终的贪吃蛇大纲要是这个样子那我们的地图如何布置呢 1.1 地图 这里不得不讲一下控制台窗口的一些知识 1.1.1 控制台窗口的坐标知识 控制台窗口的坐标如下所示横向的是X轴从左向右依次增⻓纵向是Y轴从上到下依次增⻓。 1.1.2 宽字符 在游戏地图上我们打印墙体使用宽字符□打印蛇使用宽字符 ● 打印食物使用宽字符★或图中的那个 普通的字符是占一个字节的这类宽字符是占用 2 个字节。 宽字符的来源 过去C语言并不适合非英语国家地区使用。 C语言最初假定字符都是自己的。但是这些假定并不是在世界的任何地方都适用。 后来为了使C语言适应国际化C语言的标准中不断加入了国际化的支持。比如加入和宽字符的类型 wchar_t 和宽字符的输入和输出函数加入和 locale.h 头文件其中提供了允许程序员针对特定地区通常是国家或者说某种特定语言的地理区域调整程序行为的函数。 1 locale.h本地化 locale.h提供的函数用于控制C标准库中对于不同的地区会产生不一样行为的部分。 2 setlocale函数 因为各个地区的代码语言环境不同需要转换到 本地环境, 才能支持宽字符如汉字的输出 用 作为第 2 个参数(注意:就是一对双引号中间没有空格) 调用 setlocale 函数就可以切换到本地模式这种模式下程序会适应本地环境。比如切换到我们的本地模式后就支持宽字符汉字的输出等。 #includelocale.h // 包含头文件 setlocale(LC_ALL, );//切换到本地环境中文环境 3宽字符 的 打印格式 宽字符要 在单引号前面加上 L表示宽字符宽字符的打印用wprintf对应wprintf()的占位符为%lc 假设 A 为一个窄字符B 为 宽字符 窄字符printf(%c, A)宽字符wprintf(L%lc, B); 从输出的结果来看我们发现一个普通字符占一个字符的位置 但是打印一个宽字符占用 2 个字符的位置那么我们如果 要在贪吃蛇中使用宽字符就得处理好地图上坐标的计算。 下图边框上的墙体就是 一种宽字符□ 宽度占 2 个字符位 1个坐标可以放1个正常字符即窄字符 2个坐标可以存放1个宽字符 1.1.3 地图坐标 我们假设实现一个棋盘 27 行 58 列的棋盘行和列可以根据自己的情况修改再围绕地图画出墙因为 宽字符的原因最好 是 2 * 行数 列数 如下 void CreateMap() {// 上29个宽格子SetPos(0, 0);// 从 (0, 0)开始for (int i 0; i 56; i 2) // 每隔两格打印一个 宽字符{wprintf(L%c, WALL);}//下SetPos(0, 26);for (int i 0; i 56; i 2){wprintf(L%c, WALL);}// 打印 左右 坐标需要跟着不断变化 因为 默认打印是 横着打印的//左for (int i 1; i 25; i){SetPos(0, i);wprintf(L%c, WALL);}//右for (int i 1; i 25; i){SetPos(56, i);wprintf(L%c, WALL);} } 1.2 蛇身和食物 初始化状态假设蛇的长度是 5 蛇身的每个节点是●在固定的一个坐标处比如 (24,5) 处开始出现蛇连续 5 个节点。 注意 蛇的每个节点的 x 坐标必须是 2 的倍数 即偶数否则撞墙时可能会出现蛇的一个节点有一半出现在墙体中另外一半在墙外的现象坐标不好对齐。 关于食物就是在墙体内随机生成一个坐标x坐标必须是 2 的倍数坐标不能和蛇的身体重合然后打印★。 void InitSnake(pSnake ps) {// 创建5个节点: pSnakeNode cur NULL;for (int i 0; i 5; i){cur (pSnakeNode)malloc(sizeof(SnakeNode));if (cur NULL){perror(InitSnake:malloc);return;}cur-x POS_X 2 * i;cur-y POS_Y;cur-next NULL;// 头插法// 没有节点下if (ps-pSnake NULL) // pSnake 是 指向蛇头 的指针即 单链表中 的头指针{ps-pSnake cur;}// 已经有节点 原本不是有 ps-pSnake cur; pSnake 存放着 上一个节点的地址借这个来 链接下一个节点然后更新 pSnake, 最后 pSnake 一直保持指向蛇头else{cur-next ps-pSnake;ps-pSnake cur;}}//打印蛇的身体// 打印 蛇身: 找到蛇头指针循环遍历 打印相应坐标 就行// 注意这个 cur 和上面的 cur 含义 不同其实是 临时变量的 意思cur ps-pSnake;while (cur){SetPos(cur-x, cur-y);wprintf(L%lc, BODY);cur cur-next;}// 初始化贪吃蛇的其他信息ps-dir RIGHT; // 初始默认向右ps-FoodWeight 10;ps-pFood NULL;ps-Score 0;ps-SleepTime 200; // 休眠 200 msps-state OK; } 1.3 数据结构设计 在游戏运行的过程中蛇每次吃一个⻝物蛇的身体就会变⻓一节如果我们使用 链表 存储蛇的信息那么蛇的每一节其实就是链表的每个节点。每个节点只要记录好蛇身节点在地图上的坐标就行 所以蛇节点结构如下 // 蛇身节点 // 蛇身用链表来维护一个蛇身节点就是一个链表节点 // 蛇在行动时蛇身的每一个节点的坐标都在变化所以要不断记录下来 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 state;//维护游戏进行状态蛇身撞墙、吃到自己、手动退出上面定义了一个枚举类型int Score;//当前获得分数int FoodWeight;//默认每个食物 10 分int SleepTime;//每走一步休眠时间控制蛇移动的速度(本质蛇身在移动过程中 可以发现蛇身节点在 一闪一闪的停顿其实是Sleep 控制睡眠时间来控制总体移动速度) }Snake, * pSnake; // pSnake 是 指向贪吃蛇的指针: 下面就用上了 蛇的方向可以一一列举使用枚举 //蛇走的方向 enum DIRECTION {UP 1, // 上DOWN, // 下LEFT, // 左RIGHT // 右 };游戏状态可以一一列举使用枚举 /蛇的状态: 维护游戏状态的 枚举类型 enum GAME_STATE {OK,//正常运行ESC, // 按 ESC 键退出KILL_BY_WALL,//撞墙KILL_BY_SELF,//咬到自己//END_NOMAL//正常结束 }; 1.4 整个游戏流程设计 2. 核⼼逻辑实现分析 2.1 游戏主逻辑 GameStart(snake); 游戏开始 GameRun(snake); 游戏过程 GameEnd(snake); 游戏结束 #includesnake.hvoid test() {int ch 0;do{// 创建一条贪吃蛇Snake snake { 0 };// 把蛇传过去因为蛇移动时就是变化的过程则应该传地址GameStart(snake); // 游戏开始前的初始化按键功能提示、游戏界面打印、墙体初始化等等GameRun(snake); // 游戏过程GameEnd(snake);//善后工作SetPos(20, 15);printf(再来一局y/n);ch getchar();getchar();} while (ch Y || ch y);} int main() {//适配本地中文环境setlocale(LC_ALL, );//贪吃蛇游戏的设置test();return 0; } 2.2 游戏开始 一、打印欢迎信息 WelcomeToGame(); 二、绘制地图 CreateMap(); 三、初始化蛇 InitSnake(ps); 四、创建食物 CreateFood(ps); // 游戏前准备 void GameStart(pSnake ps) {// 一、设置控制台的信息窗口大小、窗口名// 设置控制台窗口的长宽设置控制台窗口的大小// 因为光是墙体就 27行 58列 了界面右侧还要 写一些介绍指引则干脆 列为 100 行 30 system(mode con cols100 lines30);system(title 贪吃蛇);// 设置cmd窗口名称 // 二、隐藏光标不想光标在一直闪HANDLE handle GetStdHandle(STD_OUTPUT_HANDLE);//隐藏光标操作 6.5.1节 那里有示例CONSOLE_CURSOR_INFO cursorinfo; //使用 6.5.1 的结构体定义一个结构体变量名为 CursorInfoGetConsoleCursorInfo(handle, cursorinfo); //???? 为什么没有这句话还不行了获取控制台光标信息若不想查看也可以不写此句//先修改成员变量再传进Set函数中句柄 光标结构体相当于对 句柄所指定的控制台窗口就行光标修改设置cursorinfo.bVisible false; //隐藏控制台光标SetConsoleCursorInfo(handle, cursorinfo); //设置控制台光标状态//一、打印欢迎信息WelcomeToGame();//二、绘制地图CreateMap(); //三、初始化蛇InitSnake(ps); // 要把 蛇 的结构体的传过去才能修改蛇的状态信息//四、创建食物CreateFood(ps); // 因为 食物也是 链表节点最后要链接到 蛇身链表上的因此涉及到 蛇身的修改要传入蛇的头指针 } 游戏前准备 GameStart(pSnake ps) 的 四个子函数 先封装 设置坐标函数 //封装一个设置光标位置的函数 SetPos void SetPos(int x, int y) {//获取标准输出设备的句柄HANDLE handle GetStdHandle(STD_OUTPUT_HANDLE);//设置标准输出上光标的位置为posCOORD pos { x,y };SetConsoleCursorPosition(handle, pos); } 一、打印欢迎信息 void WelcomeToGame() {//一、 欢迎页面// 1、改变打印光标的坐标: 要处在 窗口中央前面设置窗口 列为 100 行 30 SetPos(38, 13); // 坐标的设置 看感觉来吧 printf(欢迎来到贪吃蛇小游戏\n);// 打印完上面这句后会有一句请按任意键继续… 位置不好看可以再次设置光标位置其实本质上就是一个新的光标SetPos(38, 16); // 请按任意键继续… 的位置system(pause); //一张页面打印完成后 暂停一下请按任意键继续… 然后打印另一张// 清空屏幕信息要打印下一张页面system(cls);//二、功能介绍页面SetPos(29, 8);printf(1、用 ↑ ↓ ← → 来控制蛇的移动 \n);SetPos(29, 10);printf(2、F3 是 加速F4 是 减速 O(∩_∩)O\n);SetPos(29, 12);printf(3、加速可以获得更高的分数 (づ 3)づ\n);SetPos(38, 16); // 请按任意键继续… 的位置system(pause); //一张页面打印完成后 暂停一下// 清空屏幕信息要打印下一张页面system(cls); }二、绘制地图 void CreateMap() {// 上29个宽格子SetPos(0, 0);// 从 (0, 0)开始for (int i 0; i 56; i 2) // 每隔两格打印一个 宽字符{wprintf(L%c, WALL);}//下SetPos(0, 26);for (int i 0; i 56; i 2){wprintf(L%c, WALL);}// 打印 左右 坐标需要跟着不断变化 因为 默认打印是 横着打印的//左for (int i 1; i 25; i){SetPos(0, i);wprintf(L%c, WALL);}//右for (int i 1; i 25; i){SetPos(56, i);wprintf(L%c, WALL);} }三、初始化 蛇 // 其实再屏幕上面显示出来相应坐标的 打印数据本质是要 定位到 相应坐标SetPos 函数 很重要 // 蛇最开始⻓度为5节每节对应链表的⼀个节点蛇⾝的每⼀个节点都有⾃⼰的坐标。 // 创建5个节点然后将每个节点存放在链表中进⾏管理。 void InitSnake(pSnake ps) {// 创建5个节点: pSnakeNode cur NULL;for (int i 0; i 5; i){cur (pSnakeNode)malloc(sizeof(SnakeNode));if (cur NULL){perror(InitSnake:malloc);return;}cur-x POS_X 2 * i;cur-y POS_Y;cur-next NULL;// 头插法// 没有节点下if (ps-pSnake NULL) // pSnake 是 指向蛇头 的指针即 单链表中 的头指针{ps-pSnake cur;}// 已经有节点 原本不是有 ps-pSnake cur; pSnake 存放着 上一个节点的地址借这个来 链接下一个节点然后更新 pSnake, 最后 pSnake 一直保持指向蛇头else{cur-next ps-pSnake;ps-pSnake cur;}}//打印蛇的身体// 打印 蛇身: 找到蛇头指针循环遍历 打印相应坐标 就行// 注意这个 cur 和上面的 cur 含义 不同其实是 临时变量的 意思cur ps-pSnake;while (cur){SetPos(cur-x, cur-y);wprintf(L%lc, BODY);cur cur-next;}// 初始化贪吃蛇的其他信息ps-dir RIGHT; // 初始默认向右ps-FoodWeight 10;ps-pFood NULL;ps-Score 0;ps-SleepTime 200; // 休眠 200 msps-state OK; }四、创建食物 // 思路 // 1、食物随机出现就是 随机一个坐标打印难点运算 随机数的取模 // 2、x 的范围2 ~ 54 — 生成 0 ~ 52 2 — rand()%53 2 因为 x 是占 2 个窄字符的 x 的取值必须是 偶数不然蛇 吃 不全即 不能全覆盖 // 3、y 的范围1 ~ 25 — 生成 0 ~ 24 2 — rand()%25 1 // 创建食物注意事项 // 1.食物是随机出现的坐标就是随机的 // 2.坐标必须在墙内 // 3.坐标不能在蛇的身体上 void CreateFood(pSnake ps) {// 1.食物是随机出现的坐标就是随机的// 2.坐标必须在墙内int x 0;int y 0;again:do{x rand() % 53 2;y rand() % 24 1;} while (x % 2 ! 0); // 这一步啥意思呀42min 左右// 3.坐标不能在蛇的身体上遍历蛇身一一对比对比//判断食物是否在蛇身上pSnakeNode cur ps-pSnake;while (cur){if (x cur-x y cur-y){goto again;}cur cur-next;}//创建食物: 一个新节点pSnakeNode pFood (pSnakeNode)malloc(sizeof(SnakeNode));// (pSnakeNode) 和 (SnakeNode) 千万别混淆if (pFood NULL){perror(CreateFood():malloc());return;}pFood-x x;pFood-y y;ps-pFood pFood; // pFood 是 贪吃蛇 结构体中的成员变量 pfood 是新创建的新食物节点// 思路将食物放进 贪吃蛇 结构体中判定重合后就头插法没有就一直 移动//打印食物SetPos(x, y);wprintf(L%c, FOOD);//getchar(); // 注意 这个别忘了关闭 }2.3 游戏运行 游戏运行期间右侧打印帮助信息提示玩家 根据游戏状态检查游戏是否继续如果是状态是OK游戏继续否则游戏结束。 如果游戏继续就是检测按键情况确定蛇下一步的方向或者是否加速减速是否暂停或者退出游戏。 确定了蛇的方向和速度蛇就可以移动了。 // 游戏运行过程 void GameRun(pSnake ps) {// 一、打印界面右侧的帮助提示信息PrintHelpInfo();// 游戏一般 是 do while循环总之都需要先让游戏先运行一次do{//当前的分数情况: 打印不断变化的分数技巧不断更新 蛇节点的 Score 更新就打印SetPos(65, 4);//[BACKGROUND代表背景就是背景FOREGROUND代表前景就是字体颜色 【http://t.csdnimg.cn/VFHPV】// SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_RED | BACKGROUND_INTENSITY);CONSOLE_FONT_INFOEX cfi;cfi.cbSize sizeof cfi;cfi.dwFontSize.X 0; //字宽cfi.dwFontSize.Y 20;//字高cfi.FontWeight FW_NORMAL;//粗细SetCurrentConsoleFontEx(GetStdHandle(STD_OUTPUT_HANDLE), FALSE, cfi);printf(总分%5d, ps-Score);SetPos(65, 6 );printf(食物的分值%02d, ps-FoodWeight);// 二、检测按键判断发出的指令使用 那个判断虚拟键值的函数// 上下左右ESC空格F3F4// 不能同时 方向 等于 相反方向if (KEY_PRESS(VK_UP) ps-dir ! DOWN)// 上 正在向上则同时 蛇 的方向不能等于 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_ESCAPE)){ps-state ESC; // 将状态该成 退出状态break;}else if (KEY_PRESS(VK_SPACE)) // 空格 (VK_SPACE){//游戏要暂定// 封装一个函数 Pause() : 暂定和回复暂定总不能一直暂停下去呀Pause();//暂定和回复暂定}else if (KEY_PRESS(VK_F3)) // 加速即休眠时间 变短同时分数变多而且 次数有限制{if (ps-SleepTime 80) // 最低下限SleepTime 80 初始化sleep 是 200ms{ps-SleepTime - 30;ps-FoodWeight 2;}}else if (KEY_PRESS(VK_F4)) // 减速 即休眠时间 变长同时分数变少{if (ps-FoodWeight 2) // 最低下限FoodWeight 2{ps-SleepTime 30;ps-FoodWeight - 2;}}// 三 四 步 别写反// 三、走一步: 每轮移动一步要检测是否撞墙…..等状态// 蛇身移动较为复杂封装成一个函数便于分析SnakeMove(ps);// 四、睡眠一下Sleep(ps-SleepTime);} while (ps-state OK); // 结束条件当游戏状态 不是 OK 时 就结束} 游戏运行过程函数GameRun(pSnake ps) 的 几个子函数 一、PrintHelpInfo()打印界面右侧的帮助提示信息 二、Pause()游戏暂停键 三、SnakeMove(ps)蛇身移动较为复杂 一、PrintHelpInfo()打印界面右侧的帮助提示信息 void PrintHelpInfo() {// 设置位置SetPos(65, 8);printf(温馨提示\n);SetPos(65, 10);printf(1、不能穿墙\n);SetPos(65, 11);printf(2、不能咬到自己\n);SetPos(65, 12);printf(3、按 ESC 退出游戏\n);SetPos(65, 13);printf(4、按 空格 暂停游戏\n);SetPos(65, 15);printf(操作回顾\n);SetPos(65, 17);printf(5、用 ↑↓ ← → 来控制蛇的移动 \n);SetPos(65, 18);printf(6、F3 加速F4 减速\n);SetPos(65, 19);printf(7、加速可以获得更高的分数\n);SetPos(65, 22);printf(版权 时差\n);// getchar(); // 注意 这个别忘了关闭 }二、Pause()游戏暂停键 void Pause()//游戏暂停 {// tip : 死循环的 sleep , 再循环中 判断 再次点击空格键 即可回复while (1){Sleep(200);if (KEY_PRESS(VK_SPACE)){break;}} } 三、SnakeMove(ps)蛇身移动较为复杂 分成几个子函数 NextIsFood(ps, pNext)判断蛇头 下一步处 是否是 食物EatFood(ps, pNext); 是食物就吃掉NotEatFood(ps, pNext); 不是食物就正常走一步KillByWall(ps);// 检测撞墙KillBySelf(ps);// 是否撞到自己 // 蛇身 移动 // 移动的本质 没吃食物则长度不变将 方向节点 链接到 蛇头 上free 掉 尾节点 void SnakeMove(pSnake ps) {// 根据按键调整移动方向// 创建下一个节点表示下一个方向的第一个节点根据图 更好理解 1h43min // 取名方向节点pSnakeNode pNext (pSnakeNode)malloc(sizeof(SnakeNode));if (pNext NULL){perror(SnakeMove():malloc:);return;}pNext-next NULL;// 指向空就好// 利用 方向节点// pSnake 是 贪吃蛇 结构体的成员变量指向蛇头 // 通过 改变 方向节点 的坐标位置引导蛇头的移动switch (ps-dir){case UP:pNext-x ps-pSnake-x;pNext-y ps-pSnake-y - 1;break;case DOWN:pNext-x ps-pSnake-x;pNext-y ps-pSnake-y 1;break;case LEFT:pNext-x ps-pSnake-x - 2;pNext-y ps-pSnake-y;break;case RIGHT:pNext-x ps-pSnake-x 2;pNext-y ps-pSnake-y;break;}//下一个坐标处是否是食物if (NextIsFood(ps, pNext)){//是食物就吃掉将 方向节点 链接到 蛇身上将 食物指针 free 释放掉EatFood(ps, pNext);}else{//不是食物就正常一步 没吃食物则长度不变将 方向节点 链接到 蛇头 上free 掉 尾节点NotEatFood(ps, pNext);}// getchar(); // 检测蛇移动时不能用 getchar 了 否则蛇 走一步 就不走了//检测撞墙KillByWall(ps);//是否撞到自己KillBySelf(ps); }1、NextIsFood(ps, pNext)判断蛇头 下一步处 是否是 食物 // 判断蛇头 下一步处 是否是 食物就是判断 方向指针pNext 是不是 食物 int NextIsFood(pSnake ps, pSnakeNode pNext) {// 两者坐标重合 就是了if (ps-pFood-x pNext-x ps-pFood-y pNext-y) return 1;//下一个坐标处是食物else return 0; } 2、EatFood(ps, pNext); 是食物就吃掉 //下一步处 是食物就吃掉将 方向节点 链接到 蛇身上头插法将 食物指针 free 释放掉 void EatFood(pSnake ps, pSnakeNode pNext) {pNext-next ps-pSnake; // 直接让 方向节点 指向头节点ps-pSnake pNext; // 更新头指针// 打印蛇身每次更新一轮就打印一次// PrintSnake(ps); // 这里注意一下 是不是不能用好像不是改了后 依旧停留在 1.0 版本//打印蛇pSnakeNode cur ps-pSnake;while (cur){SetPos(cur-x, cur-y);wprintf(L%c, BODY);cur cur-next;}// 分数变化ps-Score ps-FoodWeight;// 释放 旧食物节点吃掉了 就不存在了free(ps-pFood);//创建新食物;CreateFood(ps); } 3、 NotEatFood(ps, pNext); 不是食物就正常走一步 void NotEatFood(pSnake ps, pSnakeNode pNext) {// 头插法链接方向指针相当于移动了一步pNext-next ps-pSnake;// 直接让 方向节点 指向头节点ps-pSnake pNext;// 更新头指针// 释放 尾节点同时还要保留 尾节点 的前一个倒数第二个实际上 就是 单链表的 尾删法pSnakeNode cur ps-pSnake;while (cur-next-next){SetPos(cur-x, cur-y);wprintf(L%lc, BODY);cur cur-next;}// 将尾节点的位置 打印成 空白字符不能 还打印蛇身// 先 打印成 空白后再释放别急着释放SetPos(cur-next-x, cur-next-y);printf( );// 注意 空格是 两个free(cur-next);cur-next NULL;//易错// 注意前面以及在 循环遍历蛇身了同时就可以进行 打印 蛇身节点// 打印蛇身每次更新一轮就打印一次// PrintSnake(ps); } 4、 KillByWall(ps);// 检测撞墙 void KillByWall(pSnake ps) {if (ps-pSnake-x 0 ||ps-pSnake-x 56 ||ps-pSnake-y 0 ||ps-pSnake-y 26){ps-state KILL_BY_WALL;} } 5、KillBySelf(ps);// 是否撞到自己 void KillBySelf(pSnake ps) {pSnakeNode cur ps-pSnake-next;while (cur){if (cur-x ps-pSnake-x cur-y ps-pSnake-y){ps-state KILL_BY_SELF;return;}cur cur-next;} } 2.4 游戏结束 游戏状态不再是OK游戏继续的时候要告知游戏结束的原因并且释放蛇身节点。 void GameEnd(pSnake ps) {SetPos(15, 12);switch (ps-state){case ESC:printf(主动退出游戏\n);break;case KILL_BY_WALL:printf(很遗憾撞墙了游戏结束\n);break;case KILL_BY_SELF:printf(很遗憾撞到自己游戏结束\n);break;}//释放pSnakeNode cur ps-pSnake;pSnakeNode del NULL;while (cur){del cur;cur cur-next;free(del);}free(ps-pFood);ps NULL;}3.总代码概览 Snake.c #define _CRT_SECURE_NO_WARNINGS 1#includesnake.h//封装一个设置光标位置的函数 SetPos void SetPos(int x, int y) {HANDLE handle GetStdHandle(STD_OUTPUT_HANDLE);COORD pos { x,y };SetConsoleCursorPosition(handle, pos); }// 打印欢迎信息 void WelcomeToGame() {//一、 欢迎页面// 1、改变打印光标的坐标: 要处在 窗口中央前面设置窗口 列为 100 行 30 SetPos(38, 13); // 坐标的设置 看感觉来吧 printf(欢迎来到贪吃蛇小游戏\n);// 打印完上面这句后会有一句请按任意键继续… 位置不好看可以再次设置光标位置其实本质上就是一个新的光标SetPos(38, 16); // 请按任意键继续… 的位置system(pause); //一张页面打印完成后 暂停一下请按任意键继续… 然后打印另一张// 清空屏幕信息要打印下一张页面system(cls);//二、功能介绍页面SetPos(29, 8);printf(1、用 ↑ ↓ ← → 来控制蛇的移动 \n);SetPos(29, 10);printf(2、F3 是 加速F4 是 减速 O(∩_∩)O\n);SetPos(29, 12);printf(3、加速可以获得更高的分数 (づ 3)づ\n);SetPos(38, 16); // 请按任意键继续… 的位置system(pause); //一张页面打印完成后 暂停一下// 清空屏幕信息要打印下一张页面system(cls); }// 绘制地图 void CreateMap() {// 上29个宽格子SetPos(0, 0);// 从 (0, 0)开始for (int i 0; i 56; i 2) // 每隔两格打印一个 宽字符{wprintf(L%c, WALL);}//下SetPos(0, 26);for (int i 0; i 56; i 2){wprintf(L%c, WALL);}// 打印 左右 坐标需要跟着不断变化 因为 默认打印是 横着打印的//左for (int i 1; i 25; i){SetPos(0, i);wprintf(L%c, WALL);}//右for (int i 1; i 25; i){SetPos(56, i);wprintf(L%c, WALL);} }// 其实再屏幕上面显示出来相应坐标的 打印数据本质是要 定位到 相应坐标SetPos 函数 很重要 // 初始化 蛇 // 蛇最开始⻓度为5节每节对应链表的⼀个节点蛇⾝的每⼀个节点都有⾃⼰的坐标。 // 创建5个节点然后将每个节点存放在链表中进⾏管理。 void InitSnake(pSnake ps) {// 创建5个节点: pSnakeNode cur NULL;for (int i 0; i 5; i){cur (pSnakeNode)malloc(sizeof(SnakeNode));if (cur NULL){perror(InitSnake:malloc);return;}cur-x POS_X 2 * i;cur-y POS_Y;cur-next NULL;// 头插法// 没有节点下if (ps-pSnake NULL) // pSnake 是 指向蛇头 的指针即 单链表中 的头指针{ps-pSnake cur;}// 已经有节点 原本不是有 ps-pSnake cur; pSnake 存放着 上一个节点的地址借这个来 链接下一个节点然后更新 pSnake, 最后 pSnake 一直保持指向蛇头else{cur-next ps-pSnake;ps-pSnake cur;}}//打印蛇的身体// 打印 蛇身: 找到蛇头指针循环遍历 打印相应坐标 就行// 注意这个 cur 和上面的 cur 含义 不同其实是 临时变量的 意思cur ps-pSnake;while (cur){SetPos(cur-x, cur-y);wprintf(L%lc, BODY);cur cur-next;}// 初始化贪吃蛇的其他信息ps-dir RIGHT; // 初始默认向右ps-FoodWeight 10;ps-pFood NULL;ps-Score 0;ps-SleepTime 200; // 休眠 200 msps-state OK; }// 创建食物 // 思路 // 1、食物随机出现就是 随机一个坐标打印难点运算 随机数的取模 // 2、x 的范围2 ~ 54 — 生成 0 ~ 52 2 — rand()%53 2 因为 x 是占 2 个窄字符的 x 的取值必须是 偶数不然蛇 吃 不全即 不能全覆盖 // 3、y 的范围1 ~ 25 — 生成 0 ~ 24 2 — rand()%25 1 // 创建食物注意事项 // 1.食物是随机出现的坐标就是随机的 // 2.坐标必须在墙内 // 3.坐标不能在蛇的身体上 void CreateFood(pSnake ps) {// 1.食物是随机出现的坐标就是随机的// 2.坐标必须在墙内int x 0;int y 0;again:do{x rand() % 53 2;y rand() % 24 1;} while (x % 2 ! 0); // 这一步啥意思呀42min 左右// 3.坐标不能在蛇的身体上遍历蛇身一一对比对比//判断食物是否在蛇身上pSnakeNode cur ps-pSnake;while (cur){if (x cur-x y cur-y){goto again;}cur cur-next;}//创建食物: 一个新节点pSnakeNode pFood (pSnakeNode)malloc(sizeof(SnakeNode));// (pSnakeNode) 和 (SnakeNode) 千万别混淆if (pFood NULL){perror(CreateFood():malloc());return;}pFood-x x;pFood-y y;ps-pFood pFood; // pFood 是 贪吃蛇 结构体中的成员变量 pfood 是新创建的新食物节点// 思路将食物放进 贪吃蛇 结构体中判定重合后就头插法没有就一直 移动//打印食物SetPos(x, y);wprintf(L%c, FOOD);//getchar(); // 注意 这个别忘了关闭 }// 游戏前准备 void GameStart(pSnake ps) {// 一、设置控制台的信息窗口大小、窗口名// 设置控制台窗口的长宽设置控制台窗口的大小// 因为光是墙体就 27行 58列 了界面右侧还要 写一些介绍指引则干脆 列为 100 行 30 system(mode con cols100 lines30);system(title 贪吃蛇);// 设置cmd窗口名称 // 二、隐藏光标不想光标在一直闪HANDLE handle GetStdHandle(STD_OUTPUT_HANDLE);//隐藏光标操作 6.5.1节 那里有示例CONSOLE_CURSOR_INFO cursorinfo; //使用 6.5.1 的结构体定义一个结构体变量名为 CursorInfoGetConsoleCursorInfo(handle, cursorinfo); //???? 为什么没有这句话还不行了获取控制台光标信息若不想查看也可以不写此句//先修改成员变量再传进Set函数中句柄 光标结构体相当于对 句柄所指定的控制台窗口就行光标修改设置cursorinfo.bVisible false; //隐藏控制台光标SetConsoleCursorInfo(handle, cursorinfo); //设置控制台光标状态//打印欢迎信息WelcomeToGame();//绘制地图CreateMap(); //初始化蛇InitSnake(ps); // 要把 蛇 的结构体的传过去才能修改蛇的状态信息//创建食物CreateFood(ps); // 因为 食物也是 链表节点最后要链接到 蛇身链表上的因此涉及到 蛇身的修改要传入蛇的头指针 }void PrintHelpInfo() {// 设置位置SetPos(65, 8);printf(温馨提示\n);SetPos(65, 10);printf(1、不能穿墙\n);SetPos(65, 11);printf(2、不能咬到自己\n);SetPos(65, 12);printf(3、按 ESC 退出游戏\n);SetPos(65, 13);printf(4、按 空格 暂停游戏\n);SetPos(65, 15);printf(操作回顾\n);SetPos(65, 17);printf(5、用 ↑↓ ← → 来控制蛇的移动 \n);SetPos(65, 18);printf(6、F3 加速F4 减速\n);SetPos(65, 19);printf(7、加速可以获得更高的分数\n);SetPos(65, 22);printf(版权 时差\n);// getchar(); // 注意 这个别忘了关闭 }// 按 空格 暂定和回复暂定 void Pause()//游戏暂停 {// tip : 死循环的 sleep , 再循环中 判断 再次点击空格键 即可回复while (1){Sleep(200);if (KEY_PRESS(VK_SPACE)){break;}} }// 判断蛇头 下一步处 是否是 食物就是判断 方向指针pNext 是不是 食物 int NextIsFood(pSnake ps, pSnakeNode pNext) {// 两者坐标重合 就是了if (ps-pFood-x pNext-x ps-pFood-y pNext-y) return 1;//下一个坐标处是食物else return 0; }//下一步处 是食物就吃掉将 方向节点 链接到 蛇身上头插法将 食物指针 free 释放掉 void EatFood(pSnake ps, pSnakeNode pNext) {pNext-next ps-pSnake; // 直接让 方向节点 指向头节点ps-pSnake pNext; // 更新头指针// 打印蛇身每次更新一轮就打印一次// PrintSnake(ps); // 这里注意一下 是不是不能用好像不是改了后 依旧停留在 1.0 版本//打印蛇pSnakeNode cur ps-pSnake;while (cur){SetPos(cur-x, cur-y);wprintf(L%c, BODY);cur cur-next;}// 分数变化ps-Score ps-FoodWeight;// 释放 旧食物节点吃掉了 就不存在了free(ps-pFood);//创建新食物;CreateFood(ps); }void NotEatFood(pSnake ps, pSnakeNode pNext) {// 头插法链接方向指针相当于移动了一步pNext-next ps-pSnake;// 直接让 方向节点 指向头节点ps-pSnake pNext;// 更新头指针// 释放 尾节点同时还要保留 尾节点 的前一个倒数第二个实际上 就是 单链表的 尾删法pSnakeNode cur ps-pSnake;while (cur-next-next){SetPos(cur-x, cur-y);wprintf(L%lc, BODY);cur cur-next;}// 将尾节点的位置 打印成 空白字符不能 还打印蛇身// 先 打印成 空白后再释放别急着释放SetPos(cur-next-x, cur-next-y);printf( );// 注意 空格是 两个free(cur-next);cur-next NULL;//易错// 注意前面以及在 循环遍历蛇身了同时就可以进行 打印 蛇身节点// 打印蛇身每次更新一轮就打印一次// PrintSnake(ps); }void KillByWall(pSnake ps) {if (ps-pSnake-x 0 ||ps-pSnake-x 56 ||ps-pSnake-y 0 ||ps-pSnake-y 26){ps-state KILL_BY_WALL;} }void KillBySelf(pSnake ps) {pSnakeNode cur ps-pSnake-next;while (cur){if (cur-x ps-pSnake-x cur-y ps-pSnake-y){ps-state KILL_BY_SELF;return;}cur cur-next;} }// 蛇身 移动 // 移动的本质 没吃食物则长度不变将 方向节点 链接到 蛇头 上free 掉 尾节点 void SnakeMove(pSnake ps) {// 根据按键调整移动方向// 创建下一个节点表示下一个方向的第一个节点根据图 更好理解 1h43min // 取名方向节点pSnakeNode pNext (pSnakeNode)malloc(sizeof(SnakeNode));if (pNext NULL){perror(SnakeMove():malloc:);return;}pNext-next NULL;// 指向空就好// 利用 方向节点// pSnake 是 贪吃蛇 结构体的成员变量指向蛇头 // 通过 改变 方向节点 的坐标位置引导蛇头的移动switch (ps-dir){case UP:pNext-x ps-pSnake-x;pNext-y ps-pSnake-y - 1;break;case DOWN:pNext-x ps-pSnake-x;pNext-y ps-pSnake-y 1;break;case LEFT:pNext-x ps-pSnake-x - 2;pNext-y ps-pSnake-y;break;case RIGHT:pNext-x ps-pSnake-x 2;pNext-y ps-pSnake-y;break;}//下一个坐标处是否是食物if (NextIsFood(ps, pNext)){//是食物就吃掉将 方向节点 链接到 蛇身上将 食物指针 free 释放掉EatFood(ps, pNext);}else{//不是食物就正常一步 没吃食物则长度不变将 方向节点 链接到 蛇头 上free 掉 尾节点NotEatFood(ps, pNext);}// getchar(); // 检测蛇移动时不能用 getchar 了 否则蛇 走一步 就不走了//检测撞墙KillByWall(ps);//是否撞到自己KillBySelf(ps); }// 游戏运行过程 void GameRun(pSnake ps) {// 一、打印界面右侧的帮助提示信息PrintHelpInfo();// 游戏一般 是 do while循环总之都需要先让游戏先运行一次do{//当前的分数情况: 打印不断变化的分数技巧不断更新 蛇节点的 Score 更新就打印SetPos(65, 4);//[BACKGROUND代表背景就是背景FOREGROUND代表前景就是字体颜色 【http://t.csdnimg.cn/VFHPV】// SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_RED | BACKGROUND_INTENSITY);CONSOLE_FONT_INFOEX cfi;cfi.cbSize sizeof cfi;cfi.dwFontSize.X 0; //字宽cfi.dwFontSize.Y 20;//字高cfi.FontWeight FW_NORMAL;//粗细SetCurrentConsoleFontEx(GetStdHandle(STD_OUTPUT_HANDLE), FALSE, cfi);printf(总分%5d, ps-Score);SetPos(65, 6 );printf(食物的分值%02d, ps-FoodWeight);// 二、检测按键判断发出的指令使用 那个判断虚拟键值的函数// 上下左右ESC空格F3F4// 不能同时 方向 等于 相反方向if (KEY_PRESS(VK_UP) ps-dir ! DOWN)// 上 正在向上则同时 蛇 的方向不能等于 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_ESCAPE)){ps-state ESC; // 将状态该成 退出状态break;}else if (KEY_PRESS(VK_SPACE)) // 空格 (VK_SPACE){//游戏要暂定// 封装一个函数 Pause() : 暂定和回复暂定总不能一直暂停下去呀Pause();//暂定和回复暂定}else if (KEY_PRESS(VK_F3)) // 加速即休眠时间 变短同时分数变多而且 次数有限制{if (ps-SleepTime 80) // 最低下限SleepTime 80 初始化sleep 是 200ms{ps-SleepTime - 30;ps-FoodWeight 2;}}else if (KEY_PRESS(VK_F4)) // 减速 即休眠时间 变长同时分数变少{if (ps-FoodWeight 2) // 最低下限FoodWeight 2{ps-SleepTime 30;ps-FoodWeight - 2;}}// 三 四 步 别写反// 三、走一步: 每轮移动一步要检测是否撞墙…..等状态// 蛇身移动较为复杂封装成一个函数便于分析SnakeMove(ps);// 四、睡眠一下Sleep(ps-SleepTime);} while (ps-state OK); // 结束条件当游戏状态 不是 OK 时 就结束}void GameEnd(pSnake ps) {SetPos(15, 12);switch (ps-state){case ESC:printf(主动退出游戏\n);break;case KILL_BY_WALL:printf(很遗憾撞墙了游戏结束\n);break;case KILL_BY_SELF:printf(很遗憾撞到自己游戏结束\n);break;}//释放pSnakeNode cur ps-pSnake;pSnakeNode del NULL;while (cur){del cur;cur cur-next;free(del);}free(ps-pFood);ps NULL;}Snake.h #pragma once #includelocale.h #includestdlib.h #includestdio.h #includewindows.h #includestdbool.h #define WALL L□ // 因为 每次打印都要写一次 L什么什么的 还不如直接定义一个宏 #define BODY L● // 打印是 蛇身 #define FOOD L※ // 打印食物 //Đʘ◨ↇↀↈ不行的 // D● // 蛇 默认的起始坐标位置要确定 起始的蛇身 每个 节点的 坐标#define POS_X 24//蛇初始坐标 #define POS_Y 5#define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) 0x1) ? 1 : 0 ) // 枚举类型不熟的可以去翻笔记 // enum 就是 集合版的 define//蛇走的方向 enum DIRECTION {UP 1, // 上DOWN, // 下LEFT, // 左RIGHT // 右 };//蛇的状态: 维护游戏状态的 枚举类型 enum GAME_STATE {OK,//正常运行ESC, // 按 ESC 键退出KILL_BY_WALL,//撞墙KILL_BY_SELF,//咬到自己//END_NOMAL//正常结束 };// 蛇身节点 // 蛇身用链表来维护一个蛇身节点就是一个链表节点 // 蛇在行动时蛇身的每一个节点的坐标都在变化所以要不断记录下来 typedef struct SnakeNode {int x;int y;struct SnakeNode* next; }SnakeNode, * pSnakeNode; // 创建一个指向蛇身节点的结构体指针//贪吃蛇 // 整条蛇 // 要管理整条贪吃蛇当前状态过程的信息我们再封装一个Snake的结构来维护整条贪吃蛇 typedef struct Snake {pSnakeNode pSnake;//维护整条蛇的指针指向蛇头pSnakeNode pFood;//维护食物的指针食物实际上也是蛇身节点只是打印不一样enum DIRECTION dir;//蛇头的方向默认是向右enum GAME_STATUS state;//维护游戏进行状态蛇身撞墙、吃到自己、手动退出上面定义了一个枚举类型int Score;//当前获得分数int FoodWeight;//默认每个食物 10 分int SleepTime;//每走一步休眠时间控制蛇移动的速度(本质蛇身在移动过程中 可以发现蛇身节点在 一闪一闪的停顿其实是Sleep 控制睡眠时间来控制总体移动速度) }Snake, * pSnake; // pSnake 是 指向贪吃蛇的指针: 下面就用上了其实就是 Snake*//定位控制台光标位置 void SetPos(int x, int y);//游戏开始前的准备 void GameStart(pSnake ps);//欢迎界面 void WelcomeToGame();//打印欢迎信息//绘制地图 void CreateMap();//创建地图//初始化蛇 void InitSnake(pSnake ps);//创建食物 void CreateFood(pSnake ps);//游戏运行的整个逻辑 void GameRun(pSnake ps);//打印帮助信息 void PrintHelpInfo();//蛇移动的函数- 每次走一步 void SnakeMove(pSnake ps);//判断蛇头的下一步要走的位置处是否是食物 int NextIsFood(pSnake ps, pSnakeNode pNext);//下一步要走的位置处就是食物就吃掉食物 void EatFood(pSnake ps, pSnakeNode pNext);//下一步要走的位置处不是食物不吃食物 void NotEatFood(pSnake ps, pSnakeNode pNext);//检测撞墙 void KillByWall(pSnake ps);//撞到自己 void KillBySelf(pSnake ps);//游戏结束资源释放 void GameEnd(pSnake ps); test.c #define _CRT_SECURE_NO_WARNINGS 1 #includesnake.hvoid test() {int ch 0;do{// 创建一条贪吃蛇Snake snake { 0 };// 把蛇传过去因为蛇移动时就是变化的过程则应该传地址GameStart(snake); // 游戏开始前的初始化按键功能提示、游戏界面打印、墙体初始化等等GameRun(snake); // 游戏过程GameEnd(snake);//善后工作SetPos(20, 15);printf(再来一局y/n);ch getchar();getchar();} while (ch Y || ch y);} int main() {//适配本地中文环境setlocale(LC_ALL, );//贪吃蛇游戏的设置test();return 0; }
- 上一篇: 购物网站制作例子简单的logo设计
- 下一篇: 姑苏营销型网站建设电话我负责与你们公司网站建设的沟通
相关文章
-
购物网站制作例子简单的logo设计
购物网站制作例子简单的logo设计
- 技术栈
- 2026年04月20日
-
购物网站支付功能怎么做防水网站的外链如何找
购物网站支付功能怎么做防水网站的外链如何找
- 技术栈
- 2026年04月20日
-
购物网站怎么做ux设计师是做什么的
购物网站怎么做ux设计师是做什么的
- 技术栈
- 2026年04月20日
-
姑苏营销型网站建设电话我负责与你们公司网站建设的沟通
姑苏营销型网站建设电话我负责与你们公司网站建设的沟通
- 技术栈
- 2026年04月20日
-
古楼角网站建设建设一个网站需要几个角色
古楼角网站建设建设一个网站需要几个角色
- 技术栈
- 2026年04月20日
-
古镇建设网站环境设计网站推荐
古镇建设网站环境设计网站推荐
- 技术栈
- 2026年04月20日
