手机网站建设的整体流程,空间转移 wordpress,广州注册公司如何经营,天眼查企业查询官网网页版【C语言】基于C语言实现的贪吃蛇游戏 #x1f525;个人主页#xff1a;大白的编程日记
#x1f525;专栏#xff1a;C语言学习之路 文章目录 【C语言】基于C语言实现的贪吃蛇游戏前言一.最终实现效果一.Win32 API介绍1.1Win32 API1.2控制台程序1.3控制台屏幕上的坐标COORD…【C语言】基于C语言实现的贪吃蛇游戏 个人主页大白的编程日记
专栏C语言学习之路 文章目录 【C语言】基于C语言实现的贪吃蛇游戏前言一.最终实现效果一.Win32 API介绍1.1Win32 API1.2控制台程序1.3控制台屏幕上的坐标COORD1.4GetStdHandle1.5GetConsoleCursorInfo1.6SetConsoleCursorInfo1.7SetConsoleCursorPosition1.8GetAsyncKeyState 二.C语言的国际化2.1国际化2.2locale.h本地化2.3类项2.4setlocale函数2.5宽字符的打印 三.思路分析四.GameStar函数4.1设置控制台信息4.2欢迎界面的打印4.3地图的绘制4.4初始化贪吃蛇 五.GameRun函数5.1提示信息函数5.2打印分数5.3检测键值5.4蛇的移动5.5检测是否撞墙 六.GameEnd函数七.游戏主体设计八.源码后言 前言 哈喽各位小伙伴大家好今天给大家带来的是使用C语言实现的贪吃蛇小游戏。也是检验C语言是否学好的试金石。话不多说咱们进入正题向大厂冲锋 一.最终实现效果 贪吃蛇实现视频 一.Win32 API介绍
本次实现贪吃蛇会使用到的⼀些Win32 API知识接下来我们就学习⼀下什么是Win32 API。
1.1Win32 API Windows 这个多作业系统除了协调应⽤程序的执行、分配内存、管理资源之外 它同时也是⼀个很大的服务中心调用这个服务中心的各种服务每⼀种服务就是⼀个函数可以帮应用程序达到开启视窗、描绘图形、使⽤周边设备等目的由于这些函数服务的对象是应⽤程序(Application) 所以便称之为 Application Programming Interface简称 API 函数。 WIN32 API也就是Microsoft Windows32位平台的应用程序编程接口。 1.2控制台程序 平常我们运行起来的黑框程序其实就是控制台程序。 那这个控制台的大小我们可不可设置呢其实是能的。
设置大小 我们可以使用cmd命令来设置控制台窗口的长宽设置控制台窗口的大小30行100列。
mode con cols100 lines30我们要设置控制台的话就需要使用system函数system函数可以用来执行系统命令。
int main()
{system(mode con cols40 lines40);return 0;
}设置名字 控制台的名字也能修改使用title命令即可
title 贪吃蛇int main()
{system(mode con cols100 lines30);system(title 贪吃蛇);getchar();return 0;
}控制台设置 我们平时的默认是控制台终端但是它实现不了我们想要的效果所以我们要修改一下。 先点箭头后出现的设置 之后找到默认终端程序应用改为控制台主机即可。
1.3控制台屏幕上的坐标COORD 我们的贪吃蛇游戏里面有蛇食物墙等等。他们涉及到的位置是我们游戏中的关键信息那我们再屏幕上也要定位他们的位置那怎么定位呢这就涉及到COORD了。 COORD是WindowsAPI中定义的⼀个结构体表示一个字符在控制台屏幕幕缓冲区上的坐标坐标系(00)的原点位于缓冲区的顶部左侧单元格。 COORD是一种结构体类型
结构体类型
typedef struct _COORD {SHORT X;SHORT Y;
} COORD, *PCOORD;头文件 使用COORD结构体需要包含windows.h的头文件
那现在我们定义一个坐标就可以这样写 COORD pos { 2,3 };COORD pos1 { 5,6 };1.4GetStdHandle GetStdHandle是⼀个WindowsAPI函数。它用于从⼀个特定的标准设备标准输入、标准输出或标准错误中取得⼀个句柄用来标识不同设备的数值使用这个句柄可以操作设备。 我们在控制台窗口中进行光标隐藏等操作时都需要先获得这个窗口。而GetStdHandle就可以获得一个对窗口操作的把手也就是句柄。通过这个把手我们就可以对控制台窗口进行操作。 HANDLE GetStdHandle(DWORD nStdHandle);参数 标准设备。 此参数的取值可为下列值之一。
那我们贪吃蛇进行蛇的移动食物的绘制等都是在屏幕上输出信息。所以我们需要或许标准输出设备的句柄 HANDLE houtput NULL;houtputGetStdHandle(STD_OUTPUT_HANDLE);1.5GetConsoleCursorInfo 大家可以发现我们运行时窗口会有一个光标闪烁那等下贪吃蛇运行时我们是不希望它闪烁的有没有办法把它隐藏掉呢那我们得先获取光标信息。 GetConsoleCursorInfo是检索有关指定控制台屏幕缓冲区的光标大小可见性的信息的函数 它的参数有两个
光标控制台句柄
第一个参数是一个跟光标关联的控制台窗口的句柄 _In_ HANDLE hConsoleOutput,光标信息 第二个参数是指向光标信息结构体CONSOLE_CURSOR_INFO的指针 _Out_ PCONSOLE_CURSOR_INFO lpConsoleCursorInfo光标信息结构体 dwSize由光标填充的字符单元格的百分比。此值介于1到100之间。光标外观会变化范围从完全填充单元格到单元底部的水平线条 bVisible游标的可见性。如果光标可见则此成员为TRUE。 那我们现在获取光标就可以这样写
HANDLE hOutput NULL;
//获取标准输出的句柄(⽤来标识不同设备的数值)
hOutput GetStdHandle(STD_OUTPUT_HANDLE);CONSOLE_CURSOR_INFO CursorInfo{0};//光标信息结构体
GetConsoleCursorInfo(hOutput, CursorInfo);//获取控制台光标信息 现在我们输出一下光标的信息看一下 //获取标准输出的句柄(⽤来标识不同设备的数值) HANDLE hOutput NULL;hOutput GetStdHandle(STD_OUTPUT_HANDLE);CONSOLE_CURSOR_INFO CursorInfo { 0 };//光标信息结构体GetConsoleCursorInfo(hOutput, CursorInfo);//获取控制台光标信息 printf(%d, CursorInfo.dwSize);现在我们把光标大小设为50
HANDLE hOutput NULL;
hOutput GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO CursorInfo { 0 };//光标信息结构体
GetConsoleCursorInfo(hOutput, CursorInfo);//获取控制台光标信息
CursorInfo.dwSize 50;可是为什么光标大小没变呢 这是因为我们只修改了光标的信息我们还需要设置修改后光标信息。
1.6SetConsoleCursorInfo
SetConsoleCursorInfo是用来设置指定控制台屏幕缓冲区的光标的大小和可见性。
参数
BOOL WINAPI SetConsoleCursorInfo(_In_ HANDLE hConsoleOutput,_In_ const CONSOLE_CURSOR_INFO *lpConsoleCursorInfo
);它的参数和GetConsoleCursorInfo一样。 现在我们来设置光标信息
修改光标大小 隐藏光标
1.7SetConsoleCursorPosition 那我们如何让光标移动到指定位置呢 设置指定控制台屏幕缓冲区中的光标位置我们将想要设置的坐标信息放在COORD类型的pos中调用SetConsoleCursorPosition函数将光标位置设置到指定的位置。 参数 一个是控制台窗口的句柄 一个是光标位置的结构体 BOOL WINAPI SetConsoleCursorPosition(HANDLE hConsoleOutput,COORD pos
);那我们现在想定位光标到第10行第5列就可以这样写。
COORD pos { 10, 5};HANDLE hOutput NULL;//获取标准输出的句柄(⽤来标识不同设备的数值) hOutput GetStdHandle(STD_OUTPUT_HANDLE);//设置标准输出上光标的位置为pos SetConsoleCursorPosition(hOutput, pos);为了方便我们后面游戏打印食物和移动蛇的位置我们封装⼀个设置光标位置的函数 //设置光标的坐标
void SetPos(short x, short y)
{COORD pos { x, y };HANDLE hOutput NULL;//获取标准输出的句柄(⽤来标识不同设备的数值) hOutput GetStdHandle(STD_OUTPUT_HANDLE);//设置标准输出上光标的位置为pos SetConsoleCursorPosition(hOutput, pos);
}1.8GetAsyncKeyState 我们贪吃蛇游戏需要根据键盘的的按键进行上下左右的移动。 那如何让程序获取我们的按键信息呢 这时就需要用到GetAsyncKeyState函数 将键盘上每个键的虚拟键值传递给函数函数通过返回值来分辨按键的状态。 GetAsyncKeyState的返回值是short类型在上⼀次调用GetAsyncKeyState 函数后如果返回的16位的short数据中最高 位是1说明按键的状态是按下如果最⾼是0说明按键的状态是抬起如果最低位被置为1则说明该按键被按过否则为0。 如果我们要判断⼀个键是否被按过可以检测GetAsyncKeyState返回值的最低值是否为1. 参数 函数的参数是一个int类型代表要检测按键的虚拟键值
SHORT GetAsyncKeyState([in] int vKey
);虚拟键值表
这样我们就可以定义一个宏来检测按键是否被按过只需检测函数返回值的最低位即可。
#define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) 0x1) ? 1 : 0 )二.C语言的国际化 在游戏地图上我们打印墙体使⽤宽字符□打印蛇使⽤宽字符●打印食物使用宽字符★ 普通的字符是占⼀个字节的这类宽字符是占用2个字节。 这⾥再简单的讲⼀下C语言的国际化特性相关的知识过去C语言并不适合非英语国家地区使用。C语言最初假定字符都是单字节的。但是这些假定并不是在世界的任何地方都适用。 2.1国际化 C语言字符默认是采⽤ASCII编码的ASCII字符集采⽤的是单字节编码且只使用了单字节中的低7 位最高位是没有使用的可表⽰为0xxxxxxxx可以看到ASCII字符集共包含128个字符在英语国家中128个字符是基本够用的但是在其他国家语言中比如在法语中字母上方有注音符号它就无法用ASCII码表示。于是⼀些欧洲国家就决定利用字节中闲置的最高位编入新的符号。 比如法语中的é的编码为130⼆进制10000010。这样⼀来这些欧洲国家使⽤的编码体系可以表示最多256个符号。但是这⾥又出现了新的问题。不同的国家有不同的字母因此哪怕它们都使用256个符号的编码⽅式代表的字母却不⼀样。比如130在法语编码中代表了é在希伯来语编码中却代表了字⺟Gimel在俄语编码中又会代表另⼀个符号。但是不管怎样所有这些编码方式中0–127表示的符号是⼀样的不⼀样的只是128–255的这一段。 至于亚洲国家的文字使⽤的符号就更多了汉字就多达10万左右。⼀个字节只能表示256种符号肯定是不够的就必须使用多个字节表达⼀个符号。比如简体中文常见的编码方式是GB2312使用两个字节表示⼀个汉字所以理论上最多可以表示256x2565536个符号。 后来为了使C语言适应国际化C语言的标准中不断加入了国际化的支持。比如加入了宽字符的类型wchar_t 和宽字符的输⼊和输出函数加⼊了locale.h头文件其中提供了允许程序员针对特定地区通常是国家或者说某种特定语⾔的地理区域调整程序行为的函数。 2.2locale.h本地化
locale.h提供的函数用于控制C标准库中对于不同的地区会产生不⼀样行为的部分。 在标准中依赖地区的部分有以下几项
数字量的格式货币量的格式字符集日期和时间的表示形式
2.3类项
通过修改地区程序可以改变它的行为来适应世界的不同区域。但地区的改变可能会影响库的许多部分其中⼀部分可能是我们不希望修改的。所以C语言支持针对不同的类项进行修改下面的⼀个宏指定⼀个类项
LC_COLLATE影响字符串比较函数 strcoll() 和 strxfrm()LC_CTYPE影响字符处理函数的行为。LC_MONETARY影响货币格式。LC_NUMERIC影响 printf() 的数字格式。LC_TIME影响时间格式 strftime() 和 wcsftime() 。LC_ALL-针对所有类项修改将以上所有类别设置为给定的语⾔环境。
2.4setlocale函数 setlocale函数⽤于修改当前地区可以针对一个类项修改也可以针对所有类项。setlocale的第⼀个参数可以是前⾯说明的类项中的⼀个那么每次只会影响⼀个类项如果第⼀个参数是LC_ALL就会影响所有的类项。 C标准给第⼆个参数仅定义了2种可能取值“C”正常模式和本地模式。
正常模式 在任意程序执行开始都会隐藏式执行调用:
setlocale(LC_ALL, C);地区设置为C时库函数按正常方式执行小数点是⼀个点
本地模式 当程序运行起来后想改变地区就只能显示调用setlocale函数。⽤作为第2个参数调用setlocale函数就可以切换到本地模式这种模式下程序会适应本地环境。 比如切换到我们的本地模式后就支持宽字符汉字的输出等。
setlocale(LC_ALL, );//切换到本地环境 2.5宽字符的打印 那如果想在屏幕上打印宽字符怎么打印呢 宽字符的字面量必须加上前缀“L”否则C语言会把字面量当作窄字符类型处理。前缀“L”在单引 号前面表⽰宽字符对应 wprintf() 的占位符为 %lc 在双引号前⾯表示宽字符串对应 wprintf() 的占位符为 %ls 。 #include stdio.h
#includelocale.h
int main() {setlocale(LC_ALL, );wchar_t ch1 L●; wchar_t ch2 L⽐;wchar_t ch3 L特;wchar_t ch4 L★;printf(%c%c\n, a, b);wprintf(L%lc\n, ch1);wprintf(L%lc\n, ch2);wprintf(L%lc\n, ch3);wprintf(L%lc\n, ch4);return 0;
}三.思路分析 我们先把游戏分为三个大的模块。每个模块分别负责不同的功能。 每个模块具体实现又由多个小的函数模块合理设计拼接后组成。大体的思路如下 GameStar函数 GameStar函数负责进入窗口大小和名字的设置光标的隐藏。 欢迎界面的打印地图的绘制。 贪吃蛇的创建和初始化食物的创建。 GameRun函数 右侧打印帮助信息-PrintHelpInfo 打印当前已获得分数和每个食物的分数以提示用户 根据按键情况移动蛇-SnakeMove直到游戏是结束状态 SnakeMove 根据蛇头的坐标和方向计算下⼀个节点的坐标 判断下⼀个节点是否是食物-NextIsFood 是食物就吃掉-EatFood 不是食物吃掉食物尾巴删除⼀节-NoFood 判断是否撞墙-KillByWall 判断是否撞上自己-KillBySelf GameEnd 告知游戏结束的原因 释放蛇身节点
四.GameStar函数
为了方便阅读代码这里先把我们定义好的宏给大家看
实现效果
4.1设置控制台信息
首先我们需要设置控制台大小名字还要隐藏光标。 这些操作我们前面讲过的了。不再过多赘述了。
system(mode con cols100 lines30);//设置控制台窗口大小
system(title 贪吃蛇);//设置控制台名字
HANDLE houtput NULL;
houtput GetStdHandle(STD_OUTPUT_HANDLE);
//定义一个光标信息的结构体
CONSOLE_CURSOR_INFO cursor_info ;
//获取和houtput句柄相关的控制台上的光标信息存放在cursor_info中
GetConsoleCursorInfo(houtput, cursor_info);
//修改光标隐藏光标
cursor_info.bVisible false;
//设置和houtput句柄相关的控制台上的光标信息
SetConsoleCursorInfo(houtput, cursor_info);4.2欢迎界面的打印 这里我们把欢迎界面的打印封装为WelcomeToSnake函数 我们只需要将光标定位到指定位置然后打印文字即可。 注意这里涉及到程序的暂停我们用system函数即可实现。 打印完切换下一个界面时我们清理屏幕。这个用system函数也能实现 再定位打印文字。
void WelcomeToSnake()
{setpos(40, 14);//定位光标wprintf(L欢迎来到贪吃蛇小游戏~\n);setpos(42, 20);//定位光标system(pause);//暂停程序system(cls);//清理屏幕setpos(25, 14);//定位光标wprintf(L分别用↑.↓.←.→控制移动方向F3加速F4减速\n);setpos(25, 15);//定位光标wprintf(L加速能够获得更高的分数\n);setpos(42, 20);//定位光标system(pause);//暂停程序system(cls);//清理屏幕
}4.3地图的绘制 地图的绘制我们也单独封装为一个Greatmap()函数 假设我们要绘制一个27行 58列的地图我们只需要关注好四个角落点的坐标 然后用四层for循环打印即可。
void Greatmap()
{setpos(0, 0);//定位原点for (int i 0; i 29; i)//打印第一行墙体{wprintf(L%lc, WALL);}setpos(0, 26);//定位第二行起点for (int i 0; i 29; i)//打印第二行墙体{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);}
}这里大家要注意行的打印光标不需要移动但是列的打印光标需要移动。
4.4初始化贪吃蛇 游戏中食物的信息贪吃蛇的信息蛇的移动方向 游戏状态食物分数总得分蛇的移动快慢休眠时间 食物信息。都是我们游戏过程中需要维护的信息。我们维护游戏其实就是维护这些信息。我们把他们用结构体包含作为贪吃蛇我们只需要维护这贪吃蛇即可。 游戏状态 四种游戏状态正常游戏 撞墙 咬到自己 玩家主动结束。 用枚举类型即可。
enum STATUS//游戏的状态
{ok 1,kill_by_wall,kill_by_self,end
};蛇的移动方向 四种移动状态上 下 左 右。 用枚举类型即可。
enum DIRECTION//枚举蛇的方向
{up 1,down,left ,right
};蛇头 蛇的维护我们只需要一个指向蛇头的指针即可。食物我们也可以看成蛇的节点。蛇的节点我们用链表来表示。
typedef struct SnakeNode //蛇身
{int x;int y;struct SnakeNode* next;
}Snakenode,* pSnakenode;其他信息 食物分数 总分 休眠时间 用int类型即可。
int fool_score;
int score;
int sleep_time;贪吃蛇信息
typedef struct Snake//游戏中贪吃蛇的信息
{pSnakenode shead;pSnakenode snakefool;enum DRIECTION dir;enum STATUS statu;int fool_score;int score;int sleep_time;
}Snake,*psnake;创建食物函数 我们创建一个蛇的节点作为食物节点。再用rand函数生成横纵坐标。 然后循环遍历区判断食物节点和和蛇身的每一个节点坐标是否有重合。 重合就用goto语句继续生成坐标知道不重合为止。接着打印食物即可。
void GreatFool(psnake ps)
{pSnakenode pcur ps-shead;pSnakenode fool (pSnakenode)malloc(sizeof(Snakenode));//创建蛇的节点作为食物节点if (fool NULL)//判空{perror(GreatFool:malloc);return;}int x, y;
again:do{x rand() % 53 2;//生成横坐标y rand() % 25 1;//纵坐标} while (x % 2 ! 0);//保证横坐标为偶数while (pcur)//遍历蛇身{if (pcur-x x pcur-y y)goto again;//食物和蛇身重叠重新生成pcur pcur-next;}fool-x x;//赋值横坐标fool-y y;//赋值纵坐标fool-next NULL;//置空setpos(fool-x, fool-y);//定位光标wprintf(L%lc, FOOD);//打印食物ps-snakefool fool;//存放食物节点
}最后我们再循环创建五个初始的蛇身节点初始化贪吃蛇信息再创建食物即可。
void Initsnake(psnake ps)
{psnake pcur ps;for (int i 0; i 5; i)//创建五个蛇的节点{pSnakenode node (pSnakenode)malloc(sizeof(Snakenode));//创建蛇的节点if (node NULL)//判空{perror(Initsnake:malloc);return;}node-next NULL;node-x POS_X 2 * i;//横坐标node-y POS_Y;//纵坐标if (pcur-shead NULL){pcur-shead node;//蛇头节点第一个节点}else{node-next pcur-shead;//头插法pcur-shead node;}while (node)//遍历蛇身打印{setpos(node-x, node-y);//定位蛇身坐标wprintf(L%lc, BODY);//打印node node-next;//遍历下一个蛇身节点}}ps-dir right;//默认移动方向为右ps-statu ok;//游戏状态正常ps-fool_score 10;//默认食物分数为10ps-score 0;//总分为0ps-sleep_time 200;//休眠时间默认为0GreatFool(ps);//创建食物
}五.GameRun函数
5.1提示信息函数 在游戏时我们打印一下提示信息给玩家。封装PrintHelpInfo()
void PrintHelpInfo()//打印提示信息
{setpos(64, 15);wprintf(L不能穿墙不能咬到⾃⼰\n);setpos(64, 16);printf(分别用↑.↓.←.→分别控制蛇的移动\n);setpos(64, 17);wprintf(LF3 为加速F4 为减速\n);setpos(64, 18);wprintf(LESC 退出游戏.space暂停游戏.);setpos(64, 19);wprintf(L成杰MAKE);
}5.2打印分数 游戏过程食物分数和总得分都需要更新 我们也要打印出来。
setpos(64, 10);//定位
printf(总分%2d, ps-score);//打印信息
setpos(64, 11);//定位
printf(当前食物分数%2d, ps-fool_score);//打印信息5.3检测键值
游戏过程我们需要根据键盘输入进行相对的相应 蛇的移动暂停 加速减速等。 我们前面已经定义了一个宏直接使用即可
#define KEY_PRESS(VK) ((GetAsyncKeyState(VK) 0x1) ? 1 : 0 )//检测虚拟键值
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))//检测ESC按键
{ps-statu end;//设置退出游戏状态
}
else if (KEY_PRESS(VK_SPACE) )//检测space按键
{Pause();//暂停游戏
}
else if (KEY_PRESS(VK_F3))//检车f3加速按键
{if (ps-sleep_time 120)//设置加速上限{ps-sleep_time - 20;//减少休眠加快速度ps-fool_score 2;//分数增加}
}
else if (KEY_PRESS(VK_F4))//检测f4减速按键
{if (ps-fool_score 2)//设置减速上限{ps-sleep_time 20;//增加休眠时间减慢速度ps-fool_score - 2;//分数减少}
}注意我们移动时不能与当前移动方向相反。 加速减速我们加减休眠时间即可。 暂停我们用pause函数即可检测space按键按过直接break跳出即可。 否则一直休眠。
void Pause()
{while (1){Sleep(200);//休眠if (KEY_PRESS(VK_SPACE))//检测是否再次按space按键{break;//继续游戏}}
}检测完后我们再根据按键输入进行蛇的移动即可。 我们封装为Snakemove函数。 我们先创建一个节点作为下一个蛇头节点。 然后根据移动方向给节点横纵坐标赋值即可。
pSnakenode pcur (pSnakenode)malloc(sizeof(Snakenode));//生成蛇头下一个位置的节点
if (pcur NULL)//判空
{perror(Snakemove:malloc);return;
}
switch (ps-dir)//检测蛇移动方向
{
case up:pcur-yps-shead-y -1;//赋值横坐标pcur-x ps-shead-x;//赋值纵坐标break;
case down:pcur-y ps-shead-y 1;//赋值横坐pcur-x ps-shead-x; // 赋值纵坐标break;
case right:pcur-x ps-shead-x 2;//赋值横坐pcur-y ps-shead-y; // 赋值纵坐标break;
case left:pcur-x ps-shead-x -2;//赋值横坐pcur-y ps-shead-y; // 赋值纵坐标break;
}5.4蛇的移动
蛇的移动分两种情况
吃食物移动 若下一个位置是食物。 我们释放开辟的节点然后让食物节点成为新的蛇头节点 然后遍历打印蛇身再创建新的食物即可。同时更新得分。
void EatFood(pSnakenode pn, psnake ps)//吃食物
{ps-snakefool-next ps-shead;//食物节点链接蛇头ps-shead ps-snakefool;//食物节点成为蛇头free(pn);//释放新开辟的蛇头节点pn NULL;pSnakenode cur ps-shead;while (cur)//遍历打印蛇身{setpos(cur-x, cur-y);wprintf(L%lc, BODY);cur cur-next;}ps-score ps-fool_score;//总分增加GreatFool(ps);//生成新的食物
}不吃食物移动 让新开辟节点链接原来的蛇头新开辟的节点成为新的蛇头。 再遍历找到倒数第二个尾节点释放尾巴节点再让倒数第二个节点指向空封尾。同时记得打印空白覆盖掉蛇尾节点打印的信息。再遍历打印蛇身即可。
void NotFood(pSnakenode pn, psnake ps)
{pn-next ps-shead;//让新的蛇头连接原来的蛇头ps-shead pn;//让蛇下一个位置的节点成为新的蛇头pSnakenode pcur ps-shead;while (pcur-next-next ! NULL)//找到蛇尾倒数第二个节点{pcur pcur-next;//移动}pSnakenode dle pcur;setpos(pcur-next-x, pcur-next-y);//定位光标到最后的蛇尾printf( );//覆盖原来蛇尾打印的信息free(dle-next);//释放最后的蛇尾节点dle-next NULL;pSnakenode cur pn;while (cur){setpos(cur-x, cur-y);wprintf(L%lc, BODY);cur cur-next;}
}5.5检测是否撞墙
我们只需要检测横坐标是否为0或56 纵坐标是否为0或26。 再根据穿墙的横坐标或纵坐标不变修改蛇头另一变化坐标即可。 最后即可设置游戏状态即可。 要实现穿墙我们再让蛇每次移动都刷新城墙即可。
void KillByWall(psnake ps)
{if (ps-shead-x 56)//检测是否撞到第二列城墙{ps-shead-x 0;//重新定位穿墙后的横坐标位置第一列出}else if (ps-shead-x 0)//检测是否撞到第一列城墙{ps-shead-x 56;//重新定位穿墙后的横坐标位置第二列出}else if (ps-shead-y 0)//检测是否撞到第一行城墙{ps-shead-y 26;//重新定位穿墙后的纵坐标位置第一行出}else if (ps-shead-y 26)//检测是否撞到第二行城墙{ps-shead-y 0;//重新定位穿墙后的纵坐标位置第二行出}
}检测是否咬到自己 我们只需要判断当前蛇头坐标是否与其他蛇身坐标重合即可。 用循环遍历蛇身在判断即可。最后即可设置游戏状态即可
void KillBySelf( psnake ps)
{pSnakenode pcur ps-shead-next;//保存蛇头下一个节点while (pcur)//遍历蛇身{if (ps-shead-x pcur-x ps-shead-y pcur-y)//蛇身与蛇头重合{ps-statu kill_by_self;//设置游戏状态break;//结束循环}pcur pcur-next;//移动}
}每次移动后都检测撞墙和咬到自己。 判断游戏状态正常则继续游戏。
六.GameEnd函数
游戏结束我们需要检测游戏状态再打印对应提示信息。 最后循环遍历销毁蛇身节点和食物节点即可
void GemaEnd(psnake ps)
{pSnakenode pcur ps-shead;setpos(20, 12);//定位光标switch (ps-statu)//游戏状态{case kill_by_wall://撞墙wprintf(L您撞到墙上游戏结束);break;case kill_by_self://咬到自己wprintf(L您咬到自己游戏结束);break;case end://自己结束wprintf(L您主动结束了游戏);break;}setpos(0, 27);//定位光标while (pcur)//遍历蛇身销毁链表{pSnakenode del pcur;//保存销毁节点pcur pcur-next;//移动下一个销毁节点free(del);//销毁del NULL;}free(ps-snakefool);//销毁食物ps-snakefool NULL;
} 七.游戏主体设计
最后我们调用三大模块函数即可再用do_while循环给用户选择是否继续再来一局游戏的选择。注意使用宽字符要记得本地化。
setlocale(LC_ALL, );
void test()
{char ch 0;do{Snake snake { 0 };GemeStar(snake);GameRun(snake);GemaEnd(snake);setpos(20, 15);wprintf(L再来一局吗(Y/N):);ch getchar();setpos(0, 27);} while (ch Y || ch y);
}八.源码
Snake.h
#pragma once
#includeWindows.h
#includestdlib.h
#includestdio.h
#includestdbool.h
#includelocale.h
#includetime.h
#define WALL L□
#define FOOD L★
#define BODY L●
#define POS_X 24
#define POS_Y 5
#define KEY_PRESS(VK) ((GetAsyncKeyState(VK) 0x1) ? 1 : 0 )//检测虚拟键值
enum DIRECTION//枚举蛇的方向
{up 1,down,left ,right
};
enum STATUS//游戏的状态
{ok 1,kill_by_wall,kill_by_self,end
};
typedef struct SnakeNode //蛇身
{int x;int y;struct SnakeNode* next;
}Snakenode,* pSnakenode;
typedef struct Snake//游戏中贪吃蛇的信息
{pSnakenode shead;pSnakenode snakefool;enum DRIECTION dir;enum STATUS statu;int fool_score;int score;int sleep_time;
}Snake,*psnake;
void setpos(short x,short y);
void GemeStar(psnake ps);
void Greatmap();
void Initsnake(psnake ps);
void GreatFool(psnake ps);
void GameRun(psnake ps);
void PrintHelpInfo();
void Pause();
void Snakemove(psnake ps);
int NextIsFood(pSnakenode pn, psnake ps);
void EatFood(pSnakenode pn, psnake ps);
void NotFood(pSnakenode pn, psnake ps);
void KillByWall(psnake ps);
void KillBySelf(psnake ps);
void GemaEnd(psnake ps);Snake.c
#define _CRT_SECURE_NO_WARNINGS 1
#includesnake.h
void WelcomeToSnake()
{setpos(40, 14);//定位光标wprintf(L欢迎来到贪吃蛇小游戏~\n);setpos(42, 20);//定位光标system(pause);//暂停程序system(cls);//清理屏幕setpos(25, 14);//定位光标wprintf(L分别用↑.↓.←.→控制移动方向F3加速F4减速\n);setpos(25, 15);//定位光标wprintf(L加速能够获得更高的分数\n);setpos(42, 20);//定位光标system(pause);//暂停程序system(cls);//清理屏幕
}
void GemeStar(psnake ps)
{system(mode con cols100 lines30);//设置控制台窗口大小system(title 贪吃蛇);//设置控制台名字HANDLE houtput NULL;houtput GetStdHandle(STD_OUTPUT_HANDLE);//定义一个光标信息的结构体CONSOLE_CURSOR_INFO cursor_info ;//获取和houtput句柄相关的控制台上的光标信息存放在cursor_info中GetConsoleCursorInfo(houtput, cursor_info);//修改光标隐藏光标cursor_info.bVisible false;//设置和houtput句柄相关的控制台上的光标信息SetConsoleCursorInfo(houtput, cursor_info);WelcomeToSnake();//欢迎界面打印 Greatmap();//地图打印Initsnake(ps);//初始化贪吃蛇的信息
}
void Greatmap()
{setpos(0, 0);//定位原点for (int i 0; i 29; i)//打印第一行墙体{wprintf(L%lc, WALL);}setpos(0, 26);//定位第二行起点for (int i 0; i 29; i)//打印第二行墙体{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 setpos(short x, short y)//定位坐标函数
{HANDLE houtput NULL;houtput GetStdHandle(STD_OUTPUT_HANDLE);//创建句柄COORD pos {x,y};//坐标的结构体SetConsoleCursorPosition(houtput, pos);//设置光标位置
}
void Initsnake(psnake ps)
{psnake pcur ps;for (int i 0; i 5; i)//创建五个蛇的节点{pSnakenode node (pSnakenode)malloc(sizeof(Snakenode));//创建蛇的节点if (node NULL)//判空{perror(Initsnake:malloc);return;}node-next NULL;node-x POS_X 2 * i;//横坐标node-y POS_Y;//纵坐标if (pcur-shead NULL){pcur-shead node;//蛇头节点第一个节点}else{node-next pcur-shead;//头插法pcur-shead node;}while (node)//遍历蛇身打印{setpos(node-x, node-y);//定位蛇身坐标wprintf(L%lc, BODY);//打印node node-next;//遍历下一个蛇身节点}}ps-dir right;//默认移动方向为右ps-statu ok;//游戏状态正常ps-fool_score 10;//默认食物分数为10ps-score 0;//总分为0ps-sleep_time 200;//休眠时间默认为0GreatFool(ps);//创建食物
}
void GreatFool(psnake ps)
{pSnakenode pcur ps-shead;pSnakenode fool (pSnakenode)malloc(sizeof(Snakenode));//创建蛇的节点作为食物节点if (fool NULL)//判空{perror(GreatFool:malloc);return;}int x, y;
again:do{x rand() % 53 2;//生成横坐标y rand() % 25 1;//纵坐标} while (x % 2 ! 0);//保证横坐标为偶数while (pcur)//遍历蛇身{if (pcur-x x pcur-y y)goto again;//食物和蛇身重叠重新生成pcur pcur-next;}fool-x x;//赋值横坐标fool-y y;//赋值纵坐标fool-next NULL;//置空setpos(fool-x, fool-y);//定位光标wprintf(L%lc, FOOD);//打印食物ps-snakefool fool;//存放食物节点
}
void PrintHelpInfo()//打印提示信息
{setpos(64, 15);wprintf(L不能穿墙不能咬到⾃⼰\n);setpos(64, 16);printf(分别用↑.↓.←.→分别控制蛇的移动\n);setpos(64, 17);wprintf(LF3 为加速F4 为减速\n);setpos(64, 18);wprintf(LESC 退出游戏.space暂停游戏.);setpos(64, 19);wprintf(L成杰MAKE);
}
void Pause()
{while (1){Sleep(200);//休眠if (KEY_PRESS(VK_SPACE))//检测是否再次按space按键{break;//继续游戏}}
}
int NextIsFood(pSnakenode pn, psnake ps)
{return (pn-x ps-snakefool-x pn-y ps-snakefool-y);//判断下一个节点是否是食物
}void Snakemove(psnake ps)
{pSnakenode pcur (pSnakenode)malloc(sizeof(Snakenode));//生成蛇头下一个位置的节点if (pcur NULL)//判空{perror(Snakemove:malloc);return; }switch (ps-dir)//检测蛇移动方向{case up:pcur-yps-shead-y -1;//赋值横坐标pcur-x ps-shead-x;//赋值纵坐标break;case down:pcur-y ps-shead-y 1;//赋值横坐pcur-x ps-shead-x; // 赋值纵坐标break;case right:pcur-x ps-shead-x 2;//赋值横坐pcur-y ps-shead-y; // 赋值纵坐标break;case left:pcur-x ps-shead-x -2;//赋值横坐pcur-y ps-shead-y; // 赋值纵坐标break;}if (NextIsFood(pcur, ps))//判断下一个节点是否为食物{EatFood(pcur, ps);//吃食物的移动}else{NotFood(pcur,ps);//不吃食物的移动}
}
void GameRun(psnake ps)
{PrintHelpInfo();//打印提示信息do{setpos(64, 10);//定位printf(总分%2d, ps-score);//打印信息setpos(64, 11);//定位printf(当前食物分数%2d, ps-fool_score);//打印信息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))//检测ESC按键{ps-statu end;//设置退出游戏状态}else if (KEY_PRESS(VK_SPACE) )//检测space按键{Pause();//暂停游戏}else if (KEY_PRESS(VK_F3))//检车f3加速按键{if (ps-sleep_time 120)//设置加速上限{ps-sleep_time - 20;//减少休眠加快速度ps-fool_score 2;//分数增加}}else if (KEY_PRESS(VK_F4))//检测f4减速按键{if (ps-fool_score 2)//设置减速上限{ps-sleep_time 20;//增加休眠时间减慢速度ps-fool_score - 2;//分数减少}}Snakemove(ps);//蛇移动Greatmap();//每次移动刷新城墙实现穿墙Sleep(ps-sleep_time);//休眠KillByWall(ps);//检测是否撞墙KillBySelf(ps);//检测是否咬到自己} while (ps-statu ok);//检测游戏状态
}
void EatFood(pSnakenode pn, psnake ps)//吃食物
{ps-snakefool-next ps-shead;//食物节点链接蛇头ps-shead ps-snakefool;//食物节点成为蛇头free(pn);//释放新开辟的蛇头节点pn NULL;pSnakenode cur ps-shead;while (cur)//遍历打印蛇身{setpos(cur-x, cur-y);wprintf(L%lc, BODY);cur cur-next;}ps-score ps-fool_score;//总分增加GreatFool(ps);//生成新的食物
}
void NotFood(pSnakenode pn, psnake ps)
{pn-next ps-shead;//让新的蛇头连接原来的蛇头ps-shead pn;//让蛇下一个位置的节点成为新的蛇头pSnakenode pcur ps-shead;while (pcur-next-next ! NULL)//找到蛇尾倒数第二个节点{pcur pcur-next;//移动}pSnakenode dle pcur;setpos(pcur-next-x, pcur-next-y);//定位光标到最后的蛇尾printf( );//覆盖原来蛇尾打印的信息free(dle-next);//释放最后的蛇尾节点dle-next NULL;pSnakenode cur pn;while (cur){setpos(cur-x, cur-y);wprintf(L%lc, BODY);cur cur-next;}
}
void KillByWall(psnake ps)
{if (ps-shead-x 56)//检测是否撞到第二列城墙{ps-shead-x 0;//重新定位穿墙后的横坐标位置第一列出}else if (ps-shead-x 0)//检测是否撞到第一列城墙{ps-shead-x 56;//重新定位穿墙后的横坐标位置第二列出}else if (ps-shead-y 0)//检测是否撞到第一行城墙{ps-shead-y 26;//重新定位穿墙后的纵坐标位置第一行出}else if (ps-shead-y 26)//检测是否撞到第二行城墙{ps-shead-y 0;//重新定位穿墙后的纵坐标位置第二行出}
}
void KillBySelf( psnake ps)
{pSnakenode pcur ps-shead-next;//保存蛇头下一个节点while (pcur)//遍历蛇身{if (ps-shead-x pcur-x ps-shead-y pcur-y)//蛇身与蛇头重合{ps-statu kill_by_self;//设置游戏状态break;//结束循环}pcur pcur-next;//移动}
}
void GemaEnd(psnake ps)
{pSnakenode pcur ps-shead;setpos(20, 12);//定位光标switch (ps-statu)//游戏状态{case kill_by_wall://撞墙wprintf(L您撞到墙上游戏结束);break;case kill_by_self://咬到自己wprintf(L您咬到自己游戏结束);break;case end://自己结束wprintf(L您主动结束了游戏);break;}setpos(0, 27);//定位光标while (pcur)//遍历蛇身销毁链表{pSnakenode del pcur;//保存销毁节点pcur pcur-next;//移动下一个销毁节点free(del);//销毁del NULL;}free(ps-snakefool);//销毁食物ps-snakefool NULL;
} test.c
#define _CRT_SECURE_NO_WARNINGS 1
#includesnake.h
void test()
{char ch 0;do{Snake snake { 0 };GemeStar(snake);GameRun(snake);GemaEnd(snake);setpos(20, 15);wprintf(L再来一局吗(Y/N):);ch getchar();setpos(0, 27);} while (ch Y || ch y);
}
int main(){setlocale(LC_ALL, );test();return 0;
}后言 这就是用C语言写出来的贪吃蛇小游戏。这游戏基本使用到C语言所学的全部内容。也算是C语言的试金石吧今天就分享到这里感谢小伙伴的耐心垂阅咱们下期见拜拜~