Week 9: Reading Responses

Physical Computing’s Greatest hits and misses

Reading this text really made me think differently about what it means to design interfaces, especially ones that don’t rely on screens or traditional controls. The idea that your own hands can act as a cursor, like in the Atlas Gloves project, really struck me. It’s such a nice twist on a familiar interaction we use on a daily basis, and it made me realize that innovation can come from reimagining how we use our bodies every day to communicate with technology. Oftentimes, innovation is synonymous to me with inventing entirely new tools, but this showed me that you can use simple materials like a couple of LEDs, gloves, and a webcam and still end up with something cool and interactive.

What also stood out to me was how these projects prioritize experience and embodiment. The Atlas Gloves weren’t just a technical experiment, but rather about movement, spatial awareness, and making the virtual world feel physically accessible. That made me realize that physical computing is as much about how people feel when they use something as it is about how it works. Whether it’s navigating Google Earth with a wave of your hand or playing a theremin-style instrument with motion, there’s a strong emotional and sensory layer involved. That really inspired me to think about my own projects in this class not just as tools or tasks, but as ways to spark connection and curiosity in the people who use them. As a side note, it also really reminded me of kinect sensors on Xbox where you can bowl by doing the motion of bowling or play table tennis my pretending to hold a paddle and smacking.

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

Reading Tom Igoe’s article “Making Interactive Art: Set the Stage, Then Shut Up and Listen” made me reconsider the role of an artist in interactive installations. I used to believe that providing detailed explanations would help audiences connect with my work, but Igoe suggests that over-explaining can limit personal interpretation. He emphasizes creating a context that encourages participants to explore and derive their own meanings, comparing this approach to a director guiding actors without dictating their every move. This perspective highlights the importance of designing experiences that invite engagement and allow for a range of responses, which makes me reflect on how I can craft environments that speak for themselves and foster genuine interaction. It’s also a true testament to how self-explanatory what you create is for people. Like our midterm projects or assignments, we often have to direct our classmates on how to use the controls because we didn’t make it completely obvious. It’s easy to forget that not everyone knows how it was made and how it is supposed to work. Seeing how others try to make it work and whether they get it right rather than explaining makes the interaction much better.

Week 9: Analog and Digital Sensors Assignment

Google Drive Link: https://drive.google.com/file/d/1_hd31ynpr4AzkeD99QR3nakPaNJlEiRF/view?usp=sharing

My idea for this assignment was to have a light that would automatically turn on when it was dark in the room while the other light could be manually turned on. Kind of like a smart light vs a regular light switch light. To do this is use the photoresistor to get the values of the brightness in the room and coded the arduino such that under a certain threshold, the light would automatically turn on.

The circuit diagram looked like this:

The code for it can be seen here:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const int LED_PIN = 9;
const int LIGHT_THRESHOLD = 500;
// the setup routine runs once when you press reset:
void setup() {
// initialize serial communication at 9600 bits per second:
pinMode(LED_PIN, OUTPUT);
Serial.begin(9600);
}
// the loop routine runs over and over again forever:
void loop() {
// read the input on analog pin
int sensorValue = analogRead(A2);
// print out the value you read:
Serial.println(sensorValue);
delay(1); // delay in between reads for stability
if (sensorValue < LIGHT_THRESHOLD) { //codes the light to turn on when brightness is low
digitalWrite(LED_PIN, HIGH);
} else {
digitalWrite(LED_PIN, LOW);
}
delay(100);
}
const int LED_PIN = 9; const int LIGHT_THRESHOLD = 500; // the setup routine runs once when you press reset: void setup() { // initialize serial communication at 9600 bits per second: pinMode(LED_PIN, OUTPUT); Serial.begin(9600); } // the loop routine runs over and over again forever: void loop() { // read the input on analog pin int sensorValue = analogRead(A2); // print out the value you read: Serial.println(sensorValue); delay(1); // delay in between reads for stability if (sensorValue < LIGHT_THRESHOLD) { //codes the light to turn on when brightness is low digitalWrite(LED_PIN, HIGH); } else { digitalWrite(LED_PIN, LOW); } delay(100); }
const int LED_PIN = 9;
const int LIGHT_THRESHOLD = 500;
// the setup routine runs once when you press reset:
void setup() {
  // initialize serial communication at 9600 bits per second:
  pinMode(LED_PIN, OUTPUT);
  Serial.begin(9600);
}

// the loop routine runs over and over again forever:
void loop() {
  // read the input on analog pin
  int sensorValue = analogRead(A2);
  // print out the value you read:
  Serial.println(sensorValue);
  delay(1);  // delay in between reads for stability
  if (sensorValue < LIGHT_THRESHOLD) { //codes the light to turn on when brightness is low
    digitalWrite(LED_PIN, HIGH);
  } else {
    digitalWrite(LED_PIN, LOW);
  }
  delay(100);
}


Overall, it wasn’t too difficult. I just struggled a bit with getting the wires in the correct places and accidentally blew a light because the resistor wasn’t plugged in all the way. It’s tricky dealing with so many wires on the board, I wish it could look more streamlined.

 

Week 8 Reading Responses

Norman, “Emotion & Design: Attractive things work better”

In this reading, Norman’s idea of “attractive things work better” really struck me because I didn’t realize how forgiving I was of things that were more aesthetically pleasing. This plays a huge role when it comes to marketing because brands that are marketed as much more luxury and aesthetic have a positive affect on people compared to cheaper, less aesthetic brands. This made me reflect on my own experiences, like how I’m more patient with my prettier jewelry pieces even if the clasp is annoying, whereas I quickly become irritated with an cheaper and less aesthetic jewelry, even if it technically functions well. Norman also discusses how anxiety can interfere with usability by causing stress, which narrows a person’s ability to think flexibly and adapt. I found it fascinating that aesthetics can influence not just perception but also actual performance, as users who feel calm and engaged are much better equipped to navigate challenges that come with items.

Her Code Got Humans on the Moon:

I found the dedication that Margaret Hamilton had to her work incredible. As a working mother during a time when women would often stay at home, finding time to balance work and home is truly applause worthy and showcases her dedication and passion for her work. Her meticulous approach to programming, combined with her insistence on rigorous testing and error prevention, was absolutely crucial in ensuring the success of the moon landing. I was particularly struck by her emphasis on anticipating potential errors, a mindset that transformed how mission-critical software was developed which is something we could use in our coding as well. Her work demonstrates how careful planning and foresight can mitigate human errors, which, in high-stakes environments like space travel, could really mean the difference between life and death.

Creative Switch Assignment

For this week’s assignment, I wanted to create something that utilized my handy octo-buddy, especially when it came to detecting its emotions. I decided to use two lights: one red and one green. When the octopus was flipped to happy and placed on the sensor, the green light would light up. When it was angry, the red light would light up. To do this, I utilized copper tape and aluminum foil, flattened into a coin like shape. I took the copper tape and attached the wires for the switches in the tape that I then attached to the legs of the octopus – one on each side for each emotion. I also inserted a wire in the aluminum foil.

The result can be seen here: https://drive.google.com/file/d/1nE8djbPHpdQABXpe-pDiYClpSpO_8Dnd/view?usp=drive_link

All in all, it was a bit hard to get the octopus’ legs to align with the sensor when filming the video, but in the end, it wasn’t too hard of an assignment to complete. I do wish we had more discreet and longer wires, so the octopus didn’t have to be so close to the Arduino board.

Midterm Project

Link to the Sketch: https://editor.p5js.org/izza.t/full/V9Mv_WERI

For my midterm, I decided to do a spin-off of a classic maze that is also heavily inspired by 2 other things I love: mystery and cats. The story of the game involves an archeologist exploring an abandoned tomb that is said to hold a large treasure with their cat. Suddenly, something spooks the cats and it runs off into the tomb. It is then revealed that the tomb is like a maze, and the user must navigate through it. The catch? It’s pitch black apart from the small light from their flashlight and there are booby traps in certain parts of the maze like a mummy, beetles, and a snake that will cause the player to have to restart if the player collides into them.

The game works by showing a start screen that explains the situation and what keys to use to navigate the game. The player uses the arrow keys to navigate up, down, left, and right through the maze and can use the ‘C’ key to call for their cat and hear it’s meow which expands their flashlight radius momentarily (almost like a power up). This feature does have a cooldown feature that is displayed at the top. The user must navigate through the maze to the end to win and find both their cat and the lost treasure. They must do so without triggering any of the booby traps and dealing with all of the dead ends in the maze.

The maze itself took the longest time to create, almost an entire day, as it all had to be hardcoded using an image I had found of tomb-like stone walls online. Then, creating the flashlight like circle around the player and making sure that only pieces of the maze that are within that circle are displayed required the use of masking. This required me to use a new function called drawingContext which unlocks more features of the canvas in p5js and allowed me to do that. The code for which I’m very proud of and can be seen below.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
class Maze {
constructor() {
this.walls = [
// Outer boundaries (these are always visible)
{x: 75, y: 0, w: 1200, h: 30}, // Top
{x: 0, y: 570, w: 1200, h: 30}, // Bottom
{x: 0, y: 0, w: 30, h: 600}, // Left
{x: 970, y: 0, w: 30, h: 535}, // Right
// Interior walls (these are only visible within the flashlight radius)
{x: 75, y: 25, w: 10, h: 100},
{x: 75, y: 165, w: 10, h: 220},
{x: 75, y: 425, w: 10, h: 90},
{x: 145, y: 25, w: 10, h: 100},
{x: 195, y: 25, w: 10, h: 100},
{x: 195, y: 115, w: 75, h: 10},
{x: 75, y: 115, w: 75, h: 10},
{x: 75, y: 165, w: 200, h: 10},
{x: 265, y:115, w: 10, h: 55},
{x: 75, y: 375, w: 165, h: 10},
{x: 75, y: 425, w: 215, h: 10},
{x: 280, y: 295, w: 10, h: 140},
{x: 235, y: 355, w: 10, h: 30},
{x: 105, y: 345, w: 140, h: 10},
{x: 155, y: 295, w: 135, h: 10},
{x: 155, y: 255, w: 10, h: 50},
{x: 105, y: 205, w: 10, h: 150},
{x: 75, y: 515, w: 210, h: 10},
{x: 280, y: 515, w: 10, h: 60},
{x: 105, y: 490, w: 205, h: 10},
{x: 105, y: 450, w: 205, h: 10},
{x: 105, y: 450, w: 10, h: 50},
{x: 155, y: 255, w: 155, h: 10},
{x: 115, y: 205, w: 195, h: 10},
{x: 300, y:95, w: 10, h: 115},
{x: 235, y: 85, w: 75, h: 10},
{x: 300, y:255, w: 10, h: 205},
{x: 300, y:490, w: 10, h: 85},
{x: 225, y: 25, w: 10, h: 70},
{x: 345, y: 25, w: 10, h: 320},
{x: 345, y: 345, w: 225, h: 10},
{x: 345, y: 395, w: 60, h: 10},
{x: 465, y: 395, w: 60, h: 10},
{x: 345, y: 395, w: 10, h: 180},
{x: 515, y: 395, w: 10, h: 180},
{x: 565, y: 345, w: 10, h: 180},
{x: 565, y: 525, w: 50, h: 10},
{x: 605, y: 300, w: 10, h: 230},
{x: 655, y: 165, w: 10, h: 410},
{x: 405, y: 300, w: 205, h: 10},
{x: 405, y: 255, w: 205, h: 10},
{x: 405, y: 225, w: 205, h: 10},
{x: 605, y: 225, w: 10, h: 40},
{x: 405, y: 255, w: 10, h: 50},
{x: 405, y: 25, w: 10, h: 210},
{x: 465, y: 165, w: 195, h: 10},
{x: 465, y: 85, w: 10, h: 90},
{x: 465, y: 85, w: 225, h: 10},
{x: 685, y: 85, w: 10, h: 25},
{x: 685, y: 150, w: 10, h: 425},
{x: 745, y: 20, w: 10, h: 345},
{x: 745, y: 410, w: 10, h: 120},
{x: 745, y: 525, w: 70, h: 10},
{x: 805, y: 450, w: 10, h: 85},
{x: 865, y: 450, w: 10, h: 85},
{x: 805, y: 445, w: 70, h: 10},
{x: 865, y: 525, w: 140, h: 10},
{x: 745, y: 410, w: 145, h: 10},
{x: 935, y: 410, w: 40, h: 10},
{x: 745, y: 355, w: 170, h: 10},
{x: 905, y: 90, w: 10, h: 275},
{x: 845, y: 90, w: 60, h: 10},
{x: 845, y: 90, w: 10, h: 245},
{x: 785, y: 20, w: 10, h: 315},
{x: 785, y: 325, w: 60, h: 10},
];
}
display() {
for (let i = 0; i < 4; i++) {
let wall = this.walls[i];
image(stoneWall, wall.x, wall.y, wall.w, wall.h);
}
//creating the masking
for (let i = 4; i < this.walls.length; i++) {
let wall = this.walls[i];
// Create a mask for the wall
drawingContext.save();
drawingContext.beginPath();
drawingContext.arc(player.x, player.y, lightRadius, 0, TWO_PI);
drawingContext.clip();
image(stoneWall, wall.x, wall.y, wall.w, wall.h);
drawingContext.restore();
}
}
class Maze { constructor() { this.walls = [ // Outer boundaries (these are always visible) {x: 75, y: 0, w: 1200, h: 30}, // Top {x: 0, y: 570, w: 1200, h: 30}, // Bottom {x: 0, y: 0, w: 30, h: 600}, // Left {x: 970, y: 0, w: 30, h: 535}, // Right // Interior walls (these are only visible within the flashlight radius) {x: 75, y: 25, w: 10, h: 100}, {x: 75, y: 165, w: 10, h: 220}, {x: 75, y: 425, w: 10, h: 90}, {x: 145, y: 25, w: 10, h: 100}, {x: 195, y: 25, w: 10, h: 100}, {x: 195, y: 115, w: 75, h: 10}, {x: 75, y: 115, w: 75, h: 10}, {x: 75, y: 165, w: 200, h: 10}, {x: 265, y:115, w: 10, h: 55}, {x: 75, y: 375, w: 165, h: 10}, {x: 75, y: 425, w: 215, h: 10}, {x: 280, y: 295, w: 10, h: 140}, {x: 235, y: 355, w: 10, h: 30}, {x: 105, y: 345, w: 140, h: 10}, {x: 155, y: 295, w: 135, h: 10}, {x: 155, y: 255, w: 10, h: 50}, {x: 105, y: 205, w: 10, h: 150}, {x: 75, y: 515, w: 210, h: 10}, {x: 280, y: 515, w: 10, h: 60}, {x: 105, y: 490, w: 205, h: 10}, {x: 105, y: 450, w: 205, h: 10}, {x: 105, y: 450, w: 10, h: 50}, {x: 155, y: 255, w: 155, h: 10}, {x: 115, y: 205, w: 195, h: 10}, {x: 300, y:95, w: 10, h: 115}, {x: 235, y: 85, w: 75, h: 10}, {x: 300, y:255, w: 10, h: 205}, {x: 300, y:490, w: 10, h: 85}, {x: 225, y: 25, w: 10, h: 70}, {x: 345, y: 25, w: 10, h: 320}, {x: 345, y: 345, w: 225, h: 10}, {x: 345, y: 395, w: 60, h: 10}, {x: 465, y: 395, w: 60, h: 10}, {x: 345, y: 395, w: 10, h: 180}, {x: 515, y: 395, w: 10, h: 180}, {x: 565, y: 345, w: 10, h: 180}, {x: 565, y: 525, w: 50, h: 10}, {x: 605, y: 300, w: 10, h: 230}, {x: 655, y: 165, w: 10, h: 410}, {x: 405, y: 300, w: 205, h: 10}, {x: 405, y: 255, w: 205, h: 10}, {x: 405, y: 225, w: 205, h: 10}, {x: 605, y: 225, w: 10, h: 40}, {x: 405, y: 255, w: 10, h: 50}, {x: 405, y: 25, w: 10, h: 210}, {x: 465, y: 165, w: 195, h: 10}, {x: 465, y: 85, w: 10, h: 90}, {x: 465, y: 85, w: 225, h: 10}, {x: 685, y: 85, w: 10, h: 25}, {x: 685, y: 150, w: 10, h: 425}, {x: 745, y: 20, w: 10, h: 345}, {x: 745, y: 410, w: 10, h: 120}, {x: 745, y: 525, w: 70, h: 10}, {x: 805, y: 450, w: 10, h: 85}, {x: 865, y: 450, w: 10, h: 85}, {x: 805, y: 445, w: 70, h: 10}, {x: 865, y: 525, w: 140, h: 10}, {x: 745, y: 410, w: 145, h: 10}, {x: 935, y: 410, w: 40, h: 10}, {x: 745, y: 355, w: 170, h: 10}, {x: 905, y: 90, w: 10, h: 275}, {x: 845, y: 90, w: 60, h: 10}, {x: 845, y: 90, w: 10, h: 245}, {x: 785, y: 20, w: 10, h: 315}, {x: 785, y: 325, w: 60, h: 10}, ]; } display() { for (let i = 0; i < 4; i++) { let wall = this.walls[i]; image(stoneWall, wall.x, wall.y, wall.w, wall.h); } //creating the masking for (let i = 4; i < this.walls.length; i++) { let wall = this.walls[i]; // Create a mask for the wall drawingContext.save(); drawingContext.beginPath(); drawingContext.arc(player.x, player.y, lightRadius, 0, TWO_PI); drawingContext.clip(); image(stoneWall, wall.x, wall.y, wall.w, wall.h); drawingContext.restore(); } }
class Maze {
  constructor() {
    this.walls = [
      // Outer boundaries (these are always visible)
      {x: 75,    y: 0,    w: 1200, h: 30},   // Top
      {x: 0,    y: 570,  w: 1200, h: 30},   // Bottom
      {x: 0,    y: 0,    w: 30,    h: 600}, // Left
      {x: 970, y: 0,    w: 30,    h: 535}, // Right

      // Interior walls (these are only visible within the flashlight radius)
      {x: 75,  y: 25,  w: 10,   h: 100},
      {x: 75,  y: 165,  w: 10,   h: 220},
      {x: 75,  y: 425,  w: 10,   h: 90},
      {x: 145,  y: 25,  w: 10,   h: 100},
      {x: 195,  y: 25,  w: 10,   h: 100},
      {x: 195,  y: 115,  w: 75,  h: 10},
      {x: 75,  y: 115,  w: 75,  h: 10},
      {x: 75,  y: 165,  w: 200,  h: 10},
      {x: 265,  y:115,  w: 10,   h: 55},
      {x: 75,  y: 375,  w: 165,  h: 10},
      {x: 75,  y: 425,  w: 215,  h: 10},
      {x: 280,  y: 295,  w: 10,   h: 140},
      {x: 235,  y: 355,  w: 10,   h: 30},
      {x: 105,  y: 345,  w: 140,  h: 10},
      {x: 155,  y: 295,  w: 135,  h: 10},
      {x: 155,  y: 255,  w: 10,   h: 50},
      {x: 105,  y: 205,  w: 10,   h: 150},
      {x: 75,  y: 515,  w: 210,  h: 10},
      {x: 280,  y: 515,  w: 10,  h: 60},
      {x: 105,  y: 490,  w: 205,  h: 10},
      {x: 105,  y: 450,  w: 205,  h: 10},
      {x: 105,  y: 450,  w: 10,  h: 50},
      {x: 155,  y: 255,  w: 155,   h: 10},
      {x: 115,  y: 205,  w: 195,   h: 10},
      {x: 300,  y:95,  w: 10,   h: 115},
      {x: 235,  y: 85,  w: 75,  h: 10},
      {x: 300,  y:255,  w: 10,   h: 205},
      {x: 300,  y:490,  w: 10,   h: 85},
      {x: 225,  y: 25,  w: 10,   h: 70},
      {x: 345,  y: 25,  w: 10,   h: 320},
      {x: 345,  y: 345,  w: 225,   h: 10},
      {x: 345,  y: 395,  w: 60,   h: 10},
      {x: 465,  y: 395,  w: 60,   h: 10},
      {x: 345,  y: 395,  w: 10,   h: 180},
      {x: 515,  y: 395,  w: 10,   h: 180},
      {x: 565,  y: 345,  w: 10,   h: 180},
      {x: 565,  y: 525,  w: 50,   h: 10},
      {x: 605,  y: 300,  w: 10,   h: 230},
      {x: 655,  y: 165,  w: 10,   h: 410},
      {x: 405,  y: 300,  w: 205,   h: 10},
      {x: 405,  y: 255,  w: 205,   h: 10},
      {x: 405,  y: 225,  w: 205,   h: 10},
      {x: 605,  y: 225,  w: 10,   h: 40},
      {x: 405,  y: 255,  w: 10,   h: 50},
      {x: 405,  y: 25,  w: 10,   h: 210},
      {x: 465,  y: 165,  w: 195,   h: 10},
      {x: 465,  y: 85,  w: 10,   h: 90},
      {x: 465,  y: 85,  w: 225,   h: 10},
      {x: 685,  y: 85,  w: 10,   h: 25},
      {x: 685,  y: 150,  w: 10,   h: 425},
      {x: 745,  y: 20,  w: 10,   h: 345},
      {x: 745,  y: 410,  w: 10,   h: 120},
      {x: 745,  y: 525,  w: 70,   h: 10},
      {x: 805,  y: 450,  w: 10,   h: 85},
      {x: 865,  y: 450,  w: 10,   h: 85},
      {x: 805,  y: 445,  w: 70,   h: 10},
      {x: 865,  y: 525,  w: 140,   h: 10},
      {x: 745,  y: 410,  w: 145,   h: 10},
      {x: 935,  y: 410,  w: 40,   h: 10},
      {x: 745,  y: 355,  w: 170,   h: 10},
      {x: 905,  y: 90,  w: 10,   h: 275},
      {x: 845,  y: 90,  w: 60,   h: 10},
      {x: 845,  y: 90,  w: 10,   h: 245},
      {x: 785,  y: 20,  w: 10,   h: 315},
      {x: 785,  y: 325,  w: 60,   h: 10},
    ];
  }

  display() {
    for (let i = 0; i < 4; i++) {
      let wall = this.walls[i];
      image(stoneWall, wall.x, wall.y, wall.w, wall.h);
    }

//creating the masking
    for (let i = 4; i < this.walls.length; i++) {
      let wall = this.walls[i];
      // Create a mask for the wall
      drawingContext.save();
      drawingContext.beginPath();
      drawingContext.arc(player.x, player.y, lightRadius, 0, TWO_PI);
      drawingContext.clip();
      image(stoneWall, wall.x, wall.y, wall.w, wall.h);

      drawingContext.restore();
    }
  }

The outline of the maze itself can be seen in this image below which I took before I made the maze pitch black apart from the flashlight.

The flashlight itself is another thing I am very proud of as I wanted to make it look as natural as the way the light coming from a flashlight looks like. This means it has a bright center and a smooth gradient to transparent. For this effect, I had to learn how to use the blendMode in p5js which allowed me to create it in the easiest way. The code for this can be seen below.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
function drawFlashlight() {
fill(0);
noStroke();
rect(0, 0, width, height);
blendMode(SCREEN);
drawingContext.globalCompositeOperation = 'lighter';
// Draw the flashlight gradient
let gradient = drawingContext.createRadialGradient(
player.x, player.y, lightRadius / 4,
player.x, player.y, lightRadius
);
gradient.addColorStop(0, 'rgba(255, 255, 255, 1)');
gradient.addColorStop(1, 'rgba(0, 0, 0, 0)');
drawingContext.fillStyle = gradient;
drawingContext.beginPath();
drawingContext.arc(player.x, player.y, lightRadius, 0, TWO_PI);
drawingContext.fill();
blendMode(BLEND);
}
function drawFlashlight() { fill(0); noStroke(); rect(0, 0, width, height); blendMode(SCREEN); drawingContext.globalCompositeOperation = 'lighter'; // Draw the flashlight gradient let gradient = drawingContext.createRadialGradient( player.x, player.y, lightRadius / 4, player.x, player.y, lightRadius ); gradient.addColorStop(0, 'rgba(255, 255, 255, 1)'); gradient.addColorStop(1, 'rgba(0, 0, 0, 0)'); drawingContext.fillStyle = gradient; drawingContext.beginPath(); drawingContext.arc(player.x, player.y, lightRadius, 0, TWO_PI); drawingContext.fill(); blendMode(BLEND); }
function drawFlashlight() {
  fill(0);
  noStroke();
  rect(0, 0, width, height);

  blendMode(SCREEN); 
  drawingContext.globalCompositeOperation = 'lighter'; 

  // Draw the flashlight gradient
  let gradient = drawingContext.createRadialGradient(
    player.x, player.y, lightRadius / 4,
    player.x, player.y, lightRadius 
  );
  gradient.addColorStop(0, 'rgba(255, 255, 255, 1)');
  gradient.addColorStop(1, 'rgba(0, 0, 0, 0)'); 
  drawingContext.fillStyle = gradient;
  drawingContext.beginPath();
  drawingContext.arc(player.x, player.y, lightRadius, 0, TWO_PI);
  drawingContext.fill();
  blendMode(BLEND);
}

The result of this and the masking can be seen in the image below (with a peep of one the traps – the mummy):

The game proved to be more challenging than I anticipated, and I quickly realized I had set the bar a little to high for myself. Originally, I wanted the player to be a sprite of an archeologist and to have all the booby traps be animated and moving. However, with time constraints and animation struggles, I wasn’t able to do that (but would love to try and incorporate in the future). I also wanted for the cat to somehow appear at the edge of the flashlight’s radius when the ‘C’ key was pressed, the radius of the flashlight expanded, and the meow sound occurred.

Overall, I am still very proud of the game I ended up creating. It is fun, has an overarching Egyptian theme through carefully selected images and even the Papyrus font, and has a niche to it that sets it apart from other games. I learned a lot about p5js and how to code new things while working on this game, and I hope you enjoy playing it!

Week 5: Midterm Update

For my midterm, I decided to do a spin-off of a classic maze that is also heavily inspired by 2 other things I love: mystery and cats. The story of the game involves a person who is admiring an abandoned tomb that is said to hold a large treasure with their cat. Suddenly, something spooks the cats and it runs off into the tomb. It is then revealed that the tomb is like a maze, and the user must navigate through it. The catch? It’s pitch black apart from the small light from their flashlight and there are booby traps all around them. My idea is to have the user be able to call for the cat when a key like the spacebar is pressed, and when the cat responds, a larger part of the maze is temporarily revealed, as if the cat is just ahead of the user, running away or perhaps even guiding them through the maze until the user reaches the end/the treasure. Turning into a booby trap will result in “death” and the user will need to start over. I’m kind of imagining the layout to look like story pages or a comic book with the interactive game in the middle.

This task is certainly difficult as it requires a number of different elements, object-oriented programming, design/aesthetics, object animation, and even the possible use of sprites. Those sprites and getting the character and cat to move smoothly through the maze are the most challenging. Plus, getting the animation for booby traps and dealing with the character’s collision with them. Before I could even begin coding, it was really important that I knew what everything I would be designing looked like, so I tried to make it easier by finding gifs for specific animations I needed that could not be done in the program. That way, I knew exactly what I needed to try and create in the program myself and what I already have. For example, I found this gif of a cat being scared which I can use in the beginning scene when the cat is spooked and runs (I’m not sure if this website supports gifs, but you can kind of get the idea). I’m also working on creating the classes for all of the elements in the game right now. Here’s an example of what I have for the player, the traps, and even the cat:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
class Player {
constructor(x, y) {
this.x = x;
this.y = y;
this.size = 20;
}
move() {
if (keyIsDown(LEFT_ARROW)) this.x -= 2;
if (keyIsDown(RIGHT_ARROW)) this.x += 2;
if (keyIsDown(UP_ARROW)) this.y -= 2;
if (keyIsDown(DOWN_ARROW)) this.y += 2;
}
display() {
fill(255);
ellipse(this.x, this.y, this.size);
}
}
class Cat {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
class Trap {
constructor(x, y) {
this.x = x;
this.y = y;
this.size = 20;
}
display() {
fill(255, 0, 0);
rect(this.x, this.y, this.size, this.size);
}
checkCollision(player) {
return dist(player.x, player.y, this.x, this.y) < this.size;
}
}
class Player { constructor(x, y) { this.x = x; this.y = y; this.size = 20; } move() { if (keyIsDown(LEFT_ARROW)) this.x -= 2; if (keyIsDown(RIGHT_ARROW)) this.x += 2; if (keyIsDown(UP_ARROW)) this.y -= 2; if (keyIsDown(DOWN_ARROW)) this.y += 2; } display() { fill(255); ellipse(this.x, this.y, this.size); } } class Cat { constructor(x, y) { this.x = x; this.y = y; } } class Trap { constructor(x, y) { this.x = x; this.y = y; this.size = 20; } display() { fill(255, 0, 0); rect(this.x, this.y, this.size, this.size); } checkCollision(player) { return dist(player.x, player.y, this.x, this.y) < this.size; } }
class Player {
    constructor(x, y) {
        this.x = x;
        this.y = y;
        this.size = 20;
    }
    move() {
        if (keyIsDown(LEFT_ARROW)) this.x -= 2;
        if (keyIsDown(RIGHT_ARROW)) this.x += 2;
        if (keyIsDown(UP_ARROW)) this.y -= 2;
        if (keyIsDown(DOWN_ARROW)) this.y += 2;
    }
    display() {
        fill(255);
        ellipse(this.x, this.y, this.size);
    }
}

class Cat {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }
}

class Trap {
    constructor(x, y) {
        this.x = x;
        this.y = y;
        this.size = 20;
    }
    display() {
        fill(255, 0, 0);
        rect(this.x, this.y, this.size, this.size);
    }
    checkCollision(player) {
        return dist(player.x, player.y, this.x, this.y) < this.size;
    }
}

 

 

Week 5: Reading Response

The main difference between computer and human vision is that while humans can instantly recognize faces, objects, and contexts, a computer sees only raw pixel data unless explicitly programmed to interpret it. We have the ability to recognize people, expressions, colors, animals, etc instantly that computers have to be taught how to do and even that to only some extent. To help computers “see” or track objects of interest, techniques such as frame differencing, background subtraction, and brightness thresholding are used to extract meaningful features from video input. These methods allow interactive systems to detect movement, isolate subjects, and respond dynamically to user actions, forming the foundation of many interactive media works. However, each of these techniques has limitations, such as sensitivity to lighting changes or reliance on high-contrast visuals, highlighting the need for carefully designed environments to optimize detection accuracy.

Still, computer vision has improved vastly over the past few decades and has especially become much more accessible for artists, designers, and even beginner programmers to incorporate vision-based interactivity into their work. Software environments like Processing, Max/MSP/Jitter, and Macromedia Director provide frameworks for integrating computer vision, either through direct coding or via plug-ins and toolkits. Additionally, stand-alone applications like BigEye and EyesWeb expand these capabilities, enabling real-time motion tracking and expressive gesture analysis that is much easier to get your hands on.

In interactive art, computer vision’s capacity for tracking and surveillance introduces both creative potential and ethical concerns. Artists can leverage motion detection and object tracking to create immersive, responsive installations that engage audiences much more than just standing there staring. However, the same tools that enable interactivity can also invoke concerns about surveillance, privacy, and data collection. Since interactive art often explores the relationship between technology and human experience, the ability of computer vision to monitor and track movement, especially without explicit user consent, raises questions about autonomy and control. What information is the computer storing? Can it be used against me? These are questions people who are being recorded don’t even have the chance to ask. Ultimately, while computer vision expands the possibilities for dynamic and participatory art, its application must be carefully considered to balance both innovation and creativity with ethical responsibility.

Assignment 4: Generative Text

For this project, I was inspired by the windows at the Louvre Abu Dhabi that have quotes on them. I’ve always thought that poetry is incredibly beautiful powerful, and something that could have a section in a museum to inspire those that want to read it. Therefore, for my project, I decided to create what I call the “The Museum of Poems”. The concept involves a museum scene with a frame in the very center that displays a random quote about love or life for the person to see. My hope is that whatever they read, although short, inspires them or motivates them in some way. I gathered 2-line poems from various reddit posts, online blogs, quora posts, and even ChatGPT, and put them all into a giant list for the program to pick and display at random. My museum ended up looking like this:

The hardest part of this project was that after I initially finished, I accidentally clicked refresh and lost the entire project. I thought I had saved it, but turns out I hadn’t, and there was no way to recover it. Luckily, there was a point where I asked ChatGPT to help me solve a problem I was running into and gave it all my code, so I could copy that back. However, this was still in the earlier stage of my project, and I had to redo a lot of it from scratch including importing all my fonts and images again, putting in all the poems in the list, the gradient background, the wood flooring, etc. Speaking of the gradient background, that part along with the wood flooring is the part of the code I am most proud of. I had to learn how to use the lerpColor function, mapping properly, as well as using an image in repetition to create a pattern. The result can be seen here:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
//Creates the entire background and floor of the museum
function drawMuseum() {
//Draws the gradient background
for (let i = 0; i <= height - 100; i++) {
let inter = map(i, 0, height - 100, 0, 1);
let wallColor = lerpColor(color('#ECC2EC'), color('#DCB5A5'), inter);
stroke(wallColor);
line(0, i, width, i);
}
// Creates the wood texture on the floor
for (let i = 0; i < width + 50; i += 100) {
// Draw the first set of wood planks
image(wood, i, height - 100, 100, 50);
image(wood, i - 50, height - 50, 100, 50);
// Adds borders around each plank
stroke('#BC7C64');
noFill();
rect(i, height - 100, 100, 50);
rect(i - 50, height - 50, 100, 50);
}
}
//Creates the entire background and floor of the museum function drawMuseum() { //Draws the gradient background for (let i = 0; i <= height - 100; i++) { let inter = map(i, 0, height - 100, 0, 1); let wallColor = lerpColor(color('#ECC2EC'), color('#DCB5A5'), inter); stroke(wallColor); line(0, i, width, i); } // Creates the wood texture on the floor for (let i = 0; i < width + 50; i += 100) { // Draw the first set of wood planks image(wood, i, height - 100, 100, 50); image(wood, i - 50, height - 50, 100, 50); // Adds borders around each plank stroke('#BC7C64'); noFill(); rect(i, height - 100, 100, 50); rect(i - 50, height - 50, 100, 50); } }
//Creates the entire background and floor of the museum
function drawMuseum() {

  //Draws the gradient background
  for (let i = 0; i <= height - 100; i++) {
    let inter = map(i, 0, height - 100, 0, 1);
    let wallColor = lerpColor(color('#ECC2EC'), color('#DCB5A5'), inter);
    stroke(wallColor);
    line(0, i, width, i); 
  }
  
 // Creates the wood texture on the floor
  for (let i = 0; i < width + 50; i += 100) {
    // Draw the first set of wood planks
    image(wood, i, height - 100, 100, 50); 
    image(wood, i - 50, height - 50, 100, 50);
    
  // Adds borders around each plank
    stroke('#BC7C64');
    noFill();
    rect(i, height - 100, 100, 50);
    rect(i - 50, height - 50, 100, 50);
  }
}

Overall, I am really proud of how the “The Museum of Poems” turned out. I wish though, that I could make it somehow look more realistic. I feel like the wall and the flooring has a pretty big contrast in terms of aesthetic which is something to consider for the future.

 

Week 4: Reading Response

One thing that drives me absolutely insane is paper straws. Nothing disappoints me more than getting my coffee or any other drink really and seeing a paper straw. And no, of course I’m not against saving the environment or the turtles, but those flimsy things are horrible for mixing drinks and get all soggy so fast. Any milkshake or even whipped cream and  the straw will give up and disintegrate before my eyes, refusing to let me finish my drink without crumpling up into a useless, unpleasant, undrinkable, thing in my cup. Sometimes I even have to get my hands dirty while trying to get a firmer grip on the straw just to mix it. With sustainability being a key issue for many, there are many other biodegradable alternatives up and coming on the market like wood straws or even sugarcane straws that have much better, 24 hour durability compared to like 30 seconds with a paper straw. While places in the UAE are slowly starting to implement these, it will take time before I never have to see a paper straw again.

This highlights why, as Norman explains, human-centered design is so important. While paper straws match the societal push for sustainability and look great for companies, they don’t account for the humans actually using them that suffer through the sogginess and disintegration of paper straws themselves. One of the other things Norman talks about is signifiers and affordances. Since perceived affordance can take the form of many things, it is important to have signifiers to make clear what the instructions are. I think this is especially important in interactive media because we often assume people will know to click, move their mouse around, press the arrow keys, etc in order to make something happen on the screen. However, as a designer, things that seem self-intuitive to us may not always be self-intuitive to others and can have many affordances. Therefore, having signifiers is important to make sure our projects are understandable and usable to a broader audience.

Assignment 3: Geometrical Orbs

For this assignment, my inspiration came from an image I saw while scrolling on Instagram:

The image was coupled with some headline, but I can’t remember what exactly it was. The main thing that struck me about this photo was how it didn’t seem like the orbs and their nodes had any noticeable pattern, and yet somehow, this sphere was created. Did someone create the sphere and then click around? Was there a program? What are the chances this was done randomly? Thus, I was inspired to create a program that has users create orbs that bounce around the screen when they drag their mouse around, and if they are close, create a line connecting them. The output would be constantly evolving and up to the user as they can choose to add more orbs if they want. Who knows, maybe a sphere like shape would be seen on the screen temporarily. The results of my efforts are as follows:

(Drag your mouse!)

This assignment ended up being much harder than I expected because I had to make the orbs all look and act different, but not too different that they look bad on screen. I learned a lot about the p5.vector() and the types of attributes I can apply to it when creating my orb class which can be seen below. Figuring out how to create the connection line only if the orbs are close to each other also took a little bit.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
class Orb {
constructor(x, y) {
this.pos = createVector(x, y); // Position vector
this.vel = p5.Vector.random2D(); // Assign velocity
this.vel.mult(random(1, 3)); // Assign magnitude
this.size = random(8, 20); // Random size for each orb
this.color = color(random(100, 255), random(100, 255), random(255), 150); // Random semi-transparent color
}
move() {
this.pos.add(this.vel); // Update position
// Bounce off edges
if (this.pos.x <= 0 || this.pos.x >= width) {
this.vel.x *= -1;
}
if (this.pos.y <= 0 || this.pos.y >= height) {
this.vel.y *= -1;
}
}
display() {
noStroke();
fill(this.color);
ellipse(this.pos.x, this.pos.y, this.size); // Draws orb
}
// Determines if orbs are within 100 pixels of each other
isCloseTo(other) {
let distance = this.pos.dist(other.pos);
if (distance < 100) {
return true;
} else {
return false;
}
}
}
class Orb { constructor(x, y) { this.pos = createVector(x, y); // Position vector this.vel = p5.Vector.random2D(); // Assign velocity this.vel.mult(random(1, 3)); // Assign magnitude this.size = random(8, 20); // Random size for each orb this.color = color(random(100, 255), random(100, 255), random(255), 150); // Random semi-transparent color } move() { this.pos.add(this.vel); // Update position // Bounce off edges if (this.pos.x <= 0 || this.pos.x >= width) { this.vel.x *= -1; } if (this.pos.y <= 0 || this.pos.y >= height) { this.vel.y *= -1; } } display() { noStroke(); fill(this.color); ellipse(this.pos.x, this.pos.y, this.size); // Draws orb } // Determines if orbs are within 100 pixels of each other isCloseTo(other) { let distance = this.pos.dist(other.pos); if (distance < 100) { return true; } else { return false; } } }
class Orb {
  constructor(x, y) {
    this.pos = createVector(x, y); // Position vector
    this.vel = p5.Vector.random2D(); // Assign velocity
    this.vel.mult(random(1, 3)); // Assign magnitude
    this.size = random(8, 20); // Random size for each orb
    this.color = color(random(100, 255), random(100, 255), random(255), 150); // Random semi-transparent color
  }
  
  move() {
    this.pos.add(this.vel); // Update position
    // Bounce off edges
    if (this.pos.x <= 0 || this.pos.x >= width) {
      this.vel.x *= -1;
    }
    if (this.pos.y <= 0 || this.pos.y >= height) {
      this.vel.y *= -1;
    }
  }
  
  display() {
    noStroke();
    fill(this.color);
    ellipse(this.pos.x, this.pos.y, this.size); // Draws orb
  }

// Determines if orbs are within 100 pixels of each other
  isCloseTo(other) {
    let distance = this.pos.dist(other.pos);
    if (distance < 100) {
      return true;
    } else {
    return false;
  }
 }
}

To improve, I think I could play around more with the colors and upping the interactivity. It could be that the user can determine the colors of the orbs or have the colors all be the same but change each time the mouse is dragged. Ideally, it would also be cool to have the speed or density with which the user clicks affect the amount of orbs created. Overall though, I had fun working on this assignment and learned a lot.