C语言项目---贪吃蛇

目录

  • 一 、知识铺垫
    • 1.win32API介绍
  • 二、贪吃蛇的数据结构的设计
    • 1.整体框架
    • 2.初始化界面
    • 3.贪吃蛇的运行
    • 4.游戏的退出
  • 三、整体代码

一 、知识铺垫

贪吃蛇涉及的知识:C语言函数、枚举、结构体、动态内存管理、预处理指令、链表、win32API等

1.win32API介绍

Windows除了协调应用程序、分配内存、管理资源之外,同时也是一个很大的服务中心,(每一种服务就是一个函数),可以磅应用程序达到开启视窗,描绘图形、使用周边设备等目的,由于这些函数服务的对象是应用程序,所以便称之为API,的应用程序编程接口。
C语言的system函数可以达到执行系统操作。
GetStdHandle是一个Windows API函数,它用于从一个特定的标准设备(标准输入、标准输出或标准错误)中取得一个句柄(用于标识不同设备的数值),使用这个句柄可以操作设备。
意思就是要操作这个设备,需要获取这个的操作权限,GetStdHandle就是起这个作用
CONSOLE_CURSOR_INFO:这个结构体,包含有关控制光标的信息
dwSize,由光标填充的字符单元格的百分比。由1到100之间。光标外观会变化,返回从完全填充单元格到单元底部的水平线条。
bVisible,游标的可见性。如果光标可见,则此成员为TRUE。

说这么多大家可能对函数GetStdHandle有点懵,接下来我将用代码演示,让大家理解加深。
代码演示:

int main()
{//先获取信息才能修改//COORD pos = { 40,10 };//设置了一个坐标CONSOLE_CURSOR_INFO cursor_info = { 0 };//创建一个变量HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);GetConsoleCursorInfo(handle, &cursor_info);//获取//cursor_info.dwSize = 0;//将光标信息改掉,设置控制台信息cursor_info.bVisible = false;//默认为true,将可见信息改为falseSetConsoleCursorInfo(handle, &cursor_info);//将光标信息设置进去,利用这个函数return 0;
}

运行结果:
在这里插入图片描述
可以看见在运行界面上,没有光标了,对比一下有光标的默认界面:
在这里插入图片描述
可以看见有光标的界面很影响游戏体验,所以,在运行游戏之前需要把光标去掉。把结构体cursor_info的一个成员bVisible设置为false
接下来介绍识别键盘按键的函数:
GetAsyncKeyState
将键盘上每一个键的虚拟键值传递给函数,函数通过返回值分辨键盘的状态。
GetAsyncKeyState返回值是short类型,在上一次调用GetAsyncKeyState函数后,如果返回的16位的short数据中,最高位是1,说明按键的状态是按下,如果最高位是0,说明键盘的状态是抬起,如果最低位被置为1则说明,该键被安国,否则为0.
意思就是:我们可以通过这个函数来检测键盘上的键是否被按过时,这个函数会返回一个shor类型的值,而short类型的值占十六个比特位,
00000000 00000000
最高位如果是1,则当前的状态是按下 1-按下
最高位如果是0,则当前的状态是抬起 0-抬起
最低位如果置为1,则说明按过 1-按过
最低位如果置为0,则说明没有按过 0-没有按过
对于GetAsyncKeyState这个函数来说是识别键盘按键的,但是如何识别呢,按照上面的叙述我们先不管高位,如果低位是1就表示按过,如果低位是0就表示未按过,那我们可以将这个这个函数&1,如果是按过最后得到的结果就是1,因为不管高位是多少&0都是0,所以只用考虑最低位,如果最后结果是0就表示未按过
接下来我们用代码测试一下:

#define KEY_PRESS(vk) (GetAsyncKeyState(vk)&0x1? 1:0)
int main()
{while (1){if (KEY_PRESS(0x30))printf("0\n");else if (KEY_PRESS(0x31))printf("1\n");else if (KEY_PRESS(0x32))printf("2\n");else if (KEY_PRESS(0x33))printf("3\n");else if (KEY_PRESS(0x34))printf("4\n");else if (KEY_PRESS(0x35))printf("5\n");else if (KEY_PRESS(0x36))printf("6\n");else if (KEY_PRESS(0x37))printf("7\n");else if (KEY_PRESS(0x38))printf("8\n");else if (KEY_PRESS(0x39))printf("9\n");}
}

在这里插入图片描述

注意:这里运行界面上出现的12345并不是scanf函数输入上去的,而是我们通过识别键盘按钮打印在屏幕上面的。
接下来介绍宽字符概念
宽字符
普通字符占一个字节,但是宽字符占两个字节,再简单一点讲一下,C语言的国际化特性相关的知识,过去C语言并不适合非英语国家使用,C语言最初假定字符都是单字节的,但是这些假定并不是再世界上任何地方都适用。
我们将宽字符和窄字符对比一下:
在这里插入图片描述
可以看到,上面的是两个窄字符,下面是一个宽字符,可以看出两个窄字符等于一个宽字符的

C语言默认采用ASCII编码的,ASCII字符采用的是单字节编码,且只使用了单字节的七位。
后来为了使C语言适应国际化,C语言的标准中不断加入了国际化的支持。
<locale.h>提供的函数用于控制c标准库中对于不同的地区会产生不一样行为的部分。
在标准中,依赖地区的部分有以下几项:
数字量的格式
货币量的格式
字符集
日期和时间的表示形式
类项:
通过修改地区,程序可以改变它的行为来适应世界的不同区域,但地区的改变可能影响库的许多部分,其中一部分可能是我们不希望修改的。所以C语言支持针对不同的类项进行修改,下面的一个宏,指定一个类项:
LC_COLLATE:影响字符串比较函数等等……………
Setlocale函数
用于修改当前地区,可以针对一个类项进行修改,也可以针对所有类型进行修改。
Setlocale的第一个参数可以是前面说明的类项中的一个,那么每次只会影响一个类项,如果第一个参数是LC_ALL,就会影响所有类项。。
C标准给第二个参数仅定义了两种可能得取值:“c”(正常模式)和“本地模式”。
在任意程序执行开始,都会隐藏式直系那个调用这个函数并选择正常模式,默认就是正常模式,对应的所有项都是正常模式。
当程序运行起来之后想改变地区,就只能显示调用setlocale函数,用“”作为第二个参数,调用setlocale函数就切换为本地模式,这种模式下的程序适应本地环境。
Setlocale的返回值是一个字符串指针,表示已经设置好的格式,如果调用失败,则返回空指针。
Setlocale可以用来查询当前地区 ,这时第二个参数设为NULL就可以了。
下面代码演示的是 C语言默认的本地信息是什么?
在这里插入图片描述
宽字符的打印
宽字符的字面量必须加上前缀L,否则C语言会把字面量当做宅字符类型处理,前缀L在单引号前面,表示宽字符,宽字符的打印用wprintf,对应wprintf()的占位符为%lc;在双引号前面,表示宽字符串,对应wprintf()的占位符为%ls。

二、贪吃蛇的数据结构的设计

1.整体框架


void test()
{//创建贪吃蛇Snake snake = { 0 };GameStart(&snake);//游戏开始前的初始化GameRun(&snake);//玩游戏的过程GameEnd(&snake);//善后工作
}int main()
{//修改适配本地中文环境setlocale(LC_ALL, "");test();//贪吃蛇游戏测试
}

整个游戏分为三个部分:游戏的初始化,游戏的运行过程、和最后游戏结束的界面

2.初始化界面

游戏需要搭建一个框架:
在这里插入图片描述
按照宽字符定义来说,墙的长度就应该是宽的两倍。
打印一个宽为28的棋盘长就必须是56,代码如下:

void CreateMap()
{//上SetPos(0, 0);int i = 0;for (i = 0;i < 56;i+=2){wprintf(L"%lc", WALL);}//下SetPos(0, 25);for (i = 0;i < 56;i += 2){wprintf(L"%lc", WALL);}//左SetPos(0, 1);for (i = 0;i < 24;i++){wprintf(L"%lc\n", WALL);}//右for (i = 0;i < 24;i++){SetPos(54, 1 + i);wprintf(L"%lc", WALL);}
}

运行结果:
在这里插入图片描述
进入棋盘前的信息:

void SetPos(int x, int y)
{//获得设备句柄HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);//根据句柄设置光标的位置COORD pos = { x,y };SetConsoleCursorPosition(handle, pos);
}void WelcomeGame()
{//打印欢迎信息SetPos(38, 13);printf("欢迎来到贪吃蛇小游戏\n");SetPos(38, 15);system("pause");system("cls");//功能介绍信息SetPos(32, 12);printf("用↑.↓.←.→来控制蛇的移动,F4是加速,F5是减速");SetPos(32, 14);printf("加速能得到更高的分数");SetPos(32, 16);system("pause");system("cls");
}

我们还需要设置蛇的初始位置,对于蛇身,我们可以通过单向链表将其串起来,然后遍历逐个打印,


//游戏的状态
enum GAME_STATUS
{OK = 1,//正常运行ESC,//按了ESC键退出,正常退出KILL_BY_WALL,//撞墙了KILL_BY_SELF,//撞到自身了
};//蛇行走的方向
enum DIRECTION
{UP=1,//向上DOWN,//向下LEFT,//向左RIGHT,//向右
};
//蛇身节点的定义
typedef struct SnakeNode
{int x;int y;struct SnakeNode* next;
}SnakeNode;
typedef struct SnakeNode* pSnakeNode;
//每一个节点在动的时候都在变化,所以每一个动的时候都要用坐标来记录其位置
//用结构体指针指向下一个蛇身的节点//贪吃蛇---整个游戏的维护
typedef struct Snake
{pSnakeNode pSnake;//维护整条蛇的指针pSnakeNode pFood;//指向食物的指针int score;//当前累计的分数int foodweight;//一个食物的分数int SleepTime;//蛇休眠的时间,休眠的时间越短,蛇的速度越快,休眠的时间越长,休眠的速度越慢enum GAME_STATUS status;//游戏当前的状态enum DIRECTION dir;//......
}Snake,*pSnake;

出了蛇身需要管理,还有分数和单个食物的分数,还有蛇的休眠时间,还有当前游戏状态,蛇的方向都需要管理,我们一并将其封装在一个结构体中管理。

蛇的默认其实位置,我们就将其定在(24,5)这个坐标上。
接下来,打印蛇身并初始化分数还有状态等信息…

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){printf("InitSnake():malloc() fail\n");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;for (i = 0;i < 5;i++){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;//200毫秒ps->status = OK;
}

对于蛇身,我们起始位置,先创建五个节点,意思就是游戏开始蛇的长度便是5,然后我们利用头插法将其串起来。

创建食物:

void CreateFood(pSnake ps)
{int x = 0;int y = 0;again:do{x = rand() % 53 + 2;//2~54y = rand() % 24 + 1;//1~25} while (x % 2 != 0);pSnakeNode cur = ps->pSnake;while (cur != NULL){if (cur->x == x && cur->y == y){goto again;}cur = cur->next;}//创建食物pSnakeNode food = (pSnakeNode)malloc(sizeof(SnakeNode));if (food == NULL){perror("CreateFood():malloc() ");return;}food->x = x;food->y = y;ps->pFood = food;SetPos(food->x, food->y);wprintf(L"%lc", FOOD);
}

对于创建食物我们需要注意的是,食物也是一个蛇的节点,因为蛇吃了食物之后,会变长,所以食物会变成蛇的新的节点。

我们将以上函数都封装在一个文件当中:

void GameStart(pSnake ps)
{//设置控制台的信息,窗口大小,窗口名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);//打印欢迎信息WelcomeGame();//绘制地图CreateMap();//初始化蛇InitSnake(ps);//创建食物CreateFood(ps);
}

3.贪吃蛇的运行

void GameRun(pSnake ps)
{//打印帮助信息PrintHelpInfo();do{//当前的分数情况SetPos(65, 9);printf("总分:%5d\n", ps->score);SetPos(65, 10);printf("食物的分值:%02d\n", ps->foodweight);//监测按键//上、下、左、右、ESC、space、F1、F2if (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_RIGHT) && ps->dir != LEFT){ps->dir = RIGHT;}else if (KEY_PRESS(VK_LEFT) && ps->dir != RIGHT){ps->dir = LEFT;}else if (KEY_PRESS(VK_ESCAPE)){ps->status = ESC;break;}else if (KEY_PRESS(VK_SPACE)){//游戏暂停pause();//暂停和恢复暂停}else if (KEY_PRESS(VK_F1)){if (ps->SleepTime >= 80){ps->SleepTime -= 30;ps->foodweight += 2;}}else if (KEY_PRESS(VK_F2)){if (ps->foodweight > 2){ps->SleepTime += 30;ps->foodweight -= 2;}}//睡眠一下Sleep(ps->SleepTime);//走一步SnakeMove(ps);} while (ps->status==OK);
}

先利用上面讲的识别键盘按键的函数把需要用到的按键给识别了,然后在最前面打印需要的信息:

void PrintHelpInfo()
{SetPos(65, 14);printf("不能穿墙,不能咬到自己\n");SetPos(65, 15);printf("用↑.↓.←.→分别控制蛇的移动\n");SetPos(65, 16);printf("F1:加速  F2:减速");SetPos(65, 17);printf("ESC:退出游戏  space:暂停游戏");
}

然后对蛇移动的函数进行封装:

==注意:对于蛇的移动,我们需要对他的异于其他方向的临近的坐标进行统计,然后用Switch case语句把每一个方向对应的下一个坐标写出来:
坐标写出来之后,就需要判断是否是食物,如果是食物的话我们就需要将食物同化为蛇身的节点,如果不是食物的话,就需要将下一个节点作为蛇的头,然后将最后一个尾节点给删去,这样就达到了移动的效果

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 RIGHT:pnext->x = ps->pSnake->x + 2;pnext->y = ps->pSnake->y;break;case LEFT:pnext->x = ps->pSnake->x - 2;pnext->y = ps->pSnake->y;break;}if (NextIsFood(ps,pnext)){//吃掉食物EatFood(ps,pnext);}else{//不是食物正常走一步NotEatFood(ps,pnext);}//监测撞墙KillByWall(ps);//撞到自己KillBySelf(ps);
}

如果是食物:

void EatFood(pSnake ps, pSnakeNode pnext)
{pnext->next = ps->pSnake;ps->pSnake = pnext;pSnakeNode cur = ps->pSnake;//打印蛇身while (cur){SetPos(cur->x, cur->y);wprintf(L"%lc", BODY);cur = cur->next;}ps->score += ps->foodweight;free(ps->pFood);CreateFood(ps);
}

如果不是食物:

void NotEatFood(pSnake ps, pSnakeNode pnext)
{pnext->next = ps->pSnake;ps->pSnake = pnext;pSnakeNode cur = ps->pSnake;while (cur->next->next != NULL){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;
}

判断完是不是食物之后还需要判断下一个位置是否是墙或者是否是自己蛇身除头外的任何一个节点:

//监测撞墙
void KillByWall(pSnake ps)
{if (ps->pSnake->x == 0 || ps->pSnake->x ==54 ){ps->status = KILL_BY_WALL;}else if (ps->pSnake->y == 0 || ps->pSnake->y == 26){ps->status = KILL_BY_WALL;}
}//撞到自己
void KillBySelf(pSnake ps)
{pSnakeNode next = ps->pSnake->next;while (next != NULL){if (next->x == ps->pSnake->x && next->y == ps->pSnake->y){ps->status = KILL_BY_SELF;return;}next = next->next;}
}

4.游戏的退出

游戏的退出需要根据游戏不同的状态来打印不同的信息,比如正常退出,又比如撞墙,又比如撞到自己的蛇身。
还有就是游戏结束后我们需要对蛇的蛇身进行内存释放,还有食物等等,将链表的每个节点进行遍历释放。

void GameEnd(pSnake ps)
{SetPos(25, 12);switch (ps->status){//正常退出case ESC:printf("正常退出\n");break;//撞自己case KILL_BY_WALL:printf("很遗憾,撞墙了,游戏结束\n");break;//撞墙case KILL_BY_SELF:printf("很遗憾,咬到自己了,游戏结束\n");break;}SetPos(25, 26);//释放贪吃蛇的链表资源pSnakeNode cur = ps->pSnake;pSnakeNode next = NULL;while (cur){next = cur->next;free(cur);cur = next;}free(ps->pFood);ps = NULL;
}

三、整体代码

//test.c
#include "snake.h"void test()
{//创建贪吃蛇int ch = 0;do{ Snake snake = { 0 };GameStart(&snake);//游戏开始前的初始化GameRun(&snake);//玩游戏的过程GameEnd(&snake);//善后工作SetPos(25, 13);printf("再来一局吗?(Y/N):");ch = getchar();getchar();//清理\n} while (ch == 'Y' || ch == 'y');
}int main()
{//修改适配本地中文环境setlocale(LC_ALL, "");test();//贪吃蛇游戏测试
}//snake.c
#include "snake.h"void SetPos(int x, int y)
{//获得设备句柄HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);//根据句柄设置光标的位置COORD pos = { x,y };SetConsoleCursorPosition(handle, pos);
}void WelcomeGame()
{//打印欢迎信息SetPos(38, 13);printf("欢迎来到贪吃蛇小游戏\n");SetPos(38, 15);system("pause");system("cls");//功能介绍信息SetPos(32, 12);printf("用↑.↓.←.→来控制蛇的移动,F4是加速,F5是减速");SetPos(32, 14);printf("加速能得到更高的分数");SetPos(32, 16);system("pause");system("cls");
}void CreateMap()
{//上SetPos(0, 0);int i = 0;for (i = 0;i < 56;i+=2){wprintf(L"%lc", WALL);}//下SetPos(0, 25);for (i = 0;i < 56;i += 2){wprintf(L"%lc", WALL);}//左SetPos(0, 1);for (i = 0;i < 24;i++){wprintf(L"%lc\n", WALL);}//右for (i = 0;i < 24;i++){SetPos(54, 1 + i);wprintf(L"%lc", WALL);}
}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){printf("InitSnake():malloc() fail\n");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;for (i = 0;i < 5;i++){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;//200毫秒ps->status = OK;
}void CreateFood(pSnake ps)
{int x = 0;int y = 0;again:do{x = rand() % 53 + 2;//2~54y = rand() % 24 + 1;//1~25} while (x % 2 != 0);pSnakeNode cur = ps->pSnake;while (cur != NULL){if (cur->x == x && cur->y == y){goto again;}cur = cur->next;}//创建食物pSnakeNode food = (pSnakeNode)malloc(sizeof(SnakeNode));if (food == NULL){perror("CreateFood():malloc() ");return;}food->x = x;food->y = y;ps->pFood = food;SetPos(food->x, food->y);wprintf(L"%lc", FOOD);
}
void GameStart(pSnake ps)
{//设置控制台的信息,窗口大小,窗口名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);//打印欢迎信息WelcomeGame();//绘制地图CreateMap();//初始化蛇InitSnake(ps);//创建食物CreateFood(ps);
}void PrintHelpInfo()
{SetPos(65, 14);printf("不能穿墙,不能咬到自己\n");SetPos(65, 15);printf("用↑.↓.←.→分别控制蛇的移动\n");SetPos(65, 16);printf("F1:加速  F2:减速");SetPos(65, 17);printf("ESC:退出游戏  space:暂停游戏");
}void pause()
{while (1){if (KEY_PRESS(VK_SPACE)){break;}Sleep(100);}
}int NextIsFood(pSnake ps, pSnakeNode pnext)
{if (ps->pFood->x == pnext->x && ps->pFood->y == pnext->y){return 1;}else{return 0;}
}void EatFood(pSnake ps, pSnakeNode pnext)
{pnext->next = ps->pSnake;ps->pSnake = pnext;pSnakeNode cur = ps->pSnake;//打印蛇身while (cur){SetPos(cur->x, cur->y);wprintf(L"%lc", BODY);cur = cur->next;}ps->score += ps->foodweight;free(ps->pFood);CreateFood(ps);
}void NotEatFood(pSnake ps, pSnakeNode pnext)
{pnext->next = ps->pSnake;ps->pSnake = pnext;pSnakeNode cur = ps->pSnake;while (cur->next->next != NULL){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;
}//监测撞墙
void KillByWall(pSnake ps)
{if (ps->pSnake->x == 0 || ps->pSnake->x ==54 ){ps->status = KILL_BY_WALL;}else if (ps->pSnake->y == 0 || ps->pSnake->y == 26){ps->status = KILL_BY_WALL;}
}//撞到自己
void KillBySelf(pSnake ps)
{pSnakeNode next = ps->pSnake->next;while (next != NULL){if (next->x == ps->pSnake->x && next->y == ps->pSnake->y){ps->status = KILL_BY_SELF;return;}next = next->next;}
}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 RIGHT:pnext->x = ps->pSnake->x + 2;pnext->y = ps->pSnake->y;break;case LEFT:pnext->x = ps->pSnake->x - 2;pnext->y = ps->pSnake->y;break;}if (NextIsFood(ps,pnext)){//吃掉食物EatFood(ps,pnext);}else{//不是食物正常走一步NotEatFood(ps,pnext);}//监测撞墙KillByWall(ps);//撞到自己KillBySelf(ps);
}void GameRun(pSnake ps)
{//打印帮助信息PrintHelpInfo();do{//当前的分数情况SetPos(65, 9);printf("总分:%5d\n", ps->score);SetPos(65, 10);printf("食物的分值:%02d\n", ps->foodweight);//监测按键//上、下、左、右、ESC、space、F1、F2if (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_RIGHT) && ps->dir != LEFT){ps->dir = RIGHT;}else if (KEY_PRESS(VK_LEFT) && ps->dir != RIGHT){ps->dir = LEFT;}else if (KEY_PRESS(VK_ESCAPE)){ps->status = ESC;break;}else if (KEY_PRESS(VK_SPACE)){//游戏暂停pause();//暂停和恢复暂停}else if (KEY_PRESS(VK_F1)){if (ps->SleepTime >= 80){ps->SleepTime -= 30;ps->foodweight += 2;}}else if (KEY_PRESS(VK_F2)){if (ps->foodweight > 2){ps->SleepTime += 30;ps->foodweight -= 2;}}//睡眠一下Sleep(ps->SleepTime);//走一步SnakeMove(ps);} while (ps->status==OK);
}void GameEnd(pSnake ps)
{SetPos(25, 12);switch (ps->status){//正常退出case ESC:printf("正常退出\n");break;//撞自己case KILL_BY_WALL:printf("很遗憾,撞墙了,游戏结束\n");break;//撞墙case KILL_BY_SELF:printf("很遗憾,咬到自己了,游戏结束\n");break;}SetPos(25, 26);//释放贪吃蛇的链表资源pSnakeNode cur = ps->pSnake;pSnakeNode next = NULL;while (cur){next = cur->next;free(cur);cur = next;}free(ps->pFood);ps = NULL;
}//snake.h
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<locale.h>
#include<stdlib.h>
#include<Windows.h>
#include<stdbool.h>
#define WALL L'□'
#define FOOD L'★'
#define BODY L'●'
#define KEY_PRESS(vk) (GetAsyncKeyState(vk)&0x1? 1:0)//蛇默认的起始坐标
#define POS_X 24
#define POS_Y 5//游戏的状态
enum GAME_STATUS
{OK = 1,//正常运行ESC,//按了ESC键退出,正常退出KILL_BY_WALL,//撞墙了KILL_BY_SELF,//撞到自身了
};//蛇行走的方向
enum DIRECTION
{UP=1,//向上DOWN,//向下LEFT,//向左RIGHT,//向右
};//蛇身节点的定义
typedef struct SnakeNode
{int x;int y;struct SnakeNode* next;
}SnakeNode;
typedef struct SnakeNode* pSnakeNode;
//每一个节点在动的时候都在变化,所以每一个动的时候都要用坐标来记录其位置
//用结构体指针指向下一个蛇身的节点//贪吃蛇---整个游戏的维护
typedef struct Snake
{pSnakeNode pSnake;//维护整条蛇的指针pSnakeNode pFood;//指向食物的指针int score;//当前累计的分数int foodweight;//一个食物的分数int SleepTime;//蛇休眠的时间,休眠的时间越短,蛇的速度越快,休眠的时间越长,休眠的速度越慢enum GAME_STATUS status;//游戏当前的状态enum DIRECTION dir;//......
}Snake,*pSnake;//游戏开始的准备环节
void GameStart(pSnake ps);//欢迎界面
void WelcomeGame();//绘制地图
void CreateMap();//初始化贪吃蛇
void InitSnake(pSnake ps);//创建食物void CreateFood(pSnake ps);//游戏运行
void GameRun(pSnake snake);//打印帮助信息
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 SetPos(int x, int y);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/449547.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

关于破解IDEA后启动闪退的问题

问题描述&#xff1a;2023.1启动不了&#xff0c;双击桌面图标&#xff0c;没有响应。 解决办法&#xff1a; 打开C:\Users\c\AppData\Roaming\JetBrains\IntelliJIdea2023.1\idea64.exe.vmoptions 这个文件。 内容如下所示&#xff1a; 删除红框的数据以后&#xff0c;再登录…

「递归算法」:Pow(x,n)

一、题目 实现 pow(x, n) &#xff0c;即计算 x 的整数 n 次幂函数&#xff08;即&#xff0c;xn &#xff09;。 示例 1&#xff1a; 输入&#xff1a;x 2.00000, n 10 输出&#xff1a;1024.00000示例 2&#xff1a; 输入&#xff1a;x 2.10000, n 3 输出&#xff1a;9…

使用pyinstaller打包tkinter程序

主要问题&#xff1a; &#xff08;1&#xff09;如何同时打包多个python文件 &#xff08;2&#xff09;打包过程中有缺失的包怎么处理 &#xff08;3&#xff09;如何解决打包程序过大的问题 以上三个问题是使用pyinstaller打包python文件常见的问题&#xff0c;我将以自己…

Django的web框架Django Rest_Framework精讲(二)

文章目录 1.自定义校验功能&#xff08;1&#xff09;validators&#xff08;2&#xff09;局部钩子&#xff1a;单字段校验&#xff08;3&#xff09;全局钩子&#xff1a;多字段校验 2.raise_exception 参数3.context参数4.反序列化校验后保存&#xff0c;新增和更新数据&…

【数据库】创建索引的注意事项

&#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a;数据库 ⛳️ 功不唐捐&#xff0c;玉汝于成 目录 前言 正文 结语 我的其他博客 前言 在数据库设计和优化中&#xff0c;索引的合理使用是提高查询性能和加速数据检索的关键因素之一。通过选…

关于华为应用市场上架,申请权限未告知目的被驳回问题的简单处理方式

关于华为应用市场上架过程中出现的【您的应用在运行时&#xff0c;未同步告知权限申请的使用目的&#xff0c;向用户索取&#xff08;存储、拍照&#xff09;等权限&#xff0c;不符合华为应用市场审核标准。】 使用方式&#xff1a; 1、引入 import permision from "/m…

使用MATLAB驱动USRP-N320实现OFDM自收自发

文章目录 前言一、收发代码二、截取一帧 OFDM三、执行主函数四、运行结果五、资源自取 前言 本文作为实验结果记录及测试&#xff0c;方便后面回顾所做的工作。本文基于一台电脑和一台 USRP 设备实现了 OFDM 自发和自收功能 一、收发代码 ofdm_tx_rx_test.m 核心代码&#x…

Linux 系统开始配置

文章目录 备份源为root 设置密码安装基本工具切换root 用户删除snap从 Ubuntu 移除 Snap 后使用 deb 文件安装软件商店和 Firefox在 Ubuntu 系统恢复到 Snap 软件包总结 删除 vim安装neovim在线安装neovim压缩安装neovim安装lazyvim安装剪切板 安装qt配置 Qt 环境不在sudoers文…

预处理详解(上)

⽬录&#xff1a; 1. 预定义符号 2. #define定义常量 3. #define定义宏 4. 带有副作⽤的宏参数 5. 宏替换的规则 6. 宏函数的对⽐ 7. #和## 8. 命名约定 9. #undef 10. 命令⾏定义 11. 条件编译 12. 头⽂件的包含 13. 其他预处理指令 正⽂开始 1. 预…

Facebook未来展望:社交媒体的下一个篇章

社交媒体一直是连接人与人之间的纽带&#xff0c;而Facebook则一直在推动这一领域的发展。随着科技不断演进和社会需求的不断变迁&#xff0c;Facebook正积极筹谋社交媒体的下一个篇章。本文将深入剖析Facebook的未来展望&#xff0c;探讨其在社交媒体领域所迎接的新时代。 1. …

2023年计算机视觉领域突破性研究有哪些?

B站&#xff1a;啥都会一点的研究生公众号&#xff1a;啥都会一点的研究生 ​回顾2023年&#xff0c;计算机视觉领域有哪些较为突出的研究成果&#xff1f;一起看看吧 SAM(Segment Anything Model) SAM 由 Meta AI 开发&#xff0c;是 CV 中分割任务的基础模型&#xff0c;彻…

Lambda表达式(匿名函数)

C11中引入了lambda表达式&#xff0c;定义匿名的内联函数。 我们可以直接原地定义函数而不用再跑到外面去定义函数跳来跳去。 同时在stl的排序上也有作用。 [capture] (parameters) mutable ->return-type {statement}下面逐一介绍各个参数的含义. [capture] : 捕获&#…