
Coin Duel
Five coins are tossed, and whichever face lands on more of them wins. It’s a neon 3D betting arena where the price changes every round but the maths behind it never does.
- Role
- Genre
- Return
- Exactly 97.000%
- Stack
- Rendering
- Fairness
- Block seed, sealed per duel
01The Game
A slot in the floor of a neon arena mints five coins and fires them up into a column of light. Each coin has a SUN on one side and a MOON on the other. You bet on the face you think will land on more of them.
A coin flip is the easiest bet in the world to price, and one of the hardest to make interesting to watch. My goal was to keep the bet exactly that simple, with one question, two buttons and no ties, while giving the game some shape, momentum and the chance of a comeback. The important rule was that none of that extra drama could ever affect the coins themselves.
One question
Two buttons and nothing else. I also built bets on the winning margin and a 5-0 lottery bet, and they were priced correctly, but I cut them because they made the game harder to understand.
Something to watch
Whichever face lands on the majority of coins settles the bet. The size of the win then decides how hard the hit lands in a duel that keeps a running score.
A price that moves
Each face gets a new price every round, taken from a ladder of prices that is never in the player’s favour.
02Screenshots
These screenshots were captured straight from the game. Tap any of them to see it bigger.





03Core Loop
- 1
Lobby
Two fighters stand on their platforms, each starting with 12 life and a hexagonal shield.
- 2
Bet
You get 20 seconds to back SUN or MOON at this round’s price.
- 3
Toss
The mint shoots five coins up into the column of light.
- 4
Settle
The faces are counted, and the camera keeps all five coins in view until the strike lands.
- 5
Strike
The score turns into damage. Shields wear down, and once a shield breaks, hits do double damage.
- 6
Review
This is a proper phase with no timer. It only ends when you press the button.
04Key Features
Five minted coins
With an odd number of coins, every bet is settled. There’s no tie and no refund.
Breakable shields
Shields wear down at a rate that usually breaks them around round seven, which is why the last third of a duel is where the big hits happen.
A procedural neon pit
The coin faces, hex shields, floor patterns, LED scoreboard and a holographic crowd are all generated in code. On top of that, there’s a bloom effect and a custom vignette.
Synthesised sound
Sound effects are generated in WebAudio. The one recorded music track only gets downloaded the first time music is actually needed.
DOM accessibility mirror
Every control and readout in the 3D world has a real HTML copy, so the game works with keyboards and screen readers.
One source of geometry
The sun and moon designs come from a single file of vector paths. That same file is used to draw the logo, the SVG buttons and the canvas texture on the 3D coins.
05The Maths
Let k be the number of coins that land SUN-up, so k follows Binomial(5, ½). The score is 2k − 5, which means it’s always ±1, ±3 or ±5 and never 0.
The bet
The chance of SUN winning is P(k ≥ 3) = 16/32 = ½, which follows from symmetry. So the fair price on either face is exactly 2.00×.
The fight
Damage equals the size of the score. A 3-2 graze happens 62.50% of the time, a 4-1 hit 31.25% and a 5-0 sweep 6.25%. The average damage is 1.875, so a duel starting at 12 life lasts about 12 rounds.
The price ladder
Every round, each face is priced from six matching steps between 1.89× and 1.99×, picked using a second slice of the sealed block value. The average comes out at exactly 1.94×, which makes the return to player exactly 97.000%.
Never above fair
Even the best price, 1.99×, is below the fair 2.00×, so no single round ever favours the player. Someone who only ever bet at the best price would get 99.5% back. A swing of ±0.05 is as wide as the price can go while still being honest.
The shield never touches the coin
Shields only change the damage after the score is known, and the bet is settled on the score alone. check() confirms this, because if a worn-down fighter were more likely to lose a toss, the real price would quietly change.
06Under the Hood
React ↔ three.js contract
React looks after the wallet, the bet, the duel and the current phase, and it hands the 3D world a read-only WorldView. Player input comes back as Intents. The engine/ folder never imports React and components/ never imports three.js. Three other games in the arcade use this same setup.
Crop-proof camera
Every camera shot defines an area that must never be cut off, and the camera works out how far back it needs to be for the current screen shape. That way, phones never lose a coin off the edge.
Self-hosted fonts
The 3D layer draws its text with fillText, so if a web font failed to load, the whole interface would fall back to Times New Roman. Hosting the fonts myself avoids that.
Duel-wide seal
A server route fetches the block value. It seeds mulberry32 for every toss in the duel and is revealed once one of the fighters goes down.
07Challenges & Solutions
08Results & Takeaways
- A finished 3D betting game with a generated arena, breakable shields, a holographic crowd, generated sound and a full HTML copy of the interface for accessibility.
- Every duel is locked to a block value from the first toss to the final knockout.
- Next up: publishing a hash of the seed in advance, settling bets on the server, and letting two players duel each other on the same sealed coins.
What I learned
- Cutting things is the hard part. Every square I removed was priced correctly, but each one made the game worse.
- Words are part of the design. Getting the wording right took longer than getting the maths right.
- There’s no good length for a fixed review timer, so the review screen doesn’t have one.