@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/element/alg.js
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <cube-alg> — an algorithm as written, in sync with a <cube-player>: the
|
|
3
|
+
* move playing now is highlighted, played ones fade, click a move to jump to
|
|
4
|
+
* it. Comments, grouping and pauses stay as written (repeats and commutator
|
|
5
|
+
* inverses light up the move they copy).
|
|
6
|
+
*
|
|
7
|
+
* <cube-player id="p" alg="(R U R' U')3 // sexy × 3"></cube-player>
|
|
8
|
+
* <cube-alg for="p"></cube-alg>
|
|
9
|
+
*
|
|
10
|
+
* Text: its own `alg` attribute, else the player's `alg`, else the player's
|
|
11
|
+
* moves (a recorded solve). Styling: --cc-alg-font, --cc-alg-size,
|
|
12
|
+
* --cc-alg-done, --cc-alg-current, --cc-alg-current-bg, --cc-alg-comment;
|
|
13
|
+
* parts alg, token, token-done, token-current, token-todo, comment.
|
|
14
|
+
*/
|
|
15
|
+
import { formatAlg, parseAlgDocument } from "../core/index.js";
|
|
16
|
+
import { ElementBase } from "./base.js";
|
|
17
|
+
const STYLES = /* css */ `
|
|
18
|
+
:host {
|
|
19
|
+
--cc-alg-font: ui-monospace, "SF Mono", Menlo, monospace;
|
|
20
|
+
--cc-alg-size: 16px;
|
|
21
|
+
--cc-alg-done: color-mix(in srgb, currentColor 40%, transparent);
|
|
22
|
+
--cc-alg-current: currentColor;
|
|
23
|
+
--cc-alg-current-bg: color-mix(in srgb, #4f8cff 25%, transparent);
|
|
24
|
+
--cc-alg-comment: color-mix(in srgb, currentColor 50%, transparent);
|
|
25
|
+
display: block;
|
|
26
|
+
font: 500 var(--cc-alg-size) / 1.6 var(--cc-alg-font);
|
|
27
|
+
white-space: pre-wrap;
|
|
28
|
+
}
|
|
29
|
+
.token { cursor: pointer; border-radius: 0.2em; padding: 0 0.08em; transition: color 0.12s, background 0.12s; }
|
|
30
|
+
.token:hover { background: color-mix(in srgb, currentColor 10%, transparent); }
|
|
31
|
+
.done { color: var(--cc-alg-done); }
|
|
32
|
+
.current { color: var(--cc-alg-current); background: var(--cc-alg-current-bg); font-weight: 700; }
|
|
33
|
+
.comment { color: var(--cc-alg-comment); font-style: italic; }
|
|
34
|
+
`;
|
|
35
|
+
export class CubeAlg extends ElementBase {
|
|
36
|
+
static observedAttributes = ["for", "alg"];
|
|
37
|
+
root;
|
|
38
|
+
player = null;
|
|
39
|
+
doc = null;
|
|
40
|
+
text = "";
|
|
41
|
+
/** Token index (by source start) of each move. */
|
|
42
|
+
tokenOf = [];
|
|
43
|
+
tokens = [];
|
|
44
|
+
onTime = (e) => this.highlight(e.detail.applied);
|
|
45
|
+
onLoad = () => this.render();
|
|
46
|
+
constructor() {
|
|
47
|
+
super();
|
|
48
|
+
this.root = this.attachShadow({ mode: "open" });
|
|
49
|
+
this.root.innerHTML = `<style>${STYLES}</style><div class="alg" part="alg"></div>`;
|
|
50
|
+
this.root.addEventListener("click", (e) => {
|
|
51
|
+
const t = e.target.closest(".token");
|
|
52
|
+
if (!t || !this.player)
|
|
53
|
+
return;
|
|
54
|
+
// Jump to just before the first move written there.
|
|
55
|
+
const k = this.tokenOf.indexOf(Number(t.dataset.token));
|
|
56
|
+
if (k >= 0)
|
|
57
|
+
this.player.seekToMove(k);
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
connectedCallback() {
|
|
61
|
+
this.connect();
|
|
62
|
+
}
|
|
63
|
+
disconnectedCallback() {
|
|
64
|
+
this.disconnect();
|
|
65
|
+
}
|
|
66
|
+
attributeChangedCallback() {
|
|
67
|
+
if (this.isConnected)
|
|
68
|
+
this.connect();
|
|
69
|
+
}
|
|
70
|
+
/** Follow this player (instead of `for`). */
|
|
71
|
+
attach(player) {
|
|
72
|
+
this.disconnect();
|
|
73
|
+
this.player = player;
|
|
74
|
+
player.addEventListener("timeupdate", this.onTime);
|
|
75
|
+
player.addEventListener("load", this.onLoad);
|
|
76
|
+
this.render();
|
|
77
|
+
}
|
|
78
|
+
connect() {
|
|
79
|
+
const id = this.getAttribute("for");
|
|
80
|
+
const p = id ? document.getElementById(id) : null;
|
|
81
|
+
if (p)
|
|
82
|
+
this.attach(p);
|
|
83
|
+
else
|
|
84
|
+
this.render();
|
|
85
|
+
}
|
|
86
|
+
disconnect() {
|
|
87
|
+
this.player?.removeEventListener("timeupdate", this.onTime);
|
|
88
|
+
this.player?.removeEventListener("load", this.onLoad);
|
|
89
|
+
this.player = null;
|
|
90
|
+
}
|
|
91
|
+
render() {
|
|
92
|
+
this.text = this.getAttribute("alg") ?? this.player?.getAttribute("alg") ?? (this.player ? formatAlg(this.player.moves) : "");
|
|
93
|
+
const box = this.root.querySelector(".alg");
|
|
94
|
+
box.innerHTML = "";
|
|
95
|
+
this.tokens = [];
|
|
96
|
+
try {
|
|
97
|
+
this.doc = parseAlgDocument(this.text);
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
box.textContent = this.text; // unparsable: plain text (the player reports the error)
|
|
101
|
+
this.doc = null;
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
// Cut the text into plain runs, move tokens (one per written move) and comments.
|
|
105
|
+
const starts = [...new Set(this.doc.sources.map((s) => s.start))].sort((a, b) => a - b);
|
|
106
|
+
const spans = [
|
|
107
|
+
...starts.map((start) => ({ kind: "token", start, end: this.doc.sources.find((s) => s.start === start).end })),
|
|
108
|
+
...this.doc.comments.map((c) => ({ kind: "comment", start: c.start, end: c.end })),
|
|
109
|
+
].sort((a, b) => a.start - b.start);
|
|
110
|
+
let at = 0;
|
|
111
|
+
for (const s of spans) {
|
|
112
|
+
if (s.start > at)
|
|
113
|
+
box.append(this.text.slice(at, s.start));
|
|
114
|
+
const el = document.createElement("span");
|
|
115
|
+
el.textContent = this.text.slice(s.start, s.end);
|
|
116
|
+
if (s.kind === "token") {
|
|
117
|
+
el.className = "token todo";
|
|
118
|
+
el.dataset.token = String(s.start);
|
|
119
|
+
el.setAttribute("part", "token token-todo");
|
|
120
|
+
this.tokens.push(el);
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
el.className = "comment";
|
|
124
|
+
el.setAttribute("part", "comment");
|
|
125
|
+
}
|
|
126
|
+
box.append(el);
|
|
127
|
+
at = s.end;
|
|
128
|
+
}
|
|
129
|
+
if (at < this.text.length)
|
|
130
|
+
box.append(this.text.slice(at));
|
|
131
|
+
this.tokenOf = this.doc.sources.map((s) => s.start);
|
|
132
|
+
this.highlight(this.player?.applied ?? 0);
|
|
133
|
+
}
|
|
134
|
+
/** Moves [0, applied) are done; move `applied` is next / playing. */
|
|
135
|
+
highlight(applied) {
|
|
136
|
+
const current = this.tokenOf[applied];
|
|
137
|
+
const done = new Set(this.tokenOf.slice(0, applied));
|
|
138
|
+
for (const el of this.tokens) {
|
|
139
|
+
const t = Number(el.dataset.token);
|
|
140
|
+
const status = t === current ? "current" : done.has(t) ? "done" : "todo";
|
|
141
|
+
el.className = `token ${status}`;
|
|
142
|
+
el.setAttribute("part", `token token-${status}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
export function defineCubeAlg(tag = "cube-alg") {
|
|
147
|
+
if (!customElements.get(tag))
|
|
148
|
+
customElements.define(tag, class extends CubeAlg {
|
|
149
|
+
});
|
|
150
|
+
}
|
package/element/base.js
ADDED
package/element/bld.d.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <cube-bld> — blindfolded (Old Pochmann) memo and execution on a smart cube.
|
|
3
|
+
*
|
|
4
|
+
* el.attach(session); // memo of the cube as it is now; then follows the execution
|
|
5
|
+
* el.scheme = "speffz"; // or "ruwix" (default), or a LetterScheme
|
|
6
|
+
* el.setAttribute("rotation", "x2 y'"); // how you hold it (yellow top, orange front…)
|
|
7
|
+
* el.setAttribute("order", "corners"); // corners first (default edges first)
|
|
8
|
+
*
|
|
9
|
+
* Shows the letters in pairs, edges then (parity) then corners; the letter
|
|
10
|
+
* due is highlighted, done ones fade. A swap with another letter than the
|
|
11
|
+
* one due is called out. reveal="all" (default) / "done" / "none" hides the
|
|
12
|
+
* letters still to do (memo practice).
|
|
13
|
+
*
|
|
14
|
+
* Customising like the other elements: --cc-seq-* variables and --cc-accent,
|
|
15
|
+
* parts (edges, corners, label, letters, pair, letter, letter-done /
|
|
16
|
+
* -current / -todo / -hidden, parity, message, controls, button), slot
|
|
17
|
+
* "controls" (default: Show / Hide, Restart), `headless` (build your own
|
|
18
|
+
* from the `progress` event).
|
|
19
|
+
*
|
|
20
|
+
* Events: progress (detail: BldProgress + memo), letter (a letter done:
|
|
21
|
+
* { step, index }), wrong ({ expected, got }), complete.
|
|
22
|
+
*/
|
|
23
|
+
import { BldTracker, type BldProgress, type LetterScheme, SCHEMES } from "../bld/index.js";
|
|
24
|
+
import { type Move, type State } from "../core/index.js";
|
|
25
|
+
import { ElementBase } from "./base.js";
|
|
26
|
+
import { type MoveSource } from "./sequence.js";
|
|
27
|
+
export interface BldMessages {
|
|
28
|
+
edges: string;
|
|
29
|
+
corners: string;
|
|
30
|
+
parity: string;
|
|
31
|
+
wrong: string;
|
|
32
|
+
complete: string;
|
|
33
|
+
show: string;
|
|
34
|
+
hide: string;
|
|
35
|
+
restart: string;
|
|
36
|
+
}
|
|
37
|
+
export declare class CubeBld extends ElementBase {
|
|
38
|
+
static observedAttributes: string[];
|
|
39
|
+
private readonly root;
|
|
40
|
+
private source;
|
|
41
|
+
private off;
|
|
42
|
+
private tracker;
|
|
43
|
+
private _scheme;
|
|
44
|
+
private shownAll;
|
|
45
|
+
private lastDone;
|
|
46
|
+
private completed;
|
|
47
|
+
private _messages;
|
|
48
|
+
constructor();
|
|
49
|
+
attributeChangedCallback(name: string): void;
|
|
50
|
+
/** The letter scheme: "ruwix" (default), "speffz" or your own. */
|
|
51
|
+
get scheme(): LetterScheme;
|
|
52
|
+
set scheme(s: LetterScheme | keyof typeof SCHEMES);
|
|
53
|
+
get messages(): BldMessages;
|
|
54
|
+
set messages(m: Partial<BldMessages>);
|
|
55
|
+
/** Memo and progress (null before anything is set). */
|
|
56
|
+
get progress(): (BldProgress & {
|
|
57
|
+
memo: BldTracker["memo"];
|
|
58
|
+
}) | null;
|
|
59
|
+
/** The memo as text, e.g. "Letters memorized for edges: QU SR NX…". */
|
|
60
|
+
get memoText(): {
|
|
61
|
+
edges: string;
|
|
62
|
+
corners: string;
|
|
63
|
+
} | null;
|
|
64
|
+
/** Memo of the attached cube as it is now, then follow its moves. Returns detach. */
|
|
65
|
+
attach(source: MoveSource): () => void;
|
|
66
|
+
detach(): void;
|
|
67
|
+
/** Memo again from `state` (default: the attached cube as it is now). */
|
|
68
|
+
reset(state?: State): void;
|
|
69
|
+
/** A face turn from the cube (called for you when attached). */
|
|
70
|
+
push(move: Move): void;
|
|
71
|
+
/** Show every letter / back to the reveal mode. */
|
|
72
|
+
toggleShown(): void;
|
|
73
|
+
private visible;
|
|
74
|
+
private update;
|
|
75
|
+
private labelButtons;
|
|
76
|
+
}
|
|
77
|
+
export declare function defineCubeBld(tag?: string): void;
|
|
78
|
+
declare global {
|
|
79
|
+
interface HTMLElementTagNameMap {
|
|
80
|
+
"cube-bld": CubeBld;
|
|
81
|
+
}
|
|
82
|
+
}
|
package/element/bld.js
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <cube-bld> — blindfolded (Old Pochmann) memo and execution on a smart cube.
|
|
3
|
+
*
|
|
4
|
+
* el.attach(session); // memo of the cube as it is now; then follows the execution
|
|
5
|
+
* el.scheme = "speffz"; // or "ruwix" (default), or a LetterScheme
|
|
6
|
+
* el.setAttribute("rotation", "x2 y'"); // how you hold it (yellow top, orange front…)
|
|
7
|
+
* el.setAttribute("order", "corners"); // corners first (default edges first)
|
|
8
|
+
*
|
|
9
|
+
* Shows the letters in pairs, edges then (parity) then corners; the letter
|
|
10
|
+
* due is highlighted, done ones fade. A swap with another letter than the
|
|
11
|
+
* one due is called out. reveal="all" (default) / "done" / "none" hides the
|
|
12
|
+
* letters still to do (memo practice).
|
|
13
|
+
*
|
|
14
|
+
* Customising like the other elements: --cc-seq-* variables and --cc-accent,
|
|
15
|
+
* parts (edges, corners, label, letters, pair, letter, letter-done /
|
|
16
|
+
* -current / -todo / -hidden, parity, message, controls, button), slot
|
|
17
|
+
* "controls" (default: Show / Hide, Restart), `headless` (build your own
|
|
18
|
+
* from the `progress` event).
|
|
19
|
+
*
|
|
20
|
+
* Events: progress (detail: BldProgress + memo), letter (a letter done:
|
|
21
|
+
* { step, index }), wrong ({ expected, got }), complete.
|
|
22
|
+
*/
|
|
23
|
+
import { BldTracker, SCHEMES, formatMemo } from "../bld/index.js";
|
|
24
|
+
import { solvedState } from "../core/index.js";
|
|
25
|
+
import { ElementBase } from "./base.js";
|
|
26
|
+
import { SEQUENCE_STYLES, escapeHtml } from "./sequence.js";
|
|
27
|
+
const STYLES = /* css */ `
|
|
28
|
+
.rows { display: flex; flex-direction: column; gap: 0.35em; }
|
|
29
|
+
.row { display: flex; align-items: baseline; gap: 0.6em; flex-wrap: wrap; }
|
|
30
|
+
.label { font: 600 11px/1 system-ui, sans-serif; text-transform: uppercase; letter-spacing: 0.06em; opacity: 0.7; min-width: 5.5em; }
|
|
31
|
+
.letters { display: flex; flex-wrap: wrap; gap: 0.1em var(--cc-seq-gap); font: 600 var(--cc-seq-size) / 1.5 var(--cc-seq-font); }
|
|
32
|
+
.pair { display: inline-flex; }
|
|
33
|
+
.letter { padding: 0 0.12em; border-radius: 0.25em; transition: color 0.15s ease, background 0.15s ease; }
|
|
34
|
+
.none { opacity: 0.5; font: 500 13px system-ui, sans-serif; }
|
|
35
|
+
.parity { font: 600 12px/1 system-ui, sans-serif; padding: 0.3em 0.55em; border-radius: 999px; background: var(--cc-control-bg); }
|
|
36
|
+
.message.wrong { color: var(--cc-seq-undo); }
|
|
37
|
+
`;
|
|
38
|
+
export class CubeBld extends ElementBase {
|
|
39
|
+
static observedAttributes = ["reveal", "rotation", "scheme", "order"];
|
|
40
|
+
root;
|
|
41
|
+
source = null;
|
|
42
|
+
off = null;
|
|
43
|
+
tracker = null;
|
|
44
|
+
_scheme = SCHEMES.ruwix;
|
|
45
|
+
shownAll = false;
|
|
46
|
+
lastDone = 0;
|
|
47
|
+
completed = false;
|
|
48
|
+
_messages = {
|
|
49
|
+
edges: "Edges",
|
|
50
|
+
corners: "Corners",
|
|
51
|
+
parity: "Parity",
|
|
52
|
+
wrong: "Swapped with {got} instead of {expected} — undo it",
|
|
53
|
+
complete: "All letters done",
|
|
54
|
+
show: "Show",
|
|
55
|
+
hide: "Hide",
|
|
56
|
+
restart: "Restart",
|
|
57
|
+
};
|
|
58
|
+
constructor() {
|
|
59
|
+
super();
|
|
60
|
+
this.root = this.attachShadow({ mode: "open" });
|
|
61
|
+
this.root.innerHTML = `<style>${SEQUENCE_STYLES}${STYLES}</style>
|
|
62
|
+
<div class="container" part="container">
|
|
63
|
+
<div class="rows"></div>
|
|
64
|
+
<slot name="message"><div class="message" part="message" role="status"></div></slot>
|
|
65
|
+
<slot name="controls"><div class="controls" part="controls">
|
|
66
|
+
<button part="button button-reveal" data-action="reveal" aria-pressed="false"></button>
|
|
67
|
+
<button part="button button-restart" data-action="restart"></button>
|
|
68
|
+
</div></slot>
|
|
69
|
+
</div>`;
|
|
70
|
+
this.root.addEventListener("click", (e) => {
|
|
71
|
+
const action = e.target.closest("[data-action]")?.dataset.action;
|
|
72
|
+
if (action === "reveal")
|
|
73
|
+
this.toggleShown();
|
|
74
|
+
else if (action === "restart")
|
|
75
|
+
this.reset();
|
|
76
|
+
});
|
|
77
|
+
this.labelButtons();
|
|
78
|
+
}
|
|
79
|
+
attributeChangedCallback(name) {
|
|
80
|
+
if (name === "scheme") {
|
|
81
|
+
const s = this.getAttribute("scheme");
|
|
82
|
+
if (s && SCHEMES[s])
|
|
83
|
+
this._scheme = SCHEMES[s];
|
|
84
|
+
}
|
|
85
|
+
if (name === "scheme" || name === "rotation" || name === "order")
|
|
86
|
+
this.reset();
|
|
87
|
+
else
|
|
88
|
+
this.update();
|
|
89
|
+
}
|
|
90
|
+
/** The letter scheme: "ruwix" (default), "speffz" or your own. */
|
|
91
|
+
get scheme() {
|
|
92
|
+
return this._scheme;
|
|
93
|
+
}
|
|
94
|
+
set scheme(s) {
|
|
95
|
+
this._scheme = typeof s === "string" ? SCHEMES[s] : s;
|
|
96
|
+
this.reset();
|
|
97
|
+
}
|
|
98
|
+
get messages() {
|
|
99
|
+
return { ...this._messages };
|
|
100
|
+
}
|
|
101
|
+
set messages(m) {
|
|
102
|
+
this._messages = { ...this._messages, ...m };
|
|
103
|
+
this.labelButtons();
|
|
104
|
+
this.update();
|
|
105
|
+
}
|
|
106
|
+
/** Memo and progress (null before anything is set). */
|
|
107
|
+
get progress() {
|
|
108
|
+
return this.tracker ? { ...this.tracker.progress, memo: this.tracker.memo } : null;
|
|
109
|
+
}
|
|
110
|
+
/** The memo as text, e.g. "Letters memorized for edges: QU SR NX…". */
|
|
111
|
+
get memoText() {
|
|
112
|
+
const m = this.tracker?.memo;
|
|
113
|
+
return m ? { edges: formatMemo(m.edges), corners: formatMemo(m.corners) } : null;
|
|
114
|
+
}
|
|
115
|
+
/** Memo of the attached cube as it is now, then follow its moves. Returns detach. */
|
|
116
|
+
attach(source) {
|
|
117
|
+
this.detach();
|
|
118
|
+
this.source = source;
|
|
119
|
+
this.off = source.on("move", (e) => this.push(e.move));
|
|
120
|
+
this.reset(source.state);
|
|
121
|
+
return () => this.detach();
|
|
122
|
+
}
|
|
123
|
+
detach() {
|
|
124
|
+
this.off?.();
|
|
125
|
+
this.off = null;
|
|
126
|
+
this.source = null;
|
|
127
|
+
}
|
|
128
|
+
/** Memo again from `state` (default: the attached cube as it is now). */
|
|
129
|
+
reset(state) {
|
|
130
|
+
const start = state ?? this.source?.state ?? solvedState();
|
|
131
|
+
this.tracker = new BldTracker(start, {
|
|
132
|
+
scheme: this._scheme,
|
|
133
|
+
rotation: this.getAttribute("rotation") ?? undefined,
|
|
134
|
+
order: this.getAttribute("order") === "corners" ? "corners" : "edges",
|
|
135
|
+
});
|
|
136
|
+
this.lastDone = 0;
|
|
137
|
+
this.completed = false;
|
|
138
|
+
this.update();
|
|
139
|
+
}
|
|
140
|
+
/** A face turn from the cube (called for you when attached). */
|
|
141
|
+
push(move) {
|
|
142
|
+
if (!this.tracker || this.completed)
|
|
143
|
+
return;
|
|
144
|
+
const p = this.tracker.push(move);
|
|
145
|
+
for (let i = this.lastDone; i < p.done; i++)
|
|
146
|
+
this.dispatchEvent(new CustomEvent("letter", { detail: { step: p.steps[i], index: i } }));
|
|
147
|
+
this.lastDone = p.done;
|
|
148
|
+
if (p.wrong)
|
|
149
|
+
this.dispatchEvent(new CustomEvent("wrong", { detail: p.wrong }));
|
|
150
|
+
this.update();
|
|
151
|
+
if (p.complete) {
|
|
152
|
+
this.completed = true;
|
|
153
|
+
this.dispatchEvent(new CustomEvent("complete", { detail: this.progress }));
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/** Show every letter / back to the reveal mode. */
|
|
157
|
+
toggleShown() {
|
|
158
|
+
this.shownAll = !this.shownAll;
|
|
159
|
+
this.labelButtons();
|
|
160
|
+
this.update();
|
|
161
|
+
}
|
|
162
|
+
visible(status) {
|
|
163
|
+
if (this.shownAll)
|
|
164
|
+
return true;
|
|
165
|
+
const r = this.getAttribute("reveal");
|
|
166
|
+
return r === "none" ? false : r === "done" ? status === "done" : true;
|
|
167
|
+
}
|
|
168
|
+
update() {
|
|
169
|
+
const p = this.tracker?.progress;
|
|
170
|
+
const rows = this.root.querySelector(".rows");
|
|
171
|
+
if (!p) {
|
|
172
|
+
rows.innerHTML = "";
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
const lettersOf = (kind) => {
|
|
176
|
+
const idx = p.steps.map((s, i) => (s.kind === kind ? i : -1)).filter((i) => i >= 0);
|
|
177
|
+
if (!idx.length)
|
|
178
|
+
return `<span class="none">—</span>`;
|
|
179
|
+
const pairs = [];
|
|
180
|
+
for (let j = 0; j < idx.length; j += 2) {
|
|
181
|
+
const pair = idx.slice(j, j + 2).map((i) => {
|
|
182
|
+
const st = p.status[i];
|
|
183
|
+
const shown = this.visible(st);
|
|
184
|
+
return `<span class="letter ${st}${shown ? "" : " hidden"}" part="letter letter-${st}${shown ? "" : " letter-hidden"}">${shown ? escapeHtml(p.steps[i].letter) : "•"}</span>`;
|
|
185
|
+
});
|
|
186
|
+
pairs.push(`<span class="pair" part="pair">${pair.join("")}</span>`);
|
|
187
|
+
}
|
|
188
|
+
return pairs.join("");
|
|
189
|
+
};
|
|
190
|
+
const parityIdx = p.steps.findIndex((s) => s.kind === "parity");
|
|
191
|
+
const parity = parityIdx >= 0 ? `<span class="parity letter ${p.status[parityIdx]}" part="parity letter-${p.status[parityIdx]}">${escapeHtml(this._messages.parity)}</span>` : "";
|
|
192
|
+
// In solving order; parity after the first kind.
|
|
193
|
+
const row = (kind, withParity) => `<div class="row" part="${kind}s"><span class="label" part="label">${escapeHtml(kind === "edge" ? this._messages.edges : this._messages.corners)}</span><span class="letters" part="letters">${lettersOf(kind)}</span>${withParity ? parity : ""}</div>`;
|
|
194
|
+
const cornersFirst = this.getAttribute("order") === "corners";
|
|
195
|
+
rows.innerHTML = cornersFirst ? row("corner", true) + row("edge", false) : row("edge", true) + row("corner", false);
|
|
196
|
+
const message = this.root.querySelector(".message");
|
|
197
|
+
if (message) {
|
|
198
|
+
const text = p.wrong
|
|
199
|
+
? this._messages.wrong.replace("{got}", p.wrong.got).replace("{expected}", p.wrong.expected)
|
|
200
|
+
: p.complete ? this._messages.complete : "";
|
|
201
|
+
message.textContent = text;
|
|
202
|
+
message.classList.toggle("on", !!text);
|
|
203
|
+
message.classList.toggle("wrong", !!p.wrong);
|
|
204
|
+
}
|
|
205
|
+
this.dispatchEvent(new CustomEvent("progress", { detail: this.progress }));
|
|
206
|
+
}
|
|
207
|
+
labelButtons() {
|
|
208
|
+
const reveal = this.root.querySelector('[data-action="reveal"]');
|
|
209
|
+
if (reveal) {
|
|
210
|
+
reveal.textContent = this.shownAll ? this._messages.hide : this._messages.show;
|
|
211
|
+
reveal.setAttribute("aria-pressed", String(this.shownAll));
|
|
212
|
+
}
|
|
213
|
+
const restart = this.root.querySelector('[data-action="restart"]');
|
|
214
|
+
if (restart)
|
|
215
|
+
restart.textContent = this._messages.restart;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
export function defineCubeBld(tag = "cube-bld") {
|
|
219
|
+
if (!customElements.get(tag))
|
|
220
|
+
customElements.define(tag, class extends CubeBld {
|
|
221
|
+
});
|
|
222
|
+
}
|
package/element/index.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { defineCubePlayer } from "./player.js";
|
|
2
|
+
import { defineCubeScramble } from "./scramble.js";
|
|
3
|
+
import { defineCubeAlg } from "./alg.js";
|
|
4
|
+
import { defineCubeAlgPractice } from "./practice.js";
|
|
5
|
+
import { defineCubeBld } from "./bld.js";
|
|
6
|
+
export * from "./model.js";
|
|
7
|
+
export * from "./player.js";
|
|
8
|
+
export * from "./scramble.js";
|
|
9
|
+
export * from "./alg.js";
|
|
10
|
+
export * from "./practice.js";
|
|
11
|
+
export * from "./bld.js";
|
|
12
|
+
export * from "./sequence.js";
|
|
13
|
+
export { ICONS, STYLES } from "./styles.js";
|
|
14
|
+
// Importing the package registers <cube-player>, <cube-scramble>, <cube-alg-practice>, <cube-bld> and <cube-alg>; define*("my-tag") for other names.
|
|
15
|
+
if (typeof customElements !== "undefined") {
|
|
16
|
+
defineCubePlayer();
|
|
17
|
+
defineCubeScramble();
|
|
18
|
+
defineCubeAlg();
|
|
19
|
+
defineCubeAlgPractice();
|
|
20
|
+
defineCubeBld();
|
|
21
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The player's logic without the DOM — what the controls show and do, as
|
|
3
|
+
* pure functions (tested in bun; the element only wires them to buttons).
|
|
4
|
+
*/
|
|
5
|
+
import { type Method, type Move } from "../core/index.js";
|
|
6
|
+
import { type Recording, type Segment } from "../timeline/index.js";
|
|
7
|
+
/** A mark on the progress bar, e.g. where a stage ends. */
|
|
8
|
+
export interface Marker {
|
|
9
|
+
/** ms of recording time. */
|
|
10
|
+
time: number;
|
|
11
|
+
label: string;
|
|
12
|
+
}
|
|
13
|
+
/** "12.34" under a minute, "1:02.34" above. */
|
|
14
|
+
export declare function formatTime(ms: number): string;
|
|
15
|
+
/**
|
|
16
|
+
* An algorithm played at a steady tempo, as a recording: each move takes one
|
|
17
|
+
* beat (1 / movesPerSecond), each pause (`.` in the text) one beat of stillness.
|
|
18
|
+
*/
|
|
19
|
+
export declare function tempoRecording(alg: string | readonly Move[], movesPerSecond: number): {
|
|
20
|
+
recording: Recording;
|
|
21
|
+
interval: number;
|
|
22
|
+
};
|
|
23
|
+
/** Where each stage of `method` ends in `rec` (skipped stages left out). */
|
|
24
|
+
export declare function stageMarkers(method: Method, rec: Recording): Marker[];
|
|
25
|
+
/** Time to jump to for one move forward (+1) or back (−1) from `time`: the end of a move, or the start. */
|
|
26
|
+
export declare function stepTime(rec: Recording, time: number, direction: 1 | -1): number;
|
|
27
|
+
/** Fraction 0..1 of the way through. */
|
|
28
|
+
export declare const fraction: (time: number, duration: number) => number;
|
|
29
|
+
/**
|
|
30
|
+
* Moves that set the cube up before an algorithm plays: the setup, then —
|
|
31
|
+
* for `anchor = "end"` (the algorithm SOLVES the cube) — the algorithm's
|
|
32
|
+
* inverse, so playing it ends solved (or back at the setup).
|
|
33
|
+
*/
|
|
34
|
+
export declare function startMoves(setup: string | readonly Move[], alg: readonly Move[], anchor?: "start" | "end"): Move[];
|
|
35
|
+
/** Default tooltip text of a segment: "F2L 2 · FL · 3.21 s (recognition 0.80 · execution 2.41) · 9 moves". */
|
|
36
|
+
export declare function segmentText(s: Segment): string;
|
package/element/model.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The player's logic without the DOM — what the controls show and do, as
|
|
3
|
+
* pure functions (tested in bun; the element only wires them to buttons).
|
|
4
|
+
*/
|
|
5
|
+
import { invert, parseAlg, parseAlgDocument } from "../core/index.js";
|
|
6
|
+
import { stageTimings } from "../timeline/index.js";
|
|
7
|
+
/** "12.34" under a minute, "1:02.34" above. */
|
|
8
|
+
export function formatTime(ms) {
|
|
9
|
+
const s = Math.max(0, ms) / 1000;
|
|
10
|
+
if (s < 60)
|
|
11
|
+
return s.toFixed(2);
|
|
12
|
+
const m = Math.floor(s / 60);
|
|
13
|
+
return `${m}:${(s - m * 60).toFixed(2).padStart(5, "0")}`;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* An algorithm played at a steady tempo, as a recording: each move takes one
|
|
17
|
+
* beat (1 / movesPerSecond), each pause (`.` in the text) one beat of stillness.
|
|
18
|
+
*/
|
|
19
|
+
export function tempoRecording(alg, movesPerSecond) {
|
|
20
|
+
const doc = typeof alg === "string" ? parseAlgDocument(alg) : { moves: [...alg], pausesBefore: alg.map(() => 0) };
|
|
21
|
+
const interval = 1000 / Math.max(0.1, movesPerSecond);
|
|
22
|
+
let beats = 0;
|
|
23
|
+
const moves = doc.moves.map((move, i) => {
|
|
24
|
+
beats += doc.pausesBefore[i] + 1;
|
|
25
|
+
return { move, t: Math.round(beats * interval) };
|
|
26
|
+
});
|
|
27
|
+
return { recording: { scramble: [], moves, totalMs: Math.round(beats * interval) }, interval };
|
|
28
|
+
}
|
|
29
|
+
/** Where each stage of `method` ends in `rec` (skipped stages left out). */
|
|
30
|
+
export function stageMarkers(method, rec) {
|
|
31
|
+
return stageTimings(method, rec)
|
|
32
|
+
.stages.filter((s) => !s.skipped)
|
|
33
|
+
.map((s) => ({ time: s.endMs, label: s.detail ? `${s.label} (${s.detail})` : s.label }));
|
|
34
|
+
}
|
|
35
|
+
/** Time to jump to for one move forward (+1) or back (−1) from `time`: the end of a move, or the start. */
|
|
36
|
+
export function stepTime(rec, time, direction) {
|
|
37
|
+
const ends = rec.moves.map((m) => m.t);
|
|
38
|
+
if (direction > 0)
|
|
39
|
+
return ends.find((t) => t > time + 0.5) ?? rec.totalMs;
|
|
40
|
+
const before = ends.filter((t) => t < time - 0.5);
|
|
41
|
+
return before.length ? before[before.length - 1] : 0;
|
|
42
|
+
}
|
|
43
|
+
/** Fraction 0..1 of the way through. */
|
|
44
|
+
export const fraction = (time, duration) => (duration > 0 ? Math.min(1, Math.max(0, time / duration)) : 0);
|
|
45
|
+
/**
|
|
46
|
+
* Moves that set the cube up before an algorithm plays: the setup, then —
|
|
47
|
+
* for `anchor = "end"` (the algorithm SOLVES the cube) — the algorithm's
|
|
48
|
+
* inverse, so playing it ends solved (or back at the setup).
|
|
49
|
+
*/
|
|
50
|
+
export function startMoves(setup, alg, anchor = "start") {
|
|
51
|
+
const s = typeof setup === "string" ? parseAlg(setup) : [...setup];
|
|
52
|
+
return anchor === "end" ? [...s, ...invert(alg)] : s;
|
|
53
|
+
}
|
|
54
|
+
/** Default tooltip text of a segment: "F2L 2 · FL · 3.21 s (recognition 0.80 · execution 2.41) · 9 moves". */
|
|
55
|
+
export function segmentText(s) {
|
|
56
|
+
const parts = [s.label];
|
|
57
|
+
if (s.detail)
|
|
58
|
+
parts.push(s.detail);
|
|
59
|
+
const total = `${formatTime(s.end - s.start)} s`;
|
|
60
|
+
parts.push(s.split !== undefined ? `${total} (recognition ${formatTime(s.split - s.start)} · execution ${formatTime(s.end - s.split)})` : total);
|
|
61
|
+
if (s.moves !== undefined)
|
|
62
|
+
parts.push(`${s.moves} move${s.moves === 1 ? "" : "s"}`);
|
|
63
|
+
return parts.join(" · ");
|
|
64
|
+
}
|