当前位置: 首页 > news >正文

手撕——贪吃蛇小游戏(下)

引言

        上一章介绍了实现贪吃蛇小游戏必备的知识点。

        这章,让我们一起开启手搓核弹之旅吧。

先附上贪吃蛇代码的git:贪吃蛇小游戏_4_23 · Shown_shuai/learn_c - 码云 - 开源中国 (gitee.com) 

上一章的窗口: 

手撕——贪吃蛇小游戏(上)(前置)-CSDN博客

一、整体思维导图

二、创建贪吃蛇

在游戏开始前,先本地化,为了后面打印的需要

int main()
{//设置本地化setlocale(LC_ALL, "");return 0;
}

用链表来维护贪吃蛇

在snack.h中进行类型的声明 

1. 蛇身的节点

//蛇身体的节点
typedef struct SnakeNode
{//坐标int x;int y;//指向下一个节点的指针struct SnakeNode* nest;
}SnakeNode,* pSnakeNode;
//pSnakeNode是指向节点的指针类型
//等价于:typedef struct SnakeNode* pSnakeNode;

2. 创建贪吃蛇

贪吃蛇有许多需要维护的信息,将贪吃蛇创建为一个结构体。

//蛇的方向
enum DIRECTION
{UP = 1, //上DOWN,   //下LEFT,   //左RIGHT   //右
};
//蛇的状态
//游戏正常,撞墙,撞到自己,正常退出
enum GAME_STATUS
{OK,            //正常KILL_BY_WALL,  //撞墙KILL_BY_SELF,	//撞到自己END_NORMAL     //正常退出
};//贪吃蛇
typedef struct Snake
{pSnakeNode _pSnake;   //指向蛇头的指针pSnakeNode _pFood;    //指向食物的指针enum DIRECTION _dir;  //蛇的方向enum GAME_STATUES _status;  //游戏的状态int _food_weight;   //一个食物的分数int _score;         //总分数int _sleep_time;    //休息时间,时间越长,速度越慢,越短,速度越快
}Snake, *pSnake;

3.用枚举结构体,来枚举蛇属性的状态: 


//蛇的方向
enum DIRECTION
{UP = 1, //上DOWN,   //下LEFT,   //左RIGHT   //右
};//蛇的状态
//游戏正常,撞墙,撞到自己,正常退出
enum GAME_STATUS
{OK,            //正常KILL_BY_WALL,  //撞墙KILL_BY_SELF,	//撞到自己END_NORMAL     //正常退出
};

4. 定义特殊的符号,和特殊的数据

#define POS_X 24 //游戏刚开始的时候,蛇头的位置
#define POS_Y 5  //游戏刚开始的时候,蛇头的位置
#define WALL L'□'
#define BOOY L'●'
#define FOOD L'★'

三、开始游戏

1.设置游戏窗口大小和名字,隐藏光标

//1.设置窗口的大小,光标隐藏
system("mode con cols=110 lines=35");
system("title 贪吃蛇");HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);//获取标准输出的句柄(通过这个句柄来对控制台进行操作)CONSOLE_CURSOR_INFO curInfo;                     //存有光标信息的结构体
//获得和hOutput句柄相关的控制台上的光标信息,存放在curInfo中
GetConsoleCursorInfo(houtput, &curInfo); curInfo.bVisible = false;  //修改光标信息//设置获得和hOutput句柄相关的控制台上的光标信息
SetConsoleCursorInfo(houtput, &curInfo); 

2. 打印欢迎界面和功能介绍(上一章的知识点):

//光标定位
void SetPos(int x, int y)
{//获取标准输出的句柄(通过这个句柄来对控制台进行操作)HANDLE hOutput = NULL;hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//定位光标的位置COORD pos = { x, y };SetConsoleCursorPosition(hOutput, pos);
}//打印欢迎界面和功能介绍
void WelcomeToGame()
{SetPos(45, 16);printf("欢迎来的贪吃蛇小游戏\n");SetPos(48, 20);system("pause");system("cls"); //清空屏幕SetPos(35, 16);wprintf(L"用↑,↓,←,→来控制小蛇的移动,按F3加速,F4减速\n");SetPos(43, 18);wprintf(L"加速可以获得更高的分数");SetPos(45, 23);system("pause");system("cls");
}

3.绘制地图:

//3.绘制地图
void CreateMap()
{int i = 0;//上for (int i = 0; i < 35; i++){wprintf(L"%lc", WALL);}//下SetPos(0, 30);for (i = 0; i < 35; i++){wprintf(L"%lc", WALL);}//左for (i = 1; i < 30; i++){SetPos(0, i);wprintf(L"%lc\n", WALL);}//右for (i = 1; i < 30; i++){SetPos(68, i);wprintf(L"%lc\n", WALL);}SetPos(80, 0);
}

4.创建蛇

申请5个节点,使用头插,将蛇链接起来

//初始化蛇身
void InitSnake(pSnake ps)
{int i = 0;pSnakeNode cur = NULL;for (i = 0; i < 5; i++){cur = (pSnakeNode)malloc(sizeof(SnakeNode));if (cur == NULL){perror("InitSanke(): malloc fail!");return;}cur->nest = NULL;cur->x = POS_X + 2 * i;cur->y = POS_Y;if (ps->_pSnake == NULL){ps->_pSnake = cur;}else{cur->nest = ps->_pSnake;ps->_pSnake = cur;}}//打印:cur = ps->_pSnake;while (cur){SetPos(cur->x, cur->y);wprintf(L"%lc", BOOY);cur = cur->nest;}//设置贪吃蛇的属性ps->_dir = RIGHT;ps->_score = 0;ps->_food_weight = 10;ps->_sleep_time = 200;ps->_status = OK;
}

5.创建食物

//创建食物
void CreateFood(pSnake ps)
{int x = 0;  //取值范围:2 ~ 66  0~64int y = 0;  //          1 ~ 30  0~29
again:do{x = rand() % 65 + 2;y = rand() % 29 + 1;} while (x % 2 != 0);   //x 必须是 2 的倍数//x 和 y 的坐标不能和蛇的坐标相同pSnakeNode cur = ps->_pSnake;while (cur){if (x == cur->x && y == cur->y){goto again;}cur = cur->nest;}//创建食物的节点:pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode));if (pFood == NULL){perror("CreaterFood():malloc fail");return;}pFood->x = x;pFood->y = y;pFood->nest = NULL;SetPos(x, y);wprintf(L"%lc", FOOD);ps->_pFood = pFood;}

6.初始化游戏的所以代码:

//游戏的初始化
void GameStart(pSnake ps)
{//0.设置窗口的大小,光标隐藏system("mode con cols=110 lines=35");system("title 贪吃蛇");HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);//获取标准输出的句柄(通过这个句柄来对控制台进行操作)CONSOLE_CURSOR_INFO curInfo;                     //存有光标信息的结构体//获得和hOutput句柄相关的控制台上的光标信息,存放在curInfo中GetConsoleCursorInfo(houtput, &curInfo); curInfo.bVisible = false;  //修改光标信息//设置获得和hOutput句柄相关的控制台上的光标信息SetConsoleCursorInfo(houtput, &curInfo); //1.打印欢迎界面//2.功能介绍WelcomeToGame();//3.绘制地图CreateMap();//4.创建蛇InitSnake(ps);//5.创建食物CreateFood(ps);
}

四、运行游戏

1. 打印帮助信息

void PrintHelpInfo()
{SetPos(74, 15);wprintf(L"%s", L"不能穿墙,不能咬到自己");SetPos(71, 16);wprintf(L"%s", L"用↑,↓,←,→来控制小蛇的移动");SetPos(74, 17);wprintf(L"%s", L"按F3加速,F4减速\n");SetPos(71, 18);wprintf(L"%s", L"ESC:退出游戏,space:暂停游戏");SetPos(74, 20);wprintf(L"%s", L"制作者:@Run_Teenage");
}

2.定义一个宏,检测按键是否被按过

上一章有讲,可以去看一下

#define KEY_PRESS(kv) ((GetAsyncKeyState(kv)&1) ? 1:0)

3.游戏运行时,整体结构:

//游戏的运行:
void GameRun(pSnake ps)
{//打印帮助信息PrintHelpInfo();do{//打印总分数和食物的分数SetPos(74,10);printf("总分数:%d\n", ps->_score);SetPos(74, 11);printf("当前食物的分数:%2d\n", ps->_food_weight);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_DOWN) && ps->_dir != UP)//按下键时,并且向上走,不会有反应{ps->_dir = DOWN;}else if (KEY_PRESS(VK_SPACE)){//暂停Pause();}else if (KEY_PRESS(VK_F3)){//加速if (ps->_sleep_time > 80){ps->_sleep_time -= 30;ps->_food_weight += 2;}}else if (KEY_PRESS(VK_F4)){//减速if (ps->_food_weight > 2){ps->_sleep_time += 30;ps->_food_weight -= 2;}}else if (KEY_PRESS(VK_ESCAPE)){//正常退出游戏ps->_status = END_NORMAL;}//贪吃蛇走SnakeMove(ps);  //蛇走一步的过程Sleep(ps->_sleep_time);} while (ps->_status == OK);
}

4.实现暂停模块

//暂停
void Pause()
{while (1){Sleep(200);if (KEY_PRESS(VK_SPACE)){break;}}
}

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);
}

5.1检查下一个位置是不是食物

//检测下一个坐标是不是食物
int NextIsFood(pSnakeNode pn, pSnake ps)
{return (pn->x == ps->_pFood->x && pn->y == ps->_pFood->y);
}

5.2下一个是食物

//下一个位置是食物,吃掉食物
void EatFood(pSnakeNode pn, pSnake ps)
{//头插ps->_pFood->nest = ps->_pSnake;ps->_pSnake = ps->_pFood;//释放next节点free(pn);pn = NULL;pSnakeNode cur = ps->_pSnake;while (cur){SetPos(cur->x, cur->y);wprintf(L"%c", BOOY);cur = cur->nest;}ps->_score += ps->_food_weight;//重新创建食物CreateFood(ps);
}

5.3下一个位置不是食物

//下一个位置不是食物
void NoFood(pSnakeNode pn, pSnake ps)
{//头插pn->nest = ps->_pSnake;ps->_pSnake = pn;pSnakeNode cur = ps->_pSnake;while (cur->nest->nest != NULL){SetPos(cur->x, cur->y);wprintf(L"%c", BOOY);cur = cur->nest;}//把最后一个位置的节点打印成空格SetPos(cur->nest->x , cur->nest->y);printf("  ");//释放最后一个位置的节点free(cur->nest);//把倒数第二的节点的地址置为NULLcur->nest = NULL;
}

5.4检查蛇是否撞到墙

//检测蛇是否撞墙
void KillByWall(pSnake ps)
{if (ps->_pSnake->x == 0 || ps->_pSnake->x == 68 ||ps->_pSnake->y == 0 || ps->_pSnake->y == 29){ps->_status = KILL_BY_WALL;}
}

5.5检查蛇是否撞到自己

遍历蛇头后面的节点的坐标是否和蛇的头节点重合,重合,则说明自己撞到了自己

//检测蛇是否撞到自己
void KillBySelf(pSnake ps)
{pSnakeNode cur = ps->_pSnake->nest;while (cur){if (cur->x == ps->_pSnake->x && cur->y == ps->_pSnake->y){ps->_status = KILL_BY_SELF;break;}cur = cur->nest;}
}

五、游戏结束 (善后工作)

打印游戏是为什么结束的,释放贪吃蛇的节点

//结束游戏 —— 善后工作(释放内存之类的)
void GameEnd(pSnake ps)
{SetPos(25, 15);switch (ps->_status){case END_NORMAL:printf("正常结束游戏");break;case KILL_BY_WALL:printf("撞上墙了,游戏结束");break;case KILL_BY_SELF:printf("咬到自己了,游戏结束");break;}//释放蛇身节点pSnakeNode cur = ps->_pSnake;while (cur){pSnakeNode del = cur;cur = cur->nest;free(del);}
}

六、合并游戏的全部流程,处理细节问题

#include"snack.h"void test()
{int ch = 0;do{system("cls");//创建贪吃蛇Snake snake = { 0 };//初始化游戏//1.打印欢迎界面//2.功能介绍//3.绘制地图//4.创建蛇//5.创建食物//6.设置游戏的相关信息GameStart(&snake);//运行游戏GameRun(&snake);//结束游戏 —— 善后工作(释放内存之类的)GameEnd(&snake);while (_kbhit()){_getch();    //吸收蛇动时,按键盘的键值带来的影响,不然游戏会有小bug}SetPos(24, 16);printf("再来一局吗?(Y/N)");ch = getchar();while (getchar() != '\n');} while (ch == 'Y' || ch == 'y');SetPos(0, 32);
}
int main()
{//设置本地化setlocale(LC_ALL, "");srand((unsigned int )time(NULL));test();return 0;
}

七、所以代码可以在引言的git中拿取

相关文章:

  • 如何通过挖掘需求、SEO优化及流量变现成功出海?探索互联网产品的盈利之道
  • Java高频面试之并发编程-08
  • C++/SDL 进阶游戏开发 —— 双人塔防(代号:村庄保卫战 14)
  • 前端分页与瀑布流最佳实践笔记 - React Antd 版
  • ADC读取异常情况汇总
  • pcm数据不支持存储在json里面,需要先转base64
  • 机器学习——Seaborn练习题
  • 怎样给MP3音频重命名?是时候管理下电脑中的音频文件名了
  • 月之暗面开源-音频理解、生成和对话生成模型:Kimi-Audio-7B-Instruct
  • 【Java面试笔记:进阶】23.请介绍类加载过程,什么是双亲委派模型?
  • 第二章、在Windows上部署Dify:从修仙小说到赛博飞升的硬核指南
  • AI在医疗领域的10大应用:从疾病预测到手术机器人
  • madvise MADV_FREE对文件页统计的影响及原理
  • Java求职面试:从Spring Boot到微服务架构的全面解析
  • NGINX upstream、stream、四/七层负载均衡以及案例示例
  • qt编译报错error: ‘VideoSrcCtrl‘ does not name a type
  • vue中将html2canvas转成的图片传递给后台java
  • idea软件配置移动到D盘
  • 20250427在ubuntu16.04.7系统上编译NanoPi NEO开发板的FriendlyCore系统解决问题mkimage not found
  • Jetpack Compose多布局实现:状态驱动与自适应UI设计全解析
  • 马上评丨学生举报食堂饭菜有蛆,教育局应该护谁的犊子
  • 伊朗外长:美伊谈判进展良好,讨论了很多技术细节
  • 全球首台环形CT直线加速器在沪正式开机,系我国自主研发
  • 比亚迪一季度日赚亿元,净利润同比翻倍至91.55亿元
  • 科克托是说真话的骗子,而毕加索是一言不发、让大家去猜的人
  • 人民论坛:是民生小事,也是融合大势