C语言:贪吃蛇

目录

目录

1.游戏成品展示

2.准备工作

2.1第一步:控制台设置

2.2第二步:Win32API函数介绍

2.2.1Win32API简介

2.2.2GetStdHandle

2.2.3GetConsoleCursorInfo

2.2.4SetConsoleCursorInfo

2.2.5SetConsoleCursorPosition

2.2.6GetAsyncKeyState 

2.3第三步:setlocale函数.宽字符打印

3.贪吃蛇游戏实现

3.1游戏大纲

3.2snake.h:符号定义.类型声明.头文件包含

3.3test.c:构建游戏整体逻辑

3.4游戏开始(GameStart)

GameStart框架

3.4.1隐藏光标,设置窗口

3.4.2打印欢迎界面和功能介绍——WelcomeToGame

3.4.3绘制地图——CreateMap

3.4.4创建蛇,初始化游戏信息——InitSnake

3.4.5创建食物——CreateFood

GameStart完整代码

3.5游戏运行(GameRun)

GameRun框架

3.5.1右侧打印帮助信息——PrintHelpInfo

3.5.2打印当前已获得总分和食物的分数

3.5.3获取按键状态

3.5.4根据按键状态移动蛇——SnakeMove

SnakeMove框架

3.5.4.1根据蛇头的坐标和方向, 计算下一个节点的坐标

3.5.4.2判断下一个位置是否是食物——NextIsFood

3.5.4.3是食物就吃掉——EatFood

3.5.4.4不是食物,吃掉食物,尾巴删除一节——NoFood

3.5.4.5判断是否撞墙——KillByWall

3.5.4.6判断是否撞上自己——KillBySelf

SnakeMove完整代码

3.5.5循环2~4,直到游戏状态不为OK

GameRun完整代码

3.6游戏结束(GameEnd)

GameEnd框架

3.6.1打印游戏结束的原因

3.6.2.销毁蛇身节点

GameEnd完整代码

3.7循环游戏

4.贪吃蛇完整参考代码

snake.h

snake.c

test.c



贪吃蛇,一种很贪吃的蛇

贪吃蛇是一款久负盛名的游戏,它也和俄罗斯方块,扫雷等游戏位列经典游戏的行列。

接下来,我们用500行代码实现这个游戏!

技术要点:

C语⾔函数、枚举、结构体、动态内存管理、预处理指令、链表、Win32API等。

1.游戏成品展示

_贪吃蛇

2.准备工作

2.1第一步:控制台设置

代码运行起来后,我们有一个调试控制台窗口:

ba39bf5b0bd549b488542e7e3cebe811.png

16aad668f6464bcca832a71806646314.png

写上一段代码:运行

1620f47b80c2480ea757a609abb146a3.png

bd03ad32bc9b47c0ad491d819c4ee354.png

b07c3c8f44e34b6f91d838e98e5a860c.png

2.2第二步:Win32API函数介绍

2.2.1Win32API简介

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

在贪吃蛇项目中,我们需要使用到5个API个函数:

GetStdHandle、GetConsoleCursorInfo、SetConsoleCursorInfo、SetConsoleCursorPosition、GetAsyncKeyState

我们来一一领略

2.2.2GetStdHandle

GetStdHandle是⼀个Windows API函数。它⽤于从⼀个特定的标准设备(标准输⼊、标准输出或标准错误)中取得⼀个句柄(⽤来标识不同设备的数值),使⽤这个句柄可以操作设备。

在贪吃蛇项目中,我们隐藏光标、打印地图等一系列操作,都是在我们的输出设备——屏幕上进行。因此,我们需要得到对我们屏幕的操作权限,也就是句柄。

54dccd6e404f45da954fe39b097324f1.png

2.2.3GetConsoleCursorInfo

检索有关指定控制台屏幕缓冲区的光标⼤⼩和可⻅性的信息

BOOL WINAPI GetConsoleCursorInfo(
HANDLE hConsoleOutput,
PCONSOLE_CURSOR_INFO lpConsoleCursorInfo
);
PCONSOLE_CURSOR_INFO 是指向 CONSOLE_CURSOR_INFO 结构的指针,该结构接收有关主机游标

_CONSOLE_CURSOR_INFO是一个结构体: 

typedef struct _CONSOLE_CURSOR_INFO {
  DWORD dwSize;//由游标填充的字符单元的百分比
  BOOL  bVisible;//游标的可见性
} CONSOLE_CURSOR_INFO, *PCONSOLE_CURSOR_INFO;

62821575e2d44e2e81370c7f53f93f25.png

2.2.4SetConsoleCursorInfo

相信各位发现了,GetConsoleCursorInfo和SetConsoleCursorInfo,一个是Get,一个是Set

我们获取了屏幕上光标的信息后,可以对它的dwSize和bVisible两个成员进行修改,因为我们

要隐藏光标,所以把成员bVisible设置为false即可隐藏光标。设置完成后,需要使用SetConsoleCursorInfo设置标准输出设备,才能产生效果

代码如下:

30da59d24bdf4bc5a1708f31e4e3759d.png

2.2.5SetConsoleCursorPosition

在游戏实现的地图打印和信息打印的环节,我们需要定位光标的位置,SetConsoleCursorPosition

应运而生!

SetConsoleCursorPosition:

设置指定控制台屏幕缓冲区中的光标位置,我们将想要设置的坐标信息放在COORD类型的pos中,调⽤SetConsoleCursorPosition函数将光标位置设置到指定的位置。

BOOL WINAPI SetConsoleCursorPosition(
HANDLE hConsoleOutput,
COORD pos
);

这里简单介绍一下COORD:

COORD是Windows API中定义的⼀个结构体,表⽰⼀个字符在控制台屏幕幕缓冲区上的坐标,坐标系(0,0)的原点位于缓冲区的顶部左侧单元格。

216792a0e7d241838b440db5dbd85653.png

COORD类型的声明:

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

 3f18e160c42e4857ad991c1bd3007ea3.png

于是,我们可以封装一个函数,定位我们光标的位置:

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

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

这是我们实现贪吃蛇游戏的一个利器。

2.2.6GetAsyncKeyState 

获取按键情况,GetAsyncKeyState的函数原型如下:

SHORT GetAsyncKeyState(
int vKey
);

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

在我们实现的贪吃蛇游戏中,我们无须一直按着按键,只需判断方向键是否按过,以此来改变蛇的运动方向即可。因此,我们关注GetAsyncKeyState返回值的最低位的值是否为1。

1c06949c1210457a9511537ce41b2c85.png

参考:虚拟键码

到此为止,我们就把贪吃蛇项目设计的5个WIN32API介绍完毕!!!

我们离胜利进了一步!!!

2.3第三步:setlocale函数.宽字符打印

在游戏地图上,我们打印墙体使⽤宽字符:□,打印蛇使⽤宽字符●,打印⻝物使⽤宽字符★
普通的字符是占⼀个字节的,这类宽字符是占⽤2个字节。

而我们想打印宽字符,就用到setlocale函数切换到本地模式,才能达成目的。

setlocale函数声明:

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

这里我们做对category(类项)做一下简单介绍: 

 通过修改地区,程序可以改变它的⾏为来适应世界的不同区域。但地区的改变可能会影响库的许多部分,其中⼀部分可能是我们不希望修改的。所以C语⾔⽀持针对不同的类项进⾏修改,下⾯的⼀个宏,指定⼀个类项:
• LC_COLLATE:影响字符串⽐较函数 strcoll() 和 strxfrm() 。
• LC_CTYPE:影响字符处理函数的⾏为。
• LC_MONETARY:影响货币格式。
• LC_NUMERIC:影响 printf() 的数字格式。
• LC_TIME:影响时间格式 strftime() 和 wcsftime() 。
• LC_ALL:针对所有类项修改,将以上所有类别设置为给定的语⾔环境。

setlocale函数⽤于修改当前地区,可以针对⼀个类项修改,也可以针对所有类项。
setlocale的第⼀个参数可以是前⾯说明的类项中的⼀个,那么每次只会影响⼀个类项,如果第⼀个参数是LC_ALL,就会影响所有的类项。
C标准给第⼆个参数仅定义了2种可能取值:"C"(正常模式)和" "(本地模式)。
在任意程序执⾏开始,都会隐藏式执⾏调⽤:

setlocale(LC_ALL, "C");

当地区设置为"C"时,库函数按正常⽅式执⾏,⼩数点是⼀个点。
当程序运⾏起来后想改变地区,就只能显⽰调⽤setlocale函数。⽤" "作为第2个参数,调⽤setlocale函数就可以切换到本地模式,这种模式下程序会适应本地环境。
⽐如:切换到我们的本地模式后就⽀持宽字符(汉字)的输出等。 

setlocale(LC_ALL, " "); //切换到本地环境 

那如果想在屏幕上打印宽字符,怎么打印呢?
宽字符的字⾯量必须加上前缀“L”,否则C语⾔会把字⾯量当作窄字符类型处理。前缀“L”在单引
号前⾯,表⽰宽字符,对应 wprintf() 的占位符为 %lc ;在双引号前⾯,表⽰宽字符串,对应
wprintf() 的占位符为 %ls 。 

78866619a58c40ab8e78ecc872affb8c.png

演示:

fc466813773342589f14385bb4eb662f.png

a141dc3c32254c3ab8f8f92424cc6716.png

3.贪吃蛇游戏实现

3.1游戏大纲

我们创建三个文件:test.c、snake.c和snake.h

test.c:包含游戏的整体逻辑

snake.h:包含所有头文件定义常量类型声明函数声明

snake.c:实现snake.h中声明的函数

主逻辑分为3个过程:
• 游戏开始(GameStart)完成游戏的初始化
• 游戏运⾏(GameRun)完成游戏运⾏逻辑的实现
• 游戏结束(GameEnd)完成游戏结束的说明,实现资源释放

3.2snake.h:符号定义.类型声明.头文件包含

我们先在snake.h的头文件中,包含头文件定义符号声明类型:

3.3test.c:构建游戏整体逻辑

我们把游戏整体逻辑封装到test()中:

有的功能我们直接在三个阶段的函数主体中实现,有的功能我们另外封装成函数去实现:

例如,GameStart函数中,第一个功能"隐藏光标,设置窗口",我们直接在GameStart函数主体中实现;而第二个功能“打印欢迎界面和功能介”,我们封装成了WelcomeToGame函数另外去实现。

接下来,我们在snake.h中声明函数,再到snake.c的文件中一一实现即可。

当然,在snake.c中,我们不要忘记包含我们"snake.h"的头文件。

3.4游戏开始(GameStart)

1.隐藏光标,设置窗口
2.打印欢迎界面和功能介绍——WelcomeToGame
3.绘制地图——CreateMap
4.创建蛇,初始化游戏信息——InitSnake
5.创建食物——CreateFood

以上,我们在GameStart的函数主体中另外封装了4个函数。

snake.h中声明我们要实现的函数:

接下来我们到snake.c的文件中实现:

GameStart框架

3.4.1隐藏光标,设置窗口

3.4.2打印欢迎界面和功能介绍——WelcomeToGame

运行:

3.4.3绘制地图——CreateMap

3.4.4创建蛇,初始化游戏信息——InitSnake

3.4.5创建食物——CreateFood

1.x坐标必须为2的倍数:

2.

x坐标的范围:2~54

y坐标的范围:1~25

3.x和y的坐标不能和蛇身节点的坐标冲突:

rand函数的调用,需要在主函数main中调用srand,用来初始化生成随机数的种子:

GameStart完整代码

3.5游戏运行(GameRun)

1.右侧打印帮助信息——PrintHelpInfo
2.打印当前已获得总分和食物的分数
3.获取按键状态

4.根据按键状态移动蛇——SnakeMove
 4.1根据蛇头的坐标和方向,计算下一个节点的坐标
 4.2判断下一个位置是否是食物——NextIsFood
 4.3是食物就吃掉——EatFood
 4.4不是食物,吃掉食物,尾巴删除一节——NoFood
 4.5判断是否撞墙——KillByWall
 4.6判断是否撞上自己——KillBySelf

5.循环2~4,直到游戏状态不为OK

以上,我们在GameRun函数的主体中另外封装了2个函数,而在SnakeMove函数的主体中还另外封装了4个函数。

先在snake.h中声明我们要实现的函数:

接下来我们到snake.c的文件中实现:

GameRun框架

3.5.1右侧打印帮助信息——PrintHelpInfo

3.5.2打印当前已获得总分和食物的分数

3.5.3获取按键状态

要改变方向需要满足两个条件:1.按下按键;2.不能与当前的方向相反。

比如,蛇的方向是向右,你按下←不能转向,否则会自己撞上自己。

3.5.4根据按键状态移动蛇——SnakeMove

SnakeMove框架

3.5.4.1根据蛇头的坐标和方向, 计算下一个节点的坐标

3.5.4.2判断下一个位置是否是食物——NextIsFood

3.5.4.3是食物就吃掉——EatFood

注意把食物的分数加到总分上,而且要重新创建食物

3.5.4.4不是食物,吃掉食物,尾巴删除一节——NoFood

要注意把最后一个节点释放掉,同时把倒数第二个节点的next指针置为NULL

3.5.4.5判断是否撞墙——KillByWall

蛇头的坐标若为:

(0,0)

(0,26)

(56,0)

(56,26)

就是撞墙了,把游戏状态修改为KILL_BY_WALL

3.5.4.6判断是否撞上自己——KillBySelf

从第二个蛇身节点开始遍历,判断蛇头坐标与蛇身坐标是否相同

若相同,就是撞上了自己,游戏状态修改为KILL_BY_SELF

SnakeMove完整代码

3.5.5循环2~4,直到游戏状态不为OK

GameRun完整代码

3.6游戏结束(GameEnd)

1.打印游戏结束的原因
2.销毁蛇身节点

我们直接在GameEnd函数的主体中,实现以上两个功能。

先在snake.h中声明我们要实现的函数:

接下来我们到snake.c的文件中实现:

GameEnd框架


3.6.1打印游戏结束的原因


3.6.2.销毁蛇身节点

GameEnd完整代码

3.7循环游戏

来到test.c的文件中,完成我们的最后一步!

4.贪吃蛇完整参考代码

snake.h

#define _CRT_SECURE_NO_WARNINGS
#include <locale.h>
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <time.h>
#include <stdbool.h>

//定义墙体.蛇身.食物的符号
#define WALL L'□'//墙体
#define BODY L'●'//蛇身
#define FOOD L'★'//食物

//定义蛇的坐标
#define POS_X 24
#define POS_Y 5

//类型的声明

//蛇身节点
typedef struct SnakeNode
{
	int x;
	int y;
	struct SnakeNode* next;
}SnakeNode, *pSnakeNode;
//typedef SnakeNode* pSnakeNode;

enum DIRECTION
{
	UP = 1,
	DOWN,
	LEFT, 
	RIGHT
};

enum GAME_STATUS
{
	OK,
	KILL_BY_WALL,//撞墙
	KILL_BY_SELF,//撞到自己
	END_NORMAL//正常退出
};

//贪吃蛇
typedef struct Snake
{
	pSnakeNode pSnake;//指向蛇头的指针
	pSnakeNode pFood;//指向食物的指针
	enum DIRECTION dir;//蛇的方向
	enum GAME_STATUS status;//游戏状态
	int sleep_time;//休眠时间,时间越短,速度越快,时间越长,速度越慢
	int score;//总分数
	int foodWeight;//一个食物的分数
}Snake,*pSnake;

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

//游戏的初始化
void GameStart(pSnake ps);
//1.隐藏光标,设置窗口
//2.打印欢迎界面和功能介绍——WelcomeToGame
void WelcomeToGame();
//3.绘制地图——CreateMap
void CreateMap();
//4.创建蛇,初始化游戏信息——InitSnake
void InitSnake(pSnake ps);
//5.创建食物——CreateFood
void CreateFood(pSnake ps);


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

//1.右侧打印帮助信息——PrintHelpInfo
void PrintHelpInfo();

//2.打印当前已获得总分和食物的分数
//3.获取按键状态

//4.根据按键状态移动蛇——SnakeMove
void SnakeMove(pSnake ps);

// 4.1根据蛇头的坐标和方向,计算下一个节点的坐标

// 4.2判断下一个位置是否是食物——NextIsFood
int NextIsFood(pSnakeNode pn, pSnake ps);

// 4.3是食物就吃掉——EatFood
void EatFood(pSnakeNode pn, pSnake ps);

// 4.4不是食物,吃掉食物,尾巴删除一节——NoFood
void NoFood(pSnakeNode pn, pSnake ps);

// 4.5判断是否撞墙——KillByWall
void KillByWall(pSnake ps);

// 4.6判断是否撞上自己——KillBySelf
void KillBySelf(pSnake ps);

//5.循环2~4,直到游戏状态不为OK


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

//1.打印游戏结束的原因
//2.销毁蛇身节点

snake.c

#define _CRT_SECURE_NO_WARNINGS
#include "snake.h"

void SetPos(short x, short y)
{
	HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);//获取标准输出设备的句柄
	COORD pos = { x,y };//定义光标位置
	SetConsoleCursorPosition(houtput, pos);//设置光标位置
}
//2.打印欢迎界面和功能介绍——WelcomeToGame
void WelcomeToGame()
{
	//打印欢迎界面
	SetPos(40,15);//定位光标位置
	printf("欢迎来到贪吃蛇小游戏");
	SetPos(40, 25);//让"按任意键继续..."出现的位置美观
	system("pause");
	system("cls");

	//打印功能介绍
	SetPos(25, 12);
	printf("用 ↑. ↓ . ← . → 来控制蛇的移动,按F3加速,F4减速\n");
	SetPos(25, 13);
	printf("加速能够得到更高的分数\n");
	SetPos(40, 25);//让"按任意键继续..."出现的位置美观
	system("pause");
	system("cls");
}

//3.绘制地图——CreateMap
void CreateMap()
{
	//绘制27行,58列的地图
	int i = 0;
	//上
	SetPos(0, 0);
	for (i = 0; i < 29; i++)//一个宽字符'□'占两列,58/2=29
	{
		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 = 0; i <= 25; i++)
	{
		SetPos(56, i);
		wprintf(L"%lc", WALL);
	}
}

//4.创建蛇,初始化游戏信息——InitSnake
void InitSnake(pSnake ps)
{
	//创建5个蛇身节点
	pSnakeNode cur = NULL;
	int i = 0;
	for (i = 0; i < 5; i++)
	{
	    cur = (pSnakeNode)malloc(sizeof(SnakeNode));
		if (cur == NULL)
		{
			perror("InitSnaek::malloc()");
			return;
		}
		//节点创建成功,给蛇身节点的坐标赋值.next指针置空
		cur->x = POS_X + 2 * i;//24 26 28 30 32
		cur->y = POS_Y;        //5  5  5  5  5
		cur->next = NULL;

		//头插法——把蛇身节点插入链表中
			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->status = OK;//游戏状态
	ps->sleep_time = 200;//蛇的速度,时间越短,速度越快,时间越长,速度越慢
	ps->score = 0;//总分数
	ps->foodWeight = 10;//食物的分数
}

//5.创建食物——CreateFood
void CreateFood(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不为2的倍数,重新生成坐标

	//x和y不能和蛇身节点冲突
	pSnakeNode cur = ps->pSnake;
	//遍历蛇身节点,判断蛇身节点坐标与食物节点坐标是否冲突
	while (cur)
	{
		if (cur->x == x && cur->y == y)
		{
			goto again;
		}
		cur = cur->next;
	}

	//创建食物节点
	pSnakeNode pFood =(pSnakeNode)malloc(sizeof(SnakeNode));
	if (pFood == NULL)
	{
		perror("CreateFood::malloc()");
		return;
	}
	//食物节点创建成功
	pFood->x = x;
	pFood->y = y;
	pFood->next = NULL;

	//打印食物
	SetPos(pFood->x, pFood->y);
	wprintf(L"%lc", FOOD);

	//存储到Snake中指向食物节点的指针中
	ps->pFood = pFood;
}

//游戏运行
void GameStart(pSnake ps)
{
	//1.隐藏光标,设置窗口
	HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);//获取标准输出设备的句柄
	CONSOLE_CURSOR_INFO cursor_info = { 0 };
	GetConsoleCursorInfo(houtput, &cursor_info);//获取控制台光标信息
	cursor_info.bVisible = false;//隐藏控制台光标
	SetConsoleCursorInfo(houtput, &cursor_info);//设置控制台光标信息
	
	system("mode con cols=100 lines=30");
	system("title 贪吃蛇");

	//2.打印欢迎界面和功能介绍——WelcomeToGame
	WelcomeToGame();

	//3.绘制地图——CreateMap
	CreateMap();

	//4.创建蛇,初始化游戏信息——InitSnake
    InitSnake(ps);

	//5.创建食物——CreateFood
	CreateFood(ps);
}

//1.右侧打印帮助信息——PrintHelpInfo
void PrintHelpInfo()
{
	SetPos(64, 15);
	wprintf(L"%ls", L"不能穿墙,不能咬到自己");
	SetPos(64, 16);
	wprintf(L"%ls", L"用 ↑. ↓ . ← . → 来控制蛇的移动");
	SetPos(64, 17);
	wprintf(L"%ls", L"按F3加速,F4减速");
	SetPos(64, 18);
	wprintf(L"%ls", L"按ESC退出游戏,按空格暂停游戏");

	SetPos(64, 20);
	wprintf(L"%ls", L"Chocolate_旭日东升制作");
}

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

void Pause()
{
	while (1)
	{
		Sleep(200);
		if (KEY_PRESS(VK_SPACE))//按空格继续游戏
		{
			break;
		}
	}
}

// 4.2判断下一个节点是否为食物——NextIsFood
int NextIsFood(pSnakeNode pn, pSnake ps)
{
	return (pn->x == ps->pFood->x && pn->y == ps->pFood->y);
}


// 4.3是食物就吃掉——EatFood
void EatFood(pSnakeNode pn, pSnake ps)
{
	//头插法
	ps->pFood->next = ps->pSnake;
	ps->pSnake = ps->pFood;

	//打印蛇
	pSnakeNode cur = ps->pSnake;
	while (cur)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	ps->score += ps->foodWeight;

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

// 4.4不是食物,吃掉食物,尾巴删除一节——NoFood
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);
	//把倒数第二个节点的next指针置为NULL
	cur->next = NULL;
}

// 4.5判断是否撞墙——KillByWall
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;
	}
}

// 4.6判断是否撞上自己——KillBySelf
void KillBySelf(pSnake ps)
{
	pSnakeNode cur = ps->pSnake->next;
	while (cur)
	{
		if (ps->pSnake->x == cur->x && ps->pSnake->y == cur->y)
		{
			ps->status = KILL_BY_SELF;
			break;
		}
		cur = cur->next;
	}
}


	//4.根据按键情况移动蛇
	void SnakeMove(pSnake ps)
	{
		
		pSnakeNode pNextNode = (pSnakeNode)malloc(sizeof(SnakeNode));//创建一个节点,表示蛇即将到的下一个节点
		if (pNextNode == NULL)
		{
			perror("SnakeMove::malloc()");
			return;
		}

		// 4.1根据蛇头的坐标和方向, 计算下一个节点的坐标
		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;
		}

		// 4.2判断下一个节点是否为食物——NextIsFood
		if (NextIsFood(pNextNode, ps))
		{
			// 4.3是食物就吃掉——EatFood
			EatFood(pNextNode, ps);
		}
		else
		{
			// 4.4不是食物,吃掉食物,尾巴删除一节——NoFood
			NoFood(pNextNode, ps);
		}

		// 4.5判断是否撞墙——KillByWall
		KillByWall(ps);

		// 4.6判断是否撞上自己——KillBySelf
		KillBySelf(ps);
	}

//游戏运行
void GameRun(pSnake ps)
{

		//1.打印帮助信息
		PrintHelpInfo();

		//5.循环2~4,直到游戏状态不为OK
		do
		{
			//2.打印当前已获得分数和食物的分数
			SetPos(64, 10);
			printf("总分数:%d\n", ps->score);
			SetPos(64, 11);
			printf("当前食物的分数:%2d\n", ps->foodWeight);

			//3.获取按键情况
			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_F3))
			{
				//加速
				if (ps->sleep_time > 50)
				{
					ps->sleep_time -= 30;
					ps->foodWeight += 2;//一个食物分数最高是20分
				}
			}
			else if (KEY_PRESS(VK_F4))
			{
				//减速
				if (ps->sleep_time < 320)
				{
					ps->sleep_time += 30;
					ps->foodWeight -= 2;//一个食物分数最低是2分
				}
			}
			else if (KEY_PRESS(VK_SPACE))
			{
				//暂停
				Pause();
			}
			else if (KEY_PRESS(VK_ESCAPE))
			{
				//正常退出
				ps->status = END_NORMAL;
			}

			//4.根据按键情况移动蛇
			SnakeMove(ps);
			Sleep(ps->sleep_time);//蛇每次移动要休眠的时间,时间短,速度快,时间长,速度慢

		} while (ps->status == OK);
}

//游戏结束
void GameEnd(pSnake ps)
{
	//1.告知游戏结束的原因
	SetPos(24, 12);
	switch (ps->status)
	{
	case END_NORMAL:
		printf("您主动退出游戏\n");
		break;
	case KILL_BY_WALL:
		printf("您撞墙了,游戏结束!\n");
		break;
	case KILL_BY_SELF:
		printf("您撞上了自己,游戏结束!\n");
		break;
	}

	//2.释放蛇身节点
	pSnakeNode cur = ps->pSnake;
	while (cur)
	{
		pSnakeNode next = cur->next;
		free(cur);
		cur = next;
	}
	
}

test.c

#define _CRT_SECURE_NO_WARNINGS
#include "snake.h"

void test()
{
	srand((unsigned int)time(NULL));

	int ch = 0;
	do
	{
		system("cls");//每次循环清理屏幕

		//创建贪吃蛇——包含贪吃蛇所有游戏信息
		Snake snake = { 0 };

		//游戏的初始化——GameStart
		//1.隐藏光标,设置窗口
		//2.打印欢迎界面和功能介绍——WelcomeToGame
		//3.绘制地图——CreateMap
		//4.创建蛇,初始化游戏信息——InitSnake
		//5.创建食物——CreateFood
		GameStart(&snake);


		//游戏运行——GameRun
		//1.右侧打印帮助信息——PrintHelpInfo
		//2.打印当前已获得总分和食物的分数
		//3.获取按键状态

		//4.根据按键状态移动蛇——SnakeMove
		// 4.1根据蛇头的坐标和方向,计算下一个节点的坐标
		// 4.2判断下一个位置是否是食物——NextIsFood
		// 4.3是食物就吃掉——EatFood
		// 4.4不是食物,吃掉食物,尾巴删除一节——NoFood
		// 4.5判断是否撞墙——KillByWall
		// 4.6判断是否撞上自己——KillBySelf

		//5.循环2~4,直到游戏状态不为OK
		GameRun(&snake);


		//游戏结束——游戏善后工作
		//1.打印游戏结束的原因
		//2.销毁蛇身节点
		GameEnd(&snake);

		SetPos(24, 13);
		printf("再来一局吗?(Y/N):");
		ch = getchar();
		getchar();//清理玩家回车——\n
	} while (ch == 'Y' || ch == 'y');

	SetPos(0, 27);
}

int main()
{
	setlocale(LC_ALL, "");//切换到本地环境,以支持宽字符'□''●''★'的打印
	test();
	return 0;
}

✿✿✿完结撒花✿✿✿

  • 39
    点赞
  • 53
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值