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

news2024/11/17 17:45:23

文章目录

  • 前言
  • 一、游戏开始界面设计
    • 首先 - 打印环境界面
    • 其次 - 游戏地图、蛇身及食物的设计
      • 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;
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【飞桨AI实战】人体姿态估计:零基础入门,从模型训练到应用开发

前言 本次分享将带领大家从 0 到 1 完成一个人体姿态估计任务&#xff0c;覆盖数据准备、模型训练、推理部署和应用开发的全流程&#xff0c;项目将采用以PaddlePaddle为核心的飞桨深度学习框架进行开发&#xff0c;并总结开发过程中踩过的一些坑&#xff0c;希望能为有类似项…

模电期末复习(二)放大电路的基本原理和分析方法

放大电路的基本原理和分析方法 2.1 放大的概念2.2 放大电路的主要技术指标2.3 单管共发射极放大电路2.3.1 单管共发射极放大电路的组成2.3.2 单管共射放大电路的工作原理 2.4 放大电路的基本分析方法2.4.1 直流通路与交流通路2.4.2 静态工作点的近似估算2.4.3 图解法&#xff…

第23天:安全开发-PHP应用后台模块SessionCookieToken身份验证唯一性

第二十三天 一、PHP后台身份验证模块实现 二、Cookie&Session技术&差异 1.生成cookie的原理图过程&#xff1a;见上图 客户端向服务器发送HTTP请求。服务器检查请求头中是否包含cookie信息。如果请求头中包含cookie信息&#xff0c;则服务器使用该cookie来识别客户端…

C++奇迹之旅:构造函数和析构函数

文章目录 &#x1f4dd;类的6个默认成员函数&#x1f320; 构造函数&#x1f309; 概念&#x1f309;特性&#x1f309;三种默认构造函数 &#x1f320; 析构函数&#x1f320; 特性&#x1f6a9;总结 &#x1f4dd;类的6个默认成员函数 如果一个类中什么成员都没有&#xff0…

OpenHarmony其他工具类—lua

简介 Lua是一种功能强大、高效、轻量级、可嵌入的脚本语言。 支持过程编程、面向对象编程、函数编程、数据驱动编程和数据描述。 下载安装 直接在OpenHarmony-SIG仓中搜索lua并下载。 使用说明 以OpenHarmony 3.1 Beta的rk3568版本为例 将下载的lua库代码存在以下路径&#…

javase__进阶 day13stream流和方法引用

1.不可变集合 1.1 什么是不可变集合 ​ 是一个长度不可变&#xff0c;内容也无法修改的集合 1.2 使用场景 ​ 如果某个数据不能被修改&#xff0c;把它防御性地拷贝到不可变集合中是个很好的实践。 ​ 当集合对象被不可信的库调用时&#xff0c;不可变形式是安全的。 简单…

von Mises-Fisher Distribution (代码解析)

torch.distribution 中包含了很多概率分布的实现&#xff0c;本文首先通过均匀分布来说明 Distribution 的具体用法, 然后再解释 von Mises-Fisher 分布的实现, 其公式推导见 von Mises-Fisher Distribution. 1. torch.distribution.Distribution 以下是 Uniform 的源码: cl…

黑灰产行业简介

参考&#xff1a;2021年黑灰产行业研究及趋势洞察报告 1. 有哪些场景面临大量黑灰产攻击&#xff1f; 1.营销活动场景 -- 该场景最为猖獗 1. 抹机及接码注册&#xff1a;黑灰产会使用抹机工具修改设备参数伪装成一台新设备&#xff0c;再配合联系卡商进行手机号接码&#xf…

面试(05)————Redis篇

目录 一、项目中哪些地方使用了redis 问题一&#xff1a;发生了缓存穿透该怎么解决&#xff1f; 方案一&#xff1a;缓存空数据 方案二&#xff1a;布隆过滤器 模拟面试 问题二&#xff1a; 发生了缓存击穿该怎么解决&#xff1f; 方案一&#xff1a;互斥锁 方案二&#xff…

vue3:树的默认勾选和全选、取消全选

实现的功能&#xff0c;上面有个选择框&#xff0c;当选中全部时&#xff0c;下方树被全选 代码&#xff1a; <template><div><el-select v-model"selectAll" style"margin-bottom: 10px;" change"handleSelectAllChange">&…

Vue2 —— 学习(九)

目录 一、全局事件总线 &#xff08;一&#xff09;全局总线介绍 关系图 对图中的中间商 x 的要求 1.所有组件都能看到 2.有 $on $off $emit &#xff08;二&#xff09;案例 发送方 student 接收方 二、消息订阅和发布 &#xff08;一&#xff09;介绍 &#xff08…

4.6 CORS 支持跨域

CORS (Cross-Origin Resource Sharing &#xff09;是由 W3C 制定的一种跨域资源共享技术标准&#xff0c;其目的就是为了解决前端的跨域请求。在 Java EE 开发中&#xff0c;最常见的前端跨域请求解决方案是 JSONP &#xff0c;但JSONP 只支持 GET 请求&#xff0c;这是 个很大…

数据结构之排序了如指掌(三)

目录 题外话 正题 快速排序 Hoare法 Hoare法思路 Hoare法代码详解 挖坑法 挖坑法思路 挖坑法代码 前后指针法 前后指针法思路 前后指针法代码 小结 题外话 我们接着把没有写完的排序内容完成,快速排序其实大同小异,大家好好把思路整理一下 正题 快速排序 快速排序一…

HarmonyOS 状态管理

在声明式 UI 框架中&#xff0c;数据的改变触发 UI 的重新渲染。在 ArkUI 中不是所有数据的变化都会触发 UI 重新渲染&#xff0c;只有 状态变量 才会引起 UI 重新渲染。 状态变量 状态变量&#xff1a; 指被状态装饰器装饰的变量&#xff0c;只有这种变量的改变才会引起 UI …

(2022级)成都工业学院数据库原理及应用实验七: 数据库安全

写在前面 1、基于2022级软件工程/计算机科学与技术实验指导书 2、成品仅提供参考 3、如果成品不满足你的要求&#xff0c;请寻求其他的途径 运行环境 window11家庭版 Navicat Premium 16 Mysql 8.0.36 实验要求 1、创建数据库hospital,在hospital数据库中创建科室表De…

第一个STM32F767IGT6核心板

一. 制作原因 起先是因为参加计算机设计大赛准备的板子&#xff0c;其作用是连接OV5640摄像头来识别车牌号&#xff0c;主要外设有摄像头&#xff0c;SDRAM&#xff0c;网口等。 二. 原理图和PCB 原理图 PCB 三. 测试 1. 测试SDRAM功能 按下按键我们可以在串口中看到内存…

LTspice/Simplis仿真代码使用

LTspice/Simplis仿真代码使用 HI uu们 HI uu们,关注我的uu经常看到我上面贴了很多仿真代码,但是不知道怎么用. 今天来介绍下仿真代码怎么用, 比如说,我们用OP07做了一个跟随器,如图1所示. 图1:OP07跟随器 他的仿真代码非常简单,如下所示 XU1 N002 vout N001 N003 vout LT…

【Linux】详解进程通信中信号量的本质同步和互斥的概念临界资源和临界区的概念

一、同步和互斥的概念 1.1、同步 访问资源在安全的前提下&#xff0c;具有一定的顺序性&#xff0c;就叫做同步。在多道程序系统中&#xff0c;由于资源有限&#xff0c;进程或线程之间可能产生冲突。同步机制就是为了解决这些冲突&#xff0c;保证进程或线程之间能够按照既定…

都以为他是自杀20年后遗书曝光才明白张国荣的离世不简单

在时光的长河中&#xff0c;有些瞬间如同流星划过天际&#xff0c;短暂却又璀璨夺目。二十年前的那个春天&#xff0c;一个灵魂从这个喧嚣世界悄然离去&#xff0c;留下无尽的叹息与疑问。他就是张国荣——一位被誉为“风华绝代”的巨星。许多人曾认定他的离开是一场悲剧性的自…

计算机网络 -- 多人聊天室

一 程序介绍和核心功能 这是基于 UDP 协议实现的一个网络程序&#xff0c;主要功能是 构建一个多人聊天室&#xff0c;当某个用户发送消息时&#xff0c;其他用户可以立即收到&#xff0c;形成一个群聊。 这个程序由一台服务器和n个客户端组成&#xff0c;服务器扮演了一个接受…