一、界面初始化
在上方显示得分,下方进行游戏,吃到一个食物加十分。
用一个结构体表示蛇的坐标,枚举表示方向
enum DIRECTION
{
up,
down,
left,
right
};
typedef struct
{
int x;
int y;
} _snake;
void snake_init(void)
{
POINT_COLOR=GREEN;
LCD_ShowString(40,20,100,20,24,"SCORE:");
LCD_ShowNum(160, 20, 0, 3, 24);
LCD_DrawLine(0,79, 240, 79);
snake[0].x=1;
snake[0].y=0;//蛇第一个点坐标
snake[1].x=0;
snake[1].y=0; //蛇第二个点坐标
dir=right;//蛇头的方向
length=2;//蛇长度
draw_point(snake[0].x, snake[0].y,BLACK) ;
draw_point(snake[1].x, snake[1].y,BLACK) ;
food();//生成食物
}
我使用的是320X240的lcd屏,将屏幕竖屏分为两部分,下半部分游戏部分为240X240,这里设置蛇的每一个节点大小为16X16的大小,横为x,竖为y,即为15X15的大小棋盘。
void draw_point(u16 x, u16 y, u16 color)//画蛇的节点
{
for(int i=0;i<snake_point;i++)
{
for(int j=0;j<snake_point;j++)
{
LCD_Fast_DrawPoint( snake_point*x+i, snake_point*y+80+j,color);
}
}
}
宏定义引出蛇的每一个结点像素大小与最终胜利的得分(这里初始蛇身为2即当蛇的长度为32时胜利,吃到一个食物得10分)
#define snake_point 16 //蛇节点大小
#define win_score 300 //最终胜利得分
定义初始蛇长度以及一个结构体数组,数组大小为棋盘大小。例如节点大小为16,棋盘的横纵数量为240/16=15,把整个棋盘吃满蛇的长度为15的平方即225,从零开始计数的,减去一即可。
_snake snake[(240/snake_point)*(240/snake_point)-1];
u16 length=2;
左边是16x16的,右边是8x8的
二、食物生成
使用rand()函数产生随机数
void food(void) //生成食物
{
do
{
food_x=(rand()%(240/snake_point));
food_y=(rand()%(240/snake_point)); //根据蛇的节点大小生成食物,此时节点为16X16,所以生成0-14的随机数
}
while(LCD_ReadPoint(food_x*snake_point, food_y*snake_point+80)==BLACK); //检查该点是否为空位
draw_point(food_x,food_y,RED); //画食物
}
三、游戏结束
当蛇越界以及吃到自己身体时游戏结束,在屏幕上显示“GAME OVER”
当得分到达设定的分数时游戏胜利,在屏幕上显示“YOU WIN”
u16 snake_over(void)
{
//判断蛇有没有越界
if(snake[0].x>(240/snake_point-1) || snake[0].y>(240/snake_point-1) || snake[0].x<0 || snake[0].y<0)
{
POINT_COLOR=RED;
LCD_ShowString(60,140,100,40,24,"GAME OVER");
return 1;
}
//判断蛇头有没有吃到自己身体
for(int i=1;i<length;i++)
{
if(snake[0].x==snake[i].x && snake[0].y==snake[i].y)
{
POINT_COLOR=RED;
LCD_ShowString(60,140,100,40,24,"GAME OVER");
return 1;
}
}
if((length-2)*10==win_score)
{
POINT_COLOR=RED;
LCD_ShowString(60,140,100,40,24," YOU WIN ");
return 1;
}
return 0;
}
四、蛇的移动
蛇每一步移动时,先将最后一个节点保存下来,然后从后到前依次更新每个节点的位置,随后判断蛇的方向,根据方向更新蛇头的坐标。如果吃到食物则长度加一并且将之前保存的节点作为尾巴,如果没吃到食物,则删除。最后判断游戏是否结束即可。
例如此时蛇的长度为3,分别为snak0,snake1,sanke2,先将snake3的坐标保存,然后从后往前让snake2的坐标等于snake1的坐标,snake1的坐标等于sanke0的坐标,再判断蛇的方向,根据方向更新蛇头sanke0的坐标,随后判断是否吃到食物,如果吃到食物,则让snake3的坐标为之前保留snake2的坐标。
每一步移动过程只需要画出蛇头以及是否删除蛇尾即可。吃到食物保留之前的蛇尾,没吃到就删除。
void snake_move(void)
{
u16 snake_lastx;
u16 snake_lasty;
snake_lastx=snake[length-1].x;
snake_lasty=snake[length-1].y; //将蛇的最后一个节点保存下来
for(int i=length-1;i>0;i--)
{
snake[i].x=snake[i-1].x;
snake[i].y=snake[i-1].y; //从后到前将除了蛇头之外的节点依次更新
}
switch (dir) //判断方向,更新蛇头
{
case up: snake[0].y--;
break;
case down: snake[0].y++;
break;
case left: snake[0].x--;
break;
case right: snake[0].x++;
break;
}
draw_point(snake[0].x,snake[0].y,BLACK); //画蛇头节点
if(snake[0].x==food_x && snake[0].y==food_y) //如果吃到食物则蛇长度加一得分加10并且保留之前的节点并变为尾巴,否则删除
{
length++;
LCD_ShowNum(160, 20, (length-2)*10, 3, 24);
snake[length-1].x=snake_lastx;
snake[length-1].y=snake_lasty;
food();
}
else
{
draw_point(snake_lastx,snake_lasty,WHITE);//背景是白色的,所以画白色就表示删除
}
if(snake_over()==1) //判断游戏是否结束
{
while(start_flag==0); //等待游戏重新开始信号
}
if(start_flag==1)
{
LCD_Clear(WHITE); //清屏
snake_init(); //初始化
start_flag=0; //清空标志位
}
}
main函数只需要延时每一步的移动即可,延时大小代表蛇的移动速度。
int main(void)
{
delay_init(); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
uart_init(9600); //串口初始化为9600
LED_Init(); //LED端口初始化
LCD_Init();
EXTIX_Init(); //外部中断初始化
snake_init();
while(1)
{
snake_move();
//LED0=!LED0;
delay_ms(400);
}
}
五、控制方向
用四个按键来分别控制蛇的移动方向,这里用外部中断的方式来控制,以保证程序运行时能够及时改变方向,当每一个按键按下时,要确保此时的方向不是对应的反方向,否则会吃到自己。
这里进行上下方向改变的时候不是简单的判断是否为反方向,是因为如果按键按快了,连续改变了两次方向,而此时蛇并没有移动,会吃到自己。所以当进行上下方向改变的时候,我们判断蛇头与蛇的第二个节点是否在用一竖轴上,即他们的x坐标是否相同,如果相同,则不能改变方向。判断左右同理。
//外部中断0服务程序
void EXTI0_IRQHandler(void)
{
delay_ms(10);//消抖
if(WK_UP==1) //WK_UP按键
{
if( snake[0].x != snake[1].x) //这里不用dir!=up;因为如果按快了会改变两次方向,而
//此时蛇并没有移动,所以会吃到自己
dir=down;
}
EXTI_ClearITPendingBit(EXTI_Line0); //清除LINE0上的中断标志位
}
//外部中断2服务程序
void EXTI2_IRQHandler(void)
{
delay_ms(10);//消抖
if(KEY2==0) //按键KEY2
{
if( snake[0].x != snake[1].x)
dir=up;
}
EXTI_ClearITPendingBit(EXTI_Line2); //清除LINE2上的中断标志位
}
//外部中断3服务程序
void EXTI3_IRQHandler(void)
{
delay_ms(10);//消抖
if(KEY1==0) //按键KEY1
{
if(snake[0].y != snake[1].y)
dir=right;
}
EXTI_ClearITPendingBit(EXTI_Line3); //清除LINE3上的中断标志位
}
void EXTI4_IRQHandler(void)
{
delay_ms(10);//消抖
if(KEY0==0) //按键KEY0
{
if(snake[0].y != snake[1].y)
dir=left;
}
EXTI_ClearITPendingBit(EXTI_Line4); //清除LINE4上的中断标志位
}
六、labview扩展
由于硬件资源受限,我的开发板只有三个按键,所以这里使用labview模拟几个按键使用。
上下左右四个按键控制方向,start按键控制游戏重启。
labview与单片机使用串口通信传递数据,当某一个按键按下时,向单片机发送数据,再由单片机处理接收到的数据进行操作。
labview需要使用VISA控件,首先配置串口参数,创建输入控件即可。
在条件结构里发送数据。
读取从键盘输入的值,在进行判断,这里用WASD来控制上下左右,回车键重启游戏。只需要判断从键盘获取的值是否为我们想要的值即可,随后进行处理。
将按键的动作改为单击触发,这样按下一次表示触发一次。
单片机在串口中断里判断接收到的数据,随后进行方向与重启处理。
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中断(接收到的数据必须是0x0d 0x0a结尾)
{
Res =USART_ReceiveData(USART1); //读取接收到的数据
if((USART_RX_STA&0x8000)==0)//接收未完成
{
if(USART_RX_STA&0x4000)//接收到了0x0d
{
if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始
else USART_RX_STA|=0x8000; //接收完成了
}
else //还没收到0X0D
{
if(Res==0x0d)USART_RX_STA|=0x4000;
else
{
USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;
USART_RX_STA++;
if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收
}
}
}
if(Res==0 && snake[0].x != snake[1].x)
dir=up;
if(Res==1 && snake[0].x != snake[1].x)
dir=down;
if(Res==2 && snake[0].y != snake[1].y)
dir=left;
if(Res==3 && snake[0].y != snake[1].y)
dir=right;
if(Res==5 )
start_flag=1;//重新开始标志
}
七、总结
在lcd屏上玩贪吃蛇,可以由按键控制,也可以由labview控制,可以由电脑键盘控制。
第一次写博客,记录一下《*……*》