贪吃蛇的实现(二)

一、前言

        通过上一篇文章----《贪吃蛇的实现(一)》,讲述了贪吃蛇所要用到的控制台的函数,那么本章要实现的就是贪吃蛇游戏的运行,接下来,就请我们一起来学习吧,此外在这里附上上一篇文章的链接,以供大家了解控制台函数的相关知识。贪吃蛇的实现(一)-CSDN博客

二、贪吃蛇的游戏框架

2.1准备阶段

        准备阶段总共分为三个:吃蛇进入界面的设置、贪吃蛇围墙、蛇身和食物的初始化。

2.2游戏运行阶段

        游戏运行阶段所要注意的细节就多了,包括贪吃蛇的移动和移动快慢,用来控制贪吃蛇的按键设置,游戏暂停、退出、游戏的运行状态(正常、撞自己死亡、撞墙死亡、正常结束)等。

2.3游戏退出后阶段

        当游戏结束后,我们应做好游戏的善后工作,及时的释放蛇身的空间,并提示玩家是否再玩一局,如果不是则完全退出游戏。

三、主程序设计

        主程序设计主要有三种:游戏初始化、游戏运行、游戏结束。

四、结构定义

        贪吃蛇的实现是基于链表的结构,所以应对蛇定义一个链表的结构,而蛇的全局情况则分为多类:蛇的状态、蛇的移动、蛇本身、食物、蛇的方向、当前获得的分数、每个食物的分数、每走一步所要休眠的时间。

具体定义如下:

//蛇节点的定义
typedef struct SNAKE
{
	int x;
	int y;
	struct SNAKE* next;
}SnakeSelf;

//蛇的状态
enum SnakeState
{
	OK = 1,
	Kill_By_Wall,
	Kill_By_Self,
	Exit
};

//蛇的方向
enum Direction
{
	Up = 1,
	Down,
	Left,
	Right
};

//蛇本身的情况
typedef struct SNAKESTATE
{
	SnakeSelf* snake;
	SnakeSelf* food;
	enum SnakeState;
	enum Direction;
	int score;
	int food_score;
	int Sleep_time;
}snakestate;

五、游戏程序设计

5.1游戏初始化

        初始化贪吃蛇,首先要设置窗口大小,再绘制欢迎界面、游戏地图、贪吃蛇蛇身建立和食物的生成。

5.1.1设置窗口大小
//0.先设置窗口大小,再光标隐藏
system("mode con cols=100 lines=35");
system("title 贪吃蛇");
HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);
//隐藏操作
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(houtput, &CursorInfo);
CursorInfo.bVisible = false;
SetConsoleCursorInfo(houtput, &CursorInfo);
5.1.2绘制欢迎界面

        绘制欢迎界面,首先要将光标定位,再根据界面中合适的位置进行打印游戏规则。

//光标设置
void Set_cur_pos(short x, short y)
{
	HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);
	COORD pos = { x,y };
	SetConsoleCursorPosition(houtput, pos);
}

// 欢迎界面
void WelcomeToGame()
{
	//光标定位
	Set_cur_pos(40, 13);//光标定位
	wprintf(L"欢迎来到贪吃蛇小游戏\n");
	Set_cur_pos(41, 25);
	system("pause");
	system("cls");
	Set_cur_pos(25, 13);
	wprintf(L"用↑.↓.←.→控制贪吃蛇的方向,F3为加速,F4为减速");
	Set_cur_pos(25, 14);
	wprintf(L"吃掉食物可以获得更高的分数\n");
	Set_cur_pos(41, 25);
	system("pause");
	system("cls");
}

5.1.3绘制地图

        对于游戏中地图的设置,大家可以根据自己的想法去实现,但是要特别注意的是,系统内部的坐标轴与我们平时理解的并不相同,具体如下:

        如图所示,图上的y轴与我们平时所认知的一样,都是依次增加1,但是如果我们要用符号或者是中文环境打印的话,就是一个中文记号(包括中文文字、符号等)占两个单位,也就是说在打印墙体的时候,要把握好横向和竖向的比例关系,否则就会出现许多意料不到的场景。

//光标设置
void Set_cur_pos(short x, short y)
{
	HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);
	COORD pos = { x,y };
	SetConsoleCursorPosition(houtput, pos);
}

//绘制地图
void CreatMap()
{
	//1.上
	//Set_cur_pos(0, 0);//定位坐标
	for (int i = 0; i < 29; i++)
		wprintf(L"%lc", WALL);
	//2.下
	Set_cur_pos(2,26);
	for (int i = 1 ; i < 29; i++)
	{
		
		wprintf(L"%lc", WALL);
	}
	//3.左
	Set_cur_pos(0, 1);
	for (int i = 0; i < 26; i++)
	{
		Set_cur_pos(0, i+1);
		wprintf(L"%lc", WALL);
	}
	
	//4.右
	Set_cur_pos(58, 1);
	for (int i = 0; i < 25; i++)
	{
		Set_cur_pos(56, i+1);
		wprintf(L"%lc", WALL);
	}
	Set_cur_pos(61, 30);
	
}

        如此一来,一个简易的围墙就建好了。

5.1.4创建蛇身

          对蛇身的定义,我们是基于链表的实现,在创建过程中蛇身的x轴要是一个偶数,因为在游戏运行中,蛇的移动是一格一格的向上下左右移动,如果x轴坐标为奇数,就可能会出现蛇尾卡在墙体中等情况。

      

//初始化蛇身
void SnakeInit(ppsnake pf)
{
	psnake cur = NULL;
	for (int i = 0; i < 3; i++)
	{
		cur = (psnake)malloc(sizeof(SnakeNode));
		if (cur == NULL)
		{
			perror("SnakeInit():malloc");
			return;
		}
		cur->x = POS_X + (i * 2);
		cur->y = POS_Y;
		cur->next = NULL;
		if (pf->snakehead == NULL)
		{
			pf->snakehead = cur;
		}
		else
		{
			cur->next = pf->snakehead;
			pf->snakehead = cur;
		}
	}
	cur = pf->snakehead;
	while (cur)
	{
		Set_cur_pos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	//设置贪吃蛇属性
	pf->direction = RIGHT;
	pf->food_score = 10;
	pf->sleep_time = 200;
	pf->state = OK;
}
5.1.5创建食物

        食物的创建主要分为3个注意项:一是每次食物的出现都必须是随机的,二是食物必须在围墙的范围内,三是食物的位置不能卡在墙体中或者卡在蛇身的位置中,所以食物节点的生成必须是随机且在范围之内。

//创建食物
void CreatFood(ppsnake pf)
{
	int x = 0;
	int y = 0;
again:
	x = rand() % 53 + 2;
	y = rand() % 25 + 1;
	while (x % 2 != 0)
	{
		x = rand() % 53 + 2;
	}
	psnake cur = pf->snakehead;
	while (cur)
	{
		if (x == cur->x && y == cur->y)
		{
			goto again;
		}
		cur = cur->next;
	}
	//创建食物节点
	psnake tmp = (psnake)malloc(sizeof(SnakeNode));
	if (tmp == NULL)
	{
		perror("SnakeInit():malloc");
		return;
	}
	tmp->x = x;
	tmp->y = y;
	tmp->next = NULL;
	Set_cur_pos(x,y);
	wprintf(L"%lc", FOOD);
	pf->food = tmp;
	//getchar();
}

        至此,游戏初始化就完成了。

5.2运行游戏

        游戏运行环节是整个游戏的核心,它包括了分数的更新、蛇的移动、每走一步蛇的状态的判定、蛇的速度、蛇吃掉食物后的变化等等。

        在写程序前,先把贪吃蛇的头文件中定义的类型一一列举出来 ,供大家参考。

#include<stdio.h>
#include<locale.h>
#include<assert.h>
#include<stdlib.h>
#include<windows.h>
#include<stdbool.h>
#include<time.h>
#define POS_X 24
#define POS_Y 3
#define BODY L'●'
#define WALL L'■'
#define FOOD L'◆'
//键值定义
#define key_val(vk) ( (GetAsyncKeyState(vk) & 0x1) ? 1 : 0 )
//类型定义

//蛇的方向
enum SnakeDirection
{
	UP = 1,
	DOWN,
	LEFT,
	RIGHT
};

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

//贪吃蛇结构的定义
typedef struct SnakeNode
{
	//贪吃蛇的坐标
	int x;
	int y;
	//贪吃蛇的下一个节点
	struct SnakeNode* next;
}SnakeNode, *psnake;

//贪吃蛇定义
typedef struct Snake
{
	//指向蛇头的节点
	psnake snakehead;
	//指向食物的节点
	psnake food;
	//蛇的方向
	enum SnakeDirection direction;
	//蛇的状态
	enum SnakeState state;
	//总分数
	int score;
	//食物的分数
	int food_score;
	//蛇的速度
	int sleep_time;
}Snake,*ppsnake;

游戏运行程序:

//撞墙
void Kill_By_Wall(ppsnake pf)
{
	if (pf->snakehead->x == 0 ||
		pf->snakehead->x == 56 ||
		pf->snakehead->y == 0 ||
		pf->snakehead->y == 26)
	{
		Set_cur_pos(35, 10);
		wprintf(L"%s", L"你撞墙了");
		pf->state = KILL_BY_WALL;
	}
}

//撞到自己
void Kill_By_Self(ppsnake pf)
{
	psnake cur = NULL;
	if (pf->snakehead->next->next)
	{
		cur = pf->snakehead->next->next;
		while (cur)
		{
			if (pf->snakehead->x == cur->x && pf->snakehead->y == cur->y)
			{
				Set_cur_pos(35, 10);
				wprintf(L"%s", L"你撞到自己了");
				pf->state = KILL_BY_SELF;
				break;
			}
			cur = cur->next;
		}
	}
	
}


//蛇的移动
void SnakeMove(ppsnake pf)
{
	psnake tmp = (psnake)malloc(sizeof(SnakeNode));
	if (tmp == NULL)
	{
		perror("SnakeMove():malloc");
		return;
	}
	switch (pf->direction)
	{
	case UP:
		tmp->x = pf->snakehead->x;
		tmp->y = pf->snakehead->y - 1;
		break;
	case DOWN:
		tmp->x = pf->snakehead->x;
		tmp->y = pf->snakehead->y + 1;
		break;
	case LEFT:
		tmp->x = pf->snakehead->x - 2;
		tmp->y = pf->snakehead->y;
		break;
	case RIGHT:
		tmp->x = pf->snakehead->x + 2;
		tmp->y = pf->snakehead->y;
		break;
	}
	if (NextIsFood(pf,tmp))
	{
		EatFood(pf, tmp);//pf是蛇定义的节点,tmp是蛇走的下一个节点的指针
	}
	else
	{
		NoFood(pf, tmp);//pf是蛇定义的节点,tmp是蛇走的下一个节点的指针
	}
	Kill_By_Wall(pf);
	Kill_By_Self(pf);
}


//游戏运行
void GameRun(ppsnake pf)
{
	//打印帮助信息
	PrintHelpInfo(pf);
	do
	{
		Set_cur_pos(65, 10);
		printf("总分数:%d,当前食物分数:%d", pf->score, pf->food_score);
		if (key_val(VK_UP) && pf->direction != DOWN)
		{
			pf->direction = UP;
		}
		else if (key_val(VK_DOWN) && pf->direction != UP)
		{
			pf->direction = DOWN;
		}
		else if (key_val(VK_LEFT) && pf->direction != RIGHT)
		{
			pf->direction = LEFT;
		}
		else if (key_val(VK_RIGHT) && pf->direction != LEFT)
		{
			pf->direction = RIGHT;
		}
		//暂停
		else if (key_val(VK_SPACE))
		{
			Pause();
		}
		//加速
		else if (key_val(VK_F3))
		{
			if (pf->sleep_time > 80)
			{
				pf->sleep_time -= 30;
				pf->score += 2;
			}
		}
		//减速
		else if (key_val(VK_F4))
		{
			if (pf->score>2)
			{
				pf->sleep_time += 30;
				pf->score -= 2;
			}
		}
		//退出
		else if (key_val(VK_ESCAPE))
		{
			pf->state = EXIT;
		}
		SnakeMove(pf);
		Sleep(200);
	} while (pf->state == OK);
}

        贪吃蛇的运行程序比较繁杂,接下来我们一起剖析一下。

        运行程序开始后,要首先时时判别蛇的运行状态,当蛇的状态为OK时,则一直循环,然后是系统要接受键盘上传入的信息,是上、下、左、右,还是暂停或正常退出,当为上下左右时,则要对蛇身的节点进行依次改变,如果下一节点的坐标与食物的坐标相同,则要增加蛇身的长度,如果下一节点为 自己蛇身的节点或是墙体的节点,那么则会将蛇的状态置为异常,循环结束。

5.3游戏结束

        当游戏结束后,对于建立的蛇身的节点要依次释放掉,并给玩家选择,是要退出游戏还是再来一局。

//游戏结束
void GameOver(ppsnake pf)
{
	system("cls");
	Set_cur_pos(45, 10);
	switch (pf->state)
	{
	case EXIT:
		printf("您主动退出,游戏结束");
		break;
	case KILL_BY_WALL:
		printf("您撞到了墙,游戏结束");
		break;
	case KILL_BY_SELF:
		printf("您撞到了自己,游戏结束");
		break;
	}
	psnake cur = pf->snakehead;
	while (cur)
	{
		pf->snakehead = pf->snakehead->next;
		cur->x = 0;
		cur->y = 0;
		free(cur);
		cur = pf->snakehead;
	}
}

最后,附上贪吃蛇的主程序。

//游戏测试
void test()
{
	char ch;
	do
	{
		//创建贪吃蛇
		Snake snake = { 0 };
		//初始化游戏
		SnakeStart(&snake);
		//运行游戏
		GameRun(&snake);
		//游戏结束(善后工作)
		GameOver(&snake);
		system("cls");
		Set_cur_pos(43, 15);
		printf("再玩一局(Y)");
		Set_cur_pos(43, 16);
		printf("退出游戏(N)");
		ch = getchar();
	} while (ch == 'Y' || ch == 'y');
}

int main()
{
	//设置设备本地化
	setlocale(LC_ALL, "");
	srand((unsigned)time(NULL));
	test();
	return 0;
}

  • 30
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值