@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.
Files changed (279) hide show
  1. package/LICENSE +373 -0
  2. package/README.md +118 -0
  3. package/analyze/client.d.ts +18 -0
  4. package/analyze/client.js +42 -0
  5. package/analyze/index.d.ts +114 -0
  6. package/analyze/index.js +151 -0
  7. package/analyze/rotations.d.ts +3 -0
  8. package/analyze/rotations.js +15 -0
  9. package/analyze/roux.d.ts +67 -0
  10. package/analyze/roux.js +95 -0
  11. package/analyze/worker.d.ts +6 -0
  12. package/analyze/worker.js +31 -0
  13. package/analyze/zz.d.ts +47 -0
  14. package/analyze/zz.js +102 -0
  15. package/bld/index.d.ts +14 -0
  16. package/bld/index.js +14 -0
  17. package/bld/memo.d.ts +66 -0
  18. package/bld/memo.js +139 -0
  19. package/bld/scheme.d.ts +30 -0
  20. package/bld/scheme.js +35 -0
  21. package/bld/skin.d.ts +26 -0
  22. package/bld/skin.js +46 -0
  23. package/bld/tracker.d.ts +68 -0
  24. package/bld/tracker.js +145 -0
  25. package/bluetooth/clock.d.ts +22 -0
  26. package/bluetooth/clock.js +37 -0
  27. package/bluetooth/cubeSkins.d.ts +31 -0
  28. package/bluetooth/cubeSkins.js +32 -0
  29. package/bluetooth/gyro.d.ts +31 -0
  30. package/bluetooth/gyro.js +46 -0
  31. package/bluetooth/index.d.ts +5 -0
  32. package/bluetooth/index.js +5 -0
  33. package/bluetooth/session.d.ts +140 -0
  34. package/bluetooth/session.js +201 -0
  35. package/bluetooth/simulated.d.ts +52 -0
  36. package/bluetooth/simulated.js +80 -0
  37. package/bluetooth/vendor/smartcube-web-bluetooth/gan-cube-definitions.d.ts +38 -0
  38. package/bluetooth/vendor/smartcube-web-bluetooth/gan-cube-definitions.js +51 -0
  39. package/bluetooth/vendor/smartcube-web-bluetooth/gan-cube-encrypter.d.ts +35 -0
  40. package/bluetooth/vendor/smartcube-web-bluetooth/gan-cube-encrypter.js +70 -0
  41. package/bluetooth/vendor/smartcube-web-bluetooth/gan-cube-protocol.d.ts +273 -0
  42. package/bluetooth/vendor/smartcube-web-bluetooth/gan-cube-protocol.js +846 -0
  43. package/bluetooth/vendor/smartcube-web-bluetooth/gan-gen1.d.ts +38 -0
  44. package/bluetooth/vendor/smartcube-web-bluetooth/gan-gen1.js +340 -0
  45. package/bluetooth/vendor/smartcube-web-bluetooth/gan-gen234-packet-validate.d.ts +11 -0
  46. package/bluetooth/vendor/smartcube-web-bluetooth/gan-gen234-packet-validate.js +118 -0
  47. package/bluetooth/vendor/smartcube-web-bluetooth/gan-mac-salt.d.ts +2 -0
  48. package/bluetooth/vendor/smartcube-web-bluetooth/gan-mac-salt.js +15 -0
  49. package/bluetooth/vendor/smartcube-web-bluetooth/gan-smart-cube.d.ts +18 -0
  50. package/bluetooth/vendor/smartcube-web-bluetooth/gan-smart-cube.js +152 -0
  51. package/bluetooth/vendor/smartcube-web-bluetooth/gan-smart-timer.d.ts +74 -0
  52. package/bluetooth/vendor/smartcube-web-bluetooth/gan-smart-timer.js +161 -0
  53. package/bluetooth/vendor/smartcube-web-bluetooth/gan-write-queue.d.ts +7 -0
  54. package/bluetooth/vendor/smartcube-web-bluetooth/gan-write-queue.js +11 -0
  55. package/bluetooth/vendor/smartcube-web-bluetooth/gatt-characteristic-write.d.ts +2 -0
  56. package/bluetooth/vendor/smartcube-web-bluetooth/gatt-characteristic-write.js +36 -0
  57. package/bluetooth/vendor/smartcube-web-bluetooth/index.d.ts +4 -0
  58. package/bluetooth/vendor/smartcube-web-bluetooth/index.js +4 -0
  59. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/address-hints.d.ts +12 -0
  60. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/address-hints.js +122 -0
  61. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/build-picker-options.d.ts +11 -0
  62. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/build-picker-options.js +55 -0
  63. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/default-optional-services.d.ts +21 -0
  64. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/default-optional-services.js +47 -0
  65. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/device-name-scan-filters.d.ts +6 -0
  66. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/device-name-scan-filters.js +24 -0
  67. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/gatt-snapshot.d.ts +6 -0
  68. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/gatt-snapshot.js +57 -0
  69. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/mac-candidates.d.ts +11 -0
  70. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/mac-candidates.js +67 -0
  71. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/mac-probe-moyu32.d.ts +8 -0
  72. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/mac-probe-moyu32.js +98 -0
  73. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/mac-probe-qiyi.d.ts +8 -0
  74. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/mac-probe-qiyi.js +128 -0
  75. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/moyu32-session-crypto.d.ts +5 -0
  76. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/moyu32-session-crypto.js +60 -0
  77. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/normalize-uuid.d.ts +2 -0
  78. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/normalize-uuid.js +8 -0
  79. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/packet-sanity.d.ts +7 -0
  80. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/packet-sanity.js +142 -0
  81. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/profile-rank.d.ts +6 -0
  82. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/profile-rank.js +21 -0
  83. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/types.d.ts +32 -0
  84. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/types.js +1 -0
  85. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/ble-utils.d.ts +6 -0
  86. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/ble-utils.js +68 -0
  87. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/connect.d.ts +3 -0
  88. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/connect.js +178 -0
  89. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/cubie-cube.d.ts +16 -0
  90. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/cubie-cube.js +141 -0
  91. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/index.d.ts +11 -0
  92. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/index.js +10 -0
  93. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocol.d.ts +26 -0
  94. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocol.js +8 -0
  95. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/gan.d.ts +3 -0
  96. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/gan.js +265 -0
  97. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/giiker.d.ts +3 -0
  98. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/giiker.js +345 -0
  99. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/gocube.d.ts +15 -0
  100. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/gocube.js +382 -0
  101. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/moyu-mhc.d.ts +3 -0
  102. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/moyu-mhc.js +352 -0
  103. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/moyu-v1.d.ts +57 -0
  104. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/moyu-v1.js +243 -0
  105. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/moyu32.d.ts +3 -0
  106. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/moyu32.js +454 -0
  107. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/qiyi.d.ts +3 -0
  108. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/qiyi.js +424 -0
  109. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/types.d.ts +78 -0
  110. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/types.js +1 -0
  111. package/bluetooth/vendor/smartcube-web-bluetooth/utils.d.ts +45 -0
  112. package/bluetooth/vendor/smartcube-web-bluetooth/utils.js +150 -0
  113. package/cfop/caseData.d.ts +16 -0
  114. package/cfop/caseData.js +88 -0
  115. package/cfop/cases.d.ts +42 -0
  116. package/cfop/cases.js +116 -0
  117. package/cfop/f2l.d.ts +28 -0
  118. package/cfop/f2l.js +88 -0
  119. package/cfop/f2lData.d.ts +15 -0
  120. package/cfop/f2lData.js +49 -0
  121. package/cfop/index.d.ts +41 -0
  122. package/cfop/index.js +110 -0
  123. package/cfop/slots.d.ts +2 -0
  124. package/cfop/slots.js +1 -0
  125. package/core/arrows.d.ts +24 -0
  126. package/core/arrows.js +22 -0
  127. package/core/bytes.d.ts +27 -0
  128. package/core/bytes.js +145 -0
  129. package/core/centers.d.ts +32 -0
  130. package/core/centers.js +83 -0
  131. package/core/checks.d.ts +53 -0
  132. package/core/checks.js +85 -0
  133. package/core/cubies.d.ts +45 -0
  134. package/core/cubies.js +169 -0
  135. package/core/frames.d.ts +61 -0
  136. package/core/frames.js +126 -0
  137. package/core/geometry.d.ts +58 -0
  138. package/core/geometry.js +132 -0
  139. package/core/index.d.ts +19 -0
  140. package/core/index.js +19 -0
  141. package/core/masks.d.ts +32 -0
  142. package/core/masks.js +39 -0
  143. package/core/method.d.ts +35 -0
  144. package/core/method.js +30 -0
  145. package/core/moves.d.ts +48 -0
  146. package/core/moves.js +88 -0
  147. package/core/notation.d.ts +48 -0
  148. package/core/notation.js +226 -0
  149. package/core/physical.d.ts +53 -0
  150. package/core/physical.js +99 -0
  151. package/core/practice.d.ts +62 -0
  152. package/core/practice.js +87 -0
  153. package/core/sequence.d.ts +90 -0
  154. package/core/sequence.js +157 -0
  155. package/core/stages.d.ts +90 -0
  156. package/core/stages.js +21 -0
  157. package/core/state.d.ts +55 -0
  158. package/core/state.js +140 -0
  159. package/core/stateCodec.d.ts +12 -0
  160. package/core/stateCodec.js +51 -0
  161. package/core/stream.d.ts +45 -0
  162. package/core/stream.js +75 -0
  163. package/core/tracker.d.ts +62 -0
  164. package/core/tracker.js +121 -0
  165. package/element/alg.d.ts +45 -0
  166. package/element/alg.js +150 -0
  167. package/element/base.d.ts +2 -0
  168. package/element/base.js +3 -0
  169. package/element/bld.d.ts +82 -0
  170. package/element/bld.js +222 -0
  171. package/element/index.d.ts +8 -0
  172. package/element/index.js +21 -0
  173. package/element/model.d.ts +36 -0
  174. package/element/model.js +64 -0
  175. package/element/player.d.ts +196 -0
  176. package/element/player.js +680 -0
  177. package/element/practice.d.ts +75 -0
  178. package/element/practice.js +150 -0
  179. package/element/scramble.d.ts +50 -0
  180. package/element/scramble.js +107 -0
  181. package/element/sequence.d.ts +126 -0
  182. package/element/sequence.js +254 -0
  183. package/element/styles.d.ts +32 -0
  184. package/element/styles.js +196 -0
  185. package/image/cache.d.ts +21 -0
  186. package/image/cache.js +66 -0
  187. package/image/index.d.ts +2 -0
  188. package/image/index.js +2 -0
  189. package/image/png-node.d.ts +6 -0
  190. package/image/png-node.js +10 -0
  191. package/image/svg.d.ts +36 -0
  192. package/image/svg.js +215 -0
  193. package/lbl/index.d.ts +10 -0
  194. package/lbl/index.js +31 -0
  195. package/methods/index.d.ts +20 -0
  196. package/methods/index.js +33 -0
  197. package/package.json +133 -0
  198. package/petrus/index.d.ts +11 -0
  199. package/petrus/index.js +27 -0
  200. package/react/index.d.ts +176 -0
  201. package/react/index.js +228 -0
  202. package/render/arrows.d.ts +68 -0
  203. package/render/arrows.js +190 -0
  204. package/render/build/attachments.d.ts +35 -0
  205. package/render/build/attachments.js +94 -0
  206. package/render/build/models.d.ts +9 -0
  207. package/render/build/models.js +27 -0
  208. package/render/build/tiles.d.ts +48 -0
  209. package/render/build/tiles.js +194 -0
  210. package/render/gltf.d.ts +10 -0
  211. package/render/gltf.js +106 -0
  212. package/render/index.d.ts +12 -0
  213. package/render/index.js +12 -0
  214. package/render/layers.d.ts +21 -0
  215. package/render/layers.js +24 -0
  216. package/render/pictures.d.ts +46 -0
  217. package/render/pictures.js +80 -0
  218. package/render/pieceModels.d.ts +38 -0
  219. package/render/pieceModels.js +43 -0
  220. package/render/renderer.d.ts +165 -0
  221. package/render/renderer.js +647 -0
  222. package/render/tile.d.ts +114 -0
  223. package/render/tile.js +390 -0
  224. package/render/viewports.d.ts +25 -0
  225. package/render/viewports.js +26 -0
  226. package/roux/cmll.d.ts +35 -0
  227. package/roux/cmll.js +121 -0
  228. package/roux/cmllData.d.ts +12 -0
  229. package/roux/cmllData.js +49 -0
  230. package/roux/index.d.ts +52 -0
  231. package/roux/index.js +175 -0
  232. package/skin/attachments.d.ts +82 -0
  233. package/skin/attachments.js +110 -0
  234. package/skin/index.d.ts +3 -0
  235. package/skin/index.js +3 -0
  236. package/skin/shapes.d.ts +112 -0
  237. package/skin/shapes.js +147 -0
  238. package/skin/skin.d.ts +911 -0
  239. package/skin/skin.js +412 -0
  240. package/solve/client.d.ts +58 -0
  241. package/solve/client.js +56 -0
  242. package/solve/handle.d.ts +6 -0
  243. package/solve/handle.js +38 -0
  244. package/solve/index.d.ts +8 -0
  245. package/solve/index.js +7 -0
  246. package/solve/lse.d.ts +42 -0
  247. package/solve/lse.js +151 -0
  248. package/solve/pieces.d.ts +18 -0
  249. package/solve/pieces.js +24 -0
  250. package/solve/protocol.d.ts +70 -0
  251. package/solve/protocol.js +16 -0
  252. package/solve/scramble.d.ts +76 -0
  253. package/solve/scramble.js +187 -0
  254. package/solve/stage.d.ts +108 -0
  255. package/solve/stage.js +532 -0
  256. package/solve/twophase.d.ts +36 -0
  257. package/solve/twophase.js +229 -0
  258. package/solve/worker.d.ts +6 -0
  259. package/solve/worker.js +15 -0
  260. package/timeline/codec.d.ts +23 -0
  261. package/timeline/codec.js +84 -0
  262. package/timeline/index.d.ts +7 -0
  263. package/timeline/index.js +7 -0
  264. package/timeline/recording.d.ts +19 -0
  265. package/timeline/recording.js +20 -0
  266. package/timeline/replay.d.ts +68 -0
  267. package/timeline/replay.js +145 -0
  268. package/timeline/segments.d.ts +31 -0
  269. package/timeline/segments.js +39 -0
  270. package/timeline/share.d.ts +30 -0
  271. package/timeline/share.js +62 -0
  272. package/timeline/stats.d.ts +34 -0
  273. package/timeline/stats.js +56 -0
  274. package/timeline/timings.d.ts +30 -0
  275. package/timeline/timings.js +54 -0
  276. package/zz/index.d.ts +29 -0
  277. package/zz/index.js +68 -0
  278. package/zz/seeds.d.ts +9 -0
  279. package/zz/seeds.js +29 -0
@@ -0,0 +1,35 @@
1
+ /**
2
+ * The method engine's data model: a method is an ordered list of stages, each
3
+ * a check on the CANONICAL view (see checks.ts / frames.ts). Concrete methods
4
+ * live in their own packages (@cubecore/cfop, roux, zz, petrus, lbl) —
5
+ * adding one needs no engine change.
6
+ *
7
+ * Stages may report a `detail` (e.g. which F2L slot was just finished), in
8
+ * canonical terms; the tracker translates it to physical faces.
9
+ */
10
+ import type { State } from "./state.js";
11
+ export interface Stage {
12
+ id: string;
13
+ label: string;
14
+ done(s: State): boolean;
15
+ /** Canonical slot / sub-step finished at this stage, given the details already reported. */
16
+ detail?(s: State, earlier: readonly string[]): string | undefined;
17
+ /**
18
+ * The case this stage starts from (e.g. "OLL 27", "T", "OLL skip"),
19
+ * recognised on the state the previous stage finished in.
20
+ */
21
+ recognize?(s: State): string | undefined;
22
+ }
23
+ export interface Method {
24
+ id: string;
25
+ name: string;
26
+ stages: readonly Stage[];
27
+ }
28
+ /**
29
+ * Four stages "`label` 1…4" for things finished in any order (F2L pairs,
30
+ * LBL corners…): stage n is done once `count` lists n items (and `guard`
31
+ * holds); its detail is the item that was just added.
32
+ */
33
+ export declare function countedStages(prefix: string, label: string, count: (s: State) => readonly string[], guard: (s: State) => boolean): Stage[];
34
+ /** Orient, permute, AUF — the last layer of CFOP, ZZ and Petrus. */
35
+ export declare const LAST_LAYER_STAGES: readonly Stage[];
package/core/method.js ADDED
@@ -0,0 +1,30 @@
1
+ /**
2
+ * The method engine's data model: a method is an ordered list of stages, each
3
+ * a check on the CANONICAL view (see checks.ts / frames.ts). Concrete methods
4
+ * live in their own packages (@cubecore/cfop, roux, zz, petrus, lbl) —
5
+ * adding one needs no engine change.
6
+ *
7
+ * Stages may report a `detail` (e.g. which F2L slot was just finished), in
8
+ * canonical terms; the tracker translates it to physical faces.
9
+ */
10
+ import * as C from "./checks.js";
11
+ const newItem = (done, earlier) => done.find((x) => !earlier.includes(x));
12
+ /**
13
+ * Four stages "`label` 1…4" for things finished in any order (F2L pairs,
14
+ * LBL corners…): stage n is done once `count` lists n items (and `guard`
15
+ * holds); its detail is the item that was just added.
16
+ */
17
+ export function countedStages(prefix, label, count, guard) {
18
+ return [1, 2, 3, 4].map((n) => ({
19
+ id: `${prefix}-${n}`,
20
+ label: `${label} ${n}`,
21
+ done: (s) => guard(s) && count(s).length >= n,
22
+ detail: (s, earlier) => newItem(count(s), earlier),
23
+ }));
24
+ }
25
+ /** Orient, permute, AUF — the last layer of CFOP, ZZ and Petrus. */
26
+ export const LAST_LAYER_STAGES = [
27
+ { id: "oll", label: "OLL", done: (s) => C.f2lSolved(s) && C.topOriented(s) },
28
+ { id: "pll", label: "PLL", done: C.solvedUpToAuf },
29
+ { id: "auf", label: "AUF", done: C.isSolved },
30
+ ];
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Moves as facelet permutations, derived from geometry.ts.
3
+ *
4
+ * Families: face turns U R F D L B, slices M E S (M follows L, E follows D,
5
+ * S follows F), wide turns u r f d l b (also written Uw, Rw…), and whole-cube
6
+ * rotations x y z (x follows R, y follows U, z follows F).
7
+ *
8
+ * A permutation `p` is applied as `next[i] = prev[p[i]]`.
9
+ */
10
+ import { type Axis } from "./geometry.js";
11
+ export type MoveFamily = "U" | "R" | "F" | "D" | "L" | "B" | "u" | "r" | "f" | "d" | "l" | "b" | "M" | "E" | "S" | "x" | "y" | "z";
12
+ /** 1 = clockwise quarter, 2 = half, -1 = counter-clockwise (prime). */
13
+ export type Amount = 1 | 2 | -1;
14
+ export interface Move {
15
+ family: MoveFamily;
16
+ amount: Amount;
17
+ /**
18
+ * Signed quarter turns as written, when that says more than `amount`:
19
+ * R2' = -2 (a half turn, counter-clockwise), R3 = 3, R3' = -3. Only the
20
+ * way to turn (arrows, display) — the state uses `amount`.
21
+ */
22
+ written?: number;
23
+ }
24
+ export type MoveKind = "face" | "wide" | "slice" | "rotation";
25
+ interface FamilyDef {
26
+ axis: Axis;
27
+ /** Counter-clockwise quarter turns about +axis for ONE clockwise turn of this family. */
28
+ q: 1 | -1;
29
+ /** Which layer coordinates along the axis turn. */
30
+ layers: readonly number[];
31
+ kind: MoveKind;
32
+ }
33
+ export declare const FAMILY: Record<MoveFamily, FamilyDef>;
34
+ export declare const MOVE_FAMILIES: MoveFamily[];
35
+ export type Permutation = Uint8Array;
36
+ export declare function identityPermutation(): Permutation;
37
+ /** `a` then `b`. */
38
+ export declare function composePermutations(a: Permutation, b: Permutation): Permutation;
39
+ export declare function movePermutation(move: Move): Permutation;
40
+ export declare function moveKind(move: Move): MoveKind;
41
+ export declare function invertMove(move: Move): Move;
42
+ export declare function formatMove(move: Move): string;
43
+ /** Signed quarter turns the way it's meant to be turned (written, else from the amount: R2 = 2). */
44
+ export declare const writtenQuarters: (move: Move) => number;
45
+ /** Normalise a quarter-turn count to an Amount, or null for a full turn. */
46
+ export declare function toAmount(quarters: number): Amount | null;
47
+ export declare const amountQuarters: (a: Amount) => number;
48
+ export {};
package/core/moves.js ADDED
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Moves as facelet permutations, derived from geometry.ts.
3
+ *
4
+ * Families: face turns U R F D L B, slices M E S (M follows L, E follows D,
5
+ * S follows F), wide turns u r f d l b (also written Uw, Rw…), and whole-cube
6
+ * rotations x y z (x follows R, y follows U, z follows F).
7
+ *
8
+ * A permutation `p` is applied as `next[i] = prev[p[i]]`.
9
+ */
10
+ import { FACELETS, FACELET_COUNT, faceletAt, rotate } from "./geometry.js";
11
+ const ALL = [-1, 0, 1];
12
+ export const FAMILY = {
13
+ R: { axis: 0, q: -1, layers: [1], kind: "face" },
14
+ L: { axis: 0, q: 1, layers: [-1], kind: "face" },
15
+ U: { axis: 1, q: -1, layers: [1], kind: "face" },
16
+ D: { axis: 1, q: 1, layers: [-1], kind: "face" },
17
+ F: { axis: 2, q: -1, layers: [1], kind: "face" },
18
+ B: { axis: 2, q: 1, layers: [-1], kind: "face" },
19
+ r: { axis: 0, q: -1, layers: [1, 0], kind: "wide" },
20
+ l: { axis: 0, q: 1, layers: [-1, 0], kind: "wide" },
21
+ u: { axis: 1, q: -1, layers: [1, 0], kind: "wide" },
22
+ d: { axis: 1, q: 1, layers: [-1, 0], kind: "wide" },
23
+ f: { axis: 2, q: -1, layers: [1, 0], kind: "wide" },
24
+ b: { axis: 2, q: 1, layers: [-1, 0], kind: "wide" },
25
+ M: { axis: 0, q: 1, layers: [0], kind: "slice" },
26
+ E: { axis: 1, q: 1, layers: [0], kind: "slice" },
27
+ S: { axis: 2, q: -1, layers: [0], kind: "slice" },
28
+ x: { axis: 0, q: -1, layers: ALL, kind: "rotation" },
29
+ y: { axis: 1, q: -1, layers: ALL, kind: "rotation" },
30
+ z: { axis: 2, q: -1, layers: ALL, kind: "rotation" },
31
+ };
32
+ export const MOVE_FAMILIES = Object.keys(FAMILY);
33
+ export function identityPermutation() {
34
+ return Uint8Array.from({ length: FACELET_COUNT }, (_, i) => i);
35
+ }
36
+ /** `a` then `b`. */
37
+ export function composePermutations(a, b) {
38
+ const out = new Uint8Array(FACELET_COUNT);
39
+ for (let i = 0; i < FACELET_COUNT; i++)
40
+ out[i] = a[b[i]];
41
+ return out;
42
+ }
43
+ function quarterPermutation(def) {
44
+ const perm = identityPermutation();
45
+ for (const f of FACELETS) {
46
+ if (!def.layers.includes(f.pos[def.axis]))
47
+ continue;
48
+ const dest = faceletAt(rotate(f.pos, def.axis, def.q), rotate(f.normal, def.axis, def.q));
49
+ perm[dest] = f.index;
50
+ }
51
+ return perm;
52
+ }
53
+ const PERMS = new Map();
54
+ for (const family of MOVE_FAMILIES) {
55
+ const q1 = quarterPermutation(FAMILY[family]);
56
+ const q2 = composePermutations(q1, q1);
57
+ const q3 = composePermutations(q2, q1);
58
+ PERMS.set(`${family}1`, q1);
59
+ PERMS.set(`${family}2`, q2);
60
+ PERMS.set(`${family}-1`, q3);
61
+ }
62
+ export function movePermutation(move) {
63
+ return PERMS.get(`${move.family}${move.amount}`);
64
+ }
65
+ export function moveKind(move) {
66
+ return FAMILY[move.family].kind;
67
+ }
68
+ export function invertMove(move) {
69
+ const out = { family: move.family, amount: move.amount === 2 ? 2 : -move.amount };
70
+ if (move.written !== undefined)
71
+ out.written = -move.written;
72
+ return out;
73
+ }
74
+ export function formatMove(move) {
75
+ if (move.written !== undefined) {
76
+ const n = Math.abs(move.written);
77
+ return move.family + (n === 1 ? "" : String(n)) + (move.written < 0 ? "'" : "");
78
+ }
79
+ return move.family + (move.amount === 2 ? "2" : move.amount === -1 ? "'" : "");
80
+ }
81
+ /** Signed quarter turns the way it's meant to be turned (written, else from the amount: R2 = 2). */
82
+ export const writtenQuarters = (move) => move.written ?? (move.amount === -1 ? -1 : move.amount);
83
+ /** Normalise a quarter-turn count to an Amount, or null for a full turn. */
84
+ export function toAmount(quarters) {
85
+ const n = ((quarters % 4) + 4) % 4;
86
+ return n === 0 ? null : n === 1 ? 1 : n === 2 ? 2 : -1;
87
+ }
88
+ export const amountQuarters = (a) => (a === -1 ? 3 : a);
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Algorithm notation: parse, format, invert, simplify, mirror, metrics.
3
+ *
4
+ * Accepted: face turns (R R' R2 R2' R3), wide turns in both spellings
5
+ * (r / Rw, r' / Rw'), slices M E S, rotations x y z, grouping with repetition
6
+ * `(R U R' U')3`, commutators `[A, B]` = A B A' B', conjugates `[A: B]` =
7
+ * A B A'. Typographic apostrophes (’ ′) are accepted, `//` starts a comment,
8
+ * `.` is a pause (one beat). parseAlg flattens everything to a Move[];
9
+ * parseAlgDocument also says where each move came from in the text.
10
+ */
11
+ import { type Move } from "./moves.js";
12
+ export declare class NotationError extends Error {
13
+ readonly position: number;
14
+ constructor(message: string, position: number);
15
+ }
16
+ /** Where a move (or pause) came from in the text: [start, end) character offsets. */
17
+ export interface SourceRange {
18
+ start: number;
19
+ end: number;
20
+ }
21
+ /**
22
+ * An algorithm as written: the moves, where each came from in the text
23
+ * (repeats and commutator inverses point at the move they copy), pauses (`.`,
24
+ * one beat each — `pausesBefore[k]` counts the beats before move k), and
25
+ * `// comments`. For showing the text in sync with playback.
26
+ */
27
+ export interface AlgDocument {
28
+ moves: Move[];
29
+ sources: SourceRange[];
30
+ pausesBefore: number[];
31
+ comments: (SourceRange & {
32
+ text: string;
33
+ })[];
34
+ }
35
+ export declare function parseAlgDocument(text: string): AlgDocument;
36
+ export declare function parseAlg(text: string): Move[];
37
+ export declare function formatAlg(moves: readonly Move[]): string;
38
+ export declare function invert(moves: readonly Move[]): Move[];
39
+ /** Merge adjacent turns of the same family (R R → R2, R R' → nothing), repeatedly. */
40
+ export declare function simplify(moves: readonly Move[]): Move[];
41
+ /** Mirror left↔right (the plane between L and R): R ↔ L', U → U', M and x unchanged, … */
42
+ export declare function mirrorLR(moves: readonly Move[]): Move[];
43
+ export type Metric = "htm" | "qtm" | "stm" | "etm";
44
+ /**
45
+ * HTM: face/wide turn = 1, slice = 2 (two outer layers), rotation = 0.
46
+ * QTM: like HTM but half turns count 2. STM: slices count 1. ETM: every token 1.
47
+ */
48
+ export declare function moveCount(moves: readonly Move[], metric?: Metric): number;
@@ -0,0 +1,226 @@
1
+ /**
2
+ * Algorithm notation: parse, format, invert, simplify, mirror, metrics.
3
+ *
4
+ * Accepted: face turns (R R' R2 R2' R3), wide turns in both spellings
5
+ * (r / Rw, r' / Rw'), slices M E S, rotations x y z, grouping with repetition
6
+ * `(R U R' U')3`, commutators `[A, B]` = A B A' B', conjugates `[A: B]` =
7
+ * A B A'. Typographic apostrophes (’ ′) are accepted, `//` starts a comment,
8
+ * `.` is a pause (one beat). parseAlg flattens everything to a Move[];
9
+ * parseAlgDocument also says where each move came from in the text.
10
+ */
11
+ import { FAMILY, amountQuarters, formatMove, invertMove, moveKind, toAmount } from "./moves.js";
12
+ export class NotationError extends Error {
13
+ position;
14
+ constructor(message, position) {
15
+ super(`${message} (at ${position})`);
16
+ this.position = position;
17
+ }
18
+ }
19
+ const FACE_LETTERS = "URFDLB";
20
+ const WIDE_LETTERS = "urfdlb";
21
+ const OTHER_LETTERS = "MESxyz";
22
+ class Parser {
23
+ text;
24
+ pos = 0;
25
+ comments = [];
26
+ constructor(text) {
27
+ this.text = text;
28
+ }
29
+ parse() {
30
+ const items = this.sequence(null);
31
+ this.skipSpace();
32
+ if (this.pos < this.text.length)
33
+ throw new NotationError(`Unexpected "${this.text[this.pos]}"`, this.pos);
34
+ return items;
35
+ }
36
+ skipSpace() {
37
+ while (this.pos < this.text.length) {
38
+ const c = this.text[this.pos];
39
+ if (c === "/" && this.text[this.pos + 1] === "/") {
40
+ const start = this.pos;
41
+ while (this.pos < this.text.length && this.text[this.pos] !== "\n")
42
+ this.pos++;
43
+ this.comments.push({ start, end: this.pos, text: this.text.slice(start + 2, this.pos).trim() });
44
+ }
45
+ else if (/\s/.test(c))
46
+ this.pos++;
47
+ else
48
+ break;
49
+ }
50
+ }
51
+ /** Items until `stop` (one of the given closing characters) or end of input. */
52
+ sequence(stop) {
53
+ const out = [];
54
+ for (;;) {
55
+ this.skipSpace();
56
+ const c = this.text[this.pos];
57
+ if (c === undefined || (stop !== null && stop.includes(c)))
58
+ return out;
59
+ if (c === "(") {
60
+ this.pos++;
61
+ const inner = this.sequence(")");
62
+ this.expect(")");
63
+ out.push(...repeat(inner, this.count()));
64
+ }
65
+ else if (c === "[") {
66
+ this.pos++;
67
+ const a = this.sequence(",:");
68
+ const sep = this.text[this.pos];
69
+ if (sep !== "," && sep !== ":")
70
+ throw new NotationError('Expected "," or ":" in brackets', this.pos);
71
+ this.pos++;
72
+ const b = this.sequence("]");
73
+ this.expect("]");
74
+ const group = sep === "," ? [...a, ...b, ...invertItems(a), ...invertItems(b)] : [...a, ...b, ...invertItems(a)];
75
+ out.push(...repeat(group, this.count()));
76
+ }
77
+ else if (c === ".") {
78
+ // A pause (one beat, as in cubing.js).
79
+ out.push({ pause: true, start: this.pos, end: this.pos + 1 });
80
+ this.pos++;
81
+ }
82
+ else {
83
+ const start = this.pos;
84
+ const m = this.move();
85
+ if (m)
86
+ out.push({ move: m, start, end: this.pos }); // null = a full turn (R4), which does nothing
87
+ }
88
+ }
89
+ }
90
+ expect(c) {
91
+ this.skipSpace();
92
+ if (this.text[this.pos] !== c)
93
+ throw new NotationError(`Expected "${c}"`, this.pos);
94
+ this.pos++;
95
+ }
96
+ count() {
97
+ const m = /^\d+/.exec(this.text.slice(this.pos));
98
+ if (!m)
99
+ return 1;
100
+ this.pos += m[0].length;
101
+ return Number(m[0]);
102
+ }
103
+ move() {
104
+ const start = this.pos;
105
+ const c = this.text[this.pos];
106
+ let family;
107
+ if (FACE_LETTERS.includes(c)) {
108
+ this.pos++;
109
+ if (this.text[this.pos] === "w") {
110
+ this.pos++;
111
+ family = c.toLowerCase();
112
+ }
113
+ else
114
+ family = c;
115
+ }
116
+ else if (WIDE_LETTERS.includes(c) || OTHER_LETTERS.includes(c)) {
117
+ this.pos++;
118
+ family = c;
119
+ }
120
+ else {
121
+ throw new NotationError(`Unknown move "${c}"`, start);
122
+ }
123
+ let quarters = this.count();
124
+ if (/['’′]/.test(this.text[this.pos] ?? "")) {
125
+ this.pos++;
126
+ quarters = -quarters;
127
+ }
128
+ const amount = toAmount(quarters);
129
+ if (amount === null)
130
+ return null;
131
+ // Keep the way it's written when the amount alone would lose it (R2', R3, R3').
132
+ const plain = amount === -1 ? -1 : amount;
133
+ return quarters === plain ? { family, amount } : { family, amount, written: quarters };
134
+ }
135
+ }
136
+ function repeat(items, n) {
137
+ const out = [];
138
+ for (let i = 0; i < n; i++)
139
+ out.push(...items);
140
+ return out;
141
+ }
142
+ /** Inverse keeping each move's source (commutators / conjugates point back at what was written). */
143
+ function invertItems(items) {
144
+ return [...items].reverse().map((it) => ("move" in it ? { ...it, move: invertMove(it.move) } : it));
145
+ }
146
+ export function parseAlgDocument(text) {
147
+ const parser = new Parser(text);
148
+ const items = parser.parse();
149
+ const moves = [], sources = [], pausesBefore = [];
150
+ let beats = 0;
151
+ for (const it of items) {
152
+ if ("pause" in it)
153
+ beats++;
154
+ else {
155
+ moves.push(it.move);
156
+ sources.push({ start: it.start, end: it.end });
157
+ pausesBefore.push(beats);
158
+ beats = 0;
159
+ }
160
+ }
161
+ return { moves, sources, pausesBefore, comments: parser.comments };
162
+ }
163
+ export function parseAlg(text) {
164
+ return parseAlgDocument(text).moves;
165
+ }
166
+ export function formatAlg(moves) {
167
+ return moves.map(formatMove).join(" ");
168
+ }
169
+ export function invert(moves) {
170
+ return [...moves].reverse().map(invertMove);
171
+ }
172
+ /** Merge adjacent turns of the same family (R R → R2, R R' → nothing), repeatedly. */
173
+ export function simplify(moves) {
174
+ const out = [];
175
+ for (const m of moves) {
176
+ const last = out[out.length - 1];
177
+ if (last && last.family === m.family) {
178
+ out.pop();
179
+ const merged = toAmount(amountQuarters(last.amount) + amountQuarters(m.amount));
180
+ if (merged !== null)
181
+ out.push({ family: m.family, amount: merged });
182
+ }
183
+ else
184
+ out.push(m);
185
+ }
186
+ return out;
187
+ }
188
+ const LAYER_MIRROR = { R: "L", L: "R", r: "l", l: "r" };
189
+ /** Mirror left↔right (the plane between L and R): R ↔ L', U → U', M and x unchanged, … */
190
+ export function mirrorLR(moves) {
191
+ return moves.map((m) => {
192
+ const def = FAMILY[m.family];
193
+ if (def.axis === 0) {
194
+ const family = LAYER_MIRROR[m.family] ?? m.family;
195
+ const flip = FAMILY[family].q !== def.q;
196
+ return { family, amount: (flip && m.amount !== 2 ? -m.amount : m.amount) };
197
+ }
198
+ return invertMove(m);
199
+ });
200
+ }
201
+ /**
202
+ * HTM: face/wide turn = 1, slice = 2 (two outer layers), rotation = 0.
203
+ * QTM: like HTM but half turns count 2. STM: slices count 1. ETM: every token 1.
204
+ */
205
+ export function moveCount(moves, metric = "htm") {
206
+ let n = 0;
207
+ for (const m of moves) {
208
+ const kind = moveKind(m);
209
+ const half = m.amount === 2 ? 2 : 1;
210
+ switch (metric) {
211
+ case "etm":
212
+ n += 1;
213
+ break;
214
+ case "stm":
215
+ n += kind === "rotation" ? 0 : 1;
216
+ break;
217
+ case "htm":
218
+ n += kind === "rotation" ? 0 : kind === "slice" ? 2 : 1;
219
+ break;
220
+ case "qtm":
221
+ n += kind === "rotation" ? 0 : (kind === "slice" ? 2 : 1) * half;
222
+ break;
223
+ }
224
+ }
225
+ return n;
226
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * What a smart cube actually sees. A Bluetooth cube reports only turns of its
3
+ * six faces, measured against its own centres: no rotations, no slices, no
4
+ * wide turns. An algorithm written with them is, physically, face turns plus
5
+ * a change in how the cube is held:
6
+ *
7
+ * r = L + x (the two turning layers = the opposite face the other way, plus a rotation)
8
+ * M = R L' + x'
9
+ * r U = L F (after r the cube is held x: the U you turn is the physical F)
10
+ *
11
+ * `toFaceTurns` converts an algorithm to the face turns a smart cube would
12
+ * report; `OrientationTracker` does it move by move and always knows how the
13
+ * cube is held — where each centre is after an algorithm with M slices or
14
+ * rotations (Roux LSE, rotations between F2L pairs…).
15
+ *
16
+ * Orientation is a Frame (see frames.ts): canonical = the algorithm's view
17
+ * (U = up in your hands), physical = the cube's own centres.
18
+ */
19
+ import { type Frame } from "./frames.js";
20
+ import { type Axis, type Face } from "./geometry.js";
21
+ import { type Move } from "./moves.js";
22
+ /**
23
+ * One move in the holder's view → the face turns (in the holder's view) and
24
+ * the rotation of the whole cube it implies. A move turning the middle layer
25
+ * is the same as rotating the cube that way and turning the other layers back.
26
+ */
27
+ export declare function decompose(move: Move): {
28
+ turns: Move[];
29
+ rotation: {
30
+ axis: Axis;
31
+ quarters: number;
32
+ } | null;
33
+ };
34
+ /**
35
+ * Follows an algorithm move by move: which physical face each turn is, and how
36
+ * the cube is held afterwards. Feed it the moves of what you ask the solver to
37
+ * do; compare `push` results with what the smart cube reports.
38
+ */
39
+ export declare class OrientationTracker {
40
+ private m;
41
+ constructor(start?: Frame);
42
+ /** How the cube is held: `frame.face.U` is the physical face (centre) now on top, etc. */
43
+ get frame(): Frame;
44
+ /** Physical face (by its centre) in each position of the holder's view. */
45
+ get centers(): Record<Face, Face>;
46
+ /** The physical face turns this move is — what a smart cube would report. */
47
+ push(move: Move | string): Move[];
48
+ }
49
+ /** An algorithm as the face turns a smart cube reports, and how the cube is held at the end. */
50
+ export declare function toFaceTurns(alg: readonly Move[] | string, start?: Frame): {
51
+ moves: Move[];
52
+ frame: Frame;
53
+ };
@@ -0,0 +1,99 @@
1
+ /**
2
+ * What a smart cube actually sees. A Bluetooth cube reports only turns of its
3
+ * six faces, measured against its own centres: no rotations, no slices, no
4
+ * wide turns. An algorithm written with them is, physically, face turns plus
5
+ * a change in how the cube is held:
6
+ *
7
+ * r = L + x (the two turning layers = the opposite face the other way, plus a rotation)
8
+ * M = R L' + x'
9
+ * r U = L F (after r the cube is held x: the U you turn is the physical F)
10
+ *
11
+ * `toFaceTurns` converts an algorithm to the face turns a smart cube would
12
+ * report; `OrientationTracker` does it move by move and always knows how the
13
+ * cube is held — where each centre is after an algorithm with M slices or
14
+ * rotations (Roux LSE, rotations between F2L pairs…).
15
+ *
16
+ * Orientation is a Frame (see frames.ts): canonical = the algorithm's view
17
+ * (U = up in your hands), physical = the cube's own centres.
18
+ */
19
+ import { FRAMES, IDENTITY_FRAME } from "./frames.js";
20
+ import { FACE_NORMAL, apply, faceOfNormal, multiply, rotationMatrix } from "./geometry.js";
21
+ import { FAMILY, toAmount } from "./moves.js";
22
+ import { parseAlg } from "./notation.js";
23
+ const FACE_ON_AXIS = {
24
+ 0: { 1: "R", [-1]: "L" },
25
+ 1: { 1: "U", [-1]: "D" },
26
+ 2: { 1: "F", [-1]: "B" },
27
+ };
28
+ const key = (m) => m.flat().map((v) => (v === 0 ? 0 : v)).join(","); // no -0
29
+ const byMatrix = new Map(FRAMES.map((f) => [key(f.matrix), f]));
30
+ const frameOf = (m) => byMatrix.get(key(m));
31
+ const transpose = (m) => [
32
+ [m[0][0], m[1][0], m[2][0]],
33
+ [m[0][1], m[1][1], m[2][1]],
34
+ [m[0][2], m[1][2], m[2][2]],
35
+ ];
36
+ /** Signed quarter turns (CCW about +axis) of a move. */
37
+ const quartersOf = (m) => FAMILY[m.family].q * (m.amount === -1 ? -1 : m.amount === 2 ? 2 : 1);
38
+ /** A face turn of `quarters` CCW about +axis on the layer at `layer` (±1), as a Move of that face. */
39
+ function faceTurn(axis, layer, quarters) {
40
+ const face = FACE_ON_AXIS[axis][layer];
41
+ const amount = toAmount(quarters * FAMILY[face].q); // q is ±1, so dividing = multiplying
42
+ return amount === null ? null : { family: face, amount };
43
+ }
44
+ /**
45
+ * One move in the holder's view → the face turns (in the holder's view) and
46
+ * the rotation of the whole cube it implies. A move turning the middle layer
47
+ * is the same as rotating the cube that way and turning the other layers back.
48
+ */
49
+ export function decompose(move) {
50
+ const def = FAMILY[move.family];
51
+ const q = quartersOf(move);
52
+ if (!def.layers.includes(0)) {
53
+ return { turns: def.layers.map((l) => faceTurn(def.axis, l, q)).filter((m) => m !== null), rotation: null };
54
+ }
55
+ const others = [-1, 1].filter((l) => !def.layers.includes(l));
56
+ return { turns: others.map((l) => faceTurn(def.axis, l, -q)).filter((m) => m !== null), rotation: { axis: def.axis, quarters: q } };
57
+ }
58
+ /**
59
+ * Follows an algorithm move by move: which physical face each turn is, and how
60
+ * the cube is held afterwards. Feed it the moves of what you ask the solver to
61
+ * do; compare `push` results with what the smart cube reports.
62
+ */
63
+ export class OrientationTracker {
64
+ m;
65
+ constructor(start = IDENTITY_FRAME) {
66
+ this.m = start.matrix;
67
+ }
68
+ /** How the cube is held: `frame.face.U` is the physical face (centre) now on top, etc. */
69
+ get frame() {
70
+ return frameOf(this.m);
71
+ }
72
+ /** Physical face (by its centre) in each position of the holder's view. */
73
+ get centers() {
74
+ return this.frame.face;
75
+ }
76
+ /** The physical face turns this move is — what a smart cube would report. */
77
+ push(move) {
78
+ const moves = typeof move === "string" ? parseAlg(move) : [move];
79
+ const out = [];
80
+ for (const mv of moves) {
81
+ const { turns, rotation } = decompose(mv);
82
+ for (const t of turns) {
83
+ // Holder-view face → the physical face in that position (same turning direction: rotations are proper).
84
+ const physical = faceOfNormal(apply(this.m, FACE_NORMAL[t.family]));
85
+ out.push({ family: physical, amount: t.amount });
86
+ }
87
+ // Turning the cube by R (holder's view) moves the body: body = M · R⁻¹ · view.
88
+ if (rotation)
89
+ this.m = multiply(this.m, transpose(rotationMatrix(rotation.axis, rotation.quarters)));
90
+ }
91
+ return out;
92
+ }
93
+ }
94
+ /** An algorithm as the face turns a smart cube reports, and how the cube is held at the end. */
95
+ export function toFaceTurns(alg, start = IDENTITY_FRAME) {
96
+ const t = new OrientationTracker(start);
97
+ const moves = (typeof alg === "string" ? parseAlg(alg) : alg).flatMap((m) => t.push(m));
98
+ return { moves, frame: t.frame };
99
+ }