← Portfolio
Live in your browser

match3-engine

The board below is running the actual Java engine from a real Android game I built (pre-launch), compiled to JavaScript by TeaVM. Every rule — what counts as a match, what a special gem clears, how gravity refills — is decided by the same Java that runs on the device. This page only draws pixels and forwards your clicks.

Starting the engine…
// Play it

Click two adjacent gems to swap

The engine decides whether your swap is legal — if no match would form, it refuses, exactly as it does on-device. Match 3+ to clear; the cascade loop resolves until the board settles.

Score
0
Moves
0
Best chain
0
Has a move?


        
↔ ↕ striped✦ wrapped★ color bomb▨ blocker⛓ chain
What the engine is guaranteeing while you play: every board it generates has at least one legal move (it re-rolls until it does, so “Has a move?” should never say deadlocked on a fresh board), blockers never match and never clear, and a 5-run or an L/T intersection is detected as such — that's the intersection flag in the cascade log.
// The bug this port found

The browser caught what the JVM hid

Gem ids are identity keys for the renderer — two gems sharing one animate as a single gem. The engine used to mint them from System.nanoTime() plus a random number. On a JVM that's fine: the clock is high-resolution, and 128,000 gems produced zero collisions. Browsers clamp their clock to ~100µs to mitigate Spectre, so nanoTime barely advances — the same code produced 301 duplicate ids per 128,000 gems here.

Compiling to a second platform is what surfaced it. The fix is a monotonic counter, so uniqueness no longer depends on the clock underneath. Press the button — this mints gems through the live engine and checks every id:

Unique ids seen
0
Duplicates
0
Live boards
0