@cubingcenter/scrambler 0.6.0 → 0.6.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/core/types.d.ts +1 -1
- package/dist/core/types.d.ts.map +1 -1
- package/dist/puzzles/cube3/solver/browser-loader.d.ts.map +1 -1
- package/dist/puzzles/cube3/solver/browser-loader.js +26 -1
- package/dist/puzzles/cube3/solver/generated/manifest.json +14 -0
- package/dist/puzzles/cube3/solver/generated/phase1-udco.bin +1 -0
- package/dist/puzzles/cube3/solver/generated/phase1-udeo.bin +1 -0
- package/dist/puzzles/cube3/solver/generated-assets.d.ts.map +1 -1
- package/dist/puzzles/cube3/solver/generated-assets.js +2 -1
- package/dist/puzzles/cube3/solver/node-loader.d.ts.map +1 -1
- package/dist/puzzles/cube3/solver/node-loader.js +22 -0
- package/dist/puzzles/cube3/solver/pair-browser-loader.d.ts +11 -0
- package/dist/puzzles/cube3/solver/pair-browser-loader.d.ts.map +1 -0
- package/dist/puzzles/cube3/solver/pair-browser-loader.js +93 -0
- package/dist/puzzles/cube3/solver/pair-node-loader.d.ts +6 -0
- package/dist/puzzles/cube3/solver/pair-node-loader.d.ts.map +1 -0
- package/dist/puzzles/cube3/solver/pair-node-loader.js +79 -0
- package/dist/puzzles/cube3/solver/pair-tables-types.d.ts +37 -0
- package/dist/puzzles/cube3/solver/pair-tables-types.d.ts.map +1 -0
- package/dist/puzzles/cube3/solver/pair-tables-types.js +29 -0
- package/dist/puzzles/cube3/solver/pair-tables.d.ts +29 -0
- package/dist/puzzles/cube3/solver/pair-tables.d.ts.map +1 -0
- package/dist/puzzles/cube3/solver/pair-tables.js +69 -0
- package/dist/puzzles/cube3/solver/phase1.d.ts +3 -1
- package/dist/puzzles/cube3/solver/phase1.d.ts.map +1 -1
- package/dist/puzzles/cube3/solver/phase1.js +32 -2
- package/dist/puzzles/cube3/solver/phase2.d.ts +3 -0
- package/dist/puzzles/cube3/solver/phase2.d.ts.map +1 -1
- package/dist/puzzles/cube3/solver/phase2.js +63 -5
- package/dist/puzzles/cube3/solver/tables.d.ts +16 -0
- package/dist/puzzles/cube3/solver/tables.d.ts.map +1 -1
- package/dist/puzzles/cube3/solver/tables.js +4 -2
- package/dist/puzzles/cube4/scrambler.d.ts +4 -1
- package/dist/puzzles/cube4/scrambler.d.ts.map +1 -1
- package/dist/puzzles/cube4/scrambler.js +101 -14
- package/dist-cjs/puzzles/cube3/solver/browser-loader.js +26 -1
- package/dist-cjs/puzzles/cube3/solver/generated/manifest.json +14 -0
- package/dist-cjs/puzzles/cube3/solver/generated/phase1-udco.bin +1 -0
- package/dist-cjs/puzzles/cube3/solver/generated/phase1-udeo.bin +1 -0
- package/dist-cjs/puzzles/cube3/solver/generated-assets.js +2 -1
- package/dist-cjs/puzzles/cube3/solver/node-loader.js +22 -0
- package/dist-cjs/puzzles/cube3/solver/pair-browser-loader.js +96 -0
- package/dist-cjs/puzzles/cube3/solver/pair-node-loader.js +85 -0
- package/dist-cjs/puzzles/cube3/solver/pair-tables-types.js +33 -0
- package/dist-cjs/puzzles/cube3/solver/pair-tables.js +107 -0
- package/dist-cjs/puzzles/cube3/solver/phase1.js +32 -2
- package/dist-cjs/puzzles/cube3/solver/phase2.js +63 -5
- package/dist-cjs/puzzles/cube3/solver/tables.js +4 -2
- package/dist-cjs/puzzles/cube4/scrambler.js +101 -15
- package/package.json +4 -1
|
@@ -75,7 +75,8 @@ async function loadGeneratedCube3Phase1Tables() {
|
|
|
75
75
|
edgeOrientation: tables.pruningTables.edgeOrientation,
|
|
76
76
|
udSlice: tables.pruningTables.udSlice,
|
|
77
77
|
cornerOrientationEdgeOrientation: tables.pruningTables.cornerOrientationEdgeOrientation
|
|
78
|
-
}
|
|
78
|
+
},
|
|
79
|
+
jointTables: tables.jointTables
|
|
79
80
|
};
|
|
80
81
|
}
|
|
81
82
|
async function loadGeneratedCube3Phase2Tables() {
|
|
@@ -32,7 +32,29 @@ function readGeneratedCube3SolverTables() {
|
|
|
32
32
|
slicePermutation: readPruningTable(assetDirectory, manifest.pruningTables.slicePermutation),
|
|
33
33
|
cornerPermutationSlicePermutation: readPruningTable(assetDirectory, manifest.pruningTables.cornerPermutationSlicePermutation),
|
|
34
34
|
udEdgePermutationSlicePermutation: readPruningTable(assetDirectory, manifest.pruningTables.udEdgePermutationSlicePermutation)
|
|
35
|
+
},
|
|
36
|
+
jointTables: readJointTables(assetDirectory, manifest.jointTables)
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function readJointTables(assetDirectory, entries) {
|
|
40
|
+
if (!entries?.udCo || !entries.udEo) {
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
const readJoint = (entry) => {
|
|
44
|
+
const buffer = (0, node_fs_1.readFileSync)(node_path_1.default.join(assetDirectory, entry.file));
|
|
45
|
+
const expectedBytes = Math.ceil((entry.rangeA * entry.rangeB) / 2);
|
|
46
|
+
if (buffer.length !== expectedBytes) {
|
|
47
|
+
throw new Error(`Invalid byte size for ${entry.file}.`);
|
|
35
48
|
}
|
|
49
|
+
const values = new Uint8Array(buffer);
|
|
50
|
+
if (checksum(values) !== entry.checksum) {
|
|
51
|
+
throw new Error(`Checksum mismatch for ${entry.file}.`);
|
|
52
|
+
}
|
|
53
|
+
return values;
|
|
54
|
+
};
|
|
55
|
+
return {
|
|
56
|
+
jointUdCo: readJoint(entries.udCo),
|
|
57
|
+
jointUdEo: readJoint(entries.udEo)
|
|
36
58
|
};
|
|
37
59
|
}
|
|
38
60
|
function readManifest(assetDirectory) {
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Browser loader for the combined (cp x uep) pair pruning tables.
|
|
4
|
+
*
|
|
5
|
+
* Fetches the plain .bin files from the web assets base URL (the web sync
|
|
6
|
+
* script copies `generated/pair/` wholesale; compression variants are not
|
|
7
|
+
* used for the pair tables yet — the 55 MB dist table is the dominant
|
|
8
|
+
* download either way).
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.loadBrowserPairTables = loadBrowserPairTables;
|
|
12
|
+
const pair_tables_types_js_1 = require("./pair-tables-types.js");
|
|
13
|
+
const PAIR_DIR = "puzzles/cube3/solver/generated/pair";
|
|
14
|
+
function resolveBaseUrl(provided) {
|
|
15
|
+
if (provided) {
|
|
16
|
+
return provided.replace(/\/$/, "");
|
|
17
|
+
}
|
|
18
|
+
if (typeof document !== "undefined") {
|
|
19
|
+
const script = document.currentScript;
|
|
20
|
+
if (script?.src) {
|
|
21
|
+
const base = new URL(".", script.src).href;
|
|
22
|
+
return base.replace(/\/$/, "");
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return ".";
|
|
26
|
+
}
|
|
27
|
+
async function loadBrowserPairTables(baseUrl) {
|
|
28
|
+
const resolvedBase = resolveBaseUrl(baseUrl);
|
|
29
|
+
const manifest = await fetchManifest(resolvedBase);
|
|
30
|
+
if (manifest === undefined) {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
const fetchBin = async (file, expectedBytes, expectedChecksum) => {
|
|
34
|
+
const url = `${resolvedBase}/${PAIR_DIR}/${file}`;
|
|
35
|
+
const response = await fetch(url);
|
|
36
|
+
if (!response.ok) {
|
|
37
|
+
throw new Error(`Failed to load pair table ${url}: ${response.status} ${response.statusText}`);
|
|
38
|
+
}
|
|
39
|
+
const buffer = await response.arrayBuffer();
|
|
40
|
+
if (buffer.byteLength !== expectedBytes) {
|
|
41
|
+
throw new Error(`Invalid byte size for ${file}.`);
|
|
42
|
+
}
|
|
43
|
+
const values = new Uint8Array(buffer);
|
|
44
|
+
const actual = (0, pair_tables_types_js_1.checksumBytes)(values);
|
|
45
|
+
if (actual !== expectedChecksum) {
|
|
46
|
+
throw new Error(`Checksum mismatch for ${file}: expected ${expectedChecksum}, received ${actual}.`);
|
|
47
|
+
}
|
|
48
|
+
return values;
|
|
49
|
+
};
|
|
50
|
+
const fetchU16 = async (file, count, expectedChecksum) => {
|
|
51
|
+
const values = new Uint16Array(count);
|
|
52
|
+
const buffer = await fetchBin(file, count * 2, expectedChecksum);
|
|
53
|
+
for (let index = 0; index < count; index += 1) {
|
|
54
|
+
values[index] = buffer[index * 2] | (buffer[index * 2 + 1] << 8);
|
|
55
|
+
}
|
|
56
|
+
return values;
|
|
57
|
+
};
|
|
58
|
+
const fetchU32 = async (file, count, expectedChecksum) => {
|
|
59
|
+
const values = new Uint32Array(count);
|
|
60
|
+
const buffer = await fetchBin(file, count * 4, expectedChecksum);
|
|
61
|
+
for (let index = 0; index < count; index += 1) {
|
|
62
|
+
values[index] =
|
|
63
|
+
buffer[index * 4] |
|
|
64
|
+
(buffer[index * 4 + 1] << 8) |
|
|
65
|
+
(buffer[index * 4 + 2] << 16) |
|
|
66
|
+
(buffer[index * 4 + 3] << 24);
|
|
67
|
+
}
|
|
68
|
+
return values;
|
|
69
|
+
};
|
|
70
|
+
const canonCpMin = await fetchU16("phase2-pair-canoncpmin.bin", 40320, manifest.checksums.canonCpMin);
|
|
71
|
+
const canonTauStart = await fetchU32("phase2-pair-canonctaustart.bin", 40321, manifest.checksums.canonTauStart);
|
|
72
|
+
const canonTaus = await fetchBin("phase2-pair-canonctaus.bin", canonTauStart[40320], manifest.checksums.canonTaus);
|
|
73
|
+
const conjEp = await fetchU16("phase2-pair-conjep.bin", 16 * 40320, manifest.checksums.conjEp);
|
|
74
|
+
const dist = await fetchBin("phase2-pair-dist.bin", manifest.tableBytes, manifest.checksums.dist);
|
|
75
|
+
return {
|
|
76
|
+
denseMin: (0, pair_tables_types_js_1.buildDenseMin)(canonCpMin, manifest.nMinima),
|
|
77
|
+
canonCpMin,
|
|
78
|
+
canonTauStart,
|
|
79
|
+
canonTaus,
|
|
80
|
+
conjEp,
|
|
81
|
+
dist,
|
|
82
|
+
nMinima: manifest.nMinima,
|
|
83
|
+
maxDepth: manifest.maxDepth
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
async function fetchManifest(baseUrl) {
|
|
87
|
+
const url = `${baseUrl}/${PAIR_DIR}/phase2-pair-manifest.json`;
|
|
88
|
+
const response = await fetch(url);
|
|
89
|
+
if (response.status === 404) {
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
if (!response.ok) {
|
|
93
|
+
throw new Error(`Failed to load pair table manifest from ${url}: ${response.status} ${response.statusText}`);
|
|
94
|
+
}
|
|
95
|
+
return await response.json();
|
|
96
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Node loader for the combined (cp x uep) pair pruning tables.
|
|
4
|
+
*/
|
|
5
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
6
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
7
|
+
};
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.loadNodePairTables = loadNodePairTables;
|
|
10
|
+
const node_fs_1 = require("node:fs");
|
|
11
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
12
|
+
const pair_tables_types_js_1 = require("./pair-tables-types.js");
|
|
13
|
+
const PAIR_DIR = "puzzles/cube3/solver/generated/pair";
|
|
14
|
+
function loadNodePairTables() {
|
|
15
|
+
const dir = resolvePairDirectory();
|
|
16
|
+
if (dir === undefined) {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
const manifest = JSON.parse((0, node_fs_1.readFileSync)(node_path_1.default.join(dir, "phase2-pair-manifest.json"), "utf8"));
|
|
20
|
+
const readBin = (file, expectedBytes, expectedChecksum) => {
|
|
21
|
+
const buffer = (0, node_fs_1.readFileSync)(node_path_1.default.join(dir, file));
|
|
22
|
+
if (buffer.length !== expectedBytes) {
|
|
23
|
+
throw new Error(`Invalid byte size for ${file}.`);
|
|
24
|
+
}
|
|
25
|
+
const values = new Uint8Array(buffer);
|
|
26
|
+
const actual = (0, pair_tables_types_js_1.checksumBytes)(values);
|
|
27
|
+
if (actual !== expectedChecksum) {
|
|
28
|
+
throw new Error(`Checksum mismatch for ${file}: expected ${expectedChecksum}, received ${actual}.`);
|
|
29
|
+
}
|
|
30
|
+
return values;
|
|
31
|
+
};
|
|
32
|
+
const readU16 = (file, count, expectedChecksum) => {
|
|
33
|
+
const values = new Uint16Array(count);
|
|
34
|
+
const buffer = readBin(file, count * 2, expectedChecksum);
|
|
35
|
+
for (let index = 0; index < count; index += 1) {
|
|
36
|
+
values[index] = buffer[index * 2] | (buffer[index * 2 + 1] << 8);
|
|
37
|
+
}
|
|
38
|
+
return values;
|
|
39
|
+
};
|
|
40
|
+
const readU32 = (file, count, expectedChecksum) => {
|
|
41
|
+
const values = new Uint32Array(count);
|
|
42
|
+
const buffer = readBin(file, count * 4, expectedChecksum);
|
|
43
|
+
for (let index = 0; index < count; index += 1) {
|
|
44
|
+
values[index] =
|
|
45
|
+
buffer[index * 4] |
|
|
46
|
+
(buffer[index * 4 + 1] << 8) |
|
|
47
|
+
(buffer[index * 4 + 2] << 16) |
|
|
48
|
+
(buffer[index * 4 + 3] << 24);
|
|
49
|
+
}
|
|
50
|
+
return values;
|
|
51
|
+
};
|
|
52
|
+
const canonCpMin = readU16("phase2-pair-canoncpmin.bin", 40320, manifest.checksums.canonCpMin);
|
|
53
|
+
const canonTauStart = readU32("phase2-pair-canonctaustart.bin", 40321, manifest.checksums.canonTauStart);
|
|
54
|
+
const canonTaus = readBin("phase2-pair-canonctaus.bin", canonTauStart[40320], manifest.checksums.canonTaus);
|
|
55
|
+
const conjEp = readU16("phase2-pair-conjep.bin", 16 * 40320, manifest.checksums.conjEp);
|
|
56
|
+
const dist = readBin("phase2-pair-dist.bin", manifest.tableBytes, manifest.checksums.dist);
|
|
57
|
+
return {
|
|
58
|
+
denseMin: (0, pair_tables_types_js_1.buildDenseMin)(canonCpMin, manifest.nMinima),
|
|
59
|
+
canonCpMin,
|
|
60
|
+
canonTauStart,
|
|
61
|
+
canonTaus,
|
|
62
|
+
conjEp,
|
|
63
|
+
dist,
|
|
64
|
+
nMinima: manifest.nMinima,
|
|
65
|
+
maxDepth: manifest.maxDepth
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function resolvePairDirectory() {
|
|
69
|
+
const envDirectory = process.env.CUBINGCENTER_SCRAMBLER_TABLES_DIR;
|
|
70
|
+
if (envDirectory && (0, node_fs_1.existsSync)(node_path_1.default.join(envDirectory, "pair", "phase2-pair-manifest.json"))) {
|
|
71
|
+
return node_path_1.default.join(envDirectory, "pair");
|
|
72
|
+
}
|
|
73
|
+
const cwd = process.cwd();
|
|
74
|
+
const candidates = [
|
|
75
|
+
node_path_1.default.join(cwd, "src", PAIR_DIR),
|
|
76
|
+
node_path_1.default.join(cwd, "dist", PAIR_DIR),
|
|
77
|
+
node_path_1.default.join(cwd, "dist-cjs", PAIR_DIR)
|
|
78
|
+
];
|
|
79
|
+
for (const candidate of candidates) {
|
|
80
|
+
if ((0, node_fs_1.existsSync)(node_path_1.default.join(candidate, "phase2-pair-manifest.json"))) {
|
|
81
|
+
return candidate;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Pair-table types shared by the node and browser loaders.
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.checksumBytes = checksumBytes;
|
|
7
|
+
exports.buildDenseMin = buildDenseMin;
|
|
8
|
+
function checksumBytes(values) {
|
|
9
|
+
let hash = 0x811c9dc5;
|
|
10
|
+
for (let index = 0; index < values.length; index += 1) {
|
|
11
|
+
hash ^= values[index];
|
|
12
|
+
hash = Math.imul(hash, 0x01000193);
|
|
13
|
+
}
|
|
14
|
+
return hash >>> 0;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Builds the dense class map (first-appearance order, matching exp3-gen).
|
|
18
|
+
*/
|
|
19
|
+
function buildDenseMin(canonCpMin, expectedCount) {
|
|
20
|
+
const denseMin = new Uint16Array(40320).fill(0xffff);
|
|
21
|
+
let next = 0;
|
|
22
|
+
for (let cp = 0; cp < 40320; cp += 1) {
|
|
23
|
+
const minCp = canonCpMin[cp];
|
|
24
|
+
if (denseMin[minCp] === 0xffff) {
|
|
25
|
+
denseMin[minCp] = next;
|
|
26
|
+
next += 1;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (next !== expectedCount) {
|
|
30
|
+
throw new Error(`Dense class count mismatch: ${next} != ${expectedCount}.`);
|
|
31
|
+
}
|
|
32
|
+
return denseMin;
|
|
33
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Combined (cp x uep) phase-2 pruning table (experiment:
|
|
4
|
+
* experiment/cube3-pair-pruning). The exact distance to (cp==0 && uep==0)
|
|
5
|
+
* with the slice permutation free, canonicalized under the slice-16
|
|
6
|
+
* conjugation group:
|
|
7
|
+
*
|
|
8
|
+
* index = denseMin[canonCpMin[cp]] * 40320 + uep*
|
|
9
|
+
* uep* = min over the cp-minimizer set (canonTaus) of conjEp[g][uep]
|
|
10
|
+
*
|
|
11
|
+
* The distance is ~3.5-4 moves tighter than max(cpsp, uepsp) (the plain
|
|
12
|
+
* phase-2 bound) and admissible for the joint phase-2 problem
|
|
13
|
+
* (d_pair <= d_joint), so it prunes the deep phase-2 searches
|
|
14
|
+
* exponentially. Measured in the native C++ repo (exp3): phase-2 drops
|
|
15
|
+
* from ~34k nodes to ~37 (990x) and the full scramble from ~5.7 ms to
|
|
16
|
+
* ~1.2 ms. Generated by the C++ tool `tools/exp3-gen`, ~1 min,
|
|
17
|
+
* deterministic, FNV-1a checksummed.
|
|
18
|
+
*
|
|
19
|
+
* The tables live in `puzzles/cube3/solver/generated/pair/` next to the
|
|
20
|
+
* standard solver tables.
|
|
21
|
+
*/
|
|
22
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
23
|
+
if (k2 === undefined) k2 = k;
|
|
24
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
25
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
26
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
27
|
+
}
|
|
28
|
+
Object.defineProperty(o, k2, desc);
|
|
29
|
+
}) : (function(o, m, k, k2) {
|
|
30
|
+
if (k2 === undefined) k2 = k;
|
|
31
|
+
o[k2] = m[k];
|
|
32
|
+
}));
|
|
33
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
34
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
35
|
+
}) : function(o, v) {
|
|
36
|
+
o["default"] = v;
|
|
37
|
+
});
|
|
38
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
39
|
+
var ownKeys = function(o) {
|
|
40
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
41
|
+
var ar = [];
|
|
42
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
43
|
+
return ar;
|
|
44
|
+
};
|
|
45
|
+
return ownKeys(o);
|
|
46
|
+
};
|
|
47
|
+
return function (mod) {
|
|
48
|
+
if (mod && mod.__esModule) return mod;
|
|
49
|
+
var result = {};
|
|
50
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
51
|
+
__setModuleDefault(result, mod);
|
|
52
|
+
return result;
|
|
53
|
+
};
|
|
54
|
+
})();
|
|
55
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
56
|
+
exports.getCachedPairTables = getCachedPairTables;
|
|
57
|
+
exports.loadCube3PairTables = loadCube3PairTables;
|
|
58
|
+
exports.pairBound = pairBound;
|
|
59
|
+
let cached;
|
|
60
|
+
function getCachedPairTables() {
|
|
61
|
+
return cached;
|
|
62
|
+
}
|
|
63
|
+
async function loadCube3PairTables() {
|
|
64
|
+
if (cached === undefined) {
|
|
65
|
+
cached = await loadPairTables();
|
|
66
|
+
}
|
|
67
|
+
return cached;
|
|
68
|
+
}
|
|
69
|
+
async function loadPairTables() {
|
|
70
|
+
try {
|
|
71
|
+
if (typeof self !== "undefined") {
|
|
72
|
+
const loader = await Promise.resolve().then(() => __importStar(require(
|
|
73
|
+
/* webpackIgnore: true */
|
|
74
|
+
/* @vite-ignore */
|
|
75
|
+
"./pair-browser-loader.js")));
|
|
76
|
+
return await loader.loadBrowserPairTables();
|
|
77
|
+
}
|
|
78
|
+
const loader = await Promise.resolve().then(() => __importStar(require(
|
|
79
|
+
/* webpackIgnore: true */
|
|
80
|
+
/* @vite-ignore */
|
|
81
|
+
"./pair-node-loader.js")));
|
|
82
|
+
return loader.loadNodePairTables();
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* The pair distance of (cp, uep) — the exact distance to (cp==0 && uep==0)
|
|
90
|
+
* with the slice free. Admissible as a phase-2 lower bound.
|
|
91
|
+
*/
|
|
92
|
+
function pairBound(tables, cp, uep) {
|
|
93
|
+
const minCp = tables.canonCpMin[cp];
|
|
94
|
+
const start = tables.canonTauStart[cp];
|
|
95
|
+
const end = tables.canonTauStart[cp + 1];
|
|
96
|
+
let bestUep = 0x7fffffff;
|
|
97
|
+
for (let index = start; index < end; index += 1) {
|
|
98
|
+
const g = tables.canonTaus[index];
|
|
99
|
+
const e = tables.conjEp[g * 40320 + uep];
|
|
100
|
+
if (e < bestUep) {
|
|
101
|
+
bestUep = e;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
const pairIndex = tables.denseMin[minCp] * 40320 + bestUep;
|
|
105
|
+
const nibble = tables.dist[pairIndex >> 1];
|
|
106
|
+
return (nibble >> ((pairIndex & 1) << 2)) & 0x0f;
|
|
107
|
+
}
|
|
@@ -32,11 +32,13 @@ const PHASE1_VALID_MOVES = Array.from({ length: 7 }, (_, prevPlusOne) => Array.f
|
|
|
32
32
|
}));
|
|
33
33
|
async function solveCube3Phase1(state, options = {}) {
|
|
34
34
|
const tables = options.tables ?? await (0, tables_js_1.loadCube3Phase1Tables)();
|
|
35
|
+
const jointTables = options.jointTables ?? tables.jointTables;
|
|
35
36
|
const maxDepth = options.maxDepth ?? 12;
|
|
36
37
|
const start = encodeCube3Phase1Coordinates(state);
|
|
37
38
|
const deadline = options.timeoutMs === undefined ? undefined : performance.now() + options.timeoutMs;
|
|
38
39
|
const context = {
|
|
39
40
|
tables,
|
|
41
|
+
jointTables,
|
|
40
42
|
maxDepth,
|
|
41
43
|
deadline,
|
|
42
44
|
visitedNodes: 0,
|
|
@@ -56,7 +58,19 @@ async function solveCube3Phase1(state, options = {}) {
|
|
|
56
58
|
const combinedIndex = (start.cornerOrientation << 11) + start.edgeOrientation;
|
|
57
59
|
const coeo = (coeoTable[combinedIndex >> 1] >> ((combinedIndex & 1) << 2)) & 0x0f;
|
|
58
60
|
const udSliceDist = (udPruningTable[start.udSlice >> 1] >> ((start.udSlice & 1) << 2)) & 0x0f;
|
|
59
|
-
|
|
61
|
+
let minDepth = coeo > udSliceDist ? coeo : udSliceDist;
|
|
62
|
+
if (jointTables) {
|
|
63
|
+
const jc = (jointTables.jointUdCo[(start.udSlice * 2187 + start.cornerOrientation) >> 1] >>
|
|
64
|
+
(((start.udSlice * 2187 + start.cornerOrientation) & 1) << 2)) & 0x0f;
|
|
65
|
+
if (jc > minDepth) {
|
|
66
|
+
minDepth = jc;
|
|
67
|
+
}
|
|
68
|
+
const je = (jointTables.jointUdEo[(start.udSlice * 2048 + start.edgeOrientation) >> 1] >>
|
|
69
|
+
(((start.udSlice * 2048 + start.edgeOrientation) & 1) << 2)) & 0x0f;
|
|
70
|
+
if (je > minDepth) {
|
|
71
|
+
minDepth = je;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
60
74
|
for (let depth = minDepth; depth <= maxDepth; depth += 1) {
|
|
61
75
|
const path = [];
|
|
62
76
|
const solved = searchPhase1(start.cornerOrientation, start.edgeOrientation, start.udSlice, -1, -1, depth, path, context);
|
|
@@ -128,10 +142,26 @@ function searchPhase1(cornerOrientation, edgeOrientation, udSlice, previousFace,
|
|
|
128
142
|
const combinedIndex = (nextCornerOrientation << 11) + nextEdgeOrientation;
|
|
129
143
|
const coeo = (coeoTable[combinedIndex >> 1] >> ((combinedIndex & 1) << 2)) & 0x0f;
|
|
130
144
|
const udSliceDist = (udPruningTable[nextUdSlice >> 1] >> ((nextUdSlice & 1) << 2)) & 0x0f;
|
|
131
|
-
|
|
145
|
+
let nextLowerBound = coeo > udSliceDist ? coeo : udSliceDist;
|
|
132
146
|
if (nextLowerBound > remainingDepth - 1) {
|
|
133
147
|
continue;
|
|
134
148
|
}
|
|
149
|
+
const jointTables = context.jointTables;
|
|
150
|
+
if (jointTables) {
|
|
151
|
+
const udCoIndex = nextUdSlice * 2187 + nextCornerOrientation;
|
|
152
|
+
const jc = (jointTables.jointUdCo[udCoIndex >> 1] >> ((udCoIndex & 1) << 2)) & 0x0f;
|
|
153
|
+
if (jc > nextLowerBound) {
|
|
154
|
+
nextLowerBound = jc;
|
|
155
|
+
}
|
|
156
|
+
const udEoIndex = nextUdSlice * 2048 + nextEdgeOrientation;
|
|
157
|
+
const je = (jointTables.jointUdEo[udEoIndex >> 1] >> ((udEoIndex & 1) << 2)) & 0x0f;
|
|
158
|
+
if (je > nextLowerBound) {
|
|
159
|
+
nextLowerBound = je;
|
|
160
|
+
}
|
|
161
|
+
if (nextLowerBound > remainingDepth - 1) {
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
135
165
|
const face = PHASE1_MOVE_FACES[moveIndex];
|
|
136
166
|
path.push(context.tables.phase1Moves[moveIndex]);
|
|
137
167
|
if (searchPhase1(nextCornerOrientation, nextEdgeOrientation, nextUdSlice, face, previousFace, remainingDepth - 1, path, context)) {
|
|
@@ -12,6 +12,7 @@ const coordinates_js_1 = require("./coordinates.js");
|
|
|
12
12
|
const phase1_js_1 = require("./phase1.js");
|
|
13
13
|
const tables_js_1 = require("./tables.js");
|
|
14
14
|
const search_utils_js_1 = require("./search-utils.js");
|
|
15
|
+
const pair_tables_js_1 = require("./pair-tables.js");
|
|
15
16
|
class Cube3Phase2SearchError extends Error {
|
|
16
17
|
visitedNodes;
|
|
17
18
|
constructor(message, visitedNodes) {
|
|
@@ -49,11 +50,13 @@ async function solveCube3Phase2(state, options = {}) {
|
|
|
49
50
|
throw new Cube3Phase2SearchError("3x3 Phase 2 requires a state already in G1.", 0);
|
|
50
51
|
}
|
|
51
52
|
const tables = options.tables ?? await (0, tables_js_1.loadCube3Phase2Tables)();
|
|
53
|
+
const pairTables = options.pairTables ?? await (0, pair_tables_js_1.loadCube3PairTables)();
|
|
52
54
|
const maxDepth = options.maxDepth ?? 18;
|
|
53
55
|
const start = encodeCube3Phase2Coordinates(state);
|
|
54
56
|
const deadline = options.timeoutMs === undefined ? undefined : performance.now() + options.timeoutMs;
|
|
55
57
|
const context = {
|
|
56
58
|
tables,
|
|
59
|
+
pairTables,
|
|
57
60
|
deadline,
|
|
58
61
|
visitedNodes: 0,
|
|
59
62
|
timedOut: false
|
|
@@ -65,7 +68,13 @@ async function solveCube3Phase2(state, options = {}) {
|
|
|
65
68
|
const combinedEPIndex = start.udEdgePermutation * spRange + start.slicePermutation;
|
|
66
69
|
const cpDist = (cpspTable[combinedCPIndex >> 1] >> ((combinedCPIndex & 1) << 2)) & 0x0f;
|
|
67
70
|
const epDist = (epspTable[combinedEPIndex >> 1] >> ((combinedEPIndex & 1) << 2)) & 0x0f;
|
|
68
|
-
|
|
71
|
+
let minDepth = cpDist > epDist ? cpDist : epDist;
|
|
72
|
+
if (pairTables) {
|
|
73
|
+
const pair = (0, pair_tables_js_1.pairBound)(pairTables, start.cornerPermutation, start.udEdgePermutation);
|
|
74
|
+
if (pair > minDepth) {
|
|
75
|
+
minDepth = pair;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
69
78
|
const initialPreviousFace = options.previousFace ?? -1;
|
|
70
79
|
const initialPreviousPreviousFace = options.previousPreviousFace ?? -1;
|
|
71
80
|
for (let depth = minDepth; depth <= maxDepth; depth += 1) {
|
|
@@ -160,6 +169,38 @@ function searchPhase2(cornerPermutation, udEdgePermutation, slicePermutation, pr
|
|
|
160
169
|
const cpOffset = cornerPermutation * moveCount;
|
|
161
170
|
const epOffset = udEdgePermutation * moveCount;
|
|
162
171
|
const spOffset = slicePermutation * moveCount;
|
|
172
|
+
// Without the pair tables (the default npm package), keep the exact
|
|
173
|
+
// reference traversal — zero overhead. With them (turbo tier), collect the
|
|
174
|
+
// children with the pair-distance bound and explore in ascending bound
|
|
175
|
+
// order (stable: ties keep the reference move order): same depth-minimal
|
|
176
|
+
// solutions, far fewer nodes.
|
|
177
|
+
if (context.pairTables === undefined) {
|
|
178
|
+
for (let i = 0; i < validMoves.length; i += 1) {
|
|
179
|
+
const moveIndex = validMoves[i];
|
|
180
|
+
const nextCornerPermutation = cpTable[cpOffset + moveIndex];
|
|
181
|
+
const nextUdEdgePermutation = epTable[epOffset + moveIndex];
|
|
182
|
+
const nextSlicePermutation = spTable[spOffset + moveIndex];
|
|
183
|
+
const combinedCPIndex = nextCornerPermutation * spRange + nextSlicePermutation;
|
|
184
|
+
const combinedEPIndex = nextUdEdgePermutation * spRange + nextSlicePermutation;
|
|
185
|
+
const cpDist = (cpspTable[combinedCPIndex >> 1] >> ((combinedCPIndex & 1) << 2)) & 0x0f;
|
|
186
|
+
const epDist = (epspTable[combinedEPIndex >> 1] >> ((combinedEPIndex & 1) << 2)) & 0x0f;
|
|
187
|
+
const nextLowerBound = cpDist > epDist ? cpDist : epDist;
|
|
188
|
+
if (nextLowerBound > remainingDepth - 1) {
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
path.push(context.tables.phase2Moves[moveIndex]);
|
|
192
|
+
if (searchPhase2(nextCornerPermutation, nextUdEdgePermutation, nextSlicePermutation, PHASE2_MOVE_FACES[moveIndex], previousFace, remainingDepth - 1, path, context)) {
|
|
193
|
+
return true;
|
|
194
|
+
}
|
|
195
|
+
path.pop();
|
|
196
|
+
if (context.timedOut) {
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
const pairTables = context.pairTables;
|
|
203
|
+
const children = [];
|
|
163
204
|
for (let i = 0; i < validMoves.length; i += 1) {
|
|
164
205
|
const moveIndex = validMoves[i];
|
|
165
206
|
const nextCornerPermutation = cpTable[cpOffset + moveIndex];
|
|
@@ -169,13 +210,29 @@ function searchPhase2(cornerPermutation, udEdgePermutation, slicePermutation, pr
|
|
|
169
210
|
const combinedEPIndex = nextUdEdgePermutation * spRange + nextSlicePermutation;
|
|
170
211
|
const cpDist = (cpspTable[combinedCPIndex >> 1] >> ((combinedCPIndex & 1) << 2)) & 0x0f;
|
|
171
212
|
const epDist = (epspTable[combinedEPIndex >> 1] >> ((combinedEPIndex & 1) << 2)) & 0x0f;
|
|
172
|
-
|
|
213
|
+
let nextLowerBound = cpDist > epDist ? cpDist : epDist;
|
|
173
214
|
if (nextLowerBound > remainingDepth - 1) {
|
|
174
215
|
continue;
|
|
175
216
|
}
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
217
|
+
const pair = (0, pair_tables_js_1.pairBound)(pairTables, nextCornerPermutation, nextUdEdgePermutation);
|
|
218
|
+
if (pair > nextLowerBound) {
|
|
219
|
+
nextLowerBound = pair;
|
|
220
|
+
}
|
|
221
|
+
if (nextLowerBound > remainingDepth - 1) {
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
children.push({
|
|
225
|
+
moveIndex,
|
|
226
|
+
lowerBound: nextLowerBound,
|
|
227
|
+
cornerPermutation: nextCornerPermutation,
|
|
228
|
+
udEdgePermutation: nextUdEdgePermutation,
|
|
229
|
+
slicePermutation: nextSlicePermutation
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
children.sort((left, right) => left.lowerBound - right.lowerBound);
|
|
233
|
+
for (const child of children) {
|
|
234
|
+
path.push(context.tables.phase2Moves[child.moveIndex]);
|
|
235
|
+
if (searchPhase2(child.cornerPermutation, child.udEdgePermutation, child.slicePermutation, PHASE2_MOVE_FACES[child.moveIndex], previousFace, remainingDepth - 1, path, context)) {
|
|
179
236
|
return true;
|
|
180
237
|
}
|
|
181
238
|
path.pop();
|
|
@@ -226,6 +283,7 @@ function searchPhase1Interleaved(cornerOrientation, edgeOrientation, udSlice, pr
|
|
|
226
283
|
const maxPhase2Depth = 6;
|
|
227
284
|
const phase2Context = {
|
|
228
285
|
tables: context.phase2Tables,
|
|
286
|
+
pairTables: undefined,
|
|
229
287
|
deadline: context.deadline,
|
|
230
288
|
visitedNodes: 0,
|
|
231
289
|
timedOut: false
|
|
@@ -60,7 +60,8 @@ function generateCube3Phase1Tables() {
|
|
|
60
60
|
edgeOrientation: generateCube3PruningTable(edgeOrientation),
|
|
61
61
|
udSlice: generateCube3PruningTable(udSlice),
|
|
62
62
|
cornerOrientationEdgeOrientation: generateCube3CombinedPruningTable(cornerOrientation, edgeOrientation)
|
|
63
|
-
}
|
|
63
|
+
},
|
|
64
|
+
jointTables: undefined
|
|
64
65
|
};
|
|
65
66
|
}
|
|
66
67
|
function generateCube3SolverTables() {
|
|
@@ -87,7 +88,8 @@ function generateCube3SolverTables() {
|
|
|
87
88
|
slicePermutation: phase2Tables.pruningTables.slicePermutation,
|
|
88
89
|
cornerPermutationSlicePermutation: phase2Tables.pruningTables.cornerPermutationSlicePermutation,
|
|
89
90
|
udEdgePermutationSlicePermutation: phase2Tables.pruningTables.udEdgePermutationSlicePermutation
|
|
90
|
-
}
|
|
91
|
+
},
|
|
92
|
+
jointTables: undefined
|
|
91
93
|
};
|
|
92
94
|
}
|
|
93
95
|
function generateCube3Phase2Tables() {
|