Engineering
Shipping 2048: Small Game, Real Polish
A short look at tile animations, drag controls, and offline play for the browser 2048 on this site.
2048 is a small game, but the first version on this site had a UX problem: the logic was correct, yet every arrow press felt random. Numbers changed in place with no clear sense of what slid or merged.
This post is a short note on how I shipped a version that actually feels good to play.
Keep the engine separate
The rules live in plain TypeScript — slide a row, merge equals, spawn a tile, check win/loss. React only handles rendering and input.
That split means the grid math is easy to reason about and test without mounting components. Other games on the site (Sudoku, Bagh Bakri) follow the same pattern: engine first, UI second.
Tiles need stable IDs
Rendering a 4×4 grid with fixed cell keys makes every update look like a teleport. The fix was a tile layer: each number gets an id that survives as it moves across the board. When two tiles merge, one id stays with the doubled value.
The UI animates position changes with Motion — slide on move, a small pop on merge, scale-in when a new tile spawns. That was the biggest quality jump.
Input and offline play
Keyboard arrows work as expected. On desktop and mobile I also added drag/swipe so you can pull the board in a direction and release.
The game is a PWA, so after one online visit it caches and runs offline. Stats (best score, best tile, games played) persist in the browser via a small per-game tracker.
Worth the effort?
For a weekend-sized puzzle, yes — if you care about feel. The algorithm is well known; the polish is in movement, input, and making progress stick.
Play 2048 here if you want to try it.