A browser-based interactive puzzle where a cube is represented as a 2D unfolded net. All moves follow real cube rotation rules — the twist is that tiles visually fly between non-adjacent regions of the flat layout, revealing the cube's topology.
Built with Three.js. No build step, no frameworks.
[U]
[L] [F] [R] [B]
[D]Each face is a 3×3 grid identified by its center tile color (the center never moves):
| Face | Color | Spatial | Numeric | Classic |
|---|---|---|---|---|
| U | White | W |
1 |
U |
| D | Yellow | X |
2 |
D |
| F | Green | S |
3 |
F |
| B | Blue | F |
4 |
B |
| L | Orange | A |
5 |
L |
| R | Red | D |
6 |
R |
Controls:
- Left click a face → rotate clockwise
- Right click a face → rotate counter-clockwise
- Keyboard → rotate face (key depends on selected layout)
- Shift + key or Shift + click → counter-clockwise
- Scramble button → 20 random moves
- Reset button → solved state
Choose a layout from the dropdown in the top-right corner:
-
Spatial (default) — keys mirror the cross-shaped net on your keyboard. Left hand, clustered:
W ← U A S D F ← L F R B X ← D -
Numeric — sequential:
1through6for U, D, F, B, L, R -
Classic — standard Singmaster notation:
UDFBLR(for cubers)
Hover over a face to see which tiles will be affected (highlighted) and the rotation direction (curved arrow on center tile).
Serve the files over HTTP (ES modules require it):
# Python
python3 -m http.server 8765
# Node
npx serve .Open http://localhost:8765 in a browser.
index.html Entry point, CSS, UI overlay, Three.js import map
js/
CubeModel.js Pure data model — state, moves, permutations
CubeView.js Three.js rendering — tiles, backgrounds, animations
Controller.js Input handling, animation orchestration, game logic
main.js Bootstrap — scene, camera, renderer, render loopMaintains state[face][row][col] where each cell stores the face name it currently belongs to. Moves are data-driven via MOVE_DEFS — each move defines a face to rotate and 4 edge strips forming a cyclic permutation. Strip ordering bakes in coordinate reversals (notably for the B face), so the cycle logic is a single generic loop.
Key methods:
applyMove(move)/applyMoveCCW(move)— mutate stategetMovePermutation(move)— returns[{from, to}, ...]describing where each tile moves (used for animation)isSolved()— checks all faces uniform
Renders the net as a Three.js scene with an orthographic camera. Each tile is a rounded-rectangle ShapeGeometry with its own MeshBasicMaterial. Face backgrounds provide visual grouping. The view also manages:
- Hover highlights — semi-transparent overlays on affected tiles
- Rotation indicators — canvas-rendered curved arrows (CW/CCW) shown at face center
- Flying tiles — temporary meshes that animate between positions during moves
Bridges input to model and view. Handles raycasting for mouse interaction, keyboard shortcuts, and a Promise-based animation queue. Moves are queued and processed sequentially — each move creates ghost tiles that lerp from source to destination positions over 250ms (80ms during scramble).
- Orthographic camera, fixed view — the puzzle is inherently 2D; perspective would distort it
- Data-driven moves — all 6 face rotations share the same application logic; only the strip definitions differ
- Ghost tile animation — tiles "fly" between net regions rather than fading, making the cube topology visible
- CCW via 3× CW — counter-clockwise uses
applyMovethree times, avoiding a separate code path for state mutation whilegetMovePermutationCCWreverses the cycle for correct animation direction