TETRIS

Код:
#include 
#include 
#include 
#include "GyverButton.h"

#define OLED_DC      26
#define OLED_CS      31
#define OLED_RESET   24
#define BLOCK_WIDTH  4
#define FRAME_WIDTH  12
#define FRAME_HEIGHT 23
#define BTN1         2
#define BTN2         12
#define BTN3         13
#define BuzzerPin    15
#define NORMAL_SPEED 300
#define FAST_SPEED   20

Adafruit_SSD1306 display(OLED_DC, OLED_RESET, OLED_CS);
GButton btn1(BTN1, LOW_PULL, NORM_OPEN);
GButton btn2(BTN2, LOW_PULL, NORM_OPEN);
GButton btn3(BTN3, LOW_PULL, NORM_OPEN);

struct point_t
{
  byte x;
  byte y;
};

struct figure_t
{
  point_t points[4];
  point_t pos = {FRAME_WIDTH / 2 - 1, FRAME_HEIGHT - 3};
  int rotation = 0;
};

int gameSpeed = 100;
const long buttonsPressSteep = 100;

unsigned long previousMillis = 0;
unsigned long buttonsPreviousMillis = 0;
bool gameStatus = true;
byte score = 0;
bool playingField[FRAME_WIDTH][FRAME_HEIGHT];
figure_t j_figure;
figure_t i_figure;
figure_t o_figure;
figure_t z_figure;
figure_t figures[4];
figure_t nextFigure;
figure_t curFigure;


void setup()
{
  randomSeed(analogRead(A5));
  InitFugures();
  display.begin(SSD1306_SWITCHCAPVCC);
  display.setRotation(1);
  display.display();
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(WHITE);
  StartGame();
}

void loop() {
  display.clearDisplay();
  display.drawRect(0, 10, 63, 128 - 10, WHITE);
  TimerUpdate();
  UpdateEveryFrame();
  display.display();
}

void UpdateEveryFrame()
{
  display.setCursor(0, 0);
  display.println(score);
  DrawPlayingField();
  DrawNextFigure();
  DrawFigure(curFigure);
  btn1.tick();
  btn2.tick();
  btn3.tick();
  if (!gameStatus)
  {
    display.setCursor(5, 12);
    display.println("Game Over");
  }
}

void Update()
{
  curFigure.pos.y--;
  Colision(curFigure);
}
void Colision(figure_t figure)
{
  for (byte i = 0 ; i < 4; i++)
  {
    byte xPos = figure.pos.x + figure.points[i].x;
    byte yPos = figure.pos.y + figure.points[i].y;

    //Ground check
    if (yPos == 0)
    {
      AddFigureToField(figure);
      CheckFilledRow();
      GenerateNextFigure();
      return;
    }
    if (playingField[xPos][yPos - 1])
    {
      if (FindMaxY(figure) == FRAME_HEIGHT - 3)
      {
        GameOver();
        return;
      }
      AddFigureToField(figure);
      CheckFilledRow();
      GenerateNextFigure();
      return;
    }
  }
}

void Rotate(figure_t& figure)
{
  figure.rotation += 90;
  if (figure.rotation >= 360)
    figure.rotation -= 360;

  for (byte i = 0 ; i < 4; i++)
  {
    byte tempX = figure.points[i].x;
    figure.points[i].x = figure.points[i].y;
    figure.points[i].y = tempX;
  }
}


void Buttons()
{
  if (btn3.state())
  {
    if (!gameStatus)
    {
      StartGame();
    }
    else
    {
      if (FindMinX(curFigure) != 0)
      {
        bool canMove = true;
        for (byte i = 0 ; i < 4; i++)
        {
          byte xPos = curFigure.pos.x + curFigure.points[i].x;
          byte yPos = curFigure.pos.y + curFigure.points[i].y;
          if (playingField[xPos - 1][yPos])
          {
            canMove = false;
            break;
          }
        }
        if (canMove)
        {
          curFigure.pos.x--;
          Colision(curFigure);
        }
      }
    }
  }
  if (btn1.state())
  {
    if (!gameStatus)
    {
      StartGame();
    }
    else
    {
      if (FindMaxX(curFigure) != FRAME_WIDTH - 1)
      {
        bool canMove = true;
        for (byte i = 0 ; i < 4; i++)
        {
          byte xPos = curFigure.pos.x + curFigure.points[i].x;
          byte yPos = curFigure.pos.y + curFigure.points[i].y;
          if (playingField[xPos + 1][yPos])
          {
            canMove = false;
            break;
          }
        }
        if (canMove)
        {
          curFigure.pos.x++;
          Colision(curFigure);
        }
      }
    }
  }


  if (btn2.isHold())
  {
    gameSpeed = FAST_SPEED;
    tone(BuzzerPin, 2000);
  }
  if (btn2.isRelease())
  {
    gameSpeed = NORMAL_SPEED;
    tone(BuzzerPin, 5000, 400);
  }
  if (btn2.isClick())
  {
    if (!gameStatus)
    {
      StartGame();
    }
    else
      Rotate(curFigure);
  }
}

void AddScore()
{
  score++;
  tone(BuzzerPin, 3000, 300);
  tone(BuzzerPin, 6000, 200);
  tone(BuzzerPin, 2000, 300);
}
void StartGame()
{
  for (byte y = 0 ; y < 23; y++)
    for (byte x = 0 ; x < 12; x++)
      playingField[x][y] = false;
  score = 0;
  gameStatus = true;
  nextFigure = figures[random(4)];
  curFigure = figures[random(4)];
  //nextFigure = o_figure;
  //curFigure = o_figure;
}
void GameOver()
{
  gameStatus = false;
}
void GenerateNextFigure()
{
  curFigure = nextFigure;
  nextFigure = figures[random(4)];
  //nextFigure = o_figure;
}

void AddFigureToField(figure_t figure)
{
  for (byte i = 0; i < 4; i++)
  {
    playingField[figure.pos.x + figure.points[i].x][ figure.pos.y + figure.points[i].y] = true;
  }
}

void CheckFilledRow()
{
  for (byte k = 0; k < 4; k++)
  {
    for (byte y = 0; y < FRAME_HEIGHT - 1; y++)
    {
      bool filledRow = true;
      for (byte x = 0; x < FRAME_WIDTH; x++)
      {
        if (!playingField[x][y])
        {
          filledRow = false;
          break;
        }
      }
      if (filledRow)
      {
        AddScore();
        for (byte i = y; i < FRAME_HEIGHT - 1; i++)
        {
          for (byte j = 0; j < FRAME_WIDTH; j++)
          {
            playingField[j][i] = playingField[j][i + 1];
          }
        }
      }
    }
  }
}

void DrawBlock(byte x, byte y, byte color = WHITE)
{
  display.fillRect(2 + (x * (BLOCK_WIDTH + 1)), 128 - 1 - 10 - ((y - 1) * (BLOCK_WIDTH + 1)), BLOCK_WIDTH, BLOCK_WIDTH, color);
}

void DrawFigure(figure_t figure, byte x, byte y)
{
  for (byte i = 0 ; i < 4; i++)
    DrawBlock(x + figure.points[i].x, y + figure.points[i].y);
}
void DrawFigure(figure_t figure)
{
  DrawFigure(figure, figure.pos.x, figure.pos.y);
}
void DrawNextFigure()
{
  for (byte i = 0 ; i < 4; i++)
    display.fillRect(44 + (nextFigure.points[i].x * (BLOCK_WIDTH + 1)), (nextFigure.points[i].y * (BLOCK_WIDTH + 1)), BLOCK_WIDTH, BLOCK_WIDTH, WHITE);
}
void DrawPlayingField()
{
  for (byte y = 0 ; y < 23; y++)
    for (byte x = 0 ; x < 12; x++)
    {
      DrawBlock(x, y);
      if (playingField[x][y])
        DrawBlock(x, y, WHITE);
      else
        DrawBlock(x, y, BLACK);
    }
}

void TimerUpdate()
{
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= gameSpeed)
  {
    previousMillis = currentMillis;
    if (gameStatus)
      Update();
  }
  if (currentMillis - buttonsPreviousMillis >= buttonsPressSteep)
  {
    buttonsPreviousMillis = currentMillis;
    Buttons();
  }
}

byte FindMinX(figure_t figure)
{
  byte xPos = figure.pos.x + figure.points[0].x;
  for (byte i = 1 ; i < 4; i++)
    if (xPos > figure.pos.x + figure.points[i].x)
      xPos = figure.pos.x + figure.points[i].x;
  return xPos;
}

byte FindMaxX(figure_t figure)
{
  byte xPos = figure.pos.x + figure.points[0].x;
  for (byte i = 1 ; i < 4; i++)
    if (xPos < figure.pos.x + figure.points[i].x)
      xPos = figure.pos.x + figure.points[i].x;
  return xPos;
}

byte FindMinY(figure_t figure)
{
  byte yPos = figure.pos.y + figure.points[0].y;
  for (byte i = 1 ; i < 4; i++)
    if (yPos > figure.pos.y + figure.points[i].y)
      yPos = figure.pos.y + figure.points[i].y;
  return yPos;
}

byte FindMaxY(figure_t figure)
{
  byte yPos = figure.pos.y + figure.points[0].y;
  for (byte i = 1 ; i < 4; i++)
    if (yPos < figure.pos.y + figure.points[i].y)
      yPos = figure.pos.y + figure.points[i].y;
  return yPos;
}

void InitFugures()
{
  j_figure.points[0] = {0, 0};
  j_figure.points[1] = {0, 1};
  j_figure.points[2] = {1, 1};
  j_figure.points[3] = {2, 1};

  i_figure.points[0] = {0, 0};
  i_figure.points[1] = {1, 0};
  i_figure.points[2] = {2, 0};
  i_figure.points[3] = {3, 0};

  o_figure.points[0] = {0, 0};
  o_figure.points[1] = {0, 1};
  o_figure.points[2] = {1, 0};
  o_figure.points[3] = {1, 1};

  z_figure.points[0] = {0, 0};
  z_figure.points[1] = {1, 0};
  z_figure.points[2] = {1, 1};
  z_figure.points[3] = {2, 1};

  figures[0] = j_figure;
  figures[1] = i_figure;
  figures[2] = o_figure;
  figures[3] = z_figure;
}