day dreaminj fish

This sketch creates a dreamy fish bowl where a sleepy orange fish drifts peacefully, displays changing dreams in thought bubbles, and responds to your typed messages. Click to feed the fish, chat with it, and discover that if you don't interact enough, the fish will fall asleep—and dream of vengeance with a 1-in-10 chance to turn the tables.

🧪 Try This!

Experiment with the code by making these changes:

  1. Change the fish's color — The fish is orange because of color(255,140,0). Try making it blue (0,100,255), purple (200,0,255), or any RGB combination you like.
  2. Make the fish move faster when chasing food — Increase the moveSpeed from 0.05 to 0.15 so the fish snappily chases falling food particles instead of drifting dreamily.
  3. Make the fish never fall asleep — The fish falls asleep when all five dreams are shown. Comment out or remove the sleep-trigger conditional to make the fish dream forever without consequence.
  4. Increase the vengeance chance to 50% — Change the 1-in-10 odds of vengeance to 1-in-2 (50% chance), making the fish far more likely to turn on you when feeding.
  5. Make dream-cycling twice as fast — Each dream currently displays for 180 frames. Change it to 90 frames so the fish cycles through all five dreams in half the time, creating urgency.
  6. Add your own fish response — Insert a new response string into the fishResponses array, like "Did I say that out loud?", so the fish has a new thing to say when you talk to it.
Prefer the full editor? Open it there →

📖 About This Sketch

This sketch creates an interactive fish bowl with a dreamy, slightly unsettling personality. The fish floats in a bowl, cycles through dreams like 'Freedom' and 'A Bigger Bowl', responds to your chat messages, and eats food particles when you click. What makes it special is the emotional arc: the fish has moods—awake and dreaming, content when fed, and dangerously vengeful if neglected. Visually, it combines animation loops, interactive text input, particle effects, and a dramatic Game Over sequence powered by p5.js's drawing, event handling, and state management.

The code is organized into clear sections: setup() initializes the canvas and UI elements; draw() handles the main animation loop with fish movement, bubble generation, and dream cycling; drawFish() abstracts fish rendering with sleeping and awake states; handleShooting() manages the revenge sequence; and event handlers (mousePressed, handleTalk) drive interactivity. By studying this sketch, you'll learn how to layer multiple animation systems, manage complex state across frames, position UI elements dynamically, and create emotional storytelling through code.

⚙️ How It Works

  1. When the sketch loads, setup() creates a full-window canvas, positions an input field and buttons at the bottom, and initializes variables for the fish's position, dreams, and responses.
  2. Every frame, draw() clears the background, draws the blue bowl outline, and updates the main fish's position—either drifting in a sine wave or chasing food particles with smooth lerp-based movement.
  3. While the fish is awake and not displaying a custom thought, it cycles through five dreams ('The Ocean', 'Freedom', etc.) displayed in a thought bubble near the top. Small animated bubbles float upward from the fish's head.
  4. When you type a message and press Enter or click 'Talk', the fish responds with a random message (like 'That's deep.') that appears in the thought bubble for 3 seconds, resetting the dream cycle.
  5. Clicking anywhere on the canvas (except the input field) triggers a feeding action: a brown food particle falls into the bowl, the fish is marked as fed for several seconds, and there's a 1-in-10 chance the fish suddenly becomes vengeful.
  6. If all five dreams are displayed without you clicking to feed the fish, it falls asleep (eyes become X marks), input elements hide, and if it's vengeful, the screen goes black and the fish shoots a bullet at your last mouse position before showing a 'Game Over' message. Clicking the message restarts the sketch.

🎓 Concepts You'll Learn

State management (sleeping, feeding, shooting)Animation loops and smooth motion (lerp)Particle systems (bubbles and food)Event handling (mouse clicks, keyboard input)UI positioning and responsivenessConditional logic for state transitionsDrawing primitives and transformations

📝 Code Breakdown

setup()

setup() runs once when the sketch loads. It creates the canvas, initializes all variables, styles the UI elements, and sets up event listeners. Everything in setup() is preparation; the real animation happens in draw().

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:

calculation Canvas and text setup createCanvas(windowWidth,windowHeight);

Creates a canvas that fills the entire browser window

calculation Input field styling inputField = createInput();

Creates a text input for the player to type messages to the fish

calculation Button styling and events submitButton = createButton('Talk');

Creates a submit button and links it to the handleTalk function

conditional Enter key handler if (event.key === 'Enter') {

Allows the player to submit messages by pressing Enter

createCanvas(windowWidth,windowHeight);
Creates a canvas that matches the full browser window size, so the sketch fills the screen.
textAlign(CENTER,CENTER);
Centers all text horizontally and vertically by their center point, not their top-left corner.
displayedDreamsIndices = new Set();
Initializes an empty Set to track which dreams have been shown (prevents repeating dreams until all are cycled).
currentFishX = width / 2;
Places the fish horizontally at the center of the canvas.
fishY = height/2+60 + 10;
Places the fish vertically near the center of the bowl (bowl is at height/2+60, fish sits slightly below center).
inputField = createInput();
Creates a text input field where the player types messages to the fish.
inputField.hide();
Hides the input field initially; it will only appear when the fish is awake and not sleeping.
submitButton = createButton('Talk');
Creates a green 'Talk' button that the player clicks to submit their message to the fish.
submitButton.mousePressed(handleTalk);
Connects the button click event to the handleTalk() function, which displays the fish's response.
if (event.key === 'Enter') {
Detects when the player presses the Enter key in the input field, allowing quick message submission without clicking the button.

draw()

draw() is the heartbeat of the sketch—it runs 60 times per second. Every frame, it clears the canvas, recalculates positions based on state variables (like isFishSleeping, isFed, currentFishThought), draws shapes, and updates arrays (bubbles, food). The layered if-statements manage complex state transitions: the fish's behavior changes dramatically depending on whether it's sleeping, fed, showing a thought, or about to shoot. This is how you create emotional arcs in interactive art—state variables drive everything you see.

🔬 This loop moves each bubble upward. What happens if you change b.y-=b.v to b.y+=b.v so bubbles sink instead of rise? Or change circle(b.x,b.y,b.r*2) to circle(b.x,b.y,b.r*4) to draw fatter bubbles?

  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);
  }
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, targetFishX, 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

    // NEW: 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 };
    }
  } 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 (27 lines)

🔧 Subcomponents:

conditional Game Over shooting handler if (isShootingPlayer) {

If the fish is in revenge mode, skip normal drawing and show the Game Over sequence instead

conditional Fish movement target calculation if (!isFishSleeping && currentFishThought === "") {

Only calculate movement targets when the fish is awake and not showing a custom thought

conditional Food-seeking priority if (foodParticles.length > 0) {

If food is in the bowl, the fish chases it instead of drifting

conditional Bubble spawn chance if (!isFishSleeping && currentFishThought === "" && random(1)<0.02) {

Randomly generates new bubbles floating upward from the fish's head

conditional Thought bubble display if (!isFishSleeping) {

Draws the thought bubble and all its component ellipses only when the fish is awake

conditional Custom thought vs. dream logic if (currentFishThought !== "" && frameCount - thoughtDisplayStartTime < thoughtDisplayDuration) {

Shows the fish's response if active; otherwise cycles through dreams

for-loop Bubble animation loop for(let i=bubbles.length-1;i>=0;i--){

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

conditional Sleep state trigger if (displayedDreamsIndices.size === dreams.length && !isFishSleeping && !isFed) {

Puts the fish to sleep if all dreams have been shown and it hasn't been fed recently

conditional Fed duration countdown if (isFed && frameCount - fedTime > feedDuration) {

Marks the fish as no longer fed after enough frames have passed since it was fed

for-loop Food particle animation for(let i=foodParticles.length-1; i>=0; i--) {

Updates gravity on each food particle, draws it, and removes it when eaten or fallen away

if (isShootingPlayer) {
If the fish is in revenge mode, skip all normal drawing and jump to the Game Over sequence.
handleShooting();
Calls the function that manages the black-screen Game Over visuals and bullet animation.
return;
Exits draw() immediately, preventing any further drawing (bowl, fish, bubbles) for this frame.
background(180,220,255);
Fills the entire canvas with a light blue color, clearing the previous frame's drawings.
let bowlR=200, bx=width/2, by=height/2+60;
Defines the bowl's radius (200 pixels), its center x (middle of canvas), and its center y (slightly below middle).
circle(bx,by,bowlR);
Draws a white circular outline representing the fish bowl.
let targetFishX = bx + 40*sin(frameCount*0.01);
By default, the fish's target x-position oscillates left and right using a sine wave based on frameCount.
if (foodParticles.length > 0) {
If food particles exist in the bowl, the fish will prioritize chasing them instead of drifting.
currentFishX = lerp(currentFishX, targetFishX, moveSpeed);
Smoothly moves the fish toward its target x-position by blending its current and target position; lower moveSpeed = dreamier motion.
drawFish(currentFishX, fishY, 1, color(255,140,0), 1, isFishSleeping);
Draws the main orange fish at its current position, passing in the sleeping state to determine eye style (open or X marks).
if (!isFishSleeping && currentFishThought === "" && random(1)<0.02) {
Has a 2% chance each frame to spawn a new bubble, but only if the fish is awake and not showing a custom thought.
bubbles.push({x:currentFishX+35,y:fishY-5,r:6+random(4),v:1+random(1)});
Adds a new bubble object to the bubbles array with position near the fish's head, a random radius, and a random upward velocity.
if (!isFishSleeping) {
Only draw the thought bubble and its text if the fish is awake; sleeping fish don't think.
let tx=bx, ty=by-160;
Sets the thought bubble's position to the horizontal center of the bowl and 160 pixels above the bowl's center.
ellipse(tx,ty,160,90);
Draws the main large thought bubble (160 pixels wide, 90 tall) where the fish's dreams and thoughts are displayed.
if (currentFishThought !== "" && frameCount - thoughtDisplayStartTime < thoughtDisplayDuration) {
If a custom thought (fish's response) is active and hasn't expired, display it; otherwise display the current dream.
let currentDreamIndex = floor(frameCount/180)%dreams.length;
Cycles through dream indices: every 180 frames, the dream changes. The % operator wraps around to restart the cycle.
displayedDreamsIndices.add(currentDreamIndex);
Records which dream index is being shown so we can track when all five dreams have been cycled.
b.y-=b.v;
Moves each bubble upward by decreasing its y-position (lower y = higher on screen) by its velocity.
if(b.y<by-bowlR/2-40)bubbles.splice(i,1);
Removes the bubble from the array once it floats above the top of the bowl, cleaning up memory.
if (displayedDreamsIndices.size === dreams.length && !isFishSleeping && !isFed) {
Checks if all five dreams have been shown AND the fish hasn't been fed recently; if true, the fish falls asleep.
isFishSleeping = true;
Sets the sleep flag to true, which changes the fish's eye style and pauses its movement.
if (random(1) < (1/10)) {
Has a 10% chance to trigger vengeance mode when the fish falls asleep (1 in 10).
isShootingPlayer = true;
Activates the Game Over shooting sequence, causing next frame's draw() to call handleShooting() instead of normal drawing.
if (isFed && frameCount - fedTime > feedDuration) {
If the fish was fed and enough frames have passed (one full dream cycle), marks it as no longer fed.
fp.v += 0.2;
Applies gravity to the food particle by increasing its downward velocity each frame.
if (fp.y > fishY + 20 || (fp.y > fishY - 10 && fp.y < fishY + 10 && abs(fp.x - currentFishX) < 30)) {
Removes the food particle if it falls below the bowl or if it overlaps with the fish's position (eaten).

drawFish()

drawFish() is a helper function that abstracts away the complexity of drawing the fish. By accepting parameters like x, y, fishScale, fishColor, direction, and isSleeping, this function can draw the same fish in different states and positions without repeating code. Notice how push() and pop() protect the transform state—this ensures that translating and scaling the fish doesn't affect subsequent drawings in draw(). The isSleeping parameter shows how a single boolean flag can change the entire visual appearance and mood of the character.

🔬 This conditional draws either X eyes (sleeping) or a black circle (awake). What happens if you change strokeWeight(1) to strokeWeight(4) to make the X thicker? Or change circle(20, -5, 5) to circle(20, -5, 15) to make the awake eye huge?

  // 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
  }
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:

calculation Transform setup push(); translate(x, y); scale(direction * fishScale, fishScale);

Saves the current transform state, moves the origin to the fish's position, and flips it horizontally or scales it

calculation Fish body shapes ellipse(0, 0, 70, 40); triangle(-35, 0, -55, -15, -55, 15);

Draws the fish's oval body and triangular tail at the origin (now the fish's x, y position)

conditional Sleep vs. awake eyes if (isSleeping) {

Determines whether to draw X eyes (sleeping) or a black circle eye (awake)

push();
Saves the current canvas state (position, rotation, scale, fill, stroke, etc.) so we can restore it after drawing the fish.
translate(x, y);
Moves the origin (0,0) to the fish's position, so all subsequent drawing happens relative to the fish, not the canvas.
scale(direction * fishScale, fishScale);
Scales the fish by the fishScale factor; multiplying by direction (-1 or 1) flips it horizontally if needed.
noStroke();
Disables outlines for all shapes drawn next, so the fish body and tail are solid without borders.
fill(fishColor);
Sets the fill color to the fishColor passed in (usually orange for the main fish).
ellipse(0, 0, 70, 40);
Draws the fish's main body as an oval centered at (0, 0) relative to the fish's position—70 pixels wide, 40 tall.
triangle(-35, 0, -55, -15, -55, 15);
Draws a triangular tail pointing left, with vertices at the left side of the fish body.
if (isSleeping) {
Checks the isSleeping parameter to decide which eye style to draw.
stroke(0);
Sets the stroke color to black for drawing the X eyes.
line(eyeX - 2.5, eyeY - 2.5, eyeX + 2.5, eyeY + 2.5);
Draws one diagonal line of the X, from top-left to bottom-right relative to the eye position.
line(eyeX + 2.5, eyeY - 2.5, eyeX - 2.5, eyeY + 2.5);
Draws the other diagonal line of the X, from top-right to bottom-left, completing the X shape.
circle(20, -5, 5);
If not sleeping, draws a small black circle for the fish's open eye, positioned near the front of the head.
pop();
Restores the canvas state saved by push(), undoing the translate and scale so subsequent draws happen normally.

handleShooting()

handleShooting() is called only when the fish enters revenge mode (1 in 10 chance when it falls asleep). It showcases several advanced p5.js techniques: custom movement with normalized direction vectors, distance-based animation timing, and delayed message display. The bullet movement uses a classic pattern—calculate direction, normalize it, and multiply by speed—that you'll see in many creative coding sketches. The dramatic 90-frame delay before showing the message creates suspense, teaching that timing and delay can be as important as visuals in storytelling.

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 (12 lines)

🔧 Subcomponents:

calculation Fish with gun drawing push(); translate(currentFishX, fishY);

Draws the fish at its current position and adds a gun outline

conditional Bullet trajectory calculation if (distance > bullet.speed) {

Moves the bullet incrementally toward the target position, frame by frame

conditional Bullet impact detection } else {

When the bullet reaches its target, stops its movement and starts the Game Over message timer

conditional Game Over message display if (frameCount - gameOverStartTime > gameOverDelayFrames) {

After a delay, displays the dramatic 'YOU LET THE FISH DREAM TOO LONG...' message

background(0);
Fills the entire canvas with black, creating a dramatic Game Over atmosphere.
translate(currentFishX, fishY);
Moves the origin to the fish's position so the fish and gun are drawn relative to where the fish is.
ellipse(0, 0, 70, 40);
Draws the fish's body at the origin (now the fish's position), same size and style as normal.
line(eyeX_gun + 5, eyeY_gun + 5, eyeX_gun + 30, eyeY_gun);
Draws the gun barrel as a line extending from near the fish's eye to the right, slanting upward.
let dirX = bullet.targetX - bullet.x;
Calculates how far the bullet needs to travel horizontally to reach the target (mouse position).
let distance = dist(bullet.x, bullet.y, bullet.targetX, bullet.targetY);
Calculates the straight-line distance from the bullet to the target using the dist() function.
bullet.x += dirX / distance * bullet.speed;
Moves the bullet toward the target by dividing the direction by distance (normalizing) and multiplying by speed.
bullet = null;
Clears the bullet variable, signaling that it has hit and should stop moving.
gameOverStartTime = frameCount;
Records the current frame count as the moment the bullet hit, used to delay the Game Over message.
ellipse(bullet.x, bullet.y, 10, 10);
Draws the bullet as a red circle at its current position while it's flying toward the target.
if (frameCount - gameOverStartTime > gameOverDelayFrames) {
Waits 90 frames (1.5 seconds) after the bullet hits before showing the dramatic message.
text(gameOverMessage, width / 2, height / 2);
Displays the haunting 'YOU LET THE FISH DREAM TOO LONG...' message centered on the screen.

handleTalk()

handleTalk() bridges the gap between user input and the fish's response. It shows how to validate input (checking for empty strings), manage UI state (clearing the input field), and trigger animations (setting currentFishThought and thoughtDisplayStartTime). The function also resets the dream cycle, teaching an important lesson: interactivity should reset or interrupt state machines, keeping the experience fresh.

🔬 This function ignores the player's message and just responds with a random line. What happens if you add a line like console.log(userText) right after inputField.value("") to see what the player actually typed in the browser console (press F12 to open developer tools)?

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
}
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 Empty input check if (userText.trim() !== "") {

Ensures the user typed something before responding; ignores whitespace-only input

calculation Random response selection currentFishThought = random(fishResponses);

Picks a random response from the fishResponses array for the fish to 'say'

calculation State reset after talk displayedDreamsIndices = new Set();

Clears the dream history so the cycle restarts when you talk to the fish

let userText = inputField.value();
Retrieves whatever text the player typed into the input field.
if (userText.trim() !== "") {
Checks if the text is not empty (trim() removes leading/trailing whitespace); only proceeds if there's actual content.
currentFishThought = random(fishResponses);
Selects a random response from the fishResponses array ('That's deep.', 'I'm listening.', etc.) to display in the thought bubble.
thoughtDisplayStartTime = frameCount;
Records the current frame count so the response displays for exactly thoughtDisplayDuration frames (3 seconds).
displayedDreamsIndices = new Set();
Clears the dream history (empties the Set), so the fish restarts its dream cycle from the beginning.
bubbles = [];
Clears all floating bubbles, giving a clean slate when the fish responds.
inputField.value("");
Erases the text from the input field, preparing it for the player's next message.

mousePressed()

mousePressed() is the main event handler for user interaction. It must handle three completely different scenarios: restarting after Game Over, preventing double-clicks on UI elements, and feeding the fish. The function carefully uses early returns (return statements) to avoid executing code in the wrong state—this is called 'guard clause' pattern and keeps complex logic readable. The 1 in 10 chance for vengeance teaches probability and consequences: every action (feeding) carries a small risk, creating emotional stakes and replayability.

🔬 These lines reset the fish's state when you feed it. What happens if you remove the line frameCount = 0? The dreams won't reset, so the fish will keep cycling where it left off. Or what if you remove currentFishX = width / 2 so the fish doesn't jump back to center?

  // 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 });

  // NEW: 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 (13 lines)

🔧 Subcomponents:

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

Detects when the player clicks after the Game Over message and resets the entire sketch

conditional Sleep state check if (isFishSleeping) {

Prevents feeding or interaction if the fish is asleep (unless it's during the Game Over sequence)

conditional UI element click prevention if (inputField.elt === document.activeElement || submitButton.elt === document.activeElement || visitWebsiteButton.elt === document.activeElement) {

Ignores clicks on input field or buttons to prevent accidental feeding while typing

calculation Food spawning and state update foodParticles.push({ x: bx + random(-bowlR/3, bowlR/3), y: by - bowlR/2, r: 8, v: 0 });

Creates a new food particle at the top of the bowl with random x position

conditional Vengeance mode chance if (random(1) < (1/10)) {

1 in 10 chance to activate the shooting sequence when feeding the fish

if (isShootingPlayer && bullet === null && frameCount - gameOverStartTime > gameOverDelayFrames) {
Checks three conditions: is shooting mode active, has the bullet hit, and has the delay passed? If all true, player clicked to restart.
isShootingPlayer = false;
Exits shooting mode, returning to normal gameplay.
frameCount = 0;
Resets frameCount to 0, which resets the dream cycle and all timing-dependent behavior.
if (isFishSleeping) {
If the fish is sleeping and not in a Game Over sequence, exits immediately without doing anything.
if (inputField.elt === document.activeElement || submitButton.elt === document.activeElement || visitWebsiteButton.elt === document.activeElement) {
Checks if any UI element has focus (is currently active); if so, ignores the click to prevent accidental feeding while typing.
currentFishThought = "";
Clears any active response message, so the fish returns to displaying dreams.
currentFishX = width / 2;
Resets the fish's x-position to the center of the canvas for a fresh feeding moment.
isFed = true;
Marks the fish as fed, which prevents it from falling asleep for feedDuration frames.
fedTime = frameCount;
Records the current frameCount so we can calculate when the feeding period ends.
let bowlR=200, bx=width/2, by=height/2+60;
Re-calculates the bowl's position and size; matches the bowl drawn in draw().
foodParticles.push({ x: bx + random(-bowlR/3, bowlR/3), y: by - bowlR/2, r: 8, v: 0 });
Adds a new food particle object to the foodParticles array, with random x-position and initial y at the water surface.
if (random(1) < (1/10)) {
Has a 10% chance to trigger vengeance mode every time you feed the fish—a surprise penalty!
bullet = { x: currentFishX, y: fishY, targetX: mouseX, targetY: mouseY, speed: 15 };
Creates a bullet object with current fish position as starting point and the player's current mouse position as the target.

positionInputElements()

positionInputElements() is a utility function that calculates and applies positions for all UI elements. It's called from setup() and windowResized(), ensuring elements stay properly positioned even when the browser window resizes. The centering math (width - totalWidth) / 2 is a classic pattern for horizontal centering. Separating this into a helper function keeps setup() clean and makes it easy to adjust the layout in one place.

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 (11 lines)

🔧 Subcomponents:

calculation Button dimension variables let inputWidth = 200; let buttonWidth = 60; let padding = 10;

Defines the UI element widths and spacing constants

calculation Center positioning calculation let startX = (width - totalInputButtonWidth) / 2;

Calculates the x-position to center the input and button group

calculation Position element placement inputField.position(startX, yPosition);

Places the input field and button at calculated positions using p5.js position() method

let inputWidth = 200;
Defines the input field's width as 200 pixels.
let buttonWidth = 60;
Defines the 'Talk' button's width as 60 pixels.
let padding = 10;
Defines the spacing between elements (input field and button) as 10 pixels.
let totalInputButtonWidth = inputWidth + padding + buttonWidth;
Calculates the combined width of both elements plus the gap: 200 + 10 + 60 = 270 pixels.
let startX = (width - totalInputButtonWidth) / 2;
Calculates the x-position to center both elements horizontally on the canvas.
let yPosition = height - 50;
Positions both elements 50 pixels from the bottom of the window.
inputField.position(startX, yPosition);
Places the input field at the calculated centered x-position and yPosition.
submitButton.position(startX + inputWidth + padding, yPosition);
Places the button immediately to the right of the input field (offset by inputWidth + padding).
let visitButtonWidth = visitWebsiteButton.width;
Gets the actual rendered width of the 'Visit Website' button from the DOM.
let visitButtonX = width - visitButtonWidth - padding;
Positions the button flush against the right edge of the window with padding spacing.
visitWebsiteButton.position(visitButtonX, visitButtonY);
Places the 'Visit Website' button in the top-right corner at the calculated position.

handleVisitWebsite()

handleVisitWebsite() is a simple wrapper around the JavaScript window.open() function. It demonstrates how p5.js sketches can integrate with the broader web by triggering native browser actions. The '_blank' parameter ensures the link opens in a new tab, preserving the sketch. This pattern is useful for crediting artists, linking to source code, or driving traffic to related content.

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 using the native JavaScript window.open() method; '_blank' means the link opens in a new tab, not replacing the current page.

windowResized()

windowResized() is a special p5.js event handler that fires automatically whenever the browser window is resized. It's essential for responsive sketches that need to adapt to different screen sizes. Notice how it conditionally resets the fish only if it's awake—this preserves the emotional state during interaction. It's good practice to handle resizing early and often, rather than assuming a fixed screen size.

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 (5 lines)

🔧 Subcomponents:

calculation Canvas resize resizeCanvas(windowWidth,windowHeight);

Resizes the p5.js canvas to match the new window dimensions

conditional Conditional fish repositioning if (!isFishSleeping && !isShootingPlayer) {

Only resets fish position if it's awake and not in a Game Over sequence

resizeCanvas(windowWidth,windowHeight);
Resizes the p5.js canvas to match the current browser window size.
if (!isFishSleeping && !isShootingPlayer) {
Only repositions the fish if it's awake and not shooting; sleeping or Game Over fish stay put.
currentFishX = width / 2;
Resets the fish's x-position to the center of the newly resized canvas.
fishY = height/2+60 + 10;
Resets the fish's y-position to account for the new canvas height.
positionInputElements();
Recalculates and applies new positions for all UI elements based on the new window size.

📦 Key Variables

bubbles array

Stores active bubble objects that float upward from the fish. Each bubble has x, y, r (radius), and v (velocity) properties.

let bubbles = [];
dreams array

Stores the five thoughts the fish cycles through: 'The Ocean', 'Freedom', 'A Bigger Bowl', 'A girl fish', 'food'. These appear in the thought bubble.

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

Stores the randomized responses the fish gives when the player types a message ('That's deep.', 'I'm listening.', etc.).

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

Tracks whether the fish is asleep (true) or awake (false). When true, the fish draws X eyes instead of open eyes.

let isFishSleeping = false;
currentFishX number

Stores the fish's current x-position (horizontal) on the canvas.

let currentFishX = width / 2;
fishY number

Stores the fish's current y-position (vertical) on the canvas.

let fishY = height/2+60 + 10;
isFed boolean

Tracks whether the fish was recently fed (true) or is hungry (false). Prevents sleep while fed.

let isFed = false;
fedTime number

Stores the frameCount when the fish was last fed, used to calculate if the feeding period has expired.

let fedTime = -Infinity;
foodParticles array

Stores active food particle objects that fall into the bowl. Each has x, y, r (radius), and v (velocity) properties.

let foodParticles = [];
isShootingPlayer boolean

Tracks whether the fish is in revenge mode (true), causing the Game Over sequence to play.

let isShootingPlayer = false;
bullet object

Stores the bullet object's data (x, y, targetX, targetY, speed) while the fish is shooting at the player.

let bullet = null;
currentFishThought string

Stores the fish's current response to the player (e.g., 'That's deep.'). Empty when showing dreams.

let currentFishThought = "";
thoughtDisplayStartTime number

Stores the frameCount when currentFishThought was set, used to time when the response expires.

let thoughtDisplayStartTime = -Infinity;
displayedDreamsIndices object (Set)

A Set that tracks which dream indices (0-4) have been displayed in this cycle. Used to detect when all dreams are shown.

let displayedDreamsIndices = new Set();
inputField object (p5.Renderer)

The HTML input field element where the player types messages to the fish.

let inputField = createInput();
submitButton object (p5.Renderer)

The HTML button element labeled 'Talk' that the player clicks to submit a message.

let submitButton = createButton('Talk');
visitWebsiteButton object (p5.Renderer)

The HTML button element labeled 'Visit Corbun's Weird Webs' that opens an external website.

let visitWebsiteButton = createButton('Visit...');

🔧 Potential Improvements (7)

Here are some ways this code could be enhanced:

BUG draw() - Bubble boundary check

The bubble removal condition uses a hardcoded offset (by-bowlR/2-40) that might not match the exact top of the thought bubble, causing bubbles to sometimes linger visibly above the bowl.

💡 Define a constant for the thought bubble height (currently 90) and use it consistently: const THOUGHT_BUBBLE_HEIGHT = 90; then check if(b.y < by - bowlR/2 - THOUGHT_BUBBLE_HEIGHT) for precision.

PERFORMANCE draw() - Food particle loop

The food particle loop iterates backward (i--) correctly, but creating the bubble array and clearing bubbles = [] every frame creates unnecessary garbage collection pressure if many bubbles exist.

💡 Instead of bubbles = [], selectively remove just expired bubbles in the loop using splice. Only clear the entire array when resetting state (after feeding or talking).

STYLE Global variable declarations

Many variables are declared without clear comments explaining their purpose (e.g., bowlR is redefined in draw() instead of being a global constant).

💡 Define bowl parameters as constants at the top: const BOWL_RADIUS = 200; const BOWL_X_OFFSET = 0; const BOWL_Y_OFFSET = 60; This makes the code more maintainable and prevents magic numbers scattered throughout.

FEATURE mousePressed() - Food randomness

Food always spawns with randomness in x (within -bowlR/3 to bowlR/3) but always at the same y (top of bowl). The randomness feels incomplete.

💡 Add slight y-randomness too: y: by - bowlR/2 + random(-10, 10), or vary the food size and type (round vs. rectangular particles) to create visual variety.

BUG mousePressed() - Game Over restart logic

When restarting after Game Over, frameCount = 0 is set in JavaScript, but p5.js's frameCount is a read-only built-in variable that cannot be reset this way. This line has no effect.

💡 Instead of frameCount = 0, use a custom counter like let cycleFrameCount = 0; and replace all frameCount references in the dream cycling logic with cycleFrameCount. Then reset cycleFrameCount = 0; when restarting.

STYLE setup() - Button styling

Button styles are applied individually (submitButton.style(...), .style(...), etc.) which is verbose and makes global style changes difficult.

💡 Create a helper function like styleButton(button, bgColor) that applies consistent styling, then call it for each button: styleButton(submitButton, '#4CAF50'); This improves maintainability.

FEATURE handleTalk() - AI responses

Fish responses are always random and never acknowledge what the player typed. The interaction feels one-sided and doesn't reward careful message crafting.

💡 Add keyword matching: if(userText.toLowerCase().includes('food')) { currentFishThought = 'Mmm, food?'; } This creates the illusion of understanding and encourages player experimentation.

🔄 Code Flow

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

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

graph TD start[Start] --> setup[setup] setup --> canvas-init[canvas-init] setup --> input-field-creation[input-field-creation] setup --> button-creation[button-creation] setup --> positioninputelements[positionInputElements] setup --> windowresized[windowResized] setup --> draw[draw loop] click setup href "#fn-setup" click canvas-init href "#sub-canvas-init" click input-field-creation href "#sub-input-field-creation" click button-creation href "#sub-button-creation" click positioninputelements href "#fn-positioninputelements" click windowresized href "#fn-windowresized" draw --> shooting-check[shooting-check] draw --> fish-target-calc[fish-target-calc] draw --> food-priority[food-priority] draw --> bubble-generation[bubble-generation] draw --> thought-bubble-draw[thought-bubble-draw] draw --> thought-or-dream[thought-or-dream] draw --> bubble-update-loop[bubble-update-loop] draw --> sleep-trigger[sleep-trigger] draw --> fed-duration-check[fed-duration-check] draw --> food-particle-loop[food-particle-loop] draw --> drawfish[drawFish] click draw href "#fn-draw" click shooting-check href "#sub-shooting-check" click fish-target-calc href "#sub-fish-target-calc" click food-priority href "#sub-food-priority" click bubble-generation href "#sub-bubble-generation" click thought-bubble-draw href "#sub-thought-bubble-draw" click thought-or-dream href "#sub-thought-or-dream" click bubble-update-loop href "#sub-bubble-update-loop" click sleep-trigger href "#sub-sleep-trigger" click fed-duration-check href "#sub-fed-duration-check" click food-particle-loop href "#sub-food-particle-loop" click drawfish href "#fn-drawfish" drawfish --> push-translate-scale[push-translate-scale] drawfish --> body-drawing[body-drawing] drawfish --> sleeping-eye[sleeping-eye] drawfish --> fish-drawing[fish-drawing] click push-translate-scale href "#sub-push-translate-scale" click body-drawing href "#sub-body-drawing" click sleeping-eye href "#sub-sleeping-eye" click fish-drawing href "#sub-fish-drawing" mousepressed[mousePressed] --> text-validation[text-validation] mousepressed --> feeding-logic[feeding-logic] mousepressed --> game-over-reset[game-over-reset] mousepressed --> ui-element-check[ui-element-check] mousepressed --> vengeance-trigger[vengeance-trigger] click mousepressed href "#fn-mousepressed" click text-validation href "#sub-text-validation" click feeding-logic href "#sub-feeding-logic" click game-over-reset href "#sub-game-over-reset" click ui-element-check href "#sub-ui-element-check" click vengeance-trigger href "#sub-vengeance-trigger" handletalk[handleTalk] --> response-selection[response-selection] handletalk --> state-reset[state-reset] click handletalk href "#fn-handletalk" click response-selection href "#sub-response-selection" click state-reset href "#sub-state-reset" handleshooting[handleShooting] --> bullet-movement[bullet-movement] handleshooting --> bullet-hit[bullet-hit] handleshooting --> gameover-message[gameover-message] click handleshooting href "#fn-handleshooting" click bullet-movement href "#sub-bullet-movement" click bullet-hit href "#sub-bullet-hit" click gameover-message href "#sub-gameover-message" windowresized --> canvas-resize[canvas-resize] windowresized --> fish-reset-conditional[fish-reset-conditional] click canvas-resize href "#sub-canvas-resize" click fish-reset-conditional href "#sub-fish-reset-conditional"

❓ Frequently Asked Questions

What visual experience does the 'day dreaminj fish' sketch provide?

The sketch creates a dreamy and slightly surreal fish bowl environment where a sleepy fish drifts amidst floating thought bubbles, showcasing its whimsical dreams and moods.

How can users interact with the 'day dreaminj fish' sketch?

Users can type into a chat box to communicate with the fish, which responds with various thoughts, and they can also feed the fish to influence its mood.

What creative coding techniques are demonstrated in the 'day dreaminj fish' sketch?

This sketch showcases techniques such as managing state with variables, dynamic visual effects with floating bubbles, and user input handling for interactive storytelling.

Preview

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