C语言结课实战项目_贪吃蛇小游戏

news/2024/5/19 0:05:26

✨✨所属专栏:C语言✨✨

✨✨作者主页:嶔某✨✨

游戏源代码链接:function/贪吃蛇 · 钦某/c-language-learning - 码云 - 开源中国 (gitee.com)

最终实现效果:

实现基本的功能:

void set_pos(short x, short y);//定位光标位置void Game_Start(pSnake ps);//初始化void WelcomeToGame(void);//打印欢迎界面void CreateMap(void);//创建地图void InitSnake(pSnake ps);//初始化蛇身void CreateFood(pSnake ps);//创建食物void Game_Run(pSnake ps);//游戏运行逻辑void SnakeMove(pSnake ps);//蛇的移动bool NextIsFood(pSnakeNode pn,pSnake ps);//判断下一位置是否为食物void EatFood(pSnakeNode pn, pSnake ps);//吃掉食物void NoFood(pSnakeNode pn, pSnake ps);//下一个位置不是食物void KillByWall(pSnake ps);//检测撞墙void KillBySelf(pSnake ps);//检测撞自己void Game_End(pSnake ps);//游戏善后

• 贪吃蛇地图绘制
• 蛇吃⻝物的功能(上、下、左、右⽅向键控制蛇的动作)
• 蛇撞墙死亡
• 蛇撞⾃⾝死亡
• 计算得分
• 蛇⾝加速、减速
• 暂停游戏

运用到的知识:C语⾔函数、枚举、结构体、动态内存管理、预处理指令、链表、Win32API等

根据游戏进程解释代码:

这里分为下面几个函数对游戏进行实现:

system("cls");
//创建贪吃蛇
pSnakeNode pSnake = NULL;Snake snake = { 0 };//初始化游戏
Game_Start(&snake);//运行游戏
Game_Run(&snake);结束游戏
Game_End(&snake);

游戏初始化:

void Game_Start(pSnake ps)//初始化
{//0.设置窗口大小/名字system("mode con cols=100 lines=30");system("title 贪吃蛇");//1.隐藏光标HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);CONSOLE_CURSOR_INFO CursorInfo;GetConsoleCursorInfo(houtput,&CursorInfo);CursorInfo.bVisible = false;SetConsoleCursorInfo(houtput,&CursorInfo);//2.打印欢迎界面,介绍功能WelcomeToGame();//3.绘制地图CreateMap();//4.创建蛇InitSnake(ps);//5.创建食物CreateFood(ps);
}

首先进入游戏,我们应该将窗口名称改为 “贪吃蛇” 并将光标隐藏掉。再在中间打印游戏信息。

这里用到的函数有:

(1)system("mode con cols=100 lines=30");

将窗口设置为100列,30行

(2)system("title 贪吃蛇");

将title设置为贪吃蛇

(3)system("pause");

暂停程序,按下任意键继续

(4)system("cls");

清理屏幕

/*******************************************/
//0.设置窗口大小/名字
system("mode con cols=100 lines=30");
system("title 贪吃蛇");
/*******************************************/
void set_pos(short x, short y)
{//获得标准输出设备的句柄HANDLE houtput = NULL; houtput = GetStdHandle(STD_OUTPUT_HANDLE);//定位光标的位置COORD pos = { x,y };SetConsoleCursorPosition(houtput,pos);
}
/*******************************************/
void WelcomeToGame()
{set_pos(40, 12);wprintf(L"欢迎来到贪吃蛇小游戏");set_pos(42,18);system("pause");system("cls");set_pos(30, 12);wprintf(L"用上下左右控制蛇的移动,按 {[ 加速, ]} 减速\n");set_pos(38, 13);wprintf(L"加速可以得到更高的分数\n");set_pos(42, 18);system("pause");system("cls");
}
/********************************************/

 之后我们要把地图打印出来:

分别打印上下左右的墙,将墙宏定义为WALL,字符为’□‘

void CreateMap()
{//上int i = 0;for (i = 0; i < 29; i++){wprintf(L"%lc", WALL);}//下set_pos(0,25);for (i = 0; i < 29; i++){wprintf(L"%lc", WALL);}//左for (i = 1; i < 25; i++){set_pos(0, i);wprintf(L"%lc", WALL);}//右for (i = 1; i < 25; i++){set_pos(56, i);wprintf(L"%lc", WALL);}
}

然后我们将贪吃蛇创建出来,将蛇有关的信息用结构体和枚举类型封装起来,将蛇身用链表维护。

蛇头指针:指向链表头节点的指针,方便对蛇身进行维护

食物指针:从开发角度来说,其实食物也是蛇的一个节点,当蛇头的下一个位置为食物时,将食物的节点头插到蛇身上面。

方向:对蛇的方向进行枚举

游戏状态:方便判断蛇的状态:(1)正常(2)撞墙(3)撞到自己(4)正常退出每一次while循环后判断游戏状态

食物权重:每次加速食物权重+2,减速-2。

总成绩:每吃掉一个食物,蛇身长度+1,分数+=食物权重。

每走一步的缓冲时间:缓冲时间越短,蛇走得越快;反之越慢。

#define POS_X 24
#define POS_Y 5#define WALL L'□'
#define BODY L'●'
#define FOOD L'★'
typedef struct SnakeNode
{//坐标int x;int y;//下一个节点struct SnakeNode* next;
}SnakeNode,*pSnakeNode;enum DRECCTION//方向
{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 DRECCTION _dir;//蛇的方向enum GAME_STAYUS _status;//游戏状态int _food_weight;//一个食物都分数int _score;//总成绩int _sleep_time;//休息时间}Snake,*pSnake;
/************************************/
void InitSnake(pSnake ps)//初始化蛇
{int i = 0;pSnakeNode cur = NULL;for (int i = 0; i < 5; i++){cur = (pSnakeNode)malloc(sizeof(SnakeNode));if (cur == NULL){perror("InitSnake()::malloc()");return;}cur->next = NULL;cur->x = POS_X + 2 * i;cur->y = POS_Y;//头插插入if (ps->_pSnake == NULL)ps->_pSnake = cur;else{cur->next = ps->_pSnake;ps->_pSnake = cur;}}cur = ps->_pSnake;while (cur){set_pos(cur->x,cur->y);wprintf(L"%lc", BODY);cur = cur->next;}//设置蛇的属性ps->_dir = RIGHT;//默认ps->_score = 0;ps->_food_weight = 10;ps->_sleep_time = 200;//单位为msps->_status = OK;
}

创建食物

生成随机数,赋给x,y。

这里x和y都有范围,不能超出地图边界,并且不能与蛇身重合。

void CreateFood(pSnake ps)//创建食物
{int x = 0;int y = 0;
again:do{x = rand() % 52 + 2;//2~54y = rand() % 24 + 1;//1~25} while (x % 2 != 0);//x为2的倍数//不能和蛇身的坐标相同pSnakeNode cur = ps->_pSnake;while (cur){if (x == cur->x && y == cur->y)goto again;cur = cur->next;}//创建食物节点pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode));if (pFood == NULL){perror("CreateFood()::malloc");return;}pFood->x = x;pFood->y = y;pFood->next = NULL;set_pos(x, y);wprintf(L"%lc", FOOD);ps->_pFood = pFood;
}

 游戏开始:


void Game_Run(pSnake ps)//游戏运行逻辑
{//打印帮助信息PrintHelpInfo();do{//打印分数,食物权重set_pos(64, 8);printf("总分数:%d\n",ps->_score);set_pos(64, 9);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_SPACE)){//暂停Pause();}else if (KEY_PRESS(VK_ESCAPE)){//正常退出ps->_status = END_NORMAL;}else if (KEY_PRESS(VK_OEM_6)){//加速if (ps->_sleep_time > 80){ps->_sleep_time -= 30;ps->_food_weight += 2;}}else if (KEY_PRESS(VK_OEM_4)){//减速if (ps->_food_weight > 2){ps->_sleep_time += 30;ps->_food_weight -= 2;}}SnakeMove(ps);//蛇走一步的过程Sleep(ps->_sleep_time);} while (ps->_status == OK);
}

用dowhile循环对主体进行不断刷新

每次循环后让系统暂停一段时间(初始为200ms)

打印相关信息

蛇每走一步,分数都有可能变化,每次循环都打印一次。

按键检测

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

(1)用虚拟键值检测是否按下上下左右键,按下相应键并且蛇的当前方向不能与之相反。

(2)检测是否按下空格,按下就进函数:

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

 再次按下空格退出函数。

(3)检测加速减速,按下加速键就将缓冲时间变短,食物权重增加;反之变长,食物权重减少。(这里也是有范围的,食物权重不能为负数,也不能过大)

蛇的移动

进入函数,创建蛇头的下一个位置所在的节点,并根据方向算出所在位置。

判断下一位置是否为食物:
bool NextIsFood(pSnakeNode pn, pSnake ps)//判断下一位置是否为食物
{return (ps->_pFood->x == pn->x && ps->_pFood->y == pn->y);
}
 下一位置是食物:

先将新节点头插进蛇身,打印蛇身在屏幕上,总分数加上食物权重。

再次创建食物。

void EatFood(pSnakeNode pn, pSnake ps)
{//头插ps->_pFood->next = ps->_pSnake;ps->_pSnake = ps->_pFood;//释放下一个位置的节点free(pn);pn = NULL;//打印pSnakeNode cur = ps->_pSnake;while (cur){set_pos(cur->x, cur->y);wprintf(L"%lc",BODY);cur = cur->next;}ps->_score += ps->_food_weight;//重新创建食物CreateFood(ps);
}
下一位置不是食物:

创建下一位置的节点,也是头插,但是在打印蛇身之后,将蛇尾位置打印两个空格(不打印空格蛇身就不会清除一直留在屏幕上:拖尾),将蛇尾的节点释放掉。(cur->next一定要置空,不能让它为野指针)

void NoFood(pSnakeNode pn, pSnake ps)//下一个位置不是食物
{//头插pn->next = ps->_pSnake;ps->_pSnake = pn;pSnakeNode cur = ps->_pSnake;while (cur->next->next){set_pos(cur->x,cur->y);wprintf(L"%lc",BODY);cur = cur->next;}//把最后一个节点打印空格set_pos(cur->next->x,cur->next->y);printf("  ");//将最后一个节点释放free(cur->next);//将倒数第二个节点置为空cur->next = NULL;
}
 检测是否撞墙:

判断蛇头坐标位置是否超出范围,若超出范围,将蛇的状态改为KILL_BY_WALL

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;}
}
检测是否撞到自己:

遍历蛇身链表,若坐标重合,将蛇的状态改为KILL_BY_SELF,并且跳出循环。

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;break;}cur = cur->next;}
}
 蛇移动一步的总函数:
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);
}

 游戏结束:

判断游戏结束的原因,并打印。

释放蛇身链表

void Game_End(pSnake ps)//游戏善后
{set_pos(24,12);switch (ps->_status){case END_NORMAL:printf("您主动结束游戏\n");break;case KILL_BY_WALL:printf("您被墙单杀了\n");break;case KILL_BY_SELF:printf("您被自己单杀了\n");break;}//释放蛇身链表pSnakeNode cur = ps->_pSnake;while (cur){pSnakeNode del = cur;cur = cur->next;free(del);}
}

后续改进:

1.穿墙

2.食物分类

3.多个食物

4.双人游戏

……

 本期博客到这里就结束了,如果有什么错误,欢迎指出,如果对你有帮助,请点个赞,谢谢!


http://www.mrgr.cn/p/52602153

相关文章

需求 分析

需求分析的任务 需求分析的任务 1、需求分析是软件定义时期的最后一个阶段&#xff0c;它的基本任务是准确地回答“系统必须做什么?”这个问题。 2、确定系统必须完成哪些工作&#xff0c;也就是对目标系统提出完整、准确、清晰、具体的要求。 3、系统分析员应该写出软件需求…

世强硬创获昕感科技授权代理,SiC MOSFET实现超低导通电阻

近日&#xff0c;世强先进&#xff08;深圳&#xff09;科技股份有限公司&#xff08;下称“世强先进”&#xff09;获北京昕感科技有限责任公司&#xff08;下称“昕感科技”&#xff0c;英文名&#xff1a;NEXIC&#xff09;授权代理&#xff0c;为光伏、储能、电网、新能源汽…

一文搞懂“对账系统”

对于每天都需要对账的生意来讲,如果遇上大的额数,就会出现困难,为了提升核对效率以及准确性,对账系统有一定的改变是避免不了的,下面是笔者整理的关于“对账系统”的内容分享,想要了解相关内容的可以接着继续往下了解了解哦!账目核算是财务工作的必要部分,随着线上交易…

UE4纯C++实现游戏快捷栏之将快捷栏注册到玩家状态

我们有了UI有了物品信息,接下来我们便需要给每一个玩家绑定一个快捷栏了,我们分以下几部分来实现我们玩家的快捷栏。1.Types.h struct ShortcutContainer:我们定义快捷栏的单个容器结构体,其内部存储玩家所引用的快捷栏的单个格子的信息数据基础的,我们将在结构体中保存{单…

个人博客系统的设计与实现

https://download.csdn.net/download/liuhaikang/89222885http://点击下载源码和论文 本 科 毕 业 设 计&#xff08;论文&#xff09; 题 目&#xff1a;个人博客系统的设计与实现 专题题目&#xff1a; 本 科 毕 业 设 计&#xff08;论文&#xff09;任 务 书 题 …

buuctf-pwn-1.test_your_nc

简单题,知道nc怎么用就好 基本用法 nc 地址 端口号 地址可以是一个域名,也可以是ip地址,地址和端口中间不是:,而是一个空格 连接上之后直接ls就看到flag文件了,cat flag查看flag文件的内容获取flag 注意windows上的nc需要下载,linux上一般会自带flag{c41a6f35-bb45-4c01…

十六进制转换

十进制转换为十六进制——利用栈的“先进后出”的思想 题目:设计一个进制转换程序,使用顺序栈设计一个把十进制数转换为十六进制数的接口,实现当通过键盘输入一个非负的十进制数,可以在终端输出对应的十六进制数。 思路: 1.输入一个十进制数num 2.定义一个链表 3.将余数(…

【Python数据库】Redis

文章目录 [toc]数据插入数据查询数据更新数据删除查询存在的所有key 个人主页&#xff1a;丷从心 系列专栏&#xff1a;Python数据库 学习指南&#xff1a;Python学习指南 数据插入 from redis import Redisdef insert_data():redis_cli Redis(hostlocalhost, port6379, db…

linux(麒麟 centos7)安装7z

1、下载7-Zip下载地址:7-Zip - 程序下载2、解压mkdir 7zip --创建文件夹7zipmv 7z2301-linux-x64.tar.xz 7zip/ --移动cd 7zip tar -xvJf 7z2301-linux-x64.tar.xz --解压 输入ll 查看解压后的文件3、安装cp 7zzs /usr/local/bin/ 输入7zzs 查看是否安装成功4、…

NFT tokenURI使用去中心化IPFS链接

前言tokenURI指向 存放NFT Metadata信息的json文件 所在的URLjson文件最好用去中心化方式存储,例如IPFS 使用IPFS存储文件 自己搭建IPFS需要下载客户端和保持节点运行 较麻烦,我们可采用第三方服务商提供的服务 例如Pinata、4everland等,我们以4everland(4everland.org)为例…

iOS ------ Block的总结

前面看了Block的基本知识&#xff0c;和一些源码。但对于block怎么用的还不了解&#xff0c;代码中出现block会看不懂&#xff0c;现在来具体看一下Block的用法并做个总结。 1.Block是什么 block对象是一个C语言结构体&#xff0c;可以并入C和OC的代码中&#xff0c;Block本质…

数据结构算法题

数据结构算法题 通过键盘输入一个包括 ( 和 ) 的字符串string ,判断字符串是否有效。要求设计算法实现检查字符串是否有效,有效的字符串需满足以下条件: A.左括号必须用相同类型的右括号闭合。 B.左括号必须以正确的顺序闭合。 C.每个右括号都有一个对应的相同类型的左括号…

07节-51单片机-矩阵键盘

文章目录 1矩阵键盘原理2.扫描的概念3.弱上拉4.实战-实现矩阵键盘对应按钮按下显示对应值4.1配置代码模板 5.键盘锁 1矩阵键盘原理 在键盘中按键数量较多时&#xff0c;为了减少I/O口的占用&#xff0c;通常将按键排列成矩阵形式 采用逐行或逐列的“扫描”&#xff0c;就可以读…

爬虫抓取网站数据

Fiddler 配置fiddler工具结合浏览器插件 配置fiddler Tools--Options 抓包技巧 谷歌浏览器开启无痕浏览,使用SwitchyOmega配置好代理端口 Ctrl x 清理所有请求记录,可以删除指定不需要日志方便观察 设置按请求顺序 观察cookie,观察请求hesder cookie和row返回结果 Swit…

60天零基础干翻C++————双指针问题

移动零 题目链接&#xff1a;移动零 本题是典型的双指针算法中的数组划分类型&#xff1a; 以下面为例&#xff1a; 删除该数组所有的0. 下面引入两个指针&#xff1a; 这两个指针将区间分为了三段 初始如图 定义两个指针&#xff1a; cur会有两种情况&#xff1a; 遇到非…

UE4 寻路

寻路 就是对图的遍历,目前主要处理对可转换为二维矩阵的方格图遍历 DFS 与 BFS 两种最基础的遍历方式,DFS采用回溯思想,将从起点开始沿着一个方向搜索,直到超出界限(回溯)或者到达目的地(返回结果) //只考虑上下左右四个方向 vector<vector<pair{int,int}>> re…

Postman之全局变量与环境变量配置

实际开发中可能需要不停切换环境&#xff0c;接口中来回输入环境地址比较麻烦&#xff0c;故而通过定义变量来节约频繁更换测试地址所耗费的时间。Postman 允许定义自己的全局变量&#xff08;Globals&#xff09;与环境变量&#xff08;Environment&#xff09;&#xff0c;最…

03 OLED显示屏实现

目录前言一、软件模拟IIC协议1.开启IIC协议2.结束IIC协议3.传输数据二、OLED的操作1.传输数据的准备2.写入命令3.写入数据4.初始化函数5.设置光标6.显示字符7.显示字符串8.清屏9.显示汉字10.显示图片11.显示动图三、完整代码总结 前言 这一章主要是上一节没有讲完的项目的一个编…

ETL工具-nifi干货系列 第十七讲 nifi Input PortOut Port 实战教程

1、端口(Port),包含输入端口(Input Port)和输出端口(Out Port ) 使用一个或多个处理组构建的数据流需要一种方式将处理组连接到其他数据流组件。 处理组和处理组之间可以通过使用端口来进行连接。这里的端口和kettle中的步骤【复制记录到结果】、【从结果获取记录】是类…