【C语言】贪吃蛇项目(2)- 实现代码详解


前言

在笔者的前一篇博客中详细记载了贪吃蛇项目所需的一些必备知识以及我们进行贪吃蛇项目的整体大致思路,有需要的朋友可以自行看下 https://blog.csdn.net/2301_77954967/article/details/137881771?spm=1001.2014.3001.5501

此外,需要提醒的是,如果你也想要自己写出这样的贪吃蛇程序,你最好进行区块分类,在头文件中将所有方法文件中用到的功能先写出来。

对于等等写着写着忘记原来这个方法干嘛用的,可以点击你想要找的内容,再按 F12 就可以回溯到这个方法的最开始的编写部分了

一、游戏开始界面设计

作为一款雄安游戏我们需要有一个简单的进入界面设计,以及一些简单的游戏规则介绍,并在此之后初始化游戏的主体,包括食物,边框以及蛇身和它所需要的一系列参数。

我们一步一步来

首先 - 打印环境界面

当然为了整体性美观,我们需要将控制台的光标给隐藏起来,并且给这个控制台命名初始化边框,这就需要用到上一篇博客中所介绍的一系列控制台函数
代码如下:

	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);//设置控制台光标状态

紧接着初始化操作,我们就需要在控制台界面上打印一些欢迎游戏的字样,对此,我们需要用到上一篇博客中用到的定位光标的控制台方法,然后再打印,这里再把定位光标的方法展现一下

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

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

定位好想要打印的欢迎游戏的位置后,考研选择用宽字符的方法打印,也可以就直接用printf输出,至于要打印什么字就看个人需要了,还需要注意的一点是,我们可以使用**getchar()来暂停这个程序,更好的判断当前部分代码的正确性。
此外我们还可以利用
system(“pause”)**来暂停所运行代码,实现界面与用户交互后发生变化的能力
代码如下

//欢迎界面
void WelcomeToGame()
{
	SetPos(40, 14);
	wprintf(L"欢迎来到贪吃蛇小游戏\n");

	SetPos(42, 20);
	system("pause");
	system("cls");//清理屏幕

	SetPos(28, 14);
	wprintf(L"用↑.↓.←.→来控制蛇的移动,按F3加速,F4减速\n");

	SetPos(28, 15);
	wprintf(L"加速能够得到更高的分数\n");

	SetPos(42, 20);
	system("pause");
	system("cls");//清理屏幕
}
//打印帮助信息
void PrintfHelpInfo()
{
	SetPos(64, 13);
	wprintf(L"%ls", L"游戏规则:");

	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"JoknKi制作");
}

运行后的第一个界面
在这里插入图片描述
点击后发生改变
在这里插入图片描述

其次 - 游戏地图、蛇身及食物的设计

既然要进入游戏了,然基本的游戏界面必须搭建好

1、地图

我们先说说这个地图,我们需要知道游戏地图他的本质上还是控制台界面,可以理解为是由无数个高宽2:1的长方形组成的,左上角为原点,向右x轴增,向下y轴增,因此我们通过输出宽字符来创建游戏的边宽,需要注意,每次x加2,y加1,才能实现一个方方正正的边框
在这里插入图片描述
代码如下

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

	//下
	SetPos(0, 26);
	for (int i = 0; i < 29; i++)
	{
		wprintf(L"%c", L'□');
	}
	 
	//左
	for (int i = 1; i <= 25; i++)
	{
		SetPos(0, i);
		wprintf(L"%c", L'□');
	}
	
	//右
	for (int i = 1; i <= 25; i++)
	{
		SetPos(56, i);
		wprintf(L"%c", L'□');
	}

}

这里提一嘴,关于宽字符 ‘□’ 我们可以通过定义宏的方式让它不用反复输入,导致出现失误,同时也可以是边框的灵活性更高,以后想换边框时,更改宏定义里的宽字符就行了

2、蛇身设置及打印

蛇身的整体和各个节点在上一篇文章中已经建好了,这里利用了单链表的知识,我们同样利用宏设定初始点的坐标,然后再利用next成员名不断设置下一节点,之后遍历打印就OK了

当然仅此如此还不够,我们还必须将蛇的整体上的各个部分都设置一下,包括当前状态、食物坐标等,为其封装一个完善的结构,方便后续进行使用和判断。

当然这里的各个数据绝大多数都是先规划好运行要求等,再写进之前创建的蛇整体结构体里,当然后续如果有需要也可以额外补充进来,不要忘了就是了

代码如下

// 4.初始化蛇
void InitSnake(pSnake ps)
{
	int i = 0;
	pSnakeNode cur = NULL;

	for (int i = 0; i < 5; i++)
	{
		cur = (pSnakeNode)malloc(sizeof(SnakeNode));
		if (cur == NULL)
		{
			perror("InitSnake()::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、食物

食物作为贪吃蛇游戏的核心进行,必须具有随机性与趣味性,因此必须用到随机数,也就是利用C语言的 #include<time.h> 及其相关用法

因此我们不仅要包含头文件还需要,在开头定义 srand 即

	srand((unsigned int)time(NULL));

这样一来就设定好了

然后我们需要保证当前的食物不能与蛇的身体覆盖,这就需要遍历就行了,也还好,不难,这里介绍一下 gotoagain 可以在小范围内实现很便捷的循环功能
最后打印,代码如下

// 5.创建食物
void CreatFood(pSnake ps)
{
	int x = 0;
	int y = 0;
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()::malloc()");
		return;
	}
	pFood->x = x;
	pFood->y = y;
	pFood->next = NULL;

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

	ps->_pfood = pFood;
}

二、游戏运行环节

作为一个游戏,除了主要的游戏区域,我们还需要提示和记录的部分,像上面的欢迎信息一样打印出来就行了,打印什么内容也是根据个人喜欢所定

蛇的上下左右移动等功能

就像前面欢迎界面所介绍的那样我们除了要让蛇上下左右移动外,我们还需要加速、减速、暂停、退出等,因此我们可以通过 case 来实现。

		//打印总分数和食物的分值
		SetPos(64, 10);
		printf("总分数:%2d", ps->_score);
		SetPos(64, 11);
		printf("当前食物的分数是:%2d", 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;
			}
		}

		//贪吃蛇走一步

蛇的移动

输入好状态后,首当其冲的就是蛇的移动了

蛇的移动作为重中之重,我们不仅要考虑如何使他位移,嗨哟啊考虑位移后的状态

  • 有食物
  • 撞墙
  • 撞自己
  • 仅移动

首先我们先利用接待你是设进行最基本的唯一,就是说不管最后这么状态,先移动了再说,动完在判断

但是这里的移动不是完全移动,仅仅头节点的移动,也就是说我们创建一个新的接待你,用它根据当前运动趋势确定坐标,至于整个蛇的打印就需要根据具体情况分类讨论了

因此我们可以得出以下代码

	//创建一个节点,表示蛇即将到的下一个节点
	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;
	}

紧接着我们需要判断它这个点有没有食物,有没有撞墙,有没有撞到自己
因此我们需要写出下面三个方法

//检测下一个坐标是否是食物
int NextIsFood(pSnakeNode pn, pSnake ps)
{
	return(ps->_pfood->x == pn->x && ps->_pfood->y == pn->y);
}
//撞墙
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;
		return 1;
	}
	return 0;
}
//撞到自己
void KillBySelf(pSnake ps)
{
	pSnakeNode cur = ps->_pSnake->next;
	while (cur)
	{
		if (ps->_pSnake->x == cur->x && ps->_pSnake->y == cur->y)
		{
			return 1;
		}
		cur = cur->next;
	}
	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;
	//}
	SetPos(cur->x, cur->y);
	wprintf(L"%lc", BODY);
	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 != NULL)
	{
		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;

	KillBySelf(ps);//撞自己

	KillByWall(ps);//撞墙

}

这里程序运行的方法都讲清楚了,但是为了使程序真正运行起来,我们必须对上面这一整块内容进行while语句循环,判断条件就是当前状态是否正常即

//游戏运行逻辑
void GameRun(pSnake ps)
{
	//打印帮助信息
	PrintfHelpInfo();

	do
	{
		//打印总分数和食物的分值
		SetPos(64, 10);
		printf("总分数:%2d", ps->_score);
		SetPos(64, 11);
		printf("当前食物的分数是:%2d", 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);
}

这里简单展现一下到目前为止游戏的界面
在这里插入图片描述

三、结束游戏

还记得我们之前定义的额状态吗

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

这里我们就需要根据当前的状态输出不同的结果,所以就可以得到下面这个部分(记得释放空间)

//游戏结束
void GameEnd(pSnake ps)
{
	pSnakeNode cur = ps->_pSnake;
	SetPos(24, 12);
	switch (ps->_status)
	{
	case END_NORMAL:
		printf("您主动退出游戏\n");
		break;
	case KILL_BY_SELF:
		printf("您撞上⾃⼰了 ,游戏结束!\n");
		break;
	case KILL_BY_WALL:
		printf("您撞墙了,游戏结束!\n");
		break;
	}
	//释放蛇⾝的节点
	while (cur)
	{
		pSnakeNode del = cur;
		cur = cur->next;
		free(del);
	}
}

但是为了优化程序,使游戏能够循环运行,也就是询问玩家要不要再玩一遍,我们可以这样做,对整个区域块进行 do while 循环

void test()
{
	int ch = 0;
	do
	{
		system("cls");//清屏
		//创建贪吃蛇
		Snake snake = { 0 };

		//初始化游戏
		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);
}

代码

好了,到此为止所有的内容都讲完了,有兴趣的朋友们自己拿去玩玩吧
我把我的码云链接放在下面了
链接: https://gitee.com/JohnKingW/target-xiamen-university/tree/master/snake/snake
在这里插入图片描述

snake.h

#pragma once
#define _CRT_SECURE_NO_WARNINGS  1
#pragma warning(disable:6031)
#include<stdio.h>
#include<locale.h>
#include<windows.h>
#include<stdbool.h>
#include<stdlib.h>
#include<time.h>

#define POS_X 24
#define POS_Y 5

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

#define KEY_PRESS(vk) ((GetAsyncKeyState(vk)&1)?1:0)//通过虚拟键盘来判断某个按键是否被使用


//类型的声明
//蛇的方向
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 _pfood;//指向食物节点的指针
	enum DIRECTION _dir;//蛇的方向
	enum GAME_STATUS _status;//游戏的状态
	int _food_weight;//每个食物的分数
	int _score;//总成绩
	int _sleep_time;//休息时间,时间越短,速度越快,时间越长,速度越慢
}Snake, * pSnake;




//函数的声明

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

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

//打印介绍
void WelcomeToGame();

//创建地图
void CreatMap();

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

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

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

//游戏结束
void GameEnd(pSnake ps);

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

//检测下一个坐标是否是食物
int NextIsFood(pSnakeNode pn,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);

snake.c

#include "snake.h"

void SetPos(int x, int 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(28, 14);
	wprintf(L"用↑.↓.←.→来控制蛇的移动,按F3加速,F4减速\n");

	SetPos(28, 15);
	wprintf(L"加速能够得到更高的分数\n");

	SetPos(42, 20);
	system("pause");
	system("cls");//清理屏幕
}

//绘制地图
void CreatMap()
{
	//上
	for (int i = 0; i < 29; i++)
	{
		wprintf(L"%c", L'□');
	}

	//下
	SetPos(0, 26);
	for (int i = 0; i < 29; i++)
	{
		wprintf(L"%c", L'□');
	}
	 
	//左
	for (int i = 1; i <= 25; i++)
	{
		SetPos(0, i);
		wprintf(L"%c", L'□');
	}
	
	//右
	for (int i = 1; i <= 25; i++)
	{
		SetPos(56, i);
		wprintf(L"%c", L'□');
	}

}

// 4.初始化蛇
void InitSnake(pSnake ps)
{
	int i = 0;
	pSnakeNode cur = NULL;

	for (int i = 0; i < 5; i++)
	{
		cur = (pSnakeNode)malloc(sizeof(SnakeNode));
		if (cur == NULL)
		{
			perror("InitSnake()::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;//正常状态

}


// 5.创建食物
void CreatFood(pSnake ps)
{
	int x = 0;
	int y = 0;
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()::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 CursorInfo;
	GetConsoleCursorInfo(houtput, &CursorInfo);//获取控制台光标信息
	CursorInfo.bVisible = false;//隐藏控制台光标
	SetConsoleCursorInfo(houtput, &CursorInfo);//设置控制台光标状态

	// 1.打印环境界面
	// 2.功能介绍
	WelcomeToGame();

	// 3.绘制地图
	CreatMap();

	// 4.初始化蛇
	InitSnake(ps);
	
	// 5.创建食物
	CreatFood(ps);

	// 6.设置游戏的相关信息


}

//打印帮助信息
void PrintfHelpInfo()
{
	SetPos(64, 13);
	wprintf(L"%ls", L"游戏规则:");

	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"JoknKi制作");
}

//暂停 -> 睡眠
void pause()
{
	while (1)
	{
		if (KEY_PRESS(VK_SPACE))
		{
			break;
		}
	}
}

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

//吃掉食物
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;
	//}
	SetPos(cur->x, cur->y);
	wprintf(L"%lc", BODY);
	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 != NULL)
	{
		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;

	KillBySelf(ps);

	KillByWall(ps);

}

//撞墙
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;
		return 1;
	}
	return 0;
}

//撞到自己
void KillBySelf(pSnake ps)
{
	pSnakeNode cur = ps->_pSnake->next;
	while (cur)
	{
		if (ps->_pSnake->x == cur->x && ps->_pSnake->y == cur->y)
		{
			return 1;
		}
		cur = cur->next;
	}
	return 0;
}

//蛇走一步
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);
	}



}

//游戏运行逻辑
void GameRun(pSnake ps)
{
	//打印帮助信息
	PrintfHelpInfo();

	do
	{
		//打印总分数和食物的分值
		SetPos(64, 10);
		printf("总分数:%2d", ps->_score);
		SetPos(64, 11);
		printf("当前食物的分数是:%2d", 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)
{
	pSnakeNode cur = ps->_pSnake;
	SetPos(24, 12);
	switch (ps->_status)
	{
	case END_NORMAL:
		printf("您主动退出游戏\n");
		break;
	case KILL_BY_SELF:
		printf("您撞上⾃⼰了 ,游戏结束!\n");
		break;
	case KILL_BY_WALL:
		printf("您撞墙了,游戏结束!\n");
		break;
	}
	//释放蛇⾝的节点
	while (cur)
	{
		pSnakeNode del = cur;
		cur = cur->next;
		free(del);
	}
}

test.c

#include "snake.h"


//完成的是游戏的测试逻辑
void test()
{
	int ch = 0;
	do
	{
		system("cls");
		//创建贪吃蛇
		Snake snake = { 0 };

		//初始化游戏
		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()
{
	//设置适配本地环境
	setlocale(LC_ALL, "");
	srand((unsigned int)time(NULL));

	test();

	return 0;
}

相关推荐

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-04-21 00:50:02       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-04-21 00:50:02       100 阅读
  3. 在Django里面运行非项目文件

    2024-04-21 00:50:02       82 阅读
  4. Python语言-面向对象

    2024-04-21 00:50:02       91 阅读

热门阅读

  1. 个人开发者,Spring Boot 项目如何部署

    2024-04-21 00:50:02       35 阅读
  2. HttpServletResponse HttpServletRequest

    2024-04-21 00:50:02       32 阅读
  3. WPF: XAML语法规范详解

    2024-04-21 00:50:02       37 阅读
  4. 缓存之SpringCache整合redis

    2024-04-21 00:50:02       38 阅读
  5. 国内外大模型最全合集

    2024-04-21 00:50:02       155 阅读
  6. VTK----VTK数据结构详解(几何篇)

    2024-04-21 00:50:02       30 阅读
  7. Vue2 基础四前后端交互

    2024-04-21 00:50:02       27 阅读