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 贪吃蛇");

效果展示:

在这里插入图片描述

1.3 控制台上的坐标COORD

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

在这里插入图片描述
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 <locale.h> 本地化

<locale.h>提供的函数用于控制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;
}

在这里插入图片描述

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

2.1.4 地图坐标

假设,我们选择的坐标大小为27行和58列。
在这里插入图片描述

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<locale.h>
#include<stdio.h>
#include<windows.h>
#include<stdbool.h>
#include<time.h>


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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

如何在latex中使用第三方字体

最近想到一个问题&#xff1a;如何在 LaTeX \LaTeX LATE​X中使用第三方字体。 这个问题其实挺基础的&#xff0c;但是因为小白的 LaTeX \LaTeX LATE​X水平&#xff0c;应该说五六年了&#xff0c;毫无进步。 所以确实还是需要解决一下这个基础的问题。 小白最近使用的是TeXs…

Python | Leetcode Python题解之第65题有效数字

题目&#xff1a; 题解&#xff1a; from enum import Enumclass Solution:def isNumber(self, s: str) -> bool:State Enum("State", ["STATE_INITIAL","STATE_INT_SIGN","STATE_INTEGER","STATE_POINT","STATE_…

基于 Spring Boot 博客系统开发(五)

基于 Spring Boot 博客系统开发&#xff08;五&#xff09; 本系统是简易的个人博客系统开发&#xff0c;为了更加熟练地掌握 SprIng Boot 框架及相关技术的使用。&#x1f33f;&#x1f33f;&#x1f33f; 基于 Spring Boot 博客系统开发&#xff08;四&#xff09;&#x1f…

408数据结构-二叉树的概念、性质与存储结构 自学知识点整理

前置知识&#xff1a;树的基本概念与性质 二叉树的定义 二叉树是一种特殊的树形结构&#xff0c;其特点是每个结点至多只有两棵子树&#xff08;即二叉树中不存在度大于 2 2 2的结点&#xff09;&#xff0c;并且二叉树是有序树&#xff0c;左右子树不能互换。 与树类似&#…

fastdfs安装

fastdfs安装步骤 一 、原理 FastDFS是一个开源的轻量级分布式文件系统&#xff0c;由跟踪服务器&#xff08;tracker server&#xff09;、存储服务器&#xff08;storage server&#xff09;和客户端&#xff08;client&#xff09;三个部分组成&#xff0c;主要解决了海量数…

Flutter笔记:Widgets Easier组件库(10)快速处理承若型对话

Flutter笔记 使用Widgets Easier组件库快速处理承若型对话 - 文章信息 - Author: 李俊才 (jcLee95) Visit me at CSDN: https://jclee95.blog.csdn.netMy WebSite&#xff1a;http://thispage.tech/Email: 291148484163.com. Shenzhen ChinaAddress of this article:https://…

光固化打印--问题记录

平面翘起 原因&#xff1a;角度平&#xff0c;缺支持 解决&#xff1a; 45度角度摆放底部平面起皮 原因&#xff1a;缺少支撑&#xff0c;原始结构支持无法支撑平面。 解决&#xff1a;增加支撑

【数学 排列组合】1643. 第 K 条最小指令

本文涉及知识点 数学 排列组合 LeetCode1643. 第 K 条最小指令 Bob 站在单元格 (0, 0) &#xff0c;想要前往目的地 destination &#xff1a;(row, column) 。他只能向 右 或向 下 走。你可以为 Bob 提供导航 指令 来帮助他到达目的地 destination 。 指令 用字符串表示&am…

Mybatis之Sqlsession、Connection和Transaction三者间的关系

前言 最近在看Mybatis的源码&#xff0c;搜到这篇文章Sqlsession、Connection和Transaction原理与三者间的关系&#xff0c;debug之后发现有不少疑惑&#xff0c;于是按照原文整理了一下&#xff0c;记录下debug中的一些困惑点。 对于我们开发来讲&#xff0c;不管跟任何关系…

2024五一数学建模C题完整论文讲解(含完整python代码及几十个特征表、处理表、结果表)

大家好呀&#xff0c;从发布赛题一直到现在&#xff0c;总算完成了2024五一数学建模C题煤矿深部开采冲击地压危险预测完整的成品论文。 本论文可以保证原创&#xff0c;保证高质量。绝不是随便引用一大堆模型和代码复制粘贴进来完全没有应用糊弄人的垃圾半成品论文。 C题论文…

【Docker学习】docker version查看版本信息

就像很多应用一样&#xff0c;docker也使用version来查看版本信息。但因为docker包含有不少独立组件&#xff0c;version的作用范围会更广一些。 用法1&#xff1a; docker --version 描述&#xff1a; 输出安装的Docker CLI 的版本号。关于Docker CLI&#xff0c;请访问。 实操…

数字电路-5路呼叫显示电路和8路抢答器电路

本内容涉及两个电路&#xff0c;分别为5路呼叫显示电路和8路抢答器电路&#xff0c;包含Multisim仿真原文件&#xff0c;为掌握FPGA做个铺垫。紫色文字是超链接&#xff0c;点击自动跳转至相关博文。持续更新&#xff0c;原创不易&#xff01; 目录&#xff1a; 一、5路呼叫显…

如何免费体验 gpt2-chatbot

如何免费体验 gpt2-chatbot 就在五一假期期间&#xff0c;一个神秘模型在没有任何官方文件的情况下突然发布。发布后不到 12 小时就立即引起人工智能爱好者和专家们的关注。这个名为“gpt2-chatbot”的神秘新模型凭借其令人印象深刻的能力轰动全球。有人猜测它可能是 OpenAI 的…

Python爬取豆瓣电影Top250数据

任务 爬取豆瓣电影top250中的影片名称、影片海报、年份、地区、类型、评分、评价人数、总体评价&#xff0c;并输出到douban_top250.xlsx文件中 环境 Python 3.8 requests bs4 openpyxl 源码 # 创建一个新的Excel工作簿 workbook openpyxl.Workbook() # 获取默认的工作表…

新版security demo(二)前端

写这篇博客&#xff0c;刚好换了台电脑&#xff0c;那就借着这个demo复习下VUE环境的搭建。 一、前端项目搭建 1、安装node 官网下载安装即可。 2、安装脚手架 npm install -g vue-cli 使用脚手架搭建一个demo前端项目 vue init webpack 项目名称 3、安装依赖 这里安装…

OpenCV(三)—— 车牌筛选

本篇文章要介绍如何对从候选车牌中选出最终进行字符识别的车牌。 无论是通过 Sobel 还是 HSV 计算出的候选车牌都可能不止一个&#xff0c;需要对它们进行评分&#xff0c;选出最终要进行识别的车牌。这个过程中会用到两个理论知识&#xff1a;支持向量机和 HOG 特征。 1、支…

vivado Aurora 8B/10B IP核(9)- CRC、 Aurora 8B/10B内核的时钟接口端口

CRC 模块提供 16 位或 32 位 CRC&#xff0c;用于用户数据。 Aurora 8B/10B 内核的时钟接口端口 从相邻收发器四边形的时钟Xilinx 实现工具可以根据需要对南北路由和引脚交换到收发器时钟输入进行必要的调整&#xff0c;以将时钟从一个四线到另一个。 重要信息&#xff1a;共…

25计算机考研院校数据分析 | 哈尔滨工业大学

哈尔滨工业大学&#xff08;Harbin Institute of Technology&#xff09;&#xff0c;简称哈工大&#xff0c; 校本部位于黑龙江省哈尔滨市&#xff0c;是由工业和信息化部直属的全国重点大学&#xff0c;位列国家“双一流”、“985工程”、“211工程”&#xff0c;九校联盟 、…

【Java EE】Mybatis之XML详解

文章目录 &#x1f38d;配置数据库连接和MyBatis&#x1f340;写持久层代码&#x1f338;添加mapper接口&#x1f338;添加UserInfoXMLMapper.xml&#x1f338;单元测试 &#x1f332;CRUD&#x1f338;增(Insert)&#x1f338;删(Delete)&#x1f338;改(Update)&#x1f338;…

【MIT6.S081】Lab6: Copy-on-Write Fork for xv6(详细解答版)

实验内容网址&#xff1a;https://xv6.dgs.zone/labs/requirements/lab6.html 本实验的代码分支&#xff1a;https://gitee.com/dragonlalala/xv6-labs-2020/tree// Implement copy-on write 关键点: 内存引用计数、usertrap()、页表 思路: Copy on write 是为了优化在fork()时…