#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
#define BUTTON_PIN 2
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// Torch & puddle settings
const int torchX = 64;
const int puddleMin = 2;
const int puddleMax = 10;
const int puddleBuild = 1;
int puddleSize = puddleMin;
int puddleFrameCount = 0;
// Heat bar
const int heatBarHeight = 5;
const int warnPercent = 50;
const int warnPixelX = (SCREEN_WIDTH * warnPercent) / 100;
const int warnThreshold = 6;
// Game state
float scrollOffset = 0;
bool isGameOver = false;
bool gameStarted = false;
enum FailType { NONE, OVERHEAT, OVERFEED } failReason = NONE;
// Score system
int score = 0;
// Button tracking
bool buttonPrevState = HIGH;
unsigned long pressStart = 0;
const unsigned long holdFailMs = 1000;
// Beads
const int beadCap = 64;
int beadX[beadCap];
int beadY[beadCap];
int beadR[beadCap];
int nextBead = 0;
// Sparks
const int maxSparks = 5;
int sparkX[maxSparks];
int sparkY[maxSparks];
int sparkDX[maxSparks];
int sparkDY[maxSparks];
int sparkLife[maxSparks];
void setup() {
pinMode(BUTTON_PIN, INPUT_PULLUP);
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.clearDisplay();
display.display();
}
void loop() {
if (!gameStarted) {
static bool shown = false;
if (!shown) {
drawIntroScreen();
shown = true;
}
if (digitalRead(BUTTON_PIN) == LOW) {
delay(300);
gameStarted = true;
resetGame();
}
} else if (isGameOver) {
drawGameOver();
if (digitalRead(BUTTON_PIN) == LOW) {
delay(300);
resetGame();
}
} else {
updateGame();
drawGame();
}
delay(50);
}
void updateGame() {
scrollOffset += 1;
puddleFrameCount++;
if (puddleFrameCount >= 2) {
puddleSize += puddleBuild;
puddleFrameCount = 0;
}
if (puddleSize >= puddleMax) {
puddleSize = puddleMax;
isGameOver = true;
failReason = OVERHEAT;
return;
}
bool buttonState = (digitalRead(BUTTON_PIN) == LOW);
if (buttonState && !buttonPrevState) {
pressStart = millis();
}
if (buttonState) {
if (millis() - pressStart >= holdFailMs) {
isGameOver = true;
failReason = OVERFEED;
return;
}
}
if (!buttonState && buttonPrevState) {
unsigned long heldTime = millis() - pressStart;
if (heldTime < holdFailMs) {
if (puddleSize <= warnThreshold) {
isGameOver = true;
failReason = OVERFEED;
return;
}
if (puddleSize == 6) score += 2;
else if (puddleSize == 7) score += 1;
else if (puddleSize >= 8 && puddleSize < puddleMax) score += 3;
beadX[nextBead] = torchX + scrollOffset;
beadY[nextBead] = 57;
beadR[nextBead] = puddleSize;
nextBead = (nextBead + 1) % beadCap;
puddleSize = puddleMin;
}
}
buttonPrevState = buttonState;
// Sparks
static int sparkFrameCounter = 0;
sparkFrameCounter++;
if (sparkFrameCounter >= 2) {
sparkFrameCounter = 0;
for (int i = 0; i < maxSparks; i++) {
if (sparkLife[i] <= 0) {
sparkX[i] = torchX + random(-1, 2);
sparkY[i] = 47;
sparkDX[i] = random(-2, 3);
sparkDY[i] = -1 - random(2);
sparkLife[i] = 4;
break;
}
}
}
for (int i = 0; i < maxSparks; i++) {
if (sparkLife[i] > 0) {
sparkX[i] += sparkDX[i];
sparkY[i] += sparkDY[i];
sparkLife[i]--;
}
}
}
void drawGame() {
display.clearDisplay();
int barWidth = map(puddleSize, puddleMin, puddleMax, 0, SCREEN_WIDTH);
display.drawRect(0, 0, SCREEN_WIDTH, heatBarHeight, SSD1306_WHITE);
display.fillRect(1, 1, barWidth - 2, heatBarHeight - 2, SSD1306_WHITE);
display.drawFastVLine(warnPixelX, 0, heatBarHeight, SSD1306_WHITE);
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(SCREEN_WIDTH - 48, heatBarHeight + 2);
display.print(F("Score:"));
display.print(score);
drawTorch();
for (int x = 0; x < SCREEN_WIDTH; x++) {
display.drawPixel(x, 57, SSD1306_WHITE);
}
for (int i = 0; i < beadCap; ++i) {
int sx = beadX[i] - scrollOffset;
int sy = beadY[i];
if (sx >= 0 && sx < SCREEN_WIDTH &&
sy >= heatBarHeight && sy < SCREEN_HEIGHT)
display.fillCircle(sx, sy, beadR[i], SSD1306_WHITE);
}
display.fillCircle(torchX, 57, puddleSize, SSD1306_WHITE);
for (int i = 0; i < maxSparks; i++) {
if (sparkLife[i] > 0 &&
sparkX[i] >= 0 && sparkX[i] < SCREEN_WIDTH &&
sparkY[i] >= 0 && sparkY[i] < SCREEN_HEIGHT) {
display.drawPixel(sparkX[i], sparkY[i], SSD1306_WHITE);
}
}
display.display();
}
void drawTorch() {
display.fillRect(torchX - 4, 13, 10, 18, SSD1306_WHITE); // handle
for (int dx = 0; dx <= 1; dx++) {
display.drawLine(torchX + dx, 31, torchX + dx, 46, SSD1306_WHITE);
}
display.drawPixel(torchX, 47, SSD1306_WHITE);
}
void drawGameOver() {
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
if (failReason == OVERHEAT) {
display.setCursor(24, 24);
display.println(F("TOO MUCH HEAT"));
} else {
display.setCursor(28, 24);
display.println(F("FED TOO MUCH!"));
}
display.setCursor(38, 34);
display.print(F("Score: "));
display.println(score);
display.setCursor(34, 48);
display.println(F("Press BUTTON"));
display.display();
}
void drawIntroScreen() {
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
display.setTextSize(2);
display.setCursor(24, 16);
display.println(F("TIG SIM"));
display.setTextSize(1);
display.setCursor(28, 48);
display.println(F("Press to start"));
display.display();
}
void resetGame() {
scrollOffset = 0;
puddleSize = puddleMin;
puddleFrameCount = 0;
isGameOver = false;
failReason = NONE;
buttonPrevState = HIGH;
nextBead = 0;
score = 0;
for (int i = 0; i < beadCap; ++i) beadR[i] = 0;
for (int i = 0; i < maxSparks; i++) sparkLife[i] = 0;
}