当前位置:首页 » 《关于电脑》 » 正文

【C语言BOOS篇】贪吃蛇 一场代码与逻辑的较量(含游戏代码)

2 人参与  2024年11月23日 18:01  分类 : 《关于电脑》  评论

点击全文阅读


    

?️专栏:【C语言boss篇】

?主页:f狐o狸x


目录

一、 游戏效果展示

二、 游戏逻辑实现分析

        2.1 游戏主体逻辑

        2.2 游戏实现分析

三、贪吃蛇游戏设计

        3.1 前期准备工作

        3.2 数据结构设计

        3.3 地图、蛇身、食物设计

        3.4 游戏主逻辑

        3.5 菜单

        3.6 游戏开始

        3.6.1 打印欢迎信息

        3.6.2 绘制地图

        3.6.3 初始化蛇

        3.6.4创建食物

        3.7 游戏运行

        3.7.1 打印帮助信息

        3.7.2 蛇身移动

3.7.2.1 判断下一步是否有食物

3.7.2.2 吃到食物

3.7.2.3 正常走下一步(没吃到食物)

3.7.2.4 检测是否撞墙

3.7.2.5 检测是否咬到自己

        3.8 游戏结束

        3.8.1 保存分数

四、结语 


         贪吃蛇基本上是我们家喻户晓的小游戏了,在狐狸还在是小学幼儿园时期的时候就经常玩,后来到了初中随着互联网的发展,《贪吃蛇大作战》这个游戏又在众多网游中脱颖而出,因此身为大学生的狐狸,我决定用C语言来实现这个经典的小游戏——贪吃蛇

一、 游戏效果展示

        废话不多说,直接看结果

二、 游戏逻辑实现分析

        2.1 游戏主体逻辑

        2.2 游戏实现分析

        不难看出游戏主逻辑就三个函数,GameStart 、GameRun 、GameEnd。他们分别负责了游戏前的准备工作、游戏运行时贪吃蛇的移动和判断是否结束游戏、游戏结束时的善后工作,那我们话不多说,直接开始

三、贪吃蛇游戏设计

        3.1 前期准备工作

        如果我们想把贪吃蛇小游戏弄得好看一些就需要一些调节颜色的函数,并且打印蛇身、墙体都用到了特殊的字符,因此需要先把修改适配器到中文状态,后面我们生成食物的时候需要的坐标也是随机的,因此还要准备生成随机数

int main(){//修改适配器到中文状态setlocale(LC_ALL, "");//设置随机数srand((unsigned)time(NULL));test();return 0;}
void color(int c){SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), c); //颜色设置}

         这里是颜色的十进制对照表:

        这样我们后面只需要在打印前设置好想要的颜色就行了,想了解的朋友可以去:SetConsoleTextAttribute_360百科

        我们还需要获取案件情况来判断当前蛇运行的状态

//监测按键#define KEY_PRESS(VK)  ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )

         封装一个设置光标位置的函数方便操作

void SetPos(int x, int y){//获得设备句柄HANDLE hanlde = GetStdHandle(STD_OUTPUT_HANDLE);//根据句柄设置光标的位置COORD pos = { x, y };SetConsoleCursorPosition(hanlde, pos);}

        准备工作完了之后我们就可以开始设计下面的函数了

//颜色设置void color(int c);//打印分数void PrintScore(pSnake ps);//游戏开始前的初始化void GameStart(pSnake ps);//设置光标位置信息void SetPos(int x, int y);//打印欢迎信息void WelcomeToGame();//绘制地图void CreateMap();//初始化蛇void InitSnake(pSnake ps);//创建食物void CreateFoods(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);//保存分数void SaveScore(pSnake ps);

        3.2 数据结构设计

        这里我用的是一个链表来管理的蛇身,因为在游戏过程中,贪吃蛇每吃一个食物都会增长一个蛇身节点,正好用链表节点来控制蛇的长度

//蛇身结构体的定义typedef struct SnakeNode{int x;int y;struct SnakeNode* next;}SnakeNode,* pSnakeNode;

        另外我们需要用一个结构体来管理贪吃蛇游戏的信息,包括蛇身、食物、总分、当前分数、当前一个食物的分数、蛇的速度、当前游戏状态和当前蛇运动状态(上、下、左、右)

//贪吃蛇结构体定义typedef struct Snake{pSnakeNode pSnake; //维护蛇的指针,指向蛇头pSnakeNode pFood; //指向食物的指针int Score;//总分int ScoreMax[4];//最高分int ScoreSize;//最高分有效个数int FoodWeight;//每个食物的分数int SleepTime;//蛇的速度enum GAME_STATUS status;//游戏当前状态enum SNAKE_STATUS dir;//蛇当前走的方向}Snake,* pSnake;

        这里蛇的运动状态和游戏当前状态都可以一一列举出来,所以他俩可以使用枚举

//游戏状态枚举enum GAME_STATUS{OK = 1, //游戏正常运行状态ESC, //按esc键盘退出游戏,正常退出KILL_BY_WALL,//撞墙退出游戏KILL_BY_SELF,//咬到自己退出游戏};//蛇当前走的方向枚举enum SNAKE_STATUS{UP=1,//向上DOWN,//向下LEFT,//向左RIGHT//向右};

        3.3 地图、蛇身、食物设计

        用“ ■ ”表示地图边界、“ ★ ”表示一个食物节点、“ ● ”表示蛇头、“ ○ ”表示蛇身

        3.4 游戏主逻辑

void test(){//设置控制台信息,窗口大小,窗口名system("mode con cols=100 lines=30");system("title:贪吃蛇");//隐藏光标HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);CONSOLE_CURSOR_INFO CursorInfo;GetConsoleCursorInfo(handle, &CursorInfo);//获取控制台光标信息CursorInfo.bVisible = false;SetConsoleCursorInfo(handle, &CursorInfo);Snake snake = { 0 };int input = 0;do {system("cls");menu();scanf("%d", &input);switch (input){case 1:game(&snake);SetPos(15, 13);printf("再来亿吧~");SetPos(15, 14);system("pause");break;case 2:PrintScore(&snake);break;case 0:system("cls");color(15);//字体颜色 白 SetPos(40, 13);printf("古德拜拜~~");SetPos(0, 27);break;default:SetPos(40, 18);printf("输入错误请重新输入");}} while (input);}void game(pSnake ps){GameStart(ps);//游戏开始前的初始化GameRun(ps);//游戏运行GameEnd(ps);//游戏结束后的善后工作}

        3.5 菜单

void menu(){color(6);//墙体颜色 棕 SetPos(36, 11);int i = 0;//上边框for (i = 0; i < 28; i += 2){wprintf(L"%lc", WALL);}//下边框SetPos(36, 19);for (i = 0; i < 28; i += 2){wprintf(L"%lc", WALL);}//左边框for (i = 1; i < 8; i++){SetPos(36, i + 11);wprintf(L"%lc", WALL);}//右边框for (i = 1; i < 8; i++){SetPos(62, i + 11);wprintf(L"%lc", WALL);}SetPos(40, 13);color(15);//字体颜色 白 printf("欢迎来到贪吃蛇小游戏");SetPos(40, 14);printf("1. 开始游戏");SetPos(40, 15);printf("2. 查看最高分");SetPos(40, 16);printf("0. 退出游戏");SetPos(40, 17);printf("请选择:");}

        3.6 游戏开始

void GameStart(pSnake ps){//打印欢迎信息WelcomeToGame();//绘制地图CreateMap();//初始化蛇InitSnake(ps);//创建食物CreateFoods(ps);}

        3.6.1 打印欢迎信息

        打印一些初始信息方便玩家参考

void WelcomeToGame(){system("cls");SetPos(40, 13);printf("欢迎来到贪吃蛇小游戏");SetPos(44, 26);system("pause");system("cls");SetPos(20, 13);printf("用 ↑ . ↓ . ← . → 来控制蛇的移动,F3是加速,F4是减速");SetPos(20, 14);printf("加速能得到更高的分数");SetPos(44, 26);system("pause");system("cls");}

        3.6.2 绘制地图

void CreateMap(){int i = 0;//控制台主机 //上边框color(6);//墙体颜色 棕 SetPos(0, 0);for (i = 0; i < 58; i+=2){wprintf(L"%lc", WALL);}//下边框SetPos(0, 26);for (i = 0; i < 58; i += 2){wprintf(L"%lc", WALL);}//左边框for (i = 1; i < 26; i++){SetPos(0, i);wprintf(L"%lc", WALL);}//右边框for (i = 1; i < 26; i++){SetPos(56, i);wprintf(L"%lc", WALL);}  }

        3.6.3 初始化蛇

//初始化蛇void InitSnake(pSnake ps){//创建5个蛇身节点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->x = POS_X + i * 2;cur->y = POS_Y ;cur->next = NULL;if (ps->pSnake == NULL){ps->pSnake = cur;}else{cur->next = ps->pSnake;ps->pSnake = cur;}}//打印蛇cur = ps->pSnake;//打印蛇头color(10);//蛇的颜色 绿SetPos(cur->x, cur->y);wprintf(L"%lc", HEAD);cur = cur->next;//打印蛇身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;ps->status = OK;}

        3.6.4创建食物

        食物其实也可以算为是一个蛇的节点

//创建食物void CreateFoods(pSnake ps){//随机生成食物坐标int x = 0;int y = 0;again:do{x = rand() % 53 + 2;y = rand() % 24 + 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("CreateFoods::malloc");return;}pFood->x = x;pFood->y = y;ps->pFood = pFood;//打印食物SetPos(x, y);color(12);//食物颜色 红wprintf(L"%lc", FOOD);}

        3.7 游戏运行

//游戏运行void GameRun(pSnake ps){//打印帮助信息PrintHelpInfo();do{//打印当前分数情况color(15);//字体的颜色 白SetPos(62, 15);printf("当前食物分值:%d", ps->FoodWeight);SetPos(62, 16);printf("总分:%d", ps->Score);SetPos(62, 17);printf("最高分:%d", ps->ScoreMax[0]);//检测按键//上,下,左,右,ESC,空格,F3,F4if (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_ESCAPE)){ps->status = ESC;break;}else if (KEY_PRESS(VK_SPACE)){//游戏暂停键pause();//空格暂停和恢复暂停}else if (KEY_PRESS(VK_F3)){if (ps->SleepTime >= 80){ps->SleepTime -= 30;ps->FoodWeight += 2;}}else if (KEY_PRESS(VK_F4)){if (ps->FoodWeight > 2){ps->SleepTime += 30;ps->FoodWeight -= 2;}}//走一步SnakeMove(ps);//睡眠一下Sleep(ps->SleepTime);} while (ps->status == OK);}

        3.7.1 打印帮助信息

//打印帮助信息void PrintHelpInfo(){color(15);//字体的颜色 白SetPos(62, 12);printf("1. 不能穿墙,不能咬到自己");SetPos(62, 13);printf("2. 用 ↑.↓.←.→ 来控制蛇的移动");SetPos(62, 14);printf("3. F3是加速,F4是减速");SetPos(62, 24);printf("制作:狐狸");SetPos(62, 25);printf("祝大家都好运连连~");}

        3.7.2 蛇身移动

void SnakeMove(pSnake ps){//确定下一步的位置pSnakeNode pnext = (pSnakeNode)malloc(sizeof(SnakeNode));if (pnext == NULL){perror("SnakeMove::malloc");return;}pnext->next = NULL;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;}//判断下一步是否有食物int ret = NextIsFood(ps, pnext);if (NextIsFood(ps, pnext)){EatFood(ps, pnext);}else{//正常走一步NotEatFood(ps, pnext);}//检测是否撞墙KillByWall(ps);//检测是否咬到自己KillBySelf(ps);}
3.7.2.1 判断下一步是否有食物
//判断是否有食物int NextIsFood(pSnake ps, pSnakeNode pnext){if (ps->pFood->x == pnext->x && ps->pFood->y == pnext->y){return 1;}else{return 0;}}
3.7.2.2 吃到食物
//吃到食物void EatFood(pSnake ps, pSnakeNode pnext){//头插pnext->next = ps->pSnake;ps->pSnake = pnext;//打印蛇pSnakeNode cur = ps->pSnake;//打印蛇头color(10);//蛇的颜色 绿SetPos(cur->x, cur->y);wprintf(L"%lc", HEAD);cur = cur->next;//打印蛇身while (cur){SetPos(cur->x, cur->y);wprintf(L"%lc", BODY);cur = cur->next;}//计分ps->Score += ps->FoodWeight;//释放旧食物free(ps->pFood);//创建新的食物CreateFoods(ps);}
3.7.2.3 正常走下一步(没吃到食物)
void NotEatFood(pSnake ps, pSnakeNode pnext){//头插pnext->next = ps->pSnake;ps->pSnake = pnext;//删除尾巴pSnakeNode cur = pnext;//打印蛇头color(10);//蛇的颜色 绿SetPos(cur->x, cur->y);wprintf(L"%lc", HEAD);cur = cur->next;//打印蛇身//打印+找尾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;}
3.7.2.4 检测是否撞墙
//检测是否撞墙void 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;}}
3.7.2.5 检测是否咬到自己
//检测是否撞到自己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;}}

        3.8 游戏结束

void  GameEnd(pSnake ps){SetPos(15, 12);color(15);//字体颜色 白switch (ps->status){case ESC:printf("ESC主动退出游戏,正常退出\n");break;case KILL_BY_WALL:printf("这你都能撞墙??菜啊\n");break;case KILL_BY_SELF:printf("你是真饿了!\n");break;}//保存分数SaveScore(ps);//释放贪吃蛇的链表资源pSnakeNode cur = ps->pSnake;pSnakeNode del = NULL;while (cur){del = cur;cur = cur->next;free(del);}free(ps->pFood);ps->pSnake = NULL;ps = NULL;}

        3.8.1 保存分数

void SaveScore(pSnake ps){//写入数据int i = 0;if (ps->ScoreSize < 3){ps->ScoreMax[ps->ScoreSize] = ps->Score;ps->ScoreSize++;}else{for (i = 0; i < 3; i++){if (ps->Score > ps->ScoreMax[i]){int j = i;for (j = i; j < 3; j++){ps->ScoreMax[j + 1] = ps->ScoreMax[j];}ps->ScoreMax[i] = ps->Score;break;}}} i = 0;for (i = 0; i < 2; i++){int j = 0;for (j = 0; j < 2 - i; j++){if (ps->ScoreMax[j] < ps->ScoreMax[j + 1]){int tmp = ps->ScoreMax[j];ps->ScoreMax[j] = ps->ScoreMax[j + 1];ps->ScoreMax[j + 1] = tmp;}}}}

四、结语 

        不知不觉间,我已经学习三个月的编程了,感谢各位在我学习时的支持与陪伴,希望未来我们能一直坚持学习下去

        “ 请不要相信胜利就像山坡上的蒲公英一样唾手可得,但是请相信生活中总有美好值得我们全力以赴,哪怕粉身碎骨!”———2022卡塔尔世界杯解说贺炜


点击全文阅读


本文链接:http://zhangshiyu.com/post/190976.html

<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1