Community project

8x8 LED Matrix Puzzle Game

ESP32
Photo of 8x8 LED Matrix Puzzle Game
Generated with AI

Pruthivi Sampath

Published October 19, 2026

Build a classic falling-block puzzle game on an 8x8 LED matrix powered by an ESP32 microcontroller. This project combines hardware assembly with embedded firmware to create an interactive game where players rotate and drop pieces to clear rows, with real-time visual feedback on the LED display.

This guide provides a complete parts list, wiring diagram showing all row and column connections to the ESP32, step-by-step assembly instructions for the 1088AS bare LED matrix with protective resistors, and ready-to-upload Arduino firmware. Builders will learn matrix display multiplexing, button input handling, and game logic implementation on a microcontroller platform.

Wiring diagram

Wiring diagram for 8x8 LED Matrix Puzzle Game

Gather all the parts

QtyComponent
1

Push Button

Left control

Momentary push button switch

1

Push Button

Right control

Momentary push button switch

1

Push Button

Rotate control

Momentary push button switch

1

Push Button

Drop control

Momentary push button switch

1

1088AS Bare 8×8 Red LED Dot Matrix

A bare 16-pin red LED display with eight shared cathode rows and eight shared anode columns.

1

1.5 kΩ Resistor

1.5 kΩ

A resistor that limits current through the first LED column so the ESP32-S3 pin is protected.

1

1.5 kΩ Resistor

1.5 kΩ

A resistor that limits current through the second LED column so the ESP32-S3 pin is protected.

1

1.5 kΩ Resistor

1.5 kΩ

A resistor that limits current through the third LED column so the ESP32-S3 pin is protected.

1

1.5 kΩ Resistor

1.5 kΩ

A resistor that limits current through the fourth LED column so the ESP32-S3 pin is protected.

1

1.5 kΩ Resistor

1.5 kΩ

A resistor that limits current through the fifth LED column so the ESP32-S3 pin is protected.

1

1.5 kΩ Resistor

1.5 kΩ

A resistor that limits current through the sixth LED column so the ESP32-S3 pin is protected.

1

1.5 kΩ Resistor

1.5 kΩ

A resistor that limits current through the seventh LED column so the ESP32-S3 pin is protected.

1

1.5 kΩ Resistor

1.5 kΩ

A resistor that limits current through the eighth LED column so the ESP32-S3 pin is protected.

Assemble it in 5 steps

1. Identify the correct bare matrix

Use a 1088AS bare 8×8 red LED matrix, not a board with a display driver. Hold its printed face toward you and find the small mark that identifies pin 1; the numbered pin positions below apply only to a 1088AS.

  • The display has two rows of eight legs, for 16 legs total. Do not use the old four-pin HT16K33 or MAX7219 board.
  • A 1088BS or another matrix type has different internal connections. Connecting it with this 1088AS layout can make the picture scrambled or leave it dark, but the 1.5 kΩ resistors still protect the ESP32-S3 pins.

2. Connect the eight row legs

Run eight jumper wires directly from the bare matrix row legs to the ESP32-S3: matrix physical pin 9 → GPIO5 (row 1), pin 14 → GPIO6 (row 2), pin 8 → GPIO7 (row 3), pin 12 → GPIO8 (row 4), pin 1 → GPIO9 (row 5), pin 7 → GPIO10 (row 6), pin 2 → GPIO11 (row 7), and pin 5 → GPIO12 (row 8). These wires select one horizontal line at a time.

  • Put the matrix across the center gap of a breadboard so its two leg rows cannot touch each other.
  • Do not connect any matrix leg straight to 3V3 or GND; every one of its 16 legs is controlled by the program.

3. Add the eight protective resistors and column wires

Use one separate 1.5 kΩ resistor for every column. Connect matrix pin 13 → resistor 1 → GPIO13 (column 1), pin 3 → resistor 2 → GPIO14 (column 2), pin 4 → resistor 3 → GPIO15 (column 3), pin 10 → resistor 4 → GPIO16 (column 4), pin 6 → resistor 5 → GPIO17 (column 5), pin 11 → resistor 6 → GPIO18 (column 6), pin 15 → resistor 7 → GPIO21 (column 7), and pin 16 → resistor 8 → GPIO38 (column 8). The resistor has no direction; it limits the current so the ESP32-S3 is protected.

  • A 1.5 kΩ resistor has brown, green, red, and gold bands. Keep each resistor in series: one end to the GPIO wire and its other end to the named matrix leg.
  • Do not replace these with plain wires — too much current can damage an ESP32-S3 pin.

4. Keep the four game buttons

For each push button, place it across the breadboard center gap. Connect one leg of the left button to GPIO1 and its opposite leg to GND (signal and ground); right button GPIO2 to GND (signal and ground); rotate button GPIO3 to GND (signal and ground); and drop button GPIO4 to GND (signal and ground).

  • The program uses the ESP32-S3's built-in pull-up, so no extra resistor is needed for the buttons. A quick tap counts exactly once.
  • Make sure each button's two used legs are opposite each other, not the two legs already joined on the same side of the button.

5. Power and test

Check that no bare matrix legs touch before plugging the ESP32-S3 into USB. The USB cable powers the board; the matrix has no separate VCC or GND wire because all 16 of its legs are already connected through the row and column wiring.

  • After deploying, the falling blocks should appear. Press left, right, rotate, and drop to play.
  • If all LEDs glow, the matrix is likely fitted the wrong way around or a row and column wire is in the wrong hole; unplug USB before moving wires.

Review all connections

1. Connections between "button_left" and "ESP32"

Functionbutton_leftESP32
groundGNDGND
digitalSIGNALGPIO 1

2. Connections between "button_right" and "ESP32"

Functionbutton_rightESP32
groundGNDGND
digitalSIGNALGPIO 2

3. Connections between "button_rotate" and "ESP32"

Functionbutton_rotateESP32
groundGNDGND
digitalSIGNALGPIO 3

4. Connections between "button_drop" and "ESP32"

Functionbutton_dropESP32
groundGNDGND
digitalSIGNALGPIO 4

5. Connections between "matrix_1" and "ESP32"

Functionmatrix_1ESP32
digitalR1 / physical pin 9GPIO 5
digitalR2 / physical pin 14GPIO 6
digitalR3 / physical pin 8GPIO 7
digitalR4 / physical pin 12GPIO 8
digitalR5 / physical pin 1GPIO 9
digitalR6 / physical pin 7GPIO 10
digitalR7 / physical pin 2GPIO 11
digitalR8 / physical pin 5GPIO 12
digitalC1 / physical pin 13 → 1.5 kΩ Resistor BEXT
digitalC2 / physical pin 3 → 1.5 kΩ Resistor BEXT
digitalC3 / physical pin 4 → 1.5 kΩ Resistor BEXT
digitalC4 / physical pin 10 → 1.5 kΩ Resistor BEXT
digitalC5 / physical pin 6 → 1.5 kΩ Resistor BEXT
digitalC6 / physical pin 11 → 1.5 kΩ Resistor BEXT
digitalC7 / physical pin 15 → 1.5 kΩ Resistor BEXT
digitalC8 / physical pin 16 → 1.5 kΩ Resistor BEXT

6. Connections between "column_resistor_1" and "ESP32"

Functioncolumn_resistor_1ESP32
digitalAGPIO 13

7. Connections between "column_resistor_2" and "ESP32"

Functioncolumn_resistor_2ESP32
digitalAGPIO 14

8. Connections between "column_resistor_3" and "ESP32"

Functioncolumn_resistor_3ESP32
digitalAGPIO 15

9. Connections between "column_resistor_4" and "ESP32"

Functioncolumn_resistor_4ESP32
digitalAGPIO 16

10. Connections between "column_resistor_5" and "ESP32"

Functioncolumn_resistor_5ESP32
digitalAGPIO 17

11. Connections between "column_resistor_6" and "ESP32"

Functioncolumn_resistor_6ESP32
digitalAGPIO 18

12. Connections between "column_resistor_7" and "ESP32"

Functioncolumn_resistor_7ESP32
digitalAGPIO 21

13. Connections between "column_resistor_8" and "ESP32"

Functioncolumn_resistor_8ESP32
digitalAGPIO 38

Deploy the firmware

#include <Arduino.h>

const uint8_t LEFT_BUTTON_PIN = 1;
const uint8_t RIGHT_BUTTON_PIN = 2;
const uint8_t ROTATE_BUTTON_PIN = 3;
const uint8_t DROP_BUTTON_PIN = 4;
const uint8_t ROW1_PIN = 5;
const uint8_t ROW2_PIN = 6;
const uint8_t ROW3_PIN = 7;
const uint8_t ROW4_PIN = 8;
const uint8_t ROW5_PIN = 9;
const uint8_t ROW6_PIN = 10;
const uint8_t ROW7_PIN = 11;
const uint8_t ROW8_PIN = 12;
const uint8_t COL1_PIN = 13;
const uint8_t COL2_PIN = 14;
const uint8_t COL3_PIN = 15;
const uint8_t COL4_PIN = 16;
const uint8_t COL5_PIN = 17;
const uint8_t COL6_PIN = 18;
const uint8_t COL7_PIN = 21;
const uint8_t COL8_PIN = 38;
const uint8_t rowPins[8] = {ROW1_PIN, ROW2_PIN, ROW3_PIN, ROW4_PIN, ROW5_PIN, ROW6_PIN, ROW7_PIN, ROW8_PIN};
const uint8_t columnPins[8] = {COL1_PIN, COL2_PIN, COL3_PIN, COL4_PIN, COL5_PIN, COL6_PIN, COL7_PIN, COL8_PIN};
// Each shape uses four stored positions. Repeated positions intentionally make 1- and 2-dot pieces.
const uint8_t SHAPES[5][4][4][2] = {
  {{{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,0},{0,0},{0,0},{0,0}}},
  {{{0,0},{1,0},{0,0},{1,0}},{{0,0},{0,1},{0,0},{0,1}},{{0,0},{1,0},{0,0},{1,0}},{{0,0},{0,1},{0,0},{0,1}}},
  {{{0,0},{1,0},{2,0},{2,0}},{{0,0},{0,1},{0,2},{0,2}},{{0,0},{1,0},{2,0},{2,0}},{{0,0},{0,1},{0,2},{0,2}}},
  {{{0,0},{0,1},{1,1},{1,1}},{{0,0},{1,0},{0,1},{0,1}},{{0,0},{1,0},{1,1},{1,1}},{{1,0},{0,1},{1,1},{1,1}}},
  {{{1,0},{0,1},{1,1},{1,1}},{{0,0},{0,1},{1,1},{1,1}},{{0,0},{1,0},{0,1},{0,1}},{{1,0},{1,1},{0,1},{0,1}}}
};
uint8_t boardCells[8] = {}; int pieceType, rotation, pieceX, pieceY; unsigned long lastFall, fallInterval = 550, gameOverAt = 0, nextScanAt = 0; bool gameOver = false; uint8_t scanRow = 0;
struct Button { uint8_t pin; bool stableState; bool lastReading; unsigned long changedAt; };
Button buttons[] = {{LEFT_BUTTON_PIN,HIGH,HIGH,0},{RIGHT_BUTTON_PIN,HIGH,HIGH,0},{ROTATE_BUTTON_PIN,HIGH,HIGH,0},{DROP_BUTTON_PIN,HIGH,HIGH,0}};
bool buttonPressed(Button &b) { bool r=digitalRead(b.pin); unsigned long n=millis(); if(r!=b.lastReading){b.changedAt=n;b.lastReading=r;} if(n-b.changedAt>25 && r!=b.stableState){b.stableState=r;return r==LOW;} return false; }
bool collides(int tx,int ty,int tr) { for(uint8_t i=0;i<4;i++){int x=tx+SHAPES[pieceType][tr][i][0],y=ty+SHAPES[pieceType][tr][i][1]; if(x<0||x>=8||y>=8||(y>=0&&(boardCells[y]&(1<<x)))) return true;} return false; }
bool pixelOn(uint8_t x,uint8_t y) { if(gameOver) return x==y||x==7-y; if(boardCells[y]&(1<<x)) return true; for(uint8_t i=0;i<4;i++) if(pieceX+SHAPES[pieceType][rotation][i][0]==x&&pieceY+SHAPES[pieceType][rotation][i][1]==y) return true; return false; }
void refreshMatrix(){ unsigned long n=micros(); if((long)(n-nextScanAt)<0)return; nextScanAt=n+1000; digitalWrite(rowPins[(scanRow+7)%8],HIGH); for(uint8_t x=0;x<8;x++)digitalWrite(columnPins[x],LOW); for(uint8_t x=0;x<8;x++)if(pixelOn(x,scanRow))digitalWrite(columnPins[x],HIGH); digitalWrite(rowPins[scanRow],LOW); scanRow=(scanRow+1)%8; }
void clearFullRows(){for(int y=7;y>=0;y--)if(boardCells[y]==0xFF){for(int a=y;a>0;a--)boardCells[a]=boardCells[a-1];boardCells[0]=0;y++;if(fallInterval>180)fallInterval-=20;}}
void spawnPiece(){pieceType=random(0,5);rotation=0;pieceX=3;pieceY=0;if(collides(pieceX,pieceY,rotation)){gameOver=true;gameOverAt=millis();}}
void lockPiece(){for(uint8_t i=0;i<4;i++){int x=pieceX+SHAPES[pieceType][rotation][i][0],y=pieceY+SHAPES[pieceType][rotation][i][1];if(x>=0&&x<8&&y>=0&&y<8)boardCells[y]|=(1<<x);}clearFullRows();spawnPiece();}
void stepDown(){if(!collides(pieceX,pieceY+1,rotation))pieceY++;else lockPiece();}
void newGame(){for(uint8_t y=0;y<8;y++)boardCells[y]=0;fallInterval=550;gameOver=false;spawnPiece();}
void setup(){for(Button &b:buttons)pinMode(b.pin,INPUT_PULLUP);for(uint8_t p:rowPins){pinMode(p,OUTPUT);digitalWrite(p,HIGH);}for(uint8_t p:columnPins){pinMode(p,OUTPUT);digitalWrite(p,LOW);}randomSeed(micros());newGame();lastFall=millis();}
void loop(){refreshMatrix();unsigned long n=millis();if(gameOver){if(n-gameOverAt>1200)newGame();return;}if(buttonPressed(buttons[0])&&!collides(pieceX-1,pieceY,rotation))pieceX--;if(buttonPressed(buttons[1])&&!collides(pieceX+1,pieceY,rotation))pieceX++;if(buttonPressed(buttons[2])){int r=(rotation+1)%4;if(!collides(pieceX,pieceY,r))rotation=r;}if(buttonPressed(buttons[3])){while(!collides(pieceX,pieceY+1,rotation))pieceY++;lockPiece();}if(n-lastFall>=fallInterval){lastFall=n;stepDown();}}

Remix this project

Make it yours in one click

Open a full copy of this project in your own Schematik workspace — diagram, code, parts, and assembly steps included. Swap the sensor, add features, or redesign the whole thing with AI. The author's original stays untouched.

Open in Schematik