【c语言】贪吃蛇

当我们不想学习新知识的时候,并且特别无聊,就会突然先看看别人怎么写游戏的,今天给大家分享的是贪吃蛇,所需要的知识有结构体,枚举,以及easy-x图形库的一些基本函数就完全够用了,本来我想插入游戏的音乐,但是没找到贪吃蛇的背景音乐,所以就没加,大家可以参考我之前的博文里面有如何加音乐的教程

包含的头文件

#include <stdio.h>
#include <graphics.h>//图形库头文件
#include <conio.h>//_kbhit()函数头文件
#include <stdlib.h>//srand函数头文件
#include <time.h>//time()函数头文件

初始化界面

int main()
{
	initgraph(640,480);//初始化界面
	while (1);
	
	return 0;

}

初始化长640,宽480的界面,while(1);使得界面一直出现
在这里插入图片描述

设置背景颜色

int main()
{
	initgraph(640,480);
	setbkcolor(RGB(31, 29, 66));//设置背景颜色
	cleardevice();//清空绘图设备
	while (1);
	
	return 0;

}

setbkcolor中的RGB(31, 29, 66),打开微信,同时按下Alt键,A,可获取你截图中颜色的RGB;
在这里插入图片描述

定义蛇的信息结构体,枚举定义蛇的方向

#define SNAKE_NUM 500// 最大有500节
struct Snake
{
	int size;//蛇的节数
	int dir;//蛇的方向
	int speed;//蛇的速度
	POINT coor[SNAKE_NUM];//蛇的坐标



}snake;
enum DIR  //表示蛇的方向
{
	UP,//向上
	DOWN,//向下
	LEFT,//向左
	RIGHT,//向右
};

数据初始化

void gameinit()
{    
	snake.size = 3;//初始化蛇刚开始为三节
	snake.speed = 8;//初始速度是每按一次方向键改变八个像素的位置
	snake.dir = RIGHT;//初始蛇的方向是向右
	for (int i = 0; i < snake.size; i++)
	{
		snake.coor[i].x = 40 - 10 * i;
		snake.coor[i].y = 10;


	}




}

在for循环内定义好每一节的圆心坐标,记snake.coor[0].x为蛇头的圆心坐标的横坐标,记snake.coor[0].y为蛇头的圆心坐标的纵坐标,初始化三节,for循环定义这三节的坐标,头的圆心坐标为(40,10)中间为(30,10),最后为(20,10);初始化蛇应该是横着的。

整合函数

函数写的太乱,我们可以将相同的功能放在一个函数里面,比如说将initgraph(640, 480);放在gameinit()里面,将画的都放在gamedraw()函数里面。

//数据初始化
void gameinit()
{
	initgraph(640, 480);
	snake.size = 3;
	snake.speed = 8;
	snake.dir = RIGHT;
	for (int i = 0; i < snake.size; i++)
	{
		snake.coor[i].x = 40 - 10 * i;
		snake.coor[i].y = 10;


	}




}
void gamedraw()
{
	BeginBatchDraw();//双缓冲消除闪屏
	setbkcolor(RGB(31, 29, 66));//设置背景颜色

	cleardevice();//清空绘图设备


	
	//绘制蛇
	setfillcolor(RED);//设置蛇的颜色
	for (int i = 0; i < snake.size; i++)
	{
	solidcircle(snake.coor[i].x, snake.coor[i].y, 5);//循环绘制蛇的三节身体

	}
	EndBatchDraw();//双缓冲消除闪屏


}
int main()
{
	
	gameinit();
	gamedraw();

	while (1);
	
	return 0;

}

在绘制蛇那里setfillcolor(RED);//设置蛇的颜色,在设置蛇的颜色下面的循环中是画初始化好的三节蛇的身体,solidcircle(snake.coor[i].x, snake.coor[i].y, 5);这个是画实心圆的函数,前两个参数为圆的横纵坐标,第三个参数是圆的半径
在这里插入图片描述

键盘控制移动方向

void keycontrol()
{
	//72 80 75 77
	
	if (_kbhit())
	{
		switch (_getch())
		{
		case 'w':
		case 'W':
		case 72:
			if (snake.dir != DOWN)
			{
				snake.dir = UP;
			}
			break;
		case 's':
		case 'S':
		case 80:
			if (snake.dir != UP)
			{
				snake.dir = DOWN;
			}
			break;
		case 'a':
		case 'A':
		case 75:
			if (snake.dir != RIGHT)
			{
				snake.dir = LEFT;
			}
			break;
		case 'd':
		case 'D':
		case 77:
			if (snake.dir != LEFT)
			{
				snake.dir = RIGHT;
			}
			break;











		}
	}




}

_kbhit()是一个C和C++函数,用于非阻塞地响应键盘输入事件,即键盘敲击。它检查当前是否有键盘输入,如果有则返回一个非0值,否则返回0。
w,s,a,d对应的虚拟键值为72,80,75,77.当_kbhit()返回不为0,则有键按下,_getch 是 C 语言中常用的一个函数,它可以获取用户在控制台输入的一个字符,并且不需要回车。当按下方向键后,_getch()读取按下的字符,将蛇的方向改变成对应的方向键。为什么这里有条件判断语句if,是因为蛇在向一个方向走的时候不能直接往相反的方向走.比如说按下w,往上走,除了向下走外,向左走,和向右走的时候按向上走就可以向上走。

蛇的移动

void snakemove()
{
	for (int i = snake.size-1; i>0; i--)//身体跟着头移动
	{
		snake.coor[i] = snake.coor[i - 1];
		
	}
	switch (snake.dir)//头移动
	{
	case RIGHT:
		snake.coor[0].x+=snake.speed;
		if (snake.coor[0].x > 640)
		{
			snake.coor[0].x = 0;
		}
		break;
	case UP:
		snake.coor[0].y-= snake.speed;
		if (snake.coor[0].y < 0)
		{
			snake.coor[0].y = 480;
		}
		break;
	case DOWN:
		snake.coor[0].y+=snake.speed;
		if (snake.coor[0].y > 480)
		{
			snake.coor[0].y = 0;


		}
		break;
	case LEFT:
		snake.coor[0].x-=snake.speed;
		if (snake.coor[0].x < 0)
		{
			snake.coor[0].x = 640;
		}
		break;







	}
	


}

每次调用snakemove函数,让后面每一节等于前面那一节的坐标,就能实现尾巴身体跟着头动,switch中是此时头移动的方向,向哪个方向移动,对应方向上的x坐标或者y坐标就+或者-snake.speed;比如说向左就是对应x-snake.speed,向右就是x+snake.speed;向上就是y-snake.speed;向下就是y+snake.speed;这里为什么有判断语句if,这里是因为当蛇头到达边界位置的话,就可以从相反的边界出来,界面的大小是640*480;比如说蛇向左走,遇到左边界,就把蛇头的x坐标置为640.

主函数变化

int main()
{
	
	gameinit();
	gamedraw();

	while (1)
	{
		gamedraw();
		keycontrol();
		snakemove();
		Sleep(20);
	
	}
	
	return 0;

}

将键盘控制函数和蛇的移动的函数放在while循环里面,因为要无时无刻地获取从键盘上读来地值和蛇的移动,当不加Sleep函数时,蛇跑的特别快,让程序休眠20ms
视频演示

20230921_173904

食物信息结构体定义

struct food
{
	int x;//食物的横坐标
	int y;//食物的纵坐标
	bool flag;//食物是否被吃
	int r;//食物半径
	DWORD color;//食物颜色



}food;

食物信息初始化(修改数据初始化函数)

//数据初始化
void gameinit()
{    //设置随机数种子
	srand((unsigned int)time(NULL));//时间不断变化,
	initgraph(640, 480);//初始化界面
	snake.size = 3;
	snake.speed = 8;
	snake.dir=RIGHT;
	for (int i = 0;i<snake.size;i++)
	{
		snake.coor[i].x = 40-10*i;
		snake.coor[i].y = 10;


	}
	//随机初始化食物的横纵坐标
	food.x = rand() % 640;
	food.y = rand() % 480;
	food.color = RGB(rand() % 256, rand() % 256, rand() % 256);
	food.r = rand() % 10 + 5;
	food.flag = true;
	


}

因为食物的位置,颜色,以及食物的半径要随机生成,必须用到rand函数生成随机数,但rand生成的随机数是不变的,所以要通过srand函数生成随机数种子,食物必须在界面内,所以food.x=rand()%640的话,food.x的范围为0-639,同理food.y,以及RGB的三个参数都是0-255的,所以对256取余数,半径是在0-9之间,在加上5,食物半径就在5-14之间,food.flag 定义的是食物是否被吃掉,没被吃掉就为true,吃掉后food.flag变为false.

蛇吃食物

void eatfood()
{
	if (food.flag&&snake.coor[0].x>=food.x-food.r&&snake.coor[0].x<=food.x+food.r  && snake.coor[0].y >= food.y - food.r && snake.coor[0].y <= food.y + food.r)
	{
		food.flag = false;
		snake.size++;


	}
	if (!food.flag)//食物消失重新生成一个
	{
		food.x = rand() % 640;
		food.y = rand() % 480;
		food.color = RGB(rand() % 256, rand() % 256, rand() % 256);
		food.r = rand() % 10 + 5;
		food.flag = true;



	}




}

如果要吃掉食物用蛇头圆心的坐标等于食物圆心的坐标的话,是不容易吃到食物的,所以只要蛇头的圆心坐标落入以食物圆心做出的外切正方形的范围内的话,就算吃到,对应食物正方形的边界坐标为food.x-food.r(左边界),
food.x+food.r(右边界),food.y+food.r(上边界),food.y-food.r(下边界),只要蛇头圆心落入该范围内就算吃到,吃到的话,food.flag=false;表示食物被吃掉,并且蛇的长度snake.size++;当食物被吃掉后,food.flag=0,此时!food.flag=1;立即生成下一个食物坐标,如果这里判断蛇头是否落在食物范围内时,如果不加&&food.flag的话,蛇吃第一个食物,会由三节直接变成五节在这里插入图片描述

绘制食物(修改绘制函数)

void gamedraw()
{
	BeginBatchDraw();
	setbkcolor(RGB(31, 29, 66));//设置背景颜色

	cleardevice();//清空绘图设备


	
	//绘制蛇
	setfillcolor(RED);//设置蛇的颜色
	for (int i = 0; i < snake.size; i++)
	{
		solidcircle(snake.coor[i].x, snake.coor[i].y, 5);


	}
	//绘制食物
	if (food.flag)//如果food.flag==1的话就绘制食物
	{
		solidcircle(food.x, food.y, food.r);
	}

	EndBatchDraw();


}

主函数修改

int main()
{
	
	gameinit();
	gamedraw();

	while (1)
	{
		gamedraw();
		keycontrol();
		snakemove();
		eatfood();//增加的
		Sleep(20);
	
	}
	
	return 0;

}

游戏暂停函数

void gamestop()//空格,游戏暂停
{
	if (_kbhit())
	{
		if (_getch()== ' ')
		{
			while (_getch() != ' ');
		}



	}



}

按下空格游戏暂停,_kbhit函数作用上面解释过了,然后如果从键盘读取的空格后,缓冲区没东西,就在一直循环,在按一次就继续游戏,注意修改的主函数

修改主函数

int main()
{
	
	gameinit();
	gamedraw();

	while (1)
	{
		gamestop();
		gamedraw();
		keycontrol();
		
		snakemove();
		eatfood();
		Sleep(20);
	
	}
	
	return 0;

}

程序源码

#include <stdio.h>
#include <graphics.h>
#include <conio.h>
#include <stdlib.h>
#define SNAKE_NUM 500
enum DIR  //表示蛇的方向
{
	UP,
	DOWN,
	LEFT,
	RIGHT,
};
struct Snake//蛇结构体
{
	int size;//蛇的节数
	int dir;//蛇的方向
	int speed;//蛇的速度
	POINT coor[SNAKE_NUM];//节数



}snake;
struct food//食物结构体
{
	int x;
	int y;
	bool flag;//食物是否被吃
	int r;//食物半径
	DWORD color;//食物颜色



}food;
//数据初始化
void gameinit()
{
	initgraph(640, 480);
	snake.size = 3;
	snake.speed = 8;
	snake.dir = RIGHT;
	for (int i = 0; i < snake.size; i++)
	{
		snake.coor[i].x = 40 - 10 * i;
		snake.coor[i].y = 10;


	}




}
void gamestop()//空格,游戏暂停
{
	if (_kbhit())
	{
		if (_getch() == ' ')
		{
			while (_getch() != ' ');
		}



	}



}
void gamedraw()//画函数
{
	BeginBatchDraw();
	setbkcolor(RGB(31, 29, 66));//设置背景颜色

	cleardevice();//清空绘图设备


	
	//绘制蛇
	setfillcolor(RED);//设置蛇的颜色
	for (int i = 0; i < snake.size; i++)
	{
		solidcircle(snake.coor[i].x, snake.coor[i].y, 5);


	}
	//绘制食物
	if (food.flag)
	{
		solidcircle(food.x, food.y, food.r);
	}

	EndBatchDraw();


}
void keycontrol()//键盘控制方向
{
	//72 80 75 77

	if (_kbhit())
	{
		switch (_getch())
		{
		case 'w':
		case 'W':
		case 72:
			if (snake.dir != DOWN)
			{
				snake.dir = UP;
			}
			break;
		case 's':
		case 'S':
		case 80:
			if (snake.dir != UP)
			{
				snake.dir = DOWN;
			}
			break;
		case 'a':
		case 'A':
		case 75:
			if (snake.dir != RIGHT)
			{
				snake.dir = LEFT;
			}
			break;
		case 'd':
		case 'D':
		case 77:
			if (snake.dir != LEFT)
			{
				snake.dir = RIGHT;
			}
			break;











		}
	}




}
void snakemove()//蛇的移动
{
	for (int i = snake.size - 1; i > 0; i--)//身体跟着头移动
	{
		snake.coor[i] = snake.coor[i - 1];

	}
	switch (snake.dir)//头移动
	{
	case RIGHT:
		snake.coor[0].x += snake.speed;
		if (snake.coor[0].x > 640)
		{
			snake.coor[0].x = 0;
		}
		break;
	case UP:
		snake.coor[0].y -= snake.speed;
		if (snake.coor[0].y < 0)
		{
			snake.coor[0].y = 480;
		}
		break;
	case DOWN:
		snake.coor[0].y += snake.speed;
		if (snake.coor[0].y > 480)
		{
			snake.coor[0].y = 0;


		}
		break;
	case LEFT:
		snake.coor[0].x -= snake.speed;
		if (snake.coor[0].x < 0)
		{
			snake.coor[0].x = 640;
		}
		break;







	}



}
void eatfood()//吃食物
{
	if (food.flag && snake.coor[0].x >= food.x - food.r && snake.coor[0].x <= food.x + food.r && snake.coor[0].y >= food.y - food.r && snake.coor[0].y <= food.y + food.r)
	{
		food.flag = false;
		snake.size++;


	}
	if (!food.flag)//食物消失重新生成一个
	{
		food.x = rand() % 640;
		food.y = rand() % 480;
		food.color = RGB(rand() % 256, rand() % 256, rand() % 256);
		food.r = rand() % 10 + 5;
		food.flag = true;



	}




}
int main()
{
	
	gameinit();
	gamedraw();

	while (1)
	{
		gamestop();
		gamedraw();
		keycontrol();
		
		snakemove();
		eatfood();
		Sleep(20);
	
	}
	
	return 0;

}

程序演示

20230921_184525