医院网站推广方法,ps5如何定制网络,企业网站开发公司排行榜,发布php做的网站关注小庄 顿顿解馋૮(˶ᵔ ᵕ ᵔ˶)ა 新年快乐呀小伙伴 引言#xff1a; 小伙伴们应该都有一个做游戏的梦吧#xff1f;今天让小庄来用C语言简单实现一下我们的童年邪典贪吃蛇#xff0c;顺便巩固我们的C语言知识#xff0c;请安心食用~ 文章目录 贪吃蛇效果一.游戏前工作… 关注小庄 顿顿解馋૮(˶ᵔ ᵕ ᵔ˶)ა 新年快乐呀小伙伴 引言 小伙伴们应该都有一个做游戏的梦吧今天让小庄来用C语言简单实现一下我们的童年邪典贪吃蛇顺便巩固我们的C语言知识请安心食用~ 文章目录 贪吃蛇效果一.游戏前工作 控制台程序 光标设置 Win32API 绘制地图 控制台坐标系 控制台坐标的设置 墙壁图案 帮助信息 贪吃蛇整体维护蛇的初始化食物创建其他内容初始化 二.游戏运行时工作 游戏逻辑 按键检测 蛇的移动蛇是否吃到食物蛇是否撞墙和咬到自身 三.游戏善后工作 贪吃蛇资源的释放 贪吃蛇游戏状态 拓展 贪吃蛇效果
如下是我们将实现的效果 请看vcr
一.游戏前工作 控制台程序
平时我们运行程序弹出的黑框框就是控制台程序我们平时可能不在意它的字体颜色大小等等…我们可以根据我们的游戏进行一些调整
控制台程序标题 sysytem(title 贪吃蛇); //包含头文件window.h 我们可以根据自己的需要修改标题
控制台程序宽度和高度 ssystem(mode con colsxxx linesxxx); //cols是列长 lines是行长 我们可以根据需求自行调整游戏总界面面长宽 光标设置 在我们游戏运行时这个光标一直浮动着不太好,我们有什么办法可以隐藏呢?当然可以这里就要介绍我们的Win32API了~ Win32API
什么是API? 我们发现应用程序会有开启视窗、描绘图形、使⽤周边设备等操作,那这是怎么实现的呢 ? 那就是通过我们操作系统应用程序编程接口Application Programming Interface),这个接口的服务对象是我们的应用程序.不同的API函数能实现不同的操作. 应用程序接口的提供者是运行库什么样的运行库提供什么样的API比如Linux下的Glibc库提供POSIX APIWindows的运行库提供Windows API最常见的32位Windows提供的API又被称为Win32API
GetStdHandle GetStdHandle是⼀个Windows API函数。它⽤于从⼀个特定的标准设备标准输⼊、标准输出或标准错误中取得⼀个句柄⽤来标识不同设备的数值),使⽤这个句柄可以操作设备。
HANDLE GetStdHandle(DWORD nStdHandle);//参数是标准设备参数取值含义STD_INPUT_HANDLE标准输入设备STD_OUTPUT_HANDLE标准输出设备STD_ERROR_HANDLE标准错误设备
理解:不同设备可以理解为不同的门,不同门对应不同的锁,而我们的句柄相当于打开门的钥匙,有了钥匙才能破门而入探索新世界~
CONSOLE_CURSOR_INFO
typedef struct _CONSOLE_CURSOR_INFO {
DWORD dwSize;
BOOL bVisible;
} CONSOLE_CURSOR_INFO, *PCONSOLE_CURSOR_INFO;这是一个结构体类型,用于存储控制台光标相关信息
dwSize : 由光标填充的字符单元格的百分⽐光标外观会变化范围从完全填充单元格到单元底部的⽔平线条,该值范围为0-100超出范围不会报错但不发生变化.
bBisible : 光标的可见性.true为可见,false为不可见.
GetConsoleCursorInfo 这也是个API函数用于检索有关指定控制台屏幕缓冲区的光标大小和可见性的信息.
HANDLE hOutput NULL;
//获取标准输出的句柄(⽤来标识不同设备的数值)
hOutput GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(hOutput, CursorInfo);//获取控制台光标信息
//第一个参数是我们获得的设备句柄,有了句柄才能操作设备;
//第二个参数是_CONSOLE_CURSOR_INFO结构体,我们获得将获得的光标信息放进这个结构体变量里SetConsoleCursorInfo 设置指定控制台屏幕缓冲区的光标的⼤⼩和可⻅性。存完光标信息我们就能重新设置光标信息了.
HANDLE hOutput GetStdHandle(STD_OUTPUT_HANDLE);
//获取句柄
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(hOutput, CursorInfo);//获取控制台光标信息
CursorInfo.bVisible false; //隐藏控制台光标
SetConsoleCursorInfo(hOutput, CursorInfo);//设置控制台光标状态了解这几个API之后我们就能设置我们的光标了~ 绘制地图
我们的地图大致是这样子 我们知道这里我们就得普及一下控制台程序的坐标系了 控制台坐标系
我们的坐标系向右是x轴,从左向右增长;坐标系向下是y轴,从上至下增长 控制台坐标的设置
知道了坐标系后,我们得清楚我们打印东西是得看这个光标的,从光标的位置开始打印.所以我们得知道相关知识实现定向射靶.
控制台屏幕上的坐标COORD
COORD是WindowsAPI中定义的⼀个结构体表示⼀个字符在控制台屏幕上的坐标.
SetConsoleCursorPosition
设置指定控制台屏幕缓冲区中的光标位置我们将想要设置的坐标信息放在COORD类型的pos中调⽤SetConsoleCursorPosition函数将光标位置设置到指定的位置.
//设置光标的坐标
void SetPos(short x, short y)
{
COORD pos { x, y };
HANDLE hOutput NULL;
//获取标准输出的句柄(⽤来标识不同设备的数值)
hOutput GetStdHandle(STD_OUTPUT_HANDLE);
//设置标准输出上光标的位置为pos
SetConsoleCursorPosition(hOutput, pos);
}封装成一个函数这样我们就很方便的定向打靶了 墙壁图案
我们地图墙的边界都是由一个个◻组成的包括我们的蛇身也是由一个个特殊符号组成的这些符号能直接被打印出来吗答案是不行原因是这些宽字符占两个字节我们的char只占一个字节满足不了我们的需求所以我们需要用wchar_t 和宽字符的输⼊和输出函数以及本地化来进行字符打印 locale.h本地化 由于世界文化各有不同不同地区采用的语言也有所不同这时我们用C语言输出字符就会出现地区差异而我们的locale.h头文件提供的函数就很好地为我们解决了这个问题 setlocale函数
char* setlocale (int category, const char* locale);setlocale函数⽤于修改当前地区可以针对⼀个类项修改也可以针对所有类项以下是部分类项
类项代表意义LC_ALL所有类别LC_TIMEstrftime 和 wcsftime 函数LC_CTYPE字符处理函数LC_MONETARYlocaleconv 函数返回的货币格式信息
第二个参数只有两个取值
1.“” 表示切换到本地模式此时可以用本地的字符集等
“C” 表示默认的正常方式
本地化后我们就能打印本地区的一些宽字符了
宽字符打印
int main()
{setlocale(LC_ALL, );wchar_t ch1 L中;wchar_t ch2 L国;wchar_t ch3 L★;wchar_t ch4 L●;wprintf(L%lc\n, ch1);wprintf(L%lc\n, ch2);wprintf(L%lc\n, ch3);wprintf(L%lc\n, ch4);printf(ab\n);return 0;
}注意 宽字符类型前面要加一个L表示宽字符打印输出用wprintf接收wchar_t类型变量打印格式为L%lc 明白这些后我们就能绘制地图了代码如下
void Createmap()
{//上 从0,0开始宽字符是普通字符的两倍 不断加2Setpos(0, 0);for (int i 0; i 56; i 2){wprintf(L%lc, WALL);}//下Setpos(0, 25);for (int i 0; i 56; i 2){wprintf(L%lc, WALL);}//左for (int i 1; i 25; i){Setpos(0,i);wprintf(L%lc, WALL);}//右for (int i 1; i 25; i){Setpos(56, i);wprintf(L%lc, WALL);}
}帮助信息
我们可以在加载游戏地图前提供开始界面和帮助信息注意设置好光标的位置代码如下
void Welcomeinfo()
{Setpos(37, 13);printf(欢迎来到贪吃蛇小游戏ovo);Setpos(37, 22);system(pause);Setpos(33, 13);printf(用↑ ↓ ← →移动 shift来加速 ctrl来减速);Setpos(37, 22);system(pause);system(cls);
}大致效果如下 贪吃蛇整体维护
对于贪吃蛇的整体游戏我们有蛇睡眠时间贪吃蛇这条蛇本身游戏分数食物权重等等信息这时我们可以想到用我们的结构体来储存我们的信息同时我们蛇可以看作是链表构成里面存储坐标以及next指针
//游戏状态
enum Status
{OK 1,ESC,//正常退出KILL_BYWALL,//撞墙KILL_BYSELF//自己咬到自己
};
//蛇运动的方向
enum Die
{UP1,DOWN,LEFT,RIGHT
};//定义蛇身结点typedef int Datatype;
typedef struct SnakeNode
{//储存坐标int x;int y;struct SnakeNode* next;
}SnakeNode,* pSnakeNode;//定义整个贪吃蛇要维护的内容
typedef struct Snake
{pSnakeNode snake;//蛇pSnakeNode pFood;//食物int Score;//当前分数int FoodWeight;//每个食物的权重int sleeptime;//睡眠时间enum Status status;//游戏状态enum Dir dir;//蛇运动方向}Snake,* pSnake;蛇的初始化 对于蛇的初始化我们可以以链表的形式运用尾插链接蛇值得注意的是蛇的坐标要设置成2的倍数不然蛇蛇一半身体会卡到墙里在创建蛇的结点的同时打印蛇身 #define BODY L●
#define POS_X 24
#define POS_Y 5
//初始化蛇
void InitSnake(pSnake ps)
{//创建蛇 五个结点pSnakeNode newhead NULL;pSnakeNode newtail NULL;for (int i 0; i 5; i){//创建新结点并初始化化他们的坐标pSnakeNode cur (pSnakeNode)malloc(sizeof(SnakeNode));if (cur NULL){perror(malloc failed);exit(1);}cur-x POS_X 2*i;//这里要这个两倍不然会发生覆盖cur-y POS_Y;cur-next NULL;Setpos(cur-x,cur-y);wprintf(L%lc, BODY);//链表为空新结点作为头结点if (newhead NULL){newtail newhead cur;}else{///头插cur-next ps-snake;ps-snake cur;}}//其他内容的初始化ps-sleeptime 200;ps-Score 0;ps-FoodWeight 10;ps-status OK;ps-dir UP;ps-pFood NULL;
}食物创建 对于食物我们也可以看成链表的一个结点但有几点需要注意 食物的坐标要是随机 可以用rand函数解决食物的坐标不能与墙壁重叠且食物坐标也要是二的倍数保证不卡墙食物坐标不能与蛇身重叠 可以遍历链表进行比较不满足重新生成 void CreateFood(pSnake ps)
{//先产生坐标 int x 0;int y 0;//横坐标要是偶数again:do{x rand() % 53 2; //2-54 -- 0~522y rand() % 24 1 ; //1~24} while (x%2!0);//不能超出墙 同时不能跟这个蛇的坐标重叠pSnakeNode cur ps-snake;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;ps-pFood pFood;Setpos(x,y);wprintf(L%lc, FOOD);
}其他内容初始化
除了蛇之外我们还有游戏状态蛇睡眠时间以及分数等等
ps-snake newtail;//其他内容的初始化ps-sleeptime 200;ps-Score 0;ps-FoodWeight 10;ps-status OK;ps-dir UP;ps-pFood NULL;二.游戏运行时工作 游戏逻辑
准备好前置工作后我们就可以着手准备我们的游戏运行工作了首先我们需要确定游戏的大体逻辑
大体逻辑玩家根据游戏提示按键—根据按键判断下步该做什么工作 在状态OK下实现蛇的移动 pause状态则是暂停 ESC状态是正常退出 也就是说状态不是OK就是退出程序一个循环 按键检测
我们在C语言中怎么检测我们的按键情况呢这要请出我们一个新的API函数GetAsyncKeyState
GetAsyncKeyState
SHORT GetAsyncKeyState
(int vKey
);
//将键盘上每个键的虚拟键值传递给函数函数通过返回值来分辨按键的状态返回值返回的16位的short数据中最⾼位是1说明按键的状态是按下如果最⾼是0说明按键的状态抬起如果最低位被置为1则说明该按键被按过否则为0。
所以我们可以通过检测函数返回值的最低位是否为1来检测按键情况可以用一个宏封装
#define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) 0x1) ? 1 : 0 )基于这个API函数和我们的游戏逻辑就能实现我们游戏运行基本的代码
void pause(pSnake ps)
{while (1){if (KEY_PRESS(VK_SPACE)){return;}}
}void GameRun(pSnake ps)
{//帮助信息Helpinfo();//游戏luojido {Setpos(64, 10);printf(得分%d , ps-Score);printf(每个⻝物得分%d分, ps-FoodWeight);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_ESCAPE) ps-dir ! DOWN){//退出ps-status ESC;}else if (KEY_PRESS(VK_SPACE) ps-dir ! DOWN){//暂停pause(ps);}else if (KEY_PRESS(VK_CONTROL) ps-dir ! DOWN){//减速if (ps-sleeptime 350){ps-sleeptime 30;ps-FoodWeight - 2;if (ps-sleeptime 350){ps-FoodWeight 1;}}}else if (KEY_PRESS(VK_SHIFT) ps-dir ! DOWN){//加速 就是减少睡眠时间if (ps-sleeptime 50){ps-sleeptime - 30;ps-FoodWeight 2;}}//休眠Sleep(ps-sleeptime);//移动SnakeMove(ps);} while (ps-status OK);}注意1.要保证按键方向不冲突 2.蛇休眠时间和食物权重不能为负数 蛇的移动
接下来就是整个游戏的运行的重头戏了
蛇是否吃到食物
分析 对于我们的蛇它吃到食物蛇长就会增加也就是结点会增多那么我们需要判断每次执行按键命令时我们是否会吃到食物。如果吃到食物的话我们就根据移动方向新插入一个结点同时创建新食物如果没吃到食物我们仍然插入新结点但尾结点打印空格再释放掉
实现代码如下
int IsFood(pSnake ps,pSnakeNode cur)
{//判断新结点是否与食物的坐标重合if (cur-x ps-pFood-x cur-y ps-pFood-y)return 1;elsereturn 0;
}
void Eatfood(pSnake ps,pSnakeNode cur)
{//吃到食物直接头插cur-next ps-snake;ps-snake cur;pSnakeNode del ps-snake;while (del){Setpos(del-x, del-y);wprintf(L%lc, BODY);del del-next;}//增加总分ps-Score ps-FoodWeight;//释放食物free(ps-pFood);//创建新食物CreateFood(ps);
}void NotEatfood(pSnake ps, pSnakeNode cur)
{//仍然尾插cur-next ps-snake;ps-snake cur;//最后一个不打印pSnakeNode del ps-snake;while (del-next-next){Setpos(del-x, del-y);wprintf(L%lc, BODY);del del-next;}Setpos(del-x,del-y);wprintf(L%lc, BODY);//最后一个位置打印空格并释放Setpos(del-next-x, del-next-y);printf( );free(del-next);del-next NULL;
}
//蛇的移动
void SnakeMove(pSnake ps)
{//创建 新节点pSnakeNode cur (pSnakeNode)malloc(sizeof(SnakeNode));if (cur NULL){perror(malloc failed);exit(1);}cur-next NULL;//根据方向确定新结点的坐标switch (ps-dir){case UP:cur-x ps-snake-x;cur-y ps-snake-y - 1;break;case DOWN:cur-x ps-snake-x;cur-y ps-snake-y 1;break;case LEFT:cur-x ps-snake-x - 2;cur-y ps-snake-y;break;case RIGHT:cur-x ps-snake-x 2;cur-y ps-snake-y;break;}//是否吃到食物if (IsFood(ps, cur)){//吃到食物Eatfood(ps,cur);}else{//没吃到食物NotEatfood(ps,cur);}//是否撞墙和咬到自己Kill_ByWall();Kill_BySelf()
}蛇是否撞墙和咬到自身
在进行上下左右移动时我们可能会咬到自己或撞墙这时游戏就会结束了我们需要进行游戏状态的更新
void Kill_ByWall(pSnake ps)
{pSnakeNode pcur ps-snake;if (pcur-x 0 || pcur-x 56 || pcur-y 0 || pcur-y 26){ //蛇头坐标不能等于这个墙壁的ps-status KILL_BYWALL;return;}return;
}
void Kill_BySelf(pSnake ps)
{//遍历链表看蛇头是不是跟蛇身每个结点的坐标相等pSnakeNode cur ps-snake;while (cur){if (ps-snake-x cur-x ps-snake-y cur-y){ps-status KILL_BYSELF;return;}cur cur-next;}return;
}三.游戏善后工作
退出循环后我们需要释放游戏资源同时根据游戏状态确定提示信息 贪吃蛇资源的释放 //释放游戏资源pSnakeNode pcur ps-snake;while (pcur){pSnakeNode pNext pcur-next;free(pcur);pcur pNext;}贪吃蛇游戏状态
//根据不同游戏状态进行提示switch (ps-status){case ESC:Setpos(12,9);printf(游戏正常退出..);Setpos(12, 10);printf(游戏总分为%d, ps-Score);break;case KILL_BYWALL:Setpos(12, 9);printf(很遗憾你撞墙了);Setpos(12, 10);printf(游戏总分为%d, ps-Score);break;case KILL_BYSELF:Setpos(12, 9);printf(很遗憾你咬到自己了);Setpos(12, 10);printf(游戏总分为%d, ps-Score);break;}拓展
到这里我们基本上我们的游戏了除此之外我们可以进行拓展比如把三个过程函数封装进循环里实现多次游玩
void testSnake(void){int input 0;do{Snake s;//游戏运行前GamePre(s);//游戏运行后GameRun(s);//游戏GameEnd(s);Setpos(0, 28);printf(你是否想再来一把(1/0):);scanf(%d, input);} while (input);Setpos(0, 29);
}除此之外我们可以运用win提供的颜色API该变蛇的颜色还有可以加入背景音乐等等…
PlaySound(MAKEINTRESOURCE(IDR_WAVE1), NULL, SND_RESOURCE | SND_ASYNC);第一个参数资源文件–新建–资源文件–Accelerator–导入你的.wav文件 接着打开resource.h就能找到第一个参数啦 最后看下效果~ 最后到这里分享内容就结束啦小伙伴们能学到知识的话不妨来个一键三连祝大家2024万事顺利~