#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// Button pins
const int BTN_FEED = 2;
const int BTN_PLAY = 3;
const int BTN_SLEEP = 4;
// Buzzer pin
const int BUZZER = 8;
// Pet stats
int hunger = 50;
int happiness = 70;
int energy = 80;
int age = 0;
// Animation
int frame = 0;
unsigned long lastUpdate = 0;
unsigned long lastAnim = 0;
// Sound enabled flag
bool soundEnabled = true;
void setup() {
Serial.begin(9600);
Serial.println("Starting Virtual Pet with Sounds...");
// Setup buttons
pinMode(BTN_FEED, INPUT_PULLUP);
pinMode(BTN_PLAY, INPUT_PULLUP);
pinMode(BTN_SLEEP, INPUT_PULLUP);
// Setup buzzer
pinMode(BUZZER, OUTPUT);
// Initialize display
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println("Display failed!");
for(;;);
}
Serial.println("Display OK!");
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
// Show welcome screen with startup sound
playStartupSound();
display.setTextSize(2);
display.setCursor(10, 20);
display.println("Virtual");
display.setCursor(25, 40);
display.println("Pet!");
display.display();
delay(2000);
}
void loop() {
unsigned long now = millis();
// Update stats every 5 seconds
if (now - lastUpdate > 5000) {
hunger = min(100, hunger + 3);
happiness = max(0, happiness - 2);
energy = max(0, energy - 2);
age++;
lastUpdate = now;
// Warning beep if stats getting critical
if (hunger > 80 || happiness < 20 || energy < 20) {
playWarningSound();
}
Serial.print("H:"); Serial.print(hunger);
Serial.print(" E:"); Serial.print(happiness);
Serial.print(" T:"); Serial.println(energy);
}
// Animation frame
if (now - lastAnim > 500) {
frame = (frame + 1) % 2;
lastAnim = now;
}
// Check buttons
if (digitalRead(BTN_FEED) == LOW) {
hunger = max(0, hunger - 30);
happiness = min(100, happiness + 5);
playEatSound();
showMessage("Fed!");
delay(500);
}
if (digitalRead(BTN_PLAY) == LOW) {
if (energy > 20) {
happiness = min(100, happiness + 20);
energy = max(0, energy - 15);
playHappySound();
showMessage("Fun!");
delay(500);
} else {
playSadSound();
showMessage("Too tired!");
delay(500);
}
}
if (digitalRead(BTN_SLEEP) == LOW) {
energy = min(100, energy + 40);
playSleepSound();
showMessage("Zzz...");
delay(1000);
}
// Draw screen
display.clearDisplay();
// Check if dead
if (hunger >= 100 || happiness <= 0) {
playDeathSound();
drawDead();
} else {
drawPet();
drawStats();
drawButtons();
}
display.display();
delay(100);
}
// ============ SOUND FUNCTIONS ============
void playStartupSound() {
// Happy startup melody
tone(BUZZER, 523, 150); // C
delay(150);
tone(BUZZER, 659, 150); // E
delay(150);
tone(BUZZER, 784, 200); // G
delay(200);
}
void playEatSound() {
// Eating sound - quick ascending notes
tone(BUZZER, 400, 100);
delay(120);
tone(BUZZER, 500, 100);
delay(120);
tone(BUZZER, 600, 100);
delay(120);
}
void playHappySound() {
// Happy playful sound
tone(BUZZER, 800, 100);
delay(120);
tone(BUZZER, 1000, 100);
delay(120);
tone(BUZZER, 1200, 150);
delay(170);
}
void playSleepSound() {
// Sleepy descending melody
tone(BUZZER, 700, 200);
delay(220);
tone(BUZZER, 600, 200);
delay(220);
tone(BUZZER, 500, 300);
delay(320);
}
void playSadSound() {
// Sad descending notes
tone(BUZZER, 400, 200);
delay(220);
tone(BUZZER, 300, 300);
delay(320);
}
void playWarningSound() {
// Warning beep
tone(BUZZER, 800, 100);
delay(150);
tone(BUZZER, 800, 100);
delay(150);
}
void playDeathSound() {
// Sad death sound (only plays once)
static bool deathSoundPlayed = false;
if (!deathSoundPlayed) {
tone(BUZZER, 500, 300);
delay(320);
tone(BUZZER, 400, 300);
delay(320);
tone(BUZZER, 300, 500);
delay(520);
deathSoundPlayed = true;
}
}
// ============ DRAWING FUNCTIONS ============
void drawPet() {
int x = 64;
int y = 30;
// Body
display.fillCircle(x, y, 12, SSD1306_WHITE);
// Determine face based on stats
if (hunger > 70) {
// Hungry face
display.fillCircle(x-4, y-2, 2, SSD1306_BLACK);
display.fillCircle(x+4, y-2, 2, SSD1306_BLACK);
display.fillCircle(x, y+4, 3, SSD1306_BLACK); // Open mouth
} else if (energy < 30) {
// Tired face
display.drawLine(x-5, y-2, x-2, y-2, SSD1306_BLACK);
display.drawLine(x+2, y-2, x+5, y-2, SSD1306_BLACK);
if (frame == 0) {
display.setCursor(x+12, y-8);
display.setTextSize(1);
display.print("Z");
}
} else if (happiness < 40) {
// Sad face
display.fillCircle(x-4, y-2, 2, SSD1306_BLACK);
display.fillCircle(x+4, y-2, 2, SSD1306_BLACK);
display.drawLine(x-3, y+6, x, y+4, SSD1306_BLACK);
display.drawLine(x, y+4, x+3, y+6, SSD1306_BLACK);
} else {
// Happy face
display.fillCircle(x-4, y-2, 2, SSD1306_BLACK);
display.fillCircle(x+4, y-2, 2, SSD1306_BLACK);
display.drawLine(x-5, y+3, x-2, y+5, SSD1306_BLACK);
display.drawLine(x-2, y+5, x+2, y+5, SSD1306_BLACK);
display.drawLine(x+2, y+5, x+5, y+3, SSD1306_BLACK);
// Bounce animation
if (frame == 0) y -= 2;
}
// Feet
display.fillCircle(x-8, y+13, 3, SSD1306_WHITE);
display.fillCircle(x+8, y+13, 3, SSD1306_WHITE);
// Draw musical note if happy
if (happiness > 70 && frame == 0) {
display.setCursor(x+15, y-5);
display.setTextSize(1);
display.print((char)3); // Musical note character
}
}
void drawStats() {
display.setTextSize(1);
// Hunger (inverted - lower is better)
display.setCursor(0, 0);
display.print("H:");
int h = map(100-hunger, 0, 100, 0, 20);
display.drawRect(12, 0, 22, 6, SSD1306_WHITE);
display.fillRect(13, 1, h, 4, SSD1306_WHITE);
// Happiness
display.setCursor(38, 0);
display.print("E:");
int e = map(happiness, 0, 100, 0, 20);
display.drawRect(50, 0, 22, 6, SSD1306_WHITE);
display.fillRect(51, 1, e, 4, SSD1306_WHITE);
// Energy
display.setCursor(76, 0);
display.print("T:");
int t = map(energy, 0, 100, 0, 20);
display.drawRect(88, 0, 22, 6, SSD1306_WHITE);
display.fillRect(89, 1, t, 4, SSD1306_WHITE);
// Age
display.setCursor(114, 0);
display.print(age);
}
void drawButtons() {
display.setTextSize(1);
display.setCursor(0, 56);
display.print("Feed");
display.setCursor(44, 56);
display.print("Play");
display.setCursor(88, 56);
display.print("Sleep");
}
void drawDead() {
display.setTextSize(2);
display.setCursor(20, 15);
display.println("R.I.P.");
display.setTextSize(1);
display.setCursor(10, 35);
display.println("Pet has died");
display.setCursor(5, 50);
display.println("Reset to restart");
}
void showMessage(const char* msg) {
display.clearDisplay();
display.setTextSize(2);
display.setCursor(20, 25);
display.println(msg);
display.display();
}