@cubingcenter/scrambler 0.5.0 → 0.5.2
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/dist/puzzles/cube2/facelets.d.ts.map +1 -1
- package/dist/puzzles/cube2/facelets.js +43 -22
- package/dist/puzzles/cube2/random-state.d.ts.map +1 -1
- package/dist/puzzles/cube2/random-state.js +44 -34
- package/dist/puzzles/cube2/scrambler.d.ts.map +1 -1
- package/dist/puzzles/cube2/scrambler.js +24 -10
- package/dist/puzzles/cube2/solver/index.d.ts +2 -1
- package/dist/puzzles/cube2/solver/index.d.ts.map +1 -1
- package/dist/puzzles/cube2/solver/index.js +2 -1
- package/dist/puzzles/cube2/solver/search.d.ts +3 -0
- package/dist/puzzles/cube2/solver/search.d.ts.map +1 -1
- package/dist/puzzles/cube2/solver/search.js +42 -7
- package/dist/puzzles/cube2/solver/tables.d.ts +1 -0
- package/dist/puzzles/cube2/solver/tables.d.ts.map +1 -1
- package/dist/puzzles/cube2/solver/tables.js +3 -0
- package/dist/puzzles/cube2/validation.d.ts +1 -0
- package/dist/puzzles/cube2/validation.d.ts.map +1 -1
- package/dist/puzzles/cube2/validation.js +7 -1
- package/dist/puzzles/pyraminx/facelets.d.ts.map +1 -1
- package/dist/puzzles/pyraminx/facelets.js +43 -17
- package/dist/puzzles/pyraminx/random-state.d.ts.map +1 -1
- package/dist/puzzles/pyraminx/random-state.js +63 -29
- package/dist/puzzles/pyraminx/scrambler.d.ts.map +1 -1
- package/dist/puzzles/pyraminx/scrambler.js +35 -26
- package/dist/puzzles/pyraminx/solver/search.d.ts +3 -0
- package/dist/puzzles/pyraminx/solver/search.d.ts.map +1 -1
- package/dist/puzzles/pyraminx/solver/search.js +60 -5
- package/dist/puzzles/pyraminx/solver/tables.d.ts +1 -0
- package/dist/puzzles/pyraminx/solver/tables.d.ts.map +1 -1
- package/dist/puzzles/pyraminx/solver/tables.js +3 -0
- package/dist/puzzles/skewb/scrambler.d.ts.map +1 -1
- package/dist/puzzles/skewb/scrambler.js +21 -7
- package/dist/puzzles/skewb/solver/search.d.ts +3 -0
- package/dist/puzzles/skewb/solver/search.d.ts.map +1 -1
- package/dist/puzzles/skewb/solver/search.js +43 -3
- package/dist/puzzles/skewb/solver/tables.d.ts +1 -0
- package/dist/puzzles/skewb/solver/tables.d.ts.map +1 -1
- package/dist/puzzles/skewb/solver/tables.js +3 -0
- package/dist/random/prng.d.ts.map +1 -1
- package/dist/random/prng.js +18 -4
- package/dist-cjs/puzzles/cube2/facelets.js +43 -22
- package/dist-cjs/puzzles/cube2/random-state.js +43 -33
- package/dist-cjs/puzzles/cube2/scrambler.js +22 -8
- package/dist-cjs/puzzles/cube2/solver/index.js +18 -1
- package/dist-cjs/puzzles/cube2/solver/search.js +45 -7
- package/dist-cjs/puzzles/cube2/solver/tables.js +4 -0
- package/dist-cjs/puzzles/cube2/validation.js +8 -1
- package/dist-cjs/puzzles/pyraminx/facelets.js +43 -17
- package/dist-cjs/puzzles/pyraminx/random-state.js +63 -29
- package/dist-cjs/puzzles/pyraminx/scrambler.js +34 -25
- package/dist-cjs/puzzles/pyraminx/solver/search.js +62 -4
- package/dist-cjs/puzzles/pyraminx/solver/tables.js +4 -0
- package/dist-cjs/puzzles/skewb/scrambler.js +19 -5
- package/dist-cjs/puzzles/skewb/solver/search.js +46 -3
- package/dist-cjs/puzzles/skewb/solver/tables.js +4 -0
- package/dist-cjs/random/prng.js +18 -4
- package/package.json +1 -1
|
@@ -3,12 +3,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.pyraminxScrambleGenerator = void 0;
|
|
4
4
|
exports.generatePyraminxScramble = generatePyraminxScramble;
|
|
5
5
|
const random_state_js_1 = require("./random-state.js");
|
|
6
|
-
const moves_js_1 = require("./moves.js");
|
|
7
6
|
const index_js_1 = require("./solver/index.js");
|
|
8
7
|
const facelets_js_1 = require("./facelets.js");
|
|
9
8
|
const types_js_1 = require("./types.js");
|
|
10
|
-
|
|
11
|
-
const FACE_TIP_INDEX = { U: 0, L: 1, R: 2, B: 3 };
|
|
9
|
+
const TIP_INVERSE = ["u'", "l'", "r'", "b'"];
|
|
12
10
|
exports.pyraminxScrambleGenerator = {
|
|
13
11
|
info: {
|
|
14
12
|
eventId: "pymx",
|
|
@@ -24,47 +22,58 @@ exports.pyraminxScrambleGenerator = {
|
|
|
24
22
|
};
|
|
25
23
|
async function generatePyraminxScramble(options = {}) {
|
|
26
24
|
const state = (0, random_state_js_1.generateRandomPyraminxState)(options);
|
|
27
|
-
// Solve the core edges and centers
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
25
|
+
// Solve the core edges and centers as move indices (0..7). For index m the
|
|
26
|
+
// inverse is m ^ 1, the face is m >> 1 (0=U, 1=L, 2=R, 3=B) and the
|
|
27
|
+
// direction is m & 1 (1 = clockwise, 0 = counter-clockwise).
|
|
28
|
+
const solution = (0, index_js_1.arePyraminxTablesLoaded)()
|
|
29
|
+
? (0, index_js_1.solvePyraminxIndicesSync)(state)
|
|
30
|
+
: await (0, index_js_1.solvePyraminxIndices)(state);
|
|
31
|
+
const depth = solution.length;
|
|
32
|
+
// Single pass over the solution (reversed): build the scramble move strings
|
|
33
|
+
// while accumulating the tips rotated by the layer moves arithmetically
|
|
34
|
+
// (no state replay, no string parsing).
|
|
35
|
+
const moves = new Array(depth + 4);
|
|
33
36
|
const coreTips = [0, 0, 0, 0];
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
37
|
+
let count = 0;
|
|
38
|
+
for (let index = depth - 1; index >= 0; index -= 1) {
|
|
39
|
+
const m = solution[index] ^ 1;
|
|
40
|
+
moves[count++] = index_js_1.PYRAMINX_MOVES[m];
|
|
41
|
+
const face = m >> 1;
|
|
42
|
+
coreTips[face] = (coreTips[face] + (m & 1 ? 2 : 1)) % 3;
|
|
41
43
|
}
|
|
42
|
-
const
|
|
43
|
-
for (let i = 0; i < 4; i
|
|
44
|
+
const tipMoves = [];
|
|
45
|
+
for (let i = 0; i < 4; i += 1) {
|
|
44
46
|
const delta = (state.tips[i] - coreTips[i] + 3) % 3;
|
|
45
47
|
if (delta === 1) {
|
|
46
|
-
|
|
48
|
+
moves[count++] = types_js_1.PYRAMINX_TIPS[i];
|
|
49
|
+
tipMoves.push(i * 2);
|
|
47
50
|
}
|
|
48
51
|
else if (delta === 2) {
|
|
49
|
-
|
|
52
|
+
moves[count++] = TIP_INVERSE[i];
|
|
53
|
+
tipMoves.push(i * 2 + 1);
|
|
50
54
|
}
|
|
51
55
|
}
|
|
52
|
-
|
|
56
|
+
moves.length = count;
|
|
57
|
+
if (!options.skipValidation && !(0, index_js_1.verifyPyraminxScrambleIndices)(state, solution, tipMoves)) {
|
|
53
58
|
throw new Error("Generated Pyraminx scramble does not reproduce the target random state.");
|
|
54
59
|
}
|
|
55
60
|
const facelets = (0, facelets_js_1.toPyraminxFacelets)(state);
|
|
61
|
+
const solutionMoves = new Array(depth);
|
|
62
|
+
for (let index = 0; index < depth; index += 1) {
|
|
63
|
+
solutionMoves[index] = index_js_1.PYRAMINX_MOVES[solution[index]];
|
|
64
|
+
}
|
|
56
65
|
return {
|
|
57
66
|
eventId: "pymx",
|
|
58
|
-
scramble:
|
|
59
|
-
moves
|
|
67
|
+
scramble: moves.join(" "),
|
|
68
|
+
moves,
|
|
60
69
|
quality: "random-state",
|
|
61
70
|
generator: "pyraminx-optimal",
|
|
62
71
|
metadata: {
|
|
63
72
|
state,
|
|
64
73
|
facelets,
|
|
65
74
|
faceletString: (0, facelets_js_1.formatPyraminxFacelets)(facelets),
|
|
66
|
-
solutionMoves
|
|
67
|
-
solutionDepth:
|
|
75
|
+
solutionMoves,
|
|
76
|
+
solutionDepth: depth
|
|
68
77
|
}
|
|
69
78
|
};
|
|
70
79
|
}
|
|
@@ -2,8 +2,11 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.solvePyraminx = solvePyraminx;
|
|
4
4
|
exports.solvePyraminxWithInfo = solvePyraminxWithInfo;
|
|
5
|
+
exports.solvePyraminxIndices = solvePyraminxIndices;
|
|
6
|
+
exports.solvePyraminxIndicesSync = solvePyraminxIndicesSync;
|
|
5
7
|
exports.solvePyraminxSync = solvePyraminxSync;
|
|
6
8
|
exports.verifyPyraminxScramble = verifyPyraminxScramble;
|
|
9
|
+
exports.verifyPyraminxScrambleIndices = verifyPyraminxScrambleIndices;
|
|
7
10
|
const tables_js_1 = require("./tables.js");
|
|
8
11
|
async function solvePyraminx(state) {
|
|
9
12
|
await (0, tables_js_1.ensureTablesLoaded)();
|
|
@@ -13,7 +16,25 @@ async function solvePyraminxWithInfo(state) {
|
|
|
13
16
|
await (0, tables_js_1.ensureTablesLoaded)();
|
|
14
17
|
return solveWithTables(state);
|
|
15
18
|
}
|
|
19
|
+
// Index-based solve for the hot scramble path: returns the solution as move
|
|
20
|
+
// indices (0..7) in walk order. The inverse of index m is m ^ 1 (moves are
|
|
21
|
+
// paired U/U', L/L', R/R', B/B'), its face is m >> 1 and its direction is
|
|
22
|
+
// m & 1 (1 = clockwise, 0 = counter-clockwise).
|
|
23
|
+
async function solvePyraminxIndices(state) {
|
|
24
|
+
await (0, tables_js_1.ensureTablesLoaded)();
|
|
25
|
+
return solveWithTablesIndices(state);
|
|
26
|
+
}
|
|
27
|
+
function solvePyraminxIndicesSync(state) {
|
|
28
|
+
if (!tables_js_1.POLICY_TABLE)
|
|
29
|
+
throw new Error("Pyraminx tables not loaded");
|
|
30
|
+
return solveWithTablesIndices(state);
|
|
31
|
+
}
|
|
16
32
|
function solveWithTables(state) {
|
|
33
|
+
const indices = solveWithTablesIndices(state);
|
|
34
|
+
const moves = indices.map((index) => tables_js_1.PYRAMINX_MOVES[index]);
|
|
35
|
+
return { moves, depth: moves.length };
|
|
36
|
+
}
|
|
37
|
+
function solveWithTablesIndices(state) {
|
|
17
38
|
const centersTable = tables_js_1.CENTERS_TABLE;
|
|
18
39
|
const permTable = tables_js_1.PERMUTATION_TABLE;
|
|
19
40
|
const orientTable = tables_js_1.ORIENTATION_TABLE;
|
|
@@ -23,17 +44,20 @@ function solveWithTables(state) {
|
|
|
23
44
|
let o = (0, tables_js_1.getOrientationIndex)(state.edgesOrient);
|
|
24
45
|
let stateIndex = c + 81 * (p + tables_js_1.PERMUTATION_COUNT * o);
|
|
25
46
|
if (stateIndex === 0)
|
|
26
|
-
return
|
|
47
|
+
return [];
|
|
27
48
|
const moves = [];
|
|
28
49
|
while (stateIndex !== 0) {
|
|
29
|
-
const
|
|
30
|
-
|
|
50
|
+
const bits = stateIndex * 3;
|
|
51
|
+
const byteIndex = bits >> 3;
|
|
52
|
+
const offset = bits & 7;
|
|
53
|
+
const m = ((policy[byteIndex] | ((policy[byteIndex + 1] ?? 0) << 8)) >> offset) & 0x07;
|
|
54
|
+
moves.push(m);
|
|
31
55
|
c = centersTable[c * tables_js_1.MOVE_COUNT + m];
|
|
32
56
|
p = permTable[p * tables_js_1.MOVE_COUNT + m];
|
|
33
57
|
o = orientTable[o * tables_js_1.MOVE_COUNT + m];
|
|
34
58
|
stateIndex = c + 81 * (p + tables_js_1.PERMUTATION_COUNT * o);
|
|
35
59
|
}
|
|
36
|
-
return
|
|
60
|
+
return moves;
|
|
37
61
|
}
|
|
38
62
|
// Synchronous variant for scripts that have already ensured tables are loaded
|
|
39
63
|
function solvePyraminxSync(state) {
|
|
@@ -86,3 +110,37 @@ function verifyPyraminxScramble(state, finalMoves) {
|
|
|
86
110
|
tips[2] === state.tips[2] &&
|
|
87
111
|
tips[3] === state.tips[3]);
|
|
88
112
|
}
|
|
113
|
+
// Numeric verification for the hot scramble path: replays the scramble
|
|
114
|
+
// (solution reversed with m ^ 1 inverses, tip moves as tipIndex * 2 + dir
|
|
115
|
+
// with dir 0 = clockwise, 1 = counter-clockwise) without any string parsing.
|
|
116
|
+
function verifyPyraminxScrambleIndices(state, solution, tipMoves) {
|
|
117
|
+
const centersTable = tables_js_1.CENTERS_TABLE;
|
|
118
|
+
const permTable = tables_js_1.PERMUTATION_TABLE;
|
|
119
|
+
const orientTable = tables_js_1.ORIENTATION_TABLE;
|
|
120
|
+
if (!centersTable || !permTable || !orientTable) {
|
|
121
|
+
throw new Error("Pyraminx solver tables not loaded. Call ensureTablesLoaded() first.");
|
|
122
|
+
}
|
|
123
|
+
let c = 0;
|
|
124
|
+
let p = 0;
|
|
125
|
+
let o = 0;
|
|
126
|
+
const tips = [0, 0, 0, 0];
|
|
127
|
+
for (let index = solution.length - 1; index >= 0; index -= 1) {
|
|
128
|
+
const m = solution[index] ^ 1;
|
|
129
|
+
c = centersTable[c * tables_js_1.MOVE_COUNT + m];
|
|
130
|
+
p = permTable[p * tables_js_1.MOVE_COUNT + m];
|
|
131
|
+
o = orientTable[o * tables_js_1.MOVE_COUNT + m];
|
|
132
|
+
const face = m >> 1;
|
|
133
|
+
tips[face] = (tips[face] + (m & 1 ? 2 : 1)) % 3;
|
|
134
|
+
}
|
|
135
|
+
for (const tipMove of tipMoves) {
|
|
136
|
+
const tipIndex = tipMove >> 1;
|
|
137
|
+
tips[tipIndex] = (tips[tipIndex] + (tipMove & 1 ? 2 : 1)) % 3;
|
|
138
|
+
}
|
|
139
|
+
return (c === (0, tables_js_1.getCentersIndex)(state.centers) &&
|
|
140
|
+
p === (0, tables_js_1.getPermutationCompactIndex)(state.edgesPerm) &&
|
|
141
|
+
o === (0, tables_js_1.getOrientationIndex)(state.edgesOrient) &&
|
|
142
|
+
tips[0] === state.tips[0] &&
|
|
143
|
+
tips[1] === state.tips[1] &&
|
|
144
|
+
tips[2] === state.tips[2] &&
|
|
145
|
+
tips[3] === state.tips[3]);
|
|
146
|
+
}
|
|
@@ -20,6 +20,7 @@ exports.generateOrientationTable = generateOrientationTable;
|
|
|
20
20
|
exports.generatePolicyTable = generatePolicyTable;
|
|
21
21
|
exports.checksum = checksum;
|
|
22
22
|
exports.ensureTablesLoaded = ensureTablesLoaded;
|
|
23
|
+
exports.arePyraminxTablesLoaded = arePyraminxTablesLoaded;
|
|
23
24
|
exports.getPolicyMove = getPolicyMove;
|
|
24
25
|
exports.getCentersTransition = getCentersTransition;
|
|
25
26
|
exports.getPermutationTransition = getPermutationTransition;
|
|
@@ -298,6 +299,9 @@ async function ensureTablesLoaded() {
|
|
|
298
299
|
exports.POLICY_TABLE = tables.policyTable;
|
|
299
300
|
tablesLoaded = true;
|
|
300
301
|
}
|
|
302
|
+
function arePyraminxTablesLoaded() {
|
|
303
|
+
return tablesLoaded;
|
|
304
|
+
}
|
|
301
305
|
function getPolicyMove(stateIndex) {
|
|
302
306
|
if (!exports.POLICY_TABLE)
|
|
303
307
|
throw new Error("Pyraminx solver tables not loaded. Call ensureTablesLoaded() first.");
|
|
@@ -21,18 +21,32 @@ exports.skewbScrambleGenerator = {
|
|
|
21
21
|
};
|
|
22
22
|
async function generateSkewbScramble(options = {}) {
|
|
23
23
|
const state = await (0, random_state_js_1.generateRandomSkewbState)(options);
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
// generateRandomSkewbState guarantees the tables are loaded, so use the
|
|
25
|
+
// sync solve on every call after the first (no extra microtask).
|
|
26
|
+
const solution = (0, index_js_1.areSkewbTablesLoaded)()
|
|
27
|
+
? (0, index_js_1.solveSkewbIndicesSync)(state)
|
|
28
|
+
: await (0, index_js_1.solveSkewbIndices)(state);
|
|
29
|
+
const depth = solution.length;
|
|
30
|
+
// Scramble = inverse of the solution, reversed. For index m the inverse is
|
|
31
|
+
// m ^ 1 (moves are paired R/R', U/U', L/L', B/B').
|
|
32
|
+
const scrambleMoves = new Array(depth);
|
|
33
|
+
for (let index = 0; index < depth; index += 1) {
|
|
34
|
+
scrambleMoves[index] = index_js_1.SKEWB_MOVES[solution[depth - 1 - index] ^ 1];
|
|
35
|
+
}
|
|
26
36
|
if (!options.skipValidation) {
|
|
27
37
|
// If the scramble reproduces the target state, its inverse (the
|
|
28
38
|
// solution) solves it by construction, so a single walk suffices.
|
|
29
|
-
if (!(0, index_js_1.
|
|
39
|
+
if (!(0, index_js_1.verifySkewbScrambleIndices)(state, solution)) {
|
|
30
40
|
throw new Error("Generated Skewb scramble does not reproduce the target random state.");
|
|
31
41
|
}
|
|
32
42
|
if (scrambleMoves.length < 2)
|
|
33
43
|
throw new Error("Generated Skewb scramble is too short.");
|
|
34
44
|
}
|
|
35
45
|
const facelets = (0, facelets_js_1.toSkewbFacelets)(state);
|
|
46
|
+
const solutionMoves = new Array(depth);
|
|
47
|
+
for (let index = 0; index < depth; index += 1) {
|
|
48
|
+
solutionMoves[index] = index_js_1.SKEWB_MOVES[solution[index]];
|
|
49
|
+
}
|
|
36
50
|
const result = {
|
|
37
51
|
eventId: "skwb",
|
|
38
52
|
scramble: (0, moves_js_1.formatSkewbAlgorithm)(scrambleMoves),
|
|
@@ -43,8 +57,8 @@ async function generateSkewbScramble(options = {}) {
|
|
|
43
57
|
state,
|
|
44
58
|
facelets,
|
|
45
59
|
faceletString: (0, facelets_js_1.formatSkewbFacelets)(facelets),
|
|
46
|
-
solutionMoves
|
|
47
|
-
solutionDepth:
|
|
60
|
+
solutionMoves,
|
|
61
|
+
solutionDepth: depth,
|
|
48
62
|
},
|
|
49
63
|
};
|
|
50
64
|
if (options.seed !== undefined)
|
|
@@ -2,8 +2,11 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.solveSkewb = solveSkewb;
|
|
4
4
|
exports.solveSkewbWithInfo = solveSkewbWithInfo;
|
|
5
|
+
exports.solveSkewbIndices = solveSkewbIndices;
|
|
6
|
+
exports.solveSkewbIndicesSync = solveSkewbIndicesSync;
|
|
5
7
|
exports.solveSkewbSync = solveSkewbSync;
|
|
6
8
|
exports.verifySkewbScramble = verifySkewbScramble;
|
|
9
|
+
exports.verifySkewbScrambleIndices = verifySkewbScrambleIndices;
|
|
7
10
|
exports.verifySkewbSolution = verifySkewbSolution;
|
|
8
11
|
const tables_js_1 = require("./tables.js");
|
|
9
12
|
async function solveSkewb(state) {
|
|
@@ -14,6 +17,19 @@ async function solveSkewbWithInfo(state) {
|
|
|
14
17
|
await (0, tables_js_1.ensureTablesLoaded)();
|
|
15
18
|
return solveWithTables(state);
|
|
16
19
|
}
|
|
20
|
+
// Index-based solve for the hot scramble path: returns the solution as move
|
|
21
|
+
// indices (0..7) in walk order. The inverse of index m is m ^ 1 (moves are
|
|
22
|
+
// paired R/R', U/U', L/L', B/B').
|
|
23
|
+
async function solveSkewbIndices(state) {
|
|
24
|
+
await (0, tables_js_1.ensureTablesLoaded)();
|
|
25
|
+
return solveWithTablesIndices(state);
|
|
26
|
+
}
|
|
27
|
+
function solveSkewbIndicesSync(state) {
|
|
28
|
+
if (!tables_js_1.POLICY_TABLE) {
|
|
29
|
+
throw new Error("Skewb solver tables not loaded. Call ensureTablesLoaded() first.");
|
|
30
|
+
}
|
|
31
|
+
return solveWithTablesIndices(state);
|
|
32
|
+
}
|
|
17
33
|
// Synchronous variant for scripts that have already ensured tables are loaded
|
|
18
34
|
function solveSkewbSync(state) {
|
|
19
35
|
if (!tables_js_1.POLICY_TABLE) {
|
|
@@ -22,6 +38,11 @@ function solveSkewbSync(state) {
|
|
|
22
38
|
return solveWithTables(state).moves;
|
|
23
39
|
}
|
|
24
40
|
function solveWithTables(state) {
|
|
41
|
+
const indices = solveWithTablesIndices(state);
|
|
42
|
+
const moves = indices.map((index) => tables_js_1.SKEWB_MOVES[index]);
|
|
43
|
+
return { moves, depth: moves.length };
|
|
44
|
+
}
|
|
45
|
+
function solveWithTablesIndices(state) {
|
|
25
46
|
const policy = tables_js_1.POLICY_TABLE;
|
|
26
47
|
const permTable = tables_js_1.PERMUTATION_TABLE;
|
|
27
48
|
const orientTable = tables_js_1.ORIENTATION_TABLE;
|
|
@@ -35,18 +56,18 @@ function solveWithTables(state) {
|
|
|
35
56
|
let rC = (0, tables_js_1.getCenterIndex)(state.centers);
|
|
36
57
|
let rank = (0, tables_js_1.rankFromComponents)(r7, rO, rC);
|
|
37
58
|
if (rank === tables_js_1.SOLVED_RANK) {
|
|
38
|
-
return
|
|
59
|
+
return [];
|
|
39
60
|
}
|
|
40
61
|
const moves = [];
|
|
41
62
|
while (rank !== tables_js_1.SOLVED_RANK) {
|
|
42
63
|
const m = (0, tables_js_1.getPolicyMoveFromPacked)(policy, rank);
|
|
43
|
-
moves.push(
|
|
64
|
+
moves.push(m);
|
|
44
65
|
r7 = permTable[r7 * tables_js_1.MOVE_COUNT + m];
|
|
45
66
|
rO = orientTable[rO * tables_js_1.MOVE_COUNT + m];
|
|
46
67
|
rC = centersTable[rC * tables_js_1.MOVE_COUNT + m];
|
|
47
68
|
rank = (0, tables_js_1.rankFromComponents)(r7, rO, rC);
|
|
48
69
|
}
|
|
49
|
-
return
|
|
70
|
+
return moves;
|
|
50
71
|
}
|
|
51
72
|
// ---------------------------------------------------------------------------
|
|
52
73
|
// Coordinate-walking verification (no full state clones)
|
|
@@ -77,6 +98,28 @@ function verifySkewbScramble(state, scrambleMoves) {
|
|
|
77
98
|
final.rO === (0, tables_js_1.getOrientationIndex)(state.corners.orientation) &&
|
|
78
99
|
final.rC === (0, tables_js_1.getCenterIndex)(state.centers));
|
|
79
100
|
}
|
|
101
|
+
// Numeric verification for the hot scramble path: replays the scramble (the
|
|
102
|
+
// solution reversed with m ^ 1 inverses) without any string parsing.
|
|
103
|
+
function verifySkewbScrambleIndices(state, solution) {
|
|
104
|
+
const permTable = tables_js_1.PERMUTATION_TABLE;
|
|
105
|
+
const orientTable = tables_js_1.ORIENTATION_TABLE;
|
|
106
|
+
const centersTable = tables_js_1.CENTERS_TABLE;
|
|
107
|
+
if (!permTable || !orientTable || !centersTable) {
|
|
108
|
+
throw new Error("Skewb solver tables not loaded. Call ensureTablesLoaded() first.");
|
|
109
|
+
}
|
|
110
|
+
let r7 = 0;
|
|
111
|
+
let rO = 0;
|
|
112
|
+
let rC = 0;
|
|
113
|
+
for (let index = solution.length - 1; index >= 0; index -= 1) {
|
|
114
|
+
const m = solution[index] ^ 1;
|
|
115
|
+
r7 = permTable[r7 * tables_js_1.MOVE_COUNT + m];
|
|
116
|
+
rO = orientTable[rO * tables_js_1.MOVE_COUNT + m];
|
|
117
|
+
rC = centersTable[rC * tables_js_1.MOVE_COUNT + m];
|
|
118
|
+
}
|
|
119
|
+
return (r7 === (0, tables_js_1.getPerm7Index)(state.corners.permutation) &&
|
|
120
|
+
rO === (0, tables_js_1.getOrientationIndex)(state.corners.orientation) &&
|
|
121
|
+
rC === (0, tables_js_1.getCenterIndex)(state.centers));
|
|
122
|
+
}
|
|
80
123
|
function verifySkewbSolution(state, solutionMoves) {
|
|
81
124
|
const final = walkSkewbCoordinates({
|
|
82
125
|
r7: (0, tables_js_1.getPerm7Index)(state.corners.permutation),
|
|
@@ -12,6 +12,7 @@ exports.getStateFromIndex = getStateFromIndex;
|
|
|
12
12
|
exports.getPerm7Index = getPerm7Index;
|
|
13
13
|
exports.getPerm7FromIndex = getPerm7FromIndex;
|
|
14
14
|
exports.ensureTablesLoaded = ensureTablesLoaded;
|
|
15
|
+
exports.areSkewbTablesLoaded = areSkewbTablesLoaded;
|
|
15
16
|
exports.assignTables = assignTables;
|
|
16
17
|
exports.resetTablesForTests = resetTablesForTests;
|
|
17
18
|
exports.rankFromComponents = rankFromComponents;
|
|
@@ -187,6 +188,9 @@ async function ensureTablesLoaded() {
|
|
|
187
188
|
const tables = await (0, generated_assets_js_1.loadGeneratedSkewbSolverTables)();
|
|
188
189
|
assignTables(tables);
|
|
189
190
|
}
|
|
191
|
+
function areSkewbTablesLoaded() {
|
|
192
|
+
return tablesLoaded;
|
|
193
|
+
}
|
|
190
194
|
function assignTables(tables) {
|
|
191
195
|
exports.PERMUTATION_TABLE = tables.permutationTable;
|
|
192
196
|
exports.ORIENTATION_TABLE = tables.orientationTable;
|
package/dist-cjs/random/prng.js
CHANGED
|
@@ -2,9 +2,16 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.createRandomSource = createRandomSource;
|
|
4
4
|
const node_crypto_1 = require("node:crypto");
|
|
5
|
+
// 4096 bytes per refill (~682 values per syscall).
|
|
6
|
+
const CRYPTO_BUFFER_BYTES = 4096;
|
|
7
|
+
// Shared unseeded source: the crypto refill costs a syscall per buffer, so a
|
|
8
|
+
// per-call source would pay ~one syscall per scramble. Seeded calls stay
|
|
9
|
+
// isolated (deterministic streams must never be shared).
|
|
10
|
+
let sharedCryptoSource = null;
|
|
5
11
|
function createRandomSource(seed) {
|
|
6
12
|
if (seed === undefined) {
|
|
7
|
-
|
|
13
|
+
sharedCryptoSource ??= new CryptoRandomSource();
|
|
14
|
+
return sharedCryptoSource;
|
|
8
15
|
}
|
|
9
16
|
return new SeededRandomSource(hashSeed(seed));
|
|
10
17
|
}
|
|
@@ -14,12 +21,19 @@ class CryptoRandomSource {
|
|
|
14
21
|
next() {
|
|
15
22
|
// Batch the crypto calls: one randomBytes per move is ~2 orders of
|
|
16
23
|
// magnitude slower in Node (syscall per call). Refill lazily.
|
|
17
|
-
if (this.buffer === null || this.offset + 6 >
|
|
18
|
-
this.buffer = (0, node_crypto_1.randomBytes)(
|
|
24
|
+
if (this.buffer === null || this.offset + 6 > CRYPTO_BUFFER_BYTES) {
|
|
25
|
+
this.buffer = (0, node_crypto_1.randomBytes)(CRYPTO_BUFFER_BYTES);
|
|
19
26
|
this.offset = 0;
|
|
20
27
|
}
|
|
21
|
-
const
|
|
28
|
+
const bytes = this.buffer;
|
|
29
|
+
const offset = this.offset;
|
|
22
30
|
this.offset += 6;
|
|
31
|
+
const value = bytes[offset] * 0x10000000000 +
|
|
32
|
+
bytes[offset + 1] * 0x100000000 +
|
|
33
|
+
bytes[offset + 2] * 0x1000000 +
|
|
34
|
+
bytes[offset + 3] * 0x10000 +
|
|
35
|
+
bytes[offset + 4] * 0x100 +
|
|
36
|
+
bytes[offset + 5];
|
|
23
37
|
return value / 0x1000000000000;
|
|
24
38
|
}
|
|
25
39
|
nextInt(maxExclusive) {
|