Red LEDs, Cardboard and Heart-to-Heart Connections – A Final Project 💖

Cupid

The Concept:

Cupid first took root with a simple LED heart. From the very start, I knew I wanted to incorporate this element – a beating, glowing heart that could serve as the centrepiece for an interactive experience. However, I hadn’t quite decided how to elevate it beyond just this into something more profound and immersive.

And then 💡! – what if this heart could be linked to the user’s own pulse? By integrating a pulse sensor, I could make each person’s unique heartbeat bring the LED heart to life, creating a personalised connection.

This sparked the concept for Cupid – an interactive cardboard robot that detects your heartbeat through pulse sensing, uses it to animate a glowing LED heart in its chest, and even generates humorous, randomly selected (chatGPT generated 🤭 ) “love predictions” based on your heart rate variability data.

The goal was to craft an experience that the love child of playful and whimsy. By encouraging users to form a “heart-to-heart connection” with this quirky robot, the interaction taps into something innately human – our capacity for creating emotional bonds and embracing moments of lighthearted joy.

Brainstorming:

The Process:

The Heart:

Building the pulsing LED heart for Cupid was quite a challenge. The biggest issue was that I needed 16 LEDs, but the Arduino only had 13 digital pins available. To work around this, I had to get creative and connect the LEDs in pairs using soldering.

For each pair, I soldered the negative leg of one LED to the positive leg of the other LED. Then, I soldered the negative leg of the second LED to a 330-ohm resistor to control the current. After doing this for all 8 pairs, I soldered a single wire to the positive end of each pair.

Finally, I bundled all the negative resistor legs together and connected them to a single ground wire. This way, I could control the 16 LEDs using just 8 digital pins from the Arduino.

While this wiring setup took some careful soldering work (and far more time than I’d like to admit), it allowed me to create the synchronised pulsing heart effect that became the centrepiece of Cupid. Tinkering with the soldering iron, meticulously joining the wires and components, I found an unexpected sense of satisfaction and joy in the hands-on process. It made me realise how much I enjoy working with tangible tools.

 

 

Cupid in her early stages 😋

 

 

 

The P5 Sketch:

After the intricate work of building Cupid’s LED heart, creating the p5.js sketch felt relatively simple. I took a hands-on approach by hand-drawing the title page illustration. Then I drew Cupid’s adorable (i’m biased) robot body. I wanted to infuse the character with warmth and likability. While the p5.js coding was more technical, the artistic process of drawing Cupid made this phase of the project very enjoyable and satisfying.

Now it was time to bring her to life through code. The p5.js sketch served as the digital heart (hehe) of this project. Here’s a breakdown of some key elements of the code:

  1. Heart Animation: The pulsing LED heart effect was achieved by gradually reducing the size of the heart shape (heartSize) over time. This created a lifelike pulsation that synced with the user’s heartbeat.
if (state === 1 && heartSize > 50) {
  heartSize *= 0.97;  // Gradually reduce heart size to simulate the beat
}
  1. State-Based Interaction: Cupid’s interaction was divided into different states (0, 1, and 2) to control the flow of the experience. These states determined what was displayed on the screen and how Cupid responded to user input.
    switch (state) {
      case 0:
        imageMode(CENTER);
        image(text1, width / 2, height / 2);
        fill("#FED4D6");
        textSize(30);
        text("Press space bar to begin", width / 2, height - 100);
        break;
      case 1:
        imageMode(CENTER);
        image(cupidbot, width / 2, height / 2);
        drawHeart();
        fill("#FED4D6");
        textSize(40);
        if (timeHeartbeatDetected && millis() - timeHeartbeatDetected < 10000) {
          text("making heart to heart connection", width / 2, height / 10);
        } else if (timeHeartbeatDetected) {
          text("connection made. press enter to know your love prediction", width / 2, height / 10);
          displayPredictionText = true;  // Enable showing the prediction prompt
        } else {
          text("hold my hand to make a connection", width / 2, height / 10);
        }
        break;
      case 2:
        fill("#FED4D6");
        textSize(35);
        text(prediction, width / 2, height / 2);
        if (displayPredictionText) {
          noStroke();
          fill("#FED4D6");
          rect(width / 2 - 100, height - 150, 200, 50);  // Draw quit button
          fill("#D76770");
          textSize(30);
          text("quit", width / 2, height - 125);
        }
        break;
    }
    
    P5 Sketch:

    Arduino Code:

    const int pulsePin = A0;         // Pulse Sensor connected to analog pin A0
    int threshold = 600;             // Set a threshold to detect a beat
    bool beatDetected = false;
    unsigned long lastBeatTime = 0;
    float beatIntervals[30];         // Storage for beat intervals
    int beatCount = 0;
    unsigned long startTime;
    bool measuring = true;           // Change default to 'true' if you want to start measuring immediately
    bool countdownActive = false;
    unsigned long countdownStartedAt;
    const unsigned long countdownDuration = 10000;  // 20 seconds countdown
    
    
    // LED configuration
    const int ledPins[] = {4, 5, 6, 7, 8, 9, 10, 11, 12}; // Digital pins for LED anodes
    bool ledsOn = false; // Flag to track if LEDs are currently on
    
    
    void setup() {
     Serial.begin(9600);
     pinMode(pulsePin, INPUT);
    
    
     // Set all LED pins to output mode
     for (int i = 0; i < sizeof(ledPins) / sizeof(int); i++) {
       pinMode(ledPins[i], OUTPUT);
     }
    }
    
    
    void loop() {
     unsigned long currentTime = millis();
    
    
     if (measuring) {
       int sensorValue = analogRead(pulsePin);
      
       if (sensorValue > threshold && !beatDetected) {
         beatDetected = true;
         Serial.println("BEAT");
    
    
         if (lastBeatTime > 0 && beatCount < sizeof(beatIntervals) / sizeof(float)) {
           beatIntervals[beatCount++] = currentTime - lastBeatTime;
         }
         lastBeatTime = currentTime;
    
    
         // Toggle the LEDs
         toggleLEDs();
       } else if (sensorValue < threshold) {
         beatDetected = false;
       }
     }
    
    
     if (countdownActive && currentTime - countdownStartedAt > countdownDuration) {
       countdownActive = false;
       measuring = false; // Stop measuring after countdown
       if (beatCount > 1) {
         float hrv = calculateHRV(beatIntervals, beatCount);
         Serial.print("HRV: ");
         Serial.println(hrv);
       } else {
         Serial.println("Not enough data for HRV.");
       }
       beatCount = 0;
    
    
       // Turn off all LEDs after a brief delay
       delay(1000);
       for (int i = 0; i < sizeof(ledPins) / sizeof(int); i++) {
         digitalWrite(ledPins[i], LOW);
       }
     }
    
    
     // Check for incoming serial data to reset the measurements
     if (Serial.available() > 0) {
       String command = Serial.readStringUntil('\n');
       command.trim(); // Correct use of trim()
       if (command == "reset") {
         resetMeasurements();
       }
     }
    
    
     delay(20);
    }
    
    
    void resetMeasurements() {
     beatCount = 0;
     lastBeatTime = 0;
     measuring = true; // Restart measuring
     countdownActive = false; // Ensure countdown is ready to be triggered again
    }
    
    
    float calculateHRV(float intervals[], int count) {
     if (count == 0) return 0.0; // Avoid division by zero
    
    
     float mean = 0;
     for (int i = 0; i < count; i++) {
       mean += intervals[i];
     }
     mean /= count;
    
    
     float sd = 0; // Calculate standard deviation of intervals
     for (int i = 0; i < count; i++) {
       sd += pow(intervals[i] - mean, 2);
     }
     sd = sqrt(sd / count);
    
    
     return sd; // Return the standard deviation as a measure of HRV
    }
    
    
    void toggleLEDs() {
     // Toggle the state of all LEDs
     ledsOn = !ledsOn;
     for (int i = 0; i < sizeof(ledPins) / sizeof(int); i++) {
       digitalWrite(ledPins[i], ledsOn ? HIGH : LOW);
       }
    }
    

    The Arduino code is the brain behind Cupid’s heartbeat detection and LED synchronisation. It starts by setting up the pulse sensor on analog pin A0 and an array of digital pins for the LED heart. In the main loop, it continuously reads the pulse sensor value and compares it to a threshold to determine if a heartbeat is detected. When a beat is sensed, it triggers the LEDs to toggle their state, creating that pulsing heart effect. The code also keeps track of the time between heartbeats, allowing it to calculate the heart rate variability (HRV) after a countdown period. This HRV data is then sent to the p5.js sketch over serial to generate the love predictions.

    Finally, I assembled Cupid’s body using cardboard and enclosed all the components inside. I used a laser cutter to create two boxes, one for the head and one for the body. After cutting a small hole in one of the body pieces for the LED heart, I simply used hot glue to put everything together. Adding Cupid’s signature heart face was the finishing touch, completing her look!

    User Testing:

    IMG_5307

    Future Improvements:

    Detailed Predictions: Right now, Cupid’s predictions are based on general heart rate patterns. But by making these patterns more specific and matching them to different “tones” or themes, her predictions could feel more personal. Small changes in heart rate could lead to fun and unique predictions that match how someone is feeling.

    Better Visual Effects: Cupid’s glowing heart is already pretty to look at, but we can make it even more exciting. By adding special effects that move and change with the user’s heartbeat, I can create a more immersive experience. For example, colourful lights that follow the rhythm of your heart, making the whole experience more magical.

    Improved Design: Cupid’s current design is cute and friendly, but I can make it even better. By using nicer materials like wood or metal, I can give her a more polished look. Adding moving parts or special lights can also make her feel more alive and engaging.

    Final Thoughts:

    My favourite part of this project is the LED heart, which not only challenged me but also led to me learning so many new skills. From soldering to wiring, every step was a learning experience that I deeply enjoyed. The illustrations added a delightful touch to the project and contributed to its overall appeal. Seeing the project come together so smoothly and seamlessly was so rewarding.

    Apart from that, I’m proud of myself for creating a user experience that evokes feelings of joy and warmth. It required careful consideration of every detail, from the flow of the interaction to the aesthetics. I’m proud that I was able to design an experience that resonates with users, making the interaction with the project enjoyable and memorable.

    some more pictures from the showcase 🙂

Assignment 12

 EXERCISE 01: ARDUINO TO P5 COMMUNICATION

The task was to make something that uses only one sensor on arduino and makes the ellipse in p5 move on the
horizontal axis, in the middle of the screen, and nothing on arduino is controlled by p5.

We utilized a simple set-up consisting of a potentiometer. We mapped its values to the x-position of the ellipse on p5. The ellipse moves across the x-axis as the potentiometer is turned.

Arduino Code:
void setup() {
  Serial.begin(9600); // Initialize serial communication at 9600 baud rate
}

void loop() {
  int sensorValue = analogRead(A0); // Read the value from the potentiometer
  Serial.println(sensorValue);      // Send the value to the serial port followed by a newline character
  delay(50);                        // Delay to prevent overwhelming the serial buffer
}
P5 Sketch:
let rVal = 0;
let alpha = 255;


function setup() {
  createCanvas(640, 480);
  textSize(18);
}

function draw() {

  background(255);
  if (!serialActive) {
    text("Press Space Bar to select Serial Port", 20, 30);
  } else {
    // Print the current values
    text('Potentiometer Value = ' + str(rVal), 20, 50);
    //text('alpha = ' + str(alpha), 20, 70);
  }
  let xpos = map(rVal, 0, 1023, 0, width);  // Map the sensor value to the canvas width
  ellipse(xpos, height / 2, 50, 50);  // Draw an ellipse at the mapped position
  
}

function keyPressed() {
  if (key == " ") {
    // important to have in order to start the serial connection!!
    setUpSerial();
  }
}
function readSerial(data) {
  ////////////////////////////////////
  //READ FROM ARDUINO HERE
  ////////////////////////////////////

  if (data != null) {
   
    let fromArduino = split(trim(data), ",");
    // if the right length, then proceed
    if (fromArduino.length == 1) {
      
      rVal = int(fromArduino[0]);
      
    }

   
  }
}

EXERCISE 02: P5 TO ARDUINO COMMUNICATION

Make something that controls the LED brightness from p5.

We used a slider in p5 and connected the led to a PWM pin. The slider controls the brightness level of the LED.

Arduino Code:
//Arduino Code

// Week 11.2 Example of bidirectional serial communication

// Inputs:
// - A0 - sensor connected as voltage divider (e.g. potentiometer or light sensor)
// - A1 - sensor connected as voltage divider 
//
// Outputs:
// - 2 - LED
// - 5 - LED

int leftLedPin = 10;
int rightLedPin = 5;

void setup() {
  // Start serial communication so we can send data
  // over the USB connection to our p5js sketch
  Serial.begin(9600);

  // We'll use the builtin LED as a status output.
  // We can't use the serial monitor since the serial connection is
  // used to communicate to p5js and only one application on the computer
  // can use a serial port at once.
  pinMode(LED_BUILTIN, OUTPUT);

  // Outputs on these pins
  pinMode(leftLedPin, OUTPUT);
  pinMode(rightLedPin, OUTPUT);

  // Blink them so we can check the wiring
  digitalWrite(leftLedPin, HIGH);
  digitalWrite(rightLedPin, HIGH);
  delay(200);
  digitalWrite(leftLedPin, LOW);
  digitalWrite(rightLedPin, LOW);



  // start the handshake
  while (Serial.available() <= 0) {
    digitalWrite(LED_BUILTIN, HIGH); // on/blink while waiting for serial data
    Serial.println("0,0"); // send a starting message
    delay(300);            // wait 1/3 second
    digitalWrite(LED_BUILTIN, LOW);
    delay(50);
  }
}

void loop() {
  // wait for data from p5 before doing something
  while (Serial.available()) {
    digitalWrite(LED_BUILTIN, HIGH); // led on while receiving data

    int left = Serial.parseInt();
    int right = Serial.parseInt();
    if (Serial.read() == '\n') {
      analogWrite(leftLedPin, left);
      digitalWrite(rightLedPin, right);
      int sensor = analogRead(A0);
      delay(5);
      int sensor2 = analogRead(A1);
      delay(5);
      Serial.print(sensor);
      Serial.print(',');
      Serial.println(sensor2);
    }
  }
  digitalWrite(LED_BUILTIN, LOW);
}
P5 Sketch:

Code:
let rVal = 0;
let alpha = 255;
let left = 0; // True (1) if mouse is being clicked on left side of screen
let right = 0; // True (1) if mouse is being clicked on right side of screen

function setup() {
  createCanvas(640, 480);
  textSize(18);
  ledSlider = createSlider(0, 255, 0);
  ledSlider.position(10, 40);
  ledSlider.style('width', '200px');
}

function draw() {
  // one value from Arduino controls the background's red color
  //background(map(rVal, 0, 1023, 0, 255), 255, 200);
  background('white');

  // the other value controls the text's transparency value
  fill('black');

  if (!serialActive) {
    text("Press Space Bar to select Serial Port", 20, 30);
  } else {
    text("Connected", 20, 30);
    // Print the current values
    //text('rVal = ' + str(rVal), 20, 50);
    //text('alpha = ' + str(alpha), 20, 70);
  }

  left = ledSlider.value();
  console.log(left);
  right = 0;
  // click on one side of the screen, one LED will light up
  // click on the other side, the other LED will light up
 
}

function keyPressed() {
  if (key == " ") {
    // important to have in order to start the serial connection!!
    setUpSerial();
  }
}

// This function will be called by the web-serial library
// with each new line of data. The serial library reads
// the data until the newline and then gives it to us through
// this callback function
function readSerial(data) {
  ////////////////////////////////////
  //READ FROM ARDUINO HERE
  ////////////////////////////////////

  if (data != null) {
    // make sure there is actually a message
    // split the message
    let fromArduino = split(trim(data), ",");
    // if the right length, then proceed
    if (fromArduino.length == 2) {
      // only store values here
      // do everything with those values in the main draw loop
      
      // We take the string we get from Arduino and explicitly
      // convert it to a number by using int()
      // e.g. "103" becomes 103
      rVal = int(fromArduino[0]);
      alpha = int(fromArduino[1]);
    }

    //////////////////////////////////
    //SEND TO ARDUINO HERE (handshake)
    //////////////////////////////////
    let sendToArduino = left + "," + right + "\n";
    writeSerial(sendToArduino);
  }
}



//Arduino Code
/*
// Week 11.2 Example of bidirectional serial communication

// Inputs:
// - A0 - sensor connected as voltage divider (e.g. potentiometer or light sensor)
// - A1 - sensor connected as voltage divider 
//
// Outputs:
// - 2 - LED
// - 5 - LED

int leftLedPin = 2;
int rightLedPin = 5;

void setup() {
  // Start serial communication so we can send data
  // over the USB connection to our p5js sketch
  Serial.begin(9600);

  // We'll use the builtin LED as a status output.
  // We can't use the serial monitor since the serial connection is
  // used to communicate to p5js and only one application on the computer
  // can use a serial port at once.
  pinMode(LED_BUILTIN, OUTPUT);

  // Outputs on these pins
  pinMode(leftLedPin, OUTPUT);
  pinMode(rightLedPin, OUTPUT);

  // Blink them so we can check the wiring
  digitalWrite(leftLedPin, HIGH);
  digitalWrite(rightLedPin, HIGH);
  delay(200);
  digitalWrite(leftLedPin, LOW);
  digitalWrite(rightLedPin, LOW);



  // start the handshake
  while (Serial.available() <= 0) {
    digitalWrite(LED_BUILTIN, HIGH); // on/blink while waiting for serial data
    Serial.println("0,0"); // send a starting message
    delay(300);            // wait 1/3 second
    digitalWrite(LED_BUILTIN, LOW);
    delay(50);
  }
}

void loop() {
  // wait for data from p5 before doing something
  while (Serial.available()) {
    digitalWrite(LED_BUILTIN, HIGH); // led on while receiving data

    int left = Serial.parseInt();
    int right = Serial.parseInt();
    if (Serial.read() == '\n') {
      digitalWrite(leftLedPin, left);
      digitalWrite(rightLedPin, right);
      int sensor = analogRead(A0);
      delay(5);
      int sensor2 = analogRead(A1);
      delay(5);
      Serial.print(sensor);
      Serial.print(',');
      Serial.println(sensor2);
    }
  }
  digitalWrite(LED_BUILTIN, LOW);
}
*/
EXERCISE 03: BI-DIRECTIONAL COMMUNICATION

Take the gravity wind example and make it so: every time the ball bounces one led lights up and then turns off, and you can control the wind from one analog sensor.

Arduino Code:
arduino code : //Arduino Code

// Week 11.2 Example of bidirectional serial communication

// Inputs:
// - A0 - sensor connected as voltage divider (e.g. potentiometer or light sensor)
// - A1 - sensor connected as voltage divider 
//
// Outputs:
// - 2 - LED
// - 5 - LED

int leftLedPin = 10;
int rightLedPin = 5;

void setup() {
  // Start serial communication so we can send data
  // over the USB connection to our p5js sketch
  Serial.begin(9600);

  // We'll use the builtin LED as a status output.
  // We can't use the serial monitor since the serial connection is
  // used to communicate to p5js and only one application on the computer
  // can use a serial port at once.
  pinMode(LED_BUILTIN, OUTPUT);

  // Outputs on these pins
  pinMode(leftLedPin, OUTPUT);
  pinMode(rightLedPin, OUTPUT);

  // Blink them so we can check the wiring
  digitalWrite(leftLedPin, HIGH);
  digitalWrite(rightLedPin, HIGH);
  delay(200);
  digitalWrite(leftLedPin, LOW);
  digitalWrite(rightLedPin, LOW);



  // start the handshake
  while (Serial.available() <= 0) {
    digitalWrite(LED_BUILTIN, HIGH); // on/blink while waiting for serial data
    Serial.println("0,0"); // send a starting message
    delay(300);            // wait 1/3 second
    digitalWrite(LED_BUILTIN, LOW);
    delay(50);
  }
}

void loop() {
  // wait for data from p5 before doing something
  while (Serial.available()) {
    digitalWrite(LED_BUILTIN, HIGH); // led on while receiving data

    //int left = Serial.parseInt();
    int right = Serial.parseInt();
    int left = abs(right-1);
    if (Serial.read() == '\n') {
      digitalWrite(leftLedPin,left);
      digitalWrite(rightLedPin, right);
      int sensor = analogRead(A0);
      delay(5);
      int sensor2 = analogRead(A1);
      delay(5);
      Serial.println(sensor);
      //Serial.print(',');
      //Serial.println(sensor2);
    }
  }
  digitalWrite(LED_BUILTIN, LOW);
}
P5 Code:
/*

adapted from: https://github.com/ongzzzzzz/p5.web-serial

MIT License

Copyright (c) 2022 Ong Zhi Zheng
Copyright (c) 2022 Aaron Sherwood

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

let port, reader, writer;
let serialActive = false;

async function getPort(baud = 9600) {
  let port = await navigator.serial.requestPort();

  // Wait for the serial port to open.
  await port.open({ baudRate: baud });

  // create read & write streams
  textDecoder = new TextDecoderStream();
  textEncoder = new TextEncoderStream();
  readableStreamClosed = port.readable.pipeTo(textDecoder.writable);
  writableStreamClosed = textEncoder.readable.pipeTo(port.writable);

  reader = textDecoder.readable
    .pipeThrough(new TransformStream(new LineBreakTransformer()))
    .getReader();
  writer = textEncoder.writable.getWriter();

  return { port, reader, writer };
}

class LineBreakTransformer {
  constructor() {
    // A container for holding stream data until a new line.
    this.chunks = "";
  }

  transform(chunk, controller) {
    // Append new chunks to existing chunks.
    this.chunks += chunk;
    // For each line breaks in chunks, send the parsed lines out.
    const lines = this.chunks.split("\r\n");
    this.chunks = lines.pop();
    lines.forEach((line) => controller.enqueue(line));
  }

  flush(controller) {
    // When the stream is closed, flush any remaining chunks out.
    controller.enqueue(this.chunks);
  }
}

async function setUpSerial() {
  noLoop();
  ({ port, reader, writer } = await getPort());
  serialActive = true;
  runSerial();
  loop();
}

async function runSerial() {
  try {
    while (true) {
      if (typeof readSerial === "undefined") {
        console.log("No readSerial() function found.");
        serialActive = false;
        break;
      } else {
        const { value, done } = await reader.read();
        if (done) {
          // Allow the serial port to be closed later.
          reader.releaseLock();
          break;
        }
        readSerial(value);
      }
    }
  } catch (e) {
    console.error(e);
  }
}

async function writeSerial(msg) {
  await writer.write(msg);
}
p5 sketch:

 

Week 11 Production – Paper Piano

Concept

While looking for ideas for the musical instrument,  I came across something called HID (Human Interface Devices). These are devices that can be used to substitute for Devices that are used to interact with the computer, such as a keyboard or a mouse.  Unfortunately , The Arduino UNO board that we use is not HID compatible. Why did we need this? because we wanted to do something unique and use something unconventional for the digital input part of the assignment. This is when we came across the MakeyMakey board by Sparkfun. Using this board, we can use anything with a weak conductivity as a button that send an input to the computer. We can then use the computer as an interface between Arduino and Makey Makey and use the output of Makey Makey as an input to the Arduino via the Serial.

Once we had this figured out, It opened great possibilities. For the purpose of this assignment, we decided to draw a piano on a piece of paper and use each part as a button ( so a sound plays whenever you touch the paper).

Technical Plan (Abstract overview)

For Arduino UNO
  • Takes input from the serial , and reads it
  • Converts the letter input to a corresponding frequency (taking digital input)
  • Offsets the frequency by reading the value on the potentiometer (taking analog input)
  • Uses tone function to play this frequency on the buzzer
For Makey Makey
  • Takes input from the paper buttons and send a keystroke to the keyboard corresponding to what button is pressed.
  • Presses enter automatically so that the keystroke is entered in the serial of the Arduino board.
Other Technicalities
  • We need to make sure that the piano sounds good , for this we will use notes corresponding to an actual scale . The default scale we are using here is the C major Scale on the 4th Octave consisting of the notes C , D, E ,F , G, A and B .
  • While drawing the piano , we need to make sure that the keys are separated and each key separately conducts electricity.
  • Use alligator clips properly and make sure the connections work
  • Since the Arduino board default firmware can only output 6 letters ( w,a,s,d,f,g),  we need to configure it to replace some other outputs with letters. Here, we have reprogrammed it to output ‘h’ instead of the Up arrow and used ‘ ‘ (Blank space) as an input as well .
To use:
  • Connect both Arduino UNO and Makey Makey to the computer
  • Upload Code on Makey Makey
  • Open new Window of Arduino IDE and upload code on Arduino UNO
  • Open the Serial monitor and click on it to place the computer cursor there.
  • Start taking input from Makey Makey

Code

Code for the Arduino UNO board :
#include "pitches.h"
#include <Keyboard.h>

const int BUZZER_PIN = 8; // Pin for the buzzer
const int potentiometerPin = A0; // Pin for the potentiometer
const int duration = 200; // Duration of the tone

unsigned long lastPotReadTime = 0; // Variable to store the last time potentiometer was read
int offset =0;

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

void loop() {
// Read the potentiometer value only once every second
unsigned long currentTime = millis();
if (currentTime - lastPotReadTime >= 1000) {
offset = analogRead(potentiometerPin) / 3; // Offset the frequency based on potentiometer reading
lastPotReadTime = currentTime;

// Print the potentiometer value
Serial.println(offset);
}

// Check if there's any character available in the serial buffer
if (Serial.available() > 0) {
char receivedChar = Serial.read();
int frequency = 0; // Frequency value for the tone

// Map received character to frequency
switch (receivedChar) {
case 'w': frequency = NOTE_C5; break;
case 'a': frequency = NOTE_D5; break;
case 's': frequency = NOTE_E5; break;
case 'd': frequency = NOTE_F5; break;
case 'f': frequency = NOTE_G5; break;
case 'g': frequency = NOTE_A5; break;
case 'h': frequency = NOTE_C6; break;
case ' ': frequency = NOTE_B5; break;
default: break; // Do nothing if character is not recognized
}

if (frequency != 0) {
tone(BUZZER_PIN, frequency + offset, duration); // Play the tone
delay(duration); // Wait for the tone to complete
noTone(BUZZER_PIN); // Stop the tone
}
}
}
//CCDCFE
//CCDCGF
// for happy Birthday first two lines
Relevant Code for the Makey Makey board
To press enter (write newline) along with the character
void updateInputStates() {
inputChanged = false;
for (int i=0; i<NUM_INPUTS; i++) {
inputs[i].prevPressed = inputs[i].pressed; // store previous pressed state (only used for mouse buttons)
if (inputs[i].pressed) {
if (inputs[i].bufferSum < releaseThreshold) {
inputChanged = true;
inputs[i].pressed = false;
if (inputs[i].isKey) {
Keyboard.release(inputs[i].keyCode);
}
if (inputs[i].isMouseMotion) {
mouseHoldCount[i] = 0; // input becomes released, reset mouse hold
}
}
else if (inputs[i].isMouseMotion) {
mouseHoldCount[i]++; // input remains pressed, increment mouse hold
}
}
else if (!inputs[i].pressed) {
if (inputs[i].bufferSum > pressThreshold) { // input becomes pressed
inputChanged = true;
inputs[i].pressed = true;
if (inputs[i].isKey) {
Keyboard.press(inputs[i].keyCode);
// Print the key code before pressing Enter
Keyboard.write('\n');
}
}
}
}
#ifdef DEBUG3
if (inputChanged) {
Serial.println("change");
}
#endif
}
2. To configure the outputs of Makey Makey
#include "Arduino.h"

/*
/////////////////////////////////////////////////////////////////////////
// KEY MAPPINGS: WHICH KEY MAPS TO WHICH PIN ON THE MAKEY MAKEY BOARD? //
/////////////////////////////////////////////////////////////////////////

- edit the keyCodes array below to change the keys sent by the MaKey MaKey for each input
- the comments tell you which input sends that key (for example, by default 'w' is sent by pin D5)
- change the keys by replacing them. for example, you can replace 'w' with any other individual letter,
number, or symbol on your keyboard
- you can also use codes for other keys such as modifier and function keys (see the
the list of additional key codes at the bottom of this file)

*/

int keyCodes[NUM_INPUTS] = {
// top side of the makey makey board

KEY_UP_ARROW, // up arrow pad
KEY_DOWN_ARROW, // down arrow pad
KEY_LEFT_ARROW, // left arrow pad
KEY_RIGHT_ARROW, // right arrow pad
' ', // space button pad
MOUSE_LEFT, // click button pad

// female header on the back left side

'w', // pin D5
'a', // pin D4
's', // pin D3
'd', // pin D2
'f', // pin D1
'g', // pin D0aa

// female header on the back right side

'h', // pin A5
MOUSE_MOVE_DOWN, // pin A4
MOUSE_MOVE_LEFT, // pin A3
MOUSE_MOVE_RIGHT, // pin A2
MOUSE_LEFT, // pin A1
MOUSE_RIGHT // pin A0
};

///////////////////////////
// NOISE CANCELLATION /////
///////////////////////////
#define SWITCH_THRESHOLD_OFFSET_PERC 5 // number between 1 and 49
// larger value protects better against noise oscillations, but makes it harder to press and release
// recommended values are between 2 and 20
// default value is 5

#define SWITCH_THRESHOLD_CENTER_BIAS 55 // number between 1 and 99
// larger value makes it easier to "release" keys, but harder to "press"
// smaller value makes it easier to "press" keys, but harder to "release"
// recommended values are between 30 and 70
// 50 is "middle" 2.5 volt center
// default value is 55
// 100 = 5V (never use this high)
// 0 = 0 V (never use this low


/////////////////////////
// MOUSE MOTION /////////
/////////////////////////
#define MOUSE_MOTION_UPDATE_INTERVAL 35 // how many loops to wait between
// sending mouse motion updates

#define PIXELS_PER_MOUSE_STEP 4 // a larger number will make the mouse
// move faster

#define MOUSE_RAMP_SCALE 150 // Scaling factor for mouse movement ramping
// Lower = more sensitive mouse movement
// Higher = slower ramping of speed
// 0 = Ramping off

#define MOUSE_MAX_PIXELS 10 // Max pixels per step for mouse movement

/*

///////////////////////////
// ADDITIONAL KEY CODES ///
///////////////////////////

- you can use these codes in the keyCodes array above
- to get modifier keys, function keys, etc

KEY_LEFT_CTRL
KEY_LEFT_SHIFT
KEY_LEFT_ALT
KEY_LEFT_GUI
KEY_RIGHT_CTRL
KEY_RIGHT_SHIFT
KEY_RIGHT_ALT
KEY_RIGHT_GUI

KEY_BACKSPACE
KEY_TAB
KEY_RETURN
KEY_ESC
KEY_INSERT
KEY_DELETE
KEY_PAGE_UP
KEY_PAGE_DOWN
KEY_HOME
KEY_END
KEY_CAPS_LOCK

KEY_F1
KEY_F2
KEY_F3
KEY_F4
KEY_F5
KEY_F6
KEY_F7
KEY_F8
KEY_F9
KEY_F10
KEY_F11
KEY_F12

*/

Connections and Schematics

 

Images and Video

Challenges

  • Could not use a direct serial communication between Arduino and Makey Makey because the Makey Makey has only 2 digital output pins. The work around to this was using the PC as an interface.
  • Had to find the right notes so that the piano would sound good , after a bit of experimentation , the 4th Octave of the C scale sounded very good so we decided to go with it.
  • Had to find a correct value for the potentiometer offset, which we found through some experimentation.
  • Had to find a way to enter the output of makey makey in the serial of Arduino UNO for communication . Used the Laptop keyboard as an interface for this.
  • Had trouble using analogRead and Tone together. So,had to use the concept ofBlinkWithoutDelay using mills() function to take analogRead input only once every second.

Reflections and Further Improvements

This could be improved by :

Adding more notes
Better range
Improving aesthetics
It could be possible to ground without using one hand so that both hands are free.
Since we are using tone(),  we cannot press multiple keys at the same time .
Overall, The project turned out very well. Exploring using a different board also using Arduino to make a project that is interactable in a creative way was a great experience. This inspires us to explore further and discover new types of boards and sensors.  We hope to use what we have learned in our final projects too .

Reading Response – Making Interactive Art: Set the Stage, Then Shut Up and Listen

This idea of not over-explaining your interactive artworks really struck a chord with me. I’ve definitely been guilty of that in the past – spelling out too many specifics about what different elements “mean” and how people are “supposed” to interact with them. But as the author points out, doing that pretty much defeats the whole purpose. You’re just dictating how the audience should think and experience the piece, instead of leaving room for them to explore and interpret it themselves.

I can vividly remember one interactive installation I saw that fell into this trap. It looked really cool – these conductive surfaces that would trigger light patterns when you touched them. But then the description plaque gave you this long, explicit walkthrough of the precise sequence you “should” follow when engaging with it. It ended up feeling really prescriptive and took away from the sense of curiosity and spontaneous discovery that initially drew me to the work.

The author’s point about interactive art being the “start of a conversation” between the artist and viewer resonated so much. Rather than a static, finished product, it’s meant to be this open-ended exchange where the audience’s live participation and personal perspectives complete the experience. Kind of like a director setting up a premise and suggestions for the actors, but then letting them organically find their own emotional truths within that framework.

Moving forward, I really want to embrace that spirit of intentional ambiguity in my own interactive work. Instead of strictly defining roles and meanings, I should focus on crafting intriguing environments, suggestive arrangements of elements, and potential pathways to explore – but then step back and allow diverse interpretations to emerge organically through self-directed engagement. Creating prompts for personal dialogue rather than dictating conclusions. It’s a shift in mindset, but one I think will lead to much richer, more interactive experiences.

Reading Response – Physical Computing’s Greatest Hits (and misses)

This tour through the greatest hits (and misses) of physical computing projects was such a fun read!

One quote that particularly resonated with me was: “Sometimes when people learning about physical computing hear that a particular idea has been done before, they give up on it, because they think it’s not original. What’s great about the themes that follow here is that they allow a lot of room for originality.” As someone still finding my footing in creative disciplines, I can relate to that instinct to get discouraged if you feel like you’re just retreading old ground. But the author makes a compelling case for why revisiting familiar concepts is worthwhile – there’s an endless well of creative variations to explore.

Rather than dismissing these well-trod paths as clichés, the piece argues that putting your own spin on an established idea can make it feel totally fresh and novel. I was particularly struck by the examples under “Mechanical Pixels.” The artistic possibilities of combining precise kinetic movements with immersive audiovisuals seems endlessly fascinating. Dan Rozin’s mind-bending mechanical mirrors sound like they blur the boundaries between interactive art and raw mechanism in some delightfully bizarre ways.

At the same time, I’ll admit some of the Greatest Hits left me a bit puzzled. I’m still not 100% sure I grasp the emotional motivation behind things like “Remote Hugs” that aim to convey intimacy over a distance. Maybe I’m just a cynic, but I have a hard time imagining any unhuggable object truly capturing that warmth and connection.

The whole catalog is a humbling reminder of just how much creative ground has already been covered in this space – but also how unmapped the frontiers of invention still remain. I can only hope that I can someday create my own trail.

Colour-Changing Lamp

The challenge was to create something that blended analog and digital inputs to control a set of LEDs – one through an analog sensor and the other digitally.

The Concept:
I envisioned a vibrant desktop lamp that could cycle through a kaleidoscope of smoothly blending colours.

The Execution:
For the analog input, a potentiometer proved perfect – capable of outputting 1024 values just by twisting its knob. This enabled fluid color control.

An RGB LED became the centerpiece light source. Its red, green, and blue elements could blend into any hue based on the potentiometer’s analog output levels. A regular red LED served as the digital indicator, powering on/off with the slide switch.

I wired the potentiometer to an Arduino analog input and the slide switch to a digital input pin. The RGB LED trio connected to three PWM analog outputs for mixable color output, while the red LED patched into a separate digital pin.

The Code:
The Arduino continuously read the potentiometer’s smooth analog value via analogRead(). I then mapped this range across the full RGB spectrum, setting the three LED output levels to precisely blend the corresponding hue on the RGB model. This proved to be slightly beyond my scope and I used the help of online resources to accomplish this

For the digital side, it just checked the slide switch state – HIGH powered the separate red LED, while LOW turned it off.

// Define pin connections
int potPin = A0;           // Potentiometer at analog pin A0
int redPin = 9, greenPin = 10, bluePin = 11; // RGB LED pins
int switchPin = 2;         // Digital pin for the toggle switch
int ledPin = 13;           // Pin for the additional standard LED

void setup() {
  pinMode(redPin, OUTPUT);
  pinMode(greenPin, OUTPUT);
  pinMode(bluePin, OUTPUT);
  pinMode(ledPin, OUTPUT);     // Set the additional LED pin as output
  pinMode(switchPin, INPUT_PULLUP); // Set the switch pin as input with pull-up
}

void loop() {
  int potValue = analogRead(potPin); // Read the potentiometer value
  int hueValue = map(potValue, 0, 1023, 240, 0); // Map pot value from blue to red hue values

  // Convert hue to RGB
  float r, g, b;
  hueToRGB(hueValue, r, g, b);

  // Write RGB values to LED pins
  analogWrite(redPin, r * 255);
  analogWrite(greenPin, g * 255);
  analogWrite(bluePin, b * 255);

  // Check the state of the switch
  if (digitalRead(switchPin) == LOW) {  // Switch is pressed (toggle switch connects to GND)
    digitalWrite(ledPin, HIGH);         // Turn on the additional LED
  } else {
    digitalWrite(ledPin, LOW);          // Turn off the additional LED
  }

  delay(10); // Short delay for stability
}

void hueToRGB(int hue, float &r, float &g, float &b) {
  int s = 1; // Saturation: 1 for full color
  int v = 1; // Value: 1 for max brightness
  float C = s * v;
  float X = C * (1 - fabs(fmod(hue / 60.0, 2) - 1));
  float m = v - C;
  float r1, g1, b1;

  if (hue >= 0 && hue < 60) {
    r1 = C, g1 = 0, b1 = X;  // Red to pinkish-red
  } else if (hue < 120) {
    r1 = X, g1 = 0, b1 = C;  // Pinkish-red to purple
  } else if (hue < 180) {
    r1 = 0, g1 = X, b1 = C;  // Purple to blue
  } else if (hue < 240) {
    r1 = 0, g1 = C, b1 = X;  // Lighter blue
  } else if (hue < 300) {
    r1 = X, g1 = C, b1 = 0;  // Skip greens
  } else {
    r1 = C, g1 = X, b1 = 0;  // Skip greens to yellow
  }
  r = (r1 + m);
  g = (g1 + m);
  b = (b1 + m);
}

Challenges:

My original vision was integrating this into a physical lamp with the RGB as the main light source. However, I struggled to find an easy way to run the component wires and extend the LEDs cleanly off the breadboard – a skill I’ll need to develop.

Future Improvements:
– Adding animation modes like pulsing, gradual color-cycling, and custom fading sequences between hues.
– Using light sensors to automatically adjust brightness based on ambient lighting.
– Exploring alternative RGB mapping patterns beyond the standard spectrum for unnatural, psychedelic hue blends.
– Integrating everything into a stylish 3D printed desktop lamp enclosure.

 

My First Arduino Project – An Automatic Night Light ⚡️

For my first Arduino project, I decided to build a simple automatic night light that turns on when it gets dark. The basic concept is to use a photocell (light sensor) to detect light levels and then turn an LED on or off accordingly.

The Components:

– Arduino Uno board
– Photocell (light dependent resistor)
– LED
– Resistors
– Breadboard and jumper wires

The Concept:

A photocell is a resistor that changes resistance based on the amount of light hitting its sensor area. In bright light, the resistance is low allowing more current to flow. In darkness, the resistance is high restricting current flow.

I used this property to build a basic light sensor circuit. By connecting the photocell to one of the Arduino’s analog input pins, we can read its varying resistance values based on light levels. With some code to set a light threshold, we can then control an LED by turning it on when dark and off when bright.

const int led = 8;
const int sensor_pin = A0;
int sensor;
const int threshold = 500;

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

void loop(){
sensor = analogRead(sensor_pin);
Serial.println(sensor);
if(sensor<threshold){
digitalWrite(led,HIGH);
} else{
digitalWrite(led,LOW);
}
}

The end result is a compact night light that automatically lights up whenever the ambient light drops below the threshold level!

Future Development:
While functional, this is a very basic project. Some improvements I’d like to make are:

  • Make it portable by integrating a battery pack for a wireless night light
  • Design it into functional household objects like lamps, book lights, stair lights, etc.
  • Program different LED brightness levels based on duration of darkness

This first project taught me the basics of working with Arduino, simple circuits, analog inputs, and lighting control. I’m excited to level up my skills on more advanced projects!

Watch it in action here! :

IMG_4512

Reading Response – Her Code Got Humans on the Moon—And Invented Software Itself

This piece blew my mind. I had no idea that one of the key pioneers of modern software and coding was a working mom from the 1960s! The fact that Margaret Hamilton was leading an MIT team writing the onboard flight software for the Apollo missions while also bringing her daughter to work makes her such an icon.

Just let that sink in for a moment. At a time when women were expected to stay home and support their husband’s career, Hamilton was leading an MIT team writing the critical onboard flight software that allowed NASA to accomplish the seemingly impossible – landing astronauts on the lunar surface and returning them safely.

What makes it even more incredible is that she was doing this boundary-pushing work while also bringing her young daughter to the lab. Little Lauren was napping under mommy’s desk as Hamilton and her team were inventing core programming concepts like error prioritisation and asynchronous processing from scratch. Techniques that are still fundamental today!

The part about the “Little Old Ladies” literally weaving the software into indestructible copper wires is so fascinating. It’s a stark contrast to our current world of seamless cloud computing and automatic updates. But it captures the blind ambition and faith in human ingenuity that powered that era’s space race.

My favorite anecdote from the reading though is Hamilton advocating to add extra fault protection to the code because her daughter had exposed a flaw in the simulator – and NASA dismissing it as impossible. Then that exact scenario happening on the critical Apollo 8 mission and Hamilton’s protocol saving the day! What foresight.

Stories like this are such great reminders that the technological marvels we now take for granted were once radical frontiers explored by true visionaries and pioneers like Hamilton. At a time when the concept of “software” was barely understood, she had the brilliance to blaze that trail through the unknown and invent an entirely new discipline.

This was such an inspiring read!!

Reading Response – Three Teapots

Don Norman’s “Three Teapots” piece really got me thinking about how design isn’t just about pure functionality. The part that stuck out to me was when he talked about his three very different teapots – the bizarrely unusable Carelman one, the plain but practical Nanna pot, and the cleverly designed Ronnefeldt tilting teapot. Despite their varying levels of usability, Norman admits to using all three regularly depending on his mood and the situation. 

This challenges the idea that good design has to be 100% focused on usability above all else. Norman makes the point that aesthetics, emotion, and personal preferences also play a huge role in how we perceive and enjoy designed objects. His teapot collection shows that design excellence isn’t a one-size-fits-all thing – it’s about striking the right balance between functionality, beauty, and generating an emotional connection for the user.

I totally relate to this from my own experiences with products and objects. There have been times when something was highly usable but felt soulless and uninspiring. On the flip side, I’ve been drawn to gorgeous pieces of design that maybe weren’t the most practical but just made me feel good owning and using them. Norman reminds us that great design caters to our practical needs as humans, but also our emotional and aesthetic desires.

His points about how emotions influence our thinking and decision-making were also fascinating. The idea that positive emotions can boost our creativity and tolerance for small design flaws, while negative emotions can make us laser-focused but closed-off, is pretty mind-blowing. It makes me think designers need to consider the emotional resonance of their work, not just tick boxes for usability.

Overall, “Three Teapots” challenges the usability-over-everything mentality in a really insightful way. It argues that design should harmonize utility, beauty, and generate an emotional response in users based on their subjective needs and experiences. 

 

Crafting “Garden Guardian”: The Journey of My Midterm Project

When coming up with the idea for “Garden Guardian,” I wanted to make a charming, aesthetic game that had a challenging twist. A basic garden planting game didn’t seem very exciting on its own. I needed to add something to it.

However, this wasn’t always the case. In the very beginning, this project was pretty basic and boring. My first draft was just a game where you could plant flowers in a field, and that’s all it did. I didn’t focus much on how it looked during this early phase. It was pretty simple and not very attractive. This is what it looked like at first.

With the core functionality in place, I could now turn my attention to the aesthetics. To enhance the visuals, I utilized a combination of images generated by Dall-E and icons sourced from Google images. This allowed me to give the project a more polished and appealing look while retaining the foundational code I had already developed.

The game was pretty, but I wasn’t satisfied. That’s when I decided to throw in some pests causing trouble for the player’s garden. These pest invasions make the simple act of growing flowers into more of a defensive mission. Cultivating a colourful garden is still the main goal, but now you have to protect it as well.

Imagine this: you start with a blank canvas, ready to transform it into a breathtaking field of blooms. With a simple click, you can choose between flowers to add splashes of colour. 

But just when you think you’ve mastered the game, the real fun begins! Pesky pests appear out of nowhere, trying their best to kill your floral babies. That’s when your skills as a true Garden Guardian will be put to the test.

With this project, I really wanted to challenge my coding skills. One of the first challenges I faced was designing a game state management system. I wanted smooth transitions between the introduction screen, gameplay, and instructions. Through trial and error, I eventually settled on a streamlined approach, with the draw() function acting as the game’s heartbeat, constantly updating and rendering all the visuals used in the game (icons, buttons and backgrounds) based on the current state.

The drawGame() function became the centrepiece of my code, responsible for orchestrating the entire garden experience. I spent SO MANY hours refining this function, ensuring that the rendering of the garden background, the placement of icons, and the display of planted flowers all worked seamlessly together. I’m particularly proud of the highlighting technique I implemented, which draws attention to the currently selected icon, enhancing the overall user experience.

// Highlight the selected icon
noFill();
stroke(255, 204, 0); 
strokeWeight(2); 
let selectedIconIndex = selectedFlowerType - 1;
if (selectedIconIndex >= 0 && selectedIconIndex < iconPositions.length) {
  let pos = iconPositions[selectedIconIndex];
  rect(pos.x, pos.y, 50, 50, 10);

One of the most rewarding aspects of this project was creating the Flower class. Building a system to manage the lifecycle of each flower, from planting to potential infestation and treatment, was a true test of my object-oriented programming skills. The introducePest() method, which simulates the arrival of a pest and sets a timer for the flower’s demise if left untreated, was a particularly satisfying challenge to overcome. This took way too much time but as my mother would say, it felt like eating a ladoo (a delicious Indian dessert) when I could finally get it to work!

class Flower {
  constructor(x, y, type) {
    this.x = x;
    this.y = y;
    this.type = type;
    this.size = 50;
    this.hasPest = false;
    this.pestTimer = null;
  }

  display() {
    let img = [flowerImg1, flowerImg2, flowerImg3][this.type - 1];
    image(img, this.x - this.size / 2, this.y - this.size / 2, this.size, this.size);
    if (this.hasPest) {
      image(pestImg, this.x - this.size / 2, this.y - this.size / 2, this.size, this.size);
    }
  }

  introducePest() {
    if (!this.hasPest) {
      this.hasPest = true;
      this.pestTimer = setTimeout(() => this.die(), map(targetFlowers.planted, 0, targetFlowers.total, 4000, 3000));
    }
  }

  treatWithPesticide() {
    if (this.hasPest) {
      clearTimeout(this.pestTimer);
      this.hasPest = false;
      gameTime += 3;
    }
  }

  die() {
    let index = flowers.indexOf(this);
    if (index !== -1) {
      flowers.splice(index, 1);
      targetFlowers.planted = max(0, targetFlowers.planted - 1);
    }
  }
}

The Flower class encapsulates all the properties and behaviours of each flower in the garden. From managing the flower’s position, type, and size to handling pest infestations and treatment, this class is the backbone of the game’s core mechanics.

The introducePest() method is a prime example of the thought process behind crafting engaging gameplay. When a pest is introduced, a timer is set to simulate the potential demise of the flower if left untreated. The duration of this timer is dynamically adjusted based on the number of flowers already planted, increasing the difficulty as the game progresses. I had to do a lot of research (and some help from ChatGPT) to get this section working.  

Conversely, the treatWithPesticide() method allows players to counter the pest threat by using the pesticide icon. When a flower is treated, the pest timer is cleared, the hasPest flag is reset, and the player is rewarded with a few extra seconds on the game timer, encouraging strategic decision-making.

The die() method handles the removal of a flower from the game when it succumbs to a pest infestation. By splicing the flower from the flowers array and adjusting the targetFlowers.planted count, the game state is seamlessly updated, reflecting the player’s progress towards the target.

Throughout the development process, I encountered numerous roadblocks and debugging nightmares. However, each obstacle was an opportunity to learn and grow. I quickly realised the importance of modular code, which led me to create separate functions and classes for specific tasks, improving the overall readability and maintainability of my code.

Looking back on this journey, I’m filled with a sense of accomplishment and gratitude. “Garden Guardian” not only allowed me to create an entertaining game but also served as a valuable learning experience. I gained a deeper understanding of game mechanics, object-oriented programming, and the intricacies of creative coding with p5.js. Most importantly, I discovered the joy of problem-solving and the satisfaction of seeing my code come to life in the form of an engaging interactive experience.

However, there are still so many areas where I can improve and expand “Garden Guardian”: The scoring system needs work. Right now, you just win by planting enough flowers before time runs out. But I want a better system that scores you based on things like how many pests you treated, the variety of flowers planted, and maybe even keeping your garden completely pest-free. 

The difficulty progression could be better too. I think having the difficulty adapt based on the player’s performance would make it more engaging. If someone is struggling, the game could spawn fewer pests or give more time.

Visually, while the current look is charming, adding more detailed graphics, animations and effects could really enhance the overall aesthetic appeal. And new gameplay elements like power-ups, special abilities or different game modes could add lots of replay value.

During development, I ran into some tough challenges. Managing all the different timers and making sure the countdown was accurate when players gained extra time was really tricky. Weird edge cases, like planting flowers outside the garden area still need some work. 

Working through the issues I faced was a huge learning experience for me. It really emphasised the importance of thorough testing, keeping my code organised, and anticipating potential problems. Moving forward, I’ll apply those lessons to make my games even more polished.

“Garden Guardian” may be a small project, but it represents a big milestone in my coding journey. I had so much fun tackling challenges and adding and improving features and I cannot wait to experiment with game dev more!