@cubingcenter/scrambler 0.6.3 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -4
- package/dist/core/api.d.ts.map +1 -1
- package/dist/core/api.js +10 -2
- package/dist/puzzles/cube2/solver/browser-loader.d.ts +4 -7
- package/dist/puzzles/cube2/solver/browser-loader.d.ts.map +1 -1
- package/dist/puzzles/cube2/solver/browser-loader.js +42 -14
- package/dist/puzzles/cube2/solver/generated-assets.d.ts.map +1 -1
- package/dist/puzzles/cube2/solver/generated-assets.js +8 -3
- package/dist/puzzles/cube2/solver/search.d.ts.map +1 -1
- package/dist/puzzles/cube2/solver/search.js +3 -3
- package/dist/puzzles/cube2/solver/tables.d.ts +6 -0
- package/dist/puzzles/cube2/solver/tables.d.ts.map +1 -1
- package/dist/puzzles/cube2/solver/tables.js +16 -2
- package/dist/puzzles/cube3/index.d.ts +1 -0
- package/dist/puzzles/cube3/index.d.ts.map +1 -1
- package/dist/puzzles/cube3/moves.d.ts.map +1 -1
- package/dist/puzzles/cube3/moves.js +104 -18
- package/dist/puzzles/cube3/scrambler.d.ts +16 -1
- package/dist/puzzles/cube3/scrambler.d.ts.map +1 -1
- package/dist/puzzles/cube3/scrambler.js +1 -1
- package/dist/puzzles/cube3/solver/browser-loader.d.ts.map +1 -1
- package/dist/puzzles/cube3/solver/browser-loader.js +48 -22
- package/dist/puzzles/cube3/solver/generated-assets.d.ts.map +1 -1
- package/dist/puzzles/cube3/solver/generated-assets.js +9 -3
- package/dist/puzzles/cube3/solver/index.d.ts +5 -3
- package/dist/puzzles/cube3/solver/index.d.ts.map +1 -1
- package/dist/puzzles/cube3/solver/index.js +2 -1
- package/dist/puzzles/cube3/solver/node-loader.js +9 -2
- package/dist/puzzles/cube3/solver/phase1.d.ts +37 -0
- package/dist/puzzles/cube3/solver/phase1.d.ts.map +1 -1
- package/dist/puzzles/cube3/solver/phase1.js +156 -15
- package/dist/puzzles/cube3/solver/phase2.d.ts +75 -0
- package/dist/puzzles/cube3/solver/phase2.d.ts.map +1 -1
- package/dist/puzzles/cube3/solver/phase2.js +279 -68
- package/dist/puzzles/cube3/solver/search-utils.d.ts +1 -1
- package/dist/puzzles/cube3/solver/search-utils.d.ts.map +1 -1
- package/dist/puzzles/cube3/solver/symmetry.d.ts +24 -0
- package/dist/puzzles/cube3/solver/symmetry.d.ts.map +1 -0
- package/dist/puzzles/cube3/solver/symmetry.js +74 -0
- package/dist/puzzles/cube3/solver/tables.d.ts +6 -1
- package/dist/puzzles/cube3/solver/tables.d.ts.map +1 -1
- package/dist/puzzles/cube3/solver/tables.js +14 -12
- package/dist/puzzles/cube3/validation.d.ts.map +1 -1
- package/dist/puzzles/cube3/validation.js +15 -7
- package/dist/puzzles/cube4/browser-worker-pool.d.ts +82 -0
- package/dist/puzzles/cube4/browser-worker-pool.d.ts.map +1 -0
- package/dist/puzzles/cube4/browser-worker-pool.js +205 -0
- package/dist/puzzles/cube4/browser-worker.d.ts +2 -0
- package/dist/puzzles/cube4/browser-worker.d.ts.map +1 -0
- package/dist/puzzles/cube4/browser-worker.js +31 -0
- package/dist/puzzles/cube4/index.d.ts +10 -2
- package/dist/puzzles/cube4/index.d.ts.map +1 -1
- package/dist/puzzles/cube4/index.js +6 -2
- package/dist/puzzles/cube4/moves.d.ts +5 -0
- package/dist/puzzles/cube4/moves.d.ts.map +1 -1
- package/dist/puzzles/cube4/moves.js +212 -11
- package/dist/puzzles/cube4/reduction/browser-loader.d.ts +29 -1
- package/dist/puzzles/cube4/reduction/browser-loader.d.ts.map +1 -1
- package/dist/puzzles/cube4/reduction/browser-loader.js +227 -32
- package/dist/puzzles/cube4/reduction/center2.d.ts +44 -3
- package/dist/puzzles/cube4/reduction/center2.d.ts.map +1 -1
- package/dist/puzzles/cube4/reduction/center2.js +152 -10
- package/dist/puzzles/cube4/reduction/edge3.d.ts +4 -0
- package/dist/puzzles/cube4/reduction/edge3.d.ts.map +1 -1
- package/dist/puzzles/cube4/reduction/edge3.js +23 -3
- package/dist/puzzles/cube4/reduction/finish3.d.ts +53 -0
- package/dist/puzzles/cube4/reduction/finish3.d.ts.map +1 -0
- package/dist/puzzles/cube4/reduction/finish3.js +393 -0
- package/dist/puzzles/cube4/reduction/generated/c3_pair_dist.bin +1 -0
- package/dist/puzzles/cube4/reduction/generated/center2-manifest.json +36 -0
- package/dist/puzzles/cube4/reduction/generated/center2-pruning-wide.bin +1 -0
- package/dist/puzzles/cube4/reduction/generated/hc-distances.bin +1 -0
- package/dist/puzzles/cube4/reduction/generated/hc-manifest.json +44 -0
- package/dist/puzzles/cube4/reduction/generated/hc-move.bin +0 -0
- package/dist/puzzles/cube4/reduction/hc-pdb.d.ts +52 -0
- package/dist/puzzles/cube4/reduction/hc-pdb.d.ts.map +1 -0
- package/dist/puzzles/cube4/reduction/hc-pdb.js +220 -0
- package/dist/puzzles/cube4/reduction/node-loader.d.ts +15 -1
- package/dist/puzzles/cube4/reduction/node-loader.d.ts.map +1 -1
- package/dist/puzzles/cube4/reduction/node-loader.js +199 -25
- package/dist/puzzles/cube4/reduction/pair-transversal.d.ts +28 -0
- package/dist/puzzles/cube4/reduction/pair-transversal.d.ts.map +1 -0
- package/dist/puzzles/cube4/reduction/pair-transversal.js +107 -0
- package/dist/puzzles/cube4/reduction/pairing-coordinate.js +5 -5
- package/dist/puzzles/cube4/reduction/phase3-search.d.ts +116 -7
- package/dist/puzzles/cube4/reduction/phase3-search.d.ts.map +1 -1
- package/dist/puzzles/cube4/reduction/phase3-search.js +458 -32
- package/dist/puzzles/cube4/reduction/rotation-fix.d.ts +28 -0
- package/dist/puzzles/cube4/reduction/rotation-fix.d.ts.map +1 -0
- package/dist/puzzles/cube4/reduction/rotation-fix.js +212 -0
- package/dist/puzzles/cube4/reduction/solve.d.ts +172 -2
- package/dist/puzzles/cube4/reduction/solve.d.ts.map +1 -1
- package/dist/puzzles/cube4/reduction/solve.js +1201 -180
- package/dist/puzzles/cube4/scramble-cache.d.ts +93 -0
- package/dist/puzzles/cube4/scramble-cache.d.ts.map +1 -0
- package/dist/puzzles/cube4/scramble-cache.js +150 -0
- package/dist/puzzles/cube4/scrambler.d.ts +201 -6
- package/dist/puzzles/cube4/scrambler.d.ts.map +1 -1
- package/dist/puzzles/cube4/scrambler.js +414 -176
- package/dist/puzzles/cube4/state.d.ts.map +1 -1
- package/dist/puzzles/cube4/state.js +4 -1
- package/dist/puzzles/cube4/worker-pool.d.ts +139 -0
- package/dist/puzzles/cube4/worker-pool.d.ts.map +1 -0
- package/dist/puzzles/cube4/worker-pool.js +405 -0
- package/dist/puzzles/cube4/worker-protocol.d.ts +58 -0
- package/dist/puzzles/cube4/worker-protocol.d.ts.map +1 -0
- package/dist/puzzles/cube4/worker-protocol.js +58 -0
- package/dist/puzzles/cube4/worker.d.ts +26 -0
- package/dist/puzzles/cube4/worker.d.ts.map +1 -0
- package/dist/puzzles/cube4/worker.js +49 -0
- package/dist/puzzles/pyraminx/solver/browser-loader.d.ts +4 -8
- package/dist/puzzles/pyraminx/solver/browser-loader.d.ts.map +1 -1
- package/dist/puzzles/pyraminx/solver/browser-loader.js +44 -15
- package/dist/puzzles/pyraminx/solver/generated-assets.d.ts.map +1 -1
- package/dist/puzzles/pyraminx/solver/generated-assets.js +8 -3
- package/dist/puzzles/pyraminx/solver/search.d.ts.map +1 -1
- package/dist/puzzles/pyraminx/solver/search.js +3 -6
- package/dist/puzzles/pyraminx/solver/tables.d.ts +6 -0
- package/dist/puzzles/pyraminx/solver/tables.d.ts.map +1 -1
- package/dist/puzzles/pyraminx/solver/tables.js +17 -0
- package/dist/puzzles/shared/compressed-fetch.d.ts +37 -1
- package/dist/puzzles/shared/compressed-fetch.d.ts.map +1 -1
- package/dist/puzzles/shared/compressed-fetch.js +226 -31
- package/dist/puzzles/shared/load-once.d.ts +16 -0
- package/dist/puzzles/shared/load-once.d.ts.map +1 -0
- package/dist/puzzles/shared/load-once.js +11 -0
- package/dist/puzzles/skewb/solver/browser-loader.d.ts +4 -11
- package/dist/puzzles/skewb/solver/browser-loader.d.ts.map +1 -1
- package/dist/puzzles/skewb/solver/browser-loader.js +44 -16
- package/dist/puzzles/skewb/solver/generated-assets.d.ts.map +1 -1
- package/dist/puzzles/skewb/solver/generated-assets.js +8 -3
- package/dist/puzzles/skewb/solver/search.js +3 -3
- package/dist/puzzles/skewb/solver/tables.d.ts +6 -0
- package/dist/puzzles/skewb/solver/tables.d.ts.map +1 -1
- package/dist/puzzles/skewb/solver/tables.js +17 -0
- package/dist/puzzles/sq1/solver/browser-loader.d.ts.map +1 -1
- package/dist/puzzles/sq1/solver/browser-loader.js +58 -26
- package/dist-cjs/core/api.js +10 -2
- package/dist-cjs/puzzles/cube2/solver/browser-loader.js +42 -14
- package/dist-cjs/puzzles/cube2/solver/generated-assets.js +8 -3
- package/dist-cjs/puzzles/cube2/solver/search.js +2 -2
- package/dist-cjs/puzzles/cube2/solver/tables.js +17 -3
- package/dist-cjs/puzzles/cube3/moves.js +104 -18
- package/dist-cjs/puzzles/cube3/scrambler.js +1 -1
- package/dist-cjs/puzzles/cube3/solver/browser-loader.js +48 -22
- package/dist-cjs/puzzles/cube3/solver/generated-assets.js +9 -3
- package/dist-cjs/puzzles/cube3/solver/index.js +8 -1
- package/dist-cjs/puzzles/cube3/solver/node-loader.js +9 -2
- package/dist-cjs/puzzles/cube3/solver/phase1.js +157 -15
- package/dist-cjs/puzzles/cube3/solver/phase2.js +276 -65
- package/dist-cjs/puzzles/cube3/solver/symmetry.js +80 -0
- package/dist-cjs/puzzles/cube3/solver/tables.js +14 -12
- package/dist-cjs/puzzles/cube3/validation.js +15 -7
- package/dist-cjs/puzzles/cube4/browser-worker-pool.js +211 -0
- package/dist-cjs/puzzles/cube4/index.js +23 -1
- package/dist-cjs/puzzles/cube4/moves.js +213 -11
- package/dist-cjs/puzzles/cube4/reduction/browser-loader.js +227 -32
- package/dist-cjs/puzzles/cube4/reduction/center2.js +155 -10
- package/dist-cjs/puzzles/cube4/reduction/edge3.js +24 -3
- package/dist-cjs/puzzles/cube4/reduction/finish3.js +398 -0
- package/dist-cjs/puzzles/cube4/reduction/generated/c3_pair_dist.bin +1 -0
- package/dist-cjs/puzzles/cube4/reduction/generated/center2-manifest.json +36 -0
- package/dist-cjs/puzzles/cube4/reduction/generated/center2-pruning-wide.bin +1 -0
- package/dist-cjs/puzzles/cube4/reduction/generated/hc-distances.bin +1 -0
- package/dist-cjs/puzzles/cube4/reduction/generated/hc-manifest.json +44 -0
- package/dist-cjs/puzzles/cube4/reduction/generated/hc-move.bin +0 -0
- package/dist-cjs/puzzles/cube4/reduction/hc-pdb.js +232 -0
- package/dist-cjs/puzzles/cube4/reduction/node-loader.js +200 -25
- package/dist-cjs/puzzles/cube4/reduction/pair-transversal.js +112 -0
- package/dist-cjs/puzzles/cube4/reduction/pairing-coordinate.js +5 -5
- package/dist-cjs/puzzles/cube4/reduction/phase3-search.js +460 -33
- package/dist-cjs/puzzles/cube4/reduction/rotation-fix.js +216 -0
- package/dist-cjs/puzzles/cube4/reduction/solve.js +1203 -172
- package/dist-cjs/puzzles/cube4/scramble-cache.js +156 -0
- package/dist-cjs/puzzles/cube4/scrambler.js +416 -174
- package/dist-cjs/puzzles/cube4/state.js +4 -1
- package/dist-cjs/puzzles/cube4/worker-pool.js +447 -0
- package/dist-cjs/puzzles/cube4/worker-protocol.js +62 -0
- package/dist-cjs/puzzles/pyraminx/solver/browser-loader.js +44 -15
- package/dist-cjs/puzzles/pyraminx/solver/generated-assets.js +8 -3
- package/dist-cjs/puzzles/pyraminx/solver/search.js +2 -5
- package/dist-cjs/puzzles/pyraminx/solver/tables.js +18 -1
- package/dist-cjs/puzzles/shared/compressed-fetch.js +229 -31
- package/dist-cjs/puzzles/shared/load-once.js +14 -0
- package/dist-cjs/puzzles/skewb/solver/browser-loader.js +44 -16
- package/dist-cjs/puzzles/skewb/solver/generated-assets.js +8 -3
- package/dist-cjs/puzzles/skewb/solver/search.js +2 -2
- package/dist-cjs/puzzles/skewb/solver/tables.js +18 -1
- package/dist-cjs/puzzles/sq1/solver/browser-loader.js +58 -26
- package/docs/development.md +19 -7
- package/package.json +17 -7
|
@@ -13,6 +13,7 @@ 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
15
|
const pair_tables_js_1 = require("./pair-tables.js");
|
|
16
|
+
const symmetry_js_1 = require("./symmetry.js");
|
|
16
17
|
class Cube3Phase2SearchError extends Error {
|
|
17
18
|
visitedNodes;
|
|
18
19
|
constructor(message, visitedNodes) {
|
|
@@ -50,17 +51,11 @@ async function solveCube3Phase2(state, options = {}) {
|
|
|
50
51
|
throw new Cube3Phase2SearchError("3x3 Phase 2 requires a state already in G1.", 0);
|
|
51
52
|
}
|
|
52
53
|
const tables = options.tables ?? await (0, tables_js_1.loadCube3Phase2Tables)();
|
|
53
|
-
const pairTables = options.pairTables ?? await (0, pair_tables_js_1.loadCube3PairTables)();
|
|
54
|
+
const pairTables = options.pairTables ?? (options.skipPairTables ? undefined : await (0, pair_tables_js_1.loadCube3PairTables)());
|
|
54
55
|
const maxDepth = options.maxDepth ?? 18;
|
|
55
56
|
const start = encodeCube3Phase2Coordinates(state);
|
|
56
57
|
const deadline = options.timeoutMs === undefined ? undefined : performance.now() + options.timeoutMs;
|
|
57
|
-
const context =
|
|
58
|
-
tables,
|
|
59
|
-
pairTables,
|
|
60
|
-
deadline,
|
|
61
|
-
visitedNodes: 0,
|
|
62
|
-
timedOut: false
|
|
63
|
-
};
|
|
58
|
+
const context = createPhase2SearchContext(tables, pairTables, maxDepth, deadline, options.maxNodes);
|
|
64
59
|
const spRange = tables.moveTables.slicePermutation.range;
|
|
65
60
|
const cpspTable = tables.pruningTables.cornerPermutationSlicePermutation.values;
|
|
66
61
|
const epspTable = tables.pruningTables.udEdgePermutationSlicePermutation.values;
|
|
@@ -91,6 +86,9 @@ async function solveCube3Phase2(state, options = {}) {
|
|
|
91
86
|
visitedNodes: context.visitedNodes
|
|
92
87
|
};
|
|
93
88
|
}
|
|
89
|
+
if (context.nodesExceeded) {
|
|
90
|
+
throw new Cube3Phase2SearchError(`3x3 Phase 2 node budget exceeded (${options.maxNodes} nodes).`, context.visitedNodes);
|
|
91
|
+
}
|
|
94
92
|
if (context.timedOut) {
|
|
95
93
|
throw new Cube3Phase2SearchError(`3x3 Phase 2 search timed out after ${options.timeoutMs}ms.`, context.visitedNodes);
|
|
96
94
|
}
|
|
@@ -98,6 +96,11 @@ async function solveCube3Phase2(state, options = {}) {
|
|
|
98
96
|
throw new Cube3Phase2SearchError(`3x3 Phase 2 solution not found within depth ${maxDepth}.`, context.visitedNodes);
|
|
99
97
|
}
|
|
100
98
|
async function solveCube3State(state, options = {}) {
|
|
99
|
+
const quality = options.quality ?? "short";
|
|
100
|
+
const orientations = options.orientations ?? (quality === "best" ? 3 : 1);
|
|
101
|
+
if (orientations >= 3) {
|
|
102
|
+
return solveCube3TripleOrientation(state, { ...options, orientations });
|
|
103
|
+
}
|
|
101
104
|
const phase1 = await (0, phase1_js_1.solveCube3Phase1)(state, options.phase1);
|
|
102
105
|
let previousFace = -1;
|
|
103
106
|
let previousPreviousFace = -1;
|
|
@@ -116,6 +119,154 @@ async function solveCube3State(state, options = {}) {
|
|
|
116
119
|
};
|
|
117
120
|
const phase2 = await solveCube3Phase2(phase1.state, phase2Options);
|
|
118
121
|
const moves = [...phase1.moves, ...phase2.moves];
|
|
122
|
+
const targetLength = options.targetLength;
|
|
123
|
+
const extraDepth = options.phase1ExtraDepth ?? (quality === "fast" ? 0 : 1);
|
|
124
|
+
const solutionLimit = options.phase1SolutionLimit ?? (targetLength !== undefined ? 32 : 8);
|
|
125
|
+
const improveDepths = targetLength !== undefined ? Math.max(4, extraDepth) : extraDepth;
|
|
126
|
+
const needsTarget = targetLength !== undefined && moves.length > targetLength;
|
|
127
|
+
if (needsTarget || (targetLength === undefined && extraDepth > 0)) {
|
|
128
|
+
const p1Tables = options.phase1?.tables ?? await (0, tables_js_1.loadCube3Phase1Tables)();
|
|
129
|
+
const p2Tables = options.phase2?.tables ?? await (0, tables_js_1.loadCube3Phase2Tables)();
|
|
130
|
+
const scoredPairTables = options.phase2?.pairTables ??
|
|
131
|
+
(options.phase2?.skipPairTables ? undefined : await (0, pair_tables_js_1.loadCube3PairTables)());
|
|
132
|
+
// Work budget for the improvement loop. Preferred: `improveMaxNodes`
|
|
133
|
+
// (deterministic node counter — the wall deadline stays unarmed so the
|
|
134
|
+
// outcome cannot depend on CPU load/clock). Legacy: `timeoutMs ?? 800`
|
|
135
|
+
// wall deadline, kept for existing callers.
|
|
136
|
+
const nodeMode = options.improveMaxNodes !== undefined;
|
|
137
|
+
let remainingNodes = options.improveMaxNodes ?? 0;
|
|
138
|
+
const deadline = nodeMode ? undefined : performance.now() + (options.timeoutMs ?? 800);
|
|
139
|
+
const budgetExhausted = () => nodeMode ? remainingNodes <= 0 : performance.now() > deadline;
|
|
140
|
+
const spendNodes = (used) => {
|
|
141
|
+
if (nodeMode) {
|
|
142
|
+
remainingNodes = Math.max(0, remainingNodes - used);
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
const p2SpRange = p2Tables.moveTables.slicePermutation.range;
|
|
146
|
+
const p2cpsp = p2Tables.pruningTables.cornerPermutationSlicePermutation.values;
|
|
147
|
+
const p2uepsp = p2Tables.pruningTables.udEdgePermutationSlicePermutation.values;
|
|
148
|
+
const seenG1 = new Set();
|
|
149
|
+
let bestP1 = phase1;
|
|
150
|
+
let bestP2 = phase2;
|
|
151
|
+
let bestMoves = moves;
|
|
152
|
+
let improvementNodes = 0;
|
|
153
|
+
const minDepth = phase1.depth;
|
|
154
|
+
const scored = [];
|
|
155
|
+
for (let d = minDepth; d <= minDepth + improveDepths && d < bestMoves.length; d++) {
|
|
156
|
+
if (budgetExhausted())
|
|
157
|
+
break;
|
|
158
|
+
const { solutions: p1Solutions, visitedNodes } = await (0, phase1_js_1.collectCube3Phase1Solutions)(state, d, {
|
|
159
|
+
tables: p1Tables,
|
|
160
|
+
limit: solutionLimit,
|
|
161
|
+
...(nodeMode
|
|
162
|
+
? { maxNodes: Math.max(1, remainingNodes) }
|
|
163
|
+
: { timeoutMs: Math.max(1, deadline - performance.now()) })
|
|
164
|
+
});
|
|
165
|
+
improvementNodes += visitedNodes;
|
|
166
|
+
spendNodes(visitedNodes);
|
|
167
|
+
if (budgetExhausted())
|
|
168
|
+
break;
|
|
169
|
+
for (const p1Moves of p1Solutions) {
|
|
170
|
+
if (budgetExhausted())
|
|
171
|
+
break;
|
|
172
|
+
if (p1Moves.length >= bestMoves.length)
|
|
173
|
+
continue;
|
|
174
|
+
// Skip the phase-1 solution the initial search already completed.
|
|
175
|
+
if (p1Moves.length === phase1.moves.length &&
|
|
176
|
+
p1Moves.every((move, index) => move === phase1.moves[index])) {
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
const s1 = (0, moves_js_1.applyCube3Algorithm)(state, p1Moves);
|
|
180
|
+
const coords = encodeCube3Phase2Coordinates(s1);
|
|
181
|
+
const estKey = `${coords.cornerPermutation},${coords.udEdgePermutation},${coords.slicePermutation}`;
|
|
182
|
+
if (seenG1.has(estKey))
|
|
183
|
+
continue;
|
|
184
|
+
seenG1.add(estKey);
|
|
185
|
+
const cpIndex = coords.cornerPermutation * p2SpRange + coords.slicePermutation;
|
|
186
|
+
const epIndex = coords.udEdgePermutation * p2SpRange + coords.slicePermutation;
|
|
187
|
+
const cpDist = (p2cpsp[cpIndex >> 1] >> ((cpIndex & 1) << 2)) & 0x0f;
|
|
188
|
+
const epDist = (p2uepsp[epIndex >> 1] >> ((epIndex & 1) << 2)) & 0x0f;
|
|
189
|
+
let est = p1Moves.length + (cpDist > epDist ? cpDist : epDist);
|
|
190
|
+
if (scoredPairTables !== undefined) {
|
|
191
|
+
const pair = (0, pair_tables_js_1.pairBound)(scoredPairTables, coords.cornerPermutation, coords.udEdgePermutation);
|
|
192
|
+
const pairEst = p1Moves.length + pair;
|
|
193
|
+
if (pairEst > est)
|
|
194
|
+
est = pairEst;
|
|
195
|
+
}
|
|
196
|
+
// A lower bound at or above the best total can never strictly improve it.
|
|
197
|
+
if (est >= bestMoves.length)
|
|
198
|
+
continue;
|
|
199
|
+
const pFace = p1Moves.length > 0 ? (0, search_utils_js_1.getMoveFace)(p1Moves[p1Moves.length - 1]) : -1;
|
|
200
|
+
const ppFace = p1Moves.length > 1 ? (0, search_utils_js_1.getMoveFace)(p1Moves[p1Moves.length - 2]) : -1;
|
|
201
|
+
scored.push({ moves: p1Moves, state: s1, est, pFace, ppFace });
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
// Sort ALL candidates globally across depths by admissible total lower bound
|
|
205
|
+
scored.sort((left, right) => left.est - right.est);
|
|
206
|
+
for (const cand of scored) {
|
|
207
|
+
if (budgetExhausted())
|
|
208
|
+
break;
|
|
209
|
+
// Prune: since candidates are sorted by est, no subsequent candidate can beat bestMoves.length
|
|
210
|
+
if (cand.est >= bestMoves.length)
|
|
211
|
+
break;
|
|
212
|
+
const maxP2 = Math.min(bestMoves.length - cand.moves.length - 1, 15);
|
|
213
|
+
if (maxP2 <= 0)
|
|
214
|
+
continue;
|
|
215
|
+
try {
|
|
216
|
+
// Strip any legacy wall `timeoutMs` from the spread: in node mode
|
|
217
|
+
// the wall deadline must not be armed at all (pure function of
|
|
218
|
+
// budgets); in wall mode the per-candidate cap below re-adds it.
|
|
219
|
+
const phase2Source = options.phase2 ?? {};
|
|
220
|
+
const { timeoutMs: legacyPhase2Wall, ...phase2CandidateBase } = phase2Source;
|
|
221
|
+
// Per-candidate budget: node cap (deterministic) when the loop runs
|
|
222
|
+
// on `improveMaxNodes`, else the legacy wall cap.
|
|
223
|
+
const candidateCap = nodeMode
|
|
224
|
+
? {
|
|
225
|
+
maxNodes: Math.max(1, Math.min(options.improvePhase2MaxNodes ?? phase2CandidateBase.maxNodes ?? remainingNodes, remainingNodes))
|
|
226
|
+
}
|
|
227
|
+
: {
|
|
228
|
+
timeoutMs: Math.min(options.improvePhase2TimeoutMs ?? legacyPhase2Wall ?? 25, Math.max(1, deadline - performance.now()))
|
|
229
|
+
};
|
|
230
|
+
const candP2 = await solveCube3Phase2(cand.state, {
|
|
231
|
+
...phase2CandidateBase,
|
|
232
|
+
pairTables: scoredPairTables,
|
|
233
|
+
tables: p2Tables,
|
|
234
|
+
maxDepth: maxP2,
|
|
235
|
+
previousFace: cand.pFace,
|
|
236
|
+
previousPreviousFace: cand.ppFace,
|
|
237
|
+
...candidateCap
|
|
238
|
+
});
|
|
239
|
+
improvementNodes += candP2.visitedNodes;
|
|
240
|
+
spendNodes(candP2.visitedNodes);
|
|
241
|
+
const candTotal = cand.moves.length + candP2.moves.length;
|
|
242
|
+
if (candTotal < bestMoves.length) {
|
|
243
|
+
bestMoves = [...cand.moves, ...candP2.moves];
|
|
244
|
+
bestP1 = {
|
|
245
|
+
moves: cand.moves,
|
|
246
|
+
depth: cand.moves.length,
|
|
247
|
+
state: cand.state,
|
|
248
|
+
coordinates: (0, phase1_js_1.encodeCube3Phase1Coordinates)(cand.state),
|
|
249
|
+
visitedNodes: 0
|
|
250
|
+
};
|
|
251
|
+
bestP2 = candP2;
|
|
252
|
+
if (targetLength !== undefined && bestMoves.length <= targetLength) {
|
|
253
|
+
break;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
catch {
|
|
258
|
+
// continue
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return {
|
|
262
|
+
phase1: bestP1,
|
|
263
|
+
phase2: bestP2,
|
|
264
|
+
moves: bestMoves,
|
|
265
|
+
depth: bestMoves.length,
|
|
266
|
+
state: bestP2.state,
|
|
267
|
+
visitedNodes: phase1.visitedNodes + phase2.visitedNodes + improvementNodes
|
|
268
|
+
};
|
|
269
|
+
}
|
|
119
270
|
return {
|
|
120
271
|
phase1,
|
|
121
272
|
phase2,
|
|
@@ -125,6 +276,39 @@ async function solveCube3State(state, options = {}) {
|
|
|
125
276
|
visitedNodes: phase1.visitedNodes + phase2.visitedNodes
|
|
126
277
|
};
|
|
127
278
|
}
|
|
279
|
+
/**
|
|
280
|
+
* Best-of-N-orientations solve: UD plus rotated copies, mapped back with
|
|
281
|
+
* the inverse rotation. Rotating a solution conjugates the whole picture,
|
|
282
|
+
* so the mapped-back moves solve the original state with their length
|
|
283
|
+
* preserved. Deterministic: ties keep the earliest orientation (UD first).
|
|
284
|
+
*/
|
|
285
|
+
async function solveCube3TripleOrientation(state, options) {
|
|
286
|
+
const count = Math.min(Math.max(options.orientations ?? 1, 1), symmetry_js_1.CUBE3_ORIENTATIONS_24.length);
|
|
287
|
+
const singleOptions = { ...options, orientations: 1 };
|
|
288
|
+
let best = await solveCube3State(state, singleOptions);
|
|
289
|
+
let visitedNodes = best.visitedNodes;
|
|
290
|
+
const scramble = (0, moves_js_1.invertCube3Algorithm)(best.moves);
|
|
291
|
+
const sequences = symmetry_js_1.CUBE3_ORIENTATIONS_24.slice(1, count);
|
|
292
|
+
for (const sequence of sequences) {
|
|
293
|
+
try {
|
|
294
|
+
const rotatedState = (0, moves_js_1.applyCube3Algorithm)((0, state_js_1.createSolvedCube3State)(), (0, symmetry_js_1.rotateCube3AlgorithmBySequence)(scramble, sequence));
|
|
295
|
+
const candidate = await solveCube3State(rotatedState, singleOptions);
|
|
296
|
+
visitedNodes += candidate.visitedNodes;
|
|
297
|
+
if (candidate.moves.length < best.moves.length) {
|
|
298
|
+
best = {
|
|
299
|
+
...candidate,
|
|
300
|
+
moves: (0, symmetry_js_1.rotateCube3AlgorithmBySequence)(candidate.moves, (0, symmetry_js_1.invertCube3RotationSequence)(sequence)),
|
|
301
|
+
depth: candidate.moves.length,
|
|
302
|
+
visitedNodes
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
catch {
|
|
307
|
+
// A flaky orientation must never fail the whole solve.
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return { ...best, visitedNodes };
|
|
311
|
+
}
|
|
128
312
|
function encodeCube3Phase2Coordinates(state) {
|
|
129
313
|
return {
|
|
130
314
|
cornerPermutation: encodeCornerPermutationForPhase2(state),
|
|
@@ -147,10 +331,38 @@ const PHASE2_MOVE_FACES = tables_js_1.CUBE3_PHASE2_MOVES.map((move) => {
|
|
|
147
331
|
const char = move[0];
|
|
148
332
|
return char === "U" ? 0 : char === "D" ? 1 : char === "R" ? 2 : char === "L" ? 3 : char === "F" ? 4 : 5;
|
|
149
333
|
});
|
|
334
|
+
function createPhase2SearchContext(tables, pairTables, maxDepth, deadline, nodeCap = undefined) {
|
|
335
|
+
const scratchLevels = maxDepth + 1;
|
|
336
|
+
const scratchSize = scratchLevels * tables_js_1.CUBE3_PHASE2_MOVES.length;
|
|
337
|
+
return {
|
|
338
|
+
tables,
|
|
339
|
+
pairTables,
|
|
340
|
+
maxDepth,
|
|
341
|
+
deadline,
|
|
342
|
+
nodeCap,
|
|
343
|
+
visitedNodes: 0,
|
|
344
|
+
timedOut: false,
|
|
345
|
+
nodesExceeded: false,
|
|
346
|
+
scratchMove: new Int32Array(scratchSize),
|
|
347
|
+
scratchBound: new Int32Array(scratchSize),
|
|
348
|
+
scratchCp: new Int32Array(scratchSize),
|
|
349
|
+
scratchUep: new Int32Array(scratchSize),
|
|
350
|
+
scratchSp: new Int32Array(scratchSize)
|
|
351
|
+
};
|
|
352
|
+
}
|
|
150
353
|
function searchPhase2(cornerPermutation, udEdgePermutation, slicePermutation, previousFace, previousPreviousFace, remainingDepth, path, context) {
|
|
151
354
|
context.visitedNodes += 1;
|
|
152
|
-
if (context.
|
|
355
|
+
if (context.nodeCap !== undefined &&
|
|
356
|
+
context.visitedNodes > context.nodeCap) {
|
|
357
|
+
context.nodesExceeded = true;
|
|
358
|
+
}
|
|
359
|
+
if (!context.nodesExceeded &&
|
|
360
|
+
context.deadline !== undefined &&
|
|
361
|
+
(context.visitedNodes & 1023) === 0 &&
|
|
362
|
+
performance.now() > context.deadline) {
|
|
153
363
|
context.timedOut = true;
|
|
364
|
+
}
|
|
365
|
+
if (context.timedOut || context.nodesExceeded) {
|
|
154
366
|
return false;
|
|
155
367
|
}
|
|
156
368
|
if (remainingDepth === 0) {
|
|
@@ -166,41 +378,23 @@ function searchPhase2(cornerPermutation, udEdgePermutation, slicePermutation, pr
|
|
|
166
378
|
const spRange = context.tables.moveTables.slicePermutation.range;
|
|
167
379
|
const cpspTable = context.tables.pruningTables.cornerPermutationSlicePermutation.values;
|
|
168
380
|
const epspTable = context.tables.pruningTables.udEdgePermutationSlicePermutation.values;
|
|
381
|
+
const pairTables = context.pairTables;
|
|
169
382
|
const cpOffset = cornerPermutation * moveCount;
|
|
170
383
|
const epOffset = udEdgePermutation * moveCount;
|
|
171
384
|
const spOffset = slicePermutation * moveCount;
|
|
172
|
-
//
|
|
173
|
-
//
|
|
174
|
-
//
|
|
175
|
-
//
|
|
176
|
-
//
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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 = [];
|
|
385
|
+
// Collect the surviving children with the pair-tightened lower bound and
|
|
386
|
+
// explore them in ascending bound order (stable: ties keep the reference
|
|
387
|
+
// move order). Without the pair tables this reduces to ordering by
|
|
388
|
+
// max(cpsp, uepsp) — same depth-minimal solutions, far fewer nodes than
|
|
389
|
+
// the reference traversal. With them, same set and order as before but
|
|
390
|
+
// without per-node allocation.
|
|
391
|
+
const slot = remainingDepth * moveCount;
|
|
392
|
+
const scratchMove = context.scratchMove;
|
|
393
|
+
const scratchBound = context.scratchBound;
|
|
394
|
+
const scratchCp = context.scratchCp;
|
|
395
|
+
const scratchUep = context.scratchUep;
|
|
396
|
+
const scratchSp = context.scratchSp;
|
|
397
|
+
let childCount = 0;
|
|
204
398
|
for (let i = 0; i < validMoves.length; i += 1) {
|
|
205
399
|
const moveIndex = validMoves[i];
|
|
206
400
|
const nextCornerPermutation = cpTable[cpOffset + moveIndex];
|
|
@@ -214,29 +408,52 @@ function searchPhase2(cornerPermutation, udEdgePermutation, slicePermutation, pr
|
|
|
214
408
|
if (nextLowerBound > remainingDepth - 1) {
|
|
215
409
|
continue;
|
|
216
410
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
nextLowerBound
|
|
411
|
+
if (pairTables !== undefined) {
|
|
412
|
+
const pair = (0, pair_tables_js_1.pairBound)(pairTables, nextCornerPermutation, nextUdEdgePermutation);
|
|
413
|
+
if (pair > nextLowerBound) {
|
|
414
|
+
nextLowerBound = pair;
|
|
415
|
+
}
|
|
416
|
+
if (nextLowerBound > remainingDepth - 1) {
|
|
417
|
+
continue;
|
|
418
|
+
}
|
|
220
419
|
}
|
|
221
|
-
|
|
222
|
-
|
|
420
|
+
scratchMove[slot + childCount] = moveIndex;
|
|
421
|
+
scratchBound[slot + childCount] = nextLowerBound;
|
|
422
|
+
scratchCp[slot + childCount] = nextCornerPermutation;
|
|
423
|
+
scratchUep[slot + childCount] = nextUdEdgePermutation;
|
|
424
|
+
scratchSp[slot + childCount] = nextSlicePermutation;
|
|
425
|
+
childCount += 1;
|
|
426
|
+
}
|
|
427
|
+
// Stable insertion sort by bound (n <= 10; ties keep reference order).
|
|
428
|
+
for (let i = 1; i < childCount; i += 1) {
|
|
429
|
+
const move = scratchMove[slot + i];
|
|
430
|
+
const bound = scratchBound[slot + i];
|
|
431
|
+
const cp = scratchCp[slot + i];
|
|
432
|
+
const uep = scratchUep[slot + i];
|
|
433
|
+
const sp = scratchSp[slot + i];
|
|
434
|
+
let j = i - 1;
|
|
435
|
+
while (j >= 0 && scratchBound[slot + j] > bound) {
|
|
436
|
+
scratchMove[slot + j + 1] = scratchMove[slot + j];
|
|
437
|
+
scratchBound[slot + j + 1] = scratchBound[slot + j];
|
|
438
|
+
scratchCp[slot + j + 1] = scratchCp[slot + j];
|
|
439
|
+
scratchUep[slot + j + 1] = scratchUep[slot + j];
|
|
440
|
+
scratchSp[slot + j + 1] = scratchSp[slot + j];
|
|
441
|
+
j -= 1;
|
|
223
442
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
slicePermutation: nextSlicePermutation
|
|
230
|
-
});
|
|
443
|
+
scratchMove[slot + j + 1] = move;
|
|
444
|
+
scratchBound[slot + j + 1] = bound;
|
|
445
|
+
scratchCp[slot + j + 1] = cp;
|
|
446
|
+
scratchUep[slot + j + 1] = uep;
|
|
447
|
+
scratchSp[slot + j + 1] = sp;
|
|
231
448
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
path.push(context.tables.phase2Moves[
|
|
235
|
-
if (searchPhase2(
|
|
449
|
+
for (let i = 0; i < childCount; i += 1) {
|
|
450
|
+
const moveIndex = scratchMove[slot + i];
|
|
451
|
+
path.push(context.tables.phase2Moves[moveIndex]);
|
|
452
|
+
if (searchPhase2(scratchCp[slot + i], scratchUep[slot + i], scratchSp[slot + i], PHASE2_MOVE_FACES[moveIndex], previousFace, remainingDepth - 1, path, context)) {
|
|
236
453
|
return true;
|
|
237
454
|
}
|
|
238
455
|
path.pop();
|
|
239
|
-
if (context.timedOut) {
|
|
456
|
+
if (context.timedOut || context.nodesExceeded) {
|
|
240
457
|
return false;
|
|
241
458
|
}
|
|
242
459
|
}
|
|
@@ -281,13 +498,7 @@ function searchPhase1Interleaved(cornerOrientation, edgeOrientation, udSlice, pr
|
|
|
281
498
|
const g1State = applyCube3Phase2Moves(context.originalState, path);
|
|
282
499
|
const start2 = encodeCube3Phase2Coordinates(g1State);
|
|
283
500
|
const maxPhase2Depth = 6;
|
|
284
|
-
const phase2Context =
|
|
285
|
-
tables: context.phase2Tables,
|
|
286
|
-
pairTables: undefined,
|
|
287
|
-
deadline: context.deadline,
|
|
288
|
-
visitedNodes: 0,
|
|
289
|
-
timedOut: false
|
|
290
|
-
};
|
|
501
|
+
const phase2Context = createPhase2SearchContext(context.phase2Tables, undefined, maxPhase2Depth, context.deadline);
|
|
291
502
|
for (let d2 = 0; d2 <= maxPhase2Depth; d2 += 1) {
|
|
292
503
|
const path2 = [];
|
|
293
504
|
const solved2 = searchPhase2(start2.cornerPermutation, start2.udEdgePermutation, start2.slicePermutation, -1, -1, d2, path2, phase2Context);
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CUBE3_ORIENTATIONS_24 = exports.CUBE3_ROTATION_INVERSE = void 0;
|
|
4
|
+
exports.invertCube3RotationSequence = invertCube3RotationSequence;
|
|
5
|
+
exports.rotateCube3AlgorithmBySequence = rotateCube3AlgorithmBySequence;
|
|
6
|
+
exports.rotateCube3Algorithm = rotateCube3Algorithm;
|
|
7
|
+
const ROT_FACE = {
|
|
8
|
+
"x": { "U": "B", "R": "R", "F": "U", "D": "F", "L": "L", "B": "D" },
|
|
9
|
+
"x'": { "U": "F", "R": "R", "F": "D", "D": "B", "L": "L", "B": "U" },
|
|
10
|
+
"y": { "U": "U", "R": "F", "F": "L", "D": "D", "L": "B", "B": "R" },
|
|
11
|
+
"y'": { "U": "U", "R": "B", "F": "R", "D": "D", "L": "F", "B": "L" },
|
|
12
|
+
"z": { "U": "R", "R": "D", "F": "F", "D": "L", "L": "U", "B": "B" },
|
|
13
|
+
"z'": { "U": "L", "R": "U", "F": "F", "D": "R", "L": "D", "B": "B" },
|
|
14
|
+
"x2": { "U": "D", "R": "R", "F": "B", "D": "U", "L": "L", "B": "F" },
|
|
15
|
+
"y2": { "U": "U", "R": "L", "F": "B", "D": "D", "L": "R", "B": "F" },
|
|
16
|
+
"z2": { "U": "D", "R": "L", "F": "F", "D": "U", "L": "R", "B": "B" }
|
|
17
|
+
};
|
|
18
|
+
const INVERT_ROTATION = {
|
|
19
|
+
"x": "x'", "x'": "x",
|
|
20
|
+
"y": "y'", "y'": "y",
|
|
21
|
+
"z": "z'", "z'": "z",
|
|
22
|
+
"x2": "x2", "y2": "y2", "z2": "z2"
|
|
23
|
+
};
|
|
24
|
+
exports.CUBE3_ROTATION_INVERSE = {
|
|
25
|
+
"x": "x'",
|
|
26
|
+
"x'": "x",
|
|
27
|
+
"z": "z'",
|
|
28
|
+
"z'": "z"
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* 24 whole-cube orientations as rotation sequences, discovered by BFS over
|
|
32
|
+
* the group generated by x/y/z (canonicalized by face map, so all 24 are
|
|
33
|
+
* geometrically distinct by construction).
|
|
34
|
+
* The first three are identity, x, z — so `orientations: 3` keeps the
|
|
35
|
+
* legacy UD/x/z triple exactly.
|
|
36
|
+
*/
|
|
37
|
+
exports.CUBE3_ORIENTATIONS_24 = (() => {
|
|
38
|
+
const faces = ["U", "R", "F", "D", "L", "B"];
|
|
39
|
+
const identity = "URFDLB";
|
|
40
|
+
const composeKey = (key, gen) => {
|
|
41
|
+
const map = ROT_FACE[gen];
|
|
42
|
+
return faces.map((face, index) => map[key[index]]).join("");
|
|
43
|
+
};
|
|
44
|
+
const seen = new Map([[identity, []]]);
|
|
45
|
+
const queue = [identity];
|
|
46
|
+
while (queue.length > 0) {
|
|
47
|
+
const key = queue.shift();
|
|
48
|
+
const sequence = seen.get(key);
|
|
49
|
+
for (const gen of ["x", "y", "z"]) {
|
|
50
|
+
const next = composeKey(key, gen);
|
|
51
|
+
if (!seen.has(next)) {
|
|
52
|
+
seen.set(next, [...sequence, gen]);
|
|
53
|
+
queue.push(next);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const isLegacyTriple = (sequence) => sequence.length === 0 ||
|
|
58
|
+
(sequence.length === 1 && (sequence[0] === "x" || sequence[0] === "z"));
|
|
59
|
+
const rest = [];
|
|
60
|
+
for (const sequence of seen.values()) {
|
|
61
|
+
if (!isLegacyTriple(sequence))
|
|
62
|
+
rest.push(sequence);
|
|
63
|
+
}
|
|
64
|
+
return [[], ["x"], ["z"], ...rest];
|
|
65
|
+
})();
|
|
66
|
+
function invertCube3RotationSequence(sequence) {
|
|
67
|
+
return [...sequence].reverse().map((rotation) => INVERT_ROTATION[rotation]);
|
|
68
|
+
}
|
|
69
|
+
function rotateCube3AlgorithmBySequence(moves, sequence) {
|
|
70
|
+
let result = [...moves];
|
|
71
|
+
for (const rotation of sequence) {
|
|
72
|
+
const map = ROT_FACE[rotation];
|
|
73
|
+
result = result.map((move) => `${map[move[0]]}${move.slice(1)}`);
|
|
74
|
+
}
|
|
75
|
+
return result;
|
|
76
|
+
}
|
|
77
|
+
function rotateCube3Algorithm(moves, rotation) {
|
|
78
|
+
const map = ROT_FACE[rotation];
|
|
79
|
+
return moves.map((move) => `${map[move[0]]}${move.slice(1)}`);
|
|
80
|
+
}
|
|
@@ -16,6 +16,7 @@ const types_js_1 = require("../types.js");
|
|
|
16
16
|
const coordinates_js_1 = require("./coordinates.js");
|
|
17
17
|
const state_js_1 = require("../state.js");
|
|
18
18
|
const generated_assets_js_1 = require("./generated-assets.js");
|
|
19
|
+
const load_once_js_1 = require("../../shared/load-once.js");
|
|
19
20
|
exports.CUBE3_PHASE1_MOVES = types_js_1.CUBE3_MOVES;
|
|
20
21
|
exports.CUBE3_PHASE2_MOVES = [
|
|
21
22
|
"U",
|
|
@@ -29,20 +30,21 @@ exports.CUBE3_PHASE2_MOVES = [
|
|
|
29
30
|
"F2",
|
|
30
31
|
"B2"
|
|
31
32
|
];
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
33
|
+
// Promise slots (shared helper, see shared/load-once.ts): concurrent cold
|
|
34
|
+
// calls share ONE in-flight load instead of racing several loads and
|
|
35
|
+
// clobbering each other's results; a rejection purges the slot so the next
|
|
36
|
+
// call can retry.
|
|
37
|
+
const solverTablesSlot = { promise: undefined };
|
|
38
|
+
const phase1TablesSlot = { promise: undefined };
|
|
39
|
+
const phase2TablesSlot = { promise: undefined };
|
|
40
|
+
function loadCube3SolverTables() {
|
|
41
|
+
return (0, load_once_js_1.loadOnce)(solverTablesSlot, generated_assets_js_1.loadGeneratedCube3SolverTables);
|
|
38
42
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
return cachedPhase1Tables;
|
|
43
|
+
function loadCube3Phase1Tables() {
|
|
44
|
+
return (0, load_once_js_1.loadOnce)(phase1TablesSlot, generated_assets_js_1.loadGeneratedCube3Phase1Tables);
|
|
42
45
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
return cachedPhase2Tables;
|
|
46
|
+
function loadCube3Phase2Tables() {
|
|
47
|
+
return (0, load_once_js_1.loadOnce)(phase2TablesSlot, generated_assets_js_1.loadGeneratedCube3Phase2Tables);
|
|
46
48
|
}
|
|
47
49
|
function generateCube3Phase1Tables() {
|
|
48
50
|
const cornerOrientation = generateCube3MoveTable("cornerOrientation", coordinates_js_1.CUBE3_COORDINATE_RANGES.cornerOrientation, exports.CUBE3_PHASE1_MOVES);
|
|
@@ -47,15 +47,23 @@ function isAllowedRandomCube3State(state) {
|
|
|
47
47
|
!isOneMoveFromSolvedCube3State(state);
|
|
48
48
|
}
|
|
49
49
|
function permutationParity(permutation) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
50
|
+
// Cycle-decomposition parity in O(n) instead of O(n²) inversion counting
|
|
51
|
+
// (identical result: parity = (n − cycles) mod 2).
|
|
52
|
+
const visited = new Array(permutation.length).fill(false);
|
|
53
|
+
let parity = 0;
|
|
54
|
+
for (let start = 0; start < permutation.length; start += 1) {
|
|
55
|
+
if (visited[start] || permutation[start] === start)
|
|
56
|
+
continue;
|
|
57
|
+
let length = 0;
|
|
58
|
+
let current = start;
|
|
59
|
+
while (!visited[current]) {
|
|
60
|
+
visited[current] = true;
|
|
61
|
+
current = permutation[current];
|
|
62
|
+
length += 1;
|
|
56
63
|
}
|
|
64
|
+
parity ^= (length - 1) & 1;
|
|
57
65
|
}
|
|
58
|
-
return
|
|
66
|
+
return parity;
|
|
59
67
|
}
|
|
60
68
|
function validatePermutation(label, permutation, length) {
|
|
61
69
|
const errors = [];
|