@cubingcenter/scrambler 0.4.1 → 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.
- package/README.md +21 -0
- package/dist/core/api.d.ts.map +1 -1
- package/dist/core/api.js +6 -1
- package/dist/puzzles/skewb/facelets.d.ts.map +1 -1
- package/dist/puzzles/skewb/facelets.js +46 -23
- package/dist/puzzles/skewb/index.d.ts +2 -1
- package/dist/puzzles/skewb/index.d.ts.map +1 -1
- package/dist/puzzles/skewb/index.js +2 -1
- package/dist/puzzles/skewb/random-state.d.ts +1 -1
- package/dist/puzzles/skewb/random-state.d.ts.map +1 -1
- package/dist/puzzles/skewb/random-state.js +25 -18
- package/dist/puzzles/skewb/scrambler.d.ts.map +1 -1
- package/dist/puzzles/skewb/scrambler.js +10 -13
- package/dist/puzzles/skewb/solver/browser-loader.d.ts +20 -0
- package/dist/puzzles/skewb/solver/browser-loader.d.ts.map +1 -0
- package/dist/puzzles/skewb/solver/browser-loader.js +86 -0
- package/dist/puzzles/skewb/solver/generated/centers-table.bin +0 -0
- package/dist/puzzles/skewb/solver/generated/helpers.bin +0 -0
- package/dist/puzzles/skewb/solver/generated/manifest.json +31 -0
- package/dist/puzzles/skewb/solver/generated/orientation-table.bin +0 -0
- package/dist/puzzles/skewb/solver/generated/permutation-table.bin +0 -0
- package/dist/puzzles/skewb/solver/generated/policy.bin +0 -0
- package/dist/puzzles/skewb/solver/generated-assets.d.ts +10 -0
- package/dist/puzzles/skewb/solver/generated-assets.d.ts.map +1 -0
- package/dist/puzzles/skewb/solver/generated-assets.js +24 -0
- package/dist/puzzles/skewb/solver/index.d.ts +5 -0
- package/dist/puzzles/skewb/solver/index.d.ts.map +1 -0
- package/dist/puzzles/skewb/solver/index.js +4 -0
- package/dist/puzzles/skewb/solver/node-loader.d.ts +10 -0
- package/dist/puzzles/skewb/solver/node-loader.d.ts.map +1 -0
- package/dist/puzzles/skewb/solver/node-loader.js +91 -0
- package/dist/puzzles/skewb/solver/search.d.ts +11 -0
- package/dist/puzzles/skewb/solver/search.d.ts.map +1 -0
- package/dist/puzzles/skewb/solver/search.js +80 -0
- package/dist/puzzles/skewb/solver/table-metadata.d.ts +27 -0
- package/dist/puzzles/skewb/solver/table-metadata.d.ts.map +1 -0
- package/dist/puzzles/skewb/solver/table-metadata.js +26 -0
- package/dist/puzzles/skewb/solver/tables.d.ts +68 -0
- package/dist/puzzles/skewb/solver/tables.d.ts.map +1 -0
- package/dist/puzzles/skewb/solver/tables.js +508 -0
- package/dist/random/prng.js +10 -1
- package/dist-cjs/core/api.js +6 -1
- package/dist-cjs/puzzles/skewb/facelets.js +46 -23
- package/dist-cjs/puzzles/skewb/index.js +49 -11
- package/dist-cjs/puzzles/skewb/random-state.js +25 -18
- package/dist-cjs/puzzles/skewb/scrambler.js +9 -12
- package/dist-cjs/puzzles/skewb/solver/browser-loader.js +91 -0
- package/dist-cjs/puzzles/skewb/solver/generated/centers-table.bin +0 -0
- package/dist-cjs/puzzles/skewb/solver/generated/helpers.bin +0 -0
- package/dist-cjs/puzzles/skewb/solver/generated/manifest.json +31 -0
- package/dist-cjs/puzzles/skewb/solver/generated/orientation-table.bin +0 -0
- package/dist-cjs/puzzles/skewb/solver/generated/permutation-table.bin +0 -0
- package/dist-cjs/puzzles/skewb/solver/generated/policy.bin +0 -0
- package/dist-cjs/puzzles/skewb/solver/generated-assets.js +60 -0
- package/dist-cjs/puzzles/skewb/solver/index.js +23 -0
- package/dist-cjs/puzzles/skewb/solver/node-loader.js +97 -0
- package/dist-cjs/puzzles/skewb/solver/search.js +87 -0
- package/dist-cjs/puzzles/skewb/solver/table-metadata.js +29 -0
- package/dist-cjs/puzzles/skewb/solver/tables.js +539 -0
- package/dist-cjs/random/prng.js +10 -1
- package/package.json +6 -3
- package/dist/puzzles/skewb/solver.d.ts +0 -11
- package/dist/puzzles/skewb/solver.d.ts.map +0 -1
- package/dist/puzzles/skewb/solver.js +0 -149
- package/dist-cjs/puzzles/skewb/solver.js +0 -160
|
@@ -0,0 +1,539 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SOLVED_RANK = exports.CENTER_UNRANK = exports.PERM7_UNRANK = exports.R7_SETS = exports.ORIENT_CLASS = exports.EVEN_CENTER_FULL = exports.EVEN_CENTER_RANK = exports.POLICY_TABLE = exports.CENTERS_TABLE = exports.ORIENTATION_TABLE = exports.PERMUTATION_TABLE = exports.SKEWB_MOVES = exports.POLICY_PACKED_ENTRIES = exports.STATE_COUNT = exports.MOVE_COUNT = exports.PER_SET_SIZE = exports.ORIENT_CLASS_COUNT = exports.EVEN_CENTER_COUNT = exports.CENTER_COUNT = exports.ORIENTATION_COUNT = exports.PERMUTATION_COUNT = void 0;
|
|
4
|
+
exports.getPermutationIndex = getPermutationIndex;
|
|
5
|
+
exports.getPermutationFromIndex = getPermutationFromIndex;
|
|
6
|
+
exports.getOrientationIndex = getOrientationIndex;
|
|
7
|
+
exports.getOrientationFromIndex = getOrientationFromIndex;
|
|
8
|
+
exports.getCenterIndex = getCenterIndex;
|
|
9
|
+
exports.getCenterFromIndex = getCenterFromIndex;
|
|
10
|
+
exports.getStateIndex = getStateIndex;
|
|
11
|
+
exports.getStateFromIndex = getStateFromIndex;
|
|
12
|
+
exports.getPerm7Index = getPerm7Index;
|
|
13
|
+
exports.getPerm7FromIndex = getPerm7FromIndex;
|
|
14
|
+
exports.ensureTablesLoaded = ensureTablesLoaded;
|
|
15
|
+
exports.assignTables = assignTables;
|
|
16
|
+
exports.resetTablesForTests = resetTablesForTests;
|
|
17
|
+
exports.rankFromComponents = rankFromComponents;
|
|
18
|
+
exports.getStateRank = getStateRank;
|
|
19
|
+
exports.getStateFromRank = getStateFromRank;
|
|
20
|
+
exports.getPolicyMoveFromPacked = getPolicyMoveFromPacked;
|
|
21
|
+
exports.getPolicyMove = getPolicyMove;
|
|
22
|
+
exports.getPermutationTransition = getPermutationTransition;
|
|
23
|
+
exports.getOrientationTransition = getOrientationTransition;
|
|
24
|
+
exports.getCentersTransition = getCentersTransition;
|
|
25
|
+
exports.generatePermutationTable = generatePermutationTable;
|
|
26
|
+
exports.generateOrientationTable = generateOrientationTable;
|
|
27
|
+
exports.generateCentersTable = generateCentersTable;
|
|
28
|
+
exports.permutationParity = permutationParity;
|
|
29
|
+
exports.generateHelperTables = generateHelperTables;
|
|
30
|
+
exports.generatePolicyTable = generatePolicyTable;
|
|
31
|
+
exports.checksum = checksum;
|
|
32
|
+
const moves_js_1 = require("../moves.js");
|
|
33
|
+
const generated_assets_js_1 = require("./generated-assets.js");
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
// Coordinate encoding / decoding (kept from the original solver.ts)
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
const FACT = [1, 1, 2, 6, 24, 120, 720, 5040, 40320];
|
|
38
|
+
// Popcount lookup (0..255) for the allocation-free Lehmer encoders below.
|
|
39
|
+
const POPCOUNT = new Uint8Array(256);
|
|
40
|
+
for (let i = 1; i < 256; i++)
|
|
41
|
+
POPCOUNT[i] = POPCOUNT[i >> 1] + (i & 1);
|
|
42
|
+
function getPermutationIndex(perm) {
|
|
43
|
+
// perm length 8, but pos0 is always 0 for WCA reachable states.
|
|
44
|
+
// We encode full 8-perm via Lehmer code (0..40319), but for fixed corner it will be in subset.
|
|
45
|
+
let index = 0;
|
|
46
|
+
let used = 0;
|
|
47
|
+
const n = perm.length;
|
|
48
|
+
for (let i = 0; i < n; i++) {
|
|
49
|
+
const v = perm[i];
|
|
50
|
+
const digit = v - POPCOUNT[used & ((1 << v) - 1)];
|
|
51
|
+
used |= 1 << v;
|
|
52
|
+
index += digit * FACT[n - 1 - i];
|
|
53
|
+
}
|
|
54
|
+
return index;
|
|
55
|
+
}
|
|
56
|
+
function getPermutationFromIndex(index, n) {
|
|
57
|
+
const perm = Array(n).fill(0);
|
|
58
|
+
const temp = Array.from({ length: n }, (_, i) => i);
|
|
59
|
+
let val = index;
|
|
60
|
+
for (let i = 0; i < n; i++) {
|
|
61
|
+
const f = FACT[n - 1 - i];
|
|
62
|
+
const choice = Math.floor(val / f);
|
|
63
|
+
val %= f;
|
|
64
|
+
perm[i] = temp[choice];
|
|
65
|
+
temp.splice(choice, 1);
|
|
66
|
+
}
|
|
67
|
+
return perm;
|
|
68
|
+
}
|
|
69
|
+
function getOrientationIndex(orient) {
|
|
70
|
+
// orient length 8, index 0 is fixed 0, encode remaining 7 base3
|
|
71
|
+
let index = 0;
|
|
72
|
+
for (let i = 7; i >= 1; i--) {
|
|
73
|
+
index = index * 3 + orient[i];
|
|
74
|
+
}
|
|
75
|
+
return index;
|
|
76
|
+
}
|
|
77
|
+
function getOrientationFromIndex(index) {
|
|
78
|
+
const orient = Array(8).fill(0);
|
|
79
|
+
orient[0] = 0;
|
|
80
|
+
let val = index;
|
|
81
|
+
for (let i = 1; i < 8; i++) {
|
|
82
|
+
orient[i] = val % 3;
|
|
83
|
+
val = Math.floor(val / 3);
|
|
84
|
+
}
|
|
85
|
+
return orient;
|
|
86
|
+
}
|
|
87
|
+
function getCenterIndex(centers) {
|
|
88
|
+
let index = 0;
|
|
89
|
+
let used = 0;
|
|
90
|
+
const n = centers.length;
|
|
91
|
+
for (let i = 0; i < n; i++) {
|
|
92
|
+
const v = centers[i];
|
|
93
|
+
const digit = v - POPCOUNT[used & ((1 << v) - 1)];
|
|
94
|
+
used |= 1 << v;
|
|
95
|
+
index += digit * FACT[n - 1 - i];
|
|
96
|
+
}
|
|
97
|
+
return index;
|
|
98
|
+
}
|
|
99
|
+
function getCenterFromIndex(index) {
|
|
100
|
+
return getPermutationFromIndex(index, 6);
|
|
101
|
+
}
|
|
102
|
+
function getStateIndex(state) {
|
|
103
|
+
const p = getPermutationIndex(state.corners.permutation);
|
|
104
|
+
const c = getCenterIndex(state.centers);
|
|
105
|
+
const o = getOrientationIndex(state.corners.orientation);
|
|
106
|
+
// p in 0..40319, c in 0..719, o in 0..2186
|
|
107
|
+
// Combine: ((p * 720 + c) * 2187 + o) -> up to 63B fits in JS number (53-bit)
|
|
108
|
+
return ((p * 720 + c) * 2187 + o);
|
|
109
|
+
}
|
|
110
|
+
function getStateFromIndex(index) {
|
|
111
|
+
const o = index % 2187;
|
|
112
|
+
let val = Math.floor(index / 2187);
|
|
113
|
+
const c = val % 720;
|
|
114
|
+
const p = Math.floor(val / 720);
|
|
115
|
+
return {
|
|
116
|
+
corners: {
|
|
117
|
+
permutation: getPermutationFromIndex(p, 8),
|
|
118
|
+
orientation: getOrientationFromIndex(o),
|
|
119
|
+
},
|
|
120
|
+
centers: getCenterFromIndex(c),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
// ---------------------------------------------------------------------------
|
|
124
|
+
// Compact encoding over the WCA-reachable group (3,149,280 states)
|
|
125
|
+
// ---------------------------------------------------------------------------
|
|
126
|
+
// With the UFR corner fixed (position 0, orientation 0), the reachable group
|
|
127
|
+
// factors as:
|
|
128
|
+
// rC: even center permutations only -> 360
|
|
129
|
+
// rO: orientations of corners 1..7 -> 2187, partitioned into 9 classes
|
|
130
|
+
// r7: corner permutations of 1..7 -> exactly 4 values per (rC, rO-class)
|
|
131
|
+
// so rank = (rC_even * 2187 + rO) * 4 + k is a bijection over the reachable set.
|
|
132
|
+
exports.PERMUTATION_COUNT = 5040; // r7: corners 1..7 Lehmer
|
|
133
|
+
exports.ORIENTATION_COUNT = 2187; // rO: corners 1..7 base-3
|
|
134
|
+
exports.CENTER_COUNT = 720; // rC: centers Lehmer
|
|
135
|
+
exports.EVEN_CENTER_COUNT = 360; // reachable center arrangements
|
|
136
|
+
exports.ORIENT_CLASS_COUNT = 9; // orientation classes
|
|
137
|
+
exports.PER_SET_SIZE = 4; // corner permutations per (rC, rO-class)
|
|
138
|
+
exports.MOVE_COUNT = 8;
|
|
139
|
+
exports.STATE_COUNT = exports.EVEN_CENTER_COUNT * exports.ORIENTATION_COUNT * exports.PER_SET_SIZE; // 3_149_280
|
|
140
|
+
exports.POLICY_PACKED_ENTRIES = Math.ceil((exports.STATE_COUNT * 3) / 8);
|
|
141
|
+
exports.SKEWB_MOVES = ["R", "R'", "U", "U'", "L", "L'", "B", "B'"];
|
|
142
|
+
function getPerm7Index(perm) {
|
|
143
|
+
// Lehmer rank of the positions 1..7 (values 1..7, offset by -1).
|
|
144
|
+
// Allocation-free: digit = count of unused values below the current one,
|
|
145
|
+
// computed with a used-bitmask and a popcount lookup.
|
|
146
|
+
let index = 0;
|
|
147
|
+
let used = 0;
|
|
148
|
+
for (let i = 1; i < 8; i++) {
|
|
149
|
+
const v = perm[i] - 1;
|
|
150
|
+
const digit = v - POPCOUNT[used & ((1 << v) - 1)];
|
|
151
|
+
used |= 1 << v;
|
|
152
|
+
index += digit * FACT[7 - i];
|
|
153
|
+
}
|
|
154
|
+
return index;
|
|
155
|
+
}
|
|
156
|
+
function getPerm7FromIndex(index) {
|
|
157
|
+
const perm = Array(8).fill(0);
|
|
158
|
+
const available = [1, 2, 3, 4, 5, 6, 7];
|
|
159
|
+
let val = index;
|
|
160
|
+
for (let i = 1; i < 8; i++) {
|
|
161
|
+
const f = FACT[7 - i];
|
|
162
|
+
const choice = Math.floor(val / f);
|
|
163
|
+
val %= f;
|
|
164
|
+
perm[i] = available[choice];
|
|
165
|
+
available.splice(choice, 1);
|
|
166
|
+
}
|
|
167
|
+
return perm;
|
|
168
|
+
}
|
|
169
|
+
// ---------------------------------------------------------------------------
|
|
170
|
+
// Table loading / runtime state
|
|
171
|
+
// ---------------------------------------------------------------------------
|
|
172
|
+
let tablesLoaded = false;
|
|
173
|
+
exports.PERMUTATION_TABLE = null; // r7 * MOVE_COUNT + m
|
|
174
|
+
exports.ORIENTATION_TABLE = null; // rO * MOVE_COUNT + m
|
|
175
|
+
exports.CENTERS_TABLE = null; // rC * MOVE_COUNT + m
|
|
176
|
+
exports.POLICY_TABLE = null; // optimal move per rank, 3-bit packed
|
|
177
|
+
exports.EVEN_CENTER_RANK = null; // rC -> compact 0..359 (65535 = odd)
|
|
178
|
+
exports.EVEN_CENTER_FULL = null; // compact 0..359 -> rC (derived at load)
|
|
179
|
+
exports.ORIENT_CLASS = null; // rO -> class 0..8
|
|
180
|
+
exports.R7_SETS = null; // (rC_even * 9 + class) * 4 + i
|
|
181
|
+
exports.PERM7_UNRANK = null; // r7 * 7 + i -> piece at corner i+1 (derived at load)
|
|
182
|
+
exports.CENTER_UNRANK = null; // rC * 6 + i -> center piece (derived at load)
|
|
183
|
+
exports.SOLVED_RANK = 0;
|
|
184
|
+
async function ensureTablesLoaded() {
|
|
185
|
+
if (tablesLoaded)
|
|
186
|
+
return;
|
|
187
|
+
const tables = await (0, generated_assets_js_1.loadGeneratedSkewbSolverTables)();
|
|
188
|
+
assignTables(tables);
|
|
189
|
+
}
|
|
190
|
+
function assignTables(tables) {
|
|
191
|
+
exports.PERMUTATION_TABLE = tables.permutationTable;
|
|
192
|
+
exports.ORIENTATION_TABLE = tables.orientationTable;
|
|
193
|
+
exports.CENTERS_TABLE = tables.centersTable;
|
|
194
|
+
exports.POLICY_TABLE = tables.policyTable;
|
|
195
|
+
exports.EVEN_CENTER_RANK = tables.evenCenterRank;
|
|
196
|
+
exports.ORIENT_CLASS = tables.orientClass;
|
|
197
|
+
exports.R7_SETS = tables.r7Sets;
|
|
198
|
+
exports.EVEN_CENTER_FULL = new Uint16Array(exports.EVEN_CENTER_COUNT);
|
|
199
|
+
for (let rC = 0; rC < exports.CENTER_COUNT; rC++) {
|
|
200
|
+
const compactIdx = tables.evenCenterRank[rC];
|
|
201
|
+
if (compactIdx !== 65535)
|
|
202
|
+
exports.EVEN_CENTER_FULL[compactIdx] = rC;
|
|
203
|
+
}
|
|
204
|
+
exports.PERM7_UNRANK = new Uint8Array(exports.PERMUTATION_COUNT * 7);
|
|
205
|
+
for (let r7 = 0; r7 < exports.PERMUTATION_COUNT; r7++) {
|
|
206
|
+
const perm = getPerm7FromIndex(r7);
|
|
207
|
+
for (let i = 0; i < 7; i++)
|
|
208
|
+
exports.PERM7_UNRANK[r7 * 7 + i] = perm[i + 1];
|
|
209
|
+
}
|
|
210
|
+
exports.CENTER_UNRANK = new Uint8Array(exports.CENTER_COUNT * 6);
|
|
211
|
+
for (let rC = 0; rC < exports.CENTER_COUNT; rC++) {
|
|
212
|
+
const centers = getCenterFromIndex(rC);
|
|
213
|
+
for (let i = 0; i < 6; i++)
|
|
214
|
+
exports.CENTER_UNRANK[rC * 6 + i] = centers[i];
|
|
215
|
+
}
|
|
216
|
+
exports.SOLVED_RANK = rankFromComponents(0, 0, 0);
|
|
217
|
+
tablesLoaded = true;
|
|
218
|
+
}
|
|
219
|
+
function resetTablesForTests() {
|
|
220
|
+
tablesLoaded = false;
|
|
221
|
+
exports.PERMUTATION_TABLE = null;
|
|
222
|
+
exports.ORIENTATION_TABLE = null;
|
|
223
|
+
exports.CENTERS_TABLE = null;
|
|
224
|
+
exports.POLICY_TABLE = null;
|
|
225
|
+
exports.EVEN_CENTER_RANK = null;
|
|
226
|
+
exports.EVEN_CENTER_FULL = null;
|
|
227
|
+
exports.ORIENT_CLASS = null;
|
|
228
|
+
exports.R7_SETS = null;
|
|
229
|
+
exports.PERM7_UNRANK = null;
|
|
230
|
+
exports.CENTER_UNRANK = null;
|
|
231
|
+
}
|
|
232
|
+
// ---------------------------------------------------------------------------
|
|
233
|
+
// Compact rank helpers
|
|
234
|
+
// ---------------------------------------------------------------------------
|
|
235
|
+
function rankFromComponents(r7, rO, rC) {
|
|
236
|
+
const evenRank = exports.EVEN_CENTER_RANK[rC];
|
|
237
|
+
if (evenRank === 65535) {
|
|
238
|
+
throw new Error("Unreachable Skewb state: odd center permutation parity.");
|
|
239
|
+
}
|
|
240
|
+
const cls = exports.ORIENT_CLASS[rO];
|
|
241
|
+
const set = exports.R7_SETS;
|
|
242
|
+
const base = (evenRank * exports.ORIENT_CLASS_COUNT + cls) * exports.PER_SET_SIZE;
|
|
243
|
+
for (let i = 0; i < exports.PER_SET_SIZE; i++) {
|
|
244
|
+
if (set[base + i] === r7) {
|
|
245
|
+
return (evenRank * exports.ORIENTATION_COUNT + rO) * exports.PER_SET_SIZE + i;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
throw new Error("Unreachable Skewb state: corner permutation not in reachable set.");
|
|
249
|
+
}
|
|
250
|
+
function getStateRank(state) {
|
|
251
|
+
if (state.corners.permutation[0] !== 0) {
|
|
252
|
+
throw new Error("The Skewb solver requires the UFR corner (index 0) to be fixed " +
|
|
253
|
+
"as the frame of reference. Please normalize the state before solving.");
|
|
254
|
+
}
|
|
255
|
+
const r7 = getPerm7Index(state.corners.permutation);
|
|
256
|
+
const rO = getOrientationIndex(state.corners.orientation);
|
|
257
|
+
const rC = getCenterIndex(state.centers);
|
|
258
|
+
return rankFromComponents(r7, rO, rC);
|
|
259
|
+
}
|
|
260
|
+
// Inverse of getStateRank: decodes a reachable rank (0..3_149_279) back into
|
|
261
|
+
// a full state. Used for exact uniform random-state sampling.
|
|
262
|
+
function getStateFromRank(rank) {
|
|
263
|
+
const even = Math.floor(rank / (exports.ORIENTATION_COUNT * exports.PER_SET_SIZE));
|
|
264
|
+
const rest = rank % (exports.ORIENTATION_COUNT * exports.PER_SET_SIZE);
|
|
265
|
+
const rO = Math.floor(rest / exports.PER_SET_SIZE);
|
|
266
|
+
const k = rest % exports.PER_SET_SIZE;
|
|
267
|
+
const cls = exports.ORIENT_CLASS[rO];
|
|
268
|
+
const r7 = exports.R7_SETS[(even * exports.ORIENT_CLASS_COUNT + cls) * exports.PER_SET_SIZE + k];
|
|
269
|
+
const perm = new Array(8);
|
|
270
|
+
perm[0] = 0;
|
|
271
|
+
const permBase = r7 * 7;
|
|
272
|
+
for (let i = 0; i < 7; i++)
|
|
273
|
+
perm[i + 1] = exports.PERM7_UNRANK[permBase + i];
|
|
274
|
+
const orient = new Array(8);
|
|
275
|
+
orient[0] = 0;
|
|
276
|
+
let val = rO;
|
|
277
|
+
for (let i = 1; i < 8; i++) {
|
|
278
|
+
orient[i] = val % 3;
|
|
279
|
+
val = Math.floor(val / 3);
|
|
280
|
+
}
|
|
281
|
+
const centers = new Array(6);
|
|
282
|
+
const centerBase = exports.EVEN_CENTER_FULL[even] * 6;
|
|
283
|
+
for (let i = 0; i < 6; i++)
|
|
284
|
+
centers[i] = exports.CENTER_UNRANK[centerBase + i];
|
|
285
|
+
return { corners: { permutation: perm, orientation: orient }, centers };
|
|
286
|
+
}
|
|
287
|
+
// 3-bit little-endian unpacking of the packed policy table.
|
|
288
|
+
function getPolicyMoveFromPacked(packed, stateRank) {
|
|
289
|
+
const bits = stateRank * 3;
|
|
290
|
+
const byteIndex = bits >> 3;
|
|
291
|
+
const offset = bits & 7;
|
|
292
|
+
const word = (packed[byteIndex] | ((packed[byteIndex + 1] ?? 0) << 8)) >> offset;
|
|
293
|
+
return word & 0x07;
|
|
294
|
+
}
|
|
295
|
+
function getPolicyMove(stateRank) {
|
|
296
|
+
if (!exports.POLICY_TABLE) {
|
|
297
|
+
throw new Error("Skewb solver tables not loaded. Call ensureTablesLoaded() first.");
|
|
298
|
+
}
|
|
299
|
+
return getPolicyMoveFromPacked(exports.POLICY_TABLE, stateRank);
|
|
300
|
+
}
|
|
301
|
+
function getPermutationTransition(permIdx, move) {
|
|
302
|
+
if (!exports.PERMUTATION_TABLE) {
|
|
303
|
+
throw new Error("Skewb solver tables not loaded. Call ensureTablesLoaded() first.");
|
|
304
|
+
}
|
|
305
|
+
return exports.PERMUTATION_TABLE[permIdx * exports.MOVE_COUNT + move];
|
|
306
|
+
}
|
|
307
|
+
function getOrientationTransition(orientIdx, move) {
|
|
308
|
+
if (!exports.ORIENTATION_TABLE) {
|
|
309
|
+
throw new Error("Skewb solver tables not loaded. Call ensureTablesLoaded() first.");
|
|
310
|
+
}
|
|
311
|
+
return exports.ORIENTATION_TABLE[orientIdx * exports.MOVE_COUNT + move];
|
|
312
|
+
}
|
|
313
|
+
function getCentersTransition(centerIdx, move) {
|
|
314
|
+
if (!exports.CENTERS_TABLE) {
|
|
315
|
+
throw new Error("Skewb solver tables not loaded. Call ensureTablesLoaded() first.");
|
|
316
|
+
}
|
|
317
|
+
return exports.CENTERS_TABLE[centerIdx * exports.MOVE_COUNT + move];
|
|
318
|
+
}
|
|
319
|
+
// ---------------------------------------------------------------------------
|
|
320
|
+
// Table generation (used by the generate script)
|
|
321
|
+
// ---------------------------------------------------------------------------
|
|
322
|
+
function generatePermutationTable() {
|
|
323
|
+
const table = new Uint16Array(exports.PERMUTATION_COUNT * exports.MOVE_COUNT);
|
|
324
|
+
for (let r7 = 0; r7 < exports.PERMUTATION_COUNT; r7++) {
|
|
325
|
+
const perm = getPerm7FromIndex(r7);
|
|
326
|
+
for (let m = 0; m < exports.MOVE_COUNT; m++) {
|
|
327
|
+
const state = {
|
|
328
|
+
corners: { permutation: [...perm], orientation: [0, 0, 0, 0, 0, 0, 0, 0] },
|
|
329
|
+
centers: [0, 1, 2, 3, 4, 5],
|
|
330
|
+
};
|
|
331
|
+
const next = (0, moves_js_1.applySkewbMove)(state, exports.SKEWB_MOVES[m]);
|
|
332
|
+
table[r7 * exports.MOVE_COUNT + m] = getPerm7Index(next.corners.permutation);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
return table;
|
|
336
|
+
}
|
|
337
|
+
function generateOrientationTable() {
|
|
338
|
+
const table = new Uint16Array(exports.ORIENTATION_COUNT * exports.MOVE_COUNT);
|
|
339
|
+
for (let rO = 0; rO < exports.ORIENTATION_COUNT; rO++) {
|
|
340
|
+
const orient = getOrientationFromIndex(rO);
|
|
341
|
+
for (let m = 0; m < exports.MOVE_COUNT; m++) {
|
|
342
|
+
const state = {
|
|
343
|
+
corners: { permutation: [0, 1, 2, 3, 4, 5, 6, 7], orientation: [...orient] },
|
|
344
|
+
centers: [0, 1, 2, 3, 4, 5],
|
|
345
|
+
};
|
|
346
|
+
const next = (0, moves_js_1.applySkewbMove)(state, exports.SKEWB_MOVES[m]);
|
|
347
|
+
table[rO * exports.MOVE_COUNT + m] = getOrientationIndex(next.corners.orientation);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
return table;
|
|
351
|
+
}
|
|
352
|
+
function generateCentersTable() {
|
|
353
|
+
const table = new Uint16Array(exports.CENTER_COUNT * exports.MOVE_COUNT);
|
|
354
|
+
for (let rC = 0; rC < exports.CENTER_COUNT; rC++) {
|
|
355
|
+
const centers = getCenterFromIndex(rC);
|
|
356
|
+
for (let m = 0; m < exports.MOVE_COUNT; m++) {
|
|
357
|
+
const state = {
|
|
358
|
+
corners: { permutation: [0, 1, 2, 3, 4, 5, 6, 7], orientation: [0, 0, 0, 0, 0, 0, 0, 0] },
|
|
359
|
+
centers: [...centers],
|
|
360
|
+
};
|
|
361
|
+
const next = (0, moves_js_1.applySkewbMove)(state, exports.SKEWB_MOVES[m]);
|
|
362
|
+
table[rC * exports.MOVE_COUNT + m] = getCenterIndex(next.centers);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
return table;
|
|
366
|
+
}
|
|
367
|
+
function permutationParity(perm) {
|
|
368
|
+
let inversions = 0;
|
|
369
|
+
for (let i = 0; i < perm.length; i++) {
|
|
370
|
+
for (let j = i + 1; j < perm.length; j++) {
|
|
371
|
+
if (perm[i] > perm[j])
|
|
372
|
+
inversions++;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
return inversions % 2;
|
|
376
|
+
}
|
|
377
|
+
// Discovers the reachable-state structure with a BFS over the full product
|
|
378
|
+
// coordinate (r7, rO, rC) and derives the compact-rank helper tables:
|
|
379
|
+
// - even center permutations only (360 of 720)
|
|
380
|
+
// - rO partitioned into 9 classes by the 4-corner-permutation sets
|
|
381
|
+
function generateHelperTables(permTable, orientTable, centersTable) {
|
|
382
|
+
const evenCenterRank = new Uint16Array(exports.CENTER_COUNT).fill(65535);
|
|
383
|
+
const evenCenterFull = new Uint16Array(exports.EVEN_CENTER_COUNT);
|
|
384
|
+
let compact = 0;
|
|
385
|
+
for (let rC = 0; rC < exports.CENTER_COUNT; rC++) {
|
|
386
|
+
if (permutationParity(getCenterFromIndex(rC)) === 0) {
|
|
387
|
+
evenCenterRank[rC] = compact;
|
|
388
|
+
evenCenterFull[compact] = rC;
|
|
389
|
+
compact++;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
if (compact !== exports.EVEN_CENTER_COUNT) {
|
|
393
|
+
throw new Error(`Expected ${exports.EVEN_CENTER_COUNT} even center permutations, found ${compact}.`);
|
|
394
|
+
}
|
|
395
|
+
const pack = (r7, rO, rC) => (r7 * exports.ORIENTATION_COUNT + rO) * exports.CENTER_COUNT + rC;
|
|
396
|
+
const pairSets = new Map();
|
|
397
|
+
const seen = new Set([0]);
|
|
398
|
+
const queue = [0];
|
|
399
|
+
let head = 0;
|
|
400
|
+
while (head < queue.length) {
|
|
401
|
+
const cur = queue[head++];
|
|
402
|
+
const rC = cur % exports.CENTER_COUNT;
|
|
403
|
+
const rest = Math.floor(cur / exports.CENTER_COUNT);
|
|
404
|
+
const rO = rest % exports.ORIENTATION_COUNT;
|
|
405
|
+
const r7 = Math.floor(rest / exports.ORIENTATION_COUNT);
|
|
406
|
+
const pairKey = rC * exports.ORIENTATION_COUNT + rO;
|
|
407
|
+
let set = pairSets.get(pairKey);
|
|
408
|
+
if (!set) {
|
|
409
|
+
set = new Set();
|
|
410
|
+
pairSets.set(pairKey, set);
|
|
411
|
+
}
|
|
412
|
+
set.add(r7);
|
|
413
|
+
for (let m = 0; m < exports.MOVE_COUNT; m++) {
|
|
414
|
+
const n7 = permTable[r7 * exports.MOVE_COUNT + m];
|
|
415
|
+
const nO = orientTable[rO * exports.MOVE_COUNT + m];
|
|
416
|
+
const nC = centersTable[rC * exports.MOVE_COUNT + m];
|
|
417
|
+
const next = pack(n7, nO, nC);
|
|
418
|
+
if (!seen.has(next)) {
|
|
419
|
+
seen.add(next);
|
|
420
|
+
queue.push(next);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
if (seen.size !== exports.STATE_COUNT) {
|
|
425
|
+
throw new Error(`Reachability BFS covered ${seen.size} states, expected ${exports.STATE_COUNT}.`);
|
|
426
|
+
}
|
|
427
|
+
const setKey = (values) => [...values].sort((a, b) => a - b).join(",");
|
|
428
|
+
// Reference partition from rC = 0 (verified identical for all even rC).
|
|
429
|
+
const orientClass = new Uint8Array(exports.ORIENTATION_COUNT);
|
|
430
|
+
const classOfSet = new Map();
|
|
431
|
+
for (let rO = 0; rO < exports.ORIENTATION_COUNT; rO++) {
|
|
432
|
+
const set = pairSets.get(rO);
|
|
433
|
+
const key = setKey(set);
|
|
434
|
+
if (!classOfSet.has(key)) {
|
|
435
|
+
classOfSet.set(key, classOfSet.size);
|
|
436
|
+
}
|
|
437
|
+
orientClass[rO] = classOfSet.get(key);
|
|
438
|
+
}
|
|
439
|
+
if (classOfSet.size !== exports.ORIENT_CLASS_COUNT) {
|
|
440
|
+
throw new Error(`Expected ${exports.ORIENT_CLASS_COUNT} orientation classes, found ${classOfSet.size}.`);
|
|
441
|
+
}
|
|
442
|
+
// Verify the partition is identical for every even rC and that each
|
|
443
|
+
// (rC, class) has exactly PER_SET_SIZE corner permutations.
|
|
444
|
+
const r7Sets = new Uint16Array(exports.EVEN_CENTER_COUNT * exports.ORIENT_CLASS_COUNT * exports.PER_SET_SIZE);
|
|
445
|
+
for (let rC = 0; rC < exports.CENTER_COUNT; rC++) {
|
|
446
|
+
const rCCompact = evenCenterRank[rC];
|
|
447
|
+
if (rCCompact === 65535)
|
|
448
|
+
continue;
|
|
449
|
+
for (let rO = 0; rO < exports.ORIENTATION_COUNT; rO++) {
|
|
450
|
+
const set = pairSets.get(rC * exports.ORIENTATION_COUNT + rO);
|
|
451
|
+
if (set.size !== exports.PER_SET_SIZE) {
|
|
452
|
+
throw new Error(`Expected ${exports.PER_SET_SIZE} permutations for (rC=${rC}, rO=${rO}), found ${set.size}.`);
|
|
453
|
+
}
|
|
454
|
+
const cls = orientClass[rO];
|
|
455
|
+
if (classOfSet.get(setKey(set)) !== cls) {
|
|
456
|
+
throw new Error(`Orientation class partition differs at rC=${rC}, rO=${rO}.`);
|
|
457
|
+
}
|
|
458
|
+
const stored = [...set].sort((a, b) => a - b);
|
|
459
|
+
const base = (rCCompact * exports.ORIENT_CLASS_COUNT + cls) * exports.PER_SET_SIZE;
|
|
460
|
+
for (let i = 0; i < exports.PER_SET_SIZE; i++)
|
|
461
|
+
r7Sets[base + i] = stored[i];
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
return { evenCenterRank, orientClass, r7Sets, evenCenterFull };
|
|
465
|
+
}
|
|
466
|
+
// BFS from solved stores, for each state, the inverse of the move that first
|
|
467
|
+
// discovered it: an optimal move toward solved. Packed 3 bits per state
|
|
468
|
+
// (8 moves; the solved entry is never read, the solve loop exits first).
|
|
469
|
+
function generatePolicyTable(permTable, orientTable, centersTable, helpers) {
|
|
470
|
+
// Install the compact-rank tables for the duration of the build.
|
|
471
|
+
const saved = { evenCenterRank: exports.EVEN_CENTER_RANK, orientClass: exports.ORIENT_CLASS, r7Sets: exports.R7_SETS };
|
|
472
|
+
exports.EVEN_CENTER_RANK = helpers.evenCenterRank;
|
|
473
|
+
exports.ORIENT_CLASS = helpers.orientClass;
|
|
474
|
+
exports.R7_SETS = helpers.r7Sets;
|
|
475
|
+
try {
|
|
476
|
+
const solvedRank = rankFromComponents(0, 0, 0);
|
|
477
|
+
const distances = new Uint8Array(exports.STATE_COUNT).fill(255);
|
|
478
|
+
const policy = new Uint8Array(exports.STATE_COUNT);
|
|
479
|
+
distances[solvedRank] = 0;
|
|
480
|
+
const queue = new Uint32Array(exports.STATE_COUNT);
|
|
481
|
+
let read = 0;
|
|
482
|
+
let write = 1;
|
|
483
|
+
queue[0] = solvedRank;
|
|
484
|
+
let maxDepth = 0;
|
|
485
|
+
while (read < write) {
|
|
486
|
+
const curr = queue[read++];
|
|
487
|
+
const dist = distances[curr];
|
|
488
|
+
const even = Math.floor(curr / (exports.ORIENTATION_COUNT * exports.PER_SET_SIZE));
|
|
489
|
+
const rest = curr % (exports.ORIENTATION_COUNT * exports.PER_SET_SIZE);
|
|
490
|
+
const rO = Math.floor(rest / exports.PER_SET_SIZE);
|
|
491
|
+
const k = rest % exports.PER_SET_SIZE;
|
|
492
|
+
const cls = exports.ORIENT_CLASS[rO];
|
|
493
|
+
const r7 = exports.R7_SETS[(even * exports.ORIENT_CLASS_COUNT + cls) * exports.PER_SET_SIZE + k];
|
|
494
|
+
const rC = helpers.evenCenterFull[even];
|
|
495
|
+
const nextDist = dist + 1;
|
|
496
|
+
for (let m = 0; m < exports.MOVE_COUNT; m++) {
|
|
497
|
+
const n7 = permTable[r7 * exports.MOVE_COUNT + m];
|
|
498
|
+
const nO = orientTable[rO * exports.MOVE_COUNT + m];
|
|
499
|
+
const nC = centersTable[rC * exports.MOVE_COUNT + m];
|
|
500
|
+
const next = rankFromComponents(n7, nO, nC);
|
|
501
|
+
if (distances[next] === 255) {
|
|
502
|
+
distances[next] = nextDist;
|
|
503
|
+
policy[next] = m ^ 1; // inverse move of the discovery edge
|
|
504
|
+
if (nextDist > maxDepth)
|
|
505
|
+
maxDepth = nextDist;
|
|
506
|
+
queue[write++] = next;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
if (write !== exports.STATE_COUNT) {
|
|
511
|
+
throw new Error(`Policy BFS covered ${write} states, expected ${exports.STATE_COUNT}.`);
|
|
512
|
+
}
|
|
513
|
+
const packed = new Uint8Array(exports.POLICY_PACKED_ENTRIES);
|
|
514
|
+
for (let i = 0; i < exports.STATE_COUNT; i++) {
|
|
515
|
+
const bits = i * 3;
|
|
516
|
+
const byteIndex = bits >> 3;
|
|
517
|
+
const offset = bits & 7;
|
|
518
|
+
const value = policy[i] & 0x07;
|
|
519
|
+
packed[byteIndex] |= (value << offset) & 0xff;
|
|
520
|
+
if (offset > 5) {
|
|
521
|
+
packed[byteIndex + 1] |= value >> (8 - offset);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
return { packed, maxDepth };
|
|
525
|
+
}
|
|
526
|
+
finally {
|
|
527
|
+
exports.EVEN_CENTER_RANK = saved.evenCenterRank;
|
|
528
|
+
exports.ORIENT_CLASS = saved.orientClass;
|
|
529
|
+
exports.R7_SETS = saved.r7Sets;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
function checksum(values) {
|
|
533
|
+
let hash = 0x811c9dc5;
|
|
534
|
+
for (let i = 0; i < values.length; i++) {
|
|
535
|
+
hash ^= values[i];
|
|
536
|
+
hash = Math.imul(hash, 0x01000193);
|
|
537
|
+
}
|
|
538
|
+
return hash >>> 0;
|
|
539
|
+
}
|
package/dist-cjs/random/prng.js
CHANGED
|
@@ -9,8 +9,17 @@ function createRandomSource(seed) {
|
|
|
9
9
|
return new SeededRandomSource(hashSeed(seed));
|
|
10
10
|
}
|
|
11
11
|
class CryptoRandomSource {
|
|
12
|
+
buffer = null;
|
|
13
|
+
offset = 0;
|
|
12
14
|
next() {
|
|
13
|
-
|
|
15
|
+
// Batch the crypto calls: one randomBytes per move is ~2 orders of
|
|
16
|
+
// magnitude slower in Node (syscall per call). Refill lazily.
|
|
17
|
+
if (this.buffer === null || this.offset + 6 > 256) {
|
|
18
|
+
this.buffer = (0, node_crypto_1.randomBytes)(256);
|
|
19
|
+
this.offset = 0;
|
|
20
|
+
}
|
|
21
|
+
const value = this.buffer.readUIntBE(this.offset, 6);
|
|
22
|
+
this.offset += 6;
|
|
14
23
|
return value / 0x1000000000000;
|
|
15
24
|
}
|
|
16
25
|
nextInt(maxExclusive) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cubingcenter/scrambler",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Extensible scramble generation toolkit for CubingCenter projects.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"test": "node --test --import tsx test/*.test.ts",
|
|
35
35
|
"lint": "tsc -p tsconfig.json --noEmit",
|
|
36
36
|
"package:check": "pnpm build && node scripts/check-package.cjs",
|
|
37
|
-
"verify": "pnpm lint && pnpm test && pnpm build && pnpm cube3:tables:check && pnpm cube2:tables:check && pnpm pyraminx:tables:check && pnpm package:check",
|
|
37
|
+
"verify": "pnpm lint && pnpm test && pnpm build && pnpm cube3:tables:check && pnpm cube2:tables:check && pnpm pyraminx:tables:check && pnpm skewb:tables:check && pnpm package:check",
|
|
38
38
|
"dev": "tsx src/cli/index.ts",
|
|
39
39
|
"cube3:tables": "node --import tsx scripts/generate-cube3-solver-tables.ts",
|
|
40
40
|
"cube3:tables:check": "node --import tsx scripts/check-cube3-solver-tables.ts",
|
|
@@ -42,6 +42,8 @@
|
|
|
42
42
|
"cube2:tables:check": "node --import tsx scripts/check-cube2-solver-tables.ts",
|
|
43
43
|
"pyraminx:tables": "node --import tsx scripts/generate-pyraminx-solver-tables.ts",
|
|
44
44
|
"pyraminx:tables:check": "node --import tsx scripts/check-pyraminx-solver-tables.ts",
|
|
45
|
+
"skewb:tables": "node --import tsx scripts/generate-skewb-solver-tables.ts",
|
|
46
|
+
"skewb:tables:check": "node --import tsx scripts/check-skewb-solver-tables.ts",
|
|
45
47
|
"tables:web:sync": "node --import tsx scripts/sync-web-tables.ts",
|
|
46
48
|
"verify:scrambles": "node --import tsx scripts/verify-scrambles.ts",
|
|
47
49
|
"verify:pyraminx:exhaustive": "node --import tsx scripts/verify-pyraminx-exhaustive.ts",
|
|
@@ -49,7 +51,8 @@
|
|
|
49
51
|
"bench:cube2": "node --import tsx bench-cube2.ts",
|
|
50
52
|
"bench:cube3": "node --import tsx bench-cube3.ts",
|
|
51
53
|
"bench:skewb": "node --import tsx bench-skewb.ts",
|
|
52
|
-
"bench:pyraminx": "node --import tsx bench-pyraminx.ts"
|
|
54
|
+
"bench:pyraminx": "node --import tsx bench-pyraminx.ts",
|
|
55
|
+
"bench:phases": "node --import tsx bench-phases.ts"
|
|
53
56
|
},
|
|
54
57
|
"devDependencies": {
|
|
55
58
|
"@cubingcenter/viz": "link:../cubingcenter-viz",
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import type { SkewbMove, SkewbState } from "./types.js";
|
|
2
|
-
export declare function getPermutationIndex(perm: readonly number[]): number;
|
|
3
|
-
export declare function getPermutationFromIndex(index: number, n: number): number[];
|
|
4
|
-
export declare function getOrientationIndex(orient: readonly number[]): number;
|
|
5
|
-
export declare function getOrientationFromIndex(index: number): number[];
|
|
6
|
-
export declare function getCenterIndex(centers: readonly number[]): number;
|
|
7
|
-
export declare function getCenterFromIndex(index: number): number[];
|
|
8
|
-
export declare function getStateIndex(state: SkewbState): number;
|
|
9
|
-
export declare function getStateFromIndex(index: number): SkewbState;
|
|
10
|
-
export declare function solveSkewb(state: SkewbState): SkewbMove[];
|
|
11
|
-
//# sourceMappingURL=solver.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"solver.d.ts","sourceRoot":"","sources":["../../../src/puzzles/skewb/solver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAMxD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAWnE;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAY1E;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAOrE;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAS/D;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CASjE;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAE1D;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAOvD;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAY3D;AAID,wBAAgB,UAAU,CAAC,KAAK,EAAE,UAAU,GAAG,SAAS,EAAE,CA+CzD"}
|