Week 8 : Unusual Switch

For this week’s assignment, we had to create a switch that didn’t use our hands. I started messing around with different objects, trying to figure out what could act as a switch without actually being one. While thinking, I was absentmindedly playing with my keychain, and that’s when I realized all its connecting parts are metal and I decided to seperate it into two to act as the break in my circuit.

From there, I needed a way to complete the circuit without using my hands, so I came up with a foot pedal-style switch. I wrapped the sole of my slippers in foil to make them conductive, then taped the two separate pieces of my keychain onto a sheet of paper to keep them stable. When I step down on the connection point, my foil-covered foot acts as the bridge, completing the circuit and lighting up the bulb.

Link to video :  https://drive.google.com/drive/folders/10NqetPFYwhogeYTs0krIon77jGHFkyMm?usp=sharing

It was a really enjoyable assignment, and I had fun playing around with the circuit, switching the bulb on and off and even syncing it to the beats of songs by tapping my foot.

Week 8 : Reading Response 2

Margaret Hamilton is the kind of legend we don’t talk about enough. She wasn’t just a programmer, she was a problem-solver and a pioneer who helped put men on the moon while juggling motherhood and working in a field dominated by men. In the 1960s, software engineering wasn’t even considered a real thing yet, and yet, there she was, writing the code that saved Apollo 11 from disaster.

One of my favorite parts of her story is how NASA ignored her when she wanted to add safeguards to prevent astronauts from making mistakes. They told her, “That would never happen.” And then of course it did happen, and she had to fix it under pressure to bring Apollo 8’s astronauts home safely. That’s the kind of foresight and brilliance that makes her work so groundbreaking.

What’s even more inspiring is that she did all this while raising a kid. She’d bring her daughter to the lab, let her sleep on the floor while she worked late nights writing code. It’s proof that women have always been capable of incredible things in STEM. Hamilton didn’t just write software; she helped create an industry, proving that software wasn’t an afterthought, but the future.

Week 8 : Reading Response 1

Reading Donald Norman’s take on affect and design really got me thinking about what we do in physical computing. His idea that attractive things work better made me question the way we usually talk about design. It’s so easy to think that usability is the most important thing, but Norman makes a solid point—aesthetics, usability, and functionality all need to work together, depending on the context. That’s something I really want to keep in mind as we start building our own devices.

With physical computing, we’re not just designing screens or interfaces—we’re making real objects that people will physically interact with. That makes things more complicated because now we have to think about how something looks, how easy it is to use, and what it’s actually meant to do all at the same time. The balance between these depends on what we’re building. If it’s something like a smart home controller, aesthetics matter because it’s going to sit in someone’s house, and they’ll see it every day. But if we’re making a fire escape guidance system, function is everything. No one in an emergency should have to stop and figure out how to use it, it just has to work.

The three teapots analogy really stuck with me too. It’s a great reminder that different users and situations need different design choices. I hadn’t really thought much about how a person’s emotional state affects how they interact with a device, but it makes so much sense. If someone is stressed—like using a medical alert system—they need clear, obvious controls. No distractions, no unnecessary design elements, just pure functionality. But if we’re designing something more creative, like an interactive art installation, we can push the boundaries and make it more about the experience rather than efficiency.

As we start designing our own projects, I don’t want to fall into the trap of just thinking, whether it works or no? Of course, usability matters, but if something feels cold or uninviting, people won’t want to use it.

At the end of the day, physical computing isn’t just about circuits and code but rather about how people feel when they use what we create. A device isn’t just good because it functions. It’s successful when it resonates with people and feels right to use. That’s something I really want to keep in mind moving forward.

Midterm Project : Pixel Art

For my midterm project, I drew inspiration from the color-by-number games that are so popular on the internet. I used to be obsessed with them, so I decided to create my own pixelated version. In my project, users color cell by cell in a grid, where each cell corresponds to a letter, and each color on the palette is assigned to a letter as well. By following the key and filling in the grid, the design gradually comes to life, revealing the full picture bit by bit. There’s something incredibly relaxing about mindlessly clicking, watching the colors take shape, and seeing the image emerge one step at a time. It’s a simple yet satisfying process, where every small action contributes to a larger, beautiful result.

The game starts with a welcome screen where the user has two options: Start, to begin coloring immediately, or Help, which provides instructions on the various features the game offers. Once they click Start, they are taken to the selection screen, where they can choose a coloring page from the available options. After selecting a page, they arrive at the actual coloring screen, where the process is simple—choose a color from the palette above and start coloring. However, if the user accidentally fills in the wrong square, they can double-tap the cell to remove the color. If they wish to start over or choose another page, a Reset button clears the canvas. Once the user finishes coloring their chosen page, the game is considered complete, and they are prompted to restart if they wish to play/color again.


One aspect of my code that I’m particularly proud of is the implementation of the actual coloring page. The way the cells fill in over the image has a seamless and polished look. I also like how intuitive the overall user interface is—even first-time users can start playing without needing to read the instructions. I chose soothing shades of brown for the interface, paired with lofi background music, to enhance the game’s relaxing atmosphere.

That said, I encountered a few challenges along the way, particularly with the coloring process itself. One major issue was ensuring that the grid, which tracks cell positions, perfectly aligned with the image. Initially, a double grid effect was visible on top of the image, which was distracting. To fix this, I had to experiment with different parameters before finally finding a formula that worked—provided the image was a perfect square with no empty or extra areas. This was implemented in the ColoringPage class. Once the grid was properly aligned, tracking mouse clicks and filling in the correct areas became much smoother.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
class ColoringPage {
constructor(name, imagePath,thumbPath, rows, cols, palette,targetCells) {
this.name = name;
this.img = pageImages[name.toLowerCase()];
this.thumb = pageThumbnails[name.toLowerCase()];
this.rows = rows;
this.cols = cols;
this.cellSize = 600 / this.cols;
this.grid = Array.from({ length: this.rows }, () => Array(this.cols).fill(null));
this.palette = palette;
this.selectedColor = Object.values(palette)[0].color;
this.targetCells = targetCells;
this.filledCells = 0;
}
display() {
this.drawPalette();
image(this.img, 100, 90, 600, 600);
this.drawGrid();
this.drawColoredGrid();
}
drawGrid() {
stroke(0, 50);
noFill();
for (let row = 0; row < this.rows; row++) {
for (let col = 0; col < this.cols; col++) {
rect(100 + col * this.cellSize, 90 + row * this.cellSize, this.cellSize, this.cellSize);
}
}
}
drawColoredGrid() {
for (let row = 0; row < this.rows; row++) {
for (let col = 0; col < this.cols; col++) {
if (this.grid[row][col]) {
fill(this.grid[row][col]);
rect(100 + col * this.cellSize, 90 + row * this.cellSize, this.cellSize, this.cellSize);
}
}
}
}
drawPalette() {
let keys = Object.keys(this.palette);
let x = (width - keys.length * 60) / 2;
let y = 20;
noStroke();
for (let i = 0; i < keys.length; i++) {
let colorValue = this.palette[keys[i]].color;
let isSelected = this.selectedColor === colorValue;
let isHovered = mouseX > x + i * 60 && mouseX < x + i * 60 + 50 &&
mouseY > y && mouseY < y + 50;
let circleSize = 50;
if (isHovered) circleSize = 55;
let centerX = x + i * 60 + 30;
let centerY = y + 25;
if (isSelected) {
fill(255);
ellipse(centerX, centerY, circleSize + 8, circleSize + 8);
}
fill(colorValue);
ellipse(centerX, centerY, circleSize, circleSize);
let c = color(colorValue);
let brightnessValue = (red(c) * 0.299 + green(c) * 0.587 + blue(c) * 0.114);
fill(brightnessValue < 128 ? 255 : 0);
textSize(14);
textAlign(CENTER, CENTER);
let labelChar = this.palette[keys[i]].label;
text(labelChar, centerX, centerY);
}
}
selectColor() {
let keys = Object.keys(this.palette);
let x = (width - keys.length * 60) / 2;
for (let i = 0; i < keys.length; i++) {
if (mouseX > x + i * 60 && mouseX < x + i * 60 + 50 && mouseY > 20 && mouseY < 70) {
this.selectedColor = this.palette[keys[i]].color;
break;
}
}
}
fillCell() {
let col = floor((mouseX - 100) / this.cellSize);
let row = floor((mouseY - 90) / this.cellSize);
if (row >= 0 && row < this.rows && col >= 0 && col < this.cols) {
if (!this.grid[row][col]) {
this.grid[row][col] = this.selectedColor;
this.filledCells++;
console.log(this.filledCells)
if (this.isCompleted()) {
game.state = "final";
}
}
}
}
class ColoringPage { constructor(name, imagePath,thumbPath, rows, cols, palette,targetCells) { this.name = name; this.img = pageImages[name.toLowerCase()]; this.thumb = pageThumbnails[name.toLowerCase()]; this.rows = rows; this.cols = cols; this.cellSize = 600 / this.cols; this.grid = Array.from({ length: this.rows }, () => Array(this.cols).fill(null)); this.palette = palette; this.selectedColor = Object.values(palette)[0].color; this.targetCells = targetCells; this.filledCells = 0; } display() { this.drawPalette(); image(this.img, 100, 90, 600, 600); this.drawGrid(); this.drawColoredGrid(); } drawGrid() { stroke(0, 50); noFill(); for (let row = 0; row < this.rows; row++) { for (let col = 0; col < this.cols; col++) { rect(100 + col * this.cellSize, 90 + row * this.cellSize, this.cellSize, this.cellSize); } } } drawColoredGrid() { for (let row = 0; row < this.rows; row++) { for (let col = 0; col < this.cols; col++) { if (this.grid[row][col]) { fill(this.grid[row][col]); rect(100 + col * this.cellSize, 90 + row * this.cellSize, this.cellSize, this.cellSize); } } } } drawPalette() { let keys = Object.keys(this.palette); let x = (width - keys.length * 60) / 2; let y = 20; noStroke(); for (let i = 0; i < keys.length; i++) { let colorValue = this.palette[keys[i]].color; let isSelected = this.selectedColor === colorValue; let isHovered = mouseX > x + i * 60 && mouseX < x + i * 60 + 50 && mouseY > y && mouseY < y + 50; let circleSize = 50; if (isHovered) circleSize = 55; let centerX = x + i * 60 + 30; let centerY = y + 25; if (isSelected) { fill(255); ellipse(centerX, centerY, circleSize + 8, circleSize + 8); } fill(colorValue); ellipse(centerX, centerY, circleSize, circleSize); let c = color(colorValue); let brightnessValue = (red(c) * 0.299 + green(c) * 0.587 + blue(c) * 0.114); fill(brightnessValue < 128 ? 255 : 0); textSize(14); textAlign(CENTER, CENTER); let labelChar = this.palette[keys[i]].label; text(labelChar, centerX, centerY); } } selectColor() { let keys = Object.keys(this.palette); let x = (width - keys.length * 60) / 2; for (let i = 0; i < keys.length; i++) { if (mouseX > x + i * 60 && mouseX < x + i * 60 + 50 && mouseY > 20 && mouseY < 70) { this.selectedColor = this.palette[keys[i]].color; break; } } } fillCell() { let col = floor((mouseX - 100) / this.cellSize); let row = floor((mouseY - 90) / this.cellSize); if (row >= 0 && row < this.rows && col >= 0 && col < this.cols) { if (!this.grid[row][col]) { this.grid[row][col] = this.selectedColor; this.filledCells++; console.log(this.filledCells) if (this.isCompleted()) { game.state = "final"; } } } }
class ColoringPage {
  constructor(name, imagePath,thumbPath, rows, cols, palette,targetCells) {
    this.name = name;
    this.img = pageImages[name.toLowerCase()];  
    this.thumb = pageThumbnails[name.toLowerCase()];
    this.rows = rows;
    this.cols = cols;
    this.cellSize = 600 / this.cols;
    this.grid = Array.from({ length: this.rows }, () => Array(this.cols).fill(null));
    this.palette = palette;
    this.selectedColor = Object.values(palette)[0].color;
    this.targetCells = targetCells; 
    this.filledCells = 0;
  }

  display() {
    this.drawPalette();
    image(this.img, 100, 90, 600, 600);
    this.drawGrid();
    this.drawColoredGrid();
  }

  drawGrid() {
    stroke(0, 50);
    noFill();
    for (let row = 0; row < this.rows; row++) {
      for (let col = 0; col < this.cols; col++) {
        rect(100 + col * this.cellSize, 90 + row * this.cellSize, this.cellSize, this.cellSize);
      }
    }
  }

  drawColoredGrid() {
    for (let row = 0; row < this.rows; row++) {
      for (let col = 0; col < this.cols; col++) {
        if (this.grid[row][col]) {
          fill(this.grid[row][col]);
          rect(100 + col * this.cellSize, 90 + row * this.cellSize, this.cellSize, this.cellSize);
        }
      }
    }
  }

  drawPalette() {
    let keys = Object.keys(this.palette);
    let x = (width - keys.length * 60) / 2;
    let y = 20;

    noStroke();

    for (let i = 0; i < keys.length; i++) {
        let colorValue = this.palette[keys[i]].color;
        let isSelected = this.selectedColor === colorValue;
        let isHovered = mouseX > x + i * 60 && mouseX < x + i * 60 + 50 &&
                        mouseY > y && mouseY < y + 50;

        let circleSize = 50; 
        if (isHovered) circleSize = 55; 

        let centerX = x + i * 60 + 30;
        let centerY = y + 25;

        if (isSelected) {
            fill(255); 
            ellipse(centerX, centerY, circleSize + 8, circleSize + 8);
        }

        fill(colorValue);
        ellipse(centerX, centerY, circleSize, circleSize); 
      
      let c = color(colorValue);
      let brightnessValue = (red(c) * 0.299 + green(c) * 0.587 + blue(c) * 0.114); 

        fill(brightnessValue < 128 ? 255 : 0);
        textSize(14);
        textAlign(CENTER, CENTER);
        
        let labelChar = this.palette[keys[i]].label;  
        text(labelChar, centerX, centerY);
    }
}


  selectColor() {
    let keys = Object.keys(this.palette);
    let x = (width - keys.length * 60) / 2;
    for (let i = 0; i < keys.length; i++) {
      if (mouseX > x + i * 60 && mouseX < x + i * 60 + 50 && mouseY > 20 && mouseY < 70) {
        this.selectedColor = this.palette[keys[i]].color;
        break;
      }
    }
  }

  fillCell() {
    let col = floor((mouseX - 100) / this.cellSize);
    let row = floor((mouseY - 90) / this.cellSize);

    if (row >= 0 && row < this.rows && col >= 0 && col < this.cols) {
      if (!this.grid[row][col]) {
        this.grid[row][col] = this.selectedColor;
        this.filledCells++;
        console.log(this.filledCells)
        if (this.isCompleted()) {
          game.state = "final";
        }
      }
    }
  }

Another challenge I faced was determining the end condition for the game. Currently, the game ends when the total number of filled cells matches the number of cells corresponding to a color on the grid. However, this assumes that users only color the cells with assigned letters and don’t fill in any blank spaces. Additionally, since the game allows users to fill any cell with any color, the code does not track whether a specific cell is filled with the correct color. A possible solution would be to store a reference for each cell’s correct color in every coloring page, but I haven’t yet found a way to do this dynamically without hardcoding hundreds of cells, which would be inefficient and take up unnecessary space in the code. This is an area I would like to improve in the future.

Midterm Progress Report

Concept:

For my midterm project, I’m creating a grid version of Color by Number game. I’ve always loved these types of games because they help me relax, focus, and feel super satisfied as the artwork slowly comes to life. My goal is to bring that same experience to users by making a game that’s easy to use, fun, and visually appealing.

The game gives users a color palette, where each color is linked to a letter or number. The image itself is made up of a grid, with each cell labeled to show which color should go there. All users have to do is click on a grid cell, and the color fills in automatically. It’s quite simple for users to use.

Challenging Aspects and Risk Prevention

1. One of the biggest challenges was ensuring precise click detection on the image grid. Since the image itself doesn’t inherently support click interactions, I had to create an invisible grid and lay it on top of the image. This allowed the game to register mouse clicks accurately while keeping the visual presentation clean. However, this was difficult because I had to align the overlay perfectly so that the user wouldn’t see it, but it would still work effectively for detecting clicks and filling in colors.

2. Another challenge was allowing users to select colors from the palette and ensuring the correct color was applied to the chosen grid cell. I had to implement logic that detected which color was clicked on and then stored that color as the “active” selection. The game then needed to apply this color to any grid cell the user clicked until a new color was chosen. The difficulty came in precisely mapping click positions to the correct palette color while keeping the interface responsive and user-friendly.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
class ColoringPage {
constructor(name, imagePath, rows, cols, palette) {
this.name = name;
this.img = loadImage(imagePath);
this.rows = rows;
this.cols = cols;
this.cellSize = 600 / this.cols;
this.grid = Array.from({ length: this.rows }, () => Array(this.cols).fill(null));
this.palette = palette;
this.selectedColor = Object.values(palette)[0].color;
}
display() {
this.drawPalette();
image(this.img, 100, 90, 600, 600);
this.drawGrid();
this.drawColoredGrid();
}
drawGrid() {
stroke(0, 50);
noFill()
for (let row = 0; row < this.rows; row++) {
for (let col = 0; col < this.cols; col++) {
rect(100 + col * this.cellSize, 90 + row * this.cellSize, this.cellSize, this.cellSize);
}
}
}
drawColoredGrid() {
for (let row = 0; row < this.rows; row++) {
for (let col = 0; col < this.cols; col++) {
if (this.grid[row][col]) {
fill(this.grid[row][col]);
rect(100 + col * this.cellSize, 90 + row * this.cellSize, this.cellSize, this.cellSize);
}
}
}
}
drawPalette() {
let keys = Object.keys(this.palette);
let x = (width - keys.length * 60) / 2; // Decrease 70 to 60 for less spacing
let y = 20;
noStroke();
for (let i = 0; i < keys.length; i++) {
let colorValue = this.palette[keys[i]].color;
fill(colorValue);
ellipse(x + i * 60 + 30, y + 25, 50, 50);
let c = color(colorValue);
let brightnessValue = (red(c) * 0.299 + green(c) * 0.587 + blue(c) * 0.114); // Standard luminance formula
fill(brightnessValue < 128 ? 255 : 0); // White text for dark colors, black text for light colors
textSize(14);
textAlign(CENTER, CENTER);
text(this.palette[keys[i]].label, x + i * 60 + 30, y + 25); // Adjusted positioning to match circles
}
}
class ColoringPage { constructor(name, imagePath, rows, cols, palette) { this.name = name; this.img = loadImage(imagePath); this.rows = rows; this.cols = cols; this.cellSize = 600 / this.cols; this.grid = Array.from({ length: this.rows }, () => Array(this.cols).fill(null)); this.palette = palette; this.selectedColor = Object.values(palette)[0].color; } display() { this.drawPalette(); image(this.img, 100, 90, 600, 600); this.drawGrid(); this.drawColoredGrid(); } drawGrid() { stroke(0, 50); noFill() for (let row = 0; row < this.rows; row++) { for (let col = 0; col < this.cols; col++) { rect(100 + col * this.cellSize, 90 + row * this.cellSize, this.cellSize, this.cellSize); } } } drawColoredGrid() { for (let row = 0; row < this.rows; row++) { for (let col = 0; col < this.cols; col++) { if (this.grid[row][col]) { fill(this.grid[row][col]); rect(100 + col * this.cellSize, 90 + row * this.cellSize, this.cellSize, this.cellSize); } } } } drawPalette() { let keys = Object.keys(this.palette); let x = (width - keys.length * 60) / 2; // Decrease 70 to 60 for less spacing let y = 20; noStroke(); for (let i = 0; i < keys.length; i++) { let colorValue = this.palette[keys[i]].color; fill(colorValue); ellipse(x + i * 60 + 30, y + 25, 50, 50); let c = color(colorValue); let brightnessValue = (red(c) * 0.299 + green(c) * 0.587 + blue(c) * 0.114); // Standard luminance formula fill(brightnessValue < 128 ? 255 : 0); // White text for dark colors, black text for light colors textSize(14); textAlign(CENTER, CENTER); text(this.palette[keys[i]].label, x + i * 60 + 30, y + 25); // Adjusted positioning to match circles } }
class ColoringPage {
  constructor(name, imagePath, rows, cols, palette) {
    this.name = name;
    this.img = loadImage(imagePath);
    this.rows = rows;
    this.cols = cols;
    this.cellSize = 600 / this.cols;
    this.grid = Array.from({ length: this.rows }, () => Array(this.cols).fill(null));
    this.palette = palette;
    this.selectedColor = Object.values(palette)[0].color;
  }

  display() {
    this.drawPalette();
    image(this.img, 100, 90, 600, 600);
    this.drawGrid();
    this.drawColoredGrid();
  }

  drawGrid() {
    stroke(0, 50);
    noFill()
    for (let row = 0; row < this.rows; row++) {
      for (let col = 0; col < this.cols; col++) {
        rect(100 + col * this.cellSize, 90 + row * this.cellSize, this.cellSize, this.cellSize);
      }
    }
  }

  drawColoredGrid() {
    for (let row = 0; row < this.rows; row++) {
      for (let col = 0; col < this.cols; col++) {
        if (this.grid[row][col]) {
          fill(this.grid[row][col]);
          rect(100 + col * this.cellSize, 90 + row * this.cellSize, this.cellSize, this.cellSize);
        }
      }
    }
  }

  drawPalette() {
  let keys = Object.keys(this.palette);
  let x = (width - keys.length * 60) / 2; // Decrease 70 to 60 for less spacing
  let y = 20;
  
  noStroke();

  for (let i = 0; i < keys.length; i++) {
    let colorValue = this.palette[keys[i]].color;
    
    fill(colorValue);
    ellipse(x + i * 60 + 30, y + 25, 50, 50); 
    let c = color(colorValue);
    let brightnessValue = (red(c) * 0.299 + green(c) * 0.587 + blue(c) * 0.114); // Standard luminance formula

    fill(brightnessValue < 128 ? 255 : 0); // White text for dark colors, black text for light colors
    
    textSize(14);
    textAlign(CENTER, CENTER);
    text(this.palette[keys[i]].label, x + i * 60 + 30, y + 25); // Adjusted positioning to match circles
  }
}

 

Week 5 : Reading Response

Humans recognize faces and objects effortlessly because we rely on memory and perception, without needing to crunch huge amounts of data. Computers, on the other hand, have to go through an entire process—analyzing pixel data, running algorithms, and comparing what they see to stored information just to identify something. Plus, we’re naturally good at adapting to different environments, while computers can struggle when conditions change from what they were programmed for, often leading to glitches or poor performance.

To help computers track objects better, there are a few go-to techniques. Brightness thresholding boosts contrast by filtering colors and intensities, making objects stand out more clearly. Background subtraction helps by removing the static background so the system can focus only on what’s moving. These methods make it easier for computer vision to detect and track what matters, whether in art, security, or interactive installations.

In interactive art, computer vision takes audience engagement to the next level by allowing people to actively shape the artwork with their movements and gestures. Unlike traditional static art, these installations respond in real-time, making the experience more dynamic and immersive. By tracking participants as they move, computer vision can trigger visual, auditory, or even tactile reactions, turning viewers into active contributors rather than just passive observers. This creates a deeper connection with the artwork, giving people a sense of agency in the creative process. Whether it’s motion-triggered visuals, gesture-controlled projections, or body-driven games, computer vision is expanding the possibilities of creativity in interactive media.

Week 4 : Reading response

While reading the text, one thing I felt that Norman did not explicitly mention is the lack of instructions for devices and electronics, especially when their functionality is not immediately obvious. Many products assume that users will either figure things out through trial and error or consult a manual, but in reality, unclear controls often lead to confusion and frustration. For example, I own a digital camera, but I still have no idea how to zoom properly or what the different shooting modes actually do. There are no clear signifiers on the buttons, and the camera does not provide immediate feedback or guidance. Similarly, the projectors we have in the Baraha rooms can get difficult to use because the wall-docked interface offers multiple input options with no clear instructions on which mode is appropiate for what. Users are left guessing, which can lead to delays and unnecessary trial and error.

This lack of guidance can also be an issue in interactive media. Many systems expect users to learn how to navigate and use features on their own, but this often results in important functions being left undiscovered, reducing the overall usability of the system. This is where Norman’s principles of discoverability, feedback, and conceptual models become essential. If interfaces and products clearly communicated how to use them, users would not have to struggle with unnecessary complexity. By applying Norman’s ideas, interactive media could be improved by incorporating better signifiers, immediate feedback, and clearer mappings to ensure users understand what actions are possible. Whether in physical devices like cameras and projectors or in digital interfaces, design should guide users intuitively rather than forcing them to figure everything out on their own.

Week 4 : Generative text

For this week’s project, I wanted to create something with a cosmic theme that felt both interactive and magical. I focused on shades of blue and purple to match the theme and added a twinkling star effect as the background. The core of the project is the interactive bubbles—users can click on the screen to generate bubbles, and each bubble will display a random star sign. Clicking on an existing bubble reveals a short message related to that star sign. The letters of the message then fall down in an effect imitating a meteor shower.

CLICK TO CREATE AND POP BUBBLES

One part of the code that I’m particularly proud of is the way the bubbles and the falling messages behave. When the user clicks on a bubble, the bubble displays a message tied to the star sign. After a few seconds, the letters from the message “fall” like meteors. This falling effect was challenging to create because I had to ensure the letters moved smoothly and looked dynamic without overlapping or bunching together. Balancing the timing and position of each letter took some effort, but I’m happy with how it turned out. It adds a playful touch to the overall design.

I’m also proud of how the bubbles behave when they’re generated. Perfecting their collision and bounce behavior was tricky—it was difficult to make sure they didn’t overlap or get stuck together without affecting their smooth movement across the screen. It took a lot of experimenting to perfect the constraints that controlled their movement, Despite the challenges, I found it rewarding to see how small tweaks could make such a big improvement in the final product. These interactions made the overall experience feel more dynamic and immersive, which is exactly what I was aiming for with the cosmic theme.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
class Fortune {
constructor(x, y, message) {
this.x = x;
this.y = y;
this.message = message;
this.alpha = 0; // Start invisible for fade-in effect
this.timer = 90; // Now lasts for 3 seconds
this.fadeInSpeed = 5; // Controls how fast it fades in
this.released = false;
this.floatOffset = 0; // For a slight floating effect
}
update() {
if (this.timer > 0) {
this.timer--;
if (this.alpha < 255) this.alpha += this.fadeInSpeed; // Gradually appear
this.floatOffset = sin(frameCount * 0.1) * 2; // Subtle floating effect
} else if (!this.released) {
this.releaseLetters();
this.released = true;
}
}
display() {
if (this.timer > 0) {
push();
translate(this.x, this.y + this.floatOffset);
// Glowing text effect
fill(255, 255, 255, this.alpha);
textSize(14);
textAlign(CENTER, CENTER);
drawingContext.shadowBlur = 10;
drawingContext.shadowColor = color(255, 200, 255, this.alpha);
text(this.message, 0, 0);
pop();
}
}
releaseLetters() {
for (let i = 0; i < this.message.length; i++) {
rainingLetters.push(new Letter(this.x, this.y, this.message.charAt(i)));
}
}
}
class Letter {
constructor(x, y, char) {
this.x = x + random(-10, 10);
this.y = y;
this.char = char;
this.speed = random(1, 3);
this.alpha = 255;
}
update() {
this.y += this.speed;
this.alpha -= 3;
}
display() {
push();
translate(this.x, this.y);
fill(255, this.alpha);
textSize(20);
textAlign(CENTER, CENTER);
text(this.char, 0, 0);
pop();
}
}
class Fortune { constructor(x, y, message) { this.x = x; this.y = y; this.message = message; this.alpha = 0; // Start invisible for fade-in effect this.timer = 90; // Now lasts for 3 seconds this.fadeInSpeed = 5; // Controls how fast it fades in this.released = false; this.floatOffset = 0; // For a slight floating effect } update() { if (this.timer > 0) { this.timer--; if (this.alpha < 255) this.alpha += this.fadeInSpeed; // Gradually appear this.floatOffset = sin(frameCount * 0.1) * 2; // Subtle floating effect } else if (!this.released) { this.releaseLetters(); this.released = true; } } display() { if (this.timer > 0) { push(); translate(this.x, this.y + this.floatOffset); // Glowing text effect fill(255, 255, 255, this.alpha); textSize(14); textAlign(CENTER, CENTER); drawingContext.shadowBlur = 10; drawingContext.shadowColor = color(255, 200, 255, this.alpha); text(this.message, 0, 0); pop(); } } releaseLetters() { for (let i = 0; i < this.message.length; i++) { rainingLetters.push(new Letter(this.x, this.y, this.message.charAt(i))); } } } class Letter { constructor(x, y, char) { this.x = x + random(-10, 10); this.y = y; this.char = char; this.speed = random(1, 3); this.alpha = 255; } update() { this.y += this.speed; this.alpha -= 3; } display() { push(); translate(this.x, this.y); fill(255, this.alpha); textSize(20); textAlign(CENTER, CENTER); text(this.char, 0, 0); pop(); } }
class Fortune {
  constructor(x, y, message) {
    this.x = x;
    this.y = y;
    this.message = message;
    this.alpha = 0; // Start invisible for fade-in effect
    this.timer = 90; // Now lasts for 3 seconds
    this.fadeInSpeed = 5; // Controls how fast it fades in
    this.released = false;
    this.floatOffset = 0; // For a slight floating effect
  }

  update() {
    if (this.timer > 0) {
      this.timer--;
      if (this.alpha < 255) this.alpha += this.fadeInSpeed; // Gradually appear
      this.floatOffset = sin(frameCount * 0.1) * 2; // Subtle floating effect
    } else if (!this.released) {
      this.releaseLetters();
      this.released = true;
    }
  }

  display() {
    if (this.timer > 0) {
      push();
      translate(this.x, this.y + this.floatOffset);

      // Glowing text effect
      fill(255, 255, 255, this.alpha);
      textSize(14);
      textAlign(CENTER, CENTER);
      drawingContext.shadowBlur = 10;
      drawingContext.shadowColor = color(255, 200, 255, this.alpha);

      text(this.message, 0, 0);

      pop();
    }
  }

  releaseLetters() {
    for (let i = 0; i < this.message.length; i++) {
      rainingLetters.push(new Letter(this.x, this.y, this.message.charAt(i)));
    }
  }
}


class Letter {
  constructor(x, y, char) {
    this.x = x + random(-10, 10);
    this.y = y;
    this.char = char;
    this.speed = random(1, 3);
    this.alpha = 255;
  }

  update() {
    this.y += this.speed;
    this.alpha -= 3;
  }

  display() {
    push();
    translate(this.x, this.y);
    fill(255, this.alpha);
    textSize(20);
    textAlign(CENTER, CENTER);
    text(this.char, 0, 0);
    pop();
  }
}

Reflections and Future Ideas

Overall, I really enjoyed working on this project, even though it was a bit frustrating at times. Looking back, I feel like I could improve the overall aesthetic of the bubbles by adding a shine to make them look like they’re reflecting light. I’d also like to enhance the appearance of the text since the current font is quite simple. Another idea I have is to experiment with different styles for the falling letters—and maybe space them out more and slow the fall for a better visual experience, to better match the cosmic theme.

Week 3 : Generative Art

Concept and Inspiration

For this week’s assignment, I drew inspiration from spirals and circles, as symmetry has always intrigued me. The goal was to create a generative artwork, and after reading about interaction in this week’s reading, I wanted to give the viewer creative freedom over the composition. To achieve this, I incorporated keyboard controls for selecting different color gradients and allowed users to click anywhere on the canvas to generate a spiral at that point. Additionally, the canvas can be cleared to provide users with a blank canvas.


IMP : Read before creating ART
Color Scheme (click to choose) –

  • Rainbow: ‘R’ or ‘r’
  • Blues: ‘B’ or ‘b’
  • Blue-Green: ‘G’ or ‘g’
  • Pink-Purple: ‘P’ or ‘p’
  • Yellow-Orange-Red: ‘O’ or ‘o’
  • Purple-Red: ‘S’ or ‘s’
  • Clear Canvas: C or ‘c’

The biggest challenge I faced during this project was related to the creation and positioning of the spiral. I had to make sure that the circles followed the spiral’s curve closely, without leaving gaps between them. Initially, the gaps were too large, and the spiral looked disjointed. I spent a lot of time figuring out how to make the circles remain close together as they moved outward  and to ensure they were placed one after another in a way that created a continuous, tightly packed spiral.

The part I’m most proud of is the creating the spirals. Initially, my spirals weren’t very pretty, as the circles grew apart too much and it didn’t even look like a spiral. It took some time to figure out how to position the circles in a way that made the spiral look smooth and cohesive. After lots of tweaking, I managed to figure out the conditions where the circles would be placed in a way that created an organized spiral.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// Creates Spiral class, which acts as a template to print all spirals
class Spiral {
constructor(x, y, max_radius, num_circles, color_palette) {
this.x = x;
this.y = y;
this.max_radius = max_radius;
this.num_circles = num_circles;
this.current_circle = 0;
this.angle_increment = 10; // Places individual circles closer depending on value
this.radius_increment = 0.3; // Reduces outward distance between concentric circles of spiral depending on value
this.color_palette = color_palette;
}
// creates new mini circle each time function is called in draw function
update() {
if (this.current_circle < this.num_circles) {
this.current_circle += 1;
}
}
// prints spiral on canvas
display() {
push();
translate(this.x, this.y);
for (let i = 0; i < this.current_circle; i++) {
let angle = i * this.angle_increment;
let radius = i * this.radius_increment;
// to control transition of colors for diff color palettes
let gradient_factor = map(i, 0, this.num_circles, 0, 1);
// defining color gradient for each transition
let col;
if (this.color_palette === "rainbow") {
// Full rainbow transition
if (gradient_factor < 0.25) {
col = lerpColor(color(255, 0, 0), color(255, 255, 0), map(gradient_factor, 0, 0.25, 0, 1)); // Red → Yellow
} else if (gradient_factor < 0.5) {
col = lerpColor(color(255, 255, 0), color(0, 255, 0), map(gradient_factor, 0.25, 0.5, 0, 1)); // Yellow → Green
} else if (gradient_factor < 0.75) {
col = lerpColor(color(0, 255, 0), color(0, 0, 255), map(gradient_factor, 0.5, 0.75, 0, 1)); // Green → Blue
} else {
col = lerpColor(color(0, 0, 255), color(255, 0, 255), map(gradient_factor, 0.75, 1, 0, 1)); // Blue → Purple
}
} else if (this.color_palette === "blueGreen") {
col = lerpColor(color(0, 0, 255), color(0, 255, 0), gradient_factor); // Blue → Green
} else if (this.color_palette === "orangeRedYellow") {
col = lerpColor(color(255, 255, 0), color(255, 0, 0), gradient_factor); // Orange → Red
} else if (this.color_palette === "pinkPurple") {
col = lerpColor(color(255, 105, 180), color(96, 15, 156), gradient_factor); // Pink → Purple
} else if (this.color_palette === "purpleRed") {
col = lerpColor(color(128, 0, 128), color(255, 69, 0), gradient_factor); // Purple → Orange → Red
} else if (this.color_palette === "Blues") {
col = lerpColor(color(0, 0, 139), color(0, 255, 255), gradient_factor); // Deep Blue → Cyan
}
// prints each circle with chosen color after calculating positions
fill(col);
noStroke();
let x = cos(angle) * radius;
let y = sin(angle) * radius;
ellipse(x, y, 8);
}
pop();
}
}
// Creates Spiral class, which acts as a template to print all spirals class Spiral { constructor(x, y, max_radius, num_circles, color_palette) { this.x = x; this.y = y; this.max_radius = max_radius; this.num_circles = num_circles; this.current_circle = 0; this.angle_increment = 10; // Places individual circles closer depending on value this.radius_increment = 0.3; // Reduces outward distance between concentric circles of spiral depending on value this.color_palette = color_palette; } // creates new mini circle each time function is called in draw function update() { if (this.current_circle < this.num_circles) { this.current_circle += 1; } } // prints spiral on canvas display() { push(); translate(this.x, this.y); for (let i = 0; i < this.current_circle; i++) { let angle = i * this.angle_increment; let radius = i * this.radius_increment; // to control transition of colors for diff color palettes let gradient_factor = map(i, 0, this.num_circles, 0, 1); // defining color gradient for each transition let col; if (this.color_palette === "rainbow") { // Full rainbow transition if (gradient_factor < 0.25) { col = lerpColor(color(255, 0, 0), color(255, 255, 0), map(gradient_factor, 0, 0.25, 0, 1)); // Red → Yellow } else if (gradient_factor < 0.5) { col = lerpColor(color(255, 255, 0), color(0, 255, 0), map(gradient_factor, 0.25, 0.5, 0, 1)); // Yellow → Green } else if (gradient_factor < 0.75) { col = lerpColor(color(0, 255, 0), color(0, 0, 255), map(gradient_factor, 0.5, 0.75, 0, 1)); // Green → Blue } else { col = lerpColor(color(0, 0, 255), color(255, 0, 255), map(gradient_factor, 0.75, 1, 0, 1)); // Blue → Purple } } else if (this.color_palette === "blueGreen") { col = lerpColor(color(0, 0, 255), color(0, 255, 0), gradient_factor); // Blue → Green } else if (this.color_palette === "orangeRedYellow") { col = lerpColor(color(255, 255, 0), color(255, 0, 0), gradient_factor); // Orange → Red } else if (this.color_palette === "pinkPurple") { col = lerpColor(color(255, 105, 180), color(96, 15, 156), gradient_factor); // Pink → Purple } else if (this.color_palette === "purpleRed") { col = lerpColor(color(128, 0, 128), color(255, 69, 0), gradient_factor); // Purple → Orange → Red } else if (this.color_palette === "Blues") { col = lerpColor(color(0, 0, 139), color(0, 255, 255), gradient_factor); // Deep Blue → Cyan } // prints each circle with chosen color after calculating positions fill(col); noStroke(); let x = cos(angle) * radius; let y = sin(angle) * radius; ellipse(x, y, 8); } pop(); } }
// Creates Spiral class, which acts as a template to print all spirals
class Spiral {
  constructor(x, y, max_radius, num_circles, color_palette) {
    this.x = x;
    this.y = y;
    this.max_radius = max_radius;
    this.num_circles = num_circles;
    this.current_circle = 0;
    this.angle_increment = 10;  // Places individual circles closer depending on value
    this.radius_increment = 0.3;  // Reduces outward distance between concentric circles of spiral depending on value
    this.color_palette = color_palette;
  }

  // creates new mini circle each time function is called in draw function
  update() {
    if (this.current_circle < this.num_circles) {
      this.current_circle += 1;
    }
  }

  // prints spiral on canvas 
  display() {
    push();
    translate(this.x, this.y);

    for (let i = 0; i < this.current_circle; i++) {
      let angle = i * this.angle_increment;
      let radius = i * this.radius_increment;
      // to control transition of colors for diff color palettes
      let gradient_factor = map(i, 0, this.num_circles, 0, 1);
      
      // defining color gradient for each transition
      let col;
      if (this.color_palette === "rainbow") {
        // Full rainbow transition
        if (gradient_factor < 0.25) {
          col = lerpColor(color(255, 0, 0), color(255, 255, 0), map(gradient_factor, 0, 0.25, 0, 1)); // Red → Yellow
        } else if (gradient_factor < 0.5) {
          col = lerpColor(color(255, 255, 0), color(0, 255, 0), map(gradient_factor, 0.25, 0.5, 0, 1)); // Yellow → Green
        } else if (gradient_factor < 0.75) {
          col = lerpColor(color(0, 255, 0), color(0, 0, 255), map(gradient_factor, 0.5, 0.75, 0, 1)); // Green → Blue
        } else {
          col = lerpColor(color(0, 0, 255), color(255, 0, 255), map(gradient_factor, 0.75, 1, 0, 1)); // Blue → Purple
        }
      } else if (this.color_palette === "blueGreen") {
        col = lerpColor(color(0, 0, 255), color(0, 255, 0), gradient_factor); // Blue → Green
      } else if (this.color_palette === "orangeRedYellow") {
        col = lerpColor(color(255, 255, 0), color(255, 0, 0), gradient_factor); // Orange → Red
      } else if (this.color_palette === "pinkPurple") {
        col = lerpColor(color(255, 105, 180), color(96, 15, 156), gradient_factor); // Pink → Purple
      } else if (this.color_palette === "purpleRed") {
        col = lerpColor(color(128, 0, 128), color(255, 69, 0), gradient_factor); // Purple → Orange → Red
      } else if (this.color_palette === "Blues") {
        col = lerpColor(color(0, 0, 139), color(0, 255, 255), gradient_factor); // Deep Blue → Cyan
      }
      
      // prints each circle with chosen color after calculating positions
      fill(col);
      noStroke();
      let x = cos(angle) * radius;
      let y = sin(angle) * radius;
      ellipse(x, y, 8);
    }

    pop();
  }
}

 

Reflections and Future Improvements
Looking back, I’m happy with how everything turned out, and I especially like that so many different variations can be generated based on user interactions from such simplicity. However, I feel there’s still room to improve the spiral’s look. I want to learn how to adjust the spacing between circles so that they grow proportionally as they move outward, filling the space more evenly. Additionally, I would love to experiment with making the spiral tighter and more closely packed. Also I want to figure out a different ending for the spiral, maybe something like it keeps growing and takes over the whole screen, since right now it just stops adding circles to the spiral.

One thing I loved was how much easier my life became using OOP. Using a class to create a template for the spirals allowed me to generate as many spirals as I wanted without much effort. It’s fascinating to see how simple code structures can support more dynamic and complex artistic ideas, and I’m excited to keep refining my approach to generative art.

Reading Response : Week 3

In Chapter 1 of The Art of Interaction, “What Exactly is Interaction”, the discussion of interactivity goes beyond simple cause-and-effect relationships and explores how a system can engage users in meaningful ways. The idea that a truly interactive system should not just respond but also “think” in some capacity aligns with my own belief that interactivity is more than just reaction—it should involve some level of contemplation or adaptation. This challenges traditional perspectives on interactivity as being solely input-output driven. I found myself questioning whether an interactive system needs to have intelligence or just the illusion of it. Could randomness or unpredictability be enough to make a system feel “thoughtful” without actual intelligence?

One of my key takeaways is the importance of variety and unpredictability in user experiences. Instead of simply offering pre-defined responses to user input, I want to explore ways to make my sketches feel more dynamic and responsive. Introducing randomness in outputs could create the illusion of an evolving system that adapts to user behavior rather than just executing a set of rigid, predetermined responses. This approach also reflects my opinion that a truly interactive system should engage users in a way that feels organic and intentional, rather than mechanical.