【C语言实现贪吃蛇】(内含源码)

前言:首先在实现贪吃蛇小游戏之前,我们要先了解Win32 API的有关知识

1.Win32 API

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

2.控制台程序

平常我们运用起来的黑框程序就是控制台程序

我们可以通过执行命令来改变控制台的长宽

在我们执行cmd命令之前,我们首先要先做以下事情

点开设置,然后将windows决定改为windows控制台主机,然后点击保存,我们就可以执行有关命令了,(如果控制台主机不可以,可以将其改为windows决定

下面我介绍两个控制台窗口执行命令,可以用C语言函数的system来实现。

1.改变控制台窗口大小(mode命令)

复制代码
#include<stdio.h>
#include<windows.h>
int main()
{
	system("mode con cols=50 lines=20");//控制台列改为50列,行就改为20行
	return 0;
}

可以发现控制台行为20行,列为50行,但看起来却像个正方形,这是为什么呢?其实是因为控制台窗口行和列的比例并不是1:1的。

其坐标位置如下

2.改变控制台的窗口名字(title命令)

复制代码
#include<stdio.h>
#include<windows.h>
int main()
{
	system("title 贪吃蛇");
	return 0;
}

3.控制台屏幕上的坐标

COORDWindows API中定义的一个结构体,表示一个字符在控制台屏幕缓冲区上得坐标,坐标系(0,0)的原点位于缓冲区的顶部左侧单元格。

其中COORD的类型的声明为:

typedef struct _COORD

{

short x;

short y;

};

给坐标赋值为:

COORD pos={ 10, 15 };

4.GetStdHandle

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

HANDLE GetStdHandle(DWORD nStdHandle);

复制代码
HANDLE hOutput = NULL;
//获取标准输出的句柄(⽤来标识不同设备的数值)
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);

5.GetConsoleCursorInfo

其函数原型为:

BOOL WINAPI GetConsoleCursorInfo(
In HANDLE hConsoleOutput,
Out PCONSOLE_CURSOR_INFO lpConsoleCursorInfo

);
HANDLE为获得的标准输出句柄
PCONSOLE_CURSOR_INFO 是指向 CONSOLE_CURSOR_INFO 结构的指针,该结构接收有关主机游标

6.CONSOLE_CURSOR_INFO

这个结构体包含有关控制台光标的信息

typedef struct _ CONSOLE_CURSOR_INFO {
DWORD dwSize;
BOOL bVisible;
} CONSOLE_CURSOR_INFO, *PCONSOLE_CURSOR_INFO;

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

这里的黑色长方型为光标。

那么其占完全填充单元格的百分之多少呢?这里我们就可以通过一个代码来演示

复制代码
#include<stdio.h>
#include<windows.h>
int main()
{
	HANDLE houtput = NULL;//返回void*的指针
	houtput = GetStdHandle(STD_OUTPUT_HANDLE);

	//定义一个光标信息的结构体
	CONSOLE_CURSOR_INFO cursor_info = { 0 };
	//获取与houtput句柄相关的控制台上的光标信息,并存放在cursor_info中
	GetConsoleCursorInfo(houtput, &cursor_info);
	printf("%d", cursor_info.dwSize);
	return 0;
}

可以观察到其占完全填充光标的25%。

完全填充光标:

那如果我们想要将光标信息隐藏该怎么办呢?

bVisible表示游标的可见性。如果光标可见,则此成员为true.

复制代码
#include<stdio.h>
#include<windows.h>
#include<stdbool.h>
int main()
{
	HANDLE houtput = NULL;//返回void*的指针
	houtput = GetStdHandle(STD_OUTPUT_HANDLE);

	//定义一个光标信息的结构体
	CONSOLE_CURSOR_INFO cursor_info = { 0 };
	//获取与houtput句柄相关的控制台上的光标信息,并存放在cursor_info中
	GetConsoleCursorInfo(houtput, &cursor_info);
	cursor_info.bVisible = false;//隐藏光标
	return 0;
}

但这样并不能隐藏光标信息,还需要借助一个函数S etConsoleCursorInfo来设置控制台屏幕缓冲区的光标大小和可见性。

7.SetConsoleCursorInfo

其函数原型为:

BOOL WINAPI SetConsoleCursorInfo (
HANDLE hConsoleOutput,
const CONSOLE_CURSOR_INFO *lpConsoleCursorInfo
);

复制代码
#include<stdio.h>
#include<windows.h>
#include<stdbool.h>
int main()
{
	HANDLE houtput = NULL;//返回void*的指针
	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);
	return 0;
}


我们就可以观察到光标被隐藏了。

8.SetConsoleCursorPosition

作用:设置指定控制台屏幕缓冲区中的光标位置,我们将想要设置的坐标信息放在COORD类型的pos中,调用SetConsolePostion函数将光标位置设定到指定的位置。

函数原型:

BOOL WINAPI SetConsoleCursorPosition (
HANDLE hConsoleOutput,
COORD pos
);

这里我们就用Set_Pos分装一个设置光标的函数

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

9.GetAsyncKeyState

获取按键情况,GetAsynKeyState的函数原型如下:

SHORT GetAsyncKeyState (
int vKey
);

将键盘上的每一个键的虚拟键值传递给函数,函数通过返回值来分辨键值的状态。

GetAsyncKeyState 的返回值是short类型,在上一次调用GetAsyncKeyState函数后,如果返回的16为的short数据中,最高位是1,说明按键的状态时按下,如果最高位是0,说明按键的转台是抬起;如果最低位被置为1则说明,该按键被按过,否则为0。

这里为了方便判断按键是否被按过,我们可以定义一个宏,来判断GetAsynKeyState返回值最低为是否为1.

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

参考:虚拟键码 (Winuser.h) - Win32 apps

这里虚拟键码就可以参照上面的链接。

讲完了上面的有关知识我们就可以开始实现贪吃蛇了

首先先展示贪吃蛇的大致画面

QQ202453-133347

这里为了实现游戏地图的打印,我们就需要讲解一下控制台有关知识,控制台窗口的坐标如下所示,横向的是x轴,纵向的是y轴,从上到下依次增长。

在游戏地图上,我们打印墙体使用的是宽字符:■,打印蛇使用宽字符●,打印食物使用的宽字符是★

那什么是宽字符呢?

普通字符是占一个字节的,宽字符是占两个字节的。

过去C语言并不适用于非英语国家使用,C语言最初假定字符都是单字节的。但是这些假定并不是在世界的任何地方都适用。后来为了适应C语言国际化·,C语言的标准不断加入了国际化的支持。比如:加入宽字符的类型wchar_t和宽字符的输入和输出函数。加入了<locale.h>头文件,提供了允许程序员针对特定地区调整程序行为的函数。

10.setlocale

函数原型:char*setlocale(int category,const char*locale);

setlocale函数用于修改当前地区,可以针对一个类项,也可以所有类项,如果第一个参数是LC_ALL,就是影响所有的类项。

C标准给出了第二个参数定义了两种可能取值:"C"(正常模式)和" "(本地模式)。

从任意程序开始,都会隐藏执行调用:

setlocale(LC_ALL,"C");

如果想切换到本地模式就支持宽字符(汉字)的输出:

setlocale(LC_ALL," ");

11.宽字符的打印

宽字符的字面量必须加上前缀"L" ,否则C语言会把字面量当做窄字符类型处理,前缀"L"在单引号面前,表示宽字符,对应wprintf() 的占位付为**%lc** ;在双引号面前,表示宽字符串,对应占位付就为**%ls**.

复制代码
#include <stdio.h>
#include<locale.h>
int main() {
 setlocale(LC_ALL, "");
 wchar_t ch = L'●';
 
 printf("%c%c\n", 'a', 'b');
 
 wprintf(L"%lc\n", ch);
 return 0;
}

注:这个宽字符的实现要在Windows控制台主机上实现。

这里我们就实现棋盘27行,58列的棋盘,在围绕地图画出墙

这里我们蛇身的初始长度为5,在固定的一个坐标处开始,比如我们在(24,5)处开始打印,连续5个节点。

注:蛇的每个节点的x坐标必须是2个倍数,否则可能会出现蛇的⼀个节点有一半出现在墙体。

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

下面我们开始实现贪吃蛇(代码内含注释)

snake.h

复制代码
#define  _CRT_SECURE_NO_WARNINGS 1

#define KEY_PRESS(VK) (( GetAsyncKeyState(VK) & 0x1 )? 1 : 0)
#define POS_X 24
#define POS_Y 5
#define WALL L'■'
#define BODY L'●'
#define FOOD L'★'
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<windows.h>
#include<locale.h>
#include<time.h>
#include<conio.h>

int choice;//选择穿墙还是不穿墙

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

//蛇的状态
//正常、撞墙、撞到自己、正常退出
enum GAME_STATUS
{
	OK,
	KILL_BY_WALL,
	KILL_BY_SELF,
	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 color(int c); //(每次置为其他颜色时都要将其再置为白色,方便设置其他颜色,(也可以根据自己需求设置))

//定义光标
void Set_Pos(short x, short y);

//游戏初始化

void GameStart(pSnake ps);

//欢迎界面的打印
void WelcomeToGame();

//创建地图
void CreateMap();

//初始化蛇身
void InitSnake(pSnake ps);

//创建食物
void CreateFood(pSnake ps);

//游戏运行的逻辑
void GameRun(pSnake ps);

//蛇的移动-走一步
void SnakeMove(pSnake ps);
//判断下一个坐标是否为食物
int NextIsFood(pSnakeNode pNextNode, pSnake ps);
//吃掉食物
void EatFood(pSnakeNode pNextNode, pSnake ps);
//不是食物
void NoFood(pSnakeNode pNextNode, pSnake ps);
//蛇是否撞墙
bool KillByWall(pSnake ps);
//蛇撞墙不会死
void WallSnakeMove(pSnake ps);
//穿墙
int NoKillByWall(pSnake ps, pSnakeNode pn);
//蛇是否撞到自己
bool KillBySelf(pSnake ps);
//游戏结束
void GameEnd(pSnake ps);

snake.c

复制代码
#define  _CRT_SECURE_NO_WARNINGS 1
#include"snake.h"

void color(int c)
{
	SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), c); //颜色设置
	//注:SetConsoleTextAttribute是一个API(应用程序编程接口)
}

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

void WelcomeToGame()
{
	Set_Pos(38, 14);
	wprintf(L"欢迎来到贪吃蛇小游戏\n");
	Set_Pos(42, 20);
	system("pause");
	system("cls");
	Set_Pos(25, 14);
	wprintf(L"用↑,↓,←,→来控制蛇的移动,按F3加速,F4减速\n");
	Set_Pos(25,15);
	wprintf(L"加速能够得到更高的分数\n");
	Set_Pos(42, 20);
	system("pause");
	system("cls");
}

void CreateMap()
{
	color(3);
	//UP
	for (int i = 1; i <= 29; i++)
	{
		wprintf(L"%lc",WALL);
	}
	//DOWN
	Set_Pos(0, 26);
	for (int i = 1; i <= 29; i++)
	{
		wprintf(L"%lc", WALL);
	}
	//LEFT
	for (int i = 1; i < 26; i++)
	{
		Set_Pos(0, i);
		wprintf(L"%lc", WALL);
	}
	//RIGHT
	for (int i = 1; i < 26; i++)
	{
		Set_Pos(56, i);
		wprintf(L"%lc", WALL);
	}
	color(7);
}

void InitSnake(pSnake ps)
{ 
	pSnakeNode cur=NULL;
	for (int i = 0; i < 5; i++)
	{

		cur = (pSnakeNode)malloc(sizeof(SnakeNode));
		if (cur == NULL)
		{
			perror("InitSnake()::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)
	{
		Set_Pos(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 CreateFood(pSnake ps)
{
	int x = 0;
	int y = 0;
	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 && cur->y == 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;
	pFood->next = NULL;

	Set_Pos(x, y);
	color(12); //颜色设置为红色
	wprintf(L"%lc", FOOD);
	color(7); //颜色设置为白色
	ps->_pFood = pFood;
}


void GameStart(pSnake ps)
{	
	//0.设置窗口大小,光标隐藏
	ps->_pSnake = NULL;
	system("mode con cols=100 lines=30");
	system("title 贪吃蛇");
	HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);
	CONSOLE_CURSOR_INFO CursorInfo = { 0 };
	GetConsoleCursorInfo(houtput, &CursorInfo);
	CursorInfo.bVisible = false;
	SetConsoleCursorInfo(houtput, &CursorInfo);

	//1.打印欢迎界面
	WelcomeToGame();

	//2.绘制地图
	CreateMap();
	
	//3.创建蛇
	InitSnake(ps);

	//4.创建食物
	CreateFood(ps);
	//5.设置游戏的相关信息
}

void PrintHelpInfo()
{
	color(6);
	Set_Pos(64, 10);
	wprintf(L"%ls", L"不能穿墙,不能咬到自己\n");
	Set_Pos(64, 11);
	wprintf(L"%ls", L"用↑,↓,←,→来控制蛇的移动\n"); 
	Set_Pos(64, 12);
	wprintf(L"%ls", L"按F3加速,F4减速\n");
	Set_Pos(64, 13);
	wprintf(L"%ls", L"按ESC退出游戏,按空格暂停游戏\n");

	Set_Pos(64, 15);
	wprintf(L"%ls", L"贪吃蛇小游戏");
	color(7);
}

void Pause()
{
	while (1)
	{
		Sleep(200);
		if (KEY_PRESS(VK_SPACE))
			break;
	}
}

int NextIsFood(pSnakeNode pNextNode, pSnake ps)
{
	return (ps->_pFood->x == pNextNode->x && ps->_pFood->y == pNextNode->y);
}

void EatFood(pSnakeNode pNextNode, pSnake ps)
{
	//头插
	ps->_pFood->next = ps->_pSnake;
	ps->_pSnake = ps->_pFood;

	//释放旧的节点
	free(pNextNode);
	pNextNode = NULL;

	pSnakeNode cur = ps->_pSnake;
	//将头置为红色
	color(12); //颜色设置为红色
	Set_Pos(cur->x, cur->y);
	wprintf(L"%lc", BODY);
	cur = cur->next;
	color(7); //颜色设置为白色
	while (cur)
	{
		Set_Pos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	ps->_score += ps->_food_weight;
	Set_Pos(76, 8);//定位光标到分数旁边
	printf("+%2d", ps->_food_weight);
	Sleep(100);
	Set_Pos(76, 8);
	printf("   ");
	//重新创建食物
	CreateFood(ps);
}

bool 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;
		return false;
	}
	return true;
}


bool KillBySelf(pSnake ps)
{
	pSnakeNode cur = ps->_pSnake->next;
	while (cur)
	{
		if (cur->x == ps->_pSnake->x && cur->y == ps->_pSnake->y)
		{
			ps->_status = KILL_BY_SELF;
			return false;
		}
		cur = cur->next;
	}
	return true;
}

void NoFood(pSnakeNode pNextNode, pSnake ps)
{
	//头插
	pNextNode->next = ps->_pSnake;
	ps->_pSnake = pNextNode;
	//撞墙
	if (!KillByWall(ps))
	{
		return;
	}
	//撞到自己
	if (!KillBySelf(ps))
	{
		return;
	}
	pSnakeNode cur = ps->_pSnake;
	//将头置为红色
	color(12); //颜色设置为红色
	Set_Pos(cur->x, cur->y);
	wprintf(L"%lc", BODY);
	cur = cur->next;
	color(7); //颜色设置为白色(每次置为其他颜色时都要将其再置为白色,以便下一次置色)
	while (cur->next->next != NULL)
	{
		Set_Pos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	//把最后一个节点打印空格
	Set_Pos(cur->next->x, cur->next->y);
	printf("  ");//要两个空格
	//释放最后一个节点
	free(cur->next);
	//把倒数第二个节点的next置为空
	cur->next = NULL;
}

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);
	//撞到自己
	KillBySelf(ps);
}

int NoKillByWall(pSnake ps, pSnakeNode pn)
{
	pSnakeNode pNextNode = (pSnakeNode)malloc(sizeof(SnakeNode));
	if (pNextNode == NULL)
	{
		perror("SnakeMove()::malloc()");
		exit(1);
	}
	if (pn->x == 0)
	{
		//将头节点穿墙
		pNextNode->x = 54;
		pNextNode->y = ps->_pSnake->y;
		pNextNode->next = NULL;
		//判断下一个节点是否为食物
		if (NextIsFood(pNextNode, ps))
		{
			EatFood(pNextNode, ps);
		}
		else
		{
			NoFood(pNextNode, ps);
		}
		free(pn);
		return 0;
	}
	else if (pn->x == 56)
	{
		//将头节点穿墙
		pNextNode->x = 2;
		pNextNode->y = ps->_pSnake->y;
		pNextNode->next = NULL;
		//判断下一个节点是否为食物
		if (NextIsFood(pNextNode, ps))
		{
			EatFood(pNextNode, ps);
		}
		else
		{
			NoFood(pNextNode, ps);
		}
		free(pn);
		pn = NULL;
		return 0;
	}
	else if (pn->y == 0)
	{
		//将头节点穿墙
		pNextNode->y = 25;
		pNextNode->x = ps->_pSnake->x;
		pNextNode->next = NULL;
		//判断下一个节点是否为食物
		if (NextIsFood(pNextNode, ps))
		{
			EatFood(pNextNode, ps);
		}
		else
		{
			NoFood(pNextNode, ps);
		}
		free(pn);
		pn = NULL;
		return 0;
	}
	else if (pn->y == 26)
	{
		//将头节点穿墙
		pNextNode->y = 1;
		pNextNode->x = ps->_pSnake->x;
		pNextNode->next = NULL;
		//判断下一个节点是否为食物
		if (NextIsFood(pNextNode, ps))
		{
			EatFood(pNextNode, ps);
		}
		else
		{
			NoFood(pNextNode, ps);
		}
		free(pn); 
		pn = NULL;
		return 0;
	}
	return 1;
}

void WallSnakeMove(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 (NoKillByWall(ps, pNextNode))
	{
		if (NextIsFood(pNextNode, ps))
		{
			EatFood(pNextNode, ps);
		}
		else
		{
			NoFood(pNextNode, ps);
		}
	}
	//检测下一个坐标是否为食物
	//撞到自己
	KillBySelf(ps);
}

void GameRun(pSnake ps)
{
	PrintHelpInfo();
	do
	{
		color(6);
		//打印总分数和食物的分值
		Set_Pos(64, 8);
		printf("总分数:%d\n", ps->_score);
		Set_Pos(64, 9);
		printf("当前食物的分数:%2d\n", ps->_food_weight);
		color(7);

		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; 
			}
		}
		if (choice == '1')
		{
			WallSnakeMove(ps);//蛇可以穿墙
		}
		else
		{
			SnakeMove(ps);//蛇走一步的过程
		}
		Sleep(ps->_sleep_time);
	} while (ps->_status == OK);
}

void GameEnd(pSnake ps)
{
	Set_Pos(24, 12);
	color(6);
	switch (ps->_status)
	{
	case END_NORMAL:
		printf("你主动结束游戏\n");
		break;
	case KILL_BY_WALL:
		printf("你寄了\n");
		break;
	case KILL_BY_SELF:
		printf("一不小心撞到自己了\n");
		break;
	}
	color(7);
	pSnakeNode cur = ps->_pSnake;
	while (cur)
	{
		pSnakeNode del = cur;
		cur = cur->next;
		free(del);
	}
}

test.c

复制代码
#define  _CRT_SECURE_NO_WARNINGS 1

#include"snake.h"
#include<locale.h>


void test()
{
	int ch = 0;
	do
	{
	    color(6); //颜色设置为土黄色
		system("cls");
		//创建贪吃蛇
		Snake snake = { 0 };
		//初始化游戏
		//1.打印欢迎界面
		//2.功能介绍
		//3.绘制地图
		//4.创建蛇
		//5.创建食物
		//6.设置游戏的相关信息

		Set_Pos(38, 15);
		printf("1.穿墙");
		Set_Pos(38, 16);
		printf("2.不穿墙");
		Set_Pos(38, 18);
		printf("请选择模式:>");
		choice = getchar();
		while (getchar() != '\n');
		system("cls");
		GameStart(&snake);
		//运行游戏
		GameRun(&snake);
		//结束游戏
		GameEnd(&snake);
		while (_kbhit())
		{
			// 使用 _getch() 获取按下的键,不阻塞程序
			_getch();
			// 处理按键事件,可以根据需要进行相应的操作
		}
		Set_Pos(20, 15);
		color(6);
		printf("再来一句不老铁?>(Y/N):");
		ch = getchar();
		color(7);
		while (getchar() != '\n');

	} while (ch=='Y'||ch=='y');
	Set_Pos(0, 27);
}

int main()
{
	setlocale(LC_ALL, "");
	srand((unsigned int)time(NULL));
	test();

	return 0;
}
相关推荐
Scott9999HH6 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
码智社7 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海7 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖8 小时前
JDK 26 新特性详解
java·开发语言
马优晨9 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区11 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大11 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai11 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
এ慕ོ冬℘゜12 小时前
前端基础:什么是时间戳?JS获取时间戳三种方法与实战用途
开发语言·前端·javascript
执明wa12 小时前
LayoutInflater详解: XML是如何变成View的?
android·xml·开发语言·android studio