egg

This sketch creates an idle farming game where players plant glowing seeds that grow and generate income, while managing an 'egg level' that speeds up growth. A random wind gust periodically sweeps across the screen, resetting the game and forcing strategic decisions about when to rebirth for increased passive income.

🧪 Try This!

Experiment with the code by making these changes:

  1. Speed up seed growth dramatically — Increasing the growth increment makes seeds mature much faster, letting you see the bright green color appear almost instantly as a test
  2. Make wind gusts happen constantly — Lowering the random threshold makes dangerous wind sweeps appear much more frequently, turning the game into a high-stress scramble
  3. Plant seeds for free — Removing the money cost lets you fill the canvas with hundreds of plants without economic pressure, turning the game into pure experimentation
  4. Make the gust completely opaque and red — Changing the gust color and transparency makes it more visually dramatic and threatening—a vivid visual signal of danger
  5. Earn double money from grown seeds — Increasing passive income rewards patience and makes it easier to afford upgrades, shifting strategy toward long-term planning
  6. Make seeds much larger — Bigger circles are easier to see and feel more impactful, making the visual feedback more satisfying as your garden grows
Prefer the full editor? Open it there →

📖 About This Sketch

This sketch builds a complete idle game inside p5.js where you plant seeds that glow and grow on a dark canvas, earning you money over time. The game combines several powerful creative coding ideas: custom JavaScript classes to represent seeds, array management to track multiple objects, keyboard event handling for player input, and procedural game logic that tracks money, upgrades, and a catastrophic reset mechanic. The visual design uses glowing colors and a sweeping wind animation to create a moody, strategic atmosphere.

The code is organized around three main pieces: a Seed class that tracks individual plant growth and color, a global draw loop that updates all seeds and manages the wind gust animation, and keyboard event handlers that let you plant seeds, upgrade your egg, or rebirth for bonuses. By reading it you will learn how to build a game loop with multiple object types, manage economic simulations with incremental values, and use p5.js classes to encapsulate behavior—all skills that unlock much larger creative projects.

⚙️ How It Works

  1. When the sketch loads, setup() creates a 900x600 canvas and initializes your money at $10, your egg level at 1, and an empty array of seeds ready to be planted.
  2. Every frame, draw() clears the background to a dark moody color and updates every seed in the seeds array by calling their update() and draw() methods.
  3. Each seed grows by 0.2 × eggLevel points per frame; when growth reaches 100, it turns from dim green to bright green and generates $0.10 per frame (plus bonuses from rebirths).
  4. A 1-in-600 chance each frame triggers a wind gust that sweeps across the screen from right to left; when it passes through the 'reset zone' (x between 200-300), the game fully resets—all seeds disappear and money drops back to $10.
  5. Keyboard input lets you press P to plant a seed ($10), E to upgrade your egg level ($50, speeds all growth), or R to rebirth ($100, which resets the board but permanently increases your passive income multiplier).
  6. The strategic tension comes from deciding: do you plant now and risk losing everything to the gust, or rebirth early to guarantee higher income on your next run?

🎓 Concepts You'll Learn

Class-based object designArray iteration and managementGame state and reset mechanicsKeyboard event handlingIncremental/idle game loopsCollision detection and zonesRandom event generation

📝 Code Breakdown

setup()

setup() runs exactly once when the sketch starts. It is the perfect place to initialize your canvas, set up fonts and colors, and create any starting values your game needs.

function setup() {
  // Create a canvas with the same dimensions as your Pygame screen
  createCanvas(900, 600);
  
  // Set the font, size, and alignment for the UI text
  textFont("Arial"); // Using Arial, a common system font, similar to Pygame's default
  textSize(20);
  textAlign(LEFT, TOP);
  
  // Initialize gustX to be off-screen to the right
  gustX = width;
}
Line-by-line explanation (5 lines)
createCanvas(900, 600);
Creates the game window that is 900 pixels wide and 600 pixels tall—all drawing happens on this canvas
textFont("Arial");
Sets the font for all text drawn later (money, rebirths, instructions) to Arial, a clean readable typeface
textSize(20);
Sets the size of all text to 20 pixels tall so it's readable on the dark background
textAlign(LEFT, TOP);
Anchors text to its top-left corner, making it easier to position text starting at specific coordinates
gustX = width;
Initializes gustX (the gust's horizontal position) to the right edge of the canvas, ready to sweep left when triggered

draw()

draw() runs 60 times per second and is the heartbeat of your game. Every frame it updates all game state (seeds growing, gust moving), checks for win/lose conditions (the reset zone), and renders everything the player sees. This is where most of your game logic lives.

🔬 This loop updates and draws every seed. What happens if you add a console.log(i) inside the loop to watch the order? Why do you think we loop backwards from seeds.length - 1 instead of forwards from 0?

  for (let i = seeds.length - 1; i >= 0; i--) {
    let seed = seeds[i];
    seed.update();
    seed.draw();

🔬 This draws the gust as a semi-transparent blue rectangle. What happens if you change the 150 (alpha/transparency) to 255? To 50? How does transparency affect the fear factor of the gust?

    // Draw the gust rectangle with some transparency
    fill(200, 200, 255, 150); // R, G, B, Alpha (0-255)
    noStroke(); // No border for the rectangle
    rect(gustX, 0, 100, height); // Draw the rectangle
function draw() {
  // Fill the background with the same dark color as your Pygame screen
  background(30, 30, 40);

  // Update and draw each seed
  // We loop backwards so that if a seed is removed (e.g., in a future feature),
  // it doesn't mess up the loop's indexing.
  for (let i = seeds.length - 1; i >= 0; i--) {
    let seed = seeds[i];
    seed.update();
    seed.draw();
    
    // If the seed is grown, add money
    if (seed.isGrown()) {
      money += 0.1 + rebirths * 0.05;
    }
  }

  // Random chance to activate a wind gust
  // floor(random(1, 601)) is the p5.js equivalent of random.randint(1, 600)
  if (floor(random(1, 601)) === 1) { 
    gustActive = true;
    gustX = width; // Reset gust position to the right edge
  }

  if (gustActive) {
    gustX -= gustSpeed; // Move the gust to the left
    
    // Draw the gust rectangle with some transparency
    fill(200, 200, 255, 150); // R, G, B, Alpha (0-255)
    noStroke(); // No border for the rectangle
    rect(gustX, 0, 100, height); // Draw the rectangle
    
    // Check if the gust is in the "reset" zone
    if (gustX < 300 && gustX > 200) {
      resetGame(); // Call the reset function
    }
    
    // Deactivate the gust once it moves completely off-screen to the left
    if (gustX < -100) {
      gustActive = false;
    }
  }

  // Draw the UI text
  fill(255); // Set text color to white
  drawText(`Money: $${floor(money)}`, 10, 10); // floor() is used to display money as an integer
  drawText(`Rebirths: ${rebirths}`, 10, 35);
  drawText(`Egg Level: ${eggLevel}`, 10, 60);
  drawText("Press P to Plant Seed ($10)", 10, 100);
  drawText("Press E to Upgrade Egg ($50)", 10, 125);
  drawText("Press R to Rebirth ($100)", 10, 150);
}
Line-by-line explanation (15 lines)

🔧 Subcomponents:

for-loop Seed Update and Growth Loop for (let i = seeds.length - 1; i >= 0; i--) {

Iterates through every seed in the array backwards, updates its growth, draws it, and awards money when fully grown

conditional Random Gust Activation if (floor(random(1, 601)) === 1) {

Has a 1-in-600 chance each frame to trigger a wind gust that sweeps left across the screen

conditional Reset Zone Check if (gustX < 300 && gustX > 200) {

Detects when the gust passes through the 'reset zone' and wipes out all seeds and money

background(30, 30, 40);
Fills the entire canvas with a dark bluish-gray color, erasing everything drawn in the previous frame
for (let i = seeds.length - 1; i >= 0; i--) {
Loops through the seeds array backwards (from the last seed to the first)—this is safe if seeds ever get removed
seed.update();
Calls the seed's update() method, which increases its growth by 0.2 × eggLevel each frame
seed.draw();
Calls the seed's draw() method, which renders it as a circle—dim green when growing, bright green when fully grown
if (seed.isGrown()) {
Checks if this seed has reached its maxGrowth threshold (100 points)
money += 0.1 + rebirths * 0.05;
If the seed is grown, add $0.10 plus a bonus of $0.05 for each rebirth, rewarding you for coming back to play
if (floor(random(1, 601)) === 1) {
Checks if a random number between 1 and 600 equals exactly 1—about a 0.17% chance each frame, meaning a gust roughly every 10 seconds
gustActive = true;
Marks the gust as active so the next conditional block will draw and move it
gustX = width;
Resets the gust's position to the right edge of the canvas so it can sweep left across the screen
gustX -= gustSpeed;
Moves the gust left by gustSpeed pixels (default 8) each frame, creating a smooth sweeping animation
fill(200, 200, 255, 150);
Sets the gust's color to a pale blue with 150/255 opacity (semi-transparent), making it visible but eerie
rect(gustX, 0, 100, height);
Draws the gust as a 100-pixel-wide rectangle stretching from top to bottom of the canvas
if (gustX < 300 && gustX > 200) {
Checks if the gust is in the 'reset zone' (x between 200 and 300 pixels)—when it is, the game resets
if (gustX < -100) {
Once the gust moves completely off the left side of the canvas (past -100), deactivate it so no more resets happen until the next gust
drawText(`Money: $${floor(money)}`, 10, 10);
Displays your current money rounded down to the nearest dollar at the top-left corner of the screen

resetGame()

resetGame() is a helper function that wipes the game state clean. It is called when a gust passes through the reset zone, creating the tension that defines this game—you have accumulated wealth that can vanish in seconds. It is also called during a rebirth, which lets you start a new run with permanent bonuses.

function resetGame() {
  money = 10;
  brainrot = 0;
  seeds = []; // Clear all seeds
}
Line-by-line explanation (3 lines)
money = 10;
Resets your money back to $10, the starting amount—you lose all accumulated wealth when the gust hits
brainrot = 0;
Resets the brainrot counter to 0 (this variable is kept for code consistency but is not actively used in the game logic)
seeds = [];
Clears the entire seeds array by assigning it an empty array, instantly removing all plants from the screen

keyPressed()

keyPressed() is a built-in p5.js function that runs whenever the player presses a key. This is where you capture player input and convert it into game actions. The three conditions (P, R, E) let the player plant, upgrade, and rebirth—the three verbs that define the game loop.

function keyPressed() {
  // Check for 'p' key (case-insensitive)
  if (key === 'p' || key === 'P') {
    if (money >= seedCost) {
      // Create a new Seed object with random x, y positions
      // floor(random(min, max + 1)) is used to get an integer within the desired range
      seeds.push(new Seed(floor(random(100, 801)), floor(random(200, 501)))); 
      money -= seedCost; // Deduct seed cost
    }
  } 
  // Check for 'r' key (case-insensitive)
  else if (key === 'r' || key === 'R') {
    if (money >= 100) {
      rebirths += 1;
      eggLevel += 1;
      resetGame(); // Rebirth resets the game
    }
  } 
  // Check for 'e' key (case-insensitive)
  else if (key === 'e' || key === 'E') {
    if (money >= 50) {
      eggLevel += 1;
      money -= 50;
    }
  }
}
Line-by-line explanation (13 lines)

🔧 Subcomponents:

conditional Plant Seed (P Key) if (key === 'p' || key === 'P') {

Listens for the P key and spawns a new seed at a random location if you have enough money

conditional Rebirth (R Key) else if (key === 'r' || key === 'R') {

Listens for the R key, increases rebirths and egg level permanently, then resets the board

conditional Upgrade Egg (E Key) else if (key === 'e' || key === 'E') {

Listens for the E key and boosts your egg level (speeding all future seed growth) if you have $50

if (key === 'p' || key === 'P') {
Checks if the player pressed the 'p' or 'P' key (|| means 'or', so both lowercase and uppercase work)
if (money >= seedCost) {
Verifies you have at least $10 before allowing you to plant, preventing overspending
seeds.push(new Seed(floor(random(100, 801)), floor(random(200, 501))));
Creates a brand-new Seed object at a random x position (100–800) and random y position (200–500), then adds it to the seeds array using push()
money -= seedCost;
Subtracts $10 from your money after planting the seed
else if (key === 'r' || key === 'R') {
Checks if the player pressed the 'r' or 'R' key—this is the rebirth command
if (money >= 100) {
Verifies you have at least $100 before allowing a rebirth
rebirths += 1;
Increments the rebirth counter by 1—this permanently increases your passive income multiplier (0.05 per rebirth)
eggLevel += 1;
Increases your egg level by 1, which makes all seeds grow 0.2 faster per frame (since growth increases by 0.2 × eggLevel)
resetGame();
Calls resetGame() to clear all seeds and reset money to $10, creating a 'hard reset' that lets you restart your farm with permanent bonuses
else if (key === 'e' || key === 'E') {
Checks if the player pressed the 'e' or 'E' key—this is the upgrade command
if (money >= 50) {
Verifies you have at least $50 before allowing an upgrade
eggLevel += 1;
Increases your egg level by 1, speeding growth of all existing and future seeds immediately
money -= 50;
Subtracts $50 from your money as the cost of the upgrade

drawText()

drawText() is a simple wrapper around p5.js's text() function. Wrapper functions like this are useful because they let you change how text is rendered (add shadows, outlines, animations) in one place instead of repeating code throughout draw().

function drawText(txt, x, y) {
  text(txt, x, y);
}
Line-by-line explanation (2 lines)
function drawText(txt, x, y) {
Defines a helper function that takes three parameters: txt (the text to display), x (horizontal position), and y (vertical position)
text(txt, x, y);
Calls p5.js's built-in text() function to draw the txt string at coordinates (x, y) using the previously set font, size, and color

Seed

The Seed class is the core object type in this game. Every plant is an instance of this class, storing its position (x, y), its growth progress, and methods to update and render itself. This encapsulation—bundling data and methods together—is the foundation of object-oriented design and makes it easy to manage dozens or hundreds of seeds in an array.

🔬 This method draws a seed as a circle that changes color when grown. What happens if you change the dim green color(100, 200, 100) to color(255, 0, 0) (red)? How does color choice affect the player's emotional response to growth?

  draw() {
    // Set color based on growth status
    let fillColor = this.growth >= this.maxGrowth ? color(0, 255, 0) : color(100, 200, 100);
    fill(fillColor);
    noStroke(); // No border for the circles
    circle(this.x, this.y, 30); // Draw a circle with a diameter of 30 (radius 15)
  }
class Seed {
  constructor(x, y) {
    this.x = x;
    this.y = y;
    this.growth = 0;
    this.maxGrowth = 100; // Renamed from max_growth to camelCase
  }

  update() {
    this.growth += 0.2 * eggLevel;
  }

  draw() {
    // Set color based on growth status
    let fillColor = this.growth >= this.maxGrowth ? color(0, 255, 0) : color(100, 200, 100);
    fill(fillColor);
    noStroke(); // No border for the circles
    circle(this.x, this.y, 30); // Draw a circle with a diameter of 30 (radius 15)
  }

  isGrown() {
    return this.growth >= this.maxGrowth;
  }
}
Line-by-line explanation (14 lines)

🔧 Subcomponents:

constructor Seed Constructor constructor(x, y) {

Initializes a new seed at a given (x, y) position with zero growth and a maximum growth threshold of 100

conditional Growth-Based Color Selection let fillColor = this.growth >= this.maxGrowth ? color(0, 255, 0) : color(100, 200, 100);

Uses a ternary operator to choose bright green if the seed is fully grown, otherwise dim green to show progress

class Seed {
Declares a JavaScript class called Seed, which is a blueprint for creating seed objects that represent individual plants
constructor(x, y) {
The constructor is a special method that runs once when a new Seed is created, setting up its initial state
this.x = x;
Stores the seed's horizontal position on the canvas in this.x
this.y = y;
Stores the seed's vertical position on the canvas in this.y
this.growth = 0;
Initializes the seed's growth counter to 0—it will increase each frame until reaching maxGrowth
this.maxGrowth = 100;
Sets the threshold: when this.growth reaches 100, the seed becomes fully grown and starts earning money
update() {
Defines the update() method, which is called every frame to advance the seed's growth
this.growth += 0.2 * eggLevel;
Increases growth by 0.2 points multiplied by the current eggLevel—higher egg levels speed up growth dramatically
draw() {
Defines the draw() method, which is called every frame to render the seed as a colored circle
let fillColor = this.growth >= this.maxGrowth ? color(0, 255, 0) : color(100, 200, 100);
Uses a ternary operator (? :) to pick the color: bright pure green (0, 255, 0) if fully grown, or dim greenish (100, 200, 100) if still growing
fill(fillColor);
Sets the fill color for the circle about to be drawn
circle(this.x, this.y, 30);
Draws a circle at (this.x, this.y) with a diameter of 30 pixels (radius 15)
isGrown() {
Defines a helper method that returns true if the seed has reached maxGrowth, false otherwise
return this.growth >= this.maxGrowth;
Returns a boolean: true if growth is at least 100, enabling the draw() function to check if money should be awarded

📦 Key Variables

money number

Tracks the player's current cash amount, earned from grown seeds and spent on planting, upgrades, and rebirths

let money = 10;
brainrot number

A variable kept for code consistency but not used in the game logic; initialized to 0 and reset on game resets

let brainrot = 0;
rebirths number

Counts how many times the player has chosen to rebirth; increases passive income multiplier permanently

let rebirths = 0;
eggLevel number

Multiplier for seed growth speed (growth increases by 0.2 × eggLevel per frame); upgraded by pressing E or R

let eggLevel = 1;
seedCost number

The fixed cost in dollars to plant a new seed; prevents planting if money < seedCost

let seedCost = 10;
seeds array

An array of Seed objects representing all active plants on the canvas; cleared when a gust resets the game

let seeds = [];
gustActive boolean

Tracks whether a wind gust is currently sweeping across the screen; triggered randomly and set to false once off-screen

let gustActive = false;
gustX number

The horizontal position of the wind gust; moves left each frame and triggers a reset when between 200 and 300

let gustX;
gustSpeed number

How many pixels per frame the gust moves left; controls the speed at which the sweeping wind animation travels

let gustSpeed = 8;

🔧 Potential Improvements (5)

Here are some ways this code could be enhanced:

BUG draw() - gust reset zone

The reset zone check (gustX < 300 && gustX > 200) happens every single frame while the gust is in that zone, but resetGame() only runs once visually. If gustSpeed is very slow or the gust lingers, this is safe, but it's conceptually incorrect to call resetGame() multiple times per gust sweep.

💡 Add a flag like gustTriggeredReset = false when gustActive becomes true, then only call resetGame() if (!gustTriggeredReset && gustX < 300 && gustX > 200). Set gustTriggeredReset = true after the first reset to prevent multiple triggers per gust.

PERFORMANCE draw() - seed loop

Looping backwards (for (let i = seeds.length - 1; i >= 0; i--)) is correct if seeds are ever removed, but currently they are never deleted—seeds never leave the array even after earning money forever. Over many hours of play, the seeds array grows unbounded and the loop becomes slower.

💡 Add a 'lifetime' property to Seed and either remove seeds after they've earned enough money (destructuring after maxLifetime frames) or cap the array size. For example: if (seed.lifetime > 10000) { seeds.splice(i, 1); } to clean up old seeds.

STYLE variables - naming

The variable brainrot is declared and reset but never used, creating dead code that confuses readers about the game's intended logic

💡 Either remove the brainrot variable entirely or document why it is kept for consistency with a clearer comment. If it is meant for a future feature, add a TODO comment explaining the planned use.

FEATURE Seed class

All seeds grow at the same rate (0.2 × eggLevel) and earn the same income, making every seed identical—there is no progression or variety to encourage experimentation

💡 Add optional rarity or level tiers: let some seeds take 1.5× or 0.5× as long to grow, or earn different income amounts. For example: this.rarity = random() > 0.9 ? 'rare' : 'common', then adjust growth and income multipliers based on rarity.

FEATURE keyPressed()

Rebirths reset the board (clearing all seeds) but the rebirth costs $100 and takes the player back to square one, which can feel punishing even though it grants permanent bonuses. There is no visual way to see your rebirth count building up progress.

💡 Add a rebirth 'progress bar' or 'next rebirth cost' visual indicator, or introduce an intermediate mechanic: let players 'bank' a portion of their money across resets (like 10% carryover) to smooth the rebirth transition and make earning feel less arbitrary.

🔄 Code Flow

Code flow showing setup, draw, resetgame, keypressed, drawtext, seed

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

graph TD start[Start] --> setup[setup] setup --> draw[draw loop] draw --> seedloop[Seed Update and Growth Loop] draw --> gusttrigger[Random Gust Activation] draw --> gustresetzone[Reset Zone Check] draw --> keypressed[Key Pressed Check] seedloop --> seedconstructor[Seed Constructor] seedloop --> colorternary[Growth-Based Color Selection] gusttrigger --> gustresetzone keypressed --> plantkey[Plant Seed (P Key)] keypressed --> rebirthkey[Rebirth (R Key)] keypressed --> upgradekey[Upgrade Egg (E Key)] click setup href "#fn-setup" click draw href "#fn-draw" click seedloop href "#sub-seed-loop" click gusttrigger href "#sub-gust-trigger" click gustresetzone href "#sub-gust-reset-zone" click keypressed href "#fn-keypressed" click seedconstructor href "#sub-seed-constructor" click colorternary href "#sub-color-ternary" click plantkey href "#sub-plant-key" click rebirthkey href "#sub-rebirth-key" click upgradekey href "#sub-upgrade-key"

❓ Frequently Asked Questions

What visual experience does the 'egg' sketch offer to users?

The 'egg' sketch creates a dark, moody atmosphere where glowing seeds sway gently, enhancing the visual appeal of a garden that flourishes or is threatened by sudden wind gusts.

How can users interact with the 'egg' sketch to grow their garden?

Users can plant seeds, upgrade their garden, and initiate rebirths using keyboard controls, all while managing their resources to build a flourishing garden before the next wind gust comes.

What creative coding concepts are demonstrated in the 'egg' sketch?

The sketch showcases concepts such as dynamic object management, random event generation, and resource management, illustrating how environmental factors can impact gameplay.

Preview

egg - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of egg - Code flow showing setup, draw, resetgame, keypressed, drawtext, seed
Code Flow Diagram