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

news2024/12/23 10:07:44

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

  • 1.游戏实现背景
  • 2.Win32 API介绍
    • 2.1什么是Win32 API
    • 2.2控制台程序(Console)
    • 2.3控制台屏幕的坐标COORD
    • 2.4GetStdHandle
      • 2.4.1函数语法
      • 2.4.2函数的使用
    • 2.5GetConsoleCursorInfo
      • 2.5.1函数语法
      • 2.5.2函数的使用
    • 2.6CONSOLE_CURSOR_INFO
      • 2.6.1结构体结构
      • 2.6.2结构体的使用
    • 2.7SetConsoleCursorInfo
      • 2.7.1函数语法
      • 2.7.2函数的使用
    • 2.8SetConsoleCursorPosition
      • 2.8.1函数语法
      • 2.8.2函数的使用
      • 2.8.3设置一个光标位置的函数
    • 2.9GetAsyncKeyState
      • 2.9.1函数语法
      • 2.9.2函数的使用
  • 3.地图的实现
    • 3.1<locale.h>本地化
    • 3.2类项
    • 3.3setlocale函数
      • 3.3.1函数说明
      • 3.3.2函数的使用
    • 3.4宽字符的打印
    • 3.5地图的绘制
  • 4.游戏的完整代码实现
    • 4.1游戏开始
    • 4.2游戏运行
    • 4.3游戏结束

1.游戏实现背景

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

2.Win32 API介绍

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

2.1什么是Win32 API

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

2.2控制台程序(Console)

平时我们运行起来的黑框就是控制台程序
我们可以使用cmd命令来改变控制台窗口的长宽:长为 30、宽为30

//lines:行  cols:列
mode con cols=30 lines=30

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

title 测试API程序

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

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

	return 0;
}

最终效果:
在这里插入图片描述
但是我们会发现:我们设的长和宽分明是相等的,但是为什么控制台窗口的大小不是一个正方形呢?我们来看:

2.3控制台屏幕的坐标COORD

COORD是Windows API中定义的一个结构体,表示一个字符在屏幕缓冲区的一个坐标,坐标的分布如下:
在这里插入图片描述
COORD结构体的定义为:

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

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

COORD pos = {10, 20};

2.4GetStdHandle

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

2.4.1函数语法

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

HANDLE WINAPI GetStdHandle(
  _In_ DWORD nStdHandle
);

该函数只有一个参数nStdHandle,对于该参数的介绍为:
在这里插入图片描述
如果函数成功,那么函数将会返回指定设备的句柄

2.4.2函数的使用

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

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

2.5GetConsoleCursorInfo

2.5.1函数语法

BOOL WINAPI GetConsoleCursorInfo(
	_In_  HANDLE               hConsoleOutput,
	_Out_ PCONSOLE_CURSOR_INFO lpConsoleCursorInfo
);

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

2.5.2函数的使用

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

	return 0;
}

2.6CONSOLE_CURSOR_INFO

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

2.6.1结构体结构

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结构体的使用

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函数语法

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

2.7.2函数的使用

#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函数语法

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

2.8.2函数的使用

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

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

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

此时光标位置就被改变了:
在这里插入图片描述

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

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

//封装一个设置光标位置的函数
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函数语法

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

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

2.9.2函数的使用

//我们可以写一个宏,通过使用宏我们可以更高效地判断按键的状态
#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语言支持对不同类型进行修改,下面的一个宏,针对一个类项:

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函数说明

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函数的使用

//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即可:

//宽字符的打印
#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地图的绘制

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

//地图的绘制
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游戏开始

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

//将地图、蛇身、食物都定义为宏,方便使用
#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游戏运行

//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游戏结束

//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;
}

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

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

相关文章

自制游戏手柄--电位器的使用

在前面的讨论中&#xff0c;我们考虑了使用陀螺仪来获取手柄的运动情况来进行瞄准&#xff0c; 自制实战吃鸡手柄原理-CSDN博客 也可以使用图像识别来实现&#xff0c;这里我们再考虑下使用电位器来获取运动状态&#xff0c;一个电位器可以获取到一个平面上的旋转情况&#x…

2025年25届新文出炉:如何打造Java SpringBoot Vue个性化课程推荐系统?

✍✍计算机编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java实战 |…

根据NVeloDocx Word模板引擎生成Word(二)

前面讲到了根据“永久免费开放的E6低代码开发平台”的NVeloDocx Word模版引擎生成Word文件的基础取数方法&#xff0c;包括取本表单字段以及引用字段&#xff0c;详见《根据NVeloDocx Word模板引擎生成Word&#xff08;一&#xff09;》。 针对这种基本的取数方法&#xff0c;…

枚举相关知识点

1.是用户定义的数据类型&#xff0c;为一组相关的常量赋予有意义的名字。 2.enum常量本身带有类型信息&#xff0c;即Weekday.SUN类型是Weekday&#xff0c;编译器会自动检查出类型错误&#xff0c;在编译期间可检查错误。 3.enum定义的枚举类有什么特点。 a.定义的enu…

Ubuntu之源码编译安装nginx

参考&#xff1a;Ubuntu之源码编译安装nginx_ubuntu编译安装nginx-CSDN博客 1.下载源码后进入源码目录&#xff0c;如下&#xff1a; cd /home/jq/wf/nginx-1.26.1 2.下载相应依赖库&#xff1a; apt-get install libpcre3-dev apt-get install openssl libssl-dev apt-get…

互联网中的情绪价值

在互联网中&#xff0c;信息的传递和分享变得越来越便捷了&#xff0c;同时&#xff0c;「情绪价值」在虚拟世界中的作用也愈加凸显。 无论是在社交媒体、即时通讯工具、各类论坛社区以及短视频平台里&#xff0c;情绪价值已然成为一种无形但是至关重要的资产&#xff0c;在默默…

BaseCTF-[Week3] 出题人已疯-快坚持不下去的第六天

DIE 查&#xff0c;发现是 .NET&#xff0c;拖⼊ dnSpy 加密的代码 private void Btn_Submit_Click(object sender, RoutedEventArgs e) { char[] array this.Tb_Input.Text.ToCharArray(); char[] array2 string.Join("", this.sentences).ToCharArray(); for (i…

php、Java、python酒店预约与送餐系统 酒店管理系统 酒店预订入住系统(源码、调试、LW、开题、PPT)

&#x1f495;&#x1f495;作者&#xff1a;计算机源码社 &#x1f495;&#x1f495;个人简介&#xff1a;本人 八年开发经验&#xff0c;擅长Java、Python、PHP、.NET、Node.js、Android、微信小程序、爬虫、大数据、机器学习等&#xff0c;大家有这一块的问题可以一起交流&…

C++第四十七弹---深入理解异常机制:try, catch, throw全面解析

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】【C详解】 目录 1.C语言传统的处理错误的方式 2.C异常概念 3. 异常的使用 3.1 异常的抛出和捕获 3.2 异常的重新抛出 3.3 异常安全 3.4 异常规范 4.自定义…

宠物狗检测-目标检测数据集(包括VOC格式、YOLO格式)

宠物狗检测-目标检测数据集&#xff08;包括VOC格式、YOLO格式&#xff09; 数据集&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1roegkaGAURWUVRR-D7OzzA?pwddxv6 提取码&#xff1a;dxv6 数据集信息介绍&#xff1a; 共有20580 张图像和一一对应的标注文件 标…

如何使用微软的Copilot AI工具将Word文档转换为PowerPoint

Copilot 让你可以将 Word 文档转换为 PowerPoint 演示文稿&#xff0c;使你能够以最小的努力制作出有针对性的演示文稿。这个功能是微软AI工具包的一部分&#xff0c;对于那些曾经盯着空白幻灯片不知道从何开始的人来说&#xff0c;这是一个颠覆性的改变。要充分利用这个工具&a…

libtool 中的 .la 文件说明

libtool 中的 .la 文件说明 1 概述 在 Linux 系统中&#xff0c;libtool 是一个用于自动化编译和链接复杂软件项目的工具&#xff0c;特别是那些使用了共享库&#xff08;.so 文件在 Linux 上&#xff0c;.dylib 在 macOS 上&#xff09;的项目。它帮助处理各种编译器和链接器…

快速上手 | 数据可观测性平台 Datavines 自定义SQL规则使用指南

摘要 本文主要介绍在 Datavines平台已有规则不能满足需求的情况下&#xff0c;如何通过自定义SQL规则来实现基于业务特性的数据质量检查。 规则介绍 自定义聚合SQL规则是 Datavines 平台中内置的一个灵活的规则&#xff0c;该规则允许用户通过编写SQL的方式来实现想要的数据质…

透传:利用 vercel 部署 OpenAI Proxy

透传&#xff1a;通俗理解国内ping不通国外大模型&#xff0c;需要做一层代理通过本地调用国外大模型官方的key。 一、利用 vercel 部署 OpenAI Proxy 第一步&#xff1a;Fork OpenEE 这个仓库 https://github.com/openaiee/openaiee 第二步&#xff1a;创建vercel项目 第三步…

『功能项目』战士的平A特效【35】

我们打开上一篇34武器的切换实例的项目&#xff0c; 本章要做的事情是在战士的每次按A键时在指定位置生成一个平A特效 首先将之前下载的技能拖拽至场景中 完全解压缩后重命名为AEffect 拖拽至预制体文件夹 进入主角动画的战士动画层级 双击第一次攻击 选择Animation 创建事件 …

【C++】栈和队列、优先级队列、适配器原理

目录 一.栈和队列相关接口 二.适配器介绍 三.栈和队列模拟实现 四.deque介绍 五.优先级队列 六.优先级队列的模拟实现 1.基本结构 2.插入删除操作 一.栈和队列相关接口 1.栈&#xff08;Stack&#xff09;的接口 由于栈接口只能支持栈顶插入&#xff08;入栈&#xff0…

【C语言版】数据结构教程(二)线性表

【内容简介】本文整理数据结构&#xff08;C语言版&#xff09;相关内容的复习笔记&#xff0c;供各位朋友借鉴学习。本章内容更偏于记忆和理解&#xff0c;请读者们耐心阅读。同时&#xff0c;这里提醒各位读者&#xff0c;尽管书本上说本书用的是 C 语言版&#xff0c;但是中…

基于javaweb的茶园茶农文化交流平台的设计与实现(源码+L文+ppt)

springboot基于javaweb的茶园茶农文化交流平台的设计与实现&#xff08;源码L文ppt&#xff09;4-20 系统功能结构 系统结构图可以把杂乱无章的模块按照设计者的思维方式进行调整排序&#xff0c;可以让设计者在之后的添加&#xff0c;修改程序内容的过程中有一个很明显的思维…

使用 WebStorm 导入已有的 Vue 项目并运行的步骤与注意事项

目录 1. 引言2. WebStorm 环境准备2.1 安装 WebStorm2.2 配置 Node.js 和 npm2.3 使用 nvm 管理 Node.js 和 npm 版本2.4 npm 版本与 Vue 版本对应关系 3. 导入已有的 Vue 项目3.1 打开 Vue 项目3.2 安装项目依赖3.3 使用 nvm 控制 Node.js 和 npm 版本 4. 运行 Vue 项目4.1 启…

STM32双轮平衡小车(基于STM32F103C8T6HAL库)

STM32双轮平衡小车参考教程 这个项目是跟做以上UP的STM32双轮平衡小车&#xff0c;主要是为了学习电机驱动和PID控制。这篇我就不提供源码了&#xff0c;我也是跟学的&#xff0c;原作者也提供了源码&#xff0c;我记录一下自己的理解。 1 PID原理 1.1 PID简介 1.2 PID演示 …