more food and rated it

This sketch creates an interactive restaurant simulation where customers arrive, order food, eat at tables, and leave. The simulation includes a dynamic menu system, table management, and an unexpected mechanic where customers who can't afford expensive items become armed and dangerous.

🧪 Try This!

Experiment with the code by making these changes:

  1. Make customers super fast walkers
  2. Make everyone start broke — Change the random chance to 1.0 so 100% of customers start with zero money—the restaurant becomes a shooting gallery.
  3. Make the burger affordable — Drop the burger price from 10 million to something reasonable like 50—suddenly customers can eat peacefully again.
  4. Speed up the eating animation — Lower the eating duration so customers finish their food quickly and leave—reduces table wait times.
  5. Change the background color — Swap the sky-blue background for a deep night sky or warm sunset—instantly changes the restaurant's mood.
Prefer the full editor? Open it there →

📖 About This Sketch

This sketch builds a fully functional restaurant simulation where dozens of AI-driven customers move through a complete dining workflow. It combines several core p5.js techniques: the draw loop to animate all actors each frame, object-oriented programming with Person, Table, Restaurant, and UFO classes, touch/mouse interaction to spawn and activate customers, and a sophisticated state machine that tracks each person's journey from arrival through departure. The result is a living, breathing simulation that teaches how to orchestrate complex interactions between multiple independent agents.

The code is structured around five main classes: Person (the customers with full AI logic), Restaurant (the building and ordering zones), Table (seating management), UFO (alien abduction animation), and Bouncer (security). The simulation includes an expandable menu system, collision-free table seating for up to two people per table, and a dramatic twist—customers without enough money to afford the expensive burger become 'armed and dangerous' and shoot other patrons before fleeing. By reading this code, you'll learn how to design reusable classes, implement state machines for character behavior, manage arrays of dynamic objects, and create emergent storytelling through code logic.

⚙️ How It Works

  1. When the sketch loads, setup() creates a canvas, a restaurant building with ordering and paying zones, three dining tables inside, 200 initial customers scattered around the canvas, and populates the menu with pizza and burgers. The menu is then expanded with additional food items like ice cream, steaks, and rice.
  2. Every frame, the draw loop updates and displays the restaurant, tables, bouncer, and all customers. Each customer runs through a state machine: idle (waiting to be tapped), goingToRestaurant (walking to the ordering zone), ordering (placing their order and deducting money), waitingForTable (if no tables are free), goingToTable (walking to their assigned seat), eating (displaying a shrinking food icon), paying (walking to the paying zone), and leaving (exiting by walking or UFO beaming).
  3. Customers are spawned with random starting money (90% have 100 million, 10% have zero). When they try to order the 10-million-dollar burger and can't afford it, they enter the 'armedAndDangerous' state, grow a visible gun, and after a 1-second delay, shoot a random other customer. The shot customer is removed from the simulation. The shooter then transitions to the leaving state and eventually disappears.
  4. Players interact by tapping customers to activate them (if idle) or tapping menu items to spawn a new customer that specifically orders that food. As customers leave, new ones are spawned to replace them, keeping the simulation populated.
  5. The menu system tracks all available food items and their costs in the allFoodItems array. Clicking the 'Expand Menu' button adds nine new food types to the currentMenuOptions array, which instantly updates what customers can order.
  6. UFO abductions occur randomly (20% chance per leaving customer): instead of walking off-screen, the UFO descends, beams the customer up with a green beam effect, and flies away. The person shrinks as they ascend, creating a visual beam-up animation.

🎓 Concepts You'll Learn

State machinesObject-oriented programming (classes)Array manipulationTouch and mouse interactionAnimation and kinematicsCollision and distance detectionDynamic UI and menusEmergent behavior

📝 Code Breakdown

setup()

setup() runs once when the sketch starts. It's the perfect place to initialize all objects, create the canvas, and set starting values. Notice how it calculates positions responsively based on windowWidth and windowHeight, so the layout adapts if the browser is resized.

function setup() {
  createCanvas(windowWidth, windowHeight);
  userStartAudio(); // Important for audio on mobile devices, even if no audio is used yet

  // Define menu area
  menuArea = {
    x: windowWidth * 0.8,
    y: 10,
    w: windowWidth * 0.18,
    h: windowHeight - 20
  };

  // Calculate restaurant size and position based on window size
  let restaurantW = windowWidth * 0.4;
  let restaurantH = windowHeight * 0.6;
  let restaurantX = windowWidth * 0.3;
  let restaurantY = windowHeight * 0.2;
  restaurant = new Restaurant(restaurantX, restaurantY, restaurantW, restaurantH);

  // Create bouncer instance first (its size is set in the constructor)
  // Then calculate and set its initial position
  bouncer = new Bouncer(0, 0); // Temporary position
  bouncer.x = restaurant.orderingZone.x + restaurant.orderingZone.w + bouncer.size / 2;
  bouncer.y = restaurant.orderingZone.y + restaurant.orderingZone.h / 2;
  bouncer.targetX = bouncer.x; // Set target to initial position as well
  bouncer.targetY = bouncer.y;

  // Calculate table size and position
  let tableW = restaurantW * 0.2;
  let tableH = restaurantH * 0.2;
  let paddingX = restaurantW * 0.05;
  let paddingY = restaurantH * 0.05;

  // Create 3 tables inside the restaurant
  tables.push(new Table(restaurant.x + paddingX, restaurant.y + paddingY, tableW, tableH));
  tables.push(new Table(restaurant.x + restaurantW - tableW - paddingX, restaurant.y + paddingY, tableW, tableH));
  tables.push(new Table(restaurant.x + paddingX, restaurant.y + restaurantH - tableH - paddingY, tableW, tableH));

  // Create some initial people outside the restaurant
  // Capping at 200 people to maintain performance, as 100,000 would be too many for p5.js
  for (let i = 0; i < 200; i++) {
    // *** NEW CHANGE: 10% chance for a person to start with 0 money ***
    let personStartingMoney = initialMoney;
    if (random(1) < 0.1) { // 10% chance to be poor
      personStartingMoney = 0;
    }
    people.push(new Person(random(width), random(height), personStartingMoney));
  }

  // Create the add food button
  addFoodButton = createButton('Expand Menu');
  addFoodButton.position(10, 10);
  addFoodButton.size(150, 40);
  addFoodButton.style('background-color', '#4CAF50'); // Green
  addFoodButton.style('color', 'white');
  addFoodButton.style('border', 'none');
  addFoodButton.style('border-radius', '5px');
  addFoodButton.style('font-size', '14px');
  addFoodButton.mousePressed(addNewFoodToMenu); // Attach event handler

  // *** Call addNewFoodToMenu automatically in setup() to pre-fill menu ***
  addNewFoodToMenu();
}
Line-by-line explanation (13 lines)

🔧 Subcomponents:

initialization Canvas and Menu Setup createCanvas(windowWidth, windowHeight);

Creates a full-screen canvas and defines the menu area as a rectangle in the top-right corner

object-creation Restaurant Object Creation restaurant = new Restaurant(restaurantX, restaurantY, restaurantW, restaurantH);

Instantiates the Restaurant class with calculated dimensions and positions, creating the main game environment

object-creation Bouncer Positioning bouncer.x = restaurant.orderingZone.x + restaurant.orderingZone.w + bouncer.size / 2;

Places the bouncer next to the ordering zone so they can oversee customers placing orders

for-loop Table Creation Loop tables.push(new Table(restaurant.x + paddingX, restaurant.y + paddingY, tableW, tableH));

Creates three Table objects at different positions inside the restaurant, giving customers places to sit and eat

for-loop Initial Customer Spawn Loop for (let i = 0; i < 200; i++) {

Spawns 200 customers with random positions and money (90% wealthy, 10% broke) to populate the initial simulation

initialization Menu Expansion Button addFoodButton = createButton('Expand Menu');

Creates an interactive button that lets players add more food items to the restaurant's menu

createCanvas(windowWidth, windowHeight);
Creates a canvas that fills the entire browser window; windowWidth and windowHeight are built-in p5.js variables that auto-update
userStartAudio();
Initializes audio context on mobile devices—required before any sound can play (not used here but good practice)
menuArea = { x: windowWidth * 0.8, y: 10, w: windowWidth * 0.18, h: windowHeight - 20 };
Defines a rectangular menu area in the top-right corner (80% to the right, covering 18% width) where food items will be listed
restaurant = new Restaurant(restaurantX, restaurantY, restaurantW, restaurantH);
Creates the Restaurant object (a 40% × 60% rectangle centered on the canvas) that contains ordering zones, paying zones, and tables
bouncer = new Bouncer(0, 0);
Creates a Bouncer object at a temporary position; its final position is calculated next using the restaurant's dimensions
bouncer.x = restaurant.orderingZone.x + restaurant.orderingZone.w + bouncer.size / 2;
Positions the bouncer just to the right of the ordering zone so they can oversee customer orders (currently unused in the 'armed' mechanic)
tables.push(new Table(restaurant.x + paddingX, restaurant.y + paddingY, tableW, tableH));
Adds three Table objects to the tables array at calculated positions inside the restaurant, each with 2-person capacity
for (let i = 0; i < 200; i++) {
Loop that runs 200 times, spawning 200 initial customers scattered randomly across the canvas
if (random(1) < 0.1) { personStartingMoney = 0; }
10% random chance that a customer starts with $0 (broke)—these customers will become armed when they try to order the expensive burger
people.push(new Person(random(width), random(height), personStartingMoney));
Creates a new Person object with random x/y position and the calculated money amount, adds it to the people array
addFoodButton = createButton('Expand Menu');
Creates an HTML button that players can click to add more food items to the restaurant menu
addFoodButton.mousePressed(addNewFoodToMenu);
Connects the button's click event to the addNewFoodToMenu() function so clicking expands the menu
addNewFoodToMenu();
Automatically calls the menu expansion function once at startup, so the full menu (pizza, burger, ice cream, etc.) is ready immediately

draw()

draw() runs 60 times per second (the animation loop). Notice the backwards loop with `for (let i = people.length - 1; i >= 0; i--)` — this is the safe way to remove items from an array while iterating. Also notice how the code handles three different outcomes from person.update(): shooting, peaceful removal, or staying. This is a clean pattern for emergent behavior in multi-agent simulations.

function draw() {
  background(173, 216, 230); // Sky blue background

  restaurant.display(); // Display the restaurant

  for (let table of tables) {
    table.display(); // Display all tables
  }

  bouncer.update(); // Update the bouncer's state (it will largely be idle now for the burger scenario)
  bouncer.display(); // Display the bouncer

  // Update and display each person
  // Loop backwards to safely remove people from the array
  for (let i = people.length - 1; i >= 0; i--) {
    let person = people[i];
    let actionResult = person.update(); // actionResult could be null, "remove", or { action: "shoot", ... }

    if (actionResult && actionResult.action === "shoot") {
      let targetPerson = actionResult.target;
      let targetIndex = -1;
      // Find the target person's index in the *current* people array
      for (let j = 0; j < people.length; j++) {
        if (people[j] === targetPerson) {
          targetIndex = j;
          break;
        }
      }

      if (targetIndex > -1) {
        // Remove the target person (the "died person")
        people.splice(targetIndex, 1);
        console.log(`${person.ordererName} shot and killed ${targetPerson.ordererName}!`);
        // If the removed target was *before* the current person in the loop, adjust i
        // to prevent skipping the next element after removal.
        if (targetIndex < i) {
          i--;
        }
        // The shot person is NOT replaced.
      }
      // The shooter (person) has transitioned to "leaving" state in Person.update(),
      // so they will be removed by the "remove" action in a subsequent iteration
      // (or in the current one if their leaving animation is instant).
    } else if (actionResult === "remove") {
      // Remove the current person from the array
      people.splice(i, 1);
      // Only replace if they left peacefully (not a shooter, not shot)
      // The shooter is marked with `person.wasShooter = true`.
      // A shot person is removed in the "shoot" block and not replaced.
      if (!person.wasShooter) { // Only replace if they were not the shooter
        people.push(new Person(random(width), random(height), initialMoney)); // Replace
      }
    }

    // Display the person (if they are still in the array after potential removal/shooting in this frame)
    if (i < people.length) { // Check if the index is still valid
      people[i].display();
    }
  }

  drawMenu(); // Draw the menu on top
}
Line-by-line explanation (14 lines)

🔧 Subcomponents:

initialization Background Clear background(173, 216, 230);

Clears the canvas with sky blue each frame, erasing previous frames' drawings so animation isn't a trail

display-loop Static Object Displays restaurant.display();

Calls display methods on the restaurant, all tables, and bouncer to draw the environment each frame

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

Iterates backwards through the people array to safely update and remove people during the loop

conditional Shooting Incident Handler if (actionResult && actionResult.action === "shoot") {

Detects when an armed person shoots another customer, removes the target, adjusts loop index if needed, and logs the event

conditional Person Removal and Replacement } else if (actionResult === "remove") {

Handles customers leaving: removes them from the array, and spawns a new random customer to replace them (unless they were a shooter)

background(173, 216, 230);
Clears the entire canvas with a light blue color each frame—without this, all drawings would stack and create trails
restaurant.display();
Calls the Restaurant object's display() method, which draws the building, ordering zone, and paying zone
for (let table of tables) { table.display(); }
Loops through all three Table objects and calls their display() method to draw each table (darker brown if occupied, lighter if empty)
bouncer.update();
Updates the bouncer's state machine (currently mostly idle, but could be extended with more complex behavior)
bouncer.display();
Draws the bouncer as a dark blue circle with a 'BOUNCER' label above their head
for (let i = people.length - 1; i >= 0; i--) {
Loops backwards through the people array (from last to first) so removing elements doesn't skip any people
let actionResult = person.update();
Calls the person's update() method, which runs their state machine logic and returns null, 'remove', or a shooting action object
if (actionResult && actionResult.action === "shoot") {
Checks if the person just shot someone; if true, finds the target in the people array and removes them
people.splice(targetIndex, 1);
Removes one person (the victim) from the array at targetIndex; splice modifies the array directly
if (targetIndex < i) { i--; }
If the removed person was earlier in the loop, decrement i to compensate so the next iteration doesn't skip anyone
} else if (actionResult === "remove") {
Checks if the person left normally (not shot, not a shooter); if so, removes them and replaces with a new customer
if (!person.wasShooter) { people.push(new Person(...)); }
Only replaces the person if they weren't a shooter—shooters are not replaced to maintain population when conflicts occur
if (i < people.length) { people[i].display(); }
Safely checks that the index is still valid before calling display(), since removal might have shifted array indices
drawMenu();
Calls drawMenu() to render the menu panel in the top-right corner on top of all other graphics

addNewFoodToMenu()

This function demonstrates two key JavaScript patterns: the .some() array method (which returns true if any element passes a test) and the spread operator ... to copy arrays. The duplicate check prevents food items from appearing twice on the menu—essential for keeping the data clean. Also notice how setTimeout() is used for simple animation: changing button text, waiting 2 seconds, then changing it back. This is a common pattern for UI feedback in interactive sketches.

🔬 This loop only adds food if it's not already in allFoodItems (the duplicate check). What happens if you remove the if-statement so every call to addNewFoodToMenu() adds duplicates? Will prices change or will the same item appear twice?

  for (let food of newFood) {
    // Only add if not already present
    if (!allFoodItems.some(item => item.name === food.name)) {
      allFoodItems.push(food);
    }
  }
function addNewFoodToMenu() {
  const newFood = [
    { name: "iceCream", cost: 15 },
    { name: "strawberries", cost: 8 },
    { name: "steak", cost: 30 },
    { name: "meat", cost: 25 }, // General meat, distinct from steak
    { name: "meatballs", cost: 18 },
    { name: "rice", cost: 12 },
    { name: "cheese", cost: 14 },
    { name: "chips", cost: 7 },
    { name: "macAndCheese", cost: 20 }
  ];

  // Add new food items to the global array that defines menu options
  for (let food of newFood) {
    // Only add if not already present
    if (!allFoodItems.some(item => item.name === food.name)) {
      allFoodItems.push(food);
    }
  }
  // Update the currentMenuOptions array to include the newly added items
  currentMenuOptions = [...allFoodItems];
  console.log("New food items added to menu:", newFood.map(f => f.name).join(", "));
  addFoodButton.html('Menu Expanded!'); // Give feedback
  setTimeout(() => addFoodButton.html('Expand Menu'), 2000); // Reset button text
  drawMenu(); // Redraw the menu to show new items
}
Line-by-line explanation (10 lines)

🔧 Subcomponents:

array-initialization New Food Items Array const newFood = [

Defines nine new food items with their names and costs to be added to the restaurant's menu

for-loop Duplicate Check and Add Loop for (let food of newFood) {

Iterates through new food items and checks if they already exist before adding them to allFoodItems

ui-update Button Visual Feedback addFoodButton.html('Menu Expanded!');

Changes the button text to confirm the expansion, then resets it after 2 seconds

const newFood = [
Creates a constant array called newFood containing nine new food objects with name and cost properties
{ name: "iceCream", cost: 15 },
Defines ice cream as an object with a name property and a cost property—each food item has this structure
for (let food of newFood) {
Uses a for-of loop to iterate through each food object in the newFood array
if (!allFoodItems.some(item => item.name === food.name)) {
The .some() method checks if ANY item in allFoodItems has the same name as the current food—if none do, the condition is true
allFoodItems.push(food);
If the food is not a duplicate, adds it to the global allFoodItems array using push()
currentMenuOptions = [...allFoodItems];
The spread operator [...allFoodItems] creates a shallow copy of all food items and assigns it to currentMenuOptions, ensuring the menu is up-to-date
console.log("New food items added to menu:", newFood.map(f => f.name).join(", "));
Logs a message to the browser console with all the new food names separated by commas, using map() to extract names and join() to format them
addFoodButton.html('Menu Expanded!');
Changes the button's text to 'Menu Expanded!' to give immediate visual feedback to the player
setTimeout(() => addFoodButton.html('Expand Menu'), 2000);
Uses setTimeout() to reset the button text back to 'Expand Menu' after 2000 milliseconds (2 seconds)
drawMenu();
Immediately redraws the menu display to show the new food items on screen

drawMenu()

This function demonstrates the core of p5.js 2D drawing: fill, stroke, rect, and text. It's called every frame, so the menu is redrawn 60 times per second—this is fine for static text, but for better performance, you could draw the menu once to a graphics buffer using createGraphics() and then just display that buffer. Notice the commented-out highlight code—this is a placeholder for future interactivity like highlighting menu items on hover.

function drawMenu() {
  // Menu background
  fill(240); // Light grey
  stroke(100);
  rect(menuArea.x, menuArea.y, menuArea.w, menuArea.h, 5);

  fill(0);
  textAlign(CENTER, TOP);
  textSize(16);
  text("MENU", menuArea.x + menuArea.w / 2, menuArea.y + 10);

  textAlign(LEFT, CENTER);
  textSize(12);
  let yOffset = menuArea.y + 40;
  let itemHeight = 30;

  for (let i = 0; i < currentMenuOptions.length; i++) {
    let food = currentMenuOptions[i];
    let itemY = yOffset + i * itemHeight;

    // Highlight tapped item (optional, for visual feedback)
    // if (isTappedOnMenuItem(touchX, touchY, i)) { // Needs touchX, touchY from touchStarted
    //   fill(220);
    //   noStroke();
    //   rect(menuArea.x + 5, itemY - itemHeight / 2 + 2, menuArea.w - 10, itemHeight - 4, 3);
    // }

    fill(0);
    text(`${food.name}: $${food.cost}`, menuArea.x + 10, itemY);
  }
}
Line-by-line explanation (11 lines)

🔧 Subcomponents:

for-loop Food Items Display Loop for (let i = 0; i < currentMenuOptions.length; i++) {

Iterates through all available food items and draws each one as a line of text showing name and cost

fill(240);
Sets the fill color to light grey (240 on a 0–255 scale); all shapes drawn next will use this color
stroke(100);
Sets the outline color to dark grey (100); the next shape will have a dark border
rect(menuArea.x, menuArea.y, menuArea.w, menuArea.h, 5);
Draws a rectangle at menuArea's position with its width and height; the 5 at the end rounds the corners
textAlign(CENTER, TOP);
Sets text alignment to horizontally centered and vertically to the top of the text box
text("MENU", menuArea.x + menuArea.w / 2, menuArea.y + 10);
Draws 'MENU' as a title, horizontally centered in the panel and 10 pixels below the top
textAlign(LEFT, CENTER);
Switches alignment to left-aligned and vertically centered, suitable for displaying list items
let yOffset = menuArea.y + 40;
Sets the starting y position for food items 40 pixels below the top (leaving space for the title)
let itemHeight = 30;
Defines the vertical spacing between each menu item—each food takes 30 pixels of vertical space
for (let i = 0; i < currentMenuOptions.length; i++) {
Loops through each food item in currentMenuOptions, from index 0 to the last item
let itemY = yOffset + i * itemHeight;
Calculates the y position for this food item: start position plus the item number times item height
text(`${food.name}: $${food.cost}`, menuArea.x + 10, itemY);
Draws the food name and cost as text (using a template literal with backticks), positioned 10 pixels from the left edge

touchStarted()

This function demonstrates how p5.js handles user input: touchStarted() is called whenever the user touches the screen (on mobile) or clicks the mouse. The key insight is that p5.js provides both touches[] (an array of all active touches) and mouse alternatives (mouseX, mouseY) for cross-device compatibility. Notice the early return false inside the menu-tap block—this prevents a double-tap (once on the menu, once on the person) from activating the same person. Also notice the distance-based hit detection: dist() is used to check if a tap falls within a person's circular area, which is more forgiving than pixel-perfect rectangular checks.

🔬 This code calculates which menu item was tapped by dividing coordinates by itemHeight. What happens if you change itemHeight to 50 here while keeping drawMenu's itemHeight at 30? Will taps still land on the right item?

  // Check if a food item on the menu was tapped
  if (touchX > menuArea.x && touchX < menuArea.x + menuArea.w &&
      touchY > menuArea.y + 40 && touchY < menuArea.y + 40 + currentMenuOptions.length * 30) { // +40 for MENU title offset
    let itemHeight = 30; // Height of each menu item
    let tappedIndex = floor((touchY - (menuArea.y + 40)) / itemHeight);
function touchStarted() {
  // Use touches[0] for the first touch point on tablet
  let touchX = touches[0] ? touches[0].x : mouseX;
  let touchY = touches[0] ? touches[0].y : mouseY;

  // Check if a food item on the menu was tapped
  if (touchX > menuArea.x && touchX < menuArea.x + menuArea.w &&
      touchY > menuArea.y + 40 && touchY < menuArea.y + 40 + currentMenuOptions.length * 30) { // +40 for MENU title offset
    let itemHeight = 30; // Height of each menu item
    let tappedIndex = floor((touchY - (menuArea.y + 40)) / itemHeight); // Adjust for title offset
    if (tappedIndex >= 0 && tappedIndex < currentMenuOptions.length) {
      let tappedFood = currentMenuOptions[tappedIndex];
      // Spawn a new person with the specific order
      let newPerson = new Person(random(width), random(height), initialMoney, tappedFood.name);
      people.push(newPerson);
      newPerson.goToRestaurant();
      return false; // Prevent further interaction
    }
  }

  // Existing logic for tapping people
  for (let person of people) {
    let d = dist(touchX, touchY, person.x, person.y);
    if (d < person.size / 2) { // Check if touch is within person's circle
      person.tap(); // Activate the tapped person
      break; // Only tap one person at a time
    }
  }
  return false; // Prevent default browser behavior (scrolling/zooming)
}
Line-by-line explanation (15 lines)

🔧 Subcomponents:

initialization Touch Position Capture let touchX = touches[0] ? touches[0].x : mouseX;

Captures the x coordinate of the first touch point (or uses mouseX as fallback for desktop)

object-creation New Person Spawn let newPerson = new Person(random(width), random(height), initialMoney, tappedFood.name);

Creates a new customer with a pre-set order matching the tapped menu item

for-loop Person Tap Detection Loop for (let person of people) {

Iterates through all customers to find if one was tapped (if no menu item was tapped first)

let touchX = touches[0] ? touches[0].x : mouseX;
Uses the ternary operator (? :) to check if a touch exists; if yes, use touches[0].x, otherwise use mouseX for mouse clicks
let touchY = touches[0] ? touches[0].y : mouseY;
Same logic for Y coordinate—gets touch Y if available, otherwise falls back to mouseY
if (touchX > menuArea.x && touchX < menuArea.x + menuArea.w &&
Checks if the touch x is within the menu's horizontal bounds (right side of screen)
touchY > menuArea.y + 40 && touchY < menuArea.y + 40 + currentMenuOptions.length * 30)
Checks if the touch y is within the menu's vertical bounds, accounting for the 40-pixel title area and spacing
let tappedIndex = floor((touchY - (menuArea.y + 40)) / itemHeight);
Calculates which menu item was tapped by subtracting the menu's top offset, dividing by item height, and flooring to get an integer index
if (tappedIndex >= 0 && tappedIndex < currentMenuOptions.length) {
Validates the index to ensure it's within bounds (not negative, not beyond array length)
let tappedFood = currentMenuOptions[tappedIndex];
Retrieves the food object that was tapped from the currentMenuOptions array
let newPerson = new Person(random(width), random(height), initialMoney, tappedFood.name);
Creates a new Person with the tapped food's name as the specificOrderName parameter, so they immediately order that food
people.push(newPerson);
Adds the new person to the people array so they become part of the simulation
newPerson.goToRestaurant();
Immediately sends the new person to the restaurant to begin ordering
for (let person of people) {
If no menu tap was detected, loops through all customers to check if one was tapped
let d = dist(touchX, touchY, person.x, person.y);
Calculates the distance from the touch point to the person's position using p5.js's dist() function
if (d < person.size / 2) {
If the distance is less than half the person's size (their radius), the touch hit that person
person.tap();
Calls the tap() method on the person, which wakes them from idle state and sends them to the restaurant
return false;
Returns false to prevent default browser behaviors like scrolling or zooming

windowResized()

windowResized() is a special p5.js function that runs automatically whenever the browser window is resized. By recalling setup(), this sketch maintains a responsive layout where the restaurant, tables, and menu scale proportionally to the window size. This is good practice for sketches meant to run on different screen sizes (phones, tablets, desktops).

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
  setup(); // Re-initialize all elements based on the new window size
}
Line-by-line explanation (2 lines)

🔧 Subcomponents:

initialization Canvas Resize resizeCanvas(windowWidth, windowHeight);

Adjusts the canvas size to match the new window dimensions

function-call Setup Re-initialization setup();

Re-runs setup() to recalculate all element positions based on the new window size, keeping the layout responsive

resizeCanvas(windowWidth, windowHeight);
Calls p5.js's built-in resizeCanvas() function to adjust the canvas to match the new window width and height
setup();
Calls setup() again to recalculate all element positions—restaurant size, table positions, menu area, etc.—based on the new dimensions

Person Class

The Person class constructor shows good object-oriented design: every property a person needs is initialized here. Notice the optional parameter specificOrderName = null—this allows flexibility: normally people order randomly, but when spawned from the menu, they can be pre-configured to order a specific food. The gun mechanic properties (hasGun, shootTimer, shootDelay) are all initialized to safe defaults, then activated only in specific circumstances. This is a clean pattern for optional features in class design.

class Person {
  constructor(x, y, initialMoney, specificOrderName = null) { // Added specificOrderName
    this.x = x;
    this.y = y;
    this.size = 30;
    this.color = color(random(100, 255), random(100, 255), random(100, 255));
    this.state = "idle"; // idle, goingToRestaurant, ordering, waitingForTable, goingToTable, eating, paying, leaving, armedAndDangerous (NEW)
    this.money = initialMoney;
    this.order = null; // "pizza" or "burger" or new items
    this.specificOrderName = specificOrderName; // Store the specific order
    this.targetX = x;
    this.targetY = y;
    this.speed = 2;
    this.timer = 0; // Generic timer for delays like ordering, eating, paying, or waiting
    this.eatingDuration = 0; // Stores the total time for eating animation
    this.message = ""; // Text displayed above person
    this.table = null; // Assigned table object
    this.leavingType = "walk"; // "walk" or "ufo"
    this.ufo = null; // Instance of UFO class if this person is leaving by UFO
    this.ufoInitialPersonY = null; // Store person's Y when UFO was initiated for beaming animation

    // --- NEW PROPERTIES FOR GUN MECHANIC ---
    this.ordererName = random(["Alice", "Bob", "Charlie", "Diana", "Ethan", "Fiona", "George", "Hannah", "Isaac", "Jessica", "Kevin", "Laura"]); // For better messages
    this.hasGun = false; // Flag to indicate if the person has a gun
    this.shootTimer = 0; // Timer for the shooting delay
    this.shootDelay = 1000; // 1 second delay before shooting
    this.wasShooter = false; // Flag to indicate if this person was the shooter
  }
Line-by-line explanation (10 lines)

🔧 Subcomponents:

initialization Basic Position and Appearance this.x = x;

Initializes the person's position, size, and random color from constructor parameters

initialization State Machine Properties this.state = "idle";

Sets up the person's initial state and related properties for tracking their journey

initialization Gun Mechanic Properties this.hasGun = false;

Initializes flags and timers for the armed-and-dangerous mechanic

this.x = x;
Stores the initial x position passed to the constructor
this.y = y;
Stores the initial y position passed to the constructor
this.size = 30;
Sets the person's visual diameter to 30 pixels
this.color = color(random(100, 255), random(100, 255), random(100, 255));
Generates a random RGB color for each person so they look unique (avoids very dark colors by starting at 100)
this.state = "idle";
Sets the starting state to 'idle'—the person will do nothing until tapped
this.money = initialMoney;
Stores the amount of money the person starts with (passed from constructor)
this.specificOrderName = specificOrderName;
Stores a pre-set food order if provided (used when tapping a menu item to spawn a specific customer)
this.ordererName = random(["Alice", "Bob", ...]);
Assigns each person a random human name for better in-console messages and personality
this.hasGun = false;
Initially false; becomes true only if the person tries to order the expensive burger without enough money
this.shootDelay = 1000;
Sets a 1-second (1000 millisecond) delay before an armed person shoots—gives time for drama

Person.display()

This display() method is one of the most complex in the sketch because it draws not just the person, but also their money, food, state message, and potential gun or UFO. Each food item has its own if-else branch to customize the visual icon. The eating animation is clever: it uses map() to convert time remaining into a scale factor, then animates the food icon shrinking. This is a common pattern in creative coding: mapping one range of numbers to another using the map() function.

🔬 This code shrinks the food icon as the person eats. What happens if you change the second map() so it goes from 0.1, 1.0 to 1.0, 0.1 (reversed)? Will the food grow instead of shrink?

        let eatingProgress = map(remainingTime, 0, this.eatingDuration, 0, 1); // 1 at start, 0 at end
        let iconScale = map(eatingProgress, 0, 1, 0.1, 1.0); // Shrinks from 1.0 to 0.1
        iconSize *= iconScale;
  display() {
    fill(this.color);
    noStroke();
    ellipse(this.x, this.y, this.size); // Person body

    // Display money
    fill(0);
    textAlign(CENTER, CENTER);
    textSize(12);
    text(`$${this.money}`, this.x, this.y - this.size);

    // Display order visualization if they have one
    if (this.order) {
      let iconSize = this.size * 0.6;
      let iconY = this.y - this.size - 10; // Position above the person

      // Shrink food icon while eating
      if (this.state === "eating" && this.timer > millis() && this.eatingDuration > 0) {
        let remainingTime = this.timer - millis();
        let eatingProgress = map(remainingTime, 0, this.eatingDuration, 0, 1); // 1 at start, 0 at end
        let iconScale = map(eatingProgress, 0, 1, 0.1, 1.0); // Shrinks from 1.0 to 0.1
        iconSize *= iconScale;
        // Adjust iconY to keep the base of the icon centered vertically during shrink
        iconY = this.y - this.size - 10 + (this.size * 0.6 - iconSize);
      }

      if (this.order === "pizza") {
        fill(255, 200, 0); // Yellowish for pizza crust
        ellipse(this.x, iconY, iconSize);
        fill(200, 50, 50); // Red for sauce/pepperoni
        ellipse(this.x - iconSize * 0.15, iconY - iconSize * 0.15, iconSize * 0.2);
        ellipse(this.x + iconSize * 0.2, iconY + iconSize * 0.1, iconSize * 0.15);
      } else if (this.order === "burger") {
        fill(150, 100, 50); // Brown for bun
        ellipse(this.x, iconY - iconSize * 0.2, iconSize * 0.7, iconSize * 0.3);
        fill(100, 50, 20); // Darker brown for patty
        rectMode(CENTER); // Draw rectangle from its center
        rect(this.x, iconY - iconSize * 0.05, iconSize * 0.6, iconSize * 0.2);
        rectMode(CORNER); // Reset rectMode to default
        fill(150, 100, 50); // Brown for bun
        ellipse(this.x, iconY + iconSize * 0.2, iconSize * 0.7, iconSize * 0.3);
      } else if (this.order === "iceCream") {
        fill(180, 200, 255); // Light blue/white for ice cream
        ellipse(this.x, iconY - iconSize * 0.25, iconSize * 0.6);
        fill(220, 180, 120); // Cone color
        triangle(this.x - iconSize * 0.2, iconY,
                 this.x + iconSize * 0.2, iconY,
                 this.x, iconY + iconSize * 0.4);
      } else if (this.order === "strawberries") {
        fill(255, 0, 0); // Red for strawberry
        ellipse(this.x, iconY, iconSize * 0.5);
        fill(0, 150, 0); // Green for leaf
        triangle(this.x - iconSize * 0.1, iconY - iconSize * 0.2,
                 this.x + iconSize * 0.1, iconY - iconSize * 0.2,
                 this.x, iconY - iconSize * 0.3);
      } else if (this.order === "steak") {
        fill(120, 50, 30); // Dark brown for steak
        beginShape();
        vertex(this.x - iconSize * 0.3, iconY - iconSize * 0.2);
        vertex(this.x + iconSize * 0.3, iconY - iconSize * 0.1);
        vertex(this.x + iconSize * 0.2, iconY + iconSize * 0.2);
        vertex(this.x - iconSize * 0.2, iconY + iconSize * 0.1);
        endShape(CLOSE);
      } else if (this.order === "meat") {
        fill(150, 80, 60); // General meat color
        ellipse(this.x, iconY, iconSize * 0.7, iconSize * 0.4);
      } else if (this.order === "meatballs") {
        fill(100, 50, 20); // Meatball color
        ellipse(this.x - iconSize * 0.15, iconY - iconSize * 0.15, iconSize * 0.25);
        ellipse(this.x + iconSize * 0.15, iconY - iconSize * 0.05, iconSize * 0.25);
        ellipse(this.x - iconSize * 0.05, iconY + iconSize * 0.15, iconSize * 0.25);
      } else if (this.order === "rice") {
        fill(255); // White for rice
        let grainSize = iconSize * 0.15;
        ellipse(this.x - grainSize * 2, iconY - grainSize, grainSize * 1.5, grainSize);
        ellipse(this.x, iconY - grainSize * 0.5, grainSize * 1.5, grainSize);
        ellipse(this.x + grainSize * 2, iconY - grainSize * 0.2, grainSize * 1.5, grainSize);
      } else if (this.order === "cheese") {
        fill(255, 220, 0); // Yellow for cheese
        triangle(this.x - iconSize * 0.3, iconY,
                 this.x + iconSize * 0.3, iconY,
                 this.x, iconY - iconSize * 0.4);
        fill(200, 180, 0); // Holes
        ellipse(this.x - iconSize * 0.1, iconY - iconSize * 0.1, iconSize * 0.1);
        ellipse(this.x + iconSize * 0.15, iconY - iconSize * 0.2, iconSize * 0.08);
      } else if (this.order === "chips") {
        fill(255, 200, 0); // Yellow for chips
        rectMode(CENTER);
        rect(this.x - iconSize * 0.1, iconY, iconSize * 0.15, iconSize * 0.4);
        rect(this.x + iconSize * 0.1, iconY, iconSize * 0.15, iconSize * 0.4);
        rect(this.x, iconY + iconSize * 0.1, iconSize * 0.15, iconSize * 0.4);
        rectMode(CORNER);
      } else if (this.order === "macAndCheese") {
        fill(255, 220, 100); // Yellow for Mac and Cheese
        rectMode(CENTER);
        rect(this.x, iconY, iconSize * 0.7, iconSize * 0.4, 5);
        fill(200, 150, 0); // Some sauce/noodle bits
        ellipse(this.x - iconSize * 0.2, iconY - iconSize * 0.1, iconSize * 0.1);
        ellipse(this.x + iconSize * 0.15, iconY + iconSize * 0.1, iconSize * 0.1);
        rectMode(CORNER);
      }
    }

    // Display current message (move it slightly lower to avoid overlapping with food icon)
    if (this.message) {
      fill(255, 255, 255, 200); // Semi-transparent white background for message
      stroke(0);
      rect(this.x - 50, this.y + this.size / 2 + 10, 100, 30, 5); // Shifted down by 10
      fill(0);
      noStroke();
      textSize(10);
      text(this.message, this.x, this.y + this.size / 2 + 10 + 15); // Shifted down by 10
    }

    // Display UFO if this person is leaving by UFO
    if (this.ufo) {
      this.ufo.display();
    }
    
    // Display gun if armed
    if (this.hasGun) {
      fill(50); // Dark grey for gun
      noStroke();
      rectMode(CENTER);
      rect(this.x + this.size / 2, this.y, this.size * 0.7, this.size * 0.2); // Gun barrel
      rect(this.x + this.size / 2 + this.size * 0.3, this.y, this.size * 0.1, this.size * 0.3); // Gun stock
      rectMode(CORNER); // Reset rectMode
    }
  }
Line-by-line explanation (13 lines)

🔧 Subcomponents:

shape-drawing Person Body ellipse(this.x, this.y, this.size);

Draws the person as a filled circle in their assigned color

text-drawing Money Display text(`$${this.money}`, this.x, this.y - this.size);

conditional Food Icon Rendering if (this.order) {

Draws a visual representation of the ordered food item (pizza, burger, ice cream, etc.) above the person

animation Eating Animation if (this.state === "eating" && this.timer > millis() && this.eatingDuration > 0) {

Shrinks the food icon as the person eats, creating a visual consumption effect

text-drawing Message Display if (this.message) {

Draws a semi-transparent chat bubble with the person's current state message

shape-drawing Gun Display if (this.hasGun) {

Draws a simple gun shape next to the person if they're armed and dangerous

fill(this.color);
Sets the fill color to the person's unique color (assigned in the constructor)
ellipse(this.x, this.y, this.size);
Draws a circle at the person's position with diameter = this.size (30 pixels)
text(`$${this.money}`, this.x, this.y - this.size);
Displays the person's remaining money as text above their head using a template literal
if (this.order) {
Only draws food if this.order is not null (the person has ordered something)
let iconSize = this.size * 0.6;
Sets the food icon's size to 60% of the person's size (scales with person)
if (this.state === "eating" && this.timer > millis() && this.eatingDuration > 0) {
Checks if the person is eating AND the timer hasn't expired yet AND eating duration is positive
let eatingProgress = map(remainingTime, 0, this.eatingDuration, 0, 1);
Maps the remaining eating time to a 0–1 scale: 1 at the start (most time left), 0 when done eating
let iconScale = map(eatingProgress, 0, 1, 0.1, 1.0);
Converts the eating progress to a size scale: 0.1 (tiny, almost done) to 1.0 (full size, just started)
if (this.order === "pizza") {
Checks if the ordered food is pizza, then draws a custom pizza icon (yellow circle with red pepperoni)
if (this.message) {
Only displays a message bubble if this.message is not empty
rect(this.x - 50, this.y + this.size / 2 + 10, 100, 30, 5);
Draws a semi-transparent white rectangle as the message background, positioned below the person's head
if (this.hasGun) {
Only displays a gun if the person is armed (hasGun is true)
rect(this.x + this.size / 2, this.y, this.size * 0.7, this.size * 0.2);
Draws a small dark grey rectangle as the gun barrel, positioned to the right of the person

Person.update()

This update() method is the heart of the Person class—it's a comprehensive state machine with nine cases. Each case represents one phase of the customer journey. The key insight is that each case handles movement (using atan2 to calculate direction), state transitions (moving to the next case when conditions are met), and timing (using timers to delay actions). The 'armedAndDangerous' state and shooting mechanic add emergent drama—a normally peaceful simulation can erupt into chaos if coins aren't distributed fairly.

🔬 This code filters potential targets to exclude the shooter, people leaving, and other armed people. What happens if you remove the second part of the filter (p.state !== "leaving") so shooters CAN target people already leaving? Will escaping customers be caught?

          let potentialTargets = people.filter(p =>
            p !== this && p.state !== "leaving" && p.state !== "armedAndDangerous" // Removed gettingKickedOut as it's not used in this flow
          );

          if (potentialTargets.length > 0) {
            let targetPerson = random(potentialTargets);
            this.message = `Shot ${targetPerson.ordererName}! Leaving...`;
            this.state = "leaving"; // Shooter leaves after shooting
            this.hasGun = false; // No longer armed
            this.wasShooter = true; // Mark as shooter for draw() loop
            // Return an object to signal the shooting event
            return { action: "shoot", shooter: this, target: targetPerson };
  update() {
    this.message = ""; // Clear message each frame

    // If UFO is active for this person, update it regardless of Person state
    if (this.ufo) {
      this.ufo.update();
    }

    switch (this.state) {
      case "idle":
        // Do nothing until tapped
        break;

      case "goingToRestaurant":
        this.message = "To restaurant!";
        // Move towards the ordering zone
        if (dist(this.x, this.y, this.targetX, this.targetY) < this.speed) {
          this.x = this.targetX;
          this.y = this.targetY;
          this.state = "ordering";
          this.message = "Ordering...";
          this.orderFood(); // Place order once arrived
          this.timer = millis() + random(1000, 3000); // Simulate ordering delay
        } else {
          let angle = atan2(this.targetY - this.y, this.targetX - this.x);
          this.x += cos(angle) * this.speed;
          this.y += sin(angle) * this.speed;
        }
        break;

      case "ordering":
        this.message = `Ordering ${this.order}...`;
        if (millis() > this.timer) { // Ordering delay finished
          // Find the cost of the ordered item
          let orderedItem = allFoodItems.find(item => item.name === this.order);
          let itemCost = orderedItem ? orderedItem.cost : pizzaCost; // Default to pizza cost if not found

          // Check if they can afford the item (only the burger is expensive enough to trigger this)
          if (this.order === "burger" && this.money < itemCost) { // Only burger is expensive enough to check this
            this.message = "I can't afford this! >:(";
            this.state = "armedAndDangerous"; // Transition to new armed state immediately
            this.hasGun = true; // They got a gun!
            this.shootTimer = millis() + this.shootDelay; // Start timer for shooting
            this.order = null; // Clear order, they don't get the food
            break; // Don't proceed to goToTable
          }
          // Deduct money for the order
          this.money -= itemCost;
          this.message = `Got ${this.order}!`;
          this.goToTable(); // Attempt to go to a table (might transition to waiting)
        }
        break;

      case "waitingForTable":
        this.message = "Waiting for table...";
        // Periodically check for a table
        if (millis() > this.timer + 2000) { // Check every 2 seconds
          this.timer = millis(); // Reset check timer
          let availableTable = tables.find(t => t.currentOccupants < t.capacity); // Find table with space
          if (availableTable) {
            this.table = availableTable;
            this.table.occupy(); // Occupy a spot
            // Determine position at the table based on current occupants
            let offset = 0;
            if (this.table.currentOccupants === 1) { // First person (after occupying), sit slightly left
              offset = -this.table.w * 0.15; // Small offset left
            } else if (this.table.currentOccupants === 2) { // Second person, sit slightly right
              offset = this.table.w * 0.15; // Small offset right
            }
            this.targetX = availableTable.x + availableTable.w / 2 + offset;
            this.targetY = availableTable.y + availableTable.h / 2;
            this.speed = random(1, 2);
            this.state = "goingToTable";
            this.message = `Table found! To table with ${this.order}!`;
          }
        }
        break;

      case "goingToTable":
        this.message = `To table with ${this.order}!`;
        // Move towards their assigned table
        if (this.table && dist(this.x, this.y, this.targetX, this.targetY) < this.speed) {
          this.x = this.targetX;
          this.y = this.targetY;
          this.state = "eating";
          this.eatingDuration = random(5000, 10000); // Store the duration for animation
          this.timer = millis() + this.eatingDuration; // Eat for 5-10 seconds
          this.message = `Eating ${this.order}...`;
        } else if (!this.table) {
          // This case should ideally not be reached if goToTable intelligently handles no tables
          // If it does, it means a table was freed and then taken before they got to it.
          // For now, if no table is found here, they go back to waiting.
          this.message = "Lost table! Waiting again...";
          this.state = "waitingForTable";
          this.timer = millis();
        } else {
          let angle = atan2(this.targetY - this.y, this.targetX - this.x);
          this.x += cos(angle) * this.speed;
          this.y += sin(angle) * this.speed;
        }
        break;

      case "eating":
        this.message = `Eating ${this.order}!`;
        if (millis() > this.timer) { // Eating delay finished
          this.message = "Finished eating!";
          this.order = null; // Reset order after eating
          this.state = "paying";
          // Target the paying zone
          this.targetX = restaurant.payingZone.x + restaurant.payingZone.w / 2;
          this.targetY = restaurant.payingZone.y + restaurant.payingZone.h / 2;
          this.timer = millis() + random(1000, 3000); // Simulate paying delay
          if (this.table) {
            this.table.free(); // Free up a spot at the table
            this.table = null;
          }
        }
        break;

      case "paying":
        this.message = "Paying...";
        // Move towards the paying zone
        if (dist(this.x, this.y, this.targetX, this.targetY) < this.speed) {
          this.x = this.targetX;
          this.y = this.targetY;
          if (millis() > this.timer) { // Paying delay finished
            this.message = "Paid! Leaving...";
            this.state = "leaving";
            // Decide leaving type: 20% chance for UFO
            if (random(1) < 0.2) {
              this.leavingType = "ufo";
              this.ufo = new UFO(this.x, this.y); // Create UFO for this person
              this.ufoInitialPersonY = this.y; // Store current Y for beaming animation
            } else {
              this.leavingType = "walk";
              this.targetX = -this.size; // Move off-screen left
              this.targetY = random(height);
              this.speed = 3;
            }
          }
        } else {
          let angle = atan2(this.targetY - this.y, this.targetX - this.x);
          this.x += cos(angle) * this.speed;
          this.y += sin(angle) * this.speed;
        }
        break;

      case "armedAndDangerous": // NEW STATE
        this.message = "Revenge! >:(";
        if (millis() > this.shootTimer) { // Time to shoot!
          // Pick a target (another person, not self, not already leaving or armed)
          let potentialTargets = people.filter(p =>
            p !== this && p.state !== "leaving" && p.state !== "armedAndDangerous" // Removed gettingKickedOut as it's not used in this flow
          );

          if (potentialTargets.length > 0) {
            let targetPerson = random(potentialTargets);
            this.message = `Shot ${targetPerson.ordererName}! Leaving...`;
            this.state = "leaving"; // Shooter leaves after shooting
            this.hasGun = false; // No longer armed
            this.wasShooter = true; // Mark as shooter for draw() loop
            // Return an object to signal the shooting event
            return { action: "shoot", shooter: this, target: targetPerson };
          } else {
            // No target found, just leave
            this.message = "No target found. Leaving...";
            this.state = "leaving";
            this.hasGun = false;
            this.wasShooter = true; // Mark as shooter even if no shot fired
            return "remove"; // Shooter leaves
          }
        }
        break;

      case "leaving":
        this.message = "Leaving...";

        if (this.leavingType === "ufo") {
          // Person shrinks and moves up based on UFO animation progress
          if (this.ufo.isBeaming()) {
            let beamProgress = this.ufo.getBeamProgress();
            this.size = map(beamProgress, 0, 1, 30, 5, true); // Shrink person
            // Move person from their initial Y up to the bottom of the UFO's body
            this.y = map(beamProgress, 0, 1, this.ufoInitialPersonY, this.ufo.currentY + this.ufo.size / 2, true); // Move up
          }

          // When UFO signals it's done, remove the person
          if (this.ufo.state === "leaving" && this.ufo.currentY < -this.ufo.size) {
            this.ufo = null; // Clear UFO reference
            this.ufoInitialPersonY = null; // Clear initial Y
            return "remove"; // Remove the person
          }
        } else { // Default walking leaving
          // Move off-screen
          if (this.x < -this.size) {
            return "remove"; // Signal to remove this person from the array
          }
          let angle = atan2(this.targetY - this.y, this.targetX - this.x);
          this.x += cos(angle) * this.speed;
          this.y += sin(angle) * this.speed;
        }
        break;
    }
    return null; // Don't remove or shoot by default
  }
Line-by-line explanation (15 lines)

🔧 Subcomponents:

switch-case State Machine Switch switch (this.state) {

Routes the person's behavior based on their current state (idle, ordering, eating, etc.)

calculation Movement Logic (repeated in multiple states) let angle = atan2(this.targetY - this.y, this.targetX - this.x);

Calculates the angle from the person to their target using atan2(), then moves toward it using cos() and sin()

conditional Armed and Dangerous State case "armedAndDangerous"::

Handles the new mechanic where customers who can't afford food become armed and shoot

animation UFO Beaming Animation if (this.ufo.isBeaming()) {

Syncs the person's position and size with the UFO's beam animation as they're abducted

this.message = "";
Clears the message each frame so old messages don't persist—only current-state messages are displayed
if (this.ufo) { this.ufo.update(); }
If the person has a UFO attached, updates it regardless of the person's state so the beam continues animating
switch (this.state) {
Routes all logic based on the person's current state; each case handles one phase of the dining journey
case "idle": break;
In idle state, the person does nothing—they're waiting to be tapped by the player
if (dist(this.x, this.y, this.targetX, this.targetY) < this.speed) {
Checks if the person is close enough to their target (distance less than one frame's movement)—if yes, snap to target and advance state
let angle = atan2(this.targetY - this.y, this.targetX - this.x);
atan2() calculates the angle from the person's current position to the target; atan2 accounts for all four quadrants
this.x += cos(angle) * this.speed;
Uses trigonometry to move the person incrementally: cos(angle) gives the x-component of direction, multiplied by speed
if (this.order === "burger" && this.money < itemCost) {
Checks if the person is ordering a burger AND can't afford it—triggers the armed-and-dangerous state
this.hasGun = true;
Marks the person as armed; the display() method will now draw a gun next to them
this.shootTimer = millis() + this.shootDelay;
Sets a future timestamp when shooting will occur (1 second delay gives time for dramatic effect)
let potentialTargets = people.filter(p => p !== this && p.state !== "leaving" && p.state !== "armedAndDangerous");
Filters the people array to find valid targets: everyone except self, people already leaving, and other armed people
return { action: "shoot", shooter: this, target: targetPerson };
Returns an object (not null) to signal the draw() loop that a shooting incident occurred
if (this.ufo.isBeaming()) {
Checks if the UFO is actively beaming; if yes, animates the person shrinking and rising toward the UFO
this.size = map(beamProgress, 0, 1, 30, 5, true);
Uses map() to shrink the person from size 30 to 5 as they're beamed up (beamProgress goes 0 to 1)
return null;
Default return value; means no removal or shooting event—the person continues their normal state

Person.tap()

This simple method is the bridge between player interaction (touchStarted) and person behavior. It only activates idle customers—you can't tap someone already at the restaurant or eating. This is good UX design: it prevents confusing double-activations.

  tap() {
    if (this.state === "idle") {
      this.goToRestaurant();
    }
  }
Line-by-line explanation (2 lines)
if (this.state === "idle") {
Checks if the person is idle (not already activated or in progress)
this.goToRestaurant();
If idle, calls goToRestaurant() to set their state and target position, waking them up

Person.goToRestaurant()

This method encapsulates the setup for beginning the restaurant journey. By isolating this logic in a separate method, you can easily reuse it or modify it without cluttering update().

  goToRestaurant() {
    this.state = "goingToRestaurant";
    this.targetX = restaurant.orderingZone.x + restaurant.orderingZone.w / 2;
    this.targetY = restaurant.orderingZone.y + restaurant.orderingZone.h / 2;
    this.speed = random(1.5, 3);
  }
Line-by-line explanation (3 lines)
this.state = "goingToRestaurant";
Transitions the person from idle to goingToRestaurant state
this.targetX = restaurant.orderingZone.x + restaurant.orderingZone.w / 2;
Sets the target x to the center of the ordering zone
this.speed = random(1.5, 3);
Assigns a random speed between 1.5 and 3 pixels per frame so customers walk at different paces

Person.orderFood()

This method shows the flexibility of the system: customers can either order randomly from the menu or be pre-configured to order a specific item. The .find() method is useful here—it scans allFoodItems for a match rather than assuming a food exists.

  orderFood() {
    let chosenFood;
    if (this.specificOrderName) {
      chosenFood = allFoodItems.find(item => item.name === this.specificOrderName);
      // Clear specific order after placing it
      this.specificOrderName = null;
    } else {
      chosenFood = random(currentMenuOptions);
    }

    if (chosenFood) {
      this.order = chosenFood.name;
      this.message = `Ordered ${this.order}!`;
    } else {
      // Fallback if the chosen food somehow doesn't exist
      this.order = "pizza";
      this.message = "Ordered pizza (fallback)!";
    }
  }
Line-by-line explanation (4 lines)
if (this.specificOrderName) {
Checks if the person was created with a pre-set order (e.g., from tapping a menu item)
chosenFood = allFoodItems.find(item => item.name === this.specificOrderName);
Uses .find() to locate the specific food object in allFoodItems by matching the name
chosenFood = random(currentMenuOptions);
If no specific order, randomly picks from the current menu options
this.order = chosenFood.name;
Stores the chosen food's name in this.order (used for display, cost calculation, and animations)

Person.goToTable()

This method demonstrates intelligent queue management: if no table is available, the person waits instead of getting stuck. The offset calculation is clever—it positions customers at a table so they don't overlap visually.

  goToTable() {
    let availableTable = tables.find(t => t.currentOccupants < t.capacity); // Find a table with space
    if (availableTable) {
      this.table = availableTable;
      this.table.occupy(); // Occupy a spot at the table
      // Determine position at the table based on current occupants
      let offset = 0;
      if (this.table.currentOccupants === 1) { // First person, sit slightly left
        offset = -this.table.w * 0.15; // Small offset left
      } else if (this.table.currentOccupants === 2) { // Second person, sit slightly right
        offset = this.table.w * 0.15; // Small offset right
      }

      this.targetX = this.table.x + this.table.w / 2 + offset; // Adjust target X
      this.targetY = this.table.y + this.table.h / 2; // Keep Y centered vertically
      this.speed = random(1, 2);
      this.state = "goingToTable"; // Transition directly to goingToTable
    } else {
      // If no tables are available (all are at capacity), transition to waiting
      this.table = null; // Ensure table is null
      this.message = "Waiting for table...";
      this.state = "waitingForTable";
      this.timer = millis(); // Reset timer for waiting checks
      // The person stays at their current position (the ordering zone)
    }
  }
Line-by-line explanation (5 lines)
let availableTable = tables.find(t => t.currentOccupants < t.capacity);
Searches the tables array for the first table with space available (occupants < capacity)
this.table.occupy();
Increments the table's currentOccupants counter so the next person doesn't take the same seat
if (this.table.currentOccupants === 1) { offset = -this.table.w * 0.15; }
If this is the first person at the table, positions them slightly left (negative x offset)
if (this.table.currentOccupants === 2) { offset = this.table.w * 0.15; }
If this is the second person, positions them slightly right so both are visible without overlapping
this.state = "waitingForTable";
If no table is available, transitions to waitingForTable state instead, staying in the ordering zone

UFO Class

The UFO class encapsulates all the logic for alien abduction animation. Notice the state progression: appearing (descending), beaming (staying still and pulling up the person), and leaving (flying away). This separation of concerns makes the code maintainable.

class UFO {
  constructor(x, y) {
    this.x = x;
    this.y = y;
    this.size = 100; // Size of the UFO body
    this.color = color(150, 200, 255, 200); // Light blue with transparency
    this.beamColor = color(150, 255, 200, 150); // Greenish beam with transparency
    this.beamActive = false;
    this.beamTimer = 0;
    this.beamDuration = 1500; // Time for beaming up the person
    this.state = "appearing"; // "appearing", "beaming", "leaving"
    this.targetY = y - this.size / 2; // UFO body above the person
    this.initialY = this.targetY - this.size; // Start higher
    this.currentY = this.initialY;
    this.speed = 3; // UFO vertical speed
  }
Line-by-line explanation (5 lines)
this.size = 100;
Sets the UFO body width/height to 100 pixels
this.color = color(150, 200, 255, 200);
Light blue with transparency (200 is alpha/opacity); more transparent than opaque
this.state = "appearing";
UFO starts in 'appearing' state, meaning it's descending from above
this.targetY = y - this.size / 2;
UFO's target resting position is directly above the person (y minus half UFO size)
this.initialY = this.targetY - this.size;
UFO starts even higher, offscreen; initialY is one UFO size above the target

UFO.update()

The UFO's state machine is a mini-version of the Person's—three states, smooth transitions, and timing-based logic. Notice how beamTimer captures the current time, then the elapsed time is checked on every frame. This is a clean pattern for frame-independent animations.

🔬 This code makes the UFO fly away at 2x speed. What happens if you change the multiplier from 2 to 0.5 so it flies away slower than it descended? Or to 5 for a super-fast escape?

      case "leaving":
        this.currentY -= this.speed * 2; // Fly away faster
        // The Person class will handle removing itself when the UFO is far enough
        break;
  update() {
    switch (this.state) {
      case "appearing":
        if (this.currentY < this.targetY) {
          this.currentY += this.speed;
        } else {
          this.currentY = this.targetY;
          this.beamActive = true;
          this.beamTimer = millis();
          this.state = "beaming";
        }
        break;
      case "beaming":
        if (millis() - this.beamTimer > this.beamDuration) {
          this.beamActive = false;
          this.state = "leaving";
        }
        break;
      case "leaving":
        this.currentY -= this.speed * 2; // Fly away faster
        // The Person class will handle removing itself when the UFO is far enough
        break;
    }
  }
Line-by-line explanation (6 lines)

🔧 Subcomponents:

conditional Appearing State if (this.currentY < this.targetY) {

Descends the UFO until it reaches the target position above the person

conditional Beaming State if (millis() - this.beamTimer > this.beamDuration) {

Keeps the UFO stationary while beaming the person up; transitions to leaving after beam duration

animation Leaving State this.currentY -= this.speed * 2;

Flies the UFO upward faster than it descended, disappearing off-screen

switch (this.state) {
Routes UFO behavior based on its current state (appearing, beaming, or leaving)
if (this.currentY < this.targetY) { this.currentY += this.speed; }
If UFO hasn't reached its target position, move it down by the speed amount (positive y = down)
this.beamActive = true;
Activates the beam so display() will draw the green beam triangle
this.beamTimer = millis();
Records the current timestamp so we can measure how long beaming has been active
if (millis() - this.beamTimer > this.beamDuration) {
Checks if enough time has passed (1500ms); if yes, beam is done and UFO leaves
this.currentY -= this.speed * 2;
UFO moves upward (negative y) at twice its descent speed, creating a snappy exit

UFO.display()

The display() method builds the UFO from multiple shapes: a wide ellipse for the body, a smaller ellipse for the cockpit, and tiny ellipses for lights. The beam is a triangle that connects the UFO to the person's ground position. Notice the use of this.currentY (animated position) for the UFO body but this.y (static position) for the beam anchor—this keeps the beam connected to the person as they're pulled upward.

  display() {
    // Draw beam if active
    if (this.beamActive) {
      fill(this.beamColor);
      noStroke();
      triangle(
        this.x - this.size / 4, this.currentY + this.size / 2,
        this.x + this.size / 4, this.currentY + this.size / 2,
        this.x, this.y + this.size / 2 // Beam extends to person's original Y (ground level)
      );
    }

    // Draw UFO body
    fill(this.color);
    noStroke();
    ellipse(this.x, this.currentY, this.size, this.size / 2); // Main body
    fill(100, 150, 200, 200); // Cockpit
    ellipse(this.x, this.currentY - this.size / 8, this.size / 2, this.size / 4);
    fill(255, 255, 255, 150); // Lights
    ellipse(this.x - this.size * 0.3, this.currentY + this.size * 0.1, this.size * 0.1);
    ellipse(this.x + this.size * 0.3, this.currentY + this.size * 0.1, this.size * 0.1);
  }
Line-by-line explanation (6 lines)

🔧 Subcomponents:

shape-drawing Beam Drawing triangle(

Draws a green triangle from the UFO down to the person, creating the abduction beam effect

shape-drawing UFO Body ellipse(this.x, this.currentY, this.size, this.size / 2);

Draws a light blue ellipse as the main UFO body (wider than it is tall, like a saucer)

shape-drawing Cockpit Window ellipse(this.x, this.currentY - this.size / 8, this.size / 2, this.size / 4);

Draws a darker blue ellipse on top of the body as the UFO's cockpit/window

shape-drawing UFO Lights ellipse(this.x - this.size * 0.3, this.currentY + this.size * 0.1, this.size * 0.1);

Draws two small white circles as glowing lights on the UFO body

if (this.beamActive) {
Only draws the beam if it's currently active (during beaming state)
fill(this.beamColor);
Sets fill to the greenish beam color with transparency
triangle(
Draws a triangle (the beam) with top points at the UFO's bottom and bottom point at the person's original position
this.x, this.y + this.size / 2
The beam's bottom point is at this.y (the person's original Y), slightly below—uses this.y, not this.currentY, to keep beam anchored to person
ellipse(this.x, this.currentY, this.size, this.size / 2);
Draws the UFO body as an ellipse: 100 pixels wide, 50 pixels tall (because size/2), creating a flying-saucer shape
ellipse(this.x, this.currentY - this.size / 8, this.size / 2, this.size / 4);
Draws the cockpit on top of the UFO: 50 pixels wide, 25 pixels tall, positioned slightly above the center

UFO.isBeaming()

Simple getter method that encapsulates the beamActive property. This is good OOP practice: external code checks isBeaming() rather than directly accessing the private beamActive variable.

  isBeaming() {
    return this.beamActive;
  }
Line-by-line explanation (1 lines)
return this.beamActive;
Returns true if the beam is currently active, false otherwise—used by Person.display() to know whether to animate eating or being beamed

UFO.getBeamProgress()

This getter returns a normalized 0–1 progress value that Person.display() uses to animate shrinking and rising. The 'true' parameter in map() is constrain() built-in, which prevents values from exceeding the range—important for smooth, reliable animations.

  getBeamProgress() {
    if (this.beamActive) {
      return map(millis() - this.beamTimer, 0, this.beamDuration, 0, 1, true); // Progress 0-1
    }
    return 0;
  }
Line-by-line explanation (2 lines)
return map(millis() - this.beamTimer, 0, this.beamDuration, 0, 1, true);
Maps elapsed time (millis() - beamTimer) from 0 to beamDuration milliseconds onto a 0–1 scale; the 'true' at the end constrains the value to stay within 0–1
return 0;
If beaming is not active, returns 0 (no progress), so no animation occurs

Bouncer Class

The Bouncer class has a state machine like the Person, but it's largely unused in the current 'armed and dangerous' mechanic. The Bouncer sits idle, maintaining order. In future versions, you could activate the bouncer to physically remove armed people or enforce other rules.

class Bouncer {
  constructor(x, y) {
    this.x = x;
    this.y = y;
    this.size = 40; // Slightly larger than a regular person
    this.color = color(50, 50, 150); // Dark blue for bouncer
    this.state = "idle"; // "idle", "goingToPerson", "kickingOut", "returningToPost"
    this.targetX = x;
    this.targetY = y;
    this.speed = 4; // Faster than people
    this.personBeingKickedOut = null; // Reference to the person being handled
    this.message = "";
  }
Line-by-line explanation (3 lines)
this.color = color(50, 50, 150);
Dark blue color distinguishes the bouncer from regular customers
this.speed = 4;
Bouncer moves faster (4 px/frame) than regular people (1.5–3 px/frame) to catch problems quickly
this.state = "idle";
Bouncer starts idle, positioned near the ordering zone; states are unused in current code

Bouncer.display()

The Bouncer's display is similar to Person's: a colored circle with a label and optional message box. This consistency in UI design makes the sketch feel cohesive.

  display() {
    fill(this.color);
    noStroke();
    ellipse(this.x, this.y, this.size); // Bouncer body
    fill(0);
    textAlign(CENTER, CENTER);
    textSize(10);
    text("BOUNCER", this.x, this.y - this.size);
    if (this.message) {
      fill(255, 255, 255, 200);
      stroke(0);
      rect(this.x - 50, this.y + this.size / 2 + 10, 100, 30, 5);
      fill(0);
      noStroke();
      textSize(10);
      text(this.message, this.x, this.y + this.size / 2 + 10 + 15);
    }
  }
Line-by-line explanation (2 lines)
text("BOUNCER", this.x, this.y - this.size);
Displays a label above the bouncer so players can identify them
if (this.message) {
Displays a message box if the bouncer has something to say (currently unused, but available for future functionality)

Bouncer.update()

The Bouncer's update() is effectively a stub—all states are empty. The bouncer is included in the sketch as a foundation for future features. A full implementation could have the bouncer patrol, detect armed people, and escort them out.

  update() {
    this.message = ""; // Clear message each frame

    switch (this.state) {
      case "idle":
        // The bouncer no longer kicks out 0-money people ordering burgers in this revised flow.
        // This state now just waits for other potential bouncer actions (if added later).
        break;

      case "goingToPerson":
        // This state is now effectively unused for the 0-money burger scenario.
        break;

      case "kickingOut":
        // This state is now effectively unused for the 0-money burger scenario.
        break;

      case "returningToPost":
        // This state is now effectively unused for the 0-money burger scenario.
        break;
    }
  }
Line-by-line explanation (2 lines)
this.message = "";
Clears the message each frame, just like Person.update()
switch (this.state) {
Routes behavior based on state, but all cases are currently empty (bouncer is idle)

Restaurant Class

The Restaurant constructor uses proportional positioning (percentages like 0.1, 0.7) so zones scale with the restaurant size. This makes the layout responsive and maintainable.

class Restaurant {
  constructor(x, y, w, h) {
    this.x = x;
    this.y = y;
    this.w = w;
    this.h = h;
    // Define an ordering zone inside the restaurant
    this.orderingZone = {
      x: x + w * 0.1,
      y: y + h * 0.7,
      w: w * 0.2,
      h: h * 0.2
    };
    // Define a paying zone (different from ordering)
    this.payingZone = {
      x: x + w * 0.7,
      y: y + h * 0.7,
      w: w * 0.2,
      h: h * 0.2
    };
  }
Line-by-line explanation (2 lines)
this.orderingZone = { x: x + w * 0.1, y: y + h * 0.7, w: w * 0.2, h: h * 0.2 };
Defines the ordering zone as a 20% × 20% rectangle 10% from the left and 70% down the restaurant
this.payingZone = { x: x + w * 0.7, y: y + h * 0.7, w: w * 0.2, h: h * 0.2 };
Defines the paying zone at 70% from the left and 70% down—opposite corner from ordering zone

Restaurant.display()

The Restaurant's display method uses the previously defined zones to draw distinct regions. By calculating positions as proportions (e.g., orderingZone.x + orderingZone.w / 2), the display scales responsively if the restaurant is resized.

  display() {
    // Restaurant exterior
    fill(200, 150, 100); // Brownish color
    rect(this.x, this.y, this.w, this.h);

    // Ordering zone
    fill(180, 130, 80); // Slightly darker brown
    rect(this.orderingZone.x, this.orderingZone.y, this.orderingZone.w, this.orderingZone.h);
    fill(0);
    textAlign(CENTER, TOP);
    textSize(12);
    text("ORDER HERE", this.orderingZone.x + this.orderingZone.w / 2, this.orderingZone.y + 5);

    // Paying zone
    fill(180, 130, 80);
    rect(this.payingZone.x, this.payingZone.y, this.payingZone.w, this.payingZone.h);
    fill(0);
    textAlign(CENTER, TOP);
    textSize(12);
    text("PAY HERE", this.payingZone.x + this.payingZone.w / 2, this.payingZone.y + 5);

    // Door (entry/exit point)
    fill(100); // Grey door
    rect(this.x + this.w * 0.45, this.y + this.h - 10, this.w * 0.1, 10);
  }
Line-by-line explanation (5 lines)

🔧 Subcomponents:

shape-drawing Restaurant Exterior rect(this.x, this.y, this.w, this.h);

Draws the main restaurant building as a brown rectangle

text-drawing Zone Labels text("ORDER HERE", ...);

Labels the ordering and paying zones so players understand where customers go

shape-drawing Door rect(this.x + this.w * 0.45, this.y + this.h - 10, this.w * 0.1, 10);

Draws a small grey door at the bottom of the restaurant for visual reference

fill(200, 150, 100);
Brownish color for the restaurant exterior
rect(this.x, this.y, this.w, this.h);
Draws the main restaurant building using its position and dimensions
rect(this.orderingZone.x, this.orderingZone.y, this.orderingZone.w, this.orderingZone.h);
Draws the ordering zone as a slightly darker rectangle to distinguish it from the rest of the restaurant
text("ORDER HERE", this.orderingZone.x + this.orderingZone.w / 2, this.orderingZone.y + 5);
Draws a label in the center-top of the ordering zone so players know its purpose
rect(this.x + this.w * 0.45, this.y + this.h - 10, this.w * 0.1, 10);
Draws a small door 45% from the left at the bottom of the restaurant (mostly decorative)

Table Class

The Table class is minimal but essential: it tracks position, dimensions, capacity, and occupancy. This data-driven approach makes it easy to add more tables or change capacity.

class Table {
  constructor(x, y, w, h) {
    this.x = x;
    this.y = y;
    this.w = w;
    this.h = h;
    this.capacity = 2; // Each table can hold 2 people
    this.currentOccupants = 0;
  }
Line-by-line explanation (2 lines)
this.capacity = 2;
Each table seats a maximum of 2 customers
this.currentOccupants = 0;
Tracks how many people are currently seated (0, 1, or 2)

Table.display()

A simple visual cue: the table's color changes to indicate whether it's occupied. This gives players instant feedback about table availability.

  display() {
    fill(this.currentOccupants > 0 ? 150 : 200, 100, 50); // Darker brown if occupied
    rect(this.x, this.y, this.w, this.h);
  }
Line-by-line explanation (1 lines)
fill(this.currentOccupants > 0 ? 150 : 200, 100, 50);
Uses a ternary operator to choose darker brown (150) if occupied, or lighter brown (200) if empty

Table.isInside()

Simple bounding-box collision detection: checks if a point (x, y) falls within the rectangle defined by the table. Useful for detecting taps on tables or other interactions.

  isInside(x, y) {
    return x > this.x && x < this.x + this.w &&
      y > this.y && y < this.y + this.h;
  }
Line-by-line explanation (2 lines)
return x > this.x && x < this.x + this.w &&
Checks if the point's x is within the table's horizontal bounds
y > this.y && y < this.y + this.h;
Checks if the point's y is within the table's vertical bounds

Table.occupy()

This method enforces the table's capacity constraint. By returning a boolean, callers can check success and adjust their logic accordingly.

  occupy() {
    if (this.currentOccupants < this.capacity) {
      this.currentOccupants++;
      return true;
    }
    return false; // Table is full
  }
Line-by-line explanation (3 lines)
if (this.currentOccupants < this.capacity) {
Checks if there's room at the table (current < capacity)
this.currentOccupants++;
Increments the occupant count by 1 to reserve a seat
return true;
Returns true if successfully occupied; false if table is full

Table.free()

Simple seat release: decrements occupancy when a customer finishes eating and leaves. The check prevents the count from going negative, maintaining data integrity.

  free() {
    if (this.currentOccupants > 0) {
      this.currentOccupants--;
    }
  }
Line-by-line explanation (2 lines)
if (this.currentOccupants > 0) {
Checks that the count won't go negative
this.currentOccupants--;
Decrements the occupant count by 1, freeing a seat

📦 Key Variables

allFoodItems array of objects

Master list of all food items ever available in the restaurant (pizza, burger, ice cream, etc.) with their costs; grows when the menu is expanded

let allFoodItems = [ { name: "pizza", cost: 10 }, { name: "burger", cost: 10000000 } ];
currentMenuOptions array of objects

The currently displayed menu options—a copy of allFoodItems that updates when the menu is expanded

let currentMenuOptions = [...allFoodItems];
people array of Person objects

Stores all active customers in the simulation; customers are added when spawned and removed when they leave or are shot

let people = [];
restaurant Restaurant object

The single Restaurant instance that defines the building's location, size, and interior zones (ordering and paying)

let restaurant;
tables array of Table objects

Stores the three dining tables; each tracks its capacity and current occupancy

let tables = [];
bouncer Bouncer object

The single Bouncer instance positioned near the ordering zone (currently idle but available for future extensions)

let bouncer;
menuArea object with x, y, w, h properties

Defines the on-screen rectangle where the menu is drawn (top-right corner)

let menuArea = { x, y, w, h };
addFoodButton p5.js button element

The interactive button that calls addNewFoodToMenu() when clicked to expand the menu

let addFoodButton;
initialMoney number

The amount of money each wealthy customer starts with (100,000,000); used to detect when broke customers can't afford the burger

let initialMoney = 100000000;
pizzaCost number

Cost of pizza (10); kept for clarity and as a fallback default if a food item isn't found

let pizzaCost = 10;
burgerCost number

Cost of the burger (10,000,000); kept for clarity and reference, though the cost is also stored in allFoodItems

let burgerCost = 10000000;

🔧 Potential Improvements (6)

Here are some ways this code could be enhanced:

BUG Person.update() in 'paying' state

If a paying customer never reaches the paying zone (dist check never triggers), they become stuck and never leave

💡 Add a maximum wait time in the paying state so customers eventually leave even if pathfinding fails: if (millis() > this.timer + 10000) { this.state = 'leaving'; }

PERFORMANCE draw() function

drawMenu() is called every frame and recalculates all menu text positions even though the menu doesn't change most of the time

💡 Use createGraphics() to draw the menu once to a buffer, then just display the buffer each frame. Only redraw the buffer when the menu actually changes.

STYLE Bouncer class

Bouncer.update() has four empty switch cases with placeholder comments, cluttering the code

💡 Remove the empty cases and the switch statement entirely until bouncer functionality is actually implemented. Keep the class simple until needed.

BUG Person.update() in 'waitingForTable' state

If all tables are freed while a person is moving to goingToTable, they lose their table reference but still try to move to targetX/targetY, creating confusion

💡 In goingToTable state, double-check that this.table still exists and has space before moving. If the table was freed by another person, revert to waitingForTable.

FEATURE Person class

Currently, people have infinite patience waiting for tables—no timeout mechanism

💡 Add a maxWaitTime property and transition to 'leaving' if waiting exceeds it. Creates more realistic customer behavior and prevents the queue from growing indefinitely.

PERFORMANCE touchStarted() function

Linear search through all people on every touch event can be slow if there are hundreds of customers

💡 Use spatial partitioning (divide canvas into grid cells) or r-tree to find nearby people faster, or simply limit the people array to ~100 max.

🔄 Code Flow

Code flow showing setup, draw, addnewfoodtomenu, drawmenu, touchstarted, windowresized, person, persondisplay, personupdate, persontap, gotorestaurant, orderfood, gototable, ufo, ufoupdate, ufodisplay, ufoisbeaming, ufogetbeamprogress, bouncer, bouncerdisplay, bouncierupdate, restaurant, restaurantdisplay, table, tabledisplay, tableisinside, tableoccupy, tablefree

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

graph TD start[Start] --> setup[setup] setup --> canvas-and-menu[canvas-and-menu] setup --> restaurant-creation[restaurant-creation] setup --> bouncer-setup[bouncer-setup] setup --> table-creation-loop[table-creation-loop] setup --> people-spawn-loop[people-spawn-loop] setup --> button-creation[button-creation] setup --> setup-reinit[setup-reinit] setup --> draw[draw loop] click setup href "#fn-setup" click canvas-and-menu href "#sub-canvas-and-menu" click restaurant-creation href "#sub-restaurant-creation" click bouncer-setup href "#sub-bouncer-setup" click table-creation-loop href "#sub-table-creation-loop" click people-spawn-loop href "#sub-people-spawn-loop" click button-creation href "#sub-button-creation" click setup-reinit href "#sub-setup-reinit" draw --> background-draw[background-draw] draw --> static-displays[static-displays] draw --> person-update-loop[person-update-loop] draw --> menu-update[menu-update] click draw href "#fn-draw" click background-draw href "#sub-background-draw" click static-displays href "#sub-static-displays" click person-update-loop href "#sub-person-update-loop" click menu-update href "#sub-menu-update" person-update-loop --> person-tap-detection[person-tap-detection] person-update-loop --> shoot-handler[shoot-handler] person-update-loop --> remove-handler[remove-handler] click person-tap-detection href "#sub-person-tap-detection" click shoot-handler href "#sub-shoot-handler" click remove-handler href "#sub-remove-handler" person-tap-detection --> person-spawn[person-spawn] person-tap-detection --> person-tap-detection-loop[person-tap-detection-loop] click person-spawn href "#sub-person-spawn" click person-tap-detection-loop href "#sub-person-tap-detection-loop" person-update-loop --> personupdate[personupdate] personupdate --> state-machine-switch[state-machine-switch] personupdate --> movement-logic[movement-logic] personupdate --> armed-and-dangerous-handler[armed-and-dangerous-handler] click personupdate href "#fn-personupdate" click state-machine-switch href "#sub-state-machine-switch" click movement-logic href "#sub-movement-logic" click armed-and-dangerous-handler href "#sub-armed-and-dangerous-handler" state-machine-switch --> basic-properties[basic-properties] state-machine-switch --> state-properties[state-properties] state-machine-switch --> gun-properties[gun-properties] click basic-properties href "#sub-basic-properties" click state-properties href "#sub-state-properties" click gun-properties href "#sub-gun-properties" personupdate --> persondisplay[persondisplay] persondisplay --> person-body[person-body] persondisplay --> money-display[money-display] persondisplay --> food-icon-rendering[food-icon-rendering] persondisplay --> eating-animation[eating-animation] persondisplay --> message-display[message-display] persondisplay --> gun-display[gun-display] click persondisplay href "#fn-persondisplay" click person-body href "#sub-person-body" click money-display href "#sub-money-display" click food-icon-rendering href "#sub-food-icon-rendering" click eating-animation href "#sub-eating-animation" click message-display href "#sub-message-display" click gun-display href "#sub-gun-display" draw --> addnewfoodtomenu[addnewfoodtomenu] addnewfoodtomenu --> new-food-array[new-food-array] addnewfoodtomenu --> duplicate-check-loop[duplicate-check-loop] addnewfoodtomenu --> button-feedback[button-feedback] click addnewfoodtomenu href "#fn-addnewfoodtomenu" click new-food-array href "#sub-new-food-array" click duplicate-check-loop href "#sub-duplicate-check-loop" click button-feedback href "#sub-button-feedback" draw --> drawmenu[drawmenu] drawmenu --> menu-background[menu-background] drawmenu --> menu-title[menu-title] drawmenu --> food-items-loop[food-items-loop] click drawmenu href "#fn-drawmenu" click menu-background href "#sub-menu-background" click menu-title href "#sub-menu-title" click food-items-loop href "#sub-food-items-loop" windowresized --> canvas-resize[canvas-resize] windowresized --> setup-reinit click windowresized href "#fn-windowresized" click canvas-resize href "#sub-canvas-resize" touchstarted --> touch-position-capture[touch-position-capture] touchstarted --> menu-tap-detection[menu-tap-detection] click touchstarted href "#fn-touchstarted" click touch-position-capture href "#sub-touch-position-capture" click menu-tap-detection href "#sub-menu-tap-detection" menu-tap-detection --> menu-item-calculation[menu-item-calculation] menu-item-calculation --> person-spawn click menu-item-calculation href "#sub-menu-item-calculation" personupdate --> gotorestaurant[gotorestaurant] personupdate --> orderfood[orderfood] personupdate --> gototable[gototable] click gotorestaurant href "#fn-gotorestaurant" click orderfood href "#fn-orderfood" click gototable href "#fn-gototable" ufo --> ufoupdate[ufoupdate] ufoupdate --> ufo-beaming-animation[ufo-beaming-animation] ufo-beaming-animation --> appearing-state[appearing-state] ufo-beaming-animation --> beaming-state[beaming-state] ufo-beaming-animation --> leaving-state[leaving-state] click ufo href "#fn-ufo" click ufoupdate href "#fn-ufoupdate" click ufo-beaming-animation href "#sub-ufo-beaming-animation" click appearing-state href "#sub-appearing-state" click beaming-state href "#sub-beaming-state" click leaving-state href "#sub-leaving-state" restaurant --> restaurantdisplay[restaurantdisplay] restaurantdisplay --> exterior-drawing[exterior-drawing] restaurantdisplay --> zone-labeling[zone-labeling] restaurantdisplay --> door-drawing[door-drawing] click restaurant href "#fn-restaurant" click restaurantdisplay href "#fn-restaurantdisplay" click exterior-drawing href "#sub-exterior-drawing" click zone-labeling href "#sub-zone-labeling" click door-drawing href "#sub-door-drawing" table --> tabledisplay[tabledisplay] tabledisplay --> tableisinside[tableisinside] tabledisplay --> tableoccupy[tableoccupy] tabledisplay --> tablefree[tablefree] click table href "#fn-table" click tabledisplay href "#fn-tabledisplay" click tableisinside href "#sub-table-is-inside" click tableoccupy href "#sub-table-occupy" click tablefree href "#sub-table-free" bouncer --> bouncerdisplay[bouncerdisplay] bouncerdisplay --> bouncierupdate[bouncierupdate] click bouncer href "#fn-bouncer" click bouncerdisplay href "#fn-bouncerdisplay" click bouncierupdate href "#fn-bouncierupdate"

❓ Frequently Asked Questions

What visual elements can I expect to see in the 'more food' sketch?

The sketch features colorful characters representing people, a dynamic menu of food items, and various animations depicting their actions such as ordering and eating.

How can I interact with the 'more food' creative coding sketch?

Users can click the menu expansion button to add new food items, influencing the characters' choices and interactions within the restaurant environment.

What coding concepts are showcased in the 'more food' p5.js sketch?

This sketch demonstrates object-oriented programming through the Person class, as well as state management for character actions and interactions.

Preview

more food  and rated it - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of more food  and rated it - Code flow showing setup, draw, addnewfoodtomenu, drawmenu, touchstarted, windowresized, person, persondisplay, personupdate, persontap, gotorestaurant, orderfood, gototable, ufo, ufoupdate, ufodisplay, ufoisbeaming, ufogetbeamprogress, bouncer, bouncerdisplay, bouncierupdate, restaurant, restaurantdisplay, table, tabledisplay, tableisinside, tableoccupy, tablefree
Code Flow Diagram