Midterm Project – Oracle Lady

Oracle Cards – Midterm Project Report

Oracle Cards is an interactive web-based application I developed, inspired by the mystical allure of tarot and oracle card readings. For this project, I utilized the p5.js library to create a visually engaging and user-friendly experience that allows users to select a category and receive either advice or a reflection question. My goal was to craft an accessible and contemplative digital experience, drawing from my interest in mindfulness and self-reflection. This project leverages p5.js for rendering graphics, handling user input, and incorporating audio to enhance immersion.

About

Gameplay Instructions:
To play Oracle Cards, click the game link above to open the application in a new window. Ensure your browser supports p5.js and that you have a stable internet connection for loading assets (images and audio). The game is controlled entirely via mouse clicks, making it intuitive and accessible. Upon loading, you’ll see a welcome screen with a background image and category buttons. Click a category to proceed, then choose between receiving advice or a reflection question. The result is displayed on a card, accompanied by ambient music you can control with on-screen buttons. All images (e.g., backgrounds, cards) were sourced from royalty-free libraries, and the music tracks are licensed for non-commercial use. I do not claim ownership of these assets but have customized their integration to suit the game’s aesthetic.

How the Game Works

Oracle Cards simulates a digital card-reading experience, drawing inspiration from oracle decks used for introspection and guidance. The game progresses through three states:

  1. Start: Users select a category (“Yourself,” “Friends & Family,” or “Transcendence”).

  2. Choose Type: Users pick between “Advice” or a “Reflection Question.”

  3. Result: A randomly selected response is displayed on a card, based on the chosen category and type.

The game uses a desk-themed interface with a lady in the background (inspired by a fortune-teller aesthetic) to create an immersive atmosphere. Users can control background music via play/pause, next, and previous buttons, enhancing the reflective mood. The sequence of events is as follows:

  • The game loads with a welcome screen and category buttons.

  • Clicking a category transitions to the choice screen, displaying a card with two options.

  • Selecting a choice reveals the result on a card, with a prompt to restart by clicking anywhere.

The design emphasizes simplicity and emotional resonance, encouraging users to pause and reflect.

Game Controls

The controls are straightforward:

  • Mouse Click: Click on category buttons (e.g., “Yourself”) to select a category, choice buttons (e.g., “Advice”) to pick a type, or anywhere on the result screen to restart.

  • Music Controls: Click the previous, play/pause, or next buttons in the top-right corner to manage background music.

This minimal input method ensures accessibility for users of all skill levels.

Proud Moment

I’m particularly proud of implementing the state management system using p5.js, which seamlessly transitions between the start, chooseType, and result states without glitches. Debugging the button click detection was challenging, especially ensuring accurate hitboxes for the buttons, but I resolved this by carefully calculating their positions relative to the canvas. Additionally, integrating audio functionality with p5.js’s loadSound and managing playback states (play/pause, next/previous) was a significant achievement. The randomization of responses, tailored to each category and choice, adds replayability and depth, which I fine-tuned to ensure a balanced and meaningful user experience.

The Code

Reusability and p5.js

The code is designed with modularity in mind, separating concerns like state management, rendering, and user input. Key p5.js functions (preload, setup, draw) are organized to handle asset loading, canvas initialization, and continuous rendering efficiently. For example, the drawButtons function is reusable for rendering both category and choice buttons:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
function drawButtons(options, yStart) {
for (let i = 0; i < options.length; i++) {
let x = width / 2;
let y = yStart + i * 50;
fill(200, 100, 100); // Button color (red)
rect(x - 100, y - 20, 200, 40, 10); // Draw button with rounded corners
fill(255); // White text
textAlign(CENTER, CENTER);
text(options[i], x, y); // Display button label
}
}
function drawButtons(options, yStart) { for (let i = 0; i < options.length; i++) { let x = width / 2; let y = yStart + i * 50; fill(200, 100, 100); // Button color (red) rect(x - 100, y - 20, 200, 40, 10); // Draw button with rounded corners fill(255); // White text textAlign(CENTER, CENTER); text(options[i], x, y); // Display button label } }
function drawButtons(options, yStart) {
  for (let i = 0; i < options.length; i++) {
    let x = width / 2;
    let y = yStart + i * 50;
    fill(200, 100, 100); // Button color (red)
    rect(x - 100, y - 20, 200, 40, 10); // Draw button with rounded corners
    fill(255); // White text
    textAlign(CENTER, CENTER);
    text(options[i], x, y); // Display button label
  }
}

This function is called in both the start and chooseType states, reducing code duplication. The preload function ensures all images and sounds are loaded before rendering, with error handling via callbacks:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
deskLadyImage = loadImage('assets/desk_lady.png',
() => console.log("Desk lady image loaded successfully"),
(err) => console.error("Failed to load desk lady image:", err)
);
deskLadyImage = loadImage('assets/desk_lady.png', () => console.log("Desk lady image loaded successfully"), (err) => console.error("Failed to load desk lady image:", err) );
deskLadyImage = loadImage('assets/desk_lady.png', 
  () => console.log("Desk lady image loaded successfully"), 
  (err) => console.error("Failed to load desk lady image:", err)
);

Object-Oriented Code

While the project primarily uses functional programming, the responses object is structured hierarchically to store advice and reflection questions for each category, enabling easy access and randomization:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
let responses = {
"Yourself": {
"Advice": [
"Take a deep breath and trust yourself.",
// ... other advice
],
"Reflection Question": [
"What is one thing you truly love about yourself?",
// ... other questions
]
},
// ... other categories
};
let responses = { "Yourself": { "Advice": [ "Take a deep breath and trust yourself.", // ... other advice ], "Reflection Question": [ "What is one thing you truly love about yourself?", // ... other questions ] }, // ... other categories };
let responses = {
  "Yourself": {
    "Advice": [
      "Take a deep breath and trust yourself.",
      // ... other advice
    ],
    "Reflection Question": [
      "What is one thing you truly love about yourself?",
      // ... other questions
    ]
  },
  // ... other categories
};

The mousePressed function handles state transitions and music controls, using conditional logic to detect clicks within button boundaries:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
if (gameState === "start") {
for (let i = 0; i < categories.length; i++) {
if (mouseX > width / 2 - 100 && mouseX < width / 2 + 100 &&
mouseY > height / 2 + 150 + i * 50 - 20 && mouseY < height / 2 + 150 + i * 50 + 20) {
selectedCategory = categories[i];
gameState = "chooseType";
}
}
}
if (gameState === "start") { for (let i = 0; i < categories.length; i++) { if (mouseX > width / 2 - 100 && mouseX < width / 2 + 100 && mouseY > height / 2 + 150 + i * 50 - 20 && mouseY < height / 2 + 150 + i * 50 + 20) { selectedCategory = categories[i]; gameState = "chooseType"; } } }
if (gameState === "start") {
  for (let i = 0; i < categories.length; i++) {
    if (mouseX > width / 2 - 100 && mouseX < width / 2 + 100 &&
        mouseY > height / 2 + 150 + i * 50 - 20 && mouseY < height / 2 + 150 + i * 50 + 20) {
      selectedCategory = categories[i];
      gameState = "chooseType";
    }
  }
}

This modular approach ensures the code is maintainable and extensible.

Training the Model

No machine learning models were used in this project, as the focus was on user interaction and randomization within p5.js. However, the randomization logic for responses mimics a lightweight decision model. I curated the responses object by researching mindfulness and self-help literature, ensuring each piece of advice or question is concise yet impactful. The random function in p5.js was used to select responses, tested extensively to confirm uniform distribution across options.

Areas of Improvement

I’m delighted with Oracle Cards as a reflective and engaging experience that aligns with my vision of digital mindfulness. However, there are opportunities for enhancement:

  • Additional Features: I’d like to add animations for card transitions and a settings menu to adjust music volume or toggle visuals (e.g., enabling/disabling the crystal ball image).

  • Visual Polish: Incorporating hover effects for buttons and more varied card designs could elevate the aesthetic.

  • Content Expansion: Adding more categories or allowing users to input custom questions would increase replayability.

  • Accessibility: Implementing keyboard controls alongside mouse clicks would make the game more inclusive.

As for the game logic, I plan to explore subtle physics-based animations (e.g., card flipping) using p5.js to enhance interactivity.

Conclusion

Overall, I’m proud of Oracle Cards and how it blends creativity, technical skill, and emotional resonance. It’s a meaningful step in my journey with interactive design, and I look forward to refining it further to share its calming experience with others.

Below is the Game:

Week 4 response

Introduction:

When I was reading the book I noticed how my thoughts that I had internally were said outside. Maybe I didn’t know how to word it as well but the idea was just as clear. The way he describes the process and the way the designers think of it vs how we as the users think of the items is different and that kind of makes it uncomfortable to use. There are things specifically on campus that drive me crazy sometimes because of how it is made and not efficient.

Reading Response:

Based on the reading something that annoys me would be the doors on campus, specifically the doors at Dining Hall 2. They don’t have sensors to enter and require you to pull but then open themselves automatically. The harder you pull the more opposing force it uses to prevent you from opening it manually. I prefer doors that can just open from a push either way. But how do I use the frustration of these inefficient made doors in interactive media? Well, I would make it so that it’s user-friendly. Something that is so easy to use that intrinsic to use people, something that’s almost close to common sense for the everyday layperson. Not focusing on creating something that’s easy as a coder or an artist but something thats easy for my audience.

 

Data visualization:

Below is a data visualization that’s very simple and not advanced at all but just very basic to show data that’s all.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
let users = [];
let colors = ['#ff0000', '#00ff00', '#0000ff', '#ffff00', '#ff00ff', '#00ffff', '#ffa500', '#800080', '#008080', '#ffd700'];
let timeAwakeInput, timeWorkingInput, submitButton, newUserButton, userSelect;
let currentUser = 0;
let barWidth = 20;
function setup() {
createCanvas(800, 500);
background(240);
drawAxes();
timeAwakeInput = createSelect();
for (let i = 2; i <= 24; i += 2) {
timeAwakeInput.option(i);
}
timeAwakeInput.position(10, 10);
timeWorkingInput = createSelect();
for (let i = 0.5; i <= 24; i += 0.5) {
timeWorkingInput.option(i);
}
timeWorkingInput.position(10, 40);
submitButton = createButton('Submit Entry');
submitButton.position(10, 70);
submitButton.mousePressed(addEntry);
newUserButton = createButton('New User');
newUserButton.position(110, 70);
newUserButton.mousePressed(createNewUser);
userSelect = createSelect();
userSelect.position(10, 100);
userSelect.changed(changeUser);
createNewUser();
textAlign(LEFT, CENTER);
text('Time Awake (hours):', 150, 25);
text('Time Working (hours):', 150, 55);
}
function createNewUser() {
let newUser = {
id: users.length,
color: colors[users.length % colors.length],
entries: []
};
users.push(newUser);
userSelect.option('User ' + (newUser.id + 1), newUser.id);
userSelect.selected(newUser.id);
currentUser = newUser.id;
}
function changeUser() {
currentUser = parseInt(userSelect.value());
}
function addEntry() {
let timeAwake = parseFloat(timeAwakeInput.value());
let timeWorking = parseFloat(timeWorkingInput.value());
if (timeWorking > timeAwake) {
alert("Time working cannot exceed time awake!");
return;
}
users[currentUser].entries.push({
timeAwake: timeAwake,
timeWorking: timeWorking
});
updateGraph();
}
function drawAxes() {
stroke(0);
line(50, height - 50, width - 50, height - 50); // X-axis
line(50, height - 50, 50, 50); // Y-axis
textAlign(CENTER);
text("Time Awake (hours)", width/2, height - 10);
push();
translate(15, height/2);
rotate(-HALF_PI);
text("Time Working (hours)", 0, 0);
pop();
for (let i = 0; i <= 24; i += 4) {
let x = map(i, 0, 24, 50, width - 50);
line(x, height - 50, x, height - 45);
textAlign(CENTER);
text(i, x, height - 35);
}
for (let i = 0; i <= 24; i += 4) {
let y = map(i, 0, 24, height - 50, 50);
line(45, y, 50, y);
textAlign(RIGHT);
text(i, 40, y);
}
}
function updateGraph() {
background(240);
drawAxes();
let groupWidth = barWidth * users.length;
for (let i = 0; i < users.length; i++) {
let user = users[i];
for (let entry of user.entries) {
let x = map(entry.timeAwake, 0, 24, 50, width - 50);
let barHeight = map(entry.timeWorking, 0, 24, 0, height - 100);
// Adjust x position based on user index
let adjustedX = x - groupWidth/2 + i * barWidth + barWidth/2;
fill(user.color);
rect(adjustedX - barWidth/2, height - 50 - barHeight, barWidth, barHeight);
push();
fill(0);
textAlign(CENTER);
textSize(10);
text(entry.timeWorking + "h", adjustedX, height - 55 - barHeight);
pop();
}
}
}
let users = []; let colors = ['#ff0000', '#00ff00', '#0000ff', '#ffff00', '#ff00ff', '#00ffff', '#ffa500', '#800080', '#008080', '#ffd700']; let timeAwakeInput, timeWorkingInput, submitButton, newUserButton, userSelect; let currentUser = 0; let barWidth = 20; function setup() { createCanvas(800, 500); background(240); drawAxes(); timeAwakeInput = createSelect(); for (let i = 2; i <= 24; i += 2) { timeAwakeInput.option(i); } timeAwakeInput.position(10, 10); timeWorkingInput = createSelect(); for (let i = 0.5; i <= 24; i += 0.5) { timeWorkingInput.option(i); } timeWorkingInput.position(10, 40); submitButton = createButton('Submit Entry'); submitButton.position(10, 70); submitButton.mousePressed(addEntry); newUserButton = createButton('New User'); newUserButton.position(110, 70); newUserButton.mousePressed(createNewUser); userSelect = createSelect(); userSelect.position(10, 100); userSelect.changed(changeUser); createNewUser(); textAlign(LEFT, CENTER); text('Time Awake (hours):', 150, 25); text('Time Working (hours):', 150, 55); } function createNewUser() { let newUser = { id: users.length, color: colors[users.length % colors.length], entries: [] }; users.push(newUser); userSelect.option('User ' + (newUser.id + 1), newUser.id); userSelect.selected(newUser.id); currentUser = newUser.id; } function changeUser() { currentUser = parseInt(userSelect.value()); } function addEntry() { let timeAwake = parseFloat(timeAwakeInput.value()); let timeWorking = parseFloat(timeWorkingInput.value()); if (timeWorking > timeAwake) { alert("Time working cannot exceed time awake!"); return; } users[currentUser].entries.push({ timeAwake: timeAwake, timeWorking: timeWorking }); updateGraph(); } function drawAxes() { stroke(0); line(50, height - 50, width - 50, height - 50); // X-axis line(50, height - 50, 50, 50); // Y-axis textAlign(CENTER); text("Time Awake (hours)", width/2, height - 10); push(); translate(15, height/2); rotate(-HALF_PI); text("Time Working (hours)", 0, 0); pop(); for (let i = 0; i <= 24; i += 4) { let x = map(i, 0, 24, 50, width - 50); line(x, height - 50, x, height - 45); textAlign(CENTER); text(i, x, height - 35); } for (let i = 0; i <= 24; i += 4) { let y = map(i, 0, 24, height - 50, 50); line(45, y, 50, y); textAlign(RIGHT); text(i, 40, y); } } function updateGraph() { background(240); drawAxes(); let groupWidth = barWidth * users.length; for (let i = 0; i < users.length; i++) { let user = users[i]; for (let entry of user.entries) { let x = map(entry.timeAwake, 0, 24, 50, width - 50); let barHeight = map(entry.timeWorking, 0, 24, 0, height - 100); // Adjust x position based on user index let adjustedX = x - groupWidth/2 + i * barWidth + barWidth/2; fill(user.color); rect(adjustedX - barWidth/2, height - 50 - barHeight, barWidth, barHeight); push(); fill(0); textAlign(CENTER); textSize(10); text(entry.timeWorking + "h", adjustedX, height - 55 - barHeight); pop(); } } }
let users = [];
let colors = ['#ff0000', '#00ff00', '#0000ff', '#ffff00', '#ff00ff', '#00ffff', '#ffa500', '#800080', '#008080', '#ffd700'];
let timeAwakeInput, timeWorkingInput, submitButton, newUserButton, userSelect;
let currentUser = 0;
let barWidth = 20;

function setup() {
  createCanvas(800, 500);
  background(240);
  drawAxes();

  timeAwakeInput = createSelect();
  for (let i = 2; i <= 24; i += 2) {
    timeAwakeInput.option(i);
  }
  timeAwakeInput.position(10, 10);

  timeWorkingInput = createSelect();
  for (let i = 0.5; i <= 24; i += 0.5) {
    timeWorkingInput.option(i);
  }
  timeWorkingInput.position(10, 40);

  submitButton = createButton('Submit Entry');
  submitButton.position(10, 70);
  submitButton.mousePressed(addEntry);

  newUserButton = createButton('New User');
  newUserButton.position(110, 70);
  newUserButton.mousePressed(createNewUser);

  userSelect = createSelect();
  userSelect.position(10, 100);
  userSelect.changed(changeUser);

  createNewUser();

  textAlign(LEFT, CENTER);
  text('Time Awake (hours):', 150, 25);
  text('Time Working (hours):', 150, 55);
}

function createNewUser() {
  let newUser = {
    id: users.length,
    color: colors[users.length % colors.length],
    entries: []
  };
  users.push(newUser);
  userSelect.option('User ' + (newUser.id + 1), newUser.id);
  userSelect.selected(newUser.id);
  currentUser = newUser.id;
}

function changeUser() {
  currentUser = parseInt(userSelect.value());
}

function addEntry() {
  let timeAwake = parseFloat(timeAwakeInput.value());
  let timeWorking = parseFloat(timeWorkingInput.value());

  if (timeWorking > timeAwake) {
    alert("Time working cannot exceed time awake!");
    return;
  }

  users[currentUser].entries.push({
    timeAwake: timeAwake,
    timeWorking: timeWorking
  });

  updateGraph();
}

function drawAxes() {
  stroke(0);
  line(50, height - 50, width - 50, height - 50); // X-axis
  line(50, height - 50, 50, 50); // Y-axis
  
  textAlign(CENTER);
  text("Time Awake (hours)", width/2, height - 10);
  
  push();
  translate(15, height/2);
  rotate(-HALF_PI);
  text("Time Working (hours)", 0, 0);
  pop();
  
  for (let i = 0; i <= 24; i += 4) {
    let x = map(i, 0, 24, 50, width - 50);
    line(x, height - 50, x, height - 45);
    textAlign(CENTER);
    text(i, x, height - 35);
  }
  
  for (let i = 0; i <= 24; i += 4) {
    let y = map(i, 0, 24, height - 50, 50);
    line(45, y, 50, y);
    textAlign(RIGHT);
    text(i, 40, y);
  }
}

function updateGraph() {
  background(240);
  drawAxes();
  
  let groupWidth = barWidth * users.length;
  
  for (let i = 0; i < users.length; i++) {
    let user = users[i];
    for (let entry of user.entries) {
      let x = map(entry.timeAwake, 0, 24, 50, width - 50);
      let barHeight = map(entry.timeWorking, 0, 24, 0, height - 100);
      
      // Adjust x position based on user index
      let adjustedX = x - groupWidth/2 + i * barWidth + barWidth/2;
      
      fill(user.color);
      rect(adjustedX - barWidth/2, height - 50 - barHeight, barWidth, barHeight);
      
      push();
      fill(0);
      textAlign(CENTER);
      textSize(10);
      text(entry.timeWorking + "h", adjustedX, height - 55 - barHeight);
      pop();
    }
  }
}

 

Week 3 – Assignment

Introduction: Inspiration and Thought Process

When approaching this project, I wanted to create something that felt organic, dynamic, and visually engaging while staying true to my interest in systems, patterns, and movement. I’ve always been fascinated by the hidden structures that govern the natural world—whether it’s the way air currents shape cloud formations, how magnetic fields interact with charged particles, or how fluid dynamics influence ocean currents. These invisible forces dictate movement and structure on both microscopic and massive scales, yet they often go unnoticed in daily life.

This fascination led me to explore generative art as a way to reveal these hidden forces. By using a flow field generated through Perlin noise, I aimed to simulate an abstract yet realistic movement of particles through a force-driven system. The goal was to make the movement feel both unpredictable and structured—like a balance between chaos and order, mirroring how natural systems operate.

Another reason this concept resonated with me is my interest in interactive media and computational design. I see creative coding as a bridge between logic and aesthetics—using algorithms not just to solve problems but to create emotionally engaging experiences. This project became a way to explore how simple rules (vector fields and movement constraints) can lead to complex, emergent behaviors.

How I Decided on the Approach

Initially, I considered different ways of visualizing movement:

  • Cellular automata (which follows a discrete rule set)
  • Particle systems with basic physics (which simulate real-world gravity and collisions)
  • Algorithmic drawing techniques (such as recursive fractals)

However, I specifically wanted smooth, flowing movement, where particles appear to drift through an unseen force field rather than follow rigid, predictable patterns. This led me to research flow fields, a technique often used in generative art to create dynamic motion based on vector fields derived from Perlin noise. The key insight was that by giving each particle a force vector at every point in space, I could create an artwork where movement itself became the visual expression.

Connecting to My Interests

This project ties into my broader interest in interactive systems and generative design. In the future, I could see expanding this into an interactive piece, where users could manipulate the flow field in real-time, changing the behavior of the particles with gestures or sound inputs. Additionally, this exploration of emergent patterns aligns with my curiosity about how simple rules can create complexity, something that applies not just to visual art but also to fields like engineering, physics, and artificial intelligence.

Ultimately, this artwork serves as both a study of motion and structure and a reflection of how natural forces shape our world in ways we don’t always perceive.

My code:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
let particles = [];
let flowField;
let cols, rows;
let scl = 20; // Scale of the grid
let zOff = 0; // Noise offset for animation
function setup() {
createCanvas(600, 600);
cols = floor(width / scl);
rows = floor(height / scl);
flowField = new Array(cols * rows);
// Create particles
for (let i = 0; i < 500; i++) {
particles.push(new Particle());
}
}
function draw() {
background(0, 10); // Faint trail effect
// Generate the flow field using Perlin noise
let yOff = 0;
for (let y = 0; y < rows; y++) {
let xOff = 0;
for (let x = 0; x < cols; x++) {
let index = x + y * cols;
let angle = noise(xOff, yOff, zOff) * TWO_PI * 4;
let v = p5.Vector.fromAngle(angle);
flowField[index] = v;
xOff += 0.1;
}
yOff += 0.1;
}
zOff += 0.01;
// Update and display particles
for (let particle of particles) {
particle.follow(flowField);
particle.update();
particle.edges();
particle.show();
}
}
// Particle class
class Particle {
constructor() {
this.pos = createVector(random(width), random(height));
this.vel = createVector(0, 0);
this.acc = createVector(0, 0);
this.maxSpeed = 2;
this.color = color(random(255), random(255), random(255), 100);
}
follow(vectors) {
let x = floor(this.pos.x / scl);
let y = floor(this.pos.y / scl);
let index = x + y * cols;
let force = vectors[index];
this.applyForce(force);
}
applyForce(force) {
this.acc.add(force);
}
update() {
this.vel.add(this.acc);
this.vel.limit(this.maxSpeed);
this.pos.add(this.vel);
this.acc.mult(0);
}
edges() {
if (this.pos.x > width) this.pos.x = 0;
if (this.pos.x < 0) this.pos.x = width;
if (this.pos.y > height) this.pos.y = 0;
if (this.pos.y < 0) this.pos.y = height;
}
show() {
stroke(this.color);
strokeWeight(2);
point(this.pos.x, this.pos.y);
}
}
let particles = []; let flowField; let cols, rows; let scl = 20; // Scale of the grid let zOff = 0; // Noise offset for animation function setup() { createCanvas(600, 600); cols = floor(width / scl); rows = floor(height / scl); flowField = new Array(cols * rows); // Create particles for (let i = 0; i < 500; i++) { particles.push(new Particle()); } } function draw() { background(0, 10); // Faint trail effect // Generate the flow field using Perlin noise let yOff = 0; for (let y = 0; y < rows; y++) { let xOff = 0; for (let x = 0; x < cols; x++) { let index = x + y * cols; let angle = noise(xOff, yOff, zOff) * TWO_PI * 4; let v = p5.Vector.fromAngle(angle); flowField[index] = v; xOff += 0.1; } yOff += 0.1; } zOff += 0.01; // Update and display particles for (let particle of particles) { particle.follow(flowField); particle.update(); particle.edges(); particle.show(); } } // Particle class class Particle { constructor() { this.pos = createVector(random(width), random(height)); this.vel = createVector(0, 0); this.acc = createVector(0, 0); this.maxSpeed = 2; this.color = color(random(255), random(255), random(255), 100); } follow(vectors) { let x = floor(this.pos.x / scl); let y = floor(this.pos.y / scl); let index = x + y * cols; let force = vectors[index]; this.applyForce(force); } applyForce(force) { this.acc.add(force); } update() { this.vel.add(this.acc); this.vel.limit(this.maxSpeed); this.pos.add(this.vel); this.acc.mult(0); } edges() { if (this.pos.x > width) this.pos.x = 0; if (this.pos.x < 0) this.pos.x = width; if (this.pos.y > height) this.pos.y = 0; if (this.pos.y < 0) this.pos.y = height; } show() { stroke(this.color); strokeWeight(2); point(this.pos.x, this.pos.y); } }
let particles = [];
let flowField;
let cols, rows;
let scl = 20; // Scale of the grid
let zOff = 0; // Noise offset for animation

function setup() {
  createCanvas(600, 600);
  cols = floor(width / scl);
  rows = floor(height / scl);
  flowField = new Array(cols * rows);

  // Create particles
  for (let i = 0; i < 500; i++) {
    particles.push(new Particle());
  }
}

function draw() {
  background(0, 10); // Faint trail effect

  // Generate the flow field using Perlin noise
  let yOff = 0;
  for (let y = 0; y < rows; y++) {
    let xOff = 0;
    for (let x = 0; x < cols; x++) {
      let index = x + y * cols;
      let angle = noise(xOff, yOff, zOff) * TWO_PI * 4;
      let v = p5.Vector.fromAngle(angle);
      flowField[index] = v;
      xOff += 0.1;
    }
    yOff += 0.1;
  }
  zOff += 0.01;

  // Update and display particles
  for (let particle of particles) {
    particle.follow(flowField);
    particle.update();
    particle.edges();
    particle.show();
  }
}

// Particle class
class Particle {
  constructor() {
    this.pos = createVector(random(width), random(height));
    this.vel = createVector(0, 0);
    this.acc = createVector(0, 0);
    this.maxSpeed = 2;
    this.color = color(random(255), random(255), random(255), 100);
  }

  follow(vectors) {
    let x = floor(this.pos.x / scl);
    let y = floor(this.pos.y / scl);
    let index = x + y * cols;
    let force = vectors[index];
    this.applyForce(force);
  }

  applyForce(force) {
    this.acc.add(force);
  }

  update() {
    this.vel.add(this.acc);
    this.vel.limit(this.maxSpeed);
    this.pos.add(this.vel);
    this.acc.mult(0);
  }

  edges() {
    if (this.pos.x > width) this.pos.x = 0;
    if (this.pos.x < 0) this.pos.x = width;
    if (this.pos.y > height) this.pos.y = 0;
    if (this.pos.y < 0) this.pos.y = height;
  }

  show() {
    stroke(this.color);
    strokeWeight(2);
    point(this.pos.x, this.pos.y);
  }
}

 

Week 2 – Reading response

How are you planning to incorporate random elements into your work?

Well, I myself have am not used to the idea of producing something random, although there is beauty in the idea of randomness it’s not as alluring as the idea of systematic approach. When it’s random, it’s likely only to be produced once like that and twice if you can beat the odds. Although Casey, with in the video showed how these random elements can prove to not only provide an unique appearance, he also presents its limitations on how once done can’t be done agin, the idea of having to repeat a process to hopefully create something similar can be infuriating, but also fascinating as the random elements could go beyond expectation or never reach the expectations sent. I intend to incorporate this kind of thinking into future works, I might not be happy in the beginning due to my rigid thinking but over time I could change and will change in how to balance the randomness that can only produced once and the control of the scope in which it happens.

Where do you feel is the optimum balance between total randomness and complete control?

I think that when theres a start of a systematic approach too randomness. All randomness has to be born with in a confined space, whether by the laws of science that govern the way things move or how systems have an algorithm that will produce a random sequence based off the a equation thats been written before hand. You can’t escape, it but you can confine it with in a space. Like a science experiment you might have the tools, and test it out and then an outcome that works. You have your controlled variables and uncontrolled variables. Sometimes we are the uncontrolled variables but our way of thinking is the controlled variable. The way we move and do things, the laws of the universe these are controlled, set in stone in that moment, but once we release it without being able to calculate or speculate the outcome it then becomes random.

Week 2 Assignment

Introduction:

While looking through the same art, I thought that its actually far from what I could create at least at this point. I wanted it to simple yet something that reminds me of home. I ended up then thinking about what makes home, home? Was it the people that I missed? Was it the seas and mountains? Was it the seasons? Well, it was a bit of everything, the way the mountain changed its vegetation across the seasons, the way I’d travel to those mountains with those very precious people. The way the seasons or the feel of the seasons could not be replicated here with in the UAE.

Concept:

My concept for the post had to do with mountains and seasons. The reflection on something not season as clearly or with in the country as abundantly as my own country. I didn’t really use any references from the document but it’s essence rather gave me something I’d rather implement.

 

Production and Completion:

Although I had to go through many trials, like getting the color scheme, the clouds, the sun and moon, the mountains, as well as the time switch to show the seasons. I wasn’t satisfied, because I had a dream of how it should have made me felt. I hadn’t gotten close but I had gotten somewhere, starting with what can represent the seasons? the time? The way things change. If you look at the code you will notice that the frame rate changes at about every 3 seconds like the months. The sun changes into the after 1.6 seconds and the moon to the sun at 0.8 seconds, match 16 hours of sun and 8 hours of moon.

 

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
let sunAngle = 0;
let showMoon = false;
let seasonProgress = 0;
let currentSeason = "spring";
let mountainProgress = 0;
let seasons = ["spring", "summer", "autumn", "winter"];
let brushColors;
let icons = [];
let clouds = [];
function setup() {
createCanvas(800, 600);
brushColors = [
color(139, 69, 19), // Brown (Autumn)
color(34, 139, 34), // Green (Spring)
color(255, 165, 0), // Yellow-orange (Summer)
color(173, 216, 230) // **Distinctive icy blue for Winter**
];
frameRate(30);
generateIcons();
generateClouds();
}
function draw() {
if (currentSeason === "spring") {
background(144, 238, 144);
} else if (currentSeason === "summer") {
background(255, 223, 186);
} else if (currentSeason === "autumn") {
background(255, 165, 0);
} else if (currentSeason === "winter") {
background(100, 150, 200); // **Deeper Frost Blue for Winter**
}
drawMountain();
drawClouds();
if (!showMoon) {
drawSun();
} else {
drawMoon();
}
displayIcons();
updateIcons();
updateClouds();
if (frameCount % 90 === 0) {
changeSeason();
generateIcons();
}
}
function drawMountain() {
let layerHeight = 50;
let numLayers = 6;
for (let i = 0; i < numLayers; i++) {
let layerColor = getMountainColor(i);
stroke(layerColor);
strokeWeight(15 + (sin(mountainProgress + i * 0.5) * 5));
noFill();
beginShape();
for (let x = 0; x < width; x++) {
let y = sin(x * 0.02 + mountainProgress + i * 0.5) * (100 + i * layerHeight) + 400;
vertex(x, y);
}
endShape();
}
mountainProgress += 0.02;
}
function getMountainColor(layerIndex) {
let seasonIndex = seasons.indexOf(currentSeason);
if (currentSeason === "spring") {
return lerpColor(brushColors[1], color(255, 255, 255), layerIndex * 0.1);
} else if (currentSeason === "summer") {
return lerpColor(brushColors[2], color(255, 255, 255), layerIndex * 0.1);
} else if (currentSeason === "autumn") {
return lerpColor(brushColors[0], color(255, 255, 255), layerIndex * 0.1);
} else if (currentSeason === "winter") {
return lerpColor(brushColors[3], color(255, 255, 255), layerIndex * 0.2); // **Stronger white contrast for winter**
}
}
function drawSun() {
fill(255, 204, 0, 150);
noStroke();
let size = 100 + sin(sunAngle) * 30;
ellipse(650, 100, size, size);
sunAngle += 0.02;
if (sunAngle > TWO_PI) {
sunAngle = 0;
showMoon = true;
}
}
function drawMoon() {
let moonSize = map(sin(frameCount * 0.1), -1, 1, 50, 100);
fill(255, 255, 255, 150);
noStroke();
ellipse(650, 100, moonSize, moonSize);
if (frameCount % 24 === 0) {
showMoon = false;
}
}
function changeSeason() {
let nextSeasonIndex = (seasons.indexOf(currentSeason) + 1) % seasons.length;
currentSeason = seasons[nextSeasonIndex];
generateClouds();
}
function generateIcons() {
icons = [];
let iconSymbol = "";
if (currentSeason === "spring") {
iconSymbol = "🌸";
} else if (currentSeason === "summer") {
iconSymbol = "☀️";
} else if (currentSeason === "autumn") {
iconSymbol = "🍂";
} else if (currentSeason === "winter") {
iconSymbol = "❄️";
}
for (let i = 0; i < 5; i++) {
icons.push({
x: random(width),
y: random(100, 300),
speed: random(0.2, 0.5),
symbol: iconSymbol
});
}
}
function updateIcons() {
for (let icon of icons) {
icon.y += icon.speed;
if (icon.y > height) {
icon.y = random(100, 300);
}
}
}
function displayIcons() {
textSize(32);
textAlign(CENTER, CENTER);
for (let icon of icons) {
text(icon.symbol, icon.x, icon.y);
}
}
function generateClouds() {
clouds = [];
for (let i = 0; i < 6; i++) {
clouds.push({
x: random(width),
y: random(50, 200),
size: random(60, 100),
opacity: random(100, 200),
speed: random(0.5, 1.5)
});
}
}
function drawClouds() {
for (let cloud of clouds) {
fill(255, 255, 255, cloud.opacity);
noStroke();
ellipse(cloud.x, cloud.y, cloud.size, cloud.size * 0.6);
ellipse(cloud.x + 30, cloud.y, cloud.size * 0.8, cloud.size * 0.5);
ellipse(cloud.x - 30, cloud.y, cloud.size * 0.9, cloud.size * 0.6);
}
}
function updateClouds() {
for (let cloud of clouds) {
cloud.x += cloud.speed;
cloud.size += sin(frameCount * 0.01) * 0.5;
cloud.opacity = map(sin(frameCount * 0.01), -1, 1, 100, 200);
if (cloud.x > width + 50) {
cloud.x = -50;
cloud.y = random(50, 200);
}
}
}
let sunAngle = 0; let showMoon = false; let seasonProgress = 0; let currentSeason = "spring"; let mountainProgress = 0; let seasons = ["spring", "summer", "autumn", "winter"]; let brushColors; let icons = []; let clouds = []; function setup() { createCanvas(800, 600); brushColors = [ color(139, 69, 19), // Brown (Autumn) color(34, 139, 34), // Green (Spring) color(255, 165, 0), // Yellow-orange (Summer) color(173, 216, 230) // **Distinctive icy blue for Winter** ]; frameRate(30); generateIcons(); generateClouds(); } function draw() { if (currentSeason === "spring") { background(144, 238, 144); } else if (currentSeason === "summer") { background(255, 223, 186); } else if (currentSeason === "autumn") { background(255, 165, 0); } else if (currentSeason === "winter") { background(100, 150, 200); // **Deeper Frost Blue for Winter** } drawMountain(); drawClouds(); if (!showMoon) { drawSun(); } else { drawMoon(); } displayIcons(); updateIcons(); updateClouds(); if (frameCount % 90 === 0) { changeSeason(); generateIcons(); } } function drawMountain() { let layerHeight = 50; let numLayers = 6; for (let i = 0; i < numLayers; i++) { let layerColor = getMountainColor(i); stroke(layerColor); strokeWeight(15 + (sin(mountainProgress + i * 0.5) * 5)); noFill(); beginShape(); for (let x = 0; x < width; x++) { let y = sin(x * 0.02 + mountainProgress + i * 0.5) * (100 + i * layerHeight) + 400; vertex(x, y); } endShape(); } mountainProgress += 0.02; } function getMountainColor(layerIndex) { let seasonIndex = seasons.indexOf(currentSeason); if (currentSeason === "spring") { return lerpColor(brushColors[1], color(255, 255, 255), layerIndex * 0.1); } else if (currentSeason === "summer") { return lerpColor(brushColors[2], color(255, 255, 255), layerIndex * 0.1); } else if (currentSeason === "autumn") { return lerpColor(brushColors[0], color(255, 255, 255), layerIndex * 0.1); } else if (currentSeason === "winter") { return lerpColor(brushColors[3], color(255, 255, 255), layerIndex * 0.2); // **Stronger white contrast for winter** } } function drawSun() { fill(255, 204, 0, 150); noStroke(); let size = 100 + sin(sunAngle) * 30; ellipse(650, 100, size, size); sunAngle += 0.02; if (sunAngle > TWO_PI) { sunAngle = 0; showMoon = true; } } function drawMoon() { let moonSize = map(sin(frameCount * 0.1), -1, 1, 50, 100); fill(255, 255, 255, 150); noStroke(); ellipse(650, 100, moonSize, moonSize); if (frameCount % 24 === 0) { showMoon = false; } } function changeSeason() { let nextSeasonIndex = (seasons.indexOf(currentSeason) + 1) % seasons.length; currentSeason = seasons[nextSeasonIndex]; generateClouds(); } function generateIcons() { icons = []; let iconSymbol = ""; if (currentSeason === "spring") { iconSymbol = "🌸"; } else if (currentSeason === "summer") { iconSymbol = "☀️"; } else if (currentSeason === "autumn") { iconSymbol = "🍂"; } else if (currentSeason === "winter") { iconSymbol = "❄️"; } for (let i = 0; i < 5; i++) { icons.push({ x: random(width), y: random(100, 300), speed: random(0.2, 0.5), symbol: iconSymbol }); } } function updateIcons() { for (let icon of icons) { icon.y += icon.speed; if (icon.y > height) { icon.y = random(100, 300); } } } function displayIcons() { textSize(32); textAlign(CENTER, CENTER); for (let icon of icons) { text(icon.symbol, icon.x, icon.y); } } function generateClouds() { clouds = []; for (let i = 0; i < 6; i++) { clouds.push({ x: random(width), y: random(50, 200), size: random(60, 100), opacity: random(100, 200), speed: random(0.5, 1.5) }); } } function drawClouds() { for (let cloud of clouds) { fill(255, 255, 255, cloud.opacity); noStroke(); ellipse(cloud.x, cloud.y, cloud.size, cloud.size * 0.6); ellipse(cloud.x + 30, cloud.y, cloud.size * 0.8, cloud.size * 0.5); ellipse(cloud.x - 30, cloud.y, cloud.size * 0.9, cloud.size * 0.6); } } function updateClouds() { for (let cloud of clouds) { cloud.x += cloud.speed; cloud.size += sin(frameCount * 0.01) * 0.5; cloud.opacity = map(sin(frameCount * 0.01), -1, 1, 100, 200); if (cloud.x > width + 50) { cloud.x = -50; cloud.y = random(50, 200); } } }
let sunAngle = 0;
let showMoon = false;
let seasonProgress = 0;
let currentSeason = "spring";
let mountainProgress = 0;
let seasons = ["spring", "summer", "autumn", "winter"];
let brushColors;
let icons = [];
let clouds = [];

function setup() {
    createCanvas(800, 600);
    brushColors = [
        color(139, 69, 19),  // Brown (Autumn)
        color(34, 139, 34),  // Green (Spring)
        color(255, 165, 0),  // Yellow-orange (Summer)
        color(173, 216, 230) // **Distinctive icy blue for Winter**
    ];
    frameRate(30);
    generateIcons();
    generateClouds();
}

function draw() {
    if (currentSeason === "spring") {
        background(144, 238, 144);
    } else if (currentSeason === "summer") {
        background(255, 223, 186);
    } else if (currentSeason === "autumn") {
        background(255, 165, 0);
    } else if (currentSeason === "winter") {
        background(100, 150, 200); // **Deeper Frost Blue for Winter**
    }

    drawMountain();
    drawClouds();
    if (!showMoon) {
        drawSun();
    } else {
        drawMoon();
    }
    displayIcons();
    updateIcons();
    updateClouds();

    if (frameCount % 90 === 0) {
        changeSeason();
        generateIcons();
    }
}

function drawMountain() {
    let layerHeight = 50;
    let numLayers = 6;
    for (let i = 0; i < numLayers; i++) {
        let layerColor = getMountainColor(i);
        stroke(layerColor);
        strokeWeight(15 + (sin(mountainProgress + i * 0.5) * 5));
        noFill();
        beginShape();
        for (let x = 0; x < width; x++) {
            let y = sin(x * 0.02 + mountainProgress + i * 0.5) * (100 + i * layerHeight) + 400;
            vertex(x, y);
        }
        endShape();
    }
    mountainProgress += 0.02;
}

function getMountainColor(layerIndex) {
    let seasonIndex = seasons.indexOf(currentSeason);
    if (currentSeason === "spring") {
        return lerpColor(brushColors[1], color(255, 255, 255), layerIndex * 0.1);
    } else if (currentSeason === "summer") {
        return lerpColor(brushColors[2], color(255, 255, 255), layerIndex * 0.1);
    } else if (currentSeason === "autumn") {
        return lerpColor(brushColors[0], color(255, 255, 255), layerIndex * 0.1);
    } else if (currentSeason === "winter") {
        return lerpColor(brushColors[3], color(255, 255, 255), layerIndex * 0.2); // **Stronger white contrast for winter**
    }
}

function drawSun() {
    fill(255, 204, 0, 150);
    noStroke();
    let size = 100 + sin(sunAngle) * 30;
    ellipse(650, 100, size, size);
    sunAngle += 0.02;

    if (sunAngle > TWO_PI) {
        sunAngle = 0;
        showMoon = true;
    }
}

function drawMoon() {
    let moonSize = map(sin(frameCount * 0.1), -1, 1, 50, 100);
    fill(255, 255, 255, 150);
    noStroke();
    ellipse(650, 100, moonSize, moonSize);
    if (frameCount % 24 === 0) {
        showMoon = false;
    }
}

function changeSeason() {
    let nextSeasonIndex = (seasons.indexOf(currentSeason) + 1) % seasons.length;
    currentSeason = seasons[nextSeasonIndex];
    generateClouds();
}

function generateIcons() {
    icons = [];
    let iconSymbol = "";
    if (currentSeason === "spring") {
        iconSymbol = "🌸";
    } else if (currentSeason === "summer") {
        iconSymbol = "☀️";
    } else if (currentSeason === "autumn") {
        iconSymbol = "🍂";
    } else if (currentSeason === "winter") {
        iconSymbol = "❄️";
    }
    for (let i = 0; i < 5; i++) {
        icons.push({
            x: random(width),
            y: random(100, 300),
            speed: random(0.2, 0.5),
            symbol: iconSymbol
        });
    }
}

function updateIcons() {
    for (let icon of icons) {
        icon.y += icon.speed;
        if (icon.y > height) {
            icon.y = random(100, 300);
        }
    }
}

function displayIcons() {
    textSize(32);
    textAlign(CENTER, CENTER);
    for (let icon of icons) {
        text(icon.symbol, icon.x, icon.y);
    }
}

function generateClouds() {
    clouds = [];
    for (let i = 0; i < 6; i++) {
        clouds.push({
            x: random(width),
            y: random(50, 200),
            size: random(60, 100),
            opacity: random(100, 200),
            speed: random(0.5, 1.5)
        });
    }
}

function drawClouds() {
    for (let cloud of clouds) {
        fill(255, 255, 255, cloud.opacity);
        noStroke();
        ellipse(cloud.x, cloud.y, cloud.size, cloud.size * 0.6);
        ellipse(cloud.x + 30, cloud.y, cloud.size * 0.8, cloud.size * 0.5);
        ellipse(cloud.x - 30, cloud.y, cloud.size * 0.9, cloud.size * 0.6);
    }
}

function updateClouds() {
    for (let cloud of clouds) {
        cloud.x += cloud.speed;
        cloud.size += sin(frameCount * 0.01) * 0.5;
        cloud.opacity = map(sin(frameCount * 0.01), -1, 1, 100, 200);
        if (cloud.x > width + 50) {
            cloud.x = -50;
            cloud.y = random(50, 200);
        }
    }
}

Sketch:

Improvements:

I think I can improve a lot with more dynamic visuals and smoother transitions. I will try to include more user interaction that can allow them to do more than just have a ‘watch’ experience. They should be able to have ‘active’ experience as a participant. I could include sounds that mimic the ambiance achieved in  places in South African to make it further immersive.

 

Week 1 Self-Portrait

Motivation

Initially tried to build a simple person sketch with just a head and body, but I knew I could take it a bit further. So then I added hair and tried to change the body to look less like a stick man figure. I attempted to mimic a picture I had taken of myself.

This was what I had in mind and attempted to make within the program.

This had become the first draft after The Stickman, I can say my drawing abilities by hand are much better. But it was a learning curb.

AREAS OF IMPROVEMENT AND REFLECTION

What I noticed is I lacked the Imagination and the toolset in order to code an image. Although building a website is possible doing art was something I had never thought of as possible. I need to better understand the coding language I am working with and I need to find ways to improve how to use the available attributes and find how to use them in a more creative way.

The code snippet that I had used is simple. There I tried to keep it not complicated and kept to what I learnt originally. To make it more complicated with more attributes and something more intricate I need to study the coding language more.  I enjoy commenting the code so I don’t forget what goes where, and to not be confused.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
function setup() {
createCanvas(400, 600);
background(240); // Neutral background
// Head
fill(176, 108, 73); // Brown skin tone
ellipse(200, 150, 80, 100); // Head shape
// Hat
fill(0); // Black hat
arc(200, 120, 90, 50, PI, TWO_PI); // Hat brim
rect(160, 90, 80, 40); // Hat body
// Glasses
fill(255); // Glasses lenses
stroke(0); // Glasses frame
strokeWeight(3);
ellipse(180, 150, 30, 20); // Left lens
ellipse(220, 150, 30, 20); // Right lens
line(195, 150, 205, 150); // Bridge of glasses
// Eyes
noStroke();
fill(0); // Black pupils
ellipse(180, 150, 8, 8); // Left eye
ellipse(220, 150, 8, 8); // Right eye
// Nose
fill(0); // Same as head
triangle(195, 165, 205, 165, 200, 175); // Simple triangular nose
// Mouth
fill(0); // Neutral expression
arc(200, 185, 40, 10, 0, PI); // Simple curved mouth
// Body
fill(245, 235, 200); // Beige shirt
rect(150, 220, 100, 120, 10); // Torso
stroke(220, 215, 180);
strokeWeight(2);
for (let y = 220; y < 340; y += 10) {
line(150, y, 250, y); // Subtle shirt stripes
}
// Cross Necklace
fill(160, 160, 160); // Silver color
line(200, 220, 200, 240); // Chain
rect(195, 240, 10, 20); // Cross
// Arms
noStroke();
fill(176, 108, 73); // Skin tone
rect(120, 240, 30, 80, 10); // Left arm
rect(250, 240, 30, 80, 10); // Right arm\
// Backpack Straps
fill(0); // Black straps
rect(140, 220, 20, 60); // Left strap
rect(240, 220, 20, 60); // Right strap
// Pants
fill(0); // Black pants
rect(150, 340, 40, 120); // Left leg
rect(210, 340, 40, 120); // Right leg
// Shoes
fill(50); // Dark gray shoes
rect(140, 460, 60, 20, 5); // Left shoe
rect(200, 460, 60, 20, 5); // Right shoe
}
function setup() { createCanvas(400, 600); background(240); // Neutral background // Head fill(176, 108, 73); // Brown skin tone ellipse(200, 150, 80, 100); // Head shape // Hat fill(0); // Black hat arc(200, 120, 90, 50, PI, TWO_PI); // Hat brim rect(160, 90, 80, 40); // Hat body // Glasses fill(255); // Glasses lenses stroke(0); // Glasses frame strokeWeight(3); ellipse(180, 150, 30, 20); // Left lens ellipse(220, 150, 30, 20); // Right lens line(195, 150, 205, 150); // Bridge of glasses // Eyes noStroke(); fill(0); // Black pupils ellipse(180, 150, 8, 8); // Left eye ellipse(220, 150, 8, 8); // Right eye // Nose fill(0); // Same as head triangle(195, 165, 205, 165, 200, 175); // Simple triangular nose // Mouth fill(0); // Neutral expression arc(200, 185, 40, 10, 0, PI); // Simple curved mouth // Body fill(245, 235, 200); // Beige shirt rect(150, 220, 100, 120, 10); // Torso stroke(220, 215, 180); strokeWeight(2); for (let y = 220; y < 340; y += 10) { line(150, y, 250, y); // Subtle shirt stripes } // Cross Necklace fill(160, 160, 160); // Silver color line(200, 220, 200, 240); // Chain rect(195, 240, 10, 20); // Cross // Arms noStroke(); fill(176, 108, 73); // Skin tone rect(120, 240, 30, 80, 10); // Left arm rect(250, 240, 30, 80, 10); // Right arm\ // Backpack Straps fill(0); // Black straps rect(140, 220, 20, 60); // Left strap rect(240, 220, 20, 60); // Right strap // Pants fill(0); // Black pants rect(150, 340, 40, 120); // Left leg rect(210, 340, 40, 120); // Right leg // Shoes fill(50); // Dark gray shoes rect(140, 460, 60, 20, 5); // Left shoe rect(200, 460, 60, 20, 5); // Right shoe }
function setup() {
  createCanvas(400, 600);
  background(240); // Neutral background

  // Head
  fill(176, 108, 73); // Brown skin tone
  ellipse(200, 150, 80, 100); // Head shape

  // Hat
  fill(0); // Black hat
  arc(200, 120, 90, 50, PI, TWO_PI); // Hat brim
  rect(160, 90, 80, 40); // Hat body

  // Glasses
  fill(255); // Glasses lenses
  stroke(0); // Glasses frame
  strokeWeight(3);
  ellipse(180, 150, 30, 20); // Left lens
  ellipse(220, 150, 30, 20); // Right lens
  line(195, 150, 205, 150); // Bridge of glasses

  // Eyes
  noStroke();
  fill(0); // Black pupils
  ellipse(180, 150, 8, 8); // Left eye
  ellipse(220, 150, 8, 8); // Right eye

  // Nose
  fill(0); // Same as head
  triangle(195, 165, 205, 165, 200, 175); // Simple triangular nose

  // Mouth
  fill(0); // Neutral expression
  arc(200, 185, 40, 10, 0, PI); // Simple curved mouth

  // Body
  fill(245, 235, 200); // Beige shirt
  rect(150, 220, 100, 120, 10); // Torso
  stroke(220, 215, 180);
  strokeWeight(2);
  for (let y = 220; y < 340; y += 10) {
    line(150, y, 250, y); // Subtle shirt stripes
  }

  // Cross Necklace
  fill(160, 160, 160); // Silver color
  line(200, 220, 200, 240); // Chain
  rect(195, 240, 10, 20); // Cross

  // Arms
  noStroke();
  fill(176, 108, 73); // Skin tone
  rect(120, 240, 30, 80, 10); // Left arm
  rect(250, 240, 30, 80, 10); // Right arm\

  // Backpack Straps
  fill(0); // Black straps
  rect(140, 220, 20, 60); // Left strap
  rect(240, 220, 20, 60); // Right strap

  // Pants
  fill(0); // Black pants
  rect(150, 340, 40, 120); // Left leg
  rect(210, 340, 40, 120); // Right leg

  // Shoes
  fill(50); // Dark gray shoes
  rect(140, 460, 60, 20, 5); // Left shoe
  rect(200, 460, 60, 20, 5); // Right shoe


}

FINAL PRODUCT

Although there’s much room for improvement. I think as a first try it was good.

 

<iframe src=”https://editor.p5js.org/dzf3361/full/HyuTFS6Fv” width = 420 height=644 allow=“camera;microphone”> </iframe>