AI Coin Flip - Heads or Tails with Stats

This sketch creates an animated gold coin that flips end-over-end when clicked, using a squishing scale trick to fake a 3D flip, then lands on a random Heads or Tails result. A running tally of heads versus tails is displayed below the coin, turning a simple click interaction into a tiny stats-tracking toy.

🧪 Try This!

Experiment with the code by making these changes:

  1. Recolor the coin — The gold color comes from a single fill() call right before the coin is drawn - swap it for any RGB values to make a silver or copper coin.
  2. Make the coin bigger — The last number passed to circle() is the coin's diameter in pixels, so raising it makes the coin dramatically larger.
  3. Rig the odds toward heads — The random()<0.5 check gives a 50/50 chance of heads - lowering the threshold makes heads far less likely, tails far more likely.
Prefer the full editor? Open it there →

📖 About This Sketch

This sketch turns a single click into a satisfying animated coin flip: a gold circle squashes and stretches vertically to simulate spinning in 3D, then settles on a random H or T. Underneath the animation, the code keeps a running count of how many times heads and tails have landed, displayed live at the bottom of the screen. The whole effect is built from just a handful of p5.js ideas - the draw() loop, millis() for timing, cos() for easing, and push()/translate()/scale() for positioning and squashing the coin.

The code is deliberately compact: a single global line declares six state variables, and all the logic lives inside draw() and a tiny mousePressed() handler. By studying it you'll learn how a boolean-like flag variable can gate an animation, how elapsed time (via millis()) drives a cosine wave to create a spin illusion, and how a ternary expression can pick a random outcome and update multiple variables in one line.

⚙️ How It Works

  1. When the sketch loads, setup() creates a full-window canvas, centers all text alignment, and sets a large text size for the coin's letter.
  2. Every frame, draw() clears the background, prints the instruction text and the running H/T tally, then moves the drawing origin to the center of the screen.
  3. If a flip is in progress (the flag f is 1), the code measures how many milliseconds have passed since the flip started and uses cos() on that elapsed time to oscillate a vertical scale value, making the coin appear to spin by squashing it from full width down to a sliver and back.
  4. Once one second has passed, the animation stops: a random check picks Heads or Tails, increments the matching counter, and sets the final scale so the coin displays flat and right-side-up with its result letter.
  5. The coin circle and its H or T letter are drawn using the current scale value, so during the spin they appear to flatten and expand; when mousePressed() fires and no flip is already running, it starts a new flip by setting the flag and recording the start time.

🎓 Concepts You'll Learn

State flags for animation gatingmillis()-based timingCosine easing for fake 3D rotationpush()/translate()/scale() transformsTernary operator with comma expressionsMouse interaction with mousePressed()Template literals for dynamic text

📝 Code Breakdown

setup()

setup() runs exactly once when the sketch starts. It's the right place to configure the canvas and any drawing settings - like text alignment and size - that stay constant unless you explicitly change them later.

function setup() { createCanvas(windowWidth, windowHeight); textAlign(CENTER, CENTER); textSize(80); }
Line-by-line explanation (3 lines)
createCanvas(windowWidth, windowHeight);
Creates a canvas that fills the entire browser window, so the coin scene always matches the screen size.
textAlign(CENTER, CENTER);
Sets text so it's drawn centered on both the horizontal and vertical axis around the x,y coordinates you give it - important because the coin's letter is drawn at (0,0) after translating to the center.
textSize(80);
Sets a default large font size, later reused for the H/T letter on the coin face.

draw()

draw() runs continuously at roughly 60 frames per second. Here it doubles as both a renderer (drawing text and the coin every frame) and a tiny state machine, using the flag f and elapsed time to decide whether the coin is spinning or has landed - a common pattern for simple animations without a full physics engine.

🔬 This block decides whether the coin is still spinning or has landed. What happens if you change 1000 to 300 so the flip is much shorter, or change random()<0.5 to random()<0.1 so heads almost never wins?

    if (e<1000) sy=cos(e*.02*PI); // Flip animation: scaleY oscillates for 1 second
    else {
      f=0; (random()<0.5) ? (r='H',h++,sy=1) : (r='T',t++,sy=-1); // Random result, update counts, set final scaleY
    }

🔬 These two lines draw the instruction text and the stats text at fixed positions (height/4 and height*3/4). What happens if you move the stats text to height/4 too, so it overlaps the instructions?

  fill(0); textSize(24); text('Click to flip!', width/2, height/4); // Top instruction
  textSize(20); text(`H: ${h} | T: ${t}`, width/2, height*3/4); // Result counts
function draw() {
  background(220); // Light gray
  fill(0); textSize(24); text('Click to flip!', width/2, height/4); // Top instruction
  textSize(20); text(`H: ${h} | T: ${t}`, width/2, height*3/4); // Result counts

  push(); translate(width/2, height/2); // Center coin
  if (f) {
    let e=millis()-fs;
    if (e<1000) sy=cos(e*.02*PI); // Flip animation: scaleY oscillates for 1 second
    else {
      f=0; (random()<0.5) ? (r='H',h++,sy=1) : (r='T',t++,sy=-1); // Random result, update counts, set final scaleY
    }
  }
  scale(1,sy); fill(255,215,0); noStroke(); circle(0,0,150); // Coin circle
  fill(0); textSize(80); text(r,0,0); // Coin text
  pop();
}
Line-by-line explanation (12 lines)

🔧 Subcomponents:

conditional Flip In Progress Check if (f) {

Only runs the animation timing logic while a flip is actively happening

conditional Spinning vs Landed if (e<1000) sy=cos(e*.02*PI);

For the first second, keeps oscillating the vertical scale to simulate spinning; after that, stops the flip

calculation Random Result Ternary (random()<0.5) ? (r='H',h++,sy=1) : (r='T',t++,sy=-1);

Picks Heads or Tails with 50/50 odds, updates the matching counter, and sets the final flat scale

background(220); // Light gray
Repaints the whole canvas light gray every frame, erasing the previous frame's drawing so the animation doesn't leave trails.
fill(0); textSize(24); text('Click to flip!', width/2, height/4); // Top instruction
Draws black instructional text near the top of the screen, sized at 24 pixels.
textSize(20); text(`H: ${h} | T: ${t}`, width/2, height*3/4); // Result counts
Uses a template literal to build a string showing the current heads and tails counts, drawn near the bottom of the screen.
push(); translate(width/2, height/2); // Center coin
Saves the current drawing state and shifts the origin (0,0) to the exact center of the canvas, so everything drawn next is centered around the coin's position.
if (f) {
Checks the flag variable f - if it's 1 (truthy), a flip is currently in progress, so the animation logic below runs.
let e=millis()-fs;
Calculates how many milliseconds have elapsed since the flip started, by subtracting the stored start time fs from the current time millis().
if (e<1000) sy=cos(e*.02*PI); // Flip animation: scaleY oscillates for 1 second
While less than 1000ms (1 second) has passed, uses the cosine of the elapsed time to smoothly oscillate the vertical scale factor sy between -1 and 1, creating the illusion of the coin spinning edge-on.
else {
Once a full second has passed, switches to landing logic instead of continuing the spin.
f=0; (random()<0.5) ? (r='H',h++,sy=1) : (r='T',t++,sy=-1); // Random result, update counts, set final scaleY
Turns off the flipping flag, then uses a ternary with comma expressions to randomly choose Heads or Tails: it sets the result letter r, increments the matching counter (h or t), and sets sy to 1 or -1 so the coin displays right-side-up (or mirrored) with its final result.
scale(1,sy); fill(255,215,0); noStroke(); circle(0,0,150); // Coin circle
Applies the vertical scale factor (squashing the coin during flips), sets the fill to a gold color, removes the outline, and draws the coin as a 150-pixel circle.
fill(0); textSize(80); text(r,0,0); // Coin text
Draws the current result letter (H or T) in black, large 80px text, centered at the coin's origin.
pop();
Restores the drawing state saved earlier by push(), undoing the translate() and scale() so later frames start fresh.

mousePressed()

mousePressed() is a built-in p5.js function that automatically runs once whenever the mouse is clicked. It's the standard way to handle discrete click-based interactions, as opposed to draw()'s continuous per-frame updates.

function mousePressed() { if (!f) { f=1; fs=millis(); } }
Line-by-line explanation (2 lines)

🔧 Subcomponents:

conditional Prevent Overlapping Flips if (!f) { f=1; fs=millis(); }

Only starts a new flip if one isn't already in progress

if (!f) {
Checks that f is currently 0 (falsy), meaning no flip is running - this stops clicks from restarting the animation mid-flip.
f=1; fs=millis();
Sets the flag to 1 to start the flip, and records the current time in fs so draw() can measure elapsed time from this moment.

windowResized()

windowResized() is a built-in p5.js callback that fires automatically whenever the browser window changes size. Pairing it with resizeCanvas() keeps full-window sketches responsive without any extra setup.

function windowResized() { resizeCanvas(windowWidth, windowHeight); }
Line-by-line explanation (1 lines)
resizeCanvas(windowWidth, windowHeight);
Resizes the canvas to match the browser window whenever the window is resized, keeping the coin scene full-screen.

📦 Key Variables

h number

Counts how many times the coin has landed on Heads across all flips.

let h = 0;
t number

Counts how many times the coin has landed on Tails across all flips.

let t = 0;
f number

Acts as a boolean flag (0 or 1) indicating whether a flip animation is currently in progress.

let f = 0;
fs number

Stores the millis() timestamp when the current flip started, used to calculate elapsed animation time.

let fs = 0;
r string

Holds the letter currently displayed on the coin face - either 'H' or 'T'.

let r = 'H';
sy number

The vertical scale factor applied to the coin; oscillates during the flip to fake a spinning 3D effect and settles at 1 or -1 when landed.

let sy = 1;

🔧 Potential Improvements (4)

Here are some ways this code could be enhanced:

STYLE global variables (h, t, f, fs, r, sy)

All state is packed into single-letter variable names on one line, which makes the code hard to read and maintain for anyone beyond the original author.

💡 Use descriptive names like headsCount, tailsCount, isFlipping, flipStartTime, result, and scaleY - a few extra characters greatly improve readability without any performance cost.

BUG draw() flip landing logic

The random outcome, counter increment, and scale reset are all crammed into a single comma-expression ternary, which is hard to debug and easy to break if edited incorrectly.

💡 Expand this into a normal if/else block with separate statements, which behaves identically but is far easier to read, modify, and add features to (like sound effects on landing).

FEATURE mousePressed() / draw()

There's no way to reset the H/T counters or view a percentage breakdown, and the flip always takes exactly 1000ms regardless of user preference.

💡 Add a reset button (e.g. pressing 'r'), display win percentages alongside the raw counts, and consider persisting counts in localStorage so stats survive a page reload.

STYLE draw() flip timing

The flip duration (1000) and spin speed (0.02) are magic numbers embedded directly in a conditional, making them harder to find and tune.

💡 Pull these into named constants like const FLIP_DURATION = 1000; and const SPIN_SPEED = 0.02; declared near the top of the file, so they're easy to locate and adjust.

🔄 Code Flow

Code flow showing setup, draw, mousepressed, windowresized

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

graph TD start[Start] --> setup[setup] setup --> draw[draw loop] draw --> flipguard[flip-guard] flipguard -->|if no flip in progress| flipinprogress[flip-in-progress] flipinprogress --> animationvslanded[animation-vs-landed] animationvslanded -->|if spinning| randomresult[random-result] randomresult --> draw flipguard -->|if flip in progress| draw click setup href "#fn-setup" click draw href "#fn-draw" click flipguard href "#sub-flip-guard" click flipinprogress href "#sub-flip-in-progress" click animationvslanded href "#sub-animation-vs-landed" click randomresult href "#sub-random-result"

❓ Frequently Asked Questions

What visual experience does the AI Coin Flip sketch provide?

The sketch creates a visually engaging coin flip animation, showcasing a gold coin that spins and displays either 'H' for heads or 'T' for tails.

How can users interact with the AI Coin Flip sketch?

Users can click anywhere on the canvas to initiate a coin flip, which triggers the animation and updates the flip statistics.

What creative coding techniques are demonstrated in the AI Coin Flip sketch?

The sketch demonstrates techniques such as animation through scaling, randomization for outcome generation, and real-time state tracking for user interactions.

Preview

AI Coin Flip - Heads or Tails with Stats - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of AI Coin Flip - Heads or Tails with Stats - Code flow showing setup, draw, mousepressed, windowresized
Code Flow Diagram