C语言项目实践——贪吃蛇

news2024/12/26 19:04:30

引言:本篇博客中,我将会使用结构体,链表,WIN32 API等一系列知识完成C语言项目——贪吃蛇的实现。在观看此篇博客之前,请将这些知识所熟悉,不然可能会造成理解困难。

更多有关C语言的知识详解可前往个人主页:计信猫

目录

一,贪吃蛇项目的准备

1,三文件操作

2,贪吃蛇有关结构体的定义

3,改变窗口信息与隐藏光标

4,确定光标位置函数

 二,游戏开始(GameStart)

 1,欢迎界面的设计

2,游戏地图的创建

3,蛇的初始化

4,创建食物

​编辑

 三,游戏运行(GameRun)

 1,打印游戏帮助信息

​编辑2,游戏得分的打印与蛇的移动方向的判定

3,蛇的移动(SnakeMove)

4,检测下一个坐标是否为食物

Ⅰ,EatFood

Ⅱ,NoFood

5,判断蛇是否撞墙或者撞到自己

Ⅰ,KillByWall

Ⅱ,KillBySelf 

 四,游戏结束(GameEnd)

1,根据蛇的状态打印结束语

2,释放蛇的空间

五,游戏的优化设置

六,代码参考

1,test.c

2,  snake.c

3,  snake.h

一,贪吃蛇项目的准备

1,三文件操作

        在贪吃蛇项目中,我们会创建三个文件分别为:test.c,snake.c,snake.h。它们的作用分别为游戏函数的测试,游戏函数的实现,游戏函数的声明和结构体变量的定义。

2,贪吃蛇有关结构体的定义

        在snake.h中,我们将会定义与贪吃蛇有关的结构体。

        1,贪吃蛇身体的节点:

//定义蛇身体的节点
typedef struct snakenode
{
	//坐标
	int x;
	int y;
	struct snakenode* next;//下一个节点
}snakenode,*psnakenode;

        2,蛇的方向:

//定义方向
enum direction
{
	UP = 1,
	DOWN,
	LEFT,
	RIGHT
};

        3,蛇的状态:

//蛇的状态
enum game_state
{
	OK,//正常状态
	KILL_BY_WALL,//撞到墙
	KILL_BY_SELF,//撞到了自己
	END_NORMAL//正常结束游戏
};

        4,贪吃蛇游戏:

//贪吃蛇
typedef struct snake
{
	psnakenode _psnake;//蛇头节点
	psnakenode _pfood;//食物节点
	enum direction _dir;//蛇的方向
	enum game_state _sta;//游戏状态
	int _food_weight;//一个食物的分数
	int _score;//总成绩
	int _sleep_time;//蛇的速度
}snake,* psnake;

3,改变窗口信息与隐藏光标

        snake.c中,我们定义一个GameStart函数来包含开始游戏之前的窗口改变,光标隐藏,欢迎界面与地图打印函数

        在前文WIN32 API中我们已经对窗口信息修改和隐藏光标的函数进行过解释,所以我们这里直接使用:

void GameStart(psnake ps)
{
	//设置窗口大小,改变窗口名字
	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);//设置光标状态
}

4,确定光标位置函数

        在欢迎界面与游戏界面中,我们需要将游戏开始信息与游戏信息等打印在指定的位置,这时候我们就需要有特定的函数来确定光标的位置,以此来完成指定位置信息的打印。所以我们如下设计确定光标位置setpos()函数。

void setpos(int x,int y)
{
	HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);//获取标准输出句柄
	COORD pos = { x,y };//设定坐标
	SetConsoleCursorPosition(houtput, pos);//设定光标的坐标
}

 二,游戏开始(GameStart)

 1,欢迎界面的设计

       

        如图中的界面就是我们想要达到的欢迎界面效果,所以我们在GameStart()函数中设计一个新函数名为welcometogame()函数,函数内容如下:

void welcometogame()
{
	setpos(40, 14);
	wprintf(L"欢迎来到贪吃蛇游戏\n");
	setpos(42, 20);
	system("pause");
	system("cls");
	setpos(25, 14);
	wprintf(L"使用 ↑ . ↓ . ← . → 来进行方向移动,f3加速,f4减速\n");
	setpos(25, 15);
	wprintf(L"加速吃到食物可以增加得分\n");
	setpos(42, 20);
	system("pause");
	system("cls");
}

        首先我们使用setpos函数来确定光标的位置,再使用wprintf函数来打印内容,system(“pause”)语句则代表着暂停程序,按任意键表示继续程序,而system(“cls”)语句则代表着将当前控制台的页面全部清空,这样我们就打印出了我们的欢迎界面。

2,游戏地图的创建

        在贪吃蛇游戏中,我们将要创建如图所示的游戏地图:

        正如我们前一篇博客所讲到的,我们的控制台界面的各个地点都有着对应的坐标,如下:

        而我们所想要设计的地图的大小,就可以通过坐标的范围来圈定。所以我们的地图大小应该如下图坐标所示:

        有了坐标,我们就知道了地图的上边需要29个宽字符‘▢’,下边也是一样,而左右两边我们则可以使用setpos函数来移动光标进行左右两边边界的打印。所以我们在GameStart()函数中创建一个creatmap()函数,代码如下:

void creatmap()
{
	int i = 0;
	//上
	for (i = 0; i < 29; i++)
	{
		wprintf(L"%lc", L'□');
	}
	//下
	setpos(0, 26);//移动光标以便打印下标
	for (i = 0; i < 29; i++)
	{
		wprintf(L"%lc", L'□');
	}
	//左
	for (i = 1; i <= 25; i++)
	{
		setpos(0, i);//移动光标
		wprintf(L"%lc", L'□');
	}
	//右
	for (i = 1; i <= 25; i++)
	{
		setpos(56, i);//移动光标
		wprintf(L"%lc", L'□');
	}
	system("pause");//按任意键继续
}

        代码一走,则地图创建成功!

3,蛇的初始化

        我们将蛇想象成我们以前学到的链表,刚开始的蛇身长度为5个节点,所以我们便可以使用头插的办法来完成蛇身的初始化。

        但是,在初始化蛇身的时候,我们一定需要注意蛇的每个节点的x坐标必须是2个倍数,否则可能会出现蛇的⼀个节点有⼀半出现在墙体中, 另外⼀半在墙外的现象,坐标不好对齐。如下图所示:

        而对于蛇的身体,我们则使用宽字符‘●‘进行表示。为了方便以后更改蛇身体的坐标,墙和蛇的图标,于是我们选择在snake.h中使用define定义:

#define POS_X 24
#define POS_Y 5
#define WALL L'□'
#define BODY L'●'

        最后,我们还不要忘记对蛇的结构体中的其他成员变量的初始化。所以在GameStart()函数中,我们继续定义一个InitSnake()函数来进行对蛇进行初始化。

void InitSnake(psnake ps)
{
	int i = 0;
	psnakenode cur = NULL;
	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 + 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->_food_weight = 10;
	ps->_score = 0;
	ps->_sleep_time = 200;//单位是毫秒
	ps->_sta = OK;
	getchar();//暂停程序观察效果,不需要观察效果后可将该语句删除
}

4,创建食物

        当蛇的初始化完成后,我们就需要完成食物的初始化。但在初始化食物的时候,我们需要注意到以下三点:

1,食物必须出现在墙体之内

2,食物的x坐标一定为2的倍数

3,食物坐标不可以与蛇身的坐标重复

        在该函数中,我们也给食物下定义:

#define FOOD L'※'

为了达到坐标随机生成的目的,我们将在主函数中使用srand((unsigned int)time);(需要包含<time.h>)。所以我们的创建食物函数CreatFood()函数的实现如下:

void CreatFood(psnake ps)
{
	int x = 0;
	int y = 0;
	again:
	do
	{
		x = rand() % 53 + 2;//使横坐标在2~54之内
		y = rand() % 24 + 1;//使纵坐标在1~25之内
	} while (x % 2 != 0);//若生成的x为奇数,那么就重新生成
	psnakenode cur = ps->_psnake;//遍历链表,防止食物与蛇身重合
	while (cur)
	{
		if (x == cur->x && y == cur->y)//如果食物的坐标与蛇身重合
		{
			goto again;//重新生成
		}
		cur = cur->next;
	}
	//打印食物标号
	psnakenode food = (psnakenode)malloc(sizeof(snakenode));
	if (food == NULL)//防止申请内存失败
	{
		perror("CreatFood()::malloc()");
		return;
	}
	food->x = x;
	food->y = y;
	food->next = NULL;
	setpos(food->x, food->y);
	wprintf(L"%lc", FOOD);
	ps->_pfood = food;
	getchar();//观察食物打印效果,之后若不需要观察则可以删除该语句
}

代码一走,食物生成成功!

 三,游戏运行(GameRun)

 1,打印游戏帮助信息

        在正式开始游戏时,我们需要在游戏地图之外打印一系列信息,以帮助用户了解游戏的玩法。所以我们在GameRun()函数中封装一个PrintHelpInfo()函数来进行游戏帮助信息的打印。

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

        代码一走,效果如图:

2,游戏得分的打印与蛇的移动方向的判定

        首先,我们实现一个KEY_PRESS宏来判断某个按键是否被按下:

#define KEY_PRESS(vk) ((GetAsyncKeyState(vk)&1)?1:0)

        所以,我们便可以在GameStart()函数中使用do-while循环来打印游戏的得分与了解按键按下的情况,以便控制蛇的移动方向。

//游戏的运行
void GameRun(psnake ps)
{
	//打印游戏帮助信息
	PrintHelpInfo();
	do
	{
		//打印游戏得分信息
		setpos(64, 10);
		printf("总得分:%d\n", ps->_score);
		setpos(64, 11);
		printf("当前一个食物的分数:%2d\n", ps->_food_weight);
		//判断按键以控制蛇的移动
		if (KEY_PRESS(VK_UP) && ps->_dir != DOWN)//VK_UP为↑的虚拟键码,同时蛇的当前方向不可以向下
		{
			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();//Pause函数执行暂停操作
		}
		else if (KEY_PRESS(VK_ESCAPE))
		{
			//正常退出游戏
			ps->_sta = 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->_sleep_time < 300)
			{
				ps->_sleep_time += 30;
				ps->_food_weight -= 2;
			}
		}
		SnakeMove(ps);//蛇走一步
		Sleep(ps->_sleep_time);
	} while (ps->_sta == OK);
}

        其中Pause()函数如下:

//游戏暂停操作
void Pause()
{
	while (1)
	{
		Sleep(200);//死循环休眠则视为暂停
		if (KEY_PRESS(VK_SPACE))
		{
			break;//再次按下空格则跳出循环
		}
	}
}

3,蛇的移动(SnakeMove)

        当我们在前面获取了按键按下的情况,即蛇的状态信息之后,我们就可以使用SnakeMove()函数来对蛇的移动进行控制。

        在该函数中,我们就可以再创建一个节点(pNextNode)表示蛇头即将到达的节点位置,而该新节点的位置与当前蛇头的坐标x,y有关:

        于是新节点的位置设置如下:

//蛇走一步
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;
	}
}

4,检测下一个坐标是否为食物

        当我们的蛇头移动到下一个坐标的时候,就会产生两种情况:1,下一个坐标为食物。2,下一个坐标不为食物。两种情况应该不同方式来处理,所以我们在SnakeMove()函数中的switch循环之后再定义两个函数来处理不同情况:

//判断下一个坐标是否为食物
int NextIsFood(psnakenode pNextNode, psnake ps)
{
	return (pNextNode->x == ps->_pfood->x && pNextNode->y == ps->_pfood->y);
}
//检测下一个坐标是否为食物
	if (NextIsFood(pNextNode, ps))
	{
		EatFood(pNextNode, ps);
	}
	else
	{
		NoFood(pNextNode, ps);
	}

Ⅰ,EatFood

        该函数用于处理下一个坐标为食物的情况。该情况下,我们就可以直接将食物节点变为蛇的头节点,然后打印蛇,加分,最后再次创建食物:

//下一个坐标为食物
void EatFood(psnakenode pNextNode, psnake ps)
{
	//使用头插,将食物节点变为蛇头
	ps->_pfood->next = ps->_psnake;
	ps->_psnake = ps->_pfood;
	//释放掉pNextNode
	free(pNextNode);
	pNextNode = NULL;
	//打印出新蛇
	psnakenode cur = ps->_psnake;
	while (cur)
	{
		setpos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	//加分
	ps->_score += ps->_food_weight;
	//再次创建食物
	CreatFood(ps);
}

Ⅱ,NoFood

        该函数用于处理下一个坐标不为食物的情况。该情况下,我们则需要做到的任务如下:

1,使用头插法将pNextNode变为新的蛇头
2,把最后一个坐标打印为两个空格
3,释放掉尾节点
4,将倒数第二个节点的next置空

        所以我们画图解释:

          于是我们使用cur遍历数组,当cur->next->nextNULL时,那么就证明cur来到了倒数第二个节点,此时我们就跳出while循环,之后将cur->next指向的节点处打印两个空格(以覆盖先前打印而留下的蛇身),再将尾节点释放掉,最后将cur->next置空。 

//下一个坐标不是食物
void NoFood(psnakenode pNextNode, psnake ps)
{
	//头插法
	pNextNode->next = ps->_psnake;
	ps->_psnake = pNextNode;
	//遍历链表
	psnakenode cur = ps->_psnake;
	while (cur->next->next)
	{
		setpos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	//出了循环,cur刚好停在倒数第二个节点,所以先打印两个空格
	setpos(cur->next->x, cur->next->y);
	printf("  ");
	//释放掉尾节点
	free(cur->next);
	//将倒数第二个节点的next置空
	cur->next = NULL;
}

        这样,一个贪吃蛇游戏就初步完成了。

5,判断蛇是否撞墙或者撞到自己

        在GameRUN()函数中,我们也需要检测蛇是否在行动的过程中撞到了墙或者撞到了自己。于是我们可以在设计两个函数KillByWall()函数KillBySelf()函数来进行判断。

Ⅰ,KillByWall

        关于蛇是否撞墙的判断十分简单。我们只需要判断蛇头的坐标是否与墙的x或者y坐标重合即可。若重合,则撞墙;反之,则未撞墙。

//检测是否撞墙
void KillByWall(psnake ps)
{
	if (ps->_psnake->x == 0 || ps->_psnake->x == 56 ||
		ps->_psnake->y == 0 || ps->_psnake->y == 26)
	{
		ps->_sta = KILL_BY_WALL;
	}
}

Ⅱ,KillBySelf 

         在该函数中,我们只需要从蛇头之后的一个节点遍历蛇身,假如某段蛇身的坐标等于蛇头的坐标,那么就是撞到了自己;反之,则没有撞到自己。

//检测是否撞到自己
void KillBySelf(psnake ps)
{
	psnakenode cur = ps->_psnake->next;//从蛇头之后的一个节点开始遍历
	while (cur)
	{
		if (cur->x == ps->_psnake->x && cur->y == ps->_psnake->y)
		{
			ps->_sta = KILL_BY_SELF;
			break;//撞到了自己,退出循环
		}
		cur = cur->next;
	}
}

 四,游戏结束(GameEnd)

1,根据蛇的状态打印结束语

        有了前文所获取的蛇的结束状态,那我们就可以在GameEnd()函数中打印对应的结束语。

void GameEnd(psnake ps)
{
	setpos(24, 12);//设置光标位置,打印结束信息
	switch (ps->_sta)
	{
	case END_NORMAL:
		printf("游戏正常结束\n");
		break;
	case KILL_BY_WALL:
		printf("您撞到了墙,游戏结束\n");
		break;
	case KILL_BY_SELF:
		printf("您撞到了自己,游戏结束\n");
		break;
	}
}

2,释放蛇的空间

        当然,当我们的游戏结束后,我们使用的蛇身节点也需要被释放,而释放方法非常简单,就是我们之前在链表操作函数中学到的销毁链表操作。

//游戏结束
void GameEnd(psnake ps)
{
	//释放蛇身
	psnakenode cur = ps->_psnake;
	while (cur)
	{
		psnakenode del = cur;
		cur = cur->next;
		free(del);
		del = NULL;
	}
}

        如此,我们的贪吃蛇的主体函数就完成了!!

五,游戏的优化设置

        在这里,我们将实现游戏的多次使用和正常退出,以及项目结束的信息的位置的设置。这一切的操作,都在test.c中进行。

#include"snake.h"
void test()
{
	int ch = 0;
	do
	{
		snake ps = { 0 };
		GameStart(&ps);
		GameRun(&ps);
		GameEnd(&ps);
		setpos(20, 15);
		printf("再来一局吗?(Y/N):");
		ch = getchar();
		getchar();//清理\n
	} while (ch == 'Y' || ch == 'y');
	setpos(0, 27);
}
int main()
{
	srand((unsigned int)time);
	setlocale(LC_ALL, "");//设置本地化以打印宽字符
	test();
	return 0;
}

六,代码参考

1,test.c

#include"snake.h"
void test()
{
	int ch = 0;
	do
	{
		snake ps = { 0 };
		GameStart(&ps);
		GameRun(&ps);
		GameEnd(&ps);
		setpos(20, 15);
		printf("再来一局吗?(Y/N):");
		ch = getchar();
		getchar();//清理\n
	} while (ch == 'Y' || ch == 'y');
	setpos(0, 27);
}
int main()
{
	srand((unsigned int)time);
	setlocale(LC_ALL, "");//设置本地化以打印宽字符
	test();
	return 0;
}

2,  snake.c

#include"snake.h"
void setpos(int x,int y)
{
	HANDLE 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(25, 14);
	wprintf(L"使用 ↑ . ↓ . ← . → 来进行方向移动,f3加速,f4减速\n");
	setpos(25, 15);
	wprintf(L"加速吃到食物可以增加得分\n");
	setpos(42, 20);
	system("pause");
	system("cls");
}
void creatmap()
{
	int i = 0;
	//上
	for (i = 0; i < 29; i++)
	{
		wprintf(L"%lc", L'□');
	}
	//下
	setpos(0, 26);//移动光标以便打印下标
	for (i = 0; i < 29; i++)
	{
		wprintf(L"%lc", L'□');
	}
	//左
	for (i = 1; i <= 25; i++)
	{
		setpos(0, i);//移动光标
		wprintf(L"%lc", L'□');
	}
	//右
	for (i = 1; i <= 25; i++)
	{
		setpos(56, i);//移动光标
		wprintf(L"%lc", L'□');
	}
}
void InitSnake(psnake ps)
{
	int i = 0;
	psnakenode cur = NULL;
	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 + 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->_food_weight = 10;
	ps->_score = 0;
	ps->_sleep_time = 200;//单位是毫秒
	ps->_sta = OK;
}
void CreatFood(psnake ps)
{
	int x = 0;
	int y = 0;
	again:
	do
	{
		x = rand() % 53 + 2;//使横坐标在2~54之内
		y = rand() % 24 + 1;//使纵坐标在1~25之内
	} while (x % 2 != 0);//若生成的x为奇数,那么就重新生成
	psnakenode cur = ps->_psnake;//遍历链表,防止食物与蛇身重合
	while (cur)
	{
		if (x == cur->x && y == cur->y)//如果食物的坐标与蛇身重合
		{
			goto again;//重新生成
		}
		cur = cur->next;
	}
	//打印食物标号
	psnakenode food = (psnakenode)malloc(sizeof(snakenode));
	if (food == NULL)//防止申请内存失败
	{
		perror("CreatFood()::malloc()");
		return;
	}
	food->x = x;
	food->y = y;
	food->next = NULL;
	setpos(food->x, food->y);
	wprintf(L"%lc", FOOD);
	ps->_pfood = food;
	//getchar();//观察食物打印效果,之后若不需要观察则可以删除该语句
}
void GameStart(psnake ps)
{
	//设置窗口大小,改变窗口名字
	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);//设置光标状态
	//打印欢迎界面
	welcometogame();
	//打印地图
	creatmap();
	//初始化蛇
	InitSnake(ps);
	//创建食物
	CreatFood(ps);
}
//游戏暂停操作
void Pause()
{
	while (1)
	{
		Sleep(200);//死循环休眠则视为暂停
		if (KEY_PRESS(VK_SPACE))
		{
			break;//再次按下空格则跳出循环
		}
	}
}
//打印游戏帮助信息
void PrintHelpInfo()
{
	//打印提⽰信息
	setpos(64, 15);
	printf("不能穿墙,不能咬到自己\n");
	setpos(64, 16);
	printf("用↑.↓.←.→分别控制蛇的移动\n");
	setpos(64, 17);
	printf("F3 为加速,F4 为减速\n");
	setpos(64, 18);
	printf("ESC :退出游戏.space:暂停游戏.");
}
//判断下一个坐标是否为食物
int NextIsFood(psnakenode pNextNode, psnake ps)
{
	return (pNextNode->x == ps->_pfood->x && pNextNode->y == ps->_pfood->y);
}
//下一个坐标为食物
void EatFood(psnakenode pNextNode, psnake ps)
{
	//使用头插,将食物节点变为蛇头
	ps->_pfood->next = ps->_psnake;
	ps->_psnake = ps->_pfood;
	//释放掉pNextNode
	free(pNextNode);
	pNextNode = NULL;
	//打印出新蛇
	psnakenode cur = ps->_psnake;
	while (cur)
	{
		setpos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	//加分
	ps->_score += ps->_food_weight;
	//再次创建食物
	CreatFood(ps);
}
//下一个坐标不是食物
void NoFood(psnakenode pNextNode, psnake ps)
{
	//头插法
	pNextNode->next = ps->_psnake;
	ps->_psnake = pNextNode;
	//遍历链表
	psnakenode cur = ps->_psnake;
	while (cur->next->next)
	{
		setpos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	//出了循环,cur刚好停在倒数第二个节点,所以先打印两个空格
	setpos(cur->next->x, cur->next->y);
	printf("  ");
	//释放掉尾节点
	free(cur->next);
	//将倒数第二个节点的next置空
	cur->next = NULL;
}
//检测是否撞墙
void KillByWall(psnake ps)
{
	if (ps->_psnake->x == 0 || ps->_psnake->x == 56 ||
		ps->_psnake->y == 0 || ps->_psnake->y == 26)
	{
		ps->_sta = KILL_BY_WALL;
	}
}
//检测是否撞到自己
void KillBySelf(psnake ps)
{
	psnakenode cur = ps->_psnake->next;//从蛇头之后的一个节点开始遍历
	while (cur)
	{
		if (cur->x == ps->_psnake->x && cur->y == ps->_psnake->y)
		{
			ps->_sta = KILL_BY_SELF;
			break;
		}
		cur = cur->next;
	}
}
//蛇走一步
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);
	}
	//检测是否撞墙
	KillByWall(ps);
	//检测是否撞到自己
	KillBySelf(ps);
}
//游戏的运行
void GameRun(psnake ps)
{
	//打印游戏帮助信息
	PrintHelpInfo();
	do
	{
		//打印游戏得分信息
		setpos(64, 10);
		printf("总得分:%d\n", ps->_score);
		setpos(64, 11);
		printf("当前一个食物的分数:%2d\n", ps->_food_weight);
		//判断按键以控制蛇的移动
		if (KEY_PRESS(VK_UP) && ps->_dir != DOWN)//VK_UP为↑的虚拟键码,同时蛇的当前方向不可以向下
		{
			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();//Pause函数执行暂停操作
		}
		else if (KEY_PRESS(VK_ESCAPE))
		{
			//正常退出游戏
			ps->_sta = 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->_sleep_time < 300)
			{
				ps->_sleep_time += 30;
				ps->_food_weight -= 2;
			}
		}
		SnakeMove(ps);//蛇走一步
		Sleep(ps->_sleep_time);
	} while (ps->_sta == OK);
}
//游戏结束
void GameEnd(psnake ps)
{
	setpos(24, 12);//设置光标位置,打印结束信息
	switch (ps->_sta)
	{
	case END_NORMAL:
		printf("游戏正常结束\n");
		break;
	case KILL_BY_WALL:
		printf("您撞到了墙,游戏结束\n");
		break;
	case KILL_BY_SELF:
		printf("您撞到了自己,游戏结束\n");
		break;
	}
	//释放蛇身
	psnakenode cur = ps->_psnake;
	while (cur)
	{
		psnakenode del = cur;
		cur = cur->next;
		free(del);
		del = NULL;
	}
}

3,  snake.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<windows.h>
#include<stdbool.h>
#include<locale.h>
#include<time.h>
#define POS_X 24
#define POS_Y 5
#define WALL L'□'
#define BODY L'●'
#define FOOD L'※'
#define KEY_PRESS(vk) ((GetAsyncKeyState(vk)&1)?1:0)
//定义蛇身体的节点
typedef struct snakenode
{
	//坐标
	int x;
	int y;
	struct snakenode* next;//下一个节点
}snakenode,*psnakenode;
//定义方向
enum direction
{
	UP = 1,
	DOWN,
	LEFT,
	RIGHT
};
//蛇的状态
enum game_state
{
	OK,//正常状态
	KILL_BY_WALL,//撞到墙
	KILL_BY_SELF,//撞到了自己
	END_NORMAL//正常结束游戏
};
//贪吃蛇
typedef struct snake
{
	psnakenode _psnake;//蛇头节点
	psnakenode _pfood;//食物节点
	enum direction _dir;//蛇的方向
	enum game_state _sta;//游戏状态
	int _food_weight;//一个食物的分数
	int _score;//总成绩
	int _sleep_time;//蛇的速度
}snake,* psnake;
//设置光标位置
void setpos(int x, int y);
//开始游戏
void GameStart(psnake ps);
//打印欢迎界面
void welcometogame();
//设置光标坐标
void setpos(int x, int y);
//打印游戏地图
void creatmap();
//蛇的初始化
void InitSnake(psnake ps);
//创建食物
void CreatFood(psnake ps);
//游戏的运行
void GameRun(psnake ps);
//打印游戏帮助信息
void PrintHelpInfo();
//游戏暂停操作
void Pause();
//蛇走一步
void SnakeMove(psnake ps);
//判断下一个坐标是否为食物
int NextIsFood(psnakenode pNextNode, psnake ps);
//下一个坐标为食物
void EatFood(psnakenode pNextNode, psnake ps);
//下一个坐标不是食物
void NoFood(psnakenode pNextNode, psnake ps);
//检测是否撞墙
void KillByWall(psnake ps);
//检测是否撞到自己
void KillBySelf(psnake ps);
//游戏结束
void GameEnd(psnake ps);

        快去享受游戏吧!!! 

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

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

相关文章

AI预测福彩3D第9套算法实战化测试第1弹2024年4月22日第1次测试

经过前面多套算法的测试&#xff0c;总结了一些规律&#xff0c;对模型优化了一些参数&#xff0c;比如第8套算法的测试&#xff0c;7码的命中率由最开始的20%提高到了50%。虽然命中率有了很大的提高&#xff0c;但是由于咱们之前的算法只是为了测试和记录&#xff0c;提供的方…

C++学习进阶版(二):与文件相关的函数用法

目录 1、读取文件的指定行 &#xff08;1&#xff09;main函数中直接读 &#xff08;2&#xff09;封装成函数 ① 无返回值类型 ② 直接返回读取的内容 2、求文件的行数 3、文件内容读取成一个字符串 1、读取文件的指定行 &#xff08;1&#xff09;main函数中直接读 …

关于 Windows10 计算机丢失 MSVCP120.dll 的解决方法

今天学长跟平时一样打开电脑开始发布文章需要用到Adobe Photoshop CC 2018的时候居然给我来个Photoshop.exe-系统错误、无法启动此程序&#xff0c;因为计算机中丢失MSVCP120.dll 尝试重新安装该程序以解决此问题&#xff0c;安装上面的说明重新安装了我的Photoshop CC 打开还是…

移动端不居中问题/安卓和ios下line-height上下居中 css兼容问题

移动端开发过程&#xff0c;经常会写带0.5px边框角标类的样式&#xff0c;直接使用border设置0.5px边框&#xff0c;ios有些机型会出现显示不完整的情况。所以改用伪元素方法实现边框。代码如下&#xff1a; .comment-entry::after{content: ;position: absolute;left: 0;top: …

MySQL主从复制实现高可用性和负载均衡

大家好&#xff0c;我是咕噜铁蛋&#xff0c;今天我想和大家聊聊MySQL主从复制如何帮助我们实现高可用性和负载均衡。在如今的大数据时代&#xff0c;数据库的稳定性和性能成为了企业关注的重点&#xff0c;而MySQL主从复制正是解决这两个问题的重要工具。 一、MySQL主从复制简…

论21世纪的婚姻

什么是婚姻&#xff1f; 婚姻是一种社会与法律制度认可的男女两性基于感情自愿结成夫妻关系的社会组织形式&#xff0c;婚姻在百度百科上是这样回答的&#xff0c;通俗来说是两个人在民政局领结婚证后所组成为一个家庭。然而我认为这个是有水分的&#xff0c;在我看来&#xff…

混淆原理与实践指南

引言 &#x1f680; 在当今的软件开发领域&#xff0c;保护代码的安全性和保密性变得越来越重要。混淆&#xff08;Obfuscation&#xff09;技术作为一种保护代码的手段&#xff0c;在应对逆向工程和代码盗用方面发挥着关键作用。本文将深入探讨混淆的原理&#xff0c;以及如何…

文件上传服务器、文件展示等异步问题

问题&#xff1a; 文件上传模块&#xff1a;当文件已经上传完成&#xff0c;文件进度已经走完了&#xff0c;但是服务器响应还没有返回结果&#xff0c;出现了&#xff0c;获取不到上传后的文件路径&#xff0c;需要等待服务器返回结果后&#xff0c;才能获取文件路径并点击跳…

FPGA - ZYNQ 基于Axi_Lite的PS和PL交互

前言 在FPGA - ZYNQ 基于EMIO的PS和PL交互中介绍了ZYNQ 中PS端和PL端交互的开发流程&#xff0c;接下来构建基于基于Axi_Lite的PS和PL交互。 开发流程 Axi_Lite从机 在FPGA - AXI4_Lite&#xff08;实现用户端与axi4_lite之间的交互逻辑&#xff09;中&#xff0c;详解介绍…

weblogic反序列化漏洞(CVE-2017-10271)复现

直接用vuluhub搭建现成的靶场做 访问靶场 打开是这样表示成功 想反弹shell 就先开启kali1的nc监听&#xff0c;这就监听2233端口吧 linux&#xff1a;nc -l -p 2233 抓包修改为攻击数据包 ip和端口可以任意修改 反弹的shell 还可以写入文件shell 只需要把提供的poc POS…

4.10 SpringBoot整合Servlet、Filter过滤器和Listener

4.10 整合Servlet、Filter和Listener 1. 启动类1. 整合Servlet2. Filter过滤器3. Listener监听器4. 总结5. JavaWeb三大组件**************************************************** 一般情况下&#xff0c;使用Spring、Spring MVC这些框架之后&#xff0c;基本上就告别Servlet、…

# 从浅入深 学习 SpringCloud 微服务架构(三)注册中心 Eureka(1)

从浅入深 学习 SpringCloud 微服务架构&#xff08;三&#xff09;注册中心 Eureka&#xff08;1&#xff09; 段子手168 1、微服务的注册中心 注册中心可以说是微服务架构中的”通讯录”&#xff0c;它记录了服务和服务地址的映射关系。 在分布式架构中服务会注册到这里&am…

【HCIP学习】重发布和路由策略

一、重发布&#xff08;路由引入&#xff09; 1、背景&#xff1a; 一个网络拓扑中存在多种不同的路由协议&#xff0c;为了使多种不同的路由协议间能相互通信&#xff0c;出现了路由引入 为啥会存在多种不同的网络&#xff1f; 例如&#xff1a;OSPF由于区域架构的限制&am…

web前端 html5+css3相关知识点(跟着黑马学)8

先总结一下网页常见的布局方式&#xff1a; 1. 标准流 块级元素独占一行 -> 垂直布局 行内元素/行内块元素一行显示多个 -> 水平布局 2. 浮动 可以让原本垂直布局的块级元素变成水平布局。 3. 定位 可以让元素自由的摆放在网页的任意位置 一般用于盒子之间的层叠…

【RAG 论文】WikiChat:从 WikiPedia 检索数据来提高 LLM 的事实性的聊天机器人

论文&#xff1a;WikiChat: Stopping the Hallucination of Large Language Model Chatbots by Few-Shot Grounding on Wikipedia ⭐⭐⭐⭐ Stanford University, EMNLP 2023 相关地址&#xff1a; demo 体验地址CodeHuggingface 模型 文章目录 论文速读模型 demo一些其他的细节…

让流程图动起来

我们平时画流程&#xff0c;然后贴到文档&#xff0c;就完事了。但是过程演示的时候&#xff0c;如果只是一张静态图&#xff0c;很难吸引到听众的注意力&#xff0c;表达效果并不太好。常用的方法是可以用PPT进行动态演示&#xff0c;做PPT也是需要花一些时间&#xff0c;同时…

[StartingPoint][Tier2]Base

Task 1 Which two TCP ports are open on the remote host? (远程服务器开放了哪两个TCP端口?) $ nmap -sC -sV 10.129.234.232 22,80 Task 2 What is the relative path on the webserver for the login page? (相关的登录页面路径是什么?) /login/login.php Task 3 …

1 transformers:词典,(文本如何处理)

0 介绍 NLP任务最初&#xff0c;就是在于如何处理文本。无论从TFIDF到word2Vec的过程&#xff0c;还是BERT都是想找到文本的向量表达&#xff0c;如何表示更好处理我们的下游任务。那么&#xff0c;这个过程是如何做的呢&#xff0c;本文主要就是介绍这一个过程&#xff0c;还是…

【C++ 多态】(一)虚函数重写✍

文章目录 1.虚函数重写的三个例外1.1协变(基类与派生类虚函数返回值类型不同)1.2析构函数的重写(基类与派生类析构函数的名字不同)1.3派生类可以不写 virtual 2.面试题✍ 1.虚函数重写的三个例外 1.1协变(基类与派生类虚函数返回值类型不同) ①&#x1f34e;协变的概念&#…

Linux学习之路 -- 进程篇 -- 进程地址空间

目录 一、背景介绍 二、进程地址空间 1.看现象 2.先简单描述一下地址空间&#xff08;地址空间全在操作系统的内部&#xff09; 3.地址空间详细一点的描述 4.进程地址空间里面的内容&#xff08;部分&#xff09; 三、进程地址空间的转换机制 1.页表 2.进程地址空间和页…