100 happened

This is an epic 1000-level game where you hunt for an impossibly distant apple (10 million feet away, rendered at sub-pixel size) that gets progressively harder to find. Special events at levels 10, 20, 30, and 100 introduce a food police car, zombie, fruit chase, and airplane escape—culminating in a cake-throwing finale where you splat animals for fun.

🧪 Try This!

Experiment with the code by making these changes:

  1. Remove the panning limit
  2. Make the apple huge from the start — The apple grows from 30px to a ridiculous size, making even Level 1 trivially easy to spot.
  3. Skip directly to the cake event — Instantly jump to Level 1000, triggering the cake-throwing finale instead of playing through all 1000 levels.
  4. Make the hint stay visible forever — The red hint circle normally disappears after a few seconds. Remove the fade logic to keep it on-screen permanently.
  5. Make all apples appear on the horizon — Every level places the apple at the horizon instead of randomizing the location type, removing visual variety.
  6. Add a 'godmode' where the apple is always found — Automatically find the apple without clicking, advancing levels instantly (great for testing).
Prefer the full editor? Open it there →

📖 About This Sketch

This sketch is an ambitious, humorous game called '100 Happened' that spans 1000 levels of escalating impossibility. You stand on grass searching for an apple so far away (10 million feet, ~3 million meters) that it appears smaller than a single pixel—yet you can find it, tap it, and trigger wild events. The visuals are powered by p5.js techniques like createGraphics for pre-rendered detail, image scaling to create the 'impossible' effect, panning with transform matrices, and class-based game objects (Zombie, Bullet, Fruit, Animal, Cake) that bring chaos to life.

The code is organized into a massive event-driven system: setup() initializes sounds and buttons; setupLevel() configures each level's apple location and difficulty scaling; draw() handles 1000 regular levels plus special event levels (10, 20, 30, 100, 1000) with branching logic for each. You will learn game state management, how to use transformations to pan a zoomed-out scene, collision detection across multiple object types, sound synthesis with p5.Oscillator, touch and mouse input handling, and the architecture of a multi-phase game progression.

⚙️ How It Works

  1. When you open the sketch, setup() creates a full-window canvas, pre-renders a detailed apple in a graphics buffer, initializes p5.sound oscillators for music and effects, and calls restartGame() to begin at Level 1. The hint button appears at the bottom.
  2. setupLevel() runs and randomly places the apple somewhere on screen (horizon, sky, sun, moon, cloud, space, or house interior) at the current scaled size. The apple shrinks and a hint circle grows smaller as you advance through levels, making it harder to spot.
  3. In draw(), the scene is translated by panning offset so you can drag to explore the 'zoomed-out' world. The apple is drawn as a tiny image. When you click or tap near the apple, checkAppleInteraction() is called. For regular levels (1–9, 11–19, 21–29, 31–99, 101–999), tapping the apple immediately triggers a win animation and nextLevel().
  4. Levels 10, 20, 30, and 100 are special: tapping the apple spawns a unique event (food police car arrests the apple, zombie chases it, a fruit chases you, or you fall from a plane). Each event has its own win or lose condition and must be resolved before advancing.
  5. Level 1000 is a cake-throwing party: you throw cakes at spawned animals (dogs, cats, bunnies) to splat them. When all animals are hit, the game ends and displays a victory message.
  6. Throughout, touch and mouse handlers detect panning (drag to explore) and apple taps. Sounds play via p5.Oscillator frequency and amplitude sweeps whenever events occur.

🎓 Concepts You'll Learn

Game state management and level progressionCoordinate transformation (panning and translation)Event-driven game loops and branching logicClass-based object hierarchies (Zombie, Bullet, Fruit, Animal, Cake)Collision detection with distance and bounding boxesSound synthesis and oscillator controlTouch and mouse input handlingGraphics buffer pre-rendering for performance

📝 Code Breakdown

setup()

setup() runs once when the sketch starts. It initializes the canvas, pre-renders the apple to a graphics buffer (allowing us to draw it beautifully once and reuse it at any size), creates all the sound oscillators, and sets up UI buttons. This one-time cost makes the draw loop efficient.

🔬 This block draws the apple body and stem. What happens if you change the ellipse diameter from 40 to 60? The stem coordinates stay the same—will the stem still look proportional?

  appleBuffer.ellipse(25, 30, 40, 40);

  // Stem
  appleBuffer.stroke(139, 69, 19); // Brown for stem
  appleBuffer.strokeWeight(2);
  appleBuffer.line(25, 10, 25, 20);
function setup() {
  createCanvas(windowWidth, windowHeight);
  // Tip: Use windowWidth/windowHeight for responsive canvas

  // 1. Create a createGraphics buffer to draw a detailed apple once.
  // This allows us to draw a nice-looking apple and then scale it down to an imperceptible size.
  appleBuffer = createGraphics(50, 50);
  appleBuffer.noStroke();
  appleBuffer.fill(220, 20, 60); // Red for apple body
  appleBuffer.ellipse(25, 30, 40, 40);

  // Stem
  appleBuffer.stroke(139, 69, 19); // Brown for stem
  appleBuffer.strokeWeight(2);
  appleBuffer.line(25, 10, 25, 20);

  // Leaf
  appleBuffer.noStroke();
  appleBuffer.fill(34, 139, 34); // Green for leaf
  appleBuffer.ellipse(35, 15, 15, 8);

  // Start the audio context (good practice, even if not using audio yet)
  userStartAudio(); // This function is provided by p5.sound

  // NEW: Initialize and start background music (reverted to 'sine' wave)
  backgroundMusic = new p5.Oscillator('sine'); // Reverted to 'sine'
  backgroundMusic.freq(50); // Reverted to 50 Hz
  backgroundMusic.amp(0.05); // Reverted to 0.05 volume
  backgroundMusic.start();
  // backgroundMusic.loop(); // Removed this line, as p5.Oscillator does not have a .loop() method.
                         // .start() already makes it play continuously.

  // Sound effect for finding the apple (remains the same)
  foundAppleSound = new p5.Oscillator('triangle'); // A different waveform for distinction
  foundAppleSound.freq(440); // A higher pitch
  foundAppleSound.amp(0); // Start with 0 volume
  foundAppleSound.start();

  // NEW: Initialize sounds for zombie event
  appleShootingSound = new p5.Oscillator('square');
  appleShootingSound.freq(1000);
  appleShootingSound.amp(0);
  appleShootingSound.start();

  zombieHitSound = new p5.Oscillator('sine');
  zombieHitSound.freq(150);
  zombieHitSound.amp(0);
  zombieHitSound.start();

  zombieDeathSound = new p5.Oscillator('sawtooth');
  zombieDeathSound.freq(100);
  zombieDeathSound.amp(0);
  zombieDeathSound.start();

  zombieSpawnSound = new p5.Oscillator('triangle');
  zombieSpawnSound.freq(80);
  zombieSpawnSound.amp(0);
  zombieSpawnSound.start();

  // NEW: Initialize sounds for fruit chase event
  fruitChaseSound = new p5.Oscillator('sawtooth');
  fruitChaseSound.freq(80); // Low, rumbling frequency
  fruitChaseSound.amp(0);
  fruitChaseSound.start();

  doorOpenSound = new p5.Oscillator('sine');
  doorOpenSound.freq(600); // Higher frequency for creak/whoosh
  doorOpenSound.amp(0);
  doorOpenSound.start();

  playerCaughtSound = new p5.Oscillator('triangle');
  playerCaucaught.freq(250); // Mid-range for gulp/scream
  playerCaucaught.amp(0);
  playerCaucaught.start();

  // NEW: Initialize sounds for cake event
  cakeSplatSound = new p5.Oscillator('square');
  cakeSplatSound.amp(0);
  cakeSplatSound.start();

  animalOuchSound = new p5.Oscillator('triangle');
  animalOuchSound.amp(0);
  animalOuchSound.start();

  // NEW: Initialize sounds for airplane event
  airplaneSound = new p5.Oscillator('sawtooth');
  airplaneSound.freq(60); // Low rumble
  airplaneSound.amp(0);
  airplaneSound.start();

  splashSound = new p5.Oscillator('sine');
  splashSound.freq(400); // Higher frequency for splash
  splashSound.amp(0);
  splashSound.start();

  splatSound = new p5.Oscillator('square');
  splatSound.freq(100); // Low frequency for thud
  splatSound.amp(0);
  splatSound.start();

  // Create and position the hint button
  hintButton = createButton('Give me a Hint!');
  hintButton.mousePressed(showHint); // Call showHint() when mouse pressed on button
  hintButton.touchStarted(showHint); // Call showHint() when touched on button
  hintButton.hide(); // Hide initially

  // Removed: Create and position the "Fast Leave" button
  // fastLeaveButton = createButton('Fast Leave');
  // fastLeaveButton.mousePressed(fastLeave);
  // fastLeaveButton.touchStarted(fastLeave);
  // fastLeaveButton.hide(); // Hide initially

  // Position the hint button (now the only one)
  let totalButtonsWidth = hintButton.width; // Adjusted as fastLeaveButton is removed
  let startX = (width - totalButtonsWidth) / 2;
  hintButton.position(startX, height - 50);
  // Removed positioning for fastLeaveButton
  // fastLeaveButton.position(startX + hintButton.width + 10, height - 50);

  // Create the restart button but hide it initially
  restartButton = createButton('Restart Game');
  restartButton.position(width / 2 - restartButton.width / 2, height / 2 + 50);
  restartButton.mousePressed(restartGame);
  restartButton.touchStarted(restartGame);
  restartButton.hide();

  // NEW: Create the Jump button for Level 100
  jumpButton = createButton('Jump!');
  jumpButton.mousePressed(playerJump);
  jumpButton.touchStarted(playerJump);
  jumpButton.hide();

  // Initialize the first level
  // Instead of setupLevel(), call restartGame() to ensure all game state is properly reset for Level 1.
  restartGame(); // Reverted to starting at Level 1

  // The following lines were for teleporting to Level 1000 and are now removed:
  // level = MAX_LEVELS; // Set current level to 1000
  // triggerCakeEvent(); // Immediately trigger the cake event
}
Line-by-line explanation (8 lines)

🔧 Subcomponents:

calculation Apple Graphics Buffer Creation appleBuffer = createGraphics(50, 50);

Creates a reusable 50×50 pixel off-screen graphics buffer where a detailed apple is drawn once, then scaled down to sub-pixel size for the 'impossible' effect

calculation Sound Oscillator Initialization backgroundMusic = new p5.Oscillator('sine');

Initializes all p5.sound oscillators with different waveforms (sine, triangle, sawtooth, square) and frequencies, pre-loaded so sounds can play instantly on demand

calculation UI Button Setup hintButton = createButton('Give me a Hint!');

Creates interactive buttons (hint, restart, jump) that trigger game functions; positioned at the bottom or center of the canvas

createCanvas(windowWidth, windowHeight);
Creates a full-window canvas that will resize responsively
appleBuffer = createGraphics(50, 50);
Creates an off-screen graphics buffer 50×50 pixels where the apple will be drawn in detail once
appleBuffer.fill(220, 20, 60);
Sets the fill color to crimson red for the apple body in the buffer
appleBuffer.ellipse(25, 30, 40, 40);
Draws a red circle (the apple body) in the buffer at coordinates (25, 30) with diameter 40
backgroundMusic = new p5.Oscillator('sine');
Creates a sine-wave oscillator for background music that will play at 50 Hz throughout the game
backgroundMusic.start();
Starts the oscillator so it plays continuously in the background (no .loop() method needed)
hintButton = createButton('Give me a Hint!');
Creates an interactive button labeled 'Give me a Hint!' that calls showHint() when pressed
restartGame();
Calls the restart function to initialize all game state and begin at Level 1

draw()

draw() is the heart of the game, running 60 times per second. It handles all rendering, collision detection, state updates, and event logic for all 1000+ levels. The massive conditional branching (level === 100, level === 10, etc.) dispatches to specialized event handling, while the main flow draws the sky, grass, apple, and 'You' character. This is a good example of how game loops must manage multiple simultaneous systems (animations, collisions, input, sounds) in one function.

🔬 This AABB collision check has 4 conditions joined by &&. What happens if you change && to || (OR) on all of them? You'd only need ONE condition to be true, not all four—would the player hit the pool from very far away?

      // Check collision with pool
      if (
        youY + 10 > poolY - poolHeight / 2 && // Bottom of player hits top of pool
        youY - 10 < poolY + poolHeight / 2 && // Top of player is above bottom of pool
        youX + 20 > poolX - poolWidth / 2 && // Right of player hits left of pool
        youX - 20 < poolX + poolWidth / 2    // Left of player hits right of pool
      ) {
function draw() {
  // Handle Level 100 event first
  if (level === 100) {
    // Draw sky background
    background(135, 206, 235); // Sky blue

    // Draw the airplane
    drawAirplane(width / 2, youY - 20); // Airplane follows player's Y slightly

    // Draw ground (grass) once player starts falling
    if (playerFalling || playerInPool || playerHitGround) {
      noStroke();
      fill(34, 139, 34); // Forest green for grass
      rect(0, height * 0.7, width, height * 0.3); // Grass covers the bottom 30% of the screen

      // Draw the pool
      fill(0, 191, 255); // Deep blue for pool
      rect(poolX - poolWidth / 2, poolY - poolHeight / 2, poolWidth, poolHeight, 10);
      // Pool border
      noFill();
      stroke(200);
      strokeWeight(5);
      rect(poolX - poolWidth / 2, poolY - poolHeight / 2, poolWidth, poolHeight, 10);
    }

    // Update player position if falling
    if (playerFalling) {
      fallVelocity += GRAVITY;
      youY += fallVelocity;
      // Allow horizontal movement with mouse/touch while falling
      youX = mouseX;
      if (touches.length > 0) {
        youX = touches[0].x;
      }
      youX = constrain(youX, 20, width - 20); // Constrain player X

      // Check collision with pool
      if (
        youY + 10 > poolY - poolHeight / 2 && // Bottom of player hits top of pool
        youY - 10 < poolY + poolHeight / 2 && // Top of player is above bottom of pool
        youX + 20 > poolX - poolWidth / 2 && // Right of player hits left of pool
        youX - 20 < poolX + poolWidth / 2    // Left of player hits right of pool
      ) {
        playerFalling = false;
        playerInPool = true;
        // Play splash sound
        if (splashSound) {
          splashSound.amp(0.5, 0.1);
          splashSound.freq(400, 0.1);
          setTimeout(() => splashSound.amp(0, 0.5), 500);
        }
      }
      // Check collision with ground (if not in pool)
      else if (youY + 10 > height * 0.7) {
        playerFalling = false;
        playerHitGround = true;
        // Play splat sound
        if (splatSound) {
          splatSound.amp(0.8, 0.1);
          splatSound.freq(100, 0.1);
          setTimeout(() => splatSound.amp(0, 0.5), 500);
        }
      }
    }

    // Draw "You" character
    fill(100);
    noStroke();
    ellipse(youX, youY, 40, 20); // Use youY
    fill(0);
    textSize(16);
    textAlign(CENTER, CENTER); // Center "You" text
    text("You", youX, youY + 10); // Use youY

    // Display messages for Level 100
    fill(0);
    textSize(24);
    textAlign(CENTER, TOP);
    if (!playerFalling && !playerInPool && !playerHitGround) {
      text("Level 100: The Impossible Airplane!", width / 2, 20);
      text("Fire hit the plane! Tap 'Jump!' to escape and find the pool below!", width / 2, 60);
    } else if (playerInPool) {
      textSize(48);
      text("You survived!", width / 2, height / 2 - 50);
      textSize(24);
      text("The impossible apple is safe! Moving to the next level.", width / 2, height / 2 + 10);
      nextLevel(); // Advance to Level 101 immediately
    } else if (playerHitGround) {
      textSize(48);
      text("Game Over!", width / 2, height / 2 - 50);
      textSize(24);
      text("You hit the ground. The impossible apple was defeated.", width / 2, height / 2 + 10);
      noLoop();
      restartButton.show();
    }
    return; // Stop drawing for this frame to prevent other level logic from running
  }

  // 3. Draw the sky and grass to set the scene. (Only if not Level 100)
  background(135, 206, 235); // Sky blue

  // Display current level
  fill(0);
  textSize(20);
  textAlign(LEFT, TOP);
  text(`Level: ${level}/${MAX_LEVELS}`, 20, 20);

  // Apply the panning translation to the scene elements
  push(); // Save the current transformation matrix
  translate(translateX, translateY);

  // New: Draw location-specific background elements BEFORE grass and apple
  switch (currentLocationType) {
    case 'SUN':
      drawSun();
      break;
    case 'MOON':
      drawMoon();
      break;
    case 'CLOUD':
      drawClouds();
      break;
    case 'SPACE':
      drawStars();
      break;
    case 'HOUSE': // NEW: Draw the house
      // The house is drawn at its center, so we need to pass a center coordinate
      let houseCenterX = (width * 0.2 + width * 0.8) / 2; // Approximate center of canvas
      let houseCenterY = height * 0.65; // Just above the grass line
      drawHouse(houseCenterX, houseCenterY, 120, 80);
      break;
  }

  noStroke();
  fill(34, 139, 34); // Forest green for grass
  rect(0, height * 0.7, width, height * 0.3); // Grass covers the bottom 30% of the screen

  // Handle Level 10 event
  if (level === 10) {
    if (foodPoliceActive) {
      drawFoodPoliceCar(foodPoliceX, tinyAppleY);
      foodPoliceX -= FOOD_POLICE_SPEED; // Move left

      // Check if food police car has reached the apple
      if (foodPoliceX < tinyAppleX) {
        appleArrested = true; // Mark apple as arrested (will make it disappear)
        foodPoliceActive = false; // Food police car disappears
        jailActive = true; // Jail appears
        jailX = tinyAppleX; // Position jail at apple's location
        jailY = tinyAppleY;
        jailCarryingApple = true; // Apple is inside jail
        foundApple = true; // Set foundApple to true to trigger win message and character leaving
      }
    }

    if (jailActive) {
      drawJail(jailX, jailY, jailCarryingApple);
      jailY += FOOD_POLICE_SPEED; // Jail sinks down

      // Check if jail has gone off-screen
      if (jailY > height + 50) {
        jailActive = false;
        jailCarryingApple = false;
        // At this point, foundApple is already true, and 'You' is leaving.
        // We now explicitly call nextLevel() for Level 10 here.
        nextLevel(); // Explicitly advance to the next level for Level 10
        // We can also return here to prevent any further drawing from the old level in this frame.
        return;
      }
    }
  }
  // NEW: Handle Level 20 Zombie event
  else if (level === 20) {
    if (level20ZombieEventTriggered) { // Zombie event is active
      if (zombieActive) {
        // Move and draw zombie
        zombie.move();
        zombie.draw();

        // Apple shooting logic
        if (millis() - lastShotTime > 500) { // Shoot every 500ms
          bullets.push(new Bullet(tinyAppleX, tinyAppleY, zombie.x, zombie.y - zombie.size * 0.6)); // Target zombie head
          lastShotTime = millis();
          // Play apple shooting sound
          if (appleShootingSound) {
            appleShootingSound.amp(0.2, 0.05);
            appleShootingSound.freq(1000, 0.05);
            setTimeout(() => appleShootingSound.amp(0, 0.1), 100);
          }
        }

        // Move and draw bullets
        for (let i = bullets.length - 1; i >= 0; i--) {
          bullets[i].move();
          bullets[i].draw();
          if (!bullets[i].isAlive) {
            bullets.splice(i, 1);
          }
        }

        // Check bullet-zombie collision
        for (let i = bullets.length - 1; i >= 0; i--) {
          let b = bullets[i];
          if (b.isAlive && zombie.isAlive) {
            let d = dist(b.x, b.y, zombie.x, zombie.y - zombie.size * 0.6); // Target zombie head
            if (d < zombie.size * 0.4) {
              zombie.takeDamage();
              b.isAlive = false; // Bullet disappears on hit
              bullets.splice(i, 1);
              if (!zombie.isAlive) {
                zombieDefeated = true;
                zombieActive = false;
              }
            }
          }
        }

        // Check zombie-apple collision (zombie reaches apple)
        let d = dist(zombie.x, zombie.y - zombie.size * 0.6, tinyAppleX, tinyAppleY);
        if (d < currentAppleSize / 2 + zombie.size * 0.4) { // Zombie touches apple
          appleEaten = true;
          zombieActive = false;
          // Play apple eaten sound or game over music
          if (zombieHitSound) { // Reusing zombie hit sound for apple eaten for now
            zombieHitSound.amp(0.5, 0.1);
            zombieHitSound.freq(50, 0.1);
            setTimeout(() => zombieHitSound.amp(0, 0.5), 1000);
          }
        }
      } else if (zombieDefeated) {
        // Win Level 20
        textSize(48);
        textAlign(CENTER, TOP); // Ensure text is centered
        text("You defeated the zombie!", width / 2, height / 2 - 50);
        textSize(24);
        text("The apple is safe! Moving to the next level.", width / 2, height / 2 + 10);
        nextLevel(); // Advance to Level 21 immediately
        return; // Stop drawing for this frame to prevent flickering
      } else if (appleEaten) {
        // Lose Level 20
        textSize(48);
        textAlign(CENTER, TOP); // Ensure text is centered
        text("The zombie ate the apple!", width / 2, height / 2 - 50);
        textSize(24);
        text("Game Over. The impossible apple was defeated.", width / 2, height / 2 + 10);
        // Stop the draw loop
        noLoop();
        // You could add a button to restart here
        restartButton.show();
      }
    } else { // Zombie event not triggered yet on Level 20
      text("You are standing on the grass, looking out.", width / 2, 20);
      text("Somewhere on the horizon, 10,000,000 feet away, is a single apple.", width / 2, 60);
      text("It's so far, it would appear as a speck smaller than a pixel to the naked eye.", width / 2, 100);
      text("It's impossible to find. But wait... you hear a groan?", width / 2, 140);
      text("Can you spot it?", width / 2, 180);
    }
  }
  // NEW: Handle Level 30 Fruit Chase event
  else if (level === 30) {
    if (level30EventTriggered) {
      // Hide hint button when chase starts
      hintButton.hide(); // Moved this inside the triggered block
      // Removed: Hide fast leave button during chase
      // fastLeaveButton.hide();

      // Draw the exit door
      if (exitDoor) exitDoor.draw();

      // Update player movement: directly follow mouse/touch
      youX = mouseX; // Or touches[0].x if touchActive
      youY = mouseY; // Make youY dynamic for Level 30
      if (touches.length > 0) {
        youX = touches[0].x;
        youY = touches[0].y;
      }
      youX = constrain(youX, 20, width - 20); // Constrain player X
      youY = constrain(youY, 20, height - 20); // Constrain player Y to a reasonable range
      // The "You" character will be drawn later using youX, youY

      // If fruit is active, move and draw it, check for collision
      if (fruitActive && fruit) {
        fruit.targetX = youX; // Fruit targets the player
        fruit.targetY = youY; // Fruit targets the player's Y position
        fruit.move();
        fruit.draw();

        // Collision detection: Fruit vs Player
        let d = dist(fruit.x, fruit.y, youX, youY); // Use youY
        if (d < fruit.size / 2 + 20) { // 20 is approx half of "You" character's ellipse width
          fruitCaughtPlayer = true;
          fruitActive = false; // Fruit stops chasing
          // Play player caught sound
          if (playerCaucaught) {
            playerCaucaught.amp(0.5, 0.1);
            playerCaucaught.freq(map(random(), 0, 1, 200, 300), 0.1); // Gulp/scream sound
            setTimeout(() => playerCaucaught.amp(0, 0.5), 1000);
          }
        }
      }

      // Collision detection: Player vs Exit Door
      if (exitDoor && !playerEscaped) {
        // playerY is now youY
        if (
          youX > exitDoor.x - exitDoor.width / 2 &&
          youX < exitDoor.x + exitDoor.width / 2 &&
          youY > exitDoor.y - exitDoor.height / 2 && // Use youY
          youY < exitDoor.y + exitDoor.height / 2    // Use youY
        ) {
          playerEscaped = true;
          fruitActive = false; // Fruit stops chasing
          // Play door open sound
          if (doorOpenSound) {
            doorOpenSound.amp(0.4, 0.1);
            doorOpenSound.freq(map(random(), 0, 1, 500, 700), 0.1); // Whoosh/creak sound
            setTimeout(() => doorOpenSound.amp(0, 0.5), 500);
          }
        }
      }

      // Win/Lose conditions for Level 30
      if (playerEscaped) {
        textSize(48);
        textAlign(CENTER, TOP);
        text("You escaped the hungry fruit!", width / 2, height / 2 - 50);
        textSize(24);
        text("The impossible apple is safe! Moving to the next level.", width / 2, height / 2 + 10);
        nextLevel(); // Advance to Level 31
        return; // Stop drawing for this frame
      } else if (fruitCaughtPlayer) {
        textSize(48);
        textAlign(CENTER, TOP);
        text("The fruit ate you!", width / 2, height / 2 - 50);
        textSize(24);
        text("Game Over. The impossible apple was defeated.", width / 2, height / 2 + 10);
        noLoop(); // Stop the draw loop
        restartButton.show(); // Show the restart button
        return; // Stop drawing for this frame
      }

      // Display instructions during the chase
      fill(0);
      textSize(24);
      textAlign(CENTER, TOP);
      text("ESCAPE THE HUNGRY FRUIT!", width / 2, 20);
      text("Find the EXIT door!", width / 2, 60);

    } else { // Level 30, but event not triggered yet
      // Hint button remains visible here to help find the apple
      text("You are standing on the grass, looking out.", width / 2, 20);
      text("Somewhere on the horizon, 10,000,000 feet away, is a single apple.", width / 2, 60);
      text("It's so far, it would appear as a speck smaller than a pixel to the naked eye.", width / 2, 100);
      text("It's impossible to find. But wait... is that a fruit with eyes?", width / 2, 140);
      text("Can you spot it?", width / 2, 180);
    }
  }


  // 4. Draw the tiny, impossible apple.
  // The apple is now only drawn if it hasn't been found yet AND we haven't completed the game
  // On Level 10, the apple disappears when arrested (appleArrested = true)
  // On Level 20, the apple disappears if eaten by the zombie (appleEaten = true)
  // On Level 30, the apple disappears when the fruit chase event starts (level30EventTriggered = true)
  if (!foundApple && level <= MAX_LEVELS && !(level === 10 && appleArrested) && !(level === 20 && appleEaten) && !cakeEventActive && !level30EventTriggered) {
    image(appleBuffer, tinyAppleX, tinyAppleY, currentAppleSize, currentAppleSize);
  }

  // Draw hint circle if active
  if (hintActive) {
    noFill();
    stroke(255, 0, 0); // Red circle for hint
    strokeWeight(3);
    // Draw the circle around the apple's untranslated position
    ellipse(tinyAppleX, tinyAppleY, currentHintCircleSize, currentHintCircleSize);

    // Make the hint disappear after the currentHintDuration
    if (millis() - hintTimer > currentHintDuration) {
      hintActive = false;
    }
  }

  // Optional: Add a simple hint for your position
  // Only draw "You" if the apple hasn't been found yet for regular levels,
  // AND it's not a special event level where "You" might be handled differently or hidden
  // NEW: Also hide "You" during the cake event, and level 30 event
  if (!foundApple && level <= MAX_LEVELS && !cakeEventActive && !level30EventTriggered && level !== 100) {
    fill(100);
    noStroke();
    ellipse(youX, youY, 40, 20); // Use youY
    fill(0);
    textSize(16);
    textAlign(CENTER, CENTER); // Center "You" text
    text("You", youX, youY + 10); // Use youY
  }

  pop(); // Restore the transformation matrix, so text explanation is drawn fixed on screen

  // 5. Add text explanation to convey the concept.
  fill(0); // Black text
  textSize(24);
  textAlign(CENTER, TOP);

  if (level > MAX_LEVELS && !cakeEventActive) { // Final win message after cake event is complete
    textSize(48);
    text("YOU WON THE GAME!", width / 2, height / 2 - 50);
    textSize(24);
    text("Thanks for playing! You found all impossible apples and had some cake fun!", width / 2, height / 2 + 10);
    noLoop(); // Stop the game loop
    restartButton.show(); // Show the restart button
  } else if (foundApple && level !== 10 && level !== 20 && level !== 30) { // This block handles win for normal levels
    // NEW LOGIC: Immediately display win message and advance to the next level
    textSize(48); // Make "You win!" bigger
    text("You win!", width / 2, height / 2 - 50);
    textSize(24);
    text("You ate the impossible apple and left!", width / 2, height / 2 + 10);

    nextLevel();
    // The `nextLevel()` function will reset `foundApple` to `false` and set up the new level.
    // We can `return` here to prevent any further drawing from the old level in this frame.
    return;
  }
  // The Level 10, Level 20, Level 30, and Level 100 specific text is handled within their respective blocks above.

  // NEW: Handle Level 1000 Cake event
  if (cakeEventActive) {
    hintButton.hide(); // Hide hint button during cake event
    // Removed: Hide fast leave button during cake event
    // fastLeaveButton.hide();
    restartButton.hide(); // Hide restart button if it was shown from Level 20 loss

    // Draw animals
    for (let animal of targetAnimals) {
      animal.move();
      animal.draw();
    }

    // Draw and move thrown cakes
    for (let i = thrownCakes.length - 1; i >= 0; i--) {
      thrownCakes[i].move();
      thrownCakes[i].draw();
      if (!thrownCakes[i].isActive) {
        thrownCakes.splice(i, 1);
      }
    }

    // Check collisions between cakes and animals
    for (let i = thrownCakes.length - 1; i >= 0; i--) {
      let cake = thrownCakes[i];
      if (!cake.isActive) continue;

      for (let j = targetAnimals.length - 1; j >= 0; j--) {
        let animal = targetAnimals[j];
        if (animal.isHit) continue; // Skip already hit animals

        let d = dist(cake.x, cake.y, animal.x, animal.y);
        if (d < cake.size / 2 + animal.size / 2) {
          // Collision!
          animal.isHit = true;
          animal.splatTimer = millis();
          cake.isActive = false; // Cake disappears

          // Play cake splat sound
          if (cakeSplatSound) {
            cakeSplatSound.amp(0.6, 0.1);
            cakeSplatSound.freq(map(random(), 0, 1, 100, 200), 0.1);
            setTimeout(() => cakeSplatSound.amp(0, 0.5), 500);
          }
          // Play animal ouch sound
          if (animalOuchSound) {
            animalOuchSound.amp(0.4, 0.05);
            animalOuchSound.freq(map(random(), 0, 1, 400, 600), 0.05);
            setTimeout(() => animalOuchSound.amp(0, 0.2), 200);
          }

          thrownCakes.splice(i, 1); // Remove cake
          // No need to remove animal yet, it will animate and disappear later
          break; // Only one animal can be hit per cake
        }
      }
    }

    // Draw the cake if player is holding one
    if (hasCake) {
      push();
      translate(mouseX, mouseY);
      noStroke();

      // Cake body (brown cake)
      fill(139, 69, 19);
      rect(-15, -15, 30, 15, 5);

      // Icing (white)
      fill(255);
      ellipse(0, -15, 30, 10);

      // Cherry on top (red)
      fill(200, 0, 0);
      ellipse(0, -15 - 5, 10, 10);

      pop();
    }

    // Display instructions
    fill(0);
    textSize(32);
    textAlign(CENTER, TOP);
    text("Congratulations! You found all impossible apples!", width / 2, 20);
    textSize(24);
    if (hasCake) {
      text("Tap to throw the cake at a dog, cat, or bunny for fun!", width / 2, 60);
    } else {
      text("More cakes are coming!", width / 2, 60);
      // Automatically give another cake after a delay if all thrown cakes are gone
      if (thrownCakes.length === 0 && targetAnimals.some(a => !a.isHit)) {
        setTimeout(() => { hasCake = true; }, 1000); // Give another cake after 1 second
      }
    }

    // Check if all animals are hit
    if (targetAnimals.every(a => a.isHit)) {
      cakeEventActive = false; // End cake event
      level = MAX_LEVELS + 1; // Set level higher than MAX_LEVELS to trigger final win message
      // The final win message will be displayed in the next draw cycle
    }
  }
}
Line-by-line explanation (12 lines)

🔧 Subcomponents:

conditional Level 100 Airplane Event Handler if (level === 100) {

Handles the airplane escape sequence with gravity physics, pool/ground collision detection, and player movement tied to mouse/touch input

conditional Level 10 Food Police Event if (level === 10) {

Manages the food police car animation chasing the apple and the jail sinking down after arrest

conditional Level 20 Zombie Battle else if (level === 20) {

Handles zombie spawning, movement, bullet firing, collision detection, and win/lose conditions

conditional Level 30 Fruit Chase Event else if (level === 30) {

Manages the fruit chasing the player, exit door collision, and player movement tied to mouse/touch

conditional Apple Image Drawing if (!foundApple && level <= MAX_LEVELS && !(level === 10 && appleArrested) && !(level === 20 && appleEaten) && !cakeEventActive && !level30EventTriggered) {

Conditionally renders the apple only when it should be visible (not found, not eaten, not arrested, not in special events)

conditional Hint Circle Visualization if (hintActive) {

Draws a red circle around the apple when the hint button is pressed, then fades after the hint duration expires

conditional Level 1000 Cake Throwing Event if (cakeEventActive) {

Manages animal movement, cake throwing, collision detection between cakes and animals, splat animations, and automatic cake respawning

if (level === 100) {
Checks if we're on Level 100 to execute the airplane escape event logic before normal level drawing
background(135, 206, 235); // Sky blue
Clears the canvas with a light sky blue color each frame
drawAirplane(width / 2, youY - 20);
Draws the airplane sprite following the player's Y position (slightly offset) at the center of the screen
if (playerFalling) {
Checks if the player has jumped and is currently falling through the air
fallVelocity += GRAVITY;
Increases falling velocity by the gravity constant each frame, simulating acceleration downward
youY += fallVelocity;
Updates the player's Y position by the current falling velocity, moving them downward each frame
youX = mouseX;
Ties the player's horizontal position to the mouse X coordinate, allowing real-time steering while falling
if (youY + 10 > poolY - poolHeight / 2 &&
First part of AABB (axis-aligned bounding box) collision check: player's bottom edge below pool's top edge
translate(translateX, translateY);
Applies the panning transformation to all following draw calls, offsetting the scene by user drag input
image(appleBuffer, tinyAppleX, tinyAppleY, currentAppleSize, currentAppleSize);
Draws the pre-rendered apple buffer at the apple's position, scaled to the current level's apple size (smaller as levels increase)
ellipse(tinyAppleX, tinyAppleY, currentHintCircleSize, currentHintCircleSize);
Draws a red circle centered on the apple to reveal its location; size shrinks as levels increase
if (targetAnimals.every(a => a.isHit)) {
Uses JavaScript's .every() method to check if all animals in the array have been hit (isHit === true)

setupLevel()

setupLevel() is called whenever a new level begins (via nextLevel() or restartGame()). It resets all state variables to clean values, randomly places the apple using switch-case logic and trigonometry, initializes special event setups for milestone levels (10, 20, 30, 100), and scales difficulty parameters across the full game span using the map() function. This modular design keeps the game state predictable and makes it easy to add new locations or difficulty curves.

🔬 This switch handles 7 location types. What if you add a new case like 'case WATER:' that places the apple at random(width) and height * 0.5 (middle of the screen)? You'd also need to add 'WATER' to the random() array above. Can you predict where the apple would appear?

  switch (currentLocationType) {
    case 'HORIZON':
      tinyAppleX = random(width);
      tinyAppleY = height * 0.7; // Just above the grass line
      break;

🔬 These three map() calls scale difficulty over 1000 levels. What if you change MAX_LEVELS in the third argument to something like 100 instead? The difficulty would ramp up FASTER, reaching minimum values (5px, 50px, 1s) by level 100 instead of level 1000—would the game become too hard?

  // Map difficulty parameters based on the current level
  currentAppleSize = map(level, 1, MAX_LEVELS, 30, 5); // From 30px down to 5px
  currentHintCircleSize = map(level, 1, MAX_LEVELS, 200, 50); // From 200px down to 50px
  currentHintDuration = map(level, 1, MAX_LEVELS, 3000, 1000); // From 3 seconds down to 1 second
function setupLevel() {
  // Reset game state for the new level
  foundApple = false;
  hintActive = false;
  translateX = 0;
  translateY = 0;
  youX = width / 2; // Reset "You" position
  youY = height * 0.7 + 10; // Default "You" Y position

  // Reset Level 10 event variables
  level10EventTriggered = false;
  appleArrested = false;
  foodPoliceActive = false;
  foodPoliceX = width + 100; // Start off-screen right

  // Reset Level 20 event variables
  level20ZombieEventTriggered = false;
  zombieActive = false;
  zombieDefeated = false;
  appleEaten = false;
  zombie = null;
  bullets = [];
  lastShotTime = 0;

  // Reset Level 30 event variables
  level30EventTriggered = false;
  fruitActive = false;
  exitDoorActive = false;
  fruit = null;
  exitDoor = null;
  fruitCaughtPlayer = false;
  playerEscaped = false;

  // Reset Level 100 event variables
  level100EventActive = false;
  playerFalling = false;
  playerInPool = false;
  playerHitGround = false;
  fallVelocity = 0;
  poolX = 0;
  poolY = 0;
  poolWidth = 0;
  poolHeight = 0;
  jumpButton.hide(); // Hide jump button by default
  airplaneSound.amp(0, 0.1); // Stop airplane sound

  // Reset jail variables
  jailActive = false;
  jailCarryingApple = false;
  jailX = 0;
  jailY = 0;

  // Show/hide hint button based on game status (fast leave button removed)
  if (level <= MAX_LEVELS) { // If game is still active
    hintButton.show(); // Ensure hint button is shown if game is active
  } else { // If game is over
    hintButton.hide();
  }

  // New: Randomly decide on a location type for the apple
  currentLocationType = random(['HORIZON', 'SKY', 'SUN', 'MOON', 'CLOUD', 'SPACE', 'HOUSE']); // Added 'HOUSE'

  // Declare angle and r here to make them accessible across switch cases
  let angle;
  let r;

  switch (currentLocationType) {
    case 'HORIZON':
      tinyAppleX = random(width);
      tinyAppleY = height * 0.7; // Just above the grass line
      break;
    case 'SKY':
      tinyAppleX = random(width);
      tinyAppleY = random(height * 0.1, height * 0.6); // Random position in the upper sky
      break;
    case 'SUN':
      // Place the sun randomly in the upper part of the sky, then place apple on it
      let sunCenterX = random(width * 0.2, width * 0.8);
      let sunCenterY = random(height * 0.1, height * 0.4);
      let sunRadius = 50;
      // Place apple randomly within the sun's area
      angle = random(TWO_PI);
      r = random(sunRadius); // Random distance from sun's center
      tinyAppleX = sunCenterX + r * cos(angle);
      tinyAppleY = sunCenterY + r * sin(angle);
      break;
    case 'MOON':
      // Place the moon randomly in the upper part of the sky, then place apple on it
      let moonCenterX = random(width * 0.2, width * 0.8);
      let moonCenterY = random(height * 0.1, height * 0.4);
      let moonRadius = 40;
      // Place apple randomly within the moon's area
      angle = random(TWO_PI);
      r = random(moonRadius); // Random distance from moon's center
      tinyAppleX = moonCenterX + r * cos(angle);
      tinyAppleY = moonCenterY + r * sin(angle);
      break;
    case 'CLOUD':
      // Place apple randomly within the sky, implying it's on a cloud (clouds drawn later)
      tinyAppleX = random(width);
      tinyAppleY = random(height * 0.1, height * 0.6);
      break;
    case 'SPACE':
      tinyAppleX = random(width);
      tinyAppleY = random(0, height * 0.3); // Very high in the sky, implying space (stars drawn later)
      break;
    case 'HOUSE': // NEW: Position apple inside the house
      // For simplicity, let's place a house around the center of the canvas
      let houseCenterX = random(width * 0.2, width * 0.8);
      let houseCenterY = height * 0.65; // Just above the grass line
      let houseWidth = 120;
      let houseHeight = 80;
      tinyAppleX = random(houseCenterX - houseWidth / 3, houseCenterX + houseWidth / 3);
      tinyAppleY = random(houseCenterY - houseHeight / 3, houseCenterY + houseHeight / 3);
      break;
  }

  // For Level 30, place the exit door
  if (level === 30) {
    // Randomly place the door along one of the canvas edges
    let doorX, doorY;
    let edge = random(['top', 'bottom', 'left', 'right']);
    const DOOR_SIZE = 60; // Approximate size of the door
    switch (edge) {
      case 'top':
        doorX = random(DOOR_SIZE / 2, width - DOOR_SIZE / 2);
        doorY = DOOR_SIZE / 2;
        break;
      case 'bottom':
        doorX = random(DOOR_SIZE / 2, width - DOOR_SIZE / 2);
        doorY = height - DOOR_SIZE / 2;
        break;
      case 'left':
        doorX = DOOR_SIZE / 2;
        doorY = random(DOOR_SIZE / 2, height - DOOR_SIZE / 2);
        break;
      case 'right':
        doorX = width - DOOR_SIZE / 2;
        doorY = random(DOOR_SIZE / 2, height - DOOR_SIZE / 2);
        break;
    }
    exitDoor = new ExitDoor(doorX, doorY, DOOR_SIZE * 0.8, DOOR_SIZE * 1.2); // Door is taller than wide
  }

  // NEW: Initialize Level 100 event
  if (level === 100) {
    level100EventActive = true;
    hintButton.hide(); // Hide hint button during this event
    youX = width / 2;
    youY = height * 0.2; // Start "You" inside the plane
    jumpButton.show();
    jumpButton.position(width / 2 - jumpButton.width / 2, height * 0.8);
    airplaneSound.amp(0.3, 0.5); // Start airplane rumble
    airplaneSound.freq(random(50, 70), 0.5);
    // Place the pool randomly on the ground
    poolWidth = random(80, 150);
    poolHeight = random(40, 70);
    poolX = random(width / 4, width * 3 / 4);
    poolY = height * 0.7 + poolHeight / 2; // Position on the grass line
  }

  // Map difficulty parameters based on the current level
  currentAppleSize = map(level, 1, MAX_LEVELS, 30, 5); // From 30px down to 5px
  currentHintCircleSize = map(level, 1, MAX_LEVELS, 200, 50); // From 200px down to 50px
  currentHintDuration = map(level, 1, MAX_LEVELS, 3000, 1000); // From 3 seconds down to 1 second
  currentPanLimitX = map(level, 1, MAX_LEVELS, width / 2, width); // From half width to full width
  currentPanLimitY = map(level, 1, MAX_LEVELS, height / 4, height / 2); // From quarter height to half height
}
Line-by-line explanation (11 lines)

🔧 Subcomponents:

calculation Global State Reset foundApple = false;

Resets all level-specific and event variables to their initial state before setting up the new level

switch-case Apple Location Type Selection switch (currentLocationType) {

Randomly places the apple in one of 7 location types (HORIZON, SKY, SUN, MOON, CLOUD, SPACE, HOUSE), each with unique positioning logic

conditional Level 30 Exit Door Setup if (level === 30) {

Creates an exit door at a random edge of the canvas for the fruit chase event

conditional Level 100 Airplane Initialization if (level === 100) {

Sets up the airplane event with pool placement, sound, button visibility, and initial player position

calculation Difficulty Parameter Scaling currentAppleSize = map(level, 1, MAX_LEVELS, 30, 5);

Uses map() to scale difficulty parameters across all 1000 levels—apple size shrinks, hints disappear faster, pan limits expand

foundApple = false;
Resets the apple to unfound state so it can be discovered in the new level
youX = width / 2;
Positions the 'You' character at the horizontal center of the screen
youY = height * 0.7 + 10;
Positions the 'You' character at 70% down the screen (on the grass line) plus 10 pixels
currentLocationType = random(['HORIZON', 'SKY', 'SUN', 'MOON', 'CLOUD', 'SPACE', 'HOUSE']);
Randomly picks one of 7 location types for the apple, stored for use in the draw() background rendering
case 'HORIZON':
When HORIZON is chosen, the apple is placed on the horizon line at a random X position
angle = random(TWO_PI);
Generates a random angle in radians (0 to 2π) for placing the apple within the sun or moon circle
r = random(sunRadius);
Generates a random radius (0 to sunRadius) so the apple is somewhere inside the sun's area, not just on its edge
tinyAppleX = sunCenterX + r * cos(angle);
Calculates the apple's X position using polar-to-cartesian conversion (cos for horizontal offset)
tinyAppleY = sunCenterY + r * sin(angle);
Calculates the apple's Y position using polar-to-cartesian conversion (sin for vertical offset)
currentAppleSize = map(level, 1, MAX_LEVELS, 30, 5);
Maps the current level (1–1000) to an apple size range (30–5 pixels); higher levels = smaller apples
currentHintDuration = map(level, 1, MAX_LEVELS, 3000, 1000);
Maps levels to hint duration; early levels show hints for 3 seconds, late levels only 1 second

nextLevel()

nextLevel() is the gateway function between levels. It increments the level counter and branches logic: if you've cleared all 1000 regular levels, it triggers the special cake event; otherwise, it sets up the next level normally. This is a simple but essential piece of the game's progression system.

function nextLevel() {
  level++;
  if (level > MAX_LEVELS) {
    // Game completed!
    // Instead of a simple win message, trigger the cake event
    triggerCakeEvent(); // Call the new function
  } else {
    setupLevel(); // Set up parameters for the new level
  }
}
Line-by-line explanation (5 lines)

🔧 Subcomponents:

calculation Level Counter Increment level++;

Increases the level counter by 1, advancing the player to the next challenge

conditional Game Completion Check if (level > MAX_LEVELS) {

Determines whether the player has finished all regular levels and should enter the cake event finale

level++;
Increments the level variable by 1, moving from Level N to Level N+1
if (level > MAX_LEVELS) {
Checks if the new level exceeds the maximum (1000)—if so, the player has won all regular levels
triggerCakeEvent();
Calls the cake event setup to spawn animals and begin the cake-throwing finale
} else {
If the level is still within the normal range, set up the new level as usual
setupLevel();
Initializes the new level's parameters (apple location, difficulty scaling, event resets)

checkAppleInteraction(x, y)

checkAppleInteraction() is called from mouseReleased() and touchEnded() after converting screen coordinates back to untranslated world coordinates. It's the critical function that detects when you've clicked/tapped the apple and branches to three outcomes: (1) normal levels instantly win, (2) special event levels (10, 20, 30) trigger unique challenges instead, and (3) a sound effect plays every time. The hitboxRadius constant (currently 100) makes testing easy but should be reduced to 15–20 for actual gameplay difficulty.

🔬 Level 10 does NOT set foundApple = true immediately. What would happen if you removed the comment and added foundApple = true here? The apple would disappear and you'd skip straight to the win screen, bypassing the whole police car animation—would that be more or less fun?

    if (level === 10) {
      if (!level10EventTriggered) { // Only trigger once
        level10EventTriggered = true;
        foodPoliceActive = true;
        foodPoliceX = width + 100; // Start off-screen right
        // Do NOT set foundApple = true here. The apple remains visible until arrested.
      }
    }
function checkAppleInteraction(x, y) {
  // Only check for interaction if not already found and not past max levels
  if (foundApple || level > MAX_LEVELS || cakeEventActive || level === 100) return; // Added cakeEventActive and level 100

  // TEMPORARY: Increased hitbox radius for testing!
  // Revert this to 15 or another challenging value after confirming the logic works.
  const hitboxRadius = 100; // Was 15

  // tinyAppleX and tinyAppleY are already in the untranslated coordinate system
  let d = dist(x, y, tinyAppleX, tinyAppleY);

  if (d < hitboxRadius) {
    // Play the found apple sound effect
    foundAppleSound.amp(0.5, 0.1); // Ramp up to 0.5 volume over 0.1 seconds
    foundAppleSound.freq(880, 0.1); // Ramp up to 880Hz over 0.1 seconds
    setTimeout(() => {
      foundAppleSound.amp(0, 0.5); // Ramp down to 0 volume over 0.5 seconds
      foundAppleSound.freq(440, 0.5); // Ramp down frequency
    }, 200); // Play for 200ms

    // On Level 10, we activate the event but don't immediately set foundApple to true
    if (level === 10) {
      if (!level10EventTriggered) { // Only trigger once
        level10EventTriggered = true;
        foodPoliceActive = true;
        foodPoliceX = width + 100; // Start off-screen right
        // Do NOT set foundApple = true here. The apple remains visible until arrested.
      }
    }
    // NEW: On Level 20, activate the zombie event
    else if (level === 20) {
      if (!level20ZombieEventTriggered) { // Only trigger once
        level20ZombieEventTriggered = true;
        zombieActive = true;
        foundApple = true; // Set foundApple to true to prevent re-triggering and let "You" character leave

        // Spawn zombie from a random edge
        let spawnX, spawnY;
        let edge = random(['top', 'bottom', 'left', 'right']);
        switch (edge) {
          case 'top':
            spawnX = random(width);
            spawnY = -100;
            break;
          case 'bottom':
            spawnX = random(width);
            spawnY = height + 100;
            break;
          case 'left':
            spawnX = -100;
            spawnY = random(height);
            break;
          case 'right':
            spawnX = width + 100;
            spawnY = random(height);
            break;
        }
        zombie = new Zombie(spawnX, spawnY, tinyAppleX, tinyAppleY);
        bullets = []; // Clear any old bullets
        lastShotTime = millis();
        // Play zombie spawn sound
        if (zombieSpawnSound) {
          zombieSpawnSound.amp(0.3, 0.1);
          zombieSpawnSound.freq(80, 0.1);
          setTimeout(() => zombieSpawnSound.amp(0, 0.5), 1000);
        }
      }
    }
    // NEW: On Level 30, activate the fruit chase event
    else if (level === 30) {
      if (!level30EventTriggered) { // Only trigger once
        level30EventTriggered = true;
        fruitActive = true;
        foundApple = true; // Make apple disappear and "You" character start moving
        // Spawn fruit from a random edge, not too close to the door
        let spawnX, spawnY;
        let edge = random(['top', 'bottom', 'left', 'right']);
        switch (edge) {
          case 'top':
            spawnX = random(width);
            spawnY = -100;
            break;
          case 'bottom':
            spawnX = random(width);
            spawnY = height + 100;
            break;
          case 'left':
            spawnX = -100;
            spawnY = random(height);
            break;
          case 'right':
            spawnX = width + 100;
            spawnY = random(height);
            break;
        }
        // Fruit will target youX and youY
        fruit = new Fruit(spawnX, spawnY, youX, youY);
        // Play a scary sound effect
        // NEW: Add fruit chase sound
        if (fruitChaseSound) {
          fruitChaseSound.amp(0.3, 0.1);
          fruitChaseSound.freq(map(random(), 0, 1, 60, 90), 0.1); // Low, rumbling sound
          setTimeout(() => fruitChaseSound.amp(0, 0.5), 1000); // Play for 1 second
        }
      }
    }
    else {
      // For all other levels, set foundApple to true directly
      foundApple = true;
    }
  }
}
Line-by-line explanation (11 lines)

🔧 Subcomponents:

conditional Early Exit Guard if (foundApple || level > MAX_LEVELS || cakeEventActive || level === 100) return;

Prevents interaction checking if the apple is already found, game is over, cake event is active, or we're on the special Level 100 event

calculation Distance Calculation for Hitbox let d = dist(x, y, tinyAppleX, tinyAppleY);

Calculates the Euclidean distance between the click/tap position and the apple's center using p5.js's dist() function

calculation Sound Effect Playback foundAppleSound.amp(0.5, 0.1);

Triggers the 'found apple' sound with a frequency sweep and ramping volume envelope using setTimeout()

conditional Level 10 Police Event Trigger if (level === 10) {

On Level 10, spawning the food police car without immediately marking the apple as found

conditional Level 20 Zombie Spawn else if (level === 20) {

On Level 20, spawns a zombie from a random screen edge and starts the battle

conditional Level 30 Fruit Chase Trigger else if (level === 30) {

On Level 30, spawns a chasing fruit from a random screen edge

if (foundApple || level > MAX_LEVELS || cakeEventActive || level === 100) return;
Guard clause that exits the function early if conditions aren't right for interaction (apple already found, game over, cake event active, or on special Level 100)
const hitboxRadius = 100;
Defines a circular hitbox with radius 100 pixels around the apple—click/tap within this circle to trigger interaction (currently set high for testing)
let d = dist(x, y, tinyAppleX, tinyAppleY);
Uses p5.js's dist() function to calculate straight-line distance between click coordinates (x, y) and the apple's position
if (d < hitboxRadius) {
Checks if the distance is less than the hitbox radius—if so, the click/tap hit the apple
foundAppleSound.amp(0.5, 0.1);
Ramps the sound volume UP to 0.5 over 0.1 seconds (100ms) when apple is tapped
foundAppleSound.freq(880, 0.1);
Ramps the oscillator frequency UP to 880 Hz over 0.1 seconds, creating a rising 'ding' pitch
setTimeout(() => { foundAppleSound.amp(0, 0.5); }, 200);
After 200ms, ramps the volume DOWN to 0 over 0.5 seconds, creating a fading tail
if (level === 10) {
Special handling for Level 10: activates the police car event instead of immediately winning
if (!level10EventTriggered) {
Guard to ensure the event only triggers once, preventing repeated zombie spawns or police car activations
let edge = random(['top', 'bottom', 'left', 'right']);
Randomly chooses which screen edge the enemy (zombie or fruit) spawns from
spawnX = random(width); spawnY = -100;
If spawning from top edge, place at random X and Y = -100 (just off-screen above)

mouseDragged()

mouseDragged() fires every frame while the mouse button is held down and moving. It accumulates drag deltas into translateX and translateY, then clamps them to level-specific limits. This allows smooth camera panning across the 'zoomed-out' scene. The constrain() calls ensure you can't pan so far that you lose the sense of the game world.

function mouseDragged() {
  if (panning) {
    translateX += mouseX - lastMouseX;
    translateY += mouseY - lastMouseY;

    // Constrain panning to the current level's range
    translateX = constrain(translateX, -currentPanLimitX, currentPanLimitX);
    translateY = constrain(translateY, -currentPanLimitY, currentPanLimitY);

    lastMouseX = mouseX;
    lastMouseY = mouseY;
  }
  return false; // Prevent default browser behavior (like selecting text)
}
Line-by-line explanation (7 lines)

🔧 Subcomponents:

calculation Pan Offset Accumulation translateX += mouseX - lastMouseX;

Accumulates the drag delta into the pan offset, allowing continuous dragging across multiple frames

calculation Pan Boundary Constraint translateX = constrain(translateX, -currentPanLimitX, currentPanLimitX);

Clamps the pan offset within level-specific bounds so you can't pan infinitely far

if (panning) {
Only applies pan logic if panning mode is active (set to true in mousePressed())
translateX += mouseX - lastMouseX;
Adds the horizontal drag delta (current mouse X minus last frame's X) to the existing pan offset
translateY += mouseY - lastMouseY;
Adds the vertical drag delta (current mouse Y minus last frame's Y) to the existing pan offset
translateX = constrain(translateX, -currentPanLimitX, currentPanLimitX);
Clamps translateX between -currentPanLimitX and +currentPanLimitX, preventing panning too far left/right
translateY = constrain(translateY, -currentPanLimitY, currentPanLimitY);
Clamps translateY between -currentPanLimitY and +currentPanLimitY, preventing panning too far up/down
lastMouseX = mouseX;
Updates lastMouseX to the current frame's mouseX so next frame can calculate the delta correctly
return false;
Prevents browser default behavior (text selection, scrolling) on the canvas

📦 Key Variables

level number

Tracks the current level (1–1000+). Used to branch event logic, scale difficulty, and determine game completion.

let level = 1;
tinyAppleX number

Horizontal position of the apple on the canvas. Set randomly each level in setupLevel().

let tinyAppleX = 0;
tinyAppleY number

Vertical position of the apple on the canvas. Set randomly each level based on currentLocationType.

let tinyAppleY = 0;
foundApple boolean

Tracks whether the player has found the apple in the current level. Used to trigger win logic and hide the apple.

let foundApple = false;
currentAppleSize number

The diameter (in pixels) at which to render the apple. Scales from 30px (Level 1) down to 5px (Level 1000).

let currentAppleSize = 30;
translateX number

Horizontal panning offset applied to the scene via translate(). Accumulates drag input from mouse/touch.

let translateX = 0;
translateY number

Vertical panning offset applied to the scene via translate(). Accumulates drag input from mouse/touch.

let translateY = 0;
youX number

Horizontal position of the 'You' character (player avatar). Reset to width/2 each level, updated dynamically on Level 30.

let youX = 0;
youY number

Vertical position of the 'You' character. Defaults to height * 0.7 (grass line), changes during Level 30 chase and Level 100 falling.

let youY = 0;
hintActive boolean

Whether the hint (red circle around apple) is currently being displayed. Set true by showHint(), false after hintDuration expires.

let hintActive = false;
hintTimer number

Timestamp (from millis()) when the hint was activated. Used to determine when hint should fade.

let hintTimer = 0;
currentLocationType string

Which environment type the apple is hidden in: 'HORIZON', 'SKY', 'SUN', 'MOON', 'CLOUD', 'SPACE', or 'HOUSE'. Determines background rendering.

let currentLocationType = 'HORIZON';
backgroundMusic object (p5.Oscillator)

A sine-wave oscillator playing at 50 Hz throughout the game. Created in setup(), provides ambient background sound.

let backgroundMusic = new p5.Oscillator('sine');
foundAppleSound object (p5.Oscillator)

A triangle-wave oscillator that plays a rising-then-falling frequency sweep when the apple is tapped.

let foundAppleSound = new p5.Oscillator('triangle');
zombie object (Zombie class)

Instance of the Zombie class spawned on Level 20. Moves toward the apple and can be damaged by bullets.

let zombie = null;
bullets array of Bullet objects

Array holding all active bullets fired by the apple on Level 20. Bullets move, collide with zombies, and are removed when off-screen.

let bullets = [];
fruit object (Fruit class)

Instance of the Fruit class spawned on Level 30. Chases the player until caught or the player reaches the exit.

let fruit = null;
targetAnimals array of Animal objects

Array of dogs, cats, and bunnies spawned during the Level 1000 cake event. Each can be hit with a cake to 'splat'.

let targetAnimals = [];
thrownCakes array of Cake objects

Array of cakes in flight during the Level 1000 cake event. Cakes move toward target animals and disappear on hit or off-screen.

let thrownCakes = [];
playerFalling boolean

Whether the player is currently falling from the airplane on Level 100. Set true by playerJump(), false on collision.

let playerFalling = false;
fallVelocity number

Current vertical speed of the falling player on Level 100. Increases each frame by GRAVITY constant.

let fallVelocity = 0;
poolX number

Horizontal center position of the pool on Level 100. Set randomly in setupLevel().

let poolX = 0;
poolY number

Vertical center position of the pool on Level 100. Set to height * 0.7 (ground level) in setupLevel().

let poolY = 0;
cakeEventActive boolean

Whether the Level 1000 cake-throwing finale is currently running. Set true by triggerCakeEvent().

let cakeEventActive = false;
hasCake boolean

Whether the player is currently holding a cake during the Level 1000 event. Set false when thrown, true when respawned.

let hasCake = false;

🔧 Potential Improvements (8)

Here are some ways this code could be enhanced:

BUG draw(), Level 20 zombie event

The variable playerCaucaught is used but never defined globally; it should be playerCaughtSound (a typo repeated in Level 30 fruit chase). This will cause a runtime error on those levels.

💡 Replace all instances of 'playerCaucaught' with 'playerCaughtSound' to match the global oscillator declared in setup().

BUG setupLevel(), Level 30 door placement

The exitDoor is created inside setupLevel() but can be reset to null if setupLevel() is called again without reaching level 30. If the player somehow re-enters Level 30 after beating it, the door may not exist.

💡 Add a guard in the Level 30 event logic to check if exitDoor exists before calling exitDoor.draw() and collision checks.

PERFORMANCE draw(), Level 1000 cake event

The cake event checks collision between every cake and every animal every frame using nested loops (O(n²) complexity). With many cakes and animals, this becomes expensive.

💡 Use spatial partitioning or a simpler check: only test the most recent cake against nearby animals, or reduce the number of active cakes allowed at once.

STYLE global scope

There are 50+ global variables, making the code hard to follow and prone to naming conflicts. Variables like lastShotTime, lastMouseX, and event flags are scattered throughout.

💡 Group related variables into objects, e.g., let gameState = { level, foundApple, ... }, let level10 = { triggered, foodPoliceActive, ... }, let input = { translateX, translateY, ... }.

FEATURE checkAppleInteraction()

The hitboxRadius is set to 100 pixels for 'testing' but is hardcoded. This makes the game trivially easy and the comment suggests it should be changed back to 15 before release.

💡 Make hitboxRadius a tuneable constant at the top of the file, scaled by level difficulty (e.g., map(level, 1, MAX_LEVELS, 30, 5)) so higher levels require more precision clicks.

BUG draw(), normal level win logic

The win condition checks 'foundApple && level !== 10 && level !== 20 && level !== 30' but doesn't exclude level 100. If foundApple is true on Level 100 (which it isn't normally), the win message would display incorrectly.

💡 Add '&& level !== 100' to the win condition, or restructure the conditional to use a single else-if chain.

PERFORMANCE setup(), sound initialization

All p5.sound oscillators are initialized and started in setup(), even if they won't be used until specific levels. This uses audio resources unnecessarily.

💡 Lazy-load oscillators—only create and start them when the corresponding event triggers (e.g., create zombieSpawnSound when entering Level 20).

STYLE draw(), event branching

Level 100 has its own massive if-block at the top with an early return, while Levels 10, 20, 30 are nested else-if blocks inside the main draw logic. This inconsistency makes the code harder to maintain.

💡 Extract each level's event logic into its own function: drawLevel100(), drawLevel10Event(), etc., called conditionally from draw().

🔄 Code Flow

Code flow showing setup, draw, setupLevel, nextLevel, checkAppleInteraction, mousedragged

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

graph TD start[Start] --> setup[setup] setup --> draw[draw loop] draw --> create-apple-buffer[create-apple-buffer] draw --> init-sounds[init-sounds] draw --> create-buttons[create-buttons] draw --> apple-rendering[apple-rendering] draw --> hint-circle-draw[hint-circle-draw] draw --> guard-clause[guard-clause] guard-clause -->|if not found| checkAppleInteraction[checkAppleInteraction] checkAppleInteraction --> distance-calculation[distance-calculation] distance-calculation -->|if hit| sound-playback[sound-playback] distance-calculation -->|if level 10| level-10-event[level-10-event] distance-calculation -->|if level 20| level-20-zombie-spawn[level-20-zombie-spawn] distance-calculation -->|if level 30| level-30-fruit-spawn[level-30-fruit-spawn] draw -->|every frame| mousedragged[mousedragged] mousedragged --> pan-accumulation[pan-accumulation] pan-accumulation --> pan-constraint[pan-constraint] draw --> completion-check[completion-check] completion-check -->|if complete| nextLevel[nextLevel] nextLevel --> level-increment[level-increment] nextLevel -->|if all levels cleared| cake-event-handler[cake-event-handler] nextLevel --> setupLevel[setupLevel] setupLevel --> reset-state[reset-state] setupLevel --> apple-location-switch[apple-location-switch] setupLevel --> difficulty-mapping[difficulty-mapping] setupLevel -->|if level 30| level-30-door-placement[level-30-door-placement] setupLevel -->|if level 100| level-100-airplane-setup[level-100-airplane-setup] setupLevel -->|if level 100| level-100-airplane-logic[level-100-airplane-logic] setupLevel -->|if level 10| level-10-police-event[level-10-police-event] setupLevel -->|if level 20| level-20-zombie-event[level-20-zombie-event] click setup href "#fn-setup" click draw href "#fn-draw" click create-apple-buffer href "#sub-create-apple-buffer" click init-sounds href "#sub-init-sounds" click create-buttons href "#sub-create-buttons" click apple-rendering href "#sub-apple-rendering" click hint-circle-draw href "#sub-hint-circle-draw" click guard-clause href "#sub-guard-clause" click checkAppleInteraction href "#fn-checkAppleInteraction" click distance-calculation href "#sub-distance-calculation" click sound-playback href "#sub-sound-playback" click level-10-event href "#sub-level-10-event" click level-20-zombie-spawn href "#sub-level-20-zombie-spawn" click level-30-fruit-spawn href "#sub-level-30-fruit-spawn" click mousedragged href "#fn-mousedragged" click pan-accumulation href "#sub-pan-accumulation" click pan-constraint href "#sub-pan-constraint" click completion-check href "#sub-completion-check" click nextLevel href "#fn-nextLevel" click level-increment href "#sub-level-increment" click cake-event-handler href "#sub-cake-event-handler" click setupLevel href "#fn-setupLevel" click reset-state href "#sub-reset-state" click apple-location-switch href "#sub-apple-location-switch" click difficulty-mapping href "#sub-difficulty-mapping" click level-30-door-placement href "#sub-level-30-door-placement" click level-100-airplane-setup href "#sub-level-100-airplane-setup" click level-100-airplane-logic href "#sub-level-100-airplane-logic" click level-10-police-event href "#sub-level-10-police-event" click level-20-zombie-event href "#sub-level-20-zombie-event"

❓ Frequently Asked Questions

What visual elements can users expect to see in the 100 happened p5.js sketch?

The sketch features a whimsical landscape where users search for an 'impossible apple' set against a backdrop that includes various interactive elements like a food police car and zombies.

How can users interact with the 100 happened sketch?

Users can pan the screen and tap on the apple to trigger events, such as encountering a food police car or a zombie, enhancing the experience with hints and challenges.

What creative coding techniques are showcased in the 100 happened sketch?

This sketch demonstrates various coding concepts such as state management, event handling, and dynamic graphics rendering, all contributing to an engaging interactive experience.

Preview

100 happened - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of 100 happened - Code flow showing setup, draw, setupLevel, nextLevel, checkAppleInteraction, mousedragged
Code Flow Diagram