本工程由STM32CuBeMx工具初始化外设生成,之后手动添加LCD驱动相关文件并在主函数中初始化LCD。显示游戏开始界面和设置,确认游戏设置(蛇体颜色、蛇体速度)后,绘制游戏地图,开启相关外设功能。初始化蛇并开始游戏。
相关外设初始化
初始化外设由图可以看到:GPIO、FSMC、TIM7、TIM3和RNG硬件随机数发生器。
此外,LCD初始化相关函数在.c驱动文件中。
游戏相关的函数则放在snack中
按键设计
使用正点原子例程中的驱动文件,仅仅修改了key.h中的按键宏定义。
#define KEY0_PRES 4
#define KEY1_PRES 3
#define KEY2_PRES 2
#define WKUP_PRES 1
绘制图形化界面
在游戏绘图方面,我绘制了开始界面(包括按键提示、蛇体颜色、蛇体速度)、游戏界面(包含地图范围、地图上方的Logo)和结束界面(游戏结束动画、重开提示)。这里截图背景部分的代码:
void Drowgound(void)//背景
{
LCD_Clear(WHITE);
LCD_Draw_Picture(30,10,64,64,(uint16_t *)gImage);//蛇像
LCD_Draw_Picture(120,10,202,64,(uint16_t *)gImage_logo);//LOGO
POINT_COLOR=DARKBLUE;
LCD_ShowString(324,50,150,24,24,"Score:");//分数位置
LCD_ShowNum(324+24*3,50,score,2,24);//实时分数
LCD_DrawRectangle(320, 45, 324+24*5, 75);//分数框
POINT_COLOR=RED;
LCD_DrawRectangle(30, 80, 450, 760);//游戏边框
LCD_DrawRectangle(31, 81, 449, 759);//游戏边框加粗
}
蛇体信息
选择构建结构体存储贪吃蛇游戏相关信息,采用数组存储贪吃蛇头部以及每节的坐标。通过调用涂圆函数画出贪吃蛇的每一节身体。
/*
初始化蛇
*/
void snakeInit(){
snake.length=3; //初始长度为3
snake.snakecolor=snake.snakecolor;
snake.snakeX[0]=240;
snake.snakeX[1]=240;
snake.snakeX[2]=240;
snake.snakeY[0]=420;
snake.snakeY[1]=430;
snake.snakeY[2]=440;
snake.headX=snake.snakeX[0]; //记录下头部的位置
snake.headY=snake.snakeY[0]; //记录下头部的位置
snake.dir=1; //设置运动方向
snake.tpdir=1;//设置初始按键方向
snake.life=1; //1:蛇还活着;0:蛇死亡
generateFood();
refresh(); //调用函数显示出蛇和食物的位置
}
食物随机生成
食物的生成我们用到STM32F4上的硬件随机数发生器。它可以产生一个32位的随机数。我们将产生的随机数传递给食物的坐标、颜色(我定义了一个颜色数组,可以生成随机颜色的食物)。这里可以移植正点提供的一个在一定范围内产生随机数的函数以保证生成的食物坐标在我们的地图范围之内。此外还写了一个函数保证食物不会随机生成在蛇体身上。同样通过涂圆函数画出食物。
/*
判断随机产生的食物是否处于蛇体内
*/
uint16_t isCover(uint16_t snakeX[],uint16_t snakeY[],uint16_t foodX,uint16_t foodY){
uint8_t i;
for(i=0;i<snake.length;i++){
if(snakeX[i]==foodX&&snakeY[i]==foodY)
{
return 1;
}
}
return 0;
}
void generateFood()//创建食物坐标
{
uint16_t col=RNG_Get_RandomRange(0,11);
snake.foodX=RNG_Get_RandomRange(40,440);
snake.foodY=RNG_Get_RandomRange(90,750);
while(isCover(snake.snakeX,snake.snakeY,snake.foodX,snake.foodY))
{
snake.foodX=RNG_Get_RandomRange(40,440);
snake.foodY=RNG_Get_RandomRange(90,750);
}
snake.foodcolor=lcd_discolor[col];
}
判断是否吃到食物
判断是否吃到食物,这里用到了标准C语言中的sqrt函数。求出食物坐标和蛇头部坐标之间的距离。当距离小于我们认为规定的一个合理范围,判定为吃到食物。然后蛇体长度加一、得分加一。将食物坐标赋给蛇头坐标,继续移动下去。
if(dis<8)//如果吃到了食物
{
snake.length++; //长度加1
score++; //分数加1
for(i=1;i<snake.length;i++){ //除头部以外的坐标前移
snake.snakeX[snake.length-i]=snake.snakeX[snake.length-i-1];
snake.snakeY[snake.length-i]=snake.snakeY[snake.length-i-1];
}
snake.snakeX[0]=snake.foodX; //头部坐标等于食物的坐标
snake.snakeY[0]=snake.foodY;
generateFood(); //再生成一个食物坐标
}
蛇的移动
移动功能则是利用for循环逐个传递坐标,利用屏幕刷新表现出移动效果
/*
每隔200ms将会执行一次,以此实现蛇的运动
*/
for(i=1;i<snake.length;i++){
snake.snakeX[snake.length-i]=snake.snakeX[snake.length-i-1];
snake.snakeY[snake.length-i]=snake.snakeY[snake.length-i-1];
}
snake.snakeX[0]=snake.headX;
snake.snakeY[0]=snake.headY;
游戏运行和重开
游戏运行则是编写GameStart函数,在循环中检测按键以控制蛇,利用定时器中断计数实现蛇体移动并判断是否吃到食物以及死亡判断。当满足重开条件(蛇体死亡),代表蛇体生命的变量改变,进入if判断,利用while()卡住程序,按下指定按键即可重开。
/*
开始游戏
*/
void GameStart()
{
uint8_t key=0;
while(1){
key=KEY_Scan(0);
if(key!=0) //被按下
{
switch(key)
{
case WKUP_PRES: snake.tpdir=1;break;
case KEY1_PRES: snake.tpdir=3;break;
case KEY2_PRES: snake.tpdir=2;break;
case KEY0_PRES: snake.tpdir=4;break;
}
if(snake.life==0){
while(WK_UP);
HAL_TIM_Base_Start_IT(&htim3);
Drowgound();
score = -1;
snakeInit();
}
}
if(count==2){ //定时器设置的100ms的中断,200ms运行一次蛇的运动函数
count=0; //重新计数
snakeGo(snake.tpdir);
refresh();
}
}
}
这里仅仅展示了部分关键代码