实验目的
本实验的主要目的是通过使用STM32F407开发板和Keil软件,设计和实现一个嵌入式贪吃蛇游戏。通过这个实验从而掌握以下知识:
- 熟悉嵌入式系统开发环境。
- 学会使用STM32F407开发板的各种功能和外设。
- 理解嵌入式游戏的基本原理,包括游戏循环、图形渲染和用户输入处理。
- 实现一个完整的贪吃蛇游戏,以展示嵌入式系统的实际应用。
实验环境
- STM32F407开发板。
- Keil MDK开发环境。
- 液晶显示屏。
- 贪吃蛇游戏相关的图形和资源文件。
实验要求
- 在STM32F407开发板上实现一个完整贪吃蛇游戏。
- 在液晶显示屏上显示贪吃蛇游戏界面。
- 接受开发板上的按键输入作为游戏控制的输入。
- 实验报告应包括详细的代码说明和设计思路。
实验原理
- 游戏循环:贪吃蛇游戏通过一个主循环来运行。在每个循环迭代中,游戏会更新游戏状态、处理输入、渲染图形等。
- 图形渲染:使用LCD输出设备,通过控制像素点的颜色来绘制游戏界面。在本实验中,我们调用对应的库函数来构成图形渲染代码。
- 用户输入处理:开发板上的按键用于控制游戏,可以根据用户输入改变蛇的方向。
实验步骤
游戏页面设计
游戏初始化欢迎页面
贪吃蛇初始化欢迎页面设计: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)