C语言项目---贪吃蛇

news2024/9/28 17:26:42

目录

  • 一 、知识铺垫
    • 1.win32API介绍
  • 二、贪吃蛇的数据结构的设计
    • 1.整体框架
    • 2.初始化界面
    • 3.贪吃蛇的运行
    • 4.游戏的退出
  • 三、整体代码

一 、知识铺垫

贪吃蛇涉及的知识:C语言函数、枚举、结构体、动态内存管理、预处理指令、链表、win32API等

1.win32API介绍

Windows除了协调应用程序、分配内存、管理资源之外,同时也是一个很大的服务中心,(每一种服务就是一个函数),可以磅应用程序达到开启视窗,描绘图形、使用周边设备等目的,由于这些函数服务的对象是应用程序,所以便称之为API,的应用程序编程接口。
C语言的system函数可以达到执行系统操作。
GetStdHandle是一个Windows API函数,它用于从一个特定的标准设备(标准输入、标准输出或标准错误)中取得一个句柄(用于标识不同设备的数值),使用这个句柄可以操作设备。
意思就是要操作这个设备,需要获取这个的操作权限,GetStdHandle就是起这个作用
CONSOLE_CURSOR_INFO:这个结构体,包含有关控制光标的信息
dwSize,由光标填充的字符单元格的百分比。由1到100之间。光标外观会变化,返回从完全填充单元格到单元底部的水平线条。
bVisible,游标的可见性。如果光标可见,则此成员为TRUE。

说这么多大家可能对函数GetStdHandle有点懵,接下来我将用代码演示,让大家理解加深。
代码演示:

int main()
{
	//先获取信息才能修改
	//COORD pos = { 40,10 };//设置了一个坐标
	CONSOLE_CURSOR_INFO cursor_info = { 0 };//创建一个变量
	HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
	GetConsoleCursorInfo(handle, &cursor_info);//获取
	//cursor_info.dwSize = 0;//将光标信息改掉,设置控制台信息
	cursor_info.bVisible = false;//默认为true,将可见信息改为false
	SetConsoleCursorInfo(handle, &cursor_info);//将光标信息设置进去,利用这个函数
	return 0;
}

运行结果:
在这里插入图片描述
可以看见在运行界面上,没有光标了,对比一下有光标的默认界面:
在这里插入图片描述
可以看见有光标的界面很影响游戏体验,所以,在运行游戏之前需要把光标去掉。把结构体cursor_info的一个成员bVisible设置为false
接下来介绍识别键盘按键的函数:
GetAsyncKeyState
将键盘上每一个键的虚拟键值传递给函数,函数通过返回值分辨键盘的状态。
GetAsyncKeyState返回值是short类型,在上一次调用GetAsyncKeyState函数后,如果返回的16位的short数据中,最高位是1,说明按键的状态是按下,如果最高位是0,说明键盘的状态是抬起,如果最低位被置为1则说明,该键被安国,否则为0.
意思就是:我们可以通过这个函数来检测键盘上的键是否被按过时,这个函数会返回一个shor类型的值,而short类型的值占十六个比特位,
00000000 00000000
最高位如果是1,则当前的状态是按下 1-按下
最高位如果是0,则当前的状态是抬起 0-抬起
最低位如果置为1,则说明按过 1-按过
最低位如果置为0,则说明没有按过 0-没有按过
对于GetAsyncKeyState这个函数来说是识别键盘按键的,但是如何识别呢,按照上面的叙述我们先不管高位,如果低位是1就表示按过,如果低位是0就表示未按过,那我们可以将这个这个函数&1,如果是按过最后得到的结果就是1,因为不管高位是多少&0都是0,所以只用考虑最低位,如果最后结果是0就表示未按过
接下来我们用代码测试一下:

#define KEY_PRESS(vk) (GetAsyncKeyState(vk)&0x1? 1:0)
int main()
{
	while (1)
	{
		if (KEY_PRESS(0x30))
			printf("0\n");
		else if (KEY_PRESS(0x31))
			printf("1\n");
		else if (KEY_PRESS(0x32))
			printf("2\n");
		else if (KEY_PRESS(0x33))
			printf("3\n");
		else if (KEY_PRESS(0x34))
			printf("4\n");
		else if (KEY_PRESS(0x35))
			printf("5\n");
		else if (KEY_PRESS(0x36))
			printf("6\n");
		else if (KEY_PRESS(0x37))
			printf("7\n");
		else if (KEY_PRESS(0x38))
			printf("8\n");
		else if (KEY_PRESS(0x39))
			printf("9\n");
	}
}

在这里插入图片描述

注意:这里运行界面上出现的12345并不是scanf函数输入上去的,而是我们通过识别键盘按钮打印在屏幕上面的。
接下来介绍宽字符概念
宽字符
普通字符占一个字节,但是宽字符占两个字节,再简单一点讲一下,C语言的国际化特性相关的知识,过去C语言并不适合非英语国家使用,C语言最初假定字符都是单字节的,但是这些假定并不是再世界上任何地方都适用。
我们将宽字符和窄字符对比一下:
在这里插入图片描述
可以看到,上面的是两个窄字符,下面是一个宽字符,可以看出两个窄字符等于一个宽字符的

C语言默认采用ASCII编码的,ASCII字符采用的是单字节编码,且只使用了单字节的七位。
后来为了使C语言适应国际化,C语言的标准中不断加入了国际化的支持。
<locale.h>提供的函数用于控制c标准库中对于不同的地区会产生不一样行为的部分。
在标准中,依赖地区的部分有以下几项:
数字量的格式
货币量的格式
字符集
日期和时间的表示形式
类项:
通过修改地区,程序可以改变它的行为来适应世界的不同区域,但地区的改变可能影响库的许多部分,其中一部分可能是我们不希望修改的。所以C语言支持针对不同的类项进行修改,下面的一个宏,指定一个类项:
LC_COLLATE:影响字符串比较函数等等……………
Setlocale函数
用于修改当前地区,可以针对一个类项进行修改,也可以针对所有类型进行修改。
Setlocale的第一个参数可以是前面说明的类项中的一个,那么每次只会影响一个类项,如果第一个参数是LC_ALL,就会影响所有类项。。
C标准给第二个参数仅定义了两种可能得取值:“c”(正常模式)和“本地模式”。
在任意程序执行开始,都会隐藏式直系那个调用这个函数并选择正常模式,默认就是正常模式,对应的所有项都是正常模式。
当程序运行起来之后想改变地区,就只能显示调用setlocale函数,用“”作为第二个参数,调用setlocale函数就切换为本地模式,这种模式下的程序适应本地环境。
Setlocale的返回值是一个字符串指针,表示已经设置好的格式,如果调用失败,则返回空指针。
Setlocale可以用来查询当前地区 ,这时第二个参数设为NULL就可以了。
下面代码演示的是 C语言默认的本地信息是什么?
在这里插入图片描述
宽字符的打印
宽字符的字面量必须加上前缀L,否则C语言会把字面量当做宅字符类型处理,前缀L在单引号前面,表示宽字符,宽字符的打印用wprintf,对应wprintf()的占位符为%lc;在双引号前面,表示宽字符串,对应wprintf()的占位符为%ls。

二、贪吃蛇的数据结构的设计

1.整体框架


void test()
{
	//创建贪吃蛇
	Snake snake = { 0 };
	GameStart(&snake);//游戏开始前的初始化
	GameRun(&snake);//玩游戏的过程
	GameEnd(&snake);//善后工作
}


int main()
{
	//修改适配本地中文环境
	setlocale(LC_ALL, "");
	test();//贪吃蛇游戏测试
}

整个游戏分为三个部分:游戏的初始化,游戏的运行过程、和最后游戏结束的界面

2.初始化界面

游戏需要搭建一个框架:
在这里插入图片描述
按照宽字符定义来说,墙的长度就应该是宽的两倍。
打印一个宽为28的棋盘长就必须是56,代码如下:

void CreateMap()
{
	//上
	SetPos(0, 0);
	int i = 0;
	for (i = 0;i < 56;i+=2)
	{
		wprintf(L"%lc", WALL);
	}
	//下
	SetPos(0, 25);
	for (i = 0;i < 56;i += 2)
	{
		wprintf(L"%lc", WALL);
	}
	//左
	SetPos(0, 1);
	for (i = 0;i < 24;i++)
	{
		wprintf(L"%lc\n", WALL);
	}
	//右
	for (i = 0;i < 24;i++)
	{
		SetPos(54, 1 + i);
		wprintf(L"%lc", WALL);
	}
}

运行结果:
在这里插入图片描述
进入棋盘前的信息:

void SetPos(int x, int y)
{
	//获得设备句柄
	HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
	//根据句柄设置光标的位置
	COORD pos = { x,y };
	SetConsoleCursorPosition(handle, pos);
}


void WelcomeGame()
{
	//打印欢迎信息
	SetPos(38, 13);
	printf("欢迎来到贪吃蛇小游戏\n");
	SetPos(38, 15);
	system("pause");
	system("cls");

	//功能介绍信息
	SetPos(32, 12);
	printf("用↑.↓.←.→来控制蛇的移动,F4是加速,F5是减速");
	SetPos(32, 14);
	printf("加速能得到更高的分数");
	SetPos(32, 16);
	system("pause");
	system("cls");
}

我们还需要设置蛇的初始位置,对于蛇身,我们可以通过单向链表将其串起来,然后遍历逐个打印,


//游戏的状态
enum GAME_STATUS
{
	OK = 1,//正常运行
	ESC,//按了ESC键退出,正常退出
	KILL_BY_WALL,//撞墙了
	KILL_BY_SELF,//撞到自身了
};

//蛇行走的方向
enum DIRECTION
{
	UP=1,//向上
	DOWN,//向下
	LEFT,//向左
	RIGHT,//向右
};
//蛇身节点的定义
typedef struct SnakeNode
{
	int x;
	int y;
	struct SnakeNode* next;
}SnakeNode;
typedef struct SnakeNode* pSnakeNode;
//每一个节点在动的时候都在变化,所以每一个动的时候都要用坐标来记录其位置
//用结构体指针指向下一个蛇身的节点


//贪吃蛇---整个游戏的维护
typedef struct Snake
{
	pSnakeNode pSnake;//维护整条蛇的指针
	pSnakeNode pFood;//指向食物的指针
	int score;//当前累计的分数
	int foodweight;//一个食物的分数
	int SleepTime;//蛇休眠的时间,休眠的时间越短,蛇的速度越快,休眠的时间越长,休眠的速度越慢
	enum GAME_STATUS status;//游戏当前的状态
	enum DIRECTION dir;
	//......
}Snake,*pSnake;

出了蛇身需要管理,还有分数和单个食物的分数,还有蛇的休眠时间,还有当前游戏状态,蛇的方向都需要管理,我们一并将其封装在一个结构体中管理。

蛇的默认其实位置,我们就将其定在(24,5)这个坐标上。
接下来,打印蛇身并初始化分数还有状态等信息…

void InitSnake(pSnake ps)
{
	//创建5个蛇身节点
	pSnakeNode cur = NULL;
	int i = 0;
	for (i = 0;i < 5;i++)
	{
		cur = (pSnakeNode)malloc(sizeof(SnakeNode));
		if (cur == NULL)
		{
			printf("InitSnake():malloc() fail\n");
			return;
		}
		cur->x = POS_X + i * 2;
		cur->y = POS_Y;
		cur->next = NULL;

		//头插法
		if (ps->pSnake == NULL)
		{
			ps->pSnake = cur;
		}
		else
		{
			cur->next = ps->pSnake;
			ps->pSnake = cur;  
		}
	}
	//打印蛇身
	cur = ps->pSnake;
	for (i = 0;i < 5;i++)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	//贪吃蛇的其他信息初始化
	ps->dir = RIGHT;
	ps->foodweight = 10;
	ps->pFood = NULL;
	ps->score = 0;
	ps->SleepTime = 200;//200毫秒
	ps->status = OK;
}

对于蛇身,我们起始位置,先创建五个节点,意思就是游戏开始蛇的长度便是5,然后我们利用头插法将其串起来。

创建食物:

void CreateFood(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);
	pSnakeNode cur = ps->pSnake;
	while (cur != NULL)
	{
		if (cur->x == x && cur->y == y)
		{
			goto again;
		}
		cur = cur->next;
	}
	//创建食物
	pSnakeNode food = (pSnakeNode)malloc(sizeof(SnakeNode));
	if (food == NULL)
	{
		perror("CreateFood():malloc() ");
		return;
	}
	food->x = x;
	food->y = y;
	ps->pFood = food;
	SetPos(food->x, food->y);
	wprintf(L"%lc", FOOD);
}

对于创建食物我们需要注意的是,食物也是一个蛇的节点,因为蛇吃了食物之后,会变长,所以食物会变成蛇的新的节点。

我们将以上函数都封装在一个文件当中:

void GameStart(pSnake ps)
{
	//设置控制台的信息,窗口大小,窗口名
	system("mode con cols=100 lines=30");
	system("title 贪吃蛇");


	//隐藏光标
	HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
	CONSOLE_CURSOR_INFO CursorInfo;
	GetConsoleCursorInfo(handle, &CursorInfo);
	CursorInfo.bVisible = false;
	SetConsoleCursorInfo(handle, &CursorInfo);

	//打印欢迎信息
	WelcomeGame();
	//绘制地图
	CreateMap();
	//初始化蛇
	InitSnake(ps);
	//创建食物
	CreateFood(ps);
}

3.贪吃蛇的运行

void GameRun(pSnake ps)
{
	//打印帮助信息
	PrintHelpInfo();
	do
	{
		//当前的分数情况
		SetPos(65, 9);
		printf("总分:%5d\n", ps->score);
		SetPos(65, 10);
		printf("食物的分值:%02d\n", ps->foodweight);
		//监测按键
		//上、下、左、右、ESC、space、F1、F2
		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_RIGHT) && ps->dir != LEFT)
		{
			ps->dir = RIGHT;
		}
		else if (KEY_PRESS(VK_LEFT) && ps->dir != RIGHT)
		{
			ps->dir = LEFT;
		}
		else if (KEY_PRESS(VK_ESCAPE))
		{
			ps->status = ESC;
			break;
		}
		else if (KEY_PRESS(VK_SPACE))
		{
			//游戏暂停
			pause();//暂停和恢复暂停
		}
		else if (KEY_PRESS(VK_F1))
		{
			if (ps->SleepTime >= 80)
			{
				ps->SleepTime -= 30;
				ps->foodweight += 2;
			}
		}
		else if (KEY_PRESS(VK_F2))
		{
			if (ps->foodweight > 2)
			{
				ps->SleepTime += 30;
				ps->foodweight -= 2;
			}
		}
		//睡眠一下
		Sleep(ps->SleepTime);
		//走一步
		SnakeMove(ps);
	} while (ps->status==OK);
}

先利用上面讲的识别键盘按键的函数把需要用到的按键给识别了,然后在最前面打印需要的信息:

void PrintHelpInfo()
{
	SetPos(65, 14);
	printf("不能穿墙,不能咬到自己\n");
	SetPos(65, 15);
	printf("用↑.↓.←.→分别控制蛇的移动\n");
	SetPos(65, 16);
	printf("F1:加速  F2:减速");
	SetPos(65, 17);
	printf("ESC:退出游戏  space:暂停游戏");
}

然后对蛇移动的函数进行封装:

==注意:对于蛇的移动,我们需要对他的异于其他方向的临近的坐标进行统计,然后用Switch case语句把每一个方向对应的下一个坐标写出来:
坐标写出来之后,就需要判断是否是食物,如果是食物的话我们就需要将食物同化为蛇身的节点,如果不是食物的话,就需要将下一个节点作为蛇的头,然后将最后一个尾节点给删去,这样就达到了移动的效果

void SnakeMove(pSnake ps)
{
	pSnakeNode pnext = (pSnakeNode)malloc(sizeof(SnakeNode));
	if (pnext == NULL)
	{
		perror("SnakeMove():malloc()");
		return;
	}
	pnext->next = NULL;
	switch (ps->dir)
	{
	case UP:
		pnext->x = ps->pSnake->x;
		pnext->y = ps->pSnake->y - 1;
		break;
	case DOWN:
		pnext->x = ps->pSnake->x;
		pnext->y = ps->pSnake->y + 1;
		break;
	case RIGHT:
		pnext->x = ps->pSnake->x + 2;
		pnext->y = ps->pSnake->y;
		break;
	case LEFT:
		pnext->x = ps->pSnake->x - 2;
		pnext->y = ps->pSnake->y;
		break;
	}
	if (NextIsFood(ps,pnext))
	{
		//吃掉食物
		EatFood(ps,pnext);
	}
	else
	{
		//不是食物正常走一步
		NotEatFood(ps,pnext);
	}

	//监测撞墙
	KillByWall(ps);
	//撞到自己
	KillBySelf(ps);
}

如果是食物:

void EatFood(pSnake ps, pSnakeNode pnext)
{
	pnext->next = ps->pSnake;
	ps->pSnake = pnext;
	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);
}

如果不是食物:

void NotEatFood(pSnake ps, pSnakeNode pnext)
{
	pnext->next = ps->pSnake;
	ps->pSnake = pnext;
	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;
}

判断完是不是食物之后还需要判断下一个位置是否是墙或者是否是自己蛇身除头外的任何一个节点:

//监测撞墙
void KillByWall(pSnake ps)
{
	if (ps->pSnake->x == 0 || ps->pSnake->x ==54 )
	{
		ps->status = KILL_BY_WALL;
	}
	else if (ps->pSnake->y == 0 || ps->pSnake->y == 26)
	{
		ps->status = KILL_BY_WALL;
	}
}

//撞到自己
void KillBySelf(pSnake ps)
{
	pSnakeNode next = ps->pSnake->next;
	while (next != NULL)
	{
		if (next->x == ps->pSnake->x && next->y == ps->pSnake->y)
		{
			ps->status = KILL_BY_SELF;
			return;
		}
		next = next->next;
	}
}

4.游戏的退出

游戏的退出需要根据游戏不同的状态来打印不同的信息,比如正常退出,又比如撞墙,又比如撞到自己的蛇身。
还有就是游戏结束后我们需要对蛇的蛇身进行内存释放,还有食物等等,将链表的每个节点进行遍历释放。

void GameEnd(pSnake ps)
{
	SetPos(25, 12);
	switch (ps->status)
	{
		//正常退出
	case ESC:
		printf("正常退出\n");
		break;
		//撞自己
	case KILL_BY_WALL:
		printf("很遗憾,撞墙了,游戏结束\n");
		break;
		//撞墙
	case KILL_BY_SELF:
		printf("很遗憾,咬到自己了,游戏结束\n");
		break;
	}
	SetPos(25, 26);
	//释放贪吃蛇的链表资源
	pSnakeNode cur = ps->pSnake;
	pSnakeNode next = NULL;
	while (cur)
	{
		next = cur->next;
		free(cur);
		cur = next;
	}
	free(ps->pFood);
	ps = NULL;
}

三、整体代码

//test.c
#include "snake.h"


void test()
{
	//创建贪吃蛇
	int ch = 0;
	do
	{ 
		Snake snake = { 0 };
		GameStart(&snake);//游戏开始前的初始化
		GameRun(&snake);//玩游戏的过程
		GameEnd(&snake);//善后工作
		SetPos(25, 13);
		printf("再来一局吗?(Y/N):");
		ch = getchar();
		getchar();//清理\n
	} while (ch == 'Y' || ch == 'y');
}


int main()
{
	//修改适配本地中文环境
	setlocale(LC_ALL, "");


	test();//贪吃蛇游戏测试
}

//snake.c
#include "snake.h"


void SetPos(int x, int y)
{
	//获得设备句柄
	HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
	//根据句柄设置光标的位置
	COORD pos = { x,y };
	SetConsoleCursorPosition(handle, pos);
}


void WelcomeGame()
{
	//打印欢迎信息
	SetPos(38, 13);
	printf("欢迎来到贪吃蛇小游戏\n");
	SetPos(38, 15);
	system("pause");
	system("cls");

	//功能介绍信息
	SetPos(32, 12);
	printf("用↑.↓.←.→来控制蛇的移动,F4是加速,F5是减速");
	SetPos(32, 14);
	printf("加速能得到更高的分数");
	SetPos(32, 16);
	system("pause");
	system("cls");
}


void CreateMap()
{
	//上
	SetPos(0, 0);
	int i = 0;
	for (i = 0;i < 56;i+=2)
	{
		wprintf(L"%lc", WALL);
	}
	//下
	SetPos(0, 25);
	for (i = 0;i < 56;i += 2)
	{
		wprintf(L"%lc", WALL);
	}
	//左
	SetPos(0, 1);
	for (i = 0;i < 24;i++)
	{
		wprintf(L"%lc\n", WALL);
	}
	//右
	for (i = 0;i < 24;i++)
	{
		SetPos(54, 1 + i);
		wprintf(L"%lc", WALL);
	}
}

void InitSnake(pSnake ps)
{
	//创建5个蛇身节点
	pSnakeNode cur = NULL;
	int i = 0;
	for (i = 0;i < 5;i++)
	{
		cur = (pSnakeNode)malloc(sizeof(SnakeNode));
		if (cur == NULL)
		{
			printf("InitSnake():malloc() fail\n");
			return;
		}
		cur->x = POS_X + i * 2;
		cur->y = POS_Y;
		cur->next = NULL;

		//头插法
		if (ps->pSnake == NULL)
		{
			ps->pSnake = cur;
		}
		else
		{
			cur->next = ps->pSnake;
			ps->pSnake = cur;  
		}
	}

	//打印蛇身
	cur = ps->pSnake;
	for (i = 0;i < 5;i++)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	//贪吃蛇的其他信息初始化
	ps->dir = RIGHT;
	ps->foodweight = 10;
	ps->pFood = NULL;
	ps->score = 0;
	ps->SleepTime = 200;//200毫秒
	ps->status = OK;
}

void CreateFood(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);
	pSnakeNode cur = ps->pSnake;
	while (cur != NULL)
	{
		if (cur->x == x && cur->y == y)
		{
			goto again;
		}
		cur = cur->next;
	}
	//创建食物
	pSnakeNode food = (pSnakeNode)malloc(sizeof(SnakeNode));
	if (food == NULL)
	{
		perror("CreateFood():malloc() ");
		return;
	}
	food->x = x;
	food->y = y;
	ps->pFood = food;
	SetPos(food->x, food->y);
	wprintf(L"%lc", FOOD);
}
void GameStart(pSnake ps)
{
	//设置控制台的信息,窗口大小,窗口名
	system("mode con cols=100 lines=30");
	system("title 贪吃蛇");


	//隐藏光标
	HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
	CONSOLE_CURSOR_INFO CursorInfo;
	GetConsoleCursorInfo(handle, &CursorInfo);
	CursorInfo.bVisible = false;
	SetConsoleCursorInfo(handle, &CursorInfo);

	//打印欢迎信息
	WelcomeGame();
	//绘制地图
	CreateMap();
	//初始化蛇
	InitSnake(ps);
	//创建食物
	CreateFood(ps);
}



void PrintHelpInfo()
{
	SetPos(65, 14);
	printf("不能穿墙,不能咬到自己\n");
	SetPos(65, 15);
	printf("用↑.↓.←.→分别控制蛇的移动\n");
	SetPos(65, 16);
	printf("F1:加速  F2:减速");
	SetPos(65, 17);
	printf("ESC:退出游戏  space:暂停游戏");
}

void pause()
{
	while (1)
	{
		if (KEY_PRESS(VK_SPACE))
		{
			break;
		}
		Sleep(100);
	}
}

int NextIsFood(pSnake ps, pSnakeNode pnext)
{
	if (ps->pFood->x == pnext->x && ps->pFood->y == pnext->y)
	{
		return 1;
	}
	else
	{
		return 0;
	}
}

void EatFood(pSnake ps, pSnakeNode pnext)
{
	pnext->next = ps->pSnake;
	ps->pSnake = pnext;
	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);
}

void NotEatFood(pSnake ps, pSnakeNode pnext)
{
	pnext->next = ps->pSnake;
	ps->pSnake = pnext;
	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;
}

//监测撞墙
void KillByWall(pSnake ps)
{
	if (ps->pSnake->x == 0 || ps->pSnake->x ==54 )
	{
		ps->status = KILL_BY_WALL;
	}
	else if (ps->pSnake->y == 0 || ps->pSnake->y == 26)
	{
		ps->status = KILL_BY_WALL;
	}
}

//撞到自己
void KillBySelf(pSnake ps)
{
	pSnakeNode next = ps->pSnake->next;
	while (next != NULL)
	{
		if (next->x == ps->pSnake->x && next->y == ps->pSnake->y)
		{
			ps->status = KILL_BY_SELF;
			return;
		}
		next = next->next;
	}
}

void SnakeMove(pSnake ps)
{
	pSnakeNode pnext = (pSnakeNode)malloc(sizeof(SnakeNode));
	if (pnext == NULL)
	{
		perror("SnakeMove():malloc()");
		return;
	}
	pnext->next = NULL;
	switch (ps->dir)
	{
	case UP:
		pnext->x = ps->pSnake->x;
		pnext->y = ps->pSnake->y - 1;
		break;
	case DOWN:
		pnext->x = ps->pSnake->x;
		pnext->y = ps->pSnake->y + 1;
		break;
	case RIGHT:
		pnext->x = ps->pSnake->x + 2;
		pnext->y = ps->pSnake->y;
		break;
	case LEFT:
		pnext->x = ps->pSnake->x - 2;
		pnext->y = ps->pSnake->y;
		break;
	}
	if (NextIsFood(ps,pnext))
	{
		//吃掉食物
		EatFood(ps,pnext);
	}
	else
	{
		//不是食物正常走一步
		NotEatFood(ps,pnext);
	}

	//监测撞墙
	KillByWall(ps);
	//撞到自己
	KillBySelf(ps);
}

void GameRun(pSnake ps)
{
	//打印帮助信息
	PrintHelpInfo();
	do
	{
		//当前的分数情况
		SetPos(65, 9);
		printf("总分:%5d\n", ps->score);
		SetPos(65, 10);
		printf("食物的分值:%02d\n", ps->foodweight);
		//监测按键
		//上、下、左、右、ESC、space、F1、F2
		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_RIGHT) && ps->dir != LEFT)
		{
			ps->dir = RIGHT;
		}
		else if (KEY_PRESS(VK_LEFT) && ps->dir != RIGHT)
		{
			ps->dir = LEFT;
		}
		else if (KEY_PRESS(VK_ESCAPE))
		{
			ps->status = ESC;
			break;
		}
		else if (KEY_PRESS(VK_SPACE))
		{
			//游戏暂停
			pause();//暂停和恢复暂停
		}
		else if (KEY_PRESS(VK_F1))
		{
			if (ps->SleepTime >= 80)
			{
				ps->SleepTime -= 30;
				ps->foodweight += 2;
			}
		}
		else if (KEY_PRESS(VK_F2))
		{
			if (ps->foodweight > 2)
			{
				ps->SleepTime += 30;
				ps->foodweight -= 2;
			}
		}
		//睡眠一下
		Sleep(ps->SleepTime);
		//走一步
		SnakeMove(ps);
	} while (ps->status==OK);
}

void GameEnd(pSnake ps)
{
	SetPos(25, 12);
	switch (ps->status)
	{
		//正常退出
	case ESC:
		printf("正常退出\n");
		break;
		//撞自己
	case KILL_BY_WALL:
		printf("很遗憾,撞墙了,游戏结束\n");
		break;
		//撞墙
	case KILL_BY_SELF:
		printf("很遗憾,咬到自己了,游戏结束\n");
		break;
	}
	SetPos(25, 26);
	//释放贪吃蛇的链表资源
	pSnakeNode cur = ps->pSnake;
	pSnakeNode next = NULL;
	while (cur)
	{
		next = cur->next;
		free(cur);
		cur = next;
	}
	free(ps->pFood);
	ps = NULL;
}

//snake.h
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<locale.h>
#include<stdlib.h>
#include<Windows.h>
#include<stdbool.h>
#define WALL L'□'
#define FOOD L'★'
#define BODY L'●'
#define KEY_PRESS(vk) (GetAsyncKeyState(vk)&0x1? 1:0)


//蛇默认的起始坐标
#define POS_X 24
#define POS_Y 5


//游戏的状态
enum GAME_STATUS
{
	OK = 1,//正常运行
	ESC,//按了ESC键退出,正常退出
	KILL_BY_WALL,//撞墙了
	KILL_BY_SELF,//撞到自身了
};

//蛇行走的方向
enum DIRECTION
{
	UP=1,//向上
	DOWN,//向下
	LEFT,//向左
	RIGHT,//向右
};

//蛇身节点的定义
typedef struct SnakeNode
{
	int x;
	int y;
	struct SnakeNode* next;
}SnakeNode;
typedef struct SnakeNode* pSnakeNode;
//每一个节点在动的时候都在变化,所以每一个动的时候都要用坐标来记录其位置
//用结构体指针指向下一个蛇身的节点


//贪吃蛇---整个游戏的维护
typedef struct Snake
{
	pSnakeNode pSnake;//维护整条蛇的指针
	pSnakeNode pFood;//指向食物的指针
	int score;//当前累计的分数
	int foodweight;//一个食物的分数
	int SleepTime;//蛇休眠的时间,休眠的时间越短,蛇的速度越快,休眠的时间越长,休眠的速度越慢
	enum GAME_STATUS status;//游戏当前的状态
	enum DIRECTION dir;
	//......
}Snake,*pSnake;

//游戏开始的准备环节
void GameStart(pSnake ps);

//欢迎界面
void WelcomeGame();

//绘制地图
void CreateMap();

//初始化贪吃蛇
void InitSnake(pSnake ps);

//创建食物

void CreateFood(pSnake ps);

//游戏运行
void GameRun(pSnake snake);

//打印帮助信息
void PrintHelpInfo();

//蛇的移动
void SnakeMove(pSnake ps);

//判断蛇头的下一个节点是否是食物
int NextIsFood(pSnake ps, pSnakeNode pnext);

//下一步要走的位置处是食物,就吃掉食物
void EatFood(pSnake ps, pSnakeNode pnext);

//下一步要走的位置不是食物,
void NotEatFood(pSnake ps, pSnakeNode pnext);

//监测撞墙
void KillByWall(pSnake ps);

//撞到自己
void KillBySelf(pSnake ps);

//善后工作
void GameEnd(pSnake ps);

//坐标定位
void SetPos(int x, int y);

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

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

相关文章

vue项目在线预览和下载文档

在线预览&#xff1a;利用微软提供的Office Online平台&#xff0c;即可实现在线预览doc、ppt、excel等文档。 地址为&#xff1a;https://view.officeapps.live.com/op/view.aspx 下载&#xff1a;若要实现下载功能&#xff0c;直接将url赋值给a标签的href属性即可 下载实现方…

如何从电脑恢复已删除的文件

意外删除文件可能会导致噩梦般的场景。即使文件被故意删除&#xff0c;您仍然可能需要恢复文件的过去草稿或版本。值得庆幸的是&#xff0c;有多种方法可以恢复电脑上已删除的文件&#xff0c;无论是否花钱。以下是四种最常见的已删除文件恢复方法。 如何从电脑恢复已删除的文件…

移动端常见布局

单独移动端页面&#xff08;主流&#xff09; 1&#xff0c;流式布局&#xff08;百分比布局&#xff09; 流式布局&#xff0c;就是百分比布局&#xff0c;也称非固定像素布局 通过盒子的宽度设置成百分比来根据屏幕的宽度来进行伸缩&#xff0c;不受固定像素的限制&#x…

第二十四天| 77. 组合

Leetcode 77. 组合 题目链接&#xff1a;77 组合 题干&#xff1a;给定两个整数 n 和 k&#xff0c;返回范围 [1, n] 中所有可能的 k 个数的组合。你可以按 任何顺序 返回答案。 思考&#xff1a;回溯法。把回溯法的搜索过程抽象为树形结构。 每次从集合中选取元素&#xff0…

京东广告算法架构体系建设--大规模稀疏场景高性能训练方案演变

一、前言 京东广告训练框架随着广告算法业务发展的特点也在快速迭代升级&#xff0c;回顾近几年大致经历了两次大版本的方案架构演变。第一阶段&#xff0c;随着2016年Tensorflow训练框架的开源&#xff0c;业界开始基于Tensorflow开源框架训练更复杂的模型。模型对特征规模和…

Android之命令行烧写OTA镜像(一百八十五)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

MySQL 备份恢复

1.1 MySQL日志管理 在数据库保存数据时&#xff0c;有时候不可避免会出现数据丢失或者被破坏&#xff0c;这样情况下&#xff0c;我们必须保证数据的安全性和完整性&#xff0c;就需要使用日志来查看或者恢复数据了。 数据库中数据丢失或被破坏可能原因&#xff1a; 误删除数…

算法设计与分析实验:动态规划与回溯

目录​​​​​​​ 一、编辑距离 1.1 具体思路 1.2 思路展示 1.3 代码实现 1.4 复杂度分析 1.5 运行结果 二、买卖股票的最佳时机 2.1 具体思路 2.2 思路展示 2.3 代码实现 2.4 复杂度分析 2.5 运行结果 三、单词拆分 3.1 具体思路 3.2 思路展示 3.3 代码实现…

【Nginx】Ubuntu如何安装使用Nginx反向代理?

文章目录 使用Nginx反向代理2个web接口服务步骤 1&#xff1a;安装 Nginx步骤 2&#xff1a;启动 Nginx 服务步骤 3&#xff1a;配置 Nginx步骤 4&#xff1a;启用配置步骤 5&#xff1a;检查配置步骤 6&#xff1a;重启 Nginx步骤 7&#xff1a;访问网站 proxy_set_header 含义…

Qt程序设计-左侧菜单栏实现

创建项目,在窗体左侧添加widget,右侧上面添加容器,容器里添加label、和关闭按钮,添加stackedwidget。 widget处理 widget里面添加几个toolButton按钮,按需添加,本例子添加4个,一个弹簧verticalSpacer 将几个按钮添加到同一个按钮组。 stackedwidget stackedwidge…

无人机激光雷达标定板

机载激光雷达标定板是用于校准和验证机载激光雷达系统的设备。由于机载激光雷达系统在测量地形、建筑物和植被等方面具有广泛的应用&#xff0c;因此标定板的使用对于确保测量结果的准确性和可靠性至关重要。 标定板通常由高反射率的材料制成&#xff0c;如镀金的玻璃或陶瓷&am…

如何选择最适合的服务器

许多朋友想做一些网站&#xff0c;应用&#xff0c;游戏&#xff0c;小程序等等&#xff0c;都需要接触一个基础&#xff0c;就是服务器。服务器相当于一台24小时不关机的联网电脑&#xff0c;浏览网页或者应用相当于用户在访问这台电脑里的文件。那么如何选择最适合自己的服务…

单片机学习笔记---中断系统(含外部中断)

目录 中断介绍 中断优先级 中断嵌套 中断技术的优点 中断的结构 中断请求源 中断优先级 5个基本中断内部的结构 INT0和INT1 T0和T1 串口 中断寄存器 IE TCON 中断优先级列表 中断号 中断响应的条件 代码编写实例分析 外部中断硬件电路分析 这一节我们主要是…

spring boot yaml文件中如何设置duration对象值

Spring Boot对表示持续时间有专门的支持。如果您公开java.time.Duration属性&#xff0c;则应用程序对应Duration类型的属性有以下格式可用: long类型的常规表示(使用毫秒作为默认单位&#xff0c;除非指定了DurationUnit)java.time.Duration 使用的标准ISO-8601格式其中值和单…

echarts绘制2D地图

简介 此案例需要用到世界地图json数据&#xff0c;我把json文件放到我的资源中&#xff0c;有需要的自行下载。 安装插件 // 安装echats npm install echarts --save项目中引用 1&#xff0c;引入安装的echarts插件 import * as echarts from echarts;2&#xff0c;引入世…

Pyecharts炫酷散点图构建指南【第50篇—python:炫酷散点图】

文章目录 Pyecharts炫酷散点图构建指南引言安装Pyecharts基础散点图自定义散点图样式渐变散点图动态散点图高级标注散点图多系列散点图3D散点图时间轴散点图笛卡尔坐标系下的极坐标系散点图 总结&#xff1a; Pyecharts炫酷散点图构建指南 引言 在数据可视化领域&#xff0c;…

一些整洁代码的原则

1. 改善if判断 当代码中出现大量防卫代码的时候&#xff08;Guard Code&#xff09;&#xff0c;需要考虑是否可以改造成fail fast的模式完成。 但是给出的建议是&#xff0c;不要过分使用防卫代码 2. 无用代码&#xff0c;just delete it&#xff01; 作者给出结论的前提是&…

Java常用

文章目录 基础基础数据类型内部类Java IOIO多路复用重要概念 Channel **通道**重要概念 Buffer **数据缓存区**重要概念 Selector **选择器** 关键字final 元注解常用接口异常处理ErrorException JVM与虚拟机JVM内存模型本地方法栈虚拟机栈 Stack堆 Heap方法区 Method Area (JD…

C#拆分字符串,正则表达式Regex.Split 方法 vs String.Split 方法

目录 一、使用的方法 1.使用Split(String, String)方法 2.String.Split 方法 二、源代码 1.源码 2.生成效果 使用正则表达式可以拆分指定的字符串。同样地&#xff0c;使用字符串对象的Split方法也可以实现此功能。使用字符串对象的Split方法可以根据用户选择的拆分条件&…

2024年【A特种设备相关管理(电梯)】考试总结及A特种设备相关管理(电梯)模拟考试题库

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 A特种设备相关管理&#xff08;电梯&#xff09;考试总结是安全生产模拟考试一点通生成的&#xff0c;A特种设备相关管理&#xff08;电梯&#xff09;证模拟考试题库是根据A特种设备相关管理&#xff08;电梯&#x…