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

ops/2024/10/20 17:34:39/

目录

一、预备知识

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.ppmy.cn/ops/22133.html

相关文章

Debian 系统设置SSH 连接时长

问题现象&#xff1a; 通过finalshell工具连接Debian系统远程操作时&#xff0c;总是一下断开一下断开&#xff0c;要反复重新连接 &#xff0c;烦人&#xff01; 解决办法&#xff1a; 找到ssh安装目录下的配置文件&#xff1a;sshd_config vi sshd_config &#xff1a; 找到…

在k8s中以deployment方式部署minio

minio官网给的demo是通过pod方式部署的&#xff0c;我碰到了好几次因为k8s集群断电重启后&#xff0c;以单pod方式部署部署的minio消失。因此这里改用deplyment的方式部署minio。 以下是完整的minio部署清单 --- # Deploys a new MinIO Pod into the metadata.namespace Kube…

低代码+定制物资管理:创新解决方案探析

引言 在当今快速变化的商业环境中&#xff0c;企业面临着不断增长的挑战&#xff0c;如提高效率、降低成本、满足客户需求等。为了应对这些挑战&#xff0c;企业需要不断创新并采用先进的技术解决方案。在这样的背景下&#xff0c;低代码开发和定制化物资管理成为了引领企业变…

Maven介绍 主要包括Maven的基本介绍,作用,以及对应的Maven模型,可以对Maven有一个基本的了解

1、Maven介绍 1.1 什么是Maven Maven是Apache旗下的一个开源项目&#xff0c;是一款用于管理和构建java项目的工具。 官网&#xff1a;https://maven.apache.org/ Apache 软件基金会&#xff0c;成立于1999年7月&#xff0c;是目前世界上最大的最受欢迎的开源软件基金会&…

elasticsearch 聚合查询

文章目录 前言elasticsearch 聚合查询1. 桶聚合2. 度量聚合3. 嵌套聚合4. 指标聚合 前言 如果您觉得有用的话&#xff0c;记得给博主点个赞&#xff0c;评论&#xff0c;收藏一键三连啊&#xff0c;写作不易啊^ _ ^。   而且听说点赞的人每天的运气都不会太差&#xff0c;实在…

【源码阅读】 Golang中的database/sql库源码探究

Note&#xff1a;文章待完结 文章目录 前言一、整体目录结构二、driver包1、驱动相关driver.Driver2、驱动连接&#xff1a;driver.Conn3、预处理结构&#xff1a;Stmt4、执行结果 driver.Result5、查询结果&#xff1a;driver.Rows6、driver.RowsAffected7、driver.Value8、Va…

addEventListener()方法中的参数,以及作用

addEventListener() 方法是 JavaScript 中用于向指定元素添加事件监听器的方法。它有两个参数&#xff1a; 事件名称 (type)&#xff1a;这是一个字符串&#xff0c;表示要监听的事件名称。例如&#xff0c;click、mouseover、keydown 等。事件处理函数 (listener)&#xff1a…

JVM的垃圾回收机制(GC机制)

在Java代码运行的过程中&#xff0c;JVM发现 某些资源不需要再使用的时候&#xff0c;就会自动把资源所占的内存给回收掉&#xff0c;就不需要程序员自行操作了。“自动回收资源”就是JVM的“垃圾回收机制”&#xff0c;“垃圾回收机制”也称"GC机制"。 对于Java代码…