CatEastHat

This sketch creates a playful interactive scene where a cartoon cat, controlled by your mouse or finger, chases and 'eats' a randomly placed hat. Once the cat's head gets close enough to the hat, the hat disappears and a fun message appears, letting you click to reset and play again.

🧪 Try This!

Experiment with the code by making these changes:

  1. Make the cat huge — Increasing catSize scales up every part of the cat since all its shapes are drawn as fractions of size.
  2. Change the hat's color scheme — The hat's brim and crown are filled blue - swapping the RGB values gives it an entirely different look.
  3. Require a much closer hug to eat the hat — Tightening the collision threshold makes the cat need to nearly overlap the hat's exact center before it counts as eaten.
Prefer the full editor? Open it there →

📖 About This Sketch

CatEastHat is a lighthearted interactive drawing built with p5.js where a cartoon cat follows your cursor around the screen and 'eats' a hat sitting somewhere on the canvas. The fun comes from watching simple shapes - circles, triangles, and rectangles - combine into a recognizable cat and hat, and from the satisfying moment when the dist() function detects they've collided. It's a great example of how a handful of p5.js primitives (circle, ellipse, triangle, rect, arc) can be layered together to build a character, and how basic distance math can turn static shapes into an interactive game.

The code is organized around two custom drawing functions, drawCat() and drawHat(), that each take an x, y, and size so they can be reused and repositioned easily. The setup() function prepares the canvas and places the hat randomly, while draw() runs every frame to move the cat, check for collisions, and display a message. Studying this sketch teaches you how to build reusable drawing functions, track game state with a boolean flag, detect proximity with dist(), and respond to user input with mousePressed().

⚙️ How It Works

  1. When the sketch loads, setup() creates a full-window canvas, centers the cat, and calls resetHat() to place the hat at a random spot on screen.
  2. Every frame, draw() clears the background and updates the cat's position to match the mouse (or touch) coordinates, so the cat always follows you.
  3. If the hat hasn't been eaten yet, drawHat() renders it; drawCat() always renders the cat on top, layering shapes like the head, ears, eyes, and whiskers.
  4. Each frame, dist() measures how far the cat's center is from the hat's center; if that distance is smaller than the combined sizes of the cat and hat (minus a small offset), the hatEaten flag flips to true.
  5. Once hatEaten is true, the hat stops being drawn and a congratulatory message appears on screen inviting you to click to reset.
  6. Clicking the canvas triggers mousePressed(), which calls resetHat() to clear the eaten flag and move the hat to a new random position, starting the chase again.

🎓 Concepts You'll Learn

Custom drawing functionsCollision detection with dist()Boolean state flagsMouse/touch interactionResponsive canvas resizingLayered shape composition

📝 Code Breakdown

setup()

setup() runs exactly once when the sketch starts. It's the right place to size your canvas and set up initial positions before any animation begins.

function setup() {
  // Create a canvas that fills the entire window
  createCanvas(windowWidth, windowHeight);
  // Initialize cat at the center of the screen
  catX = width / 2;
  catY = height / 2;
  // Initialize hat at a random position, ensuring it's within bounds
  resetHat();
}
Line-by-line explanation (4 lines)
createCanvas(windowWidth, windowHeight);
Makes a canvas that exactly fills the browser window, so the sketch works on any screen size.
catX = width / 2;
Starts the cat's horizontal position in the middle of the canvas before the mouse takes over.
catY = height / 2;
Starts the cat's vertical position in the middle of the canvas.
resetHat();
Calls the helper function that picks a random spot for the hat and resets the 'eaten' flag to false.

draw()

draw() runs continuously (about 60 times per second) to create animation. Every frame it redraws the scene, updates positions from input, and re-checks game logic like collisions - this is the heartbeat of every interactive p5.js sketch.

🔬 This is the collision check. What happens if you remove the '- 20' so the cat has to get even closer before the hat counts as eaten? What if you change it to '+ 40' to make eating much easier?

  let d = dist(catX, catY, hatX, hatY);
  // If the distance is less than the combined half-sizes (minus a small offset for better feel)
  // AND the hat hasn't been eaten yet, then the hat is eaten!
  if (d < (catSize / 2 + hatSize / 2 - 20) && !hatEaten) {
function draw() {
  background(220); // Light gray background

  // Move the cat to follow the mouse/touch position
  catX = mouseX;
  catY = mouseY;

  // Draw the hat only if it hasn't been eaten
  if (!hatEaten) {
    drawHat(hatX, hatY, hatSize);
  }

  // Draw the cat
  drawCat(catX, catY, catSize);

  // Check if the cat is "eating" the hat
  // Calculate the distance between the center of the cat and the center of the hat
  let d = dist(catX, catY, hatX, hatY);
  // If the distance is less than the combined half-sizes (minus a small offset for better feel)
  // AND the hat hasn't been eaten yet, then the hat is eaten!
  if (d < (catSize / 2 + hatSize / 2 - 20) && !hatEaten) {
    hatEaten = true;
    console.log("Hat eaten!"); // Log to the console
    // You could add a sound effect here using the p5.sound library:
    // let meowSound = new p5.SoundFile('assets/meow.mp3');
    // meowSound.play();
  }

  // Display a message when the hat is eaten
  if (hatEaten) {
    fill(0); // Black text
    textSize(32);
    textAlign(CENTER, CENTER); // Center the text on the screen
    text("Meow! That was a delicious hat!", width / 2, height / 4);
    textSize(20);
    text("Click to reset", width / 2, height * 3 / 4);
  }
}
Line-by-line explanation (10 lines)

🔧 Subcomponents:

conditional Draw Hat If Not Eaten if (!hatEaten) { drawHat(hatX, hatY, hatSize); }

Only renders the hat while it still exists (hasn't been eaten).

conditional Eating Collision Check if (d < (catSize / 2 + hatSize / 2 - 20) && !hatEaten) { hatEaten = true; console.log("Hat eaten!"); }

Compares the distance between cat and hat to their combined sizes to detect when the cat has 'eaten' the hat.

conditional Show Eaten Message if (hatEaten) { fill(0); textSize(32); textAlign(CENTER, CENTER); text("Meow! That was a delicious hat!", width / 2, height / 4); textSize(20); text("Click to reset", width / 2, height * 3 / 4); }

Displays celebratory text and reset instructions once the hat has been eaten.

background(220);
Repaints the whole canvas light gray every frame, erasing the previous frame's drawing so animation looks smooth instead of smeared.
catX = mouseX;
Sets the cat's x position to wherever the mouse currently is, making the cat follow your cursor.
catY = mouseY;
Sets the cat's y position to the mouse's vertical location.
if (!hatEaten) { drawHat(hatX, hatY, hatSize); }
Only draws the hat if it hasn't been eaten yet - once eaten, it simply stops appearing.
drawCat(catX, catY, catSize);
Draws the cat at its current (mouse-following) position on top of the hat.
let d = dist(catX, catY, hatX, hatY);
Calculates the straight-line distance between the cat's center and the hat's center using p5's built-in dist() function.
if (d < (catSize / 2 + hatSize / 2 - 20) && !hatEaten) {
Checks if the cat is close enough to overlap the hat (using half of each size as a rough radius, minus 20 pixels to require closer contact) and that the hat hasn't already been eaten.
hatEaten = true;
Flips the flag so the hat stops drawing and the victory message appears from now on.
console.log("Hat eaten!");
Prints a message to the browser's developer console, useful for debugging or confirming the event fired.
if (hatEaten) { ... text(...) ... }
When the hat has been eaten, this block draws centered black text announcing the win and instructing the player to click to play again.

resetHat()

This is a reusable helper function that centralizes the logic for placing and resetting the hat, so both setup() and mousePressed() can call it without duplicating code - a good practice called DRY (Don't Repeat Yourself).

🔬 random(min, max) picks a value in that range. What happens if you swap width and height in the y line, or use random(0, width) instead - does the hat ever appear partly off-screen?

  hatX = random(hatSize, width - hatSize);
  hatY = random(hatSize, height - hatSize);
function resetHat() {
  hatEaten = false;
  // Place the hat at a random position, ensuring it's fully visible
  hatX = random(hatSize, width - hatSize);
  hatY = random(hatSize, height - hatSize);
}
Line-by-line explanation (3 lines)
hatEaten = false;
Resets the game state so the hat is considered 'not eaten' again, which makes draw() start rendering it.
hatX = random(hatSize, width - hatSize);
Picks a random x coordinate for the hat between hatSize and (canvas width minus hatSize), keeping the whole hat visible on screen instead of clipped at the edges.
hatY = random(hatSize, height - hatSize);
Picks a random y coordinate for the hat using the same edge-safe logic as the x position.

mousePressed()

mousePressed() is a special p5.js function that automatically runs whenever the mouse button is clicked (or the screen is tapped). It's the standard way to handle simple click-based interaction.

function mousePressed() {
  if (hatEaten) {
    resetHat();
  }
}
Line-by-line explanation (2 lines)

🔧 Subcomponents:

conditional Reset If Hat Eaten if (hatEaten) { resetHat(); }

Only allows resetting the game when the hat has actually been eaten, preventing accidental resets mid-chase.

if (hatEaten) {
Checks whether the hat has already been eaten before allowing a reset - clicking does nothing while the hat is still on screen.
resetHat();
Calls the helper function to place a new hat and clear the eaten flag, restarting the game.

windowResized()

windowResized() is a special p5.js function that automatically fires when the browser window size changes, letting you keep responsive sketches looking correct at any size.

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
  // Re-adjust hat position if it hasn't been eaten
  if (!hatEaten) {
    resetHat();
  }
}
Line-by-line explanation (2 lines)

🔧 Subcomponents:

conditional Reposition Hat On Resize if (!hatEaten) { resetHat(); }

Moves the hat to a new valid random position after the window changes size, so it doesn't end up off-screen or in the wrong spot.

resizeCanvas(windowWidth, windowHeight);
Resizes the canvas to match the browser window whenever it changes size (e.g., resizing the browser or rotating a device).
if (!hatEaten) { resetHat(); }
If the hat hasn't been eaten, it gets a fresh random position appropriate to the new canvas dimensions - preventing it from being stuck outside the visible area.

drawCat()

drawCat() shows how complex characters are built from simple primitive shapes (circles, triangles, ellipses, arcs, lines) all positioned using proportional math based on a single 'size' parameter, so the whole cat scales consistently when size changes.

🔬 These lines position the eyes using size * 0.25 and size * 0.1 as offsets. What happens if you increase 0.25 to 0.4 - do the eyes move further apart or closer together?

  // Eyes (circles)
  fill(255); // White eyes
  circle(x - size * 0.25, y - size * 0.1, size * 0.2);
  circle(x + size * 0.25, y - size * 0.1, size * 0.2);
function drawCat(x, y, size) {
  noStroke(); // No outline for the cat

  // Body (ellipse)
  fill(150, 75, 0); // Brownish color
  ellipse(x, y + size * 0.3, size * 1.2, size * 0.8);

  // Head (circle)
  fill(150, 75, 0);
  circle(x, y, size);

  // Ears (triangles)
  fill(150, 75, 0);
  triangle(x - size * 0.4, y - size * 0.5, x - size * 0.2, y - size * 0.8, x - size * 0.1, y - size * 0.5);
  triangle(x + size * 0.4, y - size * 0.5, x + size * 0.2, y - size * 0.8, x + size * 0.1, y - size * 0.5);
  fill(255, 192, 203); // Pink inside ears
  triangle(x - size * 0.3, y - size * 0.45, x - size * 0.2, y - size * 0.75, x - size * 0.15, y - size * 0.45);
  triangle(x + size * 0.3, y - size * 0.45, x + size * 0.2, y - size * 0.75, x + size * 0.15, y - size * 0.45);

  // Eyes (circles)
  fill(255); // White eyes
  circle(x - size * 0.25, y - size * 0.1, size * 0.2);
  circle(x + size * 0.25, y - size * 0.1, size * 0.2);
  fill(0); // Black pupils
  circle(x - size * 0.25, y - size * 0.1, size * 0.1);
  circle(x + size * 0.25, y - size * 0.1, size * 0.1);

  // Nose (triangle)
  fill(255, 192, 203); // Pink nose
  triangle(x, y + size * 0.1, x - size * 0.05, y + size * 0.15, x + size * 0.05, y + size * 0.15);

  // Mouth (arc)
  noFill();
  stroke(0);
  arc(x, y + size * 0.2, size * 0.2, size * 0.1, 0, PI); // Happy arc mouth
  strokeCap(ROUND); // Rounded ends for lines
  strokeWeight(2);
  line(x, y + size * 0.15, x, y + size * 0.2); // Line connecting nose to mouth

  // Whiskers
  strokeWeight(1);
  line(x - size * 0.3, y + size * 0.1, x - size * 0.5, y + size * 0.1);
  line(x - size * 0.3, y + size * 0.15, x - size * 0.5, y + size * 0.2);
  line(x - size * 0.3, y + size * 0.05, x - size * 0.5, y);
  line(x + size * 0.3, y + size * 0.1, x + size * 0.5, y + size * 0.1);
  line(x + size * 0.3, y + size * 0.15, x + size * 0.5, y + size * 0.2);
  line(x + size * 0.3, y + size * 0.05, x + size * 0.5, y);
}
Line-by-line explanation (11 lines)

🔧 Subcomponents:

calculation Body and Head Shapes ellipse(x, y + size * 0.3, size * 1.2, size * 0.8); fill(150, 75, 0); circle(x, y, size);

Draws the cat's oval body and round head using proportional offsets based on size.

calculation Ear Triangles triangle(x - size * 0.4, y - size * 0.5, x - size * 0.2, y - size * 0.8, x - size * 0.1, y - size * 0.5);

Draws the outer and inner ear triangles positioned relative to the head's center and size.

calculation Eyes, Nose, and Mouth circle(x - size * 0.25, y - size * 0.1, size * 0.2);

Draws the eyes, pupils, nose, and mouth arc, all scaled and positioned using multiples of 'size'.

calculation Whisker Lines line(x - size * 0.3, y + size * 0.1, x - size * 0.5, y + size * 0.1);

Draws six whisker lines (three per side) extending outward from the face.

noStroke();
Turns off outlines so shapes are drawn as solid fills without borders, giving the cat a clean cartoon look.
fill(150, 75, 0);
Sets the fill color to a brown RGB value, used for the cat's body, head, and ears.
ellipse(x, y + size * 0.3, size * 1.2, size * 0.8);
Draws an oval body below the head's center, wider than it is tall, positioned using the size parameter so it scales with the cat.
circle(x, y, size);
Draws the cat's round head centered exactly at (x, y) with a diameter of 'size'.
triangle(x - size * 0.4, y - size * 0.5, x - size * 0.2, y - size * 0.8, x - size * 0.1, y - size * 0.5);
Draws the left ear as a triangle using three (x, y) points calculated as fractions of size, positioning it above and to the left of the head.
fill(255, 192, 203);
Switches the fill color to pink for the inner ear triangles and nose.
circle(x - size * 0.25, y - size * 0.1, size * 0.2);
Draws the white part of the left eye as a small circle offset from the head's center.
fill(0);
Switches fill color to black for the pupils.
arc(x, y + size * 0.2, size * 0.2, size * 0.1, 0, PI);
Draws a half-oval arc from angle 0 to PI (180 degrees) to create a smiling mouth shape, using noFill() and stroke() so only the outline shows.
strokeWeight(1);
Sets the thickness of lines to 1 pixel, used for drawing the thin whisker lines.
line(x - size * 0.3, y + size * 0.1, x - size * 0.5, y + size * 0.1);
Draws one whisker as a straight line from a point near the face outward to the left, using size-based offsets so whiskers scale with the cat.

drawHat()

drawHat() demonstrates rectMode(CENTER), which changes how p5.js positions rectangles, plus how layering simple rectangles and a circle in the right order creates a recognizable object like a top hat.

🔬 The pom-pom's vertical position is size * 0.6 above center. What happens if you change 0.6 to a bigger or smaller number - does the pom-pom float away from the hat or sink into it?

  fill(255, 0, 0); // Red ribbon
  rect(x, y - size * 0.1, size, size * 0.1);

  // Hat pom-pom (optional)
  fill(255); // White pom-pom
  circle(x, y - size * 0.6, size * 0.3);
function drawHat(x, y, size) {
  noStroke(); // No outline for the hat

  // Hat brim
  fill(0, 0, 255); // Blue hat
  rectMode(CENTER); // Draw rectangles from their center
  rect(x, y + size * 0.2, size * 1.5, size * 0.2, 5); // Rounded corners

  // Hat crown
  fill(0, 0, 255);
  rect(x, y - size * 0.2, size, size * 0.8, 5);

  // Hat ribbon
  fill(255, 0, 0); // Red ribbon
  rect(x, y - size * 0.1, size, size * 0.1);

  // Hat pom-pom (optional)
  fill(255); // White pom-pom
  circle(x, y - size * 0.6, size * 0.3);
}
Line-by-line explanation (9 lines)

🔧 Subcomponents:

calculation Brim and Crown Rectangles rect(x, y + size * 0.2, size * 1.5, size * 0.2, 5); fill(0, 0, 255); rect(x, y - size * 0.2, size, size * 0.8, 5);

Draws the wide flat brim and the tall crown of the hat as two rounded rectangles.

calculation Ribbon and Pom-Pom rect(x, y - size * 0.1, size, size * 0.1); fill(255); circle(x, y - size * 0.6, size * 0.3);

Adds a decorative red ribbon stripe and a white pom-pom circle on top of the hat.

noStroke();
Removes outlines from the hat shapes for a flat, clean look.
fill(0, 0, 255); // Blue hat
Sets the fill color to pure blue for the hat's brim and crown.
rectMode(CENTER); // Draw rectangles from their center
Changes how rect() interprets its x, y arguments - instead of the top-left corner, they now refer to the rectangle's center point, making positioning easier.
rect(x, y + size * 0.2, size * 1.5, size * 0.2, 5); // Rounded corners
Draws the wide, flat brim of the hat below center, with a width 1.5x the size and a small height, plus a 5-pixel corner radius for rounded edges.
rect(x, y - size * 0.2, size, size * 0.8, 5);
Draws the tall rectangular crown of the hat above the brim.
fill(255, 0, 0); // Red ribbon
Switches to red for the decorative ribbon band.
rect(x, y - size * 0.1, size, size * 0.1);
Draws a thin red rectangle across the crown to represent a hat ribbon.
fill(255); // White pom-pom
Switches to white for the pom-pom on top of the hat.
circle(x, y - size * 0.6, size * 0.3);
Draws a small circle at the very top of the hat to represent a fluffy pom-pom.

📦 Key Variables

catX number

Stores the cat's current horizontal position, updated every frame to match the mouse.

let catX;
catY number

Stores the cat's current vertical position, updated every frame to match the mouse.

let catY;
catSize number

Controls the diameter of the cat's head and scales all other cat features proportionally.

let catSize = 100;
hatX number

Stores the hat's horizontal position on the canvas, set randomly by resetHat().

let hatX;
hatY number

Stores the hat's vertical position on the canvas, set randomly by resetHat().

let hatY;
hatSize number

Controls the width of the hat's brim and scales the rest of the hat's shapes.

let hatSize = 70;
hatEaten boolean

Tracks whether the cat has eaten the hat yet, controlling whether the hat is drawn and whether the win message shows.

let hatEaten = false;

🔧 Potential Improvements (4)

Here are some ways this code could be enhanced:

BUG draw() collision check

The hardcoded '-20' offset in the collision formula is a magic number with no explanation of how it was chosen, and it doesn't scale with catSize or hatSize, so resizing those variables changes the game's difficulty in unpredictable ways.

💡 Replace the fixed offset with a percentage of the sizes, e.g. `(catSize / 2 + hatSize / 2) * 0.7`, so the required closeness scales proportionally as the cat or hat size changes.

FEATURE draw()

There's no visual or audio feedback at the moment the hat is eaten besides a console.log message, which most players will never see.

💡 Add a brief animation (like a scale-up 'pop' effect or particle burst at the hat's location) or a real sound effect using the p5.sound library, as hinted at in the commented-out code.

PERFORMANCE drawCat() and drawHat()

Both drawing functions call fill() multiple times with the same values (e.g., fill(150, 75, 0) is called twice in a row for the body and head), which is slightly redundant since the color doesn't change between those calls.

💡 Remove duplicate fill() calls when the color hasn't changed since the last shape, keeping the code a bit shorter and avoiding unnecessary p5.js state changes.

STYLE drawCat() and drawHat()

Both functions use many magic numbers (like size * 0.25, size * 0.6) scattered throughout with no named constants, making it harder to understand what each multiplier controls at a glance.

💡 Consider extracting key proportions into named variables (e.g., const eyeOffsetX = size * 0.25;) at the top of each function to make the layout logic more self-documenting.

🔄 Code Flow

Code flow showing setup, draw, resethat, mousepressed, windowresized, drawcat, drawhat

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

graph TD start[Start] --> setup[setup] setup --> draw[draw loop] draw --> draw-hat-check[draw-hat-check] draw-hat-check -->|If Hat Exists| drawhat[drawHat] draw-hat-check -->|If Hat Eaten| collision-check[collision-check] collision-check -->|If Collision Detected| message-display[message-display] message-display --> reset-on-click[reset-on-click] reset-on-click -->|If Clicked| resethat[resethat] draw --> cat-body-head[cat-body-head] draw --> cat-ears[cat-ears] draw --> cat-face[cat-face] draw --> cat-whiskers[cat-whiskers] draw --> hat-brim-crown[hat-brim-crown] draw --> hat-ribbon-pompom[hat-ribbon-pompom] draw --> windowresized[windowResized] windowresized --> resize-hat-adjust[resize-hat-adjust] resize-hat-adjust -->|Reposition Hat| drawhat click setup href "#fn-setup" click draw href "#fn-draw" click drawhat href "#fn-drawhat" click resethat href "#fn-resethat" click windowresized href "#fn-windowresized" click collision-check href "#sub-collision-check" click message-display href "#sub-message-display" click reset-on-click href "#sub-reset-on-click" click resize-hat-adjust href "#sub-resize-hat-adjust" click cat-body-head href "#sub-cat-body-head" click cat-ears href "#sub-cat-ears" click cat-face href "#sub-cat-face" click cat-whiskers href "#sub-cat-whiskers" click hat-brim-crown href "#sub-hat-brim-crown" click hat-ribbon-pompom href "#sub-hat-ribbon-pompom"

❓ Frequently Asked Questions

What visual experience does the CatEastHat sketch provide?

The CatEastHat sketch features an animated cat that follows the mouse cursor and 'eats' a hat displayed on the screen, creating a playful and whimsical visual interaction.

How can users interact with the CatEastHat sketch?

Users can move the cat around by moving their mouse or touching the screen, and they can click to reset the hat after it has been eaten.

What creative coding concepts are showcased in the CatEastHat sketch?

This sketch demonstrates concepts such as object positioning, collision detection, and interactive animations using p5.js.

Preview

CatEastHat - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of CatEastHat - Code flow showing setup, draw, resethat, mousepressed, windowresized, drawcat, drawhat
Code Flow Diagram