DEV Community

Cover image for Magikarp Countdown Challenge — 30 Days Web Challenge Day 1
Muhammad Abdu ar Rahman
Muhammad Abdu ar Rahman

Posted on • Originally published at abduarrahman.com

Magikarp Countdown Challenge — 30 Days Web Challenge Day 1

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


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 {}
}, []);
Enter fullscreen mode Exit fullscreen mode

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),
  }));
}
Enter fullscreen mode Exit fullscreen mode

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);
  }
};
Enter fullscreen mode Exit fullscreen mode

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;
    }
  }
};
Enter fullscreen mode Exit fullscreen mode

Tech Stack

Technology Purpose
Next.js React framework with static export
TypeScript Type-safe game logic
Framer Motion Dice animations, card reveals, sparkle effects
Web Audio API Synthesized dice sounds, glitch effects, tick sounds
CSS Animations Background particles, ambient glow effects

Links

Follow the challenge:

Support the challenge:


Originally published at abduarrahman.com

Top comments (0)