利用RT-Thread星火一号开发板制作贪吃蛇小游戏

刚拿到星火一号板子想搞事情,逛论坛发现 @zym_0208 发布了一个贪吃蛇的demo,于是下载下来玩了一下发现有些许bug,于是我在他的基础上修改了一半,且把逻辑优化了一下

工程中使用到的devices

上下左右四个按钮,以及LCD屏幕

获取pin

#define PIN_KEY0 GET_PIN(C, 0)
#define PIN_KEY1 GET_PIN(C, 1)
#define PIN_KEY2 GET_PIN(C, 4)
#define PIN_KEY3 GET_PIN(C, 5)
#define PIN_LED_R GET_PIN(F, 12)

在main函数中设置pin模式,绑定触发模式,事件,使能中断

rt_pin_mode(PIN_KEY0, PIN_MODE_INPUT_PULLUP);
rt_pin_mode(PIN_KEY1, PIN_MODE_INPUT_PULLUP);
rt_pin_mode(PIN_KEY2, PIN_MODE_INPUT_PULLUP);
rt_pin_mode(PIN_KEY3, PIN_MODE_INPUT_PULLUP);
rt_pin_attach_irq(PIN_KEY0, PIN_IRQ_MODE_FALLING, keyDown, (void*)3);
rt_pin_attach_irq(PIN_KEY1, PIN_IRQ_MODE_FALLING, keyDown, (void*)2);
rt_pin_attach_irq(PIN_KEY2, PIN_IRQ_MODE_FALLING, keyDown, (void*)4);
rt_pin_attach_irq(PIN_KEY3, PIN_IRQ_MODE_FALLING, keyDown, (void*)1);
rt_pin_irq_enable(PIN_KEY0, PIN_IRQ_ENABLE);
rt_pin_irq_enable(PIN_KEY1, PIN_IRQ_ENABLE);
rt_pin_irq_enable(PIN_KEY2, PIN_IRQ_ENABLE);
rt_pin_irq_enable(PIN_KEY3, PIN_IRQ_ENABLE);

贪吃蛇的游戏设置

  • 图案属性,虽然lcd屏幕的显示上限很高,但是本工程只用简单的字符打印代替图案,所有字符都是16*16的正方形,所以下面所有东西的位置(x,y)都是16的倍数,如果想要更高端的显示,可以去了解一下lvgl库

  • 蛇的属性:

    struct {
        int speed;
        int len;
        int x[SNAKESIZE];
        int y[SNAKESIZE];
    }snake;
    
  • 食物属性:

    struct {
        int x;
        int y;
    }food;
    

游戏逻辑

  • 蛇碰到墙会GAMEOVER
  • 蛇头碰到蛇身会GAMEOVER
  • 蛇头碰到食物会变长一个单位
  • 没有操作时蛇会延记录的方向移动一个单位,我设置为300ms的delay
  • GAMEOVER后会显示分数

具体实现

  • 每一个循环,不管有没有吃东西,直接增加蛇长度,也就是在移动方向上头前面加一个头(抽象说法),再把蛇身体数组集体往前移一格,把原来的尾巴设为“ ”,然后再进行判断有没有吃到东西。

    lcd_show_string(snake.x[0], snake.y[0],16,"@");
        lcd_show_string(snake.x[snake.len - 1], snake.y[snake.len - 1],16," ");
        int tailx = snake.x[snake.len-1];
        int taily = snake.y[snake.len-1];
        for(int i = snake.len - 1; i > 0; i--){
           snake.x[i] = snake.x[i-1];
           snake.y[i] = snake.y[i-1];
       }
    
  • 吃到东西了,那就把尾巴再生成出来,同时搞个循环生成食物,这里的逻辑就是食物不能生在边框,也不能生在蛇已有的身体上,就这么一直生下去直到生成合法食物。

    //新生成一个合法食物
            while (1)
            {
                int sameRegion = 0;
                food.x = rand() % (MAPWIDTH/16)*16+16 ;
                food.y = rand() % (MAPHEIGHT/16)*16 + 16;
                //生成的食物横坐标的奇偶必须和初试时蛇头所在坐标的奇偶一致,因为一个字符占两个字节位置,不一致
                //会导致吃食物的时候只吃到一半
                //检查是否食物生成到边上
                if(food.x % 2 == 0 && food.x>16 && food.x<MAPWIDTH-16 && food.y>16 &&food.y<MAPHEIGHT-16)
                {
                    //检查是否食物与蛇身体重合
                    for (int i = 0; i < snake.len; i++)
                    {
                        if (snake.x[i] == food.x && snake.y[i] == food.y)
                        {
                            sameRegion = 1;
                            break;
                        }
                    }
                }
                else {
                    sameRegion = 1;
                }
                if(!sameRegion){
                    break;
                }
            }
    
  • 没吃到东西就无事发生,因为我们已经在一开始把尾巴设空过了

完整代码

/*
 * Copyright (c) 2006-2021, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2023-5-10      ShiHao       first version
 */
#include <rtthread.h>
#include <rtdevice.h>
#include <board.h>
#include <drv_lcd.h>
#include <rttlogo.h>
#include <stdio.h>
#include <stdlib.h>
#include <drv_lcd.h>
#include <time.h>
#define PIN_KEY0 GET_PIN(C, 0)
#define PIN_KEY1 GET_PIN(C, 1)
#define PIN_KEY2 GET_PIN(C, 4)
#define PIN_KEY3 GET_PIN(C, 5)
#define PIN_LED_R GET_PIN(F, 12)
#define DBG_TAG "main"
#define DBG_LVL         DBG_LOG
#include <rtdbg.h>
#define SNAKESIZE 100//蛇的身体最大节数
#define MAPWIDTH 240 //宽度
#define MAPHEIGHT 240//高度

volatile int key = 3;
int score = 0;

struct {
    int x;
    int y;
}food;

struct {
    int speed;
    int len;
    int x[SNAKESIZE];
    int y[SNAKESIZE];
}snake;
void drawMap()
{
    //打印上下边框
    for (int i = 0; i <= MAPWIDTH/16+4; i++)
    {
        //将光标移动依次到(i,0)处打印上边框
        lcd_show_string(i*12,0,16,"#");
        //将光标移动依次到(i,MAPHEIGHT)处打印下边框
        lcd_show_string(i*12,MAPHEIGHT-16,16,"#");
    }
    //打印左右边框
    for (int i = 1; i < MAPHEIGHT/16; i++)
    {
        //将光标移动依次到(0,i)处打印左边框
        lcd_show_string(0,i*16,16,"#");
        //将光标移动依次到(MAPWIDTH, i)处打印左边框
        lcd_show_string(MAPHEIGHT-16,i*16,16,"#");
    }
    //随机生成初始食物
    while (1)
    {
        srand((unsigned int)time(NULL));
        food.x = rand() % (MAPWIDTH/16)*16+16 ;
        food.y = rand() % (MAPHEIGHT/16)*16+16;
        if (food.x % 2 == 0){
            if(food.x>16 && food.x<MAPWIDTH-16 && food.y>16 &&food.y<MAPHEIGHT-16)break;
        }
    }
    lcd_show_string(food.x, food.y,16,"*");
    //初始化蛇的属性
    snake.len = 3;
    snake.speed = 16;
    //在屏幕中间生成蛇头
    snake.x[0] = 160;//x坐标为偶数
    snake.y[0] = 160;
    //打印蛇头
    lcd_show_string(snake.x[0], snake.y[0],16,"@");
    //生成初始的蛇身
    for (int i = 1; i < snake.len; i++)
    {
        //蛇身的打印,纵坐标不变,横坐标为上一节蛇身的坐标值+16
        snake.x[i] = (snake.x[i - 1] + 16);
        snake.y[i] = snake.y[i - 1];
        lcd_show_string(snake.x[i], snake.y[i],16,"#");
    }
    return;
}


void handleFood()
{
    srand(time(RT_NULL));
    lcd_show_string(snake.x[0], snake.y[0],16,"@");
    lcd_show_string(snake.x[snake.len - 1], snake.y[snake.len - 1],16," ");
    int tailx = snake.x[snake.len-1];
    int taily = snake.y[snake.len-1];
    for(int i = snake.len - 1; i > 0; i--){
       snake.x[i] = snake.x[i-1];
       snake.y[i] = snake.y[i-1];
   }
    if (snake.x[0] == food.x && snake.y[0] == food.y)//蛇头碰到食物
    {
        snake.x[snake.len] = tailx;
        snake.y[snake.len] = taily;
        lcd_show_string(tailx, taily,16,"#");
        snake.len++;//吃到食物,蛇身长度加1
        score += 10;//每个食物得10分
        //新生成一个合法食物
        while (1)
        {
            int sameRegion = 0;
            food.x = rand() % (MAPWIDTH/16)*16+16 ;
            food.y = rand() % (MAPHEIGHT/16)*16 + 16;
            //生成的食物横坐标的奇偶必须和初试时蛇头所在坐标的奇偶一致,因为一个字符占两个字节位置,不一致
            //会导致吃食物的时候只吃到一半
            //检查是否食物生成到边上
            if(food.x % 2 == 0 && food.x>16 && food.x<MAPWIDTH-16 && food.y>16 &&food.y<MAPHEIGHT-16)
            {
                //检查是否食物与蛇身体重合
                for (int i = 0; i < snake.len; i++)
                {
                    if (snake.x[i] == food.x && snake.y[i] == food.y)
                    {
                        sameRegion = 1;
                        break;
                    }
                }
            }
            else {
                sameRegion = 1;
            }
            if(!sameRegion){
                break;
            }
        }
        lcd_show_string(food.x, food.y,16,"*");
    }

    rt_kprintf("new cycle!");
    int pre_key = key ;//记录前一个按键的方向
   //判断蛇头应该往哪个方向移动
   switch (pre_key)
   {
   case 3:
       snake.x[0] -= snake.speed;//往左
       break;
   case 4:
       snake.x[0] += snake.speed;//往右
       break;
   case 1:
       snake.y[0]=snake.y[0]-snake.speed;//往上
       break;
   case 2:
       snake.y[0]=snake.y[0]+snake.speed;//往下
       break;
   }


 return;
}

void whereHead(){
    rt_kprintf("x:%d ;",snake.x[0]);
    rt_kprintf("y:%d\n",snake.y[0]);
}

rt_bool_t snakeStatus()
{
    whereHead();
    //蛇头碰到上下边界,游戏结束
    if (snake.y[0] <= 0|| snake.y[0] >= MAPHEIGHT-16)
        return RT_ERROR;
    //蛇头碰到左右边界,游戏结束
    if (snake.x[0] <= 0 || snake.x[0] >= MAPWIDTH-16)
        return RT_ERROR;
    //蛇头碰到蛇身,游戏结束
    for (int i = 1; i < snake.len; i++)
    {
        if (snake.x[i] == snake.x[0] && snake.y[i] == snake.y[0])
            return RT_ERROR;
    }
    return RT_EOK;
}
void keyDown(void *args)
{
    rt_kprintf("key %d \r\n",(int)args);
    key = (int)args;
   }
int main(void)
{
    drawMap();
    rt_pin_mode(PIN_KEY0, PIN_MODE_INPUT_PULLUP);
    rt_pin_mode(PIN_KEY1, PIN_MODE_INPUT_PULLUP);
    rt_pin_mode(PIN_KEY2, PIN_MODE_INPUT_PULLUP);
    rt_pin_mode(PIN_KEY3, PIN_MODE_INPUT_PULLUP);
    rt_pin_attach_irq(PIN_KEY0, PIN_IRQ_MODE_FALLING, keyDown, (void*)3);
    rt_pin_attach_irq(PIN_KEY1, PIN_IRQ_MODE_FALLING, keyDown, (void*)2);
    rt_pin_attach_irq(PIN_KEY2, PIN_IRQ_MODE_FALLING, keyDown, (void*)4);
    rt_pin_attach_irq(PIN_KEY3, PIN_IRQ_MODE_FALLING, keyDown, (void*)1);
    rt_pin_irq_enable(PIN_KEY0, PIN_IRQ_ENABLE);
    rt_pin_irq_enable(PIN_KEY1, PIN_IRQ_ENABLE);
    rt_pin_irq_enable(PIN_KEY2, PIN_IRQ_ENABLE);
    rt_pin_irq_enable(PIN_KEY3, PIN_IRQ_ENABLE);
    while (1)
    {
        if (snakeStatus())
            break;
        handleFood();
        rt_thread_mdelay(300);
    }
    lcd_show_string(MAPWIDTH / 2-32, MAPHEIGHT / 2,16,"Game Over!");
    lcd_show_string(MAPWIDTH / 2-32, MAPHEIGHT / 2+16,16,"Score:");
    lcd_show_num(MAPWIDTH / 2+16, MAPHEIGHT / 2+16,score,2, 16);
    rt_thread_mdelay(5000);
    return 0;
}

写在最后

我也是嵌入式小白,欢迎私信交流

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值