buy a brainrot v2

A resource-management game where you rescue cute 'brainrot' creatures spawning on a runway, deliver them to your base to earn passive income, and sell them at the market for profit. A rival bot simultaneously rescues brainrots to its own base, creating a competitive collection game with eight rarity tiers, persistent save/load codes, and real-time income generation.

🧪 Try This!

Experiment with the code by making these changes:

  1. Make creatures spawn faster — Lower the spawn interval so new brainrots appear more frequently and the game becomes chaotic
  2. Double player movement speed
  3. Make the bot much faster — Buff the bot's speed so it becomes a competitive rival and races you for creatures
  4. Increase the delivery reward tenfold — Earn 1000 money per delivery instead of 100—watch your wealth explode
  5. Allow 20 creatures on map at once — Make the runway much more crowded so there's always lots to rescue
  6. Change the background to bright cyan — The dark purple becomes vibrant—gives the whole game a different feel
Prefer the full editor? Open it there →

📖 About This Sketch

This sketch creates a whimsical resource-management game inspired by creature-collection games. Brainrots continuously spawn along a runway and walk toward the exit; you and a rival bot race to rescue them, deliver them to your respective bases, and sell them for profit. What makes it visually engaging is the colorful creature designs with rarity-based glow effects, the animated runway with checkpoints, and the dynamic market zone—all rendered with p5.js shapes and fill colors.

The code demonstrates several advanced game-design patterns: a global game state machine (start vs. playing), passive income tracked per-creature, a scrollable inventory index, AI pathfinding for the bot, collision-based pickup/delivery mechanics, and a Base64-encoded save/load system. By studying it you will learn how to organize a multi-system game, implement a simple AI agent with targeting logic, manage passive rewards over time, and give players the power to persist progress.

⚙️ How It Works

  1. When the sketch loads, setup() initializes the canvas, creates two base zones (player and bot), a runway where creatures spawn, and a market zone for selling. The game starts in 'start' state showing an instruction screen.
  2. Once the player presses a key or clicks, gameState becomes 'playing' and the main draw loop begins. Every frame, the game spawns new brainrots on the runway if fewer than 10 are alive, draws all zones and creatures, and renders the heads-up display showing money, rescued count, and income rates.
  3. Brainrots drift rightward along the runway at random speeds; if they reach the end, they despawn. You control your player character with WASD or arrow keys and can pick up any brainrot by walking into it—the creature follows above your head.
  4. When you enter your base zone while carrying a brainrot, it is placed into an available slot and begins generating passive income every frame. You can walk over it to collect that stored income instantly, or click on it to see a sell dialog and exchange it for its market value.
  5. The bot independently hunts for the rightmost uncollected brainrot, moves toward it, picks it up, and carries it to its own base—implementing a simple greedy AI. Both bases have 8 slots (4 columns × 2 rows); if a base fills up, a random brainrot in it is automatically sold to free space.
  6. Each brainrot type has a custom income-per-second value and market value determined by its rarity (Common through OG); rarer creatures earn more passive income and sell for vastly more money. Press I to open a scrollable index showing all rescued creatures, their counts, income rates, and sell values. Press Save/Load to persist your game progress as a Base64-encoded code.

🎓 Concepts You'll Learn

Game state machineCollision detection and pickup mechanicsPassive income accumulationAI pathfinding and targetingBase64 save/load encodingRarity-based value systemsScrollable UI panelsArray filtering and sorting

📝 Code Breakdown

setup()

setup() runs once when the page loads. It prepares the game world and all variables before draw() begins running.

function setup() {
  createCanvas(windowWidth, windowHeight);
  createSaveLoadButton();
  initLayout();
  initGame();
}
Line-by-line explanation (4 lines)
createCanvas(windowWidth, windowHeight);
Creates a full-screen canvas that fills the browser window
createSaveLoadButton();
Adds a button to the page that lets you save your game progress as a code or load a previously saved code
initLayout();
Calculates the positions of the two bases, the runway, and the market zone based on screen size
initGame();
Initializes all game variables: creates the player and bot, clears the brainrot array, resets money to 0, and spawns the first few creatures

draw()

draw() is the heartbeat of the sketch, running 60 times per second. It clears the screen, updates game logic only when playing, and renders everything. The gameState variable acts as a traffic cop, directing which parts of the code run when.

🔬 The start state draws the bases, runway, and creatures even before the game begins. What happens if you comment out the drawStartScreen() line so the instruction text disappears but the world remains visible?

  if (gameState === 'start') {
    drawBackgroundDecor();
    drawBaseArea(baseZone, playerBaseSlots, 'BASE', [110, 255, 180]);
    drawBaseArea(botBaseZone, botBaseSlots, 'BOT BASE', [255, 160, 160]);
    drawRunway();
    drawMarket();
    drawPlayer();
    drawBot();
    drawBrainrots();
    drawStartScreen();
    if (showIndex) drawIndexOverlay();
    return;
  }
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();
    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();
  drawBrainrots();
  drawPlayer();
  drawBot();
  drawHUD();
  if (showIndex) drawIndexOverlay();
}
Line-by-line explanation (7 lines)

🔧 Subcomponents:

conditional Start screen render if (gameState === 'start') {

When the game is in start state, render the intro screen and exit early; this prevents game logic from running until the player starts

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

Runs all game logic (spawning, movement, collision, income) only when actively playing

background(15, 14, 30);
Fills the entire canvas with a dark blue-purple color every frame, erasing the previous frame's drawings
if (gameState === 'start') {
Checks if the game is still on the title/instruction screen
if (gameState === 'playing') updateGame();
Calls updateGame() to process all movement, collisions, income, and spawning—this is where the game logic lives
drawBackgroundDecor();
Renders a grid pattern in the background to give the game world visual structure
drawBrainrots();
Loops through all creatures and draws each one with its rarity-based colors and glows
drawHUD();
Renders the heads-up display: money, rescued count, income rate, base slot usage, and status messages
if (showIndex) drawIndexOverlay();
If the player pressed I to open the index, renders the scrollable panel listing all rescued creatures and their stats

updateGame()

updateGame() orchestrates all the game logic in a specific order: income first, then player input, then bot behavior, then auto-sell if needed. This order matters—if you collected income before updating positions, creatures would earn money before moving, which is subtle but important.

🔬 These lines auto-sell creatures when a base is full. What happens if you remove or comment out both of these lines? Try it and see what error or behavior occurs when you fill up your base.

  if (areBaseSlotsFull(playerBaseSlots)) {
    sellRandomBrainrotFromBase('player');
  }
  if (areBaseSlotsFull(botBaseSlots)) {
    sellRandomBrainrotFromBase('bot');
  }
function updateGame() {
  updatePassiveIncome();
  handleInput();
  updatePlayer();
  updateBrainrots();
  handlePickup();
  handleDelivery();
  handleSelling();
  collectFromBrainrots();
  maybeSpawnBrainrots();

  updateBot();
  handleBotPickup();
  handleBotDelivery();

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

🔧 Subcomponents:

calculation Passive income accumulation updatePassiveIncome();

Adds money to each brainrot's stored income based on its per-second rate and elapsed time

calculation Player movement and interaction handleInput(); updatePlayer(); handlePickup(); handleDelivery(); handleSelling(); collectFromBrainrots();

Reads keyboard input, moves the player, handles pickup/delivery/selling interactions, and collects passive income by contact

calculation Bot AI movement and interaction updateBot(); handleBotPickup(); handleBotDelivery();

Updates the rival bot's position toward its target, handles its pickups and deliveries

conditional Auto-sell on full base if (areBaseSlotsFull(playerBaseSlots)) { sellRandomBrainrotFromBase('player'); }

When a base is completely full, automatically sells a random creature to free up space so new ones can be delivered

updatePassiveIncome();
Runs passive income logic: for each brainrot in a base, calculates how much income it generated since the last frame and stores it
handleInput();
Reads which keys are pressed (WASD or arrow keys) and updates the player's velocity accordingly
updatePlayer();
Moves the player by adding velocity to position, keeps them on-screen, and updates carried brainrot position to follow
updateBrainrots();
Moves all brainrots rightward along the runway and marks them despawned if they walk off the right edge
handlePickup();
Checks if the player is touching an uncollected brainrot and picks it up if so (only one at a time)
handleDelivery();
If the player is in the base zone while carrying a brainrot, delivers it, assigns it to a base slot, and adds money
handleSelling();
If the player walks into the market zone while carrying a brainrot, sells it for its market value
collectFromBrainrots();
If the player walks over a brainrot in their base, collects all its stored income instantly and adds it to money
maybeSpawnBrainrots();
Checks if there are fewer than maxBrainrotsOnMap alive and if enough time has passed; if so, spawns a new one
updateBot();
Moves the bot toward its current target (either a brainrot or its base) using simple pathfinding
handleBotPickup();
If the bot reaches a brainrot it is targeting, picks it up
handleBotDelivery();
If the bot enters its base while carrying, delivers the brainrot and assigns it to a slot

updatePassiveIncome()

This function implements the core idle-game mechanic: passive income accumulation. By tracking time in seconds (not frames), income stays consistent even if the frame rate varies. The 'delivered' check ensures only creatures in your base earn money.

🔬 This loop only adds income to creatures that are delivered (!br.despawned && !br.sold). What happens if you remove the despawned and sold checks—would creatures still in the runway or already sold continue generating income? Try removing the || br.despawned check and see what happens.

  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 Elapsed time calculation const dt = (now - lastPassiveTime) / 1000;

Calculates how many seconds passed since the last frame; this ensures income scales with real time, not frame rate

for-loop Per-brainrot income accumulation for (let br of brainrots) {

Loops through all creatures and adds their income-per-second × elapsed-time to their stored income

const now = millis();
Gets the current time in milliseconds since the sketch started
const dt = (now - lastPassiveTime) / 1000;
Calculates how many seconds have passed since the last time this function ran by subtracting the last timestamp and dividing by 1000
lastPassiveTime = now;
Updates the stored timestamp so the next frame can calculate elapsed time from this point
if (dt <= 0) return;
Exits early if no time has passed (prevents dividing by zero or negative time)
for (let br of brainrots) {
Loops through every brainrot in the array
if (!br.delivered || br.despawned || br.sold || br.typeIndex == null) continue;
Skips creatures that are not in a base or have been sold—only active base creatures earn income
const inc = brainrotTypes[br.typeIndex].incomePerSecond;
Looks up how much income this creature type generates per second from the global brainrotTypes data
br.storedIncome = (br.storedIncome || 0) + inc * dt;
Adds (income per second × elapsed seconds) to the creature's stored income; the || 0 ensures it starts at 0 if undefined

updateBot()

updateBot() implements a simple but effective AI: (1) if carrying, go to base, (2) else, prioritize creatures about to despawn, then pick the closest one, (3) move toward it using normalized vectors. This combines pathfinding and priority logic.

🔬 This loop finds the closest creature to the bot. The sort() above already filtered for rightmost creatures. What happens if you comment out the sort() line so the bot just picks whichever creature is nearest without caring about its position on the runway?

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

🔧 Subcomponents:

conditional Return to base if carrying if (bot.carrying && botBaseZone) {

When the bot has picked up a creature, it immediately targets its own base to deliver it

calculation AI target selection const availableBrainrots = brainrots.filter(...); availableBrainrots.sort(...); for (let br of availableBrainrots) {...}

Finds available creatures, prioritizes rightmost ones (further along runway), then picks the closest—a greedy hunting strategy

conditional Move toward target or stop if (len < bot.speed) { bot.target = null; } else { ... bot.x += bot.vx; }

If close enough, stops and clears target; otherwise moves toward it at constant speed

if (bot.carrying && botBaseZone) {
If the bot has picked up a creature, lock its target to its own base's center
const availableBrainrots = brainrots.filter((br) => !br.rescued && !br.carried && !br.despawned && !br.sold);
Creates a new array containing only brainrots that are still on the runway (not rescued, carried, despawned, or sold)
availableBrainrots.sort((a, b) => b.x - a.x);
Sorts creatures by x position descending—those further right (closer to despawning) are listed first
for (let br of availableBrainrots) {
Loops through all available creatures (in priority order) to find the closest one
const d = dist(bot.x, bot.y, br.x, br.y);
Calculates the distance from the bot to this creature using the Pythagorean distance formula
if (d < closestDist) { closestDist = d; closestBr = br; }
If this creature is closer than the previous closest, update the closest creature and its distance
const len = sqrt(dx * dx + dy * dy);
Calculates the distance from the bot to its current target
if (len < bot.speed) {
If the bot is close enough to reach the target in one frame, snap to it and clear the target
dx /= len; dy /= len;
Normalizes the direction vector so it has length 1—this ensures movement is at consistent speed regardless of distance
bot.vx = dx * bot.speed; bot.vy = dy * bot.speed;
Sets the bot's velocity in the normalized direction multiplied by its speed
bot.x += bot.vx; bot.y += bot.vy;
Moves the bot by adding velocity to its position

drawBrainrots()

drawBrainrots() demonstrates rarity-based visual design: each creature's appearance is determined by its rarity tier's colors. The special case for 'Brainrot God' with a live rainbow effect teaches you to treat rare cases differently for maximum impact. The three overlapping circles create a cute blob aesthetic, and the arcs form a smile that unifies the face.

🔬 These three circle() calls draw the creature's body as overlapping blobs. What happens if you change the first two circle sizes from br.r * 1.2 to br.r * 0.6—would the eyes become tiny?

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

🔧 Subcomponents:

conditional Skip invisible creatures if (br.despawned || br.sold) continue;

Does not draw creatures that have already left the runway or been sold

calculation Rarity-based color selection const rarity = type ? type.rarity : 'Common'; let glowCol = rarityGlowColors[rarity];

Looks up the creature's rarity and assigns the appropriate glow and body colors from the global color maps

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

Special case: Brainrot God creatures animate through a rainbow glow effect using sine waves

calculation Vertical bob animation const bob = br.delivered ? 0 : sin(br.wigglePhase) * 2;

Makes creatures on the runway bob up and down; delivered creatures stop bobbing

calculation Draw creature body 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 a cute blob shape with two eyes and one body

for (let br of brainrots) {
Loops through every brainrot in the array to draw it
if (br.despawned || br.sold) continue;
Skips drawing if the creature has already left the map or been sold
const type = brainrotTypes[br.typeIndex];
Looks up the creature's type data (name, rarity, income, value) using its index
const rarity = type ? type.rarity : 'Common';
Gets the rarity name ('Common', 'Rare', etc.) or defaults to 'Common' if type is missing
let glowCol = rarityGlowColors[rarity];
Looks up the glow color array [R, G, B] for this rarity level
if (rarity === 'Brainrot God') {
Special case: if this is the rarest type, generate a live rainbow color instead of using a fixed color
push();
Saves the current transform state (position, rotation) so changes don't affect other drawings
translate(br.x, br.y);
Moves the origin to the creature's position so all following drawings are relative to it
const bob = br.delivered ? 0 : sin(br.wigglePhase) * 2;
If delivered, bob is 0 (no movement); otherwise, bob oscillates from -2 to +2 using the sine of wiggle phase
translate(0, bob);
Moves the creature up and down slightly each frame using the bob value
if (glowCol) { noStroke(); fill(...); circle(0, 0, br.r * 2.8); }
If a glow color exists, draws a semi-transparent circle around the creature to create a rarity aura
circle(-br.r * 0.3, -br.r * 0.3, br.r * 1.2);
Draws the left eye at a slight offset using the body color
circle(br.r * 0.2, -br.r * 0.3, br.r * 1.2);
Draws the right eye at the opposite offset
circle(0, br.r * 0.1, br.r * 1.4);
Draws the main body circle, slightly below and larger than the eyes
arc(0, -br.r * 0.2, br.r * 1.4, br.r * 0.8, PI, TWO_PI);
Draws the top half of a smile (from 180° to 360°) in purple
arc(0, br.r * 0.1, br.r * 1.4, br.r * 0.8, 0, PI);
Draws the bottom half of a smile (from 0° to 180°) to complete the happy expression
text(type.name, 0, -br.r * 1.8);
Draws the creature's name in white text above its head

createSaveCode()

createSaveCode() serializes the entire game state into a single Base64-encoded string. This is a classic game-preservation technique: convert complex objects to JSON, then encode to Base64 for safe copying/pasting. The reverse process (atob + JSON.parse) restores it in loadFromCode().

function createSaveCode() {
  const carryingIndex = bot.carrying ? brainrots.indexOf(bot.carrying) : -1;
  const payload = {
    money,
    rescuedCount,
    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
    }))
  };
  const json = JSON.stringify(payload);
  return btoa(json);
}
Line-by-line explanation (8 lines)

🔧 Subcomponents:

calculation Find carrying index const carryingIndex = bot.carrying ? brainrots.indexOf(bot.carrying) : -1;

Finds the array index of the creature the bot is carrying (or -1 if none), so it can be restored on load

calculation Build save object const payload = { money, rescuedCount, bot: {...}, brainrots: brainrots.map(...) };

Creates a JavaScript object containing all game state that needs to be saved

calculation JSON + Base64 encoding const json = JSON.stringify(payload); return btoa(json);

Converts the object to a JSON string, then encodes it in Base64 so it's a single shareable code

const carryingIndex = bot.carrying ? brainrots.indexOf(bot.carrying) : -1;
If the bot is carrying a creature, finds its position in the brainrots array; otherwise, uses -1 to mean 'nothing'
const payload = {
Creates a new object that will hold all the game state needed to resume
money,
Saves the current money total
rescuedCount,
Saves the total number of creatures ever rescued
bot: { x: bot.x, y: bot.y, ... }
Saves the bot's position, size, speed, target, and what it's carrying (by index)
brainrots: brainrots.map((br) => ({ ... }))
Loops through every creature and saves its position, size, type, state (rescued/delivered/sold), and slot assignment
const json = JSON.stringify(payload);
Converts the entire object to a JSON-formatted text string
return btoa(json);
Encodes the JSON string in Base64 (a safe text encoding) and returns it as a shareable save code

handleDelivery()

handleDelivery() is the core reward moment: when you bring a creature home, it is marked as delivered (so updatePassiveIncome() will generate money for it), you earn an instant reward, and it is placed in a physical slot for display. The 'No space' timer adds feedback when the base is full.

🔬 After placing a creature, the code sets multiple flags (rescued, delivered, carried). What happens if you comment out the br.delivered = true line—would the creature still stop moving and start earning income?

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

🔧 Subcomponents:

conditional Precondition checks if (!player.carrying || !baseZone) return; if (!pointInRect(player.x, player.y, baseZone)) return;

Exits early if the player is not carrying a creature, the base doesn't exist, or the player is not in the base zone

calculation Place in slot const placed = placeInBase(br, playerBaseSlots, baseZone);

Tries to place the creature in an empty slot; returns true if successful, false if no slots available

calculation Update creature and game state br.baseOwner = 'player'; br.rescued = true; br.delivered = true; ... money += brainrotReward;

Marks the creature as rescued and delivered, clears the player's carrying state, adds reward money, and increments the rescue counter

if (!player.carrying || !baseZone) return;
Exits immediately if the player is not holding a creature or if the base zone doesn't exist
if (!pointInRect(player.x, player.y, baseZone)) return;
Exits if the player is not standing inside the base zone boundary
const br = player.carrying;
Gets a reference to the creature the player is holding
const placed = placeInBase(br, playerBaseSlots, baseZone);
Tries to place the creature into an empty slot in the player's base; returns true if successful, false if all slots are full
br.baseOwner = 'player';
Records that this creature belongs to the player (important for selling logic)
br.rescued = true; br.delivered = true;
Marks the creature as rescued and delivered so it stops moving and starts earning passive income
br.carried = false; player.carrying = null;
Clears the player's carrying state so they can pick up another creature
rescuedCount++;
Increments the total rescue counter (shows on HUD)
money += brainrotReward;
Adds the base reward (100) to money for delivering any creature
if (!placed) { noSpaceUntil = millis() + 3000; }
If placement failed because the base was full, sets a 3-second timer to show a 'No space' warning

📦 Key Variables

gameState string

Tracks whether the game is on the start screen ('start') or actively playing ('playing')—controls which code runs in draw()

let gameState = 'start';
player object

Stores the player character's position (x, y), size (r), speed, carrying state, and velocity (vx, vy)

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

Stores the rival bot's position, size, speed, carrying state, AI target, and a timer for when it has no space in its base

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

An array of all creatures currently on the map—each stores position, size, type, state flags (rescued, delivered, sold), income, and base slot assignment

let brainrots = [];
money number

The player's total money earned by selling creatures and receiving rewards

let money = 0;
rescuedCount number

The total number of creatures the player has ever rescued (shown on HUD)

let rescuedCount = 0;
showIndex boolean

Controls whether the scrollable creature index panel is visible (toggled by pressing I)

let showIndex = false;
baseZone object

Stores the position and size of the player's base zone (x, y, w, h)

let baseZone = { x: 0, y: 0, w: 220, h: 150 };
rarityValueRanges object

Maps each rarity level (Common, Rare, Epic, etc.) to a min/max selling price range—used to randomize market values

const rarityValueRanges = { Common: [25, 1700], Rare: [2000, 9700], ... };
rarityIncome object

Maps each rarity level to a default passive income per second—used if a creature type doesn't have a custom income

const rarityIncome = { Common: 1, Rare: 3, Epic: 7, ... };
brainrotTypes array

An array of all possible creature types, each with a name, rarity, and computed values (income per second, market value)

const brainrotTypes = [ { name: 'Noobini Pizzanini', rarity: 'Common', ... }, ... ];
customIncome object

Maps specific creature names to custom income-per-second values, overriding the rarity-based defaults

const customIncome = { 'Noobini Pizzanini': 10, 'Tim Cheese': 50, ... };

🔧 Potential Improvements (6)

Here are some ways this code could be enhanced:

BUG handleDelivery() and handleBotDelivery()

If all base slots are full, the creature is placed at the base's center rather than in a slot, but its slotIndex remains null. If the player clicks it to sell, the code tries to access slots[null] which may not work correctly.

💡 In placeInBase(), ensure that if no slot is available, the creature is still assigned a temporary slotIndex (e.g., index 0) or the selling logic checks slotIndex != null before accessing the array.

PERFORMANCE drawIndexOverlay()

Every frame, the function recalculates counts[] by looping through all brainrots and filtering by rarity. This is O(n × m) where n = brainrot count and m = rarity types.

💡 Cache the counts array or update it incrementally when creatures are rescued/sold instead of recalculating every frame.

STYLE updateBrainrots()

The function calculates runwayEndX inside the loop (const runwayEndX = runway.x + runway.w;), which is the same every frame.

💡 Move runwayEndX outside the loop or calculate it once per frame to improve readability and avoid redundant calculations.

FEATURE updateBot()

The bot has no way to collect passive income from its base creatures—only the player can walk over them and collect.

💡 Add automatic income collection for the bot every few seconds, or add a second AI that handles income collection from the bot's base.

BUG loadFromCode()

If a saved brainrot's slotIndex points to a slot that no longer exists (e.g., due to base resize), the code will attempt to access an out-of-bounds index.

💡 Validate slotIndex against slots.length before accessing slots[slotIndex], and fall back to placing the creature at the zone center if invalid.

FEATURE drawBrainrots()

Creatures on the runway bob up and down, but this animation doesn't reset when a new creature spawns—they start mid-bobble at a random phase.

💡 Initialize wigglePhase to a consistent value (e.g., 0 instead of random(TWO_PI)) so all new creatures bob in sync, or embrace the randomness as intentional variety.

🔄 Code Flow

Code flow showing setup, draw, updategame, updatepassiveincome, updatebot, drawbrainrots, createsavecode, handledelivery

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

graph TD start[Start] --> setup[setup] setup --> draw[draw loop] draw --> start-state-check[start-state-check] start-state-check -->|if start state| draw start-state-check -->|else| update-game-check[update-game-check] update-game-check -->|if active| updategame[updategame] update-game-check -->|else| draw updategame --> income-update[income-update] income-update --> time-calc[time-calc] time-calc --> income-loop[income-loop] income-loop -->|for each brainrot| updatepassiveincome[updatepassiveincome] updatepassiveincome --> player-logic[player-logic] player-logic --> bot-logic[bot-logic] bot-logic --> auto-sell[auto-sell] auto-sell --> drawbrainrots[drawbrainrots] drawbrainrots --> skip-despawned[skip-despawned] skip-despawned --> rarity-lookup[rarity-lookup] rarity-lookup --> rainbow-effect[rainbow-effect] rainbow-effect --> bob-animation[bob-animation] bob-animation --> body-draw[body-draw] click setup href "#fn-setup" click draw href "#fn-draw" click start-state-check href "#sub-start-state-check" click update-game-check href "#sub-update-game-check" click updategame href "#fn-updategame" click income-update href "#sub-income-update" click time-calc href "#sub-time-calc" click income-loop href "#sub-income-loop" click updatepassiveincome href "#fn-updatepassiveincome" click player-logic href "#sub-player-logic" click bot-logic href "#sub-bot-logic" click auto-sell href "#sub-auto-sell" click drawbrainrots href "#fn-drawbrainrots" click skip-despawned href "#sub-skip-despawned" click rarity-lookup href "#sub-rarity-lookup" click rainbow-effect href "#sub-rainbow-effect" click bob-animation href "#sub-bob-animation" click body-draw href "#sub-body-draw"

❓ Frequently Asked Questions

What visual elements can users expect to see in the buy a brainrot v2 sketch?

The sketch features colorful brainrot creatures that players can collect, as well as a dynamic market zone and a scrolling index panel displaying various brainrot rarities.

How can users interact with the buy a brainrot v2 sketch?

Users can collect brainrots, earn money, sell them at a market, and manage their inventory through mouse clicks and scrolling interactions.

What creative coding concepts are demonstrated in the buy a brainrot v2 sketch?

The sketch showcases concepts such as passive income mechanics, random value generation for collectibles, and interactive UI elements for gameplay management.

Preview

buy a brainrot v2 - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of buy a brainrot v2 - Code flow showing setup, draw, updategame, updatepassiveincome, updatebot, drawbrainrots, createsavecode, handledelivery
Code Flow Diagram