01 March 2026

Scaffolding: Mapping - Analog Team 5 "Meltdown"

Summary

    Meltdown is a reaction game built on the Adafruit Circuit Playground Express where you are trying to keep a nuclear reactor from melting down. When you flip the switch on, a red NeoPixel appears somewhere on the ring and that is your target. Use the potentiometer to slide a green pixel around the ring and line it up exactly with the red one. When you have got them matched, hit the breadboard button to confirm and score the point. Nail it and all the pixels flash green before a new target spawns somewhere random. Hit ten successful alignments and you win. The catch is that orange warning pixels start filling the ring the moment a new target appears, and if they fill up before you confirm your alignment the reactor overloads. When that happens everything flashes red and you have to physically shake the board to vent the reactor and get back in the game. It is a simple concept but the timer keeps the pressure on, and the shaking mechanic makes recoveries feel pretty satisfying. Flip the switch off at any point to reset and start over.

Image

Video Example

Schematic

Code

#include <Adafruit_CircuitPlayground.h>

//Definitions
#define POT_PIN    A2  
#define BUTTON_PIN 10  

//Game Settings
#define WIN_SCORE        10      
#define OVERLOAD_TIME    5000    
#define SHAKE_THRESHOLD  12.0  
//Game States
enum GameState { PLAYING, OVERLOAD, WIN };
GameState gameState = PLAYING;

//Game Variables
int score          = 0;    
int targetPixel    = 0;    
int greenPixel     = 0;    

unsigned long roundStartTime = 0;    
bool buttonWasPressed        = false;

//SETUP
void setup() {
  CircuitPlayground.begin();
  CircuitPlayground.setBrightness(40);

  pinMode(BUTTON_PIN, INPUT);

  //Seed random number generator
  randomSeed(analogRead(A0));

  delay(1000);
  startNewRound();
}

// LOOP
void loop() {

  //Switch Off
  if (!gameEnabled()) {
    // Reset game so it starts fresh when switched back on
    score = 0;
    gameState = PLAYING;
    CircuitPlayground.clearPixels();
    showIdleState();
    return;
  }

  //Switch On
  static bool wasOff = true;
  if (wasOff) {
    startNewRound();
    wasOff = false;
  }

  if (gameState == PLAYING) {

    //Read Potentiometer and Map to Pixel Position
    int potValue = analogRead(POT_PIN);
    greenPixel = map(potValue, 0, 1023, 0, 9);

    //Check Overload Timer
    if (millis() - roundStartTime > OVERLOAD_TIME) {
      gameState = OVERLOAD;
      return;
    }

    //Reactor Display
    showPlayingState();

    //Read Button for Alignment Confirmation
    bool buttonPressed = digitalRead(BUTTON_PIN);

    //Only trigger on a fresh press, not a held button
    if (buttonPressed && !buttonWasPressed) {
      if (greenPixel == targetPixel) {
        //SUCCESS
        score++;
        if (score >= WIN_SCORE) {
          gameState = WIN;
        } else {
          flashSuccess();
          startNewRound();
        }
      }
     
    }

    buttonWasPressed = buttonPressed;
  }

  //Overload State: flash red, wait for shake
  else if (gameState == OVERLOAD) {
    flashOverload();

    if (isShaking()) {
      //reset the round timer and resume play
      roundStartTime = millis();
      gameState = PLAYING;
      delay(500);
    }
  }

  //Win State
  else if (gameState == WIN) {
    flashWin();

    //After winning, flipping switch off and back on restarts
    wasOff = true;
  }

  //Track switch state
  if (!gameEnabled()) wasOff = true;
}

//GAME ENABLED
//Reads the D7 slide switch. Returns true if game should run.

bool gameEnabled() {
  return CircuitPlayground.slideSwitch();
}

//START NEW ROUND
//Assigns a new random target pixel and resets the timer

void startNewRound() {
  targetPixel      = random(0, 10);
  roundStartTime   = millis();
  buttonWasPressed = false;
}

//PLAYING STATE
//Displays the red target pixel and green player pixel.
//Yellow/orange pixels fill in over time as a warning the overload


void showPlayingState() {
  CircuitPlayground.clearPixels();
  unsigned long elapsed = millis() - roundStartTime;
  int warningPixels = map(elapsed, 0, OVERLOAD_TIME, 0, 8);

  int filled = 0;
  for (int p = 0; p < 10 && filled < warningPixels; p++) {
    if (p != targetPixel && p != greenPixel) {
      CircuitPlayground.setPixelColor(p, 80, 30, 0);
      filled++;
    }
  }

  if (greenPixel == targetPixel) {
    CircuitPlayground.setPixelColor(targetPixel, 255, 200, 0);
  } else {
    CircuitPlayground.setPixelColor(targetPixel, 255, 0, 0);  
    CircuitPlayground.setPixelColor(greenPixel,  0, 255, 0);  
  }
}

//IDLE STATE
//Displayed when the switch is off

void showIdleState() {
  for (int p = 0; p < 10; p++) {
    CircuitPlayground.setPixelColor(p, 0, 0, 20);
  }
  delay(500);
  CircuitPlayground.clearPixels();
  delay(500);
}

//FLASH SUCCESS
//All pixels flash green 3 times on a successful alignment

void flashSuccess() {
  for (int i = 0; i < 3; i++) {
    for (int p = 0; p < 10; p++) {
      CircuitPlayground.setPixelColor(p, 0, 255, 0);
    }
    delay(200);
    CircuitPlayground.clearPixels();
    delay(200);
  }
}

//FLASH OVERLOAD
//All pixels flash red while in the overload state.

void flashOverload() {
  for (int p = 0; p < 10; p++) {
    CircuitPlayground.setPixelColor(p, 255, 0, 0);
  }
  delay(150);
  CircuitPlayground.clearPixels();
  delay(150);
}

//FLASH WIN
//All pixels flash green when the player wins

void flashWin() {
  for (int p = 0; p < 10; p++) {
    CircuitPlayground.setPixelColor(p, 0, 255, 0);
  }
  delay(400);
  CircuitPlayground.clearPixels();
  delay(400);
}

//SHAKE DETECTION
//Reads all three accelerometers and checks if shaken

bool isShaking() {
  float x = CircuitPlayground.motionX();
  float y = CircuitPlayground.motionY();
  float z = CircuitPlayground.motionZ();
  float shake = sqrt(x * x + y * y + z * z);
  return shake > SHAKE_THRESHOLD;
}

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.