Arduino Project

Tetris4.0

Course Introduction

This Arduino project uses MAX7219 8x8 Dot Matrix module, four buttons to play a classic Tetris game.

Note

If this is your first time working with an Arduino project, we recommend downloading and reviewing the basic materials first.

1.1 Install Arduino IDE(Important)
1.2 Introduction of Arduino IDE

Required Components

In this project, we need the following components:

SN

COMPONENT INTRODUCTION

QUANTITY

PURCHASE LINK

1

Arduino UNO R4 WIFI

1

2

USB Type-C cable

1

×

3

Breadboard

1

BUY

4

Wires

Several

5

Button

4

6

MAX7219 Dot Matrix Module

1

Wiring

8.webp__PID:42aac44b-bb55-4d4a-a244-8fafd89d496c

Common Connections:

MAX7219 Dot Matrix Module
CLK:
Connect to 12 on the Arduino.
CS: Connect to 10 on the Arduino.
DIN: Connect to 11 on the Arduino.
GND: Connect to breadboard’s negative power bus.
VCC: Connect to breadboard’s red power bus.

Button 1
Connect to the breadboard’s negative power bus, and the other end to 3 on the Arduino board.

Button 2
Connect to the breadboard’s negative power bus, and the other end to 4 on the Arduino board.

Button 3
Connect to the breadboard’s negative power bus, and the other end to 5 on the Arduino board.

Button 4
Connect to the breadboard’s negative power bus, and the other end to 6 on the Arduino board.

Writing the Code

Note
You can copy this code into Arduino IDE.
To install the library, use the Arduino Library Manager and search for LedControl and install it.
Don’t forget to select the board(Arduino UNO R4 Minima) and the correct port before clicking the Upload button.


#include 

// MAX7219 wiring (DIN -> D11, CLK -> D12, CS -> D10)
const uint8_t PIN_DIN = 11;
const uint8_t PIN_CLK = 12;
const uint8_t PIN_CS  = 10;

LedControl lc = LedControl(PIN_DIN, PIN_CLK, PIN_CS, 1);

// Button pins
const uint8_t PIN_LEFT   = 5;
const uint8_t PIN_RIGHT  = 6;
const uint8_t PIN_DOWN   = 3;
const uint8_t PIN_ROTATE = 4;

byte field[8];

unsigned long lastDrop = 0;
unsigned long baseDropInterval = 500;
unsigned long fastDropInterval = 120;
unsigned long currentDropInterval = 500;

unsigned long lastMoveTime = 0;
const unsigned long moveRepeatInterval = 120;

bool lastRotateState = HIGH;
unsigned long lastRotateTime = 0;
const unsigned long rotateDebounce = 150;

bool gameRunning = false;
bool gameOver = false;

struct Point {
  int8_t x;
  int8_t y;

  Point(int8_t px = 0, int8_t py = 0) {
    x = px;
    y = py;
  }
};

struct ShapeDef {
  const Point* r0;
  const Point* r1;
  const Point* r2;
  const Point* r3;
  uint8_t rotCount;

  ShapeDef(const Point* a, const Point* b, const Point* c, const Point* d, uint8_t count) {
    r0 = a;
    r1 = b;
    r2 = c;
    r3 = d;
    rotCount = count;
  }

  const Point* getRot(uint8_t rot) const {
    if (rot == 0) return r0;
    if (rot == 1) return r1;
    if (rot == 2) return r2;
    return r3;
  }
};

struct Block {
  uint8_t shapeId;
  uint8_t rot;
  int8_t x;
  int8_t y;
};

const Point I0[4] = { Point(-1, 0), Point(0, 0), Point(1, 0), Point(2, 0) };
const Point I1[4] = { Point(0, -1), Point(0, 0), Point(0, 1), Point(0, 2) };

const Point O0[4] = { Point(0, 0), Point(1, 0), Point(0, 1), Point(1, 1) };

const Point T0[4] = { Point(-1, 0), Point(0, 0), Point(1, 0), Point(0, 1) };
const Point T1[4] = { Point(0, -1), Point(0, 0), Point(0, 1), Point(1, 0) };
const Point T2[4] = { Point(-1, 0), Point(0, 0), Point(1, 0), Point(0, -1) };
const Point T3[4] = { Point(0, -1), Point(0, 0), Point(0, 1), Point(-1, 0) };

const Point L0[4] = { Point(-1, 0), Point(0, 0), Point(1, 0), Point(1, 1) };
const Point L1[4] = { Point(0, -1), Point(0, 0), Point(0, 1), Point(1, -1) };
const Point L2[4] = { Point(-1, -1), Point(-1, 0), Point(0, 0), Point(1, 0) };
const Point L3[4] = { Point(-1, 1), Point(0, -1), Point(0, 0), Point(0, 1) };

const Point J0[4] = { Point(-1, 0), Point(0, 0), Point(1, 0), Point(-1, 1) };
const Point J1[4] = { Point(0, -1), Point(0, 0), Point(0, 1), Point(1, 1) };
const Point J2[4] = { Point(1, -1), Point(-1, 0), Point(0, 0), Point(1, 0) };
const Point J3[4] = { Point(-1, -1), Point(0, -1), Point(0, 0), Point(0, 1) };

const Point S0[4] = { Point(0, 0), Point(1, 0), Point(-1, 1), Point(0, 1) };
const Point S1[4] = { Point(0, -1), Point(0, 0), Point(1, 0), Point(1, 1) };

const Point Z0[4] = { Point(-1, 0), Point(0, 0), Point(0, 1), Point(1, 1) };
const Point Z1[4] = { Point(1, -1), Point(0, 0), Point(1, 0), Point(0, 1) };

const ShapeDef shapes[7] = {
  ShapeDef(I0, I1, nullptr, nullptr, 2),
  ShapeDef(O0, nullptr, nullptr, nullptr, 1),
  ShapeDef(T0, T1, T2, T3, 4),
  ShapeDef(L0, L1, L2, L3, 4),
  ShapeDef(J0, J1, J2, J3, 4),
  ShapeDef(S0, S1, nullptr, nullptr, 2),
  ShapeDef(Z0, Z1, nullptr, nullptr, 2)
};

Block cur;

inline bool rowHas(byte row, int8_t x) {
  return (row & (1 << x)) != 0;
}

byte reverse8(byte b) {
  b = (b & 0xF0) >> 4 | (b & 0x0F) << 4;
  b = (b & 0xCC) >> 2 | (b & 0x33) << 2;
  b = (b & 0xAA) >> 1 | (b & 0x55) << 1;
  return b;
}

bool checkCollision(int8_t nx, int8_t ny, uint8_t shapeId, uint8_t rot) {
  const ShapeDef& sd = shapes[shapeId];
  const Point* pts = sd.getRot(rot);

  for (uint8_t i = 0; i < 4; i++) {
    int8_t px = nx + pts[i].x;
    int8_t py = ny + pts[i].y;

    if (px < 0 || px > 7) return true;
    if (py > 7) return true;

    if (py >= 0) {
      if (rowHas(field[py], px)) return true;
    }
  }
  return false;
}

bool moveBlock(int8_t dx, int8_t dy) {
  int8_t nx = cur.x + dx;
  int8_t ny = cur.y + dy;

  if (checkCollision(nx, ny, cur.shapeId, cur.rot)) return false;

  cur.x = nx;
  cur.y = ny;
  return true;
}

void rotateBlock() {
  const ShapeDef& sd = shapes[cur.shapeId];
  if (sd.rotCount <= 1) return;

  uint8_t oldRot = cur.rot;
  uint8_t nextRot = (cur.rot + 1) % sd.rotCount;

  if (!checkCollision(cur.x, cur.y, cur.shapeId, nextRot)) {
    cur.rot = nextRot;
    return;
  }

  if (!checkCollision(cur.x - 1, cur.y, cur.shapeId, nextRot)) {
    cur.x -= 1;
    cur.rot = nextRot;
    return;
  }

  if (!checkCollision(cur.x + 1, cur.y, cur.shapeId, nextRot)) {
    cur.x += 1;
    cur.rot = nextRot;
    return;
  }

  cur.rot = oldRot;
}

void placeBlock() {
  const ShapeDef& sd = shapes[cur.shapeId];
  const Point* pts = sd.getRot(cur.rot);

  for (uint8_t i = 0; i < 4; i++) {
    int8_t px = cur.x + pts[i].x;
    int8_t py = cur.y + pts[i].y;

    if (px >= 0 && px <= 7 && py >= 0 && py <= 7) {
      field[py] |= (1 << px);
    }
  }
}

void clearLines() {
  for (int8_t y = 7; y >= 0; y--) {
    if (field[y] == 0xFF) {
      for (int8_t yy = y; yy > 0; yy--) {
        field[yy] = field[yy - 1];
      }
      field[0] = 0;
      y++;
    }
  }
}

void resetGame() {
  for (uint8_t i = 0; i < 8; i++) {
    field[i] = 0;
  }

  gameOver = false;
  lastDrop = millis();
}

void spawnBlock() {
  cur.shapeId = (uint8_t)random(0, 7);
  cur.rot = 0;
  cur.x = 3;
  cur.y = -1;

  if (cur.shapeId == 0) {
    cur.x = 2;
    cur.y = -1;
  }

  if (checkCollision(cur.x, cur.y, cur.shapeId, cur.rot)) {
    gameOver = true;
    gameRunning = false;
  }
}

void drawMapped(const byte src[8]) {
  byte rotated[8] = {0};

  for (int y = 0; y < 8; y++) {
    for (int x = 0; x < 8; x++) {
      if (src[y] & (1 << x)) {
        rotated[x] |= (1 << (7 - y));
      }
    }
  }

  for (int row = 0; row < 8; row++) {
    rotated[row] = reverse8(rotated[row]);
  }

  for (int row = 0; row < 8; row++) {
    lc.setRow(0, row, rotated[row]);
  }
}

void drawFrame() {
  byte frame[8];

  for (uint8_t y = 0; y < 8; y++) {
    frame[y] = field[y];
  }

  if (gameRunning && !gameOver) {
    const ShapeDef& sd = shapes[cur.shapeId];
    const Point* pts = sd.getRot(cur.rot);

    for (uint8_t i = 0; i < 4; i++) {
      int8_t px = cur.x + pts[i].x;
      int8_t py = cur.y + pts[i].y;

      if (px >= 0 && px <= 7 && py >= 0 && py <= 7) {
        frame[py] |= (1 << px);
      }
    }
  }

  drawMapped(frame);
}

void showGameOverAnim() {
  static unsigned long lastBlink = 0;
  static bool on = false;

  unsigned long now = millis();

  if (now - lastBlink > 300) {
    lastBlink = now;
    on = !on;
  }

  byte full[8];

  for (int i = 0; i < 8; i++) {
    full[i] = on ? 0xFF : 0x00;
  }

  drawMapped(full);
}

void handleInput() {
  bool leftPressed = (digitalRead(PIN_LEFT) == LOW);
  bool rightPressed = (digitalRead(PIN_RIGHT) == LOW);
  bool downPressed = (digitalRead(PIN_DOWN) == LOW);
  bool rotatePressed = (digitalRead(PIN_ROTATE) == LOW);

  currentDropInterval = downPressed ? fastDropInterval : baseDropInterval;

  unsigned long now = millis();

  if (now - lastMoveTime >= moveRepeatInterval) {
    if (leftPressed && !rightPressed) {
      moveBlock(-1, 0);
      lastMoveTime = now;
    } else if (rightPressed && !leftPressed) {
      moveBlock(1, 0);
      lastMoveTime = now;
    }
  }

  if (rotatePressed != lastRotateState) {
    if (now - lastRotateTime > rotateDebounce) {
      lastRotateTime = now;

      if (rotatePressed) {
        rotateBlock();
      }
    }

    lastRotateState = rotatePressed;
  }
}

bool rotatePressedNow() {
  return digitalRead(PIN_ROTATE) == LOW;
}

void setup() {
  randomSeed(analogRead(A0));

  pinMode(PIN_LEFT, INPUT_PULLUP);
  pinMode(PIN_RIGHT, INPUT_PULLUP);
  pinMode(PIN_DOWN, INPUT_PULLUP);
  pinMode(PIN_ROTATE, INPUT_PULLUP);

  lc.shutdown(0, false);
  lc.setIntensity(0, 8);
  lc.clearDisplay(0);

  resetGame();
  drawFrame();
}

void loop() {
  if (!gameRunning) {
    if (gameOver) {
      showGameOverAnim();
    } else {
      drawFrame();
    }

    if (rotatePressedNow()) {
      delay(30);

      if (rotatePressedNow()) {
        resetGame();
        spawnBlock();
        gameRunning = !gameOver;

        while (rotatePressedNow()) {
          delay(10);
        }
      }
    }

    return;
  }

  handleInput();

  unsigned long now = millis();

  if (now - lastDrop >= currentDropInterval) {
    lastDrop = now;

    if (!moveBlock(0, 1)) {
      placeBlock();
      clearLines();
      spawnBlock();

      if (gameOver) {
        gameRunning = false;
      }
    }
  }

  drawFrame();
}