Sketch 2026-03-09 18:39

This is a chaotic emoji-collecting cooking game where you tap falling emojis to gather ingredients, call customers to the serving line, and serve meals while managing mayhem from special emojis that spawn UFOs, sharks, airplanes, armed people, and police. The sketch combines emoji rain mechanics, character animations, collision detection, and state management across dozens of interactive objects.

🧪 Try This!

Experiment with the code by making these changes:

  1. Spawn multiple customers at once — Inside cookAndServe(), change numPeopleToSpawn from 1 to 3 or higher—suddenly three customers will arrive together, competing for ingredients and chaos erupts.
  2. Make emojis fall upward — In the Emoji class update() method, change the + to a - so emojis rise instead of fall. They'll drift up out of the sky!
  3. Speed up all emojis — In Emoji's constructor, multiply all speed ranges by 2 (e.g., change random(2, 5) to random(4, 10)). The rain becomes a blizzard.
  4. Disable police spawning from wanted level — In cookAndServe(), comment out or delete the entire `if (wantedLevel > 0)` block. Lobsters will increase wanted level but no police will ever spawn to enforce it.
  5. Award bonus points for normal emojis — In the mousePressed() function's 'else' branch (normal emoji collection), add a line to increment servedCount. Players get free points for basic collections!
  6. Increase max falling emojis for madness — Change MAX_FALLING_EMOJIS from 20 to a much higher number like 100. The screen will be completely covered in falling emojis—beautiful chaos!
Prefer the full editor? Open it there →

📖 About This Sketch

This sketch creates an interactive emoji-rain game where falling emojis are ingredients you tap to collect, then use to serve meals to walking customers. What makes it visually wild is the cascade of chaotic events triggered by special emojis: fish summon UFOs, lobsters boost your wanted level bringing police, orangutans arm people with guns, and dinosaurs trigger one-on-one fights. The code uses core p5.js techniques including the draw loop for animation, canvas shapes for characters and vehicles, text rendering for emojis, and mousePressed() for tap detection alongside advanced patterns like classes, state machines, and arrays of objects.

The sketch is organized into a single setup() function that builds the DOM UI, a draw() function with a switch statement routing between menu, playing, and fighting game states, and over fifteen custom classes (Emoji, Person, UFO, Shark, Airplane, PoliceCar, Fighter, etc.) that each manage their own animation, collision, and state logic. By reading it you will learn how to coordinate dozens of moving objects, implement state-based behavior, detect collisions between tapped points and emojis, manipulate the DOM for UI, and chain together event triggers that cascade into increasingly absurd scenarios.

⚙️ How It Works

  1. When the sketch loads, setup() creates the canvas at full window size and calls createUI() to build DOM elements (buttons, displays) positioned absolutely over the canvas. The initial gameState is 'MENU' and checkBrowserZoom() detects if the browser is zoomed.
  2. On every frame, draw() switches on gameState: if MENU, drawMenu() displays title and buttons; if PLAYING, handlePlayingScene() runs the main game loop; if FIGHTING, handleFightScene() runs a turn-based fight.
  3. In PLAYING mode, the scene draws falling emojis (updated via the Emoji class's update() and display() methods), the chef standing in the center, a serving line rectangle showing collected ingredients, and all active game objects: people walking to collect ingredients, UFOs approaching, sharks swimming, airplanes diving, bullets flying, police cars and SWAT teams pursuing, helicopters hovering, ambulances rescuing, and nuke explosions blooming.
  4. When you tap/click, mousePressed() checks if a falling emoji was hit using each Emoji's isHit() method. Special emojis trigger events: fish spawn a UFO targeting a random person; lobsters increase wanted level and spawn police; orangutans spawn a new person and arm an existing one with a gun; T-Rex emojis switch to FIGHTING state. Normal emojis get added to the collectedEmojis array.
  5. When you click 'Call a Customer!', cookAndServe() spawns one Person object that walks toward a collection spot, takes the first collected emoji, walks away, and increments servedCount. If your wanted level is high, police entities spawn alongside them to arrest them.
  6. Each game object (Person, UFO, Shark, etc.) has its own update() method handling movement and state transitions, and display() for drawing. Collisions (bullets hitting people, police cars catching people, ambulances picking up dead) are checked in handlePlayingScene() with conditional logic that removes objects and updates game state accordingly.

🎓 Concepts You'll Learn

Game state machines (MENU, PLAYING, FIGHTING)Object arrays and iteration with splice()ES6 class design with update() and display() patternCollision detection (distance-based)DOM manipulation and p5.Div elementsEvent handling (mousePressed, button callbacks)Emoji and text rendering in p5.jsLerp() for smooth movement and animationCanvas coordinate system and positioningInheritance (SWATTeam extends PoliceCar)

📝 Code Breakdown

setup()

setup() runs once when the sketch starts. Use it to initialize canvas size, create UI, set up event listeners, and establish the initial game state.

function setup() {
  createCanvas(windowWidth, windowHeight);
  createUI(); // Create all DOM UI elements

  // Add event listener for visual viewport changes (including zoom)
  // This is the most reliable way to detect browser's visual zoom level
  if (window.visualViewport) {
    window.visualViewport.addEventListener('resize', checkBrowserZoom);
  } else {
    // Fallback for browsers not supporting visualViewport (less reliable)
    // This will only detect window resizing, not true visual zoom.
    window.addEventListener('resize', checkBrowserZoom);
  }
  checkBrowserZoom(); // Initial check on setup

  showMenu(); // Start in menu state
}
Line-by-line explanation (6 lines)

🔧 Subcomponents:

calculation Canvas Setup createCanvas(windowWidth, windowHeight);

Creates a full-screen p5.js canvas that fills the entire browser window

function-call DOM UI Builder createUI();

Initializes all buttons, displays, and containers for the game interface

conditional Browser Zoom Detection if (window.visualViewport) { window.visualViewport.addEventListener('resize', checkBrowserZoom); }

Sets up a listener to detect when the user zooms the browser in or out

createCanvas(windowWidth, windowHeight);
Creates a p5.js canvas that spans the entire window width and height, making the game fullscreen
createUI();
Calls the custom function that builds all DOM elements (buttons, text displays) and positions them over the canvas
if (window.visualViewport) {
Checks if the browser supports the visualViewport API, which is the most reliable way to detect zoom level
window.visualViewport.addEventListener('resize', checkBrowserZoom);
Adds a listener that calls checkBrowserZoom() whenever the viewport size changes (including zoom events)
checkBrowserZoom();
Runs an initial zoom check so the game knows if the browser is already zoomed when it starts
showMenu();
Sets gameState to 'MENU' and shows the menu buttons, starting the game at the title screen

draw()

draw() runs 60 times per second by default. By using a switch statement on gameState, you elegantly separate different "scenes" of your game into different handler functions. This makes your code modular and easy to extend.

🔬 This switch statement is the game's brain. What happens if you add a new case like `case 'PAUSED': drawPausedScene(); break;` and then change gameState to 'PAUSED' somewhere else in the code?

  switch (gameState) {
    case 'MENU':
      drawMenu();
      break;
    case 'PLAYING':
      handlePlayingScene();
      break;
    case 'FIGHTING':
      handleFightScene();
      break;
  }
function draw() {
  switch (gameState) {
    case 'MENU':
      drawMenu();
      break;
    case 'PLAYING':
      handlePlayingScene();
      break;
    case 'FIGHTING':
      handleFightScene();
      break;
  }
}
Line-by-line explanation (7 lines)

🔧 Subcomponents:

switch-case Game State Router switch (gameState) { case 'MENU': ... case 'PLAYING': ... case 'FIGHTING': ... }

Routes to different handler functions based on the current game state

switch (gameState) {
Uses a switch statement to branch logic based on the current gameState variable (either 'MENU', 'PLAYING', or 'FIGHTING')
case 'MENU':
If gameState is 'MENU', the code inside this case runs
drawMenu();
Calls the drawMenu() function to display the title screen, game name, and buttons
case 'PLAYING':
If gameState is 'PLAYING', the code inside this case runs
handlePlayingScene();
Calls the main game loop handler that updates and draws all game objects (emojis, people, vehicles, etc.)
case 'FIGHTING':
If gameState is 'FIGHTING', the code inside this case runs
handleFightScene();
Calls the fight handler that manages turn-based combat between a person and a dinosaur

handlePlayingScene()

handlePlayingScene() is the core game loop that runs every frame when gameState is 'PLAYING'. It orchestrates all the visual and logical updates: backgrounds, falling emojis, characters, collision detection, and cleanup. The backward-loop pattern (`i >= 0; i--`) is critical because it allows you to safely remove items from arrays while iterating.

🔬 This loop updates and displays all falling emojis. What happens if you comment out `emoji.update();` so emojis stop moving? Or try adding a line like `emoji.y -= 1;` inside the loop to make emojis rain upward?

  // Update and display falling emojis
  for (let i = fallingEmojis.length - 1; i >= 0; i--) {
    let emoji = fallingEmojis[i];
    emoji.update();
    emoji.display();

    if (emoji.isOffScreen()) {
      // If emoji goes off-screen, replace it with a new one
      fallingEmojis[i] = new Emoji();
    }
  }
function handlePlayingScene() {
  if (isOceanActive) { // Ocean background when sharks are active
    background(100, 150, 200); // Ocean blue
  } else {
    background(220, 100, 150); // Original background color
  }

  // Update and display falling emojis
  for (let i = fallingEmojis.length - 1; i >= 0; i--) {
    let emoji = fallingEmojis[i];
    emoji.update();
    emoji.display();

    if (emoji.isOffScreen()) {
      // If emoji goes off-screen, replace it with a new one
      fallingEmojis[i] = new Emoji();
    }
  }

  // Draw the chef
  drawChef();

  // Draw the "Serving Line" area on the canvas
  // Position it above the uiContainer and the drawn chef, with some padding
  let servingLineY = height - uiContainer.elt.offsetHeight - SERVING_LINE_Y_OFFSET - SERVING_LINE_HEIGHT_ON_CANVAS;
  let servingLineHeight = SERVING_LINE_HEIGHT_ON_CANVAS;

  if (isOceanActive) { // Darker ocean surface for serving line area
    fill(50, 100, 150);
    noStroke(); // Ensure no stroke for serving line
    rect(0, servingLineY, width, servingLineHeight);
  } else {
    fill(180, 80, 120); // Original serving line background
    noStroke();
    rect(0, servingLineY, width, servingLineHeight);
  }

  // Draw collected emojis visually on the serving line
  // These are the emojis available for people to collect
  textSize(EMOJI_SIZE);
  textAlign(LEFT, CENTER);
  let emojiX = 10;
  let emojiY = servingLineY + servingLineHeight / 2;
  for (let emojiChar of collectedEmojis) {
    text(emojiChar, emojiX, emojiY);
    emojiX += EMOJI_SIZE + 5; // Space them out
  }

  // Update and display people
  for (let i = people.length - 1; i >= 0; i--) {
    let person = people[i];
    person.update();
    person.display();

    // If a person is DYING and their timer runs out, they transition to AWAITING_AMBULANCE
    if (person.state === 'DYING' && person.dyingTimer <= 0) {
      person.state = 'AWAITING_AMBULANCE';
      // Dispatch an ambulance if one isn't already targeting this person
      let ambulanceExists = ambulances.some(amb => amb.targetPerson === person);
      if (!ambulanceExists) {
        ambulances.push(new Ambulance(person, people, servingLineY));
      }
    }

    // Determine if this person is currently being targeted by any shark
    // This check is crucial to allow sharks to chase WALKING_AWAY persons off-screen.
    let isBeingChasedByShark = sharks.some(shark => shark.targetPerson === person);

    // Removed isInsideGrabbedHouse check as houses no longer exist

    // Remove person if:
    // 1. They are off-screen right AND they are NOT awaiting an ambulance AND they are NOT being chased by a shark.
    // 2. They are awaiting an ambulance AND the ambulance has picked them up (ambulance handles splicing)
    if (person.isOffScreenRight() && person.state !== 'AWAITING_AMBULANCE' && !isBeingChasedByShark) {
      people.splice(i, 1);
    }
  }

  // Update and display all active UFOs
  for (let i = ufos.length - 1; i >= 0; i--) {
    let currentUfo = ufos[i];
    currentUfo.update();
    currentUfo.display();

    if (currentUfo.isOffScreen()) {
      ufos.splice(i, 1); // Remove UFO object from the array
    }
  }

  // Update and display all active Sharks
  for (let i = sharks.length - 1; i >= 0; i--) {
    let currentShark = sharks[i];
    currentShark.update();
    currentShark.display();

    if (currentShark.isOffScreen()) {
      sharks.splice(i, 1); // Remove Shark object from the array
      // If no sharks left, revert ocean background
      if (sharks.length === 0) {
        isOceanActive = false;
      }
    }
  }

  // Update and display all active Airplanes (crashing planes)
  for (let i = airplanes.length - 1; i >= 0; i--) {
    let currentPlane = airplanes[i];
    currentPlane.update();
    currentPlane.display();

    if (currentPlane.isOffScreen()) {
      airplanes.splice(i, 1); // Remove Airplane object from the array
    }
  }

  // Update and display all active Bullets
  for (let i = bullets.length - 1; i >= 0; i--) {
    let bullet = bullets[i];
    bullet.update();
    bullet.display();

    if (bullet.isOffScreen()) {
      bullets.splice(i, 1);
    }
  }

  // Bullet-Person Collision Check
  for (let i = bullets.length - 1; i >= 0; i--) {
    let bullet = bullets[i];
    for (let j = people.length - 1; j >= 0; j--) {
      let person = people[j];
      if (person.hasGun) continue; // Skip collision check for the shooter themselves

      if (bullet.hits(person) && person.state !== 'DYING' && person.state !== 'AWAITING_AMBULANCE') {
        // Person gets shot and killed
        person.state = 'DYING';
        person.dyingTimer = 60; // Brief dying animation
        person.hasGun = false; // Lose gun on death

        // Create blood splashes at the point of impact
        for (let k = 0; k < random(2, 5); k++) { // Reduced blood splash count
          bloodSplashes.push(new BloodSplash(person.x, person.y - person.size * 0.5)); // Splashes around person's center
        }

        servedCount--; // Penalty for a person being shot
        if (servedCount < 0) servedCount = 0;
        updateUI();
        bullets.splice(i, 1); // Remove bullet
        break; // Bullet only hits one person
      }
    }
  }

  // Update and display all active BloodSplashes
  for (let i = bloodSplashes.length - 1; i >= 0; i--) {
    let splash = bloodSplashes[i];
    splash.update();
    splash.display();

    if (splash.isDead()) {
      bloodSplashes.splice(i, 1); // Remove dead splashes
    }
  }

  // Update and display all active Police Cars
  for (let i = policeCars.length - 1; i >= 0; i--) {
    let currentCar = policeCars[i];
    currentCar.update();
    currentCar.display();

    // If a police car was destroyed by a nuke, remove it after the nuke fades
    if (currentCar.state === 'DESTROYED_BY_NUKE') {
      // Find the nuke explosion created by this car (assuming it's the most recent one at its location)
      let correspondingNuke = nukes.find(n => abs(n.x - currentCar.x) < 5 && abs(n.y - currentCar.y) < 5);
      if (!correspondingNuke || correspondingNuke.isDead()) {
        policeCars.splice(i, 1); // Remove the car
      }
    } else if (currentCar.isOffScreen()) {
      policeCars.splice(i, 1); // Remove car object from the array (original logic)
    }
  }

  // Update and display all active SWAT Teams
  for (let i = swatTeams.length - 1; i >= 0; i--) {
    let currentSWAT = swatTeams[i];
    currentSWAT.update();
    currentSWAT.display();

    if (currentSWAT.isOffScreen()) {
      swatTeams.splice(i, 1); // Remove SWAT object from the array
    }
  }

  // Update and display all active Helicopters
  for (let i = helicopters.length - 1; i >= 0; i--) {
    let currentHeli = helicopters[i];
    currentHeli.update();
    currentHeli.display();

    if (currentHeli.isOffScreen()) {
      helicopters.splice(i, 1); // Remove helicopter object from the array
    }
  }

  // Update and display all active Ambulances
  for (let i = ambulances.length - 1; i >= 0; i--) {
    let currentAmbulance = ambulances[i];
    currentAmbulance.update();
    currentAmbulance.display();

    if (currentAmbulance.isOffScreen()) {
      ambulances.splice(i, 1); // Remove Ambulance object from the array
    }
  }

  // Update and display all active NukeExplosions
  for (let i = nukes.length - 1; i >= 0; i--) {
    let currentNuke = nukes[i];
    currentNuke.update();
    currentNuke.display();

    if (currentNuke.isDead()) {
      nukes.splice(i, 1); // Remove dead nuke explosion
    }
  }
}
Line-by-line explanation (14 lines)

🔧 Subcomponents:

conditional Ocean or Normal Background if (isOceanActive) { background(100, 150, 200); } else { background(220, 100, 150); }

Switches background color between ocean blue (when sharks are active) and the normal pink-peach color

for-loop Falling Emoji Update Loop for (let i = fallingEmojis.length - 1; i >= 0; i--) { emoji.update(); emoji.display(); if (emoji.isOffScreen()) { fallingEmojis[i] = new Emoji(); } }

Updates and displays each falling emoji, replacing ones that go off-screen with new ones to maintain constant rain

calculation Serving Line Rectangle let servingLineY = height - uiContainer.elt.offsetHeight - SERVING_LINE_Y_OFFSET - SERVING_LINE_HEIGHT_ON_CANVAS; rect(0, servingLineY, width, servingLineHeight);

Calculates the Y position of the serving area and draws a colored rectangle to represent where people collect ingredients

for-loop People Update and Display Loop for (let i = people.length - 1; i >= 0; i--) { person.update(); person.display(); ... }

Updates each person's state (walking, collecting, etc.), displays them, and removes them when they leave or die

nested-loop Bullet-Person Collision Detection for (let i = bullets.length - 1; i >= 0; i--) { for (let j = people.length - 1; j >= 0; j--) { if (bullet.hits(person)) { ... } } }

Checks every bullet against every person to see if they collided; if so, kills the person and creates blood splashes

if (isOceanActive) {
Checks if a shark is currently active in the game (when sharks spawn, isOceanActive becomes true)
background(100, 150, 200);
Draws an ocean blue background when sharks are active, creating a visual change in the environment
background(220, 100, 150);
Otherwise draws the normal pinkish-peach background when no sharks are present
for (let i = fallingEmojis.length - 1; i >= 0; i--) {
Loops through every falling emoji backwards (from highest index to 0) so we can safely remove items during iteration
emoji.update();
Calls the emoji's update() method to move it down the screen based on its speed
emoji.display();
Calls the emoji's display() method to draw it at its current position using text()
if (emoji.isOffScreen()) { fallingEmojis[i] = new Emoji(); }
If the emoji went below the bottom of the screen, replace it with a brand new Emoji to keep the rain constant
let servingLineY = height - uiContainer.elt.offsetHeight - SERVING_LINE_Y_OFFSET - SERVING_LINE_HEIGHT_ON_CANVAS;
Calculates where the serving line should be drawn by subtracting the UI height and offsets from the canvas height
rect(0, servingLineY, width, servingLineHeight);
Draws a rectangle spanning the full width of the canvas at the serving line Y position, showing the collection area
for (let emojiChar of collectedEmojis) { text(emojiChar, emojiX, emojiY); emojiX += EMOJI_SIZE + 5; }
Loops through collected emojis and draws each one visually on the serving line, spaced apart horizontally
person.update();
Updates the person's position and state (e.g., if walking, moves them; if collecting, counts down collection timer)
person.display();
Draws the person as a shape with a head, body, and legs, plus any collected emoji they're carrying
if (person.state === 'DYING' && person.dyingTimer <= 0) { person.state = 'AWAITING_AMBULANCE'; ambulances.push(new Ambulance(...)); }
When a person finishes dying, transition them to AWAITING_AMBULANCE and spawn an ambulance to pick them up
if (bullet.hits(person) && person.state !== 'DYING' && person.state !== 'AWAITING_AMBULANCE') { person.state = 'DYING'; ... }
Checks if a bullet collided with a living person; if so, kills them, creates blood splashes, and deducts points

mousePressed()

mousePressed() is called once every time the user clicks or taps the canvas. It's your event handler for user input. By checking gameState first, we route taps differently depending on which screen the player is viewing. The emoji hit-detection loop uses backwards iteration (i >= 0; i--) and returns after the first hit to ensure only one emoji gets tapped per click, even if they overlap.

function mousePressed() {
  if (gameState === 'MENU') {
    startGame(); // Start the game when the menu screen is tapped
    return; // Stop further mousePressed logic
  }

  // Prevent "🦕" action if browser is zoomed (now just a normal ingredient)
  if (isBrowserZoomed) {
    let message = createDiv('The browser is zoomed! This might affect the game, please reset zoom.');
    message.style('position', 'absolute');
    message.style('top', '50%');
    message.style('left', '50%');
    message.style('transform', 'translate(-50%, -50%)');        message.style('font-size', '24px');
    message.style('color', '#CC0000');
    message.style('background-color', 'rgba(255, 255, 255, 0.5)');
    message.style('padding', '15px');
    message.style('border-radius', '0');
    message.style('z-index', '1000');
    message.style('box-shadow', '0 0 10px rgba(0,0,0,0.4)');
    setTimeout(() => message.remove(), 2500);
  }

  // Check if any falling emoji was tapped
  for (let i = fallingEmojis.length - 1; i >= 0; i--) {
    let emoji = fallingEmojis[i];
    if (emoji.isHit(mouseX, mouseY)) {
      if (emoji.char === "🐟" && people.length > 0) {
        // FISH collected! Summon a UFO to kill a person
        const targetPerson = random(people); // Choose a random active person
        ufos.push(new UFO(targetPerson, people)); // Add new UFO to the array
      } else if (emoji.char === "🦭" && people.length > 0) { // Seal collected!
        // SEAL collected! Summon a Shark to kill a person
        const targetPerson = random(people); // Choose a random active person
        sharks.push(new Shark(targetPerson, people)); // Add new Shark to the array
        isOceanActive = true; // Activate ocean background
      } else if (emoji.char === "🦞") { // Lobster collected!
        // LOBSTER collected! Increase wanted level and spawn police if person is available
        wantedLevel = min(5, wantedLevel + 1); // Increase wanted level up to 5 stars
        updateUI(); // Update DOM wantedLevelDisplay immediately

        if (people.length > 0) {
          let targetPerson = people[0]; // Target the first person on the serving line
          let servingLineTopY = height - uiContainer.elt.offsetHeight - SERVING_LINE_Y_OFFSET - SERVING_LINE_HEIGHT_ON_CANVAS;
          if (wantedLevel >= 1) policeCars.push(new PoliceCar(targetPerson, people, servingLineTopY));
          if (wantedLevel >= 3) swatTeams.push(new SWATTeam(targetPerson, people, servingLineTopY));
          if (wantedLevel >= 5) helicopters.push(new PoliceHelicopter(targetPerson, people));
        }
      } else if (emoji.char === "🦀" && people.length > 0) { // Crab collected!
        // CRAB collected! Summon a single Airplane to crash into a person
        const targetPerson = random(people); // Choose a random active person
        airplanes.push(new Airplane(targetPerson, people)); // Add new Airplane to the array
      } else if (emoji.char === "🦧") { // Orangutan collected!
        let shooterPerson = people.find(p => p.hasGun && p.state !== 'DYING' && p.state !== 'AWAITING_AMBULANCE');
        let availablePeopleForGun = people.filter(p => !p.hasGun && p.state !== 'DYING' && p.state !== 'AWAITING_AMBULANCE');

        // Spawn the target person in the middle
        let newTargetPerson = new Person(width / 2); // Spawn in the middle
        people.push(newTargetPerson);

        if (!shooterPerson && availablePeopleForGun.length > 0) {
          // If no one is armed yet, arm a random available person (excluding the new target)
          let potentialShooters = people.filter(p => p !== newTargetPerson && !p.hasGun && p.state !== 'DYING' && p.state !== 'AWAITING_AMBULANCE');
          if (potentialShooters.length > 0) {
            shooterPerson = random(potentialShooters);
            shooterPerson.hasGun = true;
            shooterPerson.shootTimer = 0; // Reset shoot timer
            let message = createDiv('Someone just got a gun! New target in the middle!');
            message.style('position', 'absolute');
            message.style('top', '50%');
            message.style('left', '50%');
            message.style('transform', 'translate(-50%, -50%)');
            message.style('font-size', '32px');
            message.style('color', '#990000');
            message.style('background-color', 'rgba(255, 255, 255, 0.5)');
            message.style('padding', '20px');
            message.style('border-radius', '0');
            message.style('z-index', '1000');
            message.style('box-shadow', '2px 2px 5px rgba(0,0,0,0.3)');
            setTimeout(() => message.remove(), 3000);
          }
        } else if (shooterPerson) {
          // If there's already a shooter, they'll automatically target the new person
          let message = createDiv('New target in the middle!');
          message.style('position', 'absolute');
          message.style('top', '50%');
          message.style('left', '50%');
          message.style('transform', 'translate(-50%, -50%)');
          message.style('font-size', '32px');
          message.style('color', '#007700');
          message.style('background-color', 'rgba(255, 255, 255, 0.5)');
          message.style('padding', '20px');
          message.style('border-radius', '0');
          message.style('z-index', '1000');
          message.style('box-shadow', '2px 2px 5px rgba(0,0,0,0.3)');
          setTimeout(() => message.remove(), 3000);
        }
      } else if (emoji.char === "🦕") { // DINOSAUR collected! Now just a normal ingredient.
        collectedEmojis.push(emoji.char); // Treat it as a normal ingredient

        let message = createDiv(`Collected an ingredient: ${emoji.char}`);
        message.style('position', 'absolute');
        message.style('top', '50%');
        message.style('left', '50%');
        message.style('transform', 'translate(-50%, -50%)');
        message.style('font-size', '32px');
        message.style('color', '#007700');
        message.style('background-color', 'rgba(255, 255, 255, 0.5)');
        message.style('padding', '20px');
        message.style('border-radius', '0');
        message.style('z-index', '1000');
        message.style('box-shadow', '2px 2px 5px rgba(0,0,0,0.3)');
        setTimeout(() => message.remove(), 3000);
      } else if (emoji.char === "🦖") { // T-Rex collected! Trigger a fight!
        gameState = 'FIGHTING';
        fightMessages = []; // Clear previous fight messages
        let servingLineY = height - uiContainer.elt.offsetHeight - SERVING_LINE_Y_OFFSET - SERVING_LINE_HEIGHT_ON_CANVAS;
        fighter1 = new Fighter(width * 0.25, servingLineY, 'person', random(personWeapons));
        fighter2 = new Fighter(width * 0.75, servingLineY, 'dinosaur', random(dinosaurWeapons));
        fightMessages.unshift({ text: "A mighty T-Rex approaches! FIGHT!", lifespan: FIGHT_MESSAGE_LIFESPAN * 2 });
        fightTimer = FIGHT_TURN_DURATION;
      } else {
        // NORMAL EMOJI collected - ONLY add to ingredients, DO NOT spawn people!
        collectedEmojis.push(emoji.char); // Add emoji character to collected array

        let message = createDiv(`Collected an ingredient: ${emoji.char}`);
        message.style('position', 'absolute');
        message.style('top', '50%');
        message.style('left', '50%');
        message.style('transform', 'translate(-50%, -50%)');
        message.style('font-size', '32px');
        message.style('color', '#007700');
        message.style('background-color', 'rgba(255, 255, 255, 0.5)');
        message.style('padding', '20px');
        message.style('border-radius', '0');
        message.style('z-index', '1000');
        message.style('box-shadow', '2px 2px 5px rgba(0,0,0,0.3)');
        setTimeout(() => message.remove(), 2000);
      }

      fallingEmojis.splice(i, 1); // Remove from falling array
      fallingEmojis.push(new Emoji()); // Add a new emoji to keep the rain going
      updateUI(); // Update DOM collectedArea and button state with new emojis
      return; // Stop checking after the first hit
    }
  }
}
Line-by-line explanation (24 lines)

🔧 Subcomponents:

for-loop Emoji Hit Detection Loop for (let i = fallingEmojis.length - 1; i >= 0; i--) { let emoji = fallingEmojis[i]; if (emoji.isHit(mouseX, mouseY)) { ... } }

Loops through all falling emojis backwards and checks if any were tapped at the mouse position

conditional Fish (UFO) Event if (emoji.char === "🐟" && people.length > 0) { ufos.push(new UFO(targetPerson, people)); }

When fish is tapped, spawns a UFO that will attack a random person

conditional Seal (Shark) Event if (emoji.char === "🦭" && people.length > 0) { sharks.push(new Shark(...)); isOceanActive = true; }

When seal is tapped, spawns a shark and activates the ocean background

conditional Lobster (Wanted Level) Event if (emoji.char === "🦞") { wantedLevel = min(5, wantedLevel + 1); ... if (wantedLevel >= 1) policeCars.push(...); }

When lobster is tapped, increases wanted level and spawns police to chase the first person on the serving line

conditional Orangutan (Gun) Event if (emoji.char === "🦧") { newTargetPerson = new Person(width / 2); people.push(newTargetPerson); shooterPerson.hasGun = true; }

When orangutan is tapped, spawns a new target person in the middle and arms an existing person with a gun

conditional T-Rex (Fight) Event if (emoji.char === "🦖") { gameState = 'FIGHTING'; fighter1 = new Fighter(...); fighter2 = new Fighter(...); }

When T-Rex is tapped, switches to FIGHTING game state and creates two fighters

if (gameState === 'MENU') {
Checks if we're currently viewing the menu screen
startGame();
Calls startGame() to initialize all game variables and switch to PLAYING state
return;
Exits mousePressed() immediately after starting the game, preventing further tap logic
for (let i = fallingEmojis.length - 1; i >= 0; i--) {
Loops through all falling emojis from last to first (backward iteration allows safe removal)
if (emoji.isHit(mouseX, mouseY)) {
Checks if this emoji's position is within tap distance of the mouse click (using Emoji's isHit() method)
if (emoji.char === "🐟" && people.length > 0) {
If this is a fish emoji AND there are people on the serving line, trigger a UFO spawn
const targetPerson = random(people);
Chooses a random person from the people array to be attacked by the UFO
ufos.push(new UFO(targetPerson, people));
Creates a new UFO object targeting that person and adds it to the ufos array
if (emoji.char === "🦭" && people.length > 0) {
If this is a seal emoji AND there are people on the serving line, trigger a shark spawn
isOceanActive = true;
Sets the flag to activate the ocean background, changing the background color next frame
if (emoji.char === "🦞") {
If this is a lobster emoji, increase the wanted level and spawn police entities
wantedLevel = min(5, wantedLevel + 1);
Increases wanted level by 1, but caps it at 5 (using min() function)
if (wantedLevel >= 1) policeCars.push(new PoliceCar(...));
If wanted level is now 1 or higher, spawns a police car to chase the first person
if (emoji.char === "🦧") {
If this is an orangutan emoji, spawn a new person and arm one with a gun
let newTargetPerson = new Person(width / 2);
Creates a new person and passes width / 2 as their starting X position, placing them in the middle of the canvas
shooterPerson.hasGun = true;
Sets the hasGun flag to true for a randomly selected person, enabling them to shoot every PERSON_SHOOT_INTERVAL frames
if (emoji.char === "🦖") {
If this is a T-Rex emoji, transition to the FIGHTING game state
gameState = 'FIGHTING';
Changes the global gameState variable to 'FIGHTING', which causes draw() to call handleFightScene() instead
fighter1 = new Fighter(width * 0.25, servingLineY, 'person', random(personWeapons));
Creates a Fighter object representing a person at 25% of the canvas width, with a random weapon from personWeapons array
fighter2 = new Fighter(width * 0.75, servingLineY, 'dinosaur', random(dinosaurWeapons));
Creates a Fighter object representing a dinosaur at 75% of the canvas width, with a random weapon from dinosaurWeapons array
fallingEmojis.splice(i, 1);
Removes the tapped emoji from the fallingEmojis array using splice() at index i
fallingEmojis.push(new Emoji());
Adds a brand new Emoji object to keep the rain going (maintains constant emoji count)
updateUI();
Calls updateUI() to refresh the DOM displays (collected ingredients, served count, wanted level)
return;
Exits mousePressed() after handling the tap, preventing checking other emojis

cookAndServe()

cookAndServe() is called when the 'Call a Customer!' button is clicked. It's a key game mechanic: only if the player has collected ingredients can they spawn people who will take those ingredients and increase servedCount. The function also ties the wanted level system to the police spawning, creating escalating difficulty. The setTimeout() pattern creates a temporary pop-up message that auto-removes after a delay.

🔬 Police spawn based on wanted level thresholds (1, 3, 5). What happens if you change these thresholds—say, spawn police at 1+, 2+, 3+ instead? Try editing the numbers and see when different vehicles appear.

      // If there's a wanted level, spawn police entities for this new person
      if (wantedLevel > 0) {
        // Pass the top Y of the serving line rectangle to police vehicles
        let servingLineTopY = height - uiContainer.elt.offsetHeight - SERVING_LINE_Y_OFFSET - SERVING_LINE_HEIGHT_ON_CANVAS;
        if (wantedLevel >= 1) policeCars.push(new PoliceCar(newPerson, people, servingLineTopY));
        if (wantedLevel >= 3) swatTeams.push(new SWATTeam(newPerson, people, servingLineTopY));
        if (wantedLevel >= 5) helicopters.push(new PoliceHelicopter(newPerson, people));
      }
function cookAndServe() {
  // Only proceed if there are ingredients
  if (collectedEmojis.length > 0) {
    let numPeopleToSpawn = 1; // Only spawn one person
    for (let j = 0; j < numPeopleToSpawn; j++) {
      let newPerson = new Person();
      people.push(newPerson);

      // If there's a wanted level, spawn police entities for this new person
      if (wantedLevel > 0) {
        // Pass the top Y of the serving line rectangle to police vehicles
        let servingLineTopY = height - uiContainer.elt.offsetHeight - SERVING_LINE_Y_OFFSET - SERVING_LINE_HEIGHT_ON_CANVAS;
        if (wantedLevel >= 1) policeCars.push(new PoliceCar(newPerson, people, servingLineTopY));
        if (wantedLevel >= 3) swatTeams.push(new SWATTeam(newPerson, people, servingLineTopY));
        if (wantedLevel >= 5) helicopters.push(new PoliceHelicopter(newPerson, people));
      }
    }

    // Display message for a single customer
    let message = createDiv('A customer has arrived!');
    message.style('position', 'absolute');
    message.style('top', '50%');
    message.style('left', '50%');
    message.style('transform', 'translate(-50%, -50%)');
    message.style('font-size', '32px');
    message.style('color', '#007700');
    message.style('background-color', 'rgba(255, 255, 255, 0.5)');
    message.style('padding', '20px');
    message.style('border-radius', '0');
    message.style('z-index', '1000');
    message.style('box-shadow', '2px 2px 5px rgba(0,0,0,0.3)');
    setTimeout(() => message.remove(), 2000);
  } else {
    // If no emojis collected, show a message (DOM element)
    let noEmojisMessage = createDiv('Nothing to serve! Tap some raining emojis first.');
    noEmojisMessage.style('position', 'absolute');
    noEmojisMessage.style('top', '50%');
    noEmojisMessage.style('left', '50%');
    noEmojisMessage.style('transform', 'translate(-50%, -50%)');
    noEmojisMessage.style('font-size', '24px');
    noEmojisMessage.style('color', '#CC0000');
    noEmojisMessage.style('background-color', 'rgba(255, 255, 255, 0.5)');
    noEmojisMessage.style('padding', '15px');
    noEmojisMessage.style('border-radius', '0');
    noEmojisMessage.style('z-index', '1000');
    noEmojisMessage.style('box-shadow', '0 0 10px rgba(0,0,0,0.4)');
    setTimeout(() => noEmojisMessage.remove(), 2500);
  }
}
Line-by-line explanation (13 lines)

🔧 Subcomponents:

conditional Ingredient Availability Check if (collectedEmojis.length > 0) { ... } else { ... }

Checks if the player has collected any ingredients before spawning a customer

for-loop Customer Spawn Loop for (let j = 0; j < numPeopleToSpawn; j++) { let newPerson = new Person(); people.push(newPerson); }

Creates and adds one new Person object to the people array

conditional Police Entity Spawn (Wanted Level) if (wantedLevel > 0) { if (wantedLevel >= 1) policeCars.push(...); if (wantedLevel >= 3) swatTeams.push(...); if (wantedLevel >= 5) helicopters.push(...); }

Spawns different police entities based on the player's wanted level

function-call Customer Arrival Message let message = createDiv('A customer has arrived!'); ... setTimeout(() => message.remove(), 2000);

Creates and displays a temporary DOM message that vanishes after 2 seconds

function-call No Ingredients Error Message let noEmojisMessage = createDiv('Nothing to serve!...'); setTimeout(() => noEmojisMessage.remove(), 2500);

Shows an error message if the player tries to call a customer without collecting ingredients

if (collectedEmojis.length > 0) {
Checks if the collectedEmojis array has at least one emoji in it
let numPeopleToSpawn = 1;
Sets the number of people to spawn to 1 (only one customer arrives per button click)
let newPerson = new Person();
Creates a new Person object using the default constructor, which starts them off-screen to the right
people.push(newPerson);
Adds the newly created person to the people array so they'll be updated and drawn each frame
if (wantedLevel > 0) {
Checks if the player's wanted level is 1 or higher; if so, spawn police to pursue this customer
if (wantedLevel >= 1) policeCars.push(new PoliceCar(newPerson, people, servingLineTopY));
If wanted level is 1+, adds a police car to the array to chase the new customer
if (wantedLevel >= 3) swatTeams.push(new SWATTeam(newPerson, people, servingLineTopY));
If wanted level is 3+, adds a SWAT team to the array (in addition to police cars)
if (wantedLevel >= 5) helicopters.push(new PoliceHelicopter(newPerson, people));
If wanted level is 5 (maximum), adds a helicopter to the array (in addition to police cars and SWAT)
let message = createDiv('A customer has arrived!');
Creates a new DOM div element with the text 'A customer has arrived!'
setTimeout(() => message.remove(), 2000);
Schedules the message to be removed from the DOM after 2000 milliseconds (2 seconds)
} else {
If collectedEmojis.length is 0 (no ingredients collected), this block runs instead
let noEmojisMessage = createDiv('Nothing to serve! Tap some raining emojis first.');
Creates a DOM div with an error message telling the player to collect ingredients first
setTimeout(() => noEmojisMessage.remove(), 2500);
Schedules the error message to disappear after 2500 milliseconds (2.5 seconds)

createUI()

createUI() is called once in setup() to initialize all DOM elements. It uses p5.Div objects and the .style() method to create responsive, styled UI that sits on top of the p5.js canvas. By using flexbox layout and absolute positioning, the UI stays anchored to the bottom of the window even when the window is resized. Each button is given a mousePressed() callback, linking user interaction to game functions.

function createUI() {
  // 1. Create uiContainer (bottom part with buttons/counters)
  uiContainer = createDiv();
  uiContainer.style('position', 'absolute');
  uiContainer.style('bottom', '10px');
  uiContainer.style('width', 'calc(100% - 20px)');
  uiContainer.style('left', '10px');
  uiContainer.style('display', 'flex');
  uiContainer.style('flex-direction', 'column');
  uiContainer.style('align-items', 'center');
  uiContainer.style('background-color', 'rgba(255, 255, 255, 0.2)');
  uiContainer.style('padding', '10px');
  uiContainer.style('border-radius', '0');
  uiContainer.style('font-family', 'sans-serif');
  uiContainer.style('font-size', '16px');
  uiContainer.style('z-index', '100');

  // Collected emojis display (shows what user has tapped)
  collectedArea = createDiv('Available Ingredients: ');
  collectedArea.style('margin-bottom', '10px');
  collectedArea.style('min-height', '30px');
  collectedArea.style('background-color', 'rgba(255, 255, 255, 0.1)');
  collectedArea.style('padding', '5px 10px');
  collectedArea.style('border-radius', '0');
  uiContainer.child(collectedArea);

  // How to Play button
  howToPlayButton = createButton('How to Play');
  howToPlayButton.style('font-size', '16px');
  howToPlayButton.style('padding', '8px 15px');
  howToPlayButton.style('margin-bottom', '5px');
  howToPlayButton.style('background-color', 'rgba(0, 123, 255, 0.7)');
  howToPlayButton.style('color', 'white');
  howToPlayButton.style('border', 'none');
  howToPlayButton.style('border-radius', '0');
  howToPlayButton.style('cursor', 'pointer');
  howToPlayButton.style('box-shadow', '1px 1px 3px rgba(0,0,0,0.3)');
  howToPlayButton.style('touch-action', 'manipulation');
  howToPlayButton.mousePressed(showHowToPlay);
  uiContainer.child(howToPlayButton);

  // Tutorial how do play button
  tutorialButton = createButton('Tutorial how do play');
  tutorialButton.style('font-size', '16px');
  tutorialButton.style('padding', '8px 15px');
  tutorialButton.style('margin-bottom', '10px');
  tutorialButton.style('background-color', 'rgba(255, 193, 7, 0.7)');
  tutorialButton.style('color', 'black');
  tutorialButton.style('border', 'none');
  tutorialButton.style('border-radius', '0');
  tutorialButton.style('cursor', 'pointer');
  tutorialButton.style('box-shadow', '1px 1px 3px rgba(0,0,0,0.3)');
  tutorialButton.style('touch-action', 'manipulation');
  tutorialButton.mousePressed(showTutorial);
  uiContainer.child(tutorialButton);

  // NEW Play Button (for menu) - Styled like Roblox play button but different
  playButton = createButton('Play');
  playButton.style('font-size', '28px');
  playButton.style('font-weight', 'bold');
  playButton.style('padding', '15px 30px');
  playButton.style('margin-bottom', '20px');
  playButton.style('background-color', '#129612');
  playButton.style('color', 'white');
  playButton.style('border', '2px solid #0b6b0b');
  playButton.style('border-radius', '15px');
  playButton.style('cursor', 'pointer');
  playButton.style('box-shadow', '4px 4px 8px rgba(0,0,0,0.5)');
  playButton.style('touch-action', 'manipulation');
  playButton.mousePressed(startGame);
  uiContainer.child(playButton);

  // Cook & Serve! button (now "Call a Customer!") - assigned to callCustomerButton
  callCustomerButton = createButton('Call a Customer!');
  callCustomerButton.style('font-size', '20px');
  callCustomerButton.style('padding', '10px 20px');
  callCustomerButton.style('margin-bottom', '10px');
  callCustomerButton.style('background-color', 'rgba(76, 175, 80, 0.7)');
  callCustomerButton.style('color', 'white');
  callCustomerButton.style('border', 'none');
  callCustomerButton.style('border-radius', '0');
  callCustomerButton.style('cursor', 'pointer');
  callCustomerButton.style('box-shadow', '2px 2px 5px rgba(0,0,0,0.3)');
  callCustomerButton.style('touch-action', 'manipulation');
  callCustomerButton.mousePressed(cookAndServe);
  uiContainer.child(callCustomerButton);

  // Served meals display
  servedDisplay = createDiv('Served: 0 meals');
  servedDisplay.style('margin-bottom', '10px');
  servedDisplay.style('background-color', 'rgba(255, 255, 255, 0.1)');
  servedDisplay.style('padding', '5px 10px');
  servedDisplay.style('border-radius', '0');
  uiContainer.child(servedDisplay);

  // Wanted Level Display
  wantedLevelDisplay = createDiv('Wanted: 0 ⭐️');
  wantedLevelDisplay.style('font-size', '20px');
  wantedLevelDisplay.style('color', '#FFD700');
  wantedLevelDisplay.style('background-color', 'rgba(255, 255, 255, 0.1)');
  wantedLevelDisplay.style('padding', '5px 10px');
  wantedLevelDisplay.style('border-radius', '0');
  uiContainer.child(wantedLevelDisplay);

  // Initial update for collectedArea and servedDisplay
  updateUI();
}
Line-by-line explanation (17 lines)

🔧 Subcomponents:

calculation Main UI Container uiContainer = createDiv(); ... uiContainer.style('position', 'absolute'); ... uiContainer.style('z-index', '100');

Creates the main DOM container that holds all UI elements, positioned absolutely at the bottom of the canvas

calculation Ingredients Display collectedArea = createDiv('Available Ingredients: '); ... uiContainer.child(collectedArea);

Creates a display div showing collected emoji ingredients and adds it to the UI container

calculation Button Creation Pattern howToPlayButton = createButton('How to Play'); ... uiContainer.child(howToPlayButton); ... tutorialButton = createButton(...);

Creates styled buttons and adds them to the UI container, following a consistent pattern

calculation Play Button (Larger) playButton = createButton('Play'); ... playButton.style('font-size', '28px'); ... playButton.style('background-color', '#129612');

Creates a large, bright green play button with special styling to draw attention on the menu

uiContainer = createDiv();
Creates a new p5.Div that will hold all UI elements (buttons, displays)
uiContainer.style('position', 'absolute');
Positions the container absolutely so it sits on top of the canvas rather than below it
uiContainer.style('bottom', '10px');
Positions the container 10 pixels from the bottom edge of the window
uiContainer.style('display', 'flex'); uiContainer.style('flex-direction', 'column');
Uses flexbox layout with items stacked vertically (column direction), making buttons stack downward
uiContainer.style('align-items', 'center');
Centers all child elements horizontally within the container
uiContainer.style('z-index', '100');
Sets a high z-index so the UI appears on top of the canvas (higher z-index = rendered in front)
collectedArea = createDiv('Available Ingredients: ');
Creates a div that will display the emojis the player has collected as ingredients
uiContainer.child(collectedArea);
Adds collectedArea as a child of uiContainer so it appears inside and is positioned by flexbox
howToPlayButton = createButton('How to Play');
Creates a button with the text 'How to Play' that will show game instructions when clicked
howToPlayButton.mousePressed(showHowToPlay);
Attaches the showHowToPlay() function to this button, so clicking it calls that function
playButton.style('font-size', '28px'); playButton.style('font-weight', 'bold');
Makes the Play button's text large (28px) and bold so it stands out prominently on the menu
playButton.style('background-color', '#129612');
Sets the Play button's background to bright green (#129612), drawing the eye
playButton.mousePressed(startGame);
Attaches the startGame() function to the Play button, so clicking it starts the game
callCustomerButton.mousePressed(cookAndServe);
Attaches the cookAndServe() function to the 'Call a Customer!' button, spawning a new person when clicked
servedDisplay = createDiv('Served: 0 meals');
Creates a div to display the number of meals served (updated by updateUI() each frame)
wantedLevelDisplay = createDiv('Wanted: 0 ⭐️');
Creates a div to display the wanted level with star emojis (updated by updateUI())
updateUI();
Calls updateUI() to set initial values for all displays based on current game variables

Person (class)

The Person class uses a state machine (five states: WALKING_TO_COLLECT, COLLECTING, WALKING_AWAY, DYING, AWAITING_AMBULANCE) to manage its lifecycle. The update() method handles state transitions and logic, while display() renders different visuals depending on state. The gun logic demonstrates how to trigger an action repeatedly based on a timer—a common pattern in games. The drawPerson() method is factored out so other classes (like Fighter) can reuse it.

🔬 This state handles people walking to the collection spot. What happens if you change `collectionSpotX = width * 0.7` (in the constructor) to `width * 0.5`? People will collect at the middle instead of at 70% across the screen—try it!

      case 'WALKING_TO_COLLECT':
        this.x -= this.speed;
        if (this.x <= this.collectionSpotX) {
          this.x = this.collectionSpotX;
          this.state = 'COLLECTING';
          this.collectionTimer = COLLECTION_DURATION;
          // When they reach the spot, they take an emoji from the available pool
          if (collectedEmojis.length > 0) {
            this.collectedEmoji = collectedEmojis.shift(); // Take the first emoji
class Person {
  constructor(initialX) { // Optional initialX parameter
    // Person's feet are at GROUND_Y
    this.y = height - uiContainer.elt.offsetHeight - SERVING_LINE_Y_OFFSET;
    this.x = initialX !== undefined ? initialX : width + PERSON_SIZE; // Use initialX if provided, else off-screen right

    this.size = PERSON_SIZE; // Total height of the drawn person
    this.speed = PERSON_SPEED;
    this.collectionSpotX = width * PERSON_COLLECTION_SPOT_X_RATIO; // Specific x to collect
    this.state = 'WALKING_TO_COLLECT'; // Possible states: WALKING_TO_COLLECT, COLLECTING, WALKING_AWAY, DYING, AWAITING_AMBULANCE
    this.collectedEmoji = null; // The emoji this person collects
    this.collectionTimer = 0; // Timer for collecting state
    this.dyingTimer = 0; // Timer for dying state

    this.hasGun = false; // Does this person have a gun?
    this.shootTimer = 0; // Timer for shooting rate
    this.insideHouse = false; // New flag to mark persons inside houses (will be removed soon)
  }

  update() {
    switch (this.state) {
      case 'WALKING_TO_COLLECT':
        this.x -= this.speed;
        if (this.x <= this.collectionSpotX) {
          this.x = this.collectionSpotX;
          this.state = 'COLLECTING';
          this.collectionTimer = COLLECTION_DURATION;
          // When they reach the spot, they take an emoji from the available pool
          if (collectedEmojis.length > 0) {
            this.collectedEmoji = collectedEmojis.shift(); // Take the first emoji
          } else {
            // If no emojis available, they leave immediately
            this.state = 'WALKING_AWAY';
          }
        }
        break;
      case 'COLLECTING':
        this.collectionTimer--;
        if (this.collectionTimer <= 0) {
          this.state = 'WALKING_AWAY';
          // Only increment servedCount if they actually collected an emoji
          if (this.collectedEmoji) {
            servedCount++;
          }
          updateUI(); // Update DOM displays after collection/serving
        }
        break;
      case 'WALKING_AWAY':
        this.x += this.speed;
        // If this person just shot someone, spawn a police car for them as they leave
        if (this.justShot && people.length > 0) { // Ensure there are other people to target
          let servingLineTopY = height - uiContainer.elt.offsetHeight - SERVING_LINE_Y_OFFSET - SERVING_LINE_HEIGHT_ON_CANVAS;
          let policeCarForShooter = new PoliceCar(this, people, servingLineTopY);
          policeCarForShooter.isShooterTarget = true; // Mark this car as targeting a shooter
          policeCars.push(policeCarForShooter);
          this.justShot = false; // Reset flag
        }
        break;
      case 'DYING': // Handle the dying state
        this.dyingTimer--;
        // The actual removal from the `people` array will happen in the main `draw` loop
        // once `dyingTimer` reaches 0.
        break;
      case 'AWAITING_AMBULANCE': // Person is dead and waiting to be picked up
        // Do nothing, just display. The ambulance will handle removal.
        break;
    }

    // Armed person shooting logic
    if (this.hasGun && this.state !== 'DYING' && this.state !== 'AWAITING_AMBULANCE') {
      this.shootTimer++;
      if (this.shootTimer >= PERSON_SHOOT_INTERVAL) {
        this.shootTimer = 0;
        this.findTargetAndShoot(); // Find another person and shoot them
        this.hasGun = false; // Gun is used up after one shot
        this.justShot = true; // Set flag to spawn police car when they walk away
        this.state = 'WALKING_AWAY'; // Armed person leaves after shooting
      }
    }
  }

  display() {
    if (this.state === 'DYING' || this.state === 'AWAITING_AMBULANCE') { // Display for the dead state
      fill(0); // Black color for the dead person
      noStroke();
      rectMode(CENTER);
      push();
      translate(this.x, this.y);
      rotate(PI / 2); // Rotate to lie down (90 degrees)

      let headSize = this.size * 0.4;
      let bodyWidth = this.size * 0.3;
      let bodyHeight = this.size * 0.4;
      let legHeight = this.size * 0.2;
      let legWidth = bodyWidth * 0.4;

      // Draw the person rotated
      circle(0, -headSize / 2, headSize);
      rect(0, bodyHeight / 2, bodyWidth, bodyHeight);
      rect(-legWidth / 2, bodyHeight + legHeight / 2, legWidth, legHeight);
      rect(legWidth / 2, bodyHeight + legHeight / 2, legWidth, legHeight);
      pop();

      // Draw a red 'X' over the dead person
      stroke(255, 0, 0); // Red 'X'
      strokeWeight(3);
      line(this.x - this.size / 2, this.y - this.size / 2, this.x + this.size / 2, this.y + this.size / 2);
      line(this.x + this.size / 2, this.y - this.size / 2, this.x - this.size / 2, this.y + this.size / 2);
      noStroke();

    } else {
      this.drawPerson(this.x, this.y, this.size); // Draw the normal person shape at its position

      // If they have collected an emoji, display it next to them
      if (this.collectedEmoji) {
        textSize(EMOJI_SIZE);
        textAlign(CENTER, CENTER); // Ensure emoji is centered
        text(this.collectedEmoji, this.x - this.size / 2 - EMOJI_SIZE / 2, this.y - this.size / 2); // Adjusted emoji Y
      }

      // Draw gun if person has one
      if (this.hasGun) {
        fill(50); // Dark gray for gun
        noStroke();
        rectMode(CORNER); // Draw gun from its top-left corner
        rect(this.x + GUN_DRAW_OFFSET, this.y - this.size * 0.7, 25, 6, 2); // Simple gun body
        rect(this.x + GUN_DRAW_OFFSET + 10, this.y - this.size * 0.7 - 5, 10, 5, 2); // Gun sight/top part
        circle(this.x + GUN_DRAW_OFFSET + 25, this.y - this.size * 0.7 + 3, 5); // Muzzle flash point
      }
    }
  }

  // Method to draw the person using p5.js shapes (now a standalone function for reuse)
  drawPerson(personX, personY, personSize) {
    fill(0); // Black color for the person
    noStroke();

    let headSize = personSize * 0.4;
    let bodyWidth = personSize * 0.3;
    let bodyHeight = personSize * 0.4;
    let legHeight = personSize * 0.2;
    let legWidth = bodyWidth * 0.4;

    // Calculate Y positions relative to personY (feet)
    let headY = personY - personSize + headSize / 2; // Head drawn headSize/2 above y - size (top of person)
    let bodyY = personY - personSize + headSize + bodyHeight / 2; // Body drawn bodyHeight/2 above y - size + headSize
    let legY = personY - legHeight / 2; // Legs drawn centered at y - legHeight/2

    // Head
    circle(personX, headY, headSize);

    // Body
    rectMode(CENTER);
    rect(personX, bodyY, bodyWidth, bodyHeight);

    // Legs
    rect(personX - legWidth / 2, legY, legWidth, legHeight);
    rect(personX + legWidth / 2, legY, legWidth, legHeight);
  }

  // Find another person to shoot, prioritizing the one closest to the middle X
  findTargetAndShoot() {
    let targetPerson = null;
    let otherPeople = people.filter(p => p !== this && p.state !== 'DYING' && p.state !== 'AWAITING_AMBULANCE');

    if (otherPeople.length > 0) {
      let closestDistanceToMiddle = Infinity;
      let personInMiddle = null;

      // Find the person closest to the middle X-coordinate
      for (let person of otherPeople) {
        let distanceToMiddle = abs(person.x - width / 2);
        if (distanceToMiddle < closestDistanceToMiddle) {
          closestDistanceToMiddle = distanceToMiddle;
          personInMiddle = person;
        }
      }

      if (personInMiddle) {
        targetPerson = personInMiddle;
      } else {
        // Fallback: If for some reason no one is found (shouldn't happen if otherPeople.length > 0),
        // pick a random person.
        targetPerson = random(otherPeople);
      }

      this.shoot(targetPerson);
    }
  }

  // Create a bullet object targeting another person
  shoot(targetPerson) {
    let gunX = this.x + GUN_DRAW_OFFSET + 25; // Muzzle of the gun
    let gunY = this.y - this.size * 0.7 + 3; // Muzzle of the gun

    // Target slightly above the person's head for a more visible shot
    let targetX = targetPerson.x;
    let targetY = targetPerson.y - targetPerson.size * 0.8;

    // Calculate angle towards target
    let angle = atan2(targetY - gunY, targetX - gunY);

    // Create and add bullet to array
    bullets.push(new Bullet(gunX, gunY, angle));
  }

  isOffScreenRight() {
    return this.x > width + PERSON_SIZE;
  }
}
Line-by-line explanation (19 lines)

🔧 Subcomponents:

switch-case Person State Machine switch (this.state) { case 'WALKING_TO_COLLECT': ... case 'COLLECTING': ... case 'WALKING_AWAY': ... case 'DYING': ... case 'AWAITING_AMBULANCE': ... }

Manages person's lifecycle with different behaviors for each state

conditional Armed Person Shooting Logic if (this.hasGun && this.state !== 'DYING' && this.state !== 'AWAITING_AMBULANCE') { this.shootTimer++; if (this.shootTimer >= PERSON_SHOOT_INTERVAL) { this.findTargetAndShoot(); ... } }

Handles armed person's automatic shooting behavior every PERSON_SHOOT_INTERVAL frames

conditional Dead Person Display if (this.state === 'DYING' || this.state === 'AWAITING_AMBULANCE') { ... push(); translate(...); rotate(PI / 2); ... pop(); }

Draws a dead person rotated 90 degrees and adds a red X

conditional Alive Person Display } else { this.drawPerson(...); if (this.collectedEmoji) { text(this.collectedEmoji, ...); } if (this.hasGun) { ... } }

Draws a standing person with optional emoji and gun

constructor(initialX) {
The constructor takes an optional initialX parameter for custom starting position (used by orangutan to spawn in middle)
this.y = height - uiContainer.elt.offsetHeight - SERVING_LINE_Y_OFFSET;
Sets the person's Y position to the serving line (feet at the line, not floating above it)
this.x = initialX !== undefined ? initialX : width + PERSON_SIZE;
If initialX was provided, use it; otherwise start off-screen to the right, ready to walk in
this.state = 'WALKING_TO_COLLECT';
Initial state: person starts walking toward the collection spot on the serving line
case 'WALKING_TO_COLLECT': this.x -= this.speed;
Decreases x each frame, moving the person left toward the collection spot
if (this.x <= this.collectionSpotX) { this.state = 'COLLECTING'; this.collectionTimer = COLLECTION_DURATION; }
When the person reaches the collection spot, transition to COLLECTING state and start the collection timer
if (collectedEmojis.length > 0) { this.collectedEmoji = collectedEmojis.shift(); }
Takes the first emoji from the global collectedEmojis array (removes it from the array and stores it on the person)
case 'COLLECTING': this.collectionTimer--;
Counts down the collection timer every frame; when it reaches 0, person finishes collecting
if (this.collectedEmoji) { servedCount++; }
Increments the servedCount by 1 (awards the player a point for successfully serving this person)
case 'WALKING_AWAY': this.x += this.speed;
Increases x each frame, moving the person right off-screen
if (this.hasGun && this.state !== 'DYING' && this.state !== 'AWAITING_AMBULANCE') { this.shootTimer++; }
If the person has a gun and isn't dead, increment the shoot timer toward the next shot
if (this.shootTimer >= PERSON_SHOOT_INTERVAL) { this.shootTimer = 0; this.findTargetAndShoot(); this.hasGun = false; }
When the shoot timer reaches the interval, fire at another person, disable the gun, and transition to WALKING_AWAY
if (this.state === 'DYING' || this.state === 'AWAITING_AMBULANCE') { push(); translate(this.x, this.y); rotate(PI / 2); ... pop(); }
If dead, use push/pop and translate/rotate to draw the person lying down (rotated 90 degrees)
stroke(255, 0, 0); line(...); line(...);
Draws two red lines forming an X over the dead person's body
if (this.collectedEmoji) { text(this.collectedEmoji, this.x - this.size / 2 - EMOJI_SIZE / 2, ...); }
If the person is carrying an emoji, draws it to the left of their body
if (this.hasGun) { fill(50); rect(this.x + GUN_DRAW_OFFSET, this.y - this.size * 0.7, 25, 6, 2); ... }
If the person has a gun, draws a simple gun shape (rectangles and circle) near their head
let targetPerson = people.filter(p => p !== this && p.state !== 'DYING' && p.state !== 'AWAITING_AMBULANCE');
Filters the people array to find all living people except the shooter
let distanceToMiddle = abs(person.x - width / 2);
Calculates how far each person is from the center of the canvas (x = width / 2)
let angle = atan2(targetY - gunY, targetX - gunY);
Calculates the angle from the gun to the target using atan2, used to aim the bullet

Emoji (class)

The Emoji class is simple but powerful: each emoji tracks its own position, character, and falling speed. The speed varies by emoji type to create gameplay depth—special emojis that trigger events (fish, lobster) fall slower so they're easier to target, while normal ingredients fall faster for challenge. The isHit() method uses the distance formula (dist() function) to detect taps with a circular hit area, a common pattern in p5.js games.

class Emoji {
  constructor() {
    this.x = random(width);
    this.y = random(-height, 0); // Start off-screen above
    this.char = random(emojis);
    this.size = EMOJI_SIZE; // Consistent emoji size

    // Set speed based on the emoji character
    if (this.char === "🐟") {
      this.speed = random(0.5, 1.5); // Slower speed for fish
    } else if (this.char === "🦭") { // Slower speed for seal
      this.speed = random(1.0, 2.5);
    } else if (this.char === "🦞") { // Slower speed for lobster
      this.speed = random(0.8, 1.8);
    } else if (this.char === "🦀") { // Slower speed for crab (will be collected for event)
      this.speed = random(0.8, 1.8);
    } else if (this.char === "🦧") { // Slower speed for orangutan (gun event)
      this.speed = random(0.8, 1.8);
    } else if (this.char === "🦕") { // Slower speed for dinosaur (house grab event - now just a normal ingredient)
      this.speed = random(0.8, 1.8);
    } else {
      this.speed = random(2, 5); // Normal speed for other emojis
    }
  }

  // Update emoji position
  update() {
    this.y += this.speed;
  }

  // Display emoji on canvas
  display() {
    textSize(this.size);
    textAlign(CENTER, CENTER);
    text(this.char, this.x, this.y);
  }

  // Check if emoji is off-screen
  isOffScreen() {
    return this.y > height + this.size;
  }

  // Check if a tap at (tx, ty) hits this emoji
  isHit(tx, ty) {
    let d = dist(tx, ty, this.x, this.y);
    return d < this.size / 2; // Simple circular hit area
  }
}
Line-by-line explanation (9 lines)

🔧 Subcomponents:

conditional Conditional Speed Assignment if (this.char === "🐟") { this.speed = random(0.5, 1.5); } else if (this.char === "🦭") { ... } else { this.speed = random(2, 5); }

Sets different falling speeds for different emoji types—special emojis fall slower so they're easier to target

calculation Distance-Based Hit Detection let d = dist(tx, ty, this.x, this.y); return d < this.size / 2;

Checks if a tap is within the emoji's radius (distance formula)

this.x = random(width);
Picks a random X position across the full width of the canvas
this.y = random(-height, 0);
Starts the emoji off-screen above the canvas (Y between -height and 0)
this.char = random(emojis);
Picks a random emoji character from the emojis array (which has duplicates for probability weighting)
if (this.char === "🐟") { this.speed = random(0.5, 1.5); }
Fish fall slowly (0.5–1.5 pixels per frame) making them easier to tap before they disappear
} else { this.speed = random(2, 5); }
Normal emojis fall faster (2–5 pixels per frame), making them a challenge to catch
this.y += this.speed;
Moves the emoji down by its speed each frame, creating the falling rain effect
textSize(this.size); textAlign(CENTER, CENTER); text(this.char, this.x, this.y);
Draws the emoji at its current position, sized to EMOJI_SIZE pixels, centered on its X,Y coordinates
return this.y > height + this.size;
Returns true if the emoji has fallen below the bottom of the canvas, signaling it should be replaced
let d = dist(tx, ty, this.x, this.y); return d < this.size / 2;
Calculates the distance from the tap to the emoji center; if less than half the emoji size, it's a hit

drawChef()

drawChef() is a simple utility function that renders a static chef standing at the center of the serving line. It uses the same body-drawing logic as the Person class but adds a distinctive white chef's hat. By standing still in the center, the chef provides a visual anchor for the game world while customers walk to and from the serving line.

function drawChef() {
  fill(0); // Black color for the chef
  noStroke();

  let chefX = width / 2; // Center horizontally
  // Chef's feet are at GROUND_Y
  let chefBottomY = height - uiContainer.elt.offsetHeight - SERVING_LINE_Y_OFFSET;

  let headSize = CHEF_SIZE * 0.4;
  let bodyWidth = CHEF_SIZE * 0.3;
  let bodyHeight = CHEF_SIZE * 0.4;
  let legHeight = CHEF_SIZE * 0.2;
  let legWidth = bodyWidth * 0.4;

  // Calculate Y positions relative to chefBottomY (feet)
  let headY = chefBottomY - CHEF_SIZE + headSize / 2; // Head drawn headSize/2 above y - size (top of person)
  let bodyY = chefBottomY - CHEF_SIZE + headSize + bodyHeight / 2; // Body drawn bodyHeight/2 above y - size + headSize
  let legY = chefBottomY - legHeight / 2; // Legs drawn centered at y - legHeight/2

  // Head
  circle(chefX, headY, headSize);

  // Body
  rectMode(CENTER);
  rect(chefX, bodyY, bodyWidth, bodyHeight);

  // Legs
  rect(chefX - legWidth / 2, legY, legWidth, legHeight);
  rect(chefX + legWidth / 2, legY, legWidth, legHeight);

  // Chef's Hat
  fill(255); // White hat
  let hatBaseWidth = CHEF_SIZE * 0.6;
  let hatBaseHeight = CHEF_SIZE * 0.15;
  let hatTopRadius = CHEF_SIZE * 0.2;
  let hatTopY = headY - headSize / 2 - hatTopRadius / 2; // Above head
  rect(chefX, hatTopY + hatBaseHeight / 2, hatBaseWidth, hatBaseHeight, 5); // Base of hat with rounded corners
  circle(chefX, hatTopY, hatTopRadius); // Top of hat
  fill(0); // Reset fill color
}
Line-by-line explanation (10 lines)

🔧 Subcomponents:

calculation Chef Body Drawing circle(chefX, headY, headSize); rect(chefX, bodyY, bodyWidth, bodyHeight); rect(...legWidth...); rect(...legWidth...);

Draws the chef's head, torso, and two legs using circles and rectangles

calculation Chef Hat Drawing fill(255); ... rect(chefX, hatTopY + hatBaseHeight / 2, hatBaseWidth, hatBaseHeight, 5); circle(chefX, hatTopY, hatTopRadius);

Draws a white chef's hat above the head (base rectangle + circular top)

let chefX = width / 2;
Positions the chef horizontally at the exact center of the canvas
let chefBottomY = height - uiContainer.elt.offsetHeight - SERVING_LINE_Y_OFFSET;
Sets the chef's feet position to the serving line (same as people's feet), accounting for UI height
let headSize = CHEF_SIZE * 0.4;
Calculates head size as 40% of the total chef size (scale-relative sizing)
circle(chefX, headY, headSize);
Draws the chef's head as a circle at the calculated headY position
rectMode(CENTER);
Sets rectangle drawing mode to CENTER so rect() draws from its center point rather than top-left corner
rect(chefX, bodyY, bodyWidth, bodyHeight);
Draws the chef's body as a centered rectangle
fill(255);
Changes fill color to white for the chef's hat
rect(chefX, hatTopY + hatBaseHeight / 2, hatBaseWidth, hatBaseHeight, 5);
Draws the base of the chef's hat (a rounded rectangle with 5-pixel corner radius)
circle(chefX, hatTopY, hatTopRadius);
Draws the top/poof of the chef's hat as a white circle above the head
fill(0);
Resets fill color back to black for any subsequent drawing

📦 Key Variables

emojis array

A large array of emoji strings, with special emojis duplicated multiple times to control their spawn probability. Used by the Emoji class to pick random characters that fall from the sky.

let emojis = ["🦕", "🦕", ..., "🦖", ..., "🐟", ...];
personWeapons array

Array of weapon emojis that a person fighter can randomly select when a fight starts (swords, bows, guns, bombs, spatula, shield).

let personWeapons = ["🗡️", "🏹", "🔫", "💣", "🍳", "🛡️"];
dinosaurWeapons array

Array of weapon emojis that a dinosaur fighter can randomly select (bones, teeth, meteor, palm tree, volcano, T-Rex bite).

let dinosaurWeapons = ["🦴", "🦷", "☄️", "🌴", "🌋", "🦖"];
fallingEmojis array

Array of active Emoji objects currently falling on the canvas. Populated in startGame() and maintained at MAX_FALLING_EMOJIS count.

let fallingEmojis = [];
collectedEmojis array

Array of emoji character strings (just the emoji, not objects) that the player has tapped and collected as ingredients. People take from this array when they collect.

let collectedEmojis = [];
people array

Array of active Person objects currently on the serving line or walking. Updated each frame and can be modified by police, ambulances, and other events.

let people = [];
ufos array

Array of active UFO objects hunting and killing people. Populated when fish emoji is tapped.

let ufos = [];
sharks array

Array of active Shark objects hunting and killing people. Populated when seal emoji is tapped. Triggers ocean background when non-empty.

let sharks = [];
airplanes array

Array of active Airplane objects diving and crashing into people. Populated when crab emoji is tapped.

let airplanes = [];
bullets array

Array of active Bullet objects fired by armed people. Bullets check for collisions with other people each frame.

let bullets = [];
policeCars array

Array of active PoliceCar objects dispatched to arrest people or eliminate shooters. Spawned based on wanted level.

let policeCars = [];
swatTeams array

Array of active SWATTeam (black van) objects for higher-threat arrests. Spawned when wanted level reaches 3+.

let swatTeams = [];
helicopters array

Array of active PoliceHelicopter objects for maximum enforcement. Spawned when wanted level reaches 5.

let helicopters = [];
ambulances array

Array of active Ambulance objects that pick up dead people. Spawned automatically when a person enters AWAITING_AMBULANCE state.

let ambulances = [];
bloodSplashes array

Array of active BloodSplash objects (particle effects) created when someone is shot. They fade and disappear over time.

let bloodSplashes = [];
nukes array

Array of active NukeExplosion objects created when a police car targeting a shooter detonates. Large orange-red explosion that destroys the car.

let nukes = [];
servedCount number

Global score tracking how many people have successfully collected an emoji and been served. Incremented by Person.update() and decremented by deaths/arrests.

let servedCount = 0;
wantedLevel number

Global wanted level (0–5 stars) that controls police intensity. Incremented when lobster emoji is tapped; decreases when police arrest wanted people.

let wantedLevel = 0;
gameState string

Current game mode: 'MENU' (title screen), 'PLAYING' (main game), or 'FIGHTING' (one-on-one fight). Controls which handler draw() calls.

let gameState = 'MENU';
fighter1 object (Fighter class)

The human fighter in a one-on-one fight. Created when T-Rex emoji is tapped; destroyed when fight ends.

let fighter1 = null;
fighter2 object (Fighter class)

The dinosaur fighter in a one-on-one fight. Created when T-Rex emoji is tapped; destroyed when fight ends.

let fighter2 = null;
isOceanActive boolean

Flag indicating if a shark is currently active. When true, the background changes to ocean blue; when all sharks are gone, reverts to false.

let isOceanActive = false;
isBrowserZoomed boolean

Flag indicating if the browser is zoomed in or out beyond the initial scale. Used to warn players and prevent certain actions.

let isBrowserZoomed = false;

🔧 Potential Improvements (7)

Here are some ways this code could be enhanced:

BUG Person.isOffScreenRight()

People can be removed from the array while a shark is still chasing them, causing the shark to target a null person and crash. The check `isBeingChasedByShark` helps but doesn't prevent all edge cases.

💡 In Shark.update(), add a check before accessing this.targetPerson: `if (!this.targetPerson || !people.includes(this.targetPerson))` and transition to SWIMMING_AWAY if true. This is partially done but could be more robust.

PERFORMANCE handlePlayingScene() and draw loops

Every frame, nested loops check every bullet against every person (O(n²) complexity). With many bullets and people, this becomes slow. Blood splashes, nukes, and other particles add more iterations.

💡 Use spatial partitioning (divide canvas into grid cells) or use p5.js's built-in quadtree structures to only check nearby objects for collision. For now, a simpler fix: break out of collision loops as soon as a bullet hits (already done) and limit max bullets/objects.

STYLE mousePressed() and cookAndServe()

Creating DOM message divs is repetitive—the same code block (createDiv, style(), setTimeout) appears 10+ times throughout the file with minor text changes.

💡 Create a helper function like `showTemporaryMessage(text, duration, color)` that encapsulates the pattern. This reduces code duplication and makes future UI tweaks easier.

BUG PoliceCar.update() and nuke destruction

When a police car is marked DESTROYED_BY_NUKE, it searches for a matching nuke using position matching (`abs(n.x - currentCar.x) < 5`). This is fragile and could fail if multiple nukes exist at similar positions.

💡 Give each NukeExplosion a reference to the PoliceCar that created it, or vice versa. Store the relationship explicitly rather than relying on position-based matching.

FEATURE Game over / win condition

There is no win condition or game-over screen. Players can play indefinitely without clear goals.

💡 Add a scoring threshold (e.g., 'Serve 50 meals to win!') and transition to a WIN game state. Display a score summary and offer a replay button.

STYLE Global variable declarations

Many global variables (people, ufos, sharks, etc.) are declared at the top without clear organization. Related arrays (policeCars, swatTeams, helicopters, ambulances) could be grouped visually or documented better.

💡 Add a comment block organizing arrays by category: 'Game Objects', 'Enforcement Entities', 'Particle Effects', etc. This improves readability for future maintainers.

BUG Fighter.drawFighterShape()

When fighting, dinosaur's drawDinosaur() function is called directly, but Person.prototype.drawPerson() for the person uses a method call that depends on `this` context. Mixing patterns could cause issues.

💡 Ensure both drawDinosaur() and drawPerson() are either static helper functions or methods, not a mix. Keep calling conventions consistent.

🔄 Code Flow

Code flow showing setup, draw, handleplayingscene, mousepressed, cookandserve, createui, person, emoji, drawchef

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

graph TD start[Start] --> setup[setup] setup --> canvas-creation[Canvas Setup] setup --> ui-creation[DOM UI Builder] setup --> zoom-listener[Browser Zoom Detection] setup --> createui[createUI] setup --> draw[draw loop] click setup href "#fn-setup" click canvas-creation href "#sub-canvas-creation" click ui-creation href "#sub-ui-creation" click zoom-listener href "#sub-zoom-listener" click createui href "#fn-createui" draw --> state-switch[Game State Router] state-switch -->|PLAYING| handleplayingscene[handlePlayingScene] state-switch -->|MENU| menu-tap[Menu Screen Tap] click draw href "#fn-draw" click state-switch href "#sub-state-switch" click handleplayingscene href "#fn-handleplayingscene" click menu-tap href "#sub-menu-tap" handleplayingscene --> background-choice[Ocean or Normal Background] handleplayingscene --> emoji-loop[Falling Emoji Update Loop] handleplayingscene --> serving-line-draw[Serving Line Rectangle] handleplayingscene --> person-loop[People Update and Display Loop] handleplayingscene --> bullet-collision[Bullet-Person Collision Detection] click background-choice href "#sub-background-choice" click emoji-loop href "#sub-emoji-loop" click serving-line-draw href "#sub-serving-line-draw" click person-loop href "#sub-person-loop" click bullet-collision href "#sub-bullet-collision" emoji-loop --> emoji-hit-check[Emoji Hit Detection Loop] emoji-hit-check -->|fish| fish-event[Fish (UFO) Event] emoji-hit-check -->|seal| seal-event[Seal (Shark) Event] emoji-hit-check -->|lobster| lobster-event[Lobster (Wanted Level) Event] emoji-hit-check -->|orangutan| orangutan-event[Orangutan (Gun) Event] emoji-hit-check -->|trex| trex-event[T-Rex (Fight) Event] click emoji-hit-check href "#sub-emoji-hit-check" click fish-event href "#sub-fish-event" click seal-event href "#sub-seal-event" click lobster-event href "#sub-lobster-event" click orangutan-event href "#sub-orangutan-event" click trex-event href "#sub-trex-event" cookandserve --> ingredient-check[Ingredient Availability Check] ingredient-check -->|valid| person-spawn[Customer Spawn Loop] ingredient-check -->|invalid| error-message[No Ingredients Error Message] person-spawn --> police-spawn[Police Entity Spawn (Wanted Level)] police-spawn --> success-message[Customer Arrival Message] click cookandserve href "#fn-cookandserve" click ingredient-check href "#sub-ingredient-check" click person-spawn href "#sub-person-spawn" click police-spawn href "#sub-police-spawn" click success-message href "#sub-success-message" click error-message href "#sub-error-message" draw --> person[Person Class] person --> state-machine[Person State Machine] state-machine -->|WALKING_TO_COLLECT| alive-display[Alive Person Display] state-machine -->|DYING| dead-display[Dead Person Display] state-machine --> gun-logic[Armed Person Shooting Logic] click person href "#fn-person" click state-machine href "#sub-state-machine" click alive-display href "#sub-alive-display" click dead-display href "#sub-dead-display" click gun-logic href "#sub-gun-logic" draw --> emoji[Emoji Class] emoji --> speed-assignment[Conditional Speed Assignment] emoji --> hit-detection[Distance-Based Hit Detection] click emoji href "#fn-emoji" click speed-assignment href "#sub-speed-assignment" click hit-detection href "#sub-hit-detection" draw --> drawchef[drawChef] drawchef --> chef-body[Chef Body Drawing] drawchef --> chef-hat[Chef Hat Drawing] click drawchef href "#fn-drawchef" click chef-body href "#sub-chef-body" click chef-hat href "#sub-chef-hat"

❓ Frequently Asked Questions

What kind of visual experience does Sketch 2026-03-09 18:39 provide?

This sketch creates a whimsical scene where a variety of emojis rain down endlessly, including dinosaurs, sea creatures, and other playful icons.

How can users interact with the Sketch 2026-03-09 18:39?

Users can interact with the sketch by collecting falling emojis and potentially using them as ingredients in a playful cooking mechanic.

What creative coding concepts are showcased in Sketch 2026-03-09 18:39?

The sketch demonstrates the use of arrays to manage multiple animated objects, as well as techniques for creating infinite scrolling effects and interactive gameplay elements.

Preview

Sketch 2026-03-09 18:39 - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of Sketch 2026-03-09 18:39 - Code flow showing setup, draw, handleplayingscene, mousepressed, cookandserve, createui, person, emoji, drawchef
Code Flow Diagram