Week 4 – generative text output

Inspiration for This Project

I wanted to create an interactive and visually engaging experience that merges astrology with generative art. The idea was to provide users with a simple yet immersive way to receive a zodiac-based “psychic reading,” followed by animations and visuals. Astrology is often associated with mysticism and magic, so I aimed to reflect that via changing background colours and adding floating particles. For the visual part, I took my inspiration from this website: https://www.horoscope.com/us/index.aspx

Code Highlight I Am Proud Of

One part of the code I’m particularly proud of is the getZodiacColor function, which assigns a unique background color to each zodiac sign:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
function getZodiacColor(sign) {
let colors = {
"Aries": color(255, 99, 71), "Taurus": color(107, 142, 35), "Gemini": color(255, 215, 0), "Cancer": color(70, 130, 180),
"Leo": color(255, 165, 0), "Virgo": color(46, 139, 87), "Libra": color(123, 104, 238), "Scorpio": color(148, 0, 211),
"Sagittarius": color(255, 140, 0), "Capricorn": color(139, 69, 19), "Aquarius": color(0, 191, 255), "Pisces": color(72, 61, 139)
};
return colors[sign] || color(240);
}
function getZodiacColor(sign) { let colors = { "Aries": color(255, 99, 71), "Taurus": color(107, 142, 35), "Gemini": color(255, 215, 0), "Cancer": color(70, 130, 180), "Leo": color(255, 165, 0), "Virgo": color(46, 139, 87), "Libra": color(123, 104, 238), "Scorpio": color(148, 0, 211), "Sagittarius": color(255, 140, 0), "Capricorn": color(139, 69, 19), "Aquarius": color(0, 191, 255), "Pisces": color(72, 61, 139) }; return colors[sign] || color(240); }
function getZodiacColor(sign) {
  let colors = {
    "Aries": color(255, 99, 71), "Taurus": color(107, 142, 35), "Gemini": color(255, 215, 0), "Cancer": color(70, 130, 180),
    "Leo": color(255, 165, 0), "Virgo": color(46, 139, 87), "Libra": color(123, 104, 238), "Scorpio": color(148, 0, 211),
    "Sagittarius": color(255, 140, 0), "Capricorn": color(139, 69, 19), "Aquarius": color(0, 191, 255), "Pisces": color(72, 61, 139)
  };
  return colors[sign] || color(240);
}

This function is simple, but it instantly transforms the visual feel of the project based on the user’s selection, creating some sense of personalization.

Reflection

For future projects, I’d love to explore more complex generative animations, such as constellations that change based on the zodiac sign. Things like integrating sound effects or subtle ambient music could enhance the mystical atmosphere. Another direction could be adding more interactive elements, like having particles respond to mouse movement, making the experience feel even more magical and immersive.

Here is the full code:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
let signs = [
"Aries", "Taurus", "Gemini", "Cancer", "Leo", "Virgo",
"Libra", "Scorpio", "Sagittarius", "Capricorn", "Aquarius", "Pisces"
];
// Zodiac readings for each sign
let readings = {
"Aries": ["Today is a day for bold moves.", "A new adventure awaits you.", "Your energy will attract opportunities."],
"Taurus": ["Stay grounded, but take a leap of faith.", "Patience will bring unexpected rewards.", "A financial opportunity is coming your way."],
"Gemini": ["A conversation will spark inspiration.", "Your curiosity leads to a surprising discovery.", "Adaptability is your greatest strength today."],
"Cancer": ["Your emotions will guide you well.", "A nostalgic moment will bring clarity.", "Trust your intuition—it knows the way."],
"Leo": ["Your confidence will open doors.", "A bold move will lead to admiration.", "Shine your light and others will follow."],
"Virgo": ["Your keen eye will catch an important detail.", "Organization will bring unexpected rewards.", "A small habit change will lead to a breakthrough."],
"Libra": ["Balance is key today.", "A relationship will deepen in an unexpected way.", "Harmony will find you when you least expect it."],
"Scorpio": ["Mystery surrounds you—embrace it.", "Transformation is closer than you think.", "Your passion will lead you to new heights."],
"Sagittarius": ["An exciting journey is on the horizon.", "Your optimism will inspire someone today.", "The universe is conspiring in your favor."],
"Capricorn": ["Hard work pays off—stay focused.", "A disciplined approach will yield results.", "Your perseverance will be rewarded soon."],
"Aquarius": ["Innovation is your ally today.", "A sudden insight will change your path.", "Your unique perspective is your greatest strength."],
"Pisces": ["Your dreams hold important messages.", "Creativity will flow effortlessly.", "A moment of solitude will bring deep understanding."]
};
let dropdown, button, output;
let bgColor;
let particles = [];
function setup() {
createCanvas(400, 300);
textSize(16);
textAlign(CENTER, CENTER);
// Create dropdown menu for zodiac signs
dropdown = createSelect();
dropdown.position(100, 100);
for (let sign of signs) {
dropdown.option(sign);
}
// Create button to generate reading
button = createButton("Get Your Reading");
button.position(100, 140);
button.mousePressed(generateReading);
output = "Select your sign and receive your reading";
bgColor = color(240);
// Create floating particles for magical effect
for (let i = 0; i < 50; i++) {
particles.push(new Particle());
}
}
function draw() {
background(bgColor);
fill(50);
text("Psychic Zodiac Reading", width / 2, 50);
text(output, width / 2, 80);
// Update and show floating particles
for (let p of particles) {
p.update();
p.show();
}
}
// Generate random reading based on selected zodiac sign
function generateReading() {
let selectedSign = dropdown.value();
let possibleReadings = readings[selectedSign];
output = possibleReadings[int(random(possibleReadings.length))];
bgColor = getZodiacColor(selectedSign);
}
// Assign unique background color for each zodiac sign
function getZodiacColor(sign) {
let colors = {
"Aries": color(255, 99, 71), "Taurus": color(107, 142, 35), "Gemini": color(255, 215, 0), "Cancer": color(70, 130, 180),
"Leo": color(255, 165, 0), "Virgo": color(46, 139, 87), "Libra": color(123, 104, 238), "Scorpio": color(148, 0, 211),
"Sagittarius": color(255, 140, 0), "Capricorn": color(139, 69, 19), "Aquarius": color(0, 191, 255), "Pisces": color(72, 61, 139)
};
return colors[sign] || color(240);
}
// Particle class for floating magic effect
class Particle {
constructor() {
this.x = random(width);
this.y = random(height);
this.vx = random(-1, 1);
this.vy = random(-1, 1);
this.alpha = random(100, 255);
}
// Update particle movement
update() {
this.x += this.vx;
this.y += this.vy;
if (this.x > width || this.x < 0) this.vx *= -1;
if (this.y > height || this.y < 0) this.vy *= -1;
}
// Display particle as a glowing dot
show() {
noStroke();
fill(255, this.alpha);
ellipse(this.x, this.y, 5, 5);
}
}
let signs = [ "Aries", "Taurus", "Gemini", "Cancer", "Leo", "Virgo", "Libra", "Scorpio", "Sagittarius", "Capricorn", "Aquarius", "Pisces" ]; // Zodiac readings for each sign let readings = { "Aries": ["Today is a day for bold moves.", "A new adventure awaits you.", "Your energy will attract opportunities."], "Taurus": ["Stay grounded, but take a leap of faith.", "Patience will bring unexpected rewards.", "A financial opportunity is coming your way."], "Gemini": ["A conversation will spark inspiration.", "Your curiosity leads to a surprising discovery.", "Adaptability is your greatest strength today."], "Cancer": ["Your emotions will guide you well.", "A nostalgic moment will bring clarity.", "Trust your intuition—it knows the way."], "Leo": ["Your confidence will open doors.", "A bold move will lead to admiration.", "Shine your light and others will follow."], "Virgo": ["Your keen eye will catch an important detail.", "Organization will bring unexpected rewards.", "A small habit change will lead to a breakthrough."], "Libra": ["Balance is key today.", "A relationship will deepen in an unexpected way.", "Harmony will find you when you least expect it."], "Scorpio": ["Mystery surrounds you—embrace it.", "Transformation is closer than you think.", "Your passion will lead you to new heights."], "Sagittarius": ["An exciting journey is on the horizon.", "Your optimism will inspire someone today.", "The universe is conspiring in your favor."], "Capricorn": ["Hard work pays off—stay focused.", "A disciplined approach will yield results.", "Your perseverance will be rewarded soon."], "Aquarius": ["Innovation is your ally today.", "A sudden insight will change your path.", "Your unique perspective is your greatest strength."], "Pisces": ["Your dreams hold important messages.", "Creativity will flow effortlessly.", "A moment of solitude will bring deep understanding."] }; let dropdown, button, output; let bgColor; let particles = []; function setup() { createCanvas(400, 300); textSize(16); textAlign(CENTER, CENTER); // Create dropdown menu for zodiac signs dropdown = createSelect(); dropdown.position(100, 100); for (let sign of signs) { dropdown.option(sign); } // Create button to generate reading button = createButton("Get Your Reading"); button.position(100, 140); button.mousePressed(generateReading); output = "Select your sign and receive your reading"; bgColor = color(240); // Create floating particles for magical effect for (let i = 0; i < 50; i++) { particles.push(new Particle()); } } function draw() { background(bgColor); fill(50); text("Psychic Zodiac Reading", width / 2, 50); text(output, width / 2, 80); // Update and show floating particles for (let p of particles) { p.update(); p.show(); } } // Generate random reading based on selected zodiac sign function generateReading() { let selectedSign = dropdown.value(); let possibleReadings = readings[selectedSign]; output = possibleReadings[int(random(possibleReadings.length))]; bgColor = getZodiacColor(selectedSign); } // Assign unique background color for each zodiac sign function getZodiacColor(sign) { let colors = { "Aries": color(255, 99, 71), "Taurus": color(107, 142, 35), "Gemini": color(255, 215, 0), "Cancer": color(70, 130, 180), "Leo": color(255, 165, 0), "Virgo": color(46, 139, 87), "Libra": color(123, 104, 238), "Scorpio": color(148, 0, 211), "Sagittarius": color(255, 140, 0), "Capricorn": color(139, 69, 19), "Aquarius": color(0, 191, 255), "Pisces": color(72, 61, 139) }; return colors[sign] || color(240); } // Particle class for floating magic effect class Particle { constructor() { this.x = random(width); this.y = random(height); this.vx = random(-1, 1); this.vy = random(-1, 1); this.alpha = random(100, 255); } // Update particle movement update() { this.x += this.vx; this.y += this.vy; if (this.x > width || this.x < 0) this.vx *= -1; if (this.y > height || this.y < 0) this.vy *= -1; } // Display particle as a glowing dot show() { noStroke(); fill(255, this.alpha); ellipse(this.x, this.y, 5, 5); } }
let signs = [
  "Aries", "Taurus", "Gemini", "Cancer", "Leo", "Virgo",
  "Libra", "Scorpio", "Sagittarius", "Capricorn", "Aquarius", "Pisces"
];

// Zodiac readings for each sign
let readings = {
  "Aries": ["Today is a day for bold moves.", "A new adventure awaits you.", "Your energy will attract opportunities."],
  "Taurus": ["Stay grounded, but take a leap of faith.", "Patience will bring unexpected rewards.", "A financial opportunity is coming your way."],
  "Gemini": ["A conversation will spark inspiration.", "Your curiosity leads to a surprising discovery.", "Adaptability is your greatest strength today."],
  "Cancer": ["Your emotions will guide you well.", "A nostalgic moment will bring clarity.", "Trust your intuition—it knows the way."],
  "Leo": ["Your confidence will open doors.", "A bold move will lead to admiration.", "Shine your light and others will follow."],
  "Virgo": ["Your keen eye will catch an important detail.", "Organization will bring unexpected rewards.", "A small habit change will lead to a breakthrough."],
  "Libra": ["Balance is key today.", "A relationship will deepen in an unexpected way.", "Harmony will find you when you least expect it."],
  "Scorpio": ["Mystery surrounds you—embrace it.", "Transformation is closer than you think.", "Your passion will lead you to new heights."],
  "Sagittarius": ["An exciting journey is on the horizon.", "Your optimism will inspire someone today.", "The universe is conspiring in your favor."],
  "Capricorn": ["Hard work pays off—stay focused.", "A disciplined approach will yield results.", "Your perseverance will be rewarded soon."],
  "Aquarius": ["Innovation is your ally today.", "A sudden insight will change your path.", "Your unique perspective is your greatest strength."],
  "Pisces": ["Your dreams hold important messages.", "Creativity will flow effortlessly.", "A moment of solitude will bring deep understanding."]
};

let dropdown, button, output;
let bgColor;
let particles = [];

function setup() {
  createCanvas(400, 300);
  textSize(16);
  textAlign(CENTER, CENTER);
  
  // Create dropdown menu for zodiac signs
  dropdown = createSelect();
  dropdown.position(100, 100);
  
  for (let sign of signs) {
    dropdown.option(sign);
  }
  
  // Create button to generate reading
  button = createButton("Get Your Reading");
  button.position(100, 140);
  button.mousePressed(generateReading);
  
  output = "Select your sign and receive your reading";
  bgColor = color(240);
  
  // Create floating particles for magical effect
  for (let i = 0; i < 50; i++) {
    particles.push(new Particle());
  }
}

function draw() {
  background(bgColor);
  fill(50);
  text("Psychic Zodiac Reading", width / 2, 50);
  text(output, width / 2, 80);
  
  // Update and show floating particles
  for (let p of particles) {
    p.update();
    p.show();
  }
}

// Generate random reading based on selected zodiac sign
function generateReading() {
  let selectedSign = dropdown.value();
  let possibleReadings = readings[selectedSign];
  output = possibleReadings[int(random(possibleReadings.length))];
  bgColor = getZodiacColor(selectedSign);
}

// Assign unique background color for each zodiac sign
function getZodiacColor(sign) {
  let colors = {
    "Aries": color(255, 99, 71), "Taurus": color(107, 142, 35), "Gemini": color(255, 215, 0), "Cancer": color(70, 130, 180),
    "Leo": color(255, 165, 0), "Virgo": color(46, 139, 87), "Libra": color(123, 104, 238), "Scorpio": color(148, 0, 211),
    "Sagittarius": color(255, 140, 0), "Capricorn": color(139, 69, 19), "Aquarius": color(0, 191, 255), "Pisces": color(72, 61, 139)
  };
  return colors[sign] || color(240);
}

// Particle class for floating magic effect
class Particle {
  constructor() {
    this.x = random(width);
    this.y = random(height);
    this.vx = random(-1, 1);
    this.vy = random(-1, 1);
    this.alpha = random(100, 255);
  }

  // Update particle movement
  update() {
    this.x += this.vx;
    this.y += this.vy;
    if (this.x > width || this.x < 0) this.vx *= -1;
    if (this.y > height || this.y < 0) this.vy *= -1;
  }

  // Display particle as a glowing dot
  show() {
    noStroke();
    fill(255, this.alpha);
    ellipse(this.x, this.y, 5, 5);
  }
}

 

Object-oriented programming – Week 3

Reflection: Floating Bubbles

This artwork is inspired by the simple joy of blowing soap bubbles we used to do as kids. Just like real bubbles, the circles appear wherever you click, float around randomly, and slowly fade away or pop. I wanted each click to feel like blowing a new batch of bubbles, watching them drift and disappear. The way they change color and move in different directions makes them feel alive, conveying the playful nature of real soap bubbles.

Structure

The Circle class manages each individual bubble’s movement, opacity, and lifespan, ensuring that they appear and disappear naturally over time. Functions like setup(), draw(), and mousePressed() organize different parts of the code, keeping it easy to understand and modify.

Challenges

One issue was finding the right balance between movement and fading, so that that bubbles did not disappear too quickly while still feeling transient. Another challenge was making the interaction feel engaging, which I solved by adjusting the number of bubbles created per click and giving them different speeds and directions. Additionally, I had to optimize performance to prevent the sketch from slowing down over time, so circles are now removed once their lifespan ends.

Overall, I appreciate how this piece captures the lighthearted  beauty of soap bubbles in a digital form. To make it more realistic, I’d try to make the direction that bubbles take more random and add the effect of abrupt popping.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// Array to store circles
let circles = [];
let circleSize = 50;
let numCirclesPerClick = 5; // Number of circles generated per mouse click
function setup() {
createCanvas(windowWidth, windowHeight);
noFill(); // No fill for circles, only stroke
}
function draw() {
background(20, 20, 30, 50);
// Loop through circles in reverse order
for (let i = circles.length - 1; i >= 0; i--) {
circles[i].update();
circles[i].display();
// Remove circle when its lifespan reaches zero
if (circles[i].lifespan <= 0) {
circles.splice(i, 1);
}
}
}
// Circle class (individual moving and fading circles)
class Circle {
constructor(x, y, size) {
this.x = x;
this.y = y;
this.vx = random(-2, 2); // Assign random speed in x direction
this.vy = random(-2, 2); // Assign random speed in y direction
this.baseSize = size;
this.size = size;
this.opacity = random(100, 200);
this.growthSpeed = random(0.5, 2);
this.color = color(random(255), random(255), random(255));
this.lifespan = 200; // Circle disappears after a set time
}
// Update circle properties (position, size, opacity, lifespan)
update() {
this.x += this.vx;
this.y += this.vy;
this.size = this.baseSize + sin(frameCount * 0.05) * 10; // Oscillating size effect
this.opacity -= 2;
this.lifespan -= 2;
}
// Display the objects
display() {
stroke(this.color);
strokeWeight(2);
fill(this.color.levels[0], this.color.levels[1], this.color.levels[2], this.opacity); // fading effect
ellipse(this.x, this.y, this.size, this.size);
}
}
// generates multiple circles at the mouse click location
function mousePressed() {
for (let i = 0; i < numCirclesPerClick; i++) {
circles.push(new Circle(mouseX, mouseY, circleSize));
}
}
// Array to store circles let circles = []; let circleSize = 50; let numCirclesPerClick = 5; // Number of circles generated per mouse click function setup() { createCanvas(windowWidth, windowHeight); noFill(); // No fill for circles, only stroke } function draw() { background(20, 20, 30, 50); // Loop through circles in reverse order for (let i = circles.length - 1; i >= 0; i--) { circles[i].update(); circles[i].display(); // Remove circle when its lifespan reaches zero if (circles[i].lifespan <= 0) { circles.splice(i, 1); } } } // Circle class (individual moving and fading circles) class Circle { constructor(x, y, size) { this.x = x; this.y = y; this.vx = random(-2, 2); // Assign random speed in x direction this.vy = random(-2, 2); // Assign random speed in y direction this.baseSize = size; this.size = size; this.opacity = random(100, 200); this.growthSpeed = random(0.5, 2); this.color = color(random(255), random(255), random(255)); this.lifespan = 200; // Circle disappears after a set time } // Update circle properties (position, size, opacity, lifespan) update() { this.x += this.vx; this.y += this.vy; this.size = this.baseSize + sin(frameCount * 0.05) * 10; // Oscillating size effect this.opacity -= 2; this.lifespan -= 2; } // Display the objects display() { stroke(this.color); strokeWeight(2); fill(this.color.levels[0], this.color.levels[1], this.color.levels[2], this.opacity); // fading effect ellipse(this.x, this.y, this.size, this.size); } } // generates multiple circles at the mouse click location function mousePressed() { for (let i = 0; i < numCirclesPerClick; i++) { circles.push(new Circle(mouseX, mouseY, circleSize)); } }
// Array to store circles
let circles = [];
let circleSize = 50;
let numCirclesPerClick = 5; // Number of circles generated per mouse click

function setup() {
  createCanvas(windowWidth, windowHeight);
  noFill(); // No fill for circles, only stroke
}

function draw() {
  background(20, 20, 30, 50); 
  
  // Loop through circles in reverse order 
  for (let i = circles.length - 1; i >= 0; i--) {
    circles[i].update(); 
    circles[i].display(); 
    
    // Remove circle when its lifespan reaches zero
    if (circles[i].lifespan <= 0) {
      circles.splice(i, 1);
    }
  }
}

// Circle class (individual moving and fading circles)
class Circle {
  constructor(x, y, size) {
    this.x = x;
    this.y = y;
    this.vx = random(-2, 2); // Assign random speed in x direction
    this.vy = random(-2, 2); // Assign random speed in y direction
    this.baseSize = size;
    this.size = size;
    this.opacity = random(100, 200); 
    this.growthSpeed = random(0.5, 2); 
    this.color = color(random(255), random(255), random(255)); 
    this.lifespan = 200; // Circle disappears after a set time
  }

  // Update circle properties (position, size, opacity, lifespan)
  update() {
    this.x += this.vx; 
    this.y += this.vy; 
    this.size = this.baseSize + sin(frameCount * 0.05) * 10; // Oscillating size effect
    this.opacity -= 2; 
    this.lifespan -= 2; 
  }

  // Display the objects
  display() {
    stroke(this.color);
    strokeWeight(2);
    fill(this.color.levels[0], this.color.levels[1], this.color.levels[2], this.opacity); // fading effect
    ellipse(this.x, this.y, this.size, this.size);
  }
}

// generates multiple circles at the mouse click location
function mousePressed() {
  for (let i = 0; i < numCirclesPerClick; i++) {
    circles.push(new Circle(mouseX, mouseY, circleSize));
  }
}

 

Reading Response – Week 3

I believe that interactivity is like a conversation. It’s alive, reciprocal, and unpredictable. It thrives on an ongoing exchange where both parties listen, process, exchange ideas. Yet sometimes, we tend to mistake reaction for interaction. Imagine a fridge light turning on when you open the door – that’s a reaction. In this context, a true interaction requires intention, curiosity, and a sense of the unexpected.

When designing for interactivity, I want to create projects that thrive on engagement, not just response. In my p5.js sketches, I want to move beyond the input-output relationships and create something that listens and adapts to what the audience feeds to the “artwork”. This may mean making the visuals morph in response to prolonged interaction or rewarding users for exploration rather than just reacting to a single click. In any case, I want each person who interacts with my projects to have a unique experience.

To sum up, I think that a truly interactive system should feel like a dynamic exchange, where the user isn’t just playing with the system but within it. That’s the kind of interactivity I want to create—something more like a meaningful conversation and less like a fridge light turning on and off.