贪吃蛇设计思路.docx
《贪吃蛇设计思路.docx》由会员分享,可在线阅读,更多相关《贪吃蛇设计思路.docx(13页珍藏版)》请在冰豆网上搜索。
![贪吃蛇设计思路.docx](https://file1.bdocx.com/fileroot1/2023-1/3/517bfe03-6d32-473d-bcf5-98d596624ee1/517bfe03-6d32-473d-bcf5-98d596624ee11.gif)
贪吃蛇设计思路
《贪吃蛇》游戏设计思路
我前一段时间写了个贪吃蛇,有位吧友求思路,而网上也没有类似的教程帖子(至少我当初没找到(-_-),我只找到了现成的代码……),于是乎,我就整理了一下自己的设计思路,写下了这篇东西,如果你是老鸟,有什么说错的地方还希望您具体指出来,毕竟我的功力还不深。
。
。
。
。
。
。
。
如果你是新手,大家一起交流探讨吧,我也算是刚入门的新手。
在本帖最后,我会附上自己写的完整的程序代码,里面也有一些注释,以供大家参考。
首先,看看我的贪吃蛇的程序框图。
从图中可以看出,整个游戏就是一个大的循环,当判断蛇的生命值为0时就跳出循环游戏结束,否则继续游戏。
常用的结构是:
While
(1){
..........
//游戏内容
..........
If(……)break;
//满足游戏结束的条件时跳出循环结束游戏
}
解决了游戏的主体结构,接下来就是游戏的具体内容了。
先来看看我们需要哪些变量;
蛇的身体(链表,包含了蛇每一节的坐标,和生命值);
食物(数组,两个元素,包含了食物的坐标);
蛇的运动方向(整型,共四个值);
首先,蛇的身体应该选用什么数据类型来存储呢?
数组组是肯定不行的,因为数组的大小是固定的,你不知道你的蛇最终能达到多长(或许有高手能让蛇长到占满屏幕?
),而且在数组的头部插入数据是很麻烦的一件事,你必须把所有的数据都往后移动一个元素才行。
因此,此处我选用了链表来储存蛇的身体信息,(有的小伙伴要问了:
什么是链表啊?
这个请自行XX,我的个人看法是,链表就是对结构体的一种应用。
话说我们学了一学期的C语言,老师也没讲链表之类的东西,不知道很正常,它应该属于数据结构这一门学科的内容吧。
如果你不懂链表也没关系,数组完全可以代替它,只是在资源的利用上要浪费一点,在数据的插入与删除上要麻烦一点而已)。
链表的一个节点应该包含哪些内容呢?
首先是蛇的坐标x和y,然后是生命值(其实只有第一个节点能用上它,你也可以去掉它,然后另外单独定义一个全局变量如life来判断蛇的生死),最后是指向下一个节点的结构体指针。
定义如下:
structsnk{//蛇身体
intx;
inty;
intlife;
structsnk*link;
};
然后是食物:
intfood[2];//食物,用food[0]表示x坐标,用food[1]表示y坐标
当然,食物必须定义为全局的。
当然如果你有需要,还可以定义一个全局整型变量来保存积分:
intfen=0;//积分
需要的主要数据已经定义好,接下来是初始化。
那么,就先来初始化蛇的身体,创建一个链表;
structsnk*snake;//指向蛇链表头(第一个节点)的结构体指针
//声明两个个链表节点,初始化最开始的几节蛇身体
snake=(structsnk*)malloc(sizeof(structsnk));
snake->x=10;snake->y=10;snake->life=1;
snake->link=(structsnk*)malloc(sizeof(structsnk));
snake->link->x=10;snake->link->y=9;
snake->link->x=10;snake->link->link=NULL;
这里我只初始化了两节,你也可以多加几节,但是,最后别忘了把最后一个节点的link指针设为空,它标志着蛇的结尾,至于life,只需把第一个节点初始化为非零值即可。
接下来是初始化食物,为了能够随机生成食物,我们需要用到time.h为我们提供的随机函数,rand()和srand();为了方便后面的食物生成,我们把食物生成单独做成一个函数mkfood(),想一想,它需要哪些参数,它的返回值是什么?
为了防止生成的食物的坐标与蛇的身体重合,所以mkfood函数需要知道蛇的状态,食物是全局变量,因此函数不需要返回值,因此mkfood函数的函数首部应该是
voidmkfood(structsnk*p);//产生食物
每次调用它都需要用srand函数重设随机数种子,然后用rand函数产生随机数,分别赋值给food[0]和food[1]作为食物的x坐标和y坐标。
在这之后,需要遍历一次蛇的每一个节点,并把它们与食物的坐标比较,如有相同的,既产生的食物和蛇的身体重复了,就必须重新设定食物的坐标,这里我用的是递归调用mkfood函数来重新设定食物坐标。
如果你用递归方法来重设食物坐标,不得不提的是当你在设定随机数种子时不要使用time()函数,因为time函数只能精确到秒,一旦食物与蛇身体重复,就会递归调用mkfood函数,此时时间还远远不足一秒,所以导致随机数种子还是和原来的一样,产生的随机数也和原来一样,还是和蛇身体重复,如此循环往复的递归调用,以现在的CPU运算能力,一秒钟之内可以进行的运算次数是相当惊人的,最终导致栈的溢出,程序崩溃。
。
。
。
所以应该选用更加精确的函数,比如clock()它返回从程序启动,到调用它时经过的时间,单位是毫秒,或者干脆手动设定一个变量来做种子,每次调用之前更新这个变量。
接下来就是制作一个函数,能够按照蛇身体和食物的信息,把蛇的身体和食物打印(绘制)到屏幕的相应位置。
这个函数需要知道蛇和食物的坐标,食物是全局变量可以直接获取坐标,因此它接受一个结构体指针类型的参数,就是蛇身体的第一个节点,而它不需要返回值。
所以他的函数首部应该是:
voiddrawmap(structsnk*p,int*fd);//画出食物和蛇
至于函数内部,我们可以通过p->x或者p->y来访问蛇的头部的坐标,然后令p=p->link;来继续访问接下来一节蛇的坐标,直到p->link=NULL,这说明蛇到了结尾了。
食物是全局变量可以直接获取坐标。
获得坐标之后,你可以调用图形库来绘制,也可以在字符界面打印。
总之就是遍历一次蛇的身体,根据每一个节点的坐标,分别把它们和食物一起打印在屏幕上就是了。
接下来是蛇的移动问题,怎样让蛇自动的移动呢?
这个只需要主循环while
(1){………}每循环一次就改变一次蛇的位置即可,那么如何改变呢?
这就体现出我们为什么要用链表来储存蛇的坐标了。
我们并不需要改变蛇每一节的坐标,只需要“添头去尾”即可移动蛇,我们只需要在蛇的头部,根据蛇的运动方向添加一个节点,把它设为蛇头,判断此节点的坐标是否超出地图范围,如果超出这修改生命值为死亡,如果没有超出,则继续把它与食物的坐标进行比较,如果不同,则删除蛇的最后一个节点,蛇就向前移动一格了,如果相同,则蛇吃到了食物,此时不删除最后一节蛇的身体,蛇就增加了一节,然后再调用mkfood函数重新生成食物覆盖掉原来食物的坐标。
此处最主要的就是链表的添头去尾操作,添头其实就是新分配一块内存,把它的link指针指向原来的第一个节点,再把现在的第一个节点的地址返回给代表蛇头的结构体指针变量(把新添加的节点设为蛇头),去尾就是把最后一个节点用free()释放掉,把倒数第二个节点的link设为NULL即可。
还有就是如何确定添加的头的坐标的问题,你需要根据之前的头的坐标和代表方向的变量的值来做,比如用1234分别代表上下左右方向,当方向为1时,蛇向上运动,那么在原来的蛇头的基础上把x减1,y不变来作为新头的坐标。
其他方向同理。
然后是蛇的控制问题,
我们,可以用一个函数来获取输入,改变蛇的前进方向;它需要接收当前的运动方向(整型值),用来判断用户按下的方向是否与其相反(蛇不能向与当前方向相反的方向前进),然后返回一个运动方向,所以他的函数首部应该是:
intkeydown(intz)//获取输入
这个函数里,我们可以使用conio.h里的getch来获取键盘输入,为什么不用getchar或者scanf呢?
因为getch与getchar()虽然基本功能差不多,差别是getch()直接从键盘获取键值,不等待用户按回车,只要用户按一个键,getch就立刻返回,而getchar则是从输入缓冲区获取键值,getch返回值是用户输入的ASCII码,出错返回-1.输入的字符不会回显在屏幕上。
但是还有一个问题,那就是当程序执行到getch时会暂停,等待用户输入。
这样如果我们不按键盘,游戏就无法继续。
这个可以用conio.h里提供的kbhit()函数来检测是否有键盘被按下,
函数名:
kbhit()(VC++6.0下为_kbhit())
功能及返回值:
检查当前是否有键盘输入,若有则返回一个非0值,否则返回0
用法:
intkbhit(void);
包含头文件:
include
于是我们可以这样写:
charch;
if(kbhit()){
ch=getch();
switch(ch){
case'a':
。
。
。
。
。
。
;break;
case'd':
。
。
。
。
。
。
;break;
case'w':
。
。
。
。
。
。
;break;
case's':
。
。
。
。
。
。
;break;
default:
break;
}
}
根据按的键的不同来修改蛇的运动方向。
注意哦!
在改变运动方向时不要忘记判断方向是否与当前的方向冲突!
我们还可以用它来判断按下的是否是Esc键,如果是,则结束游戏。
至此,我们已经完成了贪吃蛇的最主要两个数据:
Food[2]和snake{
Intx;
Inty;
Intlife;
Structsnake*link;
}
几个主要的功能函数,
voiddrawmap(structsnk*p,structfood*fd);//画地图食物和蛇
intkeydown(intz);//获取输入
structsnk*mvsnk(structsnk*p,intz,structfood*fd);//移动并更新蛇的坐标
structfood*mkfood(structsnk*p);//产生食物
接下来就是将它们按照开始的程序框图组合起来,一条新鲜的贪吃蛇就出炉了!
当然,你还可以在最外面再套一个大循环以实现多次游戏。
如果你没有ege图形库,只需要稍微更改几个绘制图形的函数,就可以在控制台的字符界面实现了。
你也可以下载这里的编译完成的exe,和我修改的字符界面版:
Exe文件:
字符版:
Ege图形库版:
(相比下面的代码,增加了菜单,即exe的源代码)
以下是完整代码,其中使用了ege图形库,除了文中提到的,我还增加了保存最高纪录的功能。
//|---------------------------------------------------------|
#include
#include
#include
#include
voidmsgbox(intx,inty,intl,inth,char*c,char*c2,char*c3);
voiddrd(intx,inty);//画点
voiddrl(intx1,inty1,intx2,inty2);//画线
voiddrawmap(structsnk*p);//里用上面两个函数画地图食物和蛇
intkeydown(intz);//获取输入
structsnk*mvsnk(structsnk*p,intz);//更新蛇的坐标
voidmkfood(structsnk*p);//产生食物
intfood[2]={20,15};
structsnk{//蛇身体
intx;
inty;
intlife;
structsnk*link;
};
intspeed=100;
intfen=0;//积分
intjilu;//最高分记录
charjiluzhe[20];
intmain()
{
//初始化,
FILE*fp;//从文件载入最高记录
if((fp=fopen("jiulu.txt","r"))==NULL)jilu=0;
else{fgets(jiluzhe,20,fp);
fscanf(fp,"%d",&jilu);}
fclose(fp);
initgraph(600,600);//定义屏幕为600*600像素
setcaption("贪吃蛇");
setcolor(WHITE);
setfont(20,0,"宋体");
intz=4;
structsnk*snake;//指向蛇链表头的结构体指针
structsnk*snakelink;//用于游戏结束后释放内存时的临时存储
structfood*fd;//指向食物的结构体指针
//声明四个链表节点为初始的四节蛇身体
snake=(structsnk*)malloc(sizeof(structsnk));
snake->x=10;snake->y=10;snake->life=1;
snake->link=(structsnk*)malloc(sizeof(structsnk));
snake->link->x=10;snake->link->y=9;
snake->link->link=(structsnk*)malloc(sizeof(structsnk));
snake->link->link->x=10;snake->link->link->y=8;
snake->link->link->link=(structsnk*)malloc(sizeof(structsnk));
snake->link->link->link->x=10;snake->link->link->link->y=7;
snake->link->link->link->link=NULL;
setfontbkcolor(BLUE);
setfont(30,0,"宋体");
xyprintf(0,300,"W:
上S:
下A:
左D:
右Esc:
结束游戏退出");
setfont(20,0,"宋体");
Sleep(2300);
//游戏主循环-----------------------------------------|
while
(1){
inttemp_z;temp_z=z;
z=keydown(z);
if(z==27)break;
if(z==5){
outtextxy(300,300,"暂停");
getch();z=temp_z;}/*|*/
snake=mvsnk(snake,z);
if(snake->life==0)break;
drawmap(snake);
Sleep(300-speed);
}
setfont(50,0,"宋体");
setcolor(YELLOW);
setfontbkcolor(BLUE);
outtextxy(175,250,"GAMEOVER!
");
setcolor(WHITE);
Sleep(1500);
if(fen>jilu){
inputbox_getline("恭喜!
打破纪录了!
","您打破了最高记录!
\n留下您的大名吧!
\n按回车确认输入",jiluzhe,20);
fp=fopen("jiulu.txt","w");
fprintf(fp,"%s\n%d",jiluzhe,fen);
fclose(fp);
Sleep(1500);
}
while(snake!
=NULL){
snakelink=snake->link;
free(snake);
snake=snakelink;
}
fen=0;speed=100;
closegraph();
return0;
}
//----------------------------------------------------|
intkeydown(intz)//获取输入
{
charch;
if(kbhit()){
ch=getch();
switch(ch){
case'A':
case'a':
if(z!
=2)z=1;break;
case'D':
case'd':
if(z!
=1)z=2;break;
case'W':
case'w':
if(z!
=4)z=3;break;
case'S':
case's':
if(z!
=3)z=4;break;
case'E':
case'e':
z=5;break;
default:
break;
}
if(ch==27)z=27;
}
returnz;
}
voidmsgbox(intx,inty,intl,inth,char*c,char*c2,char*c3)
{
setfillcolor(LIGHTGRAY);
bar(x,y,x+l,y+h);
setfillcolor(WHITE);
bar(x+3,y+3,x+l-3,y+h-3);
setfillcolor(BLUE);
bar(x+3,y+3,x+l-3,y+30);
setfontbkcolor(WHITE);
setcolor(BLACK);
rectprintf(x+5,y+3,l-5,h-3,"%s%s%s",c,c2,c3);
setfontbkcolor(BLUE);
setcolor(WHITE);
}
structsnk*mvsnk(structsnk*p,intz)//更新蛇的坐标
{
structsnk*p2;
structsnk*p3;
intx,y;
x=p->x;
y=p->y;
switch(z){
case1:
x--;break;
case2:
x++;break;
case3:
y--;break;
case4:
y++;break;
}
//在蛇的头部添加一个节点
p2=(structsnk*)malloc(sizeof(structsnk));
p2->x=x;p2->y=y;
p2->link=p;
p2->life=1;
//判断蛇头是否碰到身体
p3=p;
while(p3!
=NULL){
if(p2->x==p3->x&&p2->y==p3->y)p2->life=0;
p3=p3->link;
}
//判断蛇头是否碰到墙壁
if(p2->x>28||p2->y>28||p2->x<1||p2->y<3)p2->life=0;
if(p2->x==food[0]&&p2->y==food[1]){
p2->life=2;fen++;speed<250?
speed+=2:
speed=250;
mkfood(p2);/*如果吃到食物则不删除最后一个节点,由于之前在头部增加了一个节点,所以蛇的长度增加一节*/
}else{while(p->link->link!
=NULL)p=p->link;
p3=p->link;
p->link=NULL;
free(p3);}//如果没有吃到食物则删除最后一个节点
returnp2;
}
voidmkfood(structsnk*p)
{
structsnk*p2;
p2=p;
srand((unsigned)clock());//设定随机数种子
food[1]=rand()%26+3;//随机产生食物坐标
food[0]=rand()%27+1;
//检测食物的坐标是否与蛇身体重复,如果是,则重新生成食物
do{
if(food[0]==p->x&&food[1]==p->y){mkfood(p2);}
p=p->link;
}while(p!
=NULL);
}
voiddrd(intx,inty)//画点
{
setfillcolor(GREEN);
x=x*20;
y=y*20;
bar(x+1,y+1,x+19,y+19);
}
voiddrl(intx1,inty1,intx2,inty2)//画线
{
intx,y;
setfillcolor(GREEN);
do{
x=x1*20;
y=y1*20;
bar(x+1,y+1,x+19,y+19);
x1x1++:
x2;
y1y1++:
y2;
}while(x1!
=x2||y1!
=y2);
}
voiddrawmap(structsnk*p)//画地图食物和蛇
{
cleardevice();
drl(0,0,30,0);
drl(0,2,30,2);
xyprintf(20,20,"分数:
%d速度:
%d最高记录:
%d保持者:
%s",fen,speed,jilu,jiluzhe);
drl(0,1,0,30);
drl(0,29,30,29);
drl(29,0,29,30);
drd(p->x,p->y);
drd(food[0],food[1]);
p=p->link;
while(p!
=NULL){
drd(p->x,p->y);
p=p->link;
}
}