# Rubik's cube

The Rubik's cube as a game: grab a face and the layer turns with your hand, let go and it snaps, type moves at it, scramble it, take a hint, or watch it solve itself — every turn going through the pure cube solver.

> For the complete index, see [llms.txt](https://robocn.dev/llms.txt). A Markdown version of any page is available by appending `.md` to its URL or by sending an `Accept: text/markdown` header.

## Install

```bash
bunx --bun shadcn@latest add https://robocn.dev/r/rubiks-cube.json
```

Registry item: `rubiks-cube` · [`https://robocn.dev/r/rubiks-cube.json`](https://robocn.dev/r/rubiks-cube.json)

## Notes

- Solved, not illustrated: the state, every turn, the scramble, the drag geometry, the sticker colours and the solve all come from `cube-geometry` — exact integer arithmetic, tested on its own. Illustrated: the eased travel of a turn, the rounded sticker and the swell when it comes home.
- `solve()` is a real solve: `cube-geometry` searches out a layer-by-layer line, replays it to check it, and the rig plays it one turn at a time so you can watch it. `hint()` is the first move of that line. On anything but a 3×3 there is no method, so both return `null` and nothing is queued — the cube says so rather than turning at random.
- Drag follows the hand. The press decides the layer on the first few pixels of travel and then holds it; the layer winds with the pointer, half the cube's edge to the quarter turn; the release snaps to the quarter turn it is nearest — so letting go half way back snaps back rather than through. The turn only enters the state when the settle lands.
- A move carries where it came from, so a game can time a person without timing the idle loop or the solver. That is what the demo's stopwatch runs on.
- Drag turns the layer a hand expects, from any camera angle: the drag is projected into the plane of the face you grabbed and crossed with that face's normal. A face never turns about its own normal, so dragging *on* the right face turns a front or top layer — which is how a real cube behaves.
- Solved means every face one colour, which is what a person holding one means. A cube turned round in your hands, or one whose middle slices you have turned, is still solved — and the solver knows it, sitting the cube the right way up in its head before it works and writing the answer back out in the frame you are holding.
- Six face colours are the one place the four palette roles are not enough, so each face resolves prop → `--robot-cube-u` … `--robot-cube-l` → the standard white, yellow, green, blue, red, orange. The body, the highlight and the solved glow still come from the theme.
- It needs a WebGL canvas: mount it inside `robot-stage`, which brings the lights and the orbit controls — with `orbit="free"`, so the camera goes over the top and under the bottom and every side can be looked at. Keyboard and `data-cube-*` hooks are set on that canvas element.
- Reduced motion lands each turn immediately rather than animating it, parks the behaviour loop and drops the solved swell — drag, keys and the solver all still work.

## Usage

```tsx
import { RubiksCube, type RubiksCubeApi } from "@/components/ui/rubiks-cube"
import { RobotStage } from "@/components/ui/robot-stage"

export function Game() {
  const api = React.useRef<RubiksCubeApi | null>(null)
  const [solved, setSolved] = React.useState(true)

  return (
    <>
      <RobotStage floor="none" orbit="free" className="h-96" camera={[3.4, 2.8, 4.2]}>
        <RubiksCube
          interactive
          behavior="static"
          controls={(next) => (api.current = next)}
          onSolvedChange={setSolved}
          onMove={(move, state, source) => source === "user" && console.log(move)}
        />
      </RobotStage>
      <button onClick={() => api.current?.scramble()}>Scramble</button>
      <button onClick={() => api.current?.hint()}>Hint</button>
      <button onClick={() => api.current?.solve()}>Solve it</button>
      <p>{solved ? "Solved" : "Keep going"}</p>
    </>
  )
}
```

## Props

| name | type | default | description |
| --- | --- | --- | --- |
| `order` | `number` | `3` | Cubies to a side, 2 … 7. 3 is the cube everyone means. |
| `size` | `number` | `2.4` | The cube's edge in three.js world units. |
| `algorithm` | `string | CubeMove[]` | — | Controlled: the cube is exactly this algorithm applied to a solved one, and the behaviour loop stops. Appending a move animates it; anything else rebuilds the state. |
| `behavior` | `"cycle" | "scramble" | "solve" | "static"` | `"cycle"` | What it does with nobody driving it. `solve` is the live one — it scrambles itself, solves itself with the real method, and starts again; `cycle` repeats R U R' U', which comes home every six repeats; `scramble` walks a seeded shuffle. |
| `interactive` | `boolean` | `true` | Grab a face and the layer turns with the pointer, snapping to the nearest quarter turn when you let go. Click the canvas and type at it: U D L R F B, shift for anticlockwise, S to scramble, H for a hint, enter to solve it, backspace to undo, escape to reset. |
| `controls` | `(api: RubiksCubeApi) => void` | — | Handed a driver: `turn`, `scramble`, `solve`, `hint`, `undo`, `redo`, `reset`, `state`, `history`, `solved`. |
| `faces` | `Partial<Record<CubeFace, string>>` | — | Per-face colour overrides. Each face otherwise resolves prop → `--robot-cube-<face>` → the standard scheme. |
| `scrambleOnMount` | `boolean | number` | `false` | Start shuffled rather than solved; a number says how many turns. |
| `seed` | `number` | `1` | Fixes the scramble, so two cubes on a page can be told to agree. |
| `onMove` | `(move: CubeMove, state: CubeState, source: RubiksCubeSource) => void` | — | Every turn, once it has landed, and where it came from: `user`, `solver`, `scramble`, `loop`, `undo`, `redo`. A stopwatch that times a person reads that third argument. |
| `onSolved` | `() => void` | — | The moment it comes home. |
| `onSolvedChange` | `(solved: boolean) => void` | — | Both edges of solved — the one a stopwatch starts and stops on. |
| `onHistoryChange` | `(moves: CubeMove[]) => void` | — | The turns made so far, as they are made and unmade. |
| `speed` | `number` | `0.9` | Turns per second for the loop, and how fast a turn travels. |
| `animate` | `boolean` | `true` | Off parks the machine at phase and stops rendering. A reduced-motion preference does the same. |
| `paused` | `boolean` | `false` | Freeze where it stands. |
| `phase` | `number` | `0` | Seconds of offset, so a row of machines breaks step. |
| `variant` | `"solid" | "outline" | "blueprint" | "wire"` | `"solid"` | How the machine is painted. Geometry never changes between variants. |
| `color` | `string` | `var(--robot-shell)` | Body panels — the colour the machine reads as. |
| `accent` | `string` | `var(--robot-accent)` | Status colour: tip light, live tool, readouts. |
| `metal` | `string` | `var(--robot-metal)` | Bare machined parts: collars, bolts, tool bodies. |
| `dark` | `string` | `var(--robot-dark)` | Cast joints, base, shadow side. |
| `palette` | `Partial<RobotPalette>` | — | Override any subset of roles at once, including glow and grid. |

## Source

- `src/components/ui/rubiks-cube.tsx`
