buy a brainrot v3

This sketch creates a fast-paced resource collection game where you rescue magical creatures called brainrots that walk along a runway, deliver them to your base to generate passive income, and sell them for increasingly large sums of money. A competing AI bot also rescues brainrots, and you can upgrade your speed by visiting a power-up zone.

🧪 Try This!

Experiment with the code by making these changes:

  1. Make brainrots spawn faster — Lower the spawn interval to make creatures appear more frequently, increasing early-game income growth and chaos
  2. Boost the delivery reward — Increase the money awarded for delivering a brainrot to your base, making early game progression faster
  3. Increase max creatures on runway — Allow more brainrots to spawn simultaneously, creating more competition with the bot and visual chaos
  4. Nerf the speed upgrade — Reduce how much speed each upgrade grants, making upgrades less powerful and progression more gradual
  5. Make the player blue and the bot green (swap colors)
  6. Disable passive income collection — Prevent the player from collecting income by walking over stored brainrots (forces market selling only)
  7. Make Commons extremely rare — Flip the rarity weights so Common creatures almost never spawn and Legendaries appear constantly
Prefer the full editor? Open it there →

📖 About This Sketch

This sketch builds a complete incremental game where you catch colorful creatures, manage inventory slots, and watch money flow in from passive income. It is visually engaging with rare color-coded rarity tiers (from Common gray to OG gold), smooth animations, and constant numerical feedback. The code demonstrates advanced p5.js techniques including object-oriented game state management, array filtering and sorting, base64 save encoding, per-frame passive income calculation, collision detection between multiple entity types, and an AI bot with intelligent pathfinding.

The sketch is organized into three major sections: game state and initialization (setup, initGame, initLayout), the main update loop (updateGame with its subsystems), and rendering functions (draw* functions). By reading it, you will learn how to build a complete game from the ground up—how to manage multiple interacting entities, implement save/load functionality, create AI opponents, and design game progression through rarity tiers and ever-increasing income values.

⚙️ How It Works

  1. When the sketch loads, setup() creates a full-window canvas and initializes the game world with two base zones (player and bot), a runway where brainrots spawn, a market for selling, and an upgrade zone for speed boosts. A start screen appears explaining the controls.
  2. Pressing any key transitions to 'playing' mode, and brainrots begin spawning on the left side of the runway at random rarities. Each brainrot walks forward at its own speed and vanishes if it reaches the right edge without being rescued.
  3. Every frame, updateGame() runs collision detection (handlePickup) to let the player grab brainrots by touching them, handleDelivery to place rescued brainrots in base slots, and updatePassiveIncome to accumulate money per second from delivered brainrots based on their rarity.
  4. The AI bot independently pathfinds to the closest unrescued brainrot, picks it up, carries it to its base, and deposits it—competing with you for the same creatures. Both player and bot can visit the market zone to instantly sell carrying brainrots, or click delivered brainrots in their base to sell them.
  5. Walking into the speed upgrade zone increases player.speed by 0.3 (the bot automatically inherits this), with an 8-second cooldown. Rare, Epic, and Mythic brainrots generate much higher income per second, and unique brainrots like 'Strawberry Elephant' (OG rarity) can be worth $750 billion.
  6. Press I to open a scrollable index showing all rescued brainrots, their counts, per-second income, and market value. Press S/L to save/load your progress as a base64 code, preserving money, rescued count, bot state, all brainrot positions and types, and upgrade cooldown timers.

🎓 Concepts You'll Learn

Game state managementCollision detection (circle-rectangle, circle-circle)Passive income systemsAI pathfinding and targetingSave/load encoding (Base64)Rarity and loot tablesParticle-like creature animationUI overlay and scrolling panelsPer-entity autonomous behavior

📝 Code Breakdown

setup()

setup() runs exactly once when the sketch loads. Use it to prepare everything that won't change for the entire game session.

function setup() {
  createCanvas(windowWidth, windowHeight);
  createSaveLoadButton();
  initLayout();
  initGame();
}
Line-by-line explanation (4 lines)
createCanvas(windowWidth, windowHeight);
Creates a canvas that fills the entire browser window so the game takes up the full screen
createSaveLoadButton();
Adds a clickable button in the bottom-left corner to save and load game progress
initLayout();
Sets up the positions and sizes of all game zones (bases, runway, market, upgrade area) once at the start
initGame();
Initializes player, bot, brainrot array, and all game variables to their starting values

initLayout()

initLayout() creates a fixed layout once at startup. Every zone is positioned as a fraction of windowWidth and windowHeight, so it scales smoothly when the window resizes.

function initLayout() {
  const baseW = 220;
  const baseH = 150;

  baseZone = {
    x: width * 0.1,
    y: height * 0.5 - baseH / 2,
    w: baseW,
    h: baseH
  };
  playerBaseSlots = setupBaseSlots(baseZone);

  botBaseZone = {
    x: width * 0.55,
    y: height * 0.3 - baseH / 2,
    w: baseW,
    h: baseH
  };
  botBaseSlots = setupBaseSlots(botBaseZone);

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

  marketZone = {
    x: width * 0.78,
    y: height * 0.5 - 65,
    w: 180,
    h: 130
  };

  upgradeZone = {
    x: width / 2,
    y: height / 2,
    r: 40
  };
}
Line-by-line explanation (5 lines)

🔧 Subcomponents:

assignment Player base zone positioning baseZone = { x: width * 0.1, y: height * 0.5 - baseH / 2, w: baseW, h: baseH };

Places the player's storage area on the left side of the screen and creates slot objects inside it

assignment Bot base zone positioning botBaseZone = { x: width * 0.55, y: height * 0.3 - baseH / 2, w: baseW, h: baseH };

Places the bot's competing storage area in the middle-left of the screen

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

Defines the horizontal belt where brainrots spawn and walk toward the right edge

const baseW = 220;
Base zones are 220 pixels wide—wide enough to hold 4 columns of 8 brainrots (8 total slots)
baseZone = { x: width * 0.1, y: height * 0.5 - baseH / 2, w: baseW, h: baseH };
Player base sits at 10% from the left edge and is vertically centered
playerBaseSlots = setupBaseSlots(baseZone);
Creates an array of 8 slot objects (4 columns × 2 rows) inside the base zone
runway = { x: width * 0.25, y: height * 0.7 - 20, w: width * 0.65, h: 40 };
The runway spans 25% from left to 90% from left (65% of screen width) and is positioned near the bottom
upgradeZone = { x: width / 2, y: height / 2, r: 40 };
The speed upgrade zone is a circle at the screen center with radius 40 pixels—walk into it to gain speed

initGame()

initGame() resets the entire game state. All variables are cleared and all timers are set to the current millisecond so relative time calculations (like "has 2 seconds passed?") work correctly from the start.

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

  bot = {
    x: width * 0.2,
    y: height * 0.5 + 40,
    r: 18,
    speed: player.speed,
    carrying: null,
    vx: 0,
    vy: 0,
    target: null,
    noSpaceUntil: 0
  };

  for (let slot of playerBaseSlots) slot.occupied = false;
  for (let slot of botBaseSlots) slot.occupied = false;

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

  for (let i = 0; i < 4; i++) spawnBrainrot();
}
Line-by-line explanation (9 lines)

🔧 Subcomponents:

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

Creates the player as a circle with starting position, radius, speed, and velocity tracking

assignment Bot AI initialization bot = { x: width * 0.2, y: height * 0.5 + 40, r: 18, speed: player.speed, carrying: null, vx: 0, vy: 0, target: null, noSpaceUntil: 0 };

Creates the AI opponent near the player with an empty target (no brainrot picked yet)

for-loop Reset all game variables brainrots = []; rescuedCount = 0; money = 0; lastSpawnTime = millis();

Clears all creatures, money, and timers to start fresh

for-loop Spawn initial brainrots for (let i = 0; i < 4; i++) spawnBrainrot();

Creates 4 brainrots on the runway at game start so the player has immediate targets

player = { x: width * 0.2, y: height * 0.5, r: 18, speed: 4.2, carrying: null, vx: 0, vy: 0 };
Initializes player as an object with position (20% from left, vertically centered), radius 18, speed 4.2 pixels/frame, and no brainrot being carried
bot = { x: width * 0.2, y: height * 0.5 + 40, r: 18, speed: player.speed, carrying: null, vx: 0, vy: 0, target: null, noSpaceUntil: 0 };
Bot spawns below the player with the same speed. target is null (no brainrot targeted yet) and noSpaceUntil is 0 (can move freely now)
for (let slot of playerBaseSlots) slot.occupied = false;
Marks all 8 player base slots as empty so brainrots can be placed in them
brainrots = [];
Creates an empty array—all creatures from previous games are discarded
rescuedCount = 0;
Resets the count of brainrots delivered to your base to zero
money = 0;
Resets currency to zero
lastSpawnTime = millis();
Records the current time in milliseconds so the game knows when to spawn the next brainrot
lastPassiveTime = millis();
Records when passive income calculation started so it can track elapsed seconds accurately
for (let i = 0; i < 4; i++) spawnBrainrot();
Calls spawnBrainrot() four times, creating the first wave of creatures on the runway

draw()

draw() is the main loop running 60 times per second. It branches on gameState to show either the start screen or the live game. The order of draw* calls matters: background first (clears), then world elements, then player/bot on top, then UI.

function draw() {
  background(15, 14, 30);

  if (gameState === 'start') {
    drawBackgroundDecor();
    drawBaseArea(baseZone, playerBaseSlots, 'BASE', [110, 255, 180]);
    drawBaseArea(botBaseZone, botBaseSlots, 'BOT BASE', [255, 160, 160]);
    drawRunway();
    drawMarket();
    drawSpeedUpgrade();
    drawPlayer();
    drawBot();
    drawBrainrots();
    drawStartScreen();
    if (showIndex) drawIndexOverlay();
    return;
  }

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

  drawBackgroundDecor();
  drawBaseArea(baseZone, playerBaseSlots, 'BASE', [110, 255, 180]);
  drawBaseArea(botBaseZone, botBaseSlots, 'BOT BASE', [255, 160, 160]);
  drawRunway();
  drawMarket();
  drawSpeedUpgrade();
  drawBrainrots();
  drawPlayer();
  drawBot();
  drawHUD();
  if (showIndex) drawIndexOverlay();
}
Line-by-line explanation (8 lines)

🔧 Subcomponents:

conditional Start screen state if (gameState === 'start') { ... return; }

When the game has not started, show the title screen and instructions without running game logic

conditional Game update trigger if (gameState === 'playing') updateGame();

When game is running, call updateGame() to move players, spawn creatures, detect collisions, and calculate income

background(15, 14, 30);
Fills the entire canvas with a dark purple color, erasing the previous frame
if (gameState === 'start') {
Checks if the game is in start screen mode (before the player presses a key)
if (gameState === 'playing') updateGame();
If the game is running, updateGame() advances the simulation by one frame
drawBackgroundDecor();
Draws the grid pattern in the background for visual polish
drawBaseArea(baseZone, playerBaseSlots, 'BASE', [110, 255, 180]);
Draws the player's base storage area with green accents and label
drawBrainrots();
Draws all creatures on the runway and in bases with their rarity colors and names
drawHUD();
Draws the info panel showing money, income rate, rescued count, and player/bot status in the top-left
if (showIndex) drawIndexOverlay();
If the player pressed I, display the scrollable brainrot encyclopedia

updateGame()

updateGame() is the master update function called every frame when gameState is 'playing'. Its order is crucial: income first, then player input, then collisions, then bot AI. This ensures all game logic runs in a consistent sequence.

🔬 These five lines handle everything the player can do with brainrots. What happens if you move maybeSpawnBrainrots() to the top of updateGame()—before handlePickup()? The spawn timing might shift relative to pickups. Try reordering them and observe how the feel changes.

  handlePickup();
  handleDelivery();
  handleSelling();
  collectFromBrainrots();
  maybeSpawnBrainrots();
function updateGame() {
  updatePassiveIncome();
  handleInput();
  updatePlayer();
  handlePickup();
  handleDelivery();
  handleSelling();
  collectFromBrainrots();
  maybeSpawnBrainrots();
  handleSpeedUpgrade();

  bot.speed = player.speed;
  updateBot();
  handleBotPickup();
  handleBotDelivery();

  if (areBaseSlotsFull(playerBaseSlots)) {
    sellRandomBrainrotFromBase('player');
  }
  if (areBaseSlotsFull(botBaseSlots)) {
    sellRandomBrainrotFromBase('bot');
  }
}
Line-by-line explanation (11 lines)

🔧 Subcomponents:

function-call Passive income accumulation updatePassiveIncome();

Calculates how much money each delivered brainrot has earned since the last frame

sequence Player control and motion handleInput(); updatePlayer();

Reads keyboard input and updates player position based on velocity

sequence Player pickup and delivery handlePickup(); handleDelivery(); handleSelling();

Checks if player is touching a brainrot, delivering it to base, or selling in the market

sequence Bot AI synchronization and movement bot.speed = player.speed; updateBot(); handleBotPickup(); handleBotDelivery();

Makes bot speed match player speed, then runs bot pathfinding, pickup, and delivery logic

conditional Auto-sell when bases are full if (areBaseSlotsFull(playerBaseSlots)) { sellRandomBrainrotFromBase('player'); }

If all 8 slots are occupied, automatically sell one brainrot to free space

updatePassiveIncome();
Adds income to each delivered brainrot based on elapsed time and its per-second rate
handleInput();
Reads arrow keys or WASD and sets player.vx and player.vy
updatePlayer();
Moves the player by adding velocity to position and keeps them on-screen
handlePickup();
Tests if player is touching any free brainrot and marks it as carried if true
handleDelivery();
Tests if player (with a brainrot) is inside the base zone and places it in a slot if space exists
handleSelling();
Tests if player is inside the market zone and sells their carrying brainrot for its market value
collectFromBrainrots();
Tests if player is touching any delivered brainrot in their base and claims accumulated income
maybeSpawnBrainrots();
Checks if enough time has passed to spawn a new brainrot on the runway
handleSpeedUpgrade();
Checks if player is in the upgrade zone and applies a speed boost if cooldown is finished
bot.speed = player.speed;
Ensures the bot always runs at the same speed as the player (including after upgrades)
if (areBaseSlotsFull(playerBaseSlots)) { sellRandomBrainrotFromBase('player'); }
If all 8 player base slots are occupied, automatically sell one random brainrot to free space

handleInput()

handleInput() converts key presses into velocity. The normalization step ensures that pressing two keys at once doesn't make the player move √2 times faster diagonally—a critical detail for fair gameplay.

🔬 This block normalizes movement so diagonal input has the same speed as horizontal input. What happens if you DELETE this entire block? Try it—diagonal movement will be √2 times faster (√(1²+1²) = √2). Run the game and move diagonally. The player feels twitchy!

  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 (6 lines)

🔧 Subcomponents:

conditional Arrow key input detection 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;

Reads arrow keys (LEFT_ARROW, RIGHT_ARROW, UP_ARROW, DOWN_ARROW) and WASD (65=A, 68=D, 87=W, 83=S) to set direction

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

Converts (dx, dy) into a unit vector so diagonal movement is the same speed as straight movement

assignment Apply velocity based on speed stat player.vx = dx * player.speed; player.vy = dy * player.speed;

Multiplies the normalized direction by player.speed to set the actual velocity each frame

let dx = 0;
dx (delta-x) accumulates horizontal direction: -1 for left, +1 for right, 0 if neither or both
if (keyIsDown(LEFT_ARROW) || keyIsDown(65)) dx -= 1;
If left arrow or A key is held, subtract 1 from dx (move left)
if (dx || dy) {
Only normalize if at least one direction key is pressed (dx or dy is non-zero)
const len = sqrt(dx * dx + dy * dy);
Calculate the length of the direction vector (1 if one key, √2 ≈ 1.414 if two keys diagonal)
dx /= len;
Divide dx by its length so the result is between -1 and +1 (unit vector)
player.vx = dx * player.speed;
Multiply normalized direction by player.speed to get the final pixel-per-frame velocity

handleSpeedUpgrade()

handleSpeedUpgrade() implements a classic power-up pattern: check proximity, validate cooldown, apply effect, trigger feedback. The cooldown prevents spam; the flash provides satisfying visual confirmation.

function handleSpeedUpgrade() {
  if (!upgradeZone) return;

  const now = millis();
  if (dist(player.x, player.y, upgradeZone.x, upgradeZone.y) < player.r + upgradeZone.r / 2) {
    if (now - lastUpgradeTime > upgradeCooldown) {
      player.speed += 0.3;
      lastUpgradeTime = now;
      playerUpgradeFlashUntil = now + 500;
    }
  }
}
Line-by-line explanation (7 lines)

🔧 Subcomponents:

conditional Distance check to upgrade zone if (dist(player.x, player.y, upgradeZone.x, upgradeZone.y) < player.r + upgradeZone.r / 2) {

Tests if the player is overlapping the circular upgrade zone using Euclidean distance

conditional Cooldown timer validation if (now - lastUpgradeTime > upgradeCooldown) {

Ensures at least 8 seconds have passed since the last upgrade before applying another

assignment Speed increment player.speed += 0.3;

Increases player velocity by 0.3 pixels/frame, permanently

assignment Visual feedback flash playerUpgradeFlashUntil = now + 500;

Sets the timer to flash the player green for 500ms (visual confirmation of the upgrade)

if (!upgradeZone) return;
Safety check: if upgrade zone hasn't been initialized yet, exit early to avoid errors
const now = millis();
Captures the current time in milliseconds for cooldown calculation
if (dist(player.x, player.y, upgradeZone.x, upgradeZone.y) < player.r + upgradeZone.r / 2) {
dist() calculates distance between player center and upgrade zone center. If less than radius sum, they overlap
if (now - lastUpgradeTime > upgradeCooldown) {
Checks if enough time has elapsed (8000ms = 8 seconds) since lastUpgradeTime
player.speed += 0.3;
Permanently increases the player's movement speed by 0.3 pixels per frame
lastUpgradeTime = now;
Records this upgrade moment so the next one can't happen for 8 more seconds
playerUpgradeFlashUntil = now + 500;
Triggers a 500ms color flash in drawPlayer() to give visual feedback

updatePassiveIncome()

updatePassiveIncome() runs every frame to accumulate money from rescued brainrots. The delta-time approach (using elapsed seconds) is frame-rate independent: if the game lags and one frame takes 0.1 seconds instead of 0.016, income still ticks at the correct real-world rate.

🔬 This loop only accumulates income for delivered brainrots. What if you remove the condition '!br.despawned ||' so despawned brainrots can earn? Try it—they'd still earn income even after vanishing! That's silly, so we skip 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 (7 lines)

🔧 Subcomponents:

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

Converts millisecond elapsed time to seconds so income can be calculated per-second

for-loop Accumulate income for each brainrot for (let br of brainrots) { ... br.storedIncome = (br.storedIncome || 0) + inc * dt; }

Iterates through all delivered brainrots and adds their per-second income × elapsed seconds to storedIncome

const now = millis();
Captures current time in milliseconds
const dt = (now - lastPassiveTime) / 1000;
Calculates elapsed time in seconds by subtracting the last update time and dividing by 1000
lastPassiveTime = now;
Updates the reference point for the next frame's delta time calculation
if (dt <= 0) return;
Skips income calculation if no time has passed (safety check for the very first frame)
if (!br.delivered || br.despawned || br.sold || br.typeIndex == null) continue;
Only processes brainrots that are delivered, not despawned, not sold, and have a valid type
const inc = brainrotTypes[br.typeIndex].incomePerSecond;
Looks up how much this brainrot earns per second from its type definition
br.storedIncome = (br.storedIncome || 0) + inc * dt;
Adds (income per second) × (elapsed seconds) to the stored income, using 0 if storedIncome was undefined

handlePickup()

handlePickup() is a classic collision detection pattern: loop through objects, test distance, apply effect, break. The early return and carried flag prevent double-pickup bugs.

🔬 This loop grabs the FIRST free brainrot it finds in the array. What if you comment out the 'break;' statement? Now you'd try to set player.carrying multiple times in one frame (overwriting previous assignments). Try it and watch the last brainrot in the list get picked even if others are closer!

  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;
    }
  }
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 (7 lines)

🔧 Subcomponents:

conditional Can't carry two at once if (player.carrying) return;

Prevents the player from grabbing a second brainrot while already carrying one

for-loop Test all free brainrots for collision for (let br of brainrots) { if (dist(...) < player.r + br.r) { ... break; } }

Loops through all brainrots and grabs the first one within collision distance

if (player.carrying) return;
If the player already has a brainrot, exit the function immediately (can't carry two)
for (let br of brainrots) {
Loops through every brainrot in the array
if (br.rescued || br.carried || br.despawned || br.sold) continue;
Skips brainrots that are already rescued, being carried by bot, despawned, or sold
if (dist(player.x, player.y, br.x, br.y) < player.r + br.r) {
Calculates distance between player center and brainrot center; if less than the sum of their radii, they touch
br.carried = true;
Marks this brainrot as being carried so no other entity can grab it
player.carrying = br;
Stores a reference to this brainrot in the player object
break;
Exits the loop immediately after picking up one brainrot

handleDelivery()

handleDelivery() transitions a brainrot from 'being carried' to 'stored in base,' unlocking passive income. The placement function may fail if all slots are full, triggering a warning.

function handleDelivery() {
  if (!player.carrying || !baseZone) return;
  if (!pointInRect(player.x, player.y, baseZone)) return;

  const br = player.carrying;
  const placed = placeInBase(br, playerBaseSlots, baseZone);
  br.baseOwner = 'player';
  br.rescued = true;
  br.delivered = true;
  br.carried = false;
  player.carrying = null;
  rescuedCount++;
  money += brainrotReward;

  if (!placed) {
    noSpaceUntil = millis() + 3000;
  }
}
Line-by-line explanation (11 lines)

🔧 Subcomponents:

conditional Verify player is carrying if (!player.carrying || !baseZone) return;

Exits early if the player has no brainrot or base zone isn't initialized

conditional Check if player is in base if (!pointInRect(player.x, player.y, baseZone)) return;

Tests if player is inside the rectangular base zone using axis-aligned bounding box collision

function-call Attempt to place in base slot const placed = placeInBase(br, playerBaseSlots, baseZone);

Tries to fit the brainrot into an empty slot; returns true if successful, false if base is full

assignment Award money and set delivery flags rescuedCount++; money += brainrotReward;

Increments the rescued counter and gives 100 bonus money for delivery

if (!player.carrying || !baseZone) return;
Safety check: if player has no brainrot or base zone is missing, don't proceed
if (!pointInRect(player.x, player.y, baseZone)) return;
Tests if player's current (x, y) is inside the rectangle defined by baseZone
const br = player.carrying;
Stores the brainrot reference for easier access
const placed = placeInBase(br, playerBaseSlots, baseZone);
Calls placeInBase() which finds an empty slot and moves the brainrot there; returns true if successful
br.baseOwner = 'player';
Marks this brainrot as owned by the player (for later income tracking)
br.rescued = true;
Flags the brainrot as rescued so the bot won't try to grab it
br.carried = false;
Clears the carried flag so it won't bob above the player anymore
player.carrying = null;
Clears the player's reference to this brainrot
rescuedCount++;
Increments the global rescued counter (displayed in HUD)
money += brainrotReward;
Gives 100 money as a bonus for delivering (separate from future market value)
if (!placed) { noSpaceUntil = millis() + 3000; }
If base was full, set a timer to flash a warning message for 3 seconds

collectFromBrainrots()

collectFromBrainrots() is the active income collection mechanic: walk over your base creatures to claim their earned money. The Math.floor ensures fractional cents accumulate and eventually become whole coins.

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 (9 lines)

🔧 Subcomponents:

conditional Brainrot validity filter if (!br.delivered || br.despawned || br.sold || br.typeIndex == null) continue;

Skips brainrots that aren't actively earning income (despawned, sold, etc.)

conditional Collision-based income harvest if (dist(player.x, player.y, br.x, br.y) < player.r + br.r) {

Tests if player is touching a base brainrot; if so, collects its accumulated income

for (let br of brainrots) {
Loops through all brainrots to check which ones player is touching
if (!br.delivered || br.despawned || br.sold || br.typeIndex == null) continue;
Skips brainrots that aren't in your base (despawned = gone from world, sold = removed, typeIndex == null = corrupted)
if (dist(player.x, player.y, br.x, br.y) < player.r + br.r) {
If player touches this brainrot, proceed to collect income
const available = Math.floor(br.storedIncome || 0);
Rounds down the stored income to the nearest integer (can't earn fractional cents) and stores it in 'available'
if (available > 0) {
Only collect if there's at least 1 whole unit of income to claim
money += available;
Adds the collected income to the player's money
br.storedIncome = (br.storedIncome || 0) - available;
Subtracts the claimed amount from the brainrot's stored income (leaves fractional remainder for next time)
lastCollectAmount = available;
Stores the amount so the HUD can display a +X flash
collectFlashUntil = millis() + 800;
Sets a timer to show the income flash text for 800 milliseconds

updateBot()

updateBot() is an AI behavior system: it targets its base when carrying, otherwise chooses the closest about-to-despawn brainrot. The filtering and sorting create competitive gameplay where the bot races to rescue high-risk creatures.

🔬 The bot first filters for free brainrots, sorts by x position, then finds the closest. What happens if you DELETE the sort line? Now the bot ignores priority and just picks the nearest brainrot to its current position. Run the game and watch—the bot becomes much more reactive instead of strategic!

    const availableBrainrots = brainrots.filter(
      (br) => !br.rescued && !br.carried && !br.despawned && !br.sold
    );

    availableBrainrots.sort((a, b) => b.x - a.x);

    let closestBr = null;
    let closestDist = Infinity;
    for (let br of availableBrainrots) {
      const d = dist(bot.x, bot.y, br.x, br.y);
      if (d < closestDist) {
        closestDist = d;
        closestBr = br;
      }
    }
function updateBot() {
  if (bot.carrying && botBaseZone) {
    bot.target = {
      x: botBaseZone.x + botBaseZone.w / 2,
      y: botBaseZone.y + botBaseZone.h / 2
    };
  } else if (!bot.target) {
    const availableBrainrots = brainrots.filter(
      (br) => !br.rescued && !br.carried && !br.despawned && !br.sold
    );

    availableBrainrots.sort((a, b) => b.x - a.x);

    let closestBr = null;
    let closestDist = Infinity;
    for (let br of availableBrainrots) {
      const d = dist(bot.x, bot.y, br.x, br.y);
      if (d < closestDist) {
        closestDist = d;
        closestBr = br;
      }
    }
    if (closestBr) bot.target = closestBr;
  }

  if (bot.target) {
    let dx = bot.target.x - bot.x;
    let dy = bot.target.y - bot.y;
    const len = sqrt(dx * dx + dy * dy);
    if (len < bot.speed) {
      bot.x = bot.target.x;
      bot.y = bot.target.y;
      bot.target = null;
      bot.vx = 0;
      bot.vy = 0;
    } else {
      dx /= len;
      dy /= len;
      bot.vx = dx * bot.speed;
      bot.vy = dy * bot.speed;
      bot.x += bot.vx;
      bot.y += bot.vy;
    }
  } else {
    bot.vx = 0;
    bot.vy = 0;
  }

  bot.x = constrain(bot.x + (bot.vx || 0), 30, width - 30);
  bot.y = constrain(bot.y + (bot.vy || 0), 30, height - 30);

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

🔧 Subcomponents:

conditional Homing behavior when carrying if (bot.carrying && botBaseZone) { bot.target = { x: botBaseZone.x + botBaseZone.w / 2, y: botBaseZone.y + botBaseZone.h / 2 }; }

When bot has a brainrot, automatically set target to its base center

sequence Pathfinding target selection const availableBrainrots = brainrots.filter(...); availableBrainrots.sort(...); ... if (closestBr) bot.target = closestBr;

Filters free brainrots, prioritizes rightmost ones (closer to despawn), then picks the closest unrescued target

sequence Steering toward target if (bot.target) { let dx = bot.target.x - bot.x; ... bot.x += bot.vx; }

Calculates direction to target, normalizes it, multiplies by speed, and moves bot toward destination

if (bot.carrying && botBaseZone) {
If the bot has grabbed a brainrot and the base zone exists, set the target to the base
bot.target = { x: botBaseZone.x + botBaseZone.w / 2, y: botBaseZone.y + botBaseZone.h / 2 };
Sets target to the center of the bot's base zone
const availableBrainrots = brainrots.filter((br) => !br.rescued && !br.carried && !br.despawned && !br.sold);
Filters the brainrot array to only include creatures that haven't been rescued or are free to grab
availableBrainrots.sort((a, b) => b.x - a.x);
Sorts brainrots by x position in descending order, so rightmost (closest to despawning) appear first
let closestBr = null;
Initializes the closest brainrot as null
for (let br of availableBrainrots) { const d = dist(bot.x, bot.y, br.x, br.y); if (d < closestDist) { closestDist = d; closestBr = br; } }
Loops through sorted brainrots and records whichever one is nearest to the bot
if (bot.target) {
If a target exists (either the base or a brainrot), proceed to move toward it
let dx = bot.target.x - bot.x;
Calculates horizontal distance to target
const len = sqrt(dx * dx + dy * dy);
Calculates straight-line distance to target
if (len < bot.speed) {
If within one frame's travel distance, jump directly to target to avoid overshooting
dx /= len; dy /= len;
Normalizes direction to a unit vector (length 1)
bot.vx = dx * bot.speed; bot.vy = dy * bot.speed;
Applies bot speed to normalized direction, setting velocity for this frame
bot.x += bot.vx; bot.y += bot.vy;
Moves the bot by its velocity each frame
bot.x = constrain(bot.x + (bot.vx || 0), 30, width - 30);
Adds velocity to position and clamps it to stay within canvas with a 30-pixel margin
if (bot.carrying) { bot.carrying.x = bot.x; bot.carrying.y = bot.y - bot.r - bot.carrying.r * 0.6; }
If bot is carrying a brainrot, update its position to hover above the bot's head

drawPlayer()

drawPlayer() uses push/pop/translate to create a local coordinate system centered on the player. The rotation of the mouth-triangle demonstrates atan2 to calculate heading from velocity components.

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();
  let playerColor = [80, 140, 255];
  if (millis() < playerUpgradeFlashUntil) {
    playerColor = [180, 255, 180];
  }
  fill(playerColor[0], playerColor[1], playerColor[2]);
  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 (14 lines)

🔧 Subcomponents:

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

Draws a cyan ring around the player when holding a brainrot

conditional Upgrade flash feedback let playerColor = [80, 140, 255]; if (millis() < playerUpgradeFlashUntil) { playerColor = [180, 255, 180]; }

Changes the player body from blue to green for 500ms after a speed upgrade

sequence Eyes and head decoration circle(0, -4, player.r * 1.2); ... circle(-4, -5, 3); circle(4, -5, 3);

Draws a white head and two black eyes for character identity

sequence Direction-facing arrow let angle = atan2(player.vy || 0, player.vx || 1); ... triangle(player.r * 0.6, 0, player.r * 0.1, -3, player.r * 0.1, 3);

Draws a yellow triangle mouth that points in the direction the player is moving

push();
Saves the current transformation matrix so local translate/rotate don't affect other drawings
translate(player.x, player.y);
Moves the origin to the player's position so all subsequent draws are relative to the player
if (player.carrying) {
If the player has a brainrot, draw an aura circle to indicate carrying
circle(0, 0, player.r * 2.6);
Draws a cyan ring (hollow circle) with 1.3× the player's radius
let playerColor = [80, 140, 255];
Default player color is blue [80, 140, 255]
if (millis() < playerUpgradeFlashUntil) { playerColor = [180, 255, 180]; }
If within 500ms of an upgrade, change color to green to show visual feedback
fill(playerColor[0], playerColor[1], playerColor[2]);
Sets fill color to player color (either blue or green)
circle(0, 0, player.r * 2);
Draws the player's main body as a circle
fill(240, 240, 255); circle(0, -4, player.r * 1.2);
Draws a white head/forehead slightly above the body
fill(20); circle(-4, -5, 3); circle(4, -5, 3);
Draws two small black eyes for expression
let angle = atan2(player.vy || 0, player.vx || 1);
Calculates the angle of movement direction using arctangent; defaults to 0 (right) if stationary
rotate(angle);
Rotates the coordinate system so the triangle (mouth) points in movement direction
triangle(player.r * 0.6, 0, player.r * 0.1, -3, player.r * 0.1, 3);
Draws a yellow triangle (mouth) pointing right; rotation makes it face movement direction
pop();
Restores the transformation matrix so subsequent drawings are unaffected

drawBrainrots()

drawBrainrots() demonstrates color lookup tables (rarityGlowColors, rarityBodyColors), procedural animation (wigglePhase), and special effects (rainbow Brainrot God). The three-circle composition creates a simple creature design that scales with radius.

🔬 These three circles form the creature body. What happens if you change the positions (multiply constants differently) or sizes? Try changing -br.r * 0.3 to -br.r * 0.1, or br.r * 1.2 to br.r * 2. The creature's silhouette transforms completely!

    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 (15 lines)

🔧 Subcomponents:

assignment Determine brainrot rarity and colors const type = brainrotTypes[br.typeIndex]; const rarity = type ? type.rarity : 'Common'; let glowCol = rarityGlowColors[rarity]; let bodyCol = rarityBodyColors[rarity];

Looks up the brainrot's type definition to extract rarity and assign color scheme

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

Overrides colors for Brainrot God rarity with a constantly cycling rainbow effect

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

Adds vertical sine-wave oscillation to creatures on the runway; stops when delivered

sequence Creature body composition 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);

Draws three overlapping circles to form an abstract creature silhouette

for (let br of brainrots) {
Loops through all brainrots in the array
if (br.despawned || br.sold) continue;
Skips rendering brainrots that have reached the right edge or been sold
const type = brainrotTypes[br.typeIndex];
Looks up the brainrot type definition using the typeIndex
const rarity = type ? type.rarity : 'Common';
Extracts the rarity string (e.g., 'Legendary'), defaulting to 'Common' if type is missing
let glowCol = rarityGlowColors[rarity];
Looks up the glow color [R, G, B] array for this rarity from the global object
if (rarity === 'Brainrot God') { const [rr, gg, bb] = getRainbowColor(); ... }
Special case: Brainrot God creatures cycle through rainbow colors every frame
push(); translate(br.x, br.y);
Creates a local coordinate system centered on the brainrot
const bob = br.delivered ? 0 : sin(br.wigglePhase) * 2;
Calculates vertical bounce: sine wave of amplitude 2 for runway creatures, 0 for stored ones
translate(0, bob);
Applies the bob offset to make the creature bob up and down
if (glowCol) { noStroke(); fill(glowCol[0], glowCol[1], glowCol[2], 90); circle(0, 0, br.r * 2.8); }
Draws a semi-transparent colored aura behind the creature (if glow color exists for this rarity)
const bc = bodyCol || [230, 140, 255];
Uses the rarity body color, or defaults to magenta if undefined
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);
Draws three overlapping circles: two smaller ones for eyes/head, one larger for body
arc(0, -br.r * 0.2, br.r * 1.4, br.r * 0.8, PI, TWO_PI);
Draws a purple arc (bottom half of an ellipse) above center for mouth/expression
if (type) { noStroke(); fill(255); textAlign(CENTER, BOTTOM); textSize(10); text(type.name, 0, -br.r * 1.8); }
Draws the brainrot's name in white text above its head (e.g., 'Strawberry Elephant')
pop();
Restores the transformation matrix

drawSpeedUpgrade()

drawSpeedUpgrade() demonstrates procedural animation using sine waves: pulseScale and pulseAlpha both use sin(now * frequency) to create smooth, continuous cycling. This is a common pattern for UI elements that need to draw attention.

function drawSpeedUpgrade() {
  if (!upgradeZone) return;

  push();
  const { x, y, r } = upgradeZone;

  const now = millis();
  const pulseScale = 1 + sin(now * 0.005) * 0.1;
  const pulseAlpha = 80 + sin(now * 0.003) * 60;
  noStroke();
  fill(100, 200, 255, pulseAlpha);
  circle(x, y, r * 2.6 * pulseScale);

  fill(40, 80, 150);
  circle(x, y, r * 2);

  fill(255);
  noStroke();
  textAlign(CENTER, CENTER);
  textSize(r * 0.8);
  text('⇧', x, y - r * 0.05);

  textSize(14);
  fill(220, 240, 255);
  text('SPEED', x, y + r + 16);

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

🔧 Subcomponents:

calculation Animated glow effect const pulseScale = 1 + sin(now * 0.005) * 0.1; const pulseAlpha = 80 + sin(now * 0.003) * 60; ... circle(x, y, r * 2.6 * pulseScale);

Creates a breathing/pulsating aura that scales and fades in a sine wave pattern

drawing Upgrade zone core fill(40, 80, 150); circle(x, y, r * 2);

Draws the solid dark blue circular zone where the player must stand

sequence Icon and text label text('⇧', x, y - r * 0.05); ... text('SPEED', x, y + r + 16);

Displays an up arrow icon in the center and 'SPEED' label below the zone

if (!upgradeZone) return;
Safety check: if upgrade zone isn't initialized, exit early
const now = millis();
Captures current time to drive the pulsing animation
const pulseScale = 1 + sin(now * 0.005) * 0.1;
Creates a scale factor that oscillates between 0.9 and 1.1 at a slow frequency (every 1256ms for full cycle)
const pulseAlpha = 80 + sin(now * 0.003) * 60;
Creates an alpha value oscillating between 20 and 140, controlling aura opacity
fill(100, 200, 255, pulseAlpha); circle(x, y, r * 2.6 * pulseScale);
Draws the pulsating cyan aura that grows and fades to signal the power-up
fill(40, 80, 150); circle(x, y, r * 2);
Draws the solid dark blue core zone
text('⇧', x, y - r * 0.05);
Draws an up arrow (⇧) character to symbolize speed increase
text('SPEED', x, y + r + 16);
Draws the label 'SPEED' below the zone

drawHUD()

drawHUD() displays real-time game information by querying live game state. The conditional rendering of warnings and flashes creates responsive visual feedback for player actions.

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 playerStatus = player.carrying ? 'Carrying 1 brainrot' : 'Not carrying';
  fill(player.carrying ? color(190, 255, 190) : 220);
  text(playerStatus, 16, 56);

  const botStatus = bot.carrying ? 'Bot carrying 1 brainrot' : 'Bot idle';
  fill(bot.carrying ? color(255, 190, 190) : 220);
  text(botStatus, 16, 76);

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

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

  fill(220);
  textSize(13);
  text(`Player speed: ${player.speed.toFixed(2)}`, 16, 136);
  const upgradeCooldownRemaining = max(0, (lastUpgradeTime + upgradeCooldown - millis()) / 1000);
  if (upgradeCooldownRemaining > 0) {
    fill(255, 220, 120);
    text(`Upgrade CD: ${upgradeCooldownRemaining.toFixed(1)}s`, 16, 152);
  }

  const playerSlotsUsed = playerBaseSlots.filter((s) => s.occupied).length;
  const botSlotsUsed = botBaseSlots.filter((s) => s.occupied).length;
  fill(200, 240, 200);
  text(`Player base slots: ${playerSlotsUsed}/8`, 16, 172);
  fill(255, 210, 210);
  text(`Bot base slots: ${botSlotsUsed}/8`, 16, 188);

  let infoY = 210;

  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() < bot.noSpaceUntil) {
    textAlign(CENTER, TOP);
    textSize(18);
    fill(255, 180, 180);
    text('Bot: No space!', width / 2, 40);
    textAlign(LEFT, TOP);
  }

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

🔧 Subcomponents:

sequence Status display lines text(`Brainrots on runway: ${alive}`, 16, 16); text(`Rescued: ${rescuedCount}`, 16, 36);

Shows how many creatures are on the runway and total rescued count

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

Displays what player and bot are holding, with green text when carrying

sequence Currency and passive generation text(`Money: $${formatMoney(money)}`, 16, 96); ... text(`Income: $${income}/s`, 16, 116);

Shows current money balance and per-second passive income rate

sequence Speed stat and upgrade cooldown text(`Player speed: ${player.speed.toFixed(2)}`, 16, 136); if (upgradeCooldownRemaining > 0) { text(`Upgrade CD: ${upgradeCooldownRemaining.toFixed(1)}s`, 16, 152); }

Displays current player speed and remaining cooldown on next upgrade

sequence Storage capacity display text(`Player base slots: ${playerSlotsUsed}/8`, 16, 172); text(`Bot base slots: ${botSlotsUsed}/8`, 16, 188);

Shows how many brainrots are stored in each base vs total capacity

conditional Warning flash when base full if (millis() < noSpaceUntil) { text("You don't have space!", width / 2, 20); }

Displays a 3-second warning at screen center when player base fills up

conditional Collected income feedback if (millis() < collectFlashUntil && lastCollectAmount > 0) { text(`+${formatMoney(lastCollectAmount)}`, 16, infoY + 20); }

Shows a green '+$X' flash for 800ms after player collects income from a base brainrot

const alive = brainrots.filter((b) => !b.rescued && !b.despawned && !b.sold).length;
Counts brainrots that are still on the runway (not rescued, not gone, not sold)
text(`Brainrots on runway: ${alive}`, 16, 16);
Displays the runway creature count at position (16, 16) in the top-left
const playerStatus = player.carrying ? 'Carrying 1 brainrot' : 'Not carrying';
Creates a string describing what the player is holding
fill(player.carrying ? color(190, 255, 190) : 220);
Colors the status text green if carrying, gray otherwise
const income = getTotalIncomePerSecond();
Calls getTotalIncomePerSecond() to calculate total per-second income from all delivered brainrots
text(`Income: $${income}/s`, 16, 116);
Displays the per-second income rate
text(`Player speed: ${player.speed.toFixed(2)}`, 16, 136);
Shows the player's current speed stat with 2 decimal places (e.g., '4.20')
const upgradeCooldownRemaining = max(0, (lastUpgradeTime + upgradeCooldown - millis()) / 1000);
Calculates seconds until the next speed upgrade is available, never going below 0
if (upgradeCooldownRemaining > 0) { text(`Upgrade CD: ${upgradeCooldownRemaining.toFixed(1)}s`, 16, 152); }
Only shows the cooldown timer when it's still counting down
const playerSlotsUsed = playerBaseSlots.filter((s) => s.occupied).length;
Counts how many of the 8 player base slots are currently occupied
text(`Player base slots: ${playerSlotsUsed}/8`, 16, 172);
Displays the occupied/total slot ratio
if (millis() < noSpaceUntil) { text("You don't have space!", width / 2, 20); }
If within 3 seconds of a failed delivery, shows a red warning at screen center
if (millis() < collectFlashUntil && lastCollectAmount > 0) { text(`+${formatMoney(lastCollectAmount)}`, 16, infoY + 20); }
If within 800ms of collecting income, shows the amount as a green flash

drawStartScreen()

drawStartScreen() is a simple overlay system: a semi-transparent dark rectangle overlays the game world, and text is drawn on top. It's a common pattern for menus in games.

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('SELL them at the MARKET for huge value', width / 2, height / 2 + 80);
  text('Walk over base brainrots to collect income', width / 2, height / 2 + 110);
  text('Your bot rescues too — it has its own base!', width / 2, height / 2 + 140);
  text('Walk over the SPEED ⇧ zone for a speed boost!', width / 2, height / 2 + 170);
  text('Press I to view the Brainrot Index', width / 2, height / 2 + 200);

  fill(190, 255, 190);
  text('Press any key or click to start', width / 2, height / 2 + 240);
}
Line-by-line explanation (6 lines)
fill(0, 0, 0, 140);
Sets fill to semi-transparent black (alpha 140) to darken the game world underneath
rect(0, 0, width, height);
Draws a full-screen rectangle with that color, creating an overlay
textAlign(CENTER, CENTER);
Centers text horizontally and vertically at specified coordinates
text('BRAINROT RESCUE', width / 2, height / 2 - 80);
Draws the title in large white text at screen center-top
textSize(20);
Switches to smaller font size for instruction text
fill(220);
Changes text color to light gray

pickBrainrotTypeIndex()

pickBrainrotTypeIndex() implements weighted random selection, a fundamental pattern in game design for controlling spawn rates and loot tables. The weights directly control game difficulty and progression pacing.

🔬 This weighted selection algorithm is clever: it accumulates weights until the random number lands in a bucket. What if you reverse the rarityWeights array so OG appears 70 times and Common appears 0.0001 times? Try swapping the order and watch the game fill with Legendaries and OGs instead of Commons!

  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;
  }
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 (6 lines)

🔧 Subcomponents:

assignment Weighted rarity table const rarityWeights = [ ... ];

Defines spawn probabilities for each rarity tier (Common common, OG extremely rare)

calculation Weighted random selection let total = 0; for (let rw of rarityWeights) total += rw.weight; let r = random(total); for (let rw of rarityWeights) { if (r < rw.weight) { ... } r -= rw.weight; }

Picks a rarity using inverse transform sampling: random number determines which rarity bucket is selected

sequence Pick random type of chosen rarity const candidates = brainrotTypes.map((t, i) => ({ t, i })).filter((entry) => entry.t.rarity === rarity); return candidates[floor(random(candidates.length))].i;

Filters brainrotTypes by rarity, then randomly selects one from the matching types

const rarityWeights = [ { rarity: 'Common', weight: 70 }, ... ];
Defines a weighted distribution: Common appears 70 times as often as a 1-weight rarity, OG appears 0.0001 times as often
let total = 0; for (let rw of rarityWeights) total += rw.weight;
Sums all weights (70 + 67 + 41 + ... ≈ 236) to normalize the distribution
let r = random(total);
Picks a random number between 0 and total
for (let rw of rarityWeights) { if (r < rw.weight) { rarity = rw.rarity; break; } r -= rw.weight; }
Iterates through weights, subtracting each one. When r becomes negative (was less than a weight), that rarity is selected
const candidates = brainrotTypes.map((t, i) => ({ t, i })).filter((entry) => entry.t.rarity === rarity);
Filters the global brainrotTypes array to find all creatures matching the chosen rarity
return candidates[floor(random(candidates.length))].i;
Returns the typeIndex of a random creature from the matching list

createSaveCode()

createSaveCode() demonstrates serialization: converting game objects to JSON and then Base64-encoding them so players can share save codes. The key insight is converting object references (like brainrots.indexOf()) to indices that JSON can handle.

function createSaveCode() {
  const carryingIndex = bot.carrying ? brainrots.indexOf(bot.carrying) : -1;
  const playerCarryingIndex = player.carrying ? brainrots.indexOf(player.carrying) : -1;
  const payload = {
    money,
    rescuedCount,
    player: {
      x: player.x,
      y: player.y,
      r: player.r,
      speed: player.speed,
      vx: player.vx,
      vy: player.vy,
      carryingIndex: playerCarryingIndex
    },
    bot: {
      x: bot.x,
      y: bot.y,
      r: bot.r,
      speed: bot.speed,
      carryingIndex,
      vx: bot.vx,
      vy: bot.vy,
      target: bot.target ? { x: bot.target.x, y: bot.target.y } : null,
      noSpaceUntil: bot.noSpaceUntil
    },
    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,
      baseOwner: br.baseOwner || null
    })),
    lastUpgradeTime
  };
  const json = JSON.stringify(payload);
  return btoa(json);
}
Line-by-line explanation (5 lines)

🔧 Subcomponents:

calculation Serialize carrying references const carryingIndex = bot.carrying ? brainrots.indexOf(bot.carrying) : -1; const playerCarryingIndex = player.carrying ? brainrots.indexOf(player.carrying) : -1;

Converts object references to array indices so they can be stored in JSON (references can't be serialized)

sequence Serialize all brainrot states brainrots: brainrots.map((br) => ({ x: br.x, y: br.y, r: br.r, ... }))

Maps each brainrot object into a plain JavaScript object with only serializable properties

sequence Encode to Base64 const json = JSON.stringify(payload); return btoa(json);

Converts the payload object to JSON string, then encodes to Base64 (text-safe encoding)

const carryingIndex = bot.carrying ? brainrots.indexOf(bot.carrying) : -1;
Finds the array index of the brainrot the bot is carrying (or -1 if not carrying)
const payload = { money, rescuedCount, player: { ... }, bot: { ... }, brainrots: [ ... ], lastUpgradeTime };
Creates a plain object with all game state data needed to reconstruct the game
brainrots: brainrots.map((br) => ({ x: br.x, y: br.y, ... }))
Converts each brainrot object into a plain object with only the properties that need to be saved
const json = JSON.stringify(payload);
Converts the payload object to a JSON string (e.g., '{"money":5000,...}')
return btoa(json);
Encodes the JSON string to Base64 (btoa = byte-to-ASCII), making it a text string the player can copy/paste

loadFromCode(code)

loadFromCode() is the inverse of createSaveCode(): it decodes Base64, parses JSON, reconstructs all game objects, and restores the game to its saved state. Error handling (try/catch and validators) prevents crashes from corrupted codes.

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;

  for (let slot of playerBaseSlots) slot.occupied = false;
  for (let slot of botBaseSlots) slot.occupied = false;

  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,
    baseOwner: raw.baseOwner || null
  }));

  for (let br of brainrots) {
    if (br.delivered && !br.despawned && !br.sold) {
      const ownerIsBot = br.baseOwner === 'bot';
      const slots = ownerIsBot ? botBaseSlots : playerBaseSlots;
      const zone = ownerIsBot ? botBaseZone : baseZone;

      if (br.slotIndex != null && slots[br.slotIndex]) {
        const slot = slots[br.slotIndex];
        slot.occupied = true;
        br.x = slot.x;
        br.y = slot.y;
      } else if (zone) {
        br.slotIndex = null;
        br.x = zone.x + zone.w / 2;
        br.y = zone.y + zone.h / 2;
      }
    }
  }

  const rawPlayer = data.player || {};
  player = {
    x: rawPlayer.x ?? width * 0.2,
    y: rawPlayer.y ?? height * 0.5,
    r: rawPlayer.r ?? 18,
    speed: rawPlayer.speed ?? 4.2,
    carrying: null,
    vx: rawPlayer.vx ?? 0,
    vy: rawPlayer.vy ?? 0
  };

  const rawBot = data.bot || {};
  bot = {
    x: rawBot.x ?? width * 0.2,
    y: rawBot.y ?? height * 0.5 + 40,
    r: rawBot.r ?? 18,
    speed: rawBot.speed ?? player.speed,
    carrying: null,
    vx: rawBot.vx ?? 0,
    vy: rawBot.vy ?? 0,
    target: rawBot.target ? { x: rawBot.target.x, y: rawBot.target.y } : null,
    noSpaceUntil: rawBot.noSpaceUntil ?? 0
  };

  if (
    rawPlayer.carryingIndex != null && rawPlayer.carryingIndex !== -1 &&
    brainrots[rawPlayer.carryingIndex]
  ) {
    player.carrying = brainrots[rawPlayer.carryingIndex];
    player.carrying.carried = true;
    player.carrying.x = player.x;
    player.carrying.y = player.y - player.r - player.carrying.r * 0.6;
  }

  if (
    rawBot.carryingIndex != null &&
    rawBot.carryingIndex !== -1 &&
    brainrots[rawBot.carryingIndex]
  ) {
    bot.carrying = brainrots[rawBot.carryingIndex];
    bot.carrying.carried = true;
    bot.carrying.x = bot.x;
    bot.carrying.y = bot.y - bot.r - bot.carrying.r * 0.6;
  }

  lastSpawnTime = millis();
  lastPassiveTime = millis();
  noSpaceUntil = 0;
  collectFlashUntil = 0;
  lastCollectAmount = 0;
  indexScroll = 0;
  indexMaxScroll = 0;
  lastUpgradeTime = Number(data.lastUpgradeTime) || 0;
  playerUpgradeFlashUntil = 0;

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

🔧 Subcomponents:

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

Safely decodes the Base64 string and parses JSON; returns false if corrupted

conditional Validate decoded payload if (!data || !Array.isArray(data.brainrots)) return false;

Ensures the decoded data is valid before proceeding

sequence Reconstruct brainrot objects brainrots = data.brainrots.map((raw) => ({ ... }));

Converts plain saved objects back into full brainrot objects with all properties

for-loop Place delivered brainrots back in slots for (let br of brainrots) { if (br.delivered && !br.despawned && !br.sold) { ... } }

Re-occupies base slots and repositions brainrots to match their saved slot indices

sequence Restore carrying references if (rawPlayer.carryingIndex != null && rawPlayer.carryingIndex !== -1 && brainrots[rawPlayer.carryingIndex]) { ... }

Converts saved carrying indices back into object references

try { data = JSON.parse(atob(code)); } catch (err) { return false; }
Attempts to Base64-decode and JSON-parse the code; returns false if it fails (corrupted or invalid code)
if (!data || !Array.isArray(data.brainrots)) return false;
Validates that the decoded data has a brainrots array; rejects if missing
money = Number(data.money) || 0;
Restores money, converting to a number and defaulting to 0 if undefined
for (let slot of playerBaseSlots) slot.occupied = false;
Resets all base slots as empty before restoring saved ones
brainrots = data.brainrots.map((raw) => ({ x: raw.x, y: raw.y, ... }));
Reconstructs the brainrot array from saved plain objects
if (br.slotIndex != null && slots[br.slotIndex]) { const slot = slots[br.slotIndex]; slot.occupied = true; br.x = slot.x; br.y = slot.y; }
If a brainrot has a saved slot index, re-occupy that slot and reposition the brainrot to the slot's coordinates
const rawPlayer = data.player || {};
Extracts player data from save, defaulting to empty object if missing
player = { x: rawPlayer.x ?? width * 0.2, y: rawPlayer.y ?? height * 0.5, ... };
Reconstructs the player object, using saved values or defaults if missing (the ?? operator is nullish coalescing)
if (rawPlayer.carryingIndex != null && rawPlayer.carryingIndex !== -1 && brainrots[rawPlayer.carryingIndex]) { player.carrying = brainrots[rawPlayer.carryingIndex]; ... }
If a carrying index was saved, restores the reference and marks the brainrot as carried
lastSpawnTime = millis(); lastPassiveTime = millis();
Resets time-based timers to the current moment so relative timing calculations are correct
gameState = 'playing';
Transitions the game from start screen to playing mode
return true;
Indicates successful load

keyPressed()

keyPressed() is called every time a key is pressed. It handles two game-wide commands: toggling the index and starting the game. The early return prevents 'I' from also starting the game.

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

🔧 Subcomponents:

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

Pressing I or i toggles the brainrot encyclopedia overlay open/closed

conditional Start game on keypress if (gameState === 'start') gameState = 'playing';

Any other key press transitions from start screen to playing

if (key === 'i' || key === 'I') {
Checks if the pressed key is lowercase or uppercase I
showIndex = !showIndex;
Toggles the showIndex boolean (true becomes false, false becomes true)
return;
Exits the function early so the start game code below doesn't run
if (gameState === 'start') gameState = 'playing';
If still on the start screen, pressing any other key starts the game

📦 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 character's position (x, y), radius (r), speed, velocity (vx, vy), and what brainrot is being carried

let player = { x: 200, y: 300, r: 18, speed: 4.2, carrying: null, vx: 0, vy: 0 };
bot object

Stores the AI opponent's position, radius, speed, velocity, current target (brainrot or base), and cooldown timer for when its base is full

let bot = { x: 200, y: 340, r: 18, speed: 4.2, carrying: null, vx: 0, vy: 0, target: null, noSpaceUntil: 0 };
brainrots array

Array of all creature objects currently in the game; each tracks position, state flags (rescued, delivered, sold), rarity type, and stored income

let brainrots = [];
money number

The player's total currency; increases through delivery bonuses, market sales, and passive income collection

let money = 0;
rescuedCount number

Counter of how many brainrots the player has successfully delivered to their base (displayed in HUD)

let rescuedCount = 0;
baseZone object

Rectangle defining the player's base storage area (x, y, w, h); brainrots placed here generate passive income

let baseZone = { x: 100, y: 300, w: 220, h: 150 };
playerBaseSlots array

Array of 8 slot objects where the player can store brainrots; each slot has x, y, and occupied boolean

let playerBaseSlots = [{ x: 100, y: 200, occupied: false }, ...];
botBaseSlots array

Array of 8 slot objects for the bot's storage, separate from the player's

let botBaseSlots = [...];
runway object

Rectangle defining the horizontal conveyor-like area where brainrots spawn and walk (x, y, w, h)

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

Rectangle defining the area where the player can instantly sell a carrying brainrot for its market value

let marketZone = { x: 1000, y: 400, w: 180, h: 130 };
upgradeZone object

Circle defining the speed boost power-up zone with x, y, and radius r; walking over grants +0.3 speed with 8-second cooldown

let upgradeZone = { x: 640, y: 360, r: 40 };
maxBrainrotsOnMap number

Maximum number of creatures allowed on the runway at once; controls game difficulty and pacing

let maxBrainrotsOnMap = 10;
spawnInterval number

Milliseconds between brainrot spawn events; lower values increase spawn frequency and income growth

let spawnInterval = 2000;
lastSpawnTime number

Timestamp (in ms) of the last brainrot spawn; used to track if enough time has passed to spawn another

let lastSpawnTime = 0;
lastPassiveTime number

Timestamp of the last passive income calculation; used to compute delta time for frame-independent income accumulation

let lastPassiveTime = 0;
lastUpgradeTime number

Timestamp of the last speed upgrade activation; used to enforce the 8-second cooldown between upgrades

let lastUpgradeTime = 0;
upgradeCooldown number

Milliseconds required between consecutive speed upgrades (8000 = 8 seconds)

const upgradeCooldown = 8000;
playerUpgradeFlashUntil number

Timestamp until which the player should be rendered in green (visual feedback for upgrade activation)

let playerUpgradeFlashUntil = 0;
showIndex boolean

Whether the brainrot encyclopedia overlay is currently visible (toggled by pressing I)

let showIndex = false;
indexScroll number

Vertical scroll offset (in pixels) for the index panel, allowing mouse wheel scrolling through all creatures

let indexScroll = 0;
collectFlashUntil number

Timestamp until which the '+$X' income collection text should be displayed (800ms flash)

let collectFlashUntil = 0;
lastCollectAmount number

The amount of money collected in the last income harvest (displayed in the flash text)

let lastCollectAmount = 0;
noSpaceUntil number

Timestamp until which the 'You don't have space!' warning should be shown (3 seconds after failed delivery)

let noSpaceUntil = 0;
brainrotTypes array

Master list of all creature types with their names, rarities, and computed properties (incomePerSecond, marketValue)

const brainrotTypes = [{ name: 'Strawberry Elephant', rarity: 'OG' }, ...];
rarityValueRanges object

Lookup table mapping rarity tiers to [minPrice, maxPrice] ranges for market value generation

const rarityValueRanges = { Common: [25, 1700], Legendary: [35000, 345000], ... };
rarityIncome object

Lookup table mapping rarity tiers to default per-second income rates (overridden by customIncome for unique creatures)

const rarityIncome = { Common: 1, Rare: 3, Epic: 7, ... };
customIncome object

Special per-second income rates for individual named creatures (e.g., 'Strawberry Elephant': 750000000)

const customIncome = { 'Strawberry Elephant': 750000000, ... };
rarityLabelColors object

Lookup table mapping rarity tiers to [R, G, B] text color arrays for index display

const rarityLabelColors = { Legendary: [255, 240, 130], OG: [255, 180, 255], ... };
rarityGlowColors object

Lookup table mapping rarity tiers to [R, G, B] glow effect color arrays (null for special rarities)

const rarityGlowColors = { Rare: [120, 170, 255], OG: [255, 215, 0], ... };
rarityBodyColors object

Lookup table mapping rarity tiers to [R, G, B] body color arrays for creature rendering

const rarityBodyColors = { Epic: [190, 100, 255], OG: [255, 215, 0], ... };

🔧 Potential Improvements (6)

Here are some ways this code could be enhanced:

BUG updateBot() and updatePlayer()

When bot or player velocity is high, they can overshoot the screen boundary, potentially getting stuck at edges if the constrain() margin is too small relative to speed. Position updates happen twice (once in the movement code, once in updatePlayer/updateBot with additional +vx, +vy), creating inconsistent behavior.

💡 Consolidate position updates to happen once per frame. Remove the redundant '+bot.vx' and '+bot.vy' in the constrain() lines since bot.x/bot.y were already updated above. Example: 'bot.x = constrain(bot.x, 30, width - 30);' instead of 'bot.x = constrain(bot.x + (bot.vx || 0), 30, width - 30);'

PERFORMANCE getTotalIncomePerSecond() and drawIndexOverlay()

getTotalIncomePerSecond() is called every frame in both updatePassiveIncome() and drawHUD(), recalculating the same sum redundantly. drawIndexOverlay() also recalculates it. This wastes CPU on a function that only changes when brainrots are delivered/sold.

💡 Cache the total income value and only recalculate when brainrots are delivered/sold/removed. Store it in a global variable and update it in handleDelivery(), handleSelling(), and sellBrainrotInBase() instead of recalculating 120+ times per second.

BUG placeInBase() and handleDelivery()

When a brainrot is delivered but the base is full, the brainrot is placed at zone center (zone.x + zone.w/2, zone.y + zone.h/2) instead of remaining in an empty slot. This creates a cluster of brainrots at the zone center. If the player delivers multiple times while full, they stack on top of each other, making it hard to click-sell individual ones.

💡 Add a 'fallback' radius spiral or jitter to position overflow brainrots around the base boundary instead of directly on the zone center. Or disallow delivery when slots are full and flash a warning immediately instead of placing and then clearing.

FEATURE Game-wide difficulty

Legendary and higher rarity brainrots are so profitable and spawn so rarely that the game's pacing feels inconsistent: early game is grindy Common creatures, then a lucky OG spawn instantly wins the game (750 billion income/second). Players can reach nearly infinite money.

💡 Implement a 'prestige' or 'reset' system where players can voluntarily restart but carry forward a multiplicative bonus (e.g., 1.1x income for each prestige level). Add cosmetic/progression unlockables tied to total lifetime earnings. Or cap income-per-second to slow exponential growth.

STYLE Global state variables

Game state is scattered across 25+ global variables (money, rescuedCount, noSpaceUntil, etc.), making it hard to trace what depends on what and making save/load error-prone if a variable is forgotten.

💡 Create a single `gameData` object or class that encapsulates all state: `let gameData = { money, rescuedCount, player, bot, brainrots, timers: { noSpaceUntil, lastSpawnTime, ... } }`. Pass it through functions instead of relying on globals, making state dependencies explicit.

BUG mouseWheel() and indexScroll

The index scroll calculation uses `indexScroll -= event.delta * 0.5`, which is frame-dependent. Scrolling speed varies if the scroll event fires irregularly or the frame rate drops. Also, indexMaxScroll is recalculated every frame in drawIndexOverlay(), causing scroll bounds to jitter if content height changes.

💡 Clamp indexScroll immediately after update and ensure indexMaxScroll is calculated only when the content changes (after brainrots are added/removed), not every draw frame.

🔄 Code Flow

Code flow showing setup, initlayout, initgame, draw, updategame, handleinput, handlespeedupgrade, updatepassiveincome, handlePickup, handledelivery, collectfrombrainrots, updatebot, drawplayer, drawbrainrots, drawspeedupgrade, drawhud, drawstartscreen, pickbrainrottypeindex, createsavecode, loadfromcode, keypressed

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

graph TD start[Start] --> setup[setup] setup --> initlayout[initLayout] setup --> initgame[initGame] setup --> draw[draw loop] click setup href "#fn-setup" click initlayout href "#fn-initlayout" click initgame href "#fn-initgame" click draw href "#fn-draw" draw --> start-state-branch[start-state-branch] draw --> playing-state-branch[playing-state-branch] click start-state-branch href "#sub-start-state-branch" click playing-state-branch href "#sub-playing-state-branch" start-state-branch --> drawstartscreen[drawStartScreen] drawstartscreen --> draw click drawstartscreen href "#fn-drawstartscreen" playing-state-branch --> updategame[updateGame] click updategame href "#fn-updategame" updategame --> passive-income-update[passive-income-update] updategame --> player-movement[player-movement] updategame --> player-pickup[player-pickup] updategame --> bot-sync-and-update[bot-sync-and-update] click passive-income-update href "#sub-passive-income-update" click player-movement href "#sub-player-movement" click player-pickup href "#sub-player-pickup" click bot-sync-and-update href "#sub-bot-sync-and-update" passive-income-update --> deltatime-calc[deltatime-calc] deltatime-calc --> income-loop[income-loop] click deltatime-calc href "#sub-deltatime-calc" click income-loop href "#sub-income-loop" player-movement --> arrow-key-input[arrow-key-input] arrow-key-input --> vector-normalization[vector-normalization] vector-normalization --> velocity-apply[velocity-apply] click arrow-key-input href "#sub-arrow-key-input" click vector-normalization href "#sub-vector-normalization" click velocity-apply href "#sub-velocity-apply" player-pickup --> carry-check[carry-check] carry-check --> collision-loop[collision-loop] collision-loop --> carrying-check[carrying-check] carrying-check --> zone-check[zone-check] zone-check --> slot-placement[slot-placement] slot-placement --> reward-and-flags[reward-and-flags] click carry-check href "#sub-carry-check" click collision-loop href "#sub-collision-loop" click carrying-check href "#sub-carrying-check" click zone-check href "#sub-zone-check" click slot-placement href "#sub-slot-placement" click reward-and-flags href "#sub-reward-and-flags" bot-sync-and-update --> return-to-base[return-to-base] return-to-base --> target-selection[target-selection] target-selection --> steering[steering] click return-to-base href "#sub-return-to-base" click target-selection href "#sub-target-selection" click steering href "#sub-steering" draw --> drawplayer[drawPlayer] draw --> drawbrainrots[drawBrainrots] draw --> drawspeedupgrade[drawSpeedUpgrade] draw --> drawhud[drawHUD] click drawplayer href "#fn-drawplayer" click drawbrainrots href "#fn-drawbrainrots" click drawspeedupgrade href "#fn-drawspeedupgrade" click drawhud href "#fn-drawhud" drawplayer --> carrying-ring[carrying-ring] carrying-ring --> body-color[body-color] carrying-ring --> facial-features[facial-features] carrying-ring --> velocity-arrow[velocity-arrow] click carrying-ring href "#sub-carrying-ring" click body-color href "#sub-body-color" click facial-features href "#sub-facial-features" click velocity-arrow href "#sub-velocity-arrow" drawbrainrots --> rarity-lookup[rarity-lookup] rarity-lookup --> rainbow-brainrot-god[rainbow-brainrot-god] rainbow-brainrot-god --> bob-animation[bob-animation] bob-animation --> body-drawing[body-drawing] click rarity-lookup href "#sub-rarity-lookup" click rainbow-brainrot-god href "#sub-rainbow-brainrot-god" click bob-animation href "#sub-bob-animation" click body-drawing href "#sub-body-drawing" drawspeedupgrade --> pulsating-aura[pulsating-aura] pulsating-aura --> core-zone[core-zone] pulsating-aura --> symbol-and-label[symbol-and-label] click pulsating-aura href "#sub-pulsating-aura" click core-zone href "#sub-core-zone" click symbol-and-label href "#sub-symbol-and-label" drawhud --> game-stats[game-stats] game-stats --> carrying-status[carrying-status] carrying-status --> money-and-income[money-and-income] money-and-income --> speed-and-cooldown[speed-and-cooldown] speed-and-cooldown --> base-slots[base-slots] click game-stats href "#sub-game-stats" click carrying-status href "#sub-carrying-status" click money-and-income href "#sub-money-and-income" click speed-and-cooldown href "#sub-speed-and-cooldown" click base-slots href "#sub-base-slots" updategame --> autosell-full-bases[autosell-full-bases] autosell-full-bases --> error-messages[error-messages] error-messages --> income-flash[income-flash] click autosell-full-bases href "#sub-autosell-full-bases" click error-messages href "#sub-error-messages" click income-flash href "#sub-income-flash" keypressed[keyPressed] --> index-toggle[index-toggle] keypressed --> start-game-trigger[start-game-trigger] click keypressed href "#fn-keyPressed" click index-toggle href "#sub-index-toggle" click start-game-trigger href "#sub-start-game-trigger"

❓ Frequently Asked Questions

What visual elements can I expect to see in the buy a brainrot v3 sketch?

The sketch visually represents a whimsical environment where players can collect various 'brainrots' with different rarities, alongside interactive zones for selling and upgrading.

How can I interact with the buy a brainrot v3 sketch during gameplay?

Users can collect brainrots, sell them by clicking on their base, and access a scrollable index panel to view brainrot details and manage their income.

What creative coding concepts are showcased in the buy a brainrot v3 sketch?

This sketch demonstrates concepts like passive income mechanics, dynamic spawning of collectible items, and user interface elements like scrolling panels and upgrade systems.

Preview

buy a brainrot v3 - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of buy a brainrot v3 - Code flow showing setup, initlayout, initgame, draw, updategame, handleinput, handlespeedupgrade, updatepassiveincome, handlePickup, handledelivery, collectfrombrainrots, updatebot, drawplayer, drawbrainrots, drawspeedupgrade, drawhud, drawstartscreen, pickbrainrottypeindex, createsavecode, loadfromcode, keypressed
Code Flow Diagram