@cubingcenter/scrambler 0.5.1 → 0.5.3
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 +38 -34
- package/dist/puzzles/cube2/scrambler.d.ts.map +1 -1
- package/dist/puzzles/cube2/scrambler.js +24 -10
- package/dist/puzzles/cube2/solver/browser-loader.d.ts +2 -2
- package/dist/puzzles/cube2/solver/browser-loader.d.ts.map +1 -1
- package/dist/puzzles/cube2/solver/browser-loader.js +2 -2
- package/dist/puzzles/cube2/solver/generated/manifest.json +3 -3
- package/dist/puzzles/cube2/solver/generated/policy.bin +0 -0
- package/dist/puzzles/cube2/solver/generated-assets.d.ts +1 -1
- package/dist/puzzles/cube2/solver/generated-assets.d.ts.map +1 -1
- 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/node-loader.d.ts +1 -1
- package/dist/puzzles/cube2/solver/node-loader.d.ts.map +1 -1
- package/dist/puzzles/cube2/solver/node-loader.js +2 -2
- 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 +51 -36
- package/dist/puzzles/cube2/solver/table-metadata.d.ts +2 -2
- package/dist/puzzles/cube2/solver/table-metadata.js +2 -2
- package/dist/puzzles/cube2/solver/tables.d.ts +2 -1
- package/dist/puzzles/cube2/solver/tables.d.ts.map +1 -1
- package/dist/puzzles/cube2/solver/tables.js +39 -13
- package/dist/puzzles/cube2/state.d.ts.map +1 -1
- package/dist/puzzles/cube2/state.js +3 -1
- package/dist/puzzles/cube2/validation.d.ts +2 -0
- package/dist/puzzles/cube2/validation.d.ts.map +1 -1
- package/dist/puzzles/cube2/validation.js +45 -8
- package/dist/puzzles/cube3/facelets.d.ts.map +1 -1
- package/dist/puzzles/cube3/facelets.js +53 -28
- package/dist/puzzles/cube3/random-state.d.ts.map +1 -1
- package/dist/puzzles/cube3/random-state.js +9 -2
- package/dist/puzzles/cube3/scrambler.js +1 -1
- package/dist/puzzles/cube3/state.d.ts.map +1 -1
- package/dist/puzzles/cube3/state.js +4 -1
- package/dist/puzzles/cube3/validation.d.ts.map +1 -1
- package/dist/puzzles/cube3/validation.js +5 -2
- package/dist/puzzles/skewb/random-state.d.ts +1 -0
- package/dist/puzzles/skewb/random-state.d.ts.map +1 -1
- package/dist/puzzles/skewb/random-state.js +9 -1
- package/dist/puzzles/skewb/scrambler.d.ts.map +1 -1
- package/dist/puzzles/skewb/scrambler.js +6 -2
- 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 +11 -5
- package/dist-cjs/puzzles/cube2/facelets.js +43 -22
- package/dist-cjs/puzzles/cube2/random-state.js +37 -33
- package/dist-cjs/puzzles/cube2/scrambler.js +22 -8
- package/dist-cjs/puzzles/cube2/solver/browser-loader.js +2 -2
- package/dist-cjs/puzzles/cube2/solver/generated/manifest.json +3 -3
- package/dist-cjs/puzzles/cube2/solver/generated/policy.bin +0 -0
- package/dist-cjs/puzzles/cube2/solver/index.js +18 -1
- package/dist-cjs/puzzles/cube2/solver/node-loader.js +2 -2
- package/dist-cjs/puzzles/cube2/solver/search.js +53 -35
- package/dist-cjs/puzzles/cube2/solver/table-metadata.js +2 -2
- package/dist-cjs/puzzles/cube2/solver/tables.js +41 -14
- package/dist-cjs/puzzles/cube2/state.js +3 -1
- package/dist-cjs/puzzles/cube2/validation.js +47 -8
- package/dist-cjs/puzzles/cube3/facelets.js +53 -28
- package/dist-cjs/puzzles/cube3/random-state.js +8 -1
- package/dist-cjs/puzzles/cube3/scrambler.js +1 -1
- package/dist-cjs/puzzles/cube3/state.js +4 -1
- package/dist-cjs/puzzles/cube3/validation.js +5 -2
- package/dist-cjs/puzzles/skewb/random-state.js +9 -0
- package/dist-cjs/puzzles/skewb/scrambler.js +5 -1
- package/dist-cjs/puzzles/skewb/solver/tables.js +12 -6
- package/package.json +1 -1
- package/dist/puzzles/cube2/solver/generated/distances.bin +0 -1
- package/dist/puzzles/cube3/solver/browser-worker-pool.d.ts +0 -31
- package/dist/puzzles/cube3/solver/browser-worker-pool.d.ts.map +0 -1
- package/dist/puzzles/cube3/solver/browser-worker-pool.js +0 -118
- package/dist/puzzles/cube3/solver/browser-worker.d.ts +0 -2
- package/dist/puzzles/cube3/solver/browser-worker.d.ts.map +0 -1
- package/dist/puzzles/cube3/solver/browser-worker.js +0 -71
- package/dist/puzzles/cube3/solver/worker-pool.d.ts +0 -18
- package/dist/puzzles/cube3/solver/worker-pool.d.ts.map +0 -1
- package/dist/puzzles/cube3/solver/worker-pool.js +0 -116
- package/dist/puzzles/cube3/solver/worker.d.ts +0 -2
- package/dist/puzzles/cube3/solver/worker.d.ts.map +0 -1
- package/dist/puzzles/cube3/solver/worker.js +0 -73
- package/dist-cjs/puzzles/cube2/solver/generated/distances.bin +0 -1
|
@@ -6,6 +6,9 @@ const moves_js_1 = require("./moves.js");
|
|
|
6
6
|
const random_state_js_1 = require("./random-state.js");
|
|
7
7
|
const facelets_js_1 = require("./facelets.js");
|
|
8
8
|
const index_js_1 = require("./solver/index.js");
|
|
9
|
+
// BFS_MOVES = [U, U2, U', R, R2, R', F, F2, F'] — the inverse of index m is
|
|
10
|
+
// CUBE2_MOVE_INVERSE[m] (U2/R2/F2 are self-inverse, so m ^ 1 does not apply).
|
|
11
|
+
const CUBE2_MOVE_INVERSE = [2, 1, 0, 5, 4, 3, 8, 7, 6];
|
|
9
12
|
exports.cube2ScrambleGenerator = {
|
|
10
13
|
info: {
|
|
11
14
|
eventId: "222",
|
|
@@ -21,20 +24,31 @@ exports.cube2ScrambleGenerator = {
|
|
|
21
24
|
};
|
|
22
25
|
async function generateCube2Scramble(options = {}) {
|
|
23
26
|
const state = (0, random_state_js_1.generateRandomCube2State)(options);
|
|
24
|
-
const solution =
|
|
25
|
-
|
|
27
|
+
const solution = (0, index_js_1.areCube2TablesLoaded)()
|
|
28
|
+
? (0, index_js_1.solveCube2StateIndicesSync)(state)
|
|
29
|
+
: await (0, index_js_1.solveCube2StateIndices)(state);
|
|
30
|
+
const depth = solution.length;
|
|
31
|
+
// Scramble = inverse of the solution, reversed.
|
|
32
|
+
const scrambleMoves = new Array(depth);
|
|
33
|
+
for (let index = 0; index < depth; index += 1) {
|
|
34
|
+
scrambleMoves[index] = index_js_1.BFS_MOVES[CUBE2_MOVE_INVERSE[solution[depth - 1 - index]]];
|
|
35
|
+
}
|
|
26
36
|
if (!options.skipValidation) {
|
|
27
|
-
|
|
37
|
+
// The scramble is the exact inverse of the solution, so verifying that
|
|
38
|
+
// the solution solves the target state is equivalent to verifying that
|
|
39
|
+
// the scramble reproduces it — a single numeric walk suffices.
|
|
40
|
+
if (!(0, index_js_1.verifyCube2SolutionIndices)(state, solution)) {
|
|
28
41
|
throw new Error("Generated 2x2 scramble does not reproduce the target random state.");
|
|
29
42
|
}
|
|
30
|
-
if (!(0, index_js_1.verifyCube2Solution)(state, solution.moves)) {
|
|
31
|
-
throw new Error("Generated 2x2 solution does not solve the target random state.");
|
|
32
|
-
}
|
|
33
43
|
if (scrambleMoves.length < 2) {
|
|
34
44
|
throw new Error("Generated 2x2 scramble is too short.");
|
|
35
45
|
}
|
|
36
46
|
}
|
|
37
47
|
const facelets = (0, facelets_js_1.toCube2Facelets)(state);
|
|
48
|
+
const solutionMoves = new Array(depth);
|
|
49
|
+
for (let index = 0; index < depth; index += 1) {
|
|
50
|
+
solutionMoves[index] = index_js_1.BFS_MOVES[solution[index]];
|
|
51
|
+
}
|
|
38
52
|
const result = {
|
|
39
53
|
eventId: "222",
|
|
40
54
|
scramble: (0, moves_js_1.formatCube2Algorithm)(scrambleMoves),
|
|
@@ -45,8 +59,8 @@ async function generateCube2Scramble(options = {}) {
|
|
|
45
59
|
state,
|
|
46
60
|
facelets,
|
|
47
61
|
faceletString: (0, facelets_js_1.formatCube2Facelets)(facelets),
|
|
48
|
-
solutionMoves
|
|
49
|
-
solutionDepth:
|
|
62
|
+
solutionMoves,
|
|
63
|
+
solutionDepth: depth
|
|
50
64
|
}
|
|
51
65
|
};
|
|
52
66
|
if (options.seed !== undefined) {
|
|
@@ -22,12 +22,12 @@ async function loadBrowserTables(baseUrl) {
|
|
|
22
22
|
const [permTable, orientTable, dist] = await Promise.all([
|
|
23
23
|
fetchUint16Table(resolved, manifest.files.permutationTable, table_metadata_js_1.CUBE2_SOLVER_TABLE_METADATA.permutationTable.entries),
|
|
24
24
|
fetchUint16Table(resolved, manifest.files.orientationTable, table_metadata_js_1.CUBE2_SOLVER_TABLE_METADATA.orientationTable.entries),
|
|
25
|
-
fetchUint8Table(resolved, manifest.files.
|
|
25
|
+
fetchUint8Table(resolved, manifest.files.policy, table_metadata_js_1.CUBE2_SOLVER_TABLE_METADATA.policy.entries)
|
|
26
26
|
]);
|
|
27
27
|
cachedBrowserTables = {
|
|
28
28
|
permutationTable: permTable,
|
|
29
29
|
orientationTable: orientTable,
|
|
30
|
-
|
|
30
|
+
policyTable: dist
|
|
31
31
|
};
|
|
32
32
|
return cachedBrowserTables;
|
|
33
33
|
}
|
|
Binary file
|
|
@@ -1,7 +1,24 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.verifyCube2Solution = exports.verifyCube2Scramble = exports.solveCube2State = void 0;
|
|
3
|
+
exports.PERMUTATION_TABLE = exports.ORIENTATION_TABLE = exports.ORIENTATION_COUNT = exports.MOVE_COUNT = exports.getOrientationTransition = exports.getPermutationTransition = exports.getDistance = exports.encodeOrientation = exports.encodePermutation = exports.ensureTablesLoaded = exports.POLICY_TABLE = exports.BFS_MOVES = exports.areCube2TablesLoaded = exports.verifyCube2SolutionIndices = exports.verifyCube2Solution = exports.verifyCube2Scramble = exports.solveCube2StateIndicesSync = exports.solveCube2StateIndices = exports.solveCube2State = void 0;
|
|
4
4
|
var search_js_1 = require("./search.js");
|
|
5
5
|
Object.defineProperty(exports, "solveCube2State", { enumerable: true, get: function () { return search_js_1.solveCube2State; } });
|
|
6
|
+
Object.defineProperty(exports, "solveCube2StateIndices", { enumerable: true, get: function () { return search_js_1.solveCube2StateIndices; } });
|
|
7
|
+
Object.defineProperty(exports, "solveCube2StateIndicesSync", { enumerable: true, get: function () { return search_js_1.solveCube2StateIndicesSync; } });
|
|
6
8
|
Object.defineProperty(exports, "verifyCube2Scramble", { enumerable: true, get: function () { return search_js_1.verifyCube2Scramble; } });
|
|
7
9
|
Object.defineProperty(exports, "verifyCube2Solution", { enumerable: true, get: function () { return search_js_1.verifyCube2Solution; } });
|
|
10
|
+
Object.defineProperty(exports, "verifyCube2SolutionIndices", { enumerable: true, get: function () { return search_js_1.verifyCube2SolutionIndices; } });
|
|
11
|
+
var tables_js_1 = require("./tables.js");
|
|
12
|
+
Object.defineProperty(exports, "areCube2TablesLoaded", { enumerable: true, get: function () { return tables_js_1.areCube2TablesLoaded; } });
|
|
13
|
+
Object.defineProperty(exports, "BFS_MOVES", { enumerable: true, get: function () { return tables_js_1.BFS_MOVES; } });
|
|
14
|
+
Object.defineProperty(exports, "POLICY_TABLE", { enumerable: true, get: function () { return tables_js_1.POLICY_TABLE; } });
|
|
15
|
+
Object.defineProperty(exports, "ensureTablesLoaded", { enumerable: true, get: function () { return tables_js_1.ensureTablesLoaded; } });
|
|
16
|
+
Object.defineProperty(exports, "encodePermutation", { enumerable: true, get: function () { return tables_js_1.encodePermutation; } });
|
|
17
|
+
Object.defineProperty(exports, "encodeOrientation", { enumerable: true, get: function () { return tables_js_1.encodeOrientation; } });
|
|
18
|
+
Object.defineProperty(exports, "getDistance", { enumerable: true, get: function () { return tables_js_1.getDistance; } });
|
|
19
|
+
Object.defineProperty(exports, "getPermutationTransition", { enumerable: true, get: function () { return tables_js_1.getPermutationTransition; } });
|
|
20
|
+
Object.defineProperty(exports, "getOrientationTransition", { enumerable: true, get: function () { return tables_js_1.getOrientationTransition; } });
|
|
21
|
+
Object.defineProperty(exports, "MOVE_COUNT", { enumerable: true, get: function () { return tables_js_1.MOVE_COUNT; } });
|
|
22
|
+
Object.defineProperty(exports, "ORIENTATION_COUNT", { enumerable: true, get: function () { return tables_js_1.ORIENTATION_COUNT; } });
|
|
23
|
+
Object.defineProperty(exports, "ORIENTATION_TABLE", { enumerable: true, get: function () { return tables_js_1.ORIENTATION_TABLE; } });
|
|
24
|
+
Object.defineProperty(exports, "PERMUTATION_TABLE", { enumerable: true, get: function () { return tables_js_1.PERMUTATION_TABLE; } });
|
|
@@ -17,11 +17,11 @@ function readGeneratedCube2SolverTables() {
|
|
|
17
17
|
}
|
|
18
18
|
const permutationTable = readUint16Table(assetDirectory, manifest.files.permutationTable, table_metadata_js_1.CUBE2_SOLVER_TABLE_METADATA.permutationTable.checksum);
|
|
19
19
|
const orientationTable = readUint16Table(assetDirectory, manifest.files.orientationTable, table_metadata_js_1.CUBE2_SOLVER_TABLE_METADATA.orientationTable.checksum);
|
|
20
|
-
const
|
|
20
|
+
const policyTable = readUint8Table(assetDirectory, manifest.files.policy, table_metadata_js_1.CUBE2_SOLVER_TABLE_METADATA.policy.checksum);
|
|
21
21
|
return {
|
|
22
22
|
permutationTable,
|
|
23
23
|
orientationTable,
|
|
24
|
-
|
|
24
|
+
policyTable
|
|
25
25
|
};
|
|
26
26
|
}
|
|
27
27
|
function readUint16Table(assetDirectory, entry, expectedChecksum) {
|
|
@@ -1,57 +1,57 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.solveCube2State = solveCube2State;
|
|
4
|
+
exports.solveCube2StateIndices = solveCube2StateIndices;
|
|
5
|
+
exports.solveCube2StateIndicesSync = solveCube2StateIndicesSync;
|
|
4
6
|
exports.verifyCube2Scramble = verifyCube2Scramble;
|
|
5
7
|
exports.verifyCube2Solution = verifyCube2Solution;
|
|
8
|
+
exports.verifyCube2SolutionIndices = verifyCube2SolutionIndices;
|
|
6
9
|
const state_js_1 = require("../state.js");
|
|
7
10
|
const tables_js_1 = require("./tables.js");
|
|
8
11
|
async function solveCube2State(state) {
|
|
9
12
|
await (0, tables_js_1.ensureTablesLoaded)();
|
|
13
|
+
const indices = solveWithTablesIndices(state);
|
|
14
|
+
return {
|
|
15
|
+
moves: indices.map((index) => tables_js_1.BFS_MOVES[index]),
|
|
16
|
+
depth: indices.length,
|
|
17
|
+
state: (0, state_js_1.createSolvedCube2State)()
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
// Index-based solve for the hot scramble path: returns the solution as move
|
|
21
|
+
// indices (0..8) in walk order. The inverse of index m is CUBE2_MOVE_INVERSE[m]
|
|
22
|
+
// (U2/R2/F2 are self-inverse, so m ^ 1 does not apply here).
|
|
23
|
+
async function solveCube2StateIndices(state) {
|
|
24
|
+
await (0, tables_js_1.ensureTablesLoaded)();
|
|
25
|
+
return solveWithTablesIndices(state);
|
|
26
|
+
}
|
|
27
|
+
function solveCube2StateIndicesSync(state) {
|
|
28
|
+
if (!tables_js_1.POLICY_TABLE) {
|
|
29
|
+
throw new Error("Cube2 solver tables not loaded. Call ensureTablesLoaded() first.");
|
|
30
|
+
}
|
|
31
|
+
return solveWithTablesIndices(state);
|
|
32
|
+
}
|
|
33
|
+
function solveWithTablesIndices(state) {
|
|
10
34
|
if (state.corners.permutation[6] !== 6 || state.corners.orientation[6] !== 0) {
|
|
11
35
|
throw new Error("The 2x2 solver requires the DBL corner (index 6) to be solved " +
|
|
12
36
|
"as the frame of reference. Please normalize the state before solving.");
|
|
13
37
|
}
|
|
14
38
|
const permTable = tables_js_1.PERMUTATION_TABLE;
|
|
15
39
|
const orientTable = tables_js_1.ORIENTATION_TABLE;
|
|
16
|
-
const
|
|
40
|
+
const policy = tables_js_1.POLICY_TABLE;
|
|
17
41
|
let permIndex = (0, tables_js_1.encodePermutation)(state.corners.permutation);
|
|
18
42
|
let orientIndex = (0, tables_js_1.encodeOrientation)(state.corners.orientation);
|
|
19
43
|
const moves = [];
|
|
20
|
-
//
|
|
21
|
-
|
|
22
|
-
let
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const orientOffset = orientIndex * tables_js_1.MOVE_COUNT;
|
|
30
|
-
const nextDistance = distance - 1;
|
|
31
|
-
for (let m = 0; m < tables_js_1.BFS_MOVES.length; m++) {
|
|
32
|
-
const nextPerm = permTable[permOffset + m];
|
|
33
|
-
const nextOrient = orientTable[orientOffset + m];
|
|
34
|
-
// High-performance branchless inlined lookup
|
|
35
|
-
const combined = nextPerm * tables_js_1.ORIENTATION_COUNT + nextOrient;
|
|
36
|
-
const nextDist = (distances[combined >> 1] >> ((combined & 1) << 2)) & 0x0f;
|
|
37
|
-
if (nextDist === nextDistance) {
|
|
38
|
-
moves.push(tables_js_1.BFS_MOVES[m]);
|
|
39
|
-
permIndex = nextPerm;
|
|
40
|
-
orientIndex = nextOrient;
|
|
41
|
-
distance = nextDistance;
|
|
42
|
-
foundNext = true;
|
|
43
|
-
break;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
if (!foundNext) {
|
|
47
|
-
throw new Error("BFS distance table corrupted or unreachable state.");
|
|
48
|
-
}
|
|
44
|
+
// Direct policy walk: the precomputed optimal move per state, so no
|
|
45
|
+
// candidate scanning (the old BFS-distance gradient walk) is needed.
|
|
46
|
+
let combined = permIndex * tables_js_1.ORIENTATION_COUNT + orientIndex;
|
|
47
|
+
while (combined !== 0) {
|
|
48
|
+
const m = (policy[combined >> 1] >> ((combined & 1) << 2)) & 0x0f;
|
|
49
|
+
moves.push(m);
|
|
50
|
+
permIndex = permTable[permIndex * tables_js_1.MOVE_COUNT + m];
|
|
51
|
+
orientIndex = orientTable[orientIndex * tables_js_1.MOVE_COUNT + m];
|
|
52
|
+
combined = permIndex * tables_js_1.ORIENTATION_COUNT + orientIndex;
|
|
49
53
|
}
|
|
50
|
-
return
|
|
51
|
-
moves,
|
|
52
|
-
depth: moves.length,
|
|
53
|
-
state: (0, state_js_1.createSolvedCube2State)()
|
|
54
|
-
};
|
|
54
|
+
return moves;
|
|
55
55
|
}
|
|
56
56
|
const MOVE_INDEX = {};
|
|
57
57
|
for (let i = 0; i < tables_js_1.BFS_MOVES.length; i++) {
|
|
@@ -79,3 +79,21 @@ function verifyCube2Solution(state, solutionMoves) {
|
|
|
79
79
|
const { permIndex, orientIndex } = walkCube2Coordinates((0, tables_js_1.encodePermutation)(state.corners.permutation), (0, tables_js_1.encodeOrientation)(state.corners.orientation), solutionMoves);
|
|
80
80
|
return permIndex === 0 && orientIndex === 0;
|
|
81
81
|
}
|
|
82
|
+
// Numeric verification for the hot scramble path: walks the solution (move
|
|
83
|
+
// indices, walk order) from the target state to solved. The scramble is the
|
|
84
|
+
// exact inverse of the solution, so this is equivalent to verifying that the
|
|
85
|
+
// scramble reproduces the target state — one walk, no string parsing.
|
|
86
|
+
function verifyCube2SolutionIndices(state, solution) {
|
|
87
|
+
const permTable = tables_js_1.PERMUTATION_TABLE;
|
|
88
|
+
const orientTable = tables_js_1.ORIENTATION_TABLE;
|
|
89
|
+
if (!permTable || !orientTable) {
|
|
90
|
+
throw new Error("Cube2 solver tables not loaded. Call ensureTablesLoaded() first.");
|
|
91
|
+
}
|
|
92
|
+
let permIndex = (0, tables_js_1.encodePermutation)(state.corners.permutation);
|
|
93
|
+
let orientIndex = (0, tables_js_1.encodeOrientation)(state.corners.orientation);
|
|
94
|
+
for (const m of solution) {
|
|
95
|
+
permIndex = permTable[permIndex * tables_js_1.MOVE_COUNT + m];
|
|
96
|
+
orientIndex = orientTable[orientIndex * tables_js_1.MOVE_COUNT + m];
|
|
97
|
+
}
|
|
98
|
+
return permIndex === 0 && orientIndex === 0;
|
|
99
|
+
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.BFS_MOVES = exports.
|
|
3
|
+
exports.BFS_MOVES = exports.POLICY_TABLE = exports.ORIENTATION_TABLE = exports.PERMUTATION_TABLE = exports.MOVE_COUNT = exports.ORIENTATION_COUNT = exports.PERMUTATION_COUNT = void 0;
|
|
4
4
|
exports.encodePermutation = encodePermutation;
|
|
5
5
|
exports.encodeOrientation = encodeOrientation;
|
|
6
6
|
exports.ensureTablesLoaded = ensureTablesLoaded;
|
|
7
|
+
exports.areCube2TablesLoaded = areCube2TablesLoaded;
|
|
7
8
|
exports.getDistance = getDistance;
|
|
8
9
|
exports.getPermutationTransition = getPermutationTransition;
|
|
9
10
|
exports.getOrientationTransition = getOrientationTransition;
|
|
@@ -14,19 +15,31 @@ exports.MOVE_COUNT = 9;
|
|
|
14
15
|
// ---------------------------------------------------------------------------
|
|
15
16
|
// Coordinate encoding / decoding
|
|
16
17
|
// ---------------------------------------------------------------------------
|
|
18
|
+
// Lehmer encoding of the 7 free corners (DBL at index 6 is fixed), with a
|
|
19
|
+
// bitmask instead of array removal: available values tracked as bits, the
|
|
20
|
+
// rank of a value = the popcount of the bits below it.
|
|
21
|
+
const PERM7_FACT = [720, 120, 24, 6, 2, 1, 1];
|
|
17
22
|
function encodePermutation(permutation) {
|
|
18
|
-
const perm7 = permutation.filter((_, i) => i !== 6);
|
|
19
|
-
const mapped = perm7.map((x) => (x > 6 ? x - 1 : x));
|
|
20
23
|
let index = 0;
|
|
21
|
-
|
|
24
|
+
let available = 0b1111111;
|
|
22
25
|
for (let position = 0; position < 7; position += 1) {
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
available
|
|
26
|
+
const permPosition = position < 6 ? position : position + 1;
|
|
27
|
+
const piece = permutation[permPosition];
|
|
28
|
+
const value = piece > 6 ? piece - 1 : piece;
|
|
29
|
+
const rank = popcount(available & ((1 << value) - 1));
|
|
30
|
+
index += rank * PERM7_FACT[position];
|
|
31
|
+
available &= ~(1 << value);
|
|
27
32
|
}
|
|
28
33
|
return index;
|
|
29
34
|
}
|
|
35
|
+
function popcount(x) {
|
|
36
|
+
let count = 0;
|
|
37
|
+
while (x !== 0) {
|
|
38
|
+
x &= x - 1;
|
|
39
|
+
count += 1;
|
|
40
|
+
}
|
|
41
|
+
return count;
|
|
42
|
+
}
|
|
30
43
|
function encodeOrientation(orientation) {
|
|
31
44
|
const orient7 = orientation.filter((_, i) => i !== 6);
|
|
32
45
|
let index = 0;
|
|
@@ -50,7 +63,7 @@ function factorialSmall(n) {
|
|
|
50
63
|
let tablesLoaded = false;
|
|
51
64
|
exports.PERMUTATION_TABLE = null;
|
|
52
65
|
exports.ORIENTATION_TABLE = null;
|
|
53
|
-
exports.
|
|
66
|
+
exports.POLICY_TABLE = null;
|
|
54
67
|
async function ensureTablesLoaded() {
|
|
55
68
|
if (tablesLoaded) {
|
|
56
69
|
return;
|
|
@@ -58,19 +71,33 @@ async function ensureTablesLoaded() {
|
|
|
58
71
|
const tables = await (0, generated_assets_js_1.loadGeneratedCube2SolverTables)();
|
|
59
72
|
exports.PERMUTATION_TABLE = tables.permutationTable;
|
|
60
73
|
exports.ORIENTATION_TABLE = tables.orientationTable;
|
|
61
|
-
exports.
|
|
74
|
+
exports.POLICY_TABLE = tables.policyTable;
|
|
62
75
|
tablesLoaded = true;
|
|
63
76
|
}
|
|
77
|
+
function areCube2TablesLoaded() {
|
|
78
|
+
return tablesLoaded;
|
|
79
|
+
}
|
|
64
80
|
// ---------------------------------------------------------------------------
|
|
65
81
|
// Public API
|
|
66
82
|
// ---------------------------------------------------------------------------
|
|
67
83
|
function getDistance(permIndex, orientIndex) {
|
|
68
|
-
if (!exports.
|
|
84
|
+
if (!exports.POLICY_TABLE) {
|
|
69
85
|
throw new Error("Cube2 solver tables not loaded. Call ensureTablesLoaded() first.");
|
|
70
86
|
}
|
|
71
|
-
const
|
|
72
|
-
const
|
|
73
|
-
|
|
87
|
+
const permTable = exports.PERMUTATION_TABLE;
|
|
88
|
+
const orientTable = exports.ORIENTATION_TABLE;
|
|
89
|
+
let p = permIndex;
|
|
90
|
+
let o = orientIndex;
|
|
91
|
+
let combined = p * exports.ORIENTATION_COUNT + o;
|
|
92
|
+
let distance = 0;
|
|
93
|
+
while (combined !== 0) {
|
|
94
|
+
const m = (exports.POLICY_TABLE[combined >> 1] >> ((combined & 1) << 2)) & 0x0f;
|
|
95
|
+
p = permTable[p * exports.MOVE_COUNT + m];
|
|
96
|
+
o = orientTable[o * exports.MOVE_COUNT + m];
|
|
97
|
+
combined = p * exports.ORIENTATION_COUNT + o;
|
|
98
|
+
distance += 1;
|
|
99
|
+
}
|
|
100
|
+
return distance;
|
|
74
101
|
}
|
|
75
102
|
function getPermutationTransition(move, permIndex) {
|
|
76
103
|
if (!exports.PERMUTATION_TABLE) {
|
|
@@ -25,8 +25,10 @@ function cube2StatesEqual(left, right) {
|
|
|
25
25
|
return (arraysEqual(left.corners.permutation, right.corners.permutation) &&
|
|
26
26
|
arraysEqual(left.corners.orientation, right.corners.orientation));
|
|
27
27
|
}
|
|
28
|
+
// Read-only canonical solved state for comparisons (never mutated).
|
|
29
|
+
const SOLVED_STATE = createSolvedCube2State();
|
|
28
30
|
function isSolvedCube2State(state) {
|
|
29
|
-
return cube2StatesEqual(state,
|
|
31
|
+
return cube2StatesEqual(state, SOLVED_STATE);
|
|
30
32
|
}
|
|
31
33
|
function range(length) {
|
|
32
34
|
return Array.from({ length }, (_, index) => index);
|
|
@@ -1,23 +1,56 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isTrivialCube2State = isTrivialCube2State;
|
|
3
4
|
exports.validateCube2State = validateCube2State;
|
|
4
5
|
exports.isOneMoveFromSolvedCube2State = isOneMoveFromSolvedCube2State;
|
|
6
|
+
exports.isTrivialSamplingCube2State = isTrivialSamplingCube2State;
|
|
5
7
|
exports.isAllowedRandomCube2State = isAllowedRandomCube2State;
|
|
6
8
|
exports.permutationParity = permutationParity;
|
|
7
9
|
const types_js_1 = require("./types.js");
|
|
8
10
|
const moves_js_1 = require("./moves.js");
|
|
9
11
|
const state_js_1 = require("./state.js");
|
|
10
12
|
const tables_js_1 = require("./solver/tables.js");
|
|
13
|
+
// The 18 single-move states, precomputed once so the hot random-state path
|
|
14
|
+
// checks "solved or one move from solved" with pure array comparisons
|
|
15
|
+
// (no coordinate encodings per generated state).
|
|
16
|
+
const ONE_MOVE_FROM_SOLVED_STATES = types_js_1.CUBE2_MOVES.map((move) => (0, moves_js_1.applyCube2Move)((0, state_js_1.createSolvedCube2State)(), move));
|
|
11
17
|
// Precomputed coordinate encodings of every single-move state, so the
|
|
12
18
|
// "one move from solved" check is a couple of O(n) encodings + 18 comparisons
|
|
13
19
|
// instead of 18 full state applications.
|
|
14
|
-
const ONE_MOVE_FROM_SOLVED_ENCODED =
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
const ONE_MOVE_FROM_SOLVED_ENCODED = ONE_MOVE_FROM_SOLVED_STATES.map((state) => ({
|
|
21
|
+
p: (0, tables_js_1.encodePermutation)(state.corners.permutation),
|
|
22
|
+
o: (0, tables_js_1.encodeOrientation)(state.corners.orientation)
|
|
23
|
+
}));
|
|
24
|
+
// Cheap trivial check for random-state sampling: solved + the 18 single-move
|
|
25
|
+
// states, compared by arrays with early exit (a random state differs from
|
|
26
|
+
// each candidate within the first couple of permutation entries, so the
|
|
27
|
+
// typical cost is tens of comparisons — no coordinate encodings).
|
|
28
|
+
function isTrivialCube2State(state) {
|
|
29
|
+
const cp = state.corners.permutation;
|
|
30
|
+
const co = state.corners.orientation;
|
|
31
|
+
for (const candidate of ONE_MOVE_FROM_SOLVED_STATES) {
|
|
32
|
+
const p = candidate.corners.permutation;
|
|
33
|
+
const o = candidate.corners.orientation;
|
|
34
|
+
let match = true;
|
|
35
|
+
for (let i = 0; i < 8; i += 1) {
|
|
36
|
+
if (cp[i] !== p[i]) {
|
|
37
|
+
match = false;
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (!match)
|
|
42
|
+
continue;
|
|
43
|
+
for (let i = 0; i < 8; i += 1) {
|
|
44
|
+
if (co[i] !== o[i]) {
|
|
45
|
+
match = false;
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (match)
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
return (0, state_js_1.isSolvedCube2State)(state);
|
|
53
|
+
}
|
|
21
54
|
function validateCube2State(state) {
|
|
22
55
|
const errors = [
|
|
23
56
|
...validatePermutation("corner", state.corners.permutation, types_js_1.CORNER_COUNT),
|
|
@@ -37,7 +70,13 @@ function validateCube2State(state) {
|
|
|
37
70
|
function isOneMoveFromSolvedCube2State(state) {
|
|
38
71
|
const p = (0, tables_js_1.encodePermutation)(state.corners.permutation);
|
|
39
72
|
const o = (0, tables_js_1.encodeOrientation)(state.corners.orientation);
|
|
40
|
-
return
|
|
73
|
+
return isTrivialSamplingCube2State(p, o);
|
|
74
|
+
}
|
|
75
|
+
// Cheap trivial check for random-state sampling: the caller has already
|
|
76
|
+
// encoded the coordinates once. True for the solved state and the 18
|
|
77
|
+
// single-move states (which would produce scrambles shorter than 2 moves).
|
|
78
|
+
function isTrivialSamplingCube2State(p, o) {
|
|
79
|
+
return (p === 0 && o === 0) || ONE_MOVE_FROM_SOLVED_ENCODED.some((entry) => entry.p === p && entry.o === o);
|
|
41
80
|
}
|
|
42
81
|
function isAllowedRandomCube2State(state) {
|
|
43
82
|
return validateCube2State(state).valid &&
|
|
@@ -4,6 +4,8 @@ exports.toCube3Facelets = toCube3Facelets;
|
|
|
4
4
|
exports.formatCube3Facelets = formatCube3Facelets;
|
|
5
5
|
exports.getCube3FaceOrder = getCube3FaceOrder;
|
|
6
6
|
const FACE_ORDER = ["U", "R", "F", "D", "L", "B"];
|
|
7
|
+
const STICKERS = ["U", "R", "F", "D", "L", "B"];
|
|
8
|
+
const STICKER_INDEX = { U: 0, R: 1, F: 2, D: 3, L: 4, B: 5 };
|
|
7
9
|
const CORNER_PIECE_STICKERS = [
|
|
8
10
|
["U", "R", "F"],
|
|
9
11
|
["U", "F", "L"],
|
|
@@ -52,20 +54,60 @@ const EDGE_POSITION_SLOTS = [
|
|
|
52
54
|
[["B", 5], ["L", 3]],
|
|
53
55
|
[["B", 3], ["R", 5]]
|
|
54
56
|
];
|
|
57
|
+
const CORNER_CELLS = buildCells(CORNER_POSITION_SLOTS, CORNER_PIECE_STICKERS, 3);
|
|
58
|
+
const EDGE_CELLS = buildCells(EDGE_POSITION_SLOTS, EDGE_PIECE_STICKERS, 2);
|
|
59
|
+
function buildCells(slots, stickersTable, orientationCount) {
|
|
60
|
+
const cells = [];
|
|
61
|
+
for (let position = 0; position < slots.length; position += 1) {
|
|
62
|
+
const byPiece = [];
|
|
63
|
+
for (let piece = 0; piece < stickersTable.length; piece += 1) {
|
|
64
|
+
const byOrientation = [];
|
|
65
|
+
for (let orientation = 0; orientation < orientationCount; orientation += 1) {
|
|
66
|
+
const stickers = stickersTable[piece];
|
|
67
|
+
const slotList = slots[position];
|
|
68
|
+
const cell = [];
|
|
69
|
+
for (let index = 0; index < slotList.length; index += 1) {
|
|
70
|
+
const [face, stickerIndex] = slotList[index];
|
|
71
|
+
cell.push(FACE_ORDER.indexOf(face));
|
|
72
|
+
cell.push(stickerIndex);
|
|
73
|
+
cell.push(STICKER_INDEX[stickers[(index - orientation + stickers.length) % stickers.length]]);
|
|
74
|
+
}
|
|
75
|
+
byOrientation.push(cell);
|
|
76
|
+
}
|
|
77
|
+
byPiece.push(byOrientation);
|
|
78
|
+
}
|
|
79
|
+
cells.push(byPiece);
|
|
80
|
+
}
|
|
81
|
+
return cells;
|
|
82
|
+
}
|
|
83
|
+
function placeCells(faces, cells) {
|
|
84
|
+
for (let cellIndex = 0; cellIndex < cells.length; cellIndex += 3) {
|
|
85
|
+
faces[cells[cellIndex]][cells[cellIndex + 1]] = STICKERS[cells[cellIndex + 2]];
|
|
86
|
+
}
|
|
87
|
+
}
|
|
55
88
|
function toCube3Facelets(state) {
|
|
56
|
-
const facelets =
|
|
57
|
-
|
|
58
|
-
|
|
89
|
+
const facelets = {
|
|
90
|
+
U: Array(9),
|
|
91
|
+
R: Array(9),
|
|
92
|
+
F: Array(9),
|
|
93
|
+
D: Array(9),
|
|
94
|
+
L: Array(9),
|
|
95
|
+
B: Array(9)
|
|
96
|
+
};
|
|
97
|
+
const faces = [facelets.U, facelets.R, facelets.F, facelets.D, facelets.L, facelets.B];
|
|
98
|
+
// Centers (fixed, since no center permutation on the 3x3)
|
|
99
|
+
for (let face = 0; face < 6; face += 1) {
|
|
100
|
+
faces[face][4] = STICKERS[face];
|
|
59
101
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
102
|
+
const cornerPermutation = state.corners.permutation;
|
|
103
|
+
const cornerOrientation = state.corners.orientation;
|
|
104
|
+
for (let position = 0; position < CORNER_CELLS.length; position += 1) {
|
|
105
|
+
placeCells(faces, CORNER_CELLS[position][cornerPermutation[position]][cornerOrientation[position]]);
|
|
64
106
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
107
|
+
const edgePermutation = state.edges.permutation;
|
|
108
|
+
const edgeOrientation = state.edges.orientation;
|
|
109
|
+
for (let position = 0; position < EDGE_CELLS.length; position += 1) {
|
|
110
|
+
placeCells(faces, EDGE_CELLS[position][edgePermutation[position]][edgeOrientation[position]]);
|
|
69
111
|
}
|
|
70
112
|
return facelets;
|
|
71
113
|
}
|
|
@@ -75,20 +117,3 @@ function formatCube3Facelets(facelets) {
|
|
|
75
117
|
function getCube3FaceOrder() {
|
|
76
118
|
return FACE_ORDER;
|
|
77
119
|
}
|
|
78
|
-
function createEmptyFacelets() {
|
|
79
|
-
return {
|
|
80
|
-
U: Array(9).fill("U"),
|
|
81
|
-
R: Array(9).fill("R"),
|
|
82
|
-
F: Array(9).fill("F"),
|
|
83
|
-
D: Array(9).fill("D"),
|
|
84
|
-
L: Array(9).fill("L"),
|
|
85
|
-
B: Array(9).fill("B")
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
function placeOrientedStickers(facelets, slots, stickers, orientation) {
|
|
89
|
-
for (let index = 0; index < slots.length; index += 1) {
|
|
90
|
-
const [face, stickerIndex] = slots[index];
|
|
91
|
-
const sticker = stickers[(index - orientation + stickers.length) % stickers.length];
|
|
92
|
-
facelets[face][stickerIndex] = sticker;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
@@ -4,6 +4,7 @@ exports.generateRandomCube3State = generateRandomCube3State;
|
|
|
4
4
|
const index_js_1 = require("../../random/index.js");
|
|
5
5
|
const types_js_1 = require("./types.js");
|
|
6
6
|
const validation_js_1 = require("./validation.js");
|
|
7
|
+
const state_js_1 = require("./state.js");
|
|
7
8
|
const subset_state_js_1 = require("./subset-state.js");
|
|
8
9
|
function generateRandomCube3State(options = {}) {
|
|
9
10
|
const random = (0, index_js_1.createRandomSource)(options.seed);
|
|
@@ -12,8 +13,14 @@ function generateRandomCube3State(options = {}) {
|
|
|
12
13
|
return (0, subset_state_js_1.generateSubsetCube3State)(options.subset, random);
|
|
13
14
|
}
|
|
14
15
|
for (let attempt = 0; attempt < 100; attempt += 1) {
|
|
16
|
+
// The state is legal by construction (permutations shuffled, orientation
|
|
17
|
+
// sums fixed, parity matched), so the only meaningful rejection is the
|
|
18
|
+
// trivial set (solved + 18 single-move states) — no full re-validation.
|
|
15
19
|
const state = createLegalRandomState(random);
|
|
16
|
-
if (!rejectTrivial
|
|
20
|
+
if (!rejectTrivial) {
|
|
21
|
+
return state;
|
|
22
|
+
}
|
|
23
|
+
if (!(0, state_js_1.isSolvedCube3State)(state) && !(0, validation_js_1.isOneMoveFromSolvedCube3State)(state)) {
|
|
17
24
|
return state;
|
|
18
25
|
}
|
|
19
26
|
}
|
|
@@ -48,7 +48,7 @@ async function generateCube3Scramble(options = {}) {
|
|
|
48
48
|
generatorVersion: "1.0.0",
|
|
49
49
|
facelets,
|
|
50
50
|
faceletString: (0, facelets_js_1.formatCube3Facelets)(facelets),
|
|
51
|
-
state
|
|
51
|
+
state,
|
|
52
52
|
solutionMoves: solution.moves,
|
|
53
53
|
solutionDepth: solution.depth,
|
|
54
54
|
phase1Depth: solution.phase1.depth,
|
|
@@ -17,6 +17,9 @@ function createSolvedCube3State() {
|
|
|
17
17
|
}
|
|
18
18
|
};
|
|
19
19
|
}
|
|
20
|
+
// Read-only canonical solved state for comparisons (never mutated, so a
|
|
21
|
+
// single shared instance is safe and avoids allocating per call).
|
|
22
|
+
const SOLVED_STATE = createSolvedCube3State();
|
|
20
23
|
function cloneCube3State(state) {
|
|
21
24
|
return {
|
|
22
25
|
corners: {
|
|
@@ -36,7 +39,7 @@ function cube3StatesEqual(left, right) {
|
|
|
36
39
|
arraysEqual(left.edges.orientation, right.edges.orientation));
|
|
37
40
|
}
|
|
38
41
|
function isSolvedCube3State(state) {
|
|
39
|
-
return cube3StatesEqual(state,
|
|
42
|
+
return cube3StatesEqual(state, SOLVED_STATE);
|
|
40
43
|
}
|
|
41
44
|
function range(length) {
|
|
42
45
|
return Array.from({ length }, (_, index) => index);
|
|
@@ -34,9 +34,12 @@ function validateCube3State(state) {
|
|
|
34
34
|
errors
|
|
35
35
|
};
|
|
36
36
|
}
|
|
37
|
+
// The 18 single-move states, precomputed once so the hot random-state path
|
|
38
|
+
// checks "one move from solved" with pure comparisons instead of 18 full
|
|
39
|
+
// move applications per generated state.
|
|
40
|
+
const ONE_MOVE_FROM_SOLVED_STATES = types_js_1.CUBE3_MOVES.map((move) => (0, moves_js_1.applyCube3Move)((0, state_js_1.createSolvedCube3State)(), move));
|
|
37
41
|
function isOneMoveFromSolvedCube3State(state) {
|
|
38
|
-
|
|
39
|
-
return types_js_1.CUBE3_MOVES.some((move) => (0, state_js_1.cube3StatesEqual)((0, moves_js_1.applyCube3Move)(solved, move), state));
|
|
42
|
+
return ONE_MOVE_FROM_SOLVED_STATES.some((candidate) => (0, state_js_1.cube3StatesEqual)(candidate, state));
|
|
40
43
|
}
|
|
41
44
|
function isAllowedRandomCube3State(state) {
|
|
42
45
|
return validateCube3State(state).valid &&
|
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.generateRandomSkewbState = generateRandomSkewbState;
|
|
4
|
+
exports.generateRandomSkewbStateSync = generateRandomSkewbStateSync;
|
|
4
5
|
const index_js_1 = require("../../random/index.js");
|
|
5
6
|
const index_js_2 = require("./solver/index.js");
|
|
6
7
|
async function generateRandomSkewbState(options = {}) {
|
|
7
8
|
await (0, index_js_2.ensureTablesLoaded)();
|
|
9
|
+
return generateRandomSkewbStateSync(options);
|
|
10
|
+
}
|
|
11
|
+
// Synchronous variant for callers that have already ensured the tables are
|
|
12
|
+
// loaded (e.g. the scrambler after the first call): no per-call await hop.
|
|
13
|
+
function generateRandomSkewbStateSync(options = {}) {
|
|
14
|
+
if (!(0, index_js_2.areSkewbTablesLoaded)()) {
|
|
15
|
+
throw new Error("Skewb solver tables not loaded. Call ensureTablesLoaded() first.");
|
|
16
|
+
}
|
|
8
17
|
const random = (0, index_js_1.createRandomSource)(options.seed);
|
|
9
18
|
const rejectTrivial = options.rejectTrivial ?? true;
|
|
10
19
|
const trivialRanks = rejectTrivial ? getTrivialRanks() : null;
|