Wired-Chaos

Stacker

← Back to index

Overview

This tutorial shows how to build a Stacker game on an 8x8 LED matrix using the LedControl library and a single button.

You will learn:

Jump to section - [What you need](#what-you-need) - [Step 1: Install the library](#step-1-install-the-library) - [Step 2: Wire the hardware](#step-2-wire-the-hardware) - [Step 3: Understand the game flow](#step-3-understand-the-game-flow) - [Step 4: Pin and game variables](#step-4-pin-and-game-variables) - [Step 5: Initialize the display and inputs](#step-5-initialize-the-display-and-inputs) - [Step 6: Move the block automatically](#step-6-move-the-block-automatically) - [Step 7: Handle button press to stack the block](#step-7-handle-button-press-to-stack-the-block) - [Step 8: Place the block and check alignment](#step-8-place-the-block-and-check-alignment) - [Step 9: Draw the stack and moving block](#step-9-draw-the-stack-and-moving-block) - [Step 10: Handle win and loss conditions](#step-10-handle-win-and-loss-conditions) - [Step 11: Reset the game](#step-11-reset-the-game) - [Line-by-line explanation](#line-by-line-explanation) - [Full sketch reference](#full-sketch-reference) - [Common problems and fixes](#common-problems-and-fixes)

What you need

Step 1: Install the library

  1. Open the Arduino IDE.
  2. Go to SketchInclude LibraryManage Libraries....
  3. Search for LedControl by Eberhard Fahle.
  4. Install the library.

Explanation:

Try this:

Step 2: Wire the hardware

LED matrix wiring

Button wiring

Explanation:

Try this:

Step 3: Understand the game flow

The sketch has three main parts:

  1. setup() initializes the display, button, and game state.
  2. loop() moves the current block, checks for button presses, and ends or resets the game.
  3. helper functions handle stacking, rendering, game end, and reset.

Explanation:

Try this:

Step 4: Pin and game variables

Use these pin assignments in your sketch:

int DIN = 10;
int CLK = 13;
int CS  = 7;
const int buttonPin = 8;

LedControl lc = LedControl(DIN, CLK, CS, 1);

Game variables:

int currentRow = 7;      // Start at the bottom
int currentWidth = 3;    // Start with 3 blocks
int currentPos = 0;      // Leftmost position of the moving block
int direction = 1;       // 1 for Right, -1 for Left
bool stack[8][8];        // Memory of placed blocks

unsigned long lastMoveTime = 0;
int moveInterval = 200;  // Speed of the moving block
bool gameOver = false;

Explanation:

Try this:

Step 5: Initialize the display and inputs

void setup() {
  lc.shutdown(0, false);
  lc.setIntensity(0, 5);
  lc.clearDisplay(0);

  pinMode(buttonPin, INPUT_PULLUP);
  
  // Initialize stack as empty
  for (int r = 0; r < 8; r++) {
    for (int c = 0; c < 8; c++) stack[r][c] = false;
  }
}

Explanation:

Try this:

Step 6: Move the block automatically

unsigned long currentTime = millis();
if (currentTime - lastMoveTime >= moveInterval) {
  lastMoveTime = currentTime;
  
  currentPos += direction;
  
  // Bounce off walls
  if (currentPos + currentWidth > 8 || currentPos < 0) {
    direction *= -1;
    currentPos += (direction * 2); 
  }
  
  render();
}

Explanation:

Try this:

Step 7: Handle button press to stack the block

if (digitalRead(buttonPin) == LOW) {
  delay(200); // Debounce
  placeBlock();
  while(digitalRead(buttonPin) == LOW); // Wait for release
}

Explanation:

Try this:

Step 8: Place the block and check alignment

void placeBlock() {
  int placedCount = 0;

  for (int i = 0; i < currentWidth; i++) {
    int col = currentPos + i;
    
    // If it's the bottom row, everything stays. 
    // Otherwise, check if there is a block underneath.
    if (currentRow == 7 || stack[currentRow + 1][col]) {
      stack[currentRow][col] = true;
      placedCount++;
    }
  }

  // If no blocks landed on top of others, you lose
  if (placedCount == 0) {
    handleEnd(false);
    return;
  }

  currentWidth = placedCount; // Next row will be thinner
  currentRow--;               // Move up
  moveInterval -= 15;         // Speed up slightly

  if (currentRow < 0) {
    handleEnd(true);          // You reached the top!
  }
}

Explanation:

Try this:

Step 9: Draw the stack and moving block

void render() {
  lc.clearDisplay(0);

  // Draw the established stack
  for (int r = 0; r < 8; r++) {
    for (int c = 0; c < 8; c++) {
      if (stack[r][c]) lc.setLed(0, r, c, true);
    }
  }

  // Draw the currently moving block
  for (int i = 0; i < currentWidth; i++) {
    lc.setLed(0, currentRow, currentPos + i, true);
  }
}

Explanation:

Try this:

Step 10: Handle win and loss conditions

void handleEnd(bool win) {
  gameOver = true;
  for (int i = 0; i < 3; i++) {
    lc.clearDisplay(0);
    delay(200);
    if (win) {
      // Flash full screen for win
      for(int r=0; r<8; r++) lc.setRow(0, r, 0xFF);
    } else {
      // Simple X for loss
      lc.setRow(0, 0, 0x81); lc.setRow(0, 7, 0x81);
    }
    delay(200);
  }
}

Explanation:

Try this:

Step 11: Reset the game

void resetGame() {
  currentRow = 7;
  currentWidth = 3;
  currentPos = 0;
  moveInterval = 200;
  gameOver = false;
  for (int r = 0; r < 8; r++) {
    for (int c = 0; c < 8; c++) stack[r][c] = false;
  }
}

Explanation:

Try this:

Line-by-line explanation

Full sketch reference

Use the complete code below after you have verified wiring and library installation.

#include <LedControl.h>

// Pin Definitions
int DIN = 10;
int CLK = 13;
int CS  = 7;
const int buttonPin = 8;

LedControl lc = LedControl(DIN, CLK, CS, 1);

// Game Variables
int currentRow = 7;      // Start at the bottom
int currentWidth = 3;    // Start with 3 blocks
int currentPos = 0;      // Leftmost position of the moving block
int direction = 1;       // 1 for Right, -1 for Left
bool stack[8][8];        // Memory of placed blocks

unsigned long lastMoveTime = 0;
int moveInterval = 200;  // Speed of the moving block
bool gameOver = false;

void setup() {
  lc.shutdown(0, false);
  lc.setIntensity(0, 5);
  lc.clearDisplay(0);

  pinMode(buttonPin, INPUT_PULLUP);
  
  // Initialize stack as empty
  for (int r = 0; r < 8; r++) {
    for (int c = 0; c < 8; c++) stack[r][c] = false;
  }
}

void loop() {
  if (gameOver) {
    if (digitalRead(buttonPin) == LOW) resetGame();
    return;
  }

  // 1. Handle Movement
  unsigned long currentTime = millis();
  if (currentTime - lastMoveTime >= moveInterval) {
    lastMoveTime = currentTime;
    
    currentPos += direction;
    
    // Bounce off walls
    if (currentPos + currentWidth > 8 || currentPos < 0) {
      direction *= -1;
      currentPos += (direction * 2); 
    }
    
    render();
  }

  // 2. Handle Button Press (Stacking)
  if (digitalRead(buttonPin) == LOW) {
    delay(200); // Debounce
    placeBlock();
    while(digitalRead(buttonPin) == LOW); // Wait for release
  }
}

void placeBlock() {
  int placedCount = 0;

  for (int i = 0; i < currentWidth; i++) {
    int col = currentPos + i;
    
    // If it's the bottom row, everything stays. 
    // Otherwise, check if there is a block underneath.
    if (currentRow == 7 || stack[currentRow + 1][col]) {
      stack[currentRow][col] = true;
      placedCount++;
    }
  }

  // If no blocks landed on top of others, you lose
  if (placedCount == 0) {
    handleEnd(false);
    return;
  }

  currentWidth = placedCount; // Next row will be thinner
  currentRow--;               // Move up
  moveInterval -= 15;         // Speed up slightly

  if (currentRow < 0) {
    handleEnd(true);          // You reached the top!
  }
}

void render() {
  lc.clearDisplay(0);

  // Draw the established stack
  for (int r = 0; r < 8; r++) {
    for (int c = 0; c < 8; c++) {
      if (stack[r][c]) lc.setLed(0, r, c, true);
    }
  }

  // Draw the currently moving block
  for (int i = 0; i < currentWidth; i++) {
    lc.setLed(0, currentRow, currentPos + i, true);
  }
}

void handleEnd(bool win) {
  gameOver = true;
  for (int i = 0; i < 3; i++) {
    lc.clearDisplay(0);
    delay(200);
    if (win) {
      // Flash full screen for win
      for(int r=0; r<8; r++) lc.setRow(0, r, 0xFF);
    } else {
      // Simple X for loss
      lc.setRow(0, 0, 0x81); lc.setRow(0, 7, 0x81);
    }
    delay(200);
  }
}

void resetGame() {
  currentRow = 7;
  currentWidth = 3;
  currentPos = 0;
  moveInterval = 200;
  gameOver = false;
  for (int r = 0; r < 8; r++) {
    for (int c = 0; c < 8; c++) stack[r][c] = false;
  }
}

Common problems and fixes

Tips


Back to index