UFO has cooldown like 100 seconds

This sketch is an idle clicker game where you tap a hot dog to make it grow, earn money, hire workers, and survive UFO attacks with a 100-second cooldown between attacks. The game ends when you survive for 3 minutes, combining casual gameplay with mini survival challenges.

🧪 Try This!

Experiment with the code by making these changes:

  1. Make UFO attacks happen twice as often — Change the UFO attack probability from 5% to 10% per tap, so you'll see more attack minigames.
  2. Make the hot dog grow twice as fast
  3. Earn double money from pooping — Change the poop money reward from 5 * clickPower to 10 * clickPower so upgrades come faster.
  4. Make workers feed you faster — Increase passive fullness generation from 0.005 to 0.02 per worker per frame—workers become much more valuable.
  5. Win in 1 minute instead of 3 — Change the win condition from 180,000ms (3 minutes) to 60,000ms (1 minute) for a shorter, faster-paced game.
  6. Paint the house red instead of brown — The house body is RGB(150, 75, 0) brown. Change it to bright red RGB(255, 0, 0).
Prefer the full editor? Open it there →

📖 About This Sketch

This is a complete idle clicker game built in p5.js that teaches you how to build multi-scene games with complex state management. You tap a hot dog to feed yourself, earn money from pooping, buy upgrades that increase your click power and hire workers who feed you passively. Randomly, a UFO attacks and forces you into a survival minigame where you must tap an exit door within 5 seconds—if you survive three UFO attacks spaced 100 seconds apart, you win. The game demonstrates scene switching using a state machine, cooldown timers, touch interaction handling, and the draw loop as a game engine.

The code is organized into distinct scene-drawing functions (drawHouseScene, drawEatingScene, drawShopScene, drawUFOAttackScene, drawWinScene), corresponding interaction handlers (handleHouseInteraction, handleEatingInteraction, etc.), and a central draw loop that switches scenes based on game state. By studying it, you will learn how professional games organize code into scenes, how to implement cooldowns and timers, how to detect button taps using collision bounds, and how to track game progression across multiple states.

⚙️ How It Works

  1. When the sketch loads, setup() creates a full-window canvas and initializes all game variables including money, fullness, the UFO cooldown timer, and the win condition timer set to zero.
  2. The draw loop runs 60 times per second and uses a switch statement to call the appropriate scene-drawing function based on the current sceneState variable.
  3. In the HOUSE_SCENE, a brown house with a red roof and an animated door is drawn. Tapping the door opens it; tapping again enters the eating scene and starts the win timer.
  4. In the EATING_SCENE, a growing hot dog with ketchup and mustard is displayed, along with a money counter, a shop button, and a row of stick-figure workers. Tapping the hot dog increases fullness; when fullness reaches 50, a poop animation plays and money is earned based on click power.
  5. Passive fullness generation happens if workers are hired—each worker adds a tiny amount every frame. A 5% random chance per tap triggers a UFO attack, but only if the 100-second cooldown has elapsed.
  6. In the UFO_ATTACK_SCENE, a blue UFO floats above the screen while a timer counts down from 5 seconds. The player must tap a randomly-placed green EXIT door; success returns to eating with cooldown active, but running out of time ends the game.
  7. In the SHOP_SCENE, buttons let you upgrade click power (costs increase exponentially) or hire workers (also increasing exponentially). The WINNING condition triggers after 3 minutes in the eating scene, displaying time taken and a restart button.

🎓 Concepts You'll Learn

Scene management and state machinesCooldown timers and millisecond trackingCollision detection for button tapsExponential upgrade costsPassive income generationRandom event probabilityAnimation with sine wavesGame over and win conditions

📝 Code Breakdown

setup()

setup() runs once when the sketch loads. It is the perfect place to initialize canvas size, text style, drawing modes, and game variables. Notice how we set rectMode(CENTER) here, which affects ALL rectangles drawn in the sketch—a single decision in setup makes button code cleaner everywhere.

function setup() {
  createCanvas(windowWidth, windowHeight);
  textAlign(CENTER, CENTER);
  textSize(24);
  noStroke(); // For the poop shapes and door
  rectMode(CENTER); // Draw rectangles from their center by default (for hot dog and buttons)

  // Initialize cooldown timer so the first attack is available after the cooldown
  ufoCooldownTimer = 0;
  eatingSceneStartTime = 0; // Ensure win timer is reset at start
}
Line-by-line explanation (7 lines)
createCanvas(windowWidth, windowHeight);
Creates a canvas that fills the entire browser window, making the game responsive to window size.
textAlign(CENTER, CENTER);
Centers all text both horizontally and vertically, so text appears centered at the x,y coordinates you give it.
textSize(24);
Sets the default font size for all text to 24 pixels.
noStroke();
Disables the stroke (outline) on all shapes, so only filled shapes are drawn.
rectMode(CENTER);
Changes rectangle drawing mode so that rect(x, y, w, h) draws a rectangle CENTERED at (x,y) instead of from the top-left corner—this makes button positioning easier.
ufoCooldownTimer = 0;
Initializes the UFO cooldown timer to 0, allowing an attack to trigger immediately after the cooldown duration passes.
eatingSceneStartTime = 0;
Resets the win timer tracker so it will start counting when you first enter the eating scene.

draw()

The draw loop is the engine of every p5.js sketch—it runs 60 times per second by default. This game uses draw() to call the right scene-drawing function based on sceneState, making draw() act like a traffic controller. This pattern (a switch statement routing to scene functions) is how professional games organize code into levels or screens.

🔬 This switch statement is the game's 'brain'—it decides what to draw based on the scene. What happens if you swap the order of HOUSE_SCENE and EATING_SCENE so eating draws first?

  switch (sceneState) {
    case HOUSE_SCENE:
      drawHouseScene();
      break;
    case EATING_SCENE:
      drawEatingScene();
      break;
function draw() {
  background(220); // Light grey background

  switch (sceneState) {
    case HOUSE_SCENE:
      drawHouseScene();
      break;
    case EATING_SCENE:
      drawEatingScene();
      break;
    case SHOP_SCENE: // Draw the shop scene
      drawShopScene();
      break;
    case UFO_ATTACK_SCENE: // Draw the UFO attack scene
      drawUFOAttackScene();
      break;
    case WIN_SCENE: // NEW: Draw the win scene
      drawWinScene();
      break;
  }
}
Line-by-line explanation (7 lines)

🔧 Subcomponents:

switch-case Scene State Switch switch (sceneState) { case HOUSE_SCENE: drawHouseScene(); break; ... }

Calls the correct drawing function based on which scene the game is currently in—this is the heart of the state machine that controls the entire game flow.

background(220);
Clears the canvas every frame with a light grey color (RGB 220, 220, 220). Without this line, all drawings would layer on top of each other and you would see trails.
switch (sceneState) {
Begins a switch statement that checks the value of sceneState and runs different code for each scene (HOUSE, EATING, SHOP, UFO_ATTACK, WIN).
case HOUSE_SCENE: drawHouseScene(); break;
If sceneState equals HOUSE_SCENE (value 0), call the drawHouseScene() function to draw the house, then break out of the switch.
case EATING_SCENE: drawEatingScene(); break;
If sceneState equals EATING_SCENE (value 1), call drawEatingScene() to draw the hot dog, money, and workers.
case SHOP_SCENE: drawShopScene(); break;
If sceneState equals SHOP_SCENE (value 2), call drawShopScene() to draw upgrade buttons.
case UFO_ATTACK_SCENE: drawUFOAttackScene(); break;
If sceneState equals UFO_ATTACK_SCENE (value 3), call drawUFOAttackScene() to draw the UFO and escape door.
case WIN_SCENE: drawWinScene(); break;
If sceneState equals WIN_SCENE (value 4), call drawWinScene() to display the victory screen.

drawHouseScene()

This function demonstrates several important p5.js techniques: scalable geometry (using percentages of width/height), push/pop for isolating transformations, translate and rotate for hinged door animation, and lerp for smooth animation. The nested push/pop calls are crucial—the first pair isolates the rectMode change, the second pair isolates the door's translate/rotate, ensuring the door swings around its hinge without affecting other shapes.

🔬 This code animates the door swing by interpolating (lerping) the angle. The 0.1 is how fast it swings—what happens if you change 0.1 to 0.01 (slower) or 0.3 (faster)?

  if (doorOpen) {
    doorAngle = lerp(doorAngle, doorOpenAngle, 0.1);
  } else {
    doorAngle = lerp(doorAngle, 0, 0.1);
  }
function drawHouseScene() {
  fill(0);
  text("Tap the door to open it!", width / 2, height / 4);

  // Define house dimensions
  let houseWidth = width * 0.4;
  let houseHeight = height * 0.4;
  let houseX = width / 2 - houseWidth / 2; // Top-left X for house body
  let houseY = height / 2 - houseHeight / 2; // Top-left Y for house body

  push(); // Isolate rectMode change for house body and door
  rectMode(CORNER); // Draw house body and door from top-left corner

  // Draw house body (rectangle)
  fill(150, 75, 0); // Brown
  rect(houseX, houseY, houseWidth, houseHeight);

  // Draw roof (triangle) - triangle coordinates are always CORNER mode, so this is fine.
  fill(200, 0, 0); // Red
  triangle(
    houseX, houseY,
    houseX + houseWidth, houseY,
    houseX + houseWidth / 2, houseY - houseHeight * 0.3
  );

  // Define door dimensions
  let doorWidth = houseWidth * 0.2;
  let doorHeight = houseHeight * 0.5;
  let doorX = houseX + houseWidth * 0.4; // Top-left X for door
  let doorY = houseY + houseHeight - doorHeight; // Top-left Y for door

  // Animate door opening/closing
  if (doorOpen) {
    doorAngle = lerp(doorAngle, doorOpenAngle, 0.1);
  } else {
    doorAngle = lerp(doorAngle, 0, 0.1);
  }

  // Draw door
  push(); // Isolate translate/rotate for the door
  translate(doorX, doorY); // Translate origin to the door's hinge point (top-left)
  rotate(doorAngle);
  fill(100, 50, 0); // Darker brown for door
  rect(0, 0, doorWidth, doorHeight); // Draw door relative to hinge, using rectMode(CORNER)
  // Draw doorknob
  fill(255, 200, 0); // Yellow
  ellipse(doorWidth * 0.8, doorHeight * 0.5, 10, 10); // ellipse is always CENTER mode, so this is fine
  pop(); // Restore previous transformation (origin back to doorX, doorY)

  pop(); // Restore rectMode to CENTER (from setup())

  if (doorOpen && abs(doorAngle - doorOpenAngle) < 0.05) { // Check if door is almost fully open
    fill(0);
    text("Welcome inside! Tap again to enter.", width / 2, height / 2 + houseHeight / 2 + 50);
  }
}
Line-by-line explanation (28 lines)

🔧 Subcomponents:

conditional Door Opening Animation if (doorOpen) { doorAngle = lerp(doorAngle, doorOpenAngle, 0.1); } else { doorAngle = lerp(doorAngle, 0, 0.1); }

Smoothly interpolates the door rotation angle—if doorOpen is true, lerp gradually moves doorAngle toward 90 degrees; otherwise it lerps back toward 0. This creates a smooth swing animation without jumping.

calculation House Geometry let houseWidth = width * 0.4; let houseHeight = height * 0.4;

Calculates the house size as a percentage of window dimensions so it scales responsively to different screen sizes.

fill(0);
Sets the fill color to black (RGB 0, 0, 0) for the instruction text.
text("Tap the door to open it!", width / 2, height / 4);
Draws the instruction text centered horizontally and 1/4 way down the screen.
let houseWidth = width * 0.4;
Calculates the house width as 40% of the canvas width, making it scale with the window.
let houseHeight = height * 0.4;
Calculates the house height as 40% of the canvas height for consistent proportions.
let houseX = width / 2 - houseWidth / 2;
Positions the house horizontally centered by subtracting half the house width from the canvas center.
let houseY = height / 2 - houseHeight / 2;
Positions the house vertically centered using the same centering math.
push();
Saves the current drawing state (transformations, fill color, etc.) so changes inside can be undone later with pop().
rectMode(CORNER);
Temporarily changes rectangle drawing mode to CORNER, so rect() positions from top-left instead of center (different from setup's rectMode(CENTER)).
fill(150, 75, 0);
Sets fill to brown (RGB 150, 75, 0) for the house body.
rect(houseX, houseY, houseWidth, houseHeight);
Draws the house body as a brown rectangle at the calculated position and size.
fill(200, 0, 0);
Changes fill to red (RGB 200, 0, 0) for the roof.
triangle(houseX, houseY, houseX + houseWidth, houseY, houseX + houseWidth / 2, houseY - houseHeight * 0.3);
Draws a red triangle roof with three corners: top-left and top-right of the house body, and a peak 30% of house height above the house.
let doorWidth = houseWidth * 0.2;
Calculates door width as 20% of the house width so it looks proportional.
let doorHeight = houseHeight * 0.5;
Calculates door height as 50% of house height—a tall, thin door.
let doorX = houseX + houseWidth * 0.4;
Positions door's left edge 40% across the house width (slightly left of center).
let doorY = houseY + houseHeight - doorHeight;
Positions door's top edge so the door sits at the bottom of the house (like a front door).
if (doorOpen) { doorAngle = lerp(doorAngle, doorOpenAngle, 0.1); }
If the door is open, smoothly rotate toward 90 degrees by 10% each frame using lerp (linear interpolation).
else { doorAngle = lerp(doorAngle, 0, 0.1); }
If the door is closed, smoothly rotate back to 0 degrees (closed position) by 10% each frame.
push();
Saves drawing state again before applying translate and rotate transformations to the door.
translate(doorX, doorY);
Moves the origin (0,0) to the door's hinge point (top-left corner) so the door rotates around that corner.
rotate(doorAngle);
Rotates around the new origin (the hinge) by doorAngle radians, creating the swing effect.
fill(100, 50, 0);
Sets fill to dark brown (RGB 100, 50, 0) for the door itself.
rect(0, 0, doorWidth, doorHeight);
Draws the door rectangle at the hinge point (0,0) with rectMode(CORNER), so it extends right and down from the hinge.
fill(255, 200, 0);
Changes fill to yellow (RGB 255, 200, 0) for the doorknob.
ellipse(doorWidth * 0.8, doorHeight * 0.5, 10, 10);
Draws a small yellow circle 80% across the door width and 50% down the door height (the knob).
pop();
Restores the drawing state, undoing the translate and rotate transformations.
pop();
Restores rectMode back to CENTER (from setup) so buttons and other shapes stay centered.
if (doorOpen && abs(doorAngle - doorOpenAngle) < 0.05)
Checks if the door is open AND almost fully rotated (within 0.05 radians of 90 degrees), then shows the next instruction.

drawEatingScene()

drawEatingScene is the heart of the game loop. It demonstrates several critical techniques: checking elapsed time with millis() for win conditions, using map() to link a game variable (fullness) to visual output (hot dog length), passive income generation via loop iterations, array management (iterating and splicing), and hierarchical drawing (drawing multiple worker shapes in a loop). The nested loop structure for drawing poop trails shows how to manage collections of complex objects.

🔬 This loop draws poop trails as many small ellipses. The poop.trail array has 20 segments by default. What happens if you change ellipse(p.x, p.y, p.size, p.size) to ellipse(p.x, p.y, p.size * 2, p.size * 2) to make them twice as large?

  for (let i = 0; i < poopHistory.length; i++) {
    let poop = poopHistory[i];
    fill(poop.color);
    // Draw a trail of small ellipses
    for (let j = 0; j < poop.trail.length; j++) {
      let p = poop.trail[j];
      ellipse(p.x, p.y, p.size, p.size);
    }
function drawEatingScene() {
  // NEW: Check for win condition
  if (eatingSceneStartTime > 0) {
    let elapsedTime = millis() - eatingSceneStartTime;
    if (elapsedTime >= targetWinDuration) {
      winTimeTaken = elapsedTime; // Store the time taken to win
      sceneState = WIN_SCENE;
      return; // Exit draw to immediately show win screen
    }
  }

  // Display instructions
  fill(0);
  text("Tap to 'eat' 🌭", width / 2, height / 4);

  // Display hotdog count
  fill(0);
  text(`Hotdogs Eaten: ${hotDogCount}`, width / 2, height / 4 - 40);

  // Display money
  fill(0);
  text(`Money: $${money}`, width / 2, height / 8);

  // Shop Button
  let shopButtonWidth = 100;
  let shopButtonHeight = 40;
  let shopButtonX = width - shopButtonWidth / 2 - 20; // Top-right position
  let shopButtonY = height / 8;
  fill(100, 100, 255); // Blue
  rect(shopButtonX, shopButtonY, shopButtonWidth, shopButtonHeight, 10);
  fill(255);
  text("Shop", shopButtonX, shopButtonY);

  // Draw the "hot dog"
  let hotDogLength = map(fullness, 0, maxFullness, 50, 200);
  let hotDogWidth = 30; // Fixed width for the hot dog
  let hotDogY = height / 2;
  let hotDogCenterX = width / 2;

  // Draw the bun
  fill(255, 220, 150); // Light brown for the bun
  rect(hotDogCenterX, hotDogY, hotDogLength + 20, hotDogWidth + 10, 10); // Rounded rectangle for bun
  // Draw buns ends
  ellipse(hotDogCenterX - (hotDogLength / 2 + 10), hotDogY, hotDogWidth + 10, hotDogWidth + 10);
  ellipse(hotDogCenterX + (hotDogLength / 2 + 10), hotDogY, hotDogWidth + 10, hotDogWidth + 10);

  // Draw the actual hot dog
  fill(160, 82, 45); // Sausage brown color
  rect(hotDogCenterX, hotDogY, hotDogLength, hotDogWidth, hotDogWidth / 2); // Rounded rectangle for hot dog

  // --- Add Ketchup and Mustard ---
  let condimentYOffset = hotDogY - hotDogWidth / 2 + 5; // Draw slightly above the hot dog
  let condimentWidth = 5; // Thickness of the condiment lines
  let halfDogLength = hotDogLength / 2;

  // Ketchup (Red)
  fill(255, 0, 0);
  beginShape();
  curveVertex(hotDogCenterX - halfDogLength, condimentYOffset); // Control point (off-screen left)
  curveVertex(hotDogCenterX - halfDogLength, condimentYOffset); // Start point
  curveVertex(hotDogCenterX - halfDogLength * 0.8, condimentYOffset + condimentWidth * 0.5);
  curveVertex(hotDogCenterX - halfDogLength * 0.5, condimentYOffset - condimentWidth * 0.5);
  curveVertex(hotDogCenterX - halfDogLength * 0.2, condimentYOffset + condimentWidth * 0.5);
  curveVertex(hotDogCenterX, condimentYOffset - condimentWidth * 0.5);
  curveVertex(hotDogCenterX + halfDogLength * 0.2, condimentYOffset + condimentWidth * 0.5);
  curveVertex(hotDogCenterX + halfDogLength * 0.5, condimentYOffset - condimentWidth * 0.5);
  curveVertex(hotDogCenterX + halfDogLength * 0.8, condimentYOffset + condimentWidth * 0.5);
  curveVertex(hotDogCenterX + halfDogLength, condimentYOffset); // End point
  curveVertex(hotDogCenterX + halfDogLength, condimentYOffset); // Control point (off-screen right)
  endShape();

  // Mustard (Yellow)
  fill(255, 200, 0);
  beginShape();
  curveVertex(hotDogCenterX - halfDogLength, condimentYOffset + condimentWidth * 1.5); // Control point (off-screen left)
  curveVertex(hotDogCenterX - halfDogLength, condimentYOffset + condimentWidth * 1.5); // Start point
  curveVertex(hotDogCenterX - halfDogLength * 0.7, condimentYOffset + condimentWidth * 2);
  curveVertex(hotDogCenterX - halfDogLength * 0.4, condimentYOffset + condimentWidth * 1);
  curveVertex(hotDogCenterX, condimentYOffset + condimentWidth * 2);
  curveVertex(hotDogCenterX + halfDogLength * 0.4, condimentYOffset + condimentWidth * 1);
  curveVertex(hotDogCenterX + halfDogLength * 0.7, condimentYOffset + condimentWidth * 2);
  curveVertex(hotDogCenterX + halfDogLength, condimentYOffset + condimentWidth * 1.5); // End point
  curveVertex(hotDogCenterX + halfDogLength, condimentYOffset + condimentWidth * 1.5); // Control point (off-screen right)
  endShape();
  // --- End Ketchup and Mustard ---


  // Display fullness level
  fill(0);
  text(`Fullness: ${floor(fullness)} / ${maxFullness}`, hotDogCenterX, hotDogY + hotDogWidth / 2 + 50);

  // Passive fullness generation if people are hired
  if (peopleCount > 0) {
    updateFullness(peopleCount * 0.005); // 0.005 fullness per person per frame
  }

  // Display people count and draw them
  fill(0);
  text(`People: ${peopleCount}`, width / 2, height * 0.75 - 30); // Text above the people

  let startX = width / 2 - (peopleCount - 1) * 20; // Center the people
  for (let i = 0; i < peopleCount; i++) {
    drawPerson(startX + i * 40, height * 0.75); // Draw each person in a row
  }

  // Draw the "poop" trails
  for (let i = 0; i < poopHistory.length; i++) {
    let poop = poopHistory[i];
    fill(poop.color);
    // Draw a trail of small ellipses
    for (let j = 0; j < poop.trail.length; j++) {
      let p = poop.trail[j];
      ellipse(p.x, p.y, p.size, p.size);
    }
    // Fade out the poop over time
    poop.alpha -= 5;
    if (poop.alpha <= 0) {
      poopHistory.splice(i, 1); // Remove faded poop
      i--;
    }
  }

  // Display UFO cooldown
  if (!ufoActive && millis() - ufoCooldownTimer < ufoCooldownDuration) {
    let cooldownLeft = floor((ufoCooldownDuration - (millis() - ufoCooldownTimer)) / 1000);
    fill(255, 100, 0); // Orange
    textSize(20);
    text(`UFO Cooldown: ${cooldownLeft}s`, width / 2, height * 0.9);
    textSize(24); // Restore default textSize
  }
}
Line-by-line explanation (26 lines)

🔧 Subcomponents:

conditional Win Condition Timer if (elapsedTime >= targetWinDuration) { winTimeTaken = elapsedTime; sceneState = WIN_SCENE; return; }

Checks if 3 minutes have elapsed since entering the eating scene. If so, saves the elapsed time, switches to the win scene, and exits early.

calculation Hot Dog Length Animation let hotDogLength = map(fullness, 0, maxFullness, 50, 200);

Uses map() to scale the hot dog's visual length proportionally to fullness—from 50 pixels (empty) to 200 pixels (full). This creates the illusion of the hot dog growing as you feed.

conditional Passive Fullness Generation if (peopleCount > 0) { updateFullness(peopleCount * 0.005); }

If workers are hired, automatically increases fullness every frame by a small amount per worker, simulating passive income.

for-loop Poop History Rendering and Fading for (let i = 0; i < poopHistory.length; i++) { ... poop.alpha -= 5; if (poop.alpha <= 0) { poopHistory.splice(i, 1); i--; } }

Loops through all poop trails, draws them as fading ellipse trails, and removes them from the array when they become fully transparent.

if (eatingSceneStartTime > 0) {
Checks if the eating scene timer has been initialized (not 0). If true, the win condition checking proceeds.
let elapsedTime = millis() - eatingSceneStartTime;
Calculates how many milliseconds have passed since eatingSceneStartTime was set (when entering the eating scene).
if (elapsedTime >= targetWinDuration) {
If elapsed time is at least 3 minutes (180,000 milliseconds), the player has won.
winTimeTaken = elapsedTime;
Stores the actual time taken so the win screen can display it.
sceneState = WIN_SCENE; return;
Changes to the win scene and returns early, skipping the rest of the eating scene drawing.
fill(0); text("Tap to 'eat' 🌭", width / 2, height / 4);
Sets fill to black and displays the instruction text at the top of the screen.
text(`Hotdogs Eaten: ${hotDogCount}`, width / 2, height / 4 - 40);
Displays a counter showing how many full hot dogs (poops) have been completed.
text(`Money: $${money}`, width / 2, height / 8);
Displays the current money balance.
let shopButtonX = width - shopButtonWidth / 2 - 20;
Positions the shop button 20 pixels from the right edge of the canvas (top-right corner).
let hotDogLength = map(fullness, 0, maxFullness, 50, 200);
Uses map() to scale fullness (0 to maxFullness) into visual length (50 to 200 pixels). This makes the hot dog grow as you eat.
let hotDogY = height / 2;
Positions the hot dog vertically at the canvas center.
fill(255, 220, 150); // Light brown for the bun
Sets fill to a light brown color for the bread.
rect(hotDogCenterX, hotDogY, hotDogLength + 20, hotDogWidth + 10, 10);
Draws the bun as a rounded rectangle slightly larger than the sausage, centered at the hot dog position.
ellipse(hotDogCenterX - (hotDogLength / 2 + 10), hotDogY, hotDogWidth + 10, hotDogWidth + 10);
Draws a circular end cap on the left side of the bun to create the rounded bread effect.
fill(160, 82, 45);
Changes fill to a sausage brown color.
rect(hotDogCenterX, hotDogY, hotDogLength, hotDogWidth, hotDogWidth / 2);
Draws the actual sausage as a rounded rectangle inside the bun, with width equal to hotDogWidth.
fill(255, 0, 0); beginShape(); curveVertex(...); ... endShape();
Draws a wavy red ketchup line across the hot dog using curveVertex to create smooth curves.
fill(255, 200, 0); beginShape(); curveVertex(...); ... endShape();
Draws a wavy yellow mustard line below the ketchup using the same technique.
if (peopleCount > 0) { updateFullness(peopleCount * 0.005); }
If any workers are hired, add a small amount of fullness every frame (passive income). Multiply by peopleCount so more workers = faster filling.
let startX = width / 2 - (peopleCount - 1) * 20;
Calculates the starting X position to center all workers horizontally. With 3 workers, they're spaced 40 pixels apart and centered as a group.
for (let i = 0; i < peopleCount; i++) { drawPerson(startX + i * 40, height * 0.75); }
Loops through each hired worker and draws them in a row, spaced 40 pixels apart.
for (let i = 0; i < poopHistory.length; i++) {
Loops through the array of poop trails that exist (each poop spawned when fullness maxed out).
fill(poop.color); for (let j = 0; j < poop.trail.length; j++) { ellipse(p.x, p.y, p.size, p.size); }
For each poop, sets its color and draws every point in its trail as a small ellipse, creating a path effect.
poop.alpha -= 5; if (poop.alpha <= 0) { poopHistory.splice(i, 1); i--; }
Decreases the poop's alpha (transparency) by 5 each frame. When it reaches 0 or below, remove it from the array using splice and decrement i so we don't skip the next poop.
if (!ufoActive && millis() - ufoCooldownTimer < ufoCooldownDuration) {
If a UFO is not currently active AND the cooldown timer hasn't elapsed yet, show the countdown.
let cooldownLeft = floor((ufoCooldownDuration - (millis() - ufoCooldownTimer)) / 1000);
Calculates seconds remaining: subtract elapsed time from total cooldown duration and convert milliseconds to seconds.

drawPerson()

drawPerson is a helper function called from drawEatingScene for each hired worker. It shows how to create simple reusable shapes using relative positioning—all coordinates are offset from the (x, y) passed in, so you can draw the same person anywhere just by calling drawPerson(100, 200) or drawPerson(500, 300). This is a pattern called 'encapsulation'—hiding complexity (the stick figure details) behind a simple function interface.

// Function to draw a simple stick figure person
function drawPerson(x, y) {
  fill(0); // Black for stick figure

  // Head
  ellipse(x, y - 20, 20, 20);

  // Body
  line(x, y - 10, x, y + 10);

  // Arms
  line(x - 10, y - 5, x + 10, y - 5);

  // Legs
  line(x, y + 10, x - 10, y + 25);
  line(x, y + 10, x + 10, y + 25);
}
Line-by-line explanation (6 lines)
fill(0);
Sets fill to black for all stick figure parts.
ellipse(x, y - 20, 20, 20);
Draws a circle (head) centered 20 pixels above the base position (y - 20), with diameter 20 pixels.
line(x, y - 10, x, y + 10);
Draws a vertical line (body) from 10 pixels above y to 10 pixels below y, all at x position.
line(x - 10, y - 5, x + 10, y - 5);
Draws a horizontal line (arms) from 10 pixels left to 10 pixels right, at y - 5 (upper body area).
line(x, y + 10, x - 10, y + 25);
Draws the left leg as a line from the hips (y + 10) down and to the left (x - 10).
line(x, y + 10, x + 10, y + 25);
Draws the right leg as a line from the hips down and to the right (x + 10).

drawShopScene()

drawShopScene demonstrates clean UI layout: information at the top (money, current stats), actionable buttons in the middle (with distinct colors for quick scanning), and a back button at the bottom. Notice how each button is positioned using percentages of height and width—this makes the layout responsive to different screen sizes. The use of template literals (backticks and ${}) in text makes displaying game variables cleaner and easier to read.

function drawShopScene() {
  fill(0);
  text("Shop", width / 2, height / 8);
  text(`Money: $${money}`, width / 2, height / 4);
  text(`Current Click Power: ${clickPower}`, width / 2, height / 4 + 40);
  text(`Next Clicker Upgrade Cost: $${upgradeCost}`, width / 2, height / 4 + 80);

  // Upgrade Clickers Button
  let upgradeButtonWidth = 200;
  let upgradeButtonHeight = 50;
  let upgradeButtonX = width / 2; // Centered X
  let upgradeButtonY = height / 2 - 40; // Shift up slightly to make space
  fill(50, 200, 50); // Green
  rect(upgradeButtonX, upgradeButtonY, upgradeButtonWidth, upgradeButtonHeight, 10);
  fill(255);
  text("Upgrade Clickers", upgradeButtonX, upgradeButtonY);

  // Hire People Button
  let peopleButtonWidth = 200;
  let peopleButtonHeight = 50;
  let peopleButtonX = width / 2; // Centered X
  let peopleButtonY = height / 2 + 40; // Position below clicker upgrade
  fill(100, 150, 255); // Blue
  rect(peopleButtonX, peopleButtonY, peopleButtonWidth, peopleButtonHeight, 10);
  fill(255);
  text(`Hire People (${peopleCount})`, peopleButtonX, peopleButtonY - 10);
  fill(255);
  textSize(16);
  text(`Cost: $${peopleUpgradeCost}`, peopleButtonX, peopleButtonY + 15);
  textSize(24); // Restore default textSize

  // Back to Eating Button
  let backButtonWidth = 150;
  let backButtonHeight = 40;
  let backButtonX = width / 2; // Centered X
  let backButtonY = height * 0.75 + 40; // Lower Y, shifted down
  fill(200);
  rect(backButtonX, backButtonY, backButtonWidth, backButtonHeight, 10);
  fill(0);
  text("Back to Eating", backButtonX, backButtonY);
}
Line-by-line explanation (17 lines)

🔧 Subcomponents:

calculation Shop Information Display text(`Money: $${money}`, width / 2, height / 4); text(`Current Click Power: ${clickPower}`, width / 2, height / 4 + 40);

Shows the player's current money, click power, and upgrade cost so they can make informed purchasing decisions.

fill(0); text("Shop", width / 2, height / 8);
Displays the shop title in black at the top of the screen.
text(`Money: $${money}`, width / 2, height / 4);
Shows the player's current money balance using template literals to embed the money variable.
text(`Current Click Power: ${clickPower}`, width / 2, height / 4 + 40);
Displays the current click power so the player knows their tap strength.
text(`Next Clicker Upgrade Cost: $${upgradeCost}`, width / 2, height / 4 + 80);
Shows the cost of the next upgrade so the player knows how much money to save.
let upgradeButtonX = width / 2;
Centers the upgrade button horizontally on the canvas.
let upgradeButtonY = height / 2 - 40;
Positions the upgrade button slightly above the vertical center to make room for the people button below.
fill(50, 200, 50); // Green
Sets the upgrade button fill to green.
rect(upgradeButtonX, upgradeButtonY, upgradeButtonWidth, upgradeButtonHeight, 10);
Draws the green upgrade button with rounded corners (radius 10).
fill(255); text("Upgrade Clickers", upgradeButtonX, upgradeButtonY);
Changes fill to white and draws button text centered on the button.
let peopleButtonY = height / 2 + 40;
Positions the people button slightly below center, 80 pixels below the upgrade button.
fill(100, 150, 255); // Blue
Sets the people button fill to blue to distinguish it visually from the upgrade button.
text(`Hire People (${peopleCount})`, peopleButtonX, peopleButtonY - 10);
Displays the button text slightly above the button center, showing how many people are currently hired.
textSize(16); text(`Cost: $${peopleUpgradeCost}`, peopleButtonX, peopleButtonY + 15); textSize(24);
Temporarily reduces text size to 16 to display the cost in smaller print below the button, then restores size to 24.
let backButtonX = width / 2;
Centers the back button horizontally.
let backButtonY = height * 0.75 + 40;
Positions the back button near the bottom of the screen.
fill(200); rect(backButtonX, backButtonY, backButtonWidth, backButtonHeight, 10);
Draws the back button in light grey.
fill(0); text("Back to Eating", backButtonX, backButtonY);
Draws the button text in black on the grey button.

drawUFOAttackScene()

drawUFOAttackScene demonstrates animation with sine waves, time-based countdown logic, and timeout handling. The sine wave creates smooth organic motion without explicit frame-by-frame tweaking. The countdown shows how to convert milliseconds to human-readable seconds. The setTimeout pattern shows how to delay code execution in JavaScript—crucial for game flow control like showing a message before resetting.

🔬 The UFO is drawn as two overlapping ellipses. What happens if you change 200 to 300 and 80 to 120? (Try making it wider and taller.)

  fill(150, 150, 200, 200); // Semi-transparent blue-grey
  ellipse(0, 0, 200, 80); // Main body
  ellipse(0, -40, 150, 50); // Top dome
function drawUFOAttackScene() {
  fill(0);
  text("UFO Attack! Find the exit door!", width / 2, height / 8);

  // Countdown timer
  let timeLeft = floor((ufoAttackDuration - (millis() - ufoAttackTimer)) / 1000);
  fill(255, 0, 0);
  textSize(36);
  text(`Escape in: ${timeLeft}s`, width / 2, height / 4);
  textSize(24);

  // Draw UFO
  push();
  ufoYOffset = sin(frameCount * ufoSpeed) * 20; // Floating motion
  translate(width / 2, height / 2 + ufoYOffset);

  // UFO body
  fill(150, 150, 200, 200); // Semi-transparent blue-grey
  ellipse(0, 0, 200, 80); // Main body
  ellipse(0, -40, 150, 50); // Top dome

  // UFO lights (optional)
  fill(255, 255, 0, 150);
  ellipse(-70, 20, 15, 10);
  ellipse(0, 25, 15, 10);
  ellipse(70, 20, 15, 10);

  pop();

  // Draw Exit Door Button
  fill(0, 150, 0); // Green door
  rect(exitDoorX, exitDoorY, exitDoorSize, exitDoorSize, 10);
  fill(255);
  text("EXIT", exitDoorX, exitDoorY);

  // Display escape message if any
  if (escapeMessage) {
    fill(0);
    text(escapeMessage, width / 2, height * 0.9);
  }

  // Check for timeout
  if (millis() - ufoAttackTimer > ufoAttackDuration) {
    escapeMessage = "Too slow! Game Over.";
    setTimeout(() => {
      resetGame(); // THIS IS A GAME OVER, SO WE RESET
      sceneState = HOUSE_SCENE;
      ufoCooldownTimer = millis(); // Start cooldown after game over
    }, 1500); // Show message then return to house
  }
}
Line-by-line explanation (19 lines)

🔧 Subcomponents:

calculation Escape Countdown let timeLeft = floor((ufoAttackDuration - (millis() - ufoAttackTimer)) / 1000);

Calculates how many seconds remain before the UFO attack ends. Displayed to create urgency.

calculation UFO Floating Animation ufoYOffset = sin(frameCount * ufoSpeed) * 20;

Uses sine wave to create smooth up-and-down floating motion. frameCount cycles, ufoSpeed controls frequency, and 20 is the amplitude.

conditional Game Over Timeout if (millis() - ufoAttackTimer > ufoAttackDuration) { escapeMessage = "Too slow! Game Over."; ... }

Checks if the 5-second escape timer has elapsed. If so, triggers game over and resets to house scene after 1.5 seconds.

fill(0); text("UFO Attack! Find the exit door!", width / 2, height / 8);
Displays the threat message in black at the top to alert the player.
let timeLeft = floor((ufoAttackDuration - (millis() - ufoAttackTimer)) / 1000);
Calculates remaining time: subtract elapsed time from total duration, divide by 1000 to convert milliseconds to seconds, and floor to round down to a whole number.
fill(255, 0, 0); textSize(36);
Sets fill to red and increases text size to 36 pixels to emphasize the urgency.
text(`Escape in: ${timeLeft}s`, width / 2, height / 4);
Displays the countdown in large red text, creating visual pressure on the player.
textSize(24);
Restores text size to the default 24 pixels.
push();
Saves the drawing state before applying transformations (translate) to the UFO.
ufoYOffset = sin(frameCount * ufoSpeed) * 20;
Calculates vertical offset using a sine wave: frameCount cycles smoothly, multiplied by ufoSpeed (0.05) to slow it down, multiplied by 20 for amplitude. Result ranges from -20 to +20 pixels.
translate(width / 2, height / 2 + ufoYOffset);
Moves the origin to the center of the screen, plus the floating offset, so the UFO bobs up and down around center.
fill(150, 150, 200, 200); // Semi-transparent blue-grey
Sets fill to a blue-grey color with 200 alpha (semi-transparent, since max is 255). The 4th parameter is alpha/transparency.
ellipse(0, 0, 200, 80);
Draws the main UFO body as an ellipse at origin (0, 0), 200 pixels wide and 80 pixels tall (a horizontal oval).
ellipse(0, -40, 150, 50);
Draws the top dome of the UFO 40 pixels above the body, slightly smaller (150x50) to look like a dome.
fill(255, 255, 0, 150);
Changes fill to yellow with semi-transparency (150 alpha) for the UFO lights.
ellipse(-70, 20, 15, 10); ellipse(0, 25, 15, 10); ellipse(70, 20, 15, 10);
Draws three small yellow ellipses (lights) on the UFO's underside—left, center, and right—to make it look menacing.
pop();
Restores the drawing state, undoing the translate transformation.
fill(0, 150, 0); rect(exitDoorX, exitDoorY, exitDoorSize, exitDoorSize, 10);
Draws a green square exit door at a random position (set when UFO attack started) with rounded corners.
fill(255); text("EXIT", exitDoorX, exitDoorY);
Draws white 'EXIT' text centered on the green door button.
if (escapeMessage) { fill(0); text(escapeMessage, width / 2, height * 0.9); }
If there's a message (e.g., 'Escaped the UFO!' or 'Too slow!'), display it at the bottom in black.
if (millis() - ufoAttackTimer > ufoAttackDuration) {
Checks if the escape duration (5 seconds) has elapsed since the attack started.
escapeMessage = "Too slow! Game Over."; setTimeout(() => { resetGame(); sceneState = HOUSE_SCENE; ufoCooldownTimer = millis(); }, 1500);
Sets the game-over message, then uses setTimeout to wait 1.5 seconds before resetting the game, returning to the house, and starting the cooldown timer.

drawWinScene()

drawWinScene is straightforward and demonstrates time formatting—converting raw milliseconds into readable minutes and seconds using modulo arithmetic. Notice the modulo operator (%)—it gives you the 'remainder' after division, perfect for extracting seconds after removing full minutes. The green background provides immediate visual feedback that the player won, teaching the importance of color in UX.

// NEW: Draw the win scene
function drawWinScene() {
  background(150, 255, 150); // Green background for winning
  fill(0);
  textSize(48);
  text("YOU WIN!", width / 2, height / 3);

  textSize(24);
  let minutes = floor(winTimeTaken / 60000);
  let seconds = floor((winTimeTaken % 60000) / 1000);
  text(`Time taken: ${minutes}m ${seconds}s`, width / 2, height / 2);

  // Restart Button
  let restartButtonWidth = 150;
  let restartButtonHeight = 40;
  let restartButtonX = width / 2;
  let restartButtonY = height * 0.7;
  fill(200);
  rect(restartButtonX, restartButtonY, restartButtonWidth, restartButtonHeight, 10);
  fill(0);
  text("Restart", restartButtonX, restartButtonY);
}
Line-by-line explanation (9 lines)

🔧 Subcomponents:

calculation Win Time Formatting let minutes = floor(winTimeTaken / 60000); let seconds = floor((winTimeTaken % 60000) / 1000);

Converts the total milliseconds into minutes and seconds, making it human-readable on the win screen.

background(150, 255, 150);
Fills the entire canvas with a green color (RGB 150, 255, 150) to give a celebratory feeling.
fill(0); textSize(48);
Sets fill to black and text size to 48 (large) for the victory message.
text("YOU WIN!", width / 2, height / 3);
Displays the victory message in large black text in the upper third of the screen.
textSize(24);
Reduces text size back to 24 for displaying the time.
let minutes = floor(winTimeTaken / 60000);
Calculates minutes by dividing total milliseconds by 60,000 (milliseconds per minute) and flooring the result.
let seconds = floor((winTimeTaken % 60000) / 1000);
Calculates remaining seconds: use modulo (%) to get milliseconds after the last full minute, divide by 1000 to convert to seconds, then floor.
text(`Time taken: ${minutes}m ${seconds}s`, width / 2, height / 2);
Displays the time taken formatted as 'X minutes Y seconds' at the center of the screen.
fill(200); rect(restartButtonX, restartButtonY, restartButtonWidth, restartButtonHeight, 10);
Draws a light grey restart button with rounded corners.
fill(0); text("Restart", restartButtonX, restartButtonY);
Draws black text on the button.

touchStarted()

touchStarted is a p5.js lifecycle function called every time the user taps (or clicks). By routing to scene-specific handlers, this function keeps input logic organized and modular. Returning false is important—it prevents the browser from scrolling or zooming in response to touch, letting the game maintain full control. This pattern (a router function dispatching to handlers) is common in game development.

// Function to handle "eating" (tapping)
function touchStarted() {
  if (sceneState === HOUSE_SCENE) {
    handleHouseInteraction();
  } else if (sceneState === EATING_SCENE) {
    handleEatingInteraction();
  } else if (sceneState === SHOP_SCENE) {
    handleShopInteraction();
  } else if (sceneState === UFO_ATTACK_SCENE) {
    handleUFOAttackInteraction();
  } else if (sceneState === WIN_SCENE) { // NEW: Handle win scene interaction
    handleWinInteraction();
  }
  return false; // Prevent default touch behavior
}
Line-by-line explanation (7 lines)

🔧 Subcomponents:

switch-case Touch Event Router if (sceneState === HOUSE_SCENE) { handleHouseInteraction(); } else if ...

Routes touch events to the appropriate handler function based on the current scene, ensuring the right game logic runs for each context.

function touchStarted() {
p5.js calls this function automatically whenever the user touches the screen (or clicks the mouse).
if (sceneState === HOUSE_SCENE) { handleHouseInteraction(); }
If currently in the house scene, call the house interaction handler to check if the door was tapped.
else if (sceneState === EATING_SCENE) { handleEatingInteraction(); }
If in the eating scene, call the eating handler to process hot dog taps and shop button clicks.
else if (sceneState === SHOP_SCENE) { handleShopInteraction(); }
If in the shop, call the shop handler to process upgrade and people-hire button clicks.
else if (sceneState === UFO_ATTACK_SCENE) { handleUFOAttackInteraction(); }
If in a UFO attack, call the UFO handler to check if the exit door was tapped.
else if (sceneState === WIN_SCENE) { handleWinInteraction(); }
If on the win screen, call the win handler to check if the restart button was tapped.
return false;
Returning false tells the browser not to perform its default touch behavior, allowing the game to handle all touches exclusively.

handleHouseInteraction()

handleHouseInteraction demonstrates AABB collision detection (Axis-Aligned Bounding Box)—the simplest way to detect if a point (mouse) is inside a rectangle. Notice that this function duplicates the door coordinate calculations from drawHouseScene. This duplication is a code smell—in professional code, these coordinates would be stored as variables or constants so both functions use the same source of truth. The two-tap system (first to open, second to enter) creates a deliberate pacing that feels intentional.

function handleHouseInteraction() {
  // Define door dimensions (same as in drawHouseScene)
  let houseWidth = width * 0.4;
  let houseHeight = height * 0.4;
  let houseX = width / 2 - houseWidth / 2; // Top-left X for house body
  let houseY = height / 2 - houseHeight / 2; // Top-left Y for house body

  let doorWidth = houseWidth * 0.2;
  let doorHeight = houseHeight * 0.5;
  let doorX = houseX + houseWidth * 0.4; // Top-left X for door
  let doorY = houseY + houseHeight - doorHeight; // Top-left Y for door

  // Check if touch is within door bounds
  // Note: mouseX and mouseY are still relative to the canvas origin (0,0)
  if (mouseX > doorX && mouseX < doorX + doorWidth &&
      mouseY > doorY && mouseY < doorY + doorHeight) {
    if (!doorOpen) {
      doorOpen = true; // Open the door
    } else {
      // If door is already open and tapped again, enter the eating scene
      resetGame(); // Reset game state for a fresh start when entering the eating scene from the house
      sceneState = EATING_SCENE;
      eatingSceneStartTime = millis(); // NEW: Start the win timer when entering eating scene
    }
  }
}
Line-by-line explanation (5 lines)

🔧 Subcomponents:

conditional Door Collision Detection if (mouseX > doorX && mouseX < doorX + doorWidth && mouseY > doorY && mouseY < doorY + doorHeight)

Checks if the touch/click was inside the rectangular bounds of the door using four inequalities (left, right, top, bottom boundaries).

conditional Door Open/Enter Logic if (!doorOpen) { doorOpen = true; } else { resetGame(); sceneState = EATING_SCENE; ... }

Toggles door state: first tap opens the door, second tap enters the eating scene and starts the game.

let houseWidth = width * 0.4;
Recalculates house dimensions (must match drawHouseScene exactly) so collision detection is accurate.
let doorX = houseX + houseWidth * 0.4;
Recalculates door position to match the drawing code.
if (mouseX > doorX && mouseX < doorX + doorWidth && mouseY > doorY && mouseY < doorY + doorHeight)
Checks if the tap (mouseX, mouseY) is within the door's rectangular bounds. All four conditions must be true.
if (!doorOpen) { doorOpen = true; }
If the door is currently closed, open it (set doorOpen to true), which will animate it via the lerp in drawHouseScene.
else { resetGame(); sceneState = EATING_SCENE; eatingSceneStartTime = millis(); }
If the door is already open, reset all game variables, switch to the eating scene, and start the win condition timer.

updateFullness()

updateFullness is a classic design pattern called a 'setter'—a function that changes a variable and handles side effects (like checking for poop triggers). By centralizing fullness updates here, the code avoids duplicating the poop-trigger logic. This function is called from two places: handleEatingInteraction (manual taps) and drawEatingScene (passive workers). Both paths trigger the same poop logic seamlessly, demonstrating clean separation of concerns.

// Central function to update fullness and trigger poop
function updateFullness(amount) {
  fullness += amount;

  if (fullness >= maxFullness) {
    poop();
    fullness = 0; // Reset fullness after pooping
  }
}
Line-by-line explanation (4 lines)

🔧 Subcomponents:

conditional Automatic Poop Trigger if (fullness >= maxFullness) { poop(); fullness = 0; }

When fullness reaches the maximum, automatically spawn a poop animation, earn money, and reset fullness to 0.

fullness += amount;
Adds the amount parameter to fullness. Called with clickPower when manually eating, or with (peopleCount * 0.005) for passive generation.
if (fullness >= maxFullness) {
Checks if fullness has reached or exceeded the threshold (50 by default).
poop();
Calls the poop() function to spawn the poop animation and earn money.
fullness = 0;
Resets fullness to 0 so the hot dog shrinks and the cycle repeats.

handleEatingInteraction()

handleEatingInteraction shows conditional logic for button detection (with early return to prevent eating), random probability (common in games), multi-condition checks (&&), and randomization for game variety. The cooldown check (millis() - ufoCooldownTimer > ufoCooldownDuration) is a standard pattern for rate-limiting events. Notice the comment explaining the three conditions—this is good practice for complex logic that readers should understand.

🔬 These lines randomize the exit door position. Currently it appears in the lower 40% of the screen (height * 0.6 to bottom). What happens if you change height * 0.6 to height * 0.2 to let it appear anywhere?

      exitDoorX = random(exitDoorSize / 2, width - exitDoorSize / 2);
      exitDoorY = random(height * 0.6, height - exitDoorSize / 2); // Position in lower half
function handleEatingInteraction() {
  // Shop Button interaction
  let shopButtonWidth = 100;
  let shopButtonHeight = 40;
  let shopButtonX = width - shopButtonWidth / 2 - 20;
  let shopButtonY = height / 8;

  if (mouseX > shopButtonX - shopButtonWidth / 2 && mouseX < shopButtonX + shopButtonWidth / 2 &&
      mouseY > shopButtonY - shopButtonHeight / 2 && mouseY < shopButtonY + shopButtonHeight / 2) {
    sceneState = SHOP_SCENE; // Switch to shop scene
    return; // Don't eat if tapping the shop button
  }

  // Hot dog eating logic (manual tap)
  if (fullness < maxFullness) {
    updateFullness(clickPower); // Use clickPower here!

    // Random chance for UFO attack, but only if cooldown has passed
    if (random(100) < 5 && !ufoActive && (millis() - ufoCooldownTimer > ufoCooldownDuration)) { // 5% chance with each tap, only if not already active AND cooldown is over
      ufoActive = true;
      ufoAttackTimer = millis(); // Start the timer
      exitDoorSize = random(50, 100); // Random size for the exit door
      exitDoorX = random(exitDoorSize / 2, width - exitDoorSize / 2);
      exitDoorY = random(height * 0.6, height - exitDoorSize / 2); // Position in lower half
      escapeMessage = "";
      sceneState = UFO_ATTACK_SCENE;
    }

  } else {
    // If full from manual tap, poop and reset.
    // This path is technically covered by updateFullness now, but keeping it
    // as a fallback for explicit manual interaction.
    poop();
    fullness = 0;
  }
}
Line-by-line explanation (11 lines)

🔧 Subcomponents:

conditional Shop Button Collision if (mouseX > shopButtonX - shopButtonWidth / 2 && ... mouseY < shopButtonY + shopButtonHeight / 2)

Detects if the tap was on the shop button. If so, switch to shop scene and return early (preventing eating).

conditional UFO Attack Random Trigger if (random(100) < 5 && !ufoActive && (millis() - ufoCooldownTimer > ufoCooldownDuration))

5% chance per tap to trigger UFO attack, but only if no attack is active AND the cooldown has expired. Initializes UFO scene variables.

let shopButtonWidth = 100; let shopButtonHeight = 40;
Recalculates shop button dimensions to match the button drawn in drawEatingScene.
let shopButtonX = width - shopButtonWidth / 2 - 20;
Recalculates shop button position (top-right corner) to match the drawing code.
if (mouseX > shopButtonX - shopButtonWidth / 2 && ... mouseY < shopButtonY + shopButtonHeight / 2)
AABB collision detection: checks if tap is within the button's rectangular bounds.
sceneState = SHOP_SCENE; return;
If on the shop button, switch scenes and return early so the code below (eating logic) doesn't run.
if (fullness < maxFullness) { updateFullness(clickPower); }
If not full, increase fullness by clickPower (the tap strength) via updateFullness, which will trigger poop if fullness exceeds the max.
if (random(100) < 5 && !ufoActive && (millis() - ufoCooldownTimer > ufoCooldownDuration))
Three conditions for UFO attack: (1) random(100) < 5 means 5% chance, (2) !ufoActive ensures no attack is already active, (3) cooldown has elapsed.
ufoActive = true; ufoAttackTimer = millis();
Sets the UFO state to active and records the current time as the attack start.
exitDoorSize = random(50, 100);
Randomizes the exit door size between 50 and 100 pixels, making each attack feel different.
exitDoorX = random(exitDoorSize / 2, width - exitDoorSize / 2);
Randomizes the door's X position, ensuring it stays on-screen (between door radius and canvas width minus radius).
exitDoorY = random(height * 0.6, height - exitDoorSize / 2);
Randomizes the door's Y position in the lower 40% of the screen.
sceneState = UFO_ATTACK_SCENE;
Switches to the UFO attack scene, which triggers drawUFOAttackScene each frame.

handleShopInteraction()

handleShopInteraction demonstrates the core pattern of an idle/incremental game: buy upgrades to increase income, costs grow exponentially to maintain challenge, and multiple purchases have different cost curves. The pattern of recalculating button coordinates, doing collision detection, then early-returning prevents button overlap issues. The different multipliers (1.5 vs 1.8) create strategic depth—players must decide whether to invest in click power or passive workers.

🔬 This logic handles a purchase. What happens if you change upgradeCost *= 1.5 to upgradeCost *= 2.0? (The cost will grow faster.)

    if (money >= upgradeCost) {
      money -= upgradeCost;
      clickPower++; // Increase click power
      upgradeCost = floor(upgradeCost * 1.5); // Increase cost for next upgrade
    }
function handleShopInteraction() {
  // Upgrade Clickers Button properties (must match drawShopScene)
  let upgradeButtonWidth = 200;
  let upgradeButtonHeight = 50;
  let upgradeButtonX = width / 2; // Centered X
  let upgradeButtonY = height / 2 - 40; // Shifted up

  // Check if Upgrade Clickers button was tapped
  if (mouseX > upgradeButtonX - upgradeButtonWidth / 2 && mouseX < upgradeButtonX + upgradeButtonWidth / 2 &&
      mouseY > upgradeButtonY - upgradeButtonHeight / 2 && mouseY < upgradeButtonY + upgradeButtonHeight / 2) {
    if (money >= upgradeCost) {
      money -= upgradeCost;
      clickPower++; // Increase click power
      upgradeCost = floor(upgradeCost * 1.5); // Increase cost for next upgrade
    } else {
      console.log("Not enough money for upgrade!"); // Log if not enough money
    }
    return; // Prevent further checks if this button was tapped
  }

  // Hire People Button properties (must match drawShopScene)
  let peopleButtonWidth = 200;
  let peopleButtonHeight = 50;
  let peopleButtonX = width / 2; // Centered X
  let peopleButtonY = height / 2 + 40; // Position below clicker upgrade

  // Check if Hire People button was tapped
  if (mouseX > peopleButtonX - peopleButtonWidth / 2 && mouseX < peopleButtonX + peopleButtonWidth / 2 &&
      mouseY > peopleButtonY - peopleButtonHeight / 2 && mouseY < peopleButtonY + peopleButtonHeight / 2) {
    if (money >= peopleUpgradeCost) {
      money -= peopleUpgradeCost;
      peopleCount++; // Hire a person
      peopleUpgradeCost = floor(peopleUpgradeCost * 1.8); // Increase cost for next person
    } else {
      console.log("Not enough money to hire people!"); // Log if not enough money
    }
    return; // Prevent further checks if this button was tapped
  }

  // Back to Eating Button properties (must match drawShopScene)
  let backButtonWidth = 150;
  let backButtonHeight = 40;
  let backButtonX = width / 2; // Centered X
  let backButtonY = height * 0.75 + 40; // Lower Y, shifted down

  // Check if Back to Eating button was tapped
  if (mouseX > backButtonX - backButtonWidth / 2 && mouseX < backButtonX + backButtonWidth / 2 &&
      mouseY > backButtonY - backButtonHeight / 2 && mouseY < backButtonY + backButtonHeight / 2) {
    sceneState = EATING_SCENE; // Switch back to eating scene
    return; // Exit function after handling the tap
  }
}
Line-by-line explanation (11 lines)

🔧 Subcomponents:

conditional Upgrade Clickers Purchase if (money >= upgradeCost) { money -= upgradeCost; clickPower++; upgradeCost = floor(upgradeCost * 1.5); }

Checks if player has enough money for upgrade. If so, deducts cost, increases click power, and increases next upgrade cost exponentially.

conditional Hire People Purchase if (money >= peopleUpgradeCost) { money -= peopleUpgradeCost; peopleCount++; peopleUpgradeCost = floor(peopleUpgradeCost * 1.8); }

Checks if player has enough money to hire. If so, deducts cost, increments person count, and increases next hire cost exponentially (1.8x vs 1.5x for clickers).

let upgradeButtonWidth = 200; let upgradeButtonX = width / 2; let upgradeButtonY = height / 2 - 40;
Recalculates the upgrade button dimensions and position to match drawShopScene.
if (mouseX > upgradeButtonX - upgradeButtonWidth / 2 && ... mouseY < upgradeButtonY + upgradeButtonHeight / 2)
AABB collision detection for the upgrade button.
if (money >= upgradeCost) {
Checks if the player has enough money. If not, the upgrade doesn't happen (console.log statement logs the failure).
money -= upgradeCost;
Deducts the upgrade cost from the player's money.
clickPower++;
Increases click power by 1, making each tap worth more fullness.
upgradeCost = floor(upgradeCost * 1.5);
Increases the next upgrade cost by 50% (multiplies by 1.5), creating exponential cost growth and a progression system.
return;
Exits the function early, preventing checks of other buttons. This stops a single tap from triggering multiple buttons.
if (money >= peopleUpgradeCost) {
Checks if player has enough money to hire.
peopleCount++;
Increases the number of workers, which will increase passive fullness generation.
peopleUpgradeCost = floor(peopleUpgradeCost * 1.8);
Increases the next hire cost by 80% (1.8x multiplier). Notice this is higher than clicker cost (1.5x), making workers more expensive.
sceneState = EATING_SCENE; return;
The back button switches back to eating scene and returns.

handleUFOAttackInteraction()

handleUFOAttackInteraction shows successful collision detection handling. Notice it doesn't reset the entire game (unlike the timeout path in drawUFOAttackScene)—instead it just clears the UFO state and starts the cooldown. The setTimeout pattern shows how to create a narrative pause (let the player see the success message before proceeding). The console.log calls are useful for debugging—you can open the browser's developer console (F12) to see what's happening.

function handleUFOAttackInteraction() {
  // Check if tap is on the exit door
  if (mouseX > exitDoorX - exitDoorSize / 2 && mouseX < exitDoorX + exitDoorSize / 2 &&
      mouseY > exitDoorY - exitDoorSize / 2 && mouseY < exitDoorY + exitDoorSize / 2) {
    console.log("EXIT button tapped successfully!"); // Confirm tap detection
    escapeMessage = "Escaped the UFO!";
    // Instead of resetGame(), just clear UFO state and return to eating
    setTimeout(() => {
      ufoActive = false;
      escapeMessage = "";
      sceneState = EATING_SCENE;
      ufoCooldownTimer = millis(); // Start cooldown after successful escape
    }, 1500); // Show message then return to eating
  } else {
    console.log("Tap missed the EXIT button."); // Log if tap was outside
  }
}
Line-by-line explanation (6 lines)

🔧 Subcomponents:

conditional Exit Door Collision Detection if (mouseX > exitDoorX - exitDoorSize / 2 && mouseX < exitDoorX + exitDoorSize / 2 && ...)

AABB collision detection for the exit door. If true, player successfully escaped; if false, missed.

if (mouseX > exitDoorX - exitDoorSize / 2 && mouseX < exitDoorX + exitDoorSize / 2 && mouseY > exitDoorY - exitDoorSize / 2 && mouseY < exitDoorY + exitDoorSize / 2)
AABB collision detection: checks if tap is within the square exit door bounds.
console.log("EXIT button tapped successfully!");
Logs success to the browser console for debugging.
escapeMessage = "Escaped the UFO!";
Sets the success message to display on screen.
setTimeout(() => { ... }, 1500);
Waits 1.5 seconds before running the code inside, giving the player time to see the success message.
ufoActive = false; sceneState = EATING_SCENE; ufoCooldownTimer = millis();
Deactivates the UFO, returns to eating, and starts the cooldown timer so another attack can't happen for 100 seconds.
else { console.log("Tap missed the EXIT button."); }
If the tap missed, logs the miss to the console for debugging.

handleWinInteraction()

handleWinInteraction is straightforward—it detects the restart button tap and resets the game. This is the final interaction handler in the chain, completing the game loop. Notice how all interaction handlers follow the same pattern: recalculate button bounds, AABB detect, then respond. This consistency makes the code predictable and maintainable.

// NEW: Handle win scene interactions
function handleWinInteraction() {
  // Restart Button properties (must match drawWinScene)
  let restartButtonWidth = 150;
  let restartButtonHeight = 40;
  let restartButtonX = width / 2;
  let restartButtonY = height * 0.7;

  // Check if Restart button was tapped
  if (mouseX > restartButtonX - restartButtonWidth / 2 && mouseX < restartButtonX + restartButtonWidth / 2 &&
      mouseY > restartButtonY - restartButtonHeight / 2 && mouseY < restartButtonY + restartButtonHeight / 2) {
    resetGame(); // Reset game state
    sceneState = HOUSE_SCENE; // Go back to the house scene
  }
}
Line-by-line explanation (4 lines)

🔧 Subcomponents:

conditional Restart Button Collision if (mouseX > restartButtonX - restartButtonWidth / 2 && ... mouseY < restartButtonY + restartButtonHeight / 2)

AABB collision detection for the restart button. If tapped, reset all game variables and return to house scene.

let restartButtonWidth = 150; let restartButtonX = width / 2; let restartButtonY = height * 0.7;
Recalculates the restart button dimensions and position to match drawWinScene.
if (mouseX > restartButtonX - restartButtonWidth / 2 && ... mouseY < restartButtonY + restartButtonHeight / 2)
AABB collision detection for the restart button.
resetGame();
Calls resetGame() to zero out all game variables and prepare for a fresh game.
sceneState = HOUSE_SCENE;
Switches back to the house scene so the player sees the house again to start a new game.

poop()

The poop() function demonstrates parametric interpolation using lerp and a normalized t parameter (0 to 1). This is fundamental to animation and procedural generation. The trail generation creates a visual narrative of the poop 'flying' from the hot dog to the ground. Notice how size decreases along the path (poopSize * (1 - t))—this is called tapering and creates a sense of motion and visual appeal. The function also shows good game design: rewarding the player with money and visible feedback (hotDogCount and the colorful animation).

🔬 This loop creates the poop path. The trail gets smaller with poopSize * (1 - t). What happens if you change that to just poopSize (constant size) or poopSize * t (gets bigger)?

  for (let i = 0; i <= numSegments; i++) {
    let t = i / numSegments;
    let x = lerp(startX, endX, t);
    let y = lerp(startY, endY, t);
    trail.push({ x: x, y: y, size: poopSize * (1 - t) }); // Smaller at the end
  }
// Function to simulate "pooping"
function poop() {
  let poopColor = color(random(50, 150), random(50, 150), random(50, 150), 200); // Random brownish-grey color
  let poopSize = random(10, 30);
  // Adjust poop start position to be below the hot dog
  let hotDogLength = map(fullness, 0, maxFullness, 50, 200);
  let hotDogWidth = 30;
  let startX = width / 2;
  let startY = height / 2 + hotDogWidth / 2 + 50; // Poop from below the hot dog
  let endX = random(width / 4, width * 3 / 4);
  let endY = height - 50;

  // Create a trail of shapes
  let trail = [];
  let numSegments = 20;
  for (let i = 0; i <= numSegments; i++) {
    let t = i / numSegments;
    let x = lerp(startX, endX, t);
    let y = lerp(startY, endY, t);
    trail.push({ x: x, y: y, size: poopSize * (1 - t) }); // Smaller at the end
  }

  // Add this poop event to history
  poopHistory.push({
    color: poopColor,
    trail: trail,
    alpha: 255
  });

  // Earn money for pooping!
  money += 5 * clickPower; // Earn more money with higher click power!

  // Increment hotdog count
  hotDogCount++;
}
Line-by-line explanation (12 lines)

🔧 Subcomponents:

for-loop Poop Trail Path Generation for (let i = 0; i <= numSegments; i++) { let t = i / numSegments; let x = lerp(startX, endX, t); let y = lerp(startY, endY, t); trail.push(...); }

Creates a smooth path from start to end position using 20 segments, each with smaller size, creating a visual trail effect.

let poopColor = color(random(50, 150), random(50, 150), random(50, 150), 200);
Creates a random brownish-grey color by randomizing each RGB channel between 50 and 150, with alpha 200 for transparency.
let poopSize = random(10, 30);
Randomly chooses the size of the poop trail, between 10 and 30 pixels.
let startX = width / 2; let startY = height / 2 + hotDogWidth / 2 + 50;
Sets the starting position below the hot dog at the center.
let endX = random(width / 4, width * 3 / 4); let endY = height - 50;
Sets a random ending position in the lower portion of the screen, between 1/4 and 3/4 across width.
let numSegments = 20;
The trail will have 21 points (0 through 20), creating a nice smooth path.
for (let i = 0; i <= numSegments; i++) {
Loops from 0 to 20 (inclusive), creating each segment of the trail.
let t = i / numSegments;
Calculates a normalized 't' value from 0 (start) to 1 (end). At i=0, t=0; at i=20, t=1.
let x = lerp(startX, endX, t); let y = lerp(startY, endY, t);
Uses lerp to interpolate position along the path. Early segments (low t) are near start, later segments are near end.
trail.push({ x: x, y: y, size: poopSize * (1 - t) });
Adds a point to the trail with interpolated x,y and a size that shrinks as t increases (poopSize * (1-t) means at t=1, size=0).
poopHistory.push({ color: poopColor, trail: trail, alpha: 255 });
Adds the complete poop object (color, trail path, and starting alpha) to the history array for drawing later.
money += 5 * clickPower;
Awards 5 base money per poop, multiplied by click power. Higher click power = more money per poop.
hotDogCount++;
Increments the hotdog counter so the player sees how many they've eaten.

resetGame()

resetGame() is a crucial utility function that demonstrates the importance of initialization. Rather than having scattered reset logic throughout the code, centralizing it in one function makes the codebase maintainable and less error-prone. Notice every global variable that can change is reset here—this is thorough. The function ensures that restarting a game truly gives a fresh slate with no leftover state from the previous game.

// Resets all game-related variables to their initial state
function resetGame() {
  fullness = 0;
  poopHistory = [];
  money = 0;
  clickPower = 1;
  upgradeCost = 10;
  hotDogCount = 0; // Reset hotdog count
  doorOpen = false;
  doorAngle = 0;
  ufoActive = false; // Make sure UFO is not active when game resets
  escapeMessage = "";
  // Reset people upgrade variables
  peopleCount = 0;
  peopleUpgradeCost = 100;
  // Reset UFO cooldown timer on full game reset
  ufoCooldownTimer = millis(); // Start cooldown for the *next* game's first attack
  // NEW: Reset win condition variables
  eatingSceneStartTime = 0;
  winTimeTaken = 0;
}
Line-by-line explanation (11 lines)
fullness = 0;
Resets the hot dog fullness to empty.
poopHistory = [];
Clears the array of poop trails so no old poops are visible.
money = 0;
Resets money to 0 for a fresh start.
clickPower = 1;
Resets click power to 1 (base strength).
upgradeCost = 10;
Resets the upgrade cost to the starting value of 10.
hotDogCount = 0;
Resets the hotdog eaten counter.
doorOpen = false; doorAngle = 0;
Closes the house door and resets its rotation angle.
ufoActive = false; escapeMessage = "";
Ensures no UFO is active and clears any escape message.
peopleCount = 0; peopleUpgradeCost = 100;
Resets workers to zero and their cost back to 100.
ufoCooldownTimer = millis();
Sets the cooldown timer to now, so the 100-second cooldown begins fresh in the new game.
eatingSceneStartTime = 0; winTimeTaken = 0;
Resets the win condition variables so the new game doesn't immediately win.

windowResized()

windowResized() is a p5.js lifecycle function that ensures the canvas remains responsive when the window changes size. Because this sketch uses width and height variables throughout (instead of hardcoded pixel values), everything scales responsively. This is a best practice for modern web apps.

// Called when preview panel is resized
function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
}
Line-by-line explanation (2 lines)
function windowResized() {
p5.js calls this function automatically whenever the browser window is resized.
resizeCanvas(windowWidth, windowHeight);
Resizes the p5.js canvas to match the new window dimensions, keeping the game responsive.

📦 Key Variables

fullness number

Tracks how full the hot dog is (0 to maxFullness). When it reaches maxFullness, the player poops and earns money.

let fullness = 0;
maxFullness number

The threshold at which the hot dog is considered 'full' and triggers a poop event (50 by default).

const maxFullness = 50;
poopHistory array

Stores all active poop trails (each poop is an object with color, trail path, and alpha). Used for drawing fading animations.

let poopHistory = [];
sceneState number

The current game scene (0=HOUSE, 1=EATING, 2=SHOP, 3=UFO_ATTACK, 4=WIN). Controls which drawing function is called each frame.

let sceneState = HOUSE_SCENE;
doorOpen boolean

Tracks whether the house door is currently open. Used to trigger the door animation and eating scene entry.

let doorOpen = false;
doorAngle number

The current rotation angle of the door in radians. Lerps between 0 (closed) and Math.PI / 2 (open 90 degrees).

let doorAngle = 0;
money number

The player's current money balance. Earned by pooping (5 * clickPower) and spent on upgrades.

let money = 0;
clickPower number

How much fullness increases per tap. Starts at 1 and increases by 1 each time the player upgrades.

let clickPower = 1;
upgradeCost number

The cost (in money) of the next click power upgrade. Increases exponentially (multiplied by 1.5) after each purchase.

let upgradeCost = 10;
hotDogCount number

Tracks how many complete hot dogs (full cycles) the player has eaten. Incremented each time they poop.

let hotDogCount = 0;
peopleCount number

The number of workers hired. Each worker adds passive fullness generation (peopleCount * 0.005 per frame).

let peopleCount = 0;
peopleUpgradeCost number

The cost (in money) of hiring the next worker. Increases exponentially (multiplied by 1.8) after each hire.

let peopleUpgradeCost = 100;
ufoActive boolean

Tracks whether a UFO attack is currently happening. Prevents multiple simultaneous attacks.

let ufoActive = false;
ufoYOffset number

The current vertical offset of the UFO due to floating animation. Calculated using sin(frameCount * ufoSpeed) * 20.

let ufoYOffset = 0;
ufoSpeed number

Controls the frequency of the UFO's floating motion. Multiplied by frameCount in the sine wave.

let ufoSpeed = 0.05;
ufoAttackTimer number

Stores the millisecond timestamp when the UFO attack started. Used to calculate the countdown timer.

let ufoAttackTimer = 0;
ufoAttackDuration number

How long the player has to escape a UFO attack in milliseconds (5000 = 5 seconds).

const ufoAttackDuration = 5000;
ufoCooldownTimer number

Stores the millisecond timestamp when the most recent UFO attack ended (or when the game started). Used to enforce the 100-second cooldown.

let ufoCooldownTimer = 0;
ufoCooldownDuration number

How long to wait between UFO attacks in milliseconds (100000 = 100 seconds). The '100 seconds' mentioned in the title.

const ufoCooldownDuration = 100000;
exitDoorX number

The X position of the exit door during a UFO attack. Randomly set when an attack starts.

let exitDoorX, exitDoorY, exitDoorSize;
exitDoorY number

The Y position of the exit door during a UFO attack. Randomly set when an attack starts.

let exitDoorX, exitDoorY, exitDoorSize;
exitDoorSize number

The width/height (in pixels) of the square exit door. Randomly set between 50-100 when an attack starts.

let exitDoorX, exitDoorY, exitDoorSize;
escapeMessage string

A message displayed during/after a UFO attack (e.g., 'Escaped the UFO!' or 'Too slow! Game Over.').

let escapeMessage = "";
eatingSceneStartTime number

Stores the millisecond timestamp when the eating scene was entered. Used to check the 3-minute win condition.

let eatingSceneStartTime = 0;
targetWinDuration number

How long the player must survive in the eating scene to win (180000 = 3 minutes).

const targetWinDuration = 180000;
winTimeTaken number

Stores the actual time (in milliseconds) the player took to win. Displayed on the win screen.

let winTimeTaken = 0;

🔧 Potential Improvements (6)

Here are some ways this code could be enhanced:

STYLE handleHouseInteraction, handleEatingInteraction, handleShopInteraction, handleUFOAttackInteraction

Door/button coordinate calculations are duplicated between drawing functions and interaction handlers (e.g., doorX, doorY are calculated in both drawHouseScene and handleHouseInteraction). This creates a maintenance burden—if you move a button visually, you must also update the collision detection code.

💡 Store button/door coordinates as global variables or constants that both drawing and interaction functions use. For example: let doorCoords = { x: 0, y: 0, w: 0, h: 0 }; calculate it once in setup or a helper function, and both draw and collision functions reference it.

PERFORMANCE drawEatingScene poop rendering loop

The poop trail rendering loop iterates through every point in every poop every frame, even poop that have nearly disappeared. For long play sessions with many poop trails, this could become slow.

💡 Only draw poop with alpha > 10 or similar threshold, or remove them from the array sooner. Add an early continue/break statement: if (poop.alpha < 10) { poopHistory.splice(i, 1); i--; continue; }

BUG handleShopInteraction and handleUFOAttackInteraction buttons

Buttons that are close together might cause coordinate overlap if the window is very small, allowing taps meant for one button to trigger another.

💡 Add console logging or visual debug rectangles to verify button bounds during development. Consider using a dedicated collision library or adding padding/tolerance thresholds.

FEATURE Game design

Once a player survives 3 UFO attacks, they've essentially mastered the game and subsequent escapes are repetitive—no escalating difficulty.

💡 Add difficulty progression: make UFOs spawn more frequently after each escape (reduce cooldown over time), or reduce the escape time limit, or make the exit door smaller. This keeps the late game engaging.

STYLE Global variables

Many related variables are scattered (e.g., ufoActive, ufoYOffset, ufoSpeed, ufoAttackTimer, ufoCooldownTimer). This makes the code harder to navigate and reason about UFO state.

💡 Group related variables into objects: let ufo = { active: false, yOffset: 0, speed: 0.05, attackTimer: 0, cooldownTimer: 0, ... }; This reduces global namespace pollution and makes code clearer.

BUG draw loop switch statement

If a touch happens between scene transitions (e.g., during a setTimeout that changes sceneState), the wrong handler might be called. No input validation exists.

💡 Add a flag like let inputLocked = true during transitions to prevent touch input from being processed during async operations like setTimeout.

🔄 Code Flow

Code flow showing setup, draw, drawhousescene, draweatingscene, drawperson, drawshopscene, drawufroattackscene, drawwinscene, touchstarted, handlehouseinteraction, updatefullness, handleeatinginteraction, handleshopinteraction, handleufroattackinteraction, handlewininteraction, poop, resetgame, windowresized

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

graph TD start[Start] --> setup[setup] setup --> draw[draw loop] draw --> scene-switch[Scene State Switch] scene-switch --> drawhousescene[drawHouseScene] drawhousescene --> house-drawing[House Geometry] house-drawing --> door-animation[Door Opening Animation] door-animation --> drawhousescene draw --> draweatingscene[drawEatingScene] draweatingscene --> win-condition-check[Win Condition Timer] win-condition-check --> draweatingscene draweatingscene --> hotdog-growth[Hot Dog Length Animation] hotdog-growth --> draweatingscene draweatingscene --> passive-income[Passive Fullness Generation] passive-income --> draweatingscene draweatingscene --> poop-animation-loop[Poop History Rendering and Fading] poop-animation-loop --> draweatingscene draw --> drawshopscene[drawShopScene] drawshopscene --> shop-text-display[Shop Information Display] shop-text-display --> drawshopscene draw --> drawufroattackscene[drawUFOAttackScene] drawufroattackscene --> countdown-timer[Escape Countdown] countdown-timer --> drawufroattackscene drawufroattackscene --> ufo-floating[UFO Floating Animation] ufo-floating --> drawufroattackscene drawufroattackscene --> timeout-check[Game Over Timeout] timeout-check --> drawufroattackscene draw --> drawwinscene[drawWinScene] drawwinscene --> time-formatting[Win Time Formatting] time-formatting --> drawwinscene touchstarted --> scene-routing[Touch Event Router] scene-routing --> handlehouseinteraction[handleHouseInteraction] handlehouseinteraction --> door-bounds-check[Door Collision Detection] door-bounds-check --> door-state-toggle[Door Open/Enter Logic] door-state-toggle --> handlehouseinteraction scene-routing --> handleeatinginteraction[handleEatingInteraction] handleeatinginteraction --> shop-button-collision[Shop Button Collision] shop-button-collision --> handleeatinginteraction handleeatinginteraction --> poop-trigger[Automatic Poop Trigger] poop-trigger --> handleeatinginteraction scene-routing --> handleshopinteraction[handleShopInteraction] handleshopinteraction --> upgrade-clickers-logic[Upgrade Clickers Purchase] upgrade-clickers-logic --> handleshopinteraction handleshopinteraction --> hire-people-logic[Hire People Purchase] hire-people-logic --> handleshopinteraction scene-routing --> handleufroattackinteraction[handleUFOAttackInteraction] handleufroattackinteraction --> exit-door-collision[Exit Door Collision Detection] exit-door-collision --> handleufroattackinteraction scene-routing --> handlewininteraction[handleWinInteraction] handlewininteraction --> restart-button-collision[Restart Button Collision] restart-button-collision --> handlewininteraction draw --> windowresized[windowResized] click setup href "#fn-setup" click draw href "#fn-draw" click drawhousescene href "#fn-drawhousescene" click draweatingscene href "#fn-draweatingscene" click drawshopscene href "#fn-drawshopscene" click drawufroattackscene href "#fn-drawufroattackscene" click drawwinscene href "#fn-drawwinscene" click touchstarted href "#fn-touchstarted" click handlehouseinteraction href "#fn-handlehouseinteraction" click updatefullness href "#fn-updatefullness" click handleeatinginteraction href "#fn-handleeatinginteraction" click handleshopinteraction href "#fn-handleshopinteraction" click handleufroattackinteraction href "#fn-handleufroattackinteraction" click handlewininteraction href "#fn-handlewininteraction" click poop href "#fn-poop" click resetgame href "#fn-resetgame" click windowresized href "#fn-windowresized" click scene-switch href "#sub-scene-switch" click door-animation href "#sub-door-animation" click house-drawing href "#sub-house-drawing" click win-condition-check href "#sub-win-condition-check" click hotdog-growth href "#sub-hotdog-growth" click passive-income href "#sub-passive-income" click poop-animation-loop href "#sub-poop-animation-loop" click shop-text-display href "#sub-shop-text-display" click countdown-timer href "#sub-countdown-timer" click ufo-floating href "#sub-ufo-floating" click timeout-check href "#sub-timeout-check" click time-formatting href "#sub-time-formatting" click scene-routing href "#sub-scene-routing" click door-bounds-check href "#sub-door-bounds-check" click door-state-toggle href "#sub-door-state-toggle" click poop-trigger href "#sub-poop-trigger" click shop-button-collision href "#sub-shop-button-collision" click ufo-attack-trigger href "#sub-ufo-attack-trigger" click upgrade-clickers-logic href "#sub-upgrade-clickers-logic" click hire-people-logic href "#sub-hire-people-logic" click exit-door-collision href "#sub-exit-door-collision" click restart-button-collision href "#sub-restart-button-collision" click trail-generation href "#sub-trail-generation"

Preview

UFO has cooldown like 100 seconds - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of UFO has cooldown like 100 seconds - Code flow showing setup, draw, drawhousescene, draweatingscene, drawperson, drawshopscene, drawufroattackscene, drawwinscene, touchstarted, handlehouseinteraction, updatefullness, handleeatinginteraction, handleshopinteraction, handleufroattackinteraction, handlewininteraction, poop, resetgame, windowresized
Code Flow Diagram