Welcome to Hackster!
Hackster is a community dedicated to learning hardware, from beginner to pro. Join us, it's free!
PCBX
Published

Building a Tetris Game with Arduino and OLED

Play classic Tetris on an OLED display! This Arduino-powered project features button controls for movement and rotation.

BeginnerProtip213
Building a Tetris Game with Arduino and OLED

Things used in this project

Story

Read more

Schematics

tetris_game_kdBqBHphpo.png

Code

Untitled file

Arduino
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Wire.h>

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
#define OLED_ADDRESS 0x3C

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

const int gridWidth = 10;
const int gridHeight = 20;
const int blockPixelSize = 2;

// Tetris shapes in a 4x4 grid
const byte shapes[7][4][4] = {
// I
{{0, 1, 0, 0},
{0, 1, 0, 0},
{0, 1, 0, 0},
{0, 1, 0, 0}},
// J
{{0, 0, 0, 0},
{1, 1, 1, 0},
{0, 0, 1, 0},
{0, 0, 0, 0}},
// L
{{0, 0, 0, 0},
{1, 1, 1, 0},
{1, 0, 0, 0},
{0, 0, 0, 0}},
// O
{{0, 0, 0, 0},
{0, 1, 1, 0},
{0, 1, 1, 0},
{0, 0, 0, 0}},
// S
{{0, 0, 0, 0},
{0, 1, 1, 0},
{1, 1, 0, 0},
{0, 0, 0, 0}},
// T
{{0, 0, 0, 0},
{1, 1, 1, 0},
{0, 1, 0, 0},
{0, 0, 0, 0}},
// Z
{{0, 0, 0, 0},
{1, 1, 0, 0},
{0, 1, 1, 0},
{0, 0, 0, 0}}
};

bool grid[gridHeight][gridWidth] = {false};

int currentX = 0;
int currentY = 0;
int currentShape = 0;
int rotation = 0;

unsigned long lastFallTime = 0;
const unsigned long fallSpeed = 500;

void setup() {
  Serial.begin(9600);
  Wire.begin();

  if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDRESS)) {
    Serial.println(F("SSD1306 allocation failed"));
    for (;;);
  }

  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
  display.display();

  pinMode(2, INPUT_PULLUP); // Left move button
  pinMode(3, INPUT_PULLUP); // Right move button
  pinMode(4, INPUT_PULLUP); // Rotate button

  newShape();
}

void loop() {
  if (isGameOver()) {
    display.clearDisplay();
    display.setCursor(10, 20);
    display.println("Game Over");
    display.display();
    delay(2000);
    memset(grid, false, sizeof(grid));
    newShape();
    return;
  }

  handleInput(); // Handle user input

  if (millis() - lastFallTime >= fallSpeed) {
    lastFallTime = millis();
    moveShapeDown();
  }

  drawFrame();
}

void drawFrame() {
  display.clearDisplay();
  drawGrid();
  drawShape(currentX, currentY, currentShape, rotation);
  display.display();
}

void drawShape(int x, int y, int shape, int rot) {
  byte rotatedShape[4][4];

  rotateShape(shape, rot, rotatedShape);

  for (int i = 0; i < 4; i++) {
    for (int j = 0; j < 4; j++) {
      if (rotatedShape[i][j]) {
        int gridX = x + j;
        int gridY = y + i;
        if (gridX >= 0 && gridX < gridWidth && gridY >= 0 && gridY < gridHeight) {
          display.fillRect(gridX * blockPixelSize, gridY * blockPixelSize, blockPixelSize, blockPixelSize, SSD1306_WHITE);
        }
      }
    }
  }
}

void drawGrid() {
  for (int y = 0; y < gridHeight; y++) {
    for (int x = 0; x < gridWidth; x++) {
      if (grid[y][x]) {
        display.fillRect(x * blockPixelSize, y * blockPixelSize, blockPixelSize, blockPixelSize, SSD1306_WHITE);
      }
    }
  }
}

void moveShapeDown() {
  if (!checkCollision(currentX, currentY + 1, currentShape, rotation)) {
    currentY++;
  } else {
    fixShape();
    checkLines();
    newShape();
  }
}

bool checkCollision(int x, int y, int shape, int rot) {
  byte rotatedShape[4][4];
  rotateShape(shape, rot, rotatedShape);

  for (int i = 0; i < 4; i++) {
    for (int j = 0; j < 4; j++) {
      if (rotatedShape[i][j]) {
        int gridX = x + j;
        int gridY = y + i;

        if (gridX < 0 || gridX >= gridWidth || gridY >= gridHeight || grid[gridY][gridX]) {
          return true;
        }
      }
    }
  }
  return false;
}

void fixShape() {
  byte rotatedShape[4][4];
  rotateShape(currentShape, rotation, rotatedShape);

  for (int i = 0; i < 4; i++) {
    for (int j = 0; j < 4; j++) {
      if (rotatedShape[i][j]) {
        int gridX = currentX + j;
        int gridY = currentY + i;
        if (gridX >= 0 && gridX < gridWidth && gridY >= 0 && gridY < gridHeight) {
          grid[gridY][gridX] = true;
        }
      }
    }
  }
}

void newShape() {
  currentX = gridWidth / 2 - 2;
  currentY = 0;
  currentShape = random(0, 7);
  rotation = 0;
}

void handleInput() {
  // Handle user button input
  if (digitalRead(2) == LOW) { // Left move
    if (!checkCollision(currentX - 1, currentY, currentShape, rotation)) {
      currentX--;
    }
  }

  if (digitalRead(3) == LOW) { // Right move
    if (!checkCollision(currentX + 1, currentY, currentShape, rotation)) {
      currentX++;
    }
  }

  if (digitalRead(4) == LOW) { // Rotate
    int newRotation = (rotation + 1) % 4;
    if (!checkCollision(currentX, currentY, currentShape, newRotation)) {
      rotation = newRotation;
    }
  }

  delay(100); // Simple debounce
}

void checkLines() {
  for (int y = gridHeight - 1; y >= 0; y--) {
    bool full = true;
    for (int x = 0; x < gridWidth; x++) {
      if (!grid[y][x]) {
        full = false;
        break;
      }
    }
    if (full) {
      for (int dy = y; dy > 0; dy--) {
        for (int x = 0; x < gridWidth; x++) {
          grid[dy][x] = grid[dy - 1][x];
        }
      }
      for (int x = 0; x < gridWidth; x++) {
        grid[0][x] = false;
      }
      y++; // Recheck this line in the next iteration
    }
  }
}

bool isGameOver() {
  for (int x = 0; x < gridWidth; x++) {
    if (grid[0][x]) {
      return true;
    }
  }
  return false;
}

void rotateShape(int shape, int rot, byte output[4][4]) {
  // Clear output shape array initially
  memset(output, 0, 16);

  // Perform rotations and store into output
  // Basic matrix rotation - 90 degrees clockwise:
  for (int i = 0; i < 4; i++) {
    for (int j = 0; j < 4; j++) {
      if (rot == 1) {
        output[i][j] = shapes[shape][3-j][i];
      } else if (rot == 2) {
        output[i][j] = shapes[shape][3-i][3-j];
          } else if (rot == 3) {
    output[i][j] = shapes[shape][j][3-i];
  } else {
    output[i][j] = shapes[shape][i][j];
  }
}
}
}

Credits

PCBX
33 projects • 9 followers
Customer Success: Your one-stop solution for PCB and PCBA services, plus component sourcing. Enjoy FREE online simulation and EDA.
Contact

Comments

Please log in or sign up to comment.