@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/core/cubies.js
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The cube as pieces: corner / edge permutation and orientation in the
|
|
3
|
+
* standard (Kociemba) numbering, plus how the centres are turned (a Frame).
|
|
4
|
+
* The compact state codec and the two-phase solver work on this; everything
|
|
5
|
+
* else in cubecore uses the 54-sticker State.
|
|
6
|
+
*
|
|
7
|
+
* Corners URF UFL ULB UBR DFR DLF DBL DRB, edges UR UF UL UB DR DF DL DB FR FL
|
|
8
|
+
* BL BR. Corner twist counts how far the U/D sticker is turned from the U/D
|
|
9
|
+
* position (0..2, clockwise); an edge is flipped (1) when its first sticker —
|
|
10
|
+
* the U/D one, or F/B for the middle-layer edges — isn't in the first place.
|
|
11
|
+
*/
|
|
12
|
+
import { FRAMES, view } from "./frames.js";
|
|
13
|
+
/** Facelets of each corner position, U/D sticker first, then clockwise. */
|
|
14
|
+
export const CORNER_FACELETS = [
|
|
15
|
+
[8, 9, 20], [6, 18, 38], [0, 36, 47], [2, 45, 11],
|
|
16
|
+
[29, 26, 15], [27, 44, 24], [33, 53, 42], [35, 17, 51],
|
|
17
|
+
];
|
|
18
|
+
/** Facelets of each edge position, the reference sticker first. */
|
|
19
|
+
export const EDGE_FACELETS = [
|
|
20
|
+
[5, 10], [7, 19], [3, 37], [1, 46], [32, 16], [28, 25],
|
|
21
|
+
[30, 43], [34, 52], [23, 12], [21, 41], [50, 39], [48, 14],
|
|
22
|
+
];
|
|
23
|
+
const centresHome = (s) => [0, 1, 2, 3, 4, 5].every((f) => s[f * 9 + 4] === f * 9 + 4);
|
|
24
|
+
/** Pieces of a state, or null if it isn't made of real pieces. */
|
|
25
|
+
export function toCubies(state) {
|
|
26
|
+
const frame = FRAMES.find((f) => centresHome(view(state, f)));
|
|
27
|
+
if (!frame)
|
|
28
|
+
return null;
|
|
29
|
+
const s = view(state, frame);
|
|
30
|
+
const cp = [], co = [], ep = [], eo = [];
|
|
31
|
+
for (const pos of CORNER_FACELETS) {
|
|
32
|
+
const st = pos.map((f) => s[f]);
|
|
33
|
+
const j = CORNER_FACELETS.findIndex((home) => home.includes(st[0]));
|
|
34
|
+
if (j < 0)
|
|
35
|
+
return null;
|
|
36
|
+
const home = CORNER_FACELETS[j];
|
|
37
|
+
const k = st.indexOf(home[0]);
|
|
38
|
+
if (k < 0 || st[(k + 1) % 3] !== home[1] || st[(k + 2) % 3] !== home[2])
|
|
39
|
+
return null;
|
|
40
|
+
cp.push(j);
|
|
41
|
+
co.push(k);
|
|
42
|
+
}
|
|
43
|
+
for (const pos of EDGE_FACELETS) {
|
|
44
|
+
const st = pos.map((f) => s[f]);
|
|
45
|
+
const j = EDGE_FACELETS.findIndex((home) => home.includes(st[0]));
|
|
46
|
+
if (j < 0)
|
|
47
|
+
return null;
|
|
48
|
+
const home = EDGE_FACELETS[j];
|
|
49
|
+
const flip = st[0] === home[0] ? 0 : 1;
|
|
50
|
+
if (st[1 - flip] !== home[1])
|
|
51
|
+
return null;
|
|
52
|
+
ep.push(j);
|
|
53
|
+
eo.push(flip);
|
|
54
|
+
}
|
|
55
|
+
if (new Set(cp).size !== 8 || new Set(ep).size !== 12)
|
|
56
|
+
return null;
|
|
57
|
+
return { frame, cp, co, ep, eo };
|
|
58
|
+
}
|
|
59
|
+
export function fromCubies(c) {
|
|
60
|
+
const s = new Uint8Array(54);
|
|
61
|
+
for (let f = 0; f < 6; f++)
|
|
62
|
+
s[f * 9 + 4] = f * 9 + 4;
|
|
63
|
+
CORNER_FACELETS.forEach((pos, i) => {
|
|
64
|
+
for (let n = 0; n < 3; n++)
|
|
65
|
+
s[pos[(n + c.co[i]) % 3]] = CORNER_FACELETS[c.cp[i]][n];
|
|
66
|
+
});
|
|
67
|
+
EDGE_FACELETS.forEach((pos, i) => {
|
|
68
|
+
for (let n = 0; n < 2; n++)
|
|
69
|
+
s[pos[(n + c.eo[i]) % 2]] = EDGE_FACELETS[c.ep[i]][n];
|
|
70
|
+
});
|
|
71
|
+
// `s` is the state seen with centres at home; put it back into the cube's own positions.
|
|
72
|
+
const state = new Uint8Array(54);
|
|
73
|
+
for (let i = 0; i < 54; i++)
|
|
74
|
+
state[c.frame.map[i]] = s[i];
|
|
75
|
+
return state;
|
|
76
|
+
}
|
|
77
|
+
/** Parity of a permutation (0 even, 1 odd). */
|
|
78
|
+
export function parity(p) {
|
|
79
|
+
let odd = 0;
|
|
80
|
+
for (let i = 0; i < p.length; i++)
|
|
81
|
+
for (let j = i + 1; j < p.length; j++)
|
|
82
|
+
if (p[i] > p[j])
|
|
83
|
+
odd ^= 1;
|
|
84
|
+
return odd;
|
|
85
|
+
}
|
|
86
|
+
/** Reachable by turning (not a reassembled cube): twist sum ≡ 0 mod 3, flips even, equal parities. */
|
|
87
|
+
export function isSolvable(c) {
|
|
88
|
+
return c.co.reduce((a, b) => a + b, 0) % 3 === 0 && c.eo.reduce((a, b) => a + b, 0) % 2 === 0 && parity(c.cp) === parity(c.ep);
|
|
89
|
+
}
|
|
90
|
+
// ─── ranks (for coordinates and the codec) ───
|
|
91
|
+
/** Lehmer rank of a permutation of 0..n-1: 0 .. n!-1. */
|
|
92
|
+
export function permRank(p) {
|
|
93
|
+
let r = 0;
|
|
94
|
+
const n = p.length;
|
|
95
|
+
for (let i = 0; i < n; i++) {
|
|
96
|
+
let smaller = 0;
|
|
97
|
+
for (let j = i + 1; j < n; j++)
|
|
98
|
+
if (p[j] < p[i])
|
|
99
|
+
smaller++;
|
|
100
|
+
r = r * (n - i) + smaller;
|
|
101
|
+
}
|
|
102
|
+
return r;
|
|
103
|
+
}
|
|
104
|
+
export function permUnrank(rank, n) {
|
|
105
|
+
const digits = new Array(n).fill(0);
|
|
106
|
+
for (let i = n - 1; i >= 0; i--) {
|
|
107
|
+
digits[i] = rank % (n - i);
|
|
108
|
+
rank = Math.floor(rank / (n - i));
|
|
109
|
+
}
|
|
110
|
+
const pool = Array.from({ length: n }, (_, i) => i);
|
|
111
|
+
return digits.map((d) => pool.splice(d, 1)[0]);
|
|
112
|
+
}
|
|
113
|
+
// ─── facelet strings ───
|
|
114
|
+
const FACE_LETTERS = "URFDLB";
|
|
115
|
+
/**
|
|
116
|
+
* A state from a 54-letter facelet string (Kociemba order U1…U9 R1… F1… D1…
|
|
117
|
+
* L1… B1…, each letter the face whose colour is there) — what smart cubes
|
|
118
|
+
* report and many tools exchange. Colours are read against the centres, so
|
|
119
|
+
* any letters work as long as each centre has its own. Null if the string
|
|
120
|
+
* isn't a reachable cube.
|
|
121
|
+
*/
|
|
122
|
+
export function stateFromFacelets(text) {
|
|
123
|
+
if (text.length !== 54)
|
|
124
|
+
return null;
|
|
125
|
+
const centreOf = new Map();
|
|
126
|
+
for (let f = 0; f < 6; f++)
|
|
127
|
+
centreOf.set(text[f * 9 + 4], f);
|
|
128
|
+
if (centreOf.size !== 6)
|
|
129
|
+
return null;
|
|
130
|
+
const colour = (i) => centreOf.get(text[i]);
|
|
131
|
+
const cls = (facelet) => Math.floor(facelet / 9); // home colour class of a facelet
|
|
132
|
+
const cp = [], co = [], ep = [], eo = [];
|
|
133
|
+
for (const pos of CORNER_FACELETS) {
|
|
134
|
+
const cols = pos.map(colour);
|
|
135
|
+
let found = false;
|
|
136
|
+
for (let j = 0; j < 8 && !found; j++) {
|
|
137
|
+
const home = CORNER_FACELETS[j].map(cls);
|
|
138
|
+
for (let k = 0; k < 3; k++) {
|
|
139
|
+
if (cols[k] === home[0] && cols[(k + 1) % 3] === home[1] && cols[(k + 2) % 3] === home[2]) {
|
|
140
|
+
cp.push(j);
|
|
141
|
+
co.push(k);
|
|
142
|
+
found = true;
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (!found)
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
for (const pos of EDGE_FACELETS) {
|
|
151
|
+
const cols = pos.map(colour);
|
|
152
|
+
const j = EDGE_FACELETS.findIndex((home) => {
|
|
153
|
+
const h = home.map(cls);
|
|
154
|
+
return (cols[0] === h[0] && cols[1] === h[1]) || (cols[0] === h[1] && cols[1] === h[0]);
|
|
155
|
+
});
|
|
156
|
+
if (j < 0)
|
|
157
|
+
return null;
|
|
158
|
+
ep.push(j);
|
|
159
|
+
eo.push(cols[0] === cls(EDGE_FACELETS[j][0]) ? 0 : 1);
|
|
160
|
+
}
|
|
161
|
+
const c = { frame: FRAMES[0], cp, co, ep, eo };
|
|
162
|
+
if (new Set(cp).size !== 8 || new Set(ep).size !== 12 || !isSolvable(c))
|
|
163
|
+
return null;
|
|
164
|
+
return fromCubies(c);
|
|
165
|
+
}
|
|
166
|
+
/** The 54-letter facelet string of a state (each letter = the colour class's home face). */
|
|
167
|
+
export function faceletsOf(state) {
|
|
168
|
+
return Array.from(state, (sticker) => FACE_LETTERS[Math.floor(sticker / 9)]).join("");
|
|
169
|
+
}
|
package/core/frames.d.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Frames — the 24 ways to hold a cube — and "views": a state re-indexed so
|
|
3
|
+
* that a chosen frame looks like the canonical orientation.
|
|
4
|
+
*
|
|
5
|
+
* Every method check in cubecore is written once, for the canonical frame
|
|
6
|
+
* (e.g. "the cross is on D, the Roux first block is on L"). To be colour and
|
|
7
|
+
* orientation neutral, the checks run on `view(state, frame)` for each frame:
|
|
8
|
+
* a frame whose view passes tells you WHERE the cross / block actually is.
|
|
9
|
+
* Colours are never compared to fixed values — only to each other and to
|
|
10
|
+
* centres — so any colour scheme works.
|
|
11
|
+
*/
|
|
12
|
+
import { type Face, type Mat3 } from "./geometry.js";
|
|
13
|
+
import type { State } from "./state.js";
|
|
14
|
+
import type { Move } from "./moves.js";
|
|
15
|
+
export interface Frame {
|
|
16
|
+
/** Stable id 0..23 (0 = identity). */
|
|
17
|
+
id: number;
|
|
18
|
+
/** Physical face that plays the canonical `face` in this frame. */
|
|
19
|
+
face: Record<Face, Face>;
|
|
20
|
+
/** view[i] = state[map[i]] — canonical facelet i ↔ physical facelet map[i]. */
|
|
21
|
+
map: Uint8Array;
|
|
22
|
+
matrix: Mat3;
|
|
23
|
+
}
|
|
24
|
+
export declare const FRAMES: readonly Frame[];
|
|
25
|
+
export declare const IDENTITY_FRAME: Frame;
|
|
26
|
+
/** The state as seen from `frame` — canonical checks can run on it directly. */
|
|
27
|
+
export declare function view(state: State, frame: Frame): State;
|
|
28
|
+
/**
|
|
29
|
+
* The state as if the cube were held in `frame`, with sticker identities
|
|
30
|
+
* renamed to match — so a solved cube is the identity again, and anything
|
|
31
|
+
* that reads pieces (toCubies, solvers, codecs) sees the frame's D as D.
|
|
32
|
+
* `view` keeps the physical sticker names; use it for colour checks.
|
|
33
|
+
*/
|
|
34
|
+
export declare function reframe(state: State, frame: Frame): State;
|
|
35
|
+
/** Inverse of `reframe`: a state described in `frame` back in the cube's own positions and names. */
|
|
36
|
+
export declare function unreframe(state: State, frame: Frame): State;
|
|
37
|
+
/** Short human label, e.g. "D-bottom, F-front" style: canonical D and F mapped to physical faces. */
|
|
38
|
+
export declare function frameLabel(frame: Frame): string;
|
|
39
|
+
/** Frames whose canonical `face` sits on the physical face `physical`. */
|
|
40
|
+
export declare function framesWith(face: Face, physical: Face): Frame[];
|
|
41
|
+
/**
|
|
42
|
+
* The frame that holds physical face `bottom` down (and `front` towards you,
|
|
43
|
+
* if given): e.g. `frameFor("U")` looks at a cube whose cross was built on U
|
|
44
|
+
* as if it were on D, so canonical masks / checks apply.
|
|
45
|
+
*/
|
|
46
|
+
export declare function frameFor(bottom: Face, front?: Face): Frame;
|
|
47
|
+
/**
|
|
48
|
+
* Colour neutral: the frame with the face whose centre shows colour class
|
|
49
|
+
* `bottomColor` down (and `frontColor` in front). Colour classes are home
|
|
50
|
+
* faces (0..5 = U R F D L B, or the letters): with the western scheme
|
|
51
|
+
* `frameForColors(state, "D")` = yellow down, whatever the scheme's colours.
|
|
52
|
+
* Uses the centres of `state`, so it works after rotations and slices.
|
|
53
|
+
*/
|
|
54
|
+
export declare function frameForColors(state: State, bottomColor: Face | number, frontColor?: Face | number): Frame;
|
|
55
|
+
/**
|
|
56
|
+
* The same physical motion described from another orientation: a move written
|
|
57
|
+
* for the canonical frame, performed on a cube held as `frame`. E.g. with the
|
|
58
|
+
* frame that puts canonical D on physical F, "R U R'" (cross on D) becomes the
|
|
59
|
+
* moves that do the same thing to a cross on F.
|
|
60
|
+
*/
|
|
61
|
+
export declare function transformMoves(moves: readonly Move[], frame: Frame): Move[];
|
package/core/frames.js
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Frames — the 24 ways to hold a cube — and "views": a state re-indexed so
|
|
3
|
+
* that a chosen frame looks like the canonical orientation.
|
|
4
|
+
*
|
|
5
|
+
* Every method check in cubecore is written once, for the canonical frame
|
|
6
|
+
* (e.g. "the cross is on D, the Roux first block is on L"). To be colour and
|
|
7
|
+
* orientation neutral, the checks run on `view(state, frame)` for each frame:
|
|
8
|
+
* a frame whose view passes tells you WHERE the cross / block actually is.
|
|
9
|
+
* Colours are never compared to fixed values — only to each other and to
|
|
10
|
+
* centres — so any colour scheme works.
|
|
11
|
+
*/
|
|
12
|
+
import { FACELETS, FACELET_COUNT, FACES, IDENTITY, apply, faceOfNormal, faceletAt, multiply, rotationMatrix, FACE_NORMAL } from "./geometry.js";
|
|
13
|
+
function buildFrames() {
|
|
14
|
+
const matrices = [IDENTITY];
|
|
15
|
+
const seen = new Set([IDENTITY.flat().join(",")]);
|
|
16
|
+
for (let i = 0; i < matrices.length; i++) {
|
|
17
|
+
for (const axis of [0, 1, 2]) {
|
|
18
|
+
const m = multiply(rotationMatrix(axis, 1), matrices[i]);
|
|
19
|
+
const k = m.flat().join(",");
|
|
20
|
+
if (!seen.has(k)) {
|
|
21
|
+
seen.add(k);
|
|
22
|
+
matrices.push(m);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return matrices.map((matrix, id) => {
|
|
27
|
+
const map = new Uint8Array(FACELET_COUNT);
|
|
28
|
+
for (const f of FACELETS)
|
|
29
|
+
map[f.index] = faceletAt(apply(matrix, f.pos), apply(matrix, f.normal));
|
|
30
|
+
const face = Object.fromEntries(FACES.map((fc) => [fc, faceOfNormal(apply(matrix, FACE_NORMAL[fc]))]));
|
|
31
|
+
return { id, face, map, matrix };
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
export const FRAMES = buildFrames();
|
|
35
|
+
export const IDENTITY_FRAME = FRAMES[0];
|
|
36
|
+
/** The state as seen from `frame` — canonical checks can run on it directly. */
|
|
37
|
+
export function view(state, frame) {
|
|
38
|
+
const out = new Uint8Array(FACELET_COUNT);
|
|
39
|
+
for (let i = 0; i < FACELET_COUNT; i++)
|
|
40
|
+
out[i] = state[frame.map[i]];
|
|
41
|
+
return out;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The state as if the cube were held in `frame`, with sticker identities
|
|
45
|
+
* renamed to match — so a solved cube is the identity again, and anything
|
|
46
|
+
* that reads pieces (toCubies, solvers, codecs) sees the frame's D as D.
|
|
47
|
+
* `view` keeps the physical sticker names; use it for colour checks.
|
|
48
|
+
*/
|
|
49
|
+
export function reframe(state, frame) {
|
|
50
|
+
if (frame === IDENTITY_FRAME)
|
|
51
|
+
return new Uint8Array(state);
|
|
52
|
+
const rename = new Uint8Array(FACELET_COUNT);
|
|
53
|
+
for (let c = 0; c < FACELET_COUNT; c++)
|
|
54
|
+
rename[frame.map[c]] = c;
|
|
55
|
+
const out = new Uint8Array(FACELET_COUNT);
|
|
56
|
+
for (let i = 0; i < FACELET_COUNT; i++)
|
|
57
|
+
out[i] = rename[state[frame.map[i]]];
|
|
58
|
+
return out;
|
|
59
|
+
}
|
|
60
|
+
/** Inverse of `reframe`: a state described in `frame` back in the cube's own positions and names. */
|
|
61
|
+
export function unreframe(state, frame) {
|
|
62
|
+
if (frame === IDENTITY_FRAME)
|
|
63
|
+
return new Uint8Array(state);
|
|
64
|
+
const out = new Uint8Array(FACELET_COUNT);
|
|
65
|
+
for (let i = 0; i < FACELET_COUNT; i++)
|
|
66
|
+
out[frame.map[i]] = frame.map[state[i]];
|
|
67
|
+
return out;
|
|
68
|
+
}
|
|
69
|
+
/** Short human label, e.g. "D-bottom, F-front" style: canonical D and F mapped to physical faces. */
|
|
70
|
+
export function frameLabel(frame) {
|
|
71
|
+
return `${frame.face.D}${frame.face.F}`;
|
|
72
|
+
}
|
|
73
|
+
/** Frames whose canonical `face` sits on the physical face `physical`. */
|
|
74
|
+
export function framesWith(face, physical) {
|
|
75
|
+
return FRAMES.filter((f) => f.face[face] === physical);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* The frame that holds physical face `bottom` down (and `front` towards you,
|
|
79
|
+
* if given): e.g. `frameFor("U")` looks at a cube whose cross was built on U
|
|
80
|
+
* as if it were on D, so canonical masks / checks apply.
|
|
81
|
+
*/
|
|
82
|
+
export function frameFor(bottom, front) {
|
|
83
|
+
const f = FRAMES.find((fr) => fr.face.D === bottom && (!front || fr.face.F === front));
|
|
84
|
+
if (!f)
|
|
85
|
+
throw new Error(`No frame with ${bottom} down and ${front} in front`);
|
|
86
|
+
return f;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Colour neutral: the frame with the face whose centre shows colour class
|
|
90
|
+
* `bottomColor` down (and `frontColor` in front). Colour classes are home
|
|
91
|
+
* faces (0..5 = U R F D L B, or the letters): with the western scheme
|
|
92
|
+
* `frameForColors(state, "D")` = yellow down, whatever the scheme's colours.
|
|
93
|
+
* Uses the centres of `state`, so it works after rotations and slices.
|
|
94
|
+
*/
|
|
95
|
+
export function frameForColors(state, bottomColor, frontColor) {
|
|
96
|
+
const cls = (c) => (typeof c === "number" ? c : FACES.indexOf(c));
|
|
97
|
+
const faceWith = (c) => FACES.find((f) => Math.floor(state[FACES.indexOf(f) * 9 + 4] / 9) === c);
|
|
98
|
+
return frameFor(faceWith(cls(bottomColor)), frontColor === undefined ? undefined : faceWith(cls(frontColor)));
|
|
99
|
+
}
|
|
100
|
+
// ─── Re-expressing moves in another frame ───
|
|
101
|
+
const FACE_FAMILY = { U: "U", R: "R", F: "F", D: "D", L: "L", B: "B" };
|
|
102
|
+
const WIDE_FAMILY = { U: "u", R: "r", F: "f", D: "d", L: "l", B: "b" };
|
|
103
|
+
/** Slice / rotation named by the face it turns like, plus whether that's inverted. */
|
|
104
|
+
const SLICE_BY_FACE = { L: ["M", false], R: ["M", true], D: ["E", false], U: ["E", true], F: ["S", false], B: ["S", true] };
|
|
105
|
+
const ROTATION_BY_FACE = { R: ["x", false], L: ["x", true], U: ["y", false], D: ["y", true], F: ["z", false], B: ["z", true] };
|
|
106
|
+
const FACE_OF_FAMILY = { U: "U", R: "R", F: "F", D: "D", L: "L", B: "B", u: "U", r: "R", f: "F", d: "D", l: "L", b: "B" };
|
|
107
|
+
const FOLLOWS = { M: "L", E: "D", S: "F", x: "R", y: "U", z: "F" };
|
|
108
|
+
const inv = (a) => (a === 2 ? 2 : -a);
|
|
109
|
+
/**
|
|
110
|
+
* The same physical motion described from another orientation: a move written
|
|
111
|
+
* for the canonical frame, performed on a cube held as `frame`. E.g. with the
|
|
112
|
+
* frame that puts canonical D on physical F, "R U R'" (cross on D) becomes the
|
|
113
|
+
* moves that do the same thing to a cross on F.
|
|
114
|
+
*/
|
|
115
|
+
export function transformMoves(moves, frame) {
|
|
116
|
+
return moves.map((m) => {
|
|
117
|
+
const face = FACE_OF_FAMILY[m.family];
|
|
118
|
+
if (face) {
|
|
119
|
+
const target = frame.face[face];
|
|
120
|
+
return { family: (m.family === m.family.toUpperCase() ? FACE_FAMILY : WIDE_FAMILY)[target], amount: m.amount };
|
|
121
|
+
}
|
|
122
|
+
const follows = FOLLOWS[m.family];
|
|
123
|
+
const [family, flipped] = (m.family === "x" || m.family === "y" || m.family === "z" ? ROTATION_BY_FACE : SLICE_BY_FACE)[frame.face[follows]];
|
|
124
|
+
return { family, amount: flipped ? inv(m.amount) : m.amount };
|
|
125
|
+
});
|
|
126
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cube geometry — facelet positions in 3D, computed once. Every move, frame
|
|
3
|
+
* and block definition is derived from these positions instead of hand-typed
|
|
4
|
+
* permutation tables, so the tables can't contain typos.
|
|
5
|
+
*
|
|
6
|
+
* Coordinates: x → right, y → up, z → towards the viewer (front).
|
|
7
|
+
* Faces are ordered U R F D L B (Kociemba order); facelet index =
|
|
8
|
+
* face * 9 + row * 3 + col, rows/cols as seen when looking straight at the
|
|
9
|
+
* face in the usual net (U with F below it, D with F above it, the four side
|
|
10
|
+
* faces with U on top).
|
|
11
|
+
*/
|
|
12
|
+
export type Face = "U" | "R" | "F" | "D" | "L" | "B";
|
|
13
|
+
export declare const FACES: readonly Face[];
|
|
14
|
+
export type Vec3 = readonly [number, number, number];
|
|
15
|
+
export declare const FACE_NORMAL: Record<Face, Vec3>;
|
|
16
|
+
/**
|
|
17
|
+
* Tangent axes per face, as seen when looking straight at the face in the
|
|
18
|
+
* usual net: a = right, b = up, a × b = n (outward normal).
|
|
19
|
+
*/
|
|
20
|
+
export declare const FACE_BASIS: Record<Face, {
|
|
21
|
+
a: Vec3;
|
|
22
|
+
b: Vec3;
|
|
23
|
+
n: Vec3;
|
|
24
|
+
}>;
|
|
25
|
+
export interface Facelet {
|
|
26
|
+
index: number;
|
|
27
|
+
face: Face;
|
|
28
|
+
/** Position of the cubie this sticker is on. */
|
|
29
|
+
pos: Vec3;
|
|
30
|
+
/** Outward normal (the direction the sticker faces). */
|
|
31
|
+
normal: Vec3;
|
|
32
|
+
}
|
|
33
|
+
export declare const FACELETS: readonly Facelet[];
|
|
34
|
+
export declare const FACELET_COUNT = 54;
|
|
35
|
+
/** Facelet index at a cubie position facing `normal`, or -1. */
|
|
36
|
+
export declare function faceletAt(pos: Vec3, normal: Vec3): number;
|
|
37
|
+
/** Index of the centre facelet of a face. */
|
|
38
|
+
export declare const centerOf: (face: Face) => number;
|
|
39
|
+
export declare function faceOfNormal(n: Vec3): Face;
|
|
40
|
+
export type Axis = 0 | 1 | 2;
|
|
41
|
+
/** Rotate by `q` counter-clockwise quarter turns (any integer) about +axis. */
|
|
42
|
+
export declare function rotate(v: Vec3, axis: Axis, q: number): Vec3;
|
|
43
|
+
/** 3×3 integer matrix (rows) — used for cube orientations (frames). */
|
|
44
|
+
export type Mat3 = readonly [Vec3, Vec3, Vec3];
|
|
45
|
+
export declare const IDENTITY: Mat3;
|
|
46
|
+
export declare function apply(m: Mat3, v: Vec3): Vec3;
|
|
47
|
+
export declare function rotationMatrix(axis: Axis, q: number): Mat3;
|
|
48
|
+
export declare function multiply(a: Mat3, b: Mat3): Mat3;
|
|
49
|
+
export type CubieKind = "corner" | "edge" | "center";
|
|
50
|
+
export interface Cubie {
|
|
51
|
+
pos: Vec3;
|
|
52
|
+
kind: CubieKind;
|
|
53
|
+
/** Facelet indices on this cubie. */
|
|
54
|
+
facelets: number[];
|
|
55
|
+
}
|
|
56
|
+
export declare const CUBIES: readonly Cubie[];
|
|
57
|
+
/** The cubie a facelet position belongs to. */
|
|
58
|
+
export declare const CUBIE_OF_FACELET: readonly Cubie[];
|
package/core/geometry.js
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cube geometry — facelet positions in 3D, computed once. Every move, frame
|
|
3
|
+
* and block definition is derived from these positions instead of hand-typed
|
|
4
|
+
* permutation tables, so the tables can't contain typos.
|
|
5
|
+
*
|
|
6
|
+
* Coordinates: x → right, y → up, z → towards the viewer (front).
|
|
7
|
+
* Faces are ordered U R F D L B (Kociemba order); facelet index =
|
|
8
|
+
* face * 9 + row * 3 + col, rows/cols as seen when looking straight at the
|
|
9
|
+
* face in the usual net (U with F below it, D with F above it, the four side
|
|
10
|
+
* faces with U on top).
|
|
11
|
+
*/
|
|
12
|
+
export const FACES = ["U", "R", "F", "D", "L", "B"];
|
|
13
|
+
export const FACE_NORMAL = {
|
|
14
|
+
U: [0, 1, 0],
|
|
15
|
+
R: [1, 0, 0],
|
|
16
|
+
F: [0, 0, 1],
|
|
17
|
+
D: [0, -1, 0],
|
|
18
|
+
L: [-1, 0, 0],
|
|
19
|
+
B: [0, 0, -1],
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Tangent axes per face, as seen when looking straight at the face in the
|
|
23
|
+
* usual net: a = right, b = up, a × b = n (outward normal).
|
|
24
|
+
*/
|
|
25
|
+
export const FACE_BASIS = {
|
|
26
|
+
U: { a: [1, 0, 0], b: [0, 0, -1], n: [0, 1, 0] },
|
|
27
|
+
D: { a: [1, 0, 0], b: [0, 0, 1], n: [0, -1, 0] },
|
|
28
|
+
F: { a: [1, 0, 0], b: [0, 1, 0], n: [0, 0, 1] },
|
|
29
|
+
B: { a: [-1, 0, 0], b: [0, 1, 0], n: [0, 0, -1] },
|
|
30
|
+
R: { a: [0, 0, -1], b: [0, 1, 0], n: [1, 0, 0] },
|
|
31
|
+
L: { a: [0, 0, 1], b: [0, 1, 0], n: [-1, 0, 0] },
|
|
32
|
+
};
|
|
33
|
+
/** Cubie position of facelet (row, col) on `face`. */
|
|
34
|
+
function faceletPosition(face, row, col) {
|
|
35
|
+
switch (face) {
|
|
36
|
+
case "U":
|
|
37
|
+
return [col - 1, 1, row - 1];
|
|
38
|
+
case "R":
|
|
39
|
+
return [1, 1 - row, 1 - col];
|
|
40
|
+
case "F":
|
|
41
|
+
return [col - 1, 1 - row, 1];
|
|
42
|
+
case "D":
|
|
43
|
+
return [col - 1, -1, 1 - row];
|
|
44
|
+
case "L":
|
|
45
|
+
return [-1, 1 - row, col - 1];
|
|
46
|
+
case "B":
|
|
47
|
+
return [1 - col, 1 - row, -1];
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
export const FACELETS = FACES.flatMap((face, f) => Array.from({ length: 9 }, (_, i) => ({
|
|
51
|
+
index: f * 9 + i,
|
|
52
|
+
face,
|
|
53
|
+
pos: faceletPosition(face, Math.floor(i / 3), i % 3),
|
|
54
|
+
normal: FACE_NORMAL[face],
|
|
55
|
+
})));
|
|
56
|
+
export const FACELET_COUNT = 54;
|
|
57
|
+
const key = (pos, normal) => `${pos.join(",")}|${normal.join(",")}`;
|
|
58
|
+
const BY_POS_NORMAL = new Map(FACELETS.map((f) => [key(f.pos, f.normal), f.index]));
|
|
59
|
+
/** Facelet index at a cubie position facing `normal`, or -1. */
|
|
60
|
+
export function faceletAt(pos, normal) {
|
|
61
|
+
return BY_POS_NORMAL.get(key(pos, normal)) ?? -1;
|
|
62
|
+
}
|
|
63
|
+
/** Index of the centre facelet of a face. */
|
|
64
|
+
export const centerOf = (face) => FACES.indexOf(face) * 9 + 4;
|
|
65
|
+
export function faceOfNormal(n) {
|
|
66
|
+
return FACES.find((f) => FACE_NORMAL[f].every((c, i) => c === n[i]));
|
|
67
|
+
}
|
|
68
|
+
/** One counter-clockwise quarter turn about the +axis (right-hand rule). */
|
|
69
|
+
function quarter(v, axis) {
|
|
70
|
+
const [x, y, z] = v;
|
|
71
|
+
switch (axis) {
|
|
72
|
+
case 0:
|
|
73
|
+
return [x, -z, y];
|
|
74
|
+
case 1:
|
|
75
|
+
return [z, y, -x];
|
|
76
|
+
case 2:
|
|
77
|
+
return [-y, x, z];
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/** Rotate by `q` counter-clockwise quarter turns (any integer) about +axis. */
|
|
81
|
+
export function rotate(v, axis, q) {
|
|
82
|
+
let out = v;
|
|
83
|
+
for (let i = 0; i < ((q % 4) + 4) % 4; i++)
|
|
84
|
+
out = quarter(out, axis);
|
|
85
|
+
return out;
|
|
86
|
+
}
|
|
87
|
+
export const IDENTITY = [
|
|
88
|
+
[1, 0, 0],
|
|
89
|
+
[0, 1, 0],
|
|
90
|
+
[0, 0, 1],
|
|
91
|
+
];
|
|
92
|
+
export function apply(m, v) {
|
|
93
|
+
return [
|
|
94
|
+
m[0][0] * v[0] + m[0][1] * v[1] + m[0][2] * v[2],
|
|
95
|
+
m[1][0] * v[0] + m[1][1] * v[1] + m[1][2] * v[2],
|
|
96
|
+
m[2][0] * v[0] + m[2][1] * v[1] + m[2][2] * v[2],
|
|
97
|
+
];
|
|
98
|
+
}
|
|
99
|
+
export function rotationMatrix(axis, q) {
|
|
100
|
+
const cols = [rotate([1, 0, 0], axis, q), rotate([0, 1, 0], axis, q), rotate([0, 0, 1], axis, q)];
|
|
101
|
+
return [
|
|
102
|
+
[cols[0][0], cols[1][0], cols[2][0]],
|
|
103
|
+
[cols[0][1], cols[1][1], cols[2][1]],
|
|
104
|
+
[cols[0][2], cols[1][2], cols[2][2]],
|
|
105
|
+
];
|
|
106
|
+
}
|
|
107
|
+
export function multiply(a, b) {
|
|
108
|
+
const row = (i) => [0, 1, 2].map((j) => a[i][0] * b[0][j] + a[i][1] * b[1][j] + a[i][2] * b[2][j]);
|
|
109
|
+
return [row(0), row(1), row(2)];
|
|
110
|
+
}
|
|
111
|
+
export const CUBIES = (() => {
|
|
112
|
+
const map = new Map();
|
|
113
|
+
for (const f of FACELETS) {
|
|
114
|
+
const k = f.pos.join(",");
|
|
115
|
+
let c = map.get(k);
|
|
116
|
+
if (!c) {
|
|
117
|
+
const nonZero = f.pos.filter((v) => v !== 0).length;
|
|
118
|
+
c = { pos: f.pos, kind: nonZero === 3 ? "corner" : nonZero === 2 ? "edge" : "center", facelets: [] };
|
|
119
|
+
map.set(k, c);
|
|
120
|
+
}
|
|
121
|
+
c.facelets.push(f.index);
|
|
122
|
+
}
|
|
123
|
+
return [...map.values()];
|
|
124
|
+
})();
|
|
125
|
+
/** The cubie a facelet position belongs to. */
|
|
126
|
+
export const CUBIE_OF_FACELET = (() => {
|
|
127
|
+
const out = [];
|
|
128
|
+
for (const c of CUBIES)
|
|
129
|
+
for (const f of c.facelets)
|
|
130
|
+
out[f] = c;
|
|
131
|
+
return out;
|
|
132
|
+
})();
|
package/core/index.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export * from "./geometry.js";
|
|
2
|
+
export * from "./moves.js";
|
|
3
|
+
export * from "./notation.js";
|
|
4
|
+
export * from "./state.js";
|
|
5
|
+
export * from "./frames.js";
|
|
6
|
+
export * as checks from "./checks.js";
|
|
7
|
+
export * from "./method.js";
|
|
8
|
+
export * from "./tracker.js";
|
|
9
|
+
export * from "./masks.js";
|
|
10
|
+
export * from "./centers.js";
|
|
11
|
+
export * from "./physical.js";
|
|
12
|
+
export * from "./stream.js";
|
|
13
|
+
export * from "./bytes.js";
|
|
14
|
+
export * from "./cubies.js";
|
|
15
|
+
export * from "./stateCodec.js";
|
|
16
|
+
export * from "./sequence.js";
|
|
17
|
+
export * from "./practice.js";
|
|
18
|
+
export * from "./arrows.js";
|
|
19
|
+
export * from "./stages.js";
|
package/core/index.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export * from "./geometry.js";
|
|
2
|
+
export * from "./moves.js";
|
|
3
|
+
export * from "./notation.js";
|
|
4
|
+
export * from "./state.js";
|
|
5
|
+
export * from "./frames.js";
|
|
6
|
+
export * as checks from "./checks.js";
|
|
7
|
+
export * from "./method.js";
|
|
8
|
+
export * from "./tracker.js";
|
|
9
|
+
export * from "./masks.js";
|
|
10
|
+
export * from "./centers.js";
|
|
11
|
+
export * from "./physical.js";
|
|
12
|
+
export * from "./stream.js";
|
|
13
|
+
export * from "./bytes.js";
|
|
14
|
+
export * from "./cubies.js";
|
|
15
|
+
export * from "./stateCodec.js";
|
|
16
|
+
export * from "./sequence.js";
|
|
17
|
+
export * from "./practice.js";
|
|
18
|
+
export * from "./arrows.js";
|
|
19
|
+
export * from "./stages.js";
|
package/core/masks.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Masks — which stickers matter. A mask gives each STICKER (by its home
|
|
3
|
+
* facelet, i.e. piece identity) a display state, so it follows the pieces as
|
|
4
|
+
* they move: masking "the cross edges" keeps highlighting them wherever they
|
|
5
|
+
* are, which is what a "watch these pieces" view needs.
|
|
6
|
+
*
|
|
7
|
+
* Masks are defined for the canonical frame (cross on D, last layer on U, Roux
|
|
8
|
+
* blocks on L/R) and mapped to any frame, so they are colour neutral too:
|
|
9
|
+
* `buildMask(CFOP_MASKS.oll, frame)` masks the last layer of a cube solved
|
|
10
|
+
* with the cross on any face.
|
|
11
|
+
*/
|
|
12
|
+
import { type Cubie, type Facelet } from "./geometry.js";
|
|
13
|
+
import { type Frame } from "./frames.js";
|
|
14
|
+
import type { State } from "./state.js";
|
|
15
|
+
/**
|
|
16
|
+
* regular: full colour · dim: darkened colour (context) · ignored: grey (not
|
|
17
|
+
* relevant) · oriented: one "this sticker matters for orientation" colour ·
|
|
18
|
+
* invisible: not drawn.
|
|
19
|
+
*/
|
|
20
|
+
export type MaskState = "regular" | "dim" | "ignored" | "oriented" | "invisible";
|
|
21
|
+
export declare const MASK_STATES: readonly MaskState[];
|
|
22
|
+
/** Per home facelet (piece identity) → index into MASK_STATES. */
|
|
23
|
+
export type Mask = Uint8Array;
|
|
24
|
+
/** Rule over the CANONICAL home facelet and its cubie. */
|
|
25
|
+
export type MaskRule = (facelet: Facelet, cubie: Cubie) => MaskState;
|
|
26
|
+
export declare function buildMask(rule: MaskRule, frame?: Frame): Mask;
|
|
27
|
+
export declare const fullMask: () => Mask;
|
|
28
|
+
/** Display state of whatever sticker currently sits at facelet position `position`. */
|
|
29
|
+
export declare function maskStateAt(mask: Mask, state: State, position: number): MaskState;
|
|
30
|
+
export type MaskPreset = "full" | "first-layer" | "ll" | "centers" | "void";
|
|
31
|
+
export declare const MASK_PRESETS: MaskPreset[];
|
|
32
|
+
export declare function presetMask(preset: MaskPreset, frame?: Frame): Mask;
|
package/core/masks.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Masks — which stickers matter. A mask gives each STICKER (by its home
|
|
3
|
+
* facelet, i.e. piece identity) a display state, so it follows the pieces as
|
|
4
|
+
* they move: masking "the cross edges" keeps highlighting them wherever they
|
|
5
|
+
* are, which is what a "watch these pieces" view needs.
|
|
6
|
+
*
|
|
7
|
+
* Masks are defined for the canonical frame (cross on D, last layer on U, Roux
|
|
8
|
+
* blocks on L/R) and mapped to any frame, so they are colour neutral too:
|
|
9
|
+
* `buildMask(CFOP_MASKS.oll, frame)` masks the last layer of a cube solved
|
|
10
|
+
* with the cross on any face.
|
|
11
|
+
*/
|
|
12
|
+
import { CUBIE_OF_FACELET, FACELETS, FACELET_COUNT } from "./geometry.js";
|
|
13
|
+
import { IDENTITY_FRAME } from "./frames.js";
|
|
14
|
+
export const MASK_STATES = ["regular", "dim", "ignored", "oriented", "invisible"];
|
|
15
|
+
const code = (s) => MASK_STATES.indexOf(s);
|
|
16
|
+
export function buildMask(rule, frame = IDENTITY_FRAME) {
|
|
17
|
+
const mask = new Uint8Array(FACELET_COUNT);
|
|
18
|
+
for (const f of FACELETS)
|
|
19
|
+
mask[frame.map[f.index]] = code(rule(f, CUBIE_OF_FACELET[f.index]));
|
|
20
|
+
return mask;
|
|
21
|
+
}
|
|
22
|
+
export const fullMask = () => new Uint8Array(FACELET_COUNT); // all "regular" (code 0)
|
|
23
|
+
/** Display state of whatever sticker currently sits at facelet position `position`. */
|
|
24
|
+
export function maskStateAt(mask, state, position) {
|
|
25
|
+
return MASK_STATES[mask[state[position]]];
|
|
26
|
+
}
|
|
27
|
+
const RULES = {
|
|
28
|
+
full: () => "regular",
|
|
29
|
+
"first-layer": (f, c) => (c.kind === "center" || c.pos[1] === -1 ? "regular" : "ignored"),
|
|
30
|
+
ll: () => "regular",
|
|
31
|
+
/** Only the centres in colour — orientation practice, colour-neutral recognition. */
|
|
32
|
+
centers: (f, c) => (c.kind === "center" ? "regular" : "ignored"),
|
|
33
|
+
/** Void cube: no centres. */
|
|
34
|
+
void: (f, c) => (c.kind === "center" ? "invisible" : "regular"),
|
|
35
|
+
};
|
|
36
|
+
export const MASK_PRESETS = Object.keys(RULES);
|
|
37
|
+
export function presetMask(preset, frame = IDENTITY_FRAME) {
|
|
38
|
+
return buildMask(RULES[preset], frame);
|
|
39
|
+
}
|