C语言链表贪吃蛇(VS2022版)


大家好,这次的内容是讲解我对 C语言链表贪吃蛇 的理解,以下代码使用的是 VS2022环境下的C语言,若有错误,恳请读者大大们批评指正。

在上一篇博客 C语言数组贪吃蛇(VS2022版) 中,虽然本人初步实现了贪吃蛇的玩法,但是还有许多可改进的地方,如:

1、画面横纵字符坐标不统一,影响游戏观感。
2、getch函数 接收玩家操作会受 Sleep函数暂停 的影响,有明显的操作延迟。
3、代码主要变量分散,不利于后期的维护。
4、数组虽能实现空间移动,但利用率太低,可用链表思想优化。

准备文件 test.c、Snake.h和Snake.c

视频演示

C语言链表贪吃蛇

一、前置知识

1.Win32 API 的使用

链表贪吃蛇会用到Win32 API的键盘操作,来解决 getch的操作延迟问题,还有光标操作。Win32 API 光标隐藏定位和键盘读取等常用函数 这篇博客已经整理完毕,感兴趣的读者可以去看看。

2.宽字符的使用

为解决横纵字符坐标统一,需要使用宽字符。C语言宽字符 wchar_t 类型 这篇博客已经整理完毕,这里就不赘述了。

二、封装核心数据和函数

数据

在游戏运行的过程中,蛇每次吃一个食物,蛇的身体就会变长一节,如果我们使用链表存储蛇的信息,那么蛇的每一节其实就是链表的每个节点。每个节点只要记录好蛇身节点在地图上的坐标就行,所以蛇节点结构如下:

typedef struct SnakeNode	// 蛇身节点
{
	// 蛇的坐标
	int x;
	int y;
	// 连接下一个蛇身
	struct SnakeNode* next;
} SnakeNode, * pSnakeNode;

蛇的方向,可以一一列举,使用枚举:

typedef enum DIRECTION		// 蛇移动方向
{
	UP = 1,					// 上
	DOWN,					// 下
	LEFT,					// 左
	RIGHT					// 右
} DIRECTION;

游戏状态,可以一一列举,使用枚举:

typedef enum GAME_STATUS	// 游戏状态
{
	NORMAL = 1,				// 游戏正常
	KILL_BY_WALL,			// 蛇撞墙
	KILL_BY_SELF,			// 蛇吃到自己
	END_NORMAL,				// 玩家主动退出
} GAME_STATUS;

要方便管理整条贪吃蛇,我们就需要封装一个Snake的结构来维护整条贪吃蛇:

typedef struct Snake
{
	pSnakeNode pHead;		// 蛇头指针
	DIRECTION dir;			// 蛇移动方向
	GAME_STATUS sta;		// 游戏状态
	pSnakeNode pfood;		// 食物位置
	int food_score;			// 食物分数
	int score;				// 游戏得分
	int sleepTime;			// 暂停时间
} Snake, *pSnake;

函数

因为要频繁调用Win32 API 中的移动光标函数,则将它封装成一个函数:

void SetPos(int x, int y)
{
	HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);	// 获得句柄
	COORD pos = { x, y };								// 坐标结构体
	SetConsoleCursorPosition(hOutput, pos);				// 设置光标位置
}

三、游戏核心逻辑

游戏主逻辑

程序开始就设置程序支持本地模式,然后进入游戏的主逻辑。主逻辑分为3个过程:
1.游戏开始(GameStart)完成游戏的初始化
2.游戏运行(GameRun)完成游戏运行逻辑的实现
3.游戏结束(GameEnd)完成游戏结束的说明,实现资源释放
在 test.c 中:

#include "Snake.h"

void test()
{
	srand((unsigned int)time(NULL));
	char input = 0;

	do
	{
		system("cls");

		// 创建贪吃蛇
		Snake snake = { 0 };		
		
		// 初始化游戏
		GameStart(&snake);

		// 游戏运行
		GameRun(&snake);

		// 游戏结束
		GameEnd(&snake);

		SetPos(20, 15);
		printf("再来一局吗?(Y/N):");
		input = getchar();
		while (getchar() != '\n');				// 清理多余缓冲区字符

	} while (input == 'Y' || input == 'y');
	SetPos(0, 27);
}

int main()
{
	setlocale(LC_ALL, "");			// 设置到本地模式
	test();

	return 0;
}

游戏开始

这个模块完成游戏的初始化任务:
1.控制台窗口大小和名字的设置
2.鼠标光标的隐藏
3.打印欢迎界面
4.创建地图
5.初始化蛇
6.创建第一个食物

void GameStart(pSnake snake)
{
	//1.控制台窗口大小和名字的设置
	system("mode con cols=100 lines=30");
	system("title 贪吃蛇");

	//2.鼠标光标的隐藏
	HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
	CONSOLE_CURSOR_INFO CursorInfo;
	GetConsoleCursorInfo(hOutput, &CursorInfo);
	CursorInfo.bVisible = false;
	SetConsoleCursorInfo(hOutput, &CursorInfo);

	//3.打印欢迎界面
	WelcomeToGame();
	
	//4.创建地图
	GreateMap();
	
	//5.初始化蛇
	InitSnake();

	//6.创建第一个食物
	GreateFood();
}

打印欢迎界面

在游戏正式开始之前,做⼀些功能提醒

void WelcomeToGame()
{
	// 第一个界面
	SetPos(38, 14);
	wprintf(L"欢迎来到贪吃蛇小游戏\n");
	SetPos(40, 20);
	system("pause");	// 暂停
	system("cls");		// 清屏
	
	// 第二个界面
	SetPos(30, 14);
	wprintf(L"用 ↑. ↓. ←. → 来控制蛇的移动,按F3加速,F4减速\n");
	SetPos(30, 15);
	wprintf(L"加速能得到更高的分数\n");
	SetPos(43, 20);
	system("pause");
	system("cls");
}

创建地图

我们假设实现⼀个棋盘27行,58列的棋盘(行和列可以根据自己的情况修改),再围绕地图画出墙。

创建地图就是将墙打印出来,因为是宽字符打印,所以使用wprintf函数,打印格式串前使用L打印地图的关键是要算好坐标,才能在想要的位置打印墙体。

上:(0,0)到(56,0)
下:(0,26)到(56,26)
左:(0,1)到(0,25)
右:(56,1)到(56,25)

void GreateMap()
{
	// WALL的宏定义:#define WALL L'■'
	SetPos(0, 0);
	for (int i = 0; i <= 56; i += 2)	// 上
	{
		wprintf(L"%lc", WALL);
	}

	SetPos(0, 26);
	for (int i = 0; i <= 56; i += 2)	// 下
	{
		wprintf(L"%lc", WALL);
	}

	for (int i = 1; i <= 25; ++i)		// 左
	{
		SetPos(0, i);
		wprintf(L"%lc", WALL);
	}

	for (int i = 1; i <= 25; ++i)		// 右
	{
		SetPos(56, i);
		wprintf(L"%lc", WALL);
	}
}

初始化蛇

初始化状态,假设蛇的长度是5,蛇身的每个节点是●,在固定的一个坐标处,比如(24, 5)处开始出现蛇,连续5个节点。

注意:蛇的每个节点的x坐标必须是2个倍数,否则可能会出现蛇的一个节点有⼀半出现在墙体中,另外⼀般在墙外的现象,坐标不好对齐。

蛇最开始长度为5节,每节对应链表的⼀个节点,蛇身的每⼀个节点都有自己的坐标。创建5个节点,然后将每个节点存放在链表中进行管理。创建完蛇身后,将蛇的每⼀节打印在屏幕上。
• 蛇的初始位置从 (24,5) 开始。
再设置当前游戏的状态,蛇移动的速度,默认的方向,初始成绩,每个食物的分数。
• 游戏状态是:NORMAL
• 蛇的移动速度:200毫秒
• 蛇的默认方向:RIGHT
• 初始成绩:0
• 每个⻝物的分数:10

void InitSnake(pSnake snake)
{
	pSnakeNode cur = NULL;

	for (int i = 0; i < 5; ++i)
	{
		cur = (pSnakeNode)malloc(sizeof(SnakeNode));
		if (cur == NULL)
		{
			perror("InitSnake()::malloc fail");
			exit(1);
		}
		cur->next = NULL;
		cur->x = POS_X + i * 2;			// POS_X的宏定义:#define POS_X 24
		cur->y = POS_Y;					// POS_Y的宏定义:#define POS_Y 4

		if (snake->pHead == NULL)		// 蛇身节点连接
		{
			snake->pHead = cur;
		}
		else
		{
			cur->next = snake->pHead;
			snake->pHead = cur;
		}
	}

	cur = snake->pHead;					// 打印节点
	while (cur)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);			// BODY的宏定义:#define BODY L'●'
		cur = cur->next;
	}

	// 属性设置
	snake->dir = RIGHT;					// 默认向右
	snake->score = 0;
	snake->food_score = 10;
	snake->sleep_time = 200;			// 单位为毫秒
	snake->sta = NORMAL;
}

创建第一个食物

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

● 先随机生成食物的坐标
● x坐标必须是2的倍数
● 食物的坐标不能和蛇身每个节点的坐标重复
● 创建食物节点,打印食物

void GreateFood(pSnake snake)
{
	int x = 0;
	int y = 0;

again:
	x = (rand() % 27 + 1) * 2;
	y = rand() % 24 + 1;

	// 检查冲突
	pSnakeNode cur = snake->pHead;
	while (cur)
	{
		if (x == cur->x && y == cur->y)
		{
			goto again;
		}
		cur = cur->next;
	}

	// 创建食物节点
	pSnakeNode pfood = (pSnakeNode)malloc(sizeof(SnakeNode));
	if (pfood == NULL)
	{
		perror("GreateFood()::malloc faild");
		exit(1);
	}

	pfood->x = x;
	pfood->y = y;
	pfood->next = NULL;

	SetPos(x, y);
	wprintf(L"%lc", FOOD);			// FOOD的宏定义:#define FOOD L'★'

	snake->food = pfood;
}

游戏运行

这个模块完成游戏的运行任务:
1.打印帮助信息PrintHelpInfo
2.蛇身移动
—2.1.NextIsFood
—2.2.EatFood
—2.3.NoFood
—2.4.KillByWall
—2.5.KillBySelf

游戏运行期间,右侧打印帮助信息,提示玩家,坐标开始位置(64, 15),根据游戏状态检查游戏是否继续,如果是状态是OK,游戏继续,否则游戏结束。

如果游戏继续,就是检测按键情况,确定蛇下⼀步的方向,或者是否加速减速,是否暂停或者退出游戏。

需要的虚拟按键的罗列:
• 上:VK_UP
• 下:VK_DOWN
• 左:VK_LEFT
• 右:VK_RIGHT
• 空格:VK_SPACE
• ESC:VK_ESCAPE
• F3:VK_F3
• F4:VK_F4

确定了蛇的方向和速度,蛇就可以移动了。

void GameRun(pSnake snake) 
{
	// 打印帮助信息
	PrintHelpInfo();
	do
	{
		// 打印总分数和食物
		SetPos(64, 10);
		printf("总分数:%d", snake->score);
		SetPos(64, 11);
		printf("当前食物分数:%-2d", snake->food_score);
	
		// KEY_PRESS的宏定义:#define KEY_PRESS(VK) ((GetAsyncKeyState(VK)&0x1) ? 1 : 0)
		if (KEY_PRESS(VK_UP) && snake->dir != DOWN)
		{
			snake->dir = UP;
		}
		else if (KEY_PRESS(VK_DOWN) && snake->dir != UP)
		{
			snake->dir = DOWN;
		}
		else if (KEY_PRESS(VK_LEFT) && snake->dir != RIGHT)
		{
			snake->dir = LEFT;
		}
		else if (KEY_PRESS(VK_RIGHT) && snake->dir != LEFT)
		{
			snake->dir = RIGHT;
		}
		else if (KEY_PRESS(VK_SPACE))
		{
			// 暂停
			Pause();
		}
		else if (KEY_PRESS(VK_ESCAPE))
		{
			// 正常退出
			snake->sta = END_NORMAL;
		}
		else if (KEY_PRESS(VK_F3))
		{
			// 加速
			if (snake->sleepTime > 80)
			{
				snake->sleepTime -= 30;
				snake->food_score += 2;
			}
		}
		else if (KEY_PRESS(VK_F4))
		{
			// 减速
			if (snake->food_score > 2)
			{
				snake->sleepTime += 30;
				snake->food_score -= 2;
			}
		}

		SnakeMove(snake);	// 蛇移动一步

		Sleep(snake->sleepTime);

	} while (snake->sta == NORMAL);
}

打印帮助信息PrintHelpInfo和暂停

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退出游戏,按空格暂停游戏");
}
void Pause()
{
	while (true)
	{
		Sleep(200);
		if (KEY_PRESS(VK_SPACE))
		{
			break;
		}
	}
}

蛇身移动

先创建下⼀个节点,根据移动方向和蛇头的坐标,蛇移动到下⼀个位置的坐标。

确定了下⼀个位置后,看下⼀个位置是否是食物(NextIsFood),是食物就做吃食物处理(EatFood),如果不是⻝物则做前进⼀步的处理(NoFood)。

蛇身移动后,判断此次移动是否会造成撞墙(KillByWall)或者撞上自己蛇身(KillBySelf),从而影响游戏的状态。

void SnakeMove(pSnake snake)
{
	// 创建一个节点,表示下一个要走的节点
	pSnakeNode NextNode = (pSnakeNode)malloc(sizeof(SnakeNode));
	if (NextNode == NULL)
	{
		perror("SnakeMove()::malloc faild");
		exit(1);
	}

	switch (snake->dir)
	{
	case UP:
		NextNode->x = snake->pHead->x;
		NextNode->y = snake->pHead->y - 1;
		break;
	case DOWN:
		NextNode->x = snake->pHead->x;
		NextNode->y = snake->pHead->y + 1;
		break;
	case LEFT:
		NextNode->x = snake->pHead->x - 2;
		NextNode->y = snake->pHead->y;
		break;
	case RIGHT:
		NextNode->x = snake->pHead->x + 2;
		NextNode->y = snake->pHead->y;
		break;
	}

	// 检测下一个是否是食物
	if (NextIsFood(snake, NextNode))
	{
		EatFood(snake, NextNode);
	}
	else
	{
		NoFood(snake, NextNode);
	}

	// 检测蛇是否撞墙
	KillByWall(snake);

	// 检测蛇是否撞到自己
	KillBySelf(snake);
}
NextIsFood
bool NextIsFood(pSnake snake, pSnakeNode move)
{
	return (snake->pfood->x == move->x && snake->pfood->y == move->y);
}
EatFood
void EatFood(pSnake snake, pSnakeNode NextNode)
{
	 // 头插法
	snake->pfood->next = snake->pHead;
	snake->pHead = snake->pfood;

	// 释放下一个位置的节点
	free(NextNode);
	NextNode = NULL;
	pSnakeNode cur = snake->pHead;
	while (cur)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	snake->score += snake->food_score;

	// 重新创建食物
	GreateFood(snake);
}
NoFood
void NoFood(pSnake snake, pSnakeNode NextNode)
{
	// 头插法
	NextNode->next = snake->pHead;
	snake->pHead = NextNode;

	pSnakeNode cur = snake->pHead;
	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;
}
KillByWall

判断蛇头的坐标是否和墙的坐标冲突

// 检测蛇是否撞墙
void KillByWall(pSnake snake)
{
	if (snake->pHead->x == 0 || snake->pHead->x == 56 ||
		snake->pHead->y == 0 || snake->pHead->y == 26)
	{
		snake->sta = KILL_BY_WALL;
	}
}
KillBySelf

判断蛇头的坐标是否和蛇身体的坐标冲突

// 检测蛇是否撞到自己
void KillBySelf(pSnake snake)
{
	pSnakeNode cur = snake->pHead->next;
	while (cur)
	{
		if (snake->pHead->x == cur->x && snake->pHead->y == cur->y)
		{
			snake->sta = KILL_BY_SELF;
			break;
		}
		cur = cur->next;
	}
}

游戏结束

游戏状态不再是NORMAL(游戏继续)的时候,要告知游戏结束的原因,并且释放蛇身节点。

void GameEnd(pSnake snake)
{
	SetPos(24, 12);
	switch (snake->sta)
	{
	case END_NORMAL:
		printf("您主动结束游戏\n");
		break;
	case KILL_BY_WALL:
		printf("您撞到墙上,游戏结束\n");
		break;
	case KILL_BY_SELF:
		printf("您撞到了自己,游戏结束\n");
		break;
	}

	// 释放蛇身链表
	pSnakeNode cur = snake->pHead;
	while (cur)
	{
		pSnakeNode del = cur;
		cur = cur->next;
		free(del);
	}
}

四、游戏源代码

test.c

在 test.c 中:

#include "Snake.h"

void test()
{
	srand((unsigned int)time(NULL));
	char input = 0;

	do
	{
		system("cls");

		// 创建贪吃蛇
		Snake snake = { 0 };		
		
		// 初始化游戏
		GameStart(&snake);

		// 游戏运行
		GameRun(&snake);

		// 游戏结束
		GameEnd(&snake);

		SetPos(20, 15);
		printf("再来一局吗?(Y/N):");
		input = getchar();
		while (getchar() != '\n');				// 清理多余缓冲区字符

	} while (input == 'Y' || input == 'y');
	SetPos(0, 27);
}

int main()
{
	setlocale(LC_ALL, "");			// 设置到本地模式
	test();

	return 0;
}

Snake.h

在 Snake.h 中:

#pragma once

#define _CRT_SECURE_NO_WARNINGS 1

#include <locale.h>
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <Windows.h>
#include <time.h>

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

#define POS_X 24
#define POS_Y 4

#define WALL L'■'
#define BODY L'●'
#define FOOD L'★'

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

typedef enum GAME_STATUS		// 游戏状态
{
	NORMAL = 1,					// 游戏正常
	KILL_BY_WALL,				// 撞墙
	KILL_BY_SELF,				// 吃到蛇身
	END_NORMAL,					// 正常退出
} GAME_STATUS;

// 蛇身节点
typedef struct SnakeNode
{
	// 坐标位置
	int x;
	int y;
	// 下一个节点
	struct SnakeNode* next;
} SnakeNode, * pSnakeNode;

// 将蛇各个信息封装成一个对象,方便维护
typedef struct Snake
{
	pSnakeNode pHead;		// 蛇头位置
	pSnakeNode pfood;		// 食物位置
	DIRECTION dir;			// 蛇移动方向
	GAME_STATUS sta;		// 游戏状态
	int food_score;			// 食物分数
	int score;				// 总得分
	int sleepTime;			// 间隔时间
} Snake, * pSnake;

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

// 游戏开始
void GameStart(pSnake snake);

// 欢迎界面
void WelcomeToGame();

// 创建地图
void GreateMap();

// 初始化贪吃蛇
void InitSnake(pSnake snake);

// 创建食物
void GreateFood(pSnake snake);

// 游戏运行
void GameRun(pSnake snake);

// 蛇移动一步
void SnakeMove(pSnake snake);

// 判断下一个坐标是否是食物
bool NextIsFood(pSnake snake, pSnakeNode move);

void EatFood(pSnake snake, pSnakeNode NextNode);

void NoFood(pSnake snake, pSnakeNode NextNode);

// 检测蛇是否撞墙
void KillByWall(pSnake snake);

// 检测蛇是否撞到自己
void KillBySelf(pSnake snake);

void GameEnd(pSnake snake);

Snake.c

在 Snake.c 中:

#include "Snake.h"

void SetPos(int x, int y)
{
	HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);
	COORD pos = { x, y };
	SetConsoleCursorPosition(houtput, pos);
}

void WelcomeToGame()
{
	SetPos(38, 14);
	wprintf(L"欢迎来到贪吃蛇小游戏\n");
	SetPos(40, 20);
	system("pause");
	system("cls");

	SetPos(30, 14);
	wprintf(L"用 ↑. ↓. ←. → 来控制蛇的移动,按F3加速,F4减速\n");
	SetPos(30, 15);
	wprintf(L"加速能得到更高的分数\n");
	SetPos(43, 20);
	system("pause");
	system("cls");
}

void GreateMap()
{
	SetPos(0, 0);
	for (int i = 0; i <= 56; i += 2)	// 上
	{
		wprintf(L"%lc", WALL);
	}

	SetPos(0, 26);
	for (int i = 0; i <= 56; i += 2)	// 下
	{
		wprintf(L"%lc", WALL);
	}

	for (int i = 1; i <= 25; ++i)	// 左
	{
		SetPos(0, i);
		wprintf(L"%lc", WALL);
	}

	for (int i = 1; i <= 25; ++i)	// 右
	{
		SetPos(56, i);
		wprintf(L"%lc", WALL);
	}

}

void InitSnake(pSnake snake)
{
	pSnakeNode cur = NULL;

	for (int i = 0; i < 5; ++i)
	{
		cur = (pSnakeNode)malloc(sizeof(SnakeNode));
		if (cur == NULL)
		{
			perror("InitSnake()::malloc fail");
			exit(1);
		}
		cur->next = NULL;
		cur->x = POS_X + i * 2;
		cur->y = POS_Y;

		if (snake->pHead == NULL)		// 蛇身节点连接
		{ 
			snake->pHead = cur;
		}
		else
		{
			cur->next = snake->pHead;
			snake->pHead = cur;
		}
	}

	cur = snake->pHead;					// 打印节点
	while (cur)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}

	// 属性设置
	snake->dir = RIGHT;					// 默认向右
	snake->score = 0;					
	snake->food_score = 10;
	snake->sleepTime = 200;				// 单位为毫秒
	snake->sta = NORMAL;	
}

void GreateFood(pSnake snake)
{
	int x = 0;
	int y = 0;

again:
	x = (rand() % 27 + 1) * 2;
	y = rand() % 24 + 1;

	// 检查冲突
	pSnakeNode cur = snake->pHead;
	while (cur)
	{
		if (x == cur->x && y == cur->y)
		{
			goto again;
		}
		cur = cur->next;
	}

	// 创建食物节点
	pSnakeNode pfood = (pSnakeNode)malloc(sizeof(SnakeNode));
	if (pfood == NULL)
	{
		perror("GreateFood()::malloc faild");
		exit(1);
	}

	pfood->x = x;
	pfood->y = y;
	pfood->next = NULL;

	SetPos(x, y);
	wprintf(L"%lc", FOOD);

	snake->pfood = pfood;
}

void GameStart(pSnake snake)
{
	//	0.设置窗口和光标
	system("mode con cols=100 lines=30");
	system("title 贪吃蛇");
	HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);
	CONSOLE_CURSOR_INFO CursorInfo;
	GetConsoleCursorInfo(houtput, &CursorInfo);
	CursorInfo.bVisible = false;
	SetConsoleCursorInfo(houtput, &CursorInfo);
	//	1.打印界面和功能介绍
	WelcomeToGame();
	//	2.绘制地图
	GreateMap();
	//	3.创建蛇
	InitSnake(snake);
	//	4.创建食物
	GreateFood(snake);
}

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退出游戏,按空格暂停游戏");
}


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

bool NextIsFood(pSnake snake, pSnakeNode move)
{
	return (snake->pfood->x == move->x && snake->pfood->y == move->y);
}

void EatFood(pSnake snake, pSnakeNode NextNode)
{
	 // 头插法
	snake->pfood->next = snake->pHead;
	snake->pHead = snake->pfood;

	// 释放下一个位置的节点
	free(NextNode);
	NextNode = NULL;
	pSnakeNode cur = snake->pHead;
	while (cur)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	snake->score += snake->food_score;

	// 重新创建食物
	GreateFood(snake);
}

void NoFood(pSnake snake, pSnakeNode NextNode)
{
	// 头插法
	NextNode->next = snake->pHead;
	snake->pHead = NextNode;

	pSnakeNode cur = snake->pHead;
	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 snake)
{
	if (snake->pHead->x == 0 || snake->pHead->x == 56 ||
		snake->pHead->y == 0 || snake->pHead->y == 26)
	{
		snake->sta = KILL_BY_WALL;
	}
}

// 检测蛇是否撞到自己
void KillBySelf(pSnake snake)
{
	pSnakeNode cur = snake->pHead->next;
	while (cur)
	{
		if (snake->pHead->x == cur->x && snake->pHead->y == cur->y)
		{
			snake->sta = KILL_BY_SELF;
			break;
		}
		cur = cur->next;
	}
}

void SnakeMove(pSnake snake)
{
	// 创建一个节点,表示下一个要走的节点
	pSnakeNode NextNode = (pSnakeNode)malloc(sizeof(SnakeNode));
	if (NextNode == NULL)
	{
		perror("SnakeMove()::malloc faild");
		exit(1);
	}

	switch (snake->dir)
	{
	case UP:
		NextNode->x = snake->pHead->x;
		NextNode->y = snake->pHead->y - 1;
		break;
	case DOWN:
		NextNode->x = snake->pHead->x;
		NextNode->y = snake->pHead->y + 1;
		break;
	case LEFT:
		NextNode->x = snake->pHead->x - 2;
		NextNode->y = snake->pHead->y;
		break;
	case RIGHT:
		NextNode->x = snake->pHead->x + 2;
		NextNode->y = snake->pHead->y;
		break;
	}

	// 检测下一个是否是食物
	if (NextIsFood(snake, NextNode))
	{
		EatFood(snake, NextNode);
	}
	else
	{
		NoFood(snake, NextNode);
	}

	// 检测蛇是否撞墙
	KillByWall(snake);

	// 检测蛇是否撞到自己
	KillBySelf(snake);
}

void GameRun(pSnake snake) 
{
	PrintHelpInfo();
	do
	{
		// 打印总分数和食物
		SetPos(64, 10);
		printf("总分数:%d", snake->score);
		SetPos(64, 11);
		printf("当前食物分数:%-2d", snake->food_score);

		if (KEY_PRESS(VK_UP) && snake->dir != DOWN)
		{
			snake->dir = UP;
		}
		else if (KEY_PRESS(VK_DOWN) && snake->dir != UP)
		{
			snake->dir = DOWN;
		}
		else if (KEY_PRESS(VK_LEFT) && snake->dir != RIGHT)
		{
			snake->dir = LEFT;
		}
		else if (KEY_PRESS(VK_RIGHT) && snake->dir != LEFT)
		{
			snake->dir = RIGHT;
		}
		else if (KEY_PRESS(VK_SPACE))
		{
			// 暂停
			Pause();
		}
		else if (KEY_PRESS(VK_ESCAPE))
		{
			// 正常退出
			snake->sta = END_NORMAL;
		}
		else if (KEY_PRESS(VK_F3))
		{
			// 加速
			if (snake->sleepTime > 80)
			{
				snake->sleepTime -= 30;
				snake->food_score += 2;
			}
		}
		else if (KEY_PRESS(VK_F4))
		{
			// 减速
			if (snake->food_score > 2)
			{
				snake->sleepTime += 30;
				snake->food_score -= 2;
			}
		}

		SnakeMove(snake);	// 蛇移动一步

		Sleep(snake->sleepTime);

	} while (snake->sta == NORMAL);
}

void GameEnd(pSnake snake)
{
	SetPos(24, 12);
	switch (snake->sta)
	{
	case END_NORMAL:
		printf("您主动结束游戏\n");
		break;
	case KILL_BY_WALL:
		printf("您撞到墙上,游戏结束\n");
		break;
	case KILL_BY_SELF:
		printf("您撞到了自己,游戏结束\n");
		break;
	}

	// 释放蛇身链表
	pSnakeNode cur = snake->pHead;
	while (cur)
	{
		pSnakeNode del = cur;
		cur = cur->next;
		free(del);
	}
}
  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值