@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
|
@@ -4,6 +4,7 @@ import { encodePermutation, encodeSlicePermutation, encodeUdEdgePermutation } fr
|
|
|
4
4
|
import { isCube3Phase1Solved, solveCube3Phase1 } 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
|
+
import { loadCube3PairTables, pairBound } from "./pair-tables.js";
|
|
7
8
|
export class Cube3Phase2SearchError extends Error {
|
|
8
9
|
visitedNodes;
|
|
9
10
|
constructor(message, visitedNodes) {
|
|
@@ -40,11 +41,13 @@ export async function solveCube3Phase2(state, options = {}) {
|
|
|
40
41
|
throw new Cube3Phase2SearchError("3x3 Phase 2 requires a state already in G1.", 0);
|
|
41
42
|
}
|
|
42
43
|
const tables = options.tables ?? await loadCube3Phase2Tables();
|
|
44
|
+
const pairTables = options.pairTables ?? await loadCube3PairTables();
|
|
43
45
|
const maxDepth = options.maxDepth ?? 18;
|
|
44
46
|
const start = encodeCube3Phase2Coordinates(state);
|
|
45
47
|
const deadline = options.timeoutMs === undefined ? undefined : performance.now() + options.timeoutMs;
|
|
46
48
|
const context = {
|
|
47
49
|
tables,
|
|
50
|
+
pairTables,
|
|
48
51
|
deadline,
|
|
49
52
|
visitedNodes: 0,
|
|
50
53
|
timedOut: false
|
|
@@ -56,7 +59,13 @@ export async function solveCube3Phase2(state, options = {}) {
|
|
|
56
59
|
const combinedEPIndex = start.udEdgePermutation * spRange + start.slicePermutation;
|
|
57
60
|
const cpDist = (cpspTable[combinedCPIndex >> 1] >> ((combinedCPIndex & 1) << 2)) & 0x0f;
|
|
58
61
|
const epDist = (epspTable[combinedEPIndex >> 1] >> ((combinedEPIndex & 1) << 2)) & 0x0f;
|
|
59
|
-
|
|
62
|
+
let minDepth = cpDist > epDist ? cpDist : epDist;
|
|
63
|
+
if (pairTables) {
|
|
64
|
+
const pair = pairBound(pairTables, start.cornerPermutation, start.udEdgePermutation);
|
|
65
|
+
if (pair > minDepth) {
|
|
66
|
+
minDepth = pair;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
60
69
|
const initialPreviousFace = options.previousFace ?? -1;
|
|
61
70
|
const initialPreviousPreviousFace = options.previousPreviousFace ?? -1;
|
|
62
71
|
for (let depth = minDepth; depth <= maxDepth; depth += 1) {
|
|
@@ -151,6 +160,38 @@ function searchPhase2(cornerPermutation, udEdgePermutation, slicePermutation, pr
|
|
|
151
160
|
const cpOffset = cornerPermutation * moveCount;
|
|
152
161
|
const epOffset = udEdgePermutation * moveCount;
|
|
153
162
|
const spOffset = slicePermutation * moveCount;
|
|
163
|
+
// Without the pair tables (the default npm package), keep the exact
|
|
164
|
+
// reference traversal — zero overhead. With them (turbo tier), collect the
|
|
165
|
+
// children with the pair-distance bound and explore in ascending bound
|
|
166
|
+
// order (stable: ties keep the reference move order): same depth-minimal
|
|
167
|
+
// solutions, far fewer nodes.
|
|
168
|
+
if (context.pairTables === undefined) {
|
|
169
|
+
for (let i = 0; i < validMoves.length; i += 1) {
|
|
170
|
+
const moveIndex = validMoves[i];
|
|
171
|
+
const nextCornerPermutation = cpTable[cpOffset + moveIndex];
|
|
172
|
+
const nextUdEdgePermutation = epTable[epOffset + moveIndex];
|
|
173
|
+
const nextSlicePermutation = spTable[spOffset + moveIndex];
|
|
174
|
+
const combinedCPIndex = nextCornerPermutation * spRange + nextSlicePermutation;
|
|
175
|
+
const combinedEPIndex = nextUdEdgePermutation * spRange + nextSlicePermutation;
|
|
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 = [];
|
|
154
195
|
for (let i = 0; i < validMoves.length; i += 1) {
|
|
155
196
|
const moveIndex = validMoves[i];
|
|
156
197
|
const nextCornerPermutation = cpTable[cpOffset + moveIndex];
|
|
@@ -160,13 +201,29 @@ function searchPhase2(cornerPermutation, udEdgePermutation, slicePermutation, pr
|
|
|
160
201
|
const combinedEPIndex = nextUdEdgePermutation * spRange + nextSlicePermutation;
|
|
161
202
|
const cpDist = (cpspTable[combinedCPIndex >> 1] >> ((combinedCPIndex & 1) << 2)) & 0x0f;
|
|
162
203
|
const epDist = (epspTable[combinedEPIndex >> 1] >> ((combinedEPIndex & 1) << 2)) & 0x0f;
|
|
163
|
-
|
|
204
|
+
let nextLowerBound = cpDist > epDist ? cpDist : epDist;
|
|
164
205
|
if (nextLowerBound > remainingDepth - 1) {
|
|
165
206
|
continue;
|
|
166
207
|
}
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
208
|
+
const pair = pairBound(pairTables, nextCornerPermutation, nextUdEdgePermutation);
|
|
209
|
+
if (pair > nextLowerBound) {
|
|
210
|
+
nextLowerBound = pair;
|
|
211
|
+
}
|
|
212
|
+
if (nextLowerBound > remainingDepth - 1) {
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
children.push({
|
|
216
|
+
moveIndex,
|
|
217
|
+
lowerBound: nextLowerBound,
|
|
218
|
+
cornerPermutation: nextCornerPermutation,
|
|
219
|
+
udEdgePermutation: nextUdEdgePermutation,
|
|
220
|
+
slicePermutation: nextSlicePermutation
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
children.sort((left, right) => left.lowerBound - right.lowerBound);
|
|
224
|
+
for (const child of children) {
|
|
225
|
+
path.push(context.tables.phase2Moves[child.moveIndex]);
|
|
226
|
+
if (searchPhase2(child.cornerPermutation, child.udEdgePermutation, child.slicePermutation, PHASE2_MOVE_FACES[child.moveIndex], previousFace, remainingDepth - 1, path, context)) {
|
|
170
227
|
return true;
|
|
171
228
|
}
|
|
172
229
|
path.pop();
|
|
@@ -217,6 +274,7 @@ function searchPhase1Interleaved(cornerOrientation, edgeOrientation, udSlice, pr
|
|
|
217
274
|
const maxPhase2Depth = 6;
|
|
218
275
|
const phase2Context = {
|
|
219
276
|
tables: context.phase2Tables,
|
|
277
|
+
pairTables: undefined,
|
|
220
278
|
deadline: context.deadline,
|
|
221
279
|
visitedNodes: 0,
|
|
222
280
|
timedOut: false
|
|
@@ -40,6 +40,20 @@ export interface Cube3SolverTables {
|
|
|
40
40
|
cornerPermutationSlicePermutation: Cube3PruningTable;
|
|
41
41
|
udEdgePermutationSlicePermutation: Cube3PruningTable;
|
|
42
42
|
};
|
|
43
|
+
jointTables: Cube3JointTables | undefined;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Phase-1 joint tables (ud x co / ud x eo): exact distances to the ud-slice
|
|
47
|
+
* solved together with the corner/edge orientations. Tighten the phase-1
|
|
48
|
+
* bound from max(coeo, ud) to max(coeo, ud, udco, udeo) — a ~7x speedup
|
|
49
|
+
* (the starting minDepth becomes exact, eliminating failing depth
|
|
50
|
+
* iterations). Part of the standard tables (~1 MB).
|
|
51
|
+
*/
|
|
52
|
+
export interface Cube3JointTables {
|
|
53
|
+
/** 495 x 2187, 4-bit packed: index = ud * 2187 + cornerOrientation. */
|
|
54
|
+
jointUdCo: Uint8Array;
|
|
55
|
+
/** 495 x 2048, 4-bit packed: index = ud * 2048 + edgeOrientation. */
|
|
56
|
+
jointUdEo: Uint8Array;
|
|
43
57
|
}
|
|
44
58
|
export interface Cube3Phase1Tables {
|
|
45
59
|
phase1Moves: readonly Cube3Move[];
|
|
@@ -47,6 +61,8 @@ export interface Cube3Phase1Tables {
|
|
|
47
61
|
pruningTables: Pick<Cube3SolverTables["pruningTables"], "cornerOrientation" | "edgeOrientation" | "udSlice"> & {
|
|
48
62
|
cornerOrientationEdgeOrientation: Cube3PruningTable;
|
|
49
63
|
};
|
|
64
|
+
/** Present when the manifest ships the joint tables (default). */
|
|
65
|
+
jointTables: Cube3JointTables | undefined;
|
|
50
66
|
}
|
|
51
67
|
export interface Cube3Phase2Tables {
|
|
52
68
|
phase2Moves: readonly Cube3Move[];
|
|
@@ -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;AAQ7D,MAAM,MAAM,mBAAmB,GAAG,MAAM,oBAAoB,CAAC;AAE7D,MAAM,MAAM,qBAAqB,GAC7B,mBAAmB,GACnB,kCAAkC,GAClC,mCAAmC,GACnC,mCAAmC,CAAC;AAExC,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,mBAAmB,CAAC;IAChC,KAAK,EAAE,SAAS,SAAS,EAAE,CAAC;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB,EAAE,MAAM,CAAC;IACzB,MAAM,EAAE,UAAU,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,qBAAqB,CAAC;IAClC,KAAK,EAAE,SAAS,SAAS,EAAE,CAAC;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,UAAU,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,SAAS,SAAS,EAAE,CAAC;IAClC,WAAW,EAAE,SAAS,SAAS,EAAE,CAAC;IAClC,UAAU,EAAE;QACV,iBAAiB,EAAE,cAAc,CAAC;QAClC,eAAe,EAAE,cAAc,CAAC;QAChC,OAAO,EAAE,cAAc,CAAC;QACxB,iBAAiB,EAAE,cAAc,CAAC;QAClC,iBAAiB,EAAE,cAAc,CAAC;QAClC,gBAAgB,EAAE,cAAc,CAAC;KAClC,CAAC;IACF,aAAa,EAAE;QACb,iBAAiB,EAAE,iBAAiB,CAAC;QACrC,eAAe,EAAE,iBAAiB,CAAC;QACnC,OAAO,EAAE,iBAAiB,CAAC;QAC3B,gCAAgC,EAAE,iBAAiB,CAAC;QACpD,iBAAiB,EAAE,iBAAiB,CAAC;QACrC,iBAAiB,EAAE,iBAAiB,CAAC;QACrC,gBAAgB,EAAE,iBAAiB,CAAC;QACpC,iCAAiC,EAAE,iBAAiB,CAAC;QACrD,iCAAiC,EAAE,iBAAiB,CAAC;KACtD,CAAC;
|
|
1
|
+
{"version":3,"file":"tables.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube3/solver/tables.ts"],"names":[],"mappings":"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"}
|
|
@@ -46,7 +46,8 @@ export function generateCube3Phase1Tables() {
|
|
|
46
46
|
edgeOrientation: generateCube3PruningTable(edgeOrientation),
|
|
47
47
|
udSlice: generateCube3PruningTable(udSlice),
|
|
48
48
|
cornerOrientationEdgeOrientation: generateCube3CombinedPruningTable(cornerOrientation, edgeOrientation)
|
|
49
|
-
}
|
|
49
|
+
},
|
|
50
|
+
jointTables: undefined
|
|
50
51
|
};
|
|
51
52
|
}
|
|
52
53
|
export function generateCube3SolverTables() {
|
|
@@ -73,7 +74,8 @@ export function generateCube3SolverTables() {
|
|
|
73
74
|
slicePermutation: phase2Tables.pruningTables.slicePermutation,
|
|
74
75
|
cornerPermutationSlicePermutation: phase2Tables.pruningTables.cornerPermutationSlicePermutation,
|
|
75
76
|
udEdgePermutationSlicePermutation: phase2Tables.pruningTables.udEdgePermutationSlicePermutation
|
|
76
|
-
}
|
|
77
|
+
},
|
|
78
|
+
jointTables: undefined
|
|
77
79
|
};
|
|
78
80
|
}
|
|
79
81
|
export function generateCube3Phase2Tables() {
|
|
@@ -2,9 +2,12 @@
|
|
|
2
2
|
* The 4x4 scramble generator: sample a uniform random state, solve it with
|
|
3
3
|
* the reduction solver (phases 1-4), and invert the solution. The scramble
|
|
4
4
|
* always reproduces the sampled target state and facelets exactly.
|
|
5
|
+
*
|
|
6
|
+
* If the solver fails after 5 attempts it falls back to cubing.js
|
|
7
|
+
* `randomScrambleForEvent("444")` (also random-state). If that also fails,
|
|
8
|
+
* a WCA-compatible random-moves sequence (40 moves) is returned.
|
|
5
9
|
*/
|
|
6
10
|
import type { ScrambleGenerator, ScrambleOptions, ScrambleResult } from "../../core/index.js";
|
|
7
|
-
export declare function isCube4TablesLoaded(): boolean;
|
|
8
11
|
export declare const cube4ScrambleGenerator: ScrambleGenerator;
|
|
9
12
|
export declare function generateCube4Scramble(options?: ScrambleOptions & {
|
|
10
13
|
skipValidation?: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scrambler.d.ts","sourceRoot":"","sources":["../../../src/puzzles/cube4/scrambler.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"scrambler.d.ts","sourceRoot":"","sources":["../../../src/puzzles/cube4/scrambler.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAmJ9F,eAAO,MAAM,sBAAsB,EAAE,iBAYpC,CAAC;AAEF,wBAAsB,qBAAqB,CACzC,OAAO,GAAE,eAAe,GAAG;IAAE,cAAc,CAAC,EAAE,OAAO,CAAA;CAAO,GAC3D,OAAO,CAAC,cAAc,CAAC,CAyFzB"}
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
* The 4x4 scramble generator: sample a uniform random state, solve it with
|
|
3
3
|
* the reduction solver (phases 1-4), and invert the solution. The scramble
|
|
4
4
|
* always reproduces the sampled target state and facelets exactly.
|
|
5
|
+
*
|
|
6
|
+
* If the solver fails after 5 attempts it falls back to cubing.js
|
|
7
|
+
* `randomScrambleForEvent("444")` (also random-state). If that also fails,
|
|
8
|
+
* a WCA-compatible random-moves sequence (40 moves) is returned.
|
|
5
9
|
*/
|
|
6
10
|
import { formatCube4Algorithm, applyCube4Algorithm } from "./moves.js";
|
|
7
11
|
import { generateRandomCube4State } from "./random-state.js";
|
|
@@ -9,6 +13,95 @@ import { createSolvedCube4State, cube4StatesEqual } from "./state.js";
|
|
|
9
13
|
import { formatCube4Facelets, toCube4Facelets } from "./facelets.js";
|
|
10
14
|
import { solveCube4State } from "./reduction/solve.js";
|
|
11
15
|
import { solveCube3State } from "../cube3/solver/index.js";
|
|
16
|
+
import { createRandomSource } from "../../random/index.js";
|
|
17
|
+
// ────────────────────────────────────────────────────────────
|
|
18
|
+
// cubing.js fallback (random-state, WCA-compliant)
|
|
19
|
+
// ────────────────────────────────────────────────────────────
|
|
20
|
+
/**
|
|
21
|
+
* Try to generate a 4x4 scramble using cubing.js `randomScrambleForEvent`.
|
|
22
|
+
* Uses a dynamic import so browser bundles can tree-shake it when unused.
|
|
23
|
+
* Returns null if cubing.js is unavailable.
|
|
24
|
+
*/
|
|
25
|
+
async function generateCube4WithCubingJs() {
|
|
26
|
+
try {
|
|
27
|
+
const { randomScrambleForEvent } = await import(
|
|
28
|
+
/* webpackIgnore: true */
|
|
29
|
+
/* @vite-ignore */
|
|
30
|
+
"cubing/scramble");
|
|
31
|
+
const alg = await randomScrambleForEvent("444");
|
|
32
|
+
const scramble = alg.toString();
|
|
33
|
+
const moves = scramble.trim().split(/\s+/);
|
|
34
|
+
return {
|
|
35
|
+
eventId: "444",
|
|
36
|
+
scramble,
|
|
37
|
+
moves,
|
|
38
|
+
quality: "random-state",
|
|
39
|
+
generator: "cubing-js",
|
|
40
|
+
metadata: {
|
|
41
|
+
generator: "cubing-js",
|
|
42
|
+
distribution: "random-state",
|
|
43
|
+
note: "Generated by cubing.js randomScrambleForEvent as fallback."
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// ────────────────────────────────────────────────────────────
|
|
52
|
+
// Last-resort: random-moves (no external dep, always works)
|
|
53
|
+
// ────────────────────────────────────────────────────────────
|
|
54
|
+
const CUBE4_FACES = ["U", "R", "F", "D", "L", "B"];
|
|
55
|
+
// Opposite face indices (U↔D, R↔L, F↔B)
|
|
56
|
+
const CUBE4_OPPOSITE = { U: "D", D: "U", R: "L", L: "R", F: "B", B: "F" };
|
|
57
|
+
const CUBE4_SUFFIXES = ["", "2", "'"];
|
|
58
|
+
const CUBE4_WIDE_FACES = ["Uw", "Rw", "Fw", "Dw", "Lw", "Bw"];
|
|
59
|
+
// Opposite wide-move faces
|
|
60
|
+
const CUBE4_WIDE_OPPOSITE = {
|
|
61
|
+
Uw: "Dw", Dw: "Uw", Rw: "Lw", Lw: "Rw", Fw: "Bw", Bw: "Fw"
|
|
62
|
+
};
|
|
63
|
+
// All available move bases (outer + wide). Outer and wide turns are treated
|
|
64
|
+
// independently for the same-face/opposite-face check using their base letter.
|
|
65
|
+
const ALL_MOVE_BASES = [...CUBE4_FACES, ...CUBE4_WIDE_FACES];
|
|
66
|
+
function generateCube4RandomMoves(options = {}) {
|
|
67
|
+
const rng = createRandomSource(options.seed);
|
|
68
|
+
const moveCount = 40;
|
|
69
|
+
const moves = [];
|
|
70
|
+
let lastFace = null;
|
|
71
|
+
let secondLastFace = null;
|
|
72
|
+
const getBaseLetter = (face) => face.length > 1 ? face[0] : face;
|
|
73
|
+
for (let i = 0; i < moveCount; i++) {
|
|
74
|
+
let face;
|
|
75
|
+
// Reject same face as last or opposite face when the last two moves
|
|
76
|
+
// share the same axis (to avoid redundant move pairs like R L R).
|
|
77
|
+
do {
|
|
78
|
+
face = ALL_MOVE_BASES[rng.nextInt(ALL_MOVE_BASES.length)];
|
|
79
|
+
} while (face === lastFace ||
|
|
80
|
+
(lastFace !== null &&
|
|
81
|
+
(CUBE4_OPPOSITE[face] === lastFace || CUBE4_WIDE_OPPOSITE[face] === lastFace) &&
|
|
82
|
+
secondLastFace !== null &&
|
|
83
|
+
getBaseLetter(secondLastFace) === getBaseLetter(face)));
|
|
84
|
+
const suffix = CUBE4_SUFFIXES[rng.nextInt(3)];
|
|
85
|
+
moves.push(`${face}${suffix}`);
|
|
86
|
+
secondLastFace = lastFace;
|
|
87
|
+
lastFace = face;
|
|
88
|
+
}
|
|
89
|
+
const scramble = moves.join(" ");
|
|
90
|
+
return {
|
|
91
|
+
eventId: "444",
|
|
92
|
+
scramble,
|
|
93
|
+
moves,
|
|
94
|
+
quality: "random-moves",
|
|
95
|
+
generator: "cube4-random-moves-fallback",
|
|
96
|
+
metadata: {
|
|
97
|
+
generator: "cube4-random-moves-fallback",
|
|
98
|
+
distribution: "random-moves",
|
|
99
|
+
note: "Reduction solver and cubing.js both failed; using random-moves.",
|
|
100
|
+
...options.metadata
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
// ────────────────────────────────────────────────────────────
|
|
12
105
|
let cachedTables;
|
|
13
106
|
async function loadCube4Tables(baseUrl) {
|
|
14
107
|
if (cachedTables)
|
|
@@ -40,18 +133,6 @@ async function loadCube4Tables(baseUrl) {
|
|
|
40
133
|
cachedTables = tables;
|
|
41
134
|
return tables;
|
|
42
135
|
}
|
|
43
|
-
export function isCube4TablesLoaded() {
|
|
44
|
-
// For the scrambler retry logic, we need to know if tables are already
|
|
45
|
-
// loaded to avoid re-loading on every attempt.
|
|
46
|
-
try {
|
|
47
|
-
// @ts-ignore
|
|
48
|
-
const loader = globalThis;
|
|
49
|
-
return false;
|
|
50
|
-
}
|
|
51
|
-
catch {
|
|
52
|
-
return false;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
136
|
export const cube4ScrambleGenerator = {
|
|
56
137
|
info: {
|
|
57
138
|
eventId: "444",
|
|
@@ -59,7 +140,7 @@ export const cube4ScrambleGenerator = {
|
|
|
59
140
|
puzzle: "cube-4x4",
|
|
60
141
|
quality: "random-state",
|
|
61
142
|
generator: "cube4-reduction",
|
|
62
|
-
description: "Random-state 4x4 scramble via the reduction solver (centers + pairing + 3x3 finish)."
|
|
143
|
+
description: "Random-state 4x4 scramble via the reduction solver (centers + pairing + 3x3 finish). Falls back to random-moves if the solver fails."
|
|
63
144
|
},
|
|
64
145
|
generate(options = {}) {
|
|
65
146
|
return generateCube4Scramble(options);
|
|
@@ -136,5 +217,11 @@ export async function generateCube4Scramble(options = {}) {
|
|
|
136
217
|
throw error;
|
|
137
218
|
}
|
|
138
219
|
}
|
|
139
|
-
|
|
220
|
+
// All solver attempts exhausted — try cubing.js first, then random-moves.
|
|
221
|
+
console.warn("[cube4-scrambler] Reduction solver failed after 5 attempts; trying cubing.js fallback.", lastError);
|
|
222
|
+
const cubingJsResult = await generateCube4WithCubingJs();
|
|
223
|
+
if (cubingJsResult)
|
|
224
|
+
return cubingJsResult;
|
|
225
|
+
console.warn("[cube4-scrambler] cubing.js fallback also failed; using random-moves.");
|
|
226
|
+
return generateCube4RandomMoves(options);
|
|
140
227
|
}
|
|
@@ -53,10 +53,35 @@ async function loadBrowserTables(baseUrl) {
|
|
|
53
53
|
slicePermutation: await fetchPruningTable(resolvedBase, manifest.pruningTables.slicePermutation),
|
|
54
54
|
cornerPermutationSlicePermutation: await fetchPruningTable(resolvedBase, manifest.pruningTables.cornerPermutationSlicePermutation),
|
|
55
55
|
udEdgePermutationSlicePermutation: await fetchPruningTable(resolvedBase, manifest.pruningTables.udEdgePermutationSlicePermutation)
|
|
56
|
-
}
|
|
56
|
+
},
|
|
57
|
+
jointTables: await fetchJointTables(resolvedBase, manifest.jointTables)
|
|
57
58
|
};
|
|
58
59
|
return browserTables;
|
|
59
60
|
}
|
|
61
|
+
async function fetchJointTables(baseUrl, entries) {
|
|
62
|
+
if (!entries?.udCo || !entries.udEo) {
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
const fetchJoint = async (entry) => {
|
|
66
|
+
const buffer = await (0, compressed_fetch_js_1.fetchTableBuffer)(baseUrl, TABLES_DIR, entry.file, {
|
|
67
|
+
cacheName: CACHE_NAME,
|
|
68
|
+
compressed: entry.compressed !== false
|
|
69
|
+
});
|
|
70
|
+
const expectedBytes = Math.ceil((entry.rangeA * entry.rangeB) / 2);
|
|
71
|
+
if (buffer.byteLength !== expectedBytes) {
|
|
72
|
+
throw new Error(`Invalid byte size for ${entry.file}.`);
|
|
73
|
+
}
|
|
74
|
+
const values = new Uint8Array(buffer);
|
|
75
|
+
if (checksum(values) !== entry.checksum) {
|
|
76
|
+
throw new Error(`Checksum mismatch for ${entry.file}.`);
|
|
77
|
+
}
|
|
78
|
+
return values;
|
|
79
|
+
};
|
|
80
|
+
return {
|
|
81
|
+
jointUdCo: await fetchJoint(entries.udCo),
|
|
82
|
+
jointUdEo: await fetchJoint(entries.udEo)
|
|
83
|
+
};
|
|
84
|
+
}
|
|
60
85
|
async function fetchManifest(baseUrl) {
|
|
61
86
|
const url = `${baseUrl}/puzzles/cube3/solver/generated/manifest.json`;
|
|
62
87
|
const response = await fetch(url);
|
|
@@ -400,5 +400,19 @@
|
|
|
400
400
|
"checksum": 2840452350,
|
|
401
401
|
"maxDepth": 12
|
|
402
402
|
}
|
|
403
|
+
},
|
|
404
|
+
"jointTables": {
|
|
405
|
+
"udCo": {
|
|
406
|
+
"file": "phase1-udco.bin",
|
|
407
|
+
"rangeA": 495,
|
|
408
|
+
"rangeB": 2187,
|
|
409
|
+
"checksum": 1999233281
|
|
410
|
+
},
|
|
411
|
+
"udEo": {
|
|
412
|
+
"file": "phase1-udeo.bin",
|
|
413
|
+
"rangeA": 495,
|
|
414
|
+
"rangeB": 2048,
|
|
415
|
+
"checksum": 4017545214
|
|
416
|
+
}
|
|
403
417
|
}
|
|
404
418
|
}
|