嵌入式STM32F407开发板贪吃蛇游戏

古月 工程项目 2024-02-14

实验目的

本实验的主要目的是通过使用STM32F407开发板和Keil软件,设计和实现一个嵌入式贪吃蛇游戏。通过这个实验从而掌握以下知识:

  1. 熟悉嵌入式系统开发环境。
  2. 学会使用STM32F407开发板的各种功能和外设。
  3. 理解嵌入式游戏的基本原理,包括游戏循环、图形渲染和用户输入处理。
  4. 实现一个完整的贪吃蛇游戏,以展示嵌入式系统的实际应用。

实验环境

  1. STM32F407开发板。
  2. Keil MDK开发环境。
  3. 液晶显示屏。
  4. 贪吃蛇游戏相关的图形和资源文件。

实验要求

  1. 在STM32F407开发板上实现一个完整贪吃蛇游戏。
  2. 在液晶显示屏上显示贪吃蛇游戏界面。
  3. 接受开发板上的按键输入作为游戏控制的输入。
  4. 实验报告应包括详细的代码说明和设计思路。

实验原理

  1. 游戏循环:贪吃蛇游戏通过一个主循环来运行。在每个循环迭代中,游戏会更新游戏状态、处理输入、渲染图形等。
  2. 图形渲染:使用LCD输出设备,通过控制像素点的颜色来绘制游戏界面。在本实验中,我们调用对应的库函数来构成图形渲染代码。
  3. 用户输入处理:开发板上的按键用于控制游戏,可以根据用户输入改变蛇的方向。

实验步骤

游戏页面设计

游戏初始化欢迎页面

贪吃蛇初始化欢迎页面设计:logo+“请按任意键继续”

游戏初始化选择页面

游戏初始化选择页面包含三个选项:

1.模式选择

2.历史最佳纪录

3.退出

采用按键进行选项选择,选择“模式选择”进入模式选择页面,选择“历史最佳纪录”进入历史最佳纪录页面,选择“退出”返回游戏初始化欢迎页面,选择返回键(四个键中剩下的那个键),返回游戏初始化欢迎页面。

模式选择页面

模式选择页面包含三个选项:

1.经典模式

2.时间模式

3.特殊模式

选择对应键进入对应模式(特殊模式有对应的选择页面),选择返回键返回游戏初始化选择页面。

历史纪录页面

页面内容为:

“模式名称”+“:”+“对应分数“

“按任意键继续”

选择任意键返回游戏初始化选择页面。

特殊模式选择页面

特殊模式选择页面包含三个选项:

1.迷宫模式

2.障碍物模式

3.奖励模式

选择对应按键进入对应模式,选择返回键返回模式选择页面。

游戏结束页面

选择对应游戏模式后即进入游戏页面立即开始游戏,在按照游戏规则完成游戏后进入游戏结束页面,页面内容为:

”你的分数是“+”:“+”对应分数“

”按任意键继续“

选择任意键返回游戏初始化选择页面

页面交互逻辑

页面之间的交互逻辑具体如下:

模式游戏逻辑

经典模式

1.清除屏幕,并将屏幕分为两个矩形,一个矩形为logo显示界面,另一个矩形作为游戏界面。

2.随机生成两个大小为M的相邻像素块(单位像素块大小固定为M)作为初始蛇的身体,决定其中一个为头,并确定初始行进方向,同时,在出蛇身体外的游玩区域部分随机生成一个大小为M的像素块作为初始食物。

3.每吃下一个像素块食物,蛇的身体尾部增加一个大小为M的像素块。

4.贪吃蛇头部为红色,身体部位为黑色。

5.由四个按键控制贪吃蛇的转向。

6.若贪吃蛇的头部触碰到身体,则游戏结束;触碰边界,则游戏结束。

7.游戏矩阵的边界不可触碰。

8.贪吃蛇每次循环先决定方向改变,再由当前方向移动一个基本像素单位。

9.得分显示将实时显示分数,并在每次游戏结束后与最高得分进行比较,并选取其中高者纪录为新纪录。

10.贪吃蛇的移动速度由循环的延时决定。

时间模式

1.逻辑基本与经典模式相同。

2.游戏条件新增,游戏时间倒计时结束,则游戏结束。

3.每场比赛的游戏时间由循环次数控制。

4.游戏时间将以倒计时的形式显示屏幕对应位置。

特殊模式之迷宫模式

1.基本逻辑与经典模式相同。

2.游戏区域增加固定的迷宫墙壁像素模块。

3.每个墙壁像素大小为M,颜色为黄色。

4.迷宫在边界处留有一个出口,由起点到达终点则游戏成功。

5.游戏条件新增,触碰墙壁,则游戏结束。

特殊模式之障碍物模式

1.基本逻辑与经典模式相同。

2.新增,每生成一个小像素点的同时,在非贪吃蛇身体和小像素点所在位置的地方生成一个大小为M的障碍物像素点。

3.障碍物像素点在游戏结束前将一直存在。

4.障碍物可被贪吃蛇触碰,触碰后游戏结束。

特殊模式之奖励模式

1.基本逻辑与经典模式相同。

2.新增,每吃到一定数量的小食物,生成9M大小的奖励像素块。

3.吃掉该像素块后分数加3,身体长度加3节。

代码实现

硬件按键扫描实现

在gpio.h中定义全局的按键扫描函数:

uint8_t KEY_Scan(uint8_t mode)
{
    static uint8_t key_up=1;
    if(mode)key_up=1;
    if(key_up&&(KEY3==0||KEY4==0||KEY5==0||KEY6==0))
    {
        HAL_Delay(10);
        key_up=0;
        if(KEY3==0)return 3;
        else if(KEY4==0)return 4;
        else if(KEY5==0)return 5;
        else if(KEY6==0)return 6;
    }else if(KEY3==1&&KEY4==1&&KEY5==1&&KEY6==1)key_up=1;
    return 0;
}

对KEY_Scan(0)函数则有,对应按键按下则返回对应数值(3,4,5,6),否则返回零。

硬件LCD显示实现

饮用lcd.h中的库函数来实现屏幕上相关元素的绘制:

void LCD_DrawPicture(uint16_t x0, uint16_t y0, uint16_t width, uint16_t height, const uint8_t* picture)
void LCD_Fill(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t color)
void LCD_DrawRectangle(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t color)
void LCD_Clear(uint16_t color)

上述四个库函数的功能分别是:指定位置绘制图片、指定位置填充矩形、指定位置绘制矩形、清除屏幕并用指定颜色填充。使用上述四个函数即可完成贪吃蛇游戏的全部元素绘制。

页面交互逻辑实现

采用状态机的方式来处理按键输入,进行输入分析,并进行状态转换:

int main(void){

    //初始化

    GameState current_state = WELCOME_PAGE;


    while(1){
        current_state = handleState(current_state);
        
    }
}

//页面交互逻辑函数 状态机
GameState handleSate(GameState current_state){
    static int welcomePageShown = 0;//静态变量
    static int mainmenuPageShown = 0;
    static int modeselectionPageShown = 0;
    static int specialmodeselectionPageShown=0;
    int key=0;

    switch(current_state){

        case WELCOME_PAGE:
            if (!welcomePageShown) {
                LCD_DrawPicture(240, 136, 400, 245, snake_Image); 
                welcomePageShown = 1; 
            }

            key = KEY_Scan(0);

            if(key!=0){
                welcomePageShown=0;
                return MAIN_MENU;
            }
            else{
                return WELCOME_PAGE;
            }
            break;

        case MAIN_MENU:
            if(!mainmenuPageShown){
                LCD_DrawPicture(240, 136, 400, 245, mainmenu_Image); 
                mainmenuPageShown=1;
            }

            key = KEY_Scan(0);

            if(key==0){
                return MAIN_MENU;
            }
            else if(key==3){
                return MODE_SELECTION;
            }
            else if(key==4){
                return HISTORY;
            }
            else if(key==5||key==6){
                welcomePageShown=0;
                return WELCOME_PAGE;
            }
            break;

        case MODE_SELECTION:
            if(!modeselectionPageShown){
                LCD_DrawPicture(240, 136, 400, 245, modeselection_Image); 
                modeselectionPageShown=1;
            }

            key = KEY_Scan(0);

            if(key==0){
                return MODE_SELECTION;
            }
            else if(key==3){
                return CLASSIC_MODE;
            }
            else if(key==4){
                return TIME_MODE;
            }
            else if(key==5){
                return SPECIAL_MODE_SELECTION;
            }
            else if(key==6){
                mainmenuPageShown=0;
                return MAIN_MENU;
            }
            break;

        case CLASSIC_MODE:
            return GamePlayClassicMode();//仅当游戏结束时返回GAME_OVER 游戏循环在函数内部实现
            break;

        case TIME_MODE:
            return GamePlayTimeMode();//仅当游戏结束时返回GAME_OVER 游戏循环在函数内部实现
            break;

        case SPECIAL_MODE_SELECTION:
            if(!specialmodeselectionPageShown){
            LCD_DrawPicture(240, 136, 400, 245, specialmodeselectionPageShown_Image); 
            specialmodeselectionPageShown=1;
            }

            key = KEY_Scan(0);

            if(key==0){
                return SPECIAL_MODE_SELECTION;
            }
            else if(key==3){
                return SPECIAL_LABYRINTH;
            }
            else if(key==4){
                return SPECIAL_OBSTACLE;
            }
            else if(key==5){
                return SPECIAL_REWARD;
            }
            else if(key==6){
                modeselectionPageShown=0;
                return MODE_SELECTION;
            }
            break;

        case SPECIAL_LABYRINTH:
            return GamePlaySpecialLabyrinth();//仅当游戏结束时返回GAME_OVER 游戏循环在函数内部实现
            break;

        case SPECIAL_OBSTACLE:
            return GamePlaySpecialObstacle();//仅当游戏结束时返回GAME_OVER 游戏循环在函数内部实现
            break;

        case SPECIAL_REWARD:
            return GamePlaySpecialReward();//仅当游戏结束时返回GAME_OVER 游戏循环在函数内部实现
            break;

        case GAME_OVER:
            return DrawGameOver();//仅当有按键输入时返回MAIN_MENU

        case HISTORY:
            return DrawHistory();//仅当有按键输入时返回MAIN_MENU

    }
}

这里定义游戏状态的结构体,初始游戏状态为初始欢迎界面,后续跟进按键来进行状态之间的转换,每进行一次状态转换,状态机函数进行一次处理,并保持该状态直到读取到下一次按键输入。该代码是游戏的主循环代码,实现了前文所提到的页面交互逻辑。

相关元素的初始定义
typedef enum {
    CLASSIC_MODE,
    GAME_OVER,
    HISTORY,
    MAIN_MENU,
    MODE_SELECTION,
    SPECIAL_REWARD,
    SPECIAL_LABYRINTH,
    SPECIAL_MODE_SELECTION,
    SPECIAL_OBSTACLE,
    TIME_MODE,
    WELCOME_PAGE
} GameState;

typedef struct SnakeNode{
    int x;
    int y;
    uint16_t color;
    struct     SnakeNode *next,*prev;
}SNAKE_t,*SNAKELIST;

在main.h文件中,我们对蛇的结构体和游戏状态的枚举进行了初始化定义,其中蛇的结构体由x与y坐标以及颜色决定,采用双向链表的结构。

初始游戏界面的绘制
lcd_clear(Dcyan);
LCD_Fill(5,20,240,265,Blue);
LCD_DrawPicture(250,17,470,262,Image_snakelogo);
LCD_ShowString(10,0,(uint8_t*)"now your score :",Red,Dcyan);
        
SNAKE_t head;
head.x = 30;
head.y = 40;
head.color = Red;
head.next = NULL;  
    
SNAKE_t body1;
body1.x = head.x - 9; 
body1.y = head.y;
body1.color = Blue;
body1.next = NULL;  
head.next = &body1;  

LCD_DrawNode(head); 
LCD_DrawNode(body1);  
HAL_Delay(160);
enum Direction direction = RIGHT;  
LCD_Fill(foodx-4,foody-4,foodx+4,foody+4,White);

上述代码进行了蛇的结构体的初始话,包含蛇头和蛇身的初始话,以及食物的初始话,并对显示屏幕进行了初始化绘制。

蛇身绘制与清除
void LCD_DrawNode(SNAKE_t snake){
  if(snake.color==Red){
    LCD_DrawRectangle(snake.x-4,snake.y-4,snake.x+4,snake.y+4,Dgreen );
    LCD_Fill(snake.x-3,snake.y-3,snake.x+3,snake.y+3,Red);
  }
  else{
    LCD_DrawRectangle(snake.x-4,snake.y-4,snake.x+4,snake.y+4,Dgreen );
    LCD_Fill(snake.x-3,snake.y-3,snake.x+3,snake.y+3,Blue);
  }
}

void LCD_ClearNode(SNAKE_t snake){
  LCD_DrawRectangle(snake.x-4,snake.y-4,snake.x+4,snake.y+4,Blue );
  LCD_Fill(snake.x-3,snake.y-3,snake.x+3,snake.y+3,Blue);
}

在上述代码中,我们定义了蛇身的绘制函数,它接受一个蛇的结构体作为参数,通过颜色来分别绘制蛇头和蛇身,通过调用lcd.h库中的函数来实现。在每一次循环中,我们先清除蛇身,根据方向来进行蛇的移动,最后将蛇绘制出来,即实现了蛇的动态移动。

按键输入与方向控制
Key = KEY_Scan(0);  

        if(Key != 0)
        {
            switch(Key)
            {
                case 3: if(direction != DOWN) direction = UP; break;
                case 4: if(direction != RIGHT) direction = LEFT; break;
                case 5: if(direction != LEFT) direction = RIGHT; break;
                case 6: if(direction != UP) direction = DOWN; break;
            }
        }

        temp = head.next;
        int lastX = head.x, lastY = head.y;
        while(temp != NULL) {
            int currentX = temp->x;
            int currentY = temp->y;
            temp->x = lastX;
            temp->y = lastY;
            lastX = currentX;
            lastY = currentY;
            temp = temp->next;
        }

        switch(direction)
        {
            case UP:    head.y -= 9; break;
            case DOWN:  head.y += 9; break;
            case LEFT:  head.x -= 9; break;
            case RIGHT: head.x += 9; break;
        }
        LCD_DrawNode(head); 
        temp = head.next;
        while(temp != NULL) {
            LCD_DrawNode(*temp);
            temp = temp->next;
        }

通过Key = KEY_Scan(0)来进行按键输入,根据蛇的当前方向来进行方向更新,与当前方向相同或相反的情况下保持当前移动方向。进行方向更新后,对蛇头的坐标依据方向来进行移动,根据移动结构,依次更新蛇的每一节身体并绘制。

食物判定
dis=(foody-head.y)*(foody-head.y)+(foodx-head.x)*(foodx-head.x);

              if(dis<80){
                    score+=1;
                    LCD_Fill(foodx-4,foody-4,foodx+4,foody+4,Blue);
                    foodx=randnum(foodnum);
                    HAL_Delay(1);
                    foody=randnum(foodnum);
                    foodnum+=1;
                    LCD_Fill(foodx-4,foody-4,foodx+4,foody+4,White);
                    SNAKE_t *newBodyPart = malloc(sizeof(SNAKE_t));
                    if (newBodyPart != NULL) {
                    newBodyPart->x = 0; 
                    newBodyPart->y = 0; 
                    newBodyPart->color = Black; 
                    newBodyPart->next = NULL;

                    SNAKE_t *current = &head;
                    while (current->next != NULL) {
                            current = current->next;
                    }
                    current->next = newBodyPart; 
                    newBodyPart->x = current->x;
                    newBodyPart->y = current->y;
                    }
                }

根据食物与蛇头坐标之间的几何距离来进行是否吃到食物的判定,在吃到食物后,清除原有食物,并随机生成新的食物并绘制,同时,进行分数的更新。在吃到食物后,通过引入新的蛇的结构体元素来实现蛇身的增长,并绘制更新后的蛇。

游戏结束判定
if (head.x < 10 || head.x > 235 || head.y < 25 || head.y > 260) {
lcd_clear(Blue);
LCD_ShowString(20,100,(uint8_t*)"Game Over !!!",Red,Blue);
LCD_ShowString(20,120,(uint8_t*)"Your final score is :",Red,Blue);
LCD_ShowString(20,140,(uint8_t*)scoreStr,Red,Blue);
LCD_ShowString(20,160,(uint8_t*)"Try again?",Red,Blue);
break; 
}

    
for (SNAKE_t *temp = head.next; temp != NULL; temp = temp->next) {
if (head.x == temp->x && head.y == temp->y) {
lcd_clear(Blue);
LCD_ShowString(20,100,(uint8_t*)"Game Over !!!",Red,Blue);
LCD_ShowString(20,140,(uint8_t*)scoreStr,Red,Blue);
LCD_ShowString(20,160,(uint8_t*)"Try again?",Red,Blue);
break; 
}
}

上述代码对蛇的位置进行检测,如果触碰到了边界或者触碰到了自己的身体,则退出循环,游戏结束并绘制游戏结束界面,进行分数的显示。

分数的显示与更新
sprintf(scoreStr,"%d",score);
LCD_ShowString(138,0,(uint8_t*)scoreStr,Red,Dcyan);
HAL_Delay(160); 

这里对分数进行格式转换并在指定地点进行绘制,以实现分数的更新,同时,主循环中的时延决定了蛇的移动速度,也决定了游戏的难度。

时间模式的实现
gametime++; 

if(gametime>100){
                lcd_clear(Blue);
                LCD_ShowString(20,100,(uint8_t*)"Game Over !!!",Red,Blue);
                LCD_ShowString(20,120,(uint8_t*)"Your final score is :",Red,Blue);
                LCD_ShowString(20,140,(uint8_t*)scoreStr,Red,Blue);
                LCD_ShowString(20,160,(uint8_t*)"Try again?",Red,Blue);
                        break;
                    }
                    
remaintime=20-(float)gametime*0.2;
                sprintf(remaintimeStr,"%f",remaintime);
                LCD_ShowString(165,0,(uint8_t*)remaintimeStr,Red,Dcyan);

这段代码在主循环中使用变量gametiem来实现游戏时间的控制,在游戏结束的判定中加入对游戏剩余时间的判断,同时,在指定位置实时更新并绘制游戏剩余时间,从而实现了时间模式。

奖励模式的实现
if((specialFood == 0 && dis < 18) || (specialFood == 1 && dis < 32)){
                    int bodyPartsToAdd = 1; 
                    if(specialFood == 1){
            score += 3; 
            specialFood = 0; 
            bodyPartsToAdd = 3; 
                        LCD_Fill(foodx-6,foody-6,foodx+6,foody+6,Blue);
        } else {
            score += 1; 
                        LCD_Fill(foodx-4,foody-4,foodx+4,foody+4,Blue);
        }
                for(int i = 0; i < bodyPartsToAdd; i++){
            SNAKE_t *newBodyPart = malloc(sizeof(SNAKE_t));
            if(newBodyPart != NULL){
                newBodyPart->x = 0; 
                newBodyPart->y = 0; 
                newBodyPart->color = Black; 
                newBodyPart->next = NULL;

                SNAKE_t *current = &head;
                while(current->next != NULL){
                    current = current->next;
                }
                current->next = newBodyPart; 
                newBodyPart->x = current->x;
                newBodyPart->y = current->y;
            }
        }
                if(score%2==0&&score!=0&&specialFood==0){
                    specialFood=1;
                    foodx=randnum(foodnum);
                    HAL_Delay(1);
                    foody=randnum(foodnum);
                    foodnum+=1;
                    LCD_Fill(foodx-6,foody-6,foodx+6,foody+6,Red);
                }
                else{
                    specialFood=0;
                    foodx=randnum(foodnum);
                    HAL_Delay(1);
                    foody=randnum(foodnum);
                    foodnum+=1;
                    LCD_Fill(foodx-4,foody-4,foodx+4,foody+4,White);
                }
                }

这段代码使用变量specialFood来控制奖励模式中大食物的生成。根据分数来改变下一次生成的食物,并在食物判定中对不同的食物的判定距离进行的分别的判断。同时,食物不同,分数和身体的增长也不同。

障碍物模式的实现
for(int i =0;i<=numObstacles;i++){
                float j=(obstacles[i][1]-head.y)*(obstacles[i][1]-head.y)+(obstacles[i][0]-head.x)*(obstacles[i][0]-head.x);
                            if(j<80){
                lcd_clear(Blue);
                LCD_ShowString(20,100,(uint8_t*)"Game Over !!!",Red,Blue);
                LCD_ShowString(20,120,(uint8_t*)"Your final score is :",Red,Blue);
                LCD_ShowString(20,140,(uint8_t*)scoreStr,Red,Blue);
                LCD_ShowString(20,160,(uint8_t*)"Try again?",Red,Blue);
                    break; 
                            }
                }

HAL_Delay(5);
int ObstacleX=randnum(foodnum);
HAL_Delay(5);
int ObstacleY=randnum(foodnum);
LCD_DrawRectangle(ObstacleX-4,ObstacleY-4,ObstacleX+4,ObstacleY+4,White);
LCD_Fill(ObstacleX-3,ObstacleY-3,ObstacleX+3,ObstacleY+3,Red);
obstacles[numObstacles][0]=ObstacleX;
obstacles[numObstacles][1]=ObstacleY;
numObstacles++;

这里在每吃到一个食物后进行一个障碍物的生成,障碍物的坐标信息用二维数组保存,并在游戏结束判定中加入对是否碰到障碍物的判定,通过遍历障碍物数组实现。

迷宫模式的实现
int mazeMap[27][26] = {
    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
    {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
    {1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
    {1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
    {1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
    {1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
    {1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
    {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1},
    {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
    {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
    {1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1},
    {1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
    {1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
    {1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
    {1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
    {1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
    {1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1},
    {1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
    {1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1},
    {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
    {1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
        {1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
        {1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1},
    {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3},
    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
};

for(int m =0;m<27;m++){
        for(int n=0;n<26;n++){
        if(mazeMap[m][n]==1){
        LCD_DrawRectangle(9*n+6,9*m+21,9*n+14,9*m+29,White);
        LCD_Fill(9*n+7,9*m+22,9*n+13,9*m+28,Red);
        }
    }
}
if(num<92){
                Key=path[num];
                num++;
        LCD_ClearNode(head);  
        }
                else{
                lcd_clear(Blue);
                LCD_ShowString(20,100,(uint8_t*)"Game Win !!!",Red,Blue);
                LCD_ShowString(20,160,(uint8_t*)"You've found the shortest path!",Red,Blue);
                 break; 
                }  

上述代码使用一个二维数组来保存迷宫的结构,其中1为墙壁,0为可通行通道,3为出口。在主循环中实现迷宫的绘制,根据广度优先搜索找到一条最短路径并保存在path[]数组中,每次循环从数组中读取对应的按键,从而实现贪吃蛇的自动寻路,作为迷宫模式的一种最优解,并通过LCD屏幕呈现出来。

以上即本实验的核心代码与实现。

评论(0)

发布评论