A fully-featured clone of the classic 2048 puzzle game, built with React, TypeScript, and Vite.
You can try the live demo here: 👉 https://2048-pro.vercel.app
- Configurable rules — adjust board size, win tile, initial tile count, and spawn probabilities before every game via the settings screen
- Four-directional moves — keyboard arrow keys or on-screen buttons
- Tile spawning — a
2(90%) or4(10%) spawns after every valid move - Win / lose detection — overlay shown when you reach 2048 or run out of moves
- AI suggestion — offline Expectimax algorithm (depth 4) recommends the best next move
- Auto-play — let the AI play the game continuously
- Score tracking — current score and all-time best persisted in
localStorage
| Layer | Choice |
|---|---|
| UI | React 19 |
| Language | TypeScript 5 |
| Bundler | Vite 5 |
| Styles | SCSS Modules |
| Testing | Vitest |
- Node.js 18+
- npm 9+
npm installnpm run devOpen http://localhost:5173 in your browser.
npm run buildOutput is written to dist/.
npm run preview# Watch mode
npm test
# Single run (CI)
npm run test:runTests live alongside their source files in __tests__/ directories.
src/
├── components/
│ ├── Board/ # Grid rendering
│ ├── ConfigForm/ # Game setup screen
│ ├── Controls/ # Move buttons, suggest & auto-play
│ ├── GameHeader/ # Score, best, new-game, settings
│ ├── GameOverlay/ # Win / lose overlay
│ ├── GameScreen/ # Main game layout
│ └── Tile/ # Individual tile with colour mapping
├── game/
│ ├── ai/ # AI strategy and its config
│ ├── engine/ # Board operations and core game primitives
│ └── rules/ # Game rules, defaults, and spawn behaviour
├── utils/
│ ├── loadJsonConfig.ts # Shared JSON config loading helper
│ ├── random.ts # Shared random helper functions
│ └── storage.ts # localStorage best-score persistence
└── hooks/
├── useGame.ts # Central game state
├── useAutoPlay.ts # AI auto-play loop
└── useKeyboard.ts # Arrow-key bindings
The AI uses an Expectimax search with the following design:
- Algorithm — Expectimax, alternating between player nodes (max) and chance nodes (expected value over tile spawns)
- Search depth — 4 half-moves; gives a good balance between move quality and response time
- Chance node capping — when there are many empty cells the branching factor is capped at 6 randomly sampled positions to keep the AI responsive in the early game
- Heuristics (scored and summed):
- Empty cells — more free space means more flexibility
- Highest tile — rewards progress toward the win tile
- Monotonicity — rewards boards where tile values decrease toward a corner
- Smoothness — penalises large differences between adjacent tiles
All AI parameters are defined in ai.config.json and can be tuned without touching code:
No network calls or API keys are required; everything runs locally in the browser.
The defaults are defined in game.config.json. Edit that file to change the out-of-the-box values before building.
{
"boardSize": 4, // N×N grid size (e.g. 5 → 5×5)
"initialTileValues": [2], // tile values used at game start (e.g. [2, 4])
"initialTileCountMin": 2, // fewest tiles placed at start
"initialTileCountMax": 8, // most tiles placed at start
"spawnDistribution": [ // weighted spawn chances (auto-normalised)
{ "value": 2, "probability": 0.9 },
{ "value": 4, "probability": 0.1 }
],
"winTile": 2048 // tile value that wins the game (e.g. 4096)
}
{ "maxDepth": 4, // how many half-moves ahead to search (higher = smarter but slower) "maxEmptySample": 6, // max empty cells sampled per chance node (limits branching) "weights": { "emptyCount": 270, // reward for each empty cell (flexibility) "maxTile": 100, // reward for the highest tile (progress toward win) "monotonicity": 70, // reward for tiles decreasing toward a corner (ordered layout) "smoothness": 50 // penalty for large differences between adjacent tiles } }