day dreaming fish 3

This interactive sketch creates a dreamy underwater world where a charming fish cycles through its hopes and worries in floating thought bubbles. Players can chat with the fish, feed it by clicking, and watch it move gracefully through a bowl while small bubbles float upward—but neglect the fish too long and it falls asleep with an ominous twist.

🧪 Try This!

Experiment with the code by making these changes:

  1. Speed up the fish's swimming — Increase the sine wave amplitude to make the fish wander wider, or increase the fish's movement speed by changing moveSpeed.
  2. Make food fall faster — Increase gravity to pull food down more aggressively, simulating a hungrier fish world.
  3. Change the fish color — Modify the RGB values passed to drawFish() to create a different-colored main fish.
  4. Make bubbles spawn more often — Increase the bubble spawn chance from 0.02 (2%) to a higher value like 0.1 (10%).
  5. Add a new dream — Add a sixth dream to the dreams array—the fish will cycle through it along with the others.
  6. Extend feeding time — Make the fed duration longer by multiplying the base duration, so the fish stays awake longer after feeding.
Prefer the full editor? Open it there →

📖 About This Sketch

This sketch animates an expressive fish inside a bowl that dreams, responds to chat messages, and hungers for food. The fish cycles through a list of dreams displayed in thought bubbles while swimming in smooth sine-wave patterns, and you can feed it by clicking to reset its dream cycle. The sketch combines animation with state management using variables like isFishSleeping, isFed, and currentFishThought to create a character that reacts to player input in meaningful ways.

The code is organized around a main draw loop that handles the fish's movement, thought bubble display, food particle physics, and interactive state changes. By studying it, you'll learn how to use lerp() for smooth motion, manage multiple interactive states, handle HTML input elements from within p5.js, and use frameCount to drive timed animations. The sketch also demonstrates text timing, collision detection with food, and the use of Sets to track which dreams have been displayed.

⚙️ How It Works

  1. When the sketch loads, setup() creates a full-window canvas, initializes the fish at the center of the bowl, and creates HTML input elements (input field, Talk button, and Visit Website button) that appear at the bottom of the canvas.
  2. The draw loop renders a light blue background and draws the bowl outline, then moves the main fish toward a target position—either following food particles or swimming in a gentle sine-wave pattern.
  3. On every frame, the fish cycles through dreams from an array, displaying each dream in a thought bubble for 180 frames (3 seconds) before moving to the next. The thought bubble system also supports custom responses when you type a message.
  4. When you click the canvas (away from input fields), the fish is 'fed,' food particles spawn and fall into the bowl with gravity, and the dream cycle resets. After each dream is shown once, the fish falls asleep with X eyes if it hasn't been fed.
  5. The sketch tracks feeding duration using frameCount, so the fish stays hungry and wakes up after one complete dream cycle passes without food. Bubbles float upward while the fish is awake, creating visual interest above the fish's head.

🎓 Concepts You'll Learn

State management with boolean flagsTiming and frameCount-based animationSmooth motion with lerp()Thought bubbles and text displayParticle physics with gravityHTML input integration with p5.jsCollision detection

📝 Code Breakdown

setup()

setup() runs once when the sketch first loads. It's the perfect place to initialize the canvas, set starting variable values, and configure interactive elements like buttons and input fields.

🔬 These lines style the input field. What happens if you change 'width' from '200px' to '400px'? Try it and watch the input field grow wider.

  inputField.style('width', '200px');
  inputField.style('padding', '5px');
  inputField.style('border', '1px solid #ccc');
  inputField.style('border-radius', '5px');
  inputField.style('font-size', '16px');
function setup(){
  createCanvas(windowWidth,windowHeight);
  textAlign(CENTER,CENTER);
  textSize(20);

  displayedDreamsIndices = new Set(); // Initialize the Set
  currentFishX = width / 2; // Initialize main fish's starting x-position
  fishY = height/2+60 + 10; // Initialize main fishY to its default position

  // Create input field and button
  inputField = createInput();
  inputField.style('width', '200px');
  inputField.style('padding', '5px');
  inputField.style('border', '1px solid #ccc');
  inputField.style('border-radius', '5px');
  inputField.style('font-size', '16px');
  inputField.style('font-family', 'Arial, sans-serif');
  inputField.attribute('placeholder', 'What do you want to say?');
  inputField.hide(); // Start hidden

  submitButton = createButton('Talk');
  submitButton.style('background-color', '#4CAF50');
  submitButton.style('color', 'white');
  submitButton.style('padding', '6px 12px');
  submitButton.style('border', 'none');
  submitButton.style('border-radius', '5px');
  submitButton.style('cursor', 'pointer');
  submitButton.style('font-size', '16px');
  submitButton.style('font-family', 'Arial, sans-serif');
  submitButton.hide(); // Start hidden

  // NEW: Create the "Visit Website" button
  visitWebsiteButton = createButton('Visit Corbun\'s Weird Webs');
  visitWebsiteButton.style('background-color', '#008CBA'); // Blue color
  visitWebsiteButton.style('color', 'white');
  visitWebsiteButton.style('padding', '8px 16px');
  visitWebsiteButton.style('border', 'none');
  visitWebsiteButton.style('border-radius', '5px');
  visitWebsiteButton.style('cursor', 'pointer');
  visitWebsiteButton.style('font-size', '16px');
  visitWebsiteButton.style('font-family', 'Arial, sans-serif');

  // Position elements initially (will be updated in draw or windowResized)
  positionInputElements();

  // Event listener for button click
  submitButton.mousePressed(handleTalk);

  // NEW: Event listener for "Visit Website" button click
  visitWebsiteButton.mousePressed(handleVisitWebsite);

  // Event listener for Enter key in input field
  inputField.elt.addEventListener('keydown', function(event) {
    if (event.key === 'Enter') {
      handleTalk();
      event.preventDefault(); // Prevent default form submission behavior
    }
  });
}
Line-by-line explanation (10 lines)

🔧 Subcomponents:

initialization Canvas and text properties createCanvas(windowWidth,windowHeight);

Creates a canvas that fills the entire window and sets default text alignment and size

initialization Fish starting position currentFishX = width / 2; // Initialize main fish's starting x-position fishY = height/2+60 + 10; // Initialize main fishY to its default position

Places the fish in the center horizontally and slightly below the bowl's vertical center

initialization Input field styling inputField = createInput(); inputField.style('width', '200px'); inputField.style('padding', '5px'); inputField.style('border', '1px solid #ccc'); inputField.style('border-radius', '5px'); inputField.style('font-size', '16px'); inputField.style('font-family', 'Arial, sans-serif'); inputField.attribute('placeholder', 'What do you want to say?'); inputField.hide();

Creates a styled input field with placeholder text and hides it until the fish is awake

initialization Button creation and styling submitButton = createButton('Talk'); submitButton.style('background-color', '#4CAF50'); submitButton.style('color', 'white'); submitButton.style('padding', '6px 12px'); submitButton.style('border', 'none'); submitButton.style('border-radius', '5px'); submitButton.style('cursor', 'pointer'); submitButton.style('font-size', '16px'); submitButton.style('font-family', 'Arial, sans-serif'); submitButton.hide();

Creates a green 'Talk' button with styling and hides it initially

initialization Event listeners for interaction submitButton.mousePressed(handleTalk); visitWebsiteButton.mousePressed(handleVisitWebsite); inputField.elt.addEventListener('keydown', function(event) { if (event.key === 'Enter') { handleTalk(); event.preventDefault(); } });

Connects buttons and keyboard input to their respective handler functions

createCanvas(windowWidth,windowHeight);
Creates a canvas that fills the entire browser window—essential for making the sketch responsive
textAlign(CENTER,CENTER);
Centers all text both horizontally and vertically, making thought bubble text appear in the middle of the bubble
displayedDreamsIndices = new Set();
A Set is like an array but stores only unique values—used to track which dreams have been shown, so we know when all dreams have been displayed once
currentFishX = width / 2;
Positions the fish horizontally at the center of the canvas by calculating half the width
fishY = height/2+60 + 10;
Positions the fish vertically below the bowl's center (height/2+60 is the bowl center, +10 is the fish's offset inside)
inputField.attribute('placeholder', 'What do you want to say?');
Sets the gray hint text that appears inside the input field before the user types
inputField.hide();
Hides the input field initially—it will only appear when the fish wakes up and is ready to chat
positionInputElements();
Calls a custom function to calculate and set the exact screen positions of the input, button, and website link
submitButton.mousePressed(handleTalk);
Connects the Talk button to the handleTalk function—when clicked, handleTalk() runs
inputField.elt.addEventListener('keydown', function(event) {
Listens for keyboard key presses in the input field—used to detect the Enter key for submitting without clicking the button

draw()

The draw() function is the beating heart of the sketch—it runs 60 times per second, updating positions, checking states, and rendering everything. Notice how it uses conditionals (if statements) to handle different modes: awake, sleeping, and shooting. The fish's movement uses lerp(), a powerful technique for smooth animation. Food particles demonstrate gravity by adding velocity each frame—a foundational physics concept you'll use in many sketches.

🔬 This snippet controls bubble spawning. What happens if you change the 0.02 to 0.1? Try it—the fish should bubble way more frantically. What about 0.001 for barely any bubbles?

  if (!isFishSleeping && currentFishThought === "" && random(1)<0.02) {
    bubbles.push({x:currentFishX+35,y:fishY-5,r:6+random(4),v:1+random(1)});
  }
function draw(){
  // NEW: If the fish is shooting, handle that and stop drawing anything else
  if (isShootingPlayer) {
    handleShooting();
    // Hide input elements when shooting
    inputField.hide();
    submitButton.hide();
    visitWebsiteButton.hide(); // NEW: Hide visit button too
    return;
  }

  background(180,220,255);

  let bowlR=200, bx=width/2, by=height/2+60;
  noFill();
  stroke(255);
  strokeWeight(4);
  circle(bx,by,bowlR);

  // --- Main Fish Logic ---

  // Determine main fish's target X and Y
  // Only calculate if the main fish is awake and not actively displaying a custom thought
  if (!isFishSleeping && currentFishThought === "") {
    let targetFishX = bx + 40*sin(frameCount*0.01); // Default sine wave movement
    let targetFishY = by + 10; // Default y-position

    // If there's food, prioritize moving towards the food
    if (foodParticles.length > 0) {
      let closestFood = foodParticles[0]; // For simplicity, just target the first food particle
      targetFishX = closestFood.x;
      targetFishY = closestFood.y - 5; // Aim slightly above the food
    }

    // Move main fish towards target X and Y using lerp for smooth motion
    // @docs https://p5js.org/reference/p5/lerp/
    let moveSpeed = 0.05; // How quickly the main fish moves towards its target
    currentFishX = lerp(currentFishX, currentFishX, moveSpeed);
    fishY = lerp(fishY, targetFishY, moveSpeed);
  }
  // If sleeping or displaying a thought, currentFishX and fishY are static.

  // Original main fish drawing
  drawFish(currentFishX, fishY, 1, color(255,140,0), 1, isFishSleeping);

  // Generate bubbles only if main fish is not sleeping AND not displaying a custom thought
  if (!isFishSleeping && currentFishThought === "" && random(1)<0.02) {
    bubbles.push({x:currentFishX+35,y:fishY-5,r:6+random(4),v:1+random(1)});
  }

  // Draw thought bubble and text ONLY if the main fish is not sleeping
  if (!isFishSleeping) {
    let tx=bx, ty=by-160;
    noStroke();
    fill(255,255,255,230);
    ellipse(tx,ty,160,90); // Main thought bubble
    ellipse(tx-40,ty+5,60,50); // Small bubble 1
    ellipse(tx+40,ty+10,60,50); // Small bubble 2

    // Draw small bubbles near main fish's head
    ellipse(currentFishX+20,fishY-40,18,14);
    ellipse(currentFishX+10,fishY-20,12,9);

    fill(50);

    // NEW: Display currentFishThought if active, otherwise original dream logic
    if (currentFishThought !== "" && frameCount - thoughtDisplayStartTime < thoughtDisplayDuration) {
      text(currentFishThought, tx, ty - 3);
    } else {
      currentFishThought = ""; // Clear thought if duration passed
      thoughtDisplayStartTime = -Infinity; // Reset timer
      let currentDreamIndex = floor(frameCount/180)%dreams.length;
      displayedDreamsIndices.add(currentDreamIndex);
      let dream=dreams[currentDreamIndex];
      text(dream,tx,ty-3);
    }
  }


  // Draw and update bubbles (if still active)
  stroke(200);
  noFill();
  for(let i=bubbles.length-1;i>=0;i--){
    let b=bubbles[i];
    b.y-=b.v;
    circle(b.x,b.y,b.r*2); // @docs https://p5js.org/reference/p5/circle/
    if(b.y<by-bowlR/2-40)bubbles.splice(i,1);
  }

  // NEW: Check if all dreams have been displayed and main fish is not yet sleeping, not fed.
  if (displayedDreamsIndices.size === dreams.length && !isFishSleeping && !isFed) {
    isFishSleeping = true; // Fish falls asleep
    bubbles = []; // Bubbles disappear
    inputField.hide(); // Hide input when sleeping
    submitButton.hide();
    visitWebsiteButton.hide(); // NEW: Hide visit button when sleeping

    // --- REMOVED: 1 in 10 chance to pull out a gun ---
    // if (random(1) < (1/10)) { // 1 in 10 chance
    //   isShootingPlayer = true;
    //   // Initialize bullet to shoot towards current mouse position
    //   bullet = { x: currentFishX, y: fishY, targetX: mouseX, targetY: mouseY, speed: 15 };
    // }
  } else if (!isFishSleeping && !isShootingPlayer) { // Only show input elements if awake and not shooting
    inputField.show();
    submitButton.show();
    visitWebsiteButton.show(); // NEW: Show visit button when awake
  }

  // Check if the main fish is still fed
  if (isFed && frameCount - fedTime > feedDuration) {
    isFed = false;
  }

  // Draw and update food particles
  fill(139, 69, 19); // Brown color for food
  noStroke();
  for(let i=foodParticles.length-1; i>=0; i--) {
    let fp = foodParticles[i];
    fp.v += 0.2; // Gravity
    fp.y += fp.v;

    // Remove if below the bowl or "eaten" by the main fish
    if (fp.y > fishY + 20 || (fp.y > fishY - 10 && fp.y < fishY + 10 && abs(fp.x - currentFishX) < 30)) {
      foodParticles.splice(i, 1);
    } else {
      circle(fp.x, fp.y, fp.r);
    }
  }
}
Line-by-line explanation (26 lines)

🔧 Subcomponents:

conditional Game Over shooting state if (isShootingPlayer) { handleShooting(); inputField.hide(); submitButton.hide(); visitWebsiteButton.hide(); return; }

If the fish is in shooting mode, run the Game Over sequence and exit draw() early, preventing normal fish rendering

conditional Fish target position calculation if (!isFishSleeping && currentFishThought === "") { let targetFishX = bx + 40*sin(frameCount*0.01); let targetFishY = by + 10; if (foodParticles.length > 0) { let closestFood = foodParticles[0]; targetFishX = closestFood.x; targetFishY = closestFood.y - 5; } let moveSpeed = 0.05; currentFishX = lerp(currentFishX, targetFishX, moveSpeed); fishY = lerp(fishY, targetFishY, moveSpeed); }

Calculates where the fish should move: either following a sine wave, or moving toward food if available

conditional Bubble spawning if (!isFishSleeping && currentFishThought === "" && random(1)<0.02) { bubbles.push({x:currentFishX+35,y:fishY-5,r:6+random(4),v:1+random(1)}); }

Creates new bubbles rising from the fish at a 2% chance per frame, but only when awake and not showing a custom thought

conditional Thought bubble rendering if (!isFishSleeping) { let tx=bx, ty=by-160; noStroke(); fill(255,255,255,230); ellipse(tx,ty,160,90); ellipse(tx-40,ty+5,60,50); ellipse(tx+40,ty+10,60,50); ellipse(currentFishX+20,fishY-40,18,14); ellipse(currentFishX+10,fishY-20,12,9); fill(50); if (currentFishThought !== "" && frameCount - thoughtDisplayStartTime < thoughtDisplayDuration) { text(currentFishThought, tx, ty - 3); } else { currentFishThought = ""; thoughtDisplayStartTime = -Infinity; let currentDreamIndex = floor(frameCount/180)%dreams.length; displayedDreamsIndices.add(currentDreamIndex); let dream=dreams[currentDreamIndex]; text(dream,tx,ty-3); } }

Draws the white thought bubble with supporting bubbles, and displays either a custom response or the current dream text

for-loop Bubble animation and cleanup for(let i=bubbles.length-1;i>=0;i--){ let b=bubbles[i]; b.y-=b.v; circle(b.x,b.y,b.r*2); if(b.y<by-bowlR/2-40)bubbles.splice(i,1); }

Updates each bubble's position upward, draws it, and removes it when it rises above the bowl

conditional Fish sleep trigger if (displayedDreamsIndices.size === dreams.length && !isFishSleeping && !isFed) { isFishSleeping = true; bubbles = []; inputField.hide(); submitButton.hide(); visitWebsiteButton.hide(); }

When all dreams have been shown once and the fish hasn't been fed, it falls asleep and input elements disappear

conditional Feeding duration check if (isFed && frameCount - fedTime > feedDuration) { isFed = false; }

After the feeding duration expires, marks the fish as hungry again so it will fall asleep if dreams run out

for-loop Food particle physics and collision for(let i=foodParticles.length-1; i>=0; i--) { let fp = foodParticles[i]; fp.v += 0.2; fp.y += fp.v; if (fp.y > fishY + 20 || (fp.y > fishY - 10 && fp.y < fishY + 10 && abs(fp.x - currentFishX) < 30)) { foodParticles.splice(i, 1); } else { circle(fp.x, fp.y, fp.r); } }

Updates food particles with gravity, detects collision with the fish, and removes eaten food or particles that fall below the bowl

if (isShootingPlayer) {
Checks if the fish is in Game Over shooting mode—if so, run the special sequence instead of normal drawing
handleShooting();
Calls the function that renders the Game Over sequence, including the black screen, fish with gun, and bullet animation
return;
Exits the draw() function immediately, skipping all normal fish and bowl rendering—this prevents drawing anything else during Game Over
background(180,220,255);
Fills the entire canvas with a light blue color, erasing the previous frame to enable smooth animation
let bowlR=200, bx=width/2, by=height/2+60;
Defines the bowl's radius (200), center x (middle of canvas), and center y (below the vertical center)
circle(bx,by,bowlR);
Draws a white circle outline representing the fish bowl at the calculated position
if (!isFishSleeping && currentFishThought === "") {
Only update the fish's movement target if it's awake and not showing a custom chat response
let targetFishX = bx + 40*sin(frameCount*0.01);
Creates a smooth sine wave motion: the fish drifts left and right around the bowl center as frameCount increases
if (foodParticles.length > 0) {
If there's food in the bowl, the fish will chase it instead of following the sine wave pattern
currentFishX = lerp(currentFishX, targetFishX, moveSpeed);
Smoothly interpolates the fish's current x position toward the target position using lerp—creates natural easing instead of jerky movement
if (!isFishSleeping && currentFishThought === "" && random(1)<0.02) {
Only create bubbles if the fish is awake, not showing a response, and the random 2% chance succeeds
bubbles.push({x:currentFishX+35,y:fishY-5,r:6+random(4),v:1+random(1)});
Adds a new bubble object to the array with a starting position near the fish's mouth, random size, and random upward velocity
if (!isFishSleeping) {
Only draw the thought bubble shape and text if the fish is awake—sleeping fish don't dream
fill(255,255,255,230);
Sets the fill color to white with slight transparency (230 out of 255 alpha), making the thought bubble semi-opaque
ellipse(tx,ty,160,90);
Draws the main thought bubble as a horizontal ellipse centered above the bowl
if (currentFishThought !== "" && frameCount - thoughtDisplayStartTime < thoughtDisplayDuration) {
If the fish is displaying a chat response and hasn't timed out yet, show the custom response instead of dreams
let currentDreamIndex = floor(frameCount/180)%dreams.length;
Calculates which dream to show: divides frameCount by 180 to get a new index every 3 seconds, then uses modulo to cycle through the dreams array
displayedDreamsIndices.add(currentDreamIndex);
Records this dream index in the Set so we know it has been shown—when all 5 dreams are in this Set, the fish falls asleep
for(let i=bubbles.length-1;i>=0;i--){
Loops through the bubbles array backward—this allows safe removal of items while iterating
b.y-=b.v;
Moves the bubble upward by subtracting its velocity from its y position (lower y = higher on screen)
if(b.y<by-bowlR/2-40)bubbles.splice(i,1);
Removes the bubble from the array if it rises above the top of the bowl—prevents an infinite array of old bubbles
if (displayedDreamsIndices.size === dreams.length && !isFishSleeping && !isFed) {
If the Set contains all dream indices (meaning every dream has been shown) and the fish hasn't been fed, it's time to sleep
isFishSleeping = true;
Sets the sleep flag to true—this triggers X eyes in drawFish and prevents bubbles from spawning
if (isFed && frameCount - fedTime > feedDuration) {
Checks if enough frames have passed since feeding—once the duration expires, the fish is hungry again
fp.v += 0.2;
Adds 0.2 to the food particle's velocity every frame, simulating gravitational acceleration pulling it downward
if (fp.y > fishY + 20 || (fp.y > fishY - 10 && fp.y < fishY + 10 && abs(fp.x - currentFishX) < 30)) {
Removes the food if it falls below the fish or if it collides with the fish (is within 30 pixels horizontally and 10 pixels vertically)

handleVisitWebsite()

This simple function bridges p5.js with the browser's built-in web navigation. It demonstrates how p5.js sketches can interact with the wider web, opening links on button click. The '_blank' parameter is important—without it, the new page would replace your sketch.

function handleVisitWebsite() {
  window.open('https://corbun-does-weird-webs.my.canva.site/', '_blank');
}
Line-by-line explanation (1 lines)
window.open('https://corbun-does-weird-webs.my.canva.site/', '_blank');
Opens the specified URL in a new browser tab (the '_blank' parameter ensures it doesn't replace the current page)

positionInputElements()

This function demonstrates responsive layout—it calculates positions based on the canvas size, so it adapts when you resize the window. It's called in setup() and windowResized(), ensuring buttons stay properly positioned. Notice how it separates layout logic from rendering, making the code easier to read and maintain.

🔬 These lines center the input field and button at the bottom. What happens if you change 'height - 50' to 'height / 2'? Try it—the input elements will move to the middle of the screen!

  let startX = (width - totalInputButtonWidth) / 2;
  let yPosition = height - 50;
function positionInputElements() {
  let inputWidth = 200;
  let buttonWidth = 60;
  let padding = 10;

  // Calculate total width of input and talk button
  let totalInputButtonWidth = inputWidth + padding + buttonWidth;

  // Calculate starting x position to center them
  let startX = (width - totalInputButtonWidth) / 2;
  let yPosition = height - 50; // Position input/talk buttons 50 pixels from the bottom

  inputField.position(startX, yPosition);
  submitButton.position(startX + inputWidth + padding, yPosition);

  // NEW: Position the "Visit Website" button in the top-right corner
  let visitButtonWidth = visitWebsiteButton.width; // Get the actual width of the button
  let visitButtonX = width - visitButtonWidth - padding; // Padding from the right edge
  let visitButtonY = padding; // Padding from the top edge

  visitWebsiteButton.position(visitButtonX, visitButtonY);
}
Line-by-line explanation (9 lines)

🔧 Subcomponents:

calculation Center input and Talk button let totalInputButtonWidth = inputWidth + padding + buttonWidth; let startX = (width - totalInputButtonWidth) / 2;

Calculates the combined width and then finds the x-position that centers both the input field and button together

calculation Position Visit Website button let visitButtonWidth = visitWebsiteButton.width; let visitButtonX = width - visitButtonWidth - padding; let visitButtonY = padding;

Places the Visit Website button in the top-right corner with padding from the edges

let inputWidth = 200;
Stores the input field's width in pixels—used to calculate button positioning
let padding = 10;
Defines the space between elements and from screen edges in pixels
let totalInputButtonWidth = inputWidth + padding + buttonWidth;
Calculates the combined width of input field, space between, and button—needed to center the pair
let startX = (width - totalInputButtonWidth) / 2;
Calculates where to start drawing so the input+button pair is centered: (total canvas width - pair width) divided by 2
let yPosition = height - 50;
Places the input and button 50 pixels from the bottom of the canvas
inputField.position(startX, yPosition);
Sets the input field's screen position using the calculated coordinates
submitButton.position(startX + inputWidth + padding, yPosition);
Places the button immediately to the right of the input field, with padding between them
let visitButtonWidth = visitWebsiteButton.width;
Gets the actual rendered width of the Visit Website button so we can position it accurately
let visitButtonX = width - visitButtonWidth - padding;
Positions the Visit Website button from the right edge: canvas width minus button width minus padding from edge

handleTalk()

This function bridges the HTML input elements with the sketch's state variables. It demonstrates event handling, string validation, and the pattern of resetting complex state. Notice it sets thoughtDisplayStartTime to frameCount—this is the key to timed text display in draw(), where the duration countdown is calculated as 'frameCount - thoughtDisplayStartTime'.

function handleTalk() {
  let userText = inputField.value();
  if (userText.trim() !== "") {
    // Instead of displaying user's text, display a random fish response
    currentFishThought = random(fishResponses);
    thoughtDisplayStartTime = frameCount; // Start timer for display
    displayedDreamsIndices = new Set(); // Reset dream cycle after talking
    bubbles = []; // Clear bubbles
  }
  inputField.value(""); // Clear input field
}
Line-by-line explanation (7 lines)

🔧 Subcomponents:

conditional Check for empty input if (userText.trim() !== "") {

Ensures the user typed something before responding—ignores spaces-only input by using trim()

calculation Random fish response currentFishThought = random(fishResponses);

Picks a random response from the fishResponses array instead of echoing the user's text

state-reset Reset dream cycle displayedDreamsIndices = new Set(); bubbles = [];

Clears the dream history and bubbles, allowing the fish to dream again from the start after talking

let userText = inputField.value();
Retrieves whatever the user typed into the input field
if (userText.trim() !== "") {
Checks if the input is not empty—trim() removes leading/trailing spaces, so ' ' counts as empty
currentFishThought = random(fishResponses);
Picks a random response from the fishResponses array—the fish responds with its own thought, not echoing the user
thoughtDisplayStartTime = frameCount;
Records the current frame number—used in draw() to count down the display duration
displayedDreamsIndices = new Set();
Empties the Set of shown dreams, so the dream cycle restarts after talking
bubbles = [];
Clears all floating bubbles from the screen, creating a pause before dreaming resumes
inputField.value("");
Clears the input field after sending, preparing it for the next message

handleShooting()

This function demonstrates advanced graphics concepts: push/pop for coordinate transformation, translate/scale for positioning relative objects, and vector math for bullet movement. The 'if (distance > bullet.speed)' check is essential—it prevents overshooting the target by checking if one more speed step would pass it. The gameOverDelayFrames variable creates dramatic tension by delaying the message.

function handleShooting() {
  background(0); // Black screen for Game Over

  // Always draw the fish with the gun
  push();
  translate(currentFishX, fishY);
  scale(1, 1); // Main fish always faces right for shooting

  // Draw fish body (same as normal)
  noStroke();
  fill(255, 140, 0);
  ellipse(0, 0, 70, 40);
  triangle(-35, 0, -55, -15, -55, 15);

  // Draw fish eye (it's awake but in shooting mode, so draw the open eye)
  fill(0);
  noStroke();
  circle(20, -5, 5); // Eye relative to 0,0

  // Define eyeX and eyeY for the gun, relative to the translated origin (fish center)
  let eyeX_gun = 20;
  let eyeY_gun = -5;

  // Draw a simple gun
  stroke(100);
  strokeWeight(3);
  line(eyeX_gun + 5, eyeY_gun + 5, eyeX_gun + 30, eyeY_gun); // Barrel
  line(eyeX_gun + 15, eyeY_gun + 5, eyeX_gun + 15, eyeY_gun + 15); // Handle
  pop();

  if (bullet) {
    // Move bullet towards target
    let dirX = bullet.targetX - bullet.x;
    let dirY = bullet.targetY - bullet.y;
    let distance = dist(bullet.x, bullet.y, bullet.targetX, bullet.targetY); // @docs https://p5js.org/reference/p5/dist/

    if (distance > bullet.speed) {
      bullet.x += dirX / distance * bullet.speed;
      bullet.y += dirY / distance * bullet.speed;
    } else {
      // Bullet has reached target
      bullet = null; // Stop moving the bullet
      gameOverStartTime = frameCount; // Start the Game Over message timer
      // Store current mouse position for Game Over message
      gameOverX = mouseX;
      gameOverY = mouseY;
    }

    // Draw bullet
    fill(200, 0, 0); // Red bullet
    noStroke();
    ellipse(bullet.x, bullet.y, 10, 10);
  } else {
    // Bullet has hit, now wait for the delay before displaying the message
    if (frameCount - gameOverStartTime > gameOverDelayFrames) {
      // Display Game Over message at last mouse position
      fill(255);
      textSize(24);
      textAlign(CENTER, CENTER);
      text(gameOverMessage, width / 2, height / 2);
      textAlign(CENTER, CENTER);
      textSize(16);
      fill(200);
      text("Click to restart", width / 2, height - 50);
    }
  }
}
Line-by-line explanation (13 lines)

🔧 Subcomponents:

rendering Draw fish with gun push(); translate(currentFishX, fishY); scale(1, 1); noStroke(); fill(255, 140, 0); ellipse(0, 0, 70, 40); triangle(-35, 0, -55, -15, -55, 15); fill(0); noStroke(); circle(20, -5, 5); let eyeX_gun = 20; let eyeY_gun = -5; stroke(100); strokeWeight(3); line(eyeX_gun + 5, eyeY_gun + 5, eyeX_gun + 30, eyeY_gun); line(eyeX_gun + 15, eyeY_gun + 5, eyeX_gun + 15, eyeY_gun + 15); pop();

Renders the fish's body, eye, and a simple gun using translate and line drawing

conditional Bullet trajectory calculation if (bullet) { let dirX = bullet.targetX - bullet.x; let dirY = bullet.targetY - bullet.y; let distance = dist(bullet.x, bullet.y, bullet.targetX, bullet.targetY); if (distance > bullet.speed) { bullet.x += dirX / distance * bullet.speed; bullet.y += dirY / distance * bullet.speed; } else { bullet = null; gameOverStartTime = frameCount; gameOverX = mouseX; gameOverY = mouseY; } fill(200, 0, 0); noStroke(); ellipse(bullet.x, bullet.y, 10, 10); }

Moves the bullet toward its target using vector math, draws it, and stops it when it reaches the target

conditional Display Game Over message if (frameCount - gameOverStartTime > gameOverDelayFrames) { fill(255); textSize(24); textAlign(CENTER, CENTER); text(gameOverMessage, width / 2, height / 2); textAlign(CENTER, CENTER); textSize(16); fill(200); text("Click to restart", width / 2, height - 50); }

After the delay expires, displays the Game Over message and restart instructions

background(0);
Fills the entire canvas with black, creating the ominous Game Over atmosphere
push();
Saves the current drawing state (position, rotation, scale, fill, stroke) before making changes
translate(currentFishX, fishY);
Moves the coordinate system so (0,0) is at the fish's center—all subsequent coordinates are relative to the fish
ellipse(0, 0, 70, 40);
Draws the fish's body as a horizontal ellipse centered at the translated origin
triangle(-35, 0, -55, -15, -55, 15);
Draws the fish's tail as a triangle pointing left from the body
circle(20, -5, 5);
Draws a small black circle for the fish's open eye
line(eyeX_gun + 5, eyeY_gun + 5, eyeX_gun + 30, eyeY_gun);
Draws the gun's barrel as a line extending from the fish's eye area
pop();
Restores the saved drawing state, undoing the translate and returning coordinates to normal
let dirX = bullet.targetX - bullet.x;
Calculates the horizontal distance from the bullet to its target—positive means target is to the right
let distance = dist(bullet.x, bullet.y, bullet.targetX, bullet.targetY);
Calculates the straight-line distance between bullet and target using the dist() function
bullet.x += dirX / distance * bullet.speed;
Moves the bullet one step toward the target: dividing direction by distance creates a unit vector, multiplied by speed
if (frameCount - gameOverStartTime > gameOverDelayFrames) {
Waits for 90 frames (1.5 seconds) after the bullet hits before showing the message
text(gameOverMessage, width / 2, height / 2);
Displays the vengeance message centered on the screen in white text

drawFish(x, y, fishScale, fishColor, direction, isSleeping)

This function demonstrates reusable component design—instead of drawing the fish inline in draw(), we encapsulate it in a function that accepts parameters like color and scale. Notice the use of push/pop to contain the coordinate transformation (translate and scale), preventing them from affecting later drawings. The conditional eye rendering shows how to give characters emotion through simple shape changes—sleeping with X eyes is instantly recognizable.

function drawFish(x, y, fishScale, fishColor, direction, isSleeping) {
  push();
  translate(x, y); // @docs https://p5js.org/reference/p5/translate/
  scale(direction * fishScale, fishScale); // Flip horizontally based on direction and fishScale // @docs https://p5js.org/reference/p5/scale/

  // Draw fish body
  noStroke(); // @docs https://p5js.org/reference/p5/noStroke/
  fill(fishColor); // @docs https://p5js.org/reference/p5/fill/
  ellipse(0, 0, 70, 40); // Body relative to 0,0 // @docs https://p5js.org/reference/p5/ellipse/
  triangle(-35, 0, -55, -15, -55, 15); // Tail relative to 0,0 // @docs https://p5js.org/reference/p5/triangle/

  // Draw fish eye conditionally
  if (isSleeping) {
    stroke(0);
    strokeWeight(1); // @docs https://p5js.org/reference/p5/strokeWeight/
    let eyeX = 20;
    let eyeY = -5;
    line(eyeX - 2.5, eyeY - 2.5, eyeX + 2.5, eyeY + 2.5); // @docs https://p5js.org/reference/p5/line/
    line(eyeX + 2.5, eyeY - 2.5, eyeX - 2.5, eyeY + 2.5);
  } else {
    fill(0);
    noStroke();
    circle(20, -5, 5); // Eye relative to 0,0
  }
  pop(); // @docs https://p5js.org/reference/p5/pop/
}
Line-by-line explanation (13 lines)

🔧 Subcomponents:

rendering Fish body and tail ellipse(0, 0, 70, 40); triangle(-35, 0, -55, -15, -55, 15);

Draws the main fish shape: an ellipse for the body and a triangle for the tail

conditional Eye appearance based on sleep state if (isSleeping) { stroke(0); strokeWeight(1); let eyeX = 20; let eyeY = -5; line(eyeX - 2.5, eyeY - 2.5, eyeX + 2.5, eyeY + 2.5); line(eyeX + 2.5, eyeY - 2.5, eyeX - 2.5, eyeY + 2.5); } else { fill(0); noStroke(); circle(20, -5, 5); }

Draws either X eyes (sleeping) or a black circle (awake)

push();
Saves the current drawing settings so changes don't affect other drawings later
translate(x, y);
Moves the origin (0,0) to the fish's position—all subsequent coordinates are relative to the fish
scale(direction * fishScale, fishScale);
Scales the fish and flips it horizontally if direction is -1 (negative scale flips the image)
noStroke();
Removes the outline, so shapes are filled with solid color only
fill(fishColor);
Sets the fill color to whatever was passed in (orange for the main fish)
ellipse(0, 0, 70, 40);
Draws the fish's body as an ellipse 70 pixels wide and 40 pixels tall, centered at the origin
triangle(-35, 0, -55, -15, -55, 15);
Draws the fish's tail pointing left: three corners form a triangle from left-center to upper and lower-left
if (isSleeping) {
Checks if the fish should have its eyes closed (sleeping)
stroke(0);
Sets the line color to black for the X eyes
line(eyeX - 2.5, eyeY - 2.5, eyeX + 2.5, eyeY + 2.5);
Draws the first line of the X from upper-left to lower-right
line(eyeX + 2.5, eyeY - 2.5, eyeX - 2.5, eyeY + 2.5);
Draws the second line of the X from upper-right to lower-left, completing the sleep expression
circle(20, -5, 5);
Draws a small black circle for the awake eye when not sleeping
pop();
Restores all the saved drawing settings, undoing the translate and scale

windowResized()

windowResized() is automatically called by p5.js whenever the browser window is resized. This function ensures the sketch remains responsive and visually correct at any screen size. Notice the conditional logic—it only recenter the fish if it's awake, respecting the sleeping or Game Over states.

function windowResized(){
  resizeCanvas(windowWidth,windowHeight); // @docs https://p5js.org/reference/p5/resizeCanvas/
  // Re-initialize fishX and fishY if canvas resized and fish is awake
  if (!isFishSleeping && !isShootingPlayer) {
    currentFishX = width / 2;
    fishY = height/2+60 + 10; // Reset fishY
  }
  positionInputElements(); // Reposition input elements on resize
}
Line-by-line explanation (3 lines)

🔧 Subcomponents:

initialization Resize canvas to window resizeCanvas(windowWidth,windowHeight);

Updates the canvas dimensions to match the new window size

conditional Recenter fish on resize if (!isFishSleeping && !isShootingPlayer) { currentFishX = width / 2; fishY = height/2+60 + 10; }

Resets the fish to the center if it's awake, preventing it from being off-screen after resize

resizeCanvas(windowWidth,windowHeight);
Automatically resizes the p5.js canvas to match the new window dimensions—essential for responsive design
if (!isFishSleeping && !isShootingPlayer) {
Only recenter the fish if it's awake and not in Game Over mode—allows the fish to stay in its own position during other states
positionInputElements();
Recalculates and updates the button positions to match the new canvas size

mousePressed()

mousePressed() demonstrates complex state management—it has multiple conditional branches that guard against unintended actions. Notice the order: check Game Over first, then sleep guard, then element checks, then execute feeding. This hierarchy ensures the right action happens in each state. The frameCount = 0 is particularly clever—it resets animation timing without actually reloading the sketch, creating a smooth restart.

🔬 These lines reset the fish state when you feed it. What happens if you remove 'frameCount = 0;'? The dream timing won't reset—the fish might be in the middle of a dream instead of starting fresh.

  // Always trigger a feeding action when clicked on the canvas (not input/button)
  displayedDreamsIndices = new Set(); // Clear dream history so it cycles again
  bubbles = []; // Clear any remaining bubbles
  currentFishX = width / 2; // Reset fish's x-position to the center
  fishY = height/2+60 + 10; // Reset fish's y-position
  frameCount = 0; // Reset frameCount to start dream cycle from the beginning
function mousePressed() {
  // NEW: If Game Over, clicking resets the sketch
  // Added check for gameOverStartTime and delay to ensure message is visible before reset
  if (isShootingPlayer && bullet === null && frameCount - gameOverStartTime > gameOverDelayFrames) { // Only reset if bullet has hit and delay passed
    isShootingPlayer = false;
    isFishSleeping = false;
    bullet = null;
    gameOverStartTime = -Infinity; // Reset timer
    displayedDreamsIndices = new Set();
    bubbles = [];
    currentFishX = width / 2;
    fishY = height/2+60 + 10;
    frameCount = 0;
    isFed = false;
    fedTime = -Infinity;
    foodParticles = [];
    currentFishThought = ""; // Clear thought on reset
    thoughtDisplayStartTime = -Infinity; // Clear thought timer
    // Show input elements on reset
    inputField.show();
    submitButton.show();
    visitWebsiteButton.show(); // NEW: Show visit button on reset
    positionInputElements(); // Reposition on reset
    return;
  }

  // If the fish is sleeping (and not shooting), it cannot be brought back.
  if (isFishSleeping) {
    return;
  }

  // --- The following code will only execute if the fish is NOT sleeping ---

  // Check if click was on the input field or button. If so, don't feed.
  // We handle input via handleTalk() called by button.mousePressed() or Enter key.
  // This prevents accidental feeding when trying to type.
  // Also check the new visitWebsiteButton
  if (inputField.elt === document.activeElement || submitButton.elt === document.activeElement || visitWebsiteButton.elt === document.activeElement) {
    return;
  }

  // Always clear custom thought and reset dream cycle when clicking on the canvas
  // This ensures the fish goes back to dreaming if it was displaying a custom thought
  currentFishThought = "";
  thoughtDisplayStartTime = -Infinity;

  // Always trigger a feeding action when clicked on the canvas (not input/button)
  displayedDreamsIndices = new Set(); // Clear dream history so it cycles again
  bubbles = []; // Clear any remaining bubbles
  currentFishX = width / 2; // Reset fish's x-position to the center
  fishY = height/2+60 + 10; // Reset fish's y-position
  frameCount = 0; // Reset frameCount to start dream cycle from the beginning

  isFed = true; // Fish is now fed
  fedTime = frameCount; // Record the time it was fed

  // Add a food particle to fall into the bowl
  let bowlR=200, bx=width/2, by=height/2+60; // Re-calculate bowl center and radius
  // Spawn food at the top of the water level, within the bowl's width
  foodParticles.push({ x: bx + random(-bowlR/3, bowlR/3), y: by - bowlR/2, r: 8, v: 0 });

  // --- REMOVED: 1 in 10 chance to pull out a gun every time you feed him ---
  // if (random(1) < (1/10)) { // 1 in 10 chance
  //   isShootingPlayer = true;
  //   // Initialize bullet to shoot towards current mouse position
  //   bullet = { x: currentFishX, y: fishY, targetX: mouseX, targetY: mouseY, speed: 15 };
  // }
}
Line-by-line explanation (8 lines)

🔧 Subcomponents:

conditional Game Over reset condition if (isShootingPlayer && bullet === null && frameCount - gameOverStartTime > gameOverDelayFrames) {

Checks if Game Over is complete before allowing restart—must wait for message to display

state-reset Reset all game variables isShootingPlayer = false; isFishSleeping = false; bullet = null; gameOverStartTime = -Infinity; displayedDreamsIndices = new Set(); bubbles = []; currentFishX = width / 2; fishY = height/2+60 + 10; frameCount = 0; isFed = false; fedTime = -Infinity; foodParticles = []; currentFishThought = ""; thoughtDisplayStartTime = -Infinity;

Returns all variables to their initial values, effectively restarting the entire sketch

conditional Sleeping fish guard if (isFishSleeping) { return; }

Prevents any feeding actions when the fish is sleeping—once asleep, it's gone

conditional Check if click on input elements if (inputField.elt === document.activeElement || submitButton.elt === document.activeElement || visitWebsiteButton.elt === document.activeElement) { return; }

Prevents accidental feeding when user is trying to type in the input field or click buttons

state-change Trigger feeding isFed = true; fedTime = frameCount; foodParticles.push({ x: bx + random(-bowlR/3, bowlR/3), y: by - bowlR/2, r: 8, v: 0 });

Marks the fish as fed, records the frame, and spawns a food particle

if (isShootingPlayer && bullet === null && frameCount - gameOverStartTime > gameOverDelayFrames) {
Checks three things: is the fish in Game Over mode, has the bullet already hit, and has the message delay passed—only then allow a reset
frameCount = 0;
Resets the frame counter to 0, effectively restarting the animation timing so dreams cycle from the beginning
if (isFishSleeping) {
If the fish is sleeping, immediately exit the function—no feeding or interaction is possible once asleep
if (inputField.elt === document.activeElement || submitButton.elt === document.activeElement || visitWebsiteButton.elt === document.activeElement) {
Checks if the user clicked on an interactive element—if so, return without feeding to avoid accidental actions
currentFishThought = "";
Clears any custom response the fish was displaying, so it returns to normal dreaming
isFed = true;
Marks the fish as fed, which extends the frameCount before it falls asleep
fedTime = frameCount;
Records the exact frame when feeding occurred—used to calculate when the fed duration expires
foodParticles.push({ x: bx + random(-bowlR/3, bowlR/3), y: by - bowlR/2, r: 8, v: 0 });
Creates a new food particle at a random x-position within the bowl width, starting at the top with zero velocity

📦 Key Variables

bubbles array

Stores all currently floating bubbles with their positions, sizes, and velocities. Bubbles rise from the fish's mouth when it's dreaming.

let bubbles=[];
dreams array

Stores the five dreams the fish cycles through: 'The Ocean', 'Freedom', 'A Bigger Bowl', 'A girl fish', and 'food'.

let dreams=['The Ocean','Freedom','A Bigger Bowl',"A girl fish",'food'];
fishResponses array

Stores random responses the fish can say when you chat with it, like 'Oh, really?' or 'Tell me more...'

let fishResponses = ["Oh, really?", "That's a nice thought!", ...];
displayedDreamsIndices Set

Tracks which dreams have been shown (stores 0-4). When size equals 5, all dreams have been shown and the fish falls asleep.

let displayedDreamsIndices = new Set();
isFishSleeping boolean

True when the fish is sleeping with X eyes, false when awake. Controls whether bubbles and thoughts appear.

let isFishSleeping = false;
currentFishX number

Stores the fish's horizontal position. Updated each frame to move toward target position using lerp().

let currentFishX = 0;
fishY number

Stores the fish's vertical position inside the bowl. Updated similarly to currentFishX for smooth motion.

let fishY = 0;
isFed boolean

True after clicking to feed the fish, false when hunger timer expires. Controls how long before the fish falls asleep.

let isFed = false;
fedTime number

Stores the frameCount when the fish was last fed. Used to calculate when feedDuration expires.

let fedTime = -Infinity;
feedDuration number

Number of frames (900) the fish stays awake after feeding—equals 5 dreams × 180 frames per dream.

const feedDuration = dreams.length * 180;
foodParticles array

Stores all food particles currently falling through the bowl with their positions, sizes, and velocities.

let foodParticles = [];
isShootingPlayer boolean

True during the Game Over sequence when the fish is shooting. Controls whether draw() renders normal or Game Over visuals.

let isShootingPlayer = false;
bullet object

Stores bullet position, target, and speed during Game Over. Null when no bullet is active.

let bullet = null;
gameOverMessage string

The text displayed when the fish wins: 'YOU LET THE FISH DREAM TOO LONG. NOW IT DREAMS OF VENGEANCE.'

let gameOverMessage = "YOU LET THE FISH DREAM TOO LONG...";
gameOverStartTime number

Stores the frameCount when the bullet hit the target, used to delay the Game Over message display.

let gameOverStartTime = -Infinity;
gameOverDelayFrames number

Number of frames (90) to wait after bullet hit before showing the Game Over message. Creates suspenseful pause.

const gameOverDelayFrames = 90;
inputField object

The HTML input element where players type messages to the fish. Created and styled in setup().

let inputField;
submitButton object

The 'Talk' button that sends messages to the fish. Created and styled in setup().

let submitButton;
currentFishThought string

Stores the fish's current response to display in the thought bubble. Empty string means displaying dreams instead.

let currentFishThought = "";
thoughtDisplayStartTime number

Stores the frameCount when a thought was set. Used to count down the display duration (180 frames).

let thoughtDisplayStartTime = -Infinity;
thoughtDisplayDuration number

Number of frames (180) to display the fish's chat response before returning to dreams.

const thoughtDisplayDuration = 180;
visitWebsiteButton object

The 'Visit Corbun's Weird Webs' button in the top-right corner. Opens a URL when clicked.

let visitWebsiteButton;

🔧 Potential Improvements (6)

Here are some ways this code could be enhanced:

BUG draw() - fish movement

In the line 'currentFishX = lerp(currentFishX, currentFishX, moveSpeed);', the fish is lerp'ing to itself (the second argument should be targetFishX, not currentFishX), so the fish doesn't actually move toward targets.

💡 Change to 'currentFishX = lerp(currentFishX, targetFishX, moveSpeed);' so the fish smoothly moves toward the calculated target position.

BUG mousePressed() - food spawning

Food particles are spawned at 'y: by - bowlR/2', which places them at the top of the bowl outline, not at a realistic water surface level. If the bowl is larger, food may spawn outside visible water.

💡 Calculate a proper water level or spawn food at a more centered position like 'y: by - 50' to ensure it's always inside the bowl.

PERFORMANCE draw() - bubble rendering

Bubbles are iterated through every frame but never cleaned up efficiently if many accumulate. The check 'if(b.y<by-bowlR/2-40)' might miss some bubbles if bowlR changes.

💡 Use a more robust cleanup based on distance from screen or time elapsed, and consider limiting the maximum number of bubbles in the array.

STYLE Global variables

Many global variables are declared without consistent comments or organization. Variables are scattered and some are duplicated (e.g., gameOverX, gameOverY are declared but never used).

💡 Group related variables together (fish state, UI elements, animation timers) with clear section comments for readability.

FEATURE draw() and handleTalk()

The fish's random responses are generic and don't respond contextually to what the user types. Every message gets the same random response regardless of content.

💡 Parse the user's input for keywords (e.g., detect 'hungry', 'sleep', 'freedom') and select contextually appropriate responses from fishResponses.

BUG mousePressed() - Game Over reset

After resetting from Game Over, frameCount is set to 0, but this happens in real-time—if the user clicks quickly, frameCount might not truly reset because p5.js continues incrementing it in the background.

💡 Use a separate custom frame counter variable instead of relying on frameCount, or use millis() for more reliable timing.

🔄 Code Flow

Code flow showing setup, draw, handlevisitwebsite, positioninputelements, handletalk, handleshooting, drawfish, windowresized, mousepressed

💡 Click on function names in the diagram to jump to their code

graph TD start[Start] --> setup[setup] setup --> canvas-setup[canvas-setup] setup --> fish-position-init[fish-position-init] setup --> input-field-creation[input-field-creation] setup --> button-creation[button-creation] setup --> event-listeners[event-listeners] setup --> positioninputelements[positioninputelements] setup --> draw[draw loop] click setup href "#fn-setup" click canvas-setup href "#sub-canvas-setup" click fish-position-init href "#sub-fish-position-init" click input-field-creation href "#sub-input-field-creation" click button-creation href "#sub-button-creation" click event-listeners href "#sub-event-listeners" click positioninputelements href "#fn-positioninputelements" draw --> shooting-check[shooting-check] draw --> fish-movement-logic[fish-movement-logic] draw --> bubble-generation[bubble-generation] draw --> thought-bubble-display[thought-bubble-display] draw --> bubble-update-loop[bubble-update-loop] draw --> sleep-trigger[sleep-trigger] draw --> fed-check[fed-check] draw --> food-particle-loop[food-particle-loop] click draw href "#fn-draw" click shooting-check href "#sub-shooting-check" click fish-movement-logic href "#sub-fish-movement-logic" click bubble-generation href "#sub-bubble-generation" click thought-bubble-display href "#sub-thought-bubble-display" click bubble-update-loop href "#sub-bubble-update-loop" click sleep-trigger href "#sub-sleep-trigger" click fed-check href "#sub-fed-check" click food-particle-loop href "#sub-food-particle-loop" shooting-check -->|if shooting mode| game-over-message[game-over-message] shooting-check -->|else| drawfish[drawfish] click game-over-message href "#sub-game-over-message" click drawfish href "#fn-drawfish" fish-movement-logic -->|calculate target position| fish-position[fish-position] fish-position -->|follow sine wave or move toward food| drawfish bubble-generation -->|2% chance| new-bubble[new-bubble] new-bubble -->|if awake| bubble-update-loop thought-bubble-display -->|draw thought bubble| thought-bubble[thought-bubble] thought-bubble -->|display response or dream text| drawfish bubble-update-loop -->|update position| bubble[bubble] bubble -->|remove if above bowl| bubble-update-loop sleep-trigger -->|if dreams shown and not fed| drawfish fed-check -->|if feeding duration expired| drawfish food-particle-loop -->|update particles| food-particle[food-particle] food-particle -->|detect collision| food-particle-loop positioninputelements --> input-button-centering[input-button-centering] positioninputelements --> visit-button-positioning[visit-button-positioning] click input-button-centering href "#sub-input-button-centering" click visit-button-positioning href "#sub-visit-button-positioning" mousepressed[mousePressed] -->|check Game Over| reset-check[reset-check] reset-check -->|if reset complete| reset-all-state[reset-all-state] reset-all-state -->|reset variables| setup click mousepressed href "#fn-mousepressed" click reset-check href "#sub-reset-check" click reset-all-state href "#sub-reset-all-state" handletalk[handleTalk] --> empty-check[empty-check] empty-check -->|if not empty| response-selection[response-selection] response-selection --> reset-dreams[reset-dreams] click handletalk href "#fn-handletalk" click empty-check href "#sub-empty-check" click response-selection href "#sub-response-selection" click reset-dreams href "#sub-reset-dreams" handleshooting[handleShooting] --> bullet-movement[bullet-movement] bullet-movement -->|check distance| game-over-message click handleshooting href "#fn-handleshooting" click bullet-movement href "#sub-bullet-movement"

❓ Frequently Asked Questions

What visual experience does the 'day dreaming fish 3' sketch offer?

The sketch creates a dreamy underwater world where a fish drifts while surrounded by glowing visuals and floating thought bubbles representing its hopes and worries.

How can users interact with the fish in this creative coding sketch?

Users can type messages to chat with the fish, feed it, and even witness its transformation into a vengeful dreamer if left to snooze too long.

What creative coding concepts are showcased in 'day dreaming fish 3'?

The sketch demonstrates concepts such as state management, interactive user input, and dynamic visual storytelling through animations and thought bubbles.

Preview

day dreaming fish 3 - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of day dreaming fish 3 - Code flow showing setup, draw, handlevisitwebsite, positioninputelements, handletalk, handleshooting, drawfish, windowresized, mousepressed
Code Flow Diagram