C语言实现贪吃蛇游戏教程及源码
C语言实现贪吃蛇游戏教程及源码
本文将详细介绍如何用C语言实现一个经典的贪吃蛇游戏。从游戏的主逻辑到具体的函数实现,包括地图创建、蛇身移动、食物生成等关键部分,都将逐一进行讲解。
一、源码简介
这是一个可以进行贪吃蛇游戏的小程序,采用C语言进行编写。玩家可以通过上下左右键控制蛇的运动方向,吃到食物可以得分,如果撞墙或者咬到自身,游戏就会结束。
编译环境:VC6.0(采取纯C语言写法)
第三方库:无
二、运行截图
游戏结束界面
三、源码解析
整体程序逻辑
程序的整体逻辑如下:
- 开始界面
- 进行游戏
- 初始化
- 主循环:
- 根据输入按键的不同,做出不同的反应。
- 每经过一段时间,蛇进行移动。
- 结束游戏
主函数以及游戏运行的Gamerun
函数如下:
int main()
{
Gamestart();
Gamerun();
Gameend();
return 0;
}
void Gamerun()
{
Initsnake(); // 初始化蛇
Createfood(); // 创建食物
while(1)
{
Pos(64,10);
printf("得分:%d ",score);
if(GetAsyncKeyState(VK_UP)&&status!=D)status=U; // 根据之前是否有按下某种按键,改变前进方向或者暂停
else if(GetAsyncKeyState(VK_DOWN)&&status!=U)status=D;
else if(GetAsyncKeyState(VK_LEFT)&&status!=R)status=L;
else if(GetAsyncKeyState(VK_RIGHT)&&status!=L)status=R;
else if(GetAsyncKeyState(VK_SPACE))Pause();
else if(GetAsyncKeyState(VK_ESCAPE))
{
exit(0);
break;
}
else;
Sleep(sleeptime); // 经过一段时间继续前进
if(Snakemove()); // 如果行动成功(没有死)
else
break; // 否则跳出循环
}
}
接收输入按键
接收输入按键可以用<Windows.h>
中的GetAsyncKeyState
函数,它可以判断之前的一段时间内是否输入了某按键。我们用U,D,L,R来表示蛇头朝向上下左右。经过定义之后,可以用这四个字母为status赋值。
#define U 1
#define D 2
#define L 3
#define R 4
int status;
蛇并不能180度转弯,所以要注意判定按键方向是否与目前前进方向相反。另外我们在这里还用了一个Pos
函数。这个函数的目的是将光标定位到(x,y)处,并且能在此进行写入。
void Pos(int x,int y) // 定义一个设置光标位置的函数
{
COORD pos;
HANDLE hOutput;
pos.X=x;
pos.Y=y;
hOutput=GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleCursorPosition(hOutput,pos);
}
用到的几个函数都可以在<windows.h>
当中找到。COORD
是Windows API中定义的一种结构,表示一个字符在控制台屏幕上的坐标;GetStdHandle
用于返回一个句柄,表明是输入,输出还是错误(参数分别是STD_INPUT_HANDLE
,STD_OUTPUT_HANDLE
,STD_ERROR_HANDLE
),HANDLE
是对应的句柄的结构体;SetConsoleCursorPosition
可在指定的控制台屏幕缓冲区中设置光标位置。更多内容可以在控制台文档 - Windows Console | Microsoft Docs中搜索找到。
Pause
是另外一个我们定义的函数。如果不按下空格,程序就会循环Sleep
指令。
void Pause() // 定义暂停函数
{
while(1)
{
Sleep(100);
if(GetAsyncKeyState(VK_ESCAPE))break;
else;
}
}
数据存储结构
在进行具体游戏函数的编写之前,我们还需要确定游戏数据是如何存储及调用的,也就是如何保存蛇身的坐标。像蛇身这样的线性结构,非常适合用一个链表进行存储。将蛇身的一个点的坐标放入一个结构体当中,结构体中还有指向下一个结构体的指针。
struct SNAKE // 定义蛇身上的一个点
{
int x,y;
SNAKE *next; // 定义一个指针,指向蛇下一个点的地址
};
准备工作做完之后,接下来的是游戏主体部分。接下来我们要写的函数包括:
void Creatmap() // 定义创建地图的函数
void Initsnake() // 初始化函数
int Hitwall() // 检测是否撞墙
int Eatitself() // 检测是否碰到自身
void Createfood() // 定义创建食物的函数
int Snakemove() // 定义蛇身行动的函数
void Gamestart() // 开始页面以及初始化
void Gameend() // 游戏结束
其中Snakemove
会用到Hitwall
,Eatitself
,Createfood
。
具体函数实现
- 创建地图
void Creatmap() // 定义创建地图的函数
{
int i;
for(i=0;i<58;i+=2) // 打印上下边框
{
Pos(i,0);
printf("■");
Pos(i,26);
printf("■");
}
for(i=1;i<26;i++) // 打印左右边框
{
Pos(0,i);
printf("■");
Pos(56,i);
printf("■");
}
}
利用好之前定义的Pos
函数就可以了。
- 初始化蛇身以及游戏参数
void Initsnake() // 初始化函数
{
sleeptime=300;
score=0;
status=D;
SNAKE *tail;
int i;
tail=(SNAKE*)malloc(sizeof(SNAKE)); // 从蛇尾开始,头插法,以x,y设定开始的位置//
tail->x=24;
tail->y=5;
tail->next=NULL;
for(i=1;i<=4;i++) // 设定整个蛇身体各点的位置
{
head=(SNAKE*)malloc(sizeof(SNAKE));
head->next=tail;
head->x=24+2*i;
head->y=5;
tail=head;
}
while(tail!=NULL) // 从头到尾,输出蛇身
{
Pos(tail->x,tail->y);
printf("■");
tail=tail->next;
}
}
malloc
函数用于动态为指针分配内存空间。其参数为分配空间的大小,返回值为该空间地址。注意这个地址对应void
类型的指针,所以一定要用(SNAKE*)
这样方式进行强制类型转化。
malloc
函数的意义是初始化指针。如果指针不进行初始化,那么就会指向一个没有意义的地址,成为一个野指针,在对指针指向的位置进行读或写操作时,程序就会报错。类似的函数还有new
,有兴趣的同学可以查查它们的区别。
- 检测撞墙行为
int Hitwall() // 检测是否撞墙
{
if(head->x==0||head->x==56||head->y==0||head->y==26)return 1;
else
return 0;
}
- 检测是否碰到自身
int Eatitself() // 检测是否碰到自身
{
SNAKE *s;
s=head->next;
while(s->next!=NULL) // 利用循环对每个点进行判定
{
if(s->x==head->x&&s->y==head->y)return 1;
else
s=s->next;
}
return 0;
}
- 创建食物
void Createfood() // 定义创建食物的函数
{
SNAKE *food_1;
SNAKE *q;
srand((unsigned)time(NULL)); // 利用时间获取随机种子
food_1=(SNAKE*)malloc(sizeof(SNAKE));
food_1->x=2*(rand()%26)+2; // 利用随机种子获取坐标
food_1->y=rand()%24+1;
q=head;
while(q->next!=NULL) // 利用循环对每个点进行判定
{
if(q->x==food_1->x&&q->y==food_1->y) // 判断蛇身是否与食物重合
{
free(food_1);
food_1=NULL; // 不将地址置为NULL,也会让指针变为野指针
Createfood();
}
q=q->next;
}
Pos(food_1->x,food_1->y);
food=food_1;
printf("■"); // 打印食物
}
对于食物位置的数据,我们同样用SNAKE
结构体进行存储,这样可以便于与蛇身进行连接。因为食物的位置要随机生成,所以我们需要一个随机种子来表征这种随机性。
srand
函数在<stdlib.h>
当中,是随机数发生器的初始化函数。其参数只有一个,为随机数产生器的初始值(种子值)。为了防止随机数出现重复,我们常用(unsigned)time(&t)
来生成随机种子,原理是使用 time
函数来获得系统时间,它的返回值为从 00:00:00 GMT, January 1, 1970 到现在所持续的秒数,然后将time_t
型数据转化为(unsigned)
型再传给srand
函数。当然这还有另外一个用法,不用定义time_t
型t
变量,即: srand((unsigned) time(NULL))
,直接传入一个空指针,因为程序中往往并不需要经过参数获得的数据。
- 蛇身行动
int Snakemove() // 定义蛇身行动的函数
{
SNAKE *nexthead; // 蛇将要走到的位置
nexthead=(SNAKE*)malloc(sizeof(SNAKE));
SNAKE *n;
if(status==U) // 根据移动方向,计算下一个点的坐标
{
nexthead->x=head->x;
nexthead->y=head->y-1;
nexthead->next=head;
}
else if(status==D)
{
nexthead->x=head->x;
nexthead->y=head->y+1;
nexthead->next=head;
}
else if(status==L)
{
nexthead->x=head->x-2;
nexthead->y=head->y;
nexthead->next=head;
}
else
{
nexthead->x=head->x+2;
nexthead->y=head->y;
nexthead->next=head;
}
head=nexthead;
if(Hitwall()) // 是否撞墙
{
typelose=1;
return 0;
}
else;
if(Eatitself()) // 是否咬到自身
{
typelose=2;
return 0;
}
else;
if(head->x==food->x&&head->y==food->y) // 如果前方是食物
{
score++;
Createfood();
}
else
{
n=head;
while(n->next->next!=NULL)n=n->next; // 将移动到的下一个点打印,同时去掉尾部的点
Pos(n->next->x,n->next->y);
printf(" ");
Pos(head->x,head->y);
printf("■");
free(n->next);
n->next=NULL;
}
return 1;
}
- 游戏开始及终止
void Gamestart() // 开始页面以及初始化
{
system("mode con cols=100 lines=30");
Startpage(); // 开始页面
Creatmap(); // 绘制地图
}
void Gameend() // 游戏结束
{
system("cls"); // 清除屏幕
Pos(24,12);
if(typelose==1)
{
printf("对不起,您撞到墙了。游戏结束.");
}
else if(typelose==2)
{
printf("对不起,您咬到自己了。游戏结束.");
}
Pos(24,13);
printf("您的得分是%d\n",score);
}
四、完整源码
完整源码可以点击下方链接下载: