#include <LiquidCrystal.h>
#include <EEPROM.h>
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
const int btnLeft = 8,
const int btnRight = 9,
const int btnSpeed = 10,
const int btnReset = 6,
const int btnSaveToggle = 1,
const int buzzer = 7;
// Custom Sprites
byte arrowChar[] = { B00000, B10000, B11000, B11100, B11110, B11100, B11000, B10000 };
byte shield1Player[] = { B00001, B10001, B11001, B11101, B11111, B11101, B11001, B10001 };
byte rockChar[] = { B01110, B11111, B11111, B11111, B11111, B11111, B11111, B01110 };
byte balloonChar[] = { B01110, B10001, B10001, B01110, B00100, B00100, B00010, B00100 };
byte shieldItem[] = { B00100, B00100, B00100, B00100, B00100, B00100, B00100, B00100 };
byte shield2Player[] = { B00011, B10011, B11011, B11111, B11111, B11111, B11011, B10011 };
enum Type { ROCK, BALLOON, SHIELD_UP };
struct Entity { int row, col, oldRow, oldCol; Type type; bool active; };
const int MAX_ENTITIES = 4;
Entity entities[MAX_ENTITIES];
int score = 0, highScore = 0, playerPos = 0, oldPlayerPos = 0, gameSpeed = 220, shieldCount = 0, speedClicks = 0;
bool gameOver = true, frenzyMode = false, frenzyUsed = false, allowSave = true, showBest = false, isMuted = false;
unsigned long lastUpdate = 0, frenzyStartTime = 0, lastEndScreenSwap = 0, lastNoteTime = 0, musicMuteUntil = 0;
// I didn't want this part to be repeating over and over so I made it a four-part symphony.
// The notes were made with AI because I'm not very musically oriented.
int longSymphony[] = {
294, 330, 349, 392, 440, 392, 349, 330,
294, 440, 523, 587, 523, 440, 392, 349,
440, 440, 392, 349, 330, 294, 262, 294,
587, 523, 440, 392, 440, 349, 330, 294
};
int currentNote = 0;
void setup() {
pinMode(btnLeft, INPUT_PULLUP); pinMode(btnRight, INPUT_PULLUP);
pinMode(btnSpeed, INPUT_PULLUP); pinMode(btnReset, INPUT_PULLUP);
pinMode(btnSaveToggle, INPUT_PULLUP); pinMode(buzzer, OUTPUT);
highScore = EEPROM.read(0);
lcd.begin(16, 2);
lcd.createChar(0, arrowChar); lcd.createChar(1, shield1Player);
lcd.createChar(2, rockChar); lcd.createChar(3, balloonChar);
lcd.createChar(4, shieldItem); lcd.createChar(5, shield2Player);
showIntro();
}
void safeTone(int pin, int freq, int dur) {
if (!isMuted) tone(pin, freq, dur);
}
void showIntro() {
lcd.clear(); lcd.setCursor(1, 0); lcd.print("RISE & RUBBLE");
for (int i = 0; i < 16; i++) {
safeTone(buzzer, longSymphony[i % 32], 120);
lcd.setCursor(0, 1); for(int c=0; c<16; c++) lcd.print(" ");
lcd.setCursor(0, 1); lcd.write(byte(0));
int rockPos = 15 - i;
if (rockPos >= 0) { lcd.setCursor(rockPos, 1); lcd.write(byte(2)); }
delay(180);
}
noTone(buzzer);
lcd.clear();
while (digitalRead(btnReset) == HIGH) {
checkSaveAndMuteButtons();
if ((millis() / 500) % 2 == 0) { lcd.setCursor(2, 0); lcd.print("PRESS START"); }
else { lcd.setCursor(2, 0); lcd.print(" "); }
delay(10);
}
safeTone(buzzer, 440, 400);
resetGame();
}
void checkSaveAndMuteButtons() {
if (digitalRead(btnSaveToggle) == LOW) {
unsigned long pressStart = millis();
while (digitalRead(btnSaveToggle) == LOW) {
if (millis() - pressStart > 1000) {
isMuted = !isMuted;
lcd.clear(); lcd.setCursor(5, 0); lcd.print(isMuted ? "SOUND OFF" : "SOUND ON");
delay(800); lcd.clear(); return;
}
}
allowSave = !allowSave;
lcd.clear(); lcd.setCursor(3, 0); lcd.print(allowSave ? "SAVE ON" : "SAVE OFF");
delay(800); lcd.clear();
}
}
void playMusic() {
if (millis() < musicMuteUntil || isMuted) return;
int noteDuration = 140; // Fast Irish Jig tempo
if (millis() - lastNoteTime > noteDuration + 20) {
lastNoteTime = millis();
safeTone(buzzer, longSymphony[currentNote], noteDuration);
currentNote = (currentNote + 1) % 32;
}
}
void loop() {
if (digitalRead(btnReset) == LOW) { safeTone(buzzer, 294, 150); delay(250); resetGame(); }
checkSaveAndMuteButtons();
if (gameOver) {
if (millis() - lastEndScreenSwap > 2000) {
lastEndScreenSwap = millis(); showBest = !showBest; lcd.clear();
if (showBest) { lcd.setCursor(3, 0); lcd.print("BEST SCORE"); lcd.setCursor(7, 1); lcd.print(highScore); }
else { lcd.setCursor(3, 0); lcd.print("YOUR SCORE"); lcd.setCursor(7, 1); lcd.print(score); }
}
return;
}
playMusic();
if (digitalRead(btnSpeed) == LOW) {
musicMuteUntil = millis() + 500; safeTone(buzzer, 880, 100);
speedClicks = (speedClicks + 1) % 5; gameSpeed = 220 - (speedClicks * 35);
lcd.clear(); lcd.setCursor(4, 0); lcd.print("LEVEL "); lcd.print(speedClicks + 1);
delay(400); lcd.clear();
}
if (digitalRead(btnLeft) == LOW && playerPos != 0) { musicMuteUntil = millis() + 80; safeTone(buzzer, 1800, 20); playerPos = 0; }
if (digitalRead(btnRight) == LOW && playerPos != 1) { musicMuteUntil = millis() + 80; safeTone(buzzer, 1800, 20); playerPos = 1; }
if (millis() - lastUpdate > gameSpeed) { lastUpdate = millis(); updateGame(); drawGame(); }
}
void updateGame() {
if (!frenzyUsed && score >= 100) {
frenzyMode = true; frenzyUsed = true; frenzyStartTime = millis(); clearEntities();
musicMuteUntil = millis() + 1000;
for(int i=0; i<4; i++) { safeTone(buzzer, 880 + (i*200), 100); delay(150); }
}
if (frenzyMode && (millis() - frenzyStartTime > 10000)) { frenzyMode = false; clearEntities(); }
for (int i = 0; i < MAX_ENTITIES; i++) {
if (entities[i].active) {
entities[i].oldRow = entities[i].row; entities[i].oldCol = entities[i].col; entities[i].row--;
if (entities[i].row == 0 && entities[i].col == playerPos) handleCollision(i);
else if (entities[i].row < 0) { entities[i].active = false; if (entities[i].type == ROCK) score++; }
} else if (random(0, 100) < (frenzyMode ? 60 : 15)) spawnEntity(i);
}
}
void drawGame() {
if (playerPos != oldPlayerPos) { lcd.setCursor(0, oldPlayerPos); lcd.print(" "); oldPlayerPos = playerPos; }
for (int i = 0; i < MAX_ENTITIES; i++) {
if (entities[i].oldRow >= 0) { lcd.setCursor(entities[i].oldRow, entities[i].oldCol); lcd.print(" "); entities[i].oldRow = -1; }
}
lcd.setCursor(0, playerPos); lcd.write(byte(shieldCount == 0 ? 0 : (shieldCount == 1 ? 1 : 5)));
for (int i = 0; i < MAX_ENTITIES; i++) {
if (entities[i].active) { lcd.setCursor(entities[i].row, entities[i].col); lcd.write(entities[i].type == ROCK ? 2 : (entities[i].type == BALLOON ? 3 : 4)); }
}
lcd.setCursor(14, 0); if (score < 100) lcd.print(" "); if (score < 10) lcd.print(" "); lcd.print(score);
}
void handleCollision(int i) {
musicMuteUntil = millis() + 500;
if (entities[i].type == ROCK) {
if (shieldCount > 0) { shieldCount--; score = max(0, score - 5); safeTone(buzzer, 200, 200); }
else {
noTone(buzzer);
for(int f=400; f>100; f-=20) { safeTone(buzzer, f, 80); delay(90); }
gameOver = true;
if (score > highScore) { highScore = score; if (allowSave) EEPROM.update(0, highScore); }
lcd.setCursor(0, playerPos); lcd.write(byte(2));
delay(1000);
lcd.clear();
}
} else {
safeTone(buzzer, (entities[i].type == BALLOON ? 2200 : 2800), 80);
if (entities[i].type == BALLOON) score += 5; else shieldCount++;
}
entities[i].active = false;
}
void spawnEntity(int i) {
for (int j = 0; j < MAX_ENTITIES; j++) { if (entities[j].active && entities[j].row > 13) return; }
entities[i].active = true; entities[i].row = 15; entities[i].col = random(0, 2); entities[i].oldRow = -1;
entities[i].type = frenzyMode ? BALLOON : (random(100) < 80 ? ROCK : (random(100) < 70 ? BALLOON : SHIELD_UP));
}
void clearEntities() { for (int i = 0; i < MAX_ENTITIES; i++) { entities[i].active = false; entities[i].oldRow = -1; } lcd.clear(); }
void resetGame() {
score = 0; speedClicks = 0; gameSpeed = 220; shieldCount = 0;
gameOver = false; frenzyMode = false; frenzyUsed = false; currentNote = 0; musicMuteUntil = 0;
clearEntities();
}