ESP32: 编写贪吃蛇小游戏 一 (Arduino)

前言

因为接触到了 bpibit 的 LED 矩阵,所以萌生了写出一个贪吃蛇小游戏

记录一下编写程序的过程以及思考,毕竟还是一只菜鸟,所以在程序逻辑、代码优化上还是有些不足,希望有大佬能够指出,我会认真学习大佬给的建议

这篇文章是以上一篇文章为基础写的,所以有兴趣可以看我上一篇文章ESP32: BPI-BIT 开发板外设 按键与LED矩阵 学习(Arduino)

划分部分

我把贪吃蛇小游戏分为几个部分

  1. 蛇的移动与碰撞
  2. 苹果的生成

操作工具

测试使用的开发板: bpi-bit v1.2

应用到的外设: WS2812b

测试使用的软件:PlatformIO

蛇的移动与碰撞

将编号转换为坐标轴

因为在void SetPixelColor(uint16_t indexPixel, typename T_COLOR_FEATURE::ColorObject color)中,输入的是面板的整数,不利于后面蛇方向移动程序的编写,所以写了一个将(x,y)矩阵变为整数编号形式的函数

// 将(x,y)变为整数编号
int Count(int x, int y)
{
  return (x - 1) + (y - 1) * 5;
}

这样就把板子的 LED 面板变成了 XY 矩阵的形式,整个程序都是以 XY 矩阵为基础的

在这里插入图片描述

编写了一个 shake 类

class Snake
{
public:
    int snakeLen = 3; // 蛇的长度

    // 蛇身体的位置
    int *snakeLenX = new int[snakeLen];
    int *snakeLenY = new int[snakeLen];

    int hx = 3, hy = 3; // 头部位置
    int tx = 1, ty = 3; // 尾部位置

    bool IsSnakeCollision(int hx, int hy); // 判断蛇是否碰撞
    void updateSnakePosition(bool appleFlag,int tx,int ty);            // 更新蛇的位置
    bool Key_Scan();                       // 扫描按键
    void DirectionAndCount();              // 蛇运动方向判断
    

    // 开局一条蛇的位置
    Snake()
    {
        snakeLenX[0] = 1;
        snakeLenX[1] = 2;
        snakeLenX[2] = 3;
        snakeLenY[0] = 3;
        snakeLenY[1] = 3;
        snakeLenY[2] = 3;
    };

    String keyDirection; // 按键控制的运动方向  分别有:"upper" "lower" "right" "left"
    String hdirection;   // 头部朝向 分别有:"_A" "_B"
};

碰撞检测

bool Snake::IsSnakeCollision(int hx, int hy)
{
    // 墙壁碰撞
    if (hx > 5 || hx < 1 || hy > 5 || hy < 1)
        return true;

    // 自身碰撞
    for (int t = 0; t < snakeLen-1; t++)
    {
        if (hx == snakeLenX[t])
            for (int i = 0; i < snakeLen-1; i++)
            {
                if (hy == snakeLenY[t])
                    return true;
            }
    }
    return false;
}

检测蛇头是否超出了 XY 矩阵的范围,若超出则说明碰撞到墙壁。检测蛇头是否与身体重叠,若重叠则说明自身碰撞。

更新蛇位置

void Snake::updateSnakePosition(bool appleFlag,int tx,int ty)
{
    if (!appleFlag)
    {
        for (int t = 0; t < snakeLen - 1; t++)
        {
            snakeLenX[t] = snakeLenX[t + 1];
            snakeLenY[t] = snakeLenY[t + 1];
        }
        snakeLenX[snakeLen - 1] = hx;
        snakeLenY[snakeLen - 1] = hy;
    }
    else
    {
        for (int t = (snakeLen - 1); t >0; t--)
        {
            snakeLenX[t] = snakeLenX[t - 1];
            snakeLenY[t] = snakeLenY[t - 1];
        }
        snakeLenX[0]=tx;
        snakeLenY[0]=ty;
    }
}

当 appleFlag 为 true 时,说明蛇吃到了苹果,此时把原本消失尾部加到蛇的数组里面去。当 appleFlag 为 false 时,说明蛇在移动,此时把数组的标号减少一位,然后把蛇头的位置放在了最高位

扫描按键

bool Shake::Key_Scan()
{
    static u_char key_up = 1;
    keyDirection = "_S";
    if (key_up && (digitalRead(BUTTON_A) == LOW || digitalRead(BUTTON_B) == LOW))
    {
        delayMicroseconds(10);
        key_up = 0;
        if (digitalRead(BUTTON_A) == LOW)
        {
            keyDirection = "_A";
        }
        else if (digitalRead(BUTTON_B) == LOW)
        {
            keyDirection = "_B";
        }
        return true;
    }
    else if (digitalRead(BUTTON_A) == HIGH && digitalRead(BUTTON_B) == HIGH)
    {
        key_up = 1;
        return false;
    }else
    {
       return false;
    }
    
}

判断是否有按 A/B 键。这里不支持连按

蛇运动方向判断

void Shake::DirectionAndCount()
{

    if (hdirection == "right")
    {
        if (keyDirection == "_A")
        {
            hy += 1;
            hdirection = "lower";
        }
        else if (keyDirection == "_B")
        {
            hy -= 1;
            hdirection = "upper";
        }
    }
    else if (hdirection == "left")
    {
        if (keyDirection == "_A")
        {
            hy -= 1;
            hdirection = "upper";
        }
        else if (keyDirection == "_B")
        {
            hy += 1;
            hdirection = "lower";
        }
    }
    else if (hdirection == "upper")
    {
        if (keyDirection == "_A")
        {
            hx += 1;
            hdirection = "right";
        }
        else if (keyDirection == "_B")
        {
            hx -= 1;
            hdirection = "left";
        }
    }
    else if (hdirection == "lower")
    {
        if (keyDirection == "_A")
        {
            hx -= 1;
            hdirection = "left";
        }
        else if (keyDirection == "_B")
        {
            hx += 1;
            hdirection = "right";
        }
    }
}

简单的运用 if…else… 语句来进行判断。通过当前的蛇头运动方向以及按键控制的方向,来判断下一步蛇头的运动方向

定时前进

利用 beforeTime = clock() 获取当前时间,再利用 clock()-before 来获取时间间隔;当满足时间间隔条件时,执行下面函数

// 自动前进
void AutoWalk()
{
  if (shake.hdirection == "upper")
  {
    shake.hy -= 1;
  }
  else if (shake.hdirection == "lower")
  {
    shake.hy += 1;
  }
  else if (shake.hdirection == "left")
  {
    shake.hx -= 1;
  }
  else if (shake.hdirection == "right")
  {
    shake.hx += 1;
  }
  strip.SetPixelColor(Count(shake.shakeLenX[0], shake.shakeLenY[0]), black);
  shake.updateShakePosition();
}

每隔一段时间,判读蛇头的运动方向,变化蛇头的位置。为了实现移动效果,使蛇尾的位置消失,其实就是将蛇尾的位置变黑。然后再更新蛇的位置,把新的蛇尾放在了数组的最后。

显示蛇

void DisplaySnake()
{
  for (int i = 0; i < snake.snakeLen; i++)
  {
    strip.SetPixelColor(Count(snake.snakeLenX[i], snake.snakeLenY[i]), green);
  }
  strip.SetPixelColor(Count(snake.snakeLenX[snake.snakeLen - 1], snake.snakeLenY[snake.snakeLen - 1]), purple);
  strip.Show();
}   

利用循环在数组里提取蛇的位置,并显示出来

参考资料

指针和动态分配内存 (不定长度数组)------新标准c++程序设计