Skip to content
· 6 min read · 0 views

Magikarp Countdown Challenge — 30 Days Web Challenge Day 1

Roll 4 dice to get a random target number, then try to stop a timer at exactly that many milliseconds. Hit it and watch the countdown drop. Miss it and time goes up. Catch the hidden Magikarp to begin!

// table of contents (10 sections)

Try it live at 30days.abduarrahman.com and the source code is on GitHub.


Live Demo

Catch the Magikarp to start the challenge — roll dice, match the timer, race the countdown:


The Origin

It started with a random comment in a group chat: “You should build one web feature every day for 30 days.” Someone said it as a joke. I took it seriously.

Day 1 had to set the tone. Something fun, something with sound, something that hooks you in. I thought about dice, timers, and the classic Magikarp meme — why not make people chase a bouncing fish before they even start the challenge?

The Magikarp Countdown Challenge was born: roll 4 dice to get a random target (1000-9999 milliseconds), then try to stop a precision timer at exactly that number. Hit it? The countdown drops. Miss it? Time goes up. Run out of time? Game over. But there’s an easter egg…


What I Built

A multi-phase mini-game with:

  • Bouncing Magikarp landing screen — a fish that bounces around the screen with physics, bubble trails, and ripple effects. Click it to start
  • 4-dice rolling sequence — dice fly in from random angles, land with satisfying sounds, and combine into a target number
  • Precision timer matching — stop the millisecond timer at exactly the target number
  • Live countdown — the timer ticks down in real-time with color changes (blue → yellow → red)
  • Easter egg: the Gyarados glitch — get within ±100ms of the target and the screen glitches out with a multi-layered digital crash sound, then reveals Gyarados
  • Synthesized audio — all sounds are generated with the Web Audio API (no audio files needed)

How It Works

Dice Rolling with Sound Synthesis

Each die rolls with a rapid randomization animation, then lands on its final value. The sound is synthesized on-the-fly using the Web Audio API — a noise burst through a bandpass filter that mimics dice clatter:

const playDiceSound = useCallback(() => {
  try {
    const ctx = new AudioContext();
    const duration = 0.15;
    const bufferSize = ctx.sampleRate * duration;
    const buffer = ctx.createBuffer(1, bufferSize, ctx.sampleRate);
    const data = buffer.getChannelData(0);

    // Generate noise burst (sounds like dice clatter)
    for (let i = 0; i < bufferSize; i++) {
      const t = i / ctx.sampleRate;
      const envelope = Math.exp(-t * 30);
      data[i] = (Math.random() * 2 - 1) * envelope * 0.3;
    }

    const source = ctx.createBufferSource();
    source.buffer = buffer;

    // Band-pass filter for dice-like tone
    const filter = ctx.createBiquadFilter();
    filter.type = "bandpass";
    filter.frequency.value = 3000;
    filter.Q.value = 1;

    source.connect(filter);
    filter.connect(ctx.destination);
    source.start();
    source.stop(ctx.currentTime + duration);
    setTimeout(() => ctx.close(), 500);
  } catch {}
}, []);

The dice fly to random positions using pre-generated landing spots:

function generateDiceLandingSpots() {
  return [0, 1, 2, 3].map(() => ({
    x: (Math.random() - 0.5) * 80,  // -40% to +40% from center
    y: (Math.random() - 0.5) * 60,  // -30% to +30% from center
    rotate: Math.floor(Math.random() * 90 - 45),
  }));
}

Timer Matching Logic

The core mechanic: 4 dice combine into a target number (e.g., 4-2-7-1 = 4271ms). You click to start a count-up timer, then click again to stop it. If you hit the exact number, the countdown drops. Miss it, and time goes up:

const handleClick = () => {
  if (gameState !== "counting") return;
  cancelAnimationFrame(rafRef.current);
  setIsCounting(false);

  const clickedMs = Math.round(countUpRef.current);
  const gap = Math.abs(clickedMs - targetNumber);

  if (clickedMs === targetNumber) {
    // Exact match — countdown drops
    setMatchResult("exact");
    const newCountdown = liveCountdown - targetNumber / 1000;
    if (newCountdown <= 0) {
      setGameState("victory");
    }
  } else if (gap <= 100) {
    // Easter egg: ±100ms → crash → glitch → Gyarados!
    setMatchResult("exact");
    setTimeout(() => setGameState("glitch"), 500);
    setTimeout(() => setGameState("victory"), 4500);
  } else {
    // Miss — countdown increases
    setMatchResult("miss");
    const newCountdown = liveCountdown + targetNumber / 1000;
    setCountdown(newCountdown);
  }
};

The Glitch Easter Egg

When you hit within ±100ms, the game triggers a 4-layer glitch sound: a sawtooth oscillator sweep, a square wave digital screech, gated white noise, and rapid digital beeps — all synthesized in real-time with the Web Audio API.

Magikarp Landing Physics

The landing screen features a bouncing Magikarp driven by requestAnimationFrame:

const animate = (time: number) => {
  if (!lastTimeRef.current) lastTimeRef.current = time;
  const delta = Math.min((time - lastTimeRef.current) / 16, 3);
  lastTimeRef.current = time;

  const { vx, vy } = velRef.current;
  const { x, y } = posRef.current;
  let nx = x + vx * delta;
  let ny = y + vy * delta;

  // Bounce off walls
  if (nx < marginX) { nx = marginX; bvx = Math.abs(bvx); }
  if (nx > 100 - marginX) { nx = 100 - marginX; bvx = -Math.abs(bvx); }
  if (ny < marginY) { ny = marginY; bvy = Math.abs(bvy); }
  if (ny > 90 - marginY) { ny = 90 - marginY; bvy = -Math.abs(bvy); }

  // Randomize direction on bounce
  if (bounced) {
    bvx += (Math.random() - 0.5) * 0.3;
    bvy += (Math.random() - 0.5) * 0.3;
    const speed = Math.sqrt(bvx * bvx + bvy * bvy);
    const targetSpeed = 0.2 + Math.random() * 0.15;
    if (speed > 0) {
      bvx = (bvx / speed) * targetSpeed;
      bvy = (bvy / speed) * targetSpeed;
    }
  }
};

Tech Stack

TechnologyPurpose
Next.jsReact framework with static export
TypeScriptType-safe game logic
Framer MotionDice animations, card reveals, sparkle effects
Web Audio APISynthesized dice sounds, glitch effects, tick sounds
CSS AnimationsBackground particles, ambient glow effects

Follow the challenge:

Support the challenge:

You might also like

Enjoyed This Post?

Want to discuss the topic, have questions, or looking to collaborate on something similar? Drop a comment below or reach out directly.

Discussion