Skip to content

Repository files navigation

html5-2048

CI Pages License: MIT

Classic 2048 puzzle, written in vanilla JavaScript with no build step — just open index.html (or play the live demo) and you have a working game.

Play it now

Live demo → https://alexdreamien.github.io/html5-2048/

Animated GIF of gameplay goes here — replace docs/demo.gif with a screen recording once the game is deployed.

Demo placeholder

Features

  • 4 x 4 board, classic 2048 rules. Two starting tiles, one new tile after every move (value 2 with 90% probability, 4 with 10%).
  • Keyboard and touch input: arrow keys / WASD on desktop, swipe on mobile. Works equally well in either form factor.
  • Configurable win target — 1024 by default, 2048 selectable. After reaching it you can either start a new game or keep playing for a higher score.
  • Persistent best score stored in localStorage.
  • Smooth animations — tiles slide via CSS transitions, merges pop, spawns scale in.
  • Detects game over (no moves available) and game won (target reached).

Stack

  • HTML5, ES modules, vanilla CSS
  • No frameworks, no bundler, no build step for the game itself
  • Vitest — used only to test the pure game-logic module; the game ships without it

Architecture

index.html      Markup and entry point
css/style.css   Theme, layout, tile colours, animations
js/game.js      Pure game logic (no DOM): move, slideRowLeft, canMove,
                hasReachedTarget, spawnTile. Returns a `transitions` array
                that drives slide animations in the renderer.
js/render.js    Renderer class. Owns absolutely-positioned tile DOM
                nodes, animates moves via CSS transition on transform,
                merges via a "pop" keyframe, spawns via an "appear"
                keyframe.
js/input.js     Keyboard + touch -> direction callback (24-px swipe
                threshold, ignores multi-touch).
js/main.js      Wires the three layers together, persists state.
tests/          Vitest suite over game.js.

The split between pure logic and DOM is the key design choice: every non-trivial behaviour (slide-and-merge, no double-merging, win/lose detection, transition computation) is covered by unit tests because the logic doesn't touch the DOM. The renderer and input handler are thin adapters around it.

Run locally

The game has no dependencies, but ES modules need to be served over HTTP (file:// blocks them). Any static server works:

# Python
python -m http.server 8080

# Node (one-off)
npx http-server -c-1 -p 8080 .

Then open http://localhost:8080.

Tests

npm install
npm test

Vitest covers:

  • Single-row slide & merge (gap closing, no chain-merging, no double-merge within a turn)
  • Move correctness in all four directions; moved flag false when nothing changes; no mutation of input
  • Merge coordinate and transition reporting (used by the renderer)
  • Game over (full board, no equal neighbours) vs. still-playable
  • Win-target detection
  • Spawn probability split (2 / 4) and full-board no-op behaviour

CI runs the same suite on every push and pull request — see .github/workflows/ci.yml. On push to main, .github/workflows/pages.yml redeploys the live demo, so the URL above always reflects the latest commit.

Out of scope

By design: no online leaderboard or backend, no accounts, no sound, no undo, no field sizes other than 4 x 4. The scope deliberately stays at "classic 2048 you can open in a browser".

License

MIT.

About

Classic 2048 puzzle in vanilla JavaScript. No frameworks, no build step. Play live on GitHub Pages.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages