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

文章目录

  • 前言
  • 一、游戏开始界面设计
    • 首先 - 打印环境界面
    • 其次 - 游戏地图、蛇身及食物的设计
      • 1、地图
      • 2、蛇身设置及打印
      • 3、食物
  • 二、游戏运行环节
    • 蛇的上下左右移动等功能
    • 蛇的移动
  • 三、结束游戏
  • 代码


前言

在笔者的前一篇博客中详细记载了贪吃蛇项目所需的一些必备知识以及我们进行贪吃蛇项目的整体大致思路,有需要的朋友可以自行看下 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;
}

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

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

相关文章

mysql_explain执行计划字段解析

【README】 本文对 explain打印的执行结果的字段进行解析&#xff1b; 本文总结自&#xff1a; MySQL :: MySQL 8.3 Reference Manual :: 10.8.2 EXPLAIN Output Formathttps://dev.mysql.com/doc/refman/8.3/en/explain-output.html 列名含义id选择标识select_type选择类型…

移动Web学习09-响应式布局bootstrap案例开发

3、综合案例-AlloyTeam移动全端 准备工作 HTML 结构 <title>腾讯全端</title> <link rel"shortcut icon" href"favicon.ico" type"image/x-icon"> <!-- 层叠性&#xff1a;咱们的css 要 层叠 框架的 --> <link rel&…

存储过程的使用(二)

目录 带 OUT 参数的存储过程 输入一个编号&#xff0c;查询数据表 emp中是否有这个编号&#xff0c;如果有返回对应员工姓名&#xff0c;如果没有&#xff0c;则提示没有对应员工 使用 EXEC 命令或者 PRINT执行含有 OUT参数的存储过程 使用 PL/SQL 块编辑程序调用含有 OUT …

JAVA 项目<果园之窗>_2

上节主要是理论流程&#xff0c;这次直接用实际例子过一遍整个流程 目标是向数据库添加一个员工 上述是前端页面&#xff0c;点击保存 浏览器向我后端发送http请求 后端这一部分专门接收employee请求 在这里对http post请求进行转换成JAVA数据&#xff0c;并处理数据&#xff…

Spring Boot后端与Vue前端融合:构建高效旅游管理系统

作者介绍&#xff1a;✌️大厂全栈码农|毕设实战开发&#xff0c;专注于大学生项目实战开发、讲解和毕业答疑辅导。 &#x1f345;获取源码联系方式请查看文末&#x1f345; 推荐订阅精彩专栏 &#x1f447;&#x1f3fb; 避免错过下次更新 Springboot项目精选实战案例 更多项目…

python_列表和元组

介绍 列表&#xff08;List&#xff09;和元组&#xff08;Tuple&#xff09;是Python中两种不同的数据结构&#xff0c;它们都可以用来存储一系列的元素。下面是它们的主要特点和区别&#xff1a; 列表&#xff08;List&#xff09; 可变性&#xff1a;列表是可变的&…

广西模板厂有哪些厂家

在广西地区&#xff0c;建筑行业蓬勃发展&#xff0c;建筑模板作为建筑施工的重要材料&#xff0c;需求量逐渐增加。在这个市场中&#xff0c;贵港市能强优品木业有限公司以其卓越的产品质量和丰富的生产经验而闻名&#xff0c;成为广西地区的知名建筑模板生产厂家。 作为一家具…

OpenCV4.9使用 inRange 的阈值操作

返回:OpenCV系列文章目录&#xff08;持续更新中......&#xff09; 上一篇:OpenCV4.9​​​​基本阈值操作 下一篇&#xff1a;利用OpenCV4.9制作自己的线性滤波器&#xff01; ​目标 在本教程中&#xff0c;您将学习如何&#xff1a; 使用 OpenCV cv&#xff1a;&#xff…

【错题集-编程题】大数乘法(模拟 + 高精度乘法)

题目链接&#xff1a;大数乘法_牛客题霸_牛客网 (nowcoder.com) 一、分析题目 根据列竖式运算的过程模拟即可&#xff0c;但是我们可以改进⼀下列竖式的过程&#xff1a; 先计算⽆进位相乘并且相加后的结果&#xff1b;然后再处理进位。 细节&#xff1a;题目所给的两个字符串…

OpenHarmony 视频播放开发教程~

介绍 本示例主要展示了网络视频播放的相关功能。使用ohos.multimedia.avsession等接口实现视频播放的功能。 效果预览 主页 使用说明 点击播放按钮&#xff0c;应用的播放状态发生变化。点击暂停按钮&#xff0c;应用的播放状态开始变化。点击上一个按钮&#xff0c;界面展…

Ribbon 添加快速访问区域

添加快速访问区域挺简单的&#xff0c;实例如下所示&#xff1a; void QtRightFuncDemo::createQuickAccessBar() { RibbonQuickAccessBar* quickAccessBar ribbonBar()->quickAccessBar(); QAction* action quickAccessBar->actionCustomizeButton(); act…

如何查找一篇英文文献的源代码?(论文中没有源代码链接时)如何查找一篇论文的实现代码从而复现论文?

有两个网址&#xff0c;从这两个网址里面能找到论文相关代码&#xff0c;但不确定是不是人家论文里的源代码&#xff0c;但是根据论文实在找不到的情况下&#xff0c;只能试试这两个网址了 1. https://paperswithcode.com/ 2. https://www.catalyzex.com/

团队协作:如何利用 Gitee 实现多人合作项目的版本控制

文章目录 前言一、名词解释1、Git是什么&#xff1f;2、Gitee、GitHub和GitLab 二、操作步骤1.安装Git2.创建Gitee仓库3.用vscode连接仓库4. 克隆远程仓库 总结 前言 在软件开发中&#xff0c;有效地管理代码是至关重要的。Gitee 是一个功能强大的代码托管平台&#xff0c;提供…

Sentinel 流控注解使用

大概原理&#xff1a;通过反射解析注解 SentinelResource信息完成调用&#xff0c;处理方法&#xff0c;类似AOP编程 处理方法的返回类型要保持一致&#xff0c;参数和顺序保持一致&#xff0c; 可以在参数列表最后加 com.alibaba.csp.sentinel.slots.block.BlockException; …

探索C语言数据结构:利用顺序表完成通讯录的实现

在好久之前我就已经学习过顺序表&#xff0c;但是在前几天再次温习顺序表的时候&#xff0c;我惊奇的发现顺序编表可以完成我们日常使用的通讯录的功能&#xff0c;那么今天就来好好通过博客总结一下通讯录如何完成吧。 常常会回顾努力的自己&#xff0c;所以要给自己的努力留…

【JavaSE】浅谈Java异常

前言 本篇文章是对Java异常体系相关内容及部分注意事项的的讲解。 一. 认识异常 在每个人的生命历程中&#xff0c;或多或少都会遇到生病或受伤的情况&#xff0c;比如&#xff1a;皮肤擦伤、感冒、发烧、患上某些传染病等等。不管“病情”严重与否&#xff0c;这些都可以算…

数据链路层协议——以太网协议

目录 要解决的问题 以太网协议 以太网帧格式 MAC地址 MAC地址和IP地址 MTU MTU对IP协议的影响 MTU对UDP协议的影响 MTU对TCP协议的影响 ARP协议 ARP协议格式 ARP协议的工作流程 ARP缓存表 要解决的问题 上一篇我们也说了&#xff0c;数据从应用层一步步封装到了网…

leetcode:滑动窗口----3. 无重复字符的最长子串

给定一个字符串 s &#xff0c;请你找出其中不含有重复字符的 最长 子串 的长度。 示例 1: 输入: s "abcabcbb" 输出: 3 解释: 因为无重复字符的最长子串是 "abc"&#xff0c;所以其长度为 3。示例 2: 输入: s "bbbbb" 输出: 1 解释: 因为…

工业现场ModbusTCP转EtherNETIP网关引领生物现场领新浪潮

生物质发生器是一种能够产生、培养生物的设备。客户现场需要将生物发生器连接到罗克韦尔系统&#xff0c;但是二者协议无法直接通讯&#xff0c;需要通过开疆智能ModbusTCP转Ethernet/IP网关将两者进行通讯连接&#xff0c;生物质发生器以其独特的工作原理和优势&#xff0c;使…

【命名空间详解】c++入门

目录 命名空间的定义 1.命名空间的正常定义 2.命名空间还可以嵌套 3. 命名空间可以合并 命名空间的使用 1.加命名空间名称及作用域限定符 2.使用using将命名空间中某个成员引入 3.使用using namespace 命名空间名称 引入 输入&#xff0c;输出 输出 命名空间的定义 …