AI Clumsy Robot - Watch It Fail at Stairs

This sketch animates a small robot that endlessly repeats a four-phase slapstick loop: walking up to a staircase, attempting to climb it, tumbling backward in a dramatic fall, and sheepishly walking back to try again. Speech bubbles ('I got this!' / 'Ow...') and an attempts counter reinforce the comedic, persistent-but-clumsy character.

🧪 Try This!

Experiment with the code by making these changes:

  1. Speed up the loop — Lowering cycle makes the robot repeat its walk-climb-fall-recover sequence much faster.
  2. Make the robot bigger — Raising the size multiplier scales up both the robot and the staircase relative to the window.
  3. Change the robot's catchphrases — Swap out the speech bubble text to give the robot new personality.
Prefer the full editor? Open it there →

📖 About This Sketch

This sketch builds a tiny animated character - a square-bodied robot with circle eyes and a line mouth - that walks toward a staircase, climbs a step, spectacularly flips over backward, and shuffles back to try again, forever. It's a great study in p5.js character animation because it combines push()/translate()/rotate() to rig a rotating body, normalized time (a value that sweeps from 0 to 1) to drive a multi-phase state machine, and lerp() to smoothly interpolate position and rotation between keyframes. Speech bubbles built from rect(), triangle(), and text() add comic timing, while an attempts counter shows how simple modulo arithmetic on frameCount can trigger one-time events inside a repeating loop.

The entire sketch lives in just three functions - setup(), draw(), and windowResized() - with no classes, arrays, or helper functions at all. Everything the robot does each frame is recalculated from scratch based on where frameCount falls within a repeating 'cycle' of frames, which is a powerful pattern to learn: instead of storing and updating state over time, the whole animation is a pure function of a single progress value (u). Studying this sketch teaches you how far you can get with conditional phase logic and interpolation alone, without needing physics engines or object-oriented structure.

⚙️ How It Works

  1. setup() creates a canvas that fills the browser window, sets rectMode to CENTER so shapes are positioned from their middle instead of their corner, and fixes a text size for later labels.
  2. Every frame, draw() clears the background, draws the ground line and five stacked stair rectangles, then computes u - a value from 0 to 1 showing progress through the current 360-frame cycle (frameCount % cycle / cycle).
  3. Based on which range u falls into, the code picks one of four movement phases: walking toward the stairs, climbing up with a backward lean, tumbling over in a big backward flip, or walking back to the starting spot - each phase uses lerp() to smoothly blend the robot's x, y position and rotation angle between phase start and end.
  4. The robot's body, eyes, and mouth are drawn inside a push()/translate()/rotate()/pop() block, so the rotation angle computed for the current phase spins only the robot, not the ground or stairs.
  5. A speech bubble reading 'I got this!' or 'Ow...' appears above the robot during specific parts of u, built from a rounded rect, a triangle pointer, and text() positioned relative to the robot's current location.
  6. Because frameCount increases forever, u cycles from 0 back to 0 every 360 frames, replaying the same walk-climb-fall-recover sequence endlessly - and each time a new cycle begins, frameCount % cycle == 1 triggers the attempts counter to increment once.

🎓 Concepts You'll Learn

Animation driven by a normalized progress value (u)Conditional phase logic acting as a state machinelerp() for smooth interpolation between keyframespush()/translate()/rotate()/pop() for local transformsUsing frameCount % cycle for repeating and one-shot eventsSpeech bubble UI built from primitive shapes and text()

📝 Code Breakdown

setup()

setup() runs once when the sketch starts, before draw() begins looping. It's the right place to configure canvas size and any drawing modes/defaults (like rectMode and textSize) that should apply for the entire sketch.

function setup(){createCanvas(windowWidth,windowHeight);rectMode(CENTER);textSize(20);}
Line-by-line explanation (3 lines)
createCanvas(windowWidth,windowHeight);
Creates a canvas that fills the entire browser window, so the scene scales to whatever screen it's viewed on.
rectMode(CENTER);
Tells p5.js to draw all future rectangles from their center point rather than their top-left corner - this makes it much easier to position and rotate the robot's body.
textSize(20);
Sets the default font size used later for the attempts counter and speech bubble text.

draw()

draw() runs continuously (about 60 times per second) and is where nearly all of this sketch's logic lives: recalculating the robot's position, rotation, and expression from scratch every frame based on a normalized time value. This 'stateless per-frame' approach is a lightweight alternative to storing and updating velocity/position variables over time.

🔬 This loop draws the 5 stacked stair steps. What happens if you change i<5 to i<10 - do the stairs get taller, and does the robot's climb still line up with them?

for(let i=0;i<5;i++)rect(sx+sw/2,g-sh*(i+0.5),sw,sh);

🔬 This phase makes the robot tumble backward, rotating from -0.4 to 3.5 radians (about 200 degrees). What happens if you change 3.5 to 6.28 for a full 360-degree spin instead of a half-flip?

else if(u<0.75){let p=(u-0.5)/0.25;x=stairX+s*0.3-s*1.5*p;y=g-s*1.2+s*1.6*p;a=lerp(-0.4,3.5,p);}
function draw(){
  if(frameCount%cycle==1)attempts++;
  background(190);
  let g=height*0.75,s=min(width,height)*0.1;
  stroke(120);line(0,g,width,g);fill(220);let sx=width*0.6,sh=height*0.05,sw=width*0.3;
  for(let i=0;i<5;i++)rect(sx+sw/2,g-sh*(i+0.5),sw,sh);
  let u=(frameCount%cycle)/cycle;let startX=width*0.15,stairX=sx-s;
  let x,y,a=0;
  if(u<0.35){let p=u/0.35;x=lerp(startX,stairX,p);y=g-s/2;}
  else if(u<0.5){let p=(u-0.35)/0.15;x=lerp(stairX,stairX+s*0.3,p);y=lerp(g-s/2,g-s*1.2,p);a=lerp(0,-0.4,p);}
  else if(u<0.75){let p=(u-0.5)/0.25;x=stairX+s*0.3-s*1.5*p;y=g-s*1.2+s*1.6*p;a=lerp(-0.4,3.5,p);}
  else{let fx=stairX+s*0.3-s*1.5,fy=g-s*1.2+s*1.6;let p=(u-0.75)/0.25;x=lerp(fx,startX,p);y=lerp(fy,g-s/2,p);a=lerp(3.5,0,p);}
  push();translate(x,y);rotate(a);stroke(0);fill(245);rect(0,0,s,s,8);
  fill(0);let ex=s*0.2,ey=-s*0.1;circle(-ex,ey,s*0.15);circle(ex,ey,s*0.15);
  strokeWeight(2);noFill();if(u<0.5||u>0.9)line(-s*0.15,s*0.1,s*0.15,s*0.05);
  else if(u<0.75)line(-s*0.15,s*0.05,s*0.15,s*0.1);
  else line(-s*0.15,s*0.08,s*0.15,s*0.08);pop();
  noStroke();fill(0);textAlign(LEFT,TOP);text("Attempts: "+attempts,10,10);
  let msg="";if(u<0.3)msg="I got this!";else if(u>0.55&&u<0.8)msg="Ow...";
  if(msg!=""){let tw=textWidth(msg)+20,bx=x+s*1.4,by=y-s*1.4;fill(255);stroke(0);rectMode(CENTER);rect(bx,by,tw,30,10);
    triangle(bx-tw*0.3,by+15,bx-tw*0.45,by+25,bx-tw*0.15,by+25);fill(0);noStroke();textAlign(CENTER,CENTER);text(msg,bx,by);}
}
Line-by-line explanation (19 lines)

🔧 Subcomponents:

conditional One-Shot Attempts Counter if(frameCount%cycle==1)attempts++;

Increments the attempts counter exactly once per cycle, right after a new cycle begins, using modulo arithmetic on frameCount.

for-loop Draw Staircase for(let i=0;i<5;i++)rect(sx+sw/2,g-sh*(i+0.5),sw,sh);

Loops 5 times to draw stacked rectangles that form the visible staircase, each one step higher than the last.

conditional Four-Phase Animation State Machine if(u<0.35){...} else if(u<0.5){...} else if(u<0.75){...} else{...}

Splits the 0-to-1 progress value u into four ranges (approach, climb, fall, recover), each computing a different formula for the robot's x, y, and rotation angle.

conditional Mouth Expression Switch if(u<0.5||u>0.9)line(...); else if(u<0.75)line(...); else line(...);

Draws a different mouth-line tilt depending on the phase, giving the illusion of the robot's expression changing (calm vs. pained during the fall).

conditional Speech Bubble Display if(msg!=""){...}

Only draws the speech bubble shape and text when there is an active message ('I got this!' or 'Ow...') for the current phase.

if(frameCount%cycle==1)attempts++;
Checks if we're exactly 1 frame into a new cycle; if so, increments the attempts counter once (not every frame).
background(190);
Repaints the whole canvas light gray every frame, erasing the previous frame's drawing so the animation doesn't leave trails.
let g=height*0.75,s=min(width,height)*0.1;
g is the y-coordinate of the ground line (75% down the screen); s is a size unit for the robot and stairs, based on the smaller of width/height so it scales on any screen.
stroke(120);line(0,g,width,g);fill(220);let sx=width*0.6,sh=height*0.05,sw=width*0.3;
Draws the horizontal ground line, then sets up variables for the staircase's starting x-position (sx), each step's height (sh), and each step's width (sw).
for(let i=0;i<5;i++)rect(sx+sw/2,g-sh*(i+0.5),sw,sh);
Loops 5 times to draw 5 stacked rectangles, each one shifted up by sh, forming the visible staircase.
let u=(frameCount%cycle)/cycle;let startX=width*0.15,stairX=sx-s;
u is the normalized progress through the current cycle, always between 0 and 1. startX is where the robot begins its walk; stairX is where the staircase begins.
if(u<0.35){let p=u/0.35;x=lerp(startX,stairX,p);y=g-s/2;}
During the first 35% of the cycle, the robot walks from startX to stairX at ground level - lerp() smoothly interpolates its x position.
else if(u<0.5){let p=(u-0.35)/0.15;x=lerp(stairX,stairX+s*0.3,p);y=lerp(g-s/2,g-s*1.2,p);a=lerp(0,-0.4,p);}
From 35% to 50%, the robot climbs up onto the first step, moving slightly forward and up while leaning backward (rotation goes from 0 to -0.4 radians).
else if(u<0.75){let p=(u-0.5)/0.25;x=stairX+s*0.3-s*1.5*p;y=g-s*1.2+s*1.6*p;a=lerp(-0.4,3.5,p);}
From 50% to 75%, the robot tumbles backward and downward while rotating from -0.4 to 3.5 radians (about 200 degrees) - this is the comedic fall.
else{let fx=stairX+s*0.3-s*1.5,fy=g-s*1.2+s*1.6;let p=(u-0.75)/0.25;x=lerp(fx,startX,p);y=lerp(fy,g-s/2,p);a=lerp(3.5,0,p);}
For the final 25%, the robot recovers: it walks from the fall's ending position back to startX, and its rotation eases back to 0 (standing upright again).
push();translate(x,y);rotate(a);stroke(0);fill(245);rect(0,0,s,s,8);
Saves the current transform, moves the origin to the robot's position, rotates by angle a, then draws the robot's rounded square body at the new local (0,0).
fill(0);let ex=s*0.2,ey=-s*0.1;circle(-ex,ey,s*0.15);circle(ex,ey,s*0.15);
Draws two small black circles as eyes, offset left and right (ex) and slightly up (ey) from the body's center.
strokeWeight(2);noFill();if(u<0.5||u>0.9)line(-s*0.15,s*0.1,s*0.15,s*0.05);
Sets up a thicker outline-only mouth line, and during the walking/climbing/recovering phases draws it in a neutral tilted position.
else if(u<0.75)line(-s*0.15,s*0.05,s*0.15,s*0.1);
During the fall phase (50-75%), draws the mouth tilted the opposite way to suggest a pained expression.
else line(-s*0.15,s*0.08,s*0.15,s*0.08);pop();
For the remaining moments, draws a flat mouth, then pop() restores the transform so later drawing isn't affected by the rotation/translation.
noStroke();fill(0);textAlign(LEFT,TOP);text("Attempts: "+attempts,10,10);
Draws the attempts counter text in the top-left corner of the canvas.
let msg="";if(u<0.3)msg="I got this!";else if(u>0.55&&u<0.8)msg="Ow...";
Decides which speech bubble message (if any) should be shown right now, based on which part of the cycle u is in.
if(msg!=""){let tw=textWidth(msg)+20,bx=x+s*1.4,by=y-s*1.4;fill(255);stroke(0);rectMode(CENTER);rect(bx,by,tw,30,10);
If there's an active message, calculates the bubble's width from the text, positions it above and to the side of the robot, and draws a white rounded rectangle for the bubble background.
triangle(bx-tw*0.3,by+15,bx-tw*0.45,by+25,bx-tw*0.15,by+25);fill(0);noStroke();textAlign(CENTER,CENTER);text(msg,bx,by);}
Draws a small triangle pointer beneath the bubble (like a comic-book speech bubble tail) and then writes the message text centered inside the bubble.

windowResized()

windowResized() is a special p5.js callback that fires whenever the browser window is resized. Pairing it with resizeCanvas() keeps full-window sketches responsive without any extra work.

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; resizes the canvas to match, so the scene stays full-screen and responsive.

📦 Key Variables

attempts number

Counts how many times the robot has attempted the climb, incremented once per animation cycle and displayed on-screen.

let attempts=0,cycle=360;
cycle number

The number of frames one complete walk-climb-fall-recover animation loop takes; controls the overall speed of the repeating sequence.

let attempts=0,cycle=360;

🔧 Potential Improvements (4)

Here are some ways this code could be enhanced:

BUG draw() - if(frameCount%cycle==1)attempts++;

This relies on frameCount hitting exactly cycle+1 to trigger the counter; if the browser ever skips frames (tab throttling, heavy load), the exact remainder of 1 could be missed and a whole attempt would go uncounted.

💡 Track the previous frame's u value and detect wraparound (e.g., when u drops from near 1 back to near 0) instead of relying on an exact modulo match.

STYLE draw()

Single-letter variable names (g, s, u, a, p, x, y, ex, ey) make the phase logic hard to follow for readers unfamiliar with the code.

💡 Rename to descriptive names like groundY, unitSize, progress, angle, phaseProgress for much easier readability.

STYLE draw() phase thresholds

Magic numbers like 0.35, 0.5, 0.75, 1.5, and 1.6 are scattered through the if/else chain, making it hard to retune the timing of each phase.

💡 Extract these as named constants (e.g., const WALK_END = 0.35, CLIMB_END = 0.5, FALL_END = 0.75) declared once near the top of the file.

FEATURE draw()

The animation is fully deterministic and non-interactive - it always plays out identically every cycle.

💡 Add mouse or keyboard interaction, such as clicking to trigger an instant fall, or slightly randomizing the fall's rotation/distance each cycle for variety.

🔄 Code Flow

Code flow showing setup, draw, windowresized

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

graph TD start[Start] --> setup[setup] setup --> draw[draw loop] draw --> attempts-counter[One-Shot Attempts Counter] draw --> stairs-loop[Draw Staircase] draw --> phase-state-machine[Four-Phase Animation State Machine] draw --> mouth-expression[Mouth Expression Switch] draw --> speech-bubble[Speech Bubble Display] attempts-counter -->|Increment on new cycle| draw stairs-loop -->|Loop 5 times| draw phase-state-machine -->|Split u into ranges| draw mouth-expression -->|Change mouth-line tilt| draw speech-bubble -->|Draw bubble if active message| draw click setup href "#fn-setup" click draw href "#fn-draw" click attempts-counter href "#sub-attempts-counter" click stairs-loop href "#sub-stairs-loop" click phase-state-machine href "#sub-phase-state-machine" click mouth-expression href "#sub-mouth-expression" click speech-bubble href "#sub-speech-bubble"

❓ Frequently Asked Questions

What visual experience does the AI Clumsy Robot sketch provide?

The sketch features a charming little robot attempting to climb stairs, accompanied by humorous speech bubbles and a persistent attempts counter, creating an entertaining slapstick comedy atmosphere.

Is there any way for users to interact with the AI Clumsy Robot sketch?

While the sketch is primarily a visual performance, users can enjoy the endless cycle of the robot's attempts without direct interaction.

What creative coding techniques are showcased in the AI Clumsy Robot sketch?

This sketch demonstrates concepts such as animation, interpolation, and real-time text display, effectively combining movement and humor in a programmatic art form.

Preview

AI Clumsy Robot - Watch It Fail at Stairs - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of AI Clumsy Robot - Watch It Fail at Stairs - Code flow showing setup, draw, windowresized
Code Flow Diagram