基于M5StickC Plus实现俄罗斯方块游戏
基于M5StickC Plus使用Arduino API实现俄罗斯方块游戏
标签
嵌入式系统
MPU
显示
ESP32
M5Stick
2022暑假在家练
tony
更新2022-09-02
南京信息工程大学
686

1.项目需求

  • 设计或移植一款经典的游戏
  • 可以通过LCD屏显示
  • 可以通过按键和游戏机姿态控制游戏的动作

2.完成的功能

(1)完成设计(移植)经典游戏《俄罗斯方块》

  • 设计平台:Arduino
  • 编写语言:C
  • 编写环境:Win10
  • 硬件平台:M5StickCPlus

FjwhCdBrPmWffTQS475dCqmRkgYj

(2)完成通过按键和姿态控制《俄罗斯方块》游戏的动作

  • 使用函数:M5.BtnB.wasReleased( ); getAhrsData( );
  • 硬件平台:Key按键、MPU6886

3.实现思路

  • 编写(移植)《俄罗斯方块》游戏的运行逻辑;
  • 依托M5提供的LCD库编写LCD屏幕显示界面;

4.实现过程

(1)程序框图

FnEDazn9K1HM4kqJxlc3jIfZh20T

(2)俄罗斯方块逻辑编写

   (a)方块生成

这里运用了bool来判断是否要生成新的方块组合。代码如下:

bool GetSquares(Block block, Point pos, int rot, Point* squares) {
  bool overlap = false;
  for (int i = 0; i < 4; ++i) {
    Point p;
    p.X = pos.X + block.square[rot][i].X;
    p.Y = pos.Y + block.square[rot][i].Y;
    overlap |= p.X < 0 || p.X >= Width || p.Y < 0 || p.Y >= 
      Height || screen[p.X][p.Y] != 0;
    squares[i] = p;
  }
  return !overlap;
}

   (b)方块类型信息存储

构造方块结构体,存储方块的结构、颜色等信息,把方块用矩阵来表示,再利用数组来将信息存储。代码如下:

struct Block {Point square[4][4]; int numRotate, color;};
Block blocks[7] = {
  {{{{-1,0},{0,0},{1,0},{2,0}},{{0,-1},{0,0},{0,1},{0,2}},
  {{0,0},{0,0},{0,0},{0,0}},{{0,0},{0,0},{0,0},{0,0}}},2,1},
  {{{{0,-1},{1,-1},{0,0},{1,0}},{{0,0},{0,0},{0,0},{0,0}},
  {{0,0},{0,0},{0,0},{0,0}},{{0,0},{0,0},{0,0},{0,0}}},1,2},
  {{{{-1,-1},{-1,0},{0,0},{1,0}},{{-1,1},{0,1},{0,0},{0,-1}},
  {{-1,0},{0,0},{1,0},{1,1}},{{1,-1},{0,-1},{0,0},{0,1}}},4,3},
  {{{{-1,0},{0,0},{0,1},{1,1}},{{0,-1},{0,0},{-1,0},{-1,1}},
  {{0,0},{0,0},{0,0},{0,0}},{{0,0},{0,0},{0,0},{0,0}}},2,4},
  {{{{-1,0},{0,0},{1,0},{1,-1}},{{-1,-1},{0,-1},{0,0},{0,1}},
  {{-1,1},{-1,0},{0,0},{1,0}},{{0,-1},{0,0},{0,1},{1,1}}},4,5},
  {{{{-1,1},{0,1},{0,0},{1,0}},{{0,-1},{0,0},{1,0},{1,1}},
  {{0,0},{0,0},{0,0},{0,0}},{{0,0},{0,0},{0,0},{0,0}}},2,6},
  {{{{-1,0},{0,0},{1,0},{0,-1}},{{0,-1},{0,0},{0,1},{-1,0}},
  {{-1,0},{0,0},{1,0},{0,1}},{{0,-1},{0,0},{0,1},{1,0}}},4,7}
};

(c)按键和姿态读取

   调用M5官方提供的按键读取函数和IMU的姿态读取函数,对按键的读取和MPU6886姿态的判断单独形成一个函数,后续使用方便。

bool KeyPadLoop(){
  M5.IMU.getAhrsData(&pitch, &roll, &yaw);
  if(pitch<-5)
  {
    if(pom==0)
  {
    pom=1;
    ClearKeys();
    but_LEFT =true;
    return true;
    }
    }
    else 
    {
      pom=0;
      }
    
  if(pitch>15)
  {
    if(pom2==0)
      {
        pom2=1;
        ClearKeys();
        but_RIGHT=true;
        return true;
        }
  }else 
    {
      pom2=0;
    }
if(M5.BtnB.wasReleased()){
    if(pom3==0)
  {pom3=1;ClearKeys();but_B =true;return true;}
  }else {pom3=0;}
  
  if(M5.BtnA.wasReleased()){
    if(pom4==0)
  {pom4=1;ClearKeys();but_A =true;return true;}
  }else {pom4=0;}
  
  return false;
}

(d)方块移动和旋转

GetNextPosRot()函数结合玩家在硬件操作反馈的结果,来判断方块组合该如何移动。代码如下:

void GetNextPosRot(Point* pnext_pos, int* pnext_rot) {
  bool received = KeyPadLoop();

  if (but_LEFT) started = true;
  if (!started) return;
  pnext_pos->X = pos.X;
  pnext_pos->Y = pos.Y;
  if ((fall_cnt = (fall_cnt + 1) % 10) == 0) pnext_pos->Y += 1;
  else if (1) 
  {
    if (but_LEFT) 
      { but_LEFT = false; pnext_pos->X -= 1;}
    else if (but_RIGHT) 
      { but_RIGHT = false; pnext_pos->X += 1;}
    else if (but_A) 
      { but_A = false;
      *pnext_rot = (*pnext_rot + block.numRotate - 1)%block.numRotate; 
    }
    else if(but_B)
    {
      but_B = false;
      *pnext_rot = (*pnext_rot + 1)%block.numRotate;
      }
  }
}

(e)存储和更新积累的方块

利用for循环,不断扫描screen数组内数值,以判断下落的方块是否与底部接触,接触则更新数组并生成新的方块下落。当无法生成新的方块时,游戏结束。代码如下:

void ReviseScreen(Point next_pos, int next_rot) {
  if (!started) return;
  Point next_squares[4];
  for (int i = 0; i < 4; ++i) 
    screen[pos.X + block.square[rot][i].X][pos.Y + block.square[rot][i].Y] = 0;
  if (GetSquares(block, next_pos, next_rot, next_squares)) {
   for (int i = 0; i < 4; ++i){
     screen[next_squares[i].X][next_squares[i].Y] = block.color;
   }
   pos = next_pos; 
   rot = next_rot;
  }
  else {
   for (int i = 0; i < 4; ++i) screen[pos.X + 
    block.square[rot][i].X][pos.Y + block.square[rot][i].Y] = block.color;
   if (next_pos.Y == pos.Y + 1) {
    DeleteLine(); 
    PutStartPos();
    if (!GetSquares(block, pos, rot, next_squares)) {
     for (int i = 0; i < 4; ++i) 
      screen[pos.X + block.square[rot][i].X][pos.Y + block.square[rot][i].Y] = block.color;
      GameOver();
    }
   }
  }
  Draw();
}

(f)自动清除满行方块

从底部开始判断是否有空的一行方块,如果没有空白的就让这一行方块存储上一行方块的信息,以此类推,这样就可以达到消行的效果了。代码如下:

void DeleteLine() {
  for (int j = 0; j < Height; ++j) {
    bool Delete = true;
    for (int i = 0; i < Width; ++i) if (screen[i][j] == 0) Delete = false;
    if (Delete)
    {
       score++;
       if(score%5==0)
        {
        lvl++;
        game_speed=game_speed-2;
        M5.Lcd.drawString("LEVL:"+String(lvl),88,8,1);
        }
       M5.Lcd.drawString("SCORE:"+String(score),14,8,1);
          for (int k = j; k >= 1; --k) 
              {
      
    
    for (int i = 0; i < Width; ++i)
    {
      screen[i][k] = screen[i][k - 1];
    }
  }
}}}

(g)LCD屏幕显示

每个方块的数组控制一定大小的像素,调用M5中LCD 的库函数对屏幕进行刷新:

void Draw() {                               // Draw 120x240 in the center
  for (int i = 0; i < Width; ++i) for (int j = 0; j < Height; ++j)
   for (int k = 0; k < Length; ++k) for (int l = 0; l < Length; ++l)
    backBuffer[j * Length + l][i * Length + k] = BlockImage[screen[i][j]][k][l];
    M5.Lcd.drawBitmap(12, 20, 110, 220, (uint8_t*)backBuffer);
}

5.遇到的难题及未来计划

   在实现俄罗斯方块的主体功能方面较为顺利,但是在调用LCD的函数显示特定图片方面出现了些许疑问,将图片转换为数组后,并不能顺利的显示成图片,而是许多白点,目前推测是屏幕的刷新方向设置有差错,希望未来能解决这一问题。

   M5StickCPlus的外设很多,目前仅使用到了LCD、姿态传感器、按键这些,还有许多功能可以开发,希望以后可以充分利用。

附件下载
CplusTetris.ino
团队介绍
2020级学生,对嵌入式感兴趣,但是有点懒q(≧▽≦q)
团队成员
tony
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号