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

目录

一、预备知识

1.1 Win32 API介绍

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

1.2 修改控制台相关属性

我们可以使用cmd命令来设置控制台窗口的长宽:设置控制台窗口的大小,30行,100列.

	system("mode con cols=100 lines=30");

也可以通过命令设置控制台窗口的名字:贪吃蛇

	system("title 贪吃蛇");

效果展示:

C语言-------实现贪吃蛇小游戏_第1张图片

1.3 控制台上的坐标COORD

COORD是WindowsAPI中定义的⼀个结构体,表示一个字符在控制台屏幕幕缓冲区上的坐标,坐标系(0,0)的原点位于缓冲区的顶部左侧单元格。控制台上的坐标如图展示,横向为x轴,从左到右依次增长;纵向为y轴,从上到下依次增长.

C语言-------实现贪吃蛇小游戏_第2张图片
COORD类型的声明:

typedef struct _COORD {
 SHORT X;
 SHORT Y;
} COORD, *PCOORD;

然后对坐标进行赋值:

OORD pos = { 10, 15 };

1.4 GetStdHandle

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

示例:

	//获得标准输出设备的句柄
	HANDLE houtput = NULL;
	houtput = GetStdHandle(STD_OUTPUT_HANDLE);

1.5 GetConsoleCursorInfo

GetConsoleCursorInfo是用来检索有关指定控制台屏幕缓冲区的光标大小和可见性的信息
示例:

	//获取标准输出的句柄
	HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);
	//定义一个光标信息的结构体
	CONSOLE_CURSOR_INFO cursor_info = { 0 };
	//获取和houtput句柄相关的控制台上的光标信息,存放在cursor_info中
	GetConsoleCursorInfo(houtput, &cursor_info);

GetConsoleCursorInfo

这个结构体,包含有关控制台光标的信息.
它含有两个功能:dwSize和bVisible,它们分别是什么意思呢?

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

bVisible: 表示游标的可见性。如果光标可见,则此成员为TRUE。反之,为FALSE。

在这里,我们要使用隐藏光标这一功能.

	//隐藏控制台光标
	cursor_info.bVisible = false;

1.6 SetConsoleCursorInfo

表示设置指定控制台屏幕缓冲区的光标的大小和可见性。

	//设置控制台上的光标信息
	SetConsoleCursorInfo(houtput, &cursor_info);

1.7 SetConsoleCursorPosition

表示设置指定控制台屏幕缓冲区中的光标位置,我们想要将设置的坐标信息放在COORD类型的pos中,然后调用SetConsoleCursorPosition函数将光标位置设置到指定的位置。因此,我们定义一个函数-------SetPos,表示设置光标位置的函数.

void SetPos(short x, short y)
{
	//获得标准输出设备的句柄
	HANDLE houtput = NULL;
	houtput = GetStdHandle(STD_OUTPUT_HANDLE);

	//定位光标的位置 
	COORD pos = { x,y };
	SetConsoleCursorPosition(houtput, pos);
}

1.8 GetAsyncKeyState

表示获取按键情况.函数的原型为:

SHORT GetAsyncKeyState(
 int vKey
);

我们将键盘上每个键的虚拟键值传递给函数,函数通过返回值来分辨按键的状态。
判断依据:GetAsyncKeyState的返回值是short类型,在上⼀次调GetAsyncKeyState函数后,如果返回的16位的short数据中,最高位是1,说明按键的状态是按下,如果最高是0,说明按键的状态是抬起;如果最低位被置为1则说明,该按键被按过,否则为0。
因此,要检测一个键是否被按过,就可以检测GetAsyncKeyState返回值的最低值是否为1.

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

二、游戏设计

2.1 地图

我们假设打印墙体使用宽字符:□,打印蛇身使用宽字符●,打印食物使用宽字符★。
注: 宽字符和普通的字符是有区别的,一个普通的字符表示占用一个字节的大小,而一个宽字符表示占用两个字节的大小。

这里可能就会有人产生疑惑-----到底什么是宽字符呢?下面就让我给大家详细介绍一下宽字符的相关知识以及打印。

2.1.1 本地化

提供的函数用于控制C标准库中对于不同的地区会产生不⼀样行为的部分。

在标准中,依赖地区的部分有以下几项:
• 数字量的格式
• 货币量的格式
• 字符集
• 日期和时间的表示形式

2.1.2 setlocale

函数原型为:

char* setlocale (int category, const char* locale);

setlocale函数用于修改当前地区,可以针对⼀个类项修改,也可以针对所有类项。

setlocale的第⼀个参数可以是前面说明的类项中的⼀个,那么每次只会影响⼀个类项,如果第⼀个参数是LC_ALL,就会影响所有的类项。

C标准给第⼆个参数仅定义了2种可能取值:“C”(正常模式)和" "(本地模式)。

在此处,我们要用到本地模式:

	setlocale(LC_ALL, "");

2.1.3 宽字符打印

宽字符的字面量必须加上前缀“L”,否则C语言会把字面量当作窄字符类型处理。前缀“L”在单引号前面,表示宽字符,对应wprintf() 的占位符为%lc ;在双引号前面,表示宽字符串,对应wprintf() 的占位符为%ls 。
下面我们用代码来感受一下宽字符的使用及打印出来的效果

int main()
{
	//设置本地化
	setlocale(LC_ALL, "");

	//打印字符
	char a = 'a';
	char b = 'b';
	printf("%c%c\n", a, b);

	wchar_t wc1 = L'比';
	wchar_t wc2 = L'特';

	wprintf(L"%lc\n", wc1);
	wprintf(L"%lc\n", wc2);
	return 0;
}

C语言-------实现贪吃蛇小游戏_第3张图片

我们可以发现,上面说过宽字符占两个字节的大小和普通字符占一个字节的大小的说法完全没有问题的。

2.1.4 地图坐标

假设,我们选择的坐标大小为27行和58列。
C语言-------实现贪吃蛇小游戏_第4张图片

2.2 蛇身和食物

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

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

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

2.3 相关数据结构设计

我们使用链表来存储蛇的信息,那么蛇的每⼀节其实就是链表的每个节点。每个节点只要记录好蛇身节点在地图上的坐标就行了。
蛇身的结构如下:

typedef struct SnakeNode
{
	//坐标
	int x;
	int y;
	//指向下一个节点的指针
	struct SnakeNode* next;
}SnakeNode,*pSnakeNode;

如果我们想管理整条贪吃蛇,我们需要再封装一个Snake的结构来维护整条贪吃蛇:

//贪吃蛇
typedef struct Snake
{
	pSnakeNode _pSnake;//指向蛇头的指针
	pSnakeNode _pFood;//指向食物的指针
	enum DIRECTION _dir;//蛇的方向
	enum GAME_STATUS _status;//蛇的状态
	int _food_weight;//一个食物的分数
	int _score;//总成绩
	int _sleep_time;//休息时间  时间越短,速度越快;时间越长,速度越慢
}Snake,*pSnake;

关于蛇的方向,可以使用枚举

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

关于蛇的状态,也同样可以使用枚举:

//蛇的状态
enum GAME_STATUS
{
	OK,//正常
	KILL_BY_WALL,//撞墙
	KILL_BY_SLEF,//撞到自己
	END_NORMAL//正常退出
};

三、游戏逻辑思路分析

3.1 整体思路

• 游戏开始(GameStart)负责完成游戏的初始化
• 游戏运行(GameRun)负责完成游戏运行逻辑的实现
• 游戏结束(GameEnd)负责完成游戏结束的说明,实现资源释放

• 总体思路:

void test()
{
	int ch = 0;
	do
	{
		//创建贪吃蛇
		Snake snake = { 0 };
		//初始化游戏
		//1.打印欢迎界面
		//2.功能介绍
		//3.绘制地图
		//4.创建蛇
		//5.创建食物
		//6.设置游戏的相关信息
		GameStart(&snake);
		//运行游戏
		GameRun(&snake);
		//结束游戏  -  善后工作
		GameEnd(&snake);
		SetPos(20, 15);
		system("pause");
		SetPos(20, 15);
		printf("再来一局吗?(Y/N):");
		ch = getchar();
		while(getchar() != '\n');
	} while (ch == 'Y' || ch == 'y');
	SetPos(0, 27);
}

int main()
{
	//设置适配本地化环境
	setlocale(LC_ALL, "");
	srand((unsigned int)time(NULL));
	test();
	return 0;
}

3.2 游戏开始

需完成的操作:

1.控制台窗口大小的设置

2.控制台窗口名字的设置

3.⿏标光标的隐藏

4.打印欢迎界面

5.创建地图

6.初始化蛇身

7.创建⻝物

3.2.1 打印欢迎界面

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

void WelcomeToGame()
{
	SetPos(40, 14);
	wprintf(L"欢迎来到贪吃蛇小游戏\n");
	SetPos(42, 18);
	system("pause");
	system("cls");
	SetPos(33, 13);
	wprintf(L"用↑.↓.←.→来控制蛇的运动,按F3加速,按F4减速\n");
	SetPos(42, 16);
	wprintf(L"加速能够得到更多的分数\n");
	SetPos(42, 25);
	system("pause");
	system("cls");
}

3.2.2 创建地图

易错点:坐标的计算
上:(0,0)到(56,0)
下:(0,26)到(56,26)
左:(0,1)到(0,25)
右:(56,1)到(56,25)

void CreatMap()
{
	//上
	int i = 0;
	for (i = 0; i < 29; i++)
	{
		wprintf(L"%lc", WALL);
	}

	//下
	SetPos(0, 26);
	for (i = 0; i < 29; i++)
	{
		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);
	}
}

3.2.3 初始化蛇身

假设蛇的初始位置从(24,5)开始
游戏状态是:OK
蛇的移动速度:200毫秒
蛇的默认⽅向:RIGHT
初始成绩:0
每个⻝物的分数:10

void InitSnake(pSnake ps)
{
	int i = 0;
	pSnakeNode cur = NULL;
	for (i = 0; i < 5; i++)
	{
		cur = (pSnakeNode)malloc(sizeof(SnakeNode));
		if (cur == NULL)
		{
			perror("InitSnke()::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)
	{
		SetPos(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;
}

3.2.4 创建食物

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

void CreatFood(pSnake ps)
{
	int x = 0;
	int y = 0;
	//生成x是2的倍数
	//x:2~54
	//y:1~25
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 && y == cur->y)
		{
			goto again;
		}
		cur = cur->next;
	}
		//创建食物的节点
	pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode));
	if (pFood == NULL)
	{
		perror("CreatFood()::malloc()");
		return;
	}
	pFood->x = x;
	pFood->y = y;
	pFood->next = NULL;
	//定位位置
	SetPos(x, y);
	wprintf(L"%lc",FOOD );
	ps->_pFood = pFood;
}

3.3 游戏运行

需完成的操作:

1.打印帮助信息

2.检测按键

3.蛇的移动
1)下一坐标处是否是是食物——是,吃掉食物;否,不吃食物。
2)蛇是否撞墙死
3)蛇是否撞到自身而死

3.3.1 打印帮助信息

//打印帮助信息
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退出游戏,按空格暂停游戏.");
	SetPos(64, 19);
	wprintf(L"%ls", L"晚风制作.");

}

3.3.2 检测按键

封装⼀个宏KEY_PRESS,负责检测按键状态

//定义一个宏用于检测此按键是否被按下
#define KEY_PRESS(VK) ((GetAsyncKeyState(VK)&0x1)?1:0)

3.3.3 蛇的移动

//检测下一个坐标处是否是食物
int NextIsFood(pSnakeNode pn, pSnake ps)
{
	if (ps->_pFood->x == pn->x && ps->_pFood->y == pn->y)
	{
		return 1;
	}
	else
		return 0;
}

//下一个位置是食物,就吃掉食物
void EatFood(pSnakeNode pn, pSnake ps)
{
	//头插法
	ps->_pFood->next = ps->_pSnake;
	ps->_pSnake = ps->_pFood;

	//释放下一个位置的节点
	free(pn);
	pn = NULL;
	//再次打印蛇
	pSnakeNode cur = ps->_pSnake;
	while (cur)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	ps->_score += ps->_food_weight;

	//重新创建食物
	CreatFood(ps);
}

//下一个位置不是食物
void NoFood(pSnakeNode pn, pSnake ps)
{
	//头插法
	pn->next = ps->_pSnake;
	ps->_pSnake = pn;

	pSnakeNode cur = ps->_pSnake;
	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 ps)
{
	if (ps->_pSnake->x == 0 || ps->_pSnake->x == 56 || ps->_pSnake->y == 0 || ps->_pSnake->y == 26)
	{
		//撞墙
		ps->_status = KILL_BY_WALL;
	}
}

//检测蛇是否撞到自己
void KillBySlef(pSnake ps)
{
	pSnakeNode cur = ps->_pSnake->next;
	while (cur)
	{
		if (cur->x == ps->_pSnake->x && cur->y == ps->_pSnake->y)
		{
			ps->_status = KILL_BY_SLEF;
			break;
		}
		cur = cur->next;
	}
}
//蛇走一步的过程
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);
	//检测蛇是否撞到自己
	KillBySlef(ps);
}

3.4 游戏结束以及善后处理

当游戏不再继续进行时,要告知游戏结束的原因,并且释放蛇身的节点。

//结束游戏  -  善后工作
void GameEnd(pSnake ps)
{
	SetPos(20, 12);
	switch (ps->_status)
	{
	case END_NORMAL:
		printf("你主动结束游戏!\n");
		break;
	case KILL_BY_WALL:
		printf("你撞到了墙上,游戏结束!\n");
		break;
	case KILL_BY_SLEF:
		printf("你撞到了自己,游戏结束!\n");
		break;
	}

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

四、游戏代码展示

1.snake.h

在这项文件中,主要涉及游戏的相关头文件以及类型和函数的声明操作.

#pragma once

#include
#include
#include
#include
#include


#define POS_X 24
#define POS_Y 5
#define WALL L'□'
#define BODY L'●'
#define FOOD L'★'
//类型的声明

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

//蛇的状态
enum GAME_STATUS
{
	OK,//正常
	KILL_BY_WALL,//撞墙
	KILL_BY_SLEF,//撞到自己
	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 SetPos(short x, short y);

//游戏的初始化
void GameStart(pSnake ps);

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

//创建地图
void CreatMap();

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

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

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

//蛇走一步的过程
void SnakeMove(pSnake ps);

//检测下一个坐标处是否是食物
int NextIsFood(pSnakeNode pn,pSnake ps);

//下一个位置是食物,就吃掉食物
void EatFood(pSnakeNode pn, pSnake ps);

//下一个位置不是食物
void NoFood(pSnakeNode pn, pSnake ps);

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

//检测蛇是否撞到自己
void KillBySlef(pSnake ps);

//结束游戏  -  善后工作
void GameEnd(pSnake ps);

2.snake.c

在这项文件中,主要对上述声明的函数进行实现的操作.

#define _CRT_SECURE_NO_WARNINGS 1

#include"snake.h"

void SetPos(short x, short y)
{
	//获得标准输出设备的句柄
	HANDLE houtput = NULL;
	houtput = GetStdHandle(STD_OUTPUT_HANDLE);

	//定位光标的位置 
	COORD pos = { x,y };
	SetConsoleCursorPosition(houtput, pos);
}

void WelcomeToGame()
{
	SetPos(40, 14);
	wprintf(L"欢迎来到贪吃蛇小游戏\n");
	SetPos(42, 18);
	system("pause");
	system("cls");
	SetPos(33, 13);
	wprintf(L"用↑.↓.←.→来控制蛇的运动,按F3加速,按F4减速\n");
	SetPos(42, 16);
	wprintf(L"加速能够得到更多的分数\n");
	SetPos(42, 25);
	system("pause");
	system("cls");
}

void CreatMap()
{
	//上
	int i = 0;
	for (i = 0; i < 29; i++)
	{
		wprintf(L"%lc", WALL);
	}

	//下
	SetPos(0, 26);
	for (i = 0; i < 29; i++)
	{
		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 InitSnake(pSnake ps)
{
	int i = 0;
	pSnakeNode cur = NULL;
	for (i = 0; i < 5; i++)
	{
		cur = (pSnakeNode)malloc(sizeof(SnakeNode));
		if (cur == NULL)
		{
			perror("InitSnke()::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)
	{
		SetPos(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 CreatFood(pSnake ps)
{
	int x = 0;
	int y = 0;
	//生成x是2的倍数
	//x:2~54
	//y:1~25
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 && y == cur->y)
		{
			goto again;
		}
		cur = cur->next;
	}

	//创建食物的节点
	pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode));
	if (pFood == NULL)
	{
		perror("CreatFood()::malloc()");
		return;
	}
	pFood->x = x;
	pFood->y = y;
	pFood->next = NULL;
	//定位位置
	SetPos(x, y);
	wprintf(L"%lc",FOOD );
	ps->_pFood = pFood;
}

void GameStart(pSnake ps)
{
	//0.先设置窗口的大小光标隐藏
	system("mode con cols=100 lines=30");
	system("title 贪吃蛇");
	HANDLE 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);

	//1.打印欢迎界面
	//2.功能介绍
	WelcomeToGame();
	//3.绘制地图
	CreatMap();
	//4.初始化蛇身
	InitSnake(ps);   
	//5.创建食物
	CreatFood(ps);
}

//打印帮助信息
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退出游戏,按空格暂停游戏.");
	SetPos(64, 19);
	wprintf(L"%ls", L"晚风制作.");

}

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

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

//检测下一个坐标处是否是食物
int NextIsFood(pSnakeNode pn, pSnake ps)
{
	if (ps->_pFood->x == pn->x && ps->_pFood->y == pn->y)
	{
		return 1;
	}
	else
		return 0;
}

//下一个位置是食物,就吃掉食物
void EatFood(pSnakeNode pn, pSnake ps)
{
	//头插法
	ps->_pFood->next = ps->_pSnake;
	ps->_pSnake = ps->_pFood;

	//释放下一个位置的节点
	free(pn);
	pn = NULL;
	//再次打印蛇
	pSnakeNode cur = ps->_pSnake;
	while (cur)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	ps->_score += ps->_food_weight;

	//重新创建食物
	CreatFood(ps);
}

//下一个位置不是食物
void NoFood(pSnakeNode pn, pSnake ps)
{
	//头插法
	pn->next = ps->_pSnake;
	ps->_pSnake = pn;

	pSnakeNode cur = ps->_pSnake;
	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 ps)
{
	if (ps->_pSnake->x == 0 || ps->_pSnake->x == 56 || ps->_pSnake->y == 0 || ps->_pSnake->y == 26)
	{
		//撞墙
		ps->_status = KILL_BY_WALL;
	}
}

//检测蛇是否撞到自己
void KillBySlef(pSnake ps)
{
	pSnakeNode cur = ps->_pSnake->next;
	while (cur)
	{
		if (cur->x == ps->_pSnake->x && cur->y == ps->_pSnake->y)
		{
			ps->_status = KILL_BY_SLEF;
			break;
		}
		cur = cur->next;
	}
}



//蛇走一步的过程
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);
	//检测蛇是否撞到自己
	KillBySlef(ps);
}




	//游戏运行的逻辑
void GameRun(pSnake ps)
{
	//打印帮助信息
	PrintHelpInfo();
	//检测按键
	do
	{
		//打印总分数和食物的分值
		SetPos(64, 10);
		printf("总分数:%d\n", ps->_score);
		SetPos(64, 11);
		printf("当前食物的分数:%2d\n", ps->_food_weight);

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

		//蛇走一步的过程
		SnakeMove(ps);
		Sleep(ps->_sleep_time);

	} while (ps->_status == OK);
}
//结束游戏  -  善后工作
void GameEnd(pSnake ps)
{
	SetPos(20, 12);
	switch (ps->_status)
	{
	case END_NORMAL:
		printf("你主动结束游戏!\n");
		break;
	case KILL_BY_WALL:
		printf("你撞到了墙上,游戏结束!\n");
		break;
	case KILL_BY_SLEF:
		printf("你撞到了自己,游戏结束!\n");
		break;
	}

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

3.test.c

这项文件则完成函数的测试逻辑操作.

#define _CRT_SECURE_NO_WARNINGS 1

#include "snake.h"

//完成游戏的测试逻辑
void test()
{
	int ch = 0;
	do
	{
		//创建贪吃蛇
		Snake snake = { 0 };
		//初始化游戏
		//1.打印欢迎界面
		//2.功能介绍
		//3.绘制地图
		//4.创建蛇
		//5.创建食物
		//6.设置游戏的相关信息
		GameStart(&snake);
		//运行游戏
		GameRun(&snake);
		//结束游戏  -  善后工作
		GameEnd(&snake);
		SetPos(20, 15);
		system("pause");
		SetPos(20, 15);
		printf("再来一局吗?(Y/N):");
		ch = getchar();
		while(getchar() != '\n');
	} while (ch == 'Y' || ch == 'y');
	SetPos(0, 27);
}

int main()
{
	//设置适配本地化环境
	setlocale(LC_ALL, "");
	srand((unsigned int)time(NULL));
	test();
	return 0;
}

今天的分享就到这里啦,如果感觉内容不错,记得一键三连噢。创作不易,感谢大家的支持,我们下次再见!

你可能感兴趣的:(c语言,数据结构)