Fightulator

A calculator where the maths is settled by stickman fights. Type a sum and press equals: subtraction is a brawl, addition is a fusion dance, and whoever is left standing is the answer.

Solo — design and code
2026
  • JavaScript
  • HTML Canvas
  • SVG
  • Web Audio API
  • Web Speech API
The Fightulator calculator, a chunky pixel-art device labelled "FIGHTULATOR · STICKMAN EDITION" showing 0, held in two cartoon hands with the thumbs over the keys, in front of a stadium crowd of stickmen. The × and ÷ keys have small padlocks.

A calculator where the maths is settled by stickman fights. You hold a pixel calculator in first person, in a stadium full of cheering stickmen. Type a sum, press =, and the calculator drops: your numbers turn into fighters and battle it out, Street Fighter and Tekken style. When the dust settles, the calculator comes back up with the answer. Play it here.

How the maths works

Subtraction is a fight. The two numbers trade blows, and the winner keeps the leftover HP as the result: in 5 - 3 the 5 knocks out the 3 with 2 HP to spare. In 3 - 5 the 5 still wins, but it turns into an evil red -2. 1 - 1 ends in a cross counter and a double K.O., and a ghost 0 rises from the floor.

Addition is a fusion. The two fighters do the dance — FU~, SION, HA! — and merge into one bigger fighter carrying the sum.

Multiply and divide are a forbidden technique, locked behind a premium tier. The popup offers a donation QR code to pujuk the developer into unlocking them, and the fine print admits the truth: the QR is real, the feature is not, and the money goes to the kopi fund.

Eleven fighters and a boss

Every number from 0 to 10 is its own fighter, with its own look, a special move for mid-fight and a finisher for the final blow — the 5 is a ninja with Shadow Clone Strike, the 9 a knight with Holy Lance, the 10 a king with Royal Decree. A bigger number always beats a smaller one. Anything above 10 is a boss, and negative numbers fight as their evil red twins.

  • 0 · Ghost — Special: BOO! (goes see-through). Final blow: SOUL SNATCH — grows huge, soul spiral, flies through the opponent.
  • 1 · Rookie — Special: Lucky Punch. Final blow: DESPERATE HAYMAKER — shaky wind-up, dash punch.
  • 2 · Karateka — Special: Karate Chop (3 hits). Final blow: HUNDRED CHOPS — 14-hit flurry.
  • 3 · Street Kid — Special: Cap Toss. Final blow: CAP BOOMERANG — cap circles the opponent and hits 4 times.
  • 4 · Cool Guy — Special: Eye Laser. Final blow: DEAL WITH IT — giant shades drop from the sky, huge laser.
  • 5 · Ninja — Special: Shuriken ×3. Final blow: SHADOW CLONE STRIKE — 4 clones appear, criss-cross slashes.
  • 6 · Samurai — Special: Sword Slash. Final blow: IAIDO: MOON CUT — teleports behind, screen-wide slash, delayed hit.
  • 7 · Spiky — Special: Ki Blast. Final blow: MEGA NOVA BEAM — energy charge, then a giant beam.
  • 8 · Caped Hero — Special: Flying Punch. Final blow: SKY METEOR — flies off-screen, crashes down in fire.
  • 9 · Knight — Special: Shield Bash. Final blow: HOLY LANCE — giant golden sword falls from the sky.
  • 10 · King — Special: Royal Bolt. Final blow: ROYAL DECREE — lightning barrage and a shockwave.
  • 11+ · BOSS — Special: Dark Orb. Final blow: WORLD ENDER — charges a huge dark orb and throws it.

The final blow

The finisher is where the time went. The screen dims, letterbox bars slide in, the camera zooms onto the winner, and a cut-in banner names the move while the browser's own voice announces it. Then the hit freezes, and the loser flies off in slow motion:

app.js
// freeze on impact, then slow-mo while the loser(s) fly
async function hitStop(losers) {
  sfx.boom(true); sfx.cheer();
  flashAll = 0.9; shake = 18; hype = 3; vis.lines = true;
  timeScale = 0;
  await wait(450);
  timeScale = 0.25;
  const flying = Promise.all(losers.map(fly));
  await wait(1100);
  timeScale = 1;
  await flying;
}

That works because nothing in the arena runs on wall-clock time. The render loop keeps its own game clock, advanced by the real frame time multiplied by timeScale, and every tween, particle and effect is stepped from that clock. Setting one number to 0 freezes the whole fight; setting it to 0.25 puts all of it into slow motion at once.

Sound without a single audio file

Every punch, whoosh, boom and crowd cheer is synthesised in the browser with the Web Audio API — oscillators and filtered noise with pitch and volume ramps. The announcer is the browser's built-in speech synthesis. There are no audio files in the repo.

Getting it to play on phones was the fiddly part. iOS only unlocks audio inside a user gesture, only after a sound has actually started, and only allows speech later if it was started once in that same gesture. So the first tap anywhere does all three:

app.js
// iOS: play through the silent switch like a game, instead of being muted as "ambient" sound
if (navigator.audioSession) navigator.audioSession.type = 'playback';
ac = new (window.AudioContext || window.webkitAudioContext)();
// older iOS only unlocks after a sound actually starts inside the gesture
const s = ac.createBufferSource();
s.buffer = ac.createBuffer(1, 1, 22050);
s.connect(ac.destination);
s.start(0);
// iOS only allows speech later if it was started once inside a gesture
if ('speechSynthesis' in window) speechSynthesis.speak(new SpeechSynthesisUtterance(''));

Thumbs that reach

The calculator is held in two pixel hands, and pressing a button sends the nearer thumb to it. Each thumb is an SVG quadratic curve from its base to its tip, bulging slightly outward, and the tip tweens to the button in 90 ms — so on a far key the thumb visibly stretches, then springs back.

No build step

It is plain HTML, CSS and JavaScript: no framework, no bundler, no dependencies. The arena and fighters are drawn on a canvas, the hands and calculator are SVG and DOM, and it deploys as a static site. A short first-visit tutorial walks through a 1 - 1 fight, so the first thing a new player sees is the double K.O.

Something to say?

Thoughts, corrections, or just hello. It goes straight to Farish and is not published anywhere — no other visitor can see it.

Up to 2000 characters. No email address needed.