@wodzik/cubecore 0.1.8 → 0.1.10
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/bluetooth/index.d.ts +3 -0
- package/bluetooth/index.js +3 -0
- package/bluetooth/rotations.d.ts +5 -1
- package/bluetooth/rotations.js +1 -1
- package/bluetooth/timer.d.ts +10 -0
- package/bluetooth/timer.js +9 -0
- package/core/method.d.ts +2 -0
- package/core/method.js +3 -1
- package/core/tracker.js +1 -1
- package/package.json +1 -1
- package/roux/index.d.ts +3 -4
- package/roux/index.js +60 -13
package/bluetooth/index.d.ts
CHANGED
|
@@ -5,3 +5,6 @@ export * from "./simulated.js";
|
|
|
5
5
|
export * from "./cubeSkins.js";
|
|
6
6
|
export * from "./grips.js";
|
|
7
7
|
export * from "./rotations.js";
|
|
8
|
+
export * from "./timer.js";
|
|
9
|
+
/** The MAC a cube's connection found (kept per device — QiYi's handshake needs it; offer it back when asking the user for one). */
|
|
10
|
+
export { getCachedMacForDevice, removeCachedMacForDevice } from "./vendor/smartcube-web-bluetooth/smartcube/attachment/address-hints.js";
|
package/bluetooth/index.js
CHANGED
|
@@ -5,3 +5,6 @@ export * from "./simulated.js";
|
|
|
5
5
|
export * from "./cubeSkins.js";
|
|
6
6
|
export * from "./grips.js";
|
|
7
7
|
export * from "./rotations.js";
|
|
8
|
+
export * from "./timer.js";
|
|
9
|
+
/** The MAC a cube's connection found (kept per device — QiYi's handshake needs it; offer it back when asking the user for one). */
|
|
10
|
+
export { getCachedMacForDevice, removeCachedMacForDevice } from "./vendor/smartcube-web-bluetooth/smartcube/attachment/address-hints.js";
|
package/bluetooth/rotations.d.ts
CHANGED
|
@@ -67,7 +67,11 @@ export interface HeldOptions {
|
|
|
67
67
|
* always a U / D turn and a y regrip.
|
|
68
68
|
*/
|
|
69
69
|
wide?: readonly string[];
|
|
70
|
-
/**
|
|
70
|
+
/**
|
|
71
|
+
* Slices to read from both faces of an axis and the core's rotation
|
|
72
|
+
* (default M, S: an E — U D' and a y — is as likely a U / D turn and a
|
|
73
|
+
* regrip as u and d); the rest stay two face turns and a rotation.
|
|
74
|
+
*/
|
|
71
75
|
slices?: readonly string[];
|
|
72
76
|
}
|
|
73
77
|
export declare const DEFAULT_WIDE: readonly string[];
|
package/bluetooth/rotations.js
CHANGED
|
@@ -29,7 +29,7 @@ export function heldMove(move, grip) {
|
|
|
29
29
|
return canonical ? canonical + move.slice(1) : move;
|
|
30
30
|
}
|
|
31
31
|
export const DEFAULT_WIDE = ["r", "l", "f", "b"];
|
|
32
|
-
export const DEFAULT_SLICES = ["M", "
|
|
32
|
+
export const DEFAULT_SLICES = ["M", "S"];
|
|
33
33
|
export function heldTokens(moves, startRotation, rotations, options = {}) {
|
|
34
34
|
const allowed = new Set([...(options.wide ?? DEFAULT_WIDE), ...(options.slices ?? DEFAULT_SLICES)]);
|
|
35
35
|
let grip = rotateGrip(IDENTITY_GRIP, startRotation);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The GAN smart timer (Halo / Smart Timer) over Bluetooth — hands on / get
|
|
3
|
+
* set / running / stopped, and the recorded times — from the vendored
|
|
4
|
+
* smartcube-web-bluetooth.
|
|
5
|
+
*
|
|
6
|
+
* const timer = await connectGanTimer(); // needs a user gesture
|
|
7
|
+
* timer.events$.subscribe((e) => { if (e.state === GanTimerState.STOPPED) … });
|
|
8
|
+
*/
|
|
9
|
+
export { connectGanTimer, GanTimerState, makeTime, makeTimeFromTimestamp } from "./vendor/smartcube-web-bluetooth/gan-smart-timer.js";
|
|
10
|
+
export type { GanTimerConnection, GanTimerEvent, GanTimerTime, GanTimerRecordedTimes } from "./vendor/smartcube-web-bluetooth/gan-smart-timer.js";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The GAN smart timer (Halo / Smart Timer) over Bluetooth — hands on / get
|
|
3
|
+
* set / running / stopped, and the recorded times — from the vendored
|
|
4
|
+
* smartcube-web-bluetooth.
|
|
5
|
+
*
|
|
6
|
+
* const timer = await connectGanTimer(); // needs a user gesture
|
|
7
|
+
* timer.events$.subscribe((e) => { if (e.state === GanTimerState.STOPPED) … });
|
|
8
|
+
*/
|
|
9
|
+
export { connectGanTimer, GanTimerState, makeTime, makeTimeFromTimestamp } from "./vendor/smartcube-web-bluetooth/gan-smart-timer.js";
|
package/core/method.d.ts
CHANGED
|
@@ -14,6 +14,8 @@ export interface Stage {
|
|
|
14
14
|
done(s: State): boolean;
|
|
15
15
|
/** Canonical slot / sub-step finished at this stage, given the details already reported. */
|
|
16
16
|
detail?(s: State, earlier: readonly string[]): string | undefined;
|
|
17
|
+
/** The detail is absolute (colours, "edges"…), not a canonical slot to translate into physical faces. */
|
|
18
|
+
absoluteDetail?: boolean;
|
|
17
19
|
/**
|
|
18
20
|
* The case this stage starts from (e.g. "OLL 27", "T", "OLL skip"),
|
|
19
21
|
* recognised on the state the previous stage finished in.
|
package/core/method.js
CHANGED
|
@@ -19,7 +19,9 @@ export function countedStages(prefix, label, count, guard) {
|
|
|
19
19
|
id: `${prefix}-${n}`,
|
|
20
20
|
label: `${label} ${n}`,
|
|
21
21
|
done: (s) => guard(s) && count(s).length >= n,
|
|
22
|
-
|
|
22
|
+
// Only this group's own earlier details (the n − 1 just before): another
|
|
23
|
+
// group's may name the same slots (LBL: corners, then edges, per slot).
|
|
24
|
+
detail: (s, earlier) => newItem(count(s), earlier.slice(earlier.length - (n - 1))),
|
|
23
25
|
}));
|
|
24
26
|
}
|
|
25
27
|
/** Orient, permute, AUF — the last layer of CFOP, ZZ and Petrus. */
|
package/core/tracker.js
CHANGED
|
@@ -99,7 +99,7 @@ export class MethodTracker {
|
|
|
99
99
|
stage: stage.id,
|
|
100
100
|
moveIndex: this.moveCount,
|
|
101
101
|
...(time !== undefined ? { time } : {}),
|
|
102
|
-
...(raw ? { detail: physicalSlot(raw, p.frame) } : {}),
|
|
102
|
+
...(raw ? { detail: stage.absoluteDetail ? raw : physicalSlot(raw, p.frame) } : {}),
|
|
103
103
|
...(kase ? { case: kase } : {}),
|
|
104
104
|
});
|
|
105
105
|
p.stageIndex++;
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.1.
|
|
6
|
+
"version": "0.1.10",
|
|
7
7
|
"description": "A 3×3×3 cube library for the web, built around smart (Bluetooth) cubes: state, notation, methods, solvers and scrambles, a three.js renderer with skins, pictures, web components, React bindings.",
|
|
8
8
|
"license": "MPL-2.0",
|
|
9
9
|
"author": "Tomasz Wodzikowski <wodziszczakow@gmail.com>",
|
package/roux/index.d.ts
CHANGED
|
@@ -9,8 +9,7 @@
|
|
|
9
9
|
import { type Frame, type Mask, type MaskRule, type Method, type LseStageDef, type StageDef, type State } from "../core/index.js";
|
|
10
10
|
export * from "./cmll.js";
|
|
11
11
|
export declare const firstBlock: (s: State) => boolean;
|
|
12
|
-
export declare
|
|
13
|
-
/** CMLL: both blocks, and the top corners solved relative to them (up to AUF). */
|
|
12
|
+
export declare const secondBlock: (s: State) => boolean;
|
|
14
13
|
export declare const cmll: (s: State) => boolean;
|
|
15
14
|
/**
|
|
16
15
|
* LSE edge orientation: every last-six edge has its top/bottom-coloured
|
|
@@ -18,9 +17,9 @@ export declare const cmll: (s: State) => boolean;
|
|
|
18
17
|
* (the top / bottom centres on F / B), the edges in the M slice count by
|
|
19
18
|
* F / B instead (one M puts them right, flips and all).
|
|
20
19
|
*/
|
|
21
|
-
export declare
|
|
20
|
+
export declare const lseEo: (s: State) => boolean;
|
|
22
21
|
/** LSE 4b: UL and UR edges in place, with the corners aligned. */
|
|
23
|
-
export declare
|
|
22
|
+
export declare const ulUr: (s: State) => boolean;
|
|
24
23
|
export declare const ROUX: Method;
|
|
25
24
|
export type RouxMask = "fb" | "blocks" | "cmll" | "lse" | "eo" | "ulur";
|
|
26
25
|
export declare const ROUX_MASKS: Record<RouxMask, MaskRule>;
|
package/roux/index.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* pieces to each other (and to the L/R centres, which M never moves) — still
|
|
7
7
|
* no absolute colours, so any scheme and any orientation works.
|
|
8
8
|
*/
|
|
9
|
-
import { FACELETS, IDENTITY_FRAME, PIECE, buildMask, checks as C, colorAt, } from "../core/index.js";
|
|
9
|
+
import { applyMove, parseAlg, FACELETS, IDENTITY_FRAME, PIECE, buildMask, checks as C, colorAt, } from "../core/index.js";
|
|
10
10
|
import { recognizeCmll } from "./cmll.js";
|
|
11
11
|
export * from "./cmll.js";
|
|
12
12
|
const { cubieAt, faceletOn, centerIdx, opposite, upToAuf } = C;
|
|
@@ -39,12 +39,33 @@ function rouxBlock(s, side) {
|
|
|
39
39
|
return null;
|
|
40
40
|
return { bottom, front, back };
|
|
41
41
|
}
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
/**
|
|
43
|
+
* Across M: the state turned about the L–R axis, 0–3 quarters — the first
|
|
44
|
+
* turn where `pick` finds something, and what. A smart cube reports its
|
|
45
|
+
* moves relative to its centres, so an M (R L' as it reports it) turns
|
|
46
|
+
* everything but the centres: the blocks end up a quarter or two off the
|
|
47
|
+
* bottom as it sees them. The checks here only compare with the L / R
|
|
48
|
+
* centres (which M never moves) and pieces with each other, so the turned
|
|
49
|
+
* state reads the same as a cube whose M slice was never turned.
|
|
50
|
+
*/
|
|
51
|
+
const X = parseAlg("x")[0];
|
|
52
|
+
function acrossM(s, pick) {
|
|
53
|
+
let t = s;
|
|
54
|
+
for (let k = 0; k < 4; k++) {
|
|
55
|
+
const v = pick(t);
|
|
56
|
+
if (v !== null && v !== false)
|
|
57
|
+
return v;
|
|
58
|
+
t = applyMove(t, X);
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
function bothBlocks(s) {
|
|
44
63
|
const fb = rouxBlock(s, "L");
|
|
45
64
|
const sb = rouxBlock(s, "R");
|
|
46
|
-
return fb !== null && sb !== null && fb.bottom === sb.bottom && fb.front === sb.front && fb.back === sb.back;
|
|
65
|
+
return fb !== null && sb !== null && fb.bottom === sb.bottom && fb.front === sb.front && fb.back === sb.back ? fb : null;
|
|
47
66
|
}
|
|
67
|
+
export const firstBlock = (s) => acrossM(s, (t) => rouxBlock(t, "L")) !== null;
|
|
68
|
+
export const secondBlock = (s) => acrossM(s, bothBlocks) !== null;
|
|
48
69
|
const CMLL_CORNERS = { ufl: cubieAt(-1, 1, 1), ubl: cubieAt(-1, 1, -1), ufr: cubieAt(1, 1, 1), ubr: cubieAt(1, 1, -1) };
|
|
49
70
|
function cmllAligned(s) {
|
|
50
71
|
const fb = rouxBlock(s, "L");
|
|
@@ -64,7 +85,8 @@ function cmllAligned(s) {
|
|
|
64
85
|
col(ubr, "B") === fb.back);
|
|
65
86
|
}
|
|
66
87
|
/** CMLL: both blocks, and the top corners solved relative to them (up to AUF). */
|
|
67
|
-
|
|
88
|
+
const cmllAt = (s) => bothBlocks(s) !== null && upToAuf(s, cmllAligned);
|
|
89
|
+
export const cmll = (s) => acrossM(s, cmllAt) !== null;
|
|
68
90
|
const LSE_EDGES = [cubieAt(0, 1, 1), cubieAt(0, 1, -1), cubieAt(-1, 1, 0), cubieAt(1, 1, 0), cubieAt(0, -1, 1), cubieAt(0, -1, -1)];
|
|
69
91
|
/**
|
|
70
92
|
* LSE edge orientation: every last-six edge has its top/bottom-coloured
|
|
@@ -72,8 +94,9 @@ const LSE_EDGES = [cubieAt(0, 1, 1), cubieAt(0, 1, -1), cubieAt(-1, 1, 0), cubie
|
|
|
72
94
|
* (the top / bottom centres on F / B), the edges in the M slice count by
|
|
73
95
|
* F / B instead (one M puts them right, flips and all).
|
|
74
96
|
*/
|
|
75
|
-
export
|
|
76
|
-
|
|
97
|
+
export const lseEo = (s) => acrossM(s, lseEoAt) !== null;
|
|
98
|
+
function lseEoAt(s) {
|
|
99
|
+
if (!cmllAt(s))
|
|
77
100
|
return false;
|
|
78
101
|
const bottom = rouxBlock(s, "L").bottom;
|
|
79
102
|
const ud = [bottom, opposite(bottom)];
|
|
@@ -88,8 +111,9 @@ export function lseEo(s) {
|
|
|
88
111
|
});
|
|
89
112
|
}
|
|
90
113
|
/** LSE 4b: UL and UR edges in place, with the corners aligned. */
|
|
91
|
-
export
|
|
92
|
-
|
|
114
|
+
export const ulUr = (s) => acrossM(s, ulUrAt) !== null;
|
|
115
|
+
function ulUrAt(s) {
|
|
116
|
+
if (!lseEoAt(s))
|
|
93
117
|
return false;
|
|
94
118
|
return upToAuf(s, (t) => {
|
|
95
119
|
if (!cmllAligned(t))
|
|
@@ -103,17 +127,40 @@ export function ulUr(s) {
|
|
|
103
127
|
colorAt(t, faceletOn(ur, "R")) === colorAt(t, centerIdx("R")));
|
|
104
128
|
});
|
|
105
129
|
}
|
|
130
|
+
const COLOUR_LETTERS = "URFDLB";
|
|
131
|
+
const letter = (c) => COLOUR_LETTERS[c] ?? "";
|
|
132
|
+
/** A block's floor and wall colours ("DL"). */
|
|
133
|
+
function blockColours(s, side) {
|
|
134
|
+
const b = acrossM(s, (t) => {
|
|
135
|
+
const block = rouxBlock(t, side);
|
|
136
|
+
return block ? { bottom: block.bottom, wall: colorAt(t, centerIdx(side)) } : null;
|
|
137
|
+
});
|
|
138
|
+
return b ? letter(b.bottom) + letter(b.wall) : undefined;
|
|
139
|
+
}
|
|
140
|
+
/** The wall pair neither block touches ("FB"). */
|
|
141
|
+
function wallColours(s) {
|
|
142
|
+
const b = acrossM(s, bothBlocks);
|
|
143
|
+
return b ? letter(b.front) + letter(b.back) : undefined;
|
|
144
|
+
}
|
|
145
|
+
/** The floor's colour ("D"). */
|
|
146
|
+
function floorColour(s) {
|
|
147
|
+
const b = acrossM(s, (t) => rouxBlock(t, "L"));
|
|
148
|
+
return b ? letter(b.bottom) : undefined;
|
|
149
|
+
}
|
|
106
150
|
export const ROUX = {
|
|
107
151
|
id: "roux",
|
|
108
152
|
name: "Roux",
|
|
109
153
|
stages: [
|
|
110
|
-
|
|
111
|
-
|
|
154
|
+
// Details as colours (a face's letter = its centre's colour, "DL" = D-coloured floor, L-coloured wall):
|
|
155
|
+
// across M, the floor a smart cube sees moves, the colours don't.
|
|
156
|
+
{ id: "fb", label: "First block", done: firstBlock, absoluteDetail: true, detail: (s) => blockColours(s, "L") },
|
|
157
|
+
{ id: "sb", label: "Second block", done: secondBlock, absoluteDetail: true, detail: (s) => blockColours(s, "R") },
|
|
112
158
|
// CMLL recognises the case it starts from ("Sune Left Bar", "CMLL skip") — StageBoundary.case.
|
|
113
|
-
{ id: "cmll", label: "CMLL", done: cmll, recognize: (s) => (secondBlock(s) ? (recognizeCmll(s)?.id ?? "CMLL skip") : undefined) },
|
|
159
|
+
{ id: "cmll", label: "CMLL", done: cmll, absoluteDetail: true, detail: (s) => wallColours(s), recognize: (s) => (secondBlock(s) ? (recognizeCmll(s)?.id ?? "CMLL skip") : undefined) },
|
|
114
160
|
{ id: "eo", label: "EO", done: lseEo },
|
|
115
161
|
{ id: "ulur", label: "UL/UR", done: ulUr },
|
|
116
|
-
|
|
162
|
+
// Solved, every quarter about L–R shows blocks: the floor is the first block's.
|
|
163
|
+
{ id: "l4e", label: "L4E", done: C.isSolved, absoluteDetail: true, detail: (s, earlier) => earlier[0]?.[0] ?? floorColour(s) },
|
|
117
164
|
],
|
|
118
165
|
};
|
|
119
166
|
const y = (c) => c.pos[1];
|