@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
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { applyCube3Algorithm } from "../moves.js";
|
|
2
|
-
import { isSolvedCube3State } from "../state.js";
|
|
1
|
+
import { applyCube3Algorithm, invertCube3Algorithm } from "../moves.js";
|
|
2
|
+
import { createSolvedCube3State, isSolvedCube3State } from "../state.js";
|
|
3
3
|
import { encodePermutation, encodeSlicePermutation, encodeUdEdgePermutation } from "./coordinates.js";
|
|
4
|
-
import { isCube3Phase1Solved, solveCube3Phase1 } from "./phase1.js";
|
|
4
|
+
import { isCube3Phase1Solved, solveCube3Phase1, collectCube3Phase1Solutions, encodeCube3Phase1Coordinates } from "./phase1.js";
|
|
5
5
|
import { loadCube3Phase1Tables, loadCube3Phase2Tables, CUBE3_PHASE1_MOVES, CUBE3_PHASE2_MOVES } from "./tables.js";
|
|
6
6
|
import { getMoveFace, getMoveFaceByIndex, shouldSkipCube3SearchMove } from "./search-utils.js";
|
|
7
7
|
import { loadCube3PairTables, pairBound } from "./pair-tables.js";
|
|
8
|
+
import { CUBE3_ORIENTATIONS_24, invertCube3RotationSequence, rotateCube3AlgorithmBySequence } from "./symmetry.js";
|
|
8
9
|
export class Cube3Phase2SearchError extends Error {
|
|
9
10
|
visitedNodes;
|
|
10
11
|
constructor(message, visitedNodes) {
|
|
@@ -41,17 +42,11 @@ export async function solveCube3Phase2(state, options = {}) {
|
|
|
41
42
|
throw new Cube3Phase2SearchError("3x3 Phase 2 requires a state already in G1.", 0);
|
|
42
43
|
}
|
|
43
44
|
const tables = options.tables ?? await loadCube3Phase2Tables();
|
|
44
|
-
const pairTables = options.pairTables ?? await loadCube3PairTables();
|
|
45
|
+
const pairTables = options.pairTables ?? (options.skipPairTables ? undefined : await loadCube3PairTables());
|
|
45
46
|
const maxDepth = options.maxDepth ?? 18;
|
|
46
47
|
const start = encodeCube3Phase2Coordinates(state);
|
|
47
48
|
const deadline = options.timeoutMs === undefined ? undefined : performance.now() + options.timeoutMs;
|
|
48
|
-
const context =
|
|
49
|
-
tables,
|
|
50
|
-
pairTables,
|
|
51
|
-
deadline,
|
|
52
|
-
visitedNodes: 0,
|
|
53
|
-
timedOut: false
|
|
54
|
-
};
|
|
49
|
+
const context = createPhase2SearchContext(tables, pairTables, maxDepth, deadline, options.maxNodes);
|
|
55
50
|
const spRange = tables.moveTables.slicePermutation.range;
|
|
56
51
|
const cpspTable = tables.pruningTables.cornerPermutationSlicePermutation.values;
|
|
57
52
|
const epspTable = tables.pruningTables.udEdgePermutationSlicePermutation.values;
|
|
@@ -82,6 +77,9 @@ export async function solveCube3Phase2(state, options = {}) {
|
|
|
82
77
|
visitedNodes: context.visitedNodes
|
|
83
78
|
};
|
|
84
79
|
}
|
|
80
|
+
if (context.nodesExceeded) {
|
|
81
|
+
throw new Cube3Phase2SearchError(`3x3 Phase 2 node budget exceeded (${options.maxNodes} nodes).`, context.visitedNodes);
|
|
82
|
+
}
|
|
85
83
|
if (context.timedOut) {
|
|
86
84
|
throw new Cube3Phase2SearchError(`3x3 Phase 2 search timed out after ${options.timeoutMs}ms.`, context.visitedNodes);
|
|
87
85
|
}
|
|
@@ -89,6 +87,11 @@ export async function solveCube3Phase2(state, options = {}) {
|
|
|
89
87
|
throw new Cube3Phase2SearchError(`3x3 Phase 2 solution not found within depth ${maxDepth}.`, context.visitedNodes);
|
|
90
88
|
}
|
|
91
89
|
export async function solveCube3State(state, options = {}) {
|
|
90
|
+
const quality = options.quality ?? "short";
|
|
91
|
+
const orientations = options.orientations ?? (quality === "best" ? 3 : 1);
|
|
92
|
+
if (orientations >= 3) {
|
|
93
|
+
return solveCube3TripleOrientation(state, { ...options, orientations });
|
|
94
|
+
}
|
|
92
95
|
const phase1 = await solveCube3Phase1(state, options.phase1);
|
|
93
96
|
let previousFace = -1;
|
|
94
97
|
let previousPreviousFace = -1;
|
|
@@ -107,6 +110,154 @@ export async function solveCube3State(state, options = {}) {
|
|
|
107
110
|
};
|
|
108
111
|
const phase2 = await solveCube3Phase2(phase1.state, phase2Options);
|
|
109
112
|
const moves = [...phase1.moves, ...phase2.moves];
|
|
113
|
+
const targetLength = options.targetLength;
|
|
114
|
+
const extraDepth = options.phase1ExtraDepth ?? (quality === "fast" ? 0 : 1);
|
|
115
|
+
const solutionLimit = options.phase1SolutionLimit ?? (targetLength !== undefined ? 32 : 8);
|
|
116
|
+
const improveDepths = targetLength !== undefined ? Math.max(4, extraDepth) : extraDepth;
|
|
117
|
+
const needsTarget = targetLength !== undefined && moves.length > targetLength;
|
|
118
|
+
if (needsTarget || (targetLength === undefined && extraDepth > 0)) {
|
|
119
|
+
const p1Tables = options.phase1?.tables ?? await loadCube3Phase1Tables();
|
|
120
|
+
const p2Tables = options.phase2?.tables ?? await loadCube3Phase2Tables();
|
|
121
|
+
const scoredPairTables = options.phase2?.pairTables ??
|
|
122
|
+
(options.phase2?.skipPairTables ? undefined : await loadCube3PairTables());
|
|
123
|
+
// Work budget for the improvement loop. Preferred: `improveMaxNodes`
|
|
124
|
+
// (deterministic node counter — the wall deadline stays unarmed so the
|
|
125
|
+
// outcome cannot depend on CPU load/clock). Legacy: `timeoutMs ?? 800`
|
|
126
|
+
// wall deadline, kept for existing callers.
|
|
127
|
+
const nodeMode = options.improveMaxNodes !== undefined;
|
|
128
|
+
let remainingNodes = options.improveMaxNodes ?? 0;
|
|
129
|
+
const deadline = nodeMode ? undefined : performance.now() + (options.timeoutMs ?? 800);
|
|
130
|
+
const budgetExhausted = () => nodeMode ? remainingNodes <= 0 : performance.now() > deadline;
|
|
131
|
+
const spendNodes = (used) => {
|
|
132
|
+
if (nodeMode) {
|
|
133
|
+
remainingNodes = Math.max(0, remainingNodes - used);
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
const p2SpRange = p2Tables.moveTables.slicePermutation.range;
|
|
137
|
+
const p2cpsp = p2Tables.pruningTables.cornerPermutationSlicePermutation.values;
|
|
138
|
+
const p2uepsp = p2Tables.pruningTables.udEdgePermutationSlicePermutation.values;
|
|
139
|
+
const seenG1 = new Set();
|
|
140
|
+
let bestP1 = phase1;
|
|
141
|
+
let bestP2 = phase2;
|
|
142
|
+
let bestMoves = moves;
|
|
143
|
+
let improvementNodes = 0;
|
|
144
|
+
const minDepth = phase1.depth;
|
|
145
|
+
const scored = [];
|
|
146
|
+
for (let d = minDepth; d <= minDepth + improveDepths && d < bestMoves.length; d++) {
|
|
147
|
+
if (budgetExhausted())
|
|
148
|
+
break;
|
|
149
|
+
const { solutions: p1Solutions, visitedNodes } = await collectCube3Phase1Solutions(state, d, {
|
|
150
|
+
tables: p1Tables,
|
|
151
|
+
limit: solutionLimit,
|
|
152
|
+
...(nodeMode
|
|
153
|
+
? { maxNodes: Math.max(1, remainingNodes) }
|
|
154
|
+
: { timeoutMs: Math.max(1, deadline - performance.now()) })
|
|
155
|
+
});
|
|
156
|
+
improvementNodes += visitedNodes;
|
|
157
|
+
spendNodes(visitedNodes);
|
|
158
|
+
if (budgetExhausted())
|
|
159
|
+
break;
|
|
160
|
+
for (const p1Moves of p1Solutions) {
|
|
161
|
+
if (budgetExhausted())
|
|
162
|
+
break;
|
|
163
|
+
if (p1Moves.length >= bestMoves.length)
|
|
164
|
+
continue;
|
|
165
|
+
// Skip the phase-1 solution the initial search already completed.
|
|
166
|
+
if (p1Moves.length === phase1.moves.length &&
|
|
167
|
+
p1Moves.every((move, index) => move === phase1.moves[index])) {
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
const s1 = applyCube3Algorithm(state, p1Moves);
|
|
171
|
+
const coords = encodeCube3Phase2Coordinates(s1);
|
|
172
|
+
const estKey = `${coords.cornerPermutation},${coords.udEdgePermutation},${coords.slicePermutation}`;
|
|
173
|
+
if (seenG1.has(estKey))
|
|
174
|
+
continue;
|
|
175
|
+
seenG1.add(estKey);
|
|
176
|
+
const cpIndex = coords.cornerPermutation * p2SpRange + coords.slicePermutation;
|
|
177
|
+
const epIndex = coords.udEdgePermutation * p2SpRange + coords.slicePermutation;
|
|
178
|
+
const cpDist = (p2cpsp[cpIndex >> 1] >> ((cpIndex & 1) << 2)) & 0x0f;
|
|
179
|
+
const epDist = (p2uepsp[epIndex >> 1] >> ((epIndex & 1) << 2)) & 0x0f;
|
|
180
|
+
let est = p1Moves.length + (cpDist > epDist ? cpDist : epDist);
|
|
181
|
+
if (scoredPairTables !== undefined) {
|
|
182
|
+
const pair = pairBound(scoredPairTables, coords.cornerPermutation, coords.udEdgePermutation);
|
|
183
|
+
const pairEst = p1Moves.length + pair;
|
|
184
|
+
if (pairEst > est)
|
|
185
|
+
est = pairEst;
|
|
186
|
+
}
|
|
187
|
+
// A lower bound at or above the best total can never strictly improve it.
|
|
188
|
+
if (est >= bestMoves.length)
|
|
189
|
+
continue;
|
|
190
|
+
const pFace = p1Moves.length > 0 ? getMoveFace(p1Moves[p1Moves.length - 1]) : -1;
|
|
191
|
+
const ppFace = p1Moves.length > 1 ? getMoveFace(p1Moves[p1Moves.length - 2]) : -1;
|
|
192
|
+
scored.push({ moves: p1Moves, state: s1, est, pFace, ppFace });
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
// Sort ALL candidates globally across depths by admissible total lower bound
|
|
196
|
+
scored.sort((left, right) => left.est - right.est);
|
|
197
|
+
for (const cand of scored) {
|
|
198
|
+
if (budgetExhausted())
|
|
199
|
+
break;
|
|
200
|
+
// Prune: since candidates are sorted by est, no subsequent candidate can beat bestMoves.length
|
|
201
|
+
if (cand.est >= bestMoves.length)
|
|
202
|
+
break;
|
|
203
|
+
const maxP2 = Math.min(bestMoves.length - cand.moves.length - 1, 15);
|
|
204
|
+
if (maxP2 <= 0)
|
|
205
|
+
continue;
|
|
206
|
+
try {
|
|
207
|
+
// Strip any legacy wall `timeoutMs` from the spread: in node mode
|
|
208
|
+
// the wall deadline must not be armed at all (pure function of
|
|
209
|
+
// budgets); in wall mode the per-candidate cap below re-adds it.
|
|
210
|
+
const phase2Source = options.phase2 ?? {};
|
|
211
|
+
const { timeoutMs: legacyPhase2Wall, ...phase2CandidateBase } = phase2Source;
|
|
212
|
+
// Per-candidate budget: node cap (deterministic) when the loop runs
|
|
213
|
+
// on `improveMaxNodes`, else the legacy wall cap.
|
|
214
|
+
const candidateCap = nodeMode
|
|
215
|
+
? {
|
|
216
|
+
maxNodes: Math.max(1, Math.min(options.improvePhase2MaxNodes ?? phase2CandidateBase.maxNodes ?? remainingNodes, remainingNodes))
|
|
217
|
+
}
|
|
218
|
+
: {
|
|
219
|
+
timeoutMs: Math.min(options.improvePhase2TimeoutMs ?? legacyPhase2Wall ?? 25, Math.max(1, deadline - performance.now()))
|
|
220
|
+
};
|
|
221
|
+
const candP2 = await solveCube3Phase2(cand.state, {
|
|
222
|
+
...phase2CandidateBase,
|
|
223
|
+
pairTables: scoredPairTables,
|
|
224
|
+
tables: p2Tables,
|
|
225
|
+
maxDepth: maxP2,
|
|
226
|
+
previousFace: cand.pFace,
|
|
227
|
+
previousPreviousFace: cand.ppFace,
|
|
228
|
+
...candidateCap
|
|
229
|
+
});
|
|
230
|
+
improvementNodes += candP2.visitedNodes;
|
|
231
|
+
spendNodes(candP2.visitedNodes);
|
|
232
|
+
const candTotal = cand.moves.length + candP2.moves.length;
|
|
233
|
+
if (candTotal < bestMoves.length) {
|
|
234
|
+
bestMoves = [...cand.moves, ...candP2.moves];
|
|
235
|
+
bestP1 = {
|
|
236
|
+
moves: cand.moves,
|
|
237
|
+
depth: cand.moves.length,
|
|
238
|
+
state: cand.state,
|
|
239
|
+
coordinates: encodeCube3Phase1Coordinates(cand.state),
|
|
240
|
+
visitedNodes: 0
|
|
241
|
+
};
|
|
242
|
+
bestP2 = candP2;
|
|
243
|
+
if (targetLength !== undefined && bestMoves.length <= targetLength) {
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
catch {
|
|
249
|
+
// continue
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return {
|
|
253
|
+
phase1: bestP1,
|
|
254
|
+
phase2: bestP2,
|
|
255
|
+
moves: bestMoves,
|
|
256
|
+
depth: bestMoves.length,
|
|
257
|
+
state: bestP2.state,
|
|
258
|
+
visitedNodes: phase1.visitedNodes + phase2.visitedNodes + improvementNodes
|
|
259
|
+
};
|
|
260
|
+
}
|
|
110
261
|
return {
|
|
111
262
|
phase1,
|
|
112
263
|
phase2,
|
|
@@ -116,6 +267,39 @@ export async function solveCube3State(state, options = {}) {
|
|
|
116
267
|
visitedNodes: phase1.visitedNodes + phase2.visitedNodes
|
|
117
268
|
};
|
|
118
269
|
}
|
|
270
|
+
/**
|
|
271
|
+
* Best-of-N-orientations solve: UD plus rotated copies, mapped back with
|
|
272
|
+
* the inverse rotation. Rotating a solution conjugates the whole picture,
|
|
273
|
+
* so the mapped-back moves solve the original state with their length
|
|
274
|
+
* preserved. Deterministic: ties keep the earliest orientation (UD first).
|
|
275
|
+
*/
|
|
276
|
+
async function solveCube3TripleOrientation(state, options) {
|
|
277
|
+
const count = Math.min(Math.max(options.orientations ?? 1, 1), CUBE3_ORIENTATIONS_24.length);
|
|
278
|
+
const singleOptions = { ...options, orientations: 1 };
|
|
279
|
+
let best = await solveCube3State(state, singleOptions);
|
|
280
|
+
let visitedNodes = best.visitedNodes;
|
|
281
|
+
const scramble = invertCube3Algorithm(best.moves);
|
|
282
|
+
const sequences = CUBE3_ORIENTATIONS_24.slice(1, count);
|
|
283
|
+
for (const sequence of sequences) {
|
|
284
|
+
try {
|
|
285
|
+
const rotatedState = applyCube3Algorithm(createSolvedCube3State(), rotateCube3AlgorithmBySequence(scramble, sequence));
|
|
286
|
+
const candidate = await solveCube3State(rotatedState, singleOptions);
|
|
287
|
+
visitedNodes += candidate.visitedNodes;
|
|
288
|
+
if (candidate.moves.length < best.moves.length) {
|
|
289
|
+
best = {
|
|
290
|
+
...candidate,
|
|
291
|
+
moves: rotateCube3AlgorithmBySequence(candidate.moves, invertCube3RotationSequence(sequence)),
|
|
292
|
+
depth: candidate.moves.length,
|
|
293
|
+
visitedNodes
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
catch {
|
|
298
|
+
// A flaky orientation must never fail the whole solve.
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return { ...best, visitedNodes };
|
|
302
|
+
}
|
|
119
303
|
export function encodeCube3Phase2Coordinates(state) {
|
|
120
304
|
return {
|
|
121
305
|
cornerPermutation: encodeCornerPermutationForPhase2(state),
|
|
@@ -138,10 +322,38 @@ const PHASE2_MOVE_FACES = CUBE3_PHASE2_MOVES.map((move) => {
|
|
|
138
322
|
const char = move[0];
|
|
139
323
|
return char === "U" ? 0 : char === "D" ? 1 : char === "R" ? 2 : char === "L" ? 3 : char === "F" ? 4 : 5;
|
|
140
324
|
});
|
|
325
|
+
function createPhase2SearchContext(tables, pairTables, maxDepth, deadline, nodeCap = undefined) {
|
|
326
|
+
const scratchLevels = maxDepth + 1;
|
|
327
|
+
const scratchSize = scratchLevels * CUBE3_PHASE2_MOVES.length;
|
|
328
|
+
return {
|
|
329
|
+
tables,
|
|
330
|
+
pairTables,
|
|
331
|
+
maxDepth,
|
|
332
|
+
deadline,
|
|
333
|
+
nodeCap,
|
|
334
|
+
visitedNodes: 0,
|
|
335
|
+
timedOut: false,
|
|
336
|
+
nodesExceeded: false,
|
|
337
|
+
scratchMove: new Int32Array(scratchSize),
|
|
338
|
+
scratchBound: new Int32Array(scratchSize),
|
|
339
|
+
scratchCp: new Int32Array(scratchSize),
|
|
340
|
+
scratchUep: new Int32Array(scratchSize),
|
|
341
|
+
scratchSp: new Int32Array(scratchSize)
|
|
342
|
+
};
|
|
343
|
+
}
|
|
141
344
|
function searchPhase2(cornerPermutation, udEdgePermutation, slicePermutation, previousFace, previousPreviousFace, remainingDepth, path, context) {
|
|
142
345
|
context.visitedNodes += 1;
|
|
143
|
-
if (context.
|
|
346
|
+
if (context.nodeCap !== undefined &&
|
|
347
|
+
context.visitedNodes > context.nodeCap) {
|
|
348
|
+
context.nodesExceeded = true;
|
|
349
|
+
}
|
|
350
|
+
if (!context.nodesExceeded &&
|
|
351
|
+
context.deadline !== undefined &&
|
|
352
|
+
(context.visitedNodes & 1023) === 0 &&
|
|
353
|
+
performance.now() > context.deadline) {
|
|
144
354
|
context.timedOut = true;
|
|
355
|
+
}
|
|
356
|
+
if (context.timedOut || context.nodesExceeded) {
|
|
145
357
|
return false;
|
|
146
358
|
}
|
|
147
359
|
if (remainingDepth === 0) {
|
|
@@ -157,41 +369,23 @@ function searchPhase2(cornerPermutation, udEdgePermutation, slicePermutation, pr
|
|
|
157
369
|
const spRange = context.tables.moveTables.slicePermutation.range;
|
|
158
370
|
const cpspTable = context.tables.pruningTables.cornerPermutationSlicePermutation.values;
|
|
159
371
|
const epspTable = context.tables.pruningTables.udEdgePermutationSlicePermutation.values;
|
|
372
|
+
const pairTables = context.pairTables;
|
|
160
373
|
const cpOffset = cornerPermutation * moveCount;
|
|
161
374
|
const epOffset = udEdgePermutation * moveCount;
|
|
162
375
|
const spOffset = slicePermutation * moveCount;
|
|
163
|
-
//
|
|
164
|
-
//
|
|
165
|
-
//
|
|
166
|
-
//
|
|
167
|
-
//
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
const cpDist = (cpspTable[combinedCPIndex >> 1] >> ((combinedCPIndex & 1) << 2)) & 0x0f;
|
|
177
|
-
const epDist = (epspTable[combinedEPIndex >> 1] >> ((combinedEPIndex & 1) << 2)) & 0x0f;
|
|
178
|
-
const nextLowerBound = cpDist > epDist ? cpDist : epDist;
|
|
179
|
-
if (nextLowerBound > remainingDepth - 1) {
|
|
180
|
-
continue;
|
|
181
|
-
}
|
|
182
|
-
path.push(context.tables.phase2Moves[moveIndex]);
|
|
183
|
-
if (searchPhase2(nextCornerPermutation, nextUdEdgePermutation, nextSlicePermutation, PHASE2_MOVE_FACES[moveIndex], previousFace, remainingDepth - 1, path, context)) {
|
|
184
|
-
return true;
|
|
185
|
-
}
|
|
186
|
-
path.pop();
|
|
187
|
-
if (context.timedOut) {
|
|
188
|
-
return false;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
return false;
|
|
192
|
-
}
|
|
193
|
-
const pairTables = context.pairTables;
|
|
194
|
-
const children = [];
|
|
376
|
+
// Collect the surviving children with the pair-tightened lower bound and
|
|
377
|
+
// explore them in ascending bound order (stable: ties keep the reference
|
|
378
|
+
// move order). Without the pair tables this reduces to ordering by
|
|
379
|
+
// max(cpsp, uepsp) — same depth-minimal solutions, far fewer nodes than
|
|
380
|
+
// the reference traversal. With them, same set and order as before but
|
|
381
|
+
// without per-node allocation.
|
|
382
|
+
const slot = remainingDepth * moveCount;
|
|
383
|
+
const scratchMove = context.scratchMove;
|
|
384
|
+
const scratchBound = context.scratchBound;
|
|
385
|
+
const scratchCp = context.scratchCp;
|
|
386
|
+
const scratchUep = context.scratchUep;
|
|
387
|
+
const scratchSp = context.scratchSp;
|
|
388
|
+
let childCount = 0;
|
|
195
389
|
for (let i = 0; i < validMoves.length; i += 1) {
|
|
196
390
|
const moveIndex = validMoves[i];
|
|
197
391
|
const nextCornerPermutation = cpTable[cpOffset + moveIndex];
|
|
@@ -205,29 +399,52 @@ function searchPhase2(cornerPermutation, udEdgePermutation, slicePermutation, pr
|
|
|
205
399
|
if (nextLowerBound > remainingDepth - 1) {
|
|
206
400
|
continue;
|
|
207
401
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
nextLowerBound
|
|
402
|
+
if (pairTables !== undefined) {
|
|
403
|
+
const pair = pairBound(pairTables, nextCornerPermutation, nextUdEdgePermutation);
|
|
404
|
+
if (pair > nextLowerBound) {
|
|
405
|
+
nextLowerBound = pair;
|
|
406
|
+
}
|
|
407
|
+
if (nextLowerBound > remainingDepth - 1) {
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
211
410
|
}
|
|
212
|
-
|
|
213
|
-
|
|
411
|
+
scratchMove[slot + childCount] = moveIndex;
|
|
412
|
+
scratchBound[slot + childCount] = nextLowerBound;
|
|
413
|
+
scratchCp[slot + childCount] = nextCornerPermutation;
|
|
414
|
+
scratchUep[slot + childCount] = nextUdEdgePermutation;
|
|
415
|
+
scratchSp[slot + childCount] = nextSlicePermutation;
|
|
416
|
+
childCount += 1;
|
|
417
|
+
}
|
|
418
|
+
// Stable insertion sort by bound (n <= 10; ties keep reference order).
|
|
419
|
+
for (let i = 1; i < childCount; i += 1) {
|
|
420
|
+
const move = scratchMove[slot + i];
|
|
421
|
+
const bound = scratchBound[slot + i];
|
|
422
|
+
const cp = scratchCp[slot + i];
|
|
423
|
+
const uep = scratchUep[slot + i];
|
|
424
|
+
const sp = scratchSp[slot + i];
|
|
425
|
+
let j = i - 1;
|
|
426
|
+
while (j >= 0 && scratchBound[slot + j] > bound) {
|
|
427
|
+
scratchMove[slot + j + 1] = scratchMove[slot + j];
|
|
428
|
+
scratchBound[slot + j + 1] = scratchBound[slot + j];
|
|
429
|
+
scratchCp[slot + j + 1] = scratchCp[slot + j];
|
|
430
|
+
scratchUep[slot + j + 1] = scratchUep[slot + j];
|
|
431
|
+
scratchSp[slot + j + 1] = scratchSp[slot + j];
|
|
432
|
+
j -= 1;
|
|
214
433
|
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
slicePermutation: nextSlicePermutation
|
|
221
|
-
});
|
|
434
|
+
scratchMove[slot + j + 1] = move;
|
|
435
|
+
scratchBound[slot + j + 1] = bound;
|
|
436
|
+
scratchCp[slot + j + 1] = cp;
|
|
437
|
+
scratchUep[slot + j + 1] = uep;
|
|
438
|
+
scratchSp[slot + j + 1] = sp;
|
|
222
439
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
path.push(context.tables.phase2Moves[
|
|
226
|
-
if (searchPhase2(
|
|
440
|
+
for (let i = 0; i < childCount; i += 1) {
|
|
441
|
+
const moveIndex = scratchMove[slot + i];
|
|
442
|
+
path.push(context.tables.phase2Moves[moveIndex]);
|
|
443
|
+
if (searchPhase2(scratchCp[slot + i], scratchUep[slot + i], scratchSp[slot + i], PHASE2_MOVE_FACES[moveIndex], previousFace, remainingDepth - 1, path, context)) {
|
|
227
444
|
return true;
|
|
228
445
|
}
|
|
229
446
|
path.pop();
|
|
230
|
-
if (context.timedOut) {
|
|
447
|
+
if (context.timedOut || context.nodesExceeded) {
|
|
231
448
|
return false;
|
|
232
449
|
}
|
|
233
450
|
}
|
|
@@ -272,13 +489,7 @@ function searchPhase1Interleaved(cornerOrientation, edgeOrientation, udSlice, pr
|
|
|
272
489
|
const g1State = applyCube3Phase2Moves(context.originalState, path);
|
|
273
490
|
const start2 = encodeCube3Phase2Coordinates(g1State);
|
|
274
491
|
const maxPhase2Depth = 6;
|
|
275
|
-
const phase2Context =
|
|
276
|
-
tables: context.phase2Tables,
|
|
277
|
-
pairTables: undefined,
|
|
278
|
-
deadline: context.deadline,
|
|
279
|
-
visitedNodes: 0,
|
|
280
|
-
timedOut: false
|
|
281
|
-
};
|
|
492
|
+
const phase2Context = createPhase2SearchContext(context.phase2Tables, undefined, maxPhase2Depth, context.deadline);
|
|
282
493
|
for (let d2 = 0; d2 <= maxPhase2Depth; d2 += 1) {
|
|
283
494
|
const path2 = [];
|
|
284
495
|
const solved2 = searchPhase2(start2.cornerPermutation, start2.udEdgePermutation, start2.slicePermutation, -1, -1, d2, path2, phase2Context);
|
|
@@ -5,5 +5,5 @@ export declare function getMoveFaceByIndex(moveIndex: number, moves: readonly Cu
|
|
|
5
5
|
export declare const ARE_OPPOSITE_FACES: readonly boolean[];
|
|
6
6
|
export declare function areOppositeFaces(left: Cube3SearchFace, right: Cube3SearchFace): boolean;
|
|
7
7
|
export declare function shouldSkipCube3SearchMove(face: Cube3SearchFace, previousFace: Cube3SearchFace | undefined, previousPreviousFace: Cube3SearchFace | undefined): boolean;
|
|
8
|
-
export declare function lookupMoveTableByIndex(values: Int32Array, coordinate: number, moveIndex: number, moveCount: number): number;
|
|
8
|
+
export declare function lookupMoveTableByIndex(values: Int32Array | Uint16Array, coordinate: number, moveIndex: number, moveCount: number): number;
|
|
9
9
|
//# sourceMappingURL=search-utils.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search-utils.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube3/solver/search-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC;AAYrC,wBAAgB,WAAW,CAAC,IAAI,EAAE,SAAS,GAAG,eAAe,CAE5D;AAED,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,SAAS,EAAE,GAAG,eAAe,CAElG;AAED,eAAO,MAAM,kBAAkB,EAAE,SAAS,OAAO,EAOhD,CAAC;AAEF,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,eAAe,GAAG,OAAO,CAEvF;AAED,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,eAAe,EACrB,YAAY,EAAE,eAAe,GAAG,SAAS,EACzC,oBAAoB,EAAE,eAAe,GAAG,SAAS,GAChD,OAAO,CAcT;AAED,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,UAAU,
|
|
1
|
+
{"version":3,"file":"search-utils.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube3/solver/search-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC;AAYrC,wBAAgB,WAAW,CAAC,IAAI,EAAE,SAAS,GAAG,eAAe,CAE5D;AAED,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,SAAS,EAAE,GAAG,eAAe,CAElG;AAED,eAAO,MAAM,kBAAkB,EAAE,SAAS,OAAO,EAOhD,CAAC;AAEF,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,eAAe,GAAG,OAAO,CAEvF;AAED,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,eAAe,EACrB,YAAY,EAAE,eAAe,GAAG,SAAS,EACzC,oBAAoB,EAAE,eAAe,GAAG,SAAS,GAChD,OAAO,CAcT;AAED,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,UAAU,GAAG,WAAW,EAChC,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB,MAAM,CAER"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Cube3Move } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Whole-cube rotations for 3x3 move sequences.
|
|
4
|
+
*
|
|
5
|
+
* Face maps ported from the 4x4 solver's `ROT_MAP`
|
|
6
|
+
* (`puzzles/cube4/reduction/solve.ts`), restricted to the six basic faces —
|
|
7
|
+
* kept cube3-local so `solver/` never imports from `cube4` (which itself
|
|
8
|
+
* depends on this solver).
|
|
9
|
+
*/
|
|
10
|
+
export type Cube3Rotation = "x" | "x'" | "z" | "z'";
|
|
11
|
+
export declare const CUBE3_ROTATION_INVERSE: Record<Cube3Rotation, Cube3Rotation>;
|
|
12
|
+
export type Cube3RotationSequence = readonly string[];
|
|
13
|
+
/**
|
|
14
|
+
* 24 whole-cube orientations as rotation sequences, discovered by BFS over
|
|
15
|
+
* the group generated by x/y/z (canonicalized by face map, so all 24 are
|
|
16
|
+
* geometrically distinct by construction).
|
|
17
|
+
* The first three are identity, x, z — so `orientations: 3` keeps the
|
|
18
|
+
* legacy UD/x/z triple exactly.
|
|
19
|
+
*/
|
|
20
|
+
export declare const CUBE3_ORIENTATIONS_24: readonly Cube3RotationSequence[];
|
|
21
|
+
export declare function invertCube3RotationSequence(sequence: Cube3RotationSequence): string[];
|
|
22
|
+
export declare function rotateCube3AlgorithmBySequence(moves: readonly Cube3Move[], sequence: Cube3RotationSequence): Cube3Move[];
|
|
23
|
+
export declare function rotateCube3Algorithm(moves: readonly Cube3Move[], rotation: Cube3Rotation): Cube3Move[];
|
|
24
|
+
//# sourceMappingURL=symmetry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"symmetry.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube3/solver/symmetry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C;;;;;;;GAOG;AACH,MAAM,MAAM,aAAa,GAAG,GAAG,GAAG,IAAI,GAAG,GAAG,GAAG,IAAI,CAAC;AAqBpD,eAAO,MAAM,sBAAsB,EAAE,MAAM,CAAC,aAAa,EAAE,aAAa,CAKvE,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG,SAAS,MAAM,EAAE,CAAC;AAEtD;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,EAAE,SAAS,qBAAqB,EA4B9D,CAAC;AAEL,wBAAgB,2BAA2B,CAAC,QAAQ,EAAE,qBAAqB,GAAG,MAAM,EAAE,CAErF;AAED,wBAAgB,8BAA8B,CAC5C,KAAK,EAAE,SAAS,SAAS,EAAE,EAC3B,QAAQ,EAAE,qBAAqB,GAC9B,SAAS,EAAE,CAOb;AAED,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,SAAS,SAAS,EAAE,EAC3B,QAAQ,EAAE,aAAa,GACtB,SAAS,EAAE,CAGb"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
const ROT_FACE = {
|
|
2
|
+
"x": { "U": "B", "R": "R", "F": "U", "D": "F", "L": "L", "B": "D" },
|
|
3
|
+
"x'": { "U": "F", "R": "R", "F": "D", "D": "B", "L": "L", "B": "U" },
|
|
4
|
+
"y": { "U": "U", "R": "F", "F": "L", "D": "D", "L": "B", "B": "R" },
|
|
5
|
+
"y'": { "U": "U", "R": "B", "F": "R", "D": "D", "L": "F", "B": "L" },
|
|
6
|
+
"z": { "U": "R", "R": "D", "F": "F", "D": "L", "L": "U", "B": "B" },
|
|
7
|
+
"z'": { "U": "L", "R": "U", "F": "F", "D": "R", "L": "D", "B": "B" },
|
|
8
|
+
"x2": { "U": "D", "R": "R", "F": "B", "D": "U", "L": "L", "B": "F" },
|
|
9
|
+
"y2": { "U": "U", "R": "L", "F": "B", "D": "D", "L": "R", "B": "F" },
|
|
10
|
+
"z2": { "U": "D", "R": "L", "F": "F", "D": "U", "L": "R", "B": "B" }
|
|
11
|
+
};
|
|
12
|
+
const INVERT_ROTATION = {
|
|
13
|
+
"x": "x'", "x'": "x",
|
|
14
|
+
"y": "y'", "y'": "y",
|
|
15
|
+
"z": "z'", "z'": "z",
|
|
16
|
+
"x2": "x2", "y2": "y2", "z2": "z2"
|
|
17
|
+
};
|
|
18
|
+
export const CUBE3_ROTATION_INVERSE = {
|
|
19
|
+
"x": "x'",
|
|
20
|
+
"x'": "x",
|
|
21
|
+
"z": "z'",
|
|
22
|
+
"z'": "z"
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* 24 whole-cube orientations as rotation sequences, discovered by BFS over
|
|
26
|
+
* the group generated by x/y/z (canonicalized by face map, so all 24 are
|
|
27
|
+
* geometrically distinct by construction).
|
|
28
|
+
* The first three are identity, x, z — so `orientations: 3` keeps the
|
|
29
|
+
* legacy UD/x/z triple exactly.
|
|
30
|
+
*/
|
|
31
|
+
export const CUBE3_ORIENTATIONS_24 = (() => {
|
|
32
|
+
const faces = ["U", "R", "F", "D", "L", "B"];
|
|
33
|
+
const identity = "URFDLB";
|
|
34
|
+
const composeKey = (key, gen) => {
|
|
35
|
+
const map = ROT_FACE[gen];
|
|
36
|
+
return faces.map((face, index) => map[key[index]]).join("");
|
|
37
|
+
};
|
|
38
|
+
const seen = new Map([[identity, []]]);
|
|
39
|
+
const queue = [identity];
|
|
40
|
+
while (queue.length > 0) {
|
|
41
|
+
const key = queue.shift();
|
|
42
|
+
const sequence = seen.get(key);
|
|
43
|
+
for (const gen of ["x", "y", "z"]) {
|
|
44
|
+
const next = composeKey(key, gen);
|
|
45
|
+
if (!seen.has(next)) {
|
|
46
|
+
seen.set(next, [...sequence, gen]);
|
|
47
|
+
queue.push(next);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const isLegacyTriple = (sequence) => sequence.length === 0 ||
|
|
52
|
+
(sequence.length === 1 && (sequence[0] === "x" || sequence[0] === "z"));
|
|
53
|
+
const rest = [];
|
|
54
|
+
for (const sequence of seen.values()) {
|
|
55
|
+
if (!isLegacyTriple(sequence))
|
|
56
|
+
rest.push(sequence);
|
|
57
|
+
}
|
|
58
|
+
return [[], ["x"], ["z"], ...rest];
|
|
59
|
+
})();
|
|
60
|
+
export function invertCube3RotationSequence(sequence) {
|
|
61
|
+
return [...sequence].reverse().map((rotation) => INVERT_ROTATION[rotation]);
|
|
62
|
+
}
|
|
63
|
+
export function rotateCube3AlgorithmBySequence(moves, sequence) {
|
|
64
|
+
let result = [...moves];
|
|
65
|
+
for (const rotation of sequence) {
|
|
66
|
+
const map = ROT_FACE[rotation];
|
|
67
|
+
result = result.map((move) => `${map[move[0]]}${move.slice(1)}`);
|
|
68
|
+
}
|
|
69
|
+
return result;
|
|
70
|
+
}
|
|
71
|
+
export function rotateCube3Algorithm(moves, rotation) {
|
|
72
|
+
const map = ROT_FACE[rotation];
|
|
73
|
+
return moves.map((move) => `${map[move[0]]}${move.slice(1)}`);
|
|
74
|
+
}
|
|
@@ -7,7 +7,12 @@ export interface Cube3MoveTable {
|
|
|
7
7
|
moves: readonly Cube3Move[];
|
|
8
8
|
range: number;
|
|
9
9
|
solvedCoordinate: number;
|
|
10
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Move-table values. Every coordinate fits in 16 bits (max range 40320),
|
|
12
|
+
* so loaders store `Uint16Array` (half the RAM/cache of the int32 wire
|
|
13
|
+
* format); table generation still builds `Int32Array`.
|
|
14
|
+
*/
|
|
15
|
+
values: Int32Array | Uint16Array;
|
|
11
16
|
checksum: number;
|
|
12
17
|
}
|
|
13
18
|
export interface Cube3PruningTable {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tables.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube3/solver/tables.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAM7C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"tables.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube3/solver/tables.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,6 +2,7 @@ import { CUBE3_MOVES } from "../types.js";
|
|
|
2
2
|
import { CUBE3_COORDINATE_RANGES, encodeCube3Coordinates, moveCoordinate } from "./coordinates.js";
|
|
3
3
|
import { createSolvedCube3State } from "../state.js";
|
|
4
4
|
import { loadGeneratedCube3Phase1Tables, loadGeneratedCube3Phase2Tables, loadGeneratedCube3SolverTables } from "./generated-assets.js";
|
|
5
|
+
import { loadOnce } from "../../shared/load-once.js";
|
|
5
6
|
export const CUBE3_PHASE1_MOVES = CUBE3_MOVES;
|
|
6
7
|
export const CUBE3_PHASE2_MOVES = [
|
|
7
8
|
"U",
|
|
@@ -15,20 +16,21 @@ export const CUBE3_PHASE2_MOVES = [
|
|
|
15
16
|
"F2",
|
|
16
17
|
"B2"
|
|
17
18
|
];
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
// Promise slots (shared helper, see shared/load-once.ts): concurrent cold
|
|
20
|
+
// calls share ONE in-flight load instead of racing several loads and
|
|
21
|
+
// clobbering each other's results; a rejection purges the slot so the next
|
|
22
|
+
// call can retry.
|
|
23
|
+
const solverTablesSlot = { promise: undefined };
|
|
24
|
+
const phase1TablesSlot = { promise: undefined };
|
|
25
|
+
const phase2TablesSlot = { promise: undefined };
|
|
26
|
+
export function loadCube3SolverTables() {
|
|
27
|
+
return loadOnce(solverTablesSlot, loadGeneratedCube3SolverTables);
|
|
24
28
|
}
|
|
25
|
-
export
|
|
26
|
-
|
|
27
|
-
return cachedPhase1Tables;
|
|
29
|
+
export function loadCube3Phase1Tables() {
|
|
30
|
+
return loadOnce(phase1TablesSlot, loadGeneratedCube3Phase1Tables);
|
|
28
31
|
}
|
|
29
|
-
export
|
|
30
|
-
|
|
31
|
-
return cachedPhase2Tables;
|
|
32
|
+
export function loadCube3Phase2Tables() {
|
|
33
|
+
return loadOnce(phase2TablesSlot, loadGeneratedCube3Phase2Tables);
|
|
32
34
|
}
|
|
33
35
|
export function generateCube3Phase1Tables() {
|
|
34
36
|
const cornerOrientation = generateCube3MoveTable("cornerOrientation", CUBE3_COORDINATE_RANGES.cornerOrientation, CUBE3_PHASE1_MOVES);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../../src/puzzles/cube3/validation.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAIpE,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,UAAU,GAAG,qBAAqB,CA+B3E;AASD,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAExE;AAED,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAIpE;AAED,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC,
|
|
1
|
+
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../../src/puzzles/cube3/validation.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAIpE,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,UAAU,GAAG,qBAAqB,CA+B3E;AASD,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAExE;AAED,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAIpE;AAED,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC,CAmBvE"}
|