@wodzik/cubecore 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +373 -0
- package/README.md +118 -0
- package/analyze/client.d.ts +18 -0
- package/analyze/client.js +42 -0
- package/analyze/index.d.ts +114 -0
- package/analyze/index.js +151 -0
- package/analyze/rotations.d.ts +3 -0
- package/analyze/rotations.js +15 -0
- package/analyze/roux.d.ts +67 -0
- package/analyze/roux.js +95 -0
- package/analyze/worker.d.ts +6 -0
- package/analyze/worker.js +31 -0
- package/analyze/zz.d.ts +47 -0
- package/analyze/zz.js +102 -0
- package/bld/index.d.ts +14 -0
- package/bld/index.js +14 -0
- package/bld/memo.d.ts +66 -0
- package/bld/memo.js +139 -0
- package/bld/scheme.d.ts +30 -0
- package/bld/scheme.js +35 -0
- package/bld/skin.d.ts +26 -0
- package/bld/skin.js +46 -0
- package/bld/tracker.d.ts +68 -0
- package/bld/tracker.js +145 -0
- package/bluetooth/clock.d.ts +22 -0
- package/bluetooth/clock.js +37 -0
- package/bluetooth/cubeSkins.d.ts +31 -0
- package/bluetooth/cubeSkins.js +32 -0
- package/bluetooth/gyro.d.ts +31 -0
- package/bluetooth/gyro.js +46 -0
- package/bluetooth/index.d.ts +5 -0
- package/bluetooth/index.js +5 -0
- package/bluetooth/session.d.ts +140 -0
- package/bluetooth/session.js +201 -0
- package/bluetooth/simulated.d.ts +52 -0
- package/bluetooth/simulated.js +80 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/gan-cube-definitions.d.ts +38 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/gan-cube-definitions.js +51 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/gan-cube-encrypter.d.ts +35 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/gan-cube-encrypter.js +70 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/gan-cube-protocol.d.ts +273 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/gan-cube-protocol.js +846 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/gan-gen1.d.ts +38 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/gan-gen1.js +340 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/gan-gen234-packet-validate.d.ts +11 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/gan-gen234-packet-validate.js +118 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/gan-mac-salt.d.ts +2 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/gan-mac-salt.js +15 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/gan-smart-cube.d.ts +18 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/gan-smart-cube.js +152 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/gan-smart-timer.d.ts +74 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/gan-smart-timer.js +161 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/gan-write-queue.d.ts +7 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/gan-write-queue.js +11 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/gatt-characteristic-write.d.ts +2 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/gatt-characteristic-write.js +36 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/index.d.ts +4 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/index.js +4 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/address-hints.d.ts +12 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/address-hints.js +122 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/build-picker-options.d.ts +11 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/build-picker-options.js +55 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/default-optional-services.d.ts +21 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/default-optional-services.js +47 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/device-name-scan-filters.d.ts +6 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/device-name-scan-filters.js +24 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/gatt-snapshot.d.ts +6 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/gatt-snapshot.js +57 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/mac-candidates.d.ts +11 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/mac-candidates.js +67 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/mac-probe-moyu32.d.ts +8 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/mac-probe-moyu32.js +98 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/mac-probe-qiyi.d.ts +8 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/mac-probe-qiyi.js +128 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/moyu32-session-crypto.d.ts +5 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/moyu32-session-crypto.js +60 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/normalize-uuid.d.ts +2 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/normalize-uuid.js +8 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/packet-sanity.d.ts +7 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/packet-sanity.js +142 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/profile-rank.d.ts +6 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/profile-rank.js +21 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/types.d.ts +32 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/types.js +1 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/ble-utils.d.ts +6 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/ble-utils.js +68 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/connect.d.ts +3 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/connect.js +178 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/cubie-cube.d.ts +16 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/cubie-cube.js +141 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/index.d.ts +11 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/index.js +10 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocol.d.ts +26 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocol.js +8 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/gan.d.ts +3 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/gan.js +265 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/giiker.d.ts +3 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/giiker.js +345 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/gocube.d.ts +15 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/gocube.js +382 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/moyu-mhc.d.ts +3 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/moyu-mhc.js +352 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/moyu-v1.d.ts +57 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/moyu-v1.js +243 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/moyu32.d.ts +3 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/moyu32.js +454 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/qiyi.d.ts +3 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/qiyi.js +424 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/types.d.ts +78 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/types.js +1 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/utils.d.ts +45 -0
- package/bluetooth/vendor/smartcube-web-bluetooth/utils.js +150 -0
- package/cfop/caseData.d.ts +16 -0
- package/cfop/caseData.js +88 -0
- package/cfop/cases.d.ts +42 -0
- package/cfop/cases.js +116 -0
- package/cfop/f2l.d.ts +28 -0
- package/cfop/f2l.js +88 -0
- package/cfop/f2lData.d.ts +15 -0
- package/cfop/f2lData.js +49 -0
- package/cfop/index.d.ts +41 -0
- package/cfop/index.js +110 -0
- package/cfop/slots.d.ts +2 -0
- package/cfop/slots.js +1 -0
- package/core/arrows.d.ts +24 -0
- package/core/arrows.js +22 -0
- package/core/bytes.d.ts +27 -0
- package/core/bytes.js +145 -0
- package/core/centers.d.ts +32 -0
- package/core/centers.js +83 -0
- package/core/checks.d.ts +53 -0
- package/core/checks.js +85 -0
- package/core/cubies.d.ts +45 -0
- package/core/cubies.js +169 -0
- package/core/frames.d.ts +61 -0
- package/core/frames.js +126 -0
- package/core/geometry.d.ts +58 -0
- package/core/geometry.js +132 -0
- package/core/index.d.ts +19 -0
- package/core/index.js +19 -0
- package/core/masks.d.ts +32 -0
- package/core/masks.js +39 -0
- package/core/method.d.ts +35 -0
- package/core/method.js +30 -0
- package/core/moves.d.ts +48 -0
- package/core/moves.js +88 -0
- package/core/notation.d.ts +48 -0
- package/core/notation.js +226 -0
- package/core/physical.d.ts +53 -0
- package/core/physical.js +99 -0
- package/core/practice.d.ts +62 -0
- package/core/practice.js +87 -0
- package/core/sequence.d.ts +90 -0
- package/core/sequence.js +157 -0
- package/core/stages.d.ts +90 -0
- package/core/stages.js +21 -0
- package/core/state.d.ts +55 -0
- package/core/state.js +140 -0
- package/core/stateCodec.d.ts +12 -0
- package/core/stateCodec.js +51 -0
- package/core/stream.d.ts +45 -0
- package/core/stream.js +75 -0
- package/core/tracker.d.ts +62 -0
- package/core/tracker.js +121 -0
- package/element/alg.d.ts +45 -0
- package/element/alg.js +150 -0
- package/element/base.d.ts +2 -0
- package/element/base.js +3 -0
- package/element/bld.d.ts +82 -0
- package/element/bld.js +222 -0
- package/element/index.d.ts +8 -0
- package/element/index.js +21 -0
- package/element/model.d.ts +36 -0
- package/element/model.js +64 -0
- package/element/player.d.ts +196 -0
- package/element/player.js +680 -0
- package/element/practice.d.ts +75 -0
- package/element/practice.js +150 -0
- package/element/scramble.d.ts +50 -0
- package/element/scramble.js +107 -0
- package/element/sequence.d.ts +126 -0
- package/element/sequence.js +254 -0
- package/element/styles.d.ts +32 -0
- package/element/styles.js +196 -0
- package/image/cache.d.ts +21 -0
- package/image/cache.js +66 -0
- package/image/index.d.ts +2 -0
- package/image/index.js +2 -0
- package/image/png-node.d.ts +6 -0
- package/image/png-node.js +10 -0
- package/image/svg.d.ts +36 -0
- package/image/svg.js +215 -0
- package/lbl/index.d.ts +10 -0
- package/lbl/index.js +31 -0
- package/methods/index.d.ts +20 -0
- package/methods/index.js +33 -0
- package/package.json +133 -0
- package/petrus/index.d.ts +11 -0
- package/petrus/index.js +27 -0
- package/react/index.d.ts +176 -0
- package/react/index.js +228 -0
- package/render/arrows.d.ts +68 -0
- package/render/arrows.js +190 -0
- package/render/build/attachments.d.ts +35 -0
- package/render/build/attachments.js +94 -0
- package/render/build/models.d.ts +9 -0
- package/render/build/models.js +27 -0
- package/render/build/tiles.d.ts +48 -0
- package/render/build/tiles.js +194 -0
- package/render/gltf.d.ts +10 -0
- package/render/gltf.js +106 -0
- package/render/index.d.ts +12 -0
- package/render/index.js +12 -0
- package/render/layers.d.ts +21 -0
- package/render/layers.js +24 -0
- package/render/pictures.d.ts +46 -0
- package/render/pictures.js +80 -0
- package/render/pieceModels.d.ts +38 -0
- package/render/pieceModels.js +43 -0
- package/render/renderer.d.ts +165 -0
- package/render/renderer.js +647 -0
- package/render/tile.d.ts +114 -0
- package/render/tile.js +390 -0
- package/render/viewports.d.ts +25 -0
- package/render/viewports.js +26 -0
- package/roux/cmll.d.ts +35 -0
- package/roux/cmll.js +121 -0
- package/roux/cmllData.d.ts +12 -0
- package/roux/cmllData.js +49 -0
- package/roux/index.d.ts +52 -0
- package/roux/index.js +175 -0
- package/skin/attachments.d.ts +82 -0
- package/skin/attachments.js +110 -0
- package/skin/index.d.ts +3 -0
- package/skin/index.js +3 -0
- package/skin/shapes.d.ts +112 -0
- package/skin/shapes.js +147 -0
- package/skin/skin.d.ts +911 -0
- package/skin/skin.js +412 -0
- package/solve/client.d.ts +58 -0
- package/solve/client.js +56 -0
- package/solve/handle.d.ts +6 -0
- package/solve/handle.js +38 -0
- package/solve/index.d.ts +8 -0
- package/solve/index.js +7 -0
- package/solve/lse.d.ts +42 -0
- package/solve/lse.js +151 -0
- package/solve/pieces.d.ts +18 -0
- package/solve/pieces.js +24 -0
- package/solve/protocol.d.ts +70 -0
- package/solve/protocol.js +16 -0
- package/solve/scramble.d.ts +76 -0
- package/solve/scramble.js +187 -0
- package/solve/stage.d.ts +108 -0
- package/solve/stage.js +532 -0
- package/solve/twophase.d.ts +36 -0
- package/solve/twophase.js +229 -0
- package/solve/worker.d.ts +6 -0
- package/solve/worker.js +15 -0
- package/timeline/codec.d.ts +23 -0
- package/timeline/codec.js +84 -0
- package/timeline/index.d.ts +7 -0
- package/timeline/index.js +7 -0
- package/timeline/recording.d.ts +19 -0
- package/timeline/recording.js +20 -0
- package/timeline/replay.d.ts +68 -0
- package/timeline/replay.js +145 -0
- package/timeline/segments.d.ts +31 -0
- package/timeline/segments.js +39 -0
- package/timeline/share.d.ts +30 -0
- package/timeline/share.js +62 -0
- package/timeline/stats.d.ts +34 -0
- package/timeline/stats.js +56 -0
- package/timeline/timings.d.ts +30 -0
- package/timeline/timings.js +54 -0
- package/zz/index.d.ts +29 -0
- package/zz/index.js +68 -0
- package/zz/seeds.d.ts +9 -0
- package/zz/seeds.js +29 -0
package/bld/memo.js
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Old Pochmann memorisation. Starting from the buffer, follow where each
|
|
3
|
+
* sticker belongs: the sticker in the buffer belongs at A → remember A; the
|
|
4
|
+
* sticker at A belongs at B → remember B; … until the buffer's own piece
|
|
5
|
+
* comes back. Pieces left unsolved start new cycles (a cycle break: shoot
|
|
6
|
+
* to one of their stickers, follow, and finish on that piece again) — which
|
|
7
|
+
* also covers flipped edges and twisted corners in place.
|
|
8
|
+
*
|
|
9
|
+
* Executing: each letter is one swap of the buffer piece with the lettered
|
|
10
|
+
* sticker's piece (edges: T-perm with setup moves, corners: Y-perm). An odd
|
|
11
|
+
* number of edge letters means parity (an extra algorithm between edges and
|
|
12
|
+
* corners).
|
|
13
|
+
*
|
|
14
|
+
* Buffers: UR for edges, ULB for corners (the usual OP buffers).
|
|
15
|
+
*/
|
|
16
|
+
import { CORNER_FACELETS, EDGE_FACELETS, applyMove, applyMoves, parseAlg, relativeState, solvedState } from "../core/index.js";
|
|
17
|
+
import { RUWIX } from "./scheme.js";
|
|
18
|
+
/** Edge buffer: the U sticker of UR. */
|
|
19
|
+
export const EDGE_BUFFER = 5;
|
|
20
|
+
/** Corner buffer: the U sticker of ULB. */
|
|
21
|
+
export const CORNER_BUFFER = 0;
|
|
22
|
+
/** Sticker positions of each piece, rotated so a given sticker comes first (clockwise for corners). */
|
|
23
|
+
function pieces(groups) {
|
|
24
|
+
const pieceOf = new Map();
|
|
25
|
+
groups.forEach((g, i) => g.forEach((f) => pieceOf.set(f, i)));
|
|
26
|
+
const from = (sticker) => {
|
|
27
|
+
const g = groups[pieceOf.get(sticker)];
|
|
28
|
+
const k = g.indexOf(sticker);
|
|
29
|
+
return [...g.slice(k), ...g.slice(0, k)];
|
|
30
|
+
};
|
|
31
|
+
return { pieceOf, from };
|
|
32
|
+
}
|
|
33
|
+
const EDGES = pieces(EDGE_FACELETS);
|
|
34
|
+
const CORNERS = pieces(CORNER_FACELETS);
|
|
35
|
+
/** Targets (sticker positions) for one kind of piece. */
|
|
36
|
+
function trace(state, kind, buffer, order) {
|
|
37
|
+
const piece = (f) => kind.pieceOf.get(f);
|
|
38
|
+
const bufferPiece = piece(buffer);
|
|
39
|
+
const solved = (p) => kind.from(order.find((f) => piece(f) === p)).every((f) => state[f] === f);
|
|
40
|
+
const visited = new Set([bufferPiece]);
|
|
41
|
+
const targets = [];
|
|
42
|
+
// The first cycle, from the buffer.
|
|
43
|
+
let t = state[buffer];
|
|
44
|
+
while (piece(t) !== bufferPiece) {
|
|
45
|
+
targets.push(t);
|
|
46
|
+
visited.add(piece(t));
|
|
47
|
+
t = state[t];
|
|
48
|
+
}
|
|
49
|
+
// Cycle breaks: unsolved pieces not reached yet, in letter order.
|
|
50
|
+
for (const start of order) {
|
|
51
|
+
const p = piece(start);
|
|
52
|
+
if (visited.has(p) || solved(p))
|
|
53
|
+
continue;
|
|
54
|
+
targets.push(start);
|
|
55
|
+
visited.add(p);
|
|
56
|
+
t = state[start];
|
|
57
|
+
while (piece(t) !== p) {
|
|
58
|
+
targets.push(t);
|
|
59
|
+
visited.add(piece(t));
|
|
60
|
+
t = state[t];
|
|
61
|
+
}
|
|
62
|
+
targets.push(t);
|
|
63
|
+
}
|
|
64
|
+
return targets;
|
|
65
|
+
}
|
|
66
|
+
/** Sticker positions of a kind, in the scheme's letter order (cycle breaks go to the lowest letter). */
|
|
67
|
+
const letterOrder = (letters) => Object.keys(letters)
|
|
68
|
+
.map(Number)
|
|
69
|
+
.sort((a, b) => letters[a].localeCompare(letters[b]));
|
|
70
|
+
const CENTRES = [4, 13, 22, 31, 40, 49];
|
|
71
|
+
/** The 24 whole-cube rotations as states (a solved cube turned in your hands). */
|
|
72
|
+
const ROTATIONS = (() => {
|
|
73
|
+
const out = [solvedState()];
|
|
74
|
+
const seen = new Set([out[0].join(",")]);
|
|
75
|
+
const turns = parseAlg("x y");
|
|
76
|
+
for (let i = 0; i < out.length; i++) {
|
|
77
|
+
for (const t of turns) {
|
|
78
|
+
const next = applyMove(out[i], t);
|
|
79
|
+
const key = next.join(",");
|
|
80
|
+
if (!seen.has(key)) {
|
|
81
|
+
seen.add(key);
|
|
82
|
+
out.push(next);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return out;
|
|
87
|
+
})();
|
|
88
|
+
/**
|
|
89
|
+
* The state with positions and sticker names as held: turned by `rotation`
|
|
90
|
+
* if given, and with the centres where they are now counted as home (a
|
|
91
|
+
* state after rotations, e.g. "… x2 y'", reads the way the cube is now held).
|
|
92
|
+
*/
|
|
93
|
+
export function asHeld(state, rotation) {
|
|
94
|
+
const s = rotation ? applyMoves(state, rotation) : new Uint8Array(state);
|
|
95
|
+
const turned = ROTATIONS.find((r) => CENTRES.every((c) => r[c] === s[c]));
|
|
96
|
+
return turned ? relativeState(s, turned) : s;
|
|
97
|
+
}
|
|
98
|
+
/** The letters to remember for `state`. */
|
|
99
|
+
export function memo(input, options = {}) {
|
|
100
|
+
const state = asHeld(input, options.rotation);
|
|
101
|
+
const scheme = options.scheme ?? RUWIX;
|
|
102
|
+
const edgeTargets = trace(state, EDGES, options.edgeBuffer ?? EDGE_BUFFER, letterOrder(scheme.edges));
|
|
103
|
+
const cornerTargets = trace(state, CORNERS, options.cornerBuffer ?? CORNER_BUFFER, letterOrder(scheme.corners));
|
|
104
|
+
return {
|
|
105
|
+
edges: edgeTargets.map((f) => scheme.edges[f]),
|
|
106
|
+
corners: cornerTargets.map((f) => scheme.corners[f]),
|
|
107
|
+
edgeTargets,
|
|
108
|
+
cornerTargets,
|
|
109
|
+
parity: edgeTargets.length % 2 === 1,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
/** Letters in pairs, the usual way to write memo: "QU SR NX IV PR DE". */
|
|
113
|
+
export const formatMemo = (letters) => letters.reduce((out, l, i) => out + (i > 0 && i % 2 === 0 ? " " : "") + l, "");
|
|
114
|
+
/**
|
|
115
|
+
* The effect of one letter on the pieces of its kind: the buffer piece and
|
|
116
|
+
* the target's piece swap, the buffer sticker going to the target sticker
|
|
117
|
+
* (what the setup + T-perm / Y-perm + undo do to that kind of piece).
|
|
118
|
+
*/
|
|
119
|
+
export function swapToTarget(state, kind, target, buffer = kind === "edge" ? EDGE_BUFFER : CORNER_BUFFER) {
|
|
120
|
+
const k = kind === "edge" ? EDGES : CORNERS;
|
|
121
|
+
const b = k.from(buffer), t = k.from(target);
|
|
122
|
+
const out = new Uint8Array(state);
|
|
123
|
+
b.forEach((bf, i) => {
|
|
124
|
+
out[t[i]] = state[bf];
|
|
125
|
+
out[bf] = state[t[i]];
|
|
126
|
+
});
|
|
127
|
+
return out;
|
|
128
|
+
}
|
|
129
|
+
/** Sticker positions of edges / corners (to compare only one kind of piece). */
|
|
130
|
+
export const EDGE_POSITIONS = EDGE_FACELETS.flat();
|
|
131
|
+
export const CORNER_POSITIONS = CORNER_FACELETS.flat();
|
|
132
|
+
export const samePieces = (a, b, positions) => positions.every((p) => a[p] === b[p]);
|
|
133
|
+
/** Two sticker positions on the same edge / corner. */
|
|
134
|
+
export const samePiece = (kind, a, b) => {
|
|
135
|
+
const k = kind === "edge" ? EDGES : CORNERS;
|
|
136
|
+
return k.pieceOf.get(a) === k.pieceOf.get(b);
|
|
137
|
+
};
|
|
138
|
+
/** The whole-cube rotation state with these centres (a solved cube as held), or null. */
|
|
139
|
+
export const rotationState = (rotation) => applyMoves(solvedState(), rotation);
|
package/bld/scheme.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Letter schemes: a letter for each corner sticker and each edge sticker
|
|
3
|
+
* (24 + 24; centres have none). Positions are facelet indices of the cube as
|
|
4
|
+
* held (U = the up face).
|
|
5
|
+
*
|
|
6
|
+
* Both built-ins letter each face clockwise from its top-left corner / top
|
|
7
|
+
* edge, looking at the face in the usual net (U with B at its top, side
|
|
8
|
+
* faces with U at the top, D with F at its top) — A B C D on the first face,
|
|
9
|
+
* E F G H on the second, … — they differ only in the order of the faces.
|
|
10
|
+
*/
|
|
11
|
+
import { type Face } from "../core/index.js";
|
|
12
|
+
export interface LetterScheme {
|
|
13
|
+
name: string;
|
|
14
|
+
/** Letter of each corner sticker position (facelet index → letter). */
|
|
15
|
+
corners: Record<number, string>;
|
|
16
|
+
/** Letter of each edge sticker position. */
|
|
17
|
+
edges: Record<number, string>;
|
|
18
|
+
}
|
|
19
|
+
/** A scheme lettering the faces in `order`, four letters each, clockwise from the top-left. */
|
|
20
|
+
export declare function schemeByFaces(name: string, order: readonly Face[]): LetterScheme;
|
|
21
|
+
/** Speffz: U, L, F, R, B, D. */
|
|
22
|
+
export declare const SPEFFZ: LetterScheme;
|
|
23
|
+
/** The ruwix.com tutorial's scheme: U, F, R, B, L, D. */
|
|
24
|
+
export declare const RUWIX: LetterScheme;
|
|
25
|
+
export declare const SCHEMES: {
|
|
26
|
+
readonly speffz: LetterScheme;
|
|
27
|
+
readonly ruwix: LetterScheme;
|
|
28
|
+
};
|
|
29
|
+
/** Letter of any sticker position (corner or edge), or "" for a centre. */
|
|
30
|
+
export declare const letterAt: (scheme: LetterScheme, position: number) => string;
|
package/bld/scheme.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Letter schemes: a letter for each corner sticker and each edge sticker
|
|
3
|
+
* (24 + 24; centres have none). Positions are facelet indices of the cube as
|
|
4
|
+
* held (U = the up face).
|
|
5
|
+
*
|
|
6
|
+
* Both built-ins letter each face clockwise from its top-left corner / top
|
|
7
|
+
* edge, looking at the face in the usual net (U with B at its top, side
|
|
8
|
+
* faces with U at the top, D with F at its top) — A B C D on the first face,
|
|
9
|
+
* E F G H on the second, … — they differ only in the order of the faces.
|
|
10
|
+
*/
|
|
11
|
+
import { FACES } from "../core/index.js";
|
|
12
|
+
/** Within a face (row-major 0..8): corners and edges, clockwise from the top-left. */
|
|
13
|
+
const CORNER_CELLS = [0, 2, 8, 6];
|
|
14
|
+
const EDGE_CELLS = [1, 5, 7, 3];
|
|
15
|
+
const ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWX";
|
|
16
|
+
/** A scheme lettering the faces in `order`, four letters each, clockwise from the top-left. */
|
|
17
|
+
export function schemeByFaces(name, order) {
|
|
18
|
+
const corners = {};
|
|
19
|
+
const edges = {};
|
|
20
|
+
order.forEach((face, i) => {
|
|
21
|
+
const base = FACES.indexOf(face) * 9;
|
|
22
|
+
for (let j = 0; j < 4; j++) {
|
|
23
|
+
corners[base + CORNER_CELLS[j]] = ALPHABET[i * 4 + j];
|
|
24
|
+
edges[base + EDGE_CELLS[j]] = ALPHABET[i * 4 + j];
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
return { name, corners, edges };
|
|
28
|
+
}
|
|
29
|
+
/** Speffz: U, L, F, R, B, D. */
|
|
30
|
+
export const SPEFFZ = schemeByFaces("Speffz", ["U", "L", "F", "R", "B", "D"]);
|
|
31
|
+
/** The ruwix.com tutorial's scheme: U, F, R, B, L, D. */
|
|
32
|
+
export const RUWIX = schemeByFaces("ruwix", ["U", "F", "R", "B", "L", "D"]);
|
|
33
|
+
export const SCHEMES = { speffz: SPEFFZ, ruwix: RUWIX };
|
|
34
|
+
/** Letter of any sticker position (corner or edge), or "" for a centre. */
|
|
35
|
+
export const letterAt = (scheme, position) => scheme.corners[position] ?? scheme.edges[position] ?? "";
|
package/bld/skin.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A skin with the scheme's letters on the stickers. Each sticker carries the
|
|
3
|
+
* letter of its HOME position and keeps it wherever it goes — so the letter
|
|
4
|
+
* showing in the buffer is the next letter to remember.
|
|
5
|
+
*
|
|
6
|
+
* player.skin = letterSkin(SKINS.default); // colours + letters
|
|
7
|
+
* player.skin = letterSkin(SKINS.default, { alwaysShow: true }); // letters stay on masked (grey) stickers too
|
|
8
|
+
*/
|
|
9
|
+
import { type Move } from "../core/index.js";
|
|
10
|
+
import type { Skin } from "../skin/index.js";
|
|
11
|
+
import { type LetterScheme } from "./scheme.js";
|
|
12
|
+
export interface LetterSkinOptions {
|
|
13
|
+
scheme?: LetterScheme;
|
|
14
|
+
/** Letter colour. Default near-black with a white edge (reads on every sticker colour). */
|
|
15
|
+
color?: string;
|
|
16
|
+
outline?: string | null;
|
|
17
|
+
/** Letter size as a fraction of the tile. Default 0.62. */
|
|
18
|
+
size?: number;
|
|
19
|
+
/** Keep the letters on stickers a mask greys out (a "letters only" cube). Default false. */
|
|
20
|
+
alwaysShow?: boolean;
|
|
21
|
+
/** How the cube is held (as in `memo`): letters by the positions as held, upright for the holder. */
|
|
22
|
+
rotation?: string | readonly Move[];
|
|
23
|
+
}
|
|
24
|
+
/** The SVG of one letter (square, transparent). */
|
|
25
|
+
export declare function letterSvg(letter: string, color?: string, outline?: string | null): string;
|
|
26
|
+
export declare function letterSkin(base: Skin, options?: LetterSkinOptions): Skin;
|
package/bld/skin.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A skin with the scheme's letters on the stickers. Each sticker carries the
|
|
3
|
+
* letter of its HOME position and keeps it wherever it goes — so the letter
|
|
4
|
+
* showing in the buffer is the next letter to remember.
|
|
5
|
+
*
|
|
6
|
+
* player.skin = letterSkin(SKINS.default); // colours + letters
|
|
7
|
+
* player.skin = letterSkin(SKINS.default, { alwaysShow: true }); // letters stay on masked (grey) stickers too
|
|
8
|
+
*/
|
|
9
|
+
import { FACELETS, FACE_BASIS, applyMoves, solvedState } from "../core/index.js";
|
|
10
|
+
import { RUWIX, letterAt } from "./scheme.js";
|
|
11
|
+
const escape = (s) => s.replace(/[<&"]/g, (c) => ({ "<": "<", "&": "&", '"': """ })[c]);
|
|
12
|
+
/** The SVG of one letter (square, transparent). */
|
|
13
|
+
export function letterSvg(letter, color = "#111111", outline = "#ffffff") {
|
|
14
|
+
const stroke = outline ? ` stroke="${escape(outline)}" stroke-width="7" paint-order="stroke" stroke-linejoin="round"` : "";
|
|
15
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><text x="50" y="52" text-anchor="middle" dominant-baseline="central" font-family="Arial, Helvetica, sans-serif" font-weight="700" font-size="78" fill="${escape(color)}"${stroke}>${escape(letter)}</text></svg>`;
|
|
16
|
+
}
|
|
17
|
+
const same = (a, b) => a.every((v, i) => Math.abs(v - b[i]) < 1e-9);
|
|
18
|
+
const minus = (a, b) => a.map((v, i) => v - b[i]);
|
|
19
|
+
const neg = (a) => a.map((v) => -v);
|
|
20
|
+
export function letterSkin(base, options = {}) {
|
|
21
|
+
const scheme = options.scheme ?? RUWIX;
|
|
22
|
+
// Held position of each sticker (a solved cube turned as held), and how its face is turned for the holder.
|
|
23
|
+
const rot = options.rotation ? applyMoves(solvedState(), options.rotation) : solvedState();
|
|
24
|
+
const heldAt = new Map();
|
|
25
|
+
rot.forEach((sticker, pos) => heldAt.set(sticker, pos));
|
|
26
|
+
const decals = [];
|
|
27
|
+
for (const f of FACELETS) {
|
|
28
|
+
const held = heldAt.get(f.index);
|
|
29
|
+
const letter = letterAt(scheme, held);
|
|
30
|
+
if (!letter)
|
|
31
|
+
continue;
|
|
32
|
+
// The sticker's own "right" (centre → right edge of its face), as seen on the held face.
|
|
33
|
+
const face = Math.floor(f.index / 9) * 9;
|
|
34
|
+
const right = minus(FACELETS[heldAt.get(face + 5)].pos, FACELETS[heldAt.get(face + 4)].pos);
|
|
35
|
+
const { a, b } = FACE_BASIS[FACELETS[held].face];
|
|
36
|
+
const rotate = same(right, a) ? 0 : same(right, b) ? 3 : same(right, neg(a)) ? 2 : 1;
|
|
37
|
+
decals.push({
|
|
38
|
+
select: { stickers: [f.index] },
|
|
39
|
+
rotate,
|
|
40
|
+
image: letterSvg(letter, options.color, options.outline === undefined ? "#ffffff" : options.outline),
|
|
41
|
+
size: options.size ?? 0.62,
|
|
42
|
+
onlyRegular: !options.alwaysShow,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
return { ...base, decals: [...(base.decals ?? []), ...decals] };
|
|
46
|
+
}
|
package/bld/tracker.d.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Following an Old Pochmann execution on a smart cube, letter by letter.
|
|
3
|
+
*
|
|
4
|
+
* A letter counts as done when that kind of piece is exactly where the swap
|
|
5
|
+
* leaves it: edges after an edge letter, corners after a corner letter. Only
|
|
6
|
+
* one kind is compared — the swap algorithms move a couple of pieces of the
|
|
7
|
+
* other kind on the side (T-perm two corners, Y-perm two edges), which the
|
|
8
|
+
* next letters / parity put back. Any algorithm doing the same swap counts.
|
|
9
|
+
*
|
|
10
|
+
* Parity (odd letters): edges first — after the edges the two corners the
|
|
11
|
+
* T-perms moved are still swapped; the parity step is done when the corners
|
|
12
|
+
* are back as they were at the start. Corners first (`order: "corners"`) —
|
|
13
|
+
* the same the other way round: after the corners the two edges the Y-perms
|
|
14
|
+
* moved are swapped; parity is done when the edges are back.
|
|
15
|
+
*
|
|
16
|
+
* A swap with a different letter than the one due is reported (`wrong`).
|
|
17
|
+
*/
|
|
18
|
+
import { type Move, type State } from "../core/index.js";
|
|
19
|
+
import { type Memo, type MemoOptions } from "./memo.js";
|
|
20
|
+
export type BldStepKind = "edge" | "parity" | "corner";
|
|
21
|
+
export interface BldStep {
|
|
22
|
+
kind: BldStepKind;
|
|
23
|
+
/** The letter ("" for parity). */
|
|
24
|
+
letter: string;
|
|
25
|
+
/** Sticker position shot to (-1 for parity). */
|
|
26
|
+
target: number;
|
|
27
|
+
}
|
|
28
|
+
export interface BldProgress {
|
|
29
|
+
steps: BldStep[];
|
|
30
|
+
/** Steps done (the next one to do is steps[done]). */
|
|
31
|
+
done: number;
|
|
32
|
+
status: ("done" | "current" | "todo")[];
|
|
33
|
+
/** The last finished swap went to another letter than the one due. */
|
|
34
|
+
wrong: {
|
|
35
|
+
expected: string;
|
|
36
|
+
got: string;
|
|
37
|
+
kind: BldStepKind;
|
|
38
|
+
} | null;
|
|
39
|
+
/** Every letter done. */
|
|
40
|
+
complete: boolean;
|
|
41
|
+
/** The cube is solved. */
|
|
42
|
+
solved: boolean;
|
|
43
|
+
}
|
|
44
|
+
export interface BldTrackerOptions extends MemoOptions {
|
|
45
|
+
/** Which kind is solved first. Default "edges". */
|
|
46
|
+
order?: "edges" | "corners";
|
|
47
|
+
}
|
|
48
|
+
export declare class BldTracker {
|
|
49
|
+
readonly memo: Memo;
|
|
50
|
+
readonly steps: BldStep[];
|
|
51
|
+
private readonly checkpoints;
|
|
52
|
+
private readonly start;
|
|
53
|
+
private readonly held;
|
|
54
|
+
private readonly toHeld;
|
|
55
|
+
private readonly options;
|
|
56
|
+
private cur;
|
|
57
|
+
private doneCount;
|
|
58
|
+
private wrong;
|
|
59
|
+
constructor(start: State, options?: BldTrackerOptions);
|
|
60
|
+
get state(): State;
|
|
61
|
+
/** A face turn from the cube. */
|
|
62
|
+
push(move: Move): BldProgress;
|
|
63
|
+
/** A swap with another letter than the due one, just finished? */
|
|
64
|
+
private wrongSwap;
|
|
65
|
+
/** Where that kind of piece should be before the due step. */
|
|
66
|
+
private previousOfKind;
|
|
67
|
+
get progress(): BldProgress;
|
|
68
|
+
}
|
package/bld/tracker.js
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Following an Old Pochmann execution on a smart cube, letter by letter.
|
|
3
|
+
*
|
|
4
|
+
* A letter counts as done when that kind of piece is exactly where the swap
|
|
5
|
+
* leaves it: edges after an edge letter, corners after a corner letter. Only
|
|
6
|
+
* one kind is compared — the swap algorithms move a couple of pieces of the
|
|
7
|
+
* other kind on the side (T-perm two corners, Y-perm two edges), which the
|
|
8
|
+
* next letters / parity put back. Any algorithm doing the same swap counts.
|
|
9
|
+
*
|
|
10
|
+
* Parity (odd letters): edges first — after the edges the two corners the
|
|
11
|
+
* T-perms moved are still swapped; the parity step is done when the corners
|
|
12
|
+
* are back as they were at the start. Corners first (`order: "corners"`) —
|
|
13
|
+
* the same the other way round: after the corners the two edges the Y-perms
|
|
14
|
+
* moved are swapped; parity is done when the edges are back.
|
|
15
|
+
*
|
|
16
|
+
* A swap with a different letter than the one due is reported (`wrong`).
|
|
17
|
+
*/
|
|
18
|
+
import { applyMove, applyMoves, isSolved, solvedState } from "../core/index.js";
|
|
19
|
+
import { asHeld } from "./memo.js";
|
|
20
|
+
import { CORNER_BUFFER, CORNER_POSITIONS, EDGE_BUFFER, EDGE_POSITIONS, samePiece as sameKindPiece, memo, samePieces, swapToTarget } from "./memo.js";
|
|
21
|
+
import { RUWIX } from "./scheme.js";
|
|
22
|
+
export class BldTracker {
|
|
23
|
+
memo;
|
|
24
|
+
steps;
|
|
25
|
+
checkpoints;
|
|
26
|
+
start;
|
|
27
|
+
held;
|
|
28
|
+
toHeld;
|
|
29
|
+
options;
|
|
30
|
+
cur;
|
|
31
|
+
doneCount = 0;
|
|
32
|
+
wrong = null;
|
|
33
|
+
constructor(start, options = {}) {
|
|
34
|
+
const scheme = options.scheme ?? RUWIX;
|
|
35
|
+
// Work as held: moves are turned into the held frame as they come.
|
|
36
|
+
this.held = asHeld(start, options.rotation);
|
|
37
|
+
this.memo = memo(this.held);
|
|
38
|
+
this.start = this.held;
|
|
39
|
+
this.cur = new Uint8Array(this.held);
|
|
40
|
+
this.toHeld = heldMoveMap(options.rotation);
|
|
41
|
+
this.steps = [];
|
|
42
|
+
this.checkpoints = [];
|
|
43
|
+
const edges = () => {
|
|
44
|
+
let s = new Uint8Array(this.held);
|
|
45
|
+
this.memo.edgeTargets.forEach((t, i) => {
|
|
46
|
+
s = swapToTarget(s, "edge", t, options.edgeBuffer);
|
|
47
|
+
this.steps.push({ kind: "edge", letter: this.memo.edges[i], target: t });
|
|
48
|
+
this.checkpoints.push({ positions: EDGE_POSITIONS, state: s });
|
|
49
|
+
});
|
|
50
|
+
};
|
|
51
|
+
const corners = () => {
|
|
52
|
+
let c = new Uint8Array(this.held);
|
|
53
|
+
this.memo.cornerTargets.forEach((t, i) => {
|
|
54
|
+
c = swapToTarget(c, "corner", t, options.cornerBuffer);
|
|
55
|
+
this.steps.push({ kind: "corner", letter: this.memo.corners[i], target: t });
|
|
56
|
+
this.checkpoints.push({ positions: CORNER_POSITIONS, state: c });
|
|
57
|
+
});
|
|
58
|
+
};
|
|
59
|
+
// Parity: the other kind — moved on the side by the first kind's swaps — back as it was.
|
|
60
|
+
const parity = (other) => {
|
|
61
|
+
if (!this.memo.parity)
|
|
62
|
+
return;
|
|
63
|
+
this.steps.push({ kind: "parity", letter: "", target: -1 });
|
|
64
|
+
this.checkpoints.push({ positions: other, state: new Uint8Array(this.held) });
|
|
65
|
+
};
|
|
66
|
+
if (options.order === "corners") {
|
|
67
|
+
corners();
|
|
68
|
+
parity(EDGE_POSITIONS);
|
|
69
|
+
edges();
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
edges();
|
|
73
|
+
parity(CORNER_POSITIONS);
|
|
74
|
+
corners();
|
|
75
|
+
}
|
|
76
|
+
this.options = { ...options, scheme };
|
|
77
|
+
}
|
|
78
|
+
get state() {
|
|
79
|
+
return this.cur;
|
|
80
|
+
}
|
|
81
|
+
/** A face turn from the cube. */
|
|
82
|
+
push(move) {
|
|
83
|
+
this.cur = applyMove(this.cur, this.toHeld(move));
|
|
84
|
+
// Done with the next step (or several: e.g. parity needs nothing when the corners came back on their own).
|
|
85
|
+
while (this.doneCount < this.steps.length) {
|
|
86
|
+
const cp = this.checkpoints[this.doneCount];
|
|
87
|
+
if (!samePieces(this.cur, cp.state, cp.positions))
|
|
88
|
+
break;
|
|
89
|
+
this.doneCount++;
|
|
90
|
+
this.wrong = null;
|
|
91
|
+
}
|
|
92
|
+
this.wrong = this.doneCount < this.steps.length ? this.wrongSwap() : null;
|
|
93
|
+
return this.progress;
|
|
94
|
+
}
|
|
95
|
+
/** A swap with another letter than the due one, just finished? */
|
|
96
|
+
wrongSwap() {
|
|
97
|
+
const step = this.steps[this.doneCount];
|
|
98
|
+
if (step.kind === "parity")
|
|
99
|
+
return null;
|
|
100
|
+
const prev = this.previousOfKind(step.kind);
|
|
101
|
+
const positions = step.kind === "edge" ? EDGE_POSITIONS : CORNER_POSITIONS;
|
|
102
|
+
const letters = step.kind === "edge" ? this.options.scheme.edges : this.options.scheme.corners;
|
|
103
|
+
for (const [pos, letter] of Object.entries(letters)) {
|
|
104
|
+
const t = Number(pos);
|
|
105
|
+
if (t === step.target || sameKindPiece(step.kind, t, step.kind === "edge" ? (this.options.edgeBuffer ?? EDGE_BUFFER) : (this.options.cornerBuffer ?? CORNER_BUFFER)))
|
|
106
|
+
continue;
|
|
107
|
+
const buffer = step.kind === "edge" ? this.options.edgeBuffer : this.options.cornerBuffer;
|
|
108
|
+
const after = swapToTarget(prev, step.kind, t, buffer);
|
|
109
|
+
if (samePieces(this.cur, after, positions) && !samePieces(after, prev, positions))
|
|
110
|
+
return { expected: step.letter, got: letter, kind: step.kind };
|
|
111
|
+
}
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
/** Where that kind of piece should be before the due step. */
|
|
115
|
+
previousOfKind(kind) {
|
|
116
|
+
for (let i = this.doneCount - 1; i >= 0; i--)
|
|
117
|
+
if (this.steps[i].kind === kind)
|
|
118
|
+
return this.checkpoints[i].state;
|
|
119
|
+
return this.start;
|
|
120
|
+
}
|
|
121
|
+
get progress() {
|
|
122
|
+
return {
|
|
123
|
+
steps: this.steps,
|
|
124
|
+
done: this.doneCount,
|
|
125
|
+
status: this.steps.map((_, i) => (i < this.doneCount ? "done" : i === this.doneCount ? "current" : "todo")),
|
|
126
|
+
wrong: this.wrong,
|
|
127
|
+
complete: this.doneCount === this.steps.length,
|
|
128
|
+
solved: isSolved(this.cur),
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* A face turn of the cube (its own faces) as the face it is for the holder:
|
|
134
|
+
* the cube's R is the held L after y2, and so on.
|
|
135
|
+
*/
|
|
136
|
+
function heldMoveMap(rotation) {
|
|
137
|
+
if (!rotation)
|
|
138
|
+
return (m) => m;
|
|
139
|
+
const rot = applyMoves(solvedState(), rotation);
|
|
140
|
+
const faceAt = new Map();
|
|
141
|
+
for (let pos = 0; pos < 6; pos++)
|
|
142
|
+
faceAt.set(FACE_NAMES[(rot[pos * 9 + 4] / 9) | 0], FACE_NAMES[pos]);
|
|
143
|
+
return (m) => (faceAt.has(m.family) ? { ...m, family: faceAt.get(m.family) } : m);
|
|
144
|
+
}
|
|
145
|
+
const FACE_NAMES = "URFDLB";
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Move times you can trust. Browsers deliver Bluetooth notifications late
|
|
3
|
+
* and in bursts (a backgrounded tab, a busy main thread), and moves a GAN
|
|
4
|
+
* cube re-sends after a dropped packet arrive with no local time at all —
|
|
5
|
+
* so "when the notification arrived" can be seconds off. Cubes that report
|
|
6
|
+
* their own clock (GAN gen2+ and others) let us do better: map the cube's
|
|
7
|
+
* clock onto the page's (`performance.now()`) with the smallest observed
|
|
8
|
+
* difference, i.e. the least delayed notification.
|
|
9
|
+
*/
|
|
10
|
+
export declare class ClockSync {
|
|
11
|
+
private readonly window;
|
|
12
|
+
private samples;
|
|
13
|
+
/** @param window how many recent samples the offset is estimated from (the cube clock drifts slowly). */
|
|
14
|
+
constructor(window?: number);
|
|
15
|
+
/** Offset (local − cube) estimated so far, or null before the first sample. */
|
|
16
|
+
get offset(): number | null;
|
|
17
|
+
/** Feed one event: its cube time and, if it has one, the local time it arrived. */
|
|
18
|
+
observe(cubeTime: number, localTime: number | null): void;
|
|
19
|
+
/** The cube time on the page's clock (null until an offset is known). */
|
|
20
|
+
toLocal(cubeTime: number): number | null;
|
|
21
|
+
reset(): void;
|
|
22
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Move times you can trust. Browsers deliver Bluetooth notifications late
|
|
3
|
+
* and in bursts (a backgrounded tab, a busy main thread), and moves a GAN
|
|
4
|
+
* cube re-sends after a dropped packet arrive with no local time at all —
|
|
5
|
+
* so "when the notification arrived" can be seconds off. Cubes that report
|
|
6
|
+
* their own clock (GAN gen2+ and others) let us do better: map the cube's
|
|
7
|
+
* clock onto the page's (`performance.now()`) with the smallest observed
|
|
8
|
+
* difference, i.e. the least delayed notification.
|
|
9
|
+
*/
|
|
10
|
+
export class ClockSync {
|
|
11
|
+
window;
|
|
12
|
+
samples = [];
|
|
13
|
+
/** @param window how many recent samples the offset is estimated from (the cube clock drifts slowly). */
|
|
14
|
+
constructor(window = 64) {
|
|
15
|
+
this.window = window;
|
|
16
|
+
}
|
|
17
|
+
/** Offset (local − cube) estimated so far, or null before the first sample. */
|
|
18
|
+
get offset() {
|
|
19
|
+
return this.samples.length ? Math.min(...this.samples) : null;
|
|
20
|
+
}
|
|
21
|
+
/** Feed one event: its cube time and, if it has one, the local time it arrived. */
|
|
22
|
+
observe(cubeTime, localTime) {
|
|
23
|
+
if (localTime === null)
|
|
24
|
+
return;
|
|
25
|
+
this.samples.push(localTime - cubeTime);
|
|
26
|
+
if (this.samples.length > this.window)
|
|
27
|
+
this.samples.shift();
|
|
28
|
+
}
|
|
29
|
+
/** The cube time on the page's clock (null until an offset is known). */
|
|
30
|
+
toLocal(cubeTime) {
|
|
31
|
+
const o = this.offset;
|
|
32
|
+
return o === null ? null : cubeTime + o;
|
|
33
|
+
}
|
|
34
|
+
reset() {
|
|
35
|
+
this.samples = [];
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which skin suits the connected cube. Rules match the protocol id and the
|
|
3
|
+
* device / hardware name the cube reports; the first match wins, so rules an
|
|
4
|
+
* app registers (e.g. skins made for particular models) go before the
|
|
5
|
+
* built-in defaults:
|
|
6
|
+
*
|
|
7
|
+
* GAN → gan
|
|
8
|
+
* QiYi QY-SC (name QY-QYSC…) → qiyiSC
|
|
9
|
+
* MoYu (name WCU_MY…) → moyu
|
|
10
|
+
* anything else → default (stickerless, as most smart cubes are)
|
|
11
|
+
*
|
|
12
|
+
* Only looks are chosen here — nothing depends on it.
|
|
13
|
+
*/
|
|
14
|
+
import { SKINS, type Skin } from "../skin/index.js";
|
|
15
|
+
export interface CubeSkinRule {
|
|
16
|
+
/** Protocol id (smartcube-web-bluetooth: "gan-gen4", "moyu32", "qiyi"…) — string = prefix, or a RegExp. */
|
|
17
|
+
protocol?: string | RegExp;
|
|
18
|
+
/** Device or hardware name (as the cube advertises it). */
|
|
19
|
+
name?: RegExp;
|
|
20
|
+
skin: Skin | keyof typeof SKINS;
|
|
21
|
+
}
|
|
22
|
+
/** Add a rule ahead of the built-in ones (later registrations win over earlier ones). */
|
|
23
|
+
export declare function registerCubeSkin(rule: CubeSkinRule): void;
|
|
24
|
+
/** The skin for a cube: its protocol and the names it reported. */
|
|
25
|
+
export declare function skinForCube(cube: {
|
|
26
|
+
protocol: {
|
|
27
|
+
id: string;
|
|
28
|
+
};
|
|
29
|
+
name?: string;
|
|
30
|
+
hardwareName?: string;
|
|
31
|
+
}): Skin;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which skin suits the connected cube. Rules match the protocol id and the
|
|
3
|
+
* device / hardware name the cube reports; the first match wins, so rules an
|
|
4
|
+
* app registers (e.g. skins made for particular models) go before the
|
|
5
|
+
* built-in defaults:
|
|
6
|
+
*
|
|
7
|
+
* GAN → gan
|
|
8
|
+
* QiYi QY-SC (name QY-QYSC…) → qiyiSC
|
|
9
|
+
* MoYu (name WCU_MY…) → moyu
|
|
10
|
+
* anything else → default (stickerless, as most smart cubes are)
|
|
11
|
+
*
|
|
12
|
+
* Only looks are chosen here — nothing depends on it.
|
|
13
|
+
*/
|
|
14
|
+
import { SKINS } from "../skin/index.js";
|
|
15
|
+
const test = (m, value) => m === undefined || (typeof m === "string" ? value.startsWith(m) : m.test(value));
|
|
16
|
+
const BUILT_IN = [
|
|
17
|
+
{ protocol: "gan", skin: "gan" },
|
|
18
|
+
{ protocol: "qiyi", name: /^QY-QYSC/i, skin: "qiyiSC" },
|
|
19
|
+
{ protocol: "moyu", name: /^WCU_MY/i, skin: "moyu" },
|
|
20
|
+
{ skin: "default" },
|
|
21
|
+
];
|
|
22
|
+
const registered = [];
|
|
23
|
+
/** Add a rule ahead of the built-in ones (later registrations win over earlier ones). */
|
|
24
|
+
export function registerCubeSkin(rule) {
|
|
25
|
+
registered.unshift(rule);
|
|
26
|
+
}
|
|
27
|
+
/** The skin for a cube: its protocol and the names it reported. */
|
|
28
|
+
export function skinForCube(cube) {
|
|
29
|
+
const names = [cube.name ?? "", cube.hardwareName ?? ""];
|
|
30
|
+
const rule = [...registered, ...BUILT_IN].find((r) => test(r.protocol, cube.protocol.id) && (!r.name || names.some((n) => r.name.test(n))));
|
|
31
|
+
return typeof rule.skin === "string" ? SKINS[rule.skin] : rule.skin;
|
|
32
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gyroscope → renderer orientation. A smart cube reports its rotation as a
|
|
3
|
+
* quaternion in its own axes; the renderer (`CubeRenderer.setOrientation`)
|
|
4
|
+
* wants one in ours (x right, y up, z towards you) where identity = the cube
|
|
5
|
+
* as drawn. `calibrate` (or the first reading) says "the cube is held as
|
|
6
|
+
* shown right now".
|
|
7
|
+
*/
|
|
8
|
+
export interface Quat {
|
|
9
|
+
x: number;
|
|
10
|
+
y: number;
|
|
11
|
+
z: number;
|
|
12
|
+
w: number;
|
|
13
|
+
}
|
|
14
|
+
export declare const IDENTITY: Quat;
|
|
15
|
+
export declare function multiply(a: Quat, b: Quat): Quat;
|
|
16
|
+
export declare const conjugate: (q: Quat) => Quat;
|
|
17
|
+
export declare function normalize(q: Quat): Quat;
|
|
18
|
+
/** A cube's gyro axes → ours. GAN cubes: (x, z, −y). */
|
|
19
|
+
export type AxisMap = (q: Quat) => Quat;
|
|
20
|
+
export declare const GAN_AXES: AxisMap;
|
|
21
|
+
export declare class GyroCalibrator {
|
|
22
|
+
private readonly axes;
|
|
23
|
+
private basis;
|
|
24
|
+
private last;
|
|
25
|
+
constructor(axes?: AxisMap);
|
|
26
|
+
/** A raw reading → the orientation to show (identity at calibration). The first reading calibrates. */
|
|
27
|
+
orientation(raw: Quat): Quat;
|
|
28
|
+
/** "Held as shown now": the latest reading becomes identity. */
|
|
29
|
+
calibrate(): void;
|
|
30
|
+
reset(): void;
|
|
31
|
+
}
|