C语言-------实现贪吃蛇小游戏

目录

一、预备知识

1.1 Win32 API介绍

Windows 这个多作业系统除了协调应用程序的执行、分配内存、管理资源之外, 它同时也是一个很大的服务中心,调用这个服务中心的各种服务(每一种服务就是一个函数),可以帮应用程序达到开启视窗、描绘图形、使用周边设备等目的,由于这些函数服务的对象是应用程序(Application), 所以便称之为 Application Programming Interface,简称 API 函数。WIN32 API也就是Microsoft Windows 32位平台的应用程序编程接口。​

1.2 修改控制台相关属性

我们可以使用cmd命令来设置控制台窗口的长宽:设置控制台窗口的大小,30行,100列.

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

也可以通过命令设置控制台窗口的名字:贪吃蛇

	system("title 贪吃蛇");

效果展示:

在这里插入图片描述

1.3 控制台上的坐标COORD

COORD是WindowsAPI中定义的⼀个结构体,表示一个字符在控制台屏幕幕缓冲区上的坐标,坐标系(0,0)的原点位于缓冲区的顶部左侧单元格。控制台上的坐标如图展示,横向为x轴,从左到右依次增长;纵向为y轴,从上到下依次增长.

在这里插入图片描述
COORD类型的声明:

typedef struct _COORD {SHORT X;SHORT Y;
} COORD, *PCOORD;

然后对坐标进行赋值:

OORD pos = { 10, 15 };

1.4 GetStdHandle

GetStdHandle是一个Windows API函数。 它用于从一个特定的标准设备(标准输入、标准输出或标准错误)中取得一个句柄(用来标识不同设备的数值)

示例:

	//获得标准输出设备的句柄HANDLE houtput = NULL;houtput = GetStdHandle(STD_OUTPUT_HANDLE);

1.5 GetConsoleCursorInfo

GetConsoleCursorInfo是用来检索有关指定控制台屏幕缓冲区的光标大小和可见性的信息
示例:

	//获取标准输出的句柄HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);//定义一个光标信息的结构体CONSOLE_CURSOR_INFO cursor_info = { 0 };//获取和houtput句柄相关的控制台上的光标信息,存放在cursor_info中GetConsoleCursorInfo(houtput, &cursor_info);

GetConsoleCursorInfo

这个结构体,包含有关控制台光标的信息.
它含有两个功能:dwSize和bVisible,它们分别是什么意思呢?

dwSize表示由光标填充的字符单元格的百分比。此值介于1到100之间。光标外观会变化,范围从完全填充单元格到单元底部的水平线条。

bVisible: 表示游标的可见性。如果光标可见,则此成员为TRUE。反之,为FALSE。

在这里,我们要使用隐藏光标这一功能.

	//隐藏控制台光标cursor_info.bVisible = false;

1.6 SetConsoleCursorInfo

表示设置指定控制台屏幕缓冲区的光标的大小和可见性。

	//设置控制台上的光标信息SetConsoleCursorInfo(houtput, &cursor_info);

1.7 SetConsoleCursorPosition

表示设置指定控制台屏幕缓冲区中的光标位置,我们想要将设置的坐标信息放在COORD类型的pos中,然后调用SetConsoleCursorPosition函数将光标位置设置到指定的位置。因此,我们定义一个函数-------SetPos,表示设置光标位置的函数.

void SetPos(short x, short y)
{//获得标准输出设备的句柄HANDLE houtput = NULL;houtput = GetStdHandle(STD_OUTPUT_HANDLE);//定位光标的位置 COORD pos = { x,y };SetConsoleCursorPosition(houtput, pos);
}

1.8 GetAsyncKeyState

表示获取按键情况.函数的原型为:

SHORT GetAsyncKeyState(int vKey
);

我们将键盘上每个键的虚拟键值传递给函数,函数通过返回值来分辨按键的状态。
判断依据:GetAsyncKeyState的返回值是short类型,在上⼀次调GetAsyncKeyState函数后,如果返回的16位的short数据中,最高位是1,说明按键的状态是按下,如果最高是0,说明按键的状态是抬起;如果最低位被置为1则说明,该按键被按过,否则为0。
因此,要检测一个键是否被按过,就可以检测GetAsyncKeyState返回值的最低值是否为1.

#define KEY_PRESS(VK)  ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )

二、游戏设计

2.1 地图

我们假设打印墙体使用宽字符:□,打印蛇身使用宽字符●,打印食物使用宽字符★。
注: 宽字符和普通的字符是有区别的,一个普通的字符表示占用一个字节的大小,而一个宽字符表示占用两个字节的大小。

这里可能就会有人产生疑惑-----到底什么是宽字符呢?下面就让我给大家详细介绍一下宽字符的相关知识以及打印。

2.1.1 <locale.h> 本地化

<locale.h>提供的函数用于控制C标准库中对于不同的地区会产生不⼀样行为的部分。

在标准中,依赖地区的部分有以下几项:
• 数字量的格式
• 货币量的格式
• 字符集
• 日期和时间的表示形式

2.1.2 setlocale

函数原型为:

char* setlocale (int category, const char* locale);

setlocale函数用于修改当前地区,可以针对⼀个类项修改,也可以针对所有类项。

setlocale的第⼀个参数可以是前面说明的类项中的⼀个,那么每次只会影响⼀个类项,如果第⼀个参数是LC_ALL,就会影响所有的类项。

C标准给第⼆个参数仅定义了2种可能取值:“C”(正常模式)和" "(本地模式)。

在此处,我们要用到本地模式:

	setlocale(LC_ALL, "");

2.1.3 宽字符打印

宽字符的字面量必须加上前缀“L”,否则C语言会把字面量当作窄字符类型处理。前缀“L”在单引号前面,表示宽字符,对应wprintf() 的占位符为%lc ;在双引号前面,表示宽字符串,对应wprintf() 的占位符为%ls 。
下面我们用代码来感受一下宽字符的使用及打印出来的效果

int main()
{//设置本地化setlocale(LC_ALL, "");//打印字符char a = 'a';char b = 'b';printf("%c%c\n", a, b);wchar_t wc1 = L'比';wchar_t wc2 = L'特';wprintf(L"%lc\n", wc1);wprintf(L"%lc\n", wc2);return 0;
}

在这里插入图片描述

我们可以发现,上面说过宽字符占两个字节的大小和普通字符占一个字节的大小的说法完全没有问题的。

2.1.4 地图坐标

假设,我们选择的坐标大小为27行和58列。
在这里插入图片描述

2.2 蛇身和食物

我们假设蛇的长度是5,蛇身的每个节点是●,在固定的⼀个坐标处,比如(24,5)处开始出现蛇,连续5个节点。

注意:蛇的每个节点的x坐标必须是2的倍数,否则可能会出现蛇的⼀个节点有⼀半出现在墙体中,另外⼀半出现在墙外的现象,坐标不好对齐。

关于食物,就是在墙体内随机生成⼀个坐标(x坐标必须是2的倍数),但坐标不能和蛇的身体重合,然后打印★。效果如上图

2.3 相关数据结构设计

我们使用链表来存储蛇的信息,那么蛇的每⼀节其实就是链表的每个节点。每个节点只要记录好蛇身节点在地图上的坐标就行了。
蛇身的结构如下:

typedef struct SnakeNode
{//坐标int x;int y;//指向下一个节点的指针struct SnakeNode* next;
}SnakeNode,*pSnakeNode;

如果我们想管理整条贪吃蛇,我们需要再封装一个Snake的结构来维护整条贪吃蛇:

//贪吃蛇
typedef struct Snake
{pSnakeNode _pSnake;//指向蛇头的指针pSnakeNode _pFood;//指向食物的指针enum DIRECTION _dir;//蛇的方向enum GAME_STATUS _status;//蛇的状态int _food_weight;//一个食物的分数int _score;//总成绩int _sleep_time;//休息时间  时间越短,速度越快;时间越长,速度越慢
}Snake,*pSnake;

关于蛇的方向,可以使用枚举

//蛇的方向
enum DIRECTION
{UP = 1,DOWN,LEFT,RIGHT
};

关于蛇的状态,也同样可以使用枚举:

//蛇的状态
enum GAME_STATUS
{OK,//正常KILL_BY_WALL,//撞墙KILL_BY_SLEF,//撞到自己END_NORMAL//正常退出
};

三、游戏逻辑思路分析

3.1 整体思路

• 游戏开始(GameStart)负责完成游戏的初始化
• 游戏运行(GameRun)负责完成游戏运行逻辑的实现
• 游戏结束(GameEnd)负责完成游戏结束的说明,实现资源释放

• 总体思路:

void test()
{int ch = 0;do{//创建贪吃蛇Snake snake = { 0 };//初始化游戏//1.打印欢迎界面//2.功能介绍//3.绘制地图//4.创建蛇//5.创建食物//6.设置游戏的相关信息GameStart(&snake);//运行游戏GameRun(&snake);//结束游戏  -  善后工作GameEnd(&snake);SetPos(20, 15);system("pause");SetPos(20, 15);printf("再来一局吗?(Y/N):");ch = getchar();while(getchar() != '\n');} while (ch == 'Y' || ch == 'y');SetPos(0, 27);
}int main()
{//设置适配本地化环境setlocale(LC_ALL, "");srand((unsigned int)time(NULL));test();return 0;
}

3.2 游戏开始

需完成的操作:

1.控制台窗口大小的设置

2.控制台窗口名字的设置

3.⿏标光标的隐藏

4.打印欢迎界面

5.创建地图

6.初始化蛇身

7.创建⻝物

3.2.1 打印欢迎界面

在游戏正式开始之前,做⼀些功能提醒

void WelcomeToGame()
{SetPos(40, 14);wprintf(L"欢迎来到贪吃蛇小游戏\n");SetPos(42, 18);system("pause");system("cls");SetPos(33, 13);wprintf(L"用↑.↓.←.→来控制蛇的运动,按F3加速,按F4减速\n");SetPos(42, 16);wprintf(L"加速能够得到更多的分数\n");SetPos(42, 25);system("pause");system("cls");
}

3.2.2 创建地图

易错点:坐标的计算
上:(0,0)到(56,0)
下:(0,26)到(56,26)
左:(0,1)到(0,25)
右:(56,1)到(56,25)

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

3.2.3 初始化蛇身

假设蛇的初始位置从(24,5)开始
游戏状态是:OK
蛇的移动速度:200毫秒
蛇的默认⽅向:RIGHT
初始成绩:0
每个⻝物的分数:10

void InitSnake(pSnake ps)
{int i = 0;pSnakeNode cur = NULL;for (i = 0; i < 5; i++){cur = (pSnakeNode)malloc(sizeof(SnakeNode));if (cur == NULL){perror("InitSnke()::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){SetPos(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;//单位为毫秒ps->_status = OK;
}

3.2.4 创建食物

先随机生成食物的坐标
注意:x必须是2的倍数;食物的坐标不能和蛇身的每个节点的坐标重复
创建食物节点,打印食物

void CreatFood(pSnake ps)
{int x = 0;int y = 0;//生成x是2的倍数//x:2~54//y:1~25
again:do{x = rand() % 53 + 2;y = rand() % 25 + 1;} while (x % 2 != 0);//x和y的坐标不能和蛇的身体坐标冲突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("CreatFood()::malloc()");return;}pFood->x = x;pFood->y = y;pFood->next = NULL;//定位位置SetPos(x, y);wprintf(L"%lc",FOOD );ps->_pFood = pFood;
}

3.3 游戏运行

需完成的操作:

1.打印帮助信息

2.检测按键

3.蛇的移动
1)下一坐标处是否是是食物——是,吃掉食物;否,不吃食物。
2)蛇是否撞墙死
3)蛇是否撞到自身而死

3.3.1 打印帮助信息

//打印帮助信息
void PrintHelpInfo()
{SetPos(64, 14);wprintf(L"%ls", L"不能穿墙,不能咬到自己.");SetPos(64, 15);wprintf(L"%ls", L"用↑.↓.←.→来控制蛇的运动.");SetPos(64, 16);wprintf(L"%ls", L"按F3加速,按F4减速.");SetPos(64, 17);wprintf(L"%ls", L"按ESC退出游戏,按空格暂停游戏.");SetPos(64, 19);wprintf(L"%ls", L"晚风制作.");}

3.3.2 检测按键

封装⼀个宏KEY_PRESS,负责检测按键状态

//定义一个宏用于检测此按键是否被按下
#define KEY_PRESS(VK) ((GetAsyncKeyState(VK)&0x1)?1:0)

3.3.3 蛇的移动

//检测下一个坐标处是否是食物
int NextIsFood(pSnakeNode pn, pSnake ps)
{if (ps->_pFood->x == pn->x && ps->_pFood->y == pn->y){return 1;}elsereturn 0;
}//下一个位置是食物,就吃掉食物
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){SetPos(cur->x, cur->y);wprintf(L"%lc", BODY);cur = cur->next;}ps->_score += ps->_food_weight;//重新创建食物CreatFood(ps);
}//下一个位置不是食物
void NoFood(pSnakeNode pn, pSnake ps)
{//头插法pn->next = ps->_pSnake;ps->_pSnake = pn;pSnakeNode cur = ps->_pSnake;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;}//检测蛇是否撞墙
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;}
}//检测蛇是否撞到自己
void KillBySlef(pSnake ps)
{pSnakeNode cur = ps->_pSnake->next;while (cur){if (cur->x == ps->_pSnake->x && cur->y == ps->_pSnake->y){ps->_status = KILL_BY_SLEF;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);//检测蛇是否撞到自己KillBySlef(ps);
}

3.4 游戏结束以及善后处理

当游戏不再继续进行时,要告知游戏结束的原因,并且释放蛇身的节点。

//结束游戏  -  善后工作
void GameEnd(pSnake ps)
{SetPos(20, 12);switch (ps->_status){case END_NORMAL:printf("你主动结束游戏!\n");break;case KILL_BY_WALL:printf("你撞到了墙上,游戏结束!\n");break;case KILL_BY_SLEF:printf("你撞到了自己,游戏结束!\n");break;}//释放蛇身的链表pSnakeNode cur = ps->_pSnake;while (cur){pSnakeNode del = cur;cur = cur->next;free(del);}
}

四、游戏代码展示

1.snake.h

在这项文件中,主要涉及游戏的相关头文件以及类型和函数的声明操作.

#pragma once#include<locale.h>
#include<stdio.h>
#include<windows.h>
#include<stdbool.h>
#include<time.h>#define POS_X 24
#define POS_Y 5
#define WALL L'□'
#define BODY L'●'
#define FOOD L'★'
//类型的声明//蛇的方向
enum DIRECTION
{UP = 1,DOWN,LEFT,RIGHT
};//蛇的状态
enum GAME_STATUS
{OK,//正常KILL_BY_WALL,//撞墙KILL_BY_SLEF,//撞到自己END_NORMAL//正常退出
};//蛇身的节点类型
typedef struct SnakeNode
{//坐标int x;int y;//指向下一个节点的指针struct SnakeNode* next;
}SnakeNode,*pSnakeNode;//贪吃蛇
typedef struct Snake
{pSnakeNode _pSnake;//指向蛇头的指针pSnakeNode _pFood;//指向食物的指针enum DIRECTION _dir;//蛇的方向enum GAME_STATUS _status;//蛇的状态int _food_weight;//一个食物的分数int _score;//总成绩int _sleep_time;//休息时间  时间越短,速度越快;时间越长,速度越慢
}Snake,*pSnake;//函数的声明//定位光标位置
void SetPos(short x, short y);//游戏的初始化
void GameStart(pSnake ps);//欢迎界面的打印
void WelcomeToGame();//创建地图
void CreatMap();//初始化蛇身
void InitSnake(pSnake ps);//创建食物
void CreatFood(pSnake ps);//游戏运行的逻辑
void GameRun(pSnake ps);//蛇走一步的过程
void SnakeMove(pSnake ps);//检测下一个坐标处是否是食物
int NextIsFood(pSnakeNode pn,pSnake ps);//下一个位置是食物,就吃掉食物
void EatFood(pSnakeNode pn, pSnake ps);//下一个位置不是食物
void NoFood(pSnakeNode pn, pSnake ps);//检测蛇是否撞墙
void KillByWall(pSnake ps);//检测蛇是否撞到自己
void KillBySlef(pSnake ps);//结束游戏  -  善后工作
void GameEnd(pSnake ps);

2.snake.c

在这项文件中,主要对上述声明的函数进行实现的操作.

#define _CRT_SECURE_NO_WARNINGS 1#include"snake.h"void SetPos(short x, short y)
{//获得标准输出设备的句柄HANDLE houtput = NULL;houtput = GetStdHandle(STD_OUTPUT_HANDLE);//定位光标的位置 COORD pos = { x,y };SetConsoleCursorPosition(houtput, pos);
}void WelcomeToGame()
{SetPos(40, 14);wprintf(L"欢迎来到贪吃蛇小游戏\n");SetPos(42, 18);system("pause");system("cls");SetPos(33, 13);wprintf(L"用↑.↓.←.→来控制蛇的运动,按F3加速,按F4减速\n");SetPos(42, 16);wprintf(L"加速能够得到更多的分数\n");SetPos(42, 25);system("pause");system("cls");
}void CreatMap()
{//上int i = 0;for (i = 0; i < 29; i++){wprintf(L"%lc", WALL);}//下SetPos(0, 26);for (i = 0; i < 29; i++){wprintf(L"%lc", WALL);}//左for (i = 1; i <= 25; i++){SetPos(0, i);wprintf(L"%lc", WALL);}//右for (i = 1; i <= 25; i++){SetPos(56, i);wprintf(L"%lc", WALL);}
}void InitSnake(pSnake ps)
{int i = 0;pSnakeNode cur = NULL;for (i = 0; i < 5; i++){cur = (pSnakeNode)malloc(sizeof(SnakeNode));if (cur == NULL){perror("InitSnke()::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){SetPos(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;//单位为毫秒ps->_status = OK;
}void CreatFood(pSnake ps)
{int x = 0;int y = 0;//生成x是2的倍数//x:2~54//y:1~25
again:do{x = rand() % 53 + 2;y = rand() % 25 + 1;} while (x % 2 != 0);//x和y的坐标不能和蛇的身体坐标冲突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("CreatFood()::malloc()");return;}pFood->x = x;pFood->y = y;pFood->next = NULL;//定位位置SetPos(x, y);wprintf(L"%lc",FOOD );ps->_pFood = pFood;
}void GameStart(pSnake ps)
{//0.先设置窗口的大小光标隐藏system("mode con cols=100 lines=30");system("title 贪吃蛇");HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);//隐藏光标操作//定义一个光标信息的结构体CONSOLE_CURSOR_INFO cursor_info = { 0 };//获取和houtput句柄相关的控制台上的光标信息,存放在cursor_info中GetConsoleCursorInfo(houtput, &cursor_info);//隐藏控制台光标cursor_info.bVisible = false;//设置控制台上的光标信息SetConsoleCursorInfo(houtput, &cursor_info);//1.打印欢迎界面//2.功能介绍WelcomeToGame();//3.绘制地图CreatMap();//4.初始化蛇身InitSnake(ps);   //5.创建食物CreatFood(ps);
}//打印帮助信息
void PrintHelpInfo()
{SetPos(64, 14);wprintf(L"%ls", L"不能穿墙,不能咬到自己.");SetPos(64, 15);wprintf(L"%ls", L"用↑.↓.←.→来控制蛇的运动.");SetPos(64, 16);wprintf(L"%ls", L"按F3加速,按F4减速.");SetPos(64, 17);wprintf(L"%ls", L"按ESC退出游戏,按空格暂停游戏.");SetPos(64, 19);wprintf(L"%ls", L"晚风制作.");}#define KEY_PRESS(vk) ((GetAsyncKeyState(vk)&1)?1:0)//暂停
void Pause()
{while (1){Sleep(200);if (KEY_PRESS(VK_SPACE)){break;}}
}//检测下一个坐标处是否是食物
int NextIsFood(pSnakeNode pn, pSnake ps)
{if (ps->_pFood->x == pn->x && ps->_pFood->y == pn->y){return 1;}elsereturn 0;
}//下一个位置是食物,就吃掉食物
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){SetPos(cur->x, cur->y);wprintf(L"%lc", BODY);cur = cur->next;}ps->_score += ps->_food_weight;//重新创建食物CreatFood(ps);
}//下一个位置不是食物
void NoFood(pSnakeNode pn, pSnake ps)
{//头插法pn->next = ps->_pSnake;ps->_pSnake = pn;pSnakeNode cur = ps->_pSnake;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;}//检测蛇是否撞墙
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;}
}//检测蛇是否撞到自己
void KillBySlef(pSnake ps)
{pSnakeNode cur = ps->_pSnake->next;while (cur){if (cur->x == ps->_pSnake->x && cur->y == ps->_pSnake->y){ps->_status = KILL_BY_SLEF;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);//检测蛇是否撞到自己KillBySlef(ps);
}//游戏运行的逻辑
void GameRun(pSnake ps)
{//打印帮助信息PrintHelpInfo();//检测按键do{//打印总分数和食物的分值SetPos(64, 10);printf("总分数:%d\n", ps->_score);SetPos(64, 11);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_F3)){//加速if (ps->_sleep_time > 80){ps->_sleep_time -= 30;ps->_food_weight += 2;}}else if (KEY_PRESS(VK_F4)){//减速if (ps->_food_weight > 2){ps->_sleep_time += 30;ps->_food_weight -= 2;}}//蛇走一步的过程SnakeMove(ps);Sleep(ps->_sleep_time);} while (ps->_status == OK);
}
//结束游戏  -  善后工作
void GameEnd(pSnake ps)
{SetPos(20, 12);switch (ps->_status){case END_NORMAL:printf("你主动结束游戏!\n");break;case KILL_BY_WALL:printf("你撞到了墙上,游戏结束!\n");break;case KILL_BY_SLEF:printf("你撞到了自己,游戏结束!\n");break;}//释放蛇身的链表pSnakeNode cur = ps->_pSnake;while (cur){pSnakeNode del = cur;cur = cur->next;free(del);}
}

3.test.c

这项文件则完成函数的测试逻辑操作.

#define _CRT_SECURE_NO_WARNINGS 1#include "snake.h"//完成游戏的测试逻辑
void test()
{int ch = 0;do{//创建贪吃蛇Snake snake = { 0 };//初始化游戏//1.打印欢迎界面//2.功能介绍//3.绘制地图//4.创建蛇//5.创建食物//6.设置游戏的相关信息GameStart(&snake);//运行游戏GameRun(&snake);//结束游戏  -  善后工作GameEnd(&snake);SetPos(20, 15);system("pause");SetPos(20, 15);printf("再来一局吗?(Y/N):");ch = getchar();while(getchar() != '\n');} while (ch == 'Y' || ch == 'y');SetPos(0, 27);
}int main()
{//设置适配本地化环境setlocale(LC_ALL, "");srand((unsigned int)time(NULL));test();return 0;
}

今天的分享就到这里啦,如果感觉内容不错,记得一键三连噢。创作不易,感谢大家的支持,我们下次再见!

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

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

相关文章

PyVista 3D数据可视化 Python 库 简介

Pyvista是一个用于科学可视化和分析的Python库 &#xff1b;我认为它适合做一些网格数据的处理&#xff1b; 它封装了VTK&#xff08;Visualization Toolkit&#xff09;之上&#xff0c;提供了一些高级接口&#xff0c; 3D数据可视化变得更加简单和易用。 1.安装 pyvista&…

SpringMVC进阶(自定义拦截器以及异常处理)

文章目录 1.自定义拦截器1.基本介绍1.说明2.自定义拦截器的三个方法3.流程图 2.快速入门1.Myinterceptor01.java2.FurnHandler.java3.springDispatcherServlet-servlet.xml配置拦截器4.单元测试 3.拦截特定路径1.拦截指定路径2.通配符配置路径 4.细节说明5.多个拦截器1.执行流程…

LeetCode 150. 逆波兰表达式求值

LeetCode 150. 逆波兰表达式求值 1、题目 题目链接&#xff1a;150. 逆波兰表达式求值 给你一个字符串数组 tokens &#xff0c;表示一个根据 逆波兰表示法 表示的算术表达式。 请你计算该表达式。返回一个表示表达式值的整数。 注意&#xff1a; 有效的算符为 ‘’、‘-’、…

Windows电脑的显存容量查看

要查看Windows电脑的显存容量&#xff0c;可以按照以下步骤进行&#xff1a; 1、通过系统信息查看&#xff1a; 在Windows操作系统中&#xff0c;您可以使用系统信息来查看显存容量。 按下Win键 R打开“运行”对话框&#xff0c;然后输入“msinfo32”并按回车键。 在打开的系…

移动端H5页面使用Vant组件库下拉刷新和页面滚动事件冲突

Vant组件库van-pull-refresh踩坑 van-pull-refresh在开发过程会和内容中最近的拥有overflow的元素的滚动发生冲突&#xff0c;造成向上滑动的时候会下拉加载 <van-pull-refresh v-model"refreshing" refresh"onRefresh"><van-listv-model"…

如何基于Zookeeper实现注册中心模型?

在分布式系统中&#xff0c;通常会存在几十个甚至上百个服务&#xff0c;开发人员可能甚至都无法明确系统中到底有哪些服务正在运行。另一方面&#xff0c;我们很难同时确保所有服务都不出现问题&#xff0c;也很难保证当前的服务部署方式不做调整和优化。由于自动扩容、服务重…

若依ruoyi-vue中图标使用介绍

图标使用 使用方式 若依ruoyi-vue中使用全局 Svg Icon 图标组件。地址&#xff1a;src\components\SvgIcon\index.vue 该组件是在src\assets\icons\index.js文件中被注册为全局组件的&#xff0c;可以在项目任意地方使用。所有的图标都在src\assets\icons\svg目录下。可自行添…

tableau如何传参数到MySQL数据库

1、打开tableau连接本地MySQL-》新建自定义sql-》创建参数 2、新建一个简单的工作表-》把维度拖拽到行显示结果-》右键显示参数 3、参数传递到数据库sql写法 select * from yonghu where yonghu.姓名 like concat(%,<参数.姓名>,%)select * FROMabadata4WHERE abadata4…

css代码的定位及浮动

上次&#xff0c;我们解除了css的内外边距、鼠标悬停及其练习。现在我们学习css元素练习和定位。 元素转换 元素分为块元素、行元素和行内块。 display 显示&#xff1a;转换元素的类型 display:inline; display:block; display:inline-block; display:none;元素隐藏…

基于STM32单片机的汽车胎压、速度及状态监测系统设计与实现

基于STM32单片机的汽车胎压、速度及状态监测系统设计与实现 摘要&#xff1a; 随着汽车电子技术的快速发展&#xff0c;车辆状态实时监控系统的需求日益增长。本文设计并实现了一种基于STM32单片机的汽车胎压、速度及状态监测系统。该系统能够实时监测汽车的胎压、速度以及其他…

MCU自动测量单元:自动化数据采集的未来

随着科技的飞速发展&#xff0c;自动化技术在各个领域中的应用日益广泛。其中&#xff0c;MCU(微控制器)自动测量单元以其高效、精准的特性&#xff0c;成为自动化数据采集领域的佼佼者&#xff0c;引领着未来数据采集技术的革新。本文将深入探讨MCU自动测量单元的原理、优势以…

实习面试算法准备之图论

这里写目录标题 1 基础内容1.1 图的表示1.2图的遍历 2 例题2.1 所有可能的路径 1 基础内容 图没啥高深的&#xff0c;本质上就是个高级点的多叉树而已&#xff0c;适用于树的 DFS/BFS 遍历算法&#xff0c;全部适用于图。 1.1 图的表示 图的存储在算法题中常用邻接表和邻接矩…

庆祝我在CSDN上创作满四年:分享知识,共同成长

引言&#xff1a; 今天&#xff0c;我非常高兴地迎来了在CSDN平台上创作四周年的纪念日。这四年对我来说既是挑战也是成长&#xff0c;我在这里记录了自己的技术探索和心得体会&#xff0c;也收获了来自社区的宝贵知识和友谊。 正文&#xff1a; 创作初衷&#xff1a; 开始在C…

SUSE Linux Rsync+inotify精准系统同步配置实战

配置不难,也可以说难,这完全取决于需求。一.服务器状况: NFS文件服务器,存储提交的附件和图片。希望搭建一个在线的备份文件服务器,实现主服务和备份服务器之间的文件的实时同步。 Filesserver:/tmp # lsb_release -a LSB Version: n/a Distributor ID: SUSE Descri…

84.柱形图中最大的矩阵

二刷终于能过了. 思路解析: 不愧是hard,第一步就很难想, 对于每一个矩阵,我们要想清楚怎么拿到最大矩阵, 对于每个height[i],我们需要找到left和right,left是i左边第一个小于height[i]的,right是右边第一个小于height[i]的,那么他的最大矩阵就是height[i] * (right-left-…

linux下安装deepspeed

安装步骤 一开始安装deepspeed不可以使用pip直接进行安装。 这时我们需要利用git进行clone下载到本地&#xff1a; git clone https://github.com/microsoft/DeepSpeed.git 进入到deepspeed的安装目录下 cd /home/bingxing2/ailab/group/ai4agr/wzf/Tools/DeepSpeed 激活…

LeetCode-旋转链表

每日一题&#xff0c;很久没做链表的题了&#xff0c;今天做l一道相对简单的力扣中等难度题。 题目要求 给你一个链表的头节点 head &#xff0c;旋转链表&#xff0c;将链表每个节点向右移动 k 个位置。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5], k 2 输出&…

Java基础入门day41

day41 约束 实体完整性 主键约束 唯一约束 自增约束 域完整性 限制列的单元格的数据的正确性 非空约束 not null&#xff0c;非空&#xff0c;此列必须有值 create table subject03(sid int primary key auto_increment,subname varchar(20) unique not null,subHour int…

C++ 异常处理机制详解:轻松掌握异常处理技巧

C 异常处理 C 异常处理机制允许程序在运行时处理错误或意外情况。它提供了捕获和处理错误的一种结构化方式&#xff0c;使程序更加健壮和可靠。 异常处理的基本概念&#xff1a; 异常: 程序在运行时发生的错误或意外情况。抛出异常: 使用 throw 关键字将异常传递给调用堆栈。…

智慧浪潮下的产业园区:洞察智慧化转型如何打造高效、绿色、安全的新园区

目录 一、引言 二、智慧化转型的内涵与价值 三、打造高效园区的智慧化策略 1、建设智能化基础设施 2、推广智能化应用 3、构建智慧化服务平台 四、实现绿色园区的智慧化途径 1、推动绿色能源应用 2、实施绿色建筑设计 3、加强环境监测与治理 五、保障园区安全的智慧…