@wodzik/cubecore 0.1.5 → 0.1.7
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/rotations.d.ts +12 -2
- package/bluetooth/rotations.js +19 -20
- package/package.json +1 -1
- package/render/layers.js +6 -4
package/bluetooth/rotations.d.ts
CHANGED
|
@@ -60,9 +60,19 @@ export declare function heldMove(move: string, grip: Grip): string;
|
|
|
60
60
|
* y2, y y' = nothing), and slices / wide moves read from face moves with
|
|
61
61
|
* the core's rotation.
|
|
62
62
|
*/
|
|
63
|
-
export
|
|
63
|
+
export interface HeldOptions {
|
|
64
|
+
/**
|
|
65
|
+
* Wide moves to read from one face turn and the core's rotation — the rest
|
|
66
|
+
* stay a face turn and a rotation. Default r, l, f, b: a u or d is almost
|
|
67
|
+
* always a U / D turn and a y regrip. Slices (both faces of an axis) are
|
|
68
|
+
* always read.
|
|
69
|
+
*/
|
|
70
|
+
wide?: readonly string[];
|
|
71
|
+
}
|
|
72
|
+
export declare const DEFAULT_WIDE: readonly string[];
|
|
73
|
+
export declare function heldTokens(moves: readonly TimedMove[], startRotation: string, rotations: readonly RotationRecord[], options?: HeldOptions): HeldToken[];
|
|
64
74
|
/** A rotation and the face moves around it that make a slice / wide move → that move. */
|
|
65
|
-
export declare function mergeSlicesAndWides(tokens: readonly HeldToken[]): HeldToken[];
|
|
75
|
+
export declare function mergeSlicesAndWides(tokens: readonly HeldToken[], allowed?: ReadonlySet<string>): HeldToken[];
|
|
66
76
|
export interface GripEvent {
|
|
67
77
|
/** Moves the session had reported before it (its stream position). */
|
|
68
78
|
after: number;
|
package/bluetooth/rotations.js
CHANGED
|
@@ -28,34 +28,32 @@ export function heldMove(move, grip) {
|
|
|
28
28
|
const canonical = Object.keys(grip.face).find((c) => grip.face[c] === face);
|
|
29
29
|
return canonical ? canonical + move.slice(1) : move;
|
|
30
30
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
* the rotations), rotations between them (back-to-back ones combined: y y =
|
|
35
|
-
* y2, y y' = nothing), and slices / wide moves read from face moves with
|
|
36
|
-
* the core's rotation.
|
|
37
|
-
*/
|
|
38
|
-
export function heldTokens(moves, startRotation, rotations) {
|
|
31
|
+
export const DEFAULT_WIDE = ["r", "l", "f", "b"];
|
|
32
|
+
export function heldTokens(moves, startRotation, rotations, options = {}) {
|
|
33
|
+
const allowed = new Set(options.wide ?? DEFAULT_WIDE);
|
|
39
34
|
let grip = rotateGrip(IDENTITY_GRIP, startRotation);
|
|
40
|
-
const rots = snapToSlices(moves, grip, [...rotations].sort((a, b) => a.after - b.after || a.t - b.t));
|
|
35
|
+
const rots = snapToSlices(moves, grip, [...rotations].sort((a, b) => a.after - b.after || a.t - b.t), allowed);
|
|
41
36
|
const tokens = [];
|
|
42
37
|
let r = 0;
|
|
43
38
|
for (let i = 0; i <= moves.length; i++) {
|
|
44
39
|
let target = grip;
|
|
45
|
-
|
|
40
|
+
// Several at one place (a peek at the back and back: x x' x) are one net
|
|
41
|
+
// rotation — timed by the first: that's when it began (a slice's rotation
|
|
42
|
+
// then still sits with its face moves).
|
|
43
|
+
let t = null;
|
|
46
44
|
while (r < rots.length && rots[r].after <= i) {
|
|
47
45
|
target = rotateGrip(target, rots[r].move);
|
|
48
|
-
t
|
|
46
|
+
t ??= rots[r].t;
|
|
49
47
|
r++;
|
|
50
48
|
}
|
|
51
49
|
const net = rotationBetween(grip, target);
|
|
52
50
|
if (net)
|
|
53
|
-
tokens.push({ kind: "rotation", move: net, t, after: i });
|
|
51
|
+
tokens.push({ kind: "rotation", move: net, t: t ?? 0, after: i });
|
|
54
52
|
grip = target;
|
|
55
53
|
if (i < moves.length)
|
|
56
54
|
tokens.push({ kind: "move", move: heldMove(moves[i].move, grip), t: moves[i].t, index: i });
|
|
57
55
|
}
|
|
58
|
-
return mergeSlicesAndWides(tokens);
|
|
56
|
+
return mergeSlicesAndWides(tokens, allowed);
|
|
59
57
|
}
|
|
60
58
|
// ─── slices and wide moves ──────────────────────────────────────────────
|
|
61
59
|
/** A slice / wide move's face moves and its rotation, reported this far apart (ms), are one move. */
|
|
@@ -71,12 +69,13 @@ const AXIS_FACES = { x: ["R", "L"], y: ["U", "D"], z: ["F", "B"] };
|
|
|
71
69
|
const SLICE_WIDE = ["M", "E", "S", "r", "l", "u", "d", "f", "b"].flatMap((f) => [f, `${f}'`, `${f}2`]);
|
|
72
70
|
const effect = (alg) => applyMoves(solvedState(), alg).join();
|
|
73
71
|
const SLICE_WIDE_EFFECT = new Map();
|
|
74
|
-
/** The slice
|
|
75
|
-
function sliceOrWide(group) {
|
|
72
|
+
/** The slice (or an `allowed` wide move) the group (held letters and a rotation) amounts to, or null. */
|
|
73
|
+
function sliceOrWide(group, allowed) {
|
|
76
74
|
if (SLICE_WIDE_EFFECT.size === 0)
|
|
77
75
|
for (const m of SLICE_WIDE)
|
|
78
76
|
SLICE_WIDE_EFFECT.set(effect(m), m);
|
|
79
|
-
|
|
77
|
+
const m = SLICE_WIDE_EFFECT.get(effect(group.join(" ")));
|
|
78
|
+
return m && ("MES".includes(m[0]) || allowed.has(m[0])) ? m : null;
|
|
80
79
|
}
|
|
81
80
|
/**
|
|
82
81
|
* The consecutive run (up to MAX_GROUP) of moves on the axis nearest to
|
|
@@ -108,7 +107,7 @@ function axisRun(moves, from, dir, onAxis, t) {
|
|
|
108
107
|
* slice's signature (both faces of the axis) is trusted: one face and a
|
|
109
108
|
* rotation could as well be a face turn and a real regrip.
|
|
110
109
|
*/
|
|
111
|
-
function snapToSlices(moves, startGrip, rotations) {
|
|
110
|
+
function snapToSlices(moves, startGrip, rotations, allowed) {
|
|
112
111
|
let grip = startGrip;
|
|
113
112
|
const out = rotations.map((r) => ({ ...r }));
|
|
114
113
|
for (const r of out) {
|
|
@@ -130,7 +129,7 @@ function snapToSlices(moves, startGrip, rotations) {
|
|
|
130
129
|
const far = idx.some((j) => Math.abs(moves[j].t - r.t) > SNAP_MS);
|
|
131
130
|
if (far && new Set(idx.map((j) => moves[j].move[0])).size < 2)
|
|
132
131
|
continue;
|
|
133
|
-
if (sliceOrWide([...letters(idx), r.move])) {
|
|
132
|
+
if (sliceOrWide([...letters(idx), r.move], allowed)) {
|
|
134
133
|
r.after = after;
|
|
135
134
|
// It happened with those moves: its time is theirs.
|
|
136
135
|
r.t = moves[after > idx[0] ? idx.at(-1) : idx[0]].t;
|
|
@@ -143,7 +142,7 @@ function snapToSlices(moves, startGrip, rotations) {
|
|
|
143
142
|
return out.sort((a, b) => a.after - b.after || a.t - b.t);
|
|
144
143
|
}
|
|
145
144
|
/** A rotation and the face moves around it that make a slice / wide move → that move. */
|
|
146
|
-
export function mergeSlicesAndWides(tokens) {
|
|
145
|
+
export function mergeSlicesAndWides(tokens, allowed = new Set(DEFAULT_WIDE)) {
|
|
147
146
|
const out = [...tokens];
|
|
148
147
|
for (let k = 0; k < out.length; k++) {
|
|
149
148
|
const rot = out[k];
|
|
@@ -162,7 +161,7 @@ export function mergeSlicesAndWides(tokens) {
|
|
|
162
161
|
const groupMoves = group.filter((t) => t.kind === "move");
|
|
163
162
|
if (groupMoves.length !== group.length - 1 || groupMoves.some((m) => Math.abs(m.t - rot.t) > SAME_MOMENT_MS))
|
|
164
163
|
continue;
|
|
165
|
-
const merged = sliceOrWide(group.map((t) => t.move));
|
|
164
|
+
const merged = sliceOrWide(group.map((t) => t.move), allowed);
|
|
166
165
|
if (!merged)
|
|
167
166
|
continue;
|
|
168
167
|
const first = groupMoves[0];
|
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.7",
|
|
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/render/layers.js
CHANGED
|
@@ -7,11 +7,12 @@
|
|
|
7
7
|
* switches to S·m with nothing rotated. The two pictures coincide, so there is
|
|
8
8
|
* no pop — and the scene never has to track which physical cubie is where.
|
|
9
9
|
*/
|
|
10
|
-
import { FAMILY,
|
|
10
|
+
import { FAMILY, writtenQuarters } from "../core/index.js";
|
|
11
11
|
export function layerTurn(move) {
|
|
12
12
|
const def = FAMILY[move.family];
|
|
13
|
-
// A family's clockwise turn is `def.q` CCW quarters about +axis; the
|
|
14
|
-
|
|
13
|
+
// A family's clockwise turn is `def.q` CCW quarters about +axis; the turn as written multiplies it
|
|
14
|
+
// (U2' goes the other way than U2 — the state is the same, the hands aren't).
|
|
15
|
+
const quarters = def.q * writtenQuarters(move);
|
|
15
16
|
return {
|
|
16
17
|
axis: def.axis,
|
|
17
18
|
angle: (quarters * Math.PI) / 2,
|
|
@@ -20,5 +21,6 @@ export function layerTurn(move) {
|
|
|
20
21
|
}
|
|
21
22
|
/** Default animation length for a move, scaled by how far it turns (half turns take longer). */
|
|
22
23
|
export function defaultDuration(move, quarterMs = 120) {
|
|
23
|
-
|
|
24
|
+
const q = Math.abs(writtenQuarters(move));
|
|
25
|
+
return q === 1 ? quarterMs : quarterMs * (1 + 0.5 * (q - 1));
|
|
24
26
|
}
|