温馨提示:这篇文章已超过392天没有更新,请注意相关的内容是否还可用!
摘要:本文将介绍使用C语言结合EasyX图形库实现贪吃蛇游戏的页面管理技巧。通过简单易懂的方式,介绍如何创建游戏界面、处理游戏逻辑以及实现页面管理。文章将强调实战技巧,帮助读者快速掌握使用C语言和EasyX库开发贪吃蛇游戏的基本方法。
✅作者简介:大家好,我是橘橙黄又青,一个想要与大家共同进步的男人😉😉
🍎个人主页:再无B~U~G-CSDN博客
1. 游戏背景
贪吃蛇是久负盛名的游戏,它也和俄罗斯⽅块,扫雷等游戏位列经典游戏的⾏列。 在编程语⾔的教学中,我们以贪吃蛇为例,从设计到代码实现来提升学⽣的编程能⼒和逻辑能⼒。2. 游戏效果演⽰
屏幕录制
3. 课程⽬标
使⽤C语⾔在Windows环境的控制台中模拟实现经典⼩游戏贪吃蛇 实现基本的功能: • 贪吃蛇地图绘制 • 蛇吃⻝物的功能 (上、下、左、右⽅向键控制蛇的动作) • 蛇撞墙死亡 • 蛇撞⾃⾝死亡 • 计算得分 • 蛇⾝加速、减速 • 暂停游戏4. 课程定位
• 提⾼⼩⽐特对编程的兴趣 • 对C语⾔语法做⼀个基本的巩固。 • 对游戏开发有兴趣的同学做⼀个启发。 • 项⽬适合:C语⾔学完的同学,有⼀定的代码能⼒,初步接触数据结构中的链表。5. 技术要点
C语⾔函数、枚举、结构体、动态 内存管理、预处理指令、链表、Win32 API等。 整体的框架:
课前准备:
调好项目适应本地化
了解EasyX的坐标体系
6. 控制台程序
6.1 控制台程序
设置如下:
6.1 相关函数的使用
地区设置为"C"时,库函数按正常⽅式执⾏,⼩数点是⼀个点。 当程序运⾏起来后想改变地区,就只能显⽰调⽤setlocale函数。⽤" "作为第2个参数,调⽤setlocale 函数就可以切换到本地模式,这种模式下程序会适应本地环境。⽐如:切换到我们的本地模式后就⽀持宽字符(汉字)的输出等本地化

全部来自于EasyX简单图形库
6.2pause
6.3 outtextxy
两个一个输出字符,一个输出字符串
案例:
6.4cleardevice
6.5图片设置
相关用法:
6.6rectangle
6.7setlinecolor设置线条颜色
7. 游戏实现
7.1地图坐标
我们假设实现⼀个棋盘27⾏,58列的棋盘(⾏和列可以根据⾃⼰的情况修改),再围绕地图画出墙, 如下:
7.2 蛇⾝和⻝物
初始化状态,假设蛇的⻓度是5, ⽐如(24, 5)处开始出现蛇,连续5个节点。注意:蛇的每个节点的x坐标必须是2个倍数,否则可能会出现蛇的⼀个节点有⼀半⼉出现在墙体中,另外⼀般在墙外的现象,坐标不好对⻬。
7.3 数据结构设计
游戏运⾏的过程中,蛇每次吃⼀个⻝物,蛇的⾝体就会变⻓⼀节,如果我们使⽤链表存储蛇的信 息,那么蛇的每⼀节其实就是链表的每个节点。每个节点只要记录好蛇⾝节点在地图上的坐标就⾏,所以蛇节点结构如下:
蛇的⽅向,可以⼀⼀列举,使⽤枚举

7.4 游戏流程设计
8. 核⼼逻辑实现分析
8.1 游戏主逻辑
8.2 游戏开始前面的准备
8.2.1 打印欢迎界⾯
看看效果:
8.2.2 创建地图
打印地图的关键是要算好坐标,才能在想要的位置打印墙体。

8.2.3 初始化蛇⾝
蛇最开始⻓度为5节,每节对应链表的⼀个节点,蛇⾝的每⼀个节点都有⾃⼰的坐标。 创建5个节点,然后将每个节点存放在链表中进⾏管理。创建完蛇⾝后,将蛇的每⼀节打印在屏幕上。
蛇的图片是:
8.2.4 创建第⼀个⻝物
先随机⽣成⻝物的坐标
- x坐标必须是15的倍数
- ⻝物的坐标不能和蛇⾝每个节点的坐标重复
- 创建⻝物节点,打印⻝物
创建⻝物的函数:CreateFood
8.3 游戏运⾏过程
游戏运⾏期间,右侧打印帮助信息,提⽰玩家 根据游戏状态检查游戏是否继续,如果是状态是OK,游戏继续,否则游戏结束。 如果游戏继续,就是检测按键情况,确定蛇下⼀步的⽅向,或者是否加速减速,是否暂停或者退出游戏。 确定了蛇的⽅向和速度,蛇就可以移动了。
8.3.1 KEY_PRESS
检测按键状态,我们封装了⼀个宏
# define KEY_PRESS(VK) ((GetAsyncKeyState(VK)&0x1) ? 1 : 0)8.3.2 PrintHelpInfo 打印规则信息
8.3.3 蛇⾝移动
先创建下⼀个节点,根据移动⽅向和蛇头的坐标,蛇移动到下⼀个位置的坐标。 确定了下⼀个位置后,看下⼀个位置是否是⻝物(NextIsFood),是⻝物就做吃⻝物处理 (EatFood),如果不是⻝物则做前进⼀步的处理(NoFood)。 蛇⾝移动后,判断此次移动是否会造成撞墙(KillByWall)或者撞上⾃⼰蛇⾝(KillBySelf),从⽽影响游戏的状态。
8.3.3.1 NextIsFood
8.3.3.2 EatFood
8.3.3.3 NoFood
将下⼀个节点头插⼊蛇的⾝体,并将之前蛇⾝最后⼀个节点打印为空格,放弃掉蛇⾝的最后⼀个节点
8.3.3.4 KillByWall
8.3.3.5 KillBySelf
8.4 游戏结束
戏状态不再是OK(游戏继续)的时候,要告知游戏结束的原因,并且释放蛇⾝节点。
9. 参考代码
完整代码实现,分3个⽂件实现test.cpp
#include"snake.h" void test() { //修改适配本地中文环境 setlocale(LC_ALL, ""); bool c = false; char str[4] = " "; do { Snake snake = { 0 }; GameStart(&snake);//游戏开始前的初始化 GameRun(&snake);//玩游戏的过程 GameEnd(&snake);//善后的工作 outtextxy(350, 500, "再来一局吗?(Yes/No):"); scanf("%s", str); c = Determine(str); } while (c); } int main() { //修改适配本地中文环境 setlocale(LC_ALL, ""); test(); return 0; }
snake.h
#pragma once #define _CRT_SECURE_NO_WARNINGS #include #include #include #include #include #include #include #define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 ) //蛇默认的起始坐标 #define POS_X 300 #define POS_Y 200 //背景单位 #define SIZE_X 15 #define SIZE_Y 20 //游戏的状态 enum GAME_STATUS { OK = 1,//正常运行 ESC, //按了ESC键退出,正常退出 KILL_BY_WALL,//撞墙 KILL_BY_SELF //撞到自身 }; typedef struct snake { int x; int y; struct snake* next; }SnakeNode, * pSnakeNode;//结构体指针重命名 //蛇行走的方向 enum DIRECTION { UP = 1, DOWN, LEFT, RIGHT//向右 }; //贪吃蛇应该有的属性 typedef struct Snake { pSnakeNode pSnake;//维护整条蛇的指针,是指向蛇头 pSnakeNode pFood;//指向食物的指针 int Score;//当前累积的分数 int FoodWeight;//一个食物的分数 int SleepTime;//蛇休眠的时间,休眠的时间越短,蛇的速度越快,休眠的时间越长,蛇的速度越慢 enum GAME_STATUS status;//游戏当前的状态 enum DIRECTION dir;//蛇当前走的方向 //... }Snake, * pSnake; //游戏前的初始化 void GameStart(pSnake p); //打印欢迎信息 void WelcomeToGame(); //绘制地图 void CreateMap(); //初始化蛇 void InitSnake(pSnake ps); //打印图片 void picture(int x, int y); //创建食物 void CreateFood(pSnake ps); //玩游戏的过程 void GameRun(pSnake ps); //打印帮助(规则)信息 void PrintHelpInfo(); //当前分数情况 void Score(pSnake ps); //游戏要暂停 void pause(); //蛇走一步 void SnakeMove(pSnake ps); //下一个坐标是不是食物 bool 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); //判断是否再来一局 bool Determine(char str[]);
snake.cpp
#include"snake.h" void GameStart(pSnake ps) { //设置控制台信息,窗口大小,窗口名 initgraph(1300, 720, EX_SHOWCONSOLE); //打印欢迎信息 WelcomeToGame(); //绘制地图 CreateMap(); //初始化蛇 InitSnake(ps); //创建食物 CreateFood(ps); } //打印欢迎信息 void WelcomeToGame() { //欢迎信息 system("title 贪吃蛇"); outtextxy(500, 350, "欢迎来到贪吃蛇小游戏"); outtextxy(600, 400, "随意点击屏幕继续. . ."); //随意点击屏幕 system("pause"); //清空屏幕,换画面 cleardevice(); //功能介绍信息 outtextxy(400, 350, "1.用 ↑ ↓ ← → 来控制蛇的移动,F3是加速,F4是减速"); outtextxy(400, 370, "2.加速能得到更高的分数"); outtextxy(600, 400, "随意点击屏幕继续. . ."); //打印尾巴信息 system("pause"); //清空桌面 cleardevice(); } //绘制地图 void CreateMap() { IMAGE img1; //玩游戏背景图 loadimage(&img1, "picture.jpg\picture.jpg", 900, 700, true); //从0,开始画图 putimage(0, 0, &img1); //画线矩形 setlinecolor(GREEN); //rectangle(0, 0, 900, 699); //rectangle(3, 3, 897, 697); //上 int i = 0; for (i = 0; i y = POS_Y; cur->next = NULL; if (ps->pSnake == NULL) { ps->pSnake = cur; } else { cur->next = ps->pSnake; ps->pSnake = cur; } } //打印蛇的位置 cur = ps->pSnake; while (cur) { picture(cur->x, cur->y); cur = cur->next; } //初始化贪吃蛇其他属性 ps->dir = RIGHT;//向右 ps->pFood = NULL; ps->Score = 0; ps->FoodWeight = 10; ps->SleepTime = 200; ps->status = OK; } //打印蛇身 void picture(int x, int y) { IMAGE img1; loadimage(&img1, "picture.jpg\picture2.jpg", SIZE_X, SIZE_Y, true); putimage(x, y, &img1); } //创建食物 void CreateFood(pSnake ps) { pSnakeNode pNext = (pSnakeNode)malloc(sizeof(SnakeNode)); if (pNext == NULL) { perror("pNext malloc fail!"); return; } int x = 0; int y = 0; circulate: do { //避免出现在(0,0) x = rand() % 871 + 15 ; y = rand() % 681 + 20; } while (x % 15 != 0 || y % 20 != 0); pSnakeNode cur = ps->pSnake; while (cur) { if (cur->x == x && cur->y == y) { goto circulate; } cur = cur->next; } pNext->x = x; pNext->y = y; pNext->next = NULL; //维护食物 ps->pFood = pNext; //打印食物 IMAGE img3; loadimage(&img3, "picture.jpg\R-C (1).jpg", 15, 20, true); putimage(x, y, &img3); } //玩游戏的过程 void GameRun(pSnake ps) { //打印帮助(规则)信息 PrintHelpInfo(); do { //当前分数情况 Score(ps); //向上 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)) { //游戏要暂停 pause(); } //加速 else if (KEY_PRESS(VK_F3)) { if (ps->FoodWeight FoodWeight += 2; ps->SleepTime -= 50; } } //减速 else if (KEY_PRESS(VK_F4)) { if (ps->FoodWeight > 4) { ps->FoodWeight -= 2; ps->SleepTime += 50; } } //退出游戏 else if (KEY_PRESS(VK_ESCAPE)) { ps->status = ESC; } //走一步 SnakeMove(ps); //休眠 Sleep(ps->SleepTime); } while (ps->status == OK); } //打印帮助(规则)信息 void PrintHelpInfo() { //规则 setlinecolor(MAGENTA); rectangle(918, 50, 1250, 180); outtextxy(930, 60, "1.不能穿墙,不能咬到自己"); outtextxy(930, 80, "2.用 ↑.↓.←.→ 来控制蛇的移动"); outtextxy(930, 100, "3.F3是加速,F4是减速"); outtextxy(930, 120, "4.一个食物基础分10分"); outtextxy(930, 140, "5.加速一次加2分,最大加6分"); outtextxy(930, 160, "6.减速一次减2分,最大减6分"); } //当前分数情况 void Score(pSnake ps) { char num1[10]; char num2[10]; sprintf(num1, "%d", ps->Score); sprintf(num2, "%d", ps->FoodWeight); outtextxy(930, 300, "当前总积分:"); outtextxy(1020, 300, num1); outtextxy(930, 340, "当前一个食物积分:"); outtextxy(1065, 340, num2); } //游戏要暂停 void pause() { while (true) { if (KEY_PRESS(VK_SPACE)) { break; } Sleep(200); } } //蛇走一步 void SnakeMove(pSnake ps) { //生成下一个走过的节点 pSnakeNode pNext = (pSnakeNode)malloc(sizeof(SnakeNode)); if (pNext == NULL) { perror("malloc tail"); return; } pNext->next = NULL; //向上 if (ps->dir == UP) { pNext->x = ps->pSnake->x; pNext->y = ps->pSnake->y - 20; } //向下 else if (ps->dir == DOWN) { pNext->x = ps->pSnake->x; pNext->y = ps->pSnake->y + 20; } //向右 else if (ps->dir == LEFT) { pNext->x = ps->pSnake->x - 15; pNext->y = ps->pSnake->y; } //向左 else if (ps->dir == RIGHT) { pNext->x = ps->pSnake->x + 15; pNext->y = ps->pSnake->y; } //下一个坐标处是否是食物 if (NextIsFood(ps, pNext)) { //是食物就吃掉 EatFood(ps, pNext); } else { //不是食物就正常一步 NotEatFood(ps, pNext); } //检测撞墙 KillByWall(ps); //检测撞到自己 KillBySelf(ps); } //判断下一个坐标是不是食物 bool NextIsFood(pSnake ps, pSnakeNode pNext) { if (ps->pFood->x == pNext->x && ps->pFood->y == pNext->y) { return true; } else { return false; } } //是食物就吃掉 void EatFood(pSnake ps, pSnakeNode pNext) { //头插 pNext->next = ps->pSnake; ps->pSnake = pNext; //打印蛇 pSnakeNode cur = ps->pSnake; while (cur) { picture(cur->x, cur->y);S 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) { picture(cur->x, cur->y); cur = cur->next; } //把尾巴打印成背景图片 IMAGE img4; loadimage(&img4, "picture.jpg\picture4.jpg", SIZE_X, SIZE_Y, true); putimage(cur->next->x, cur->next->y, &img4); //释放尾部 free(cur->next); cur->next = NULL; } //检测撞墙 void KillByWall(pSnake ps) { if (ps->pSnake->x == 885 || ps->pSnake->y == 680 || ps->pSnake->x == 15 || ps->pSnake->y == 20) { ps->status = KILL_BY_WALL; return; } } //检测撞到自己 void KillBySelf(pSnake ps) { pSnakeNode cur = ps->pSnake->next; while (cur) { if (cur->x == ps->pSnake->x && cur->y == ps->pSnake->y) { ps->status = KILL_BY_SELF; return; } cur = cur->next; } } //结束游戏后的工作 void GameEnd(pSnake ps) { switch (ps->status) { case ESC: outtextxy(330, 300, "主动退出游戏,正常退出\n"); break; case KILL_BY_WALL: outtextxy(330, 300, "很遗憾,撞墙了,游戏结束\n"); break; case KILL_BY_SELF: outtextxy(330, 300, "很遗憾,咬到自己了,游戏结束\n"); break; } //释放贪吃蛇的链表资源 pSnakeNode cur = ps->pSnake; pSnakeNode del = NULL; while (cur) { del = cur; cur = cur->next; free(del); } //把最后打印的食物打印成背景图片 IMAGE img4; loadimage(&img4, "picture.jpg\picture4.jpg", SIZE_X, SIZE_Y, true); putimage(ps->pFood->x, ps->pFood->y, &img4); free(ps->pFood); ps = NULL; } //判断是否再来一局 bool Determine(char str[]) { char arr[4] = "Yes"; int ret = strcmp(str, arr); if (ret == 0) { return true; } else { return false; } }
好啦今天的分享就到这里了,感谢观看。
文章版权声明:除非注明,否则均为VPS857原创文章,转载或复制请以超链接形式并注明出处。
还没有评论,来说两句吧...