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

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

1.游戏实现背景

贪吃蛇小游戏在很早以前就已经存在,我们应该也玩过贪吃蛇这个游戏,这一讲实现的贪吃蛇小游戏是基于C语言,以及链表来实现的,目的在于培养代码思维,对前面的知识做出总结

2.Win32 API介绍

本次贪吃蛇的实现需要用到很多关于Win32 API的知识,我们现在来了解一下:

2.1什么是Win32 API

Win32 API是一组用于Windows操作系统的应用程序编程接口,它提供了一组函数,允许程序员访问操作系统的服务(Windows这个多作业系统除了协调程序的运行、分配内存、管理资源之外,同时是一个很大的服务中心),如 文件管理、内存管理、进程控制、图形和声音处理等

2.2控制台程序(Console)

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

我们可以使用cmd命令来改变控制台窗口的长宽:长为 30、宽为30

c 复制代码
//lines:行  cols:列
mode con cols=30 lines=30

我们还可以改变控制台窗口的名称:

c 复制代码
title 测试API程序

这些能在控制台窗口执行的命令,也能够调用C语言system函数来执行:

cpp 复制代码
//控制台程序
#include <stdlib.h>
int main()
{
	system("mode con cols=30 lines=30");//改变运行窗口的大小
	system("title 测试API程序");//改变运行窗口的名字

	return 0;
}

最终效果:

但是我们会发现:我们设的长和宽分明是相等的,但是为什么控制台窗口的大小不是一个正方形呢?我们来看:

2.3控制台屏幕的坐标COORD

COORD是Windows API中定义的一个结构体,表示一个字符在屏幕缓冲区的一个坐标,坐标的分布如下:

COORD结构体的定义为:

c 复制代码
//COORD结构体定义了x和y坐标
typedef struct _COORD {
	SHORT X;
	SHORT Y;
} COORD, * PCOORD;

我们使用这一个结构体也较为简单,假设我们要给一个坐标赋值:

c 复制代码
COORD pos = {10, 20};

2.4GetStdHandle

GetStdHandle是一个Windows API函数,用于从一个标准设备(标准输入设备、标准输出设备)中获得一个句柄(我们可以将句柄通俗地理解成:操纵操作系统的把柄),有了这个句柄,我们就可以来操纵操作系统

2.4.1函数语法

既然为函数,就应该存在语法,它的函数定义为:

c 复制代码
HANDLE WINAPI GetStdHandle(
  _In_ DWORD nStdHandle
);

该函数只有一个参数nStdHandle,对于该参数的介绍为:

如果函数成功,那么函数将会返回指定设备的句柄

2.4.2函数的使用

c 复制代码
#include <windows.h>
int main()
{
	//函数返回的是一个句柄,那么我们就可以使用一个句柄来接受,我们先创建一个句柄变量
	HANDLE hOutput = NULL;

	//获取标准输出的句柄
	hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
	return 0;
}

2.5GetConsoleCursorInfo

2.5.1函数语法

cpp 复制代码
BOOL WINAPI GetConsoleCursorInfo(
	_In_  HANDLE               hConsoleOutput,
	_Out_ PCONSOLE_CURSOR_INFO lpConsoleCursorInfo
);

函数有两个参数:
1.hConsoleOutput:这个是控制台屏幕缓冲区的句柄
2.lpConsoleCursorInfo:它是指向CONSOLE_CURSOR_INFO结构的指针,该结构可以接受有关主机光标的信息
如果该函数成功,会返回非零值,函数失败,会返回零

2.5.2函数的使用

cpp 复制代码
int main()
{
	//获得一个句柄
	HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
	
	//创建一个光标信息的变量
	CONSOLE_CURSOR_INFO Cursorinfo;
	//使用含税获取光标信息
	GetConsoleCursorInfo(hOutput, &Cursorinfo);

	return 0;
}

2.6CONSOLE_CURSOR_INFO

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

2.6.1结构体结构

c 复制代码
typedef struct _CONSOLE_CURSOR_INFO {
	DWORD dwSize;
	BOOL  bVisible;
} CONSOLE_CURSOR_INFO, * PCONSOLE_CURSOR_INFO;
结构体中包含两个参数:
1.dwSize:表示的是游标填充字符单元的百分比,该值介于1-100之间
2.bVisible:表示游标的可见性,如果游标可见,那么此成员为True

2.6.2结构体的使用

c 复制代码
int main()
{
	//获得一个句柄
	HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
	
	//创建一个光标信息的变量
	CONSOLE_CURSOR_INFO Cursorinfo;
	//使用含税获取光标信息
	GetConsoleCursorInfo(hOutput, &Cursorinfo);

	//结构体的使用:打印一下此时控制台光标占填充字符单元的百分比
	printf("%d", Cursorinfo.dwSize);//25,也就是说此时光标占一个字符单元的百分之25
	return 0;
}

2.7SetConsoleCursorInfo

2.7.1函数语法

c 复制代码
BOOL WINAPI SetConsoleCursorInfo(
  _In_       HANDLE              hConsoleOutput,
  _In_ const CONSOLE_CURSOR_INFO *lpConsoleCursorInfo
);
该函数有两个参数:
1.hConsoleOutput:指向控制台屏幕缓冲区的句柄
2.lpConsoleCursorInfo:指向CONSOLE_CURSOR_INFO结构体的指针
该函数如果成功,返回非零值,失败返回零

2.7.2函数的使用

c 复制代码
#include <stdbool.h>
int main()
{
	//获得一个句柄
	HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);

	//创建一个光标信息的变量
	CONSOLE_CURSOR_INFO Cursorinfo;
	//使用含税获取光标信息
	GetConsoleCursorInfo(hOutput, &Cursorinfo);

	//函数的使用:
	//我们可以根据我们不同的需求对光标进行修改
	//1.修改光标大小
	Cursorinfo.dwSize = 50;//直接修改结构体中的参数然后Set就好了
	SetConsoleCursorInfo(hOutput, &Cursorinfo);
	//2.隐藏光标
	Cursorinfo.bVisible = false;
	SetConsoleCursorInfo(hOutput, &Cursorinfo);
	return 0;
}

2.8SetConsoleCursorPosition

这是一个设置光标位置的函数

2.8.1函数语法

c 复制代码
BOOL WINAPI SetConsoleCursorPosition(
	_In_ HANDLE hConsoleOutput,
	_In_ COORD  dwCursorPosition
);
该函数有两个参数:
1.hConsoleOutput:控制台屏幕缓冲区的句柄
2.dwCursorPosition:指定新光标位置的COORD结构,坐标为屏幕缓冲区字符单元的列和行,坐标必须位于控制台屏幕缓冲区的边界之内
如果函数成功,则返回非零值,函数失败,返回零

2.8.2函数的使用

c 复制代码
int main()
{
	//定义一个光标结构体,标志着需要将光标定位的位置
	COORD pos = { 15, 30 };

	//获得句柄
	HANDLE hOutput = NULL;
	hOutput = GetStdHandle(STD_OUTPUT_HANDLE);

	//设置光标位置
	SetConsoleCursorPosition(hOutput, pos);
	return 0;
}

此时光标位置就被改变了:

2.8.3设置一个光标位置的函数

我们在设计贪吃蛇小游戏的过程中,要在不同位置放置食物、提示信息等,所以我们可以封装一个设置光标位置的函数,方便后续使用

c 复制代码
//封装一个设置光标位置的函数
void SetPos(short x, short y)
{
	//函数构造比较简单,只需要使用Set函数即可
	//1.光标位置结构体
	COORD pos = { x, y };

	//2.获得句柄
	HANDLE hOutput = NULL;
	hOutput = GetStdHandle(STD_OUTPUT_HANDLE);

	//3.设置光标信息
	SetConsoleCursorPosition(hOutput, pos);
}

2.9GetAsyncKeyState

这个函数是用来获取按键情况的函数,这个在游戏中作用很大

2.9.1函数语法

c 复制代码
SHORT GetAsyncKeyState(
  [in] int vKey
);
函数只有一个参数:
vKey:表示虚拟按键代码,每一个按键都有一个对应的值,将值传入就可以判断该按键是否被按着或者被按过
函数的返回值为short类型,在上一次调用该函数后,如果返回的16位的short数据中,最高位为1,说明状态是按下的,如果最低位为1,说明该按键被按过

链接: 虚拟键代码值

我们可以通过上面的一个链接来看按键的代码值为多少

2.9.2函数的使用

c 复制代码
//我们可以写一个宏,通过使用宏我们可以更高效地判断按键的状态
#define KEY_STATE(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1: 0 )//如果返回值的最低位为1,表示该按键被按过

//检测数字键的实现
int main()
{
	while(1)
	{
		if (KEY_STATE(0x30))//0x30表示按键0
		{
			printf("0\n");
		}
		else if (KEY_STATE(0x31))
		{
			printf("1\n");
		}
		else if (KEY_STATE(0x32))
		{
			printf("2\n");
		}
		else if (KEY_STATE(0x33))
		{
			printf("3\n");
		}
		else if (KEY_STATE(0x34))
		{
			printf("4\n");
		}
		else if (KEY_STATE(0x35))
		{
			printf("5\n");
		}
		else if (KEY_STATE(0x36))
		{
			printf("6\n");
		}
		else if (KEY_STATE(0x37))
		{
			printf("7\n");
		}
		else if (KEY_STATE(0x38))
		{
			printf("8\n");
		}
		else if (KEY_STATE(0x39))
		{
			printf("9\n");
		}
	}

	return 0;
}

有了上面的知识储备之后,下面我们来实现贪吃蛇

3.地图的实现

地图的实现需要用到宽字符,普通的字符占用一个字节的位置,而宽字符会占用两个字符的位置,我们打印墙体需要的是:□,打印蛇需要用的是:●,打印食物需要的是:★

3.1<locale.h>本地化

C语言默认采用ASCII编码,且使用的是低7位,最高位是没有使用的,可表示为:0xxxxxxx,可以看出,ASCII字符集共包含128个字符,这个数量的字符在英语国家是完全够用的,但是在其它国家,中国、法国等国家这些字符是不够用的,所以一些欧洲国家决定,将最高位也编入新的符号,但是不同国家的符号不同,所以同样的ASCII码也可能表示不同的符号,0-127表示的符号相同,但是128-255表示的符号就可能不同

亚洲国家的文字就更多了,汉字就有着10万左右,一个字节只能表示256种符号,这肯定是不够的,所以就必须使用多个字节表示一个符号,所以理论上就可以表示256*256=65536个符号

为了使C语言国际化,C语言标准中不断加入了国家化的支持,比如:加入宽字符的类型和宽字符的输入和输出函数,加入了<locale.h>头文件,其中提供了程序员针对特定地区调整程序行为的函数,我们下面来看一下<locale.h>头文件:

该头文件提供的函数用于控制C标准库中对于不同地区产生不同的行为的部分,在标准中,依赖地区的部分有以下几项:

1.数字量的格式

2.货币量的格式

3.字符集

4.日期和时间表示形式

3.2类项

通过修改地区,程序可以改变其行为来适应世界的不同区域,但地区的修改可能会影响库的许多部分,其中一部分有我们不想改变的,所以C语言支持对不同类型进行修改,下面的一个宏,针对一个类项:

c 复制代码
1. LC_COLLATE:影响字符串⽐较函数 strcoll() 和 strxfrm()
2. LC_CTYPE:影响字符处理函数的⾏为。
3. LC_MONETARY:影响货币格式。
4. LC_NUMERIC:影响 printf() 的数字格式。
5, LC_TIME:影响时间格式 strftime() 和 wcsftime() 。
6. LC_ALL - 针对所有类项修改,将以上所有类别设置为给定的语⾔环境。

链接: 类项详细说明

3.3setlocale函数

3.3.1函数说明

c 复制代码
char* setlocale(int category, const char* locale);
该函数有两个参数:
1. category:指的是前面讲的类项中的其中一个,如果传入的是LC_ALL,那么就会影响所有的类项
2. locale:这个参数C语言只给了两个选择:
2.1 "C"正常模式,也就是C语言标准模式
2.2 " "本地模式,也就是按照地区设置的模式
也就是说,C语言在开始时,都会隐藏一个函数调用:
stelocale(LC_ALL, "C");
该函数返回的是一个字符串指针,表示已经设置好的格式,如果调用失败,那么会返回NULL
setlocale也可以查询当前地区,只需要将第二参数设置为NULL即可

3.3.2函数的使用

c 复制代码
//setlocale函数的使用
#include <locale.h>
int main()
{
	char* loc;

	//查询默认的本地信息
	loc = setlocale(LC_ALL, NULL);
	printf("默认的本地信息:%s\n", loc);//默认的本地信息:C

	//查询设置之后的信息
	loc = setlocale(LC_ALL, "");
	printf("设置之后的本地信息:%s\n", loc);//设置之后的本地信息:Chinese (Simplified)_China.936
	return 0;
}

3.4宽字符的打印

地图、食物、蛇身都是使用宽字符构成的,所以我们要了解宽字符的打印
宽字符的打印十分简单,和平常的打印类似,但是会从printf改变为wprintf,占位符从%c改为%lc,再在需要打印的字符前加上L即可:

c 复制代码
//宽字符的打印
#include <locale.h>
int main()
{
	//先设置以下地区
	setlocale(LC_ALL, "");

	//打印宽字符
	//注意:
	//1.wprintf
	//2.两个L的位置
	wprintf(L"%lc\n", L'●');
	wprintf(L"%lc\n", L'★');
	wprintf(L"%lc\n", L'□');
	return 0;
}

宽字符与正常字符占的位置大小也不相同:

3.5地图的绘制

地图的打印相对简单,只需要通过设置坐标打印出地图即可

c 复制代码
//地图的绘制
void MapCreat()
{
	SetPos(0, 0);
	int i = 0;
	//上
	for (i = 0; i <= 56; i+=2)
	{
		wprintf(L"%lc", WALL);
	}
	//下
	SetPos(0, 26);
	for (i = 0; i <= 56; i+=2)
	{
		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);
	}
}

外边框就是地图的样子:

4.游戏的完整代码实现

因为在代码中,就已经将所有信息描述地相对清楚了,所以这里直接贴上代码

4.1游戏开始

游戏设计内容分为三个部分:开始、进行、结束

c 复制代码
//将地图、蛇身、食物都定义为宏,方便使用
#define WALL L'□'
#define BODY L'●'
#define FOOD L'★'

//设置蛇初始化时的位置
#define POS_X 24
#define POS_Y 5

//定义一个宏,用来检测按键状态
#define KEY_STATE(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1: 0 )//如果返回值的最低位为1,表示该按键被按过

//创建贪吃蛇结点结构体
typedef struct SnackNode
{
	//只需要有坐标即可以及指向下一个结点的指针即可
	int x;
	int y;
	struct SnackNode* next;
}SnackNode, *pSnackNode;

//将蛇的方向放在一个枚举类型中,使用枚举类型是因为枚举类型是有值的,可以通过==符号来进行判断
enum DIRECTION
{
	//上、下、左、右
	UP = 1,
	DOWN,
	LEFT,
	RIGHT
};

//将游戏的状态也放在一个枚举类型中
enum STATE
{
	//游戏正常运行
	OK,
	ESC_NORMAL,//按esc游戏正常退出
	KILL_BY_SELF,//撞到自己死亡
	KILL_BY_WALL//撞到墙死亡
};

//我们要将贪吃蛇游戏的信息封装在一个结构体中,方便后续查询
typedef struct Snack
{
	//贪吃蛇头结点的信息
	pSnackNode _pSnack;
	//食物结点的信息
	pSnackNode _pFood;

	//当前食物的分数\权重
	int _FoodWeight;
	
	//当前总分数
	int _Score;

	//当前蛇的速度,也就是休息时间,休息时间越短,表示蛇的速度越快
	int _SleepTime;

	//当前蛇的方向
	enum DIRECTION _Direction;

	//游戏的状态(正常、撞墙、撞到自己)
	enum STATE _State;
}Snack, *pSnack;

//1.开始游戏
void GameStart(pSnack ps);

//初始化信息的打印
void WelcomPrint();

//地图的绘制
void MapCreat();

//提示信息的打印
void HelpPrint();

//蛇身的初始化
void InitSnack(pSnack ps);

//食物的初始化
void InitFood(pSnack ps);

/

//设置光标位置的函数
void SetPos(short x, short y)
{
	//函数构造比较简单,只需要使用Set函数即可
	//1.光标位置结构体
	COORD pos = { x, y };

	//2.获得句柄
	HANDLE hOutput = NULL;
	hOutput = GetStdHandle(STD_OUTPUT_HANDLE);

	//3.设置光标信息
	SetConsoleCursorPosition(hOutput, pos);
}

//初始化信息的打印
void WelcomPrint()
{
	//设置一下光标的位置,让打印的信息出现在屏幕正中心
	SetPos(35, 14);
	wprintf(L"欢迎来到贪吃蛇小游戏!");
	SetPos(36, 16);
	system("pause");
	system("cls");
	SetPos(20, 14);
	wprintf(L"使用 ↑ . ↓ . ← . → . 分别控制蛇的移动, F3是加速,F4是减速");
	SetPos(35, 16);
	system("pause");
	system("cls");
}

//地图的绘制
void MapCreat()
{
	SetPos(0, 0);
	int i = 0;
	//上
	for (i = 0; i <= 56; i+=2)
	{
		wprintf(L"%lc", WALL);
	}
	//下
	SetPos(0, 26);
	for (i = 0; i <= 56; i+=2)
	{
		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 HelpPrint()
{
	SetPos(62, 15);
	printf("1.不能撞墙,不能咬到自己");
	SetPos(62, 16);
	printf("2.使用 ↑.↓.←.→ 分别控制蛇的移动");
	SetPos(62, 17);
	printf("3.F3加速,F4减速");
	SetPos(62, 18);
	printf("4.ESC-退出, 空格-暂停游戏");
}

//蛇身的初始化
void InitSnack(pSnack ps)
{
	pSnackNode node = NULL;
	//蛇身的打印
	//假设蛇一开始为5节,就需要开辟5个蛇结点
	for(int i = 0; i<5; i++)
	{
		node = (pSnackNode)malloc(sizeof(SnackNode));
		if (node == NULL)
		{
			perror("malloc error");
			return;
		}
		//因为宽字符占两个x的位置,所以这里需要2*i表示一个结点的x坐标
		node->x = POS_X + 2 * i;
		//而y轴不同,一个宽字符占y轴的宽度仍然是一个宽度
		node->y = POS_Y;
		node->next = NULL;

		//然后再将这5个蛇结点链接起来
		if (ps->_pSnack == NULL)
		{
			ps->_pSnack = node;
		}
		else
		{
			//头插法
			node->next = ps->_pSnack;
			ps->_pSnack = node;
		}
	}
	//通过链接起来的蛇结点来打印蛇
	node = ps->_pSnack;
	while (node)
	{
		SetPos(node->x, node->y);
		wprintf(L"%lc", BODY);
		node = node->next;
	}

	//蛇的其它参数的设置
	ps->_Direction = RIGHT;//假设开始方向为向右走
	ps->_FoodWeight = 10;//假设初始时一个食物是10分
	ps->_Score = 0;//初始时总分数为0分
	ps->_SleepTime = 200;//初始速度为200ms
	ps->_State = OK;//开始时一切正常运行
	ps->_pFood = NULL;
}

//食物的初始化
void InitFood(pSnack ps)
{
	//此时需要将食物设置在一个随机位置,使用rand函数即可
	int x = 0;
	int y = 0;
	
again:
	//x的范围应该为2-54,而且必须也是2的倍数
	do
	{
		x = rand() % 53 + 2;
		y = rand() % 25 + 1;
	} while (x % 2 != 0);

	//食物出现的位置不能够和蛇的身体重合
	pSnackNode pcur = ps->_pSnack;
	while (pcur)
	{
		if (pcur->x == x && pcur->y == y)
		{
			goto again;
		}
		pcur = pcur->next;
	}

	//创建食物结点
	pSnackNode pFood = (pSnackNode)malloc(sizeof(SnackNode));
	if (pFood == NULL)
	{
		perror("malloc fail");
		return;
	}
	pFood->x = x;
	pFood->y = y;
	ps->_pFood = pFood;

	//打印食物
	SetPos(ps->_pFood->x, ps->_pFood->y);
	wprintf(L"%lc", FOOD);
}

//1.游戏开始
void GameStart(pSnack ps)
{
	//1.初始化信息的打印
	//1.1初始化信息的设置
	system("mode con cols=100 lines=30");
	system("title 贪吃蛇");
	//1.2隐藏光标
	HANDLE hOutput = NULL;
	hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
	CONSOLE_CURSOR_INFO Cursorinfo;
	GetConsoleCursorInfo(hOutput, &Cursorinfo);
	Cursorinfo.bVisible = false;
	SetConsoleCursorInfo(hOutput, &Cursorinfo);
	//1.3初始化信息的打印
	WelcomPrint();
		 
	//2.地图的绘制
	MapCreat();

	//3.提示信息的打印
	HelpPrint();

	//蛇身的初始化
	InitSnack(ps);//因为初始化蛇身会改变蛇的参数,所以要将ps传入

	//食物的初始化
	InitFood(ps);
}

4.2游戏运行

c 复制代码
//2.游戏运行
void GameRun(pSnack ps);

//蛇撞到自己
void KillBySelf(pSnack ps);

//蛇撞到墙
void KillByWall(pSnack ps);

//蛇吃掉了食物
void EatFood(pSnack ps, pSnackNode pnext);

//蛇没吃掉食物
void NoFood(pSnack ps, pSnackNode pnext);

//游戏暂停
void Pause();

//蛇的移动
void SnackMove(pSnack ps);

///

//蛇撞到自己
void KillBySelf(pSnack ps)
{
	pSnackNode pcur = ps->_pSnack->next;
	while (pcur)
	{
		if (pcur->x == ps->_pSnack->x && pcur->y == ps->_pSnack->y)
		{
			ps->_State = KILL_BY_SELF;
		}
		pcur = pcur->next;
	}
}

//蛇撞到墙
void KillByWall(pSnack ps)
{
	if (ps->_pSnack->x == 0 ||
		ps->_pSnack->x == 56 ||
		ps->_pSnack->y == 0 ||
		ps->_pSnack->y == 26)
		ps->_State = KILL_BY_WALL;
}

//判断下一个结点是否是食物
int NextIsFood(pSnack ps, pSnackNode pnext)
{
	if (ps->_pFood->x == pnext->x
		&& ps->_pFood->y == pnext->y)
	{
		return 1;
	}
	else
	{
		return 0;
	}
}

//蛇吃掉了食物
void EatFood(pSnack ps, pSnackNode pnext)
{
	//1.需要将食物结点也加入到蛇链表上,然后再生成一个食物
	pnext->next = ps->_pSnack;
	ps->_pSnack = pnext;

	//2.将新的蛇打印出来
	pSnackNode pcur = ps->_pSnack;
	while (pcur)
	{
		SetPos(pcur->x, pcur->y);
		wprintf(L"%lc", BODY);
		pcur = pcur->next;
	}

	//3.释放原来食物的结点,然后再创建一个食物结点
	free(ps->_pFood);
	ps->_Score += ps->_FoodWeight;//加分
	InitFood(ps);
}

//蛇没吃掉食物
void NoFood(pSnack ps, pSnackNode pnext)
{
	//头插
	pnext->next = ps->_pSnack;
	ps->_pSnack = pnext;

	//打印蛇身
	pSnackNode cur = ps->_pSnack;
	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 SnackMove(pSnack ps)
{
	//蛇的移动只需要重新开辟一块新的空间,然后再将最后一块结点的空间释放即可
	//1.开辟一块新的空间
	pSnackNode newnode = (pSnackNode)malloc(sizeof(SnackNode));
	if (newnode == NULL)
	{
		perror("malloc faile!");
		return;
	}
	newnode->x = 0;
	newnode->y = 0;
	newnode->next = NULL;

	//2.先通过蛇移动的方向改变一下x和y值
	switch (ps->_Direction)
	{
	case UP:
		newnode->x = ps->_pSnack->x;
		newnode->y = ps->_pSnack->y - 1;//注意:向上移动时,需要将y值-1,而不是+1
		break;
	case DOWN:
		newnode->x = ps->_pSnack->x;
		newnode->y = ps->_pSnack->y + 1;
		break;
	case LEFT:
		newnode->x = ps->_pSnack->x - 2;
		newnode->y = ps->_pSnack->y;
		break;
	case RIGHT:
		newnode->x = ps->_pSnack->x + 2;
		newnode->y = ps->_pSnack->y;
		break;
	}

	//3.再判断蛇的状态
	//3.1如果下一个位置是食物,吃掉食物
	if(NextIsFood(ps, newnode))
	{
		EatFood(ps, newnode);
	}
	else
	{
		NoFood(ps, newnode);
	}
	//3.2蛇撞到自己,游戏结束
	KillBySelf(ps);
	//3.3蛇撞到墙,游戏结束
	KillByWall(ps);
}

//游戏暂停
void Pause()
{
	while (1)
	{
		Sleep(100);
		//只有当再次按下空格键时,停止暂停
		if (KEY_STATE(VK_SPACE))
		{
			break;
		}
	}
}

//2.游戏运行
void GameRun(pSnack ps)
{
	do
	{
		//打印得分以及食物分数情况
		SetPos(64, 10);
		printf("总得分:%05d", ps->_Score);
		SetPos(64, 11);
		printf("每个食物的分数:%2d", ps->_FoodWeight);

		//通过按键修改蛇身结构
		//1.修改蛇的方向
		if (KEY_STATE(VK_UP) && ps->_Direction != DOWN)
		{
			ps->_Direction = UP;
		}
		else if(KEY_STATE(VK_DOWN) && ps->_Direction != UP)
		{
			ps->_Direction = DOWN;
		}
		else if (KEY_STATE(VK_LEFT) && ps->_Direction != RIGHT)
		{
			ps->_Direction = LEFT;
		}
		else if (KEY_STATE(VK_RIGHT) && ps->_Direction != LEFT)
		{
			ps->_Direction = RIGHT;
		}
		//2.修改蛇的状态
		else if (KEY_STATE(VK_SPACE))
		{
			Pause();//空格键为暂停
		}
		else if (KEY_STATE(VK_F3))
		{
			//修改权重也是需要限制的
			if(ps->_SleepTime >= 80)
			{
				ps->_SleepTime -= 30;
				ps->_FoodWeight += 2;//修改休息时间就可以实现加速状态,加速时食物的得分权重会更高
			}
		}
		else if (KEY_STATE(VK_F4))
		{
			if (ps->_SleepTime < 320)
			{
				ps->_SleepTime += 30;
				ps->_FoodWeight -= 2;
			}
		}

		//蛇每次移动之后需要休息一下,来保证蛇的速度
		Sleep(ps->_SleepTime);

		//蛇的移动
		SnackMove(ps);

	} while (ps->_State == OK);//当游戏状态为正常时,表示游戏继续进行
}

4.3游戏结束

c 复制代码
//3.游戏结束
void GameEnd(pSnack ps)
{
	//游戏结束
	//1.打印结束原因
	SetPos(20, 12);
	switch (ps->_State)
	{
	case KILL_BY_SELF:
		printf("您撞到了自己,游戏结束\n");
		break;
	case KILL_BY_WALL:
		printf("您撞到了墙,游戏结束\n");
		break;
	case ESC_NORMAL:
		printf("您通过esc正常退出,游戏结束\n");
		break;
	}
	//2.释放空间
	pSnackNode pcur = ps->_pSnack;
	while (pcur)
	{
		pSnackNode del = pcur->next;
		free(pcur);
		pcur = del;
	}
	ps->_pSnack = NULL;
}
相关推荐
small_wh1te_coder19 分钟前
硬件嵌入式学习路线大总结(一):C语言与linux。内功心法——从入门到精通,彻底打通你的任督二脉!
linux·c语言·汇编·嵌入式硬件·算法·c
花落已飘1 小时前
STM32中实现shell控制台(shell窗口输入实现)
stm32·单片机·嵌入式硬件
花落已飘1 小时前
STM32中实现shell控制台(命令解析实现)
stm32·shell
没有钱的钱仔1 小时前
STM32低功耗模式全面指南
css·stm32·css3
牵牛老人3 小时前
Qt处理USB摄像头开发说明与QtMultimedia与V4L2融合应用
stm32·单片机·qt
宇钶宇夕4 小时前
针对工业触摸屏维修的系统指南和资源获取途径
单片机·嵌入式硬件·自动化
黑听人4 小时前
【力扣 简单 C】70. 爬楼梯
c语言·leetcode
杜子不疼.5 小时前
二分查找,乘法口诀表,判断闰年,判断素数,使用函数实现数组操作
c语言
和风化雨5 小时前
stm32的三种开发方式
stm32·单片机·嵌入式硬件
kanhao1005 小时前
三态逻辑详解:单片机GPIO、计算机总线系统举例
单片机·嵌入式硬件