@cubingcenter/scrambler 0.4.0 → 0.5.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 (174) hide show
  1. package/README.md +67 -3
  2. package/dist/core/api.d.ts.map +1 -1
  3. package/dist/core/api.js +18 -6
  4. package/dist/core/index.d.ts +1 -1
  5. package/dist/core/index.d.ts.map +1 -1
  6. package/dist/core/types.d.ts +7 -0
  7. package/dist/core/types.d.ts.map +1 -1
  8. package/dist/puzzles/clock/index.d.ts +1 -0
  9. package/dist/puzzles/clock/index.d.ts.map +1 -1
  10. package/dist/puzzles/clock/index.js +4 -0
  11. package/dist/puzzles/cube2/index.d.ts +1 -0
  12. package/dist/puzzles/cube2/index.d.ts.map +1 -1
  13. package/dist/puzzles/cube2/index.js +4 -0
  14. package/dist/puzzles/cube2/scrambler.d.ts.map +1 -1
  15. package/dist/puzzles/cube2/scrambler.js +4 -6
  16. package/dist/puzzles/cube2/solver/browser-loader.d.ts.map +1 -1
  17. package/dist/puzzles/cube2/solver/browser-loader.js +4 -12
  18. package/dist/puzzles/cube2/solver/index.d.ts +1 -1
  19. package/dist/puzzles/cube2/solver/index.d.ts.map +1 -1
  20. package/dist/puzzles/cube2/solver/index.js +1 -1
  21. package/dist/puzzles/cube2/solver/search.d.ts +2 -0
  22. package/dist/puzzles/cube2/solver/search.d.ts.map +1 -1
  23. package/dist/puzzles/cube2/solver/search.js +26 -0
  24. package/dist/puzzles/cube2/validation.d.ts.map +1 -1
  25. package/dist/puzzles/cube2/validation.js +15 -3
  26. package/dist/puzzles/cube3/index.d.ts +1 -0
  27. package/dist/puzzles/cube3/index.d.ts.map +1 -1
  28. package/dist/puzzles/cube3/index.js +4 -0
  29. package/dist/puzzles/cube3/solver/browser-loader.d.ts.map +1 -1
  30. package/dist/puzzles/cube3/solver/browser-loader.js +5 -29
  31. package/dist/puzzles/cube4/index.d.ts +1 -0
  32. package/dist/puzzles/cube4/index.d.ts.map +1 -1
  33. package/dist/puzzles/cube4/index.js +4 -0
  34. package/dist/puzzles/cube5x5/index.d.ts +1 -0
  35. package/dist/puzzles/cube5x5/index.d.ts.map +1 -1
  36. package/dist/puzzles/cube5x5/index.js +4 -0
  37. package/dist/puzzles/cube6x6/index.d.ts +1 -0
  38. package/dist/puzzles/cube6x6/index.d.ts.map +1 -1
  39. package/dist/puzzles/cube6x6/index.js +4 -0
  40. package/dist/puzzles/cube7x7/index.d.ts +1 -0
  41. package/dist/puzzles/cube7x7/index.d.ts.map +1 -1
  42. package/dist/puzzles/cube7x7/index.js +4 -0
  43. package/dist/puzzles/megaminx/index.d.ts +1 -0
  44. package/dist/puzzles/megaminx/index.d.ts.map +1 -1
  45. package/dist/puzzles/megaminx/index.js +4 -0
  46. package/dist/puzzles/pyraminx/index.d.ts +3 -1
  47. package/dist/puzzles/pyraminx/index.d.ts.map +1 -1
  48. package/dist/puzzles/pyraminx/index.js +6 -1
  49. package/dist/puzzles/pyraminx/scrambler.d.ts.map +1 -1
  50. package/dist/puzzles/pyraminx/scrambler.js +24 -20
  51. package/dist/puzzles/pyraminx/solver/browser-loader.d.ts +14 -0
  52. package/dist/puzzles/pyraminx/solver/browser-loader.d.ts.map +1 -0
  53. package/dist/puzzles/pyraminx/solver/browser-loader.js +63 -0
  54. package/dist/puzzles/pyraminx/solver/generated/centers-table.bin +0 -0
  55. package/dist/puzzles/pyraminx/solver/generated/manifest.json +26 -0
  56. package/dist/puzzles/pyraminx/solver/generated/orientation-table.bin +0 -0
  57. package/dist/puzzles/pyraminx/solver/generated/permutation-table.bin +0 -0
  58. package/dist/puzzles/pyraminx/solver/generated/policy.bin +0 -0
  59. package/dist/puzzles/pyraminx/solver/generated-assets.d.ts +7 -0
  60. package/dist/puzzles/pyraminx/solver/generated-assets.d.ts.map +1 -0
  61. package/dist/puzzles/pyraminx/solver/generated-assets.js +24 -0
  62. package/dist/puzzles/pyraminx/solver/index.d.ts +5 -0
  63. package/dist/puzzles/pyraminx/solver/index.d.ts.map +1 -0
  64. package/dist/puzzles/pyraminx/solver/index.js +4 -0
  65. package/dist/puzzles/pyraminx/solver/node-loader.d.ts +7 -0
  66. package/dist/puzzles/pyraminx/solver/node-loader.d.ts.map +1 -0
  67. package/dist/puzzles/pyraminx/solver/node-loader.js +73 -0
  68. package/dist/puzzles/pyraminx/solver/search.d.ts +10 -0
  69. package/dist/puzzles/pyraminx/solver/search.d.ts.map +1 -0
  70. package/dist/puzzles/pyraminx/solver/search.js +82 -0
  71. package/dist/puzzles/pyraminx/solver/table-metadata.d.ts +20 -0
  72. package/dist/puzzles/pyraminx/solver/table-metadata.d.ts.map +1 -0
  73. package/dist/puzzles/pyraminx/solver/table-metadata.js +19 -0
  74. package/dist/puzzles/pyraminx/solver/tables.d.ts +43 -0
  75. package/dist/puzzles/pyraminx/solver/tables.d.ts.map +1 -0
  76. package/dist/puzzles/pyraminx/solver/tables.js +300 -0
  77. package/dist/puzzles/shared/compressed-fetch.d.ts +12 -0
  78. package/dist/puzzles/shared/compressed-fetch.d.ts.map +1 -0
  79. package/dist/puzzles/shared/compressed-fetch.js +68 -0
  80. package/dist/puzzles/skewb/facelets.d.ts.map +1 -1
  81. package/dist/puzzles/skewb/facelets.js +46 -23
  82. package/dist/puzzles/skewb/index.d.ts +3 -1
  83. package/dist/puzzles/skewb/index.d.ts.map +1 -1
  84. package/dist/puzzles/skewb/index.js +6 -1
  85. package/dist/puzzles/skewb/random-state.d.ts +1 -1
  86. package/dist/puzzles/skewb/random-state.d.ts.map +1 -1
  87. package/dist/puzzles/skewb/random-state.js +25 -18
  88. package/dist/puzzles/skewb/scrambler.d.ts.map +1 -1
  89. package/dist/puzzles/skewb/scrambler.js +10 -13
  90. package/dist/puzzles/skewb/solver/browser-loader.d.ts +20 -0
  91. package/dist/puzzles/skewb/solver/browser-loader.d.ts.map +1 -0
  92. package/dist/puzzles/skewb/solver/browser-loader.js +86 -0
  93. package/dist/puzzles/skewb/solver/generated/centers-table.bin +0 -0
  94. package/dist/puzzles/skewb/solver/generated/helpers.bin +0 -0
  95. package/dist/puzzles/skewb/solver/generated/manifest.json +31 -0
  96. package/dist/puzzles/skewb/solver/generated/orientation-table.bin +0 -0
  97. package/dist/puzzles/skewb/solver/generated/permutation-table.bin +0 -0
  98. package/dist/puzzles/skewb/solver/generated/policy.bin +0 -0
  99. package/dist/puzzles/skewb/solver/generated-assets.d.ts +10 -0
  100. package/dist/puzzles/skewb/solver/generated-assets.d.ts.map +1 -0
  101. package/dist/puzzles/skewb/solver/generated-assets.js +24 -0
  102. package/dist/puzzles/skewb/solver/index.d.ts +5 -0
  103. package/dist/puzzles/skewb/solver/index.d.ts.map +1 -0
  104. package/dist/puzzles/skewb/solver/index.js +4 -0
  105. package/dist/puzzles/skewb/solver/node-loader.d.ts +10 -0
  106. package/dist/puzzles/skewb/solver/node-loader.d.ts.map +1 -0
  107. package/dist/puzzles/skewb/solver/node-loader.js +91 -0
  108. package/dist/puzzles/skewb/solver/search.d.ts +11 -0
  109. package/dist/puzzles/skewb/solver/search.d.ts.map +1 -0
  110. package/dist/puzzles/skewb/solver/search.js +80 -0
  111. package/dist/puzzles/skewb/solver/table-metadata.d.ts +27 -0
  112. package/dist/puzzles/skewb/solver/table-metadata.d.ts.map +1 -0
  113. package/dist/puzzles/skewb/solver/table-metadata.js +26 -0
  114. package/dist/puzzles/skewb/solver/tables.d.ts +68 -0
  115. package/dist/puzzles/skewb/solver/tables.d.ts.map +1 -0
  116. package/dist/puzzles/skewb/solver/tables.js +508 -0
  117. package/dist/random/prng.js +10 -1
  118. package/dist-cjs/core/api.js +51 -6
  119. package/dist-cjs/puzzles/clock/index.js +13 -4
  120. package/dist-cjs/puzzles/cube2/index.js +13 -4
  121. package/dist-cjs/puzzles/cube2/scrambler.js +2 -4
  122. package/dist-cjs/puzzles/cube2/solver/browser-loader.js +4 -12
  123. package/dist-cjs/puzzles/cube2/solver/index.js +3 -1
  124. package/dist-cjs/puzzles/cube2/solver/search.js +28 -0
  125. package/dist-cjs/puzzles/cube2/validation.js +14 -2
  126. package/dist-cjs/puzzles/cube3/index.js +13 -4
  127. package/dist-cjs/puzzles/cube3/solver/browser-loader.js +5 -29
  128. package/dist-cjs/puzzles/cube4/index.js +13 -4
  129. package/dist-cjs/puzzles/cube5x5/index.js +13 -4
  130. package/dist-cjs/puzzles/cube6x6/index.js +13 -4
  131. package/dist-cjs/puzzles/cube7x7/index.js +13 -4
  132. package/dist-cjs/puzzles/megaminx/index.js +13 -4
  133. package/dist-cjs/puzzles/pyraminx/index.js +50 -6
  134. package/dist-cjs/puzzles/pyraminx/scrambler.js +23 -19
  135. package/dist-cjs/puzzles/pyraminx/solver/browser-loader.js +68 -0
  136. package/dist-cjs/puzzles/pyraminx/solver/generated/centers-table.bin +0 -0
  137. package/dist-cjs/puzzles/pyraminx/solver/generated/manifest.json +26 -0
  138. package/dist-cjs/puzzles/pyraminx/solver/generated/orientation-table.bin +0 -0
  139. package/dist-cjs/puzzles/pyraminx/solver/generated/permutation-table.bin +0 -0
  140. package/dist-cjs/puzzles/pyraminx/solver/generated/policy.bin +0 -0
  141. package/dist-cjs/puzzles/pyraminx/solver/generated-assets.js +60 -0
  142. package/dist-cjs/puzzles/pyraminx/solver/index.js +23 -0
  143. package/dist-cjs/puzzles/pyraminx/solver/node-loader.js +79 -0
  144. package/dist-cjs/puzzles/pyraminx/solver/search.js +88 -0
  145. package/dist-cjs/puzzles/pyraminx/solver/table-metadata.js +22 -0
  146. package/dist-cjs/puzzles/pyraminx/solver/tables.js +327 -0
  147. package/dist-cjs/puzzles/shared/compressed-fetch.js +71 -0
  148. package/dist-cjs/puzzles/skewb/facelets.js +46 -23
  149. package/dist-cjs/puzzles/skewb/index.js +55 -8
  150. package/dist-cjs/puzzles/skewb/random-state.js +25 -18
  151. package/dist-cjs/puzzles/skewb/scrambler.js +9 -12
  152. package/dist-cjs/puzzles/skewb/solver/browser-loader.js +91 -0
  153. package/dist-cjs/puzzles/skewb/solver/generated/centers-table.bin +0 -0
  154. package/dist-cjs/puzzles/skewb/solver/generated/helpers.bin +0 -0
  155. package/dist-cjs/puzzles/skewb/solver/generated/manifest.json +31 -0
  156. package/dist-cjs/puzzles/skewb/solver/generated/orientation-table.bin +0 -0
  157. package/dist-cjs/puzzles/skewb/solver/generated/permutation-table.bin +0 -0
  158. package/dist-cjs/puzzles/skewb/solver/generated/policy.bin +0 -0
  159. package/dist-cjs/puzzles/skewb/solver/generated-assets.js +60 -0
  160. package/dist-cjs/puzzles/skewb/solver/index.js +23 -0
  161. package/dist-cjs/puzzles/skewb/solver/node-loader.js +97 -0
  162. package/dist-cjs/puzzles/skewb/solver/search.js +87 -0
  163. package/dist-cjs/puzzles/skewb/solver/table-metadata.js +29 -0
  164. package/dist-cjs/puzzles/skewb/solver/tables.js +539 -0
  165. package/dist-cjs/random/prng.js +10 -1
  166. package/package.json +20 -6
  167. package/dist/puzzles/pyraminx/solver.d.ts +0 -10
  168. package/dist/puzzles/pyraminx/solver.d.ts.map +0 -1
  169. package/dist/puzzles/pyraminx/solver.js +0 -160
  170. package/dist/puzzles/skewb/solver.d.ts +0 -11
  171. package/dist/puzzles/skewb/solver.d.ts.map +0 -1
  172. package/dist/puzzles/skewb/solver.js +0 -149
  173. package/dist-cjs/puzzles/pyraminx/solver.js +0 -170
  174. package/dist-cjs/puzzles/skewb/solver.js +0 -160
@@ -0,0 +1,300 @@
1
+ import { loadGeneratedPyraminxSolverTables } from "./generated-assets.js";
2
+ import { applyPyraminxMove } from "../moves.js";
3
+ // ---------------------------------------------------------------------------
4
+ // Coordinate encoding / decoding (kept from original solver.ts)
5
+ // ---------------------------------------------------------------------------
6
+ const FACT = [1, 1, 2, 6, 24, 120, 720];
7
+ export function getPermutationIndex(perm) {
8
+ let index = 0;
9
+ const n = perm.length;
10
+ for (let i = 0; i < n; i++) {
11
+ let count = 0;
12
+ for (let j = i + 1; j < n; j++) {
13
+ if (perm[j] < perm[i])
14
+ count++;
15
+ }
16
+ index += count * FACT[n - 1 - i];
17
+ }
18
+ return index;
19
+ }
20
+ export function getPermutationFromIndex(index, n) {
21
+ const perm = Array(n).fill(0);
22
+ const temp = Array.from({ length: n }, (_, i) => i);
23
+ let val = index;
24
+ for (let i = 0; i < n; i++) {
25
+ const f = FACT[n - 1 - i];
26
+ const choice = Math.floor(val / f);
27
+ val %= f;
28
+ perm[i] = temp[choice];
29
+ temp.splice(choice, 1);
30
+ }
31
+ return perm;
32
+ }
33
+ export function getOrientationIndex(orient) {
34
+ let index = 0;
35
+ for (let i = 0; i < 5; i++) {
36
+ index = (index << 1) | orient[i];
37
+ }
38
+ return index;
39
+ }
40
+ export function getOrientationFromIndex(index) {
41
+ const orient = Array(6).fill(0);
42
+ let temp = index;
43
+ let sum = 0;
44
+ for (let i = 4; i >= 0; i--) {
45
+ const bit = temp & 1;
46
+ orient[i] = bit;
47
+ sum += bit;
48
+ temp >>= 1;
49
+ }
50
+ orient[5] = sum % 2 === 0 ? 0 : 1;
51
+ return orient;
52
+ }
53
+ export function getCentersIndex(centers) {
54
+ return centers[0] + 3 * centers[1] + 9 * centers[2] + 27 * centers[3];
55
+ }
56
+ export function getCentersFromIndex(index) {
57
+ let temp = index;
58
+ const centers = Array(4).fill(0);
59
+ for (let i = 0; i < 4; i++) {
60
+ centers[i] = temp % 3;
61
+ temp = Math.floor(temp / 3);
62
+ }
63
+ return centers;
64
+ }
65
+ export function permutationParity(perm) {
66
+ let inv = 0;
67
+ for (let i = 0; i < perm.length; i++)
68
+ for (let j = i + 1; j < perm.length; j++)
69
+ if (perm[i] > perm[j])
70
+ inv++;
71
+ return inv % 2;
72
+ }
73
+ // Compact mapping: only even permutations (360) are reachable
74
+ export const PERMUTATION_FULL_COUNT = 720;
75
+ export const PERMUTATION_COUNT = 360;
76
+ export const PERM_720_TO_360 = new Int16Array(PERMUTATION_FULL_COUNT).fill(-1);
77
+ export const PERM_360_TO_720 = new Uint16Array(PERMUTATION_COUNT);
78
+ (function initPermMapping() {
79
+ let compact = 0;
80
+ for (let full = 0; full < PERMUTATION_FULL_COUNT; full++) {
81
+ const perm = getPermutationFromIndex(full, 6);
82
+ if (permutationParity(perm) === 0) {
83
+ PERM_720_TO_360[full] = compact;
84
+ PERM_360_TO_720[compact] = full;
85
+ compact++;
86
+ }
87
+ }
88
+ })();
89
+ export function getPermutationCompactIndex(perm) {
90
+ const full = getPermutationIndex(perm);
91
+ const compact = PERM_720_TO_360[full];
92
+ if (compact === -1)
93
+ throw new Error("Unreachable pyraminx permutation (odd parity)");
94
+ return compact;
95
+ }
96
+ export function getPermutationCompactFromFull(full) {
97
+ const c = PERM_720_TO_360[full];
98
+ if (c === -1)
99
+ throw new Error(`Full perm index ${full} is odd parity`);
100
+ return c;
101
+ }
102
+ export function getPermutationFullFromCompact(compact) {
103
+ return PERM_360_TO_720[compact];
104
+ }
105
+ export function getStateIndex(state) {
106
+ const c = getCentersIndex(state.centers);
107
+ const p = getPermutationCompactIndex(state.edgesPerm);
108
+ const o = getOrientationIndex(state.edgesOrient);
109
+ return c + 81 * (p + PERMUTATION_COUNT * o);
110
+ }
111
+ export function getPyraminxStateFromIndex(index) {
112
+ let val = index;
113
+ const centersIdx = val % 81;
114
+ val = Math.floor(val / 81);
115
+ const permCompact = val % PERMUTATION_COUNT;
116
+ const orientIdx = Math.floor(val / PERMUTATION_COUNT);
117
+ const permFull = getPermutationFullFromCompact(permCompact);
118
+ return {
119
+ centers: getCentersFromIndex(centersIdx),
120
+ edgesPerm: getPermutationFromIndex(permFull, 6),
121
+ edgesOrient: getOrientationFromIndex(orientIdx),
122
+ tips: [0, 0, 0, 0]
123
+ };
124
+ }
125
+ // ---------------------------------------------------------------------------
126
+ // Table constants
127
+ // ---------------------------------------------------------------------------
128
+ export const CENTERS_COUNT = 81;
129
+ export const ORIENTATION_COUNT = 32;
130
+ export const MOVE_COUNT = 8;
131
+ export const STATE_COUNT = 81 * PERMUTATION_COUNT * ORIENTATION_COUNT; // 933_120
132
+ export const POLICY_PACKED_ENTRIES = Math.ceil((STATE_COUNT * 3) / 8); // 349_920
133
+ // 3-bit little-endian unpacking of the packed policy table.
134
+ export function getPolicyMoveFromPacked(packed, stateIndex) {
135
+ const bits = stateIndex * 3;
136
+ const byteIndex = bits >> 3;
137
+ const offset = bits & 7;
138
+ const word = (packed[byteIndex] | ((packed[byteIndex + 1] ?? 0) << 8)) >> offset;
139
+ return word & 0x07;
140
+ }
141
+ export const PYRAMINX_MOVES = [
142
+ "U", "U'", "L", "L'", "R", "R'", "B", "B'"
143
+ ];
144
+ // ---------------------------------------------------------------------------
145
+ // Table generation (used by generate script)
146
+ // ---------------------------------------------------------------------------
147
+ export function generateCentersTable() {
148
+ const table = new Uint8Array(CENTERS_COUNT * MOVE_COUNT);
149
+ for (let c = 0; c < CENTERS_COUNT; c++) {
150
+ const centers = getCentersFromIndex(c);
151
+ for (let m = 0; m < MOVE_COUNT; m++) {
152
+ const state = {
153
+ centers: [...centers],
154
+ edgesPerm: [0, 1, 2, 3, 4, 5],
155
+ edgesOrient: [0, 0, 0, 0, 0, 0],
156
+ tips: [0, 0, 0, 0]
157
+ };
158
+ const next = applyPyraminxMove(state, PYRAMINX_MOVES[m]);
159
+ table[c * MOVE_COUNT + m] = getCentersIndex(next.centers);
160
+ }
161
+ }
162
+ return table;
163
+ }
164
+ export function generatePermutationTable() {
165
+ const table = new Uint16Array(PERMUTATION_COUNT * MOVE_COUNT);
166
+ for (let pCompact = 0; pCompact < PERMUTATION_COUNT; pCompact++) {
167
+ const full = getPermutationFullFromCompact(pCompact);
168
+ const perm = getPermutationFromIndex(full, 6);
169
+ for (let m = 0; m < MOVE_COUNT; m++) {
170
+ const state = {
171
+ centers: [0, 0, 0, 0],
172
+ edgesPerm: [...perm],
173
+ edgesOrient: [0, 0, 0, 0, 0, 0],
174
+ tips: [0, 0, 0, 0]
175
+ };
176
+ const next = applyPyraminxMove(state, PYRAMINX_MOVES[m]);
177
+ const nextFull = getPermutationIndex(next.edgesPerm);
178
+ table[pCompact * MOVE_COUNT + m] = getPermutationCompactFromFull(nextFull);
179
+ }
180
+ }
181
+ return table;
182
+ }
183
+ export function generateOrientationTable() {
184
+ const table = new Uint8Array(ORIENTATION_COUNT * MOVE_COUNT);
185
+ for (let o = 0; o < ORIENTATION_COUNT; o++) {
186
+ const orient = getOrientationFromIndex(o);
187
+ for (let m = 0; m < MOVE_COUNT; m++) {
188
+ const state = {
189
+ centers: [0, 0, 0, 0],
190
+ edgesPerm: [0, 1, 2, 3, 4, 5],
191
+ edgesOrient: [...orient],
192
+ tips: [0, 0, 0, 0]
193
+ };
194
+ const next = applyPyraminxMove(state, PYRAMINX_MOVES[m]);
195
+ table[o * MOVE_COUNT + m] = getOrientationIndex(next.edgesOrient);
196
+ }
197
+ }
198
+ return table;
199
+ }
200
+ // BFS from solved stores, for each state, the inverse of the move that first
201
+ // discovered it: an optimal move toward solved. Packed 3 bits per state
202
+ // (8 moves; the solved entry is never read, the solve loop exits first).
203
+ export function generatePolicyTable(centersTable, permTable, orientTable) {
204
+ const distances = new Uint8Array(STATE_COUNT).fill(255);
205
+ const policy = new Uint8Array(STATE_COUNT);
206
+ const solvedIdx = 0; // c=0,p=0,o=0 => 0
207
+ distances[solvedIdx] = 0;
208
+ const queue = new Uint32Array(STATE_COUNT);
209
+ let read = 0;
210
+ let write = 1;
211
+ queue[0] = solvedIdx;
212
+ let maxDepth = 0;
213
+ while (read < write) {
214
+ const curr = queue[read++];
215
+ const dist = distances[curr];
216
+ const c = curr % 81;
217
+ const rem = Math.floor(curr / 81);
218
+ const p = rem % PERMUTATION_COUNT;
219
+ const o = Math.floor(rem / PERMUTATION_COUNT);
220
+ const nextDist = dist + 1;
221
+ for (let m = 0; m < MOVE_COUNT; m++) {
222
+ const nc = centersTable[c * MOVE_COUNT + m];
223
+ const np = permTable[p * MOVE_COUNT + m];
224
+ const no = orientTable[o * MOVE_COUNT + m];
225
+ const next = nc + 81 * (np + PERMUTATION_COUNT * no);
226
+ if (distances[next] === 255) {
227
+ distances[next] = nextDist;
228
+ policy[next] = m ^ 1; // inverse move of the discovery edge
229
+ if (nextDist > maxDepth)
230
+ maxDepth = nextDist;
231
+ queue[write++] = next;
232
+ }
233
+ }
234
+ }
235
+ const packed = new Uint8Array(POLICY_PACKED_ENTRIES);
236
+ for (let i = 0; i < STATE_COUNT; i++) {
237
+ const bits = i * 3;
238
+ const byteIndex = bits >> 3;
239
+ const offset = bits & 7;
240
+ const value = policy[i] & 0x07;
241
+ packed[byteIndex] |= (value << offset) & 0xff;
242
+ if (offset > 5) {
243
+ packed[byteIndex + 1] |= value >> (8 - offset);
244
+ }
245
+ }
246
+ return { packed, maxDepth };
247
+ }
248
+ export function checksum(values) {
249
+ let hash = 0x811c9dc5;
250
+ for (let i = 0; i < values.length; i++) {
251
+ hash ^= values[i];
252
+ hash = Math.imul(hash, 0x01000193);
253
+ }
254
+ return hash >>> 0;
255
+ }
256
+ // ---------------------------------------------------------------------------
257
+ // Runtime table loading (mirrors cube2/cube3)
258
+ // ---------------------------------------------------------------------------
259
+ let tablesLoaded = false;
260
+ export let CENTERS_TABLE = null;
261
+ export let PERMUTATION_TABLE = null;
262
+ export let ORIENTATION_TABLE = null;
263
+ export let POLICY_TABLE = null;
264
+ export async function ensureTablesLoaded() {
265
+ if (tablesLoaded)
266
+ return;
267
+ const tables = await loadGeneratedPyraminxSolverTables();
268
+ CENTERS_TABLE = tables.centersTable;
269
+ PERMUTATION_TABLE = tables.permutationTable;
270
+ ORIENTATION_TABLE = tables.orientationTable;
271
+ POLICY_TABLE = tables.policyTable;
272
+ tablesLoaded = true;
273
+ }
274
+ export function getPolicyMove(stateIndex) {
275
+ if (!POLICY_TABLE)
276
+ throw new Error("Pyraminx solver tables not loaded. Call ensureTablesLoaded() first.");
277
+ return getPolicyMoveFromPacked(POLICY_TABLE, stateIndex);
278
+ }
279
+ export function getCentersTransition(centerIdx, move) {
280
+ if (!CENTERS_TABLE)
281
+ throw new Error("Pyraminx solver tables not loaded.");
282
+ return CENTERS_TABLE[centerIdx * MOVE_COUNT + move];
283
+ }
284
+ export function getPermutationTransition(permIdx, move) {
285
+ if (!PERMUTATION_TABLE)
286
+ throw new Error("Pyraminx solver tables not loaded.");
287
+ return PERMUTATION_TABLE[permIdx * MOVE_COUNT + move];
288
+ }
289
+ export function getOrientationTransition(orientIdx, move) {
290
+ if (!ORIENTATION_TABLE)
291
+ throw new Error("Pyraminx solver tables not loaded.");
292
+ return ORIENTATION_TABLE[orientIdx * MOVE_COUNT + move];
293
+ }
294
+ export function resetTablesForTests() {
295
+ tablesLoaded = false;
296
+ CENTERS_TABLE = null;
297
+ PERMUTATION_TABLE = null;
298
+ ORIENTATION_TABLE = null;
299
+ POLICY_TABLE = null;
300
+ }
@@ -0,0 +1,12 @@
1
+ export interface FetchTableBufferOptions {
2
+ /** Cache API cache name to store responses in (used by cube3). */
3
+ cacheName?: string;
4
+ /**
5
+ * Whether the manifest advertises precompressed variants for this file.
6
+ * Defaults to true (old manifests without the flag keep the old behavior);
7
+ * set to false to skip .br/.gz attempts entirely.
8
+ */
9
+ compressed?: boolean;
10
+ }
11
+ export declare function fetchTableBuffer(baseUrl: string, dir: string, file: string, options?: FetchTableBufferOptions): Promise<ArrayBuffer>;
12
+ //# sourceMappingURL=compressed-fetch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compressed-fetch.d.ts","sourceRoot":"","sources":["../../../src/puzzles/shared/compressed-fetch.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,uBAAuB;IACtC,kEAAkE;IAClE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAkBD,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,uBAA4B,GACpC,OAAO,CAAC,WAAW,CAAC,CAuBtB"}
@@ -0,0 +1,68 @@
1
+ // Shared browser-only helper to fetch solver tables, preferring the
2
+ // precompressed .br/.gz variants (decompressed with DecompressionStream)
3
+ // and falling back to the raw .bin file.
4
+ //
5
+ // Node never uses this module: node-loader.ts reads the .bin files directly
6
+ // from the filesystem.
7
+ // DecompressionStream only guarantees "gzip"/"deflate"; "br" support varies,
8
+ // so probe once and never attempt formats the runtime cannot decompress.
9
+ const SUPPORTED_FORMATS = (() => {
10
+ if (typeof DecompressionStream === "undefined")
11
+ return [];
12
+ const supported = [];
13
+ for (const [suffix, format] of [["br", "br"], ["gz", "gzip"]]) {
14
+ try {
15
+ new DecompressionStream(format);
16
+ supported.push([suffix, format]);
17
+ }
18
+ catch {
19
+ // format not supported by this runtime
20
+ }
21
+ }
22
+ return supported;
23
+ })();
24
+ export async function fetchTableBuffer(baseUrl, dir, file, options = {}) {
25
+ const urlFor = (name) => `${baseUrl}/${dir}/${name}`;
26
+ const cachedFetch = options.cacheName === undefined ? undefined : makeCachedFetch(options.cacheName);
27
+ if (options.compressed !== false && SUPPORTED_FORMATS.length > 0) {
28
+ for (const [suffix, format] of SUPPORTED_FORMATS) {
29
+ try {
30
+ const response = await (cachedFetch ? cachedFetch(urlFor(`${file}.${suffix}`)) : fetch(urlFor(`${file}.${suffix}`)));
31
+ if (response.ok && response.body) {
32
+ const stream = response.body.pipeThrough(new DecompressionStream(format));
33
+ return await new Response(stream).arrayBuffer();
34
+ }
35
+ }
36
+ catch {
37
+ // try the next candidate
38
+ }
39
+ }
40
+ }
41
+ const response = await (cachedFetch ? cachedFetch(urlFor(file)) : fetch(urlFor(file)));
42
+ if (!response.ok) {
43
+ throw new Error(`Failed to load ${file}: ${response.status} ${response.statusText}`);
44
+ }
45
+ return response.arrayBuffer();
46
+ }
47
+ function makeCachedFetch(cacheName) {
48
+ return async (url) => {
49
+ if (typeof caches === "undefined") {
50
+ return fetch(url);
51
+ }
52
+ try {
53
+ const cache = await caches.open(cacheName);
54
+ const cached = await cache.match(url);
55
+ if (cached) {
56
+ return cached;
57
+ }
58
+ const response = await fetch(url);
59
+ if (response.ok) {
60
+ await cache.put(url, response.clone());
61
+ }
62
+ return response;
63
+ }
64
+ catch {
65
+ return fetch(url);
66
+ }
67
+ };
68
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"facelets.d.ts","sourceRoot":"","sources":["../../../src/puzzles/skewb/facelets.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAgC5D,wBAAgB,yBAAyB,IAAI,aAAa,CASzD;AAaD,wBAAgB,eAAe,CAAC,KAAK,EAAE,UAAU,GAAG,aAAa,CAsBhE;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,aAAa,GAAG,MAAM,CAEnE;AAED,wBAAgB,iBAAiB,IAAI,SAAS,MAAM,EAAE,CAErD"}
1
+ {"version":3,"file":"facelets.d.ts","sourceRoot":"","sources":["../../../src/puzzles/skewb/facelets.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AA0D5D,wBAAgB,yBAAyB,IAAI,aAAa,CASzD;AAaD,wBAAgB,eAAe,CAAC,KAAK,EAAE,UAAU,GAAG,aAAa,CAqBhE;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,aAAa,GAAG,MAAM,CAEnE;AAED,wBAAgB,iBAAiB,IAAI,SAAS,MAAM,EAAE,CAErD"}
@@ -1,6 +1,7 @@
1
1
  // Facelets model matching cubingcenter-viz/src/puzzles/skewb.ts
2
2
  // Each face has 5 stickers: 0=center, 1=TL,2=TR,3=BL,4=BR (net layout)
3
3
  const FACE_ORDER = ["U", "R", "F", "D", "L", "B"];
4
+ const FACE_INDEX = { U: 0, R: 1, F: 2, D: 3, L: 4, B: 5 };
4
5
  // Canonical sticker faces per corner position: [UD sticker, RL sticker, FB sticker]
5
6
  const CORNER_STICKERS = [
6
7
  ["U", "R", "F"],
@@ -23,43 +24,65 @@ const CORNER_SLOTS = [
23
24
  [["D", 4], ["R", 4], ["B", 3]],
24
25
  [["D", 3], ["L", 3], ["B", 4]],
25
26
  ];
27
+ // For each (position, piece, orientation) the three facelet writes, flattened:
28
+ // face index, sticker slot index and sticker color index, so the hot loop is
29
+ // pure table lookups (no tuple destructuring, no modulo).
30
+ const CORNER_WRITE_COUNT = 8 * 8 * 3 * 3;
31
+ const WRITE_FACE = new Int8Array(CORNER_WRITE_COUNT);
32
+ const WRITE_SLOT = new Int8Array(CORNER_WRITE_COUNT);
33
+ const WRITE_COLOR = new Int8Array(CORNER_WRITE_COUNT);
34
+ for (let pos = 0; pos < 8; pos++) {
35
+ const slots = CORNER_SLOTS[pos];
36
+ for (let piece = 0; piece < 8; piece++) {
37
+ const stickers = CORNER_STICKERS[piece];
38
+ for (let ori = 0; ori < 3; ori++) {
39
+ const base = ((pos * 8 + piece) * 3 + ori) * 3;
40
+ for (let i = 0; i < 3; i++) {
41
+ const [face, idx] = slots[i];
42
+ const color = stickers[(i + 3 - ori) % 3];
43
+ WRITE_FACE[base + i] = FACE_INDEX[face];
44
+ WRITE_SLOT[base + i] = idx;
45
+ WRITE_COLOR[base + i] = FACE_INDEX[color];
46
+ }
47
+ }
48
+ }
49
+ }
26
50
  export function createSolvedSkewbFacelets() {
27
51
  return {
28
- U: Array.from({ length: 5 }, () => "U"),
29
- R: Array.from({ length: 5 }, () => "R"),
30
- F: Array.from({ length: 5 }, () => "F"),
31
- D: Array.from({ length: 5 }, () => "D"),
32
- L: Array.from({ length: 5 }, () => "L"),
33
- B: Array.from({ length: 5 }, () => "B"),
52
+ U: ["U", "U", "U", "U", "U"],
53
+ R: ["R", "R", "R", "R", "R"],
54
+ F: ["F", "F", "F", "F", "F"],
55
+ D: ["D", "D", "D", "D", "D"],
56
+ L: ["L", "L", "L", "L", "L"],
57
+ B: ["B", "B", "B", "B", "B"],
34
58
  };
35
59
  }
36
60
  function createEmptySkewbFacelets() {
37
61
  return {
38
- U: Array(5).fill("?"),
39
- R: Array(5).fill("?"),
40
- F: Array(5).fill("?"),
41
- D: Array(5).fill("?"),
42
- L: Array(5).fill("?"),
43
- B: Array(5).fill("?"),
62
+ U: ["?", "?", "?", "?", "?"],
63
+ R: ["?", "?", "?", "?", "?"],
64
+ F: ["?", "?", "?", "?", "?"],
65
+ D: ["?", "?", "?", "?", "?"],
66
+ L: ["?", "?", "?", "?", "?"],
67
+ B: ["?", "?", "?", "?", "?"],
44
68
  };
45
69
  }
46
70
  export function toSkewbFacelets(state) {
47
71
  const facelets = createEmptySkewbFacelets();
72
+ const faces = [facelets.U, facelets.R, facelets.F, facelets.D, facelets.L, facelets.B];
73
+ const perm = state.corners.permutation;
74
+ const orient = state.corners.orientation;
75
+ const centers = state.centers;
48
76
  // centers
49
77
  for (let face = 0; face < 6; face++) {
50
- const targetFace = FACE_ORDER[face];
51
- facelets[targetFace][0] = FACE_ORDER[state.centers[face]];
78
+ faces[face][0] = FACE_ORDER[centers[face]];
52
79
  }
53
- // corners
80
+ // corners (unrolled 3 writes per position)
54
81
  for (let pos = 0; pos < 8; pos++) {
55
- const piece = state.corners.permutation[pos];
56
- const ori = state.corners.orientation[pos];
57
- const slots = CORNER_SLOTS[pos];
58
- const stickers = CORNER_STICKERS[piece];
59
- for (let i = 0; i < 3; i++) {
60
- const [face, idx] = slots[i];
61
- facelets[face][idx] = stickers[(i + 3 - ori) % 3];
62
- }
82
+ const base = ((pos * 8 + perm[pos]) * 3 + orient[pos]) * 3;
83
+ faces[WRITE_FACE[base]][WRITE_SLOT[base]] = FACE_ORDER[WRITE_COLOR[base]];
84
+ faces[WRITE_FACE[base + 1]][WRITE_SLOT[base + 1]] = FACE_ORDER[WRITE_COLOR[base + 1]];
85
+ faces[WRITE_FACE[base + 2]][WRITE_SLOT[base + 2]] = FACE_ORDER[WRITE_COLOR[base + 2]];
63
86
  }
64
87
  return facelets;
65
88
  }
@@ -5,6 +5,8 @@ export { cloneSkewbState, createSolvedSkewbState, isSolvedSkewbState, skewbState
5
5
  export { permutationParity, validateSkewbState, isAllowedRandomSkewbState, isOneMoveFromSolvedSkewbState } from "./validation.js";
6
6
  export { generateRandomSkewbState } from "./random-state.js";
7
7
  export { createSolvedSkewbFacelets, formatSkewbFacelets, getSkewbFaceOrder, toSkewbFacelets } from "./facelets.js";
8
- export { solveSkewb, getStateIndex, getStateFromIndex } from "./solver.js";
8
+ export { solveSkewb, solveSkewbWithInfo, solveSkewbSync, verifySkewbScramble, verifySkewbSolution, getStateIndex, getStateFromIndex, } from "./solver/index.js";
9
+ export * as solver from "./solver/index.js";
9
10
  export { generateSkewbScramble, skewbScrambleGenerator } from "./scrambler.js";
11
+ export { generateScramble, generateScrambles, getEventInfo, getSupportedEvents, initializeScrambler } from "../../core/index.js";
10
12
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/puzzles/skewb/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACtD,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE,aAAa,EAAE,uBAAuB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC/G,OAAO,EACL,mBAAmB,EACnB,cAAc,EACd,oBAAoB,EACpB,oBAAoB,EACpB,eAAe,EACf,mBAAmB,EACnB,cAAc,GACf,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,eAAe,EACf,sBAAsB,EACtB,kBAAkB,EAClB,gBAAgB,GACjB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,yBAAyB,EAAE,6BAA6B,EAAE,MAAM,iBAAiB,CAAC;AAClI,OAAO,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AAC7D,OAAO,EAAE,yBAAyB,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACnH,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAC3E,OAAO,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/puzzles/skewb/index.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACtD,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE,aAAa,EAAE,uBAAuB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC/G,OAAO,EACL,mBAAmB,EACnB,cAAc,EACd,oBAAoB,EACpB,oBAAoB,EACpB,eAAe,EACf,mBAAmB,EACnB,cAAc,GACf,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,eAAe,EACf,sBAAsB,EACtB,kBAAkB,EAClB,gBAAgB,GACjB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,yBAAyB,EAAE,6BAA6B,EAAE,MAAM,iBAAiB,CAAC;AAClI,OAAO,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AAC7D,OAAO,EAAE,yBAAyB,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACnH,OAAO,EACL,UAAU,EACV,kBAAkB,EAClB,cAAc,EACd,mBAAmB,EACnB,mBAAmB,EACnB,aAAa,EACb,iBAAiB,GAClB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,MAAM,MAAM,mBAAmB,CAAC;AAC5C,OAAO,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AAE/E,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,YAAY,EACZ,kBAAkB,EAClB,mBAAmB,EACpB,MAAM,qBAAqB,CAAC"}
@@ -1,8 +1,13 @@
1
+ import { registerScrambleGenerator } from "../../core/index.js";
2
+ import { skewbScrambleGenerator } from "./scrambler.js";
3
+ registerScrambleGenerator(skewbScrambleGenerator);
1
4
  export { SKEWB_MOVES, SKEWB_FACES } from "./types.js";
2
5
  export { applySkewbAlgorithm, applySkewbMove, formatSkewbAlgorithm, invertSkewbAlgorithm, invertSkewbMove, parseSkewbAlgorithm, parseSkewbMove, } from "./moves.js";
3
6
  export { cloneSkewbState, createSolvedSkewbState, isSolvedSkewbState, skewbStatesEqual, } from "./state.js";
4
7
  export { permutationParity, validateSkewbState, isAllowedRandomSkewbState, isOneMoveFromSolvedSkewbState } from "./validation.js";
5
8
  export { generateRandomSkewbState } from "./random-state.js";
6
9
  export { createSolvedSkewbFacelets, formatSkewbFacelets, getSkewbFaceOrder, toSkewbFacelets } from "./facelets.js";
7
- export { solveSkewb, getStateIndex, getStateFromIndex } from "./solver.js";
10
+ export { solveSkewb, solveSkewbWithInfo, solveSkewbSync, verifySkewbScramble, verifySkewbSolution, getStateIndex, getStateFromIndex, } from "./solver/index.js";
11
+ export * as solver from "./solver/index.js";
8
12
  export { generateSkewbScramble, skewbScrambleGenerator } from "./scrambler.js";
13
+ export { generateScramble, generateScrambles, getEventInfo, getSupportedEvents, initializeScrambler } from "../../core/index.js";
@@ -1,3 +1,3 @@
1
1
  import type { SkewbRandomStateOptions, SkewbState } from "./types.js";
2
- export declare function generateRandomSkewbState(options?: SkewbRandomStateOptions): SkewbState;
2
+ export declare function generateRandomSkewbState(options?: SkewbRandomStateOptions): Promise<SkewbState>;
3
3
  //# sourceMappingURL=random-state.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"random-state.d.ts","sourceRoot":"","sources":["../../../src/puzzles/skewb/random-state.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,uBAAuB,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAMtE,wBAAgB,wBAAwB,CAAC,OAAO,GAAE,uBAA4B,GAAG,UAAU,CAS1F"}
1
+ {"version":3,"file":"random-state.d.ts","sourceRoot":"","sources":["../../../src/puzzles/skewb/random-state.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,uBAAuB,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAatE,wBAAsB,wBAAwB,CAC5C,OAAO,GAAE,uBAA4B,GACpC,OAAO,CAAC,UAAU,CAAC,CAiBrB"}
@@ -1,27 +1,34 @@
1
1
  import { createRandomSource } from "../../random/index.js";
2
- import { isAllowedRandomSkewbState } from "./validation.js";
3
- import { applySkewbMove } from "./moves.js";
4
- import { createSolvedSkewbState } from "./state.js";
5
- import { SKEWB_MOVES } from "./types.js";
6
- export function generateRandomSkewbState(options = {}) {
2
+ import { ensureTablesLoaded, getCentersTransition, getOrientationTransition, getPermutationTransition, getStateFromRank, MOVE_COUNT, rankFromComponents, SOLVED_RANK, STATE_COUNT, } from "./solver/index.js";
3
+ export async function generateRandomSkewbState(options = {}) {
4
+ await ensureTablesLoaded();
7
5
  const random = createRandomSource(options.seed);
8
6
  const rejectTrivial = options.rejectTrivial ?? true;
7
+ const trivialRanks = rejectTrivial ? getTrivialRanks() : null;
8
+ // Exact uniform sampling: the compact rank is a bijection over the
9
+ // 3,149,280 reachable states, so a uniform rank gives every state exactly
10
+ // the same probability (unlike a finite random walk, which only approaches
11
+ // uniformity). Trivial states (solved / one move from solved) are rejected
12
+ // and resampled, which keeps the conditional distribution uniform.
9
13
  for (let attempt = 0; attempt < 100; attempt++) {
10
- const state = createLegalRandomState(random);
11
- if (!rejectTrivial || isAllowedRandomSkewbState(state))
12
- return state;
14
+ const rank = random.nextInt(STATE_COUNT);
15
+ if (trivialRanks && trivialRanks.has(rank))
16
+ continue;
17
+ return getStateFromRank(rank);
13
18
  }
14
19
  throw new Error("Unable to generate a non-trivial random Skewb state.");
15
20
  }
16
- function createLegalRandomState(random) {
17
- // Generate a random reachable Skewb state via random walk from solved.
18
- // Skewb has 3,149,280 reachable states with WCA moves (fixed corner).
19
- // A walk of 30+ moves is sufficient to approach uniform distribution.
20
- let state = createSolvedSkewbState();
21
- const walkLength = 30 + random.nextInt(5); // 30-34 moves
22
- for (let i = 0; i < walkLength; i++) {
23
- const move = random.choice([...SKEWB_MOVES]);
24
- state = applySkewbMove(state, move);
21
+ let trivialRanks = null;
22
+ function getTrivialRanks() {
23
+ if (!trivialRanks) {
24
+ const ranks = new Set([SOLVED_RANK]);
25
+ for (let m = 0; m < MOVE_COUNT; m++) {
26
+ const r7 = getPermutationTransition(0, m);
27
+ const rO = getOrientationTransition(0, m);
28
+ const rC = getCentersTransition(0, m);
29
+ ranks.add(rankFromComponents(r7, rO, rC));
30
+ }
31
+ trivialRanks = ranks;
25
32
  }
26
- return state;
33
+ return trivialRanks;
27
34
  }
@@ -1 +1 @@
1
- {"version":3,"file":"scrambler.d.ts","sourceRoot":"","sources":["../../../src/puzzles/skewb/scrambler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAO9F,eAAO,MAAM,sBAAsB,EAAE,iBAYpC,CAAC;AAEF,wBAAsB,qBAAqB,CACzC,OAAO,GAAE,eAAe,GAAG;IAAE,cAAc,CAAC,EAAE,OAAO,CAAA;CAAO,GAC3D,OAAO,CAAC,cAAc,CAAC,CAiCzB"}
1
+ {"version":3,"file":"scrambler.d.ts","sourceRoot":"","sources":["../../../src/puzzles/skewb/scrambler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAM9F,eAAO,MAAM,sBAAsB,EAAE,iBAYpC,CAAC;AAEF,wBAAsB,qBAAqB,CACzC,OAAO,GAAE,eAAe,GAAG;IAAE,cAAc,CAAC,EAAE,OAAO,CAAA;CAAO,GAC3D,OAAO,CAAC,cAAc,CAAC,CA+BzB"}
@@ -1,7 +1,6 @@
1
1
  import { generateRandomSkewbState } from "./random-state.js";
2
- import { createSolvedSkewbState, isSolvedSkewbState, skewbStatesEqual } from "./state.js";
3
- import { applySkewbAlgorithm, formatSkewbAlgorithm, invertSkewbAlgorithm } from "./moves.js";
4
- import { solveSkewb } from "./solver.js";
2
+ import { formatSkewbAlgorithm, invertSkewbAlgorithm } from "./moves.js";
3
+ import { solveSkewb, verifySkewbScramble } from "./solver/index.js";
5
4
  import { formatSkewbFacelets, toSkewbFacelets } from "./facelets.js";
6
5
  export const skewbScrambleGenerator = {
7
6
  info: {
@@ -9,25 +8,23 @@ export const skewbScrambleGenerator = {
9
8
  name: "Skewb",
10
9
  puzzle: "skewb",
11
10
  quality: "random-state",
12
- generator: "skewb-bfs",
13
- description: "Random-state Skewb scramble generated by an optimal bidirectional BFS solver.",
11
+ generator: "skewb-optimal",
12
+ description: "Random-state Skewb scramble generated by an optimal solver with precalculated tables.",
14
13
  },
15
14
  generate(options = {}) {
16
15
  return generateSkewbScramble(options);
17
16
  },
18
17
  };
19
18
  export async function generateSkewbScramble(options = {}) {
20
- const state = generateRandomSkewbState(options);
21
- const solution = solveSkewb(state);
19
+ const state = await generateRandomSkewbState(options);
20
+ const solution = await solveSkewb(state);
22
21
  const scrambleMoves = invertSkewbAlgorithm(solution);
23
22
  if (!options.skipValidation) {
24
- const scrambledState = applySkewbAlgorithm(createSolvedSkewbState(), scrambleMoves);
25
- if (!skewbStatesEqual(scrambledState, state)) {
23
+ // If the scramble reproduces the target state, its inverse (the
24
+ // solution) solves it by construction, so a single walk suffices.
25
+ if (!verifySkewbScramble(state, scrambleMoves)) {
26
26
  throw new Error("Generated Skewb scramble does not reproduce the target random state.");
27
27
  }
28
- if (!isSolvedSkewbState(applySkewbAlgorithm(state, solution))) {
29
- throw new Error("Generated Skewb solution does not solve the target random state.");
30
- }
31
28
  if (scrambleMoves.length < 2)
32
29
  throw new Error("Generated Skewb scramble is too short.");
33
30
  }
@@ -37,7 +34,7 @@ export async function generateSkewbScramble(options = {}) {
37
34
  scramble: formatSkewbAlgorithm(scrambleMoves),
38
35
  moves: scrambleMoves,
39
36
  quality: "random-state",
40
- generator: "skewb-bfs",
37
+ generator: "skewb-optimal",
41
38
  metadata: {
42
39
  state,
43
40
  facelets,