Pet Pals (tamagotchi)

by Paras Sandhu in Circuits > Arduino

258 Views, 5 Favorites, 0 Comments

Pet Pals (tamagotchi)

IMG_4234.jpeg
IMG_4200.jpeg

I made pet pal which is similar to what you can find in a tamagotchi and was the original inspiration for this project. I made “Pet Pals” more interactive, forcing the player to physically interact with the pet to keep it alive and happy.

“Pet Pals” has a variety of content to explore, such as a feeding mini game, playing mini game, sleeping and petting actions, as well as a decaying health bar.

Supplies

IMG_9090.jpeg
IMG_9092.jpeg

Pet Pal

IMG_4234.jpeg
IMG_4153.jpeg
IMG_4154.jpeg

We first need to make our actual Pet Pal. I’ve made by Pet Pal into a dog but you can do any pet based on your preference. Don't forget to decorate it!

Keep the back of the pal empty so you can add a servo which will to a piece of cardboard sticking acting as a tail. This tail (servo) will be later coded to wag when you successfully take care of the pet or any other actions. This gives the pet much more life, making it feel real.

Happiness Meter

IMG_4174.jpeg

This is your pet’s happiness meter. The point of this contraption is to display the happiness of your pet at all times. For our game to have any purpose, this servo will very slowly keep moving making its way to the far right indicating your pet’s death.

Through coding, we will make it so that winning a mini-game, petting, or putting your pet to sleep will increase this bar towards to the left (indicated by the heart). This mechanism will prompt the user to play a game or care for the pet in order to keep it alive and to the left half of the happiness meter.

Components:

-1x Servo

-Lego (make it look better)

-Paper (Used to indicate what the 2 sides are, the far left in my case represents happiness and the far right represents death).


Code:

Servo happinessServo; // Servo that visually shows pet's happiness

int happinessLevel = 180; // Starts fully happy
int lastHappinessServoAngle = -1;
unsigned long lastHappinessUpdate = 0;
const unsigned long happinessDecayInterval = 5000; // Every 5 seconds
const int happinessDecayStep = 3; // Amount of happiness lost each decay
int decayCounter = 0;

bool deathTonePlaying = false;
bool redFlashState = false;
unsigned long lastRedFlashTime = 0;

void updateHappiness() {
unsigned long now = millis();
if (now - lastHappinessUpdate >= happinessDecayInterval) {
decreaseHappiness(happinessDecayStep);
lastHappinessUpdate = now;
decayCounter++;

if (decayCounter >= 3) {
Serial.print("🕑 Idle penalty (-");
Serial.print(decayCounter * happinessDecayStep);
Serial.println(" happiness)");
decayCounter = 0;
}
}

updateHappinessServo(false);
}

void updateHappinessServo(bool force) {
int angle = map(happinessLevel, 0, 180, 0, 180);
if (force || angle != lastHappinessServoAngle) {
happinessServo.write(angle);
Serial.print("❤️ Happiness: ");
Serial.println(happinessLevel);
lastHappinessServoAngle = angle;
}

if (happinessLevel <= 10) {
if (!deathTonePlaying) {
tone(buzzerPin, 180); // Sad tone
deathTonePlaying = true;
}
if (millis() - lastRedFlashTime >= 300) {
redFlashState = !redFlashState;
digitalWrite(redLED, redFlashState);
lastRedFlashTime = millis();
}
} else {
if (deathTonePlaying) {
noTone(buzzerPin);
deathTonePlaying = false;
}
digitalWrite(redLED, LOW);
}
}

void increaseHappiness(int amount) {
happinessLevel += amount;
if (happinessLevel > 180) happinessLevel = 180;
updateHappinessServo(false);
}

void decreaseHappiness(int amount) {
happinessLevel -= amount;
if (happinessLevel < 0) happinessLevel = 0;
updateHappinessServo(false);
}


Code Explanation:

happinessLevel is the main variable that stores how happy the pet is (0 = sad, 180 = max happiness).

Every 5 seconds (happinessDecayInterval), the updateHappiness() function automatically lowers happiness by happinessDecayStep (3 points) which simulates the pet being bored.

After three decays without interaction, a penalty message is shown in the Serial Monitor. This is to prevent flooding

The function updateHappinessServo() maps the current happiness level to the servo angle, updating the servo motor in the little block (happinessServo) to visually represent how the pet feels (Either loved or lonely).


If the happiness drops to 10 or below, the pet enters a sad/dead state:

A low buzzing tone plays.

The red LED flashes every 300ms.


The increaseHappiness() and decreaseHappiness() functions control how the pet’s happiness changes based on gameplay. These functions are what we will call upon in mini-games or when a sleep or pet action is done to increase the happiness. The decreaseHappiness() function is often used for when the player fails a minigame or when the player plays AND wins a game too much (we will discuss later).

Mini-game 1: Play Mini Game - Simon Says

IMG_4158.jpeg

This is our first mini-game, which will be referred to the “Play” mini-game was inspired by the hit game “Simon Says”. The goal of the game it to immitate a random series of 3 notes played through the buzzer using predetermined buttons which play 1 out of the 3 total pitches each. The player’s job is to press the buttons in the right sequence, matching the melody played. If done correctly, the happiness of the pet will go up, otherwise it will decrease.

Components:

-1x Piezo/Buzzer

-3x Buttons

-3x 10k Resistors

-1x Red led

-1x Green led

-2x 330 resistor (for the leds)

-Wires


Code:

// --- Button and Buzzer Setup ---
const int buttonPin1 = 6;
const int buttonPin2 = 7;
const int buttonPin3 = 8;
const int buzzerPin = 4;

const int tone1 = 440; // A4
const int tone2 = 554; // C#5
const int tone3 = 659; // E5

const int greenLED = 9;
const int redLED = 10;

// --- Happiness System ---
int happiness = 50; // Starting happiness out of 100
const int maxHappiness = 100;
const int minHappiness = 0;

void increaseHappiness(int amount) {
happiness += amount;
if (happiness > maxHappiness) happiness = maxHappiness;
Serial.print("😊 Happiness increased to: "); Serial.println(happiness);
}

void decreaseHappiness(int amount) {
happiness -= amount;
if (happiness < minHappiness) happiness = minHappiness;
Serial.print("😞 Happiness decreased to: "); Serial.println(happiness);
}

// --- Game Logic ---
const int sequenceLength = 3;
int sequence[sequenceLength];
int playStreak = 0;

void playGame() {
Serial.println("--- 🎮 MEMORY GAME ---");
generateSequence();
playSequence();

if (getPlayerInput()) {
// Correct sequence!
tone(buzzerPin, 800, 150); delay(200);
tone(buzzerPin, 1000, 150); delay(200);
wagTail(); // Your function to move servo or motor
increaseHappiness(10);
Serial.println("✅ Correct sequence! (+10 happiness)");
digitalWrite(greenLED, HIGH); delay(300); digitalWrite(greenLED, LOW);

playStreak++;
if (playStreak >= 3) {
Serial.println("⚠️ TOO MUCH PLAYING! (-15 happiness)");
digitalWrite(redLED, HIGH);
tone(buzzerPin, 200, 600);
delay(800);
digitalWrite(redLED, LOW);
decreaseHappiness(15);
playStreak = 0;
}
} else {
// Incorrect sequence
Serial.println("❌ Wrong sequence. (-10 happiness)");
tone(buzzerPin, 200, 500); delay(600);
digitalWrite(redLED, HIGH); delay(300); digitalWrite(redLED, LOW);
decreaseHappiness(10);
playStreak = 0;
}
}

void generateSequence() {
for (int i = 0; i < sequenceLength; i++) {
sequence[i] = random(0, 3); // Random: 0 = btn1, 1 = btn2, 2 = btn3
}
}

void playSequence() {
for (int i = 0; i < sequenceLength; i++) {
int t = (sequence[i] == 0) ? tone1 : (sequence[i] == 1) ? tone2 : tone3;
tone(buzzerPin, t, 400);
delay(500);
noTone(buzzerPin);
delay(300);
}
}

bool getPlayerInput() {
for (int i = 0; i < sequenceLength; i++) {
int input = waitForButton();
if (input != sequence[i]) return false;
}
return true;
}

int waitForButton() {
while (true) {
if (digitalRead(buttonPin1) == HIGH) {
tone(buzzerPin, tone1, 300);
while (digitalRead(buttonPin1) == HIGH) {} // Wait for release
delay(100);
return 0;
}
if (digitalRead(buttonPin2) == HIGH) {
tone(buzzerPin, tone2, 300);
while (digitalRead(buttonPin2) == HIGH) {}
delay(100);
return 1;
}
if (digitalRead(buttonPin3) == HIGH) {
tone(buzzerPin, tone3, 300);
while (digitalRead(buttonPin3) == HIGH) {}
delay(100);
return 2;
}
}
}


Code Explanation:

Generate Sequence:

generateSequence() creates a pattern of 3 random notes using numbers 0–2.

Each number maps to a button and sound.

Play Sequence:

playSequence() plays the tones using tone(), with delays between them. This is what the player needs to copy.

User Input:

getPlayerInput() waits for the player to press the buttons one by one and checks if it matches the original sequence using waitForButton().

Results:

If correct:

Happy tones play

Tail wags

Happiness increases by +10 (using the increaseHappiness() function)

If the player has played 3 times in a row, the pet gets mad and punishes with -15 happiness (using the decreaseHappiness() function)

If incorrect:

Sad buzzer sound

Red LED

Happiness drops by -10

Mini-Game 2: Feed Mini-game

IMG_4172.jpeg
IMG_4162.jpeg
IMG_4161.jpeg
IMG_4164.jpeg

This mini-game is called the “Feed” mini-game. Like the name suggests, it will be another game in which you feed your pet (not literally). If you feed it according to what it wants (signalled by the red or green led) 3 times in a row, you win the mini-game. Use the potentiometer to move the servo arm over the correct food! A red lit led means he wants sausage and a green lit led means he wants plants. You better hurry though! Because our pet is not very patient…

Components:

-1x Servo

-1x Potentiometer

-Food, either drawn or lego to indicate the targeted positions.

-Lego or cardboard to turn the contraption more appealing and aesthetically pleasing.

-This will use a green and red led but you should already have it wired in step 3.

Code:

#include <Servo.h>

// --- Pins ---
const int potPin = A0;
const int redLED = 9;
const int greenLED = 10;
const int servoPin = 3;

// --- Servo + Food Logic ---
Servo foodServo;
int targetFood; // 0 = sausage (red), 1 = plants (green)
int correctStreak = 0;
unsigned long startTime;
const int patienceTime = 7000; // 7 seconds to respond

// --- Happiness System ---
int happiness = 50;
const int maxHappiness = 100;
const int minHappiness = 0;

void increaseHappiness(int amount) {
happiness += amount;
if (happiness > maxHappiness) happiness = maxHappiness;
Serial.print("😊 Happiness increased to: "); Serial.println(happiness);
}

void decreaseHappiness(int amount) {
happiness -= amount;
if (happiness < minHappiness) happiness = minHappiness;
Serial.print("😞 Happiness decreased to: "); Serial.println(happiness);
}

void setup() {
Serial.begin(9600);
pinMode(redLED, OUTPUT);
pinMode(greenLED, OUTPUT);
foodServo.attach(servoPin);
randomSeed(analogRead(A1)); // randomness for food choice
}

void loop() {
feedGame(); // Run game
}

// --- Mini-Game Core ---
void feedGame() {
correctStreak = 0;

while (correctStreak < 3) {
// Randomly choose food type
targetFood = random(0, 2); // 0 = sausage, 1 = plants
indicateFood(targetFood);
startTime = millis();

Serial.println("Pet is hungry! Move servo to correct food!");

bool responded = false;
while (millis() - startTime < patienceTime) {
int potVal = analogRead(potPin);
int angle = map(potVal, 0, 1023, 0, 180);
foodServo.write(angle);

// Check if servo arm is over correct food
if (targetFood == 0 && angle >= 30 && angle <= 60) {
responded = true;
break;
} else if (targetFood == 1 && angle >= 120 && angle <= 150) {
responded = true;
break;
}
}

if (responded) {
Serial.println("✅ Correct food delivered!");
increaseHappiness(10);
correctStreak++;
} else {
Serial.println("❌ Too slow or wrong position!");
decreaseHappiness(10);
correctStreak = 0;
}

// Reset LEDs and pause between rounds
digitalWrite(redLED, LOW);
digitalWrite(greenLED, LOW);
delay(1000);
}

Serial.println("🎉 Mini-game complete! Pet is fed and happy!");
increaseHappiness(15); // Bonus
}

// --- LED Food Indication ---
void indicateFood(int foodType) {
if (foodType == 0) {
digitalWrite(redLED, HIGH); // sausage
digitalWrite(greenLED, LOW);
Serial.println("🍖 Pet wants sausage!");
} else {
digitalWrite(greenLED, HIGH); // plants
digitalWrite(redLED, LOW);
Serial.println("🥬 Pet wants plants!");
}
}


Code Explanation:

feedGame() FUNCTION

-This is the game loop that handles:

-Generating a random food request

-Showing LED indicator

-Waiting for a response within time

-Checking success or failure


while (correctStreak < 3) { ... }

-Runs until 3 correct feedings in a row.


while (millis() - startTime < patienceTime) { ... }

-Implements a countdown using millis() instead of delay() which lets the user interact without bugging the program. Its a bit random but its a prebuilt function into Arduino to help avoid these bugs.N


int potVal = analogRead(potPin);

int angle = map(potVal, 0, 1023, 0, 180);

-Maps the potentiometer range (0–1023) to servo angle (0–180°).

-Then compares to target zones:

-Sausage = angle between 30° and 60°

-Plants = angle between 120° and 150°

-This gives the user room for error, while still requiring accuracy.


increaseHappiness() & decreaseHappiness()

-Increases or decreases depending on if you win or not.


if (happiness > maxHappiness) happiness = maxHappiness;

-This makes sure that the happiness meter doesn’t exceed the limit. It is used in every action/mini-game on the pet.


randomSeed(analogRead(A1));

-This line ensures that food choices aren't always the same across resets. Without this, Arduino picks the same sequence every time. This makes it so the player has to pay attention


indicateFood(foodType)

-This turns on the right LED (either red for sausage or green for plant) and keeps tract of the food type.


if (correctStreak == 3)

Once the player gets 3 correct in a row, a happiness increase is given and the game ends. If the player loses, they lose happiness - same principle for all games.


Joystick and Sleep

IMG_4165.jpeg
IMG_4170.jpeg

Your pet is exhausted and wants to sleep. Guide it into the "sleep zone" using the joystick and keep it there for 5 continuous seconds. If you succeed, your pet falls asleep and gains happiness. If you fail by moving too much, the pet stays grumpy.

COMPONENTS:

-1x Joystick module

-Led (should already be wired)

HOW IT WORKS:

The joystick controls your pet’s position. The sleep zone is the center region of the joystick. If you stay centered for 5 seconds straight, you win. If you leave the zone, the timer resets. Green LED = pet is sleeping. Red LED = pet woke up.

CODE:

// --- Pins ---
const int joyXPin = A0;
const int joyYPin = A1;
const int greenLED = 8;
const int redLED = 9;

// --- Sleep Logic ---
const int centerMin = 450;
const int centerMax = 570;
const unsigned long sleepTime = 5000; // 5 seconds to fall asleep

unsigned long sleepStart = 0;
bool inSleepZone = false;

// --- Happiness System ---
int happiness = 50;
const int maxHappiness = 100;
const int minHappiness = 0;

void increaseHappiness(int amount) {
happiness += amount;
if (happiness > maxHappiness) happiness = maxHappiness;
Serial.print("😴 Happiness increased to: "); Serial.println(happiness);
}

void decreaseHappiness(int amount) {
happiness -= amount;
if (happiness < minHappiness) happiness = minHappiness;
Serial.print("😡 Happiness decreased to: "); Serial.println(happiness);
}

void setup() {
Serial.begin(9600);
pinMode(greenLED, OUTPUT);
pinMode(redLED, OUTPUT);
}

void loop() {
sleepGame(); // Run the mini-game
}

// --- Mini-Game Core ---
void sleepGame() {
Serial.println("😴 Guide the pet into sleep zone and keep it still!");

while (true) {
int joyX = analogRead(joyXPin);
int joyY = analogRead(joyYPin);

bool joystickInCenter = (joyX >= centerMin && joyX <= centerMax &&
joyY >= centerMin && joyY <= centerMax);

if (joystickInCenter) {
if (!inSleepZone) {
sleepStart = millis(); // just entered zone
inSleepZone = true;
Serial.println("🛏️ Pet is entering sleep zone...");
digitalWrite(greenLED, HIGH);
digitalWrite(redLED, LOW);
}

if (millis() - sleepStart >= sleepTime) {
Serial.println("🎉 Pet fell asleep!");
increaseHappiness(15);
break;
}
} else {
if (inSleepZone) {
Serial.println("😤 Pet woke up! You moved too much.");
inSleepZone = false;
digitalWrite(greenLED, LOW);
digitalWrite(redLED, HIGH);
}
}

delay(100); // smooth check interval
}

// Game over visual reset
digitalWrite(greenLED, LOW);
digitalWrite(redLED, LOW);
}


Code Explanation:

Joystick input:

int joyX = analogRead(joyXPin);

int joyY = analogRead(joyYPin);

-This reads the joystick's horizontal and vertical values. Center is roughly 512. We use a range to define the center zone (450–570) to make it more forgiving.


Sleep zone check:

bool joystickInCenter = (joyX >= centerMin && joyX <= centerMax &&

joyY >= centerMin && joyY <= centerMax);

-Both X and Y must stay in the center range. If either axis drifts too far, you exit the zone.


Time tracker:

if (!inSleepZone) {

sleepStart = millis();

}

-When you first enter the zone, the timer starts. If you stay centered for 5 full seconds (millis() - sleepStart >= 5000), the game is won.


Happiness:

increaseHappiness(15);

The pet gains happiness for winning the game. If you fail, you don’t lose happiness, but you waste time and must try again.


Led Feedback:

Green LED: You're in the zone and making progress

Red LED: You broke the pet’s sleep


JOYSTICK CONTROL:

void loop() {
int xVal = analogRead(joyX);
int yVal = analogRead(joyY);

updateHappiness();
detectPetting();

if (xVal > 700) {
sleepMode();
} else if (yVal > 700) {
playGame();
} else if (yVal < 300) {
feedGame();
}

delay(200);
}


This code links 3 directions of the joystick to a corresponding game depending on the x or y values (left right bottom). Using lego we can show what direction will led to the start of which mini-game.

555 Led Alternator

IMG_4157.jpeg
IMG_4156.jpeg
IMG_9113.jpeg

To make out project look a bit more vibrant, we can use leds. However, since we don't have any Arduino pins left, we can instead utilize the 555 timer to get a nice led swapper effect. Use the schematic or the photo to follow and create it for yourself!

Components:

-1x 555 Timer

-2x Led

-1x 10 uF capacitor

-1x 100 uF capacitor

-3x 1k resistors

-1x 10k resistor


Distance Sensor - Pet Feature

IMG_4200.jpeg
IMG_4234.jpeg

A distance sensor right above our pet’s head will be our final feature. When the player puts their hand over the pet, in a petting motion, the distance sensor will detect a smaller distance than usual and prompt and tail wag and a smaller increase in happiness!


void detectPetting() {
long d = readDistance();
if (d > 0 && d < 8) {
Serial.println("🐾 Pet detected! (+3 happiness)");
tone(buzzerPin, 1000, 100);
wagTail();
increaseHappiness(3);
delay(1000); // small pause to avoid multiple triggers
}
}

long readDistance() {
digitalWrite(trigPin, LOW);
delayMicroseconds(2);
digitalWrite(trigPin, HIGH);
delayMicroseconds(10);
digitalWrite(trigPin, LOW);

long duration = pulseIn(echoPin, HIGH, 10000);
return duration * 0.034 / 2;}


readDistance() function:

-Sends a 10 microsecond pulse to the sensor trigger pin.

-Waits for the echo pin to go HIGH and measures the pulse duration in microseconds.

-Converts the time into distance using the speed of sound:

-distance (cm)=duration×0.0342distance (cm)=2duration×0.034​

-The division by 2 accounts for the pulse traveling to the object and back.


detectPetting() function:

-Calls readDistance() to get the current distance reading.

-Checks if the distance is positive and less than 8 cm (meaning a hand is close enough).

-If so:

-Prints a message to Serial for debugging.

-Plays a happy tone on the buzzer (1000 Hz for 100 ms).

-Calls wagTail() to physically wag the tail servo.

-Calls increaseHappiness(3) to increase the pet’s happiness by 3 points.

-Delays 1 second to prevent the petting event from triggering multiple times too quickly.


Tail wag Code:

void wagTail() {

tailServo.write(60);

delay(150);

tailServo.write(120);

delay(150);

tailServo.write(90);

}


-Moves the servo in a way that imitates a wagging tail

Create

IMG_4200.jpeg
IMG_4171.jpeg
IMG_4169.jpeg
IMG_4166.jpeg

Steps 1-7 have led you to the creation of all the segments of the pet! From here, use jumper wires to hide the bread board and Arduino in the back, make a segment with the led’s and buzzers, bring out the alternating lights. Basically your game functions perfectly but now you must make it look appealing and something thats well put together. This is purely subjective so try your hand on different layouts, designs, etc.

Full Code

There are many little additions or niche concepts i added to give my code some more life. This master code has combined all the previous code for the different steps into one and made it flow much better. I would highly recommend that you work your way alone and find what you like but here is my full code if you wish to get an idea:


link