这一节中,我们来做一款经典小游戏,贪吃蛇。先看看最终效果图
在开始之前,我们把窗体创建好。
创建一个800 * 600的窗体。这一次我们使用默认的原点和坐标轴:原点在窗体左上角,X轴正方向向右,Y轴正方向向下。背景色设置为RGB(164, 225, 202),最后调用cleardevice函数,使用背景色清空整个窗体。
#include<easyx.h>#include<stdio.h>intmain(){initgraph(800,600);//设置背景色setbkcolor(RGB(164,225,202));//使用背景色清空窗体cleardevice();getchar();closegraph();return0;}
1. 定位网格
将整个800 * 600的窗体,水平分隔为20等分,垂直分隔为15等分,作为整个游戏的网格坐标系统。在上图中,蛇用5格白色的矩形表示。食物用黄色的一格矩形表示。
这样,蛇的每一格身体坐标为:
(5, 7)
(4, 7)
(3, 7)
(2, 7)
(1, 7)
食物的坐标为:
(12, 7)
为了方便观察,把窗体用线段画上上述网格。网格每一格的宽度设为40像素,用符号常量NODE_WIDTH
表示。
#defineNODE_WIDTH40
竖向线段
先绘制竖向的线段。
竖向线段中,起始点y
坐标固定为0
,终止点y
坐标固定为600
。
每条线段的起始点与终止点的x
坐标一致,且随着线段不同而变化。
设线段条数从0开始计数。
第0条线段: 起始点、终止点的x
坐标为0
。
第10条线段: 起始点、终止点的x
坐标为10 * NODE_WIDTH
。
第20条线段: 起始点、终止点的x
坐标为20 * NODE_WIDTH
。
观察各线段起始点终止点坐标,可以总结出规律:
第n条线段:起始点(n * NODE_WIDTH, 0)
、终止点(n * NODE_WIDTH, 600)
。且x
坐标的范围为[0, 800]
。
//竖线for(intx=0;x<=800;x+=NODE_WIDTH){line(x,0,x,600);}
横向线段
再绘制横向的线段。
横向线段中,起始点x
坐标固定为0
,终止点x
坐标固定为800
。
每条线段的起始点与终止点的y
坐标一致,且随着线段不同而变化。
设线段条数从0开始计数。
第0条线段: 起始点、终止点的y
坐标为0
。
第8条线段: 起始点、终止点的y
坐标为8 * NODE_WIDTH
。
第15条线段: 起始点、终止点的y
坐标为15 * NODE_WIDTH
。
观察各线段起始点终止点坐标,可以总结出规律:
第n条线段:起始点(0, n * NODE_WIDTH)
、终止点(800, n * NODE_WIDTH)
。且y
坐标的范围为[0, 600]
。
//横线for(inty=0;y<=600;y+=NODE_WIDTH){line(0,y,800,y);}
网格函数paintGrid
将绘制网格的代码封装成函数paintGrid
。
//绘制网格//横线(0,y),(800,y)0<=y<=600//竖线(x,0),(x,600)0<=x<=800voidpaintGrid(){//横线for(inty=0;y<600;y+=NODE_WIDTH){line(0,y,800,y);}//竖线for(intx=0;x<800;x+=NODE_WIDTH){line(x,0,x,600);}}
主函数中调用paintGrid
函数,给整个窗体绘制上网格。
现阶段代码如下:
#include<easyx.h>#include<stdio.h>#defineNODE_WIDTH40//绘制网格//横线(0,y),(800,y)0<=y<=600//竖线(x,0),(x,600)0<=x<=800voidpaintGrid(){//横线for(inty=0;y<600;y+=NODE_WIDTH){line(0,y,800,y);}//竖线for(intx=0;x<800;x+=NODE_WIDTH){line(x,0,x,600);}}intmain(){initgraph(800,600);//设置背景色setbkcolor(RGB(164,225,202));//使用背景色清空窗体cleardevice();//绘制网格paintGrid();getchar();closegraph();return0;}
2. 绘制蛇节点
设定初始状态下,蛇有5个节点。我们把之前设置的网格规划出来的坐标称作网格坐标。每个节点的网格坐标为:
(5, 7)
(4, 7)
(3, 7)
(2, 7)
(1, 7)
每一个蛇节点使用一个白色正方形表示,而绘制矩形需要左上角和右下角的实际坐标。白色矩形的左上角坐标与右下角实际坐标为:
左上角:【网格x坐标 * 网格宽度, 网格y坐标 * 网格宽度】
右下角:【(网格x坐标
+ 1) *网格宽度
, (网格y坐标
+ 1) *网格宽度
】
每个节点需要存储x
坐标、y
坐标两个坐标,可以定义含有两个int
成员的结构用于表示节点。
//节点typedefstruct{intx;inty;}node;
在主函数中,声明node
的数组,并将前5个元素初始化为蛇的初始位置。设节点(5, 7)
为蛇头,蛇头存储在数组的首元素中。
//蛇节点坐标nodesnake[100]={{5,7},{4,7},{3,7},{2,7},{1,7}};
现在,我们定义一个函数paintSnake
用于将所有组成蛇的矩形绘制出来。
voidpaintSnake(node*snake,intn){intleft,top,right,bottom;for(inti=0;i<n;i++){//左上角:【网格x坐标*网格宽度, 网格y坐标*网格宽度】left=snake[i].x*NODE_WIDTH;top=snake[i].y*NODE_WIDTH;//右下角:【(网格x坐标+ 1)*网格宽度, (网格y坐标+ 1)*网格宽度】right=(snake[i].x+1)*NODE_WIDTH;bottom=(snake[i].y+1)*NODE_WIDTH;//通过左上角与右下角坐标绘制矩形solidrectangle(left,top,right,bottom);}}
paintSnake
函数需要蛇节点数组首元素指针和蛇节点个数两个参数。在主函数中定义一个变量int length
用于记录蛇长度,目前初始化为5。声明蛇节点数组,绘制网格及蛇节点。
//蛇节点坐标
nodesnake[100]={{5,7},{4,7},{3,7},{2,7},{1,7}};
//蛇节点长度
intlength=5;
//绘制网格
paintGrid();
//绘制蛇节点
paintSnake(snake,length);
现阶段代码如下:
#include<easyx.h>#include<stdio.h>#defineNODE_WIDTH40//节点typedefstruct{intx;inty;}node;//绘制网格//横线(0,y),(800,y)0<=y<=600//竖线(x,0),(x,600)0<=x<=800voidpaintGrid(){//横线for(inty=0;y<600;y+=NODE_WIDTH){line(0,y,800,y);}//竖线for(intx=0;x<800;x+=NODE_WIDTH){line(x,0,x,600);}}voidpaintSnake(node*snake,intn){intleft,top,right,bottom;for(inti=0;i<n;i++){//左上角:【网格x坐标*网格宽度, 网格y坐标*网格宽度】left=snake[i].x*NODE_WIDTH;top=snake[i].y*NODE_WIDTH;//右下角:【(网格x坐标+ 1)*网格宽度, (网格y坐标+ 1)*网格宽度】right=(snake[i].x+1)*NODE_WIDTH;bottom=(snake[i].y+1)*NODE_WIDTH;//通过左上角与右下角坐标绘制矩形solidrectangle(left,top,right,bottom);}}intmain(){initgraph(800,600);//设置背景色setbkcolor(RGB(164,225,202));//使用背景色清空窗体cleardevice();//蛇节点坐标nodesnake[100]={{5,7},{4,7},{3,7},{2,7},{1,7}};//蛇节点长度intlength=5;//绘制网格paintGrid();//绘制蛇节点paintSnake(snake,length);getchar();closegraph();return0;}
3. 移动蛇节点
现在,我们希望让蛇向右运动,蛇头节点为(5, 7)
。以下两幅图为初始位置和向右移动一步后的位置。
初始位置:
(5, 7)
(4, 7)
(3, 7)
(2, 7)
(1, 7)
向右移动一步后位置:
(6, 7)
(5, 7)
(4, 7)
(3, 7)
(2, 7)
可以看出移动后,蛇节点中,尾部节点(1, 7)
被删除,而新增了一个头部节点(6,7)
。
怎样对存储蛇节点的数组进行操作,可以达到删除尾部节点,而新增一个头节点的效果呢?
逐个向后移动
从蛇尾节点前一个节点开始,即从数组下标为3
的元素开始。元素3
设置为当前元素,将当前元素的值,移动到后一个元素当中。接着,将当前元素设置为元素2
,重复该动作,直到数组元素0
的值,移动到元素1
为止。
元素3-->元素4
元素2-->元素3
元素1-->元素2
元素0-->元素1
这样即可把蛇尾节点去掉,并留出了新蛇头节点的位置。
设置新蛇头
若蛇向右运动,那么将新蛇头设置为(6,7)
即可。
但是,蛇头除了可以向右运动,还可以做另外3个方向的运动,共4个方向的运动。
上
下
左
右
设旧蛇头坐标为(x, y)
。对于各个方向的运动,新蛇头的网格坐标相对于旧蛇头的坐标作如下运算:
上:(x, y + 1)
下:(x, y - 1)
左:(x - 1, y)
右:(x + 1, y)
为了更加明确地在程序中表明方向,我们将4个方向声明为枚举类型。
//方向枚举enumdirection{eUp,eDown,eLeft,eRight};
接着定义一个函数snakeMove
,传入数组首元素指针、蛇节点个数和蛇前进方向。它将按照上述方法,依次移动蛇节点并根据前进方向设置蛇头。
//蛇节点移动voidsnakeMove(node*snake,intlength,intdirection){//从尾结点开始,前一个节点覆盖后一个节点//4321043210//EDCBA--->DECBAfor(inti=length-1;i>0;i--){snake[i]=snake[i-1];}//根据方向,确定下一个头节点nodenewHead;newHead=snake[0];if(direction==eUp){newHead.y--;}elseif(direction==eDown){newHead.y++;}elseif(direction==eLeft){newHead.x--;}else//right{newHead.x++;}//更新头节点//DECBA--->DECBNsnake[0]=newHead;}
在主函数中放置一个死循环,循环体中依次执行以下步骤:
清空整个窗体
绘制网格
绘制蛇节点
休眠500ms
向右移动蛇节点
while(1)
{
//清空整个窗体
cleardevice();
//绘制网格
paintGrid();
//绘制蛇节点
paintSnake(snake,length);
//休眠500ms
Sleep(500);
//向右移动蛇节点
snakeMove(snake,length,eRight);
}
现阶段代码如下:
#include<easyx.h>#include<stdio.h>#defineNODE_WIDTH40//节点typedefstruct{intx;inty;}node;//绘制网格//横线(0,y),(800,y)0<=y<=600//竖线(x,0),(x,600)0<=x<=800voidpaintGrid(){//横线for(inty=0;y<600;y+=NODE_WIDTH){line(0,y,800,y);}//竖线for(intx=0;x<800;x+=NODE_WIDTH){line(x,0,x,600);}}voidpaintSnake(node*snake,intn){intleft,top,right,bottom;for(inti=0;i<n;i++){//左上角:【网格x坐标*网格宽度, 网格y坐标*网格宽度】left=snake[i].x*NODE_WIDTH;top=snake[i].y*NODE_WIDTH;//右下角:【(网格x坐标+ 1)*网格宽度, (网格y坐标+ 1)*网格宽度】right=(snake[i].x+1)*NODE_WIDTH;bottom=(snake[i].y+1)*NODE_WIDTH;//通过左上角与右下角坐标绘制矩形solidrectangle(left,top,right,bottom);}}//方向枚举enumdirection{eUp,eDown,eLeft,eRight};//蛇节点移动voidsnakeMove(node*snake,intlength,intdirection){//从尾结点开始,前一个节点覆盖后一个节点//4321043210//EDCBA--->DECBAfor(inti=length-1;i>0;i--){snake[i]=snake[i-1];}//根据方向,确定下一个头节点nodenewHead;newHead=snake[0];if(direction==eUp){newHead.y--;}elseif(direction==eDown){newHead.y++;}elseif(direction==eLeft){newHead.x--;}else//right{newHead.x++;}//更新头节点//DECBA--->DECBNsnake[0]=newHead;}intmain(){initgraph(800,600);//设置背景色setbkcolor(RGB(164,225,202));//使用背景色清空窗体cleardevice();//蛇节点坐标nodesnake[100]={{5,7},{4,7},{3,7},{2,7},{1,7}};//蛇节点长度intlength=5;while(1){//清空整个窗体cleardevice();//绘制网格paintGrid();//绘制蛇节点paintSnake(snake,length);//休眠500msSleep(500);//向右移动蛇节点snakeMove(snake,length,eRight);}getchar();closegraph();return0;}
4. 控制移动方向
现在,程序开始后蛇会向右移动,接下来,加入用键盘来控制蛇的移动方向的功能。
键盘交互功能如下:
按下w键,蛇向上移动
按下s键,蛇向下移动
按下a键,蛇向左移动
按下d键,蛇向右移动
在主函数中声明一个枚举变量d
,初始为向右移动。
//移动方向enumdirectiond=eRight;
将d
传递给snakeMove
函数的第三个参数。snakeMove
函数的第三个参数可以根据方向设置新蛇头的位置。若蛇需要更改移动方向,只要更改枚举变量d
即可。
//枚举变量d,控制蛇的移动方向snakeMove(snake,length,d);
与之前的键盘交互一样,使用_getch
与_kbhit
函数配合,可以获取键盘输入且不会导致程序阻塞。使用这两个函数别忘了包含头文件#include <conio.h>
。获取到键盘输入后,通过传入枚举变量指针pD
,修改变量枚举值。
//键盘输入改变directionvoidchangeDirection(enumdirection*pD){//检查输入缓存区中是否有数据if(_kbhit()!=0){//_getch函数获取输入缓存区中的数据charc=_getch();//判断输入并转向switch(c){case'w'://向上移动*pD=eUp;break;case's'://向下移动*pD=eDown;break;case'a'://向左移动*pD=eLeft;break;case'd'://向右移动*pD=eRight;break;}}}
这里还需要注意一个问题,蛇不能后退,如果新的方向与原方向相反,那么按键无效。
//键盘输入改变directionvoidchangeDirection(enumdirection*pD){//检查输入缓存区中是否有数据if(_kbhit()!=0){//_getch函数获取输入缓存区中的数据charc=_getch();//判断输入并转向switch(c){case'w'://向上移动if(*pD!=eDown)*pD=eUp;break;case's'://向下移动if(*pD!=eUp)*pD=eDown;break;case'a'://向左移动if(*pD!=eRight)*pD=eLeft;break;case'd'://向右移动if(*pD!=eLeft)*pD=eRight;break;}}}
主函数中,在snakeMove
函数前调用changeDirection
函数检查是否有键盘输入,若有输入且非回头方向的输入,则改变方向枚举变量d
。新的方向枚举变量d
传入snakeMove
函数后,即可使用新方向设置新蛇头的位置,实现蛇改变移动方向功能。
//蛇节点坐标
nodesnake[100]={{5,7},{4,7},{3,7},{2,7},{1,7}};
//蛇节点长度
intlength=5;
enumdirectiond=eRight;
while(1)
{
cleardevice();
paintGrid();
paintSnake(snake,length);
Sleep(500);
//获取键盘输入并将方向存储到变量d
changeDirection(&d);
//根据变量d的方向移动蛇节点
snakeMove(snake,length,d);
}
现阶段代码:
#include<easyx.h>#include<stdio.h>#include<conio.h>#defineNODE_WIDTH40//节点typedefstruct{intx;inty;}node;//绘制网格//横线(0,y),(800,y)0<=y<=600//竖线(x,0),(x,600)0<=x<=800voidpaintGrid(){//横线for(inty=0;y<600;y+=NODE_WIDTH){line(0,y,800,y);}//竖线for(intx=0;x<800;x+=NODE_WIDTH){line(x,0,x,600);}}voidpaintSnake(node*snake,intn){intleft,top,right,bottom;for(inti=0;i<n;i++){//左上角:【网格x坐标*网格宽度, 网格y坐标*网格宽度】left=snake[i].x*NODE_WIDTH;top=snake[i].y*NODE_WIDTH;//右下角:【(网格x坐标+ 1)*网格宽度, (网格y坐标+ 1)*网格宽度】right=(snake[i].x+1)*NODE_WIDTH;bottom=(snake[i].y+1)*NODE_WIDTH;//通过左上角与右下角坐标绘制矩形solidrectangle(left,top,right,bottom);}}//方向枚举enumdirection{eUp,eDown,eLeft,eRight};//蛇节点移动voidsnakeMove(node*snake,intlength,intdirection){//从尾结点开始,前一个节点覆盖后一个节点//4321043210//EDCBA--->DECBAfor(inti=length-1;i>0;i--){snake[i]=snake[i-1];}//根据方向,确定下一个头节点nodenewHead;newHead=snake[0];if(direction==eUp){newHead.y--;}elseif(direction==eDown){newHead.y++;}elseif(direction==eLeft){newHead.x--;}else//right{newHead.x++;}//更新头节点//DECBA--->DECBNsnake[0]=newHead;}//键盘输入改变directionvoidchangeDirection(enumdirection*pD){//检查输入缓存区中是否有数据if(_kbhit()!=0){//_getch函数获取输入缓存区中的数据charc=_getch();//判断输入并转向switch(c){case'w'://向上移动if(*pD!=eDown)*pD=eUp;break;case's'://向下移动if(*pD!=eUp)*pD=eDown;break;case'a'://向左移动if(*pD!=eRight)*pD=eLeft;break;case'd'://向右移动if(*pD!=eLeft)*pD=eRight;break;}}}intmain(){initgraph(800,600);//设置背景色setbkcolor(RGB(164,225,202));//使用背景色清空窗体cleardevice();//蛇节点坐标nodesnake[100]={{5,7},{4,7},{3,7},{2,7},{1,7}};//蛇节点长度intlength=5;enumdirectiond=eRight;while(1){//清空整个窗体cleardevice();//绘制网格paintGrid();//绘制蛇节点paintSnake(snake,length);//休眠500msSleep(500);//获取键盘输入并将方向存储到变量dchangeDirection(&d);//根据变量d的方向移动蛇节点snakeMove(snake,length,d);}getchar();closegraph();return0;}
5. 创建食物
目前蛇的部分已经完成了,还差需要创建食物,让蛇吃到食物后长大。
主函数中声明一个node
类型的变量作为食物的节点。
nodefood;
食物的位置是随机生成的,但是有两个要求:
不能生成在窗体外
不能生成在蛇节点上
对于网格坐标来说,宽度有800/NODE_WIDTH
,即20格。高度有600 / NODE_WIDTH
,即15格。限制食物的x
坐标在区间[0, 19]
以内,y
坐标在区间[0, 14]
内。
food.x=rand()%(800/NODE_WIDTH);//区间[0,19]内food.y=rand()%(600/NODE_WIDTH);//区间[0,14]内
对于第二个条件,只能遍历所有蛇节点,检查是否食物与蛇任何一个节点重合了。如果重合,那么重新随机生成一次食物,直到食物与所有蛇节点不重合为止。
while(1){food.x=rand()%(800/NODE_WIDTH);food.y=rand()%(600/NODE_WIDTH);inti;for(i=0;i<length;i++){if(snake[i].x==food.x&&snake[i].y==food.y){break;}}if(i<length)continue;elsebreak;}
将生成食物封装成createFood
函数,函数传入两个参数,一个参数为蛇节点数组首元素指针,另一个参数为蛇节点个数。生成完成食物坐标后,返回装有食物坐标的food
结构。
//随机创建食物nodecreateFood(node*snake,intlength){nodefood;while(1){food.x=rand()%(800/NODE_WIDTH);food.y=rand()%(600/NODE_WIDTH);inti;for(i=0;i<length;i++){if(snake[i].x==food.x&&snake[i].y==food.y){break;}}if(i<length)continue;elsebreak;}returnfood;}
根据createFood
函数返回的食物坐标,在窗体上绘制一个黄色矩形代表食物。将绘制食物的代码封装成函数paintFood
。
食物的左上角坐标为:【food.x * NODE_WIDTH, food.y * NODE_WIDTH】
食物的右下角坐标为:【(food.x + 1) * NODE_WIDTH, (food.y + 1) * NODE_WIDTH】
//绘制食物voidpaintFood(nodefood){intleft,top,right,bottom;left=food.x*NODE_WIDTH;top=food.y*NODE_WIDTH;right=(food.x+1)*NODE_WIDTH;bottom=(food.y+1)*NODE_WIDTH;setfillcolor(YELLOW);solidrectangle(left,top,right,bottom);setfillcolor(WHITE);}
主函数中,第一次生成食物在循环外。循环内部每次循环绘制一次食物。使用了随机数,别忘了使用当前时间作为随机数种子。另外,time
函数需要包含头文件#include <time.h>
。
//随机生成食物
srand(unsignedint(time(NULL)));
nodefood=createFood(snake,length);
while(1)
{
cleardevice();
paintGrid();
paintSnake(snake,length);
//绘制食物
paintFood(food);
Sleep(500);
changeDirection(&d);
snakeMove(snake,length,d);
}
现阶段代码:
#include<easyx.h>#include<stdio.h>#include<conio.h>#include<time.h>#defineNODE_WIDTH40//节点typedefstruct{intx;inty;}node;//绘制网格//横线(0,y),(800,y)0<=y<=600//竖线(x,0),(x,600)0<=x<=800voidpaintGrid(){//横线for(inty=0;y<600;y+=NODE_WIDTH){line(0,y,800,y);}//竖线for(intx=0;x<800;x+=NODE_WIDTH){line(x,0,x,600);}}voidpaintSnake(node*snake,intn){intleft,top,right,bottom;for(inti=0;i<n;i++){//左上角:【网格x坐标*网格宽度, 网格y坐标*网格宽度】left=snake[i].x*NODE_WIDTH;top=snake[i].y*NODE_WIDTH;//右下角:【(网格x坐标+ 1)*网格宽度, (网格y坐标+ 1)*网格宽度】right=(snake[i].x+1)*NODE_WIDTH;bottom=(snake[i].y+1)*NODE_WIDTH;//通过左上角与右下角坐标绘制矩形solidrectangle(left,top,right,bottom);}}//方向枚举enumdirection{eUp,eDown,eLeft,eRight};//蛇节点移动voidsnakeMove(node*snake,intlength,intdirection){//从尾结点开始,前一个节点覆盖后一个节点//4321043210//EDCBA--->DECBAfor(inti=length-1;i>0;i--){snake[i]=snake[i-1];}//根据方向,确定下一个头节点nodenewHead;newHead=snake[0];if(direction==eUp){newHead.y--;}elseif(direction==eDown){newHead.y++;}elseif(direction==eLeft){newHead.x--;}else//right{newHead.x++;}//更新头节点//DECBA--->DECBNsnake[0]=newHead;}//键盘输入改变directionvoidchangeDirection(enumdirection*pD){//检查输入缓存区中是否有数据if(_kbhit()!=0){//_getch函数获取输入缓存区中的数据charc=_getch();//判断输入并转向switch(c){case'w'://向上移动if(*pD!=eDown)*pD=eUp;break;case's'://向下移动if(*pD!=eUp)*pD=eDown;break;case'a'://向左移动if(*pD!=eRight)*pD=eLeft;break;case'd'://向右移动if(*pD!=eLeft)*pD=eRight;break;}}}//绘制食物/*(x*NODE_WIDTH,y*NODE_WIDTH)@-----------||||||||||-----------@((x+1)*NODE_WIDTH,(y+1)*NODE_WIDTH)*/voidpaintFood(nodefood){intleft,top,right,bottom;left=food.x*NODE_WIDTH;top=food.y*NODE_WIDTH;right=(food.x+1)*NODE_WIDTH;bottom=(food.y+1)*NODE_WIDTH;setfillcolor(YELLOW);solidrectangle(left,top,right,bottom);setfillcolor(WHITE);}//随机创建食物nodecreateFood(node*snake,intlength){nodefood;while(1){food.x=rand()%(800/NODE_WIDTH);food.y=rand()%(600/NODE_WIDTH);inti;for(i=0;i<length;i++){if(snake[i].x==food.x&&snake[i].y==food.y){break;}}if(i<length)continue;elsebreak;}returnfood;}intmain(){initgraph(800,600);//设置背景色setbkcolor(RGB(164,225,202));//使用背景色清空窗体cleardevice();//蛇节点坐标nodesnake[100]={{5,7},{4,7},{3,7},{2,7},{1,7}};//蛇节点长度intlength=5;enumdirectiond=eRight;//食物srand(unsignedint(time(NULL)));nodefood=createFood(snake,length);while(1){//清空整个窗体cleardevice();//绘制网格paintGrid();//绘制蛇节点paintSnake(snake,length);//绘制食物paintFood(food);//休眠500msSleep(500);//获取键盘输入并将方向存储到变量dchangeDirection(&d);//根据变量d的方向移动蛇节点snakeMove(snake,length,d);}getchar();closegraph();return0;}
6. 吃掉食物并长大
观察下图,蛇头为节点(11, 7)
。而蛇目前向右运动,下一步即将吃到在(12, 7)
位置的食物。
现在蛇头移动到了(12, 7)
的位置,并吃掉了食物。原蛇尾节点(6, 7)
本应该删除,但是这时蛇吃掉了食物,需要长大一节。再将蛇尾节点(6, 7)
加回来。
为了获得被删除的原蛇尾节点,snakeMove
函数需要返回原蛇尾节点,函数返回值从void
改为node
。函数中需要记录原蛇尾节点,并在最后返回原蛇尾节点。
//蛇身体移动nodesnakeMove(node*snake,intlength,intdirection){//记录尾节点nodetail=snake[length-1];for(inti=length-1;i>0;i--){snake[i]=snake[i-1];}nodenewHead;newHead=snake[0];if(direction==eUp){newHead.y--;}elseif(direction==eDown){newHead.y++;}elseif(direction==eLeft){newHead.x--;}else{newHead.x++;}snake[0]=newHead;//返回尾节点returntail;}
在主函数的循环中,作新的蛇头节点是否与食物节点重合的判断。若蛇头节点与食物节点重合,那么蛇会长大一节。将snakeMove
函数返回的上一次尾结点添加到蛇尾后面,蛇节点长度加1。并且,食物被吃掉以后,应当重新生成新的食物,作为新的目标。
注意蛇不能无限生长而超过snake
数组的长度,当length
大于等于100时,吃到食物就不再增加长度了。
while(1)
{
cleardevice();
paintGrid();
paintSnake(snake,length);
paintFood(food);
Sleep(500);
changeDirection(&d);
nodelastTail=snakeMove(snake,length,d);
//新的蛇头节点是否与食物节点重合
if(snake[0].x==food.x&&snake[0].y==food.y)
{
//限制snake节点最大长度
if(length<100)
{
//已经吃到食物,长度+1
snake[length]=lastTail;
length++;
}
//重新生成新的食物
food=createFood(snake,length);
}
}
现阶段代码:
#include<easyx.h>#include<stdio.h>#include<conio.h>#include<time.h>#defineNODE_WIDTH40//节点typedefstruct{intx;inty;}node;//绘制网格//横线(0,y),(800,y)0<=y<=600//竖线(x,0),(x,600)0<=x<=800voidpaintGrid(){//横线for(inty=0;y<600;y+=NODE_WIDTH){line(0,y,800,y);}//竖线for(intx=0;x<800;x+=NODE_WIDTH){line(x,0,x,600);}}voidpaintSnake(node*snake,intn){intleft,top,right,bottom;for(inti=0;i<n;i++){//左上角:【网格x坐标*网格宽度, 网格y坐标*网格宽度】left=snake[i].x*NODE_WIDTH;top=snake[i].y*NODE_WIDTH;//右下角:【(网格x坐标+ 1)*网格宽度, (网格y坐标+ 1)*网格宽度】right=(snake[i].x+1)*NODE_WIDTH;bottom=(snake[i].y+1)*NODE_WIDTH;//通过左上角与右下角坐标绘制矩形solidrectangle(left,top,right,bottom);}}//方向枚举enumdirection{eUp,eDown,eLeft,eRight};//蛇身体移动nodesnakeMove(node*snake,intlength,intdirection){//for(inti=0;i<length;i++)//printf("(%d,%d)\n",snake[i].x,snake[i].y);//记录尾节点nodetail=snake[length-1];//从尾结点开始,前一个节点覆盖后一个节点//0123401234//EDCBA--->EEDCBfor(inti=length-1;i>0;i--){snake[i]=snake[i-1];}//下一个头节点nodenewHead;newHead=snake[0];if(direction==eUp){newHead.y--;}elseif(direction==eDown){newHead.y++;}elseif(direction==eLeft){newHead.x--;}else//right{newHead.x++;}//更新头节点//EDCBA--->FEDCBsnake[0]=newHead;//for(inti=0;i<length;i++)//printf("(%d,%d)\n",snake[i].x,snake[i].y);//返回尾节点returntail;}//键盘输入改变directionvoidchangeDirection(enumdirection*pD){//检查输入缓存区中是否有数据if(_kbhit()!=0){//_getch函数获取输入缓存区中的数据charc=_getch();//判断输入并转向switch(c){case'w'://向上移动if(*pD!=eDown)*pD=eUp;break;case's'://向下移动if(*pD!=eUp)*pD=eDown;break;case'a'://向左移动if(*pD!=eRight)*pD=eLeft;break;case'd'://向右移动if(*pD!=eLeft)*pD=eRight;break;}}}//绘制食物/*(x*NODE_WIDTH,y*NODE_WIDTH)@-----------||||||||||-----------@((x+1)*NODE_WIDTH,(y+1)*NODE_WIDTH)*/voidpaintFood(nodefood){intleft,top,right,bottom;left=food.x*NODE_WIDTH;top=food.y*NODE_WIDTH;right=(food.x+1)*NODE_WIDTH;bottom=(food.y+1)*NODE_WIDTH;setfillcolor(YELLOW);solidrectangle(left,top,right,bottom);setfillcolor(WHITE);}//随机创建食物nodecreateFood(node*snake,intlength){nodefood;while(1){food.x=rand()%(800/NODE_WIDTH);food.y=rand()%(600/NODE_WIDTH);inti;for(i=0;i<length;i++){if(snake[i].x==food.x&&snake[i].y==food.y){break;}}if(i<length)continue;elsebreak;}returnfood;}intmain(){initgraph(800,600);//设置背景色setbkcolor(RGB(164,225,202));//使用背景色清空窗体cleardevice();//蛇节点坐标nodesnake[100]={{5,7},{4,7},{3,7},{2,7},{1,7}};//蛇节点长度intlength=5;enumdirectiond=eRight;//食物srand(unsignedint(time(NULL)));nodefood=createFood(snake,length);while(1){//清空整个窗体cleardevice();//绘制网格paintGrid();//绘制蛇节点paintSnake(snake,length);//绘制食物paintFood(food);//休眠500msSleep(500);//获取键盘输入并将方向存储到变量dchangeDirection(&d);nodelastTail=snakeMove(snake,length,d);//新的蛇头节点是否与食物节点重合if(snake[0].x==food.x&&snake[0].y==food.y){//限制snake节点最大长度if(length<100){//已经吃到食物,长度+1snake[length]=lastTail;length++;}//重新生成新的食物food=createFood(snake,length);}}getchar();closegraph();return0;}
7.游戏结束
最后,我们需要判断游戏是否结束。游戏结束的条件为:
蛇头吃到墙壁
蛇头吃到蛇身
如果满足以上两个条件,则游戏结束,并复位所有设置,重新开始游戏。
游戏网格x坐标区间为[0, 800 / NODE_WIDTH)
,即[0, 20)
。
游戏网格y坐标区间为[0, 600 / NODE_WIDTH)
,即[0, 19)
。
蛇头snake[0].x
小于0或大于等于20,蛇头即吃到左边或右边的墙壁。
蛇头snake[0].y
小于0或大于等于15,蛇头即吃到上边或下边的墙壁。
遍历除了蛇头外的所有蛇节点坐标,若有节点坐标与蛇头坐标一致,则表明蛇头吃到了蛇身。
将上面两个结束条件封装成isGameOver
函数,若游戏结束则返回true
,否则返回false
。
boolisGameOver(node*snake,intlength){//是否撞墙if(snake[0].x<0||snake[0].x>800/NODE_WIDTH)returntrue;if(snake[0].y<0||snake[0].y>600/NODE_WIDTH)returntrue;//是否吃到蛇身for(inti=1;i<length;i++){if(snake[0].x==snake[i].x&&snake[0].y==snake[i].y)returntrue;}returnfalse;}
游戏结束后,需要调用reset
函数,复位蛇节点坐标,蛇节点长度以及前进方向。
voidreset(node*snake,int*pLength,enumdirection*d){snake[0]=node{5,7};snake[1]=node{4,7};snake[2]=node{3,7};snake[3]=node{2,7};snake[4]=node{1,7};*pLength=5;*d=eRight;}
在主函数的循环中,添加游戏结束的判断。若游戏结束,复位各种设置。由于,蛇身坐标被复位,有可能与之前的食物坐标重合。因此,也应当重新生成食物。
完整源码请加群【881577770】获取!里面有一些资料可以帮助大家更好的学习,在学习C语言的过程中遇到任何的问题,都可以发出来一起讨论,大家都是学习C/C++的,或是转行,或是大学生,还有工作中想提升自己能力的前端党,如果你是正在学习C/C++的小伙伴可以加入学习。
while(1){cleardevice();paintGrid();paintSnake(snake,length);paintFood(food);Sleep(500);changeDirection(&d);nodelastTail=snakeMove(snake,length,d);if(snake[0].x==food.x&&snake[0].y==food.y){if(length<100){snake[length]=lastTail;length++;}food=createFood(snake,length);}//游戏是否结束if(isGameOver(snake,length)==true){//游戏结束,复位设置,重新生成食物reset(snake,&length,&d);food=createFood(snake,length);}}