挑战!贪吃蛇小游戏的实现(3)

经过(1)(2)两篇文章的介绍,相信大家对该游戏的实现已经有了具体的思路,废话不多说,让我们开始实现相关的代码吧!

 1.游戏主逻辑


void test()
{
	int ch = 0;
	srand((unsigned int)time(NULL));
	do
	{
		Snake snake = { 0 };
		GameStart(&snake);
		GameRun(&snake);
		GameEnd(&snake);
		SetPos(20, 15);
		printf("再来一局吗?(Y/N):");
		ch = getchar();
	} while (ch == 'Y');
	SetPos(0, 27);
}
int main()
{
	setlocale(LC_ALL, "");//修改当前地区为本地模式
	test();
	return 0;
}

2.游戏开始

void GameStart(pSnake ps)
{
	system("mode con cols=100 lines=30");//设置控制台窗口大小为30行100列
	system("title 贪吃蛇");//设置窗口名
	HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);//获取标准输出的句柄
	CONSOLE_CURSOR_INFO CursorInfo;
	GetConsoleCursorInfo(houtput, &CursorInfo);//获取控制台光标信息
	CursorInfo.bVisible = false;//隐藏控制台光标
	SetConsoleCursorInfo(houtput, &CursorInfo);//设置控制台光标状态
	//打印欢迎界面
	WelcomeToGame();
	//打印地图
	CreateMap();
	//初始化蛇
	InitSnake(ps);
	//创造第一个食物
	CreateFood(ps);
}

2.1打印欢迎界面

在游戏正式开始之前,做一些功能提醒

void WelcomeToGame()
{
	SetPos(40, 15);
	printf("欢迎来到贪吃蛇小游戏");
	SetPos(40, 25);
	system("pause");
	system("cls");
	SetPos(25, 12);
	printf("用↑ ↓ ← →分别控制蛇的移动,F3为加速,F4为减速");
	SetPos(25, 13);
	printf("加速能够获得更高的分数");
	SetPos(40, 25);//让任意键继续出现的位置好看些
	system("pause");
	system("cls");
}

2.2创建地图

墙体打印的宽字符 #define WALL L'✖'

创建地图函数CreateMap

void CreateMap()
{
	int i = 0;
	SetPos(0, 0);
	for (i = 0; i <= 56; i += 2)
	{
		wprintf(L"%lc", WALL);
	}
	SetPos(0, 26);
	for (i = 0; i <= 56; i += 2)
	{
		wprintf(L"%lc", WALL);
	}
	for (i = 1; i < 26; i++)
	{
		SetPos(0, i);
		wprintf(L"%lc", WALL);
	}
	for (i = 1; i < 26; i++)
	{
		SetPos(56, i);
		wprintf(L"%lc", WALL);
	}
}

2.3初始化蛇身

蛇最开始的长度为5节,每节对应链表的一个节点,蛇身的每一个节点都有自己的坐标。创建5个节点,然后将每一个节点放在链表中进行管理。创建完蛇身后,将蛇的每一节打印在屏幕上,然后再设置当前游戏的1状态,蛇移动的速度,默认的方向,初始成绩,蛇的状态和每个食物的分数。

蛇身打印的宽字符: #define BODY L'〇'

初始化蛇身函数InitSnake 

#define POS_X 24

#define POS_Y 5

void InitSnake(pSnake ps)
{
	pSnakeNode cur = NULL;
	int i = 0;
	//创建蛇身节点并初始化坐标,头插法
	for (i = 0; i < 5; i++)
	{
		cur = (pSnakeNode)malloc(sizeof(SnakeNode));
		if (cur == NULL)
		{
			perror("InitSnake()::malloc()");
			return;
		}
		cur->next = NULL;
		cur->x = POS_X + i * 2;
		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->_sleepTime = 200;
	ps->_Score = 0;
	ps->_foodWeight = 10;
	ps->_Dir = RIGHT;
	ps->_Status = OK;
}

2.4创建食物

  • 先随机生成食物的坐标:x坐标必须是2的倍数并且食物的坐标不能和蛇身的每个节点坐标重复
  • 创建食物节点,打印食物,食物打印宽字符:#define FOOD L'❤'

创建食物的函数CreateFood

void CreateFood(pSnake ps)
{
	int x = 0;
	int y = 0;
again:
	do
	{
		x = rand() % 53 + 2;
		y = rand() % 25 + 1;
	} while (x % 2 != 0);//产生的x坐标应该是2的倍数,这样才能和蛇头对齐
	pSnakeNode cur = ps->_pSnake;//获取指向蛇头的指针
	while (cur)
	{
		if (cur->x == x && cur->y == y)
		{
			goto again;
		}
		cur = cur->next;
	}

	pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode));//创建食物
	if (pFood == NULL)
	{
		perror("CreateFood::malloc()");
		return;
	}
	else
	{
		pFood->x = x;
		pFood->y = y;
		SetPos(pFood->x, pFood->y);
		wprintf(L"%lc", FOOD);
		ps->_pFood = pFood;
	}

}

3.游戏运行

  • 游戏运行期间,右侧帮助打印信息,提示玩家
  • 根据游戏状态检查游戏是否继续
  • 如果游戏继续,继续检测按键状态,确定蛇下一步移动的方向以及是否加速减速暂停退出
  • 确定了上述信息后,蛇继续移动
void GameRun(pSnake ps)
{
	PrintHelpInfo();
	do
	{
		SetPos(64, 10);
		printf("得分:%5d", ps->_Score);
		printf("每个食物得分:%02d", ps->_foodWeight);
		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_ESCAPE))
		{
			ps->_Status = END_NORMAL;
			break;
		}
		else if (KEY_PRESS(VK_SPACE))
		{
			pause();
		}
		else if (KEY_PRESS(VK_F3))//加速,休眠时间减少,每次得分增加
		{
			if (ps->_sleepTime >= 50)
			{
				ps->_sleepTime -= 30;
				ps->_foodWeight += 2;
			}
		}
		else if (KEY_PRESS(VK_F4))
		{
			if (ps->_foodWeight > 2)
			{
				ps->_sleepTime += 30;
				ps->_foodWeight -= 2;
			}
		}

		Sleep(ps->_sleepTime);//睡眠一下
		SnakeMove(ps);//走一步
	} while (ps->_Status == OK);
}

3.1KEY_PRESS

为了检测按键状态,我们封装了一个宏

#define KEY_PRESS(VK) ((GetAsyncKeyState(VK)&0x1)?1:0)

3.2打印帮助信息PrintHelpInfo

void PrintHelpInfo()
{
	//打印提示信息
	SetPos(64, 15);
	printf("不能穿墙,不能咬到自己");
	SetPos(64, 16);
	printf("用↑↓←→分别控制蛇的移动");
	SetPos(64, 17);
	printf("F3为加速,F4为减速");
	SetPos(64, 18);
	printf("ESC:退出游戏 space:暂停游戏");
}

3.3蛇身移动

  • 先创建下一个节点,根据移动方向和蛇头的坐标,蛇移动到下一个位置的坐标
  • 确定了下一个位置后,看下一个位置是否是食物(NextIsFood),是食物就做吃食物处理(EatFood),不是食物就做前进一步处理(NoFood)。
  • 蛇身移动以后,判断此次移动是否会撞墙(KillByWall)或者撞到自己(KillBySelf),从而影响游戏的状态
void SnakeMove(pSnake ps)
{
	//创建下一个节点
	pSnakeNode pNextNode = (pSnakeNode)malloc(sizeof(SnakeNode));
	if (pNextNode == NULL)
	{
		perror("SnakeMove()::malloc()");
		return;
	}
	pNextNode->next = NULL;
	//要根据蛇头的坐标和方向确定下一个节点
	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);

}

NextIsFood

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

EatFood


void EatFood(pSnakeNode psn, pSnake ps)//第一个参数是下一个节点的指针,第二个参数是维护蛇的指针
{
	//头插法
	psn->next = ps->_pSnake;
	ps->_pSnake = psn;
	pSnakeNode cur = ps->_pSnake;
	//打印蛇
	while (cur)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	ps->_Score += ps->_foodWeight;
	free(ps->_pFood);//释放食物节点
	CreateFood(ps);//创建新的食物
}

NoFood

将下一个节点头插入蛇的身体,并且将之前蛇身的最后一个节点打印为空格,放弃掉原来蛇身的最后一个节点

void NoFood(pSnakeNode psn, pSnake ps)//将下一个节点插入蛇的身体,并且将蛇身的最后一个节点打印为空
{
	//头插法
	psn->next = ps->_pSnake;
	ps->_pSnake = psn;
	pSnakeNode cur = ps->_pSnake;
	//打印蛇
	while (cur->next->next)
	{
		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;	
}

KillByWall

判断蛇头的坐标是否与墙体坐标冲突

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

KillBySelf

判断蛇头的坐标是否和蛇身冲突

void KillBySelf(pSnake ps)//从第二个节点开始检测是否和头相撞了
{
	pSnakeNode cur = ps->_pSnake->next;
	while (cur)
	{
		if ((ps->_pSnake->x == cur->x) && (ps->_pSnake->y == cur->y))
		{
			ps->_Status = KILL_BY_SELF;
		}
	}
}

4.游戏结束

当游戏状态不再是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);
	}
	free(ps->_pFood);
	ps->_pFood = NULL;
	ps->_pSnake = NULL;
}

以上就是贪吃蛇小游戏的全部核心代码啦,完整的代码请大家移步我的码云:

https://gitee.com/peach-table

新年新气象!让我们用一条贪吃蛇来迎接2024的好运吧~

祝大家新的一年身体健康万事如意,发!大!财!☼

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值