Arnov Sharma
Published © MIT

MatTris — Tetris on an LED Matrix

Classic Tetris running on a custom WS2812 2020 LED-based matrix powered by RP2040

BeginnerFull instructions provided1 hour591
MatTris — Tetris on an LED Matrix

Things used in this project

Hardware components

RP2040
Raspberry Pi RP2040
×1
NextPCB  Custom PCB Board
NextPCB Custom PCB Board
×1

Software apps and online services

Fusion
Autodesk Fusion
Arduino IDE
Arduino IDE

Hand tools and fabrication machines

3D Printer (generic)
3D Printer (generic)

Story

Read more

Custom parts and enclosures

cad file

Schematics

SCH

Code

code

C/C++
#include <Adafruit_NeoPixel.h>
#include <Adafruit_GFX.h>
#include <Adafruit_NeoMatrix.h>

#define MATRIX_PIN 0
#define MATRIX_WIDTH 14
#define MATRIX_HEIGHT 20

// Buttons (ACTIVE LOW)
#define BUTTON_LEFT 28
#define BUTTON_RIGHT 27
#define BUTTON_ROTATE 5
#define BUTTON_SPEED 6 // Speed / soft drop

#define DROP_INTERVAL_NORMAL 800
#define DROP_INTERVAL_FAST 80
#define MOVE_DELAY 150
#define ROTATE_DEBOUNCE 200

unsigned long lastDropTime = 0;
unsigned long lastMoveTime = 0;
unsigned long lastRotateTime = 0;

// ---- MATRIX (YOUR CONFIRMED WORKING MAPPING) ----
Adafruit_NeoMatrix matrix = Adafruit_NeoMatrix(
MATRIX_WIDTH,
MATRIX_HEIGHT,
MATRIX_PIN,
NEO_MATRIX_TOP + NEO_MATRIX_LEFT + NEO_MATRIX_ROWS,
NEO_GRB + NEO_KHZ800
);

// ---- GAME STATE ----
uint8_t grid[MATRIX_WIDTH][MATRIX_HEIGHT] = {0};

int pieceX = 5;
int pieceY = 0;
int rotationIndex = 0;
int currentPiece = 0;

const int PIECE_SIZE = 4;

// ---- 4×4 TETROMINOES ----
const uint8_t tetrominoes[7][4][4][4] = {

// I
{
{{0,0,0,0},{1,1,1,1},{0,0,0,0},{0,0,0,0}},
{{0,0,1,0},{0,0,1,0},{0,0,1,0},{0,0,1,0}},
{{0,0,0,0},{1,1,1,1},{0,0,0,0},{0,0,0,0}},
{{0,1,0,0},{0,1,0,0},{0,1,0,0},{0,1,0,0}}
},

// O
{
{{0,1,1,0},{0,1,1,0},{0,0,0,0},{0,0,0,0}},
{{0,1,1,0},{0,1,1,0},{0,0,0,0},{0,0,0,0}},
{{0,1,1,0},{0,1,1,0},{0,0,0,0},{0,0,0,0}},
{{0,1,1,0},{0,1,1,0},{0,0,0,0},{0,0,0,0}}
},

// T
{
{{0,1,0,0},{1,1,1,0},{0,0,0,0},{0,0,0,0}},
{{0,1,0,0},{0,1,1,0},{0,1,0,0},{0,0,0,0}},
{{0,0,0,0},{1,1,1,0},{0,1,0,0},{0,0,0,0}},
{{0,1,0,0},{1,1,0,0},{0,1,0,0},{0,0,0,0}}
},

// S
{
{{0,1,1,0},{1,1,0,0},{0,0,0,0},{0,0,0,0}},
{{0,1,0,0},{0,1,1,0},{0,0,1,0},{0,0,0,0}},
{{0,1,1,0},{1,1,0,0},{0,0,0,0},{0,0,0,0}},
{{0,1,0,0},{0,1,1,0},{0,0,1,0},{0,0,0,0}}
},

// Z
{
{{1,1,0,0},{0,1,1,0},{0,0,0,0},{0,0,0,0}},
{{0,0,1,0},{0,1,1,0},{0,1,0,0},{0,0,0,0}},
{{1,1,0,0},{0,1,1,0},{0,0,0,0},{0,0,0,0}},
{{0,0,1,0},{0,1,1,0},{0,1,0,0},{0,0,0,0}}
},

// J
{
{{1,0,0,0},{1,1,1,0},{0,0,0,0},{0,0,0,0}},
{{0,1,1,0},{0,1,0,0},{0,1,0,0},{0,0,0,0}},
{{0,0,0,0},{1,1,1,0},{0,0,1,0},{0,0,0,0}},
{{0,1,0,0},{0,1,0,0},{1,1,0,0},{0,0,0,0}}
},

// L
{
{{0,0,1,0},{1,1,1,0},{0,0,0,0},{0,0,0,0}},
{{0,1,0,0},{0,1,0,0},{0,1,1,0},{0,0,0,0}},
{{0,0,0,0},{1,1,1,0},{1,0,0,0},{0,0,0,0}},
{{1,1,0,0},{0,1,0,0},{0,1,0,0},{0,0,0,0}}
}
};

// ---- GAME LOGIC ----
bool canPlace(int x, int y, int piece, int rot) {
for (int py = 0; py < PIECE_SIZE; py++) {
for (int px = 0; px < PIECE_SIZE; px++) {
if (tetrominoes[piece][rot][py][px]) {
int gx = x + px;
int gy = y + py;
if (gx < 0 || gx >= MATRIX_WIDTH ||
gy < 0 || gy >= MATRIX_HEIGHT ||
grid[gx][gy]) return false;
}
}
}
return true;
}

void placePiece() {
for (int py = 0; py < PIECE_SIZE; py++)
for (int px = 0; px < PIECE_SIZE; px++)
if (tetrominoes[currentPiece][rotationIndex][py][px])
grid[pieceX + px][pieceY + py] = 1;
}

void clearLines() {
for (int y = 0; y < MATRIX_HEIGHT; y++) {
bool full = true;
for (int x = 0; x < MATRIX_WIDTH; x++)
if (!grid[x][y]) { full = false; break; }

if (full) {
for (int yy = y; yy > 0; yy--)
for (int x = 0; x < MATRIX_WIDTH; x++)
grid[x][yy] = grid[x][yy - 1];
for (int x = 0; x < MATRIX_WIDTH; x++) grid[x][0] = 0;
}
}
}

// ---- SPAWN + GAME OVER ----
void spawnPiece() {
currentPiece = random(7);
rotationIndex = 0;
pieceX = MATRIX_WIDTH / 2 - 2;
pieceY = 0;

if (!canPlace(pieceX, pieceY, currentPiece, rotationIndex)) {
matrix.fillScreen(matrix.Color(255, 0, 0));
matrix.show();
delay(1500);
memset(grid, 0, sizeof(grid));
}
}

void drawGrid() {
matrix.fillScreen(0);

for (int x = 0; x < MATRIX_WIDTH; x++)
for (int y = 0; y < MATRIX_HEIGHT; y++)
if (grid[x][y])
matrix.drawPixel(x, y, matrix.Color(0, 255, 0));

for (int py = 0; py < PIECE_SIZE; py++)
for (int px = 0; px < PIECE_SIZE; px++)
if (tetrominoes[currentPiece][rotationIndex][py][px])
matrix.drawPixel(pieceX + px, pieceY + py, matrix.Color(0, 0, 255));

matrix.show();
}

void setup() {
matrix.begin();
matrix.setBrightness(40);
matrix.setRotation(0);

pinMode(BUTTON_LEFT, INPUT_PULLUP);
pinMode(BUTTON_RIGHT, INPUT_PULLUP);
pinMode(BUTTON_ROTATE, INPUT_PULLUP);
pinMode(BUTTON_SPEED, INPUT_PULLUP);

delay(50); // let pull-ups stabilize

randomSeed(analogRead(A0));
spawnPiece();
}

void loop() {
unsigned long now = millis();

if (!digitalRead(BUTTON_LEFT) && now - lastMoveTime > MOVE_DELAY) {
if (canPlace(pieceX - 1, pieceY, currentPiece, rotationIndex)) pieceX--;
lastMoveTime = now;
}

if (!digitalRead(BUTTON_RIGHT) && now - lastMoveTime > MOVE_DELAY) {
if (canPlace(pieceX + 1, pieceY, currentPiece, rotationIndex)) pieceX++;
lastMoveTime = now;
}

if (!digitalRead(BUTTON_ROTATE) && now - lastRotateTime > ROTATE_DEBOUNCE) {
int nextRot = (rotationIndex + 1) % 4;
if (canPlace(pieceX, pieceY, currentPiece, nextRot))
rotationIndex = nextRot;
lastRotateTime = now;
}

G
bool speedPressed = (digitalRead(BUTTON_SPEED) == LOW);
unsigned long dropInterval =
speedPressed ? DROP_INTERVAL_FAST : DROP_INTERVAL_NORMAL;

if (now - lastDropTime > dropInterval) {
lastDropTime = now;
if (canPlace(pieceX, pieceY + 1, currentPiece, rotationIndex))
pieceY++;
else {
placePiece();
clearLines();
spawnPiece();
}
}

drawGrid();
}

Credits

Arnov Sharma
363 projects • 369 followers
I'm Arnov. I build, design, and experiment with tech—3D printing, PCB design, and retro consoles are my jam.

Comments