Week 10: Musical Instrument

Link to video demo : https://drive.google.com/file/d/1KGj_M7xq6IdsS2Qwq-zbjjspCPPgcaj4/view?usp=sharing

For this assignment, I decided to build a digital trumpet using an Arduino Uno, three push buttons, a potentiometer, and a speaker. My goal was to simulate the behavior of a real trumpet in a fun and creative way, even though I knew the sound would be more electronic than acoustic. It was a great opportunity for me to explore how hardware and code can come together to create music, and I ended up learning a lot about sound generation and analog input in the process.

The concept was simple: each of the three buttons acts like a trumpet valve, and each one triggers a different note — specifically G4, A4, and B4. These are represented in the code as fixed frequencies (392 Hz, 440 Hz, and 494 Hz). When I press one of the buttons, the Arduino sends a signal to the speaker to play the corresponding note. The potentiometer is connected to analog pin A0 and is used to control the volume. This was a really cool addition because it gave the instrument a bit of expressive control — just like how a real musician might vary their breath to change the loudness of a note.

To make the sound a bit more interesting and less robotic, I added a little “vibrato” effect by randomly adjusting the pitch slightly while the note is playing. This gives the tone a subtle wobble that sounds more natural — kind of like the way a real trumpet player might shape a note with their lips. It’s still a square wave, and it’s definitely digital-sounding, but it gives it more character than just playing a flat, unchanging frequency.

If I were to continue developing this project, I have a few ideas for improvements. One would be to add more buttons or allow combinations of the three to create more notes — like a real trumpet with multiple valve positions. I’d also love to add some kind of envelope shaping, so the notes could have a smoother fade-in or fade-out instead of sounding flat and abrupt. It might also be fun to hook the project up to MIDI so it could control a software synthesizer and produce higher quality trumpet sounds. And for an extra visual touch, I could add LEDs that light up in sync with the music.

CODE :

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const int potPin = A0; // Potentiometer for volume
const int speakerPin = 8; // Speaker on PWM pin
const int buttonPins[] = {2, 3, 4}; // 3 buttons = 3 different notes
// Trumpet-like frequencies (roughly G4, A4, B4)
const int trumpetNotes[] = {392, 440, 494};
void setup() {
for (int i = 0; i < 3; i++) {
pinMode(buttonPins[i], INPUT); // External pull-down resistors
}
pinMode(speakerPin, OUTPUT);
}
void loop() {
int volume = analogRead(potPin) / 4;
for (int i = 0; i < 3; i++) {
if (digitalRead(buttonPins[i]) == HIGH) {
playTrumpetNote(trumpetNotes[i], volume);
}
}
delay(10);
}
void playTrumpetNote(int baseFreq, int volume) {
unsigned long duration = 10000; // microseconds per cycle
unsigned long startTime = micros();
while (micros() - startTime < duration) {
// Slight pitch wobble
int vibrato = random(-3, 3);
int currentFreq = baseFreq + vibrato;
int halfPeriod = 1000000 / currentFreq / 2;
analogWrite(speakerPin, volume);
delayMicroseconds(halfPeriod);
analogWrite(speakerPin, 0);
delayMicroseconds(halfPeriod);
}
}
const int potPin = A0; // Potentiometer for volume const int speakerPin = 8; // Speaker on PWM pin const int buttonPins[] = {2, 3, 4}; // 3 buttons = 3 different notes // Trumpet-like frequencies (roughly G4, A4, B4) const int trumpetNotes[] = {392, 440, 494}; void setup() { for (int i = 0; i < 3; i++) { pinMode(buttonPins[i], INPUT); // External pull-down resistors } pinMode(speakerPin, OUTPUT); } void loop() { int volume = analogRead(potPin) / 4; for (int i = 0; i < 3; i++) { if (digitalRead(buttonPins[i]) == HIGH) { playTrumpetNote(trumpetNotes[i], volume); } } delay(10); } void playTrumpetNote(int baseFreq, int volume) { unsigned long duration = 10000; // microseconds per cycle unsigned long startTime = micros(); while (micros() - startTime < duration) { // Slight pitch wobble int vibrato = random(-3, 3); int currentFreq = baseFreq + vibrato; int halfPeriod = 1000000 / currentFreq / 2; analogWrite(speakerPin, volume); delayMicroseconds(halfPeriod); analogWrite(speakerPin, 0); delayMicroseconds(halfPeriod); } }
const int potPin = A0;          // Potentiometer for volume
const int speakerPin = 8;       // Speaker on PWM pin
const int buttonPins[] = {2, 3, 4}; // 3 buttons = 3 different notes

// Trumpet-like frequencies (roughly G4, A4, B4)
const int trumpetNotes[] = {392, 440, 494}; 

void setup() {
  for (int i = 0; i < 3; i++) {
    pinMode(buttonPins[i], INPUT); // External pull-down resistors
  }
  pinMode(speakerPin, OUTPUT);
}

void loop() {
  int volume = analogRead(potPin) / 4;

  for (int i = 0; i < 3; i++) {
    if (digitalRead(buttonPins[i]) == HIGH) {
      playTrumpetNote(trumpetNotes[i], volume);
    }
  }

  delay(10); 
}

void playTrumpetNote(int baseFreq, int volume) {
  unsigned long duration = 10000; // microseconds per cycle
  unsigned long startTime = micros();

  while (micros() - startTime < duration) {
    // Slight pitch wobble
    int vibrato = random(-3, 3);
    int currentFreq = baseFreq + vibrato;
    int halfPeriod = 1000000 / currentFreq / 2;

    analogWrite(speakerPin, volume);
    delayMicroseconds(halfPeriod);
    analogWrite(speakerPin, 0);
    delayMicroseconds(halfPeriod);
  }
}

 

Week 10 – Reading Response

Reading A Brief Rant on the Future of Interaction Design felt less like a critique of current technology and more like a reminder of how disconnected we’ve become from our own bodies when interacting with digital tools. What stood out to me wasn’t just the fixation on touch or screens, but the larger idea that we’ve built a digital world that doesn’t physically involve us much at all. We sit, swipe, and speak to our devices, but rarely do we engage with them in a way that feels natural or satisfying. That idea made me reflect on how strange it is that we’ve accepted this passive interaction style as normal, even though it barely scratches the surface of what our senses and motor skills are capable of. The rant made me question whether convenience has quietly replaced depth in our relationship with technology.

What also struck me was the underlying urgency — not just to change what we build, but to change how we think about building. It challenged the assumption that progress is purely about making things smaller, faster, or more responsive. Instead, it asked: what if we measured progress by how much it involves us — our movement, our perception, our ability to explore ideas physically, not just conceptually? It reminded me that interaction design isn’t only about the interface; it’s about the experience and how deeply it aligns with our human nature. This reading didn’t just shift my thinking about interfaces — it made me realize that future design needs to be less about controlling machines and more about collaborating with them through a fuller range of expression. That’s a future I’d like to be part of.

Week 9 – Reading Response

Reading “Physical Computing’s Greatest Hits (and Misses)” was strangely comforting and inspiring at the same time. It helped me realize that a lot of the ideas I thought were already “done” or cliché—like using sensors to make interactive gloves or LED-based projects—are actually important milestones in learning and creativity. I used to feel discouraged when I saw someone else had already made something similar to what I had in mind, but this piece reframed that completely. It emphasized how each version of a repeated idea can still be fresh and meaningful when approached from a personal or unique angle. I found myself especially drawn to the “Fields of Grass” and “Things You Yell At” themes—they really match how I want people to feel something tactile or emotional when they interact with my work. This gave me permission to play, iterate, and remix existing concepts without feeling like I have to reinvent the wheel just to be valid.

That sense of permission and openness carried over into “Making Interactive Art: Set the Stage, Then Shut Up and Listen,” which really shifted how I think about authorship and control in interactive work. I’ve always felt the need to guide viewers to the “correct” meaning, but Tigoe’s argument for stepping back and letting the audience complete the piece through their own actions hit me hard. It reminded me that the most memorable interactions I’ve had with art happened when I could explore it freely, without being told what to think. The comparison to directing actors—offering intentions instead of rigid instructions—really reframed how I might approach building experiences. I’m beginning to see interactive art less like a fixed statement and more like a space for dialogue, where the audience brings the work to life and creates meaning in real time.

Week 9: Analog input & output

Video demo link : https://drive.google.com/file/d/1KHeKfNwfINI-l48dOEf03Td8BbLoIRbe/view?usp=drive_link

Hand-drawn schematic:

For this assignment, I set out to build a simple system using both a digital and an analog sensor to control two separate LEDs. My chosen components were an LDR (light-dependent resistor) for the analog input and a push button for the digital one. I liked the idea of mixing natural input (like ambient light) with a more deliberate human interaction (pressing a button) to affect two types of light output.

To keep things manageable, I started with just the LDR and one LED. The goal was to make the LED change brightness based on how much light the sensor picked up. I wired the LDR as part of a voltage divider, connected it to an analog input pin, and ran a basic analogRead() loop to see the values coming in. From there, I used the map() function to translate the light readings (from 0 to 1023) into PWM values (0 to 255) for controlling LED brightness. I also inverted the mapping, so the LED would get brighter when it was darker — which just felt more intuitive, like a night light.

Once that was working, I added the digital side of things with a simple push button. Pressing the button would turn a second LED on, and releasing it would turn it off. Simple on/off logic with digitalRead() and digitalWrite() did the trick. It was satisfying to see both LEDs responding to such different kinds of input — one gradual and ambient, the other instant and tactile.

One of the challenges I ran into was just getting reliable readings from the button. At first, it was behaving erratically because I forgot to use a pull-down resistor. Once I added that in, everything stabilized. I also realized pretty quickly that without any sort of filtering or delay, the analog LED could flicker a bit depending on the room lighting, so I added a small delay(100) to smooth things out a little.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const int ldrPin = A0; // LDR connected to analog pin A0
const int buttonPin = 2; // Push button connected to digital pin 2
const int ledAnalogPin = 9; // PWM LED pin
const int ledDigitalPin = 12; // On/off LED pin
void setup() {
pinMode(buttonPin, INPUT);
pinMode(ledAnalogPin, OUTPUT);
pinMode(ledDigitalPin, OUTPUT);
Serial.begin(9600);
}
void loop() {
int ldrValue = analogRead(ldrPin);
int brightness = map(ldrValue, 0, 1023, 255, 0); // Brighter in darkness
analogWrite(ledAnalogPin, brightness);
int buttonState = digitalRead(buttonPin);
if (buttonState == HIGH) {
digitalWrite(ledDigitalPin, HIGH);
} else {
digitalWrite(ledDigitalPin, LOW);
}
delay(100);
}
const int ldrPin = A0; // LDR connected to analog pin A0 const int buttonPin = 2; // Push button connected to digital pin 2 const int ledAnalogPin = 9; // PWM LED pin const int ledDigitalPin = 12; // On/off LED pin void setup() { pinMode(buttonPin, INPUT); pinMode(ledAnalogPin, OUTPUT); pinMode(ledDigitalPin, OUTPUT); Serial.begin(9600); } void loop() { int ldrValue = analogRead(ldrPin); int brightness = map(ldrValue, 0, 1023, 255, 0); // Brighter in darkness analogWrite(ledAnalogPin, brightness); int buttonState = digitalRead(buttonPin); if (buttonState == HIGH) { digitalWrite(ledDigitalPin, HIGH); } else { digitalWrite(ledDigitalPin, LOW); } delay(100); }
const int ldrPin = A0;              // LDR connected to analog pin A0
const int buttonPin = 2;            // Push button connected to digital pin 2
const int ledAnalogPin = 9;         // PWM LED pin
const int ledDigitalPin = 12;       // On/off LED pin

void setup() {
  pinMode(buttonPin, INPUT);
  pinMode(ledAnalogPin, OUTPUT);
  pinMode(ledDigitalPin, OUTPUT);
  Serial.begin(9600);
}

void loop() {
  int ldrValue = analogRead(ldrPin);        
  int brightness = map(ldrValue, 0, 1023, 255, 0);  // Brighter in darkness

  analogWrite(ledAnalogPin, brightness);

  int buttonState = digitalRead(buttonPin);

  if (buttonState == HIGH) {
    digitalWrite(ledDigitalPin, HIGH);
  } else {
    digitalWrite(ledDigitalPin, LOW);
  }

  delay(100);
}

Looking back, the most satisfying part was seeing the analog LED respond in real-time to light changes. Just waving my hand over the sensor and watching the LED slowly brighten gave a cool sense of control — like making a light react to shadows. It reminded me of automatic lights in stairways or hotel lobbies, except mine was sitting on a breadboard.

In the future, I’d like to swap the button for a capacitive touch sensor or maybe even a motion detector, so the LED lights up when someone walks by. Or take the LDR further by combining it with an RGB LED that changes color depending on how dark it is — there’s a lot of room to build from this foundation.

Week 8 : Reading Response

Reading about Margaret Hamilton’s story was incredibly inspiring—especially the way she navigated a male-dominated field with intelligence and persistence. What stood out to me most was how seriously she took her code, and how she fought to ensure software was treated as a critical part of the Apollo mission. I admired her foresight and the way she challenged the idea that software was somehow secondary to hardware or engineering. As someone learning to code, I often feel like software is invisible or taken for granted, so seeing Hamilton’s impact reinforced how powerful and essential it really is. Her story makes me feel like being detail-oriented, stubborn, and thoughtful in code can actually change history.

Norman’s piece made me reflect on how often I prioritize function over form—especially as someone who tinkers with circuits and code. But his argument that emotional design improves usability resonated with me deeply. I’ve definitely had moments where a sleek interface or an intuitively designed device made me feel more confident and willing to explore, even if I didn’t fully understand the technical side yet. It’s fascinating to think that our emotional response can override frustration or confusion. The idea that beauty encourages persistence really stuck with me—I now realize that good design isn’t just about solving a problem, but about shaping how people feel while solving it.

Week 8 – Creative switch

 

Link to demo :

https://drive.google.com/drive/folders/1VTiRrGRrAj3aiTELux77pBYvCo3-3btS?usp=drive_link

For this week’s unusual switch assignment, I wanted to create something active and game-like — something that felt more like play than a standard circuit. That’s how I ended up designing a target switch using a cardboard folder, aluminum foil, and a ball. Instead of pressing a button or stepping on a pedal, the switch is triggered when I successfully throw a ball at the target. The core of my design is a DIY target made by slightly opening a cardboard folder and placing aluminum foil inside each flap. These foil strips are wired to the Arduino, and act as the two sides of a switch. When the ball hits the folder, it causes the foil pieces to touch momentarily — closing the circuit and turning on an LED.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const int SwitchPin = 3;
const int LEDPin = 12;
void setup() {
pinMode(SwitchPin, INPUT_PULLUP);
pinMode(LEDPin, OUTPUT);
}
void loop() {
const int Input = digitalRead(SwitchPin);
if (Input == LOW) {
digitalWrite(LEDPin, HIGH); // LED ON when ball hits target
} else {
digitalWrite(LEDPin, LOW); // LED OFF otherwise
}
}
const int SwitchPin = 3; const int LEDPin = 12; void setup() { pinMode(SwitchPin, INPUT_PULLUP); pinMode(LEDPin, OUTPUT); } void loop() { const int Input = digitalRead(SwitchPin); if (Input == LOW) { digitalWrite(LEDPin, HIGH); // LED ON when ball hits target } else { digitalWrite(LEDPin, LOW); // LED OFF otherwise } }
const int SwitchPin = 3;
const int LEDPin = 12;

void setup() {
  pinMode(SwitchPin, INPUT_PULLUP); 
  pinMode(LEDPin, OUTPUT);
}

void loop() {
  const int Input = digitalRead(SwitchPin);

  if (Input == LOW) {
    digitalWrite(LEDPin, HIGH); // LED ON when ball hits target
  } else {
    digitalWrite(LEDPin, LOW);  // LED OFF otherwise
  }
}

The biggest challenge was making sure the foil only touches when I want it to — in this case, only when the ball hits the folder. I had to tape the foil securely and position it so that the folder remained slightly open. If it’s too loose, the foil touches on its own; too tight, and the impact doesn’t close the circuit. This project gave me a better understanding of how a simple digital input can be adapted into a physical interaction, using only basic materials like cardboard and foil. I also gained more confidence working with pull-up resistors and reading pin states accurately.

Midterm Project

Concept

I’ve always been fascinated by spooky stories, yet wanted to create something more lighthearted than a typical horror game. That is how I ended up making a short, click-based project in which the player buys a haunted house at a bargain price and must purge it of unwanted guests—literally ghosts—to unlock its true potential. From the outset, I imagined a game with a touch of urgency: each wave of ghosts adds just enough pressure to keep your heart rate up, but not so much as to be punishing or relentlessly scary. The result is a game that blends frantic clicking with a fun, eerie atmosphere and a simple storyline that sets the stakes clearly: fail, and the ghosts overwhelm you; succeed, and the house is yours, free of all things that go bump in the night.

To cement that narrative, I designed three distinct levels, each with its own flavor of tension. Level 1’s stationary ghosts ease the player in, Level 2’s moving ghosts ramp up speed and require more hits, and Level 3 features a boss ghost who teleports unpredictably to keep the player on their toes. Across these levels, the fundamental goal never changes—click fast, or you risk running out of time—but the variety of ghost behavior and the gradually intensifying difficulty create a neat progression. The final reward is a bright, newly revealed house, signifying the end of the haunting and the triumph of actually getting to live in your newly bought property.

How the Project Works

The code behind this game employs a simple state machine to orchestrate its various parts. I found this approach more intuitive than cramming all logic into one giant loop. Whenever the player transitions from, say, the INTRO state to LEVEL1, the script calls startLevel(level), which resets the timer, spawns the appropriate ghosts, and ensures there’s no carry-over from earlier states. Here is a small excerpt that illustrates how the game transitions from one level to the next or ends if all ghosts are defeated:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
if (ghosts.length === 0) {
if (level === 1) {
state = "LEVEL2";
startLevel(2);
} else if (level === 2) {
state = "LEVEL3";
startLevel(3);
} else if (level === 3) {
state = "WIN";
ghostSound.stop();
victorySound.play();
}
}
if (ghosts.length === 0) { if (level === 1) { state = "LEVEL2"; startLevel(2); } else if (level === 2) { state = "LEVEL3"; startLevel(3); } else if (level === 3) { state = "WIN"; ghostSound.stop(); victorySound.play(); } }
if (ghosts.length === 0) {
  if (level === 1) {
    state = "LEVEL2";
    startLevel(2);
  } else if (level === 2) {
    state = "LEVEL3";
    startLevel(3);
  } else if (level === 3) {
    state = "WIN";
    ghostSound.stop();
    victorySound.play();
  }
}

I’m particularly proud of how coherent each ghost type’s logic is, thanks to object-oriented design. I broke down the ghosts into three classes—StaticGhost, MovingGhost, and BossGhost—so each can handle its own movement and hit requirements. For instance, the MovingGhost class includes velocities in both x and y directions. It updates its position each frame and bounces off the canvas edges, which felt more elegant than scattering if-conditions all over the main draw() loop. This design choice also eases extension: if I someday add a new ghost that splits into two smaller ghosts on hit, I can isolate that behavior in yet another class.

Moreover, I love the inclusion of sound elements, as it heightens the atmosphere. The ghostly background audio starts the moment you tackle Level 1, giving a sense of stepping into a haunted property. On every successful hit, a short “blast” sound triggers, reinforcing the feedback loop for players clicking in a hurry. When the boss ghost is defeated, the game transitions to a “WIN” screen with a triumphant sound, showing off the newly purified house. These little touches bring a pleasant cohesiveness to the otherwise basic act of clicking on sprites and add a layer of fun that purely visual feedback might not achieve.

Areas for Improvement & Challenges Faced

While I’m delighted with the final result, I definitely noticed some spots that could be enhanced through further iteration. First, game balancing was tricky. If I set the time limits too high, the game lost its tension because players could leisurely pick off ghosts. If I made them too strict, new players often found themselves losing before they fully grasped the ghost patterns. I settled on modest values for each level, but it still demands some quick reflexes—if someone isn’t used to rapid clicking, they might find the default times a bit harsh. Introducing difficulty modes, or maybe timed boosts that freeze the ghosts for a short period, would give me more fine control over that difficulty curve and cater to different skill levels.

Another challenge was ensuring that state transitions stayed seamless. At one point, I had ghosts from Level 1 lingering into Level 2, causing the dreaded “two waves at once” bug. The fix involved carefully resetting arrays and timers inside startLevel(level) and confirming no leftover references were carried over. Though it took some debugging, it taught me the importance of thorough housekeeping when cycling through game phases—particularly in an experience that uses back-to-back waves, each with its own unique ghost behaviors. If I expand the project in the future, perhaps adding more levels or extra ghost abilities, I’ll rely heavily on these same organizational principles to keep the code from becoming unwieldy and prone to surprises. I feel the game meets my initial ambition: to craft a short, fun, and spooky challenge that doesn’t overstay its welcome. It balances straightforward gameplay—just point-and-click to vanquish ghosts—with enough variety to keep the three levels distinct and interesting. As I continue refining or building upon the concept, I plan to experiment with new ghost types or reward systems that push the idea even further, but I’m proud of how the current version stands on its own as a playful haunted-house journey.

Week 5: Reading Response

When I first delved into Golan Levin’s discussion of computer vision, I was amazed by how fundamentally different it is from our human way of perceiving the world. We can glance at a room and instantly recognize faces, objects, or even subtle differences in lighting, but a computer needs methodical instructions to interpret even the simplest movements or contrasts. Techniques like background subtraction and frame differencing demonstrate how each pixel’s change or brightness must be computed step by step, and how crucial controlled environments can be for effective tracking. In a way, I found this both exciting and challenging: exciting because it opens up possibilities for precise, algorithmic interactions, but challenging because it shows just how quickly a system can fail if the lighting shifts or a background changes color. Through these examples, I realized that crafting the right physical setup—whether that means backlighting a subject or using distinct color markers—can dramatically improve what a computer can “see” and do.

Building on these observations, I became even more intrigued when I saw how these vision techniques are applied in interactive art. While projects like Myron Krueger’s Videoplace demonstrate the joyful, participatory side of computer vision—allowing people to engage with entire environments using body gestures—David Rokeby’s Sorting Daemon reminds us that the same technology can be used for unsettling surveillance. Reflecting on these projects, I’ve come to appreciate how computer vision can empower artists to transform spectators into active participants, yet it also raises important ethical questions about privacy and consent. This duality fascinates me: on one hand, the capacity to create immersive, responsive installations feels almost magical; on the other, the act of monitoring people, even for art, can be deeply discomforting. Levin’s exploration emphasizes that as designers and creators, we need to balance our excitement for technical innovation with a thoughtful awareness of its broader social implications.

Week 5: Midterm Progress

Concept

I’m in the process of creating a haunted-house-themed game where the player has purchased a dilapidated property, mainly because it was so cheap—no one else wanted it. The catch is that it’s infested with ghosts. My overarching goal is for the player to banish these ghosts, wave by wave, ultimately revealing the house’s true beauty once it’s free of any supernatural presence. I want to capture a sense of gradual transformation: at first, the environment is dim and unnerving, but it transitions to a bright, welcoming home as the player defeats all the ghosts.

Progress So Far

So far, I have built out a state machine that includes an intro screen, a main “PLAY” state, and placeholders for the final “WIN” and “END” screens. In the intro, the game briefly explains the story—that the house was bought cheaply because of the hauntings—then moves to the main gameplay once the user clicks the start button. The basic logic for wave progression is in place: after the initial wave of slower ghosts, I plan to introduce a second wave of faster ghosts, and ultimately a boss ghost that requires multiple hits to defeat. Each ghost’s code has been thoroughly tested in small increments, ensuring that the transition between waves feels smooth. I also integrated a rudimentary health system; each time a ghost vanishes without being clicked, the player’s health decreases, raising the stakes as they progress.

Class Implementation

A key aspect of my current setup is the Ghost class, which I designed to handle movement, timing, and click interaction. By encapsulating these behaviors, I’ve managed to keep my main draw() loop more organized and make it simpler to test the game’s logic wave by wave. Here is a condensed version of the Ghost class:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
class Ghost {
constructor(x, y) {
this.x = x;
this.y = y;
this.size = 60;
this.visible = true;
this.wasClicked = false;
this.vx = random(-2, 2);
this.vy = random(-2, 2);
this.spawnTime = millis();
this.lifespan = 5000; // 5 seconds
}
move() {
if (!this.visible) return;
this.x += this.vx;
this.y += this.vy;
if (this.x < 0 || this.x + this.size > width) {
this.vx *= -1;
}
if (this.y < 0 || this.y + this.size > height) {
this.vy *= -1;
}
if (millis() - this.spawnTime > this.lifespan) {
this.visible = false;
}
}
display() {
if (this.visible) {
image(ghostImg, this.x, this.y, this.size, this.size);
}
}
isClicked(mx, my) {
return (
this.visible &&
mx > this.x && mx < this.x + this.size &&
my > this.y && my < this.y + this.size
);
}
vanish() {
this.visible = false;
this.wasClicked = true;
}
}
class Ghost { constructor(x, y) { this.x = x; this.y = y; this.size = 60; this.visible = true; this.wasClicked = false; this.vx = random(-2, 2); this.vy = random(-2, 2); this.spawnTime = millis(); this.lifespan = 5000; // 5 seconds } move() { if (!this.visible) return; this.x += this.vx; this.y += this.vy; if (this.x < 0 || this.x + this.size > width) { this.vx *= -1; } if (this.y < 0 || this.y + this.size > height) { this.vy *= -1; } if (millis() - this.spawnTime > this.lifespan) { this.visible = false; } } display() { if (this.visible) { image(ghostImg, this.x, this.y, this.size, this.size); } } isClicked(mx, my) { return ( this.visible && mx > this.x && mx < this.x + this.size && my > this.y && my < this.y + this.size ); } vanish() { this.visible = false; this.wasClicked = true; } }
class Ghost {
  constructor(x, y) {
    this.x = x;
    this.y = y;
    this.size = 60;
    this.visible = true;
    this.wasClicked = false;
    this.vx = random(-2, 2);
    this.vy = random(-2, 2);
    this.spawnTime = millis();
    this.lifespan = 5000; // 5 seconds
  }

  move() {
    if (!this.visible) return;
    this.x += this.vx;
    this.y += this.vy;
    if (this.x < 0 || this.x + this.size > width) {
      this.vx *= -1;
    }
    if (this.y < 0 || this.y + this.size > height) {
      this.vy *= -1;
    }
    if (millis() - this.spawnTime > this.lifespan) {
      this.visible = false;
    }
  }

  display() {
    if (this.visible) {
      image(ghostImg, this.x, this.y, this.size, this.size);
    }
  }

  isClicked(mx, my) {
    return (
      this.visible &&
      mx > this.x && mx < this.x + this.size &&
      my > this.y && my < this.y + this.size
    );
  }

  vanish() {
    this.visible = false;
    this.wasClicked = true;
  }
}

Using this as a foundation, I have also been working on subclasses like FastGhost and BossGhost to provide unique behaviors—faster speed, shorter lifespans, or requiring multiple hits to defeat. This object-oriented structure ensures I can easily add or modify ghost types without complicating the main game flow.

Frightening / Challenging Aspects

One of the biggest challenges I’m facing is tuning the difficulty so it feels suspenseful without being overly punishing. In particular, I need to strike a balance between ghost speed, lifespan, and the number of ghosts per wave. If ghosts vanish too slowly, it’s too easy; if they move or time out too quickly, it becomes frustrating. Another tricky part is creating a strong contrast between the haunting atmosphere at the start and the serene, beautiful environment at the end—this requires careful coordination of art assets, lighting (or color usage), and the timing of transitions so players truly feel like they’ve “rescued” the house from a dark fate.

Risk Prevention

I have set up the game flow using distinct states—“INTRO,” “PLAY,” “WIN,” and “END”—to keep code separated and avoid any messy overlaps. Testing each state individually helps isolate potential bugs early. I also made sure that all ghost interaction, movement, and collision logic lives within their respective classes, so if there’s an issue with a particular ghost type, I know exactly where to look for a fix. By incrementally adding waves and testing them (rather than coding all three at once), I can ensure that each wave behaves as intended and that wave transitions don’t break the health or scoring systems. This structured approach reduces the chance of large-scale errors going unnoticed until late in development.

Next Steps

I still need to fully integrate the final boss ghost, which will serve as the game’s climactic encounter. Once the boss is in place, I will refine the wave progression so that defeating the boss triggers a major visual shift—the unveiling of the house’s hidden beauty. Finally, I’ll spend time polishing transitions between states, adjusting ghost behaviors, and ensuring that the health system and scoring remain engaging but fair. My aim is for players to feel a real sense of accomplishment when they see the haunting gloom replaced by a warm, inviting dwelling, thereby completing the game’s central promise of transforming a scary, cheap purchase into a comfortable new home.

 

Assignment 4: Generative Text

In this assignment, I created an interactive visualization featuring animated letters that swirl, drift, and change colors as they fall across the screen. The letters are selected randomly from a character set of uppercase English letters and numbers. Each letter moves independently with unique properties such as speed, rotation, color cycling, and sinusoidal horizontal drift. The use of transparency in the background creates a subtle trailing effect, enhancing the sense of motion and fluidity. The goal of this project was to create an aesthetically engaging and dynamic animation with a mix of structured randomness and organic movement.

One part of the code that I am particularly proud of is the display() method inside the SwirlingLetter class. This function not only ensures smooth rendering of each letter but also cycles through colors using sinusoidal variations. This approach creates a mesmerizing shifting color effect without requiring predefined color schemes. Another aspect I appreciate is the reset mechanism in update(), which seamlessly resets letters when they fall below the canvas, ensuring a continuous flow of animation. Looking ahead, possible improvements include adding user interactivity, such as allowing the mouse to influence letter movement or introducing gravity variations for more dynamic behavior. Another potential enhancement could involve applying Perlin noise for smoother, more naturalistic drifting patterns, making the animation feel even more organic.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
display() {
// Calculate color based on colorPhase (sin/cos wave)
let r = 127 + 127 * sin(this.colorPhase);
let g = 127 + 127 * sin(this.colorPhase + TWO_PI / 3);
let b = 127 + 127 * sin(this.colorPhase + (2 * TWO_PI) / 3);
push();
translate(this.x, this.y);
rotate(this.angle);
textSize(this.fontSize);
fill(r, g, b);
text(this.char, 0, 0);
pop();
}
display() { // Calculate color based on colorPhase (sin/cos wave) let r = 127 + 127 * sin(this.colorPhase); let g = 127 + 127 * sin(this.colorPhase + TWO_PI / 3); let b = 127 + 127 * sin(this.colorPhase + (2 * TWO_PI) / 3); push(); translate(this.x, this.y); rotate(this.angle); textSize(this.fontSize); fill(r, g, b); text(this.char, 0, 0); pop(); }
display() {
  // Calculate color based on colorPhase (sin/cos wave)
  let r = 127 + 127 * sin(this.colorPhase);
  let g = 127 + 127 * sin(this.colorPhase + TWO_PI / 3);
  let b = 127 + 127 * sin(this.colorPhase + (2 * TWO_PI) / 3);
  
  push();
    translate(this.x, this.y);
    rotate(this.angle);
    textSize(this.fontSize);
    fill(r, g, b);
    text(this.char, 0, 0);
  pop();
}