Sketch poop

This interactive restaurant simulator lets you shoot food emojis at hungry customers sitting at tables. Customers eat, pay, and leave in sequence, while occasionally someone falls and needs an ambulance—click their phone emoji to call for help. The sketch combines emoji-based animation, collision detection, and state management to create a playful chaos simulator.

🧪 Try This!

Experiment with the code by making these changes:

  1. Make customers arrive faster — Reduce personGenerationInterval so new diners sit down more frequently, creating a bustling restaurant.
  2. Slow down the fainting events — Increase fallingInterval so fewer people collapse, making the game less chaotic and more manageable.
  3. Change the shooter emoji — Replace the rocket with any emoji you like—try a pizza cutter, a catapult, or your favorite symbol.
  4. Make bullets travel faster — Increase bulletSpeed so food reaches customers more quickly, making shooting feel snappier.
  5. Extend eating time — Make customers enjoy their food longer by raising eatingTimerDuration, slowing the pay-and-leave cycle.
Prefer the full editor? Open it there →

📖 About This Sketch

This sketch creates an interactive restaurant filled with emoji characters. You click to shoot food emojis from a rocket at the bottom of the screen toward seated customers at tables. When a food emoji hits a person, they eat it, pay for the meal, and eventually leave. Occasionally someone faints and needs an ambulance—you can click their phone emoji to call for rescue. The sketch showcases collision detection between bullets and people, dynamic state management for characters at different life stages, and responsive layout generation with an array-based restaurant seating system.

The code is organized into four main systems: a particle system for bullets, a state machine for each Person object (normal, eating, paying, leaving, fallen), an Ambulance system that arrives and departs, and a restaurant layout generator that dynamically creates tables and chairs based on window size. By studying it, you'll learn how to manage multiple interactive objects with independent states, use arrays to track game entities, detect collisions between moving and stationary objects, and respond to both clicks and state transitions.

⚙️ How It Works

  1. When the sketch loads, setup() creates a canvas and calls generateRestaurantLayout() to populate a chairs array with positions for tables and seating around the canvas.
  2. Every frame, draw() clears the background and renders the restaurant scene: tables, chairs, kitchen counter, and menu board are drawn based on static positions and the chairs array.
  3. People spawn at random unoccupied chairs every 90 frames. Each person is an object with state flags (isEating, isPaying, isLeaving, isFallen) that control how they're drawn and animated.
  4. When you click the canvas, mousePressed() shoots a food emoji from the shooter position toward the clicked location or the nearest targetable person, creating a bullet object with a direction vector.
  5. Every bullet moves each frame and checks for rectangle collision with people who aren't already eating, paying, leaving, or fallen. On collision, the bullet disappears and the person enters the eating state with a timer.
  6. People cycle through eating → paying → leaving as their timers count down. Meanwhile, every 300 frames someone randomly falls and displays a phone emoji. Clicking that phone creates an Ambulance object that arrives from offscreen, pauses to 'pick up,' then leaves.
  7. Fallen people don't get up on their own—they must be rescued by the ambulance, which removes them from the people array when it reaches them, freeing their chair for the next customer.

🎓 Concepts You'll Learn

Collision detection (AABB rectangle-rectangle)State machines and object propertiesArray management and filteringVector math and direction calculationResponsive canvas layoutEmoji rendering and text positioningEvent handling and user interactionFrame-based timers and animation

📝 Code Breakdown

setup()

setup() runs once at the start. It's your chance to initialize the canvas size, set starting positions for interactive objects, and prepare any data structures (like the chairs array) that will be used throughout the sketch.

function setup() {
  createCanvas(windowWidth, windowHeight);
  
  // Set the shooter's initial position
  shooterX = width / 2;
  shooterY = height - shooterSize;
  
  // Align text to center for easier emoji positioning
  textAlign(CENTER, CENTER);
  
  generateRestaurantLayout(); // Generate chairs on setup
}
Line-by-line explanation (5 lines)
createCanvas(windowWidth, windowHeight);
Creates a canvas that fills the entire browser window, making the restaurant full-screen.
shooterX = width / 2;
Positions the rocket shooter horizontally in the center of the canvas.
shooterY = height - shooterSize;
Places the shooter near the bottom of the canvas with a small margin.
textAlign(CENTER, CENTER);
Centers all emoji text both horizontally and vertically at their x,y coordinates, making positioning easier.
generateRestaurantLayout();
Calls the layout generator to populate the chairs array with table seating positions.

draw()

draw() runs 60 times per second. It's responsible for rendering the static restaurant (kitchen, tables, chairs, menu), spawning new people periodically, triggering random falling events, updating and displaying all active entities (people, bullets, ambulances), and checking for collisions. The backward-loop pattern (for i = length - 1; i >= 0; i--) is crucial when removing items from arrays inside loops—it prevents skipping elements.

🔬 This entire block spawns customers. What happens if you change personGenerationInterval to 30 instead of 90? Will the restaurant fill up faster or slower?

  // --- Generate People ---
  if (frameCount % personGenerationInterval === 0) {
    // Find an unoccupied chair
    let availableChairs = chairs.filter(c => !c.occupied);
    if (availableChairs.length > 0) {
      let randomChair = random(availableChairs);
      let chairIndex = chairs.indexOf(randomChair); // Get the index of the chosen chair
      
      // Create a new Person at the chair's position
      people.push(new Person(randomChair.x, randomChair.y, chairIndex));
      
      // Mark the chair as occupied
      randomChair.occupied = true;
    }
  }

🔬 This loop finds one person to hit per bullet. What if you removed the `break;` statement at the end of the collision block? Would a single bullet hit multiple people, or would it still hit just one?

    // Check for collision with people
    let hit = false;
    for (let j = people.length - 1; j >= 0; j--) {
      let person = people[j];
      
      // Only check collision if the person is not already eating, paying, leaving, OR FALLEN
      if (!person.isEating && !person.isPaying && !person.isLeaving && !person.isFallen) {
function draw() {
  background(240, 220, 180); // Warm cream color for the floor/background

  // --- Draw the Restaurant Layout ---
  // Kitchen/Counter area at the top
  fill(180, 150, 100); // Brown color for counter
  noStroke();
  rect(0, 0, width, paddingY / 2); // Counter area
  
  textSize(waiterSize * 0.8);
  fill(0);
  text("KITCHEN", width / 2, paddingY / 4);

  // Draw tables and chairs from the chairs array
  // We need to re-render chairs every frame
  let chairEmoji = "🪑";
  
  // Draw tables and their emojis
  for (let i = 0; i < chairs.length; i += 4) { // Iterate by 4 to get table groups
    let chair1 = chairs[i];
    // Calculate table center from chair positions (e.g., all chairs at same x for top/bottom, same y for left/right of a table)
    let tableX = chair1.x; 
    let tableY = chair1.y + tableSize / 2 + chairSize / 2; // Adjusted Y for table center
    
    // Draw the table rectangle first
    fill(100, 70, 40); // Darker brown for table surface
    stroke(80, 50, 20); // Add a darker stroke for definition
    strokeWeight(2); // Thicker stroke
    rectMode(CENTER);
    rect(tableX, tableY, tableSize, tableSize * 0.8); // Rectangle for the table
    
    // Draw simple table legs
    let legWidth = tableSize * 0.1; // 10% of table width
    let legHeight = tableSize * 0.3; // 30% of table width
    
    noStroke(); // Legs don't need a stroke
    rect(tableX - tableSize / 2 + legWidth / 2, tableY + tableSize * 0.8 / 2 + legHeight / 2, legWidth, legHeight); // Left leg
    rect(tableX + tableSize / 2 - legWidth / 2, tableY + tableSize * 0.8 / 2 + legHeight / 2, legWidth, legHeight); // Right leg
    
    // Draw the table emoji on top
    textSize(tableSize);
    fill(0); // Reset fill for emoji text
    text(tableEmoji, tableX, tableY);
  }
  rectMode(CORNER); // Reset rectMode to default after drawing tables
  
  // Draw chairs
  textSize(chairSize);
  fill(0); // Reset fill for emoji text
  for (let chair of chairs) {
    text(chairEmoji, chair.x, chair.y);
  }
  
  // Menu board (right side)
  textSize(menuSize);
  text(menuEmoji, width - paddingX / 2, paddingY * 2); 
  
  textSize(menuSize * 0.4);
  fill(0);
  text("MENU", width - paddingX / 2, paddingY * 2 - menuSize * 0.7);
  text("Burgers 🍔", width - paddingX / 2, paddingY * 2 + menuSize * 0.2);
  text("Pizza 🍕", width - paddingX / 2, paddingY * 2 + menuSize * 0.6);
  text("Coffee ☕", width - paddingX / 2, paddingY * 2 + menuSize * 1.0);

  // --- Draw the Shooter ---
  textSize(shooterSize);
  text(shooterEmoji, shooterX, shooterY);

  // --- Generate People ---
  if (frameCount % personGenerationInterval === 0) {
    // Find an unoccupied chair
    let availableChairs = chairs.filter(c => !c.occupied);
    if (availableChairs.length > 0) {
      let randomChair = random(availableChairs);
      let chairIndex = chairs.indexOf(randomChair); // Get the index of the chosen chair
      
      // Create a new Person at the chair's position
      people.push(new Person(randomChair.x, randomChair.y, chairIndex));
      
      // Mark the chair as occupied
      randomChair.occupied = true;
    }
  }

  // --- Trigger Falling ---
  if (frameCount % fallingInterval === 0) {
    // Find an occupied chair with a person who is not already fallen, eating, paying, or leaving
    let targetablePeople = people.filter(p => !p.isFallen && !p.isEating && !p.isPaying && !p.isLeaving);
    if (targetablePeople.length > 0) {
      let randomPerson = random(targetablePeople);
      randomPerson.fall(); // Call a new method on the Person object
    }
  }

  // --- Draw and Manage People ---
  // Loop through the people array from end to beginning for safe removal
  for (let i = people.length - 1; i >= 0; i--) {
    let person = people[i];
    
    person.display(); // Draw the person (or eating, paying, leaving, or fallen animation)
    
    // Move the person only if they are in the leaving state
    if (person.isLeaving) {
      person.moveLeaving();
    }

    // Remove person if they are leaving and have gone off-screen
    if (person.isLeaving && (person.x < -personSize || person.x > width + personSize || person.y < -personSize)) {
      chairs[person.chairIndex].occupied = false; // Free up the chair
      people.splice(i, 1);
    }
  }

  // --- Manage Ambulances ---
  // Loop through the ambulances array from end to beginning for safe removal
  for (let i = ambulances.length - 1; i >= 0; i--) {
    let currentAmbulance = ambulances[i]; // Use a different variable name to avoid conflict
    currentAmbulance.display();
    currentAmbulance.move();
    
    // Check if ambulance has left the screen
    if (currentAmbulance.state === "leaving" && 
        (currentAmbulance.x < -ambulanceSize || currentAmbulance.x > width + ambulanceSize)) {
      chairs[currentAmbulance.chairIndex].occupied = false; // Free the chair ambulance was at
      ambulances.splice(i, 1); // Remove the ambulance object from the array
    }
  }

  // --- Draw and Move Bullets, Check Collisions ---
  // Loop through the bullets array from end to beginning for safe removal
  for (let i = bullets.length - 1; i >= 0; i--) {
    let bullet = bullets[i];
    
    bullet.display(); // Draw the bullet
    bullet.move();    // Update the bullet's position

    // Check for collision with people
    let hit = false;
    for (let j = people.length - 1; j >= 0; j--) {
      let person = people[j];
      
      // Only check collision if the person is not already eating, paying, leaving, OR FALLEN
      if (!person.isEating && !person.isPaying && !person.isLeaving && !person.isFallen) {
        // Calculate bounding boxes for collision
        let bulletRectX = bullet.x - bulletSize / 2;
        let bulletRectY = bullet.y - bulletSize / 2;
        // Person's bounding box is based on their seated x, y, and size
        let personRectX = person.x - personSize / 2;
        let personRectY = person.y - personSize * 0.75; // Adjusted y for seated person's head/body

        // Use a simple rectangle collision detection
        if (collideRectRect(bulletRectX, bulletRectY, bulletSize, bulletSize,
                            personRectX, personRectY, personSize, personSize)) {
          // Collision detected!
          // Bullet disappears
          hit = true; 
          
          // Person starts eating
          person.isEating = true;
          person.eatingFoodEmoji = bullet.emoji;
          person.eatingTimer = eatingTimerDuration;
          person.hasEaten = false; // Reset in case they were hit twice quickly

          break; // Bullet can only hit one person per frame
        }
      }
    }

    // Remove bullet if it hit someone or went off-screen (top of canvas)
    if (hit || bullet.y < -bulletSize || bullet.x < -bulletSize || bullet.x > width + bulletSize || bullet.y > height + bulletSize) {
      bullets.splice(i, 1);
    }
  }
}
Line-by-line explanation (10 lines)

🔧 Subcomponents:

conditional Customer Spawning Logic if (frameCount % personGenerationInterval === 0) {

Spawns a new person at a random unoccupied chair every personGenerationInterval frames

conditional Random Fainting Trigger if (frameCount % fallingInterval === 0) {

Randomly selects a normal customer to fall every fallingInterval frames

for-loop People Update and Removal Loop for (let i = people.length - 1; i >= 0; i--) {

Displays each person and removes them when they leave off-screen; loops backward for safe array removal

for-loop Ambulance Update and Removal Loop for (let i = ambulances.length - 1; i >= 0; i--) {

Updates and displays ambulances; removes them when they leave the screen

for-loop Bullet Movement and Collision Detection for (let i = bullets.length - 1; i >= 0; i--) {

Moves each bullet and checks for collisions with hittable people; removes bullets on collision or off-screen

background(240, 220, 180);
Clears the canvas each frame with a warm cream color, erasing the previous frame's drawings.
if (frameCount % personGenerationInterval === 0) {
This condition is true every personGenerationInterval frames—use modulo to spawn people at regular intervals.
let availableChairs = chairs.filter(c => !c.occupied);
Creates a new array containing only chairs that are not occupied, using filter() to check the occupied property.
people.push(new Person(randomChair.x, randomChair.y, chairIndex));
Creates a new Person object at the chosen chair's position and adds it to the people array.
if (frameCount % fallingInterval === 0) {
Every fallingInterval frames, randomly select someone to fall and need an ambulance.
for (let i = people.length - 1; i >= 0; i--) {
Loops through the people array backward (from end to start) so we can safely remove items without skipping.
if (person.isLeaving && (person.x < -personSize || person.x > width + personSize || person.y < -personSize)) {
Checks if the leaving person has moved far enough off-screen to be removed; frees their chair when they go.
for (let i = bullets.length - 1; i >= 0; i--) {
Loops through bullets backward to safely remove those that hit people or left the canvas.
if (!person.isEating && !person.isPaying && !person.isLeaving && !person.isFallen) {
Only check collision with people who are sitting and waiting (not in any animation state).
if (collideRectRect(bulletRectX, bulletRectY, bulletSize, bulletSize, personRectX, personRectY, personSize, personSize)) {
Tests if the bullet's bounding box overlaps with the person's bounding box using rectangle collision detection.

Bullet()

The Bullet object is simple but powerful. It stores position, appearance (emoji), and direction. Unlike a traditional 'speed' variable, the direction is a pre-normalized vector scaled by bulletSpeed—this makes bullets travel in a straight line at consistent speed regardless of the distance to the target. This pattern is cleaner than calculating angle and then sine/cosine for each frame.

🔬 This move method adds direction to position every frame. What happens if you change += to *= (multiply)? Does the bullet accelerate, decelerate, or disappear?

  // Method to move the bullet
  this.move = function() {
    this.x += this.directionX; // Move according to pre-calculated direction
    this.y += this.directionY;
  };
function Bullet(x, y, emoji, directionX, directionY) { // Now takes direction as arguments
  this.x = x;
  this.y = y;
  this.emoji = emoji;
  this.directionX = directionX; // Normalized and scaled direction
  this.directionY = directionY;
  this.speed = bulletSpeed; // Still keep for reference, but directionX/Y directly control movement

  // Method to display the bullet
  this.display = function() {
    textSize(bulletSize); // Use the global bulletSize
    text(this.emoji, this.x, this.y);
  };

  // Method to move the bullet
  this.move = function() {
    this.x += this.directionX; // Move according to pre-calculated direction
    this.y += this.directionY;
  };
}
Line-by-line explanation (8 lines)
function Bullet(x, y, emoji, directionX, directionY) {
Creates a bullet object. The direction vector (directionX, directionY) is pre-calculated in mousePressed() so the bullet knows which way to fly.
this.x = x;
Stores the bullet's starting horizontal position (the shooter's x).
this.y = y;
Stores the bullet's starting vertical position (the shooter's y).
this.emoji = emoji;
Stores which food emoji this bullet should display (one from the foodEmojis array).
this.directionX = directionX;
Stores the horizontal component of the bullet's movement—added to x every frame.
this.directionY = directionY;
Stores the vertical component of the bullet's movement—added to y every frame.
this.display = function() {
Defines the display method: sets text size and draws the food emoji at the bullet's current position.
this.move = function() {
Defines the move method: updates the bullet's position by adding the direction vector each frame.

Person()

The Person object is the heart of the sketch's state machine. Each person has multiple states (normal, eating, paying, leaving, fallen) and displays themselves differently in each state. The display() method uses cascading if-else statements to draw the appropriate animation. Timers (eatingTimer, payingTimer) count down each frame, triggering transitions to the next state. The fall() method demonstrates guard clauses—it checks multiple conditions before allowing the person to fall, preventing impossible state transitions (like falling while eating).

🔬 Notice the food emoji is drawn at (this.y - personSize * 0.75). What happens if you change 0.75 to 1.5 or 0.3? Does the food appear higher, lower, or in a completely different place?

    } else if (this.isEating) {
      // Display the food emoji above the person while eating
      textSize(bulletSize);
      text(this.eatingFoodEmoji, this.x, this.y - personSize * 0.75); // Slightly higher for food

      // Draw the "satisfied" person figure
      fill(this.skinColor);
      noStroke();
      circle(this.x, this.y - personSize * 0.25, personSize * 0.5); // Head

      fill(this.clothesColor);
      rectMode(CENTER); // Draw rectangle from its center
      rect(this.x, this.y + personSize * 0.25, personSize * 0.5, personSize * 0.5); // Body

🔬 This block counts down and transitions from eating to paying. What if you changed the condition from `<= 0` to `< -30`? When would the person start paying—immediately or after a delay?

      this.eatingTimer--;
      if (this.eatingTimer <= 0) {
        this.isEating = false;
        this.hasEaten = true; // Eating phase complete
        this.isPaying = true; // Transition to paying
        this.payingTimer = payingTimerDuration;
      }
function Person(chairX, chairY, chairIndex) { // Now takes chair position and index
  // The person's position will be based on the chair they occupy
  this.x = chairX;
  this.y = chairY - chairSize / 2; // Position slightly above the chair emoji for head
  this.chairIndex = chairIndex; // Keep track of the chair they occupy
  
  // Properties for eating animation
  this.isEating = false;
  this.eatingFoodEmoji = "";
  this.eatingTimer = 0;
  this.hasEaten = false; // Flag that eating phase is complete

  // New properties for paying animation
  this.isPaying = false;
  this.payingTimer = 0;

  // New properties for leaving animation
  this.isLeaving = false;
  this.leavingSpeed = 0;
  this.leavingDirection = 0; // -1 for left, 1 for right

  // New properties for fallen state
  this.isFallen = false;
  this.fallenTimer = 0; // Keep for reference, but no self-get-up.
  this.needsAmbulance = false; // Flag to show phone emoji and call ambulance

  // Random colors for the person
  this.skinColor = color(random(180, 255), random(120, 180), random(80, 120)); // Skin tones
  this.clothesColor = color(random(50, 200), random(50, 200), random(50, 200)); // Varied clothes colors

  // Method to display the person based on their state
  this.display = function() {
    if (this.isFallen) {
      // Draw fallen person
      fill(this.skinColor);
      noStroke();
      // Draw head slightly rotated or flattened
      circle(this.x, this.y - personSize * 0.1, personSize * 0.5); // Adjusted Y for fallen head
      
      fill(this.clothesColor);
      rectMode(CENTER);
      rect(this.x, this.y + personSize * 0.3, personSize * 0.5, personSize * 0.4); // Adjusted Y for fallen body
      rectMode(CORNER);

      // Display fallen emoji above them
      textSize(bulletSize);
      fill(0);
      text(fallenEmoji, this.x, this.y - personSize * 0.75); // Slightly higher for emoji
      
      // New: Display phone emoji if ambulance is needed
      if (this.needsAmbulance) {
        textSize(bulletSize * 0.8);
        text(phoneEmoji, this.x + personSize * 0.6, this.y - personSize * 0.75); // Phone emoji next to fallen
      }
      
    } else if (this.isEating) {
      // Display the food emoji above the person while eating
      textSize(bulletSize);
      text(this.eatingFoodEmoji, this.x, this.y - personSize * 0.75); // Slightly higher for food

      // Draw the "satisfied" person figure
      fill(this.skinColor);
      noStroke();
      circle(this.x, this.y - personSize * 0.25, personSize * 0.5); // Head

      fill(this.clothesColor);
      rectMode(CENTER); // Draw rectangle from its center
      rect(this.x, this.y + personSize * 0.25, personSize * 0.5, personSize * 0.5); // Body
      rectMode(CORNER); // Reset rectMode to default

      // Add a happy mouth and eyes
      fill(0);
      stroke(0);
      strokeWeight(2);
      line(this.x - personSize * 0.1, this.y - personSize * 0.1, this.x + personSize * 0.1, this.y - personSize * 0.1); // Simple line mouth
      circle(this.x - personSize * 0.1, this.y - personSize * 0.35, personSize * 0.05); // Left eye
      circle(this.x + personSize * 0.1, this.y - personSize * 0.35, personSize * 0.05); // Right eye
      noStroke(); // Remove stroke for subsequent shapes

      this.eatingTimer--;
      if (this.eatingTimer <= 0) {
        this.isEating = false;
        this.hasEaten = true; // Eating phase complete
        this.isPaying = true; // Transition to paying
        this.payingTimer = payingTimerDuration;
      }
    } else if (this.isPaying) {
      // Display the money emoji above the person while paying
      textSize(bulletSize);
      text(moneyEmoji, this.x, this.y - personSize * 0.75); // Slightly higher for money

      // Draw the normal person figure
      fill(this.skinColor);
      noStroke();
      circle(this.x, this.y - personSize * 0.25, personSize * 0.5); // Head

      fill(this.clothesColor);
      rectMode(CENTER); // Draw rectangle from its center
      rect(this.x, this.y + personSize * 0.25, personSize * 0.5, personSize * 0.5); // Body
      rectMode(CORNER); // Reset rectMode to default

      // Add simple eyes
      fill(0);
      noStroke();
      circle(this.x - personSize * 0.1, this.y - personSize * 0.35, personSize * 0.05); // Left eye
      circle(this.x + personSize * 0.1, this.y - personSize * 0.35, personSize * 0.05); // Right eye

      this.payingTimer--;
      if (this.payingTimer <= 0) {
        this.isPaying = false;
        this.isLeaving = true; // Transition to leaving
        this.leavingSpeed = random(leavingSpeedMin, leavingSpeedMax); // Random leaving speed
        this.leavingDirection = random([-1, 1]); // Randomly leave left or right
      }
    } else if (this.isLeaving) {
      // Display the waving emoji above the person while leaving
      textSize(bulletSize);
      text(leavingEmoji, this.x, this.y - personSize * 0.75); // Slightly higher for wave

      // Draw the normal person figure
      fill(this.skinColor);
      noStroke();
      circle(this.x, this.y - personSize * 0.25, personSize * 0.5); // Head

      fill(this.clothesColor);
      rectMode(CENTER); // Draw rectangle from its center
      rect(this.x, this.y + personSize * 0.25, personSize * 0.5, personSize * 0.5); // Body
      rectMode(CORNER); // Reset rectMode to default

      // Add simple eyes
      fill(0);
      noStroke();
      circle(this.x - personSize * 0.1, this.y - personSize * 0.35, personSize * 0.05); // Left eye
      circle(this.x + personSize * 0.1, this.y - personSize * 0.35, personSize * 0.05); // Right eye
    } else {
      // Draw the normal seated person figure
      fill(this.skinColor);
      noStroke();
      circle(this.x, this.y - personSize * 0.25, personSize * 0.5); // Head

      fill(this.clothesColor);
      rectMode(CENTER); // Draw rectangle from its center
      rect(this.x, this.y + personSize * 0.25, personSize * 0.5, personSize * 0.5); // Body
      rectMode(CORNER); // Reset rectMode to default

      // Add simple eyes
      fill(0);
      noStroke();
      circle(this.x - personSize * 0.1, this.y - personSize * 0.35, personSize * 0.05); // Left eye
      circle(this.x + personSize * 0.1, this.y - personSize * 0.35, personSize * 0.05); // Right eye
    }
  };

  // New method for leaving movement
  this.moveLeaving = function() {
    if (this.isLeaving) {
      this.x += this.leavingSpeed * this.leavingDirection;
      this.y -= this.leavingSpeed * 0.5; // Move slightly upwards to simulate walking away from table
    }
  };

  // New method to make the person fall
  this.fall = function() {
    // Only fall if not already in another active state (eating, paying, leaving, or fallen)
    if (!this.isEating && !this.isPaying && !this.isLeaving && !this.isFallen) {
      this.isFallen = true;
      this.fallenTimer = fallenTimerDuration; // Still set for reference, but not used for self-get-up
      this.needsAmbulance = true; // Mark that an ambulance is needed
      // Stop any potential eating/paying process
      this.isEating = false;
      this.isPaying = false;
    }
  };
}
Line-by-line explanation (9 lines)

🔧 Subcomponents:

conditional Fallen State Display if (this.isFallen) {

Draws the person lying down with a hurt emoji and phone emoji for calling an ambulance

conditional Eating State Display } else if (this.isEating) {

Displays the food emoji above the person and draws a happy face; decrements eatingTimer and transitions to paying

conditional Paying State Display } else if (this.isPaying) {

Displays the money emoji; decrements payingTimer and transitions to leaving when done

conditional Leaving State Display } else if (this.isLeaving) {

Displays a waving emoji as the person walks away

conditional Normal Seated State Display } else {

Draws the person sitting quietly at their chair, waiting to be served

this.x = chairX;
Positions the person horizontally at the same x as their chair.
this.y = chairY - chairSize / 2;
Positions the person's head slightly above the chair emoji so they appear to be sitting.
this.skinColor = color(random(180, 255), random(120, 180), random(80, 120));
Randomly generates a skin tone from brownish-orange to light tan, making each person unique.
if (this.isFallen) {
When fallen, render the person lying down with different head and body positions than seated.
if (this.needsAmbulance) {
Inside the fallen state, check if an ambulance was called yet; if not, display the phone emoji as clickable.
this.eatingTimer--;
Count down the eating timer each frame; when it reaches 0, the person stops eating and starts paying.
this.payingTimer--;
Count down the paying timer each frame; when it reaches 0, the person finishes paying and starts leaving.
this.leavingSpeed = random(leavingSpeedMin, leavingSpeedMax);
Assigns a random speed for this person's exit—slow values make them leave gradually.
this.fall = function() {
Defines the fall method: sets isFallen to true and shows the phone emoji if not already in eating, paying, or leaving states.

Ambulance()

The Ambulance object demonstrates multi-stage state management similar to Person, but simpler: it has three states (arriving, pickingUp, leaving). The arriving state uses conditional logic to detect when the ambulance reaches its target, then removes the fallen person from the array and transitions. The ternary operator (`condition ? trueValue : falseValue`) elegantly handles opposite directions without separate if-else blocks. The ambulance's movement is slower vertically than horizontally, creating a realistic approach arc.

🔬 The ambulance moves horizontally at full speed but vertically at 0.2x speed. What happens if you change the 0.2 to 1.0? Does the ambulance approach the fallen person more directly, or does it move differently?

      // Move horizontally towards the target chair's X
      let dirX = this.arrivingDirection === -1 ? -1 : 1;
      this.x += dirX * ambulanceSpeed;
      
      // Move slightly downwards towards the target chair's Y
      let dirY = (this.targetY - this.y) > 0 ? 1 : -1;
      this.y += dirY * ambulanceSpeed * 0.2;
function Ambulance(targetChairX, targetChairY, chairIndex) {
  this.state = "arriving"; // arriving, pickingUp, leaving
  this.targetX = targetChairX;
  this.targetY = targetChairY - personSize * 0.25; // Target head/body area of fallen person
  this.chairIndex = chairIndex;
  this.pickupTimer = ambulancePickupTimerDuration;
  
  // Randomly arrive from left or right
  this.arrivingDirection = random([-1, 1]);
  if (this.arrivingDirection === -1) { // Arrive from right
    this.x = width + ambulanceSize / 2;
  } else { // Arrive from left
    this.x = -ambulanceSize / 2;
  }
  this.y = height * 0.1; // Ambulance appears near the top

  this.display = function() {
    textSize(ambulanceSize);
    fill(0);
    text(ambulanceEmoji, this.x, this.y);
    
    // Display siren emoji while arriving or leaving
    if (this.state === "arriving" || this.state === "leaving") {
      textSize(ambulanceSize * 0.6);
      text(sirenEmoji, this.x, this.y - ambulanceSize * 0.6);
    }
  };

  this.move = function() {
    if (this.state === "arriving") {
      // Move horizontally towards the target chair's X
      let dirX = this.arrivingDirection === -1 ? -1 : 1;
      this.x += dirX * ambulanceSpeed;
      
      // Move slightly downwards towards the target chair's Y
      let dirY = (this.targetY - this.y) > 0 ? 1 : -1;
      this.y += dirY * ambulanceSpeed * 0.2; // Slower vertical movement

      // Check if ambulance has reached the target chair's X position
      if (this.arrivingDirection === -1 && this.x <= this.targetX + ambulanceSize / 2) {
        this.x = this.targetX + ambulanceSize / 2; // Snap to position
        this.state = "pickingUp";
        // Remove the fallen person from the array
        people = people.filter(p => !p.isFallen || p.chairIndex !== this.chairIndex);
        // Mark the chair as occupied by the ambulance
        chairs[this.chairIndex].occupied = true;
      } else if (this.arrivingDirection === 1 && this.x >= this.targetX - ambulanceSize / 2) {
        this.x = this.targetX - ambulanceSize / 2; // Snap to position
        this.state = "pickingUp";
        // Remove the fallen person from the array
        people = people.filter(p => !p.isFallen || p.chairIndex !== this.chairIndex);
        // Mark the chair as occupied by the ambulance
        chairs[this.chairIndex].occupied = true;
      }
    } else if (this.state === "pickingUp") {
      this.pickupTimer--;
      if (this.pickupTimer <= 0) {
        this.state = "leaving";
      }
    } else if (this.state === "leaving") {
      // Move horizontally off-screen
      let dirX = this.arrivingDirection === -1 ? -1 : 1;
      this.x += dirX * ambulanceSpeed;
      // Move slightly upwards to simulate driving away
      this.y -= ambulanceSpeed * 0.2;
    }
  };
}
Line-by-line explanation (8 lines)

🔧 Subcomponents:

conditional Ambulance Arriving Logic if (this.state === "arriving") {

Moves the ambulance toward the fallen person's chair; transitions to pickingUp when reached

conditional Ambulance Pickup Logic } else if (this.state === "pickingUp") {

Pauses at the chair for a duration; counts down and transitions to leaving

conditional Ambulance Leaving Logic } else if (this.state === "leaving") {

Moves the ambulance off-screen in the direction it arrived from

conditional Siren Emoji Display if (this.state === "arriving" || this.state === "leaving") {

Shows the siren emoji only while the ambulance is moving, not while stopped

this.state = "arriving";
Ambulances start in the arriving state, moving toward the fallen person.
this.targetX = targetChairX;
Stores the x position of the chair where the fallen person is (passed from mousePressed()).
this.arrivingDirection = random([-1, 1]);
Randomly picks either -1 (arriving from right) or 1 (arriving from left).
if (this.arrivingDirection === -1) {
If arriving from the right, start the ambulance's x position off-screen to the right.
let dirX = this.arrivingDirection === -1 ? -1 : 1;
Uses the ternary operator to pick the horizontal direction: -1 to move left, 1 to move right.
this.x += dirX * ambulanceSpeed;
Moves the ambulance horizontally at ambulanceSpeed pixels per frame in the chosen direction.
people = people.filter(p => !p.isFallen || p.chairIndex !== this.chairIndex);
Removes the fallen person from the people array when the ambulance reaches them, keeping only those who aren't fallen OR are at a different chair.
this.pickupTimer--;
Counts down the pause duration while the ambulance is stopped; when it reaches 0, the ambulance leaves.

mousePressed()

mousePressed() demonstrates two key interaction patterns: click detection for UI elements (the phone emoji) and click-to-aim mechanics for shooting. It prioritizes the phone click using an early return(), preventing accidental bullets when calling ambulances. The closest-person algorithm uses p5.Vector.dist() to find the nearest valid target, creating smart auto-aim behavior that rewards proximity but respects state restrictions (eating, paying, leaving, fallen people are untargetable). The direction vector is normalized first, then scaled—a common pattern for decoupling direction from speed.

🔬 This loop finds the closest targetable person to your click. What happens if you add `&& !person.isPaying` to the first if-condition so paying people become hittable? Does the game feel different?

  // Find the closest targetable person to the mouse click
  // A person is targetable if they are NOT eating, paying, leaving, OR FALLEN
  for (let person of people) {
    if (!person.isEating && !person.isPaying && !person.isLeaving && !person.isFallen) {
      // Use the person's current seated position for distance calculation
      // Adjusted personY slightly to target their body/head area
      let personPos = createVector(person.x, person.y - personSize * 0.25); 
      let d = p5.Vector.dist(targetPos, personPos);
      if (d < minDistance) {
        minDistance = d;
        closestPerson = person;
      }
    }
  }
function mousePressed() {
  let clickPos = createVector(mouseX, mouseY);

  // FIRST, check if we clicked directly on a FALLEN person's PHONE to call the ambulance
  for (let person of people) {
    if (person.isFallen && person.needsAmbulance) {
      // Calculate the position and size of the phone emoji for this person
      let phoneEmojiX_center = person.x + personSize * 0.6;
      let phoneEmojiY_center = person.y - personSize * 0.75;
      let phoneEmojiWidth = bulletSize * 0.8; // Use the text size for the clickable area
      let phoneEmojiHeight = bulletSize * 0.8;

      let phoneRectX = phoneEmojiX_center - phoneEmojiWidth / 2;
      let phoneRectY = phoneEmojiY_center - phoneEmojiHeight / 2;

      // Check for point-rectangle collision with the phone emoji
      if (mouseX > phoneRectX && mouseX < phoneRectX + phoneEmojiWidth &&
          mouseY > phoneRectY && mouseY < phoneRectY + phoneEmojiHeight) {
        
        // Push a new Ambulance object into the ambulances array
        ambulances.push(new Ambulance(person.x, person.y, person.chairIndex));
        person.needsAmbulance = false; // Phone emoji disappears
        
        return; // Stop here, we called an ambulance, don't shoot a bullet
      }
    }
  }

  // If no fallen person was clicked, proceed with the normal food shooting logic
  let randomFoodEmoji = random(foodEmojis);
  
  let shooterPos = createVector(shooterX, shooterY);
  let targetPos = createVector(mouseX, mouseY); // Default to mouse position

  let closestPerson = null;
  let minDistance = Infinity;

  // Find the closest targetable person to the mouse click
  // A person is targetable if they are NOT eating, paying, leaving, OR FALLEN
  for (let person of people) {
    if (!person.isEating && !person.isPaying && !person.isLeaving && !person.isFallen) {
      // Use the person's current seated position for distance calculation
      // Adjusted personY slightly to target their body/head area
      let personPos = createVector(person.x, person.y - personSize * 0.25); 
      let d = p5.Vector.dist(targetPos, personPos);
      if (d < minDistance) {
        minDistance = d;
        closestPerson = person;
      }
    }
  }

  // If a closest person was found, aim at them
  if (closestPerson) {
    // Target the person's actual drawn position, not just the mouse click
    targetPos.set(closestPerson.x, closestPerson.y - personSize * 0.25); 
  }

  // Calculate direction vector from shooter to target
  let direction = p5.Vector.sub(targetPos, shooterPos);
  direction.normalize(); // Make its length 1
  direction.mult(bulletSpeed); // Scale it by bullet speed

  // Create a new bullet object at the shooter's position with the calculated direction
  let newBullet = new Bullet(shooterX, shooterY, randomFoodEmoji, direction.x, direction.y);
  
  // Add the new bullet to the bullets array
  bullets.push(newBullet);
}
Line-by-line explanation (11 lines)

🔧 Subcomponents:

for-loop Fallen Person Phone Click Detection for (let person of people) {

Loops through people to find fallen ones with visible phones; creates an ambulance if clicked

for-loop Closest Targetable Person Finder for (let person of people) {

Finds the nearest non-eating, non-paying, non-leaving, non-fallen person to aim bullets at

calculation Direction Vector Math let direction = p5.Vector.sub(targetPos, shooterPos);

Calculates the vector from shooter to target, normalizes it, and scales by bullet speed

let clickPos = createVector(mouseX, mouseY);
Creates a vector representing the click position (though clickPos is declared but not used in the current code).
for (let person of people) {
Loops through all people using a for-of loop, checking if any are fallen with a phone.
if (person.isFallen && person.needsAmbulance) {
Only proceeds if this person is fallen AND their phone hasn't been clicked yet.
let phoneEmojiX_center = person.x + personSize * 0.6;
Calculates the center x of the phone emoji, which is drawn to the right of the fallen person.
if (mouseX > phoneRectX && mouseX < phoneRectX + phoneEmojiWidth &&
Tests if the click is within the phone emoji's x-range (left and right bounds).
ambulances.push(new Ambulance(person.x, person.y, person.chairIndex));
Creates a new Ambulance object at the fallen person's position and adds it to the ambulances array.
return;
Exits mousePressed() early if an ambulance was called, preventing a bullet from being shot.
let randomFoodEmoji = random(foodEmojis);
Picks a random food emoji from the foodEmojis array to shoot.
let direction = p5.Vector.sub(targetPos, shooterPos);
Subtracts the shooter position from the target to get a vector pointing from shooter to target.
direction.normalize();
Scales the direction vector to have a length of 1, making it just a direction without magnitude.
direction.mult(bulletSpeed);
Multiplies the normalized direction by bulletSpeed, giving it the correct speed for the bullet's movement.

generateRestaurantLayout()

generateRestaurantLayout() shows responsive design principles applied to game layout. By using percentages of width and height, the restaurant reconfigures when the window resizes. The nested loop creates a grid of tables, and for each table, a 4-chair array is pushed as separate chair objects to the global chairs array. This pattern—breaking complex arrangements into modular pieces (4 chairs per table, all combined into one array)—makes the code easy to modify and understand. The floor() function ensures we only create complete tables that fit on screen.

🔬 Each table has 4 chairs in a cross pattern. What if you duplicated the first line (top chair) to create 5 chairs per table? Would customers sit in two places at once, or would the extra chair just be empty?

      // Chair positions around the table
      let chairPositions = [
        { x: tableX, y: tableY - tableSize / 2 - chairSize / 2 }, // Top chair
        { x: tableX, y: tableY + tableSize / 2 + chairSize / 2 }, // Bottom chair
        { x: tableX - tableSize / 2 - chairSize / 2, y: tableY }, // Left chair
        { x: tableX + tableSize / 2 + chairSize / 2, y: tableY }  // Right chair
      ];
function generateRestaurantLayout() {
  chairs = []; // Clear previous chairs
  
  // Calculate padding dynamically for responsiveness
  paddingX = width * 0.1;
  paddingY = height * 0.15;

  // Tables and Chairs
  let numTablesX = floor((width - 2 * paddingX) / (tableSize + paddingX / 2));
  let numTablesY = floor((height - paddingY * 2) / (tableSize + paddingY / 2)); // Adjusted Y padding for top counter

  for (let y = 0; y < numTablesY; y++) {
    for (let x = 0; x < numTablesX; x++) {
      let tableX = paddingX + x * (tableSize + paddingX / 2) + tableSize / 2;
      let tableY = paddingY + y * (tableSize + paddingY / 2) + tableSize / 2; // Adjusted Y start

      // Chair positions around the table
      let chairPositions = [
        { x: tableX, y: tableY - tableSize / 2 - chairSize / 2 }, // Top chair
        { x: tableX, y: tableY + tableSize / 2 + chairSize / 2 }, // Bottom chair
        { x: tableX - tableSize / 2 - chairSize / 2, y: tableY }, // Left chair
        { x: tableX + tableSize / 2 + chairSize / 2, y: tableY }  // Right chair
      ];

      for (let pos of chairPositions) {
        chairs.push({ x: pos.x, y: pos.y, occupied: false });
      }
    }
  }
}
Line-by-line explanation (8 lines)

🔧 Subcomponents:

calculation Dynamic Padding Calculation paddingX = width * 0.1;

Calculates responsive margins based on canvas width for flexible layout

calculation Table Grid Dimensions let numTablesX = floor((width - 2 * paddingX) / (tableSize + paddingX / 2));

Calculates how many tables fit horizontally and vertically based on available space

for-loop Nested Table Placement Loop for (let y = 0; y < numTablesY; y++) {

Iterates through grid positions to place tables and their surrounding chairs

for-loop Chair Placement Loop for (let pos of chairPositions) {

Places 4 chairs (top, bottom, left, right) around each table and adds them to the chairs array

chairs = [];
Clears the chairs array so we can rebuild it fresh (useful when the window is resized).
paddingX = width * 0.1;
Sets horizontal margin to 10% of the canvas width, making the layout responsive to window resizing.
let numTablesX = floor((width - 2 * paddingX) / (tableSize + paddingX / 2));
Calculates how many tables fit horizontally: divide available width by (table size + gap between tables).
for (let y = 0; y < numTablesY; y++) {
Outer loop iterates through rows of tables from top to bottom.
for (let x = 0; x < numTablesX; x++) {
Inner loop iterates through columns of tables from left to right.
let tableX = paddingX + x * (tableSize + paddingX / 2) + tableSize / 2;
Calculates each table's x position: start with left padding, add columns spaced by table size + gap, then center offset.
let chairPositions = [
Creates an array of 4 chair positions (top, bottom, left, right) relative to the current table center.
for (let pos of chairPositions) {
Loops through the 4 chair positions and adds each as a new object to the global chairs array.

windowResized()

windowResized() is a p5.js built-in callback that fires whenever the browser window is resized. By recalculating positions and regenerating layouts inside it, the sketch stays responsive and playable at any window size. This is essential for modern web apps that adapt to phones, tablets, and desktops.

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
  
  // Recalculate shooter position for responsiveness
  shooterX = width / 2;
  shooterY = height - shooterSize;
  
  generateRestaurantLayout(); // Regenerate chairs on resize
}
Line-by-line explanation (4 lines)
resizeCanvas(windowWidth, windowHeight);
Resizes the p5.js canvas to match the new browser window dimensions.
shooterX = width / 2;
Recalculates the shooter's x position to remain centered on the new canvas.
shooterY = height - shooterSize;
Recalculates the shooter's y position to stay at the bottom of the new canvas.
generateRestaurantLayout();
Regenerates the entire restaurant layout so tables and chairs fit the new window size.

collideRectRect()

collideRectRect() implements Axis-Aligned Bounding Box (AABB) collision detection, the foundation of 2D game physics. It tests two conditions: x-overlap and y-overlap. Both must be true for a collision. This is more efficient than circle-distance checks and works perfectly for emoji-based games where rectangular hit areas make sense. The logic is: rect1's left side is to the left of rect2's right side, AND rect1's right side is to the right of rect2's left side (overlap!), then check y the same way.

function collideRectRect(x1, y1, w1, h1, x2, y2, w2, h2) {
  // Check if rectangles overlap on the x-axis
  if (x1 < x2 + w2 && x1 + w1 > x2) {
    // Check if rectangles overlap on the y-axis
    if (y1 < y2 + h2 && y1 + h1 > y2) {
      return true; // Collision detected
    }
  }
  return false; // No collision
}
Line-by-line explanation (5 lines)

🔧 Subcomponents:

conditional X-Axis Overlap Detection if (x1 < x2 + w2 && x1 + w1 > x2) {

Tests if the left-right edges of both rectangles overlap

conditional Y-Axis Overlap Detection if (y1 < y2 + h2 && y1 + h1 > y2) {

Tests if the top-bottom edges of both rectangles overlap; true collision only if both axes overlap

function collideRectRect(x1, y1, w1, h1, x2, y2, w2, h2) {
Defines a collision detector for two axis-aligned rectangles. Parameters are x, y (top-left corner), width, and height for each rect.
if (x1 < x2 + w2 && x1 + w1 > x2) {
Tests the x-axis: rect1's left is left of rect2's right AND rect1's right is right of rect2's left. Both conditions must be true for overlap.
if (y1 < y2 + h2 && y1 + h1 > y2) {
Tests the y-axis: same logic as x, checking vertical overlap. Both x and y must overlap for a true collision.
return true;
If both x and y axes overlap, the rectangles collide—return true.
return false;
If x or y (or both) don't overlap, the rectangles don't collide—return false.

📦 Key Variables

shooterEmoji string

The emoji displayed for the player's shooter at the bottom of the screen—change it to customize the player's appearance

let shooterEmoji = "🚀";
foodEmojis array

An array of food emoji strings that are randomly selected and shot at customers; mix and match food types to suit your game's theme

["🍕", "🍔", "🍟", "🌭", "🍿"]
bullets array

Stores all currently active bullet objects being displayed and moved each frame

let bullets = [];
people array

Stores all currently active Person objects representing customers in the restaurant

let people = [];
ambulances array

Stores all currently active Ambulance objects dispatched to rescue fallen customers

let ambulances = [];
chairs array

An array of chair objects {x, y, occupied} that define seating positions and whether they're taken

[{x: 100, y: 200, occupied: false}, ...]
shooterX number

The horizontal position of the shooter on the canvas

let shooterX;
shooterY number

The vertical position of the shooter (typically near the bottom of the canvas)

let shooterY;
shooterSize number

The diameter/height of the shooter emoji in pixels

let shooterSize = 64;
bulletSpeed number

How fast bullets travel each frame after being shot (in pixels per frame)

let bulletSpeed = 10;
bulletSize number

The diameter of each bullet emoji in pixels

let bulletSize = 32;
personSize number

The overall height/size of seated customer figures in pixels

let personSize = 48;
personGenerationInterval number

How many frames pass between spawning each new customer (e.g., 90 frames ≈ 1.5 seconds at 60fps)

let personGenerationInterval = 90;
eatingTimerDuration number

How many frames a customer spends eating before they start paying

let eatingTimerDuration = 60;
payingTimerDuration number

How many frames a customer spends paying before they leave

let payingTimerDuration = 60;
leavingSpeedMin number

The minimum speed at which leaving customers walk away from the table

let leavingSpeedMin = 0.01;
leavingSpeedMax number

The maximum speed at which leaving customers walk away (random between min and max)

let leavingSpeedMax = 0.05;
fallingInterval number

How many frames pass between random customers falling and needing ambulances

let fallingInterval = 300;
fallenTimerDuration number

How many frames a fallen person waits before an ambulance arrives (reference value; they don't self-recover)

let fallenTimerDuration = 180;
ambulanceSpeed number

How fast ambulances move across the canvas when arriving or leaving

let ambulanceSpeed = 3;
ambulanceSize number

The diameter of the ambulance emoji in pixels

let ambulanceSize = 90;
ambulancePickupTimerDuration number

How long the ambulance pauses at a fallen person's location before leaving

let ambulancePickupTimerDuration = 90;
tableSize number

The width/height of each table rectangle in the restaurant layout

let tableSize = 100;
chairSize number

The size of each chair emoji in pixels

let chairSize = 60;
frameCount number

Built-in p5.js variable that increments by 1 each frame, used to trigger events at intervals

if (frameCount % personGenerationInterval === 0)

🔧 Potential Improvements (6)

Here are some ways this code could be enhanced:

BUG mousePressed() collision detection with phone emoji

The phone emoji collision box is calculated using `bulletSize * 0.8`, but if bulletSize changes during play or across different person states, the clickable area might not match where the emoji is actually drawn.

💡 Store the phone emoji's actual text size as a constant, and use it consistently in both the Person.display() method and the collision detection in mousePressed().

PERFORMANCE draw() - table rendering loop

The table and chair rendering code runs every frame even though the restaurant layout is static. The only time it changes is on window resize.

💡 Move table and chair rendering into a separate function called only in setup() and windowResized(), or use a flag to redraw only when needed. This would reduce CPU usage each frame.

FEATURE mousePressed()

Currently, bullets only aim at the closest targetable person OR the mouse click location. There's no visual feedback showing which person will be hit before you click.

💡 Draw a line or highlight around the closest targetable person while the mouse hovers, so players can predict their shot before clicking.

STYLE draw() - nested loops for table rendering

The code to draw table rectangles, legs, and emoji is mixed with chair rendering logic, making it harder to understand the layout structure at a glance.

💡 Extract the table drawing into a separate helper function like drawTable(tableX, tableY) to improve readability and reduce draw() complexity.

BUG Person.display() - multiple state checks

If a person somehow ends up in multiple states simultaneously (e.g., both isFallen and isEating), only the first if-condition (isFallen) will execute, potentially hiding a logic error.

💡 Add an assertion or guard in Person.fall() to ensure conflicting states are impossible, or add a debug mode that logs if multiple states are true at once.

PERFORMANCE draw() - collision detection loop

The bullet-person collision loop checks every bullet against every person, which is O(n*m) complexity. As the game grows with more entities, this could slow down.

💡 Implement spatial partitioning (divide the canvas into grid cells) to only check collisions between bullets and people in nearby cells, reducing checks significantly.

🔄 Code Flow

Code flow showing setup, draw, bullet, person, ambulance, mousepressed, generatelayoutfunction, windowresized, colliderect

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

graph TD start[Start] --> setup[setup] setup --> draw[draw loop] draw --> people-generation-loop[People Generation Loop] draw --> falling-trigger-loop[Falling Trigger Loop] draw --> people-update-loop[People Update Loop] draw --> ambulance-update-loop[Ambulance Update Loop] draw --> bullet-collision-loop[Bullet Collision Loop] people-generation-loop -->|Every personGenerationInterval frames| draw falling-trigger-loop -->|Every fallingInterval frames| draw people-update-loop -->|For each person| fallen-state-rendering[Fallen State Rendering] people-update-loop -->|For each person| eating-state-rendering[Eating State Rendering] people-update-loop -->|For each person| paying-state-rendering[Paying State Rendering] people-update-loop -->|For each person| leaving-state-rendering[Leaving State Rendering] people-update-loop -->|For each person| normal-state-rendering[Normal State Rendering] people-update-loop -->|Remove off-screen people| draw ambulance-update-loop -->|For each ambulance| arriving-state[Arriving State] ambulance-update-loop -->|For each ambulance| pickup-state[Pickup State] ambulance-update-loop -->|For each ambulance| leaving-state[Leaving State] ambulance-update-loop -->|Remove off-screen ambulances| draw bullet-collision-loop -->|For each bullet| direction-calculation[Direction Calculation] bullet-collision-loop -->|For each bullet| x-overlap-check[X-Axis Overlap Detection] bullet-collision-loop -->|For each bullet| y-overlap-check[Y-Axis Overlap Detection] bullet-collision-loop -->|Remove bullets on collision or off-screen| draw click setup href "#fn-setup" click draw href "#fn-draw" click people-generation-loop href "#sub-people-generation-loop" click falling-trigger-loop href "#sub-falling-trigger-loop" click people-update-loop href "#sub-people-update-loop" click ambulance-update-loop href "#sub-ambulance-update-loop" click bullet-collision-loop href "#sub-bullet-collision-loop" click fallen-state-rendering href "#sub-fallen-state-rendering" click eating-state-rendering href "#sub-eating-state-rendering" click paying-state-rendering href "#sub-paying-state-rendering" click leaving-state-rendering href "#sub-leaving-state-rendering" click normal-state-rendering href "#sub-normal-state-rendering" click arriving-state href "#sub-arriving-state" click pickup-state href "#sub-pickup-state" click leaving-state href "#sub-leaving-state" click direction-calculation href "#sub-direction-calculation" click x-overlap-check href "#sub-x-overlap-check" click y-overlap-check href "#sub-y-overlap-check"

❓ Frequently Asked Questions

What kind of visual elements can I expect to see in the Sketch Poop project?

The Sketch Poop project visually features an emoji-based shooter that launches various food emojis as bullets while animated characters interact with the environment, including eating and falling.

How can I interact with the Sketch Poop sketch?

Users can interact by controlling the shooter, which allows them to aim and shoot food emojis at animated characters, creating a playful and engaging experience.

What creative coding concepts are showcased in the Sketch Poop sketch?

This sketch demonstrates the use of object arrays for managing multiple entities, animations for character interactions, and emoji graphics for a whimsical presentation.

Preview

Sketch poop - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of Sketch poop - Code flow showing setup, draw, bullet, person, ambulance, mousepressed, generatelayoutfunction, windowresized, colliderect
Code Flow Diagram