A turning cube for the browser, with a solve you can follow.
2×2 to 7×7, drawn in plain CSS 3D. Turn it by drag, touch, wheel, keys or cubers' notation; scramble it from a seed; and watch the layer-by-layer method solve it one named step at a time.
Turn a cube → · API reference · Build a cube
A scrambled 3×3, three steps into its solve, on a desk. |
The cube on a phone, in Japanese, in the device's light or dark. |
A 3D cube simulator, a cube model, a scramble generator and a beginner's solver for the twisty puzzle everybody knows, from the 2×2 to the 7×7.
- What is different. The cube is drawn in CSS 3D, with no canvas and no WebGL, so it is sharp at any size and a screen reader is told what it is. Under it is a plain model: a cube is a string, a turn is a pure function. And the solve it shows is the one a person learns, step by named step.
- What it costs a project. Nothing: no dependencies, and one import.
npm install @johnmorrisdotca/kyuubu # or pnpm add, or yarn addimport { cubeSolved, movesNotation, randomScramble, seededRandom, solveSteps, solvedCube, turnAll } from "@johnmorrisdotca/kyuubu";
const scramble = randomScramble(3, 25, seededRandom("club night"));
movesNotation(scramble, 3); // "B L' S' U' F' R2 B R U2 B' R2 …": the same for everyone with the seed
const cube = turnAll(solvedCube(3), 3, scramble); // a cube is a string of 54 letters
const steps = solveSteps(cube, 3)!; // 17 steps: hold, white cross, white corners, …
cubeSolved(turnAll(cube, 3, steps.flatMap((step) => step.moves)), 3); // trueOn a page, one line draws a cube a person can turn:
import { CubeView } from "@johnmorrisdotca/kyuubu";
new CubeView(document.getElementById("cube"), { size: 3, keyboard: "page" });And from a terminal, on Linux, macOS or Windows:
npx @johnmorrisdotca/kyuubu --seed table # D' R2 U' F2 L' S L F' R E' B' R2 D S' R' E' B2 L' B' D2 F' E S U' M'Or with nothing to install, turn a cube in the demo.
- Puzzle and game sites. A cube that works by mouse, finger and keyboard, at any size from 2×2 to 7×7, with every turn handed to your code and a state small enough to store in a column.
- Speedcubing tools. Scrambles from a seed (
--seed "club night"), moves read and written in notation (R U R' U'), and a check that a solve solves its scramble, in the browser, on a server or in a terminal. - Teaching. The beginner's layer-by-layer method for any 2×2 or 3×3, as steps with names, reasons and the algorithm each one uses, in English and Japanese.
- Anybody who wants one on a page. Give an element a size, and there is a cube in it.
"Rubik's Cube" is a trademark of its owner. Kyuubu is not affiliated with or endorsed by them; it is a turning cube of its own, worked out from geometry.
- Any size, one model. The 2×2 up to the 7×7 run on the same few lines of geometry. There are no hand-written tables of face cycles.
- Real 3D in plain CSS. Every sticker is an element given its whole
place on the screen in one
matrix3d, worked out by the cube itself, with nothing nested in 3D, so no browser can draw it flat. While a layer turns, the inside of the cube shows as plastic, never as a hole. - Every way of turning it. Drag a sticker; roll the wheel over one; use a finger; press the keys cubers write with; or hand it notation from code.
- The layer follows your hand. A dragged layer turns with the pointer, forwards and back, and is a move only once it is let go past a point of no return. Start a turn, think better of it, and take it back.
- A plain model under the view. A cube is a string of
6 × n × nletters and a turn is a pure function, so a cube is easy to store, send, snapshot in a test, or check again on a server. - Notation in and out. It reads and writes
R U R' U',2R2,M',xand the rest. - Scrambles from a seed. The same seed gives the same scramble in every browser and on every server, so a club can race one scramble.
- A solve you can follow. The beginner's layer-by-layer method for any 2×2 or 3×3: each step with its name, what it is for, its turns and the algorithms it uses.
- Shown on the cube. The next move of a solve, or of any moves, marked on the cube itself: the layer lit, an arrow the way to drag it, the move in notation and in plain words. It waits for your hand, moves on when you make the move, and says so, with a way back, when you make another.
- Replay a record. A scramble and its solve played on the cube at the pace it was made, with play, pause, step, speed and repeat; famous record solves included, each checked to end solved; and any solve pasted in, as it is written or as a link.
- Embed it anywhere. One
<kyuubu-cube>tag, or an iframe for a site that allows no scripts. - Export and import. A solve, scramble and moves together, as JSON that reads back in, as a few lines of plain text, or as CSV for a spreadsheet.
- Themes. Every colour is an option and a CSS custom property, with three looks included and a call to change them on a cube already drawn.
- A command line.
kyuubu --seed tablein a terminal on Linux, macOS or Windows: scrambles, turns, a check and a solve, as text or JSON. - Cuboids. The Floppy 1×3×3, the Tower 2×2×3, the Domino 2×3×3 and any
a × b × cfrom 1 to 7: turned by drag with the rule that a layer turns a quarter only where its slice is square, written in the same notation, and scrambled to a state chosen fairly. Its own section. - English and Japanese, for everything the package says to a person.
- Accessible. The cube is a labelled
application, takes the keyboard, and every turn can be made without a pointer.
Each picture is a page of the demo, taken with pnpm screenshots:readme, in light and dark. Math.random is a seeded generator in the page, so every scramble is the same and the same pictures come again.
The cube: drawn in plain CSS 3D, no canvas and no WebGL. |
Every size, from the 2×2 to the 7×7, large, medium or small. |
The builder: every option, the cube and the code that makes it. |
Famous solves: replay a record, or paste a solve of your own. |
Cuboids: a Domino, and shapes from 1×1×2 to 7×7×7. |
A pillar: any box, scrambled and turned by hand. |
npm install @johnmorrisdotca/kyuubupnpm add @johnmorrisdotca/kyuubuyarn add @johnmorrisdotca/kyuubuA page with no bundler loads the cube as a tag from a CDN, naming the major version so that a release that changes what you use is one you choose:
<script type="module" src="https://cdn.jsdelivr.net/npm/@johnmorrisdotca/kyuubu@1/dist/element-define.js"></script>Kyuubu is three things, each usable without the others: an API of plain functions (turn, read notation, scramble, solve, save), a view you put in any element, and a React component that wraps the view.
The view fills the element it is given, so that element needs a size and a
position (relative or absolute).
import { cubeSolved, movesNotation, parseMoves, solvedCube, turnAll, undoAll } from "@johnmorrisdotca/kyuubu";
const moves = parseMoves("R U R' U'", 3); // the turns, or null where any of it is not one
if (moves !== null) {
const cube = turnAll(solvedCube(3), 3, moves); // "UULUUFUUFRRUBRRURRFFDFFUFFFDDRDDDDDDBLLLLLLLLBRRBBBBBB"
cubeSolved(cube, 3); // false
movesNotation(undoAll(moves), 3); // "U R U' R'": the turns that take it back
cubeSolved(turnAll(cube, 3, undoAll(moves)), 3); // true
}It has no DOM in it, so it runs the same on a server: scramble there, send the scramble, and check the moves a player sends back.
<div id="cube" style="width: 300px; height: 300px; position: relative"></div>
<p id="turned"></p>
<script type="module">
import { CubeView, moveNotation } from "./node_modules/@johnmorrisdotca/kyuubu/dist/index.js";
new CubeView(document.getElementById("cube"), {
size: 3,
keyboard: "page",
onTurn: (move) => (document.getElementById("turned").textContent = moveNotation(move, 3)),
});
</script>The files are ES modules and need no build. With a bundler, import from
@johnmorrisdotca/kyuubu as everywhere else on this page.
import { useState } from "react";
import { createRoot } from "react-dom/client";
import { moveNotation } from "@johnmorrisdotca/kyuubu";
import { Kyuubu } from "@johnmorrisdotca/kyuubu/react";
function App() {
const [turned, setTurned] = useState("");
return (
<>
<Kyuubu size={3} keyboard="page" style={{ maxWidth: 300 }} onTurn={(move) => setTurned(moveNotation(move, 3))} />
<p id="turned">{turned}</p>
</>
);
}
createRoot(document.getElementById("app")).render(<App />);The component takes the view's options as props, plus className, style
and any data-* for its <div>. With no className the box is a square as
wide as its container. The cube is made in the browser after the first
render, so server rendering draws an empty box and nothing needs a provider.
In Next.js, use it from a client component ("use client").
<script setup>
import { onBeforeUnmount, onMounted, ref } from "vue";
import { CubeView, moveNotation } from "@johnmorrisdotca/kyuubu";
const box = ref(null);
const turned = ref("");
let cube;
onMounted(() => {
cube = new CubeView(box.value, { size: 3, keyboard: "page", onTurn: (move) => (turned.value = moveNotation(move, 3)) });
});
onBeforeUnmount(() => cube?.destroy());
</script>
<template>
<div ref="box" style="width: 300px; height: 300px; position: relative"></div>
<p id="turned">{{ turned }}</p>
</template>The same one call in the framework's mount hook, and destroy() on the way
out:
<script>
import { onMount } from "svelte";
import { CubeView, moveNotation } from "@johnmorrisdotca/kyuubu";
let box;
let turned = $state("");
onMount(() => {
const cube = new CubeView(box, { size: 3, keyboard: "page", onTurn: (move) => (turned = moveNotation(move, 3)) });
return () => cube.destroy();
});
</script>
<div bind:this={box} style="width: 300px; height: 300px; position: relative"></div>
<p id="turned">{turned}</p>// Angular: a standalone component
@Component({
selector: "check-root",
template: `<div #box style="width: 300px; height: 300px; position: relative"></div><p id="turned">{{ turned() }}</p>`,
})
class App implements OnDestroy {
private box = viewChild.required<ElementRef<HTMLElement>>("box");
private cube?: CubeView;
turned = signal("");
constructor() {
afterNextRender(() => {
this.cube = new CubeView(this.box().nativeElement, { size: 3, keyboard: "page", onTurn: (move) => this.turned.set(moveNotation(move, 3)) });
});
}
ngOnDestroy() {
this.cube?.destroy();
}
}Each of the five is built from the packed tarball, opened in Chromium and
WebKit, and turned with a key by scripts/check-frameworks.mjs before a
release names it. The examples above are the ones it builds.
The builder is a page with every choice the package offers (size, scale, theme, a colour for each face, turn speed, keys, the pace of a cube that keeps turning, the replay's settings, the guide, the language), a live cube that changes as each is chosen, and the exact code for that cube as a custom element, an ES module, an iframe, React, Vue, Svelte and Angular, each with a Copy button. The choices are in the address, so a link shares the cube. It writes nothing for a choice that is what the package does when it is left out.
The page has no row of its own for any option. It is drawn from CUBE_OPTIONS,
the package's own list of its choices: each has a kebab-case id, a group, a
kind (number, boolean, choice, colour, text or moves), its default,
its choices or range, what each way of making a cube calls it (names.view for
CubeView, names.player for mountPlayer, names.cube for the attributes of
<kyuubu-cube> and the embed's address, names.turning for those of
<kyuubu-scramble>), and one plain line in English and Japanese. CUBE_OPTION_GROUPS
names the groups, and CUBE_OPTIONS_LEFT_OUT says why each option that is
not a choice (a function the page gives, a list only a program has) is not in it.
A test reads the options of the view, the player and both elements out of the
source and fails until each is a row or is left out with its reason, so a new
option cannot be added to the package and missed by the builder (types
CubeOption, OptionKind, OptionGroup, CubeMaker).
The code is written by demo/builder-code.js, pure functions from that list and
the choices made; the page is demo/builder.html and demo/builder.js. The three
files do not name an option, so they can be copied to another package with its own
list.
- Typed results. TypeScript types for everything, with a doc comment on every export.
- A random source you can replace.
randomScrambletakes any function that returns a number from 0 up to 1.seededRandom("any text")is one that gives the same numbers everywhere. - No dependencies, ES modules that also load under
require(Node 22 and later), andsideEffects: false, so a bundler drops what you do not import. - Sizes. The model, notation and scrambles alone are about 4 kB minified (2 kB gzipped) once a bundler has shaken the rest out. With the solver it is about 10 kB (4 kB gzipped). The view is about 17 kB (6 kB gzipped), and everything together 43 kB (16 kB gzipped).
- Where it runs. Every current browser with CSS 3D transforms and Pointer Events. The model, the solver and the command line run in Node 22 and later.
The cookbook, with the output of each example, is under Examples.
Every TypeScript and JavaScript block that can run is type-checked against the built package and run by pnpm test:readme, so the output after // → is what the code prints. The model has no DOM in it, so most of these run under Node.
Save this as a file and open it: one script and one element, and a cube a person can turn by drag, touch, wheel or keys:
<!doctype html>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>A cube</title>
<div id="cube" style="width: 300px; height: 300px; position: relative"></div>
<script type="module">
import { CubeView } from "https://cdn.jsdelivr.net/npm/@johnmorrisdotca/kyuubu@1/dist/index.js";
new CubeView(document.getElementById("cube"), { size: 3, keyboard: "page" });
</script>The same words give the same scramble for everyone, on every machine, so a club can share one by sharing a phrase. A cube is a string of 54 letters, and a turn is a pure function:
import { movesNotation, randomScramble, seededRandom, solvedCube, turnAll } from "@johnmorrisdotca/kyuubu";
const scramble = randomScramble(3, 25, seededRandom("club night"));
console.log(movesNotation(scramble, 3).split(" ").slice(0, 6).join(" ")); // → B L' S' U' F' R2
const cube = turnAll(solvedCube(3), 3, scramble);
console.log(cube.length); // → 54parseMoves reads cubers' notation, and gives back null where any of it is not a turn. A turn undone is the turn the other way, in the opposite order:
import { cubeSolved, movesNotation, parseMoves, solvedCube, turnAll, undoAll } from "@johnmorrisdotca/kyuubu";
const moves = parseMoves("R U R' U'", 3)!;
console.log(movesNotation(undoAll(moves), 3)); // → U R U' R'
console.log(cubeSolved(turnAll(solvedCube(3), 3, moves), 3)); // → false
console.log(parseMoves("R Q", 3)); // → nullsolveSteps is the beginner's layer-by-layer method as steps with names and reasons, in English and Japanese, and the moves of every step solve the cube:
import { cubeSolved, randomScramble, seededRandom, solveSteps, solvedCube, stageName, turnAll } from "@johnmorrisdotca/kyuubu";
const cube = turnAll(solvedCube(3), 3, randomScramble(3, 25, seededRandom("club night")));
const steps = solveSteps(cube, 3)!;
console.log(steps.length, stageName(steps[1].stage), stageName(steps[1].stage, "ja")); // → 17 White cross 白のクロス
console.log(cubeSolved(turnAll(cube, 3, steps.flatMap((step) => step.moves)), 3)); // → trueThe model has no DOM, so a server can scramble, send the scramble, and check the moves a player sends back. summarize says whether they solved it and how many turns it took:
import { parseMoves, summarize, toCSV, toText } from "@johnmorrisdotca/kyuubu";
const solve = { size: 3, scramble: parseMoves("R U2 F'", 3)!, moves: parseMoves("F U2 R'", 3)!, ms: 12340, seed: "club night" };
console.log(summarize(solve).solved, summarize(solve).count); // → true 3
console.log(toCSV([solve]).split("\n")[1]); // → ,3,R U2 F',F U2 R',3,true,12.34,club nightA solve is its size, the scramble and the turns made after it. It is written out three ways, each a pure function that returns a string, and read back without trust:
import { fromText, parseMoves, toText } from "@johnmorrisdotca/kyuubu";
const solve = { size: 3, scramble: parseMoves("R U2 F'", 3)!, moves: parseMoves("F U2 R'", 3)!, ms: 12340, seed: "club night" };
const text = toText(solve);
console.log(text.split("\n").slice(0, 3)); // → [ '3x3', "scramble: R U2 F'", "solve: F U2 R'" ]
console.log(fromText(text)?.size, fromText("not a solve")); // → 3 null<kyuubu-cube> is the cube as a tag, for a page with a framework or none: a solve, shown on a cube at the pace it was made, with controls (see Use it in your project for React, Vue, Svelte and Angular):
<script type="module" src="https://cdn.jsdelivr.net/npm/@johnmorrisdotca/kyuubu@1/dist/element-define.js"></script>
<kyuubu-cube size="4" scramble="R U R' U'" moves="U R U' R'" controls></kyuubu-cube>The same tag replays a solve with its scramble, a scrubber, speeds and a list of the moves as buttons, starting at once and beginning again at the end (see Replay a solve for every attribute):
<script type="module" src="https://cdn.jsdelivr.net/npm/@johnmorrisdotca/kyuubu@1/dist/element-define.js"></script>
<kyuubu-cube scramble="R U R' U'" moves="U R U' R'" time="2.5" controls autoplay loop></kyuubu-cube>Boxes from 1×1×2 to 7×7×7 are turned the same way, with their own entry points; one tag plays a solve on any of them:
<script type="module" src="https://cdn.jsdelivr.net/npm/@johnmorrisdotca/kyuubu@1/dist/cuboid/element-define.js"></script>
<kyuubu-cuboid dims="3x3x1" scramble="U2 R2 M2" moves="M2 R2 U2" controls></kyuubu-cuboid>npx @johnmorrisdotca/kyuubu --seed tableD' R2 U' F2 L' S L F' R E' B' R2 D S' R' E' B2 L' B' D2 F' E S U' M'
The scramble is the same for the same seed on every machine. --solve, --verify and --json do the rest of the job from a terminal: see The command line.
Every colour and the shape of a sticker is a CSS custom property, an option, or a later setTheme (the table is under Theming):
.cube { --kyuubu-up: #fff7d6; --kyuubu-front: #1b9e77; }Kyuubu reads and writes the notation cubers use.
| Written | Means |
|---|---|
R L U D F B |
A quarter turn of that face, clockwise as seen from that face |
R' |
The same face, counter-clockwise |
R2 |
A half turn |
2R, 3U' |
The 2nd or 3rd layer in from that face alone (big cubes), up to 9R |
M E S |
The middle layer of an odd cube, turning like L, D and F |
x y z |
The whole cube, turning like R, U and F |
parseMoves(text, n) returns null for anything the cube of that size
cannot turn, so input can be checked before it is used: M on a 2×2, 4R
on a 3×3, or a letter that is not a turn.
parseSolve(text, n) reads a solve the way competitors and the people who
reconstruct their solves write one, which is more than a key makes:
| Written | Means |
|---|---|
Rw, Rw', Rw2 |
A wide turn: the face and the layer behind it, as one step (WCA regulations, article 12a) |
3Rw |
Three layers deep, on a bigger cube |
r l u d f b |
The same wide turns, as reconstructions write them |
[r] [u] [f], [l] [d] [b] |
The whole cube, turned the way that face turns |
R2', R3 |
R2, and R' |
// cross |
A comment, skipped to the end of its line |
(R U R' U'), (U R' U' R)2 |
Brackets are skipped; a number after one repeats what is inside |
RUR'U' |
Moves run together with no spaces, as older reconstructions have them |
It gives back the steps it read, each with its text, its layers and whether
it counts as a move, or the first thing it could not read with its line and
place. readSolveLink(text) reads the scramble and the solve out of a link to
alg.cubing.net, so a person can paste the address.
A person turns the cube with the keys (a letter turns its face, with Shift the other way), by dragging a sticker so that its layer turns with it, by the wheel, or by touch; a layer let go before its point of no return springs back, and a drag on the seam between two layers turns both.
The options, the examples and the tables are in docs/CONTROLS.md.
A state is 6 × n × n letters, one a sticker. The faces come in the
order U R F D L B, and each face is read in rows as it is seen from
outside. A letter names the face its sticker belongs to when the cube is
solved, so a solved 2×2 is UUUURRRRFFFFDDDDLLLLBBBB.
A move is { axis, layer, turns }:
axis:0(x, to the right),1(y, up) or2(z, towards you).layer: counted from the axis's negative side,0ton − 1, or"all"for the whole cube.turns:1,2or3quarter turns by the right-hand rule.
parseMove("R", 3); // { axis: 0, layer: 2, turns: 3 }
parseMove("2R'", 4); // { axis: 0, layer: 2, turns: 1 }
parseMove("x", 3); // { axis: 0, layer: "all", turns: 3 }
encodeCubeMoves(parseMoves("R U2 F'", 3)!); // "x23y22z21": three characters a move, for a columnEvery function returns a new state and leaves the one it was given alone.
cubeSolved asks only that every face be one colour, so a solved cube turned
whole in the hand is still solved.
solveSteps is the beginner's layer-by-layer method for any 2×2 or 3×3 as steps with names, reasons and the algorithm each one uses, in English and Japanese; the cube's guide tells a person what to turn next and shows it.
The options, the examples and the tables are in docs/REPLAY.md.
A move written in notation can be shown on the cube itself: an arrow on the face to turn, in the direction to turn it, in sentences as well as letters, so that a person follows a solve by hand.
The options, the examples and the tables are in docs/REPLAY.md.
A scramble and the moves that solved it, shown on a cube at the pace they were made: what a record looked like, a move at a time. Seen move by move in a list of buttons, embedded on any site as one tag or an iframe, with the famous record solves one click away.
The options, the examples and the tables are in docs/REPLAY.md.
A cube for a list or a row of cards is drawn at a named scale (small, medium, large) and fills the width it is given.
The options, the examples and the tables are in docs/DISPLAY.md.
A cube that turns by itself, slowly, for a hero or a loading screen: it stays still for anybody who asks for less motion.
The options, the examples and the tables are in docs/DISPLAY.md.
Box-shaped turning puzzles from 1×1×2 to 7×7×7, in their own entry points (@johnmorrisdotca/kyuubu/cuboid and its /draw, /play, /element), drawn like the cube, turned the same way, with one tag that plays a solve on any of them.
The options, the examples and the tables are in docs/CUBOIDS-USE.md.
randomScramble(3, 25); // 25 turns, from Math.random
randomScramble(3, 25, seededRandom("club night")); // the same 25 turns everywhere
randomScramble(3, 25, seededRandom("club night"), { faces: true }); // outer faces only: no M, E or S
FULL_SCRAMBLE_LENGTHS[4]; // 40: the usual length for a 4×4A scramble never turns the same axis twice running, so no turn undoes or
joins the one before it, and it never leaves the cube solved. Any layer may
turn, a 3×3's middle ones among them, unless faces is set.
seededRandom(seed) gives the same numbers for the same text on every
device, and a scramble made from it is pinned by a test: the seed
"club night" will always begin B L' S' U'. It is not for secrets.
Installing the package puts kyuubu on the path. It needs Node 22 or later
and nothing else, and runs the same on Linux, macOS and Windows.
npm install -g @johnmorrisdotca/kyuubu # or use npx, as above$ kyuubu --seed table
D' R2 U' F2 L' S L F' R E' B' R2 D S' R' E' B2 L' B' D2 F' E S U' M'
$ kyuubu -n 2 --seed table
D' R2 U' F2 L' B' L F' R D' B'
$ kyuubu --apply "R U R' U'"
U U L
U U F
U U F
B L L F F D R R U B R R
L L L F F U B R R B B B
L L L F F F U R R B B B
D D R
D D D
D D D
state: UULUUFUUFRRUBRRURRFFDFFUFFFDDRDDDDDDBLLLLLLLLBRRBBBBBB
Not solved
$ kyuubu --verify --from "R U" "U' R'"
Solved. Moves: 2
$ kyuubu -n 2 --solve "R U2 F'"
Hold it x'
White layer F'
White layer F U F'
White layer F R2 F'
Steps: 4. Moves: 7
| Option | What it does |
|---|---|
--scramble |
Print a scramble. This is what happens when nothing else is asked |
--apply |
Make the turns and show the cube: unfolded, then as its state |
--verify |
Say whether the turns solve the cube it starts from. Exit code 0 if they do, 1 if not |
--solve |
Show the layer-by-layer solve of the cube after the turns, step by step (2×2 and 3×3) |
-n, --size <n> |
The cube's side, 2 to 7, as 4 or 4x4. 3 when left out |
-l, --length <n> |
How many turns a scramble has, 1 to 1000. The usual length for the size when left out |
-c, --count <n> |
How many scrambles, 1 to 100 |
-s, --seed <seed> |
The same seed gives the same scrambles. One seed serves the whole command, in order |
--faces |
Scramble with the outer faces only |
-f, --from <turns> |
The scramble the cube starts from |
--state <state> |
The cube to start from, as its 6 × n × n letters |
--stdin |
Read the turns from standard input. Windows line endings are fine |
-j, --json |
Print JSON, with a format number |
--lang <en|ja> |
English or Japanese. Otherwise LC_ALL, LC_MESSAGES or LANG decides, and where none is set (Windows), the system's language |
--no-color |
No colour. NO_COLOR is honoured too, and output that is piped is never coloured |
-h, --help, -v, --version |
| Exit code | Means |
|---|---|
| 0 | Done; under --verify, solved |
| 1 | Something asked for could not be done: a turn the cube cannot make, a state that is not a cube, a cube with no solve; under --verify, not solved |
| 2 | The command itself was wrong: an option it does not know, or one without its value |
Your shell reads ' before Kyuubu does, so quote the turns:
kyuubu --apply "R U R' U'". Turns may be one argument or several.
From another program or another language, the JSON is the way in: run
kyuubu --json, read standard output, and check the exit code.
printf "U'\nR'\n" | kyuubu --verify --from "R U" --stdin --jsonIn JavaScript there is no need for a process: runCli(args, surroundings) is
the whole command line as a pure function, returning { code, out, err }.
A solve is its size, the scramble and the turns made after it. It is written out three ways, each a pure function that returns a string; what is done with it is yours.
const solve = { size: 3, scramble: parseMoves("R U2 F'", 3)!, moves: parseMoves("F U2 R'", 3)!, ms: 12340, seed: "club night" };
toJSON(solve); // { "format": 1, "generator": "kyuubu 1.12.0", "solves": [ … ] }
fromJSON(text); // the solves back again, or null if it is not an export
toText(solve); // a few lines for a chat or a note
fromText(text); // the solve back again, or null
toCSV([solve]); // for a spreadsheet
summarize(solve); // { scramble: "R U2 F'", moves: "F U2 R'", solved: true, count: 3, start, state, size }3x3
scramble: R U2 F'
solve: F U2 R'
time: 12.34
seed: club night
time,size,scramble,moves,count,solved,seconds,seed
,3,R U2 F',F U2 R',3,true,12.34,club night
The shape of the JSON, which is what to keep if you keep solves:
{
"format": 1,
"generator": "kyuubu 1.12.0",
"solves": [
{
"size": 3,
"scramble": "R U2 F'",
"moves": "F U2 R'",
"state": "UUUUUUUUURRRRRRRRRFFFFFFFFFDDDDDDDDDLLLLLLLLLBBBBBBBBB",
"solved": true,
"count": 3,
"ms": 12340,
"seed": "club night"
}
]
}- The JSON reads back in, and nothing in it is trusted.
fromJSONputs each solve together again from its size and its notation, and works out the cube, whether it is solved and the count of moves itself.state,solvedandcountare there for a reader without the package, and are ignored on the way in. A solve whose notation the cube cannot turn is left out.msis milliseconds, andat, when there is one, is ISO 8601. formatgoes up only if a reader of the old shape would be wrong about the new one.- The text reads back too.
fromTexttakes the lines above in any order and either case,3×3for3x3, and a bare line of notation as a solve's moves on a 3×3. - The CSV is safe to open. A cell that a spreadsheet would run as a
formula (one starting with
=,+,-or@) is given a leading apostrophe. Lines end CRLF, as RFC 4180 has them, and cells are quoted where they need to be. - A cube's state on its own is already a string: keep
view.state, and hand it back as thestateoption.
The API reference lists every export of every entry point with its signature and its doc comment. It is made from the source by pnpm site, so it cannot fall behind the code.
Everything is exported from @johnmorrisdotca/kyuubu; the React component
from @johnmorrisdotca/kyuubu/react. Every export has a doc comment, which
an editor shows on hover.
| Import | What it holds |
|---|---|
@johnmorrisdotca/kyuubu |
The model, notation, scrambles, the solver, export and import, CubeView, the guide, the replay and the words |
@johnmorrisdotca/kyuubu/react |
Kyuubu, a component wrapping CubeView |
@johnmorrisdotca/kyuubu/famous |
The record solves |
@johnmorrisdotca/kyuubu/player |
mountPlayer: a solve with its controls |
@johnmorrisdotca/kyuubu/element, /element/define |
<kyuubu-cube> and <kyuubu-scramble> |
@johnmorrisdotca/kyuubu/cuboid, /cuboid/draw, /cuboid/play, /cuboid/element, /cuboid/element/define |
The cuboids: model, view, player and tag |
| Call | What it does |
|---|---|
solvedCube(n), turnAll(cube, n, moves), cubeSolved(cube, n) |
A cube is a string; a turn is a pure function |
parseMoves(text, n), movesNotation(moves, n), undoAll(moves) |
Notation in and out |
randomScramble(n, length, random), seededRandom(seed) |
A scramble from a seed |
solveSteps(cube, n), stageName(stage, language?) |
The beginner's method as named steps |
toJSON, toText, toCSV, fromJSON, fromText, summarize |
A solve kept and read back |
new CubeView(element, options) and <kyuubu-cube> |
The cube on a page |
new Replay(cube, plan, options), planReplay |
A solve played back |
The tables of every export (the model, notation, scrambles, the solve, export and import, the replay, the player and famous solves, scale, show me, words, CubeView and <Kyuubu />) are in docs/API.md, and each one, with its signature and doc comment, is in the API reference.
Every colour, and the shape of a sticker, can be set three ways: as an
option when the cube is made, as a CSS custom property on the cube's element
or any ancestor, or later with setTheme. An option wins over a custom
property.
| Custom property | Option | Default | Is |
|---|---|---|---|
--kyuubu-up |
colours.U |
#f7f7f2 |
The up face: white |
--kyuubu-right |
colours.R |
#c8102e |
The right face: red |
--kyuubu-front |
colours.F |
#009b48 |
The front face: green |
--kyuubu-down |
colours.D |
#ffd500 |
The down face: yellow |
--kyuubu-left |
colours.L |
#ff5800 |
The left face: orange |
--kyuubu-back |
colours.B |
#0046ad |
The back face: blue |
--kyuubu-plastic |
plastic |
#111 |
The plastic between stickers and inside the cube |
--kyuubu-sticker-inset |
theme.stickerInset |
6% |
How far a sticker sits in from the edge of its square |
--kyuubu-sticker-radius |
theme.stickerRadius |
14% |
How round a sticker's corners are |
--kyuubu-corner-radius |
theme.cornerRadius |
15px |
How round the cube's own corners are, on a cube 300px across (it scales with the cube); 0 is square, as is rounded: false |
A hint on the cube (showHint, and so the guide) is coloured by its own
properties, which work the same in light and dark, since the cube is drawn
the same on both:
| Custom property | Default | Is |
|---|---|---|
--kyuubu-hint-colour |
#fff |
The arrow, and the ring round every sticker of the layer to turn |
--kyuubu-hint-edge |
rgba(17, 17, 17, 0.85) |
The edge round the arrow, so that it shows on a white sticker too |
--kyuubu-hint-dim |
brightness(0.62) saturate(0.7) |
The CSS filter on every sticker the hint does not light; none dims nothing |
--kyuubu-hint-opacity |
0.96 |
How solid the arrow is |
CUBE_THEMES holds three looks: standard, stickerless (colour to the
edge of every piece) and paper, which is the one the demo site wears:
import { CUBE_THEMES, CubeView } from "@johnmorrisdotca/kyuubu";
const view = new CubeView(element, { size: 3, theme: CUBE_THEMES.paper });
view.setTheme({ colours: { U: "#fffdf7" }, plastic: "#1f2320" }); // later, without redrawing/* The same look from a stylesheet, which can follow light and dark. */
.my-cube {
--kyuubu-up: #fffdf7; --kyuubu-right: #b5452c; --kyuubu-front: #2f7a4f;
--kyuubu-down: #e0b43b; --kyuubu-left: #d97a2b; --kyuubu-back: #2b5f8f;
--kyuubu-plastic: #1f2320;
}The cube has no background of its own: it sits on whatever is behind its
element. DEFAULT_COLOURS, DEFAULT_PLASTIC and FACE_PROPERTIES (which
property colours which face) are exported.
| Limit | Value | Constant |
|---|---|---|
| Sizes the package is made and tested for | 2×2 to 7×7 | MIN_RECORD_SIZE, MAX_RECORD_SIZE |
| Sizes with a step-by-step solve | 2×2 and 3×3 | SOLVABLE_SIZES |
| Cuboid sides | 1 to 7 each, and not 1×1×1 | CUBOID_MIN_SIDE, CUBOID_MAX_SIDE |
| Cuboid states a scramble is drawn uniformly from | 20,000 | CUBOID_RANDOM_STATE_MAX |
| Layers notation reaches from a face | 9 | |
| Turns in one saved solve, scramble and moves each | 10,000 | MAX_RECORD_MOVES |
| Solves read from one file | 1,000 | MAX_RECORDS |
| Characters in a saved seed | 200 | MAX_RECORD_SEED |
| A scramble from the command line | 1 to 1,000 turns | MAX_CLI_LENGTH |
| Scrambles from the command line at once | 100 | MAX_CLI_COUNT |
The model turns a cube of any size from 2 up, and the view draws one; past 7×7 nothing is tested, and a saved solve is refused.
The biggest cubes on a phone's processor. A 7×7 is 294 stickers, each given its place on the screen in every frame of a turn.
Measured in Chromium with the processor slowed fourfold (about a phone), the longest single task over six turns of a 7×7 is 21
milliseconds (the 3×3, 9), and the longest of a dragged layer 19. The longest are at the start and the end of a turn, where the
layer's stickers are lifted out of the cube and put back; the frames between are a few milliseconds each. e2e/big.e2e.mjs
fails a change that makes a turn cost several times that.
- The cube is one control. Its box is a
role="application"named "A 3×3 cube" (3×3のキューブ in Japanese; thelabeloption changes it), and with the defaultkeyboard: "focus"it is a tab stop, so every turn can be made from the keyboard (see Controls) as well as by a pointer. - A solve is said, not only shown. The guide's line is a polite live
region, read out once for each move, in sentences ("Turn the right face
towards you.") as well as notation; a turn it did not ask for is announced
as an alert. The player's controls are real buttons with names, the speeds,
Loop and Follow report whether they are pressed, and the scrubber has a name.
The move just made is also said once, in a polite live region, and not for
every move of a solve playing; the moves are buttons named with their code,
their words and where they are, reached by one Tab stop and the arrow keys,
Home and End, the one just made marked with
aria-current. - Motion is optional. A device that asks for reduced motion gets turns made at once, a cube that keeps turning stays still, and the guide's arrow does not move.
- What it does not do. Which colour is where is not read out: a person who cannot see the cube hears what to turn and what was turned, not the state of the faces.
test/docs.test.js checks each of these against the source.
Every current browser with CSS 3D transforms and Pointer Events: Chrome, Edge, Firefox and Safari 15 or later, on desktop and mobile. There is nothing to polyfill. It is tested in Chromium and in WebKit, Safari's engine, at phone size with touch.
English and Japanese, for everything the package says: the cube's accessible
name, the steps of a solve and what each is for, the algorithms' names, and
the command line. The view follows its locale option, or the page's lang;
the command line follows --lang or the environment. The demo has a chooser
of its own, follows the browser's language on a first visit, and takes
?lang=ja or ?lang=en in the address.
Japanese: included; not yet reviewed by a native reader. Corrections welcome. Every Japanese string is listed beside its English in docs/strings-ja.md, and there is an issue template for fixing one.
- A solve for the 4×4 and up, by reduction to a 3×3 (today a big cube can only take back every turn made on it)
- Competition-style scrambles for the big cubes, which use wide turns (the notation reads wide turns already; the scrambler does not make them yet)
- More famous solves: the records before 2017 whose reconstructions are in forum threads, and the other events
- Measured times for each move of the famous solves (the player takes a time
per move already, as
stepMs; the records give only the whole time) - A drag that follows a real touch in the tests, not only a mouse
Done since the first release, and no longer on this list: Vue, Svelte and
Angular (above, each proved from the packed tarball), the <kyuubu-cube>
element for any page or framework, replaying and embedding a solve, famous
record solves, pasting a solve as it is written or as a link, each move
shown on the cube with an arrow to follow, and the other shapes (cuboids:
2×2×3, 3×3×2 and the rest).
Left out on purpose: a wrapper component for every framework (React has one;
everywhere else the element or one new CubeView does the same job, with
nothing more to keep up); a shortest-possible solver (others do that well,
and it teaches nobody the cube); a timer with inspection and penalties,
which is an application and not a cube; and anything that needs a server. Kyuubu runs
from a static page and costs nothing to host.
Ideas and pull requests are welcome.
The cube, its notation, the solver and the famous solves are plain functions
over a string, with no DOM: the state is one letter a sticker, and every turn
returns a new string. Drawing is its own layer under view/, in plain DOM and
CSS with no canvas, and the player, the custom elements and the React
component are thin wrappers over it, each its own entry point, so a page loads
only what it uses.
The file-by-file tree, with a line on each source file, is in docs/ARCHITECTURE.md: the model and the notation, the scrambler and the solver, the view, the player and the elements, the cuboids (cuboid/), and the command line. Tests live in test/, apart from the code, and test/docs.test.js runs the
README's examples. bin/ is the few lines that hand the command line the real
process, scripts/ builds the demo and its API reference page and checks the
package as npm packs it, demo/ is the site published on GitHub Pages, and
e2e/ taps it in real browsers.
Kyuubu (キューブ) is the English word "cube" as Japanese writes it: a borrowed word, spelled in katakana, the script Japanese uses for words taken from other languages. It is said in three beats, kyu-u-bu, the middle one only the vowel held longer, which is what the mark ー shows. Written more formally in the Latin alphabet it is kyūbu; the package spells the long vowel out, so that the name needs no accent to type.
Kyuubu was built for Itsutsu, a site for board games, puzzles, card games and dice games played at your own pace. Itsutsu (五つ) is Japanese for "five", after five in a row, the game the site began with. The site wanted a cube that worked on a phone without a canvas, that it could check on the server move by move, and that could show a beginner what to do next. Once that existed it seemed worth sharing.
- Itsutsu, for its cube.
That is the whole list so far. Using Kyuubu in something? Open an Add my project issue and we will add you.
Kyuubu is one of twenty-four packages, each made for the same site, each at github.com/johnmorrisdotca. The code of every one is MIT.
- Korokoro (コロコロ): dice, with notation, exact odds, real sounds and the dice of many games. Demo.
- Kyuubu (キューブ): a turning cube for the browser, 2×2 to 7×7, with record solves to replay. Demo.
- Hitotsu (一つ): a colour-card shedding game for two to eight, with the house rules people play. Demo.
- Toranpu (トランプ): a deck of playing cards, card games with computer players, and solitaires. Demo.
- Tane (種): seeded random numbers and daily seeds, the same in every browser and on every server. Demo.
- Narabe (並べ): one rules engine for abstract board games, from gomoku and Reversi to Go and checkers. Demo.
- Tenka (天下): world conquest for two to six, on a map of the real world. Demo.
- Kumimoji (組み文字): a crossword tile race, in English and Japanese kana. Demo.
- Tsunagi (繋ぎ): a line-joining logic puzzle whose every level has exactly one answer. Demo.
- Jarajara (ジャラジャラ): mahjong tiles drawn as SVG, stacked layouts, and the matching solitaire Awase. Demo.
- Suido (水道): a pipe puzzle: turn the pieces until the water reaches every drain. Demo.
- Domino (ドミノ): dominoes and Mexican Train. Demo.
- Kotoba (言葉): word lists and word-game rules in English, French, German and Japanese. Demo.
- Sugoroku (双六): backgammon and its variants, with the doubling cube and match play. Demo.
- Kazu (数): grid number puzzles: Sudoku and its variants, Futoshiki and Skyscrapers. Demo.
- Meikyuu (迷宮): mazes on squares, hexagons, triangles and circles, made from a seed and drawn through with a finger or the mouse. Demo.
- Hikidashi (引き出し): a drawer of small Japanese text tools: era dates, kanji numerals, readings and sentence difficulty. Demo.
- Chizu (地図): maps of the world and of countries' regions, in English and Japanese, with a quiz and callouts. Demo.
- Bushu (部首): find a kanji by the parts it is made of. Demo.
- Tobiishi (飛び石): peg solitaire with nine boards and seeded solvable challenges. Demo.
- Jirai (地雷): minesweeper on shaped grids with verified no-guess boards. Demo.
- Gunjin (軍人): five hidden-rank strategy games with pass-the-device play. Demo.
- Karakuri (からくり): eight hyper-casual puzzle games, some of them physics: draw a shield, pull pins, cut ropes, slide blocks, pour tubes. Demo.
- Houseki (宝石): gem and stone matching puzzles: falling triplets, stone collapse, colour chains and gem swap. Demo.
This package is Kyuubu. The demos of all twenty-four share one header and footer, so each links the rest.
pnpm install --frozen-lockfile
pnpm check # lint, types, tests and the build
pnpm test:site # the demo in real browsers
pnpm test:cli # the command line, as a child process
pnpm test:package # pack it as npm does, install it and import every entry
pnpm test:frameworks # the framework examples, built from the packed tarball and played
pnpm test:readme # every TypeScript and JavaScript example in this README, type-checked and run
pnpm screenshots:readme # retake the README's pictures into docs/images (builds the demo first)The pictures are taken on the maintainer's Mac and are retaken only when the look changes; they are in docs/images and are not in the package that npm installs.
See CONTRIBUTING.md. In short: run pnpm check before you push (see Development).
Please follow the code of conduct. A way to make the solver or a parser run for very long, or text that gets out of the cube into the page, is for the security policy, not a public issue.
See CHANGELOG.md.
The latest release is 1.12.0: the guide draws an arrow on the cube for a turn of the whole cube (x, y, z), as it already did for every layer.
MIT © John Morris The cube is drawn in code: no picture, font or sound of anyone else's ships.