贪吃蛇解析

目录

文章结尾有代码可自取

Win32API

光标的隐藏

获取按键信息

控制光标位置

游戏开始前的准备

游戏准备及介绍

加载和欢迎界面

打印游戏指南

运行游戏

打印墙体和说明

设置蛇的各个信息

初始化及打印蛇

创造食物

运行游戏

1)打印得分情况

2)获取按键信息

3)蛇走的下一步

下一步不是食物

下一步是食物

4)检查是否撞到墙或自己

游戏结束

各个文件代码统计

头文件

源文件

源文件


文章结尾有源码可自取

Win32API

此处首先对Win32API进行简单的介绍,在游戏中光标的隐藏,获得用户按键信息的都会用到;

以下操作要包含头文件<windows.h>

光标的隐藏

cpp 复制代码
//隐藏光标
void Hide_cursor(void)
{
	//获得输出控制台句柄
	HANDLE houtput = NULL;
	houtput = GetStdHandle(STD_OUTPUT_HANDLE);
	//获得控制台光标信息
	CONSOLE_CURSOR_INFO cursor_info;
	GetConsoleCursorInfo(houtput, &cursor_info);
	//将光标信息中的可见度改为0
	cursor_info.bVisible = 0;
	SetConsoleCursorInfo(houtput, &cursor_info);
}

获取按键信息

可以读取用户按下了哪一个键。

cpp 复制代码
//获取按键信息
#define KEY_PRESS(vk) (GetAsyncKeyState(vk)?1:0)
//vk表示按键
//如:Key_Press(VK_UP)如果用户按下↓,则返回1,否则是0

控制光标位置

在有一些情况不希望将光标放在控制台(黑框)的开始默认位置,我们可能需要在控制台中央打印,此时就需要对控制台光标位置进行定位。

cpp 复制代码
//定位光标位置
void SetPos(size_t x, size_t y)
{
	//获得输出控制台句柄
	HANDLE houtput = NULL;
	houtput = GetStdHandle(STD_OUTPUT_HANDLE);
	// 获取光标
	COORD cursorPos = { 0, 0 };
	
	//修改光标位置
	cursorPos.X = x;
	cursorPos.Y = y;
	SetConsoleCursorPosition(houtput,cursorPos);
}

关于Win32API的知识不过多讲解,对于初学者可以直接使用上述代码实现功能。有兴趣可以看windows的控制台函数。控制台函数 - Windows Console | Microsoft Learnhttps://learn.microsoft.com/zh-cn/windows/console/console-functions


游戏开始前的准备

游戏进行时我们有时候想要打印图案字符,eg:■◯Ő以及一些汉字(宽字符)。我们就需要将程序修改为本地状态,可以理解为不仅仅能够打印外国的英文,还能够打印我们本地中文汉字。

为了有更好的游戏体验,需要对控制台的大小和名称进行修改。

cpp 复制代码
#include<locale.h>
void Prepare(void)
{
	//先将程序改成本地模式
	setlocale(LC_ALL, "");
	//先对控制台的大小进行修改
	system("mode con cols=150 lines=40");

	//对名称进行重命名
	system("title 贪吃蛇");
}

游戏准备及介绍

隐藏完光标后,我们进行游戏开始之前的打印,范围两部分,打印加载和欢迎页面;打印游戏指南,方法。所以在游戏开始运行之前有两个页面需要打印。

加载和欢迎界面

先打印第一个封面,欢迎和加载界面。此处将代码封装成立三个函数:打印进度条函数;打印欢迎界面函数。效果如下。

进度条的实现主要是依靠一个数组,通过循环向数组里添加字符,打印数组从而动态的效果。

cpp 复制代码
//加载界面的打印
void ProcessBar(void)
{
	wchar_t bar[102] = { '\0' }; //设置一个字符串来当作进度条
	int cnt = 0;
	while (cnt <= 100)
	{
		SetPos(25, 21);  //定位
		wprintf(GREEN L"[%-101ls]" RESET"[%d%%]", bar, cnt); //打印进度条,并加上颜色
		Sleep(100);      //休眠100毫秒
		bar[cnt] = L'█';
		cnt++;
	}
}

void Print_Page1(void)
{
	//定位光标,打印欢迎语句
	SetPos(65, 18);
	wprintf(YELLOW L"欢迎进入贪吃蛇游戏" RESET);
	//打印加载的进度条
	ProcessBar();
}

可以看到,在上买了的代码中添加了控制输出字符串的颜色,定义如下可自取。

cpp 复制代码
//定义一些颜色
#define RESET       L"\033[0m"           // 恢复默认

// 前景色 (文本颜色)
#define BLACK        L"\033[30m"
#define RED          L"\033[31m"
#define GREEN        L"\033[32m"
#define YELLOW       L"\033[33m"
#define BLUE         L"\033[34m"
#define MAGENTA      L"\033[35m"
#define CYAN         L"\033[36m"
#define WHITE        L"\033[37m"

打印游戏指南

为了让用户有更好的游戏体验,需要对玩法进行说明。第二张画面打印游戏指南。效果如下。

cpp 复制代码
void Print_Page2(void)
{
	system("cls");//清屏
	SetPos(58, 17);
	wprintf(MAGENTA L"用↑↓←→来控制方向,ESC退出,SPACE暂停");
	SetPos(58, 18);
	wprintf(L"F1加速,F2减速;");
	SetPos(58, 19);
	wprintf(L"加速可以获得更多积分,减速获得积分变慢;" RESET);
	
	SetPos(58, 21);
	system("pause");
}

将page1和page2结合。

cpp 复制代码
void game_prepare(void)
{
	//隐藏光标
	Hide_cursor();
	//设置控制台名称和大小
	Prepare();
	//打印第一个画面,进入画面
	Print_Page1();
	//打印游戏指南
	Print_Page2();
}

运行游戏

打印墙体和说明

墙体的长和宽都可以自定义设置。效果如下。

打印墙体的时候要注意临界位置的讨论。

cpp 复制代码
//打印墙体
void Print_Wall(void)
{
	SetPos(0, 0);
	//打印横向
	for (int i = 0; i < LEN; i += 2)  //注意此处是+2,因为宽字符的宽度是2不像普通字符一样
		wprintf(L"%c", WALL);
	SetPos(0, WIDTH);
	for (int i = 0; i < LEN; i += 2)  
		wprintf(L"%c", WALL);

	//打印纵向
	for (int i = 1; i < WIDTH; i++)
	{
		SetPos(0, i);
		wprintf(L"%c", WALL);
		SetPos(LEN-2, i);
		wprintf(L"%c", WALL);
	}
}

//打印说明
void Print_RULE(void)
{
	SetPos(100, 8);
	wprintf(GREEN L"↑↓←→来控制方向");
	SetPos(100, 9);
	wprintf(L"ESC退出,SPACE暂停");
	SetPos(100, 10);
	wprintf(L"F1加速,F2减速");
	SetPos(100, 11);
	wprintf(L"加速可以获得更多积分,减速获得积分变慢" RESET);
}

设置蛇的各个信息

在打印蛇之前,需要考虑设置什么样的信息。此处首先就是蛇身的各个节点,还需要蛇的方向,状态,蛇的得分,以及食物信息,所以要创建多个结构体:蛇的各个信息,蛇的每个节点,关于食物的信息。

通过枚举将蛇的方向,状态一一列举出来。

cpp 复制代码
//设置方向
typedef enum DIR
{
	RIGHT,
	LEFT,
	UP,
	DOWN
}DIR;
//设置蛇的状态
typedef enum STATE
{
	FINE,            //正常
	DEAD_BYWALL,     //撞墙
	DEAD_BYBODY,     //撞到蛇身
	QUIT             //退出
}STATE;
//设置蛇节点
typedef struct Snackbody
{
	size_t _x;
	size_t _y;
	struct Snackbody* _next;
}Snackbody;
//食物信息
typedef struct Food
{
	size_t x;
	size_t y;
	size_t _each_score;
}Food;
//设置结构体来存储蛇的各个信息
typedef struct Snack
{
	Snackbody* _head;
	size_t _speed;
	size_t _score;
	DIR dir;
	STATE state;
	Food* pf;
}Snack;

初始化及打印蛇

初始化打印蛇,主要分为两步:创建蛇身的各个节点并将其链接;初始化蛇的状态,得分等信息。

cpp 复制代码
//创造节点
Snackbody* MakeBody()
{
	Snackbody* newbody = (Snackbody*)malloc(sizeof(Snackbody));
	newbody->_next = NULL;
	newbody->_x = 0;
	newbody->_y = 0;
	return newbody;
}

//对蛇进行初始化
void Init_Snack(Snack* sn)
{
	//对蛇的各个数据进行初始化
	sn->_head = NULL;
	sn->dir = RIGHT;
	sn->state = FINE;
	sn->_score = 0;
	sn->_speed = 200;

	//创建蛇身
	Snackbody** snhead = &(sn->_head);  //注意此处需要是二级指针,因为要对其地址进行修改
	Snackbody* tail = *snhead;
	for (int i = 0; i < 5; i++)
	{
		Snackbody* newbody = MakeBody();
		newbody->_x = 20 - i * 2;
		newbody->_y = 15;
		if ((*snhead) == NULL)
		{
			*snhead = newbody;
			tail = newbody;
		}
		else
		{
			(tail)->_next = newbody;
			tail = (tail)->_next;
		}
	}
	//打印
	Snackbody* cur = sn->_head;
	while (cur != NULL)
	{
		SetPos(cur->_x, cur->_y);
		wprintf(L"%c", SNACK);
		cur = cur->_next;
	}
}

创造食物

此处通过伪随机数,时间戳来确定食物的位置。

注意:创造的食物必须在墙内部,并且不能创作到蛇身上。所以在创建完食物后,要对食物的位置进行检查。

cpp 复制代码
#define FOOD L'豆'
//检查食物
bool CHECK_food(Snack* sn)
{
	size_t x = sn->pf->x;
	size_t y = sn->pf->y;
	Snackbody* cur = sn->_head;
	while (cur != NULL)
	{
		if (cur->_x == x && cur->_y == y)
			return false;
		cur = cur->_next;
	}
	return true;
}

//创造食物
void Make_food(Snack* sn)
{
	sn->pf = (Food*)malloc(sizeof(Food));
	sn->pf->_each_score = 10;

	srand((unsigned int)time(NULL));
again:
	sn->pf->x = rand()%65+2;   //食物x范围是2-66
	sn->pf->y = rand()%21+1;   //y的范围是1-21
	//检查食物是否符合要求
	//x必须是偶数,食物不能出现在蛇身上
	if (sn->pf->x % 2 != 0 || !CHECK_food(sn))
	{
		goto again;
	}
	SetPos(sn->pf->x, sn->pf->y);
	wprintf(L"%c", FOOD);
}

运行游戏

游戏是要持续进行的,所以肯定要设置循环,此处采用do...while循环,在蛇没有死之前,游戏都要能正常运行,所以循环的条件就是蛇的状态是不是FINE。

在循环中,主要有4个部分需要处理:打印侧栏得分;获取按键信息;打印蛇的下一步;检查状态。具体实现目的如下。

1)打印得分情况

游戏运行过程中要保证实时打印总得分及当前每个食物的得分。如下图所示

cpp 复制代码
SetPos(100, 6);
wprintf(L"总得分:%d", sn->_score);
SetPos(100, 7);
wprintf(L"当前食物分数:%2d", sn->pf->_each_score);

2)获取按键信息

关于按键信息如何获取,前面Win32API已经写了,此处直接使用其宏定义,获取完按键信息后要对蛇的方向进行修改。

对于不同的按键要有不同的响应:上下左右直接改变方向即可;F1需要添加分数,缩短睡眠时间;F2相反;Esc则需要改变蛇的状态,让循环停止;Space需要暂停游戏,这里使用一个死循环来模拟暂停。

cpp 复制代码
	//暂停
    void SPACE()
    {
	    while (1)
	    {
		if (KEY_PRESS(VK_SPACE))
			break;
	    }
    }

    //检查按键
	if (KEY_PRESS(VK_UP) && sn->dir != DOWN)
		sn->dir = UP;
	if (KEY_PRESS(VK_DOWN) && sn->dir != UP)
		sn->dir = DOWN;
	if (KEY_PRESS(VK_LEFT) && sn->dir != RIGHT)
		sn->dir = LEFT;
	if (KEY_PRESS(VK_RIGHT) && sn->dir != LEFT)
		sn->dir = RIGHT;
	if (KEY_PRESS(VK_F1))
	{
		if (sn->_speed > 40)
		{
			sn->pf->_each_score += 2;
			sn->_speed -= 40;
		}
	}
	if (KEY_PRESS(VK_F2))
	{
		if (sn->pf->_each_score > 2)
		{
			sn->pf->_each_score -= 2;
			sn->_speed += 40;
		}
	}
	if (KEY_PRESS(VK_SPACE))
		SPACE();
	if (KEY_PRESS(VK_ESCAPE))
	{
		sn->state = QUIT;
		break;
	}
    Sleep(sn->_speed);   //休眠,准备打印下一张图片

3)蛇走的下一步

可以通过蛇的方向来预测蛇的下一个位置,将下一个位置打印成蛇即可实现移动。此处需要考虑下一个位置是不是食物。两种情况是不同的,不是食物的话,就需要将尾部变为空,将下一个位置打印成蛇;是食物的话,就不需要对尾部进行处理,但是需要重新创建食物。

cpp 复制代码
//打印下一张图片
void Print_NEXT(Snack* sn)
{
	//创建下一个位置的节点
	Snackbody* pnext = (Snackbody*)malloc(sizeof(Snackbody));
	switch(sn->dir)
	{
	case UP:
		pnext->_x = sn->_head->_x;
		pnext->_y = sn->_head->_y - 1;
		break;
	case DOWN:
		pnext->_x = sn->_head->_x;
		pnext->_y = sn->_head->_y + 1;
		break;
	case LEFT:
		pnext->_x = sn->_head->_x - 2;
		pnext->_y = sn->_head->_y;
		break;
	case RIGHT:
		pnext->_x = sn->_head->_x + 2;
		pnext->_y = sn->_head->_y;
		break;
	}
	//下一个位置是食物
	if (pnext->_x == sn->pf->x && pnext->_y == sn->pf->y)
	{
		IS_FOOD(sn,pnext);
	}
	else
		NO_FOOD(sn,pnext);
}
下一步不是食物

不是食物的话,就需要将尾部变为空,将下一个位置打印成蛇;

cpp 复制代码
//下一个位置不是食物
void NO_FOOD(Snack* sn, Snackbody* pnext)
{
	//将下一个节点当成蛇头
	pnext->_next = sn->_head;
	sn->_head = pnext;
	//找到最后一个节点并释放
	Snackbody* cur = sn->_head;
	while (cur->_next->_next != NULL)
		cur = cur->_next;
	//将蛇尾从原本的蛇改为空
	SetPos(cur->_next->_x, cur->_next->_y);
	wprintf(L"  ");
	free(cur->_next);
	cur->_next = NULL;
	//将蛇的下一个位置打印成蛇
	SetPos(pnext->_x, pnext->_y);
	wprintf(L"%c", SNACK);
}
下一步是食物
cpp 复制代码
//下一个位置是食物
void IS_FOOD(Snack* sn,Snackbody* pnext)
{
	//将食物直接当成蛇头
	pnext->_next = sn->_head;
	sn->_head = pnext;
	SetPos(pnext->_x, pnext->_y);
	wprintf(L"%c", SNACK);
	//加分
	sn->_score += sn->pf->_each_score;
	//创建食物
	Make_food(sn);
}

4)检查是否撞到墙或自己

此处直接通过坐标进行检查即可。

cpp 复制代码
//检查是不是墙
void IF_WALL(Snack* sn)
{
	if (sn->_head->_x >= LEN - 2 || sn->_head->_x == 0)
		sn->state = DEAD_BYWALL;
	if (sn->_head->_y == WIDTH || sn->_head->_y == 0)
		sn->state = DEAD_BYWALL;
}

//检查是否撞到自己
void IF_SELF(Snack* sn)
{
	int x = sn->_head->_x;
	int y = sn->_head->_y;

	Snackbody* cur = sn->_head->_next;
	while (cur != NULL)
	{
		if (cur->_x == x && cur->_y == y)
			sn->state = DEAD_BYBODY;
		cur = cur->_next;
	}
}

游戏结束

游戏可以正常运行了,但是还要对游戏结束进行处理,要将游戏结束的信息反馈给用户,还要对游戏运行过程中动态开辟的空间进行销毁。

cpp 复制代码
//游戏结束
void Game_over(Snack* sn)
{
	system("cls");
	SetPos(65, 18);
	switch (sn->state)
	{
	case DEAD_BYWALL:
		wprintf(MAGENTA L"你撞到墙了!!!");
		break;
	case DEAD_BYBODY:
		wprintf(L"你撞到自己了!!!");
		break; 
	case QUIT:
		wprintf(L"已退出!!!" RESET);
		break;
	}
	SetPos(0, 35);
	//释放蛇身各个节点,释放动态开辟的空间
	Snackbody* cur = sn->_head;
	while (cur != NULL)
	{
		Snackbody* next = cur->_next;
		free(cur);
		cur = next;
	}
	free(sn->pf);
}

各个文件代码统计

<Blog_Snack.h>头文件

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<windows.h>
#include<locale.h>
#include<stdbool.h>
#include<time.h>

//定义一些颜色
#define RESET       L"\033[0m"           // 恢复默认

// 前景色 (文本颜色)
#define BLACK        L"\033[30m"
#define RED          L"\033[31m"
#define GREEN        L"\033[32m"
#define YELLOW       L"\033[33m"
#define BLUE         L"\033[34m"
#define MAGENTA      L"\033[35m"
#define CYAN         L"\033[36m"
#define WHITE        L"\033[37m"

#define WALL L'墙'  //也可以用█,看自己。但是一定要是宽字符
#define SNACK L'蛇'  //可以定义成其他宽字符
#define FOOD L'豆'

#define LEN 70
#define WIDTH 22

//获取按键信息
#define KEY_PRESS(vk) (GetAsyncKeyState(vk)?1:0)
//vk表示按键
//如:Key_Press(VK_UP)如果用户按下↓,则返回1,否则是0

//设置方向
typedef enum DIR
{
	RIGHT,
	LEFT,
	UP,
	DOWN
}DIR;
//设置蛇的状态
typedef enum STATE
{
	FINE,            //正常
	DEAD_BYWALL,     //撞墙
	DEAD_BYBODY,     //撞到蛇身
	QUIT             //退出
}STATE;
//设置蛇节点
typedef struct Snackbody
{
	size_t _x;
	size_t _y;
	struct Snackbody* _next;
}Snackbody;
//食物信息
typedef struct Food
{
	size_t x;
	size_t y;
	size_t _each_score;
}Food;
//设置结构体来存储蛇的各个信息
typedef struct Snack
{
	Snackbody* _head;
	size_t _speed;
	size_t _score;
	DIR dir;
	STATE state;
	Food* pf;
}Snack;

//设置控制台名称和大小
void Prepare(void);
//打印游戏指南
void Print_Page2(void);

//隐藏光标
void Hide_cursor(void);

//定位光标位置
void SetPos(size_t x, size_t y);

//开始前的准备
void game_prepare(void);

//第一个界面
void Print_Page1(void);

//加载界面的打印
void ProcessBar(void);

//运行游戏
void game_start(void);

//打印墙体
void Print_Wall(void);

//打印说明
void Print_RULE(void);

//打印蛇
void Print_Snack(Snack* sn);

//对蛇进行初始化
void Init_Snack(Snack* sn);

//创造节点
Snackbody* MakeBody();

//创造食物
void Make_food(Snack* sn);

//检查食物
bool CHECK_food(Snack* sn);

//游戏运行
void start(Snack* sn);

//打印下一张图片
void Print_NEXT(Snack* sn);

//暂停
void SPACE();

//下一个位置是食物
void IS_FOOD(Snack* sn,Snackbody* pnext);

//下一个位置不是食物
void NO_FOOD(Snack* sn, Snackbody* pnext);

//检查是不是墙
void IF_WALL(Snack* sn);

//检查是否撞到自己
void IF_SELF(Snack* sn);

//游戏结束
void Game_over(Snack* sn);

<Snack.c>源文件

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

//隐藏光标
void Hide_cursor(void)
{
	//获得输出控制台句柄
	HANDLE houtput = NULL;
	houtput = GetStdHandle(STD_OUTPUT_HANDLE);
	//获得控制台光标信息
	CONSOLE_CURSOR_INFO cursor_info;
	GetConsoleCursorInfo(houtput, &cursor_info);
	//将光标信息中的可见度改为0
	cursor_info.bVisible = 0;
	SetConsoleCursorInfo(houtput, &cursor_info);
}

//定位光标位置
void SetPos(size_t x, size_t y)
{
	//获得输出控制台句柄
	HANDLE houtput = NULL;
	houtput = GetStdHandle(STD_OUTPUT_HANDLE);
	// 获取光标
	COORD cursorPos = { 0, 0 };
	
	//修改光标位置
	cursorPos.X = x;
	cursorPos.Y = y;
	SetConsoleCursorPosition(houtput,cursorPos);
}

#include<locale.h>
void Prepare(void)
{
	//先将程序改成本地模式
	setlocale(LC_ALL, "");
	//先对控制台的大小进行修改
	system("mode con cols=150 lines=40");

	//对名称进行重命名
	system("title 贪吃蛇");
}


//加载界面的打印
void ProcessBar(void)
{
	wchar_t bar[102] = { '\0' }; //设置一个字符串来当作进度条
	int cnt = 0;
	while (cnt <= 100)
	{
		SetPos(25, 21);  //定位
		wprintf(GREEN L"[%-101ls]" RESET"[%d%%]", bar, cnt); //打印进度条,并加上颜色
		Sleep(1);      //休眠100毫秒
		bar[cnt] = L'█';
		cnt++;
	}
}

void Print_Page1(void)
{
	//定位光标,打印欢迎语句
	SetPos(65, 18);
	wprintf(YELLOW L"欢迎进入贪吃蛇游戏" RESET);
	//打印加载的进度条
	ProcessBar();
}

void Print_Page2(void)
{
	system("cls");//清屏
	SetPos(58, 17);
	wprintf(MAGENTA L"用↑↓←→来控制方向,ESC退出,SPACE暂停;");
	SetPos(58, 18);
	wprintf(L"F1加速,F2减速;");
	SetPos(58, 19);
	wprintf(L"加速可以获得更多积分,减速获得积分变慢;" RESET);
	
	SetPos(58, 21);
	system("pause");
}



//打印墙体
void Print_Wall(void)
{
	SetPos(0, 0);
	//打印横向
	for (int i = 0; i < LEN; i += 2)  //注意此处是+2,因为宽字符的宽度是2不像普通字符一样
		wprintf(L"%c", WALL);
	SetPos(0, WIDTH);
	for (int i = 0; i < LEN; i += 2)  
		wprintf(L"%c", WALL);

	//打印纵向
	for (int i = 1; i < WIDTH; i++)
	{
		SetPos(0, i);
		wprintf(L"%c", WALL);
		SetPos(LEN-2, i);
		wprintf(L"%c", WALL);
	}
}

//打印说明
void Print_RULE(void)
{
	SetPos(100, 8);
	wprintf(GREEN L"↑↓←→来控制方向");
	SetPos(100, 9);
	wprintf(L"ESC退出,SPACE暂停");
	SetPos(100, 10);
	wprintf(L"F1加速,F2减速");
	SetPos(100, 11);
	wprintf(L"加速可以获得更多积分,减速获得积分变慢" RESET);
}

//创造节点
Snackbody* MakeBody()
{
	Snackbody* newbody = (Snackbody*)malloc(sizeof(Snackbody));
	newbody->_next = NULL;
	newbody->_x = 0;
	newbody->_y = 0;
	return newbody;
}

//对蛇进行初始化
void Init_Snack(Snack* sn)
{
	//对蛇的各个数据进行初始化
	sn->_head = NULL;
	sn->dir = RIGHT;
	sn->state = FINE;
	sn->_score = 0;
	sn->_speed = 200;

	//创建蛇身
	Snackbody** snhead = &(sn->_head);  //注意此处需要是二级指针,因为要对其地址进行修改
	Snackbody* tail = *snhead;
	for (int i = 0; i < 5; i++)
	{
		Snackbody* newbody = MakeBody();
		newbody->_x = 20 - i * 2;
		newbody->_y = 15;
		if ((*snhead) == NULL)
		{
			*snhead = newbody;
			tail = newbody;
		}
		else
		{
			(tail)->_next = newbody;
			tail = (tail)->_next;
		}
	}
	//打印
	Snackbody* cur = sn->_head;
	while (cur != NULL)
	{
		SetPos(cur->_x, cur->_y);
		wprintf(L"%c", SNACK);
		cur = cur->_next;
	}
}

//检查食物
bool CHECK_food(Snack* sn)
{
	size_t x = sn->pf->x;
	size_t y = sn->pf->y;
	Snackbody* cur = sn->_head;
	while (cur != NULL)
	{
		if (cur->_x == x && cur->_y == y)
			return false;
		cur = cur->_next;
	}
	return true;
}

//创造食物
void Make_food(Snack* sn)
{
	sn->pf = (Food*)malloc(sizeof(Food));
	sn->pf->_each_score = 10;

	srand((unsigned int)time(NULL));
again:
	sn->pf->x = rand()%65+2;   //食物x范围是2-66
	sn->pf->y = rand()%21+1;   //y的范围是1-21
	//检查食物是否符合要求
	//x必须是偶数,食物不能出现在蛇身上
	if (sn->pf->x % 2 != 0 || !CHECK_food(sn))
	{
		goto again;
	}
	SetPos(sn->pf->x, sn->pf->y);
	wprintf(L"%c", FOOD);
}

//下一个位置是食物
void IS_FOOD(Snack* sn,Snackbody* pnext)
{
	//将食物直接当成蛇头
	pnext->_next = sn->_head;
	sn->_head = pnext;
	SetPos(pnext->_x, pnext->_y);
	wprintf(L"%c", SNACK);
	//加分
	sn->_score += sn->pf->_each_score;
	//创建食物
	Make_food(sn);
}

//下一个位置不是食物
void NO_FOOD(Snack* sn, Snackbody* pnext)
{
	//将下一个节点当成蛇头
	pnext->_next = sn->_head;
	sn->_head = pnext;
	//找到最后一个节点并释放
	Snackbody* cur = sn->_head;
	while (cur->_next->_next != NULL)
		cur = cur->_next;
	//将蛇尾从原本的蛇改为空
	SetPos(cur->_next->_x, cur->_next->_y);
	wprintf(L"  ");
	free(cur->_next);
	cur->_next = NULL;
	//将蛇的下一个位置打印成蛇
	SetPos(pnext->_x, pnext->_y);
	wprintf(L"%c", SNACK);
}

//打印下一张图片
void Print_NEXT(Snack* sn)
{
	//创建下一个位置的节点
	Snackbody* pnext = (Snackbody*)malloc(sizeof(Snackbody));
	switch(sn->dir)
	{
	case UP:
		pnext->_x = sn->_head->_x;
		pnext->_y = sn->_head->_y - 1;
		break;
	case DOWN:
		pnext->_x = sn->_head->_x;
		pnext->_y = sn->_head->_y + 1;
		break;
	case LEFT:
		pnext->_x = sn->_head->_x - 2;
		pnext->_y = sn->_head->_y;
		break;
	case RIGHT:
		pnext->_x = sn->_head->_x + 2;
		pnext->_y = sn->_head->_y;
		break;
	}
	//下一个位置是食物
	if (pnext->_x == sn->pf->x && pnext->_y == sn->pf->y)
	{
		IS_FOOD(sn,pnext);
	}
	else
		NO_FOOD(sn,pnext);
}

//暂停
void SPACE()
{
	while (1)
	{
		if (KEY_PRESS(VK_SPACE))
			break;
	}
}

//检查是不是墙
void IF_WALL(Snack* sn)
{
	if (sn->_head->_x >= LEN - 2 || sn->_head->_x == 0)
		sn->state = DEAD_BYWALL;
	if (sn->_head->_y == WIDTH || sn->_head->_y == 0)
		sn->state = DEAD_BYWALL;
}

//检查是否撞到自己
void IF_SELF(Snack* sn)
{
	int x = sn->_head->_x;
	int y = sn->_head->_y;

	Snackbody* cur = sn->_head->_next;
	while (cur != NULL)
	{
		if (cur->_x == x && cur->_y == y)
			sn->state = DEAD_BYBODY;
		cur = cur->_next;
	}
}

//游戏结束
void Game_over(Snack* sn)
{
	system("cls");
	SetPos(65, 18);
	switch (sn->state)
	{
	case DEAD_BYWALL:
		wprintf(MAGENTA L"你撞到墙了!!!");
		break;
	case DEAD_BYBODY:
		wprintf(L"你撞到自己了!!!");
		break; 
	case QUIT:
		wprintf(L"已退出!!!" RESET);
		break;
	}
	SetPos(0, 35);
	//释放蛇身各个节点,释放动态开辟的空间
	Snackbody* cur = sn->_head;
	while (cur != NULL)
	{
		Snackbody* next = cur->_next;
		free(cur);
		cur = next;
	}
	free(sn->pf);
}


//游戏运行
void start(Snack* sn)
{
	do {
		SetPos(100, 6);
		wprintf(L"总得分:%d", sn->_score);
		SetPos(100, 7);
		wprintf(L"当前食物分数:%2d", sn->pf->_each_score);
		//检查按键
		if (KEY_PRESS(VK_UP) && sn->dir != DOWN)
			sn->dir = UP;
		if (KEY_PRESS(VK_DOWN) && sn->dir != UP)
			sn->dir = DOWN;
		if (KEY_PRESS(VK_LEFT) && sn->dir != RIGHT)
			sn->dir = LEFT;
		if (KEY_PRESS(VK_RIGHT) && sn->dir != LEFT)
			sn->dir = RIGHT;
		if (KEY_PRESS(VK_F1))
		{
			if (sn->_speed > 40)
			{
				sn->pf->_each_score += 2;
				sn->_speed -= 40;
			}
		}
		if (KEY_PRESS(VK_F2))
		{
			if (sn->pf->_each_score > 2)
			{
				sn->pf->_each_score -= 2;
				sn->_speed += 40;
			}
		}
		if (KEY_PRESS(VK_SPACE))
			SPACE();
		if (KEY_PRESS(VK_ESCAPE))
		{
			sn->state = QUIT;
			break;
		}
		Sleep(sn->_speed);   //休眠,准备打印下一张图片
		Print_NEXT(sn);
		IF_WALL(sn);
		IF_SELF(sn);
	} while (sn->state == FINE);

	//游戏结束
	Game_over(sn);

}

<Snack_main.c>源文件

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


void game_prepare(void)
{
	
	//隐藏光标
	Hide_cursor();
	//设置控制台名称和大小
	Prepare();
	//打印第一个画面,进入画面
	Print_Page1();
	//打印游戏指南
	Print_Page2();
}

//运行游戏
void game_start(void)
{
	system("cls");
	//打印墙体
	Print_Wall();
	//打印说明
	Print_RULE();
	Snack sn;
	//对蛇进行初始化
	Init_Snack(&sn);
	//创造食物
	Make_food(&sn);
	//游戏运行
	start(&sn);
}

int main()
{
	game_prepare();
	game_start();
	return 0;
}
相关推荐
失去妙妙屋的米奇5 分钟前
Python库与Excel
开发语言·python·excel
API小爬虫18 分钟前
如何设置动态代理提高Python爬虫稳定性?
开发语言·爬虫·python
无名之逆18 分钟前
hyperlane:Rust HTTP 服务器开发的不二之选
服务器·开发语言·前端·后端·安全·http·rust
篝火悟者20 分钟前
自学-python-爬虫入门
开发语言·爬虫·python
老赵骑摩托28 分钟前
Go语言nil原理深度解析:底层实现与比较规则
开发语言·后端·golang
酷爱码28 分钟前
python和Java的区别
java·开发语言
胡斌附体39 分钟前
Qt下载模板到本地文件内容丢失问题
开发语言·qt
Suc_zhan1 小时前
实验二 如何将随机森林算法应用于激酶抑制剂分类任务
python·算法·随机森林·机器学习
小学仔1 小时前
leetcode -编辑距离
java·算法·动态规划
AI是这个时代的魔法1 小时前
Using Dyck Path to solve a leetcode puzzle
python·算法·leetcode