贪吃蛇游戏

发布于:2025-02-19 ⋅ 阅读:(22) ⋅ 点赞:(0)

目录

文章结尾有代码可自取

Win32API

光标的隐藏

获取按键信息

控制光标位置

游戏开始前的准备

游戏准备及介绍

加载和欢迎界面

打印游戏指南

运行游戏

打印墙体和说明

设置蛇的各个信息

初始化及打印蛇

创造食物

运行游戏

1)打印得分情况

2)获取按键信息

3)蛇走的下一步

下一步不是食物

下一步是食物

4)检查是否撞到墙或自己

游戏结束

各个文件代码统计

头文件

源文件

源文件


文章结尾有源码可自取

Win32API

此处首先对Win32API进行简单的介绍,在游戏中光标的隐藏,获得用户按键信息的都会用到;

以下操作要包含头文件<windows.h> 

光标的隐藏

//隐藏光标
void Hide_cursor(void)
{
	//获得输出控制台句柄
	HANDLE houtput = NULL;
	houtput = GetStdHandle(STD_OUTPUT_HANDLE);
	//获得控制台光标信息
	CONSOLE_CURSOR_INFO cursor_info;
	GetConsoleCursorInfo(houtput, &cursor_info);
	//将光标信息中的可见度改为0
	cursor_info.bVisible = 0;
	SetConsoleCursorInfo(houtput, &cursor_info);
}

获取按键信息

可以读取用户按下了哪一个键。

//获取按键信息
#define KEY_PRESS(vk) (GetAsyncKeyState(vk)?1:0)
//vk表示按键
//如:Key_Press(VK_UP)如果用户按下↓,则返回1,否则是0

控制光标位置

在有一些情况不希望将光标放在控制台(黑框)的开始默认位置,我们可能需要在控制台中央打印,此时就需要对控制台光标位置进行定位。

//定位光标位置
void SetPos(size_t x, size_t y)
{
	//获得输出控制台句柄
	HANDLE houtput = NULL;
	houtput = GetStdHandle(STD_OUTPUT_HANDLE);
	// 获取光标
	COORD cursorPos = { 0, 0 };
	
	//修改光标位置
	cursorPos.X = x;
	cursorPos.Y = y;
	SetConsoleCursorPosition(houtput,cursorPos);
}

关于Win32API的知识不过多讲解,对于初学者可以直接使用上述代码实现功能。有兴趣可以看windows的控制台函数。控制台函数 - Windows Console | Microsoft Learnhttps://learn.microsoft.com/zh-cn/windows/console/console-functions


游戏开始前的准备

游戏进行时我们有时候想要打印图案字符,eg:■◯Ő以及一些汉字(宽字符)。我们就需要将程序修改为本地状态,可以理解为不仅仅能够打印外国的英文,还能够打印我们本地中文汉字。

为了有更好的游戏体验,需要对控制台的大小和名称进行修改

#include<locale.h>
void Prepare(void)
{
	//先将程序改成本地模式
	setlocale(LC_ALL, "");
	//先对控制台的大小进行修改
	system("mode con cols=150 lines=40");

	//对名称进行重命名
	system("title 贪吃蛇");
}

游戏准备及介绍

隐藏完光标后,我们进行游戏开始之前的打印,范围两部分,打印加载和欢迎页面;打印游戏指南,方法。所以在游戏开始运行之前有两个页面需要打印。

加载和欢迎界面

先打印第一个封面,欢迎和加载界面。此处将代码封装成立三个函数:打印进度条函数;打印欢迎界面函数。效果如下。

进度条的实现主要是依靠一个数组,通过循环向数组里添加字符,打印数组从而动态的效果。

//加载界面的打印
void ProcessBar(void)
{
	wchar_t bar[102] = { '\0' }; //设置一个字符串来当作进度条
	int cnt = 0;
	while (cnt <= 100)
	{
		SetPos(25, 21);  //定位
		wprintf(GREEN L"[%-101ls]" RESET"[%d%%]", bar, cnt); //打印进度条,并加上颜色
		Sleep(100);      //休眠100毫秒
		bar[cnt] = L'█';
		cnt++;
	}
}

void Print_Page1(void)
{
	//定位光标,打印欢迎语句
	SetPos(65, 18);
	wprintf(YELLOW L"欢迎进入贪吃蛇游戏" RESET);
	//打印加载的进度条
	ProcessBar();
}

可以看到,在上买了的代码中添加了控制输出字符串的颜色,定义如下可自取。

//定义一些颜色
#define RESET       L"\033[0m"           // 恢复默认

// 前景色 (文本颜色)
#define BLACK        L"\033[30m"
#define RED          L"\033[31m"
#define GREEN        L"\033[32m"
#define YELLOW       L"\033[33m"
#define BLUE         L"\033[34m"
#define MAGENTA      L"\033[35m"
#define CYAN         L"\033[36m"
#define WHITE        L"\033[37m"

打印游戏指南

为了让用户有更好的游戏体验,需要对玩法进行说明。第二张画面打印游戏指南。效果如下。

void Print_Page2(void)
{
	system("cls");//清屏
	SetPos(58, 17);
	wprintf(MAGENTA L"用↑↓←→来控制方向,ESC退出,SPACE暂停");
	SetPos(58, 18);
	wprintf(L"F1加速,F2减速;");
	SetPos(58, 19);
	wprintf(L"加速可以获得更多积分,减速获得积分变慢;" RESET);
	
	SetPos(58, 21);
	system("pause");
}

将page1和page2结合。

void game_prepare(void)
{
	//隐藏光标
	Hide_cursor();
	//设置控制台名称和大小
	Prepare();
	//打印第一个画面,进入画面
	Print_Page1();
	//打印游戏指南
	Print_Page2();
}

运行游戏

打印墙体和说明

墙体的长和宽都可以自定义设置。效果如下。

打印墙体的时候要注意临界位置的讨论。

//打印墙体
void Print_Wall(void)
{
	SetPos(0, 0);
	//打印横向
	for (int i = 0; i < LEN; i += 2)  //注意此处是+2,因为宽字符的宽度是2不像普通字符一样
		wprintf(L"%c", WALL);
	SetPos(0, WIDTH);
	for (int i = 0; i < LEN; i += 2)  
		wprintf(L"%c", WALL);

	//打印纵向
	for (int i = 1; i < WIDTH; i++)
	{
		SetPos(0, i);
		wprintf(L"%c", WALL);
		SetPos(LEN-2, i);
		wprintf(L"%c", WALL);
	}
}

//打印说明
void Print_RULE(void)
{
	SetPos(100, 8);
	wprintf(GREEN L"↑↓←→来控制方向");
	SetPos(100, 9);
	wprintf(L"ESC退出,SPACE暂停");
	SetPos(100, 10);
	wprintf(L"F1加速,F2减速");
	SetPos(100, 11);
	wprintf(L"加速可以获得更多积分,减速获得积分变慢" RESET);
}

设置蛇的各个信息

在打印蛇之前,需要考虑设置什么样的信息。此处首先就是蛇身的各个节点,还需要蛇的方向,状态,蛇的得分,以及食物信息,所以要创建多个结构体:蛇的各个信息,蛇的每个节点,关于食物的信息。

通过枚举将蛇的方向,状态一一列举出来。

//设置方向
typedef enum DIR
{
	RIGHT,
	LEFT,
	UP,
	DOWN
}DIR;
//设置蛇的状态
typedef enum STATE
{
	FINE,            //正常
	DEAD_BYWALL,     //撞墙
	DEAD_BYBODY,     //撞到蛇身
	QUIT             //退出
}STATE;
//设置蛇节点
typedef struct Snackbody
{
	size_t _x;
	size_t _y;
	struct Snackbody* _next;
}Snackbody;
//食物信息
typedef struct Food
{
	size_t x;
	size_t y;
	size_t _each_score;
}Food;
//设置结构体来存储蛇的各个信息
typedef struct Snack
{
	Snackbody* _head;
	size_t _speed;
	size_t _score;
	DIR dir;
	STATE state;
	Food* pf;
}Snack;

初始化及打印蛇

初始化打印蛇,主要分为两步:创建蛇身的各个节点并将其链接;初始化蛇的状态,得分等信息。

//创造节点
Snackbody* MakeBody()
{
	Snackbody* newbody = (Snackbody*)malloc(sizeof(Snackbody));
	newbody->_next = NULL;
	newbody->_x = 0;
	newbody->_y = 0;
	return newbody;
}

//对蛇进行初始化
void Init_Snack(Snack* sn)
{
	//对蛇的各个数据进行初始化
	sn->_head = NULL;
	sn->dir = RIGHT;
	sn->state = FINE;
	sn->_score = 0;
	sn->_speed = 200;

	//创建蛇身
	Snackbody** snhead = &(sn->_head);  //注意此处需要是二级指针,因为要对其地址进行修改
	Snackbody* tail = *snhead;
	for (int i = 0; i < 5; i++)
	{
		Snackbody* newbody = MakeBody();
		newbody->_x = 20 - i * 2;
		newbody->_y = 15;
		if ((*snhead) == NULL)
		{
			*snhead = newbody;
			tail = newbody;
		}
		else
		{
			(tail)->_next = newbody;
			tail = (tail)->_next;
		}
	}
	//打印
	Snackbody* cur = sn->_head;
	while (cur != NULL)
	{
		SetPos(cur->_x, cur->_y);
		wprintf(L"%c", SNACK);
		cur = cur->_next;
	}
}

创造食物

此处通过伪随机数,时间戳来确定食物的位置。

注意:创造的食物必须在墙内部,并且不能创作到蛇身上。所以在创建完食物后,要对食物的位置进行检查。

#define FOOD L'豆'
//检查食物
bool CHECK_food(Snack* sn)
{
	size_t x = sn->pf->x;
	size_t y = sn->pf->y;
	Snackbody* cur = sn->_head;
	while (cur != NULL)
	{
		if (cur->_x == x && cur->_y == y)
			return false;
		cur = cur->_next;
	}
	return true;
}

//创造食物
void Make_food(Snack* sn)
{
	sn->pf = (Food*)malloc(sizeof(Food));
	sn->pf->_each_score = 10;

	srand((unsigned int)time(NULL));
again:
	sn->pf->x = rand()%65+2;   //食物x范围是2-66
	sn->pf->y = rand()%21+1;   //y的范围是1-21
	//检查食物是否符合要求
	//x必须是偶数,食物不能出现在蛇身上
	if (sn->pf->x % 2 != 0 || !CHECK_food(sn))
	{
		goto again;
	}
	SetPos(sn->pf->x, sn->pf->y);
	wprintf(L"%c", FOOD);
}

运行游戏

游戏是要持续进行的,所以肯定要设置循环,此处采用do...while循环,在蛇没有死之前,游戏都要能正常运行,所以循环的条件就是蛇的状态是不是FINE。

在循环中,主要有4个部分需要处理:打印侧栏得分;获取按键信息;打印蛇的下一步;检查状态。具体实现目的如下。

1)打印得分情况

游戏运行过程中要保证实时打印总得分及当前每个食物的得分。如下图所示

SetPos(100, 6);
wprintf(L"总得分:%d", sn->_score);
SetPos(100, 7);
wprintf(L"当前食物分数:%2d", sn->pf->_each_score);

2)获取按键信息

关于按键信息如何获取,前面Win32API已经写了,此处直接使用其宏定义,获取完按键信息后要对蛇的方向进行修改。

对于不同的按键要有不同的响应:上下左右直接改变方向即可;F1需要添加分数,缩短睡眠时间;F2相反;Esc则需要改变蛇的状态,让循环停止;Space需要暂停游戏,这里使用一个死循环来模拟暂停。

	//暂停
    void SPACE()
    {
	    while (1)
	    {
		if (KEY_PRESS(VK_SPACE))
			break;
	    }
    }

    //检查按键
	if (KEY_PRESS(VK_UP) && sn->dir != DOWN)
		sn->dir = UP;
	if (KEY_PRESS(VK_DOWN) && sn->dir != UP)
		sn->dir = DOWN;
	if (KEY_PRESS(VK_LEFT) && sn->dir != RIGHT)
		sn->dir = LEFT;
	if (KEY_PRESS(VK_RIGHT) && sn->dir != LEFT)
		sn->dir = RIGHT;
	if (KEY_PRESS(VK_F1))
	{
		if (sn->_speed > 40)
		{
			sn->pf->_each_score += 2;
			sn->_speed -= 40;
		}
	}
	if (KEY_PRESS(VK_F2))
	{
		if (sn->pf->_each_score > 2)
		{
			sn->pf->_each_score -= 2;
			sn->_speed += 40;
		}
	}
	if (KEY_PRESS(VK_SPACE))
		SPACE();
	if (KEY_PRESS(VK_ESCAPE))
	{
		sn->state = QUIT;
		break;
	}
    Sleep(sn->_speed);   //休眠,准备打印下一张图片

3)蛇走的下一步

可以通过蛇的方向来预测蛇的下一个位置,将下一个位置打印成蛇即可实现移动。此处需要考虑下一个位置是不是食物。两种情况是不同的,不是食物的话,就需要将尾部变为空,将下一个位置打印成蛇;是食物的话,就不需要对尾部进行处理,但是需要重新创建食物。

//打印下一张图片
void Print_NEXT(Snack* sn)
{
	//创建下一个位置的节点
	Snackbody* pnext = (Snackbody*)malloc(sizeof(Snackbody));
	switch(sn->dir)
	{
	case UP:
		pnext->_x = sn->_head->_x;
		pnext->_y = sn->_head->_y - 1;
		break;
	case DOWN:
		pnext->_x = sn->_head->_x;
		pnext->_y = sn->_head->_y + 1;
		break;
	case LEFT:
		pnext->_x = sn->_head->_x - 2;
		pnext->_y = sn->_head->_y;
		break;
	case RIGHT:
		pnext->_x = sn->_head->_x + 2;
		pnext->_y = sn->_head->_y;
		break;
	}
	//下一个位置是食物
	if (pnext->_x == sn->pf->x && pnext->_y == sn->pf->y)
	{
		IS_FOOD(sn,pnext);
	}
	else
		NO_FOOD(sn,pnext);
}
下一步不是食物

不是食物的话,就需要将尾部变为空,将下一个位置打印成蛇;

//下一个位置不是食物
void NO_FOOD(Snack* sn, Snackbody* pnext)
{
	//将下一个节点当成蛇头
	pnext->_next = sn->_head;
	sn->_head = pnext;
	//找到最后一个节点并释放
	Snackbody* cur = sn->_head;
	while (cur->_next->_next != NULL)
		cur = cur->_next;
	//将蛇尾从原本的蛇改为空
	SetPos(cur->_next->_x, cur->_next->_y);
	wprintf(L"  ");
	free(cur->_next);
	cur->_next = NULL;
	//将蛇的下一个位置打印成蛇
	SetPos(pnext->_x, pnext->_y);
	wprintf(L"%c", SNACK);
}
下一步是食物
//下一个位置是食物
void IS_FOOD(Snack* sn,Snackbody* pnext)
{
	//将食物直接当成蛇头
	pnext->_next = sn->_head;
	sn->_head = pnext;
	SetPos(pnext->_x, pnext->_y);
	wprintf(L"%c", SNACK);
	//加分
	sn->_score += sn->pf->_each_score;
	//创建食物
	Make_food(sn);
}

4)检查是否撞到墙或自己

此处直接通过坐标进行检查即可。

//检查是不是墙
void IF_WALL(Snack* sn)
{
	if (sn->_head->_x >= LEN - 2 || sn->_head->_x == 0)
		sn->state = DEAD_BYWALL;
	if (sn->_head->_y == WIDTH || sn->_head->_y == 0)
		sn->state = DEAD_BYWALL;
}

//检查是否撞到自己
void IF_SELF(Snack* sn)
{
	int x = sn->_head->_x;
	int y = sn->_head->_y;

	Snackbody* cur = sn->_head->_next;
	while (cur != NULL)
	{
		if (cur->_x == x && cur->_y == y)
			sn->state = DEAD_BYBODY;
		cur = cur->_next;
	}
}

游戏结束

游戏可以正常运行了,但是还要对游戏结束进行处理,要将游戏结束的信息反馈给用户,还要对游戏运行过程中动态开辟的空间进行销毁。

//游戏结束
void Game_over(Snack* sn)
{
	system("cls");
	SetPos(65, 18);
	switch (sn->state)
	{
	case DEAD_BYWALL:
		wprintf(MAGENTA L"你撞到墙了!!!");
		break;
	case DEAD_BYBODY:
		wprintf(L"你撞到自己了!!!");
		break; 
	case QUIT:
		wprintf(L"已退出!!!" RESET);
		break;
	}
	SetPos(0, 35);
	//释放蛇身各个节点,释放动态开辟的空间
	Snackbody* cur = sn->_head;
	while (cur != NULL)
	{
		Snackbody* next = cur->_next;
		free(cur);
		cur = next;
	}
	free(sn->pf);
}

各个文件代码统计

<Blog_Snack.h>头文件

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<windows.h>
#include<locale.h>
#include<stdbool.h>
#include<time.h>

//定义一些颜色
#define RESET       L"\033[0m"           // 恢复默认

// 前景色 (文本颜色)
#define BLACK        L"\033[30m"
#define RED          L"\033[31m"
#define GREEN        L"\033[32m"
#define YELLOW       L"\033[33m"
#define BLUE         L"\033[34m"
#define MAGENTA      L"\033[35m"
#define CYAN         L"\033[36m"
#define WHITE        L"\033[37m"

#define WALL L'墙'  //也可以用█,看自己。但是一定要是宽字符
#define SNACK L'蛇'  //可以定义成其他宽字符
#define FOOD L'豆'

#define LEN 70
#define WIDTH 22

//获取按键信息
#define KEY_PRESS(vk) (GetAsyncKeyState(vk)?1:0)
//vk表示按键
//如:Key_Press(VK_UP)如果用户按下↓,则返回1,否则是0

//设置方向
typedef enum DIR
{
	RIGHT,
	LEFT,
	UP,
	DOWN
}DIR;
//设置蛇的状态
typedef enum STATE
{
	FINE,            //正常
	DEAD_BYWALL,     //撞墙
	DEAD_BYBODY,     //撞到蛇身
	QUIT             //退出
}STATE;
//设置蛇节点
typedef struct Snackbody
{
	size_t _x;
	size_t _y;
	struct Snackbody* _next;
}Snackbody;
//食物信息
typedef struct Food
{
	size_t x;
	size_t y;
	size_t _each_score;
}Food;
//设置结构体来存储蛇的各个信息
typedef struct Snack
{
	Snackbody* _head;
	size_t _speed;
	size_t _score;
	DIR dir;
	STATE state;
	Food* pf;
}Snack;

//设置控制台名称和大小
void Prepare(void);
//打印游戏指南
void Print_Page2(void);

//隐藏光标
void Hide_cursor(void);

//定位光标位置
void SetPos(size_t x, size_t y);

//开始前的准备
void game_prepare(void);

//第一个界面
void Print_Page1(void);

//加载界面的打印
void ProcessBar(void);

//运行游戏
void game_start(void);

//打印墙体
void Print_Wall(void);

//打印说明
void Print_RULE(void);

//打印蛇
void Print_Snack(Snack* sn);

//对蛇进行初始化
void Init_Snack(Snack* sn);

//创造节点
Snackbody* MakeBody();

//创造食物
void Make_food(Snack* sn);

//检查食物
bool CHECK_food(Snack* sn);

//游戏运行
void start(Snack* sn);

//打印下一张图片
void Print_NEXT(Snack* sn);

//暂停
void SPACE();

//下一个位置是食物
void IS_FOOD(Snack* sn,Snackbody* pnext);

//下一个位置不是食物
void NO_FOOD(Snack* sn, Snackbody* pnext);

//检查是不是墙
void IF_WALL(Snack* sn);

//检查是否撞到自己
void IF_SELF(Snack* sn);

//游戏结束
void Game_over(Snack* sn);

<Snack.c>源文件

#define _CRT_SECURE_NO_WARNINGS 1
#include"Blog_Snack.h"

//隐藏光标
void Hide_cursor(void)
{
	//获得输出控制台句柄
	HANDLE houtput = NULL;
	houtput = GetStdHandle(STD_OUTPUT_HANDLE);
	//获得控制台光标信息
	CONSOLE_CURSOR_INFO cursor_info;
	GetConsoleCursorInfo(houtput, &cursor_info);
	//将光标信息中的可见度改为0
	cursor_info.bVisible = 0;
	SetConsoleCursorInfo(houtput, &cursor_info);
}

//定位光标位置
void SetPos(size_t x, size_t y)
{
	//获得输出控制台句柄
	HANDLE houtput = NULL;
	houtput = GetStdHandle(STD_OUTPUT_HANDLE);
	// 获取光标
	COORD cursorPos = { 0, 0 };
	
	//修改光标位置
	cursorPos.X = x;
	cursorPos.Y = y;
	SetConsoleCursorPosition(houtput,cursorPos);
}

#include<locale.h>
void Prepare(void)
{
	//先将程序改成本地模式
	setlocale(LC_ALL, "");
	//先对控制台的大小进行修改
	system("mode con cols=150 lines=40");

	//对名称进行重命名
	system("title 贪吃蛇");
}


//加载界面的打印
void ProcessBar(void)
{
	wchar_t bar[102] = { '\0' }; //设置一个字符串来当作进度条
	int cnt = 0;
	while (cnt <= 100)
	{
		SetPos(25, 21);  //定位
		wprintf(GREEN L"[%-101ls]" RESET"[%d%%]", bar, cnt); //打印进度条,并加上颜色
		Sleep(1);      //休眠100毫秒
		bar[cnt] = L'█';
		cnt++;
	}
}

void Print_Page1(void)
{
	//定位光标,打印欢迎语句
	SetPos(65, 18);
	wprintf(YELLOW L"欢迎进入贪吃蛇游戏" RESET);
	//打印加载的进度条
	ProcessBar();
}

void Print_Page2(void)
{
	system("cls");//清屏
	SetPos(58, 17);
	wprintf(MAGENTA L"用↑↓←→来控制方向,ESC退出,SPACE暂停;");
	SetPos(58, 18);
	wprintf(L"F1加速,F2减速;");
	SetPos(58, 19);
	wprintf(L"加速可以获得更多积分,减速获得积分变慢;" RESET);
	
	SetPos(58, 21);
	system("pause");
}



//打印墙体
void Print_Wall(void)
{
	SetPos(0, 0);
	//打印横向
	for (int i = 0; i < LEN; i += 2)  //注意此处是+2,因为宽字符的宽度是2不像普通字符一样
		wprintf(L"%c", WALL);
	SetPos(0, WIDTH);
	for (int i = 0; i < LEN; i += 2)  
		wprintf(L"%c", WALL);

	//打印纵向
	for (int i = 1; i < WIDTH; i++)
	{
		SetPos(0, i);
		wprintf(L"%c", WALL);
		SetPos(LEN-2, i);
		wprintf(L"%c", WALL);
	}
}

//打印说明
void Print_RULE(void)
{
	SetPos(100, 8);
	wprintf(GREEN L"↑↓←→来控制方向");
	SetPos(100, 9);
	wprintf(L"ESC退出,SPACE暂停");
	SetPos(100, 10);
	wprintf(L"F1加速,F2减速");
	SetPos(100, 11);
	wprintf(L"加速可以获得更多积分,减速获得积分变慢" RESET);
}

//创造节点
Snackbody* MakeBody()
{
	Snackbody* newbody = (Snackbody*)malloc(sizeof(Snackbody));
	newbody->_next = NULL;
	newbody->_x = 0;
	newbody->_y = 0;
	return newbody;
}

//对蛇进行初始化
void Init_Snack(Snack* sn)
{
	//对蛇的各个数据进行初始化
	sn->_head = NULL;
	sn->dir = RIGHT;
	sn->state = FINE;
	sn->_score = 0;
	sn->_speed = 200;

	//创建蛇身
	Snackbody** snhead = &(sn->_head);  //注意此处需要是二级指针,因为要对其地址进行修改
	Snackbody* tail = *snhead;
	for (int i = 0; i < 5; i++)
	{
		Snackbody* newbody = MakeBody();
		newbody->_x = 20 - i * 2;
		newbody->_y = 15;
		if ((*snhead) == NULL)
		{
			*snhead = newbody;
			tail = newbody;
		}
		else
		{
			(tail)->_next = newbody;
			tail = (tail)->_next;
		}
	}
	//打印
	Snackbody* cur = sn->_head;
	while (cur != NULL)
	{
		SetPos(cur->_x, cur->_y);
		wprintf(L"%c", SNACK);
		cur = cur->_next;
	}
}

//检查食物
bool CHECK_food(Snack* sn)
{
	size_t x = sn->pf->x;
	size_t y = sn->pf->y;
	Snackbody* cur = sn->_head;
	while (cur != NULL)
	{
		if (cur->_x == x && cur->_y == y)
			return false;
		cur = cur->_next;
	}
	return true;
}

//创造食物
void Make_food(Snack* sn)
{
	sn->pf = (Food*)malloc(sizeof(Food));
	sn->pf->_each_score = 10;

	srand((unsigned int)time(NULL));
again:
	sn->pf->x = rand()%65+2;   //食物x范围是2-66
	sn->pf->y = rand()%21+1;   //y的范围是1-21
	//检查食物是否符合要求
	//x必须是偶数,食物不能出现在蛇身上
	if (sn->pf->x % 2 != 0 || !CHECK_food(sn))
	{
		goto again;
	}
	SetPos(sn->pf->x, sn->pf->y);
	wprintf(L"%c", FOOD);
}

//下一个位置是食物
void IS_FOOD(Snack* sn,Snackbody* pnext)
{
	//将食物直接当成蛇头
	pnext->_next = sn->_head;
	sn->_head = pnext;
	SetPos(pnext->_x, pnext->_y);
	wprintf(L"%c", SNACK);
	//加分
	sn->_score += sn->pf->_each_score;
	//创建食物
	Make_food(sn);
}

//下一个位置不是食物
void NO_FOOD(Snack* sn, Snackbody* pnext)
{
	//将下一个节点当成蛇头
	pnext->_next = sn->_head;
	sn->_head = pnext;
	//找到最后一个节点并释放
	Snackbody* cur = sn->_head;
	while (cur->_next->_next != NULL)
		cur = cur->_next;
	//将蛇尾从原本的蛇改为空
	SetPos(cur->_next->_x, cur->_next->_y);
	wprintf(L"  ");
	free(cur->_next);
	cur->_next = NULL;
	//将蛇的下一个位置打印成蛇
	SetPos(pnext->_x, pnext->_y);
	wprintf(L"%c", SNACK);
}

//打印下一张图片
void Print_NEXT(Snack* sn)
{
	//创建下一个位置的节点
	Snackbody* pnext = (Snackbody*)malloc(sizeof(Snackbody));
	switch(sn->dir)
	{
	case UP:
		pnext->_x = sn->_head->_x;
		pnext->_y = sn->_head->_y - 1;
		break;
	case DOWN:
		pnext->_x = sn->_head->_x;
		pnext->_y = sn->_head->_y + 1;
		break;
	case LEFT:
		pnext->_x = sn->_head->_x - 2;
		pnext->_y = sn->_head->_y;
		break;
	case RIGHT:
		pnext->_x = sn->_head->_x + 2;
		pnext->_y = sn->_head->_y;
		break;
	}
	//下一个位置是食物
	if (pnext->_x == sn->pf->x && pnext->_y == sn->pf->y)
	{
		IS_FOOD(sn,pnext);
	}
	else
		NO_FOOD(sn,pnext);
}

//暂停
void SPACE()
{
	while (1)
	{
		if (KEY_PRESS(VK_SPACE))
			break;
	}
}

//检查是不是墙
void IF_WALL(Snack* sn)
{
	if (sn->_head->_x >= LEN - 2 || sn->_head->_x == 0)
		sn->state = DEAD_BYWALL;
	if (sn->_head->_y == WIDTH || sn->_head->_y == 0)
		sn->state = DEAD_BYWALL;
}

//检查是否撞到自己
void IF_SELF(Snack* sn)
{
	int x = sn->_head->_x;
	int y = sn->_head->_y;

	Snackbody* cur = sn->_head->_next;
	while (cur != NULL)
	{
		if (cur->_x == x && cur->_y == y)
			sn->state = DEAD_BYBODY;
		cur = cur->_next;
	}
}

//游戏结束
void Game_over(Snack* sn)
{
	system("cls");
	SetPos(65, 18);
	switch (sn->state)
	{
	case DEAD_BYWALL:
		wprintf(MAGENTA L"你撞到墙了!!!");
		break;
	case DEAD_BYBODY:
		wprintf(L"你撞到自己了!!!");
		break; 
	case QUIT:
		wprintf(L"已退出!!!" RESET);
		break;
	}
	SetPos(0, 35);
	//释放蛇身各个节点,释放动态开辟的空间
	Snackbody* cur = sn->_head;
	while (cur != NULL)
	{
		Snackbody* next = cur->_next;
		free(cur);
		cur = next;
	}
	free(sn->pf);
}


//游戏运行
void start(Snack* sn)
{
	do {
		SetPos(100, 6);
		wprintf(L"总得分:%d", sn->_score);
		SetPos(100, 7);
		wprintf(L"当前食物分数:%2d", sn->pf->_each_score);
		//检查按键
		if (KEY_PRESS(VK_UP) && sn->dir != DOWN)
			sn->dir = UP;
		if (KEY_PRESS(VK_DOWN) && sn->dir != UP)
			sn->dir = DOWN;
		if (KEY_PRESS(VK_LEFT) && sn->dir != RIGHT)
			sn->dir = LEFT;
		if (KEY_PRESS(VK_RIGHT) && sn->dir != LEFT)
			sn->dir = RIGHT;
		if (KEY_PRESS(VK_F1))
		{
			if (sn->_speed > 40)
			{
				sn->pf->_each_score += 2;
				sn->_speed -= 40;
			}
		}
		if (KEY_PRESS(VK_F2))
		{
			if (sn->pf->_each_score > 2)
			{
				sn->pf->_each_score -= 2;
				sn->_speed += 40;
			}
		}
		if (KEY_PRESS(VK_SPACE))
			SPACE();
		if (KEY_PRESS(VK_ESCAPE))
		{
			sn->state = QUIT;
			break;
		}
		Sleep(sn->_speed);   //休眠,准备打印下一张图片
		Print_NEXT(sn);
		IF_WALL(sn);
		IF_SELF(sn);
	} while (sn->state == FINE);

	//游戏结束
	Game_over(sn);

}


<Snack_main.c>源文件

#define _CRT_SECURE_NO_WARNINGS 1
#include"Blog_Snack.h"


void game_prepare(void)
{
	
	//隐藏光标
	Hide_cursor();
	//设置控制台名称和大小
	Prepare();
	//打印第一个画面,进入画面
	Print_Page1();
	//打印游戏指南
	Print_Page2();
}

//运行游戏
void game_start(void)
{
	system("cls");
	//打印墙体
	Print_Wall();
	//打印说明
	Print_RULE();
	Snack sn;
	//对蛇进行初始化
	Init_Snack(&sn);
	//创造食物
	Make_food(&sn);
	//游戏运行
	start(&sn);
}

int main()
{
	game_prepare();
	game_start();
	return 0;
}

网站公告

今日签到

点亮在社区的每一天
去签到