Arnov Sharma
Published © MIT

QuasarQ

An ESP32 Powered Space-Themed Interactive Quiz Console

BeginnerFull instructions provided1 hour10
QuasarQ

Things used in this project

Hardware components

Espressif esp32 S3 Display
×1
NextPCB Custom PCB
×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

STEP FILE

Schematics

WIRING

Code

code

C/C++
#include <Arduino_GFX_Library.h>

// --- Color Definitions ---
#define BLACK     0x0000
#define WHITE     0xFFFF
#define RED       0xF800
#define GREEN     0x07E0

// --- Display Setup ---
Arduino_DataBus *bus = new Arduino_ESP32SPI(
  4 /* DC */, 5 /* CS */, 6 /* SCK */, 7 /* MOSI */, GFX_NOT_DEFINED /* MISO */
);

Arduino_GFX *gfx = new Arduino_ST7789(
  bus, 8 /* RST */, 3 /* rotation */, true /* IPS */,
  240, 280, 0, 20, 0, 20
);

#define LCD_BL 15

// --- Button Pins ---
#define BTN_A 18
#define BTN_B 16
#define BTN_C 2
#define BTN_D 10

// --- Screen / layout constants ---
#define SCREEN_W 240
#define SCREEN_H 280

// Layout adjustments
const int QUESTION_X = 5;
const int QUESTION_Y = 17;          // question Y shifted down 5 pixels
const int OPTION_START_Y = 80;      // vertical start for first option
const int OPTION_SPACING = 45;      // vertical spacing between options
const int LETTER_SIZE = 3;          // medium-large letters (A/B/C/D)
const int CENTER_X = SCREEN_W / 2;

// --- Quiz Data (ISS question updated with full names) ---
struct Question {
  const char* text;
  const char* options[4];
  int correctIndex;
};

Question questions[20] = {
  {"Which planet is the Red Planet?", {"Venus", "Mars", "Jupiter", "Saturn"}, 1},
  {"Largest planet?", {"Earth", "Neptune", "Jupiter", "Uranus"}, 2},
  {"Most moons?", {"Saturn", "Mars", "Earth", "Mercury"}, 0},
  {"Our galaxy?", {"Andromeda", "Whirlpool", "Milky Way", "Sombrero"}, 2},
  {"First person in space?", {"Armstrong", "Gagarin", "Aldrin", "Shepard"}, 1},
  {"Closest star?", {"Sirius", "Alpha Centauri", "Sun", "Betelgeuse"}, 2},
  {"Moon landing year?", {"1965", "1969", "1972", "1959"}, 1},
  {"Planet with rings?", {"Mars", "Venus", "Saturn", "Earth"}, 2},
  {"Smallest planet?", {"Mercury", "Mars", "Venus", "Pluto"}, 0},
  {"Hubble is a...?", {"Satellite", "Probe", "Telescope", "Rover"}, 2},
  {"Which is a dwarf planet?", {"Neptune", "Pluto", "Jupiter", "Saturn"}, 1},
  {"Mars rover name?", {"Spirit", "Apollo", "Voyager", "Hubble"}, 0},
  {"Brightest planet?", {"Venus", "Mars", "Jupiter", "Saturn"}, 0},
  {"First Indian in space?", {"Kalpana", "Rakesh Sharma", "Sunita", "Vikram"}, 1},
  {"Planet with day longer than year?", {"Venus", "Earth", "Mars", "Jupiter"}, 0},
  {"What does ISS stand for?", {"International Space Station", "National Aeronautics and Space Administration", "European Space Agency", "Indian Space Research Organisation"}, 0},
  {"Most volcanoes?", {"Earth", "Venus", "Mars", "Io"}, 3},
  {"Farthest planet?", {"Neptune", "Uranus", "Pluto", "Saturn"}, 0},
  {"Not a planet?", {"Ceres", "Mars", "Earth", "Neptune"}, 0},
  {"No moons?", {"Mercury", "Mars", "Venus", "Earth"}, 0}
};

int currentQuestion = 0;
int score = 0;

// --- Button press / long press handling ---
unsigned long pressStart = 0;
bool holdingA = false;

// --- Sleep flag (display-off sleep) ---
bool isSleeping = false;
unsigned long sleepStartTime = 0;
const unsigned long SLEEP_LOCK_DELAY = 500; // lock after entering "sleep"

// --- Quiz active flag ---
bool quizActive = false;

void setup() {
  pinMode(BTN_A, INPUT_PULLUP);
  pinMode(BTN_B, INPUT_PULLUP);
  pinMode(BTN_C, INPUT_PULLUP);
  pinMode(BTN_D, INPUT_PULLUP);
  pinMode(LCD_BL, OUTPUT);
  digitalWrite(LCD_BL, HIGH);

  gfx->begin();
  gfx->fillScreen(BLACK);
  gfx->setTextWrap(true);
  gfx->setTextSize(1);
  gfx->setTextColor(WHITE);

  showSplash();
}

void loop() {
  bool btnA = !digitalRead(BTN_A);
  bool btnB = !digitalRead(BTN_B);
  bool btnC = !digitalRead(BTN_C);
  bool btnD = !digitalRead(BTN_D);

  // --- If display is "sleeping" (backlight off) ---
  if (isSleeping) {
    // allow a small lock window so the same long-press doesn't immediately wake
    if (millis() - sleepStartTime > SLEEP_LOCK_DELAY) {
      // Wake only on Button A (per your request)
      if (btnA) {
        digitalWrite(LCD_BL, HIGH);
        isSleeping = false;
        showWakeUpMessage();
        showSplash();
        quizActive = false; // wait for user to press to start
      }
    }
    delay(50);
    return;
  }

  // --- Start quiz from splash (splash stays until button pressed) ---
  if (!quizActive) {
    if (btnA || btnB || btnC || btnD) {
      quizActive = true;
      currentQuestion = 0;
      score = 0;
      showQuestion();
    }
    delay(50);
    return;
  }

  // --- While quizActive handle inputs ---
  // Button A: detect long-press for "sleep", short press for answer A
  if (btnA) {
    if (!holdingA) {
      pressStart = millis();
      holdingA = true;
    } else if (millis() - pressStart > 1500) {
      // long press detected -> enter display-off "sleep"
      enterSleepMode();
    }
  } else {
    if (holdingA) {
      // button released: treat as short press if it wasn't long
      if (millis() - pressStart < 1500) {
        checkAnswer(0);
        nextQuestion();
      }
      holdingA = false;
    }
  }

  // Other buttons: immediate selection
  if (btnB) { checkAnswer(1); nextQuestion(); }
  if (btnC) { checkAnswer(2); nextQuestion(); }
  if (btnD) { checkAnswer(3); nextQuestion(); }

  delay(50); // small debounce
}

void enterSleepMode() {
  // stop treating A as held
  holdingA = false;
  isSleeping = true;
  sleepStartTime = millis();

  // turn off backlight and show going to sleep message
  digitalWrite(LCD_BL, LOW);
  gfx->fillScreen(BLACK);
  gfx->setTextSize(2);
  gfx->setTextColor(WHITE);
  gfx->setCursor(40, 100);
  gfx->println("Going to sleep...");
}

void showWakeUpMessage() {
  gfx->fillScreen(BLACK);
  gfx->setTextSize(2);
  gfx->setTextColor(WHITE);
  gfx->setCursor(40, 100);
  gfx->println("Good morning!");
  delay(1500);
}

void nextQuestion() {
  delay(1200);
  currentQuestion++;
  if (currentQuestion < (int)(sizeof(questions)/sizeof(questions[0]))) {
    showQuestion();
  } else {
    showFinalScore();
  }
}

void showSplash() {
  gfx->fillScreen(BLACK);
  gfx->setTextSize(3);
  gfx->setTextColor(WHITE);
  gfx->setCursor(40, 80);
  gfx->println("SPACE QUIZ");

  gfx->setTextSize(1);
  gfx->setCursor(60, 150);
  gfx->println("Press any button");
}

void showQuestion() {
  gfx->fillScreen(BLACK);

  // Show question
  gfx->setTextWrap(true);
  gfx->setTextSize(2);
  gfx->setTextColor(WHITE);
  gfx->setCursor(QUESTION_X, QUESTION_Y);
  gfx->println(questions[currentQuestion].text);

  // --- Vertical options with medium letter and text, centered ---
  int y = OPTION_START_Y;
  for (int i = 0; i < 4; i++) {
    gfx->setTextSize(LETTER_SIZE);
    gfx->setTextColor(WHITE);

    String line = String((char)('A' + i)) + " - " + questions[currentQuestion].options[i];

    int16_t bx, by;
    uint16_t bw, bh;
    gfx->getTextBounds(line, 0, 0, &bx, &by, &bw, &bh);
    int startX = (SCREEN_W - bw) / 2;

    gfx->setCursor(startX, y);
    gfx->print(line);

    y += OPTION_SPACING;
  }
}

void checkAnswer(int selected) {
  gfx->fillScreen(BLACK);
  gfx->setTextSize(3);
  gfx->setCursor(40, 80);

  if (selected == questions[currentQuestion].correctIndex) {
    gfx->setTextColor(GREEN);
    gfx->println("Correct!");
    score++;
  } else {
    gfx->setTextColor(RED);
    gfx->println("Wrong!");
    gfx->setTextSize(2);
    gfx->setCursor(20, 150);
    gfx->setTextColor(WHITE);
    gfx->printf("Answer: %s", questions[currentQuestion].options[questions[currentQuestion].correctIndex]);
  }
}

void showFinalScore() {
  gfx->fillScreen(BLACK);
  gfx->setTextColor(WHITE);
  gfx->setTextSize(3);
  gfx->setCursor(40, 80);
  gfx->println("QUIZ DONE!");

  gfx->setTextSize(2);
  gfx->setCursor(60, 150);
  gfx->printf("Score: %d / %d", score, (int)(sizeof(questions)/sizeof(questions[0])));

  // Wait 10 seconds then reset to splash
  unsigned long start = millis();
  while (millis() - start < 10000) {
    delay(50);
  }

  quizActive = false;
  currentQuestion = 0;
  score = 0;
  showSplash();
}

Credits

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

Comments