AI Procrastinating Rocket - Countdown with Excuses

This sketch draws a cartoon rocket sitting on a launch pad under a bobbing cloud sky, counting down from 10 - but it almost never reaches zero. Every second there's a chance the countdown interrupts itself with a silly excuse ('Wait, did I leave the stove on?'), resets to 10, and tallies up another failed launch attempt.

🧪 Try This!

Experiment with the code by making these changes:

  1. Make excuses way more frequent — Raising the random() threshold makes the rocket panic and stall almost every tick instead of occasionally.
  2. Speed up the countdown ticks — Lowering the millisecond threshold makes the countdown race down much faster between excuses.
  3. Recolor the nose cone — The fill() color right before the nose cone's triangle() call controls its color - swap red for a bright neon green.
Prefer the full editor? Open it there →

📖 About This Sketch

This sketch creates a comedic animation of a rocket that keeps chickening out of its own launch countdown. A number ticks down from 10 once per second using p5.js's millis() function for timing, while random() rolls the dice each tick to decide whether the rocket panics and blurts out an excuse instead of continuing. Three drifting clouds bob in the background using sin() and frameCount, and the rocket itself is built entirely from basic shapes - rect() and triangle() - positioned with simple math.

The code is organized into five functions: setup() prepares the canvas and text settings, draw() runs the countdown timer and renders everything every frame, drawRocket() is a dedicated helper that draws the rocket's body/nose/fins/pad, triggerExcuse() resets the state whenever an excuse fires, and windowResized() keeps the canvas full-screen. By studying it you'll learn how to build a simple timer-driven state machine, use random() to create unpredictable behavior, and separate drawing logic into reusable helper functions.

⚙️ How It Works

  1. When the sketch loads, setup() creates a full-window canvas, centers all text alignment, and sets a base text size so labels and excuses render legibly.
  2. Every frame, draw() repaints the sky background, draws three bobbing clouds using sine waves for gentle vertical motion, then checks how much time has passed since the last countdown tick.
  3. Once a full second has passed (checked via millis()), the sketch either rolls a 25% chance to trigger a random excuse (if the countdown has already moved off 10) or decrements the countdown by one.
  4. If the countdown reaches zero, or the random chance hits, triggerExcuse() fires: it picks a random excuse string, remembers the moment it started showing, bumps the launch attempt counter, and resets the countdown back to 10.
  5. While an excuse is active, draw() displays it on screen for 2.5 seconds (tracked with excuseStart) before hiding it and letting the countdown resume ticking.
  6. drawRocket() runs every frame to redraw the rocket body, nose cone, and fins in their fixed positions, while the countdown number and attempt counter are drawn as text on top.

🎓 Concepts You'll Learn

Timing with millis()Randomness with random()State machine using boolean flagsSine wave motion for idle animationCustom helper functions for drawingGlobal variables tracking game/animation stateResponsive canvas with windowResized()

📝 Code Breakdown

setup()

setup() runs once when the sketch starts and is the ideal place to configure drawing modes and defaults (like text alignment and size) that will apply throughout the whole sketch.

function setup(){
  createCanvas(windowWidth,windowHeight);
  textAlign(CENTER,CENTER);
  textSize(32);
  rectMode(CENTER);
}
Line-by-line explanation (4 lines)
createCanvas(windowWidth,windowHeight);
Creates a canvas that fills the entire browser window, using windowWidth and windowHeight so it adapts to any screen size.
textAlign(CENTER,CENTER);
Makes all future text() calls center their text both horizontally and vertically around the given x,y coordinate - handy for centered titles.
textSize(32);
Sets the default font size in pixels for all text drawn afterward, like the countdown number and excuses.
rectMode(CENTER);
Changes rect() so its x,y arguments describe the shape's CENTER instead of its top-left corner, which makes positioning the rocket's body and pad much easier.

draw()

draw() runs continuously (about 60 times per second) and is where the sketch's whole animation loop, timer logic, and rendering happen. Using millis() instead of frameCount for timing keeps the countdown speed consistent regardless of frame rate.

🔬 This block controls the whole countdown behavior. What happens if you change 0.25 to 0.75 so the rocket is far more anxious? What if you change >1000 to >200 so it ticks five times faster?

  if(!showing&&now-lastTick>1000){
    lastTick=now;
    if(random()<0.25&&count<10) triggerExcuse();
    else{
      count--;
      if(count<=0) triggerExcuse();
    }
  }

🔬 This loop draws 3 clouds spaced across the sky. What happens if you change i<3 to i<8 to fill the sky with clouds, or change 0.01 in the sin() call to 0.1 to make them bob much faster?

  for(let i=0;i<3;i++){
    let cx=width*(0.2+0.3*i),cy=height*0.2+20*sin(frameCount*0.01+i);
    ellipse(cx,cy,120,60);
    ellipse(cx+40,cy+10,80,50);
    ellipse(cx-40,cy+10,80,50);
  }
function draw(){
  background(150,200,255);
  noStroke();fill(255);
  for(let i=0;i<3;i++){
    let cx=width*(0.2+0.3*i),cy=height*0.2+20*sin(frameCount*0.01+i);
    ellipse(cx,cy,120,60);
    ellipse(cx+40,cy+10,80,50);
    ellipse(cx-40,cy+10,80,50);
  }
  let now=millis();
  if(!showing&&now-lastTick>1000){
    lastTick=now;
    if(random()<0.25&&count<10) triggerExcuse();
    else{
      count--;
      if(count<=0) triggerExcuse();
    }
  }
  if(showing&&now-excuseStart>2500) showing=false;
  drawRocket();
  fill(0);
  text('Countdown: '+count,width/2,50);
  text('Launch Attempts: '+attempts,width/2,90);
  if(showing) text(excuse,width/2,height*0.3);
}
Line-by-line explanation (13 lines)

🔧 Subcomponents:

for-loop Cloud Drawing Loop for(let i=0;i<3;i++){

Draws three clouds across the sky, each made of three overlapping ellipses, and gently bobs them up and down using a sine wave based on frameCount.

conditional Countdown Tick Timer if(!showing&&now-lastTick>1000){

Only runs the countdown logic once per second, and only when no excuse is currently being shown, using millis() to measure elapsed time.

conditional Random Excuse Chance if(random()<0.25&&count<10) triggerExcuse();

Gives a 25% chance each tick (after the countdown has moved off its starting value) to interrupt the countdown with a random excuse instead of decrementing.

conditional Normal Countdown Decrement count--;

If the random excuse chance doesn't fire, the countdown simply decreases by one, and triggers an excuse anyway if it reaches zero - guaranteeing the rocket never actually launches.

conditional Hide Excuse After Delay if(showing&&now-excuseStart>2500) showing=false;

Automatically hides the current excuse message 2.5 seconds after it appeared so the countdown can resume.

background(150,200,255);
Repaints the whole canvas with a light sky-blue color every frame, erasing the previous frame's drawing so animation looks smooth instead of smeared.
noStroke();fill(255);
Turns off shape outlines and sets the fill color to white, used for drawing the fluffy clouds.
let cx=width*(0.2+0.3*i),cy=height*0.2+20*sin(frameCount*0.01+i);
Calculates each cloud's x position spread across the canvas width, and its y position bobbing up and down over time using a sine wave - frameCount keeps increasing so sin() produces a smooth oscillation.
let now=millis();
Grabs the number of milliseconds since the sketch started, used to measure how much time has passed for the countdown timer.
lastTick=now;
Remembers the current time as the moment of the last countdown tick, so the sketch can measure exactly one second until the next tick.
if(random()<0.25&&count<10) triggerExcuse();
Rolls a random number between 0 and 1; if it's less than 0.25 (a 25% chance) and the countdown has already ticked down at least once, an excuse fires instead of continuing the countdown.
count--;
Decreases the countdown number by one - this is what makes the numbers on screen count down.
if(count<=0) triggerExcuse();
If the countdown would reach zero or below, an excuse fires instead of ever hitting launch, keeping the rocket stuck forever.
if(showing&&now-excuseStart>2500) showing=false;
Checks if an excuse has been displayed for more than 2.5 seconds, and if so, hides it so the countdown can keep going.
drawRocket();
Calls the helper function that draws the rocket's body, nose cone, fins, and launch pad.
text('Countdown: '+count,width/2,50);
Draws the current countdown number near the top of the screen, concatenating it into a label string.
text('Launch Attempts: '+attempts,width/2,90);
Draws the running total of how many times the rocket has 'tried' to launch and failed.
if(showing) text(excuse,width/2,height*0.3);
Only draws the excuse text while showing is true, positioning it above the rocket.

drawRocket()

Breaking complex drawings into a dedicated function like drawRocket() keeps draw() readable and lets you reuse or reposition the whole rocket easily - a common pattern for organizing p5.js sketches with multiple visual elements.

🔬 These two triangles draw mirrored fins using rx-bw and rx+bw offsets. What happens if you change ry+bh/2+30 to ry+bh/2+80 on both lines to make the fins stick out much further?

  triangle(rx-bw/2,ry+bh/2,rx-bw,ry+bh/4,rx-bw,ry+bh/2+30);
  triangle(rx+bw/2,ry+bh/2,rx+bw,ry+bh/4,rx+bw,ry+bh/2+30);
function drawRocket(){
  let rx=width/2,ry=height*0.75,bh=140,bw=50;
  fill(200);rect(rx,ry,bw,bh);
  fill(220,0,60);triangle(rx,ry-bh/2-40,rx-30,ry-bh/2,rx+30,ry-bh/2);
  fill(255,120,0);
  triangle(rx-bw/2,ry+bh/2,rx-bw,ry+bh/4,rx-bw,ry+bh/2+30);
  triangle(rx+bw/2,ry+bh/2,rx+bw,ry+bh/4,rx+bw,ry+bh/2+30);
  fill(80);rect(width/2,height*0.9,140,25);
  rect(width/2,height*0.82,12,90);
}
Line-by-line explanation (8 lines)
let rx=width/2,ry=height*0.75,bh=140,bw=50;
Defines the rocket's center position (rx, ry) and its body height/width (bh, bw) so every shape below is positioned relative to these values instead of hardcoded numbers.
fill(200);rect(rx,ry,bw,bh);
Draws the rocket's main body as a light gray rectangle, centered at rx,ry thanks to rectMode(CENTER) set in setup().
fill(220,0,60);triangle(rx,ry-bh/2-40,rx-30,ry-bh/2,rx+30,ry-bh/2);
Draws the red nose cone as a triangle sitting right on top of the body.
fill(255,120,0);
Switches the fill color to orange for the two side fins drawn next.
triangle(rx-bw/2,ry+bh/2,rx-bw,ry+bh/4,rx-bw,ry+bh/2+30);
Draws the left fin using coordinates offset from the body's left edge.
triangle(rx+bw/2,ry+bh/2,rx+bw,ry+bh/4,rx+bw,ry+bh/2+30);
Draws the right fin as a mirror image of the left one, offset to the right side of the body.
fill(80);rect(width/2,height*0.9,140,25);
Draws a dark gray horizontal rectangle near the bottom of the screen to represent the launch pad platform.
rect(width/2,height*0.82,12,90);
Draws a thin vertical support strut connecting the rocket's base to the launch pad.

triggerExcuse()

This function acts as the single 'reset point' for the whole animation's state - every excuse, whether triggered randomly or by hitting zero, funnels through here to reset the same set of variables, which is a simple but effective state-machine pattern.

function triggerExcuse(){showing=true;excuse=random(excuses);excuseStart=millis();attempts++;count=10;}
Line-by-line explanation (5 lines)
showing=true;
Flips the flag that tells draw() to display the excuse text on screen.
excuse=random(excuses);
Picks a random string from the excuses array using p5's random() function, which can also pick a random item from an array (not just a random number).
excuseStart=millis();
Records the current time so draw() can later check how long the excuse has been showing.
attempts++;
Increments the launch attempts counter by one, tracking how many times the rocket has failed to launch.
count=10;
Resets the countdown all the way back to 10, ensuring the rocket never actually reaches zero and launches.

windowResized()

windowResized() is a built-in p5.js callback function that fires automatically on window resize events, making it easy to build responsive full-screen sketches without manually tracking resize events.

function windowResized(){resizeCanvas(windowWidth,windowHeight);}
Line-by-line explanation (1 lines)
resizeCanvas(windowWidth,windowHeight);
Called automatically by p5.js whenever the browser window changes size, this resizes the canvas to match so the sketch always fills the screen.

📦 Key Variables

count number

Stores the current countdown number displayed on screen, ticking down from 10 to 0 (but constantly getting reset before reaching launch).

let count=10;
lastTick number

Records the millis() timestamp of the last countdown tick, used to measure whether a full second has passed.

let lastTick=0;
excuse string

Holds the currently displayed excuse text, chosen randomly from the excuses array.

let excuse='';
showing boolean

Tracks whether an excuse message is currently being displayed, pausing the countdown while true.

let showing=false;
excuseStart number

Stores the millis() timestamp of when the current excuse started showing, so it can be hidden after 2.5 seconds.

let excuseStart=0;
attempts number

Counts how many times the rocket has triggered an excuse (i.e. how many failed launch attempts have occurred).

let attempts=0;
excuses array

A fixed list of humorous excuse strings that get randomly selected whenever the rocket stalls its countdown.

const excuses=['Wait, did I leave the stove on?', 'Actually, space looks cold...'];

🔧 Potential Improvements (4)

Here are some ways this code could be enhanced:

STYLE entire sketch.js

Almost every function crams multiple statements onto a single line (e.g. 'fill(200);rect(rx,ry,bw,bh);'), which makes the code much harder to read and debug.

💡 Put each statement on its own line and add consistent spacing/indentation - this has no effect on how the sketch runs but makes it far easier for beginners (and future you) to follow.

BUG draw() tick logic

If the browser tab is inactive or a big frame lag occurs, now-lastTick could jump by several seconds at once, but the code only ever decrements count by 1 per check, potentially causing count to fall behind real elapsed time.

💡 Consider computing how many whole seconds have elapsed (e.g. Math.floor((now-lastTick)/1000)) and decrementing count by that amount, or accept the current behavior since it's a lighthearted animation where precision doesn't matter much.

FEATURE drawRocket() and draw()

The rocket never shows any exhaust flame or launch animation, even though the theme is about (almost) launching - there's no visual payoff for reaching count 0 before it resets.

💡 Add a brief flame/smoke effect using ellipse() or particles right when count hits 0, right before triggerExcuse() resets everything, to give a funnier 'almost launched!' moment.

PERFORMANCE draw() cloud loop

Cloud x/y positions and all shape calculations are recomputed every single frame even though the clouds' horizontal spacing (cx) never actually changes - only cy needs recalculating.

💡 This is a very minor cost at only 3 clouds, but for a larger number of background elements, precompute static values like cx once in setup() and store them in an array instead of recalculating every frame.

🔄 Code Flow

Code flow showing setup, draw, drawrocket, triggerexcuse, windowresized

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

graph TD start[Start] --> setup[setup] setup --> draw[draw loop] draw --> cloudloop[cloud-loop] cloudloop --> tickconditional[tick-conditional] tickconditional --> excusechance[excuse-chance] excusechance -->|25% chance| triggerexcuse[triggerexcuse] excusechance -->|75% chance| normaldecrement[normal-decrement] normaldecrement --> tickconditional tickconditional -->|Countdown reaches zero| triggerexcuse triggerexcuse --> hideexcuse[hide-excuse] hideexcuse --> draw click setup href "#fn-setup" click draw href "#fn-draw" click cloudloop href "#sub-cloud-loop" click tickconditional href "#sub-tick-conditional" click excusechance href "#sub-excuse-chance" click normaldecrement href "#sub-normal-decrement" click hideexcuse href "#sub-hide-excuse"

❓ Frequently Asked Questions

What visual elements can I expect to see in the AI Procrastinating Rocket sketch?

This sketch features a colorful background, animated rocket graphics, and a countdown timer, along with humorous excuses displayed during the countdown.

Is there any user interaction available in the AI Procrastinating Rocket sketch?

The sketch automatically progresses through a countdown and generates excuses without user input; however, it resets the countdown after each launch attempt.

What creative coding techniques are demonstrated in the AI Procrastinating Rocket sketch?

The sketch showcases randomization for excuse generation, basic animation for rocket movement, and interactive timing with the countdown logic.

Preview

AI Procrastinating Rocket - Countdown with Excuses - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of AI Procrastinating Rocket - Countdown with Excuses - Code flow showing setup, draw, drawrocket, triggerexcuse, windowresized
Code Flow Diagram