buy a brainrot

This is an idle-game-style clicker where you rescue colorful brainrots walking down a runway, bring them back to your base to generate passive income, or sell them at a market for cash. The sketch combines collection mechanics, passive income systems, rarity tiers, and persistent save/load functionality.

🧪 Try This!

Experiment with the code by making these changes:

  1. Triple the spawn rate — Brainrots will appear much faster on the runway, making the game more chaotic and challenging
  2. Make the OG brainrot spawn every time — The rarest brainrot will appear regularly instead of once in a million times
  3. Double the rescue bonus — Each brainrot delivered earns $200 instead of $100, making early-game grinding faster
  4. Slow down the player
  5. Hold 16 brainrots instead of 8 — Increase the base's inventory capacity from 8 slots to 16
Prefer the full editor? Open it there →

📖 About This Sketch

This sketch creates a fully-featured idle game called Brainrot Rescue. Colorful creatures called brainrots walk along a runway at the bottom of the screen, and your job is to catch them before they disappear, bring them back to your base to earn passive income over time, or take them to the market to sell for one-time cash. The game is visually rich: each brainrot has a rarity tier (Common through OG) that determines its color glow, income per second, and market value. What makes this sketch impressive is its complexity—it manages 8 rarity tiers, custom income values per individual brainrot type, a scrollable index panel showing your collection, and a save/load system that encodes your entire game state as a base64 string.

The code is organized into major systems: game state (start vs. playing), player movement and interaction, brainrot spawning and behavior, base slot management for storage, passive income calculation, the market selling system, and drawing functions for all visual elements. By studying it, you will learn how to structure a multi-system game, implement passive income using frame-time deltas, create a click-to-sell mechanic on UI elements, manage camera-like scrolling in an overlay panel, and persist complex nested data through JSON serialization and encoding.

⚙️ How It Works

  1. When the sketch loads, setup() creates a canvas and calls initLayout() to position the base zone (left side), runway (bottom), and market zone (right side). initGame() spawns the first 4 brainrots on the runway, resets the player to the starting position, and sets gameState to 'start' so the title screen appears.
  2. The player presses any key or clicks to transition from the start screen to 'playing' mode, at which point the draw loop begins calling updateGame() every frame.
  3. Every frame, updatePassiveIncome() calculates how much time has passed and adds to each delivered brainrot's storedIncome based on its incomePerSecond value. handleInput() reads keyboard input (WASD or arrow keys) and sets the player's velocity. updatePlayer() moves the player and keeps any brainrot they're carrying stuck to their position.
  4. updateBrainrots() moves unrescued brainrots rightward along the runway; if they reach the end they despawn. handlePickup() checks if the player is touching a brainrot and grabs it. handleDelivery() checks if the player carrying a brainrot enters the base zone and assigns it to an available slot; if no slots are free, it drops it in the center and shows a warning.
  5. handleSelling() checks if the player carrying a brainrot touches the market zone and converts it to money instantly. maybeSpawnBrainrots() spawns new brainrots every 2 seconds if fewer than 10 are alive on the runway. collectFromBrainrots() checks if the player touches a brainrot in their base and collects its accumulated storedIncome as cash.
  6. All shapes (player, brainrots, base, runway, market, HUD) are drawn every frame. Pressing I toggles the scrollable Brainrot Index overlay. Clicking on a brainrot in the base opens a confirm dialog and allows manual selling. The save/load button lets you export the entire state as a base64 code or paste one back to restore progress.

🎓 Concepts You'll Learn

Game state managementPassive income systemsCollision detection and pickupRarity and loot tablesUI scrolling and overlaysJSON serialization and Base64 encodingAnimation and tweeningMouse and keyboard input

📝 Code Breakdown

setup()

setup() runs once when the sketch first loads. It prepares the canvas, UI, and game world layout.

function setup() {
  createCanvas(windowWidth, windowHeight);
  createSaveLoadButton();
  initLayout();
  initGame();
}
Line-by-line explanation (4 lines)
createCanvas(windowWidth, windowHeight);
Creates a full-window canvas that fills the browser and will resize if the window changes
createSaveLoadButton();
Adds an HTML button below the canvas that opens a dialog to save or load game progress
initLayout();
Calculates positions and sizes for the base zone, runway, and market zone based on the canvas size
initGame();
Initializes the player, brainrots array, money, and game state; spawns the first brainrots

draw()

draw() is p5.js's main loop—it runs 60 times per second. The gameState check lets you switch between different screens (title vs. gameplay) cleanly. On the start screen, we draw but don't update; when playing, we both update and draw every frame.

🔬 This 'start' block draws the static title screen. What happens if you remove the `return;` statement? Will both the title screen AND the game world draw at the same time?

  if (gameState === 'start') {
    drawBackgroundDecor();
    drawBase();
    drawRunway();
    drawMarket();
    drawPlayer();
    drawBrainrots();
    drawStartScreen();
    if (showIndex) drawIndexOverlay();
    return;
  }
function draw() {
  background(15, 14, 30);

  if (gameState === 'start') {
    drawBackgroundDecor();
    drawBase();
    drawRunway();
    drawMarket();
    drawPlayer();
    drawBrainrots();
    drawStartScreen();
    if (showIndex) drawIndexOverlay();
    return;
  }

  if (gameState === 'playing') updateGame();

  drawBackgroundDecor();
  drawBase();
  drawRunway();
  drawMarket();
  drawBrainrots();
  drawPlayer();
  drawHUD();
  if (showIndex) drawIndexOverlay();
}
Line-by-line explanation (9 lines)

🔧 Subcomponents:

conditional Start Screen State if (gameState === 'start') {

If the game hasn't started, draw the title screen and static world instead of updating

conditional Playing State Update if (gameState === 'playing') updateGame();

If the game is active, run all the gameplay logic (input, physics, income) once per frame

conditional Index Panel Display if (showIndex) drawIndexOverlay();

If the player pressed I, draw the scrollable collection index on top of the game

background(15, 14, 30);
Clears the canvas with a very dark blue/purple color every frame to prevent trails
if (gameState === 'start') {
Checks whether the game is on the title screen or actively playing
drawBackgroundDecor();
Draws a grid overlay for visual polish
drawBase(); drawRunway(); drawMarket();
Draws the three main world zones where gameplay happens
drawPlayer(); drawBrainrots();
Draws the player avatar and all brainrot creatures
drawStartScreen();
Draws the title, instructions, and 'press any key' prompt when gameState is 'start'
return;
Exits draw early if on start screen so gameplay logic doesn't run
if (gameState === 'playing') updateGame();
Runs all gameplay updates (input, collision, income) once per frame when actively playing
if (showIndex) drawIndexOverlay();
If the player opened the Index (pressed I), draw it as a panel on top of everything else

updateGame()

updateGame() is the 'heartbeat' of the game—it orchestrates all the systems in the right order. Each frame: income accumulates, input is read, positions update, collisions are checked, and new brainrots appear. This order matters: if you called handleDelivery() before handlePickup(), brainrots couldn't be delivered.

function updateGame() {
  updatePassiveIncome();
  handleInput();
  updatePlayer();
  updateBrainrots();
  handlePickup();
  handleDelivery();
  handleSelling();
  collectFromBrainrots();
  maybeSpawnBrainrots();
}
Line-by-line explanation (9 lines)
updatePassiveIncome();
Calculates elapsed time and adds income to each brainrot stored in the base
handleInput();
Reads keyboard presses (WASD or arrows) and sets the player's velocity vector
updatePlayer();
Moves the player by their velocity and keeps any carried brainrot attached to them
updateBrainrots();
Moves unrescued brainrots rightward and marks them despawned if they fall off the runway
handlePickup();
Checks if the player is touching a free brainrot and picks it up if not already carrying one
handleDelivery();
If the player carrying a brainrot enters the base, stores it in a slot and awards rescue money
handleSelling();
If the player carrying a brainrot touches the market zone, instantly sells it for its market value
collectFromBrainrots();
If the player walks over a brainrot in their base, collects its accumulated passive income as cash
maybeSpawnBrainrots();
Every 2 seconds, if fewer than 10 brainrots are alive on the runway, spawns a new one

updatePassiveIncome()

Passive income uses frame-time deltas (dt) rather than just adding a fixed amount per frame. This is crucial for idle games: if one frame takes 10ms and another takes 50ms, the income should still be smooth and predictable. The key is storing each brainrot's accumulated income separately so you can collect it later when you touch it.

🔬 This loop only adds income to brainrots where br.delivered is true. What happens if you remove the `!br.delivered ||` part of the condition? Will brainrots on the runway start earning income before you deliver them?

  for (let br of brainrots) {
    if (!br.delivered || br.despawned || br.sold || br.typeIndex == null) continue;
    const inc = brainrotTypes[br.typeIndex].incomePerSecond;
    br.storedIncome = (br.storedIncome || 0) + inc * dt;
  }
function updatePassiveIncome() {
  const now = millis();
  const dt = (now - lastPassiveTime) / 1000;
  lastPassiveTime = now;
  if (dt <= 0) return;

  for (let br of brainrots) {
    if (!br.delivered || br.despawned || br.sold || br.typeIndex == null) continue;
    const inc = brainrotTypes[br.typeIndex].incomePerSecond;
    br.storedIncome = (br.storedIncome || 0) + inc * dt;
  }
}
Line-by-line explanation (8 lines)

🔧 Subcomponents:

calculation Time Delta Calculation const dt = (now - lastPassiveTime) / 1000;

Computes how many seconds have elapsed since the last frame to ensure income scales with real time, not frame count

conditional Filter Valid Brainrots if (!br.delivered || br.despawned || br.sold || br.typeIndex == null) continue;

Skips brainrots that haven't been delivered to the base, have been sold, or don't have a valid type

for-loop Income Loop for (let br of brainrots) { ... }

Iterates through all brainrots and adds their per-second income multiplied by elapsed time to their stored balance

const now = millis();
Gets the current time in milliseconds since the sketch started
const dt = (now - lastPassiveTime) / 1000;
Calculates the time elapsed in seconds by subtracting the last update time from now and dividing by 1000
lastPassiveTime = now;
Records the current time so the next call to updatePassiveIncome() can calculate the delta from this point
if (dt <= 0) return;
Exits early if no time has passed (prevents errors and redundant calculations)
for (let br of brainrots) {
Loops through every brainrot in the array
if (!br.delivered || br.despawned || br.sold || br.typeIndex == null) continue;
Skips this brainrot if it's not in the base, has been removed, or has no valid type
const inc = brainrotTypes[br.typeIndex].incomePerSecond;
Looks up this brainrot's income-per-second value from the brainrotTypes array
br.storedIncome = (br.storedIncome || 0) + inc * dt;
Adds the income (income/sec × elapsed seconds) to the brainrot's accumulated storedIncome balance

handleInput()

This function reads continuous keypresses (not just single keyPress events) and converts them into a velocity vector. The normalization step is key: if the player holds two keys (like up and right), the raw direction would be (-1, 1) with length √2. Dividing by that length makes (-1/√2, 1/√2), so diagonal motion isn't 1.4× faster. This is the standard way to handle 2D movement input.

🔬 This normalization ensures diagonal movement is the same speed as straight movement. What happens if you comment out these 5 lines? Will diagonal movement become faster because you're adding both dx and dy without dividing by the vector length?

  if (dx || dy) {
    const len = sqrt(dx * dx + dy * dy);
    dx /= len;
    dy /= len;
  }
function handleInput() {
  let dx = 0;
  let dy = 0;
  if (keyIsDown(LEFT_ARROW) || keyIsDown(65)) dx -= 1;
  if (keyIsDown(RIGHT_ARROW) || keyIsDown(68)) dx += 1;
  if (keyIsDown(UP_ARROW) || keyIsDown(87)) dy -= 1;
  if (keyIsDown(DOWN_ARROW) || keyIsDown(83)) dy += 1;

  if (dx || dy) {
    const len = sqrt(dx * dx + dy * dy);
    dx /= len;
    dy /= len;
  }

  player.vx = dx * player.speed;
  player.vy = dy * player.speed;
}
Line-by-line explanation (9 lines)

🔧 Subcomponents:

conditional Arrow Key Input if (keyIsDown(LEFT_ARROW) || keyIsDown(65)) dx -= 1;

Checks if either the left arrow or A key is pressed and marks left movement

conditional WASD Key Input if (keyIsDown(UP_ARROW) || keyIsDown(87)) dy -= 1;

Checks if either the up arrow or W key is pressed and marks up movement

calculation Normalize Direction Vector const len = sqrt(dx * dx + dy * dy); dx /= len; dy /= len;

Ensures diagonal movement is no faster than straight movement by normalizing the direction vector to length 1

let dx = 0; let dy = 0;
Creates direction accumulators for horizontal and vertical movement
if (keyIsDown(LEFT_ARROW) || keyIsDown(65)) dx -= 1;
If left arrow or A key is held, subtract 1 from dx (move left). p5.js's keyCode 65 is 'A'
if (keyIsDown(RIGHT_ARROW) || keyIsDown(68)) dx += 1;
If right arrow or D key is held, add 1 to dx (move right). keyCode 68 is 'D'
if (keyIsDown(UP_ARROW) || keyIsDown(87)) dy -= 1;
If up arrow or W key is held, subtract 1 from dy (move up). keyCode 87 is 'W'
if (keyIsDown(DOWN_ARROW) || keyIsDown(83)) dy += 1;
If down arrow or S key is held, add 1 to dy (move down). keyCode 83 is 'S'
if (dx || dy) {
Only normalize if the player is actually pressing a direction key
const len = sqrt(dx * dx + dy * dy);
Calculates the length of the (dx, dy) vector using the Pythagorean theorem
dx /= len; dy /= len;
Divides both dx and dy by the vector length, scaling it to length 1 (a unit vector)
player.vx = dx * player.speed; player.vy = dy * player.speed;
Multiplies the normalized direction by the player's speed to get the final velocity each frame

updatePlayer()

updatePlayer() applies the velocity to position, clamps it to stay on-screen, and keeps any carried brainrot stuck to the player's position. The carried position formula uses subtraction: y - player.r - brainrot.r positions it above, and the * 0.6 factor gives it a slight offset that looks natural.

function updatePlayer() {
  player.x = constrain(player.x + (player.vx || 0), 30, width - 30);
  player.y = constrain(player.y + (player.vy || 0), 30, height - 30);

  if (player.carrying) {
    player.carrying.x = player.x;
    player.carrying.y = player.y - player.r - player.carrying.r * 0.6;
  }
}
Line-by-line explanation (5 lines)

🔧 Subcomponents:

calculation Position Update with Boundaries player.x = constrain(player.x + (player.vx || 0), 30, width - 30);

Moves the player horizontally and keeps them inside the canvas with a 30-pixel margin

conditional Attach Carried Brainrot if (player.carrying) { player.carrying.x = player.x; ... }

If the player is holding a brainrot, updates its position to stick to the player's side

player.x = constrain(player.x + (player.vx || 0), 30, width - 30);
Adds the horizontal velocity to x, then clamps it to stay between 30 and width-30 (preventing escape off-screen)
player.y = constrain(player.y + (player.vy || 0), 30, height - 30);
Adds the vertical velocity to y, then clamps it to stay between 30 and height-30
if (player.carrying) {
Checks if the player is currently holding a brainrot
player.carrying.x = player.x;
Sets the brainrot's x position equal to the player's x so it stays centered on them
player.carrying.y = player.y - player.r - player.carrying.r * 0.6;
Positions the brainrot above the player by moving it up by the player's radius plus a scaled portion of the brainrot's radius

updateBrainrots()

updateBrainrots() moves all unrescued brainrots rightward along the runway. The wigglePhase accumulates each frame and is used by drawBrainrots() to create a bobbing animation using sin(). When a brainrot reaches the end, it's marked despawned so it won't be drawn or updated anymore, and a new one can spawn in its place.

🔬 Brainrots despawn when br.x - br.r > runwayEndX. What happens if you change this to just br.x > runwayEndX (without subtracting the radius)? Will they disappear sooner or later?

  for (let br of brainrots) {
    if (br.delivered || br.carried || br.despawned || br.sold) continue;
    br.x += br.speed;
    br.wigglePhase += 0.08;
    if (br.x - br.r > runwayEndX) br.despawned = true;
  }
function updateBrainrots() {
  const runwayEndX = runway.x + runway.w;

  for (let br of brainrots) {
    if (br.delivered || br.carried || br.despawned || br.sold) continue;
    br.x += br.speed;
    br.wigglePhase += 0.08;
    if (br.x - br.r > runwayEndX) br.despawned = true;
  }
}
Line-by-line explanation (6 lines)

🔧 Subcomponents:

conditional Skip Non-Running Brainrots if (br.delivered || br.carried || br.despawned || br.sold) continue;

Only moves brainrots that are still running on the runway; skips those already caught, stored, or removed

conditional Despawn at Runway End if (br.x - br.r > runwayEndX) br.despawned = true;

Removes the brainrot if it reaches the end of the runway (when its left edge passes the runway boundary)

const runwayEndX = runway.x + runway.w;
Calculates the x coordinate where the runway ends
for (let br of brainrots) {
Loops through all brainrots in the array
if (br.delivered || br.carried || br.despawned || br.sold) continue;
Skips brainrots that are not on the runway (already rescued, carrying, despawned, or sold)
br.x += br.speed;
Moves the brainrot rightward by its speed value (typically 1.0 to 1.6 pixels per frame)
br.wigglePhase += 0.08;
Increments the animation phase; this is used in drawBrainrots() to create a vertical bobbing motion
if (br.x - br.r > runwayEndX) br.despawned = true;
If the brainrot's left edge (x - r) passes the runway's right boundary, mark it as despawned (removed)

handlePickup()

handlePickup() is a classic collision check: it uses p5.js's dist() function to measure the distance between two points and compares it to the sum of their radii (circle collision). The `break` ensures the player picks up only one brainrot per frame, even if they're overlapping several.

function handlePickup() {
  if (player.carrying) return;
  for (let br of brainrots) {
    if (br.rescued || br.carried || br.despawned || br.sold) continue;
    if (dist(player.x, player.y, br.x, br.y) < player.r + br.r) {
      br.carried = true;
      player.carrying = br;
      break;
    }
  }
}
Line-by-line explanation (6 lines)

🔧 Subcomponents:

conditional Prevent Multiple Carries if (player.carrying) return;

Exits early if the player is already carrying a brainrot

for-loop Pickup Collision Loop for (let br of brainrots) { if (dist(...) < ...) { ... break; } }

Loops through brainrots and picks up the first one close enough to the player

if (player.carrying) return;
If the player is already holding a brainrot, exit the function immediately (can't carry two)
for (let br of brainrots) {
Loops through all brainrots in the array
if (br.rescued || br.carried || br.despawned || br.sold) continue;
Skips brainrots that are not pickupable (already rescued, being carried, removed, or sold)
if (dist(player.x, player.y, br.x, br.y) < player.r + br.r) {
Checks if the distance between player and brainrot is less than the sum of their radii (overlap)
br.carried = true; player.carrying = br;
Sets both the brainrot and player to reflect that the player is now carrying it
break;
Exits the loop early so only one brainrot is picked up per frame

handleDelivery()

handleDelivery() implements a 'drop-off zone': when the player carrying a brainrot enters the base, it tries to place it in a free slot. If no slot is available (base full), it drops the brainrot in the center and shows a warning. This teaches grid-based inventory systems and rectangle collision detection using the pointInRect() helper.

🔬 If all 8 slots are full, the brainrot drops in the center and a warning appears for 3000ms. What if you change that 3000 to 500? Will the warning disappear faster?

  const slotIndex = baseSlots.findIndex((s) => !s.occupied);
  if (slotIndex >= 0) {
    const slot = baseSlots[slotIndex];
    slot.occupied = true;
    br.x = slot.x;
    br.y = slot.y;
    br.slotIndex = slotIndex;
  } else {
    br.x = baseZone.x + baseZone.w / 2;
    br.y = baseZone.y + baseZone.h / 2;
    br.slotIndex = null;
    noSpaceUntil = millis() + 3000;
  }
function handleDelivery() {
  if (!player.carrying) return;
  if (!pointInRect(player.x, player.y, baseZone)) return;

  const br = player.carrying;
  const slotIndex = baseSlots.findIndex((s) => !s.occupied);
  if (slotIndex >= 0) {
    const slot = baseSlots[slotIndex];
    slot.occupied = true;
    br.x = slot.x;
    br.y = slot.y;
    br.slotIndex = slotIndex;
  } else {
    br.x = baseZone.x + baseZone.w / 2;
    br.y = baseZone.y + baseZone.h / 2;
    br.slotIndex = null;
    noSpaceUntil = millis() + 3000;
  }

  br.rescued = true;
  br.delivered = true;
  br.carried = false;
  player.carrying = null;
  rescuedCount++;
  money += brainrotReward;
}
Line-by-line explanation (12 lines)

🔧 Subcomponents:

conditional Early Exit if Not Carrying if (!player.carrying) return;

Exits if the player isn't holding a brainrot

conditional Base Zone Collision Check if (!pointInRect(player.x, player.y, baseZone)) return;

Exits if the player is not inside the base zone

conditional Slot Assignment or Overflow if (slotIndex >= 0) { ... } else { ... }

Places the brainrot in an empty slot if available, or in the center of the base if full

if (!player.carrying) return;
Exits early if the player is not holding a brainrot
if (!pointInRect(player.x, player.y, baseZone)) return;
Exits early if the player is not inside the base zone (using a rectangle collision helper)
const br = player.carrying;
Stores a reference to the brainrot being carried
const slotIndex = baseSlots.findIndex((s) => !s.occupied);
Finds the first unoccupied base slot; returns -1 if all 8 slots are full
if (slotIndex >= 0) {
If an empty slot was found
const slot = baseSlots[slotIndex]; slot.occupied = true;
Gets the slot object and marks it as occupied
br.x = slot.x; br.y = slot.y; br.slotIndex = slotIndex;
Places the brainrot at the slot's position and records which slot holds it
} else {
If no empty slot was found (base is full)
br.x = baseZone.x + baseZone.w / 2; br.y = baseZone.y + baseZone.h / 2;
Places the brainrot in the center of the base as overflow
noSpaceUntil = millis() + 3000;
Sets a timer so a 'no space' warning appears for 3 seconds
br.rescued = true; br.delivered = true; br.carried = false;
Updates brainrot state flags: it's now in the base, not being carried
player.carrying = null; rescuedCount++; money += brainrotReward;
Clears the player's carrying reference, increments the rescue count, and awards the rescue bonus money

handleSelling()

handleSelling() is simpler than handleDelivery() because selling is instant and one-time: the brainrot is marked sold and despawned immediately. No slots are involved, and no passive income accumulates. This creates a strategic choice: deliver for passive income vs. sell for instant cash.

function handleSelling() {
  if (!player.carrying) return;
  if (!pointInRect(player.x, player.y, marketZone)) return;

  const br = player.carrying;
  const t = brainrotTypes[br.typeIndex];
  const baseValue = t ? t.marketValue : brainrotReward;
  money += baseValue;
  br.carried = false;
  br.sold = true;
  br.despawned = true;
  player.carrying = null;
}
Line-by-line explanation (7 lines)

🔧 Subcomponents:

conditional Market Zone Collision if (!pointInRect(player.x, player.y, marketZone)) return;

Exits if the player is not inside the market zone

if (!player.carrying) return;
Exits early if the player is not holding a brainrot
if (!pointInRect(player.x, player.y, marketZone)) return;
Exits early if the player is not inside the market zone rectangle
const br = player.carrying; const t = brainrotTypes[br.typeIndex];
Gets the brainrot and looks up its type information
const baseValue = t ? t.marketValue : brainrotReward;
Uses the brainrot's market value if it has a valid type, otherwise falls back to the rescue bonus
money += baseValue;
Adds the market value to the player's money
br.carried = false; br.sold = true; br.despawned = true;
Marks the brainrot as sold and despawned so it won't be drawn or updated
player.carrying = null;
Clears the player's carrying reference

collectFromBrainrots()

collectFromBrainrots() is called every frame and checks if the player is touching any brainrot in the base. When they are, it collects all whole dollars from that brainrot's storedIncome. The fractional cents remain for later, so income doesn't get wasted. It also triggers a flash effect in the HUD to give visual feedback.

function collectFromBrainrots() {
  for (let br of brainrots) {
    if (!br.delivered || br.despawned || br.sold || br.typeIndex == null) continue;
    if (dist(player.x, player.y, br.x, br.y) < player.r + br.r) {
      const available = Math.floor(br.storedIncome || 0);
      if (available > 0) {
        money += available;
        br.storedIncome = (br.storedIncome || 0) - available;
        lastCollectAmount = available;
        collectFlashUntil = millis() + 800;
      }
    }
  }
}
Line-by-line explanation (8 lines)

🔧 Subcomponents:

for-loop Collect Loop Over Delivered Brainrots for (let br of brainrots) { if (!br.delivered ...) continue; ... }

Iterates through brainrots and collects income from those in the base

conditional Proximity Collision if (dist(player.x, player.y, br.x, br.y) < player.r + br.r) {

Collects income only when the player is close enough to touch the brainrot

for (let br of brainrots) {
Loops through all brainrots
if (!br.delivered || br.despawned || br.sold || br.typeIndex == null) continue;
Skips brainrots that are not valid income sources (not in base, removed, or have no type)
if (dist(player.x, player.y, br.x, br.y) < player.r + br.r) {
Checks if the player is touching the brainrot (distance less than radii sum)
const available = Math.floor(br.storedIncome || 0);
Gets the whole-dollar amount of accumulated income (rounds down)
if (available > 0) {
Only collects if there's at least $1 to take
money += available;
Adds the collected amount to the player's total money
br.storedIncome = (br.storedIncome || 0) - available;
Subtracts the collected amount from the brainrot's stored income, leaving any fractional cents
lastCollectAmount = available; collectFlashUntil = millis() + 800;
Records the amount for display and sets a flash timer so the HUD shows '+$X' for 800ms

maybeSpawnBrainrots()

maybeSpawnBrainrots() is a rate-limited spawner: it prevents brainrots from spawning too fast or too many at once by checking both a count limit and a time interval. This is a common pattern in games for managing difficulty and performance.

function maybeSpawnBrainrots() {
  const alive = brainrots.filter((b) => !b.rescued && !b.despawned && !b.sold).length;
  if (alive < maxBrainrotsOnMap && millis() - lastSpawnTime > spawnInterval) {
    spawnBrainrot();
    lastSpawnTime = millis();
  }
}
Line-by-line explanation (3 lines)

🔧 Subcomponents:

calculation Count Alive Brainrots const alive = brainrots.filter((b) => !b.rescued && !b.despawned && !b.sold).length;

Counts how many brainrots are currently on the runway (not rescued, removed, or sold)

conditional Spawn Rate-Limit if (alive < maxBrainrotsOnMap && millis() - lastSpawnTime > spawnInterval) {

Only spawns a new brainrot if fewer than 10 are alive and at least 2 seconds have passed

const alive = brainrots.filter((b) => !b.rescued && !b.despawned && !b.sold).length;
Uses filter() to count brainrots that are still running on the runway (not rescued, despawned, or sold)
if (alive < maxBrainrotsOnMap && millis() - lastSpawnTime > spawnInterval) {
Spawns a new brainrot only if (1) fewer than maxBrainrotsOnMap (10) are alive, and (2) at least spawnInterval (2000ms) has passed since the last spawn
spawnBrainrot(); lastSpawnTime = millis();
Calls spawnBrainrot() to add a new brainrot to the array, and updates lastSpawnTime to now

pickBrainrotTypeIndex()

This function implements weighted random selection—a loot table system. Each rarity has a probability weight, and the function picks a rarity proportionally, then randomly selects one brainrot type from that rarity. This is the standard way games control the rarity of drops.

🔬 This array controls spawn probability. If you change OG's weight from 0.0001 to 10, will OG brainrots appear much more often? Try it and see if you can catch a Strawberry Elephant!

  const rarityWeights = [
    { rarity: 'Common', weight: 70 },
    { rarity: 'Rare', weight: 67 },
    { rarity: 'Epic', weight: 41 },
    { rarity: 'Legendary', weight: 35 },
    { rarity: 'Mythic', weight: 23 },
    { rarity: 'Brainrot God', weight: 1 },
    { rarity: 'Secret', weight: 0.1 },
    { rarity: 'OG', weight: 0.0001 }
  ];
function pickBrainrotTypeIndex() {
  const rarityWeights = [
    { rarity: 'Common', weight: 70 },
    { rarity: 'Rare', weight: 67 },
    { rarity: 'Epic', weight: 41 },
    { rarity: 'Legendary', weight: 35 },
    { rarity: 'Mythic', weight: 23 },
    { rarity: 'Brainrot God', weight: 1 },
    { rarity: 'Secret', weight: 0.1 },
    { rarity: 'OG', weight: 0.0001 }
  ];

  let total = 0;
  for (let rw of rarityWeights) total += rw.weight;

  let r = random(total);
  let rarity = 'Common';
  for (let rw of rarityWeights) {
    if (r < rw.weight) {
      rarity = rw.rarity;
      break;
    }
    r -= rw.weight;
  }

  const candidates = brainrotTypes
    .map((t, i) => ({ t, i }))
    .filter((entry) => entry.t.rarity === rarity);
  if (candidates.length === 0) return 0;
  return candidates[floor(random(candidates.length))].i;
}
Line-by-line explanation (7 lines)

🔧 Subcomponents:

calculation Weighted Rarity Selection let r = random(total); for (let rw of rarityWeights) { if (r < rw.weight) ... }

Picks a rarity tier based on weighted probabilities (Commons are much more likely than OGs)

calculation Filter Brainrot Candidates const candidates = brainrotTypes.map(...).filter(...)

Finds all brainrot types matching the selected rarity and picks one at random

const rarityWeights = [ ... ];
Defines how likely each rarity tier is to spawn. Commons are 70x more likely than Rares, which are 670x more likely than OGs
let total = 0; for (let rw of rarityWeights) total += rw.weight;
Sums all weights to get the total weight (used for normalized random selection)
let r = random(total);
Picks a random number between 0 and total
let rarity = 'Common'; for (let rw of rarityWeights) { if (r < rw.weight) { rarity = rw.rarity; break; } r -= rw.weight; }
Walks through the rarities in order, subtracting each weight from r. When r becomes negative, the current rarity is selected. This is the 'weighted random' algorithm.
const candidates = brainrotTypes.map((t, i) => ({ t, i })).filter((entry) => entry.t.rarity === rarity);
Finds all brainrot types matching the selected rarity (e.g., all 4 Legendary brainrots)
if (candidates.length === 0) return 0;
If no candidates exist (shouldn't happen), returns 0 as a safe default
return candidates[floor(random(candidates.length))].i;
Picks a random candidate from the matching brainrot types and returns its index

spawnBrainrot()

spawnBrainrot() creates and adds a new brainrot object to the game. Every spawned brainrot gets a random size, speed, and type (using the weighted loot table). The state flags are all set to false so the brainrot starts on the runway as an unrescued target.

function spawnBrainrot() {
  const r = random(12, 18);
  brainrots.push({
    x: runway.x + r * 1.1,
    y: runway.y + runway.h / 2,
    r,
    carried: false,
    rescued: false,
    delivered: false,
    despawned: false,
    sold: false,
    wigglePhase: random(TWO_PI),
    speed: random(1.0, 1.6),
    typeIndex: pickBrainrotTypeIndex(),
    storedIncome: 0,
    slotIndex: null
  });
}
Line-by-line explanation (8 lines)
const r = random(12, 18);
Picks a random radius between 12 and 18 pixels so brainrots vary in size
brainrots.push({ ... });
Adds a new brainrot object to the brainrots array
x: runway.x + r * 1.1, y: runway.y + runway.h / 2,
Positions the brainrot at the left edge of the runway, vertically centered
r,
Stores the radius for drawing and collision detection
carried: false, rescued: false, delivered: false, despawned: false, sold: false,
Initializes all state flags to false; the brainrot starts as unrescued and walking on the runway
wigglePhase: random(TWO_PI), speed: random(1.0, 1.6),
Picks random animation phase and movement speed so brainrots don't all look identical
typeIndex: pickBrainrotTypeIndex(),
Uses the weighted loot table to pick a rarity and then a specific brainrot type
storedIncome: 0, slotIndex: null
Initializes income tracking and slot assignment to empty/null states

drawPlayer()

drawPlayer() uses push/pop to isolate transformations, and translate() to draw the player relative to their position. The eyes, body, and direction arrow are all drawn in local coordinates. This is the foundation of hierarchical drawing: each object has its own local space.

function drawPlayer() {
  push();
  translate(player.x, player.y);

  if (player.carrying) {
    noFill();
    stroke(120, 240, 255, 180);
    strokeWeight(3);
    circle(0, 0, player.r * 2.6);
  }

  noStroke();
  fill(80, 140, 255);
  circle(0, 0, player.r * 2);

  fill(240, 240, 255);
  circle(0, -4, player.r * 1.2);

  fill(20);
  circle(-4, -5, 3);
  circle(4, -5, 3);

  let angle = atan2(player.vy || 0, player.vx || 1);
  if (player.vx === 0 && player.vy === 0) angle = 0;
  push();
  rotate(angle);
  fill(255, 220, 120);
  triangle(player.r * 0.6, 0, player.r * 0.1, -3, player.r * 0.1, 3);
  pop();

  pop();
}
Line-by-line explanation (10 lines)

🔧 Subcomponents:

conditional Carrying Highlight Ring if (player.carrying) { circle(0, 0, player.r * 2.6); }

Draws a glowing circle around the player when carrying a brainrot, providing visual feedback

calculation Direction Arrow let angle = atan2(player.vy || 0, player.vx || 1); ... rotate(angle); ... triangle(...);

Draws a small triangle that points in the direction the player is moving

push();
Saves the current transform state so translations don't affect other drawings
translate(player.x, player.y);
Moves the origin to the player's position so all shapes draw relative to them
if (player.carrying) { noFill(); stroke(...); circle(0, 0, player.r * 2.6); }
If the player is carrying a brainrot, draws a cyan ring around them as a visual indicator
noStroke(); fill(80, 140, 255); circle(0, 0, player.r * 2);
Draws the main player body as a blue circle
fill(240, 240, 255); circle(0, -4, player.r * 1.2);
Draws a white head/face circle above the body
fill(20); circle(-4, -5, 3); circle(4, -5, 3);
Draws two black circles as eyes
let angle = atan2(player.vy || 0, player.vx || 1);
Calculates the angle of movement using atan2 (returns angle from -π to π)
if (player.vx === 0 && player.vy === 0) angle = 0;
If the player isn't moving, default the angle to 0 (facing right)
push(); rotate(angle); ... triangle(...); pop();
Saves state, rotates to face the movement direction, draws a yellow arrow/mouth triangle, then restores state
pop();
Restores the original transform state so subsequent drawings are unaffected

drawBrainrots()

drawBrainrots() demonstrates rarity-based color theming: each rarity has predefined glow and body colors stored in objects. Special rarities like Brainrot God override these with dynamic colors. The bobbing animation uses sin() and the accumulated wigglePhase to create smooth periodic motion. The function also draws the brainrot's name using p5.js's text() function.

🔬 These three circles form the brainrot's body. The first two are the top (eyes/head), the third is the bottom (body). What happens if you add a fourth circle, like: circle(0, br.r * 0.5, br.r * 0.9)? Will it add a tail?

    circle(-br.r * 0.3, -br.r * 0.3, br.r * 1.2);
    circle(br.r * 0.2, -br.r * 0.3, br.r * 1.2);
    circle(0, br.r * 0.1, br.r * 1.4);
function drawBrainrots() {
  for (let br of brainrots) {
    if (br.despawned || br.sold) continue;

    const type = brainrotTypes[br.typeIndex];
    const rarity = type ? type.rarity : 'Common';

    let glowCol = rarityGlowColors[rarity];
    let bodyCol = rarityBodyColors[rarity];

    if (rarity === 'Brainrot God') {
      const [rr, gg, bb] = getRainbowColor();
      glowCol = [rr, gg, bb];
      bodyCol = [rr, gg, bb];
    }

    push();
    translate(br.x, br.y);

    const bob = br.delivered ? 0 : sin(br.wigglePhase) * 2;
    translate(0, bob);

    if (glowCol) {
      noStroke();
      fill(glowCol[0], glowCol[1], glowCol[2], 90);
      circle(0, 0, br.r * 2.8);
    }

    const bc = bodyCol || [230, 140, 255];
    fill(bc[0], bc[1], bc[2]);
    circle(-br.r * 0.3, -br.r * 0.3, br.r * 1.2);
    circle(br.r * 0.2, -br.r * 0.3, br.r * 1.2);
    circle(0, br.r * 0.1, br.r * 1.4);

    stroke(180, 40, 240);
    strokeWeight(2);
    noFill();
    arc(0, -br.r * 0.2, br.r * 1.4, br.r * 0.8, PI, TWO_PI);
    arc(0, br.r * 0.1, br.r * 1.4, br.r * 0.8, 0, PI);

    if (type) {
      noStroke();
      fill(255);
      textAlign(CENTER, BOTTOM);
      textSize(10);
      text(type.name, 0, -br.r * 1.8);
    }

    pop();
  }
}
Line-by-line explanation (13 lines)

🔧 Subcomponents:

calculation Rarity Color Selection let glowCol = rarityGlowColors[rarity]; let bodyCol = rarityBodyColors[rarity];

Looks up the glow and body colors for the brainrot's rarity tier

conditional Rainbow for Brainrot God if (rarity === 'Brainrot God') { const [rr, gg, bb] = getRainbowColor(); ... }

Overrides colors with a dynamic rainbow effect for Brainrot God rarity

calculation Bobbing Motion const bob = br.delivered ? 0 : sin(br.wigglePhase) * 2; translate(0, bob);

Makes brainrots on the runway bob up and down; stops bobbing once delivered

for (let br of brainrots) {
Loops through all brainrots in the array
if (br.despawned || br.sold) continue;
Skips brainrots that have been removed or sold (so they don't draw)
const type = brainrotTypes[br.typeIndex]; const rarity = type ? type.rarity : 'Common';
Looks up the brainrot's type and extracts its rarity tier
let glowCol = rarityGlowColors[rarity]; let bodyCol = rarityBodyColors[rarity];
Looks up the color scheme for this rarity from predefined objects
if (rarity === 'Brainrot God') { const [rr, gg, bb] = getRainbowColor(); glowCol = [rr, gg, bb]; bodyCol = [rr, gg, bb]; }
Special case: Brainrot God brainrots use a dynamic rainbow color instead of a fixed one
push(); translate(br.x, br.y);
Saves state and moves the origin to the brainrot's position
const bob = br.delivered ? 0 : sin(br.wigglePhase) * 2;
Calculates vertical bobbing for brainrots on the runway (0 if delivered to base)
translate(0, bob);
Applies the bobbing offset
if (glowCol) { noStroke(); fill(...); circle(0, 0, br.r * 2.8); }
Draws a semi-transparent colored glow around the brainrot
const bc = bodyCol || [230, 140, 255]; fill(bc[0], bc[1], bc[2]); circle(...); circle(...); circle(...);
Draws three circles in the brainrot's body color to make its silhouette (head, eyes, body)
stroke(180, 40, 240); strokeWeight(2); noFill(); arc(...); arc(...);
Draws two arcs (half-circles) in magenta to create a smile-like mouth
if (type) { ... text(type.name, 0, -br.r * 1.8); }
Draws the brainrot's name above its head in white text
pop();
Restores the original transform state

drawBase()

drawBase() demonstrates UI design with layered rectangles. The shadow layer adds depth, the main rectangle is the primary container, and the slot circles show inventory. The empty slots are highlighted with colored outlines to provide visual affordance (showing what's clickable/available).

function drawBase() {
  push();
  const b = baseZone;

  noStroke();
  fill(60, 200, 120, 60);
  rect(b.x - 14, b.y - 14, b.w + 28, b.h + 28, 20);

  fill(40, 120, 80);
  rect(b.x, b.y, b.w, b.h, 18);

  for (let slot of baseSlots) {
    fill(20, 70, 50, 120);
    noStroke();
    circle(slot.x, slot.y, 22);
    if (!slot.occupied) {
      stroke(110, 255, 180, 160);
      noFill();
      circle(slot.x, slot.y, 20);
    }
  }

  noStroke();
  fill(110, 255, 180);
  rect(b.x + 10, b.y + b.h * 0.25, b.w - 20, b.h * 0.5, 12);

  fill(0, 40, 20, 200);
  rect(b.x, b.y - 28, b.w, 22, 12);
  fill(220, 255, 235);
  textAlign(CENTER, CENTER);
  textSize(13);
  text('BASE', b.x + b.w / 2, b.y - 17);
  pop();
}
Line-by-line explanation (10 lines)

🔧 Subcomponents:

calculation Layered Background fill(60, 200, 120, 60); rect(...); fill(40, 120, 80); rect(...);

Draws two rounded rectangles to create a base with shadow/depth effect

for-loop Slot Rendering Loop for (let slot of baseSlots) { ... if (!slot.occupied) { ... } }

Draws each of the 8 base slots, highlighting empty ones

push(); const b = baseZone;
Saves state and stores the base zone in a short variable for convenience
noStroke(); fill(60, 200, 120, 60); rect(b.x - 14, b.y - 14, b.w + 28, b.h + 28, 20);
Draws a semi-transparent green shadow rectangle slightly larger than the base
fill(40, 120, 80); rect(b.x, b.y, b.w, b.h, 18);
Draws the main base rectangle in darker green with rounded corners
for (let slot of baseSlots) {
Loops through all 8 base slots
fill(20, 70, 50, 120); noStroke(); circle(slot.x, slot.y, 22);
Draws a dark green circle at each slot position to represent the slot background
if (!slot.occupied) { stroke(110, 255, 180, 160); noFill(); circle(slot.x, slot.y, 20); }
If the slot is empty, draws a cyan outline circle to indicate it's available
noStroke(); fill(110, 255, 180); rect(b.x + 10, b.y + b.h * 0.25, b.w - 20, b.h * 0.5, 12);
Draws a horizontal cyan bar in the center of the base for visual polish
fill(0, 40, 20, 200); rect(b.x, b.y - 28, b.w, 22, 12);
Draws a dark green label bar above the base
fill(220, 255, 235); textAlign(CENTER, CENTER); textSize(13); text('BASE', ...);
Draws the word 'BASE' in light green centered on the label bar
pop();
Restores the original drawing state

drawRunway()

drawRunway() uses repeating triangles to communicate direction and flow. The cyan left edge (start) and red right edge (end) provide intuitive color coding. This is a good example of using visual design to guide the player's understanding of game mechanics.

function drawRunway() {
  push();
  const r = runway;

  noStroke();
  fill(40, 40, 80);
  rect(r.x, r.y, r.w, r.h, 12);

  fill(90, 90, 150);
  rect(r.x, r.y + r.h * 0.35, r.w, 4);

  const segmentW = 26;
  fill(120, 150, 240, 160);
  for (let x = r.x; x < r.x + r.w; x += segmentW * 1.4) {
    triangle(
      x,
      r.y + r.h * 0.25,
      x + segmentW,
      r.y + r.h * 0.25,
      x + segmentW * 0.7,
      r.y + r.h * 0.1
    );
  }

  fill(70, 220, 180, 160);
  rect(r.x - 4, r.y, 4, r.h);
  fill(220, 80, 80, 160);
  rect(r.x + r.w, r.y, 4, r.h);

  pop();
}
Line-by-line explanation (7 lines)

🔧 Subcomponents:

for-loop Triangle Pattern Loop for (let x = r.x; x < r.x + r.w; x += segmentW * 1.4) { triangle(...); }

Draws repeating triangles across the runway to create a visual direction indicator

push(); const r = runway;
Saves state and stores runway object in a short variable
noStroke(); fill(40, 40, 80); rect(r.x, r.y, r.w, r.h, 12);
Draws the main runway rectangle in dark blue
fill(90, 90, 150); rect(r.x, r.y + r.h * 0.35, r.w, 4);
Draws a horizontal stripe across the runway center for visual interest
const segmentW = 26; fill(120, 150, 240, 160); for (let x = r.x; x < r.x + r.w; x += segmentW * 1.4) { triangle(...); }
Draws repeating triangles that point upward, spaced across the runway to show direction and rhythm
fill(70, 220, 180, 160); rect(r.x - 4, r.y, 4, r.h);
Draws a cyan left edge to mark the runway start
fill(220, 80, 80, 160); rect(r.x + r.w, r.y, 4, r.h);
Draws a red right edge to mark the runway end (danger zone)
pop();
Restores state

drawMarket()

drawMarket() follows the same layered design pattern as drawBase(). Its brown color scheme and gold accents create a mercantile feel, contrasting visually with the green base and blue runway.

function drawMarket() {
  push();
  const m = marketZone;

  noStroke();
  fill(200, 130, 80, 70);
  rect(m.x - 10, m.y - 10, m.w + 20, m.h + 20, 18);

  fill(140, 70, 40);
  rect(m.x, m.y, m.w, m.h, 16);

  fill(250, 210, 120);
  rect(m.x + 12, m.y + m.h * 0.25, m.w - 24, m.h * 0.5, 10);

  fill(80, 35, 20, 210);
  rect(m.x, m.y - 26, m.w, 22, 12);
  fill(255, 230, 200);
  textAlign(CENTER, CENTER);
  textSize(13);
  text('MARKET', m.x + m.w / 2, m.y - 15);
  pop();
}
Line-by-line explanation (7 lines)
push(); const m = marketZone;
Saves state and stores market object in a short variable
noStroke(); fill(200, 130, 80, 70); rect(m.x - 10, m.y - 10, m.w + 20, m.h + 20, 18);
Draws a semi-transparent tan shadow rectangle slightly larger than the market
fill(140, 70, 40); rect(m.x, m.y, m.w, m.h, 16);
Draws the main market rectangle in brown
fill(250, 210, 120); rect(m.x + 12, m.y + m.h * 0.25, m.w - 24, m.h * 0.5, 10);
Draws a gold horizontal bar in the center for visual appeal
fill(80, 35, 20, 210); rect(m.x, m.y - 26, m.w, 22, 12);
Draws a dark brown label bar above the market
fill(255, 230, 200); textAlign(CENTER, CENTER); textSize(13); text('MARKET', ...);
Draws the word 'MARKET' in light beige centered on the label bar
pop();
Restores state

drawHUD()

drawHUD() is a common game UI pattern: display vital information in the top-left and important warnings in the center. It uses time-based flashes (noSpaceUntil and collectFlashUntil) to show temporary messages. The color-coded status (green when carrying, white when not) provides at-a-glance information.

function drawHUD() {
  textAlign(LEFT, TOP);
  textSize(16);
  fill(255);
  const alive = brainrots.filter((b) => !b.rescued && !b.despawned && !b.sold).length;
  text(`Brainrots on runway: ${alive}`, 16, 16);
  text(`Rescued: ${rescuedCount}`, 16, 36);

  const status = player.carrying ? 'Carrying 1 brainrot' : 'Not carrying';
  fill(player.carrying ? color(190, 255, 190) : 220);
  text(status, 16, 56);

  fill(255);
  text(`Money: $${formatMoney(money)}`, 16, 76);

  const income = getTotalIncomePerSecond();
  fill(200, 255, 200);
  text(`Income: $${income}/s`, 16, 96);

  if (player.carrying && player.carrying.typeIndex != null) {
    const t = brainrotTypes[player.carrying.typeIndex];
    const val = t.marketValue || 0;
    const inc = t.incomePerSecond || 0;
    fill(220);
    textSize(13);
    text(`Carrying: ${t.name} [${t.rarity}]`, 16, 120);
    text(`Value: $${formatMoney(val)}  |  $${inc}/s`, 16, 138);
  }

  if (millis() < noSpaceUntil) {
    textAlign(CENTER, TOP);
    textSize(18);
    fill(255, 180, 180);
    text("You don't have space!", width / 2, 20);
    textAlign(LEFT, TOP);
  }

  if (millis() < collectFlashUntil && lastCollectAmount > 0) {
    textAlign(LEFT, TOP);
    textSize(14);
    fill(180, 255, 200);
    text(`+${formatMoney(lastCollectAmount)}`, 16, 116);
  }
}
Line-by-line explanation (11 lines)

🔧 Subcomponents:

conditional Carrying Status Color const status = player.carrying ? 'Carrying 1 brainrot' : 'Not carrying'; fill(player.carrying ? color(190, 255, 190) : 220);

Shows green text when carrying, white when not

conditional Carrying Brainrot Details if (player.carrying && player.carrying.typeIndex != null) { ... }

Displays the name, rarity, value, and income of the carried brainrot

conditional No Space Warning if (millis() < noSpaceUntil) { ... }

Shows a red 'no space' message for 3 seconds if the player tries to deliver when base is full

conditional Income Collection Flash if (millis() < collectFlashUntil && lastCollectAmount > 0) { ... }

Shows green '+$X' text for 800ms after collecting passive income

textAlign(LEFT, TOP); textSize(16); fill(255);
Sets up text alignment and size for HUD display
const alive = brainrots.filter((b) => !b.rescued && !b.despawned && !b.sold).length;
Counts brainrots still on the runway
text(`Brainrots on runway: ${alive}`, 16, 16);
Displays the count at top-left
const status = player.carrying ? 'Carrying 1 brainrot' : 'Not carrying';
Determines what carrying status text to display
fill(player.carrying ? color(190, 255, 190) : 220);
Sets text color to green if carrying, white if not
text(`Money: $${formatMoney(money)}`, 16, 76);
Displays total money, using formatMoney() to abbreviate large numbers
const income = getTotalIncomePerSecond();
Calculates passive income from all delivered brainrots
text(`Income: $${income}/s`, 16, 96);
Displays passive income rate
if (player.carrying && player.carrying.typeIndex != null) { ... }
If carrying a valid brainrot, displays its details (name, rarity, market value, income/s)
if (millis() < noSpaceUntil) { ... text("You don't have space!", ...); ... }
If base was full recently, shows red warning text for 3 seconds
if (millis() < collectFlashUntil && lastCollectAmount > 0) { ... text(`+${formatMoney(lastCollectAmount)}`, ...); }
If income was just collected, shows green '+$X' flash for 800ms

drawBackgroundDecor()

drawBackgroundDecor() is purely visual polish: a semi-transparent grid makes the game world feel more structured and less plain. The grid adapts to any canvas size by dividing width and height into equal cells.

function drawBackgroundDecor() {
  noStroke();
  const cols = 24;
  const rows = 16;
  const cellW = width / cols;
  const cellH = height / rows;
  fill(30, 32, 60, 40);

  for (let i = 0; i < cols; i++) rect(i * cellW, 0, 1, height);
  for (let j = 0; j < rows; j++) rect(0, j * cellH, width, 1);
}
Line-by-line explanation (5 lines)

🔧 Subcomponents:

for-loop Grid Line Loops for (let i = 0; i < cols; i++) rect(...); for (let j = 0; j < rows; j++) rect(...);

Draws vertical and horizontal lines to create a grid background pattern

noStroke(); const cols = 24; const rows = 16;
Sets up grid parameters: 24 columns and 16 rows
const cellW = width / cols; const cellH = height / rows;
Calculates the width and height of each grid cell based on canvas size
fill(30, 32, 60, 40);
Sets the grid line color to a semi-transparent dark blue
for (let i = 0; i < cols; i++) rect(i * cellW, 0, 1, height);
Draws 24 vertical lines (1 pixel wide, full height)
for (let j = 0; j < rows; j++) rect(0, j * cellH, width, 1);
Draws 16 horizontal lines (1 pixel tall, full width)

drawStartScreen()

drawStartScreen() is a title screen with instructions. It overlays a semi-transparent black rect so text is legible, then uses textAlign(CENTER, CENTER) to center everything. This is the user's first impression of your game, so clarity and visual hierarchy matter.

function drawStartScreen() {
  fill(0, 0, 0, 140);
  rect(0, 0, width, height);

  textAlign(CENTER, CENTER);
  fill(255);
  textSize(40);
  text('BRAINROT RESCUE', width / 2, height / 2 - 80);

  textSize(20);
  fill(220);
  text('Brainrots walk along the runway', width / 2, height / 2 - 40);
  text('Grab them before they reach the end!', width / 2, height / 2 - 10);
  text('Move with WASD or Arrow Keys', width / 2, height / 2 + 20);
  text('Bring them back to your BASE to earn money', width / 2, height / 2 + 50);
  text('Or SELL them at the MARKET for huge value', width / 2, height / 2 + 80);
  text('Walk over brainrots in your BASE to collect income', width / 2, height / 2 + 110);
  text('Press I to view the Brainrot Index', width / 2, height / 2 + 140);

  fill(190, 255, 190);
  text('Press any key or click to start', width / 2, height / 2 + 180);
}
Line-by-line explanation (5 lines)
fill(0, 0, 0, 140); rect(0, 0, width, height);
Draws a semi-transparent black overlay over the game world
textAlign(CENTER, CENTER);
Centers all text horizontally and vertically
fill(255); textSize(40); text('BRAINROT RESCUE', width / 2, height / 2 - 80);
Displays the title in large white text near the top
textSize(20); fill(220); text(..., width / 2, height / 2 - 40); ... text(..., width / 2, height / 2 + 180);
Displays 8 lines of instructions and controls at different y-positions, centered
fill(190, 255, 190); text('Press any key or click to start', width / 2, height / 2 + 180);
Displays the final prompt in green to draw attention

drawIndexOverlay()

drawIndexOverlay() is a complex UI: it renders a scrollable list of collectibles organized by rarity. Key techniques include: calculating content height to determine scroll range, using visibility culling to only draw on-screen items, and storing panel bounds for mouse interaction. The two-pass approach (measure then draw) ensures scroll limits are correct.

function drawIndexOverlay() {
  const panelW = min(width - 80, 520);
  const panelH = min(height - 80, height - 80);
  const x = width - panelW - 20;
  const y = 20;

  indexPanelBounds = { x, y, w: panelW, h: panelH };

  fill(5, 5, 15, 220);
  rect(x, y, panelW, panelH, 18);

  textAlign(LEFT, TOP);
  fill(255);
  textSize(20);
  text('Brainrot Index', x + 16, y + 12);

  const totalIncome = getTotalIncomePerSecond();
  textSize(13);
  fill(200, 255, 200);
  text(`Total income: $${totalIncome}/s`, x + 16, y + 36);
  text('Value = sell price at MARKET', x + 16, y + 52);

  const counts = new Array(brainrotTypes.length).fill(0);
  for (let br of brainrots) {
    if (br.delivered && !br.despawned && !br.sold && br.typeIndex != null) {
      counts[br.typeIndex]++;
    }
  }

  const visibleTop = y + 72;
  const visibleBottom = y + panelH - 40;

  // Measure content height
  let measureY = visibleTop;
  for (let r = 0; r < rarityOrder.length; r++) {
    const rarity = rarityOrder[r];
    let hasAny = brainrotTypes.some((t) => t.rarity === rarity);
    if (!hasAny) continue;

    measureY += 18;
    for (let t of brainrotTypes) {
      if (t.rarity === rarity) measureY += 16;
    }
    measureY += 8;
  }
  const contentHeight = max(0, measureY - visibleTop);
  const viewHeight = visibleBottom - visibleTop;
  indexMaxScroll = max(0, contentHeight - viewHeight);
  indexScroll = constrain(indexScroll, -indexMaxScroll, 0);

  // Draw with scroll
  let yCursor = visibleTop + indexScroll;
  textSize(12);

  for (let r = 0; r < rarityOrder.length; r++) {
    const rarity = rarityOrder[r];
    const labelColor = rarityLabelColors[rarity] || [200, 200, 200];
    let hasAny = brainrotTypes.some((t) => t.rarity === rarity);
    if (!hasAny) continue;

    if (yCursor < visibleBottom && yCursor + 18 > visibleTop) {
      fill(labelColor[0], labelColor[1], labelColor[2]);
      textSize(14);
      text(rarity, x + 16, yCursor);
    }
    yCursor += 18;

    textSize(12);
    for (let i = 0; i < brainrotTypes.length; i++) {
      const t = brainrotTypes[i];
      if (t.rarity !== rarity) continue;

      const owned = counts[i];
      const inc = t.incomePerSecond || 0;
      const val = t.marketValue || 0;

      if (yCursor < visibleBottom && yCursor + 16 > visibleTop) {
        fill(230);
        text(`- ${t.name}`, x + 28, yCursor);

        textAlign(RIGHT, TOP);
        fill(210, 240, 210);
        text(
          `x${owned}  $${inc}/s  $${formatMoney(val)}`,
          x + panelW - 16,
          yCursor
        );
        textAlign(LEFT, TOP);
      }
      yCursor += 16;
    }
    yCursor += 8;
  }

  textSize(11);
  fill(180);
  textAlign(RIGHT, BOTTOM);
  text('[I] close  •  scroll with mouse wheel', x + panelW - 16, y + panelH - 10);
  textAlign(LEFT, TOP);
}
Line-by-line explanation (19 lines)

🔧 Subcomponents:

calculation Responsive Panel Size const panelW = min(width - 80, 520); const panelH = min(height - 80, height - 80);

Scales the index panel to fit the window while maintaining a maximum size

calculation Scroll Range Calculation let measureY = visibleTop; for (let r = 0; r < rarityOrder.length; r++) { ... } indexMaxScroll = max(0, contentHeight - viewHeight);

Measures total content height and calculates how far the user can scroll

conditional Visibility Culling if (yCursor < visibleBottom && yCursor + 16 > visibleTop) { ... }

Only draws text lines that are visible in the panel, improving performance

const panelW = min(width - 80, 520); const panelH = min(height - 80, height - 80);
Calculates panel size: minimum 520px wide and height - 80px, but shrinks if window is too small
const x = width - panelW - 20; const y = 20;
Positions the panel on the right side of the screen with 20px margins
indexPanelBounds = { x, y, w: panelW, h: panelH };
Stores panel bounds so mouseWheel() and mousePressed() know where the panel is
fill(5, 5, 15, 220); rect(x, y, panelW, panelH, 18);
Draws the panel background as a dark semi-transparent rectangle with rounded corners
text('Brainrot Index', x + 16, y + 12);
Displays the panel title
const totalIncome = getTotalIncomePerSecond();
Calculates total passive income across all delivered brainrots
text(`Total income: $${totalIncome}/s`, x + 16, y + 36);
Displays the total income rate
const counts = new Array(brainrotTypes.length).fill(0);
Creates an array to track how many of each brainrot type the player owns
for (let br of brainrots) { if (br.delivered && !br.despawned && !br.sold && br.typeIndex != null) { counts[br.typeIndex]++; } }
Loops through all brainrots and increments the count for each delivered brainrot's type
const visibleTop = y + 72; const visibleBottom = y + panelH - 40;
Defines the visible area of the panel (top at y+72, bottom at y+panelH-40)
let measureY = visibleTop; for (let r = 0; r < rarityOrder.length; r++) { ... } indexMaxScroll = max(0, contentHeight - viewHeight);
Measures how much content exists and calculates the maximum scroll distance
indexScroll = constrain(indexScroll, -indexMaxScroll, 0);
Clamps the scroll value so it never goes beyond the content bounds
let yCursor = visibleTop + indexScroll;
Initializes the y position for drawing content, applying the scroll offset
for (let r = 0; r < rarityOrder.length; r++) { ... }
Loops through each rarity tier and draws its brainrot types
if (yCursor < visibleBottom && yCursor + 18 > visibleTop) { ... }
Only draws the rarity header if it's within the visible area
for (let i = 0; i < brainrotTypes.length; i++) { ... }
Loops through all brainrot types for this rarity
const owned = counts[i]; const inc = t.incomePerSecond || 0; const val = t.marketValue || 0;
Gets the player's count of this type, its income/sec, and its market value
text(`- ${t.name}`, x + 28, yCursor); ... text(`x${owned} $${inc}/s $${formatMoney(val)}`, ...);
Displays the brainrot's name on the left and its stats (count, income, value) on the right
text('[I] close • scroll with mouse wheel', x + panelW - 16, y + panelH - 10);
Displays instructions at the bottom of the panel

getRainbowColor()

getRainbowColor() uses sine waves offset by 120° (2π/3) to create smooth color cycling. Each channel (R, G, B) oscillates independently, producing a smooth rainbow effect. Multiplying millis() by 0.002 controls the animation speed.

function getRainbowColor() {
  const t = millis() * 0.002;
  return [
    127 + 128 * sin(t),
    127 + 128 * sin(t + TWO_PI / 3),
    127 + 128 * sin(t + (2 * TWO_PI) / 3)
  ];
}
Line-by-line explanation (2 lines)
const t = millis() * 0.002;
Converts elapsed milliseconds to a slowly-changing time parameter
return [ 127 + 128 * sin(t), 127 + 128 * sin(t + TWO_PI / 3), 127 + 128 * sin(t + (2 * TWO_PI) / 3) ];
Returns an RGB color where each channel oscillates between -1 and 1 (shifted to 0-255 range) with a 120° phase offset to create rainbow cycling

formatMoney()

formatMoney() converts large numbers into readable abbreviations using scientific notation (1e6 = 1,000,000). This is standard in idle games to display absurdly large values (like 750 billion for an OG brainrot) in a readable way.

function formatMoney(n) {
  if (n >= 1e12) return (n / 1e12).toFixed(2) + 'T';
  if (n >= 1e9) return (n / 1e9).toFixed(2) + 'B';
  if (n >= 1e6) return (n / 1e6).toFixed(2) + 'M';
  if (n >= 1e3) return (n / 1e3).toFixed(1) + 'K';
  return String(Math.floor(n));
}
Line-by-line explanation (5 lines)
if (n >= 1e12) return (n / 1e12).toFixed(2) + 'T';
If >= 1 trillion, divide by 1 trillion and append 'T' (e.g., 1500000000000 → '1.50T')
if (n >= 1e9) return (n / 1e9).toFixed(2) + 'B';
If >= 1 billion, divide by 1 billion and append 'B'
if (n >= 1e6) return (n / 1e6).toFixed(2) + 'M';
If >= 1 million, divide by 1 million and append 'M'
if (n >= 1e3) return (n / 1e3).toFixed(1) + 'K';
If >= 1000, divide by 1000 and append 'K' (note: only 1 decimal place, not 2)
return String(Math.floor(n));
Otherwise, round down and return as a whole number

pointInRect()

pointInRect() is axis-aligned bounding box (AABB) collision: the simplest and fastest way to check if a point is inside a rectangle. Used throughout the sketch for zone detection (base, market, index panel).

function pointInRect(px, py, rectObj) {
  return (
    px >= rectObj.x &&
    px <= rectObj.x + rectObj.w &&
    py >= rectObj.y &&
    py <= rectObj.y + rectObj.h
  );
}
Line-by-line explanation (2 lines)
px >= rectObj.x && px <= rectObj.x + rectObj.w &&
Checks if the x coordinate is within the rectangle's left and right boundaries
py >= rectObj.y && py <= rectObj.y + rectObj.h
Checks if the y coordinate is within the rectangle's top and bottom boundaries

keyPressed()

keyPressed() is p5.js's event handler for individual key presses. It runs once per key press (unlike keyIsDown() which checks continuous hold). The early return prevents the start transition from triggering when pressing I.

function keyPressed() {
  if (key === 'i' || key === 'I') {
    showIndex = !showIndex;
    return;
  }
  if (gameState === 'start') gameState = 'playing';
}
Line-by-line explanation (4 lines)

🔧 Subcomponents:

conditional Index Toggle if (key === 'i' || key === 'I') { showIndex = !showIndex; return; }

Pressing I toggles the Index panel on/off

conditional Start Screen Transition if (gameState === 'start') gameState = 'playing';

Pressing any other key when on the start screen transitions to gameplay

if (key === 'i' || key === 'I') {
Checks if the pressed key is 'i' or 'I' (case-insensitive)
showIndex = !showIndex;
Toggles the Index panel on/off
return;
Exits early so the start screen transition doesn't also trigger
if (gameState === 'start') gameState = 'playing';
If on the start screen and any other key is pressed, transitions to gameplay

mousePressed()

mousePressed() is p5.js's click handler. The index panel guard prevents accidental selling when the player is reading the Index. The assignment `my = mouseY` is a code smell—it should be `const my = mouseY;` for clarity.

function mousePressed() {
  if (gameState === 'start') {
    gameState = 'playing';
    return;
  }

  // If Index is open and click is inside panel, don't try selling
  if (showIndex && indexPanelBounds && pointInRect(mouseX, mouseY, indexPanelBounds)) {
    return;
  }

  trySellBaseBrainrot(mouseX, my = mouseY);
}
Line-by-line explanation (3 lines)

🔧 Subcomponents:

conditional Start Screen Click if (gameState === 'start') { gameState = 'playing'; return; }

Clicking anywhere on the start screen transitions to gameplay

conditional Index Panel Click Guard if (showIndex && indexPanelBounds && pointInRect(mouseX, mouseY, indexPanelBounds)) { return; }

Prevents click-to-sell from triggering when clicking on the Index panel

if (gameState === 'start') { gameState = 'playing'; return; }
If on the start screen, clicking transitions to gameplay
if (showIndex && indexPanelBounds && pointInRect(mouseX, mouseY, indexPanelBounds)) { return; }
If the Index panel is open and the click is inside it, exit early (don't try selling)
trySellBaseBrainrot(mouseX, my = mouseY);
Attempts to sell a brainrot in the base at the clicked position. Note: `my = mouseY` is a hidden assignment that also saves mouseY to `my`

mouseWheel()

mouseWheel() is p5.js's scroll handler. event.delta is positive when scrolling down and negative when scrolling up. Multiplying by 0.5 reduces sensitivity. The guard clauses ensure scrolling only works when the panel is open and the mouse is over it.

function mouseWheel(event) {
  if (!showIndex) return;
  if (indexPanelBounds && !pointInRect(mouseX, mouseY, indexPanelBounds)) return;
  indexScroll -= event.delta * 0.5;
}
Line-by-line explanation (3 lines)
if (!showIndex) return;
Exits if the Index panel is not open
if (indexPanelBounds && !pointInRect(mouseX, mouseY, indexPanelBounds)) return;
Exits if the mouse is not over the Index panel, so scrolling elsewhere doesn't scroll the panel
indexScroll -= event.delta * 0.5;
Adjusts the scroll offset by the wheel delta (negative for up, positive for down), multiplied by 0.5 for sensitivity control

createSaveLoadButton()

createSaveLoadButton() uses p5.js's DOM manipulation functions (createButton, position, style) to add an HTML button to the page. The style properties use CSS to make it match the game's aesthetic.

function createSaveLoadButton() {
  if (saveButton) saveButton.remove();
  saveButton = createButton('Save / Load');
  saveButton.position(20, height - 50);
  saveButton.style('padding', '6px 12px');
  saveButton.style('background', '#1b1b2f');
  saveButton.style('color', '#ffffff');
  saveButton.style('border', '1px solid #58a6ff');
  saveButton.style('font-family', 'monospace');
  saveButton.style('cursor', 'pointer');
  saveButton.mousePressed(onSaveLoadPressed);
}
Line-by-line explanation (5 lines)
if (saveButton) saveButton.remove();
If a button already exists, removes it to avoid duplicates
saveButton = createButton('Save / Load');
Creates a new HTML button element with the label 'Save / Load'
saveButton.position(20, height - 50);
Positions the button at x=20, y=(canvas height - 50)
saveButton.style(...);
Applies CSS styling to the button (padding, colors, border, font, cursor)
saveButton.mousePressed(onSaveLoadPressed);
Attaches the onSaveLoadPressed callback to the button's click event

onSaveLoadPressed()

onSaveLoadPressed() uses browser prompts for user input (old-school but simple). It delegates to createSaveCode() and loadFromCode() for the heavy lifting, then provides feedback via alerts.

function onSaveLoadPressed() {
  let choice = prompt('Type "save" to get a code or "load" to enter one:');
  if (!choice) return;
  choice = choice.trim().toLowerCase();
  if (choice === 'save') {
    const code = createSaveCode();
    prompt('Copy your save code:', code);
  } else if (choice === 'load') {
    const code = prompt('Enter your save code:');
    if (!code) return;
    const ok = loadFromCode(code.trim());
    if (!ok) alert('Invalid or corrupted save code.');
    else alert('Progress loaded!');
  } else {
    alert('Please type "save" or "load".');
  }
}
Line-by-line explanation (6 lines)

🔧 Subcomponents:

conditional Save Code Path if (choice === 'save') { const code = createSaveCode(); prompt('Copy your save code:', code); }

Generates and displays a save code for the player to copy

conditional Load Code Path } else if (choice === 'load') { const code = prompt('Enter your save code:'); ... }

Asks the player to paste a save code and restores the game state

let choice = prompt('Type "save" to get a code or "load" to enter one:');
Shows a browser prompt asking whether to save or load
if (!choice) return;
Exits if the user cancelled (choice is null)
choice = choice.trim().toLowerCase();
Removes whitespace and converts to lowercase for case-insensitive matching
if (choice === 'save') { const code = createSaveCode(); prompt('Copy your save code:', code); }
If 'save', generates a save code and shows it in another prompt for the user to copy
} else if (choice === 'load') { const code = prompt('Enter your save code:'); if (!code) return; const ok = loadFromCode(code.trim()); if (!ok) alert('Invalid or corrupted save code.'); else alert('Progress loaded!'); }
If 'load', prompts for a code, attempts to load it, and alerts success or failure
} else { alert('Please type "save" or "load".'); }
If neither 'save' nor 'load', shows an error message

createSaveCode()

createSaveCode() serializes the game state (money, rescue count, all brainrot properties) into a JSON string, then encodes it with btoa() (Base64). This produces a compact, shareable string that contains the entire game state. To reverse it, use atob() to decode Base64 and JSON.parse() to reconstruct the object.

function createSaveCode() {
  const payload = {
    money,
    rescuedCount,
    brainrots: brainrots.map((br) => ({
      x: br.x,
      y: br.y,
      r: br.r,
      rescued: br.rescued,
      delivered: br.delivered,
      despawned: br.despawned,
      sold: br.sold,
      wigglePhase: br.wigglePhase,
      speed: br.speed,
      typeIndex: br.typeIndex,
      storedIncome: br.storedIncome || 0,
      slotIndex: br.slotIndex
    }))
  };
  const json = JSON.stringify(payload);
  return btoa(json);
}
Line-by-line explanation (4 lines)
const payload = { money, rescuedCount, brainrots: brainrots.map(...) };
Creates an object containing the game state: money, rescue count, and an array of brainrot snapshots
brainrots: brainrots.map((br) => ({ x, y, r, rescued, delivered, despawned, sold, wigglePhase, speed, typeIndex, storedIncome, slotIndex }))
Maps each brainrot to a snapshot object containing all properties needed to restore it
const json = JSON.stringify(payload);
Converts the payload object to a JSON string
return btoa(json);
Encodes the JSON string as Base64, making it URL-safe and obfuscated (not truly encrypted)

loadFromCode()

loadFromCode() is the inverse of createSaveCode(). It decodes the Base64, parses JSON, validates the data, and reconstructs brainrots with sensible defaults. Crucially, it re-occupies base slots and repositions brainrots to match the saved state. The player is reset to a neutral position (not saved) to prevent issues if the window was resized.

function loadFromCode(code) {
  let data;
  try {
    data = JSON.parse(atob(code));
  } catch (err) {
    return false;
  }
  if (!data || !Array.isArray(data.brainrots)) return false;

  money = Number(data.money) || 0;
  rescuedCount = Number(data.rescuedCount) || 0;

  // Reset slots
  for (let slot of baseSlots) slot.occupied = false;

  // Rebuild brainrots array
  brainrots = data.brainrots.map((raw) => ({
    x: raw.x,
    y: raw.y,
    r: raw.r,
    carried: false,
    rescued: !!raw.rescued,
    delivered: !!raw.delivered,
    despawned: !!raw.despawned,
    sold: !!raw.sold,
    wigglePhase: raw.wigglePhase || 0,
    speed: raw.speed || 1.2,
    typeIndex: raw.typeIndex ?? 0,
    storedIncome: raw.storedIncome || 0,
    slotIndex: raw.slotIndex == null ? null : raw.slotIndex
  }));

  // Restore slot occupancy & positions for delivered ones
  for (let br of brainrots) {
    if (br.delivered && !br.despawned && !br.sold && br.slotIndex != null && baseSlots[br.slotIndex]) {
      const slot = baseSlots[br.slotIndex];
      slot.occupied = true;
      br.x = slot.x;
      br.y = slot.y;
    } else if (br.delivered) {
      br.slotIndex = null;
      br.x = baseZone.x + baseZone.w / 2;
      br.y = baseZone.y + baseZone.h / 2;
    }
  }

  player = {
    x: width * 0.2,
    y: height * 0.5,
    r: 18,
    speed: 4.2,
    carrying: null,
    vx: 0,
    vy: 0
  };

  lastSpawnTime = millis();
  lastPassiveTime = millis();
  noSpaceUntil = 0;
  collectFlashUntil = 0;
  lastCollectAmount = 0;
  indexScroll = 0;
  indexMaxScroll = 0;

  gameState = 'playing';
  return true;
}
Line-by-line explanation (9 lines)

🔧 Subcomponents:

calculation Decode Base64 and Parse JSON try { data = JSON.parse(atob(code)); } catch (err) { return false; }

Decodes the Base64 string and parses it as JSON; returns false if any error occurs

calculation Brainrot Array Reconstruction brainrots = data.brainrots.map((raw) => ({ ... }));

Converts raw data objects back into brainrot instances with proper defaults

for-loop Slot Occupancy Restoration for (let br of brainrots) { if (br.delivered && ...) { ... } }

Re-occupies base slots and repositions delivered brainrots to their saved slots

let data; try { data = JSON.parse(atob(code)); } catch (err) { return false; }
Attempts to decode the Base64 and parse JSON. If any error occurs (malformed code), returns false
if (!data || !Array.isArray(data.brainrots)) return false;
Validates that the decoded data has a brainrots array; returns false if not
money = Number(data.money) || 0; rescuedCount = Number(data.rescuedCount) || 0;
Restores money and rescue count with fallback defaults
for (let slot of baseSlots) slot.occupied = false;
Clears all slot occupancy before restoring
brainrots = data.brainrots.map((raw) => ({ ... }));
Maps raw data objects to full brainrot objects with proper defaults for any missing fields
for (let br of brainrots) { if (br.delivered && !br.despawned && !br.sold && br.slotIndex != null && baseSlots[br.slotIndex]) { ... } }
Loops through brainrots and marks slots as occupied for delivered brainrots, repositioning them to their saved slot positions
else if (br.delivered) { br.slotIndex = null; br.x = baseZone.x + baseZone.w / 2; br.y = baseZone.y + baseZone.h / 2; }
If a delivered brainrot's slot is invalid, drops it in the center of the base
player = { x: width * 0.2, y: height * 0.5, r: 18, speed: 4.2, carrying: null, vx: 0, vy: 0 };
Reinitializes the player to a fresh state (not saved, to ensure consistency)
lastSpawnTime = millis(); lastPassiveTime = millis(); ... gameState = 'playing'; return true;
Resets all timing variables and transitions to 'playing' state, returning true to indicate success

setupBaseSlots()

setupBaseSlots() creates a 4×2 grid of inventory slots by calculating cell positions. Each slot's x, y is at the center of its cell (col * cellW + cellW * 0.5). This layout is reusable: to make 3×3 slots, change cols = 3 and rows = 3.

function setupBaseSlots() {
  baseSlots = [];
  const cols = 4;
  const rows = 2;
  const padding = 12;
  const innerW = baseZone.w - padding * 2;
  const innerH = baseZone.h - padding * 2;
  const cellW = innerW / cols;
  const cellH = innerH / rows;

  for (let row = 0; row < rows; row++) {
    for (let col = 0; col < cols; col++) {
      baseSlots.push({
        x: baseZone.x + padding + col * cellW + cellW * 0.5,
        y: baseZone.y + padding + row * cellH + cellH * 0.5,
        occupied: false
      });
    }
  }
}
Line-by-line explanation (6 lines)

🔧 Subcomponents:

calculation Grid Cell Size Calculation const cellW = innerW / cols; const cellH = innerH / rows;

Divides the inner base area into 4 columns × 2 rows, calculating the width and height of each cell

for-loop Slot Positioning Loop for (let row = 0; row < rows; row++) { for (let col = 0; col < cols; col++) { ... } }

Creates 8 slots in a 4×2 grid, positioning each at the center of its cell

baseSlots = [];
Initializes an empty array to hold slot objects
const cols = 4; const rows = 2; const padding = 12;
Defines a 4×2 grid with 12 pixels of padding around the inner edges
const innerW = baseZone.w - padding * 2; const innerH = baseZone.h - padding * 2;
Calculates the inner area size by subtracting padding from both sides
const cellW = innerW / cols; const cellH = innerH / rows;
Divides inner area by rows/cols to get each cell's size
for (let row = 0; row < rows; row++) { for (let col = 0; col < cols; col++) { ... } }
Nested loops create 2 rows × 4 columns = 8 slots
baseSlots.push({ x: baseZone.x + padding + col * cellW + cellW * 0.5, y: baseZone.y + padding + row * cellH + cellH * 0.5, occupied: false });
For each slot, calculates its position at the center of its grid cell (baseZone offset + padding + column offset + half-cell) and adds it to the array

trySellBaseBrainrot()

trySellBaseBrainrot() finds which brainrot in the base the player clicked and shows a confirm dialog with its details. The 4-pixel tolerance makes clicking easier. The break ensures only one brainrot is selected per click.

function trySellBaseBrainrot(mx, my) {
  for (let br of brainrots) {
    if (!br.delivered || br.despawned || br.sold || br.typeIndex == null) continue;
    if (dist(mx, my, br.x, br.y) <= br.r + 4) {
      const t = brainrotTypes[br.typeIndex];
      const baseValue = t ? t.marketValue : brainrotReward;
      const stored = Math.floor(br.storedIncome || 0);
      const message =
        `${t.name} (${t.rarity})\n` +
        `Value: $${formatMoney(baseValue)}\n` +
        `Stored income: $${formatMoney(stored)}\n\n` +
        `Sell this brainrot?`;

      if (window.confirm(message)) sellBrainrotInBase(br);
      break;
    }
  }
}
Line-by-line explanation (7 lines)

🔧 Subcomponents:

conditional Click-to-Brainrot Collision if (dist(mx, my, br.x, br.y) <= br.r + 4) {

Detects if the click was within the brainrot's radius (plus a 4-pixel tolerance)

for (let br of brainrots) {
Loops through all brainrots
if (!br.delivered || br.despawned || br.sold || br.typeIndex == null) continue;
Skips brainrots not in the base or without a valid type
if (dist(mx, my, br.x, br.y) <= br.r + 4) {
Checks if the click position (mx, my) is close enough to the brainrot (distance <= radius + 4 pixel tolerance)
const t = brainrotTypes[br.typeIndex]; const baseValue = t ? t.marketValue : brainrotReward; const stored = Math.floor(br.storedIncome || 0);
Gets the brainrot's type, market value, and accumulated stored income (rounded down to dollars)
const message = `${t.name} (${t.rarity})\n...`;
Builds a multi-line confirmation message with the brainrot's name, rarity, sell value, and stored income
if (window.confirm(message)) sellBrainrotInBase(br);
Shows the message as a browser confirm dialog. If the user clicks 'OK', sells the brainrot
break;
Exits the loop after finding the first clicked brainrot, so only one is selected

sellBrainrotInBase()

sellBrainrotInBase() is called after the user confirms a sale. It adds both the market value and accumulated income to money, frees the slot, and removes the brainrot from the game. This is the payoff for storing brainrots in the base.

function sellBrainrotInBase(br) {
  const t = brainrotTypes[br.typeIndex];
  const baseValue = t ? t.marketValue : brainrotReward;
  const stored = Math.floor(br.storedIncome || 0);
  money += baseValue + stored;

  if (br.slotIndex != null && baseSlots[br.slotIndex]) {
    baseSlots[br.slotIndex].occupied = false;
  }

  br.slotIndex = null;
  br.storedIncome = 0;
  br.sold = true;
  br.despawned = true;
}
Line-by-line explanation (4 lines)
const t = brainrotTypes[br.typeIndex]; const baseValue = t ? t.marketValue : brainrotReward; const stored = Math.floor(br.storedIncome || 0);
Gets the brainrot's type, market value, and stored income
money += baseValue + stored;
Adds both the market value and accumulated stored income to the player's total money
if (br.slotIndex != null && baseSlots[br.slotIndex]) { baseSlots[br.slotIndex].occupied = false; }
If the brainrot was in a base slot, marks that slot as unoccupied
br.slotIndex = null; br.storedIncome = 0; br.sold = true; br.despawned = true;
Clears the brainrot's slot assignment, income, and marks it as sold and despawned so it won't be drawn

getTotalIncomePerSecond()

getTotalIncomePerSecond() is a simple aggregation function: it loops through all valid delivered brainrots and sums their per-second income. This value is displayed in the HUD and Index.

function getTotalIncomePerSecond() {
  let total = 0;
  for (let br of brainrots) {
    if (br.delivered && !br.despawned && !br.sold && br.typeIndex != null) {
      total += brainrotTypes[br.typeIndex].incomePerSecond;
    }
  }
  return total;
}
Line-by-line explanation (5 lines)
let total = 0;
Initializes an accumulator variable
for (let br of brainrots) {
Loops through all brainrots
if (br.delivered && !br.despawned && !br.sold && br.typeIndex != null) {
Checks if the brainrot is in the base, not removed, and has a valid type
total += brainrotTypes[br.typeIndex].incomePerSecond;
Adds this brainrot's income/second value to the total
return total;
Returns the sum of all delivered brainrots' income rates

windowResized()

windowResized() is p5.js's auto-triggered event when the window is resized. It's conservative: resizing resets the game to the start screen. For a more user-friendly approach, you could preserve the game state and just update layout positions.

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
  createSaveLoadButton();
  initLayout();
  initGame();
  gameState = 'start';
  showIndex = false;
  updateSaveButtonPosition();
}
Line-by-line explanation (6 lines)
resizeCanvas(windowWidth, windowHeight);
Resizes the p5.js canvas to match the new window dimensions
createSaveLoadButton();
Recreates the save/load button (which needs repositioning)
initLayout();
Recalculates positions for base, runway, and market zones based on new window size
initGame();
Resets the game to initial state
gameState = 'start'; showIndex = false;
Returns to the start screen and closes the Index
updateSaveButtonPosition();
Updates the save button position to match the new window height

randIntRange()

randIntRange() is a utility for inclusive integer random numbers. Math.random() gives [0, 1), so multiplying by (max - min + 1) and flooring gives [0, max - min + 1), then adding min shifts it to [min, max].

function randIntRange(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}
Line-by-line explanation (1 lines)
return Math.floor(Math.random() * (max - min + 1)) + min;
Generates a random integer between min and max (inclusive) using the standard algorithm: random * range + offset

initLayout()

initLayout() calculates zone positions and sizes based on canvas width/height using percentages. This makes the layout responsive to window resizing. Positions are relative (e.g., width * 0.1 for 10% from left) to adapt to any screen size.

function initLayout() {
  // Bigger base
  const baseW = 220;
  const baseH = 150;
  baseZone = {
    x: width * 0.1,
    y: height * 0.5 - baseH / 2,
    w: baseW,
    h: baseH
  };

  // Base slots (4x2 = 8)
  setupBaseSlots();

  // Runway
  runway = {
    x: width * 0.25,
    y: height * 0.7 - 20,
    w: width * 0.65,
    h: 40
  };

  // Market
  marketZone = {
    x: width * 0.78,
    y: height * 0.5 - 65,
    w: 180,
    h: 130
  };
}
Line-by-line explanation (4 lines)
const baseW = 220; const baseH = 150; baseZone = { x: width * 0.1, y: height * 0.5 - baseH / 2, w: baseW, h: baseH };
Creates the base zone: 220×150 pixels, positioned at left 10% and vertically centered
setupBaseSlots();
Calls setupBaseSlots() to populate the 4×2 inventory grid inside the base
runway = { x: width * 0.25, y: height * 0.7 - 20, w: width * 0.65, h: 40 };
Creates the runway: starts at 25% of width, positioned at 70% down the screen minus 20 pixels, spans 65% of screen width, 40 pixels tall
marketZone = { x: width * 0.78, y: height * 0.5 - 65, w: 180, h: 130 };
Creates the market zone: 180×130 pixels, positioned at right 78% and vertically centered

initGame()

initGame() initializes or resets the game to a fresh state. It creates the player, clears all brainrots and stats, resets timing, and spawns 4 starting brainrots. Called from setup() and again on window resize or load.

function initGame() {
  // player
  player = {
    x: width * 0.2,
    y: height * 0.5,
    r: 18,
    speed: 4.2,
    carrying: null,
    vx: 0,
    vy: 0
  };

  // clear slots & occupancy
  for (let slot of baseSlots) slot.occupied = false;

  brainrots = [];
  rescuedCount = 0;
  money = 0;
  lastSpawnTime = millis();
  lastPassiveTime = millis();
  noSpaceUntil = 0;
  collectFlashUntil = 0;
  lastCollectAmount = 0;
  indexScroll = 0;
  indexMaxScroll = 0;
  showIndex = false;

  for (let i = 0; i < 4; i++) spawnBrainrot();
}
Line-by-line explanation (5 lines)
player = { x: width * 0.2, y: height * 0.5, r: 18, speed: 4.2, carrying: null, vx: 0, vy: 0 };
Creates the player object at 20% from the left and vertically centered
for (let slot of baseSlots) slot.occupied = false;
Clears all base slot occupancy
brainrots = []; rescuedCount = 0; money = 0;
Resets the brainrots array and game stats
lastSpawnTime = millis(); lastPassiveTime = millis(); ... showIndex = false;
Resets all timing variables and UI state
for (let i = 0; i < 4; i++) spawnBrainrot();
Spawns the initial 4 brainrots to give the player something to catch immediately

updateSaveButtonPosition()

updateSaveButtonPosition() is a tiny utility to reposition the save/load button after a window resize. Called from windowResized() to keep the button in the correct spot.

function updateSaveButtonPosition() {
  if (saveButton) saveButton.position(20, height - 50);
}
Line-by-line explanation (1 lines)
if (saveButton) saveButton.position(20, height - 50);
If the save button exists, repositions it to x=20 and y=(height - 50) to stay in the bottom-left

📦 Key Variables

gameState string

Tracks whether the game is on the start screen ('start') or actively playing ('playing')

let gameState = 'start';
player object

Stores the player object with position (x, y), radius (r), speed, carrying state, and velocity (vx, vy)

let player = { x: 100, y: 100, r: 18, speed: 4.2, carrying: null, vx: 0, vy: 0 };
brainrots array

Array of all brainrot objects in the game, each with position, state flags, type, income, etc.

let brainrots = [];
baseZone object

Defines the base's rectangular zone (x, y, width, height) where brainrots are stored

let baseZone = { x: 50, y: 200, w: 220, h: 150 };
baseSlots array

Array of 8 inventory slot objects, each with position (x, y) and occupied status

let baseSlots = [];
runway object

Defines the runway zone (x, y, width, height) where brainrots spawn and walk

let runway = { x: 200, y: 450, w: 800, h: 40 };
marketZone object

Defines the market zone (x, y, width, height) where brainrots can be sold

let marketZone = { x: 1100, y: 250, w: 180, h: 130 };
rescuedCount number

Total number of brainrots the player has delivered to the base

let rescuedCount = 0;
maxBrainrotsOnMap number

Maximum number of unrescued brainrots that can exist on the runway at once (default 10)

let maxBrainrotsOnMap = 10;
spawnInterval number

Milliseconds between brainrot spawns (default 2000 = 2 seconds)

let spawnInterval = 2000;
lastSpawnTime number

Timestamp (in ms) of the last brainrot spawn, used to rate-limit spawning

let lastSpawnTime = 0;
money number

Player's total accumulated money from rescuing and selling brainrots

let money = 0;
brainrotReward number

Instant cash awarded when delivering a brainrot to the base for the first time (default 100)

const brainrotReward = 100;
lastPassiveTime number

Timestamp (in ms) of the last passive income update, used to calculate time deltas

let lastPassiveTime = 0;
showIndex boolean

Whether the Brainrot Index overlay panel is currently visible (toggled by pressing I)

let showIndex = false;
indexScroll number

Vertical scroll offset for the Index panel (negative value scrolls down)

let indexScroll = 0;
indexMaxScroll number

Maximum scroll distance for the Index panel, calculated based on content height

let indexMaxScroll = 0;
indexPanelBounds object

Stores the Index panel's bounding box (x, y, w, h) for mouse interaction detection

let indexPanelBounds = null;
noSpaceUntil number

Timestamp when the 'no space' warning expires (set when base is full)

let noSpaceUntil = 0;
lastCollectAmount number

Amount of income just collected, displayed as a green flash in the HUD

let lastCollectAmount = 0;
collectFlashUntil number

Timestamp when the income collection flash expires

let collectFlashUntil = 0;
saveButton object

Reference to the HTML save/load button for DOM manipulation

let saveButton;

🔧 Potential Improvements (8)

Here are some ways this code could be enhanced:

BUG mousePressed()

Line `trySellBaseBrainrot(mouseX, my = mouseY);` contains a hidden assignment: `my = mouseY` creates a global variable `my` as a side effect. This is confusing and error-prone.

💡 Change to: `const my = mouseY; trySellBaseBrainrot(mouseX, my);` or just `trySellBaseBrainrot(mouseX, mouseY);` if the function uses the second parameter directly.

PERFORMANCE updatePassiveIncome()

This function loops through all brainrots every frame (60 times per second), even though passive income could be updated less frequently without noticeable issues.

💡 Add a rate limit: only update every 100ms (10 times per second). This reduces calculations by 85% with minimal visual impact: `if (now - lastPassiveTime < 100) return;`

STYLE brainrotTypes array

The brainrot definitions and custom income values are hardcoded as large constants at the top of the file, making the code hard to scan. There's also duplicate data: brainrot names appear in both brainrotTypes and customIncome.

💡 Consider moving brainrot data to a separate brainrot-data.js file, or at least consolidate customIncome into the brainrotTypes objects: `{ name: 'Noobini Pizzanini', rarity: 'Common', customIncome: 10 }`

BUG collectFromBrainrots()

If the player clicks on a brainrot in the base immediately after collecting income, the storedIncome is set to the fractional remainder. However, if storedIncome ever becomes negative due to floating-point errors or other issues, the next collection could malfunction.

💡 Add a safety check: `br.storedIncome = Math.max(0, (br.storedIncome || 0) - available);` to ensure it never goes negative.

FEATURE drawBrainrots()

All brainrots bob up and down on the runway, but once delivered to the base, they stop bobbing. While intentional, there's no visual indication they're generating income.

💡 Add a subtle glow pulse or size wobble to delivered brainrots to show they're 'working' and accumulating income. Example: `const pulse = 1 + 0.1 * sin(millis() * 0.003);` applied to glow radius.

STYLE drawIndexOverlay()

The function is very long (~120 lines) and mixes concerns: measuring layout, calculating scroll, and rendering. It's hard to modify or test individual parts.

💡 Extract into helper functions: `measureIndexContent()`, `constrainIndexScroll()`, and `renderIndexContent()` to separate concerns.

BUG handleDelivery()

If the base is full and the player delivers a brainrot, it's dropped in the center of the base with no slot assignment. If they later click on that brainrot to sell it, `br.slotIndex` is null, so the slot won't be freed on sale. This is harmless (slots never get re-occupied anyway) but inconsistent.

💡 Track overflow brainrots separately or always find a slot before delivery. Alternatively, document this behavior as intended for visual clarity.

PERFORMANCE draw()

The grid background (drawBackgroundDecor) is redrawn every frame with 40 lines. It's static and never changes.

💡 Draw the grid once to a createGraphics buffer in setup(), then reuse it: `let gridBuffer; gridBuffer = createGraphics(width, height); ... image(gridBuffer, 0, 0);`

🔄 Code Flow

Code flow showing setup, draw, updategame, updatepassiveincome, handleinput, updateplayer, updatebrainrots, handlepickup, handledelivery, handleselling, collectfrombrainrots, maybespawnbrainrots, pickbrainrottypeindex, spawnbrainrot, drawplayer, drawbrainrots, drawbase, drawrunway, drawmarket, drawhud, drawbackgrounddecor, drawstartscreen, drawindexoverlay, gettrainbowcolor, formatmoney, pointinrect, keypressed, mousepressed, mousewheel, createsaveloadbutton, onsaveloadpressed, createsavecode, loadfromcode, setupbaseslots, trysellbasebrainrot, sellbrainrotinbase, gettotalincomeperr, windowresized, randintringe, initlayout, initgame, updatesavebuttonposition

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

graph TD start[Start] --> setup[setup] setup --> draw[draw loop] draw --> statecheck[state-check-start] statecheck -->|Game Not Started| drawstart[drawStartScreen] statecheck -->|Game Started| statecheckplaying[state-check-playing] statecheckplaying --> timecalc[time-delta-calc] timecalc --> updategame[updateGame] updategame --> incomeaccumulation[income-accumulation-loop] incomeaccumulation --> updatepassiveincome[updatePassiveIncome] updatepassiveincome --> handleinput[handleInput] handleinput --> arrowcheck[arrow-key-check] handleinput --> wasdcheck[wasd-key-check] wasdcheck --> vectornormalize[vector-normalization] vectornormalize --> positionupdate[position-update-constrain] positionupdate --> carriedbrainrot[carried-brainrot-attach] updategame --> updateplayer[updatePlayer] updateplayer --> updatebrainrots[updateBrainrots] updatebrainrots --> statuscheck[status-check-loop] statuscheck --> despawncheck[despawn-check] despawncheck -->|Brainrot Alive| collisioncheck[collision-check-loop] collisioncheck --> alreadycarryingcheck[already-carrying-check] alreadycarryingcheck -->|Not Carrying| handlepickup[handlePickup] handlepickup --> notcarryingcheck[not-carrying-check] notcarryingcheck --> basezonecheck[base-zone-check] basezonecheck --> slotassignment[slot-assignment-logic] updategame --> collectfrombrainrots[collectFromBrainrots] collectfrombrainrots --> deliveredbrainrotloop[delivered-brainrot-loop] deliveredbrainrotloop --> proximitycheck[proximity-check] updategame --> maybespawnbrainrots[maybeSpawnBrainrots] maybespawnbrainrots --> spawncheck[spawn-check-conditional] spawncheck --> alivecountfilter[alive-count-filter] alivecountfilter --> weightedrandom[weighted-random-selection] weightedrandom --> candidatefilter[candidate-filter] updategame --> drawplayer[drawPlayer] drawplayer --> carryingindicator[carrying-indicator] drawplayer --> directionarrow[direction-arrow] updategame --> drawbrainrots[drawBrainrots] drawbrainrots --> raritycolor[rarity-color-lookup] raritycolor --> rainbowcheck[rainbow-brainrot-god] updategame --> drawbase[drawBase] updategame --> drawrunway[drawRunway] updategame --> drawmarket[drawMarket] updategame --> drawhud[drawHUD] updategame --> drawbackgrounddecor[drawBackgroundDecor] draw --> indexoverlay[index-overlay-draw] indexoverlay --> indextoggle[index-toggle] indextoggle --> drawindex[drawIndexOverlay] draw --> keypressed[keyPressed] draw --> mousepressed[mousePressed] draw --> mousewheel[mouseWheel] draw --> createsaveloadbutton[createSaveLoadButton] createsaveloadbutton --> onsaveload[onSaveLoadPressed] onsaveload --> savepath[save-path] onsaveload --> loadpath[load-path] loadpath --> decodeparse[decode-parse] decodeparse --> brainrotreconstruction[brainrot-reconstruction] brainrotreconstruction --> slotrestoration[slot-restoration] setup --> initlayout[initLayout] initlayout --> setupbaseslots[setupBaseSlots] setupbaseslots --> slotpositioning[slot-positioning-loop] draw --> gridbackground[grid-background] gridbackground --> gridcalc[grid-calculation] draw --> starttransition[start-transition] starttransition --> startclick[start-screen-click] startclick --> draw click setup href "#fn-setup" click draw href "#fn-draw" click updategame href "#fn-updategame" click updatepassiveincome href "#fn-updatepassiveincome" click handleinput href "#fn-handleinput" click updateplayer href "#fn-updateplayer" click updatebrainrots href "#fn-updatebrainrots" click handlepickup href "#fn-handlepickup" click handledelivery href "#fn-handledelivery" click handleselling href "#fn-handleselling" click collectfrombrainrots href "#fn-collectfrombrainrots" click maybespawnbrainrots href "#fn-maybeSpawnBrainrots" click pickbrainrottypeindex href "#fn-pickBrainrotTypeIndex" click spawnbrainrot href "#fn-spawnBrainrot" click drawplayer href "#fn-drawPlayer" click drawbrainrots href "#fn-drawBrainrots" click drawbase href "#fn-drawBase" click drawrunway href "#fn-drawRunway" click drawmarket href "#fn-drawMarket" click drawhud href "#fn-drawHUD" click drawbackgrounddecor href "#fn-drawBackgroundDecor" click drawstartscreen href "#fn-drawStartScreen" click drawindexoverlay href "#fn-drawIndexOverlay" click gettrainbowcolor href "#fn-getRainbowColor" click formatmoney href "#fn-formatMoney" click pointinrect href "#fn-pointInRect" click keypressed href "#fn-keyPressed" click mousepressed href "#fn-mousePressed" click mousewheel href "#fn-mouseWheel" click createsaveloadbutton href "#fn-createSaveLoadButton" click onsaveloadpressed href "#fn-onSaveLoadPressed" click createsavecode href "#fn-createSaveCode" click loadfromcode href "#fn-loadFromCode" click setupbaseslots href "#fn-setupBaseSlots" click trysellbasebrainrot href "#fn-trySellBaseBrainrot" click sellbrainrotinbase href "#fn-sellBrainrotInBase" click gettotalincomeperr href "#fn-getTotalIncomePerSecond" click windowresized href "#fn-windowResized" click randintringe href "#fn-randIntRange" click initlayout href "#fn-initLayout" click initgame href "#fn-initGame" click updatesavebuttonposition href "#fn-updateSaveButtonPosition" click statecheck href "#sub-state-check-start" click statecheckplaying href "#sub-state-check-playing" click timecalc href "#sub-time-delta-calc" click incomeaccumulation href "#sub-income-accumulation-loop" click arrowcheck href "#sub-arrow-key-check" click wasdcheck href "#sub-wasd-key-check" click vectornormalize href "#sub-vector-normalization" click positionupdate href "#sub-position-update-constrain" click carriedbrainrot href "#sub-carried-brainrot-attach" click statuscheck href "#sub-status-check-loop" click despawncheck href "#sub-despawn-check" click collisioncheck href "#sub-collision-check-loop" click alreadycarryingcheck href "#sub-already-carrying-check" click notcarryingcheck href "#sub-not-carrying-check" click basezonecheck href "#sub-base-zone-check" click slotassignment href "#sub-slot-assignment-logic" click collectfrombrainrots href "#sub-delivered-brainrot-loop" click proximitycheck href "#sub-proximity-check" click alivecountfilter href "#sub-alive-count-filter" click spawncheck href "#sub-spawn-check-conditional" click weightedrandom href "#sub-weighted-random-selection" click candidatefilter href "#sub-candidate-filter" click carryingindicator href "#sub-carrying-indicator" click directionarrow href "#sub-direction-arrow" click raritycolor href "#sub-rarity-color-lookup" click rainbowcheck href "#sub-rainbow-brainrot-god" click drawbase href "#sub-base-background-layers" click slotpositioning href "#sub-slot-positioning-loop" click gridbackground href "#sub-grid-background" click gridcalc href "#sub-grid-calculation" click indextoggle href "#sub-index-toggle" click starttransition href "#sub-start-transition" click startclick href "#sub-start-screen-click" click savepath href "#sub-save-path" click loadpath href "#sub-load-path" click decodeparse href "#sub-decode-parse" click brainrotreconstruction href "#sub-brainrot-reconstruction" click slotrestoration href "#sub-slot-restoration" click slotpositioning href "#sub-slot-positioning-loop" click clickcollisioncheck href "#sub-click-collision-check"

❓ Frequently Asked Questions

What kind of visuals can I expect from the 'buy a brainrot' sketch?

The sketch features colorful brainrot characters that players can rescue, along with dynamic elements like a market zone and a base zone where players manage resources.

How can users interact with the 'buy a brainrot' game?

Users can collect brainrots, earn money through passive income, and engage in a selling market, all while clicking to rescue brainrots and managing their inventory.

What creative coding techniques are showcased in this p5.js sketch?

The sketch demonstrates concepts like object-oriented programming for managing brainrot types, dynamic UI elements for income tracking, and event handling for user interactions.

Preview

buy a brainrot - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of buy a brainrot - Code flow showing setup, draw, updategame, updatepassiveincome, handleinput, updateplayer, updatebrainrots, handlepickup, handledelivery, handleselling, collectfrombrainrots, maybespawnbrainrots, pickbrainrottypeindex, spawnbrainrot, drawplayer, drawbrainrots, drawbase, drawrunway, drawmarket, drawhud, drawbackgrounddecor, drawstartscreen, drawindexoverlay, gettrainbowcolor, formatmoney, pointinrect, keypressed, mousepressed, mousewheel, createsaveloadbutton, onsaveloadpressed, createsavecode, loadfromcode, setupbaseslots, trysellbasebrainrot, sellbrainrotinbase, gettotalincomeperr, windowresized, randintringe, initlayout, initgame, updatesavebuttonposition
Code Flow Diagram