The Story Behind 3D 2048

Why a 3D version of 2048, how the design evolved through player feedback, and the technology choices that made it possible. A look at the development of a small indie web game from idea to global leaderboard.

โœ๏ธ By morati ๐Ÿ“… Published ๐Ÿ”„ Updated โฑ๏ธ ~12 min read

Why 3D?

The original 2048 has been ported, themed, and remixed countless times. After playing many variants โ€” Threes!, Doge 2048, Hex 2048, and the various extensions โ€” a recurring question came up: what if 2048 were genuinely three-dimensional? Not just a visual 3D effect on a flat board, but an actual cube with merges along three axes?

Most attempts at "3D 2048" online don't fully commit. They show isometric tiles on a flat grid, or they stack two layers vertically without really using the third dimension. The core insight that drove this project: if you commit to true 3D, the strategy fundamentally changes. The third axis introduces hidden tiles, six slide directions, and spatial planning that doesn't exist in 2D.

So the goal was: make a version of 2048 that uses three dimensions for real. Slide tiles in six directions. Let players rotate the cube to see all faces. Tune the difficulty so the larger board doesn't make the game trivial. Keep the comfortable 2048 rhythm but introduce a real spatial puzzle layer.

Design decisions

Why three modes (2ร—2ร—2, 3ร—3ร—3 Normal, Hard)?

The first prototype was just 3ร—3ร—3, but early playtesting revealed a problem: the 27-cell board feels enormous to new players, and they don't know whether they're playing well or just floundering. The board is so spacious that you can survive bad moves for a long time without seeing consequences.

Adding 2ร—2ร—2 mode (only 8 cells, target 256) solved that. It's brutal, fast, and teaches merge discipline. Players who can clear 2ร—2ร—2 understand the basic 2048 mechanic. They then transition to 3ร—3ร—3 with the right instincts.

Hard mode came from a different observation. Some Reddit players reported that 3ร—3ร—3 Normal was "too easy" once they got the hang of it โ€” they could reach 2048 but the game lost intensity. Hard mode addresses this by doubling the spawn rate and increasing the 4-rate to 40%. It's a different game, not just a slightly harder Normal.

Why the camera-relative controls?

The early version of the game used world-locked controls. When you pressed "left," tiles slid along the world X-axis regardless of how you'd rotated the camera. This was technically simpler but caused a real usability problem: after rotating the camera 90 degrees, "left" felt wrong because what looked like "left" on screen was actually a different world axis.

Multiple players reported this independently on Reddit. The fix was to make controls camera-relative: when you press "left," tiles slide to your screen-left, no matter how you've rotated the camera. Internally, the input is mapped to the camera's current orientation, snapped to the nearest cardinal direction.

This decision took the game from "confusing 3D controls" to "intuitive 3D controls" in one update. It's a good example of how small UX details can dramatically affect whether players enjoy a game.

Why the back-view mini-window?

In a 3D cube, some tiles are always behind others from the player's perspective. Without a way to see them, you'd have to rotate the camera every move to verify the board state.

The back-view window shows the cube from the opposite angle in real-time. It's small enough to not dominate the screen but large enough to read tile values. You can plan moves without rotating the main camera.

This was harder to implement than expected. The mini-window is a second rendering of the same scene with a flipped camera, sharing the same tile state. Performance was a concern at first โ€” would rendering the scene twice slow things down on mobile? โ€” but careful optimization keeps it smooth even on older phones.

Why a global leaderboard?

The earlier version only had local rankings. Each player's scores lived in their browser's local storage, separate from everyone else. This felt incomplete โ€” how do you know if your 4096 run is impressive or routine?

The global leaderboard uses Firebase Firestore to store the top scores for each mode. Players' scores are submitted on game-over or game-won events, and the leaderboard shows the top scores per mode (2ร—2ร—2, 3ร—3ร—3 Normal, 3ร—3ร—3 Hard).

Anti-cheat is light-touch: a plausibility check ensures the submitted score is consistent with the highest tile achieved. We assume players are playing in good faith. The goal isn't a competitive ranked system โ€” it's just a way to see how your performance compares globally.

Technical stack

3D 2048 is a fully client-side web application. The technology choices were made to balance modern developer experience with broad browser compatibility.

React + TypeScript + Vite

The UI layer is built with React 18 and TypeScript, bundled with Vite. This is the standard modern web stack. React handles state, components, and rendering. TypeScript catches type errors at compile time, before they become runtime bugs. Vite is a fast build tool that replaces older tools like Webpack.

Vite was chosen specifically for its dev server speed โ€” hot module replacement is near-instant, which makes iteration on visual details fast.

React Three Fiber + Three.js

The 3D rendering uses React Three Fiber, which is a React wrapper for Three.js. Instead of imperatively manipulating Three.js objects, you describe the 3D scene declaratively in JSX, the same way you'd describe a regular React UI.

This was a major productivity boost. The 3D 2048 scene โ€” the cube, the tiles, the lighting, the camera โ€” is described in roughly 100 lines of declarative React code. Animating tiles uses @react-spring/three, which provides smooth spring-based animation primitives that integrate cleanly with React state.

Zustand for state management

State management is handled by Zustand, a lightweight alternative to Redux. The entire game state โ€” current tiles, score, settings, locale, rankings โ€” lives in a single Zustand store. Components subscribe to specific slices and re-render only when their slice changes.

Zustand was chosen for its minimal API. The store is just a function. There's no boilerplate, no providers, no reducers. The whole state management approach is maybe 200 lines of code.

Firebase Firestore (lite) for global rankings

Originally we considered building a custom backend (Node.js + a database somewhere), but Firebase Firestore was a better fit. It's a NoSQL database with automatic security rules, scaled by Google's infrastructure, and integrates well with web apps.

We use the "lite" SDK of Firestore specifically. The full SDK supports real-time updates and offline persistence โ€” useful for chat apps and collaborative editors, but overkill for a leaderboard. Lite SDK uses REST APIs only and is roughly one-third the bundle size. The first load of the leaderboard went from 30 seconds (full SDK with WebChannel fallback timeout) to about 2 seconds after switching to lite.

Vite Plugin PWA for installable web app

Progressive Web App support means players can install the game to their home screen on iOS, Android, and desktop. Once installed, it works offline, has its own app icon, and runs in its own window.

vite-plugin-pwa handles the service worker generation, manifest, and install prompt detection. The icons are generated from the SVG favicon using sharp โ€” a single source of truth for branding.

Two build targets: Vercel and itch.io

The game is hosted on Vercel as the primary site, but also mirrored on itch.io for indie game discovery. These have different deployment constraints.

Vercel hosts the standard Vite build, with assets in separate files (JS bundle, CSS, images). It supports the PWA service worker and the full Firebase integration.

itch.io requires a single HTML file because their hosted-game sandbox doesn't reliably serve sub-folder assets (we hit 403 errors trying to load chunks from a sub-folder). For itch.io we use vite-plugin-singlefile, which inlines all the JS and CSS into one giant index.html. The build is larger (around 1.5 MB) but reliable in the itch.io sandbox.

Two Vite configs (vite.config.ts and vite.config.itchio.ts) handle the targets. A single git push deploys to both.

The development timeline

3D 2048 was built incrementally over several weeks, mostly evenings and weekends.

Week 1: prototype

The first prototype was a 3ร—3ร—3 cube with hardcoded tile rendering, basic slide logic, and keyboard controls. No camera, no animations, no UI. It was ugly but the mechanic worked. The first "I reached 2048" run took about an hour and felt incredibly satisfying despite the cardboard-box visuals.

Week 2: visuals and polish

Added smooth tile animations with @react-spring/three. Set up a proper camera with OrbitControls so players could rotate. Built the back-view window. Designed a color palette for tile values 2 through 4096. The game started feeling like a real game.

Week 3: modes and mobile

Added 2ร—2ร—2 mode for quick games. Added mobile touch controls (swipe + on-screen D-pad). Tested heavily on phones โ€” discovered that mobile WebGL has tighter memory limits than desktop and tuned the rendering accordingly. That tuning turned out to be involved enough to deserve its own write-up: optimizing React Three Fiber for mobile covers what actually cost frames on a phone and what fixed it.

Week 4: launch and feedback

Launched on Vercel and itch.io simultaneously. Posted to r/2048 and r/WebGames. Got feedback within hours: "controls feel weird after rotating," "no clear fail state," "2 and 2048 look the same color."

Each piece of feedback became a fix:

Week 5: scaling up

Added Hard mode based on Reddit feedback. Added global leaderboard with Firebase. Added PWA install support. Wrote content pages (you're reading one). Submitted to AdSense for monetization. Continued tweaking based on player suggestions.

What I learned

A few takeaways from building this game:

1. Player feedback is gold, but you have to filter it

Reddit feedback was incredibly useful โ€” most of the major design improvements came from there. But not every suggestion should be implemented. A useful filter: does this come up from multiple independent players? If two different people independently mention the same issue, fix it. If it's one person's preference, weigh it but don't rush.

2. Mobile is harder than desktop

Even with a well-designed touch interface, mobile has constraints desktop doesn't: smaller screen, less RAM, slower CPU, no precise pointer. Every feature has to be considered through the mobile lens or it'll feel broken on phones.

3. The tech stack matters less than the design

I spent some time agonizing over technology choices. In retrospect, almost any modern web stack would have worked. The design decisions (camera-relative controls, back-view window, mode progression) mattered more than the implementation details.

4. Polish takes longer than building

Building the core mechanic โ€” the cube, the slide logic, the merges โ€” took about a week. The remaining four weeks were polish: animations, color palette, mode tuning, UX details, accessibility, mobile support, performance optimization. This ratio is normal for small games, but it's still surprising every time.

What's next

The game is still under active development. Planned improvements include:

If you have feature requests or feedback, the best way to share them is through the contact form linked from the Privacy Policy on the main game page. Or just play and tell your friends โ€” every player counts when you're a one-developer project.

Related reading

โ–ถ Play 3D 2048