@cubingcenter/scrambler 0.5.3 → 0.6.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 (244) hide show
  1. package/README.md +24 -9
  2. package/dist/cli/index.js +0 -0
  3. package/dist/core/api.d.ts.map +1 -1
  4. package/dist/core/api.js +11 -1
  5. package/dist/core/types.d.ts +1 -1
  6. package/dist/core/types.d.ts.map +1 -1
  7. package/dist/puzzles/cube4/index.d.ts +4 -2
  8. package/dist/puzzles/cube4/index.d.ts.map +1 -1
  9. package/dist/puzzles/cube4/index.js +3 -1
  10. package/dist/puzzles/cube4/random-state.d.ts +16 -0
  11. package/dist/puzzles/cube4/random-state.d.ts.map +1 -0
  12. package/dist/puzzles/cube4/random-state.js +76 -0
  13. package/dist/puzzles/cube4/reduction/browser-loader.d.ts +18 -0
  14. package/dist/puzzles/cube4/reduction/browser-loader.d.ts.map +1 -0
  15. package/dist/puzzles/cube4/reduction/browser-loader.js +95 -0
  16. package/dist/puzzles/cube4/reduction/center1.d.ts +65 -0
  17. package/dist/puzzles/cube4/reduction/center1.d.ts.map +1 -0
  18. package/dist/puzzles/cube4/reduction/center1.js +245 -0
  19. package/dist/puzzles/cube4/reduction/center2.d.ts +91 -0
  20. package/dist/puzzles/cube4/reduction/center2.d.ts.map +1 -0
  21. package/dist/puzzles/cube4/reduction/center2.js +449 -0
  22. package/dist/puzzles/cube4/reduction/center3.d.ts +47 -0
  23. package/dist/puzzles/cube4/reduction/center3.d.ts.map +1 -0
  24. package/dist/puzzles/cube4/reduction/center3.js +278 -0
  25. package/dist/puzzles/cube4/reduction/edge3.d.ts +100 -0
  26. package/dist/puzzles/cube4/reduction/edge3.d.ts.map +1 -0
  27. package/dist/puzzles/cube4/reduction/edge3.js +511 -0
  28. package/dist/puzzles/cube4/reduction/generated/center1-distance.bin +1 -0
  29. package/dist/puzzles/cube4/reduction/generated/center1-manifest.json +46 -0
  30. package/dist/puzzles/cube4/reduction/generated/center1-move-maps.bin +0 -0
  31. package/dist/puzzles/cube4/reduction/generated/center2-ct-move.bin +0 -0
  32. package/dist/puzzles/cube4/reduction/generated/center2-manifest.json +87 -0
  33. package/dist/puzzles/cube4/reduction/generated/center2-pruning.bin +1 -0
  34. package/dist/puzzles/cube4/reduction/generated/center2-rl-move.bin +0 -0
  35. package/dist/puzzles/cube4/reduction/generated/center3-manifest.json +40 -0
  36. package/dist/puzzles/cube4/reduction/generated/center3-move.bin +0 -0
  37. package/dist/puzzles/cube4/reduction/generated/center3-pruning.bin +0 -0
  38. package/dist/puzzles/cube4/reduction/generated/edge3-distances.bin +1 -0
  39. package/dist/puzzles/cube4/reduction/generated/edge3-manifest.json +12 -0
  40. package/dist/puzzles/cube4/reduction/geometry.d.ts +33 -0
  41. package/dist/puzzles/cube4/reduction/geometry.d.ts.map +1 -0
  42. package/dist/puzzles/cube4/reduction/geometry.js +302 -0
  43. package/dist/puzzles/cube4/reduction/node-loader.d.ts +17 -0
  44. package/dist/puzzles/cube4/reduction/node-loader.d.ts.map +1 -0
  45. package/dist/puzzles/cube4/reduction/node-loader.js +98 -0
  46. package/dist/puzzles/cube4/reduction/pairing-coordinate.d.ts +130 -0
  47. package/dist/puzzles/cube4/reduction/pairing-coordinate.d.ts.map +1 -0
  48. package/dist/puzzles/cube4/reduction/pairing-coordinate.js +281 -0
  49. package/dist/puzzles/cube4/reduction/phase3-search.d.ts +42 -0
  50. package/dist/puzzles/cube4/reduction/phase3-search.d.ts.map +1 -0
  51. package/dist/puzzles/cube4/reduction/phase3-search.js +133 -0
  52. package/dist/puzzles/cube4/reduction/phase3.d.ts +34 -0
  53. package/dist/puzzles/cube4/reduction/phase3.d.ts.map +1 -0
  54. package/dist/puzzles/cube4/reduction/phase3.js +83 -0
  55. package/dist/puzzles/cube4/reduction/phase4.d.ts +35 -0
  56. package/dist/puzzles/cube4/reduction/phase4.d.ts.map +1 -0
  57. package/dist/puzzles/cube4/reduction/phase4.js +82 -0
  58. package/dist/puzzles/cube4/reduction/solve.d.ts +31 -0
  59. package/dist/puzzles/cube4/reduction/solve.d.ts.map +1 -0
  60. package/dist/puzzles/cube4/reduction/solve.js +311 -0
  61. package/dist/puzzles/cube4/scrambler.d.ts +9 -1
  62. package/dist/puzzles/cube4/scrambler.d.ts.map +1 -1
  63. package/dist/puzzles/cube4/scrambler.js +123 -81
  64. package/dist/puzzles/cube4/types.d.ts +6 -0
  65. package/dist/puzzles/cube4/types.d.ts.map +1 -1
  66. package/dist/puzzles/cube4/validation.d.ts +6 -0
  67. package/dist/puzzles/cube4/validation.d.ts.map +1 -0
  68. package/dist/puzzles/cube4/validation.js +91 -0
  69. package/dist/puzzles/fto/coordinates.d.ts +53 -0
  70. package/dist/puzzles/fto/coordinates.d.ts.map +1 -0
  71. package/dist/puzzles/fto/coordinates.js +228 -0
  72. package/dist/puzzles/fto/facelets.d.ts +6 -0
  73. package/dist/puzzles/fto/facelets.d.ts.map +1 -0
  74. package/dist/puzzles/fto/facelets.js +99 -0
  75. package/dist/puzzles/fto/index.d.ts +12 -0
  76. package/dist/puzzles/fto/index.d.ts.map +1 -0
  77. package/dist/puzzles/fto/index.js +13 -0
  78. package/dist/puzzles/fto/moves.d.ts +12 -0
  79. package/dist/puzzles/fto/moves.d.ts.map +1 -0
  80. package/dist/puzzles/fto/moves.js +115 -0
  81. package/dist/puzzles/fto/random-state.d.ts +17 -0
  82. package/dist/puzzles/fto/random-state.d.ts.map +1 -0
  83. package/dist/puzzles/fto/random-state.js +40 -0
  84. package/dist/puzzles/fto/scrambler.d.ts +6 -0
  85. package/dist/puzzles/fto/scrambler.d.ts.map +1 -0
  86. package/dist/puzzles/fto/scrambler.js +49 -0
  87. package/dist/puzzles/fto/solver/engine.d.ts +18 -0
  88. package/dist/puzzles/fto/solver/engine.d.ts.map +1 -0
  89. package/dist/puzzles/fto/solver/engine.js +1168 -0
  90. package/dist/puzzles/fto/solver/index.d.ts +2 -0
  91. package/dist/puzzles/fto/solver/index.d.ts.map +1 -0
  92. package/dist/puzzles/fto/solver/index.js +1 -0
  93. package/dist/puzzles/fto/state.d.ts +16 -0
  94. package/dist/puzzles/fto/state.d.ts.map +1 -0
  95. package/dist/puzzles/fto/state.js +61 -0
  96. package/dist/puzzles/fto/types.d.ts +32 -0
  97. package/dist/puzzles/fto/types.d.ts.map +1 -0
  98. package/dist/puzzles/fto/types.js +46 -0
  99. package/dist/puzzles/fto/validation.d.ts +19 -0
  100. package/dist/puzzles/fto/validation.d.ts.map +1 -0
  101. package/dist/puzzles/fto/validation.js +143 -0
  102. package/dist/puzzles/index.d.ts +2 -0
  103. package/dist/puzzles/index.d.ts.map +1 -1
  104. package/dist/puzzles/index.js +6 -0
  105. package/dist/puzzles/skewb/index.d.ts +1 -1
  106. package/dist/puzzles/skewb/index.d.ts.map +1 -1
  107. package/dist/puzzles/skewb/index.js +1 -1
  108. package/dist/puzzles/skewb/random-state.d.ts.map +1 -1
  109. package/dist/puzzles/skewb/random-state.js +4 -1
  110. package/dist/puzzles/sq1/facelets.d.ts +12 -0
  111. package/dist/puzzles/sq1/facelets.d.ts.map +1 -0
  112. package/dist/puzzles/sq1/facelets.js +26 -0
  113. package/dist/puzzles/sq1/index.d.ts +10 -0
  114. package/dist/puzzles/sq1/index.d.ts.map +1 -0
  115. package/dist/puzzles/sq1/index.js +11 -0
  116. package/dist/puzzles/sq1/moves.d.ts +55 -0
  117. package/dist/puzzles/sq1/moves.d.ts.map +1 -0
  118. package/dist/puzzles/sq1/moves.js +220 -0
  119. package/dist/puzzles/sq1/random-state.d.ts +6 -0
  120. package/dist/puzzles/sq1/random-state.d.ts.map +1 -0
  121. package/dist/puzzles/sq1/random-state.js +49 -0
  122. package/dist/puzzles/sq1/scrambler.d.ts +10 -0
  123. package/dist/puzzles/sq1/scrambler.d.ts.map +1 -0
  124. package/dist/puzzles/sq1/scrambler.js +90 -0
  125. package/dist/puzzles/sq1/solver/browser-loader.d.ts +5 -0
  126. package/dist/puzzles/sq1/solver/browser-loader.d.ts.map +1 -0
  127. package/dist/puzzles/sq1/solver/browser-loader.js +92 -0
  128. package/dist/puzzles/sq1/solver/full-cube.d.ts +53 -0
  129. package/dist/puzzles/sq1/solver/full-cube.d.ts.map +1 -0
  130. package/dist/puzzles/sq1/solver/full-cube.js +281 -0
  131. package/dist/puzzles/sq1/solver/generated/manifest.json +50 -0
  132. package/dist/puzzles/sq1/solver/generated/shape-bottom-move.bin +0 -0
  133. package/dist/puzzles/sq1/solver/generated/shape-idx.bin +0 -0
  134. package/dist/puzzles/sq1/solver/generated/shape-prun.bin +0 -0
  135. package/dist/puzzles/sq1/solver/generated/shape-top-move.bin +0 -0
  136. package/dist/puzzles/sq1/solver/generated/shape-twist-move.bin +0 -0
  137. package/dist/puzzles/sq1/solver/generated/square-bottom-move.bin +0 -0
  138. package/dist/puzzles/sq1/solver/generated/square-prun.bin +0 -0
  139. package/dist/puzzles/sq1/solver/generated/square-top-move.bin +0 -0
  140. package/dist/puzzles/sq1/solver/generated/square-twist-move.bin +0 -0
  141. package/dist/puzzles/sq1/solver/index.d.ts +5 -0
  142. package/dist/puzzles/sq1/solver/index.d.ts.map +1 -0
  143. package/dist/puzzles/sq1/solver/index.js +4 -0
  144. package/dist/puzzles/sq1/solver/node-loader.d.ts +4 -0
  145. package/dist/puzzles/sq1/solver/node-loader.d.ts.map +1 -0
  146. package/dist/puzzles/sq1/solver/node-loader.js +113 -0
  147. package/dist/puzzles/sq1/solver/search.d.ts +27 -0
  148. package/dist/puzzles/sq1/solver/search.d.ts.map +1 -0
  149. package/dist/puzzles/sq1/solver/search.js +225 -0
  150. package/dist/puzzles/sq1/solver/shape.d.ts +22 -0
  151. package/dist/puzzles/sq1/solver/shape.d.ts.map +1 -0
  152. package/dist/puzzles/sq1/solver/shape.js +185 -0
  153. package/dist/puzzles/sq1/solver/square.d.ts +10 -0
  154. package/dist/puzzles/sq1/solver/square.d.ts.map +1 -0
  155. package/dist/puzzles/sq1/solver/square.js +90 -0
  156. package/dist/puzzles/sq1/solver/table-metadata.d.ts +48 -0
  157. package/dist/puzzles/sq1/solver/table-metadata.d.ts.map +1 -0
  158. package/dist/puzzles/sq1/solver/table-metadata.js +47 -0
  159. package/dist/puzzles/sq1/solver/tables.d.ts +17 -0
  160. package/dist/puzzles/sq1/solver/tables.d.ts.map +1 -0
  161. package/dist/puzzles/sq1/solver/tables.js +66 -0
  162. package/dist/puzzles/sq1/state.d.ts +10 -0
  163. package/dist/puzzles/sq1/state.d.ts.map +1 -0
  164. package/dist/puzzles/sq1/state.js +74 -0
  165. package/dist/puzzles/sq1/types.d.ts +32 -0
  166. package/dist/puzzles/sq1/types.d.ts.map +1 -0
  167. package/dist/puzzles/sq1/types.js +1 -0
  168. package/dist/puzzles/sq1/validation.d.ts +7 -0
  169. package/dist/puzzles/sq1/validation.d.ts.map +1 -0
  170. package/dist/puzzles/sq1/validation.js +64 -0
  171. package/dist/random/prng.d.ts.map +1 -1
  172. package/dist/random/prng.js +9 -4
  173. package/dist-cjs/core/api.js +11 -1
  174. package/dist-cjs/puzzles/cube4/index.js +11 -4
  175. package/dist-cjs/puzzles/cube4/random-state.js +79 -0
  176. package/dist-cjs/puzzles/cube4/reduction/browser-loader.js +100 -0
  177. package/dist-cjs/puzzles/cube4/reduction/center1.js +258 -0
  178. package/dist-cjs/puzzles/cube4/reduction/center2.js +462 -0
  179. package/dist-cjs/puzzles/cube4/reduction/center3.js +284 -0
  180. package/dist-cjs/puzzles/cube4/reduction/edge3.js +522 -0
  181. package/dist-cjs/puzzles/cube4/reduction/generated/center1-distance.bin +1 -0
  182. package/dist-cjs/puzzles/cube4/reduction/generated/center1-manifest.json +46 -0
  183. package/dist-cjs/puzzles/cube4/reduction/generated/center1-move-maps.bin +0 -0
  184. package/dist-cjs/puzzles/cube4/reduction/generated/center2-ct-move.bin +0 -0
  185. package/dist-cjs/puzzles/cube4/reduction/generated/center2-manifest.json +87 -0
  186. package/dist-cjs/puzzles/cube4/reduction/generated/center2-pruning.bin +1 -0
  187. package/dist-cjs/puzzles/cube4/reduction/generated/center2-rl-move.bin +0 -0
  188. package/dist-cjs/puzzles/cube4/reduction/generated/center3-manifest.json +40 -0
  189. package/dist-cjs/puzzles/cube4/reduction/generated/center3-move.bin +0 -0
  190. package/dist-cjs/puzzles/cube4/reduction/generated/center3-pruning.bin +0 -0
  191. package/dist-cjs/puzzles/cube4/reduction/generated/edge3-distances.bin +1 -0
  192. package/dist-cjs/puzzles/cube4/reduction/generated/edge3-manifest.json +12 -0
  193. package/dist-cjs/puzzles/cube4/reduction/geometry.js +308 -0
  194. package/dist-cjs/puzzles/cube4/reduction/node-loader.js +104 -0
  195. package/dist-cjs/puzzles/cube4/reduction/pairing-coordinate.js +294 -0
  196. package/dist-cjs/puzzles/cube4/reduction/phase3-search.js +138 -0
  197. package/dist-cjs/puzzles/cube4/reduction/phase3.js +86 -0
  198. package/dist-cjs/puzzles/cube4/reduction/phase4.js +87 -0
  199. package/dist-cjs/puzzles/cube4/reduction/solve.js +314 -0
  200. package/dist-cjs/puzzles/cube4/scrambler.js +122 -79
  201. package/dist-cjs/puzzles/cube4/validation.js +97 -0
  202. package/dist-cjs/puzzles/fto/coordinates.js +240 -0
  203. package/dist-cjs/puzzles/fto/facelets.js +104 -0
  204. package/dist-cjs/puzzles/fto/index.js +68 -0
  205. package/dist-cjs/puzzles/fto/moves.js +126 -0
  206. package/dist-cjs/puzzles/fto/random-state.js +43 -0
  207. package/dist-cjs/puzzles/fto/scrambler.js +53 -0
  208. package/dist-cjs/puzzles/fto/solver/engine.js +1175 -0
  209. package/dist-cjs/puzzles/fto/solver/index.js +9 -0
  210. package/dist-cjs/puzzles/fto/state.js +69 -0
  211. package/dist-cjs/puzzles/fto/types.js +49 -0
  212. package/dist-cjs/puzzles/fto/validation.js +147 -0
  213. package/dist-cjs/puzzles/index.js +7 -1
  214. package/dist-cjs/puzzles/skewb/index.js +2 -1
  215. package/dist-cjs/puzzles/skewb/random-state.js +4 -1
  216. package/dist-cjs/puzzles/sq1/facelets.js +30 -0
  217. package/dist-cjs/puzzles/sq1/index.js +52 -0
  218. package/dist-cjs/puzzles/sq1/moves.js +234 -0
  219. package/dist-cjs/puzzles/sq1/random-state.js +52 -0
  220. package/dist-cjs/puzzles/sq1/scrambler.js +95 -0
  221. package/dist-cjs/puzzles/sq1/solver/browser-loader.js +97 -0
  222. package/dist-cjs/puzzles/sq1/solver/full-cube.js +289 -0
  223. package/dist-cjs/puzzles/sq1/solver/generated/manifest.json +50 -0
  224. package/dist-cjs/puzzles/sq1/solver/generated/shape-bottom-move.bin +0 -0
  225. package/dist-cjs/puzzles/sq1/solver/generated/shape-idx.bin +0 -0
  226. package/dist-cjs/puzzles/sq1/solver/generated/shape-prun.bin +0 -0
  227. package/dist-cjs/puzzles/sq1/solver/generated/shape-top-move.bin +0 -0
  228. package/dist-cjs/puzzles/sq1/solver/generated/shape-twist-move.bin +0 -0
  229. package/dist-cjs/puzzles/sq1/solver/generated/square-bottom-move.bin +0 -0
  230. package/dist-cjs/puzzles/sq1/solver/generated/square-prun.bin +0 -0
  231. package/dist-cjs/puzzles/sq1/solver/generated/square-top-move.bin +0 -0
  232. package/dist-cjs/puzzles/sq1/solver/generated/square-twist-move.bin +0 -0
  233. package/dist-cjs/puzzles/sq1/solver/index.js +18 -0
  234. package/dist-cjs/puzzles/sq1/solver/node-loader.js +120 -0
  235. package/dist-cjs/puzzles/sq1/solver/search.js +229 -0
  236. package/dist-cjs/puzzles/sq1/solver/shape.js +191 -0
  237. package/dist-cjs/puzzles/sq1/solver/square.js +93 -0
  238. package/dist-cjs/puzzles/sq1/solver/table-metadata.js +50 -0
  239. package/dist-cjs/puzzles/sq1/solver/tables.js +106 -0
  240. package/dist-cjs/puzzles/sq1/state.js +81 -0
  241. package/dist-cjs/puzzles/sq1/types.js +2 -0
  242. package/dist-cjs/puzzles/sq1/validation.js +68 -0
  243. package/dist-cjs/random/prng.js +9 -4
  244. package/package.json +43 -14
@@ -0,0 +1,314 @@
1
+ "use strict";
2
+ /**
3
+ * Full 4x4 solve orchestration: reduction (phases 1-3) + the 3x3 finish
4
+ * (phase 4), returning the move sequence that solves the given state.
5
+ * The scramble for a sampled state is the inverted solution.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.solveCube4State = solveCube4State;
9
+ const moves_js_1 = require("../moves.js");
10
+ const center1_js_1 = require("./center1.js");
11
+ const center2_js_1 = require("./center2.js");
12
+ const center3_js_1 = require("./center3.js");
13
+ const pairing_coordinate_js_1 = require("./pairing-coordinate.js");
14
+ const edge3_js_1 = require("./edge3.js");
15
+ const phase3_search_js_1 = require("./phase3-search.js");
16
+ const phase4_js_1 = require("./phase4.js");
17
+ const index_js_1 = require("../../cube3/solver/index.js");
18
+ const state_js_1 = require("../state.js");
19
+ function cornerPermutationParity(cp) {
20
+ let parity = 0;
21
+ const seen = new Array(8).fill(false);
22
+ for (let start = 0; start < 8; start++) {
23
+ if (seen[start] || cp[start] === start)
24
+ continue;
25
+ let length = 0;
26
+ let current = start;
27
+ while (!seen[current]) {
28
+ seen[current] = true;
29
+ current = cp[current];
30
+ length++;
31
+ }
32
+ parity ^= (length - 1) & 1;
33
+ }
34
+ return parity;
35
+ }
36
+ const ROT_MOVES = {
37
+ "x": ["Rw", "Lw'"],
38
+ "y": ["Uw", "Dw'"],
39
+ "z": ["Fw", "Bw'"],
40
+ "x'": ["Rw'", "Lw"],
41
+ "y'": ["Uw'", "Dw"],
42
+ "z'": ["Fw'", "Bw"]
43
+ };
44
+ const VALID_24_ORIENTATIONS = (() => {
45
+ const map = new Map();
46
+ const solved = (0, state_js_1.createSolvedCube4State)();
47
+ const queue = [{ state: solved, path: [] }];
48
+ map.set(`${solved.centers[0]},${solved.centers[4]},${solved.centers[8]}`, []);
49
+ while (queue.length > 0) {
50
+ const { state, path } = queue.shift();
51
+ for (const [rot, moves] of Object.entries(ROT_MOVES)) {
52
+ const nextState = (0, moves_js_1.applyCube4Algorithm)(state, moves);
53
+ const key = `${nextState.centers[0]},${nextState.centers[4]},${nextState.centers[8]}`;
54
+ if (!map.has(key)) {
55
+ const nextPath = [...path, rot];
56
+ map.set(key, nextPath);
57
+ queue.push({ state: nextState, path: nextPath });
58
+ }
59
+ }
60
+ }
61
+ return map;
62
+ })();
63
+ const ROT_MAP = {
64
+ "x": { "U": "B", "R": "R", "F": "U", "D": "F", "L": "L", "B": "D" },
65
+ "x'": { "U": "F", "R": "R", "F": "D", "D": "B", "L": "L", "B": "U" },
66
+ "y": { "U": "U", "R": "F", "F": "L", "D": "D", "L": "B", "B": "R" },
67
+ "y'": { "U": "U", "R": "B", "F": "R", "D": "D", "L": "F", "B": "L" },
68
+ "z": { "U": "R", "R": "D", "F": "F", "D": "L", "L": "U", "B": "B" },
69
+ "z'": { "U": "L", "R": "U", "F": "F", "D": "R", "L": "D", "B": "B" }
70
+ };
71
+ const INVERT_ROT = {
72
+ "x": "x'", "x'": "x",
73
+ "y": "y'", "y'": "y",
74
+ "z": "z'", "z'": "z"
75
+ };
76
+ function rotateMove(move, rot) {
77
+ const map = ROT_MAP[rot];
78
+ const face = move[0];
79
+ const newFace = map[face];
80
+ return (newFace + move.slice(1));
81
+ }
82
+ function rotateAlgorithm(alg, rotSeq) {
83
+ let res = [...alg];
84
+ for (const r of rotSeq) {
85
+ res = res.map((m) => rotateMove(m, r));
86
+ }
87
+ return res;
88
+ }
89
+ function invertRotationSequence(rotSeq) {
90
+ return [...rotSeq].reverse().map((r) => INVERT_ROT[r]);
91
+ }
92
+ function applyCubeRotation(state, rotSeq) {
93
+ let s = state;
94
+ for (const r of rotSeq) {
95
+ s = (0, moves_js_1.applyCube4Algorithm)(s, ROT_MOVES[r]);
96
+ }
97
+ return s;
98
+ }
99
+ const STANDARD_ORDER = [
100
+ "U", "U2", "U'", "R", "R2", "R'", "F", "F2", "F'", "D", "D2", "D'",
101
+ "L", "L2", "L'", "B", "B2", "B'",
102
+ "Uw", "Uw2", "Uw'", "Rw", "Rw2", "Rw'", "Fw", "Fw2", "Fw'",
103
+ "Dw", "Dw2", "Dw'", "Lw", "Lw2", "Lw'", "Bw", "Bw2", "Bw'"
104
+ ];
105
+ const STD_INDEX = new Map(STANDARD_ORDER.map((m, i) => [m, i]));
106
+ function baseOf(move) {
107
+ return move.replace(/[2']$/, "");
108
+ }
109
+ function axisClass(base) {
110
+ return { U: 0, D: 0, R: 1, L: 1, F: 2, B: 2 }[base];
111
+ }
112
+ function isCancelled(previous, current) {
113
+ const pb = baseOf(previous);
114
+ const cb = baseOf(current);
115
+ if (pb === cb)
116
+ return true;
117
+ if (axisClass(pb) === axisClass(cb)) {
118
+ return STD_INDEX.get(previous) > STD_INDEX.get(current);
119
+ }
120
+ return false;
121
+ }
122
+ function solveCenter1Diverse(centers, urf, tables, cap = 80) {
123
+ const startMask = (0, center1_js_1.center1Mask)(centers, urf);
124
+ const solutions = [];
125
+ const path = [];
126
+ const minH = (0, center1_js_1.getCenter1Distance)(startMask, tables);
127
+ for (let maxD = minH; maxD <= minH + 1; maxD++) {
128
+ const dfs = (mask, depth, lm) => {
129
+ if (solutions.length >= cap)
130
+ return true;
131
+ const h = (0, center1_js_1.getCenter1Distance)(mask, tables);
132
+ if (h === 0) {
133
+ solutions.push({
134
+ moves: path.slice(0, depth).map((m) => center1_js_1.CENTER1_MOVES[m]),
135
+ length: depth
136
+ });
137
+ return false;
138
+ }
139
+ if (depth + h > maxD)
140
+ return false;
141
+ for (let m = 0; m < center1_js_1.CENTER1_MOVES.length; m++) {
142
+ if (lm >= 0 && Math.floor(lm / 3) === Math.floor(m / 3))
143
+ continue;
144
+ const next = (0, center1_js_1.applyCenter1Move)(mask, tables.moveMaps, m);
145
+ path[depth] = m;
146
+ if (dfs(next, depth + 1, m))
147
+ return true;
148
+ }
149
+ return false;
150
+ };
151
+ if (dfs(startMask, 0, -1))
152
+ break;
153
+ }
154
+ return solutions;
155
+ }
156
+ const phase3Solver = new phase3_search_js_1.FastPhase3Solver();
157
+ /**
158
+ * Solve a 4x4 state with the reduction solver + the cube3 two-phase finish.
159
+ * `solveCube3` is injected (the cube3 solver loads its tables lazily).
160
+ */
161
+ async function solveCube4State(state, tables, solveCube3) {
162
+ const sym = (0, edge3_js_1.getSymTables)();
163
+ const cornerParity = cornerPermutationParity(state.cp);
164
+ const p1Sols = solveCenter1Diverse(state.centers, 0, tables.center1, 80);
165
+ p1Sols.sort((a, b) => a.moves.length - b.moves.length);
166
+ const minP1 = p1Sols[0]?.moves.length ?? 0;
167
+ const candidates = [];
168
+ const moveLog = new Array(25);
169
+ function searchPhase2(c, r, maxl, lm, depth, bridgeState, p1Moves) {
170
+ if ((0, center2_js_1.isCenter2Goal)(c, r)) {
171
+ if (maxl === 0) {
172
+ const moves2 = moveLog.slice(0, depth).map((m) => center2_js_1.CENTER2_MOVES[m]);
173
+ const s2 = (0, moves_js_1.applyCube4Algorithm)(bridgeState, moves2);
174
+ const pairing = (0, pairing_coordinate_js_1.encodePairingState)(s2.wp);
175
+ if (pairing) {
176
+ const eXc = (pairing.parity ^ cornerParity);
177
+ const ct3 = (0, center3_js_1.encodeCenter3)(s2, eXc).coordinate;
178
+ const cdist = tables.center3.pruning[ct3];
179
+ if (cdist !== 0xff && cdist !== undefined) {
180
+ const symIdx = (0, edge3_js_1.getMvSymFrom)(pairing.edge, pairing.edge, 20, sym.sym2Raw, sym.sym2Mask, sym.raw2Sym, sym.mvrot, sym.mvroto);
181
+ const edist = (tables.edge3.distances[(symIdx >> 3) >> 1] >>
182
+ (((symIdx >> 3) & 1) * 4)) &
183
+ 0x0f;
184
+ candidates.push({
185
+ moves12: [...p1Moves, ...moves2],
186
+ length12: p1Moves.length + moves2.length,
187
+ ct3,
188
+ cdist,
189
+ edge: pairing.edge,
190
+ edist,
191
+ value: p1Moves.length + moves2.length + Math.max(cdist, edist),
192
+ state: s2
193
+ });
194
+ if (candidates.length >= 60)
195
+ return true;
196
+ }
197
+ }
198
+ }
199
+ return false;
200
+ }
201
+ if (maxl === 0)
202
+ return false;
203
+ for (let m = 0; m < center2_js_1.CENTER2_SEARCH_MOVES.length; m++) {
204
+ if (lm >= 0 && isCancelled(center2_js_1.CENTER2_SEARCH_MOVES[lm], center2_js_1.CENTER2_SEARCH_MOVES[m])) {
205
+ continue;
206
+ }
207
+ const nc = tables.center2.ctMove[c * center2_js_1.CENTER2_MOVES.length + m];
208
+ const nr = tables.center2.rlMove[r * center2_js_1.CENTER2_MOVES.length + m];
209
+ const prun = tables.center2.pruning[nc * center2_js_1.CENTER2_RL_STATES + nr];
210
+ if (prun >= maxl)
211
+ continue;
212
+ moveLog[depth] = m;
213
+ if (searchPhase2(nc, nr, maxl - 1, m, depth + 1, bridgeState, p1Moves))
214
+ return true;
215
+ }
216
+ return false;
217
+ }
218
+ let MAX_P2 = 8;
219
+ do {
220
+ OUTER: for (let length12 = minP1; length12 < minP1 + 14; length12++) {
221
+ for (const p1 of p1Sols) {
222
+ if (p1.moves.length > length12)
223
+ break;
224
+ const length2 = length12 - p1.moves.length;
225
+ if (length2 > MAX_P2 || length2 < 0)
226
+ continue;
227
+ const s1 = (0, moves_js_1.applyCube4Algorithm)(state, p1.moves);
228
+ const bridge = (0, center2_js_1.center2Bridge)(s1);
229
+ if (!bridge)
230
+ continue;
231
+ const prun = tables.center2.pruning[bridge.encoded.ct * center2_js_1.CENTER2_RL_STATES + bridge.encoded.rl];
232
+ if (prun > length2)
233
+ continue;
234
+ const fullP1Moves = [...p1.moves, ...bridge.alignment];
235
+ if (searchPhase2(bridge.encoded.ct, bridge.encoded.rl, length2, -1, 0, bridge.state, fullP1Moves)) {
236
+ break OUTER;
237
+ }
238
+ }
239
+ }
240
+ MAX_P2++;
241
+ } while (candidates.length === 0 && MAX_P2 <= 14);
242
+ if (candidates.length === 0) {
243
+ throw new Error("4x4 solve: no valid phase 2 candidates found.");
244
+ }
245
+ candidates.sort((a, b) => a.value - b.value);
246
+ let solvedCand = null;
247
+ let solvedMoves3 = null;
248
+ let standardReduced = null;
249
+ let rotPath = [];
250
+ let rotToStandard = [];
251
+ let MAX_LENGTH3 = 13;
252
+ let yieldCounter = 0;
253
+ do {
254
+ for (let length123 = candidates[0].value; length123 < 45; length123++) {
255
+ for (let i = 0; i < Math.min(candidates.length, 60); i++) {
256
+ const cand = candidates[i];
257
+ if (cand.value > length123)
258
+ break;
259
+ const length3 = length123 - cand.length12;
260
+ if (length3 > MAX_LENGTH3 || length3 < 0)
261
+ continue;
262
+ if (cand.edist <= length3 && cand.cdist <= length3) {
263
+ if (++yieldCounter % 100 === 0)
264
+ await new Promise(r => setTimeout(r, 0));
265
+ const moves3 = await phase3Solver.solveLength(cand.edge, cand.ct3, cand.edist, length3, tables.center3, tables.edge3.distances);
266
+ if (!moves3)
267
+ continue;
268
+ const reduced = (0, moves_js_1.applyCube4Algorithm)(cand.state, moves3);
269
+ const key = `${reduced.centers[0]},${reduced.centers[4]},${reduced.centers[8]}`;
270
+ const path = VALID_24_ORIENTATIONS.get(key);
271
+ if (!path)
272
+ continue;
273
+ solvedCand = cand;
274
+ solvedMoves3 = moves3;
275
+ rotPath = path;
276
+ rotToStandard = invertRotationSequence(rotPath);
277
+ standardReduced = applyCubeRotation(reduced, rotToStandard);
278
+ break;
279
+ }
280
+ }
281
+ if (solvedCand)
282
+ break;
283
+ }
284
+ MAX_LENGTH3++;
285
+ } while (!solvedCand && MAX_LENGTH3 <= 18);
286
+ if (!solvedCand || !solvedMoves3 || !standardReduced) {
287
+ throw new Error("4x4 solve: reduction failed across all candidates.");
288
+ }
289
+ const cube3State = (0, phase4_js_1.toCube3State)(standardReduced);
290
+ if (!cube3State) {
291
+ throw new Error("4x4 solve: failed to convert reduced 4x4 state to 3x3 state.");
292
+ }
293
+ let cube3Solution;
294
+ try {
295
+ cube3Solution = await solveCube3(cube3State);
296
+ }
297
+ catch (error) {
298
+ if (error instanceof index_js_1.Cube3Phase2SearchError) {
299
+ throw error;
300
+ }
301
+ throw error;
302
+ }
303
+ const fullSolInOriginal = [
304
+ ...solvedCand.moves12,
305
+ ...solvedMoves3,
306
+ ...rotateAlgorithm(cube3Solution.moves, rotPath)
307
+ ];
308
+ const fullSolToStandard = [
309
+ ...fullSolInOriginal,
310
+ ...rotToStandard.flatMap((r) => ROT_MOVES[r])
311
+ ];
312
+ const scramble = (0, moves_js_1.invertCube4Algorithm)(fullSolToStandard);
313
+ return { moves: fullSolToStandard, scramble };
314
+ }
@@ -1,4 +1,9 @@
1
1
  "use strict";
2
+ /**
3
+ * The 4x4 scramble generator: sample a uniform random state, solve it with
4
+ * the reduction solver (phases 1-4), and invert the solution. The scramble
5
+ * always reproduces the sampled target state and facelets exactly.
6
+ */
2
7
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
8
  if (k2 === undefined) k2 = k;
4
9
  var desc = Object.getOwnPropertyDescriptor(m, k);
@@ -34,102 +39,140 @@ var __importStar = (this && this.__importStar) || (function () {
34
39
  })();
35
40
  Object.defineProperty(exports, "__esModule", { value: true });
36
41
  exports.cube4ScrambleGenerator = void 0;
42
+ exports.isCube4TablesLoaded = isCube4TablesLoaded;
37
43
  exports.generateCube4Scramble = generateCube4Scramble;
38
- const state_js_1 = require("./state.js");
39
44
  const moves_js_1 = require("./moves.js");
45
+ const random_state_js_1 = require("./random-state.js");
46
+ const state_js_1 = require("./state.js");
40
47
  const facelets_js_1 = require("./facelets.js");
41
- const SCRAMBLE_LENGTH = 40;
48
+ const solve_js_1 = require("./reduction/solve.js");
49
+ const index_js_1 = require("../cube3/solver/index.js");
50
+ let cachedTables;
51
+ async function loadCube4Tables(baseUrl) {
52
+ if (cachedTables)
53
+ return cachedTables;
54
+ const isBrowser = typeof window !== "undefined" ||
55
+ typeof document !== "undefined" ||
56
+ (typeof self !== "undefined" && typeof self.importScripts === "function") ||
57
+ (typeof navigator !== "undefined" && /Moz|WebKit/.test(navigator.userAgent));
58
+ const isNode = typeof process !== "undefined" && process.versions?.node != null;
59
+ if (isBrowser && !isNode) {
60
+ const loader = await Promise.resolve().then(() => __importStar(require(
61
+ /* webpackIgnore: true */
62
+ /* @vite-ignore */
63
+ "./reduction/browser-loader.js")));
64
+ const cached = loader.getCachedCube4Tables();
65
+ if (cached) {
66
+ cachedTables = cached;
67
+ return cached;
68
+ }
69
+ const tables = await loader.loadCube4ReductionTablesBrowser(baseUrl);
70
+ cachedTables = tables;
71
+ return tables;
72
+ }
73
+ const loader = await Promise.resolve().then(() => __importStar(require(
74
+ /* webpackIgnore: true */
75
+ /* @vite-ignore */
76
+ "./reduction/node-loader.js")));
77
+ const tables = await loader.loadCube4ReductionTables();
78
+ cachedTables = tables;
79
+ return tables;
80
+ }
81
+ function isCube4TablesLoaded() {
82
+ // For the scrambler retry logic, we need to know if tables are already
83
+ // loaded to avoid re-loading on every attempt.
84
+ try {
85
+ // @ts-ignore
86
+ const loader = globalThis;
87
+ return false;
88
+ }
89
+ catch {
90
+ return false;
91
+ }
92
+ }
42
93
  exports.cube4ScrambleGenerator = {
43
94
  info: {
44
95
  eventId: "444",
45
96
  name: "4x4x4 Cube",
46
97
  puzzle: "cube-4x4",
47
98
  quality: "random-state",
48
- generator: "cubing-scramble",
49
- description: "Random-state 4x4 scramble generated by cubing.js."
99
+ generator: "cube4-reduction",
100
+ description: "Random-state 4x4 scramble via the reduction solver (centers + pairing + 3x3 finish)."
50
101
  },
51
102
  generate(options = {}) {
52
103
  return generateCube4Scramble(options);
53
104
  }
54
105
  };
55
106
  async function generateCube4Scramble(options = {}) {
56
- // Use cubing library for high-quality random-state 4x4 scrambles
57
- const { randomScrambleForEvent } = await Promise.resolve().then(() => __importStar(require("cubing/scramble")));
58
- const alg = await randomScrambleForEvent("444");
59
- const scramble = alg.toString();
60
- const moves = scramble.split(/\s+/);
61
- const targetState = (0, moves_js_1.applyCube4Algorithm)((0, state_js_1.createSolvedCube4State)(), moves);
62
- const faceletString = (0, facelets_js_1.formatCube4Facelets)((0, facelets_js_1.toCube4Facelets)(targetState));
63
- const result = {
64
- eventId: "444",
65
- scramble,
66
- moves,
67
- quality: "random-state",
68
- generator: "cubing-scramble",
69
- metadata: {
70
- totalMoves: moves.length,
71
- distribution: "random-state",
72
- state: targetState,
73
- facelets: (0, facelets_js_1.toCube4Facelets)(targetState),
74
- faceletString
75
- }
76
- };
77
- if (options.seed !== undefined) {
78
- result.seed = options.seed;
79
- }
80
- return result;
81
- }
82
- const MAX_AXIS_BLOCK = 3;
83
- const ROTATION_PAIRS = {
84
- "Uw": "Dw", "Dw": "Uw",
85
- "Rw": "Lw", "Lw": "Rw",
86
- "Fw": "Bw", "Bw": "Fw",
87
- };
88
- function getBaseMove(move) {
89
- if (move.endsWith("2") || move.endsWith("'")) {
90
- return move.slice(0, -1);
91
- }
92
- return move;
93
- }
94
- function getAxis(base) {
95
- if (base.includes("R") || base.includes("L"))
96
- return "X";
97
- if (base.includes("U") || base.includes("D"))
98
- return "Y";
99
- if (base.includes("F") || base.includes("B"))
100
- return "Z";
101
- return "";
102
- }
103
- function isRedundant(moves, newMove) {
104
- if (moves.length === 0)
105
- return false;
106
- const newBase = getBaseMove(newMove);
107
- const newAxis = getAxis(newBase);
108
- const lastBase = getBaseMove(moves[moves.length - 1]);
109
- if (lastBase === newBase)
110
- return true;
111
- const lastAxis = getAxis(lastBase);
112
- if (newAxis !== lastAxis)
113
- return false;
114
- const basesInBlock = new Set();
115
- basesInBlock.add(lastBase);
116
- let blockLength = 1;
117
- for (let i = moves.length - 2; i >= 0; i--) {
118
- const base = getBaseMove(moves[i]);
119
- if (getAxis(base) === newAxis) {
120
- basesInBlock.add(base);
121
- blockLength++;
107
+ const tables = await loadCube4Tables();
108
+ let lastError;
109
+ // Retry with different states if the solver fails to find a physical
110
+ // reduction (some random states are outside the solver's effective
111
+ // horizon with the current candidate/length caps).
112
+ for (let attempt = 0; attempt < 5; attempt++) {
113
+ let attemptOptions;
114
+ if (attempt === 0) {
115
+ attemptOptions = options;
122
116
  }
123
117
  else {
124
- break;
118
+ attemptOptions = { ...options };
119
+ if (options.seed !== undefined) {
120
+ attemptOptions.seed = `${options.seed}:retry${attempt}`;
121
+ }
122
+ else {
123
+ delete attemptOptions.seed;
124
+ }
125
+ }
126
+ const attemptState = (0, random_state_js_1.generateRandomCube4State)(attemptOptions);
127
+ try {
128
+ const solvePromise = (0, solve_js_1.solveCube4State)(attemptState, tables, (cube3State) => (0, index_js_1.solveCube3State)(cube3State));
129
+ const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error("4x4 solve: timeout")), 20000));
130
+ const { scramble: scrambleMoves } = await Promise.race([solvePromise, timeoutPromise]);
131
+ if (!options.skipValidation) {
132
+ const scrambledState = (0, moves_js_1.applyCube4Algorithm)((0, state_js_1.createSolvedCube4State)(), scrambleMoves);
133
+ if (!(0, state_js_1.cube4StatesEqual)(scrambledState, attemptState)) {
134
+ throw new Error("Generated 4x4 scramble does not reproduce the target random state.");
135
+ }
136
+ }
137
+ const facelets = (0, facelets_js_1.toCube4Facelets)(attemptState);
138
+ const result = {
139
+ eventId: "444",
140
+ scramble: (0, moves_js_1.formatCube4Algorithm)(scrambleMoves),
141
+ moves: scrambleMoves,
142
+ quality: "random-state",
143
+ generator: "cube4-reduction",
144
+ metadata: {
145
+ generator: "cube4-reduction",
146
+ state: attemptState,
147
+ facelets,
148
+ faceletString: (0, facelets_js_1.formatCube4Facelets)(facelets),
149
+ distribution: "random-state",
150
+ ...options.metadata
151
+ }
152
+ };
153
+ if (options.seed !== undefined) {
154
+ result.seed = options.seed;
155
+ }
156
+ return result;
157
+ }
158
+ catch (error) {
159
+ lastError = error;
160
+ // Retry on deterministic reduction / 3x3 failures (some random states are
161
+ // outside the solver's horizon with the current caps).
162
+ const msg = error instanceof Error ? error.message : String(error);
163
+ const isPhase2Error = typeof error?.name === "string" &&
164
+ error.name.includes("Cube3Phase2");
165
+ if (error instanceof Error &&
166
+ (msg.includes("reduction failed") ||
167
+ msg.includes("failed to convert") ||
168
+ msg.includes("no valid phase 2") ||
169
+ msg.includes("timeout") ||
170
+ isPhase2Error ||
171
+ msg.includes("Phase 2 solution not found"))) {
172
+ continue;
173
+ }
174
+ throw error;
125
175
  }
126
176
  }
127
- if (basesInBlock.has(newBase))
128
- return true;
129
- const complement = ROTATION_PAIRS[newBase];
130
- if (complement && basesInBlock.has(complement))
131
- return true;
132
- if (blockLength >= MAX_AXIS_BLOCK)
133
- return true;
134
- return false;
177
+ throw lastError;
135
178
  }
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateCube4State = validateCube4State;
4
+ exports.isOneMoveFromSolvedCube4State = isOneMoveFromSolvedCube4State;
5
+ exports.isTrivialCube4State = isTrivialCube4State;
6
+ exports.isAllowedRandomCube4State = isAllowedRandomCube4State;
7
+ const types_js_1 = require("./types.js");
8
+ const moves_js_1 = require("./moves.js");
9
+ const state_js_1 = require("./state.js");
10
+ function validateCube4State(state) {
11
+ const errors = [
12
+ ...validatePermutation("corner", state.cp, types_js_1.CORNER_COUNT),
13
+ ...validateOrientations("corner", state.co, types_js_1.CORNER_COUNT, 3),
14
+ ...validatePermutation("wing", state.wp, types_js_1.WING_COUNT),
15
+ ...validateCenters(state.centers)
16
+ ];
17
+ if (errors.length === 0) {
18
+ const cornerOrientation = sum(state.co) % 3;
19
+ if (cornerOrientation !== 0) {
20
+ errors.push("Corner orientation sum must be divisible by 3.");
21
+ }
22
+ }
23
+ return {
24
+ valid: errors.length === 0,
25
+ errors
26
+ };
27
+ }
28
+ // The 36 single-move states (12 base faces × 3 suffixes), precomputed once so
29
+ // the hot random-state path checks "solved or one move from solved" with pure
30
+ // array comparisons.
31
+ const ONE_MOVE_FROM_SOLVED_STATES = types_js_1.CUBE4_ALL_MOVES.map((move) => (0, moves_js_1.applyCube4Move)((0, state_js_1.createSolvedCube4State)(), move));
32
+ function isOneMoveFromSolvedCube4State(state) {
33
+ return ONE_MOVE_FROM_SOLVED_STATES.some((candidate) => (0, state_js_1.cube4StatesEqual)(candidate, state));
34
+ }
35
+ function isTrivialCube4State(state) {
36
+ return (0, state_js_1.isSolvedCube4State)(state) || isOneMoveFromSolvedCube4State(state);
37
+ }
38
+ function isAllowedRandomCube4State(state) {
39
+ return validateCube4State(state).valid && !isTrivialCube4State(state);
40
+ }
41
+ function validatePermutation(label, permutation, length) {
42
+ const errors = [];
43
+ const seen = new Set();
44
+ if (permutation.length !== length) {
45
+ errors.push(`${label} permutation must contain ${length} values.`);
46
+ }
47
+ for (const value of permutation) {
48
+ if (!Number.isInteger(value) || value < 0 || value >= length) {
49
+ errors.push(`${label} permutation contains out-of-range value: ${value}.`);
50
+ continue;
51
+ }
52
+ if (seen.has(value)) {
53
+ errors.push(`${label} permutation contains duplicate value: ${value}.`);
54
+ }
55
+ seen.add(value);
56
+ }
57
+ return errors;
58
+ }
59
+ function validateOrientations(label, orientations, length, modulo) {
60
+ const errors = [];
61
+ if (orientations.length !== length) {
62
+ errors.push(`${label} orientation must contain ${length} values.`);
63
+ }
64
+ for (const value of orientations) {
65
+ if (!Number.isInteger(value) || value < 0 || value >= modulo) {
66
+ errors.push(`${label} orientation contains out-of-range value: ${value}.`);
67
+ }
68
+ }
69
+ return errors;
70
+ }
71
+ function validateCenters(centers) {
72
+ const errors = [];
73
+ if (centers.length !== types_js_1.CENTER_COUNT) {
74
+ errors.push(`centers must contain ${types_js_1.CENTER_COUNT} values.`);
75
+ }
76
+ const counts = new Uint8Array(types_js_1.FACE_COUNT);
77
+ for (const value of centers) {
78
+ if (!Number.isInteger(value) || value < 0 || value >= types_js_1.FACE_COUNT) {
79
+ errors.push(`center contains out-of-range color: ${value}.`);
80
+ continue;
81
+ }
82
+ counts[value] = counts[value] + 1;
83
+ }
84
+ for (let face = 0; face < types_js_1.FACE_COUNT; face += 1) {
85
+ if (counts[face] !== types_js_1.CENTERS_PER_FACE) {
86
+ errors.push(`center color ${face} must appear exactly ${types_js_1.CENTERS_PER_FACE} times.`);
87
+ }
88
+ }
89
+ return errors;
90
+ }
91
+ function sum(values) {
92
+ let total = 0;
93
+ for (const value of values) {
94
+ total += value;
95
+ }
96
+ return total;
97
+ }