小编主页详情<-请点击
小编gitee代码仓库<-请点击


本文主要介绍了项目实践中的贪吃蛇,内容全由作者原创(无AI),同时深度解析了贪吃蛇每个环节及需要实现的内容,并带有配图帮助博友们更好的理解,点个关注不迷路,下面进入正文~~


目录

前言:

1.贪吃蛇游戏的前期准备

1.1贪吃蛇信息的管理

1.2游戏图标的管理

1.3蛇身节点的类型

2.初始化游戏GameStart

2.1先设置窗口的大小,再光标隐藏

2.2打印环境界面和功能介绍WelcomeToGame

2.3绘制地图CreateMap

2.4创建蛇身InitSnake

2.5创建食物CreateFood

3.运行游戏GameRun

3.1打印帮助信息PrintHelpInfo

3.2检测当前按键状态

3.3蛇身移动SnakeMove

3.3.1创建新节点

3.3.2检测下一个坐标处是否是食物

3.3.3贪吃蛇死亡的判定条件

4.游戏结束- 善后工作GameEnd

4.1判断游戏结束的原因

4.2释放蛇身的链表

5.贪吃蛇完整代码的呈现

5.1Snake.h

5.2Snake.c

5.3test.c

结语:


前言:

当我们学习完基本的C语言语法和链表的知识后,我们就可以尝试完成贪吃蛇这样的一个小项目。
下面是一些在执行贪吃蛇时的画面,有兴趣的博友可以在文章最后面查看完整代码,并复制到编译器上看看效果。

下面开始解析贪吃蛇全部的逻辑与代码

1.贪吃蛇游戏的前期准备

1.1贪吃蛇信息的管理

一条贪吃蛇包含着很多信息,比如说指向蛇头的指针、指向蛇头的指针、蛇的方向、游戏的状态、一个食物的分数、总成绩和蛇的速度,那当我们想修改这些数据时,我们发现这些数据零零散散的,管理起来很不方便。因此我们将贪吃蛇的信息统一管理在一个名叫Snake的结构体里。

/贪吃蛇
typedef struct Snake
{
	pSnakeNode _pSnake;//指向蛇头的指针
	pSnakeNode _Food;//指向蛇头的指针
	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_SELF,//撞到自己
	END_NORMAL//正常退出
};

1.2游戏图标的管理

在进行贪吃蛇游戏时,我们会发现有贪吃蛇的身体“实心圆”,墙“空心方块"以及食物“五角星”,每次将这些图标打印出来会很不方便,因此我们也将这些图标宏定义

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

1.3蛇身节点的类型

蛇的一个节点要包含当前位置所在的坐标以及指向下一个节点的指针,因此我们用链表将其串起来。

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

2.初始化游戏GameStart

2.1先设置窗口的大小,再光标隐藏

设置窗口大小我们可以使用头文件<window.s>包含的system函数设置,代码如下

system("mode con cols=100 lines=30");
system("title 贪吃蛇");

将光标隐藏要分为几个步骤:

1.获取标准输出的句柄
2.定义一个光标信息的变量
3.获取控制台光标信息
4.隐藏控制台光标
5.设置控制台屏幕缓冲区的光标

HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);
//影藏光标操作
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(houtput, &CursorInfo);//获取控制台光标信息
CursorInfo.bVisible = false;//隐藏控制台光标
SetConsoleCursorInfo(houtput, &CursorInfo);

2.2打印环境界面和功能介绍WelcomeToGame

打印的关键步骤是设置打印的位置,我们可以单独封装一个函数SetPos来定位打印的位置。

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

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

当每打印完一个游戏界面时,我们要用system("pause");将游戏暂时停下来,当玩家输入任意键后再到下一个界面,并且要使用system("cls");清除上一个界面打印的内容。整体代码如下

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

	SetPos(42, 20);
	system("pause");
	system("cls");
}

2.3绘制地图CreateMap

我们这里创建的地图有58行27列,且地图的方块是一个宽字符,占两个x轴坐标的位置,在打印地图时需要格外注意

void CreateMap()
{
	//上
	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);
	}
}

2.4创建蛇身InitSnake

我们的蛇身是由链表上一个一个节点构成的,因此创建蛇身可以使用我们之前讲到过的创建链表的方法,在这里蛇身的位置是固定的。

当创建好之后我们需要将蛇的每个节点打印出来(这里我们使用头插法,先创建蛇的尾巴,然后将蛇的头指针逐步指向新创建的节点)

打印完之后我们还要设置蛇的属性,比如蛇的初始方向,一个食物的分数,总分数,蛇的速度以及蛇的状态

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("InitSnake");
			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;
		}
	}
	while (cur)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	//设置贪吃蛇的属性
	ps->_dir = RIGHT;//默认向右
	ps->_food_weight = 10;
	ps->_score = 0;
	ps->_sleep_time = 200;//单位是毫秒
	ps->_status = OK;
}

2.5创建食物CreateFood

首先我们要知道,食物的本质其实就是蛇的一个节点,在蛇吃掉这个食物后,食物将成为蛇的一个新节点。

食物的生称位置应当是随机的,我们可以使用rand函数生成,在主函数最前面要加上srand((unsigned int)time(NULL));x坐标应当是2的倍数并且不能和蛇的身体坐标冲突,如果冲突的话要重新生成。

蛇的坐标成功建立后,我们就可以创建食物的节点。成功创建节点后,就可以打印食物在屏幕上。

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和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("CreateFood");
		return;
	}
	pFood->x = x;
	pFood->y = y;
	pFood->next = NULL;

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

	ps->_Food = pFood;
}

3.运行游戏GameRun

3.1打印帮助信息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退出游戏,按空格暂停游戏");
}

3.2检测当前按键状态

我们在玩游戏的时候可能会使用↑. ↓ . ← . → 来控制蛇的移动,按F3加速,F4减速,按ESC退出游戏,按空格暂停游戏,那么我们就需要检测按键是否被按过。

我们可以使用GetAsyncKeyState()函数来判断按键是否被按过,如果被按过,则返回值的最低位为1;如果没被按过,则返回值的最低位为0。

根据这个特性,我们可以将GetAsyncKeyState()函数的返回值按位与上数字1,如果值是1,那就被按过,如果是0,那就是没被按过

为了方便使用,我们可以定义一个去使用

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

vk是按键的虚拟值,如果值是1,那就被按过,如果是0,那就是没被按过

我们还需要注意的是,如果当前贪吃蛇是向右走,那么当我们按左键的时候不能更改贪吃蛇向左,很明显,这样会存在矛盾。

速度的大小也应当有上下限,一个食物分数同样也应该根据速度德的变化而变化

这里还有一个按键是空格,当我们按下空格的时候,游戏会暂停。那这个暂停我们要怎么实现呢?
其实很简单,我们可以死循环一个sleep函数,当我们检测到空格再次被按下就跳出循环

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

下面是按键功能的具体实现

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

3.3蛇身移动SnakeMove

3.3.1创建新节点

如果我们想让蛇移动,就必须创建一个结点,表示蛇即将到的下一个节点。同时这个下一个节点的位置应该是由蛇的方向决定的,例如当蛇的方向是向右的,那蛇头的横坐标加2,纵坐标不变就是下一个节点的位置

3.3.2检测下一个坐标处是否是食物

当我们蛇下一个移动的位置是食物或者不是食物时,二者的实现方法是完全不同的,我们先分析怎么判断下一个位置是不是食物。

其实很简单,我们可以先定义一个函数NextIsFood,如果新蛇头的位置和食物相同就返回1,如果不相同就返回0,方便我们后面if语句的判断

int NextIsFood(pSnakeNode pn, pSnake ps)
{
	return(ps->_Food->x == pn->x && ps->_Food->y == pn->y);
}

如果是食物我们就应该吃掉食物。同样的,我们采用头插法的方式创建新蛇头,我们可以选择用下一个位置或者食物当作新蛇头,这里我选择用食物当作蛇的新蛇头,同时别忘了释放下一个位置创建的新节点。

在完成上面的步骤后,我们还要将蛇身打印出来,同时让游戏的总分数加上一个食物的分数,别忘了重新创建一个食物

void EatFood(pSnakeNode pn, pSnake ps)
{
	//头插法
	ps->_Food->next = ps->_pSnake;
	ps->_pSnake = ps->_Food;

	//释放下一个位置的节点
	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;

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

如果不是食物,同样的,我们也需要用头插法将新蛇头的位置变成下一个节点的位置,然后将蛇身打印出来。

注意,此时我们上一个最后蛇的节点还打印在屏幕上,因此我们还需要把最后一个结点打印成空格,再释放最后一个节点,并把倒数第二个节点的地址置为NULL

void NoFood(pSnakeNode pn, pSnake ps)
{
	//头插法
	pn->next = ps->_pSnake;
	ps->_pSnake = pn;

	pSnakeNode cur = ps->_pSnake;
	while (cur->next->next != NULL)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}

	//把最后一个结点打印成空格
	SetPos(cur->next->x, cur->next->y);
	printf("  ");

	//释放最后一个结点
	free(cur->next);

	//把倒数第二个节点的地址置为NULL
	cur->next = NULL;
}

3.3.3贪吃蛇死亡的判定条件

当贪吃蛇移动下一个位置时,我们也要判断蛇是否“死亡”

当蛇撞到墙时,我们判断蛇头的坐标是否和墙是一样的,如果一样,就把蛇的状态设置成KILL_BY_WALL

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

当蛇撞到自己时,我们从蛇头的下一个节点开始遍历,如果有节点和蛇头的坐标相同,就将蛇的状态设置成KILL_BY_SELF

void KillBySelf(pSnake ps)
{
	pSnakeNode cur = ps->_pSnake->next;
	while (cur)
	{
		if (cur->x == ps->_pSnake->x && cur->y == ps->_pSnake->y)
		{
			ps->_status = KILL_BY_SELF;
			break;
		}
		cur = cur->next;
	}
}

4.游戏结束- 善后工作GameEnd

4.1判断游戏结束的原因

根据不同的结束原因,我们打印不同的结束原因在屏幕上

SetPos(24, 12);
switch (ps->_status)
{
case END_NORMAL:
	wprintf(L"您主动结束游戏\n");
	break;
case KILL_BY_WALL:
	wprintf(L"您撞到墙上,游戏结束\n");
	break;
case KILL_BY_SELF:
	wprintf(L"您撞到了自己,游戏结束\n");
	break;
}

4.2释放蛇身的链表

pSnakeNode cur = ps->_pSnake;
while (cur)
{
	pSnakeNode next = cur->next;
	free(cur);
	cur = next;
}

5.贪吃蛇完整代码的呈现

5.1Snake.h

#pragma once
#include<Windows.h>
#include<stdlib.h>
#include<stdio.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_SELF,//撞到自己
	END_NORMAL//正常退出
};
typedef struct SnakeNode
{
	int x;
	int y;
	//指向下一个节点的指针
	struct SnakeNode* next;
}SnakeNode,* pSnakeNode;

//贪吃蛇
typedef struct Snake
{
	pSnakeNode _pSnake;//指向蛇头的指针
	pSnakeNode _Food;//指向蛇头的指针
	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 CreateMap();

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

//创建食物
void CreateFood(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 KillBySelf(pSnake ps);

//游戏善后的工作
void GameEnd(pSnake ps);

5.2Snake.c

#define _CRT_SECURE_NO_WARNINGS
#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, 20);
	system("pause");
	system("cls");
	SetPos(25, 14);
	wprintf(L"用 ↑. ↓ . ← . → 来控制蛇的移动,按F3加速,F4减速\n");
	SetPos(25, 15);
	wprintf(L"加速能够得到更高的分数\n");

	SetPos(42, 20);
	system("pause");
	system("cls");
}
void CreateMap()
{
	//上
	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("InitSnake");
			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;
		}
	}
	while (cur)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	//设置贪吃蛇的属性
	ps->_dir = RIGHT;//默认向右
	ps->_food_weight = 10;
	ps->_score = 0;
	ps->_sleep_time = 200;//单位是毫秒
	ps->_status = OK;
}

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和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("CreateFood");
		return;
	}
	pFood->x = x;
	pFood->y = y;
	pFood->next = NULL;

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

	ps->_Food = pFood;
}
void GameStart(pSnake ps)
{
	//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. 绘制地图
	CreateMap();
	//3. 创建蛇
	InitSnake(ps);
	//4. 创建食物
	CreateFood(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退出游戏,按空格暂停游戏");
}

#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)
{
	return(ps->_Food->x == pn->x && ps->_Food->y == pn->y);
}

void EatFood(pSnakeNode pn, pSnake ps)
{
	//头插法
	ps->_Food->next = ps->_pSnake;
	ps->_pSnake = ps->_Food;

	//释放下一个位置的节点
	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;

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

void NoFood(pSnakeNode pn, pSnake ps)
{
	//头插法
	pn->next = ps->_pSnake;
	ps->_pSnake = pn;

	pSnakeNode cur = ps->_pSnake;
	while (cur->next->next != NULL)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}

	//把最后一个结点打印成空格
	SetPos(cur->next->x, cur->next->y);
	printf("  ");

	//释放最后一个结点
	free(cur->next);

	//把倒数第二个节点的地址置为NULL
	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 KillBySelf(pSnake ps)
{
	pSnakeNode cur = ps->_pSnake->next;
	while (cur)
	{
		if (cur->x == ps->_pSnake->x && cur->y == ps->_pSnake->y)
		{
			ps->_status = KILL_BY_SELF;
			break;
		}
		cur = cur->next;
	}
}
void SnakeMove(pSnake ps)
{
	//创建一个结点,表示蛇即将到的下一个节点
	pSnakeNode pNextNode = (pSnakeNode)malloc(sizeof(SnakeNode));
	if (pNextNode == NULL)
	{
		perror("SnakeMove");
		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);
	//检测蛇是否撞到自己
	KillBySelf(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(24, 12);
	switch (ps->_status)
	{
	case END_NORMAL:
		wprintf(L"您主动结束游戏\n");
		break;
	case KILL_BY_WALL:
		wprintf(L"您撞到墙上,游戏结束\n");
		break;
	case KILL_BY_SELF:
		wprintf(L"您撞到了自己,游戏结束\n");
		break;
	}

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

5.3test.c

#define _CRT_SECURE_NO_WARNINGS
#include<locale.h>
#include"Snake.h"

//完成的是游戏的测试逻辑

void test()
{
	int ch = 0;
	do
	{
		system("cls");
		//创建贪吃蛇
		Snake snake = { 0 };
		//初始化游戏
		//1.先设置窗口的大小,再光标隐藏
		//2. 打印环境界面和功能介绍
		//3. 绘制地图
		//4. 创建蛇
		//5. 创建食物
		GameStart(&snake);

		//运行游戏
		GameRun(&snake);
		//结束游戏 - 善后工作
		GameEnd(&snake);
		SetPos(20, 15);
		printf("再来一局吗?(Y/N):");
		ch = getchar();
		while (getchar() != '\n');
	}while(ch == 'Y' || ch == 'y');
	SetPos(0, 27);
}
int main()
{
	srand((unsigned int)time(NULL));
	//设置适配本地环境
	setlocale(LC_ALL, "");
	test();
}

结语:

这篇文章全文由作者手写,图片由画图软件所制,无AI制作,希望各位博友能有所收获
欢迎各位博友的讨论,觉得不错的小伙伴,别忘了点赞关注哦~

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐