@cubingcenter/scrambler 0.5.3 → 0.6.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 +24 -9
- package/dist/cli/index.js +0 -0
- package/dist/core/api.d.ts.map +1 -1
- package/dist/core/api.js +11 -1
- package/dist/core/types.d.ts +1 -1
- package/dist/core/types.d.ts.map +1 -1
- package/dist/puzzles/cube4/index.d.ts +4 -2
- package/dist/puzzles/cube4/index.d.ts.map +1 -1
- package/dist/puzzles/cube4/index.js +3 -1
- package/dist/puzzles/cube4/random-state.d.ts +16 -0
- package/dist/puzzles/cube4/random-state.d.ts.map +1 -0
- package/dist/puzzles/cube4/random-state.js +76 -0
- package/dist/puzzles/cube4/reduction/browser-loader.d.ts +18 -0
- package/dist/puzzles/cube4/reduction/browser-loader.d.ts.map +1 -0
- package/dist/puzzles/cube4/reduction/browser-loader.js +95 -0
- package/dist/puzzles/cube4/reduction/center1.d.ts +65 -0
- package/dist/puzzles/cube4/reduction/center1.d.ts.map +1 -0
- package/dist/puzzles/cube4/reduction/center1.js +245 -0
- package/dist/puzzles/cube4/reduction/center2.d.ts +91 -0
- package/dist/puzzles/cube4/reduction/center2.d.ts.map +1 -0
- package/dist/puzzles/cube4/reduction/center2.js +449 -0
- package/dist/puzzles/cube4/reduction/center3.d.ts +47 -0
- package/dist/puzzles/cube4/reduction/center3.d.ts.map +1 -0
- package/dist/puzzles/cube4/reduction/center3.js +278 -0
- package/dist/puzzles/cube4/reduction/edge3.d.ts +100 -0
- package/dist/puzzles/cube4/reduction/edge3.d.ts.map +1 -0
- package/dist/puzzles/cube4/reduction/edge3.js +511 -0
- package/dist/puzzles/cube4/reduction/generated/center1-distance.bin +1 -0
- package/dist/puzzles/cube4/reduction/generated/center1-manifest.json +46 -0
- package/dist/puzzles/cube4/reduction/generated/center1-move-maps.bin +0 -0
- package/dist/puzzles/cube4/reduction/generated/center2-ct-move.bin +0 -0
- package/dist/puzzles/cube4/reduction/generated/center2-manifest.json +87 -0
- package/dist/puzzles/cube4/reduction/generated/center2-pruning.bin +1 -0
- package/dist/puzzles/cube4/reduction/generated/center2-rl-move.bin +0 -0
- package/dist/puzzles/cube4/reduction/generated/center3-manifest.json +40 -0
- package/dist/puzzles/cube4/reduction/generated/center3-move.bin +0 -0
- package/dist/puzzles/cube4/reduction/generated/center3-pruning.bin +0 -0
- package/dist/puzzles/cube4/reduction/generated/edge3-distances.bin +1 -0
- package/dist/puzzles/cube4/reduction/generated/edge3-manifest.json +12 -0
- package/dist/puzzles/cube4/reduction/geometry.d.ts +33 -0
- package/dist/puzzles/cube4/reduction/geometry.d.ts.map +1 -0
- package/dist/puzzles/cube4/reduction/geometry.js +302 -0
- package/dist/puzzles/cube4/reduction/node-loader.d.ts +17 -0
- package/dist/puzzles/cube4/reduction/node-loader.d.ts.map +1 -0
- package/dist/puzzles/cube4/reduction/node-loader.js +98 -0
- package/dist/puzzles/cube4/reduction/pairing-coordinate.d.ts +130 -0
- package/dist/puzzles/cube4/reduction/pairing-coordinate.d.ts.map +1 -0
- package/dist/puzzles/cube4/reduction/pairing-coordinate.js +281 -0
- package/dist/puzzles/cube4/reduction/phase3-search.d.ts +42 -0
- package/dist/puzzles/cube4/reduction/phase3-search.d.ts.map +1 -0
- package/dist/puzzles/cube4/reduction/phase3-search.js +133 -0
- package/dist/puzzles/cube4/reduction/phase3.d.ts +34 -0
- package/dist/puzzles/cube4/reduction/phase3.d.ts.map +1 -0
- package/dist/puzzles/cube4/reduction/phase3.js +83 -0
- package/dist/puzzles/cube4/reduction/phase4.d.ts +35 -0
- package/dist/puzzles/cube4/reduction/phase4.d.ts.map +1 -0
- package/dist/puzzles/cube4/reduction/phase4.js +82 -0
- package/dist/puzzles/cube4/reduction/solve.d.ts +31 -0
- package/dist/puzzles/cube4/reduction/solve.d.ts.map +1 -0
- package/dist/puzzles/cube4/reduction/solve.js +311 -0
- package/dist/puzzles/cube4/scrambler.d.ts +9 -1
- package/dist/puzzles/cube4/scrambler.d.ts.map +1 -1
- package/dist/puzzles/cube4/scrambler.js +123 -81
- package/dist/puzzles/cube4/types.d.ts +6 -0
- package/dist/puzzles/cube4/types.d.ts.map +1 -1
- package/dist/puzzles/cube4/validation.d.ts +6 -0
- package/dist/puzzles/cube4/validation.d.ts.map +1 -0
- package/dist/puzzles/cube4/validation.js +91 -0
- package/dist/puzzles/fto/coordinates.d.ts +53 -0
- package/dist/puzzles/fto/coordinates.d.ts.map +1 -0
- package/dist/puzzles/fto/coordinates.js +228 -0
- package/dist/puzzles/fto/facelets.d.ts +6 -0
- package/dist/puzzles/fto/facelets.d.ts.map +1 -0
- package/dist/puzzles/fto/facelets.js +99 -0
- package/dist/puzzles/fto/index.d.ts +12 -0
- package/dist/puzzles/fto/index.d.ts.map +1 -0
- package/dist/puzzles/fto/index.js +13 -0
- package/dist/puzzles/fto/moves.d.ts +12 -0
- package/dist/puzzles/fto/moves.d.ts.map +1 -0
- package/dist/puzzles/fto/moves.js +115 -0
- package/dist/puzzles/fto/random-state.d.ts +17 -0
- package/dist/puzzles/fto/random-state.d.ts.map +1 -0
- package/dist/puzzles/fto/random-state.js +40 -0
- package/dist/puzzles/fto/scrambler.d.ts +6 -0
- package/dist/puzzles/fto/scrambler.d.ts.map +1 -0
- package/dist/puzzles/fto/scrambler.js +49 -0
- package/dist/puzzles/fto/solver/engine.d.ts +18 -0
- package/dist/puzzles/fto/solver/engine.d.ts.map +1 -0
- package/dist/puzzles/fto/solver/engine.js +1168 -0
- package/dist/puzzles/fto/solver/index.d.ts +2 -0
- package/dist/puzzles/fto/solver/index.d.ts.map +1 -0
- package/dist/puzzles/fto/solver/index.js +1 -0
- package/dist/puzzles/fto/state.d.ts +16 -0
- package/dist/puzzles/fto/state.d.ts.map +1 -0
- package/dist/puzzles/fto/state.js +61 -0
- package/dist/puzzles/fto/types.d.ts +32 -0
- package/dist/puzzles/fto/types.d.ts.map +1 -0
- package/dist/puzzles/fto/types.js +46 -0
- package/dist/puzzles/fto/validation.d.ts +19 -0
- package/dist/puzzles/fto/validation.d.ts.map +1 -0
- package/dist/puzzles/fto/validation.js +143 -0
- package/dist/puzzles/index.d.ts +2 -0
- package/dist/puzzles/index.d.ts.map +1 -1
- package/dist/puzzles/index.js +6 -0
- package/dist/puzzles/skewb/index.d.ts +1 -1
- package/dist/puzzles/skewb/index.d.ts.map +1 -1
- package/dist/puzzles/skewb/index.js +1 -1
- package/dist/puzzles/skewb/random-state.d.ts.map +1 -1
- package/dist/puzzles/skewb/random-state.js +4 -1
- package/dist/puzzles/sq1/facelets.d.ts +12 -0
- package/dist/puzzles/sq1/facelets.d.ts.map +1 -0
- package/dist/puzzles/sq1/facelets.js +26 -0
- package/dist/puzzles/sq1/index.d.ts +10 -0
- package/dist/puzzles/sq1/index.d.ts.map +1 -0
- package/dist/puzzles/sq1/index.js +11 -0
- package/dist/puzzles/sq1/moves.d.ts +55 -0
- package/dist/puzzles/sq1/moves.d.ts.map +1 -0
- package/dist/puzzles/sq1/moves.js +220 -0
- package/dist/puzzles/sq1/random-state.d.ts +6 -0
- package/dist/puzzles/sq1/random-state.d.ts.map +1 -0
- package/dist/puzzles/sq1/random-state.js +49 -0
- package/dist/puzzles/sq1/scrambler.d.ts +10 -0
- package/dist/puzzles/sq1/scrambler.d.ts.map +1 -0
- package/dist/puzzles/sq1/scrambler.js +90 -0
- package/dist/puzzles/sq1/solver/browser-loader.d.ts +5 -0
- package/dist/puzzles/sq1/solver/browser-loader.d.ts.map +1 -0
- package/dist/puzzles/sq1/solver/browser-loader.js +92 -0
- package/dist/puzzles/sq1/solver/full-cube.d.ts +53 -0
- package/dist/puzzles/sq1/solver/full-cube.d.ts.map +1 -0
- package/dist/puzzles/sq1/solver/full-cube.js +281 -0
- package/dist/puzzles/sq1/solver/generated/manifest.json +50 -0
- package/dist/puzzles/sq1/solver/generated/shape-bottom-move.bin +0 -0
- package/dist/puzzles/sq1/solver/generated/shape-idx.bin +0 -0
- package/dist/puzzles/sq1/solver/generated/shape-prun.bin +0 -0
- package/dist/puzzles/sq1/solver/generated/shape-top-move.bin +0 -0
- package/dist/puzzles/sq1/solver/generated/shape-twist-move.bin +0 -0
- package/dist/puzzles/sq1/solver/generated/square-bottom-move.bin +0 -0
- package/dist/puzzles/sq1/solver/generated/square-prun.bin +0 -0
- package/dist/puzzles/sq1/solver/generated/square-top-move.bin +0 -0
- package/dist/puzzles/sq1/solver/generated/square-twist-move.bin +0 -0
- package/dist/puzzles/sq1/solver/index.d.ts +5 -0
- package/dist/puzzles/sq1/solver/index.d.ts.map +1 -0
- package/dist/puzzles/sq1/solver/index.js +4 -0
- package/dist/puzzles/sq1/solver/node-loader.d.ts +4 -0
- package/dist/puzzles/sq1/solver/node-loader.d.ts.map +1 -0
- package/dist/puzzles/sq1/solver/node-loader.js +113 -0
- package/dist/puzzles/sq1/solver/search.d.ts +27 -0
- package/dist/puzzles/sq1/solver/search.d.ts.map +1 -0
- package/dist/puzzles/sq1/solver/search.js +225 -0
- package/dist/puzzles/sq1/solver/shape.d.ts +22 -0
- package/dist/puzzles/sq1/solver/shape.d.ts.map +1 -0
- package/dist/puzzles/sq1/solver/shape.js +185 -0
- package/dist/puzzles/sq1/solver/square.d.ts +10 -0
- package/dist/puzzles/sq1/solver/square.d.ts.map +1 -0
- package/dist/puzzles/sq1/solver/square.js +90 -0
- package/dist/puzzles/sq1/solver/table-metadata.d.ts +48 -0
- package/dist/puzzles/sq1/solver/table-metadata.d.ts.map +1 -0
- package/dist/puzzles/sq1/solver/table-metadata.js +47 -0
- package/dist/puzzles/sq1/solver/tables.d.ts +17 -0
- package/dist/puzzles/sq1/solver/tables.d.ts.map +1 -0
- package/dist/puzzles/sq1/solver/tables.js +66 -0
- package/dist/puzzles/sq1/state.d.ts +10 -0
- package/dist/puzzles/sq1/state.d.ts.map +1 -0
- package/dist/puzzles/sq1/state.js +74 -0
- package/dist/puzzles/sq1/types.d.ts +32 -0
- package/dist/puzzles/sq1/types.d.ts.map +1 -0
- package/dist/puzzles/sq1/types.js +1 -0
- package/dist/puzzles/sq1/validation.d.ts +7 -0
- package/dist/puzzles/sq1/validation.d.ts.map +1 -0
- package/dist/puzzles/sq1/validation.js +64 -0
- package/dist/random/prng.d.ts.map +1 -1
- package/dist/random/prng.js +9 -4
- package/dist-cjs/core/api.js +11 -1
- package/dist-cjs/puzzles/cube4/index.js +11 -4
- package/dist-cjs/puzzles/cube4/random-state.js +79 -0
- package/dist-cjs/puzzles/cube4/reduction/browser-loader.js +100 -0
- package/dist-cjs/puzzles/cube4/reduction/center1.js +258 -0
- package/dist-cjs/puzzles/cube4/reduction/center2.js +462 -0
- package/dist-cjs/puzzles/cube4/reduction/center3.js +284 -0
- package/dist-cjs/puzzles/cube4/reduction/edge3.js +522 -0
- package/dist-cjs/puzzles/cube4/reduction/generated/center1-distance.bin +1 -0
- package/dist-cjs/puzzles/cube4/reduction/generated/center1-manifest.json +46 -0
- package/dist-cjs/puzzles/cube4/reduction/generated/center1-move-maps.bin +0 -0
- package/dist-cjs/puzzles/cube4/reduction/generated/center2-ct-move.bin +0 -0
- package/dist-cjs/puzzles/cube4/reduction/generated/center2-manifest.json +87 -0
- package/dist-cjs/puzzles/cube4/reduction/generated/center2-pruning.bin +1 -0
- package/dist-cjs/puzzles/cube4/reduction/generated/center2-rl-move.bin +0 -0
- package/dist-cjs/puzzles/cube4/reduction/generated/center3-manifest.json +40 -0
- package/dist-cjs/puzzles/cube4/reduction/generated/center3-move.bin +0 -0
- package/dist-cjs/puzzles/cube4/reduction/generated/center3-pruning.bin +0 -0
- package/dist-cjs/puzzles/cube4/reduction/generated/edge3-distances.bin +1 -0
- package/dist-cjs/puzzles/cube4/reduction/generated/edge3-manifest.json +12 -0
- package/dist-cjs/puzzles/cube4/reduction/geometry.js +308 -0
- package/dist-cjs/puzzles/cube4/reduction/node-loader.js +104 -0
- package/dist-cjs/puzzles/cube4/reduction/pairing-coordinate.js +294 -0
- package/dist-cjs/puzzles/cube4/reduction/phase3-search.js +138 -0
- package/dist-cjs/puzzles/cube4/reduction/phase3.js +86 -0
- package/dist-cjs/puzzles/cube4/reduction/phase4.js +87 -0
- package/dist-cjs/puzzles/cube4/reduction/solve.js +314 -0
- package/dist-cjs/puzzles/cube4/scrambler.js +122 -79
- package/dist-cjs/puzzles/cube4/validation.js +97 -0
- package/dist-cjs/puzzles/fto/coordinates.js +240 -0
- package/dist-cjs/puzzles/fto/facelets.js +104 -0
- package/dist-cjs/puzzles/fto/index.js +68 -0
- package/dist-cjs/puzzles/fto/moves.js +126 -0
- package/dist-cjs/puzzles/fto/random-state.js +43 -0
- package/dist-cjs/puzzles/fto/scrambler.js +53 -0
- package/dist-cjs/puzzles/fto/solver/engine.js +1175 -0
- package/dist-cjs/puzzles/fto/solver/index.js +9 -0
- package/dist-cjs/puzzles/fto/state.js +69 -0
- package/dist-cjs/puzzles/fto/types.js +49 -0
- package/dist-cjs/puzzles/fto/validation.js +147 -0
- package/dist-cjs/puzzles/index.js +7 -1
- package/dist-cjs/puzzles/skewb/index.js +2 -1
- package/dist-cjs/puzzles/skewb/random-state.js +4 -1
- package/dist-cjs/puzzles/sq1/facelets.js +30 -0
- package/dist-cjs/puzzles/sq1/index.js +52 -0
- package/dist-cjs/puzzles/sq1/moves.js +234 -0
- package/dist-cjs/puzzles/sq1/random-state.js +52 -0
- package/dist-cjs/puzzles/sq1/scrambler.js +95 -0
- package/dist-cjs/puzzles/sq1/solver/browser-loader.js +97 -0
- package/dist-cjs/puzzles/sq1/solver/full-cube.js +289 -0
- package/dist-cjs/puzzles/sq1/solver/generated/manifest.json +50 -0
- package/dist-cjs/puzzles/sq1/solver/generated/shape-bottom-move.bin +0 -0
- package/dist-cjs/puzzles/sq1/solver/generated/shape-idx.bin +0 -0
- package/dist-cjs/puzzles/sq1/solver/generated/shape-prun.bin +0 -0
- package/dist-cjs/puzzles/sq1/solver/generated/shape-top-move.bin +0 -0
- package/dist-cjs/puzzles/sq1/solver/generated/shape-twist-move.bin +0 -0
- package/dist-cjs/puzzles/sq1/solver/generated/square-bottom-move.bin +0 -0
- package/dist-cjs/puzzles/sq1/solver/generated/square-prun.bin +0 -0
- package/dist-cjs/puzzles/sq1/solver/generated/square-top-move.bin +0 -0
- package/dist-cjs/puzzles/sq1/solver/generated/square-twist-move.bin +0 -0
- package/dist-cjs/puzzles/sq1/solver/index.js +18 -0
- package/dist-cjs/puzzles/sq1/solver/node-loader.js +120 -0
- package/dist-cjs/puzzles/sq1/solver/search.js +229 -0
- package/dist-cjs/puzzles/sq1/solver/shape.js +191 -0
- package/dist-cjs/puzzles/sq1/solver/square.js +93 -0
- package/dist-cjs/puzzles/sq1/solver/table-metadata.js +50 -0
- package/dist-cjs/puzzles/sq1/solver/tables.js +106 -0
- package/dist-cjs/puzzles/sq1/state.js +81 -0
- package/dist-cjs/puzzles/sq1/types.js +2 -0
- package/dist-cjs/puzzles/sq1/validation.js +68 -0
- package/dist-cjs/random/prng.js +9 -4
- package/package.json +43 -14
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase 4: reduce the solved-centers + paired-wings 4x4 state to a 3x3
|
|
3
|
+
* cubie state and solve it with the existing cube3 two-phase solver.
|
|
4
|
+
*
|
|
5
|
+
* The corner permutation/orientation carry over directly (same numbering as
|
|
6
|
+
* the cube3). The 12 paired edges: each slot holds one 3x3 edge; the edge
|
|
7
|
+
* permutation = the pair id and the orientation = the chirality flip (which
|
|
8
|
+
* wing of the pair sits at the slot's first-half position).
|
|
9
|
+
*/
|
|
10
|
+
import { applyCube4Algorithm } from "../moves.js";
|
|
11
|
+
import { PAIRING_SLOTS } from "./pairing-coordinate.js";
|
|
12
|
+
// The "right-chiral" wing/position classes (same set as facelets.ts).
|
|
13
|
+
const RIGHT_GROUP = new Set([1, 2, 4, 6, 8, 11, 13, 15, 16, 19, 20, 23]);
|
|
14
|
+
// For each 3x3 edge position p (positions 2p, 2p+1), the pairing slot whose
|
|
15
|
+
// first-half position belongs to that pair.
|
|
16
|
+
const SLOT_OF_EDGE = (() => {
|
|
17
|
+
const table = new Array(12);
|
|
18
|
+
for (let slot = 0; slot < 12; slot++) {
|
|
19
|
+
const pair = PAIRING_SLOTS[slot][0] >> 1;
|
|
20
|
+
table[pair] = slot;
|
|
21
|
+
}
|
|
22
|
+
return table;
|
|
23
|
+
})();
|
|
24
|
+
/** Verify the reduction preconditions (the reference's `to333Facelet` check). */
|
|
25
|
+
export function checkCube4Reduction(state) {
|
|
26
|
+
for (let face = 0; face < 6; face++) {
|
|
27
|
+
const color = state.centers[face * 4];
|
|
28
|
+
for (let i = 1; i < 4; i++) {
|
|
29
|
+
if (state.centers[face * 4 + i] !== color) {
|
|
30
|
+
return { wingsPaired: false, centersSolved: false };
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
for (const [f, s] of PAIRING_SLOTS) {
|
|
35
|
+
if ((state.wp[f] >> 1) !== (state.wp[s] >> 1)) {
|
|
36
|
+
return { wingsPaired: false, centersSolved: true };
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Build the 3x3 cubie state from a reduced 4x4 state. Returns null if the
|
|
43
|
+
* reduction preconditions are not met.
|
|
44
|
+
*/
|
|
45
|
+
export function toCube3State(state) {
|
|
46
|
+
if (checkCube4Reduction(state) !== null)
|
|
47
|
+
return null;
|
|
48
|
+
const edges = { permutation: new Array(12), orientation: new Array(12) };
|
|
49
|
+
for (let edge = 0; edge < 12; edge++) {
|
|
50
|
+
// the 3x3 edge positions are the position pairs (2p, 2p+1); the slot of
|
|
51
|
+
// the edge p is the slot whose first-half position belongs to that pair.
|
|
52
|
+
const slot = SLOT_OF_EDGE[edge];
|
|
53
|
+
const first = PAIRING_SLOTS[slot][0];
|
|
54
|
+
const wing = state.wp[first];
|
|
55
|
+
edges.permutation[edge] = wing >> 1;
|
|
56
|
+
// the chirality flip: the derived edge is flipped when the wing and the
|
|
57
|
+
// slot-half have opposite chirality (the facelets.ts RIGHT_GROUP rule)
|
|
58
|
+
edges.orientation[edge] = RIGHT_GROUP.has(wing) !== RIGHT_GROUP.has(first) ? 1 : 0;
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
corners: {
|
|
62
|
+
permutation: Array.from(state.cp),
|
|
63
|
+
orientation: Array.from(state.co)
|
|
64
|
+
},
|
|
65
|
+
edges
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Full solve: run the reduction phases (1-3) + the 3x3 finish. The caller
|
|
70
|
+
* provides the phase-3 solution; this applies it and finishes with the cube3
|
|
71
|
+
* solver, returning the complete move sequence.
|
|
72
|
+
*/
|
|
73
|
+
export async function solveReduced4x4(phase3End, phase3Moves, solveCube3) {
|
|
74
|
+
const state = applyCube4Algorithm(phase3End, phase3Moves);
|
|
75
|
+
const reductionError = checkCube4Reduction(state);
|
|
76
|
+
if (reductionError !== null) {
|
|
77
|
+
throw new Error(`4x4 reduction failed: wingsPaired=${reductionError.wingsPaired} centersSolved=${reductionError.centersSolved}`);
|
|
78
|
+
}
|
|
79
|
+
const cube3State = toCube3State(state);
|
|
80
|
+
const solution = await solveCube3(cube3State);
|
|
81
|
+
return { moves: [...phase3Moves, ...solution.moves] };
|
|
82
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Full 4x4 solve orchestration: reduction (phases 1-3) + the 3x3 finish
|
|
3
|
+
* (phase 4), returning the move sequence that solves the given state.
|
|
4
|
+
* The scramble for a sampled state is the inverted solution.
|
|
5
|
+
*/
|
|
6
|
+
import type { Cube4State, Cube4Move } from "../types.js";
|
|
7
|
+
import { type Center1Tables } from "./center1.js";
|
|
8
|
+
import { type Center2Tables } from "./center2.js";
|
|
9
|
+
import { type Center3Tables } from "./center3.js";
|
|
10
|
+
import { type Edge3Tables } from "./edge3.js";
|
|
11
|
+
import type { Cube3State, Cube3Move } from "../../cube3/types.js";
|
|
12
|
+
export interface Cube4SolveTables {
|
|
13
|
+
center1: Center1Tables;
|
|
14
|
+
center2: Center2Tables;
|
|
15
|
+
center3: Center3Tables;
|
|
16
|
+
edge3: Edge3Tables;
|
|
17
|
+
}
|
|
18
|
+
export interface Cube4SolveResult {
|
|
19
|
+
/** The complete solve sequence (solves the input state). */
|
|
20
|
+
moves: Cube4Move[];
|
|
21
|
+
/** The scramble (the inverted solution). */
|
|
22
|
+
scramble: Cube4Move[];
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Solve a 4x4 state with the reduction solver + the cube3 two-phase finish.
|
|
26
|
+
* `solveCube3` is injected (the cube3 solver loads its tables lazily).
|
|
27
|
+
*/
|
|
28
|
+
export declare function solveCube4State(state: Cube4State, tables: Cube4SolveTables, solveCube3: (state: Cube3State) => Promise<{
|
|
29
|
+
moves: Cube3Move[];
|
|
30
|
+
}>): Promise<Cube4SolveResult>;
|
|
31
|
+
//# sourceMappingURL=solve.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"solve.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube4/reduction/solve.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAEzD,OAAO,EAKL,KAAK,aAAa,EACnB,MAAM,cAAc,CAAC;AACtB,OAAO,EAML,KAAK,aAAa,EACnB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAiB,KAAK,aAAa,EAAE,MAAM,cAAc,CAAC;AAKjE,OAAO,EAGL,KAAK,WAAW,EACjB,MAAM,YAAY,CAAC;AAGpB,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAIlE,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,aAAa,CAAC;IACvB,OAAO,EAAE,aAAa,CAAC;IACvB,OAAO,EAAE,aAAa,CAAC;IACvB,KAAK,EAAE,WAAW,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,4DAA4D;IAC5D,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,4CAA4C;IAC5C,QAAQ,EAAE,SAAS,EAAE,CAAC;CACvB;AAuKD;;;GAGG;AACH,wBAAsB,eAAe,CACnC,KAAK,EAAE,UAAU,EACjB,MAAM,EAAE,gBAAgB,EACxB,UAAU,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC;IAAE,KAAK,EAAE,SAAS,EAAE,CAAA;CAAE,CAAC,GACjE,OAAO,CAAC,gBAAgB,CAAC,CAqL3B"}
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Full 4x4 solve orchestration: reduction (phases 1-3) + the 3x3 finish
|
|
3
|
+
* (phase 4), returning the move sequence that solves the given state.
|
|
4
|
+
* The scramble for a sampled state is the inverted solution.
|
|
5
|
+
*/
|
|
6
|
+
import { applyCube4Algorithm, invertCube4Algorithm } from "../moves.js";
|
|
7
|
+
import { center1Mask, getCenter1Distance, applyCenter1Move, CENTER1_MOVES } from "./center1.js";
|
|
8
|
+
import { center2Bridge, isCenter2Goal, CENTER2_MOVES, CENTER2_SEARCH_MOVES, CENTER2_RL_STATES } from "./center2.js";
|
|
9
|
+
import { encodeCenter3 } from "./center3.js";
|
|
10
|
+
import { encodePairingState } from "./pairing-coordinate.js";
|
|
11
|
+
import { getSymTables, getMvSymFrom } from "./edge3.js";
|
|
12
|
+
import { FastPhase3Solver } from "./phase3-search.js";
|
|
13
|
+
import { toCube3State } from "./phase4.js";
|
|
14
|
+
import { Cube3Phase2SearchError } from "../../cube3/solver/index.js";
|
|
15
|
+
import { createSolvedCube4State } from "../state.js";
|
|
16
|
+
function cornerPermutationParity(cp) {
|
|
17
|
+
let parity = 0;
|
|
18
|
+
const seen = new Array(8).fill(false);
|
|
19
|
+
for (let start = 0; start < 8; start++) {
|
|
20
|
+
if (seen[start] || cp[start] === start)
|
|
21
|
+
continue;
|
|
22
|
+
let length = 0;
|
|
23
|
+
let current = start;
|
|
24
|
+
while (!seen[current]) {
|
|
25
|
+
seen[current] = true;
|
|
26
|
+
current = cp[current];
|
|
27
|
+
length++;
|
|
28
|
+
}
|
|
29
|
+
parity ^= (length - 1) & 1;
|
|
30
|
+
}
|
|
31
|
+
return parity;
|
|
32
|
+
}
|
|
33
|
+
const ROT_MOVES = {
|
|
34
|
+
"x": ["Rw", "Lw'"],
|
|
35
|
+
"y": ["Uw", "Dw'"],
|
|
36
|
+
"z": ["Fw", "Bw'"],
|
|
37
|
+
"x'": ["Rw'", "Lw"],
|
|
38
|
+
"y'": ["Uw'", "Dw"],
|
|
39
|
+
"z'": ["Fw'", "Bw"]
|
|
40
|
+
};
|
|
41
|
+
const VALID_24_ORIENTATIONS = (() => {
|
|
42
|
+
const map = new Map();
|
|
43
|
+
const solved = createSolvedCube4State();
|
|
44
|
+
const queue = [{ state: solved, path: [] }];
|
|
45
|
+
map.set(`${solved.centers[0]},${solved.centers[4]},${solved.centers[8]}`, []);
|
|
46
|
+
while (queue.length > 0) {
|
|
47
|
+
const { state, path } = queue.shift();
|
|
48
|
+
for (const [rot, moves] of Object.entries(ROT_MOVES)) {
|
|
49
|
+
const nextState = applyCube4Algorithm(state, moves);
|
|
50
|
+
const key = `${nextState.centers[0]},${nextState.centers[4]},${nextState.centers[8]}`;
|
|
51
|
+
if (!map.has(key)) {
|
|
52
|
+
const nextPath = [...path, rot];
|
|
53
|
+
map.set(key, nextPath);
|
|
54
|
+
queue.push({ state: nextState, path: nextPath });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return map;
|
|
59
|
+
})();
|
|
60
|
+
const ROT_MAP = {
|
|
61
|
+
"x": { "U": "B", "R": "R", "F": "U", "D": "F", "L": "L", "B": "D" },
|
|
62
|
+
"x'": { "U": "F", "R": "R", "F": "D", "D": "B", "L": "L", "B": "U" },
|
|
63
|
+
"y": { "U": "U", "R": "F", "F": "L", "D": "D", "L": "B", "B": "R" },
|
|
64
|
+
"y'": { "U": "U", "R": "B", "F": "R", "D": "D", "L": "F", "B": "L" },
|
|
65
|
+
"z": { "U": "R", "R": "D", "F": "F", "D": "L", "L": "U", "B": "B" },
|
|
66
|
+
"z'": { "U": "L", "R": "U", "F": "F", "D": "R", "L": "D", "B": "B" }
|
|
67
|
+
};
|
|
68
|
+
const INVERT_ROT = {
|
|
69
|
+
"x": "x'", "x'": "x",
|
|
70
|
+
"y": "y'", "y'": "y",
|
|
71
|
+
"z": "z'", "z'": "z"
|
|
72
|
+
};
|
|
73
|
+
function rotateMove(move, rot) {
|
|
74
|
+
const map = ROT_MAP[rot];
|
|
75
|
+
const face = move[0];
|
|
76
|
+
const newFace = map[face];
|
|
77
|
+
return (newFace + move.slice(1));
|
|
78
|
+
}
|
|
79
|
+
function rotateAlgorithm(alg, rotSeq) {
|
|
80
|
+
let res = [...alg];
|
|
81
|
+
for (const r of rotSeq) {
|
|
82
|
+
res = res.map((m) => rotateMove(m, r));
|
|
83
|
+
}
|
|
84
|
+
return res;
|
|
85
|
+
}
|
|
86
|
+
function invertRotationSequence(rotSeq) {
|
|
87
|
+
return [...rotSeq].reverse().map((r) => INVERT_ROT[r]);
|
|
88
|
+
}
|
|
89
|
+
function applyCubeRotation(state, rotSeq) {
|
|
90
|
+
let s = state;
|
|
91
|
+
for (const r of rotSeq) {
|
|
92
|
+
s = applyCube4Algorithm(s, ROT_MOVES[r]);
|
|
93
|
+
}
|
|
94
|
+
return s;
|
|
95
|
+
}
|
|
96
|
+
const STANDARD_ORDER = [
|
|
97
|
+
"U", "U2", "U'", "R", "R2", "R'", "F", "F2", "F'", "D", "D2", "D'",
|
|
98
|
+
"L", "L2", "L'", "B", "B2", "B'",
|
|
99
|
+
"Uw", "Uw2", "Uw'", "Rw", "Rw2", "Rw'", "Fw", "Fw2", "Fw'",
|
|
100
|
+
"Dw", "Dw2", "Dw'", "Lw", "Lw2", "Lw'", "Bw", "Bw2", "Bw'"
|
|
101
|
+
];
|
|
102
|
+
const STD_INDEX = new Map(STANDARD_ORDER.map((m, i) => [m, i]));
|
|
103
|
+
function baseOf(move) {
|
|
104
|
+
return move.replace(/[2']$/, "");
|
|
105
|
+
}
|
|
106
|
+
function axisClass(base) {
|
|
107
|
+
return { U: 0, D: 0, R: 1, L: 1, F: 2, B: 2 }[base];
|
|
108
|
+
}
|
|
109
|
+
function isCancelled(previous, current) {
|
|
110
|
+
const pb = baseOf(previous);
|
|
111
|
+
const cb = baseOf(current);
|
|
112
|
+
if (pb === cb)
|
|
113
|
+
return true;
|
|
114
|
+
if (axisClass(pb) === axisClass(cb)) {
|
|
115
|
+
return STD_INDEX.get(previous) > STD_INDEX.get(current);
|
|
116
|
+
}
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
function solveCenter1Diverse(centers, urf, tables, cap = 80) {
|
|
120
|
+
const startMask = center1Mask(centers, urf);
|
|
121
|
+
const solutions = [];
|
|
122
|
+
const path = [];
|
|
123
|
+
const minH = getCenter1Distance(startMask, tables);
|
|
124
|
+
for (let maxD = minH; maxD <= minH + 1; maxD++) {
|
|
125
|
+
const dfs = (mask, depth, lm) => {
|
|
126
|
+
if (solutions.length >= cap)
|
|
127
|
+
return true;
|
|
128
|
+
const h = getCenter1Distance(mask, tables);
|
|
129
|
+
if (h === 0) {
|
|
130
|
+
solutions.push({
|
|
131
|
+
moves: path.slice(0, depth).map((m) => CENTER1_MOVES[m]),
|
|
132
|
+
length: depth
|
|
133
|
+
});
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
if (depth + h > maxD)
|
|
137
|
+
return false;
|
|
138
|
+
for (let m = 0; m < CENTER1_MOVES.length; m++) {
|
|
139
|
+
if (lm >= 0 && Math.floor(lm / 3) === Math.floor(m / 3))
|
|
140
|
+
continue;
|
|
141
|
+
const next = applyCenter1Move(mask, tables.moveMaps, m);
|
|
142
|
+
path[depth] = m;
|
|
143
|
+
if (dfs(next, depth + 1, m))
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
return false;
|
|
147
|
+
};
|
|
148
|
+
if (dfs(startMask, 0, -1))
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
return solutions;
|
|
152
|
+
}
|
|
153
|
+
const phase3Solver = new FastPhase3Solver();
|
|
154
|
+
/**
|
|
155
|
+
* Solve a 4x4 state with the reduction solver + the cube3 two-phase finish.
|
|
156
|
+
* `solveCube3` is injected (the cube3 solver loads its tables lazily).
|
|
157
|
+
*/
|
|
158
|
+
export async function solveCube4State(state, tables, solveCube3) {
|
|
159
|
+
const sym = getSymTables();
|
|
160
|
+
const cornerParity = cornerPermutationParity(state.cp);
|
|
161
|
+
const p1Sols = solveCenter1Diverse(state.centers, 0, tables.center1, 80);
|
|
162
|
+
p1Sols.sort((a, b) => a.moves.length - b.moves.length);
|
|
163
|
+
const minP1 = p1Sols[0]?.moves.length ?? 0;
|
|
164
|
+
const candidates = [];
|
|
165
|
+
const moveLog = new Array(25);
|
|
166
|
+
function searchPhase2(c, r, maxl, lm, depth, bridgeState, p1Moves) {
|
|
167
|
+
if (isCenter2Goal(c, r)) {
|
|
168
|
+
if (maxl === 0) {
|
|
169
|
+
const moves2 = moveLog.slice(0, depth).map((m) => CENTER2_MOVES[m]);
|
|
170
|
+
const s2 = applyCube4Algorithm(bridgeState, moves2);
|
|
171
|
+
const pairing = encodePairingState(s2.wp);
|
|
172
|
+
if (pairing) {
|
|
173
|
+
const eXc = (pairing.parity ^ cornerParity);
|
|
174
|
+
const ct3 = encodeCenter3(s2, eXc).coordinate;
|
|
175
|
+
const cdist = tables.center3.pruning[ct3];
|
|
176
|
+
if (cdist !== 0xff && cdist !== undefined) {
|
|
177
|
+
const symIdx = getMvSymFrom(pairing.edge, pairing.edge, 20, sym.sym2Raw, sym.sym2Mask, sym.raw2Sym, sym.mvrot, sym.mvroto);
|
|
178
|
+
const edist = (tables.edge3.distances[(symIdx >> 3) >> 1] >>
|
|
179
|
+
(((symIdx >> 3) & 1) * 4)) &
|
|
180
|
+
0x0f;
|
|
181
|
+
candidates.push({
|
|
182
|
+
moves12: [...p1Moves, ...moves2],
|
|
183
|
+
length12: p1Moves.length + moves2.length,
|
|
184
|
+
ct3,
|
|
185
|
+
cdist,
|
|
186
|
+
edge: pairing.edge,
|
|
187
|
+
edist,
|
|
188
|
+
value: p1Moves.length + moves2.length + Math.max(cdist, edist),
|
|
189
|
+
state: s2
|
|
190
|
+
});
|
|
191
|
+
if (candidates.length >= 60)
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
if (maxl === 0)
|
|
199
|
+
return false;
|
|
200
|
+
for (let m = 0; m < CENTER2_SEARCH_MOVES.length; m++) {
|
|
201
|
+
if (lm >= 0 && isCancelled(CENTER2_SEARCH_MOVES[lm], CENTER2_SEARCH_MOVES[m])) {
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
const nc = tables.center2.ctMove[c * CENTER2_MOVES.length + m];
|
|
205
|
+
const nr = tables.center2.rlMove[r * CENTER2_MOVES.length + m];
|
|
206
|
+
const prun = tables.center2.pruning[nc * CENTER2_RL_STATES + nr];
|
|
207
|
+
if (prun >= maxl)
|
|
208
|
+
continue;
|
|
209
|
+
moveLog[depth] = m;
|
|
210
|
+
if (searchPhase2(nc, nr, maxl - 1, m, depth + 1, bridgeState, p1Moves))
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
let MAX_P2 = 8;
|
|
216
|
+
do {
|
|
217
|
+
OUTER: for (let length12 = minP1; length12 < minP1 + 14; length12++) {
|
|
218
|
+
for (const p1 of p1Sols) {
|
|
219
|
+
if (p1.moves.length > length12)
|
|
220
|
+
break;
|
|
221
|
+
const length2 = length12 - p1.moves.length;
|
|
222
|
+
if (length2 > MAX_P2 || length2 < 0)
|
|
223
|
+
continue;
|
|
224
|
+
const s1 = applyCube4Algorithm(state, p1.moves);
|
|
225
|
+
const bridge = center2Bridge(s1);
|
|
226
|
+
if (!bridge)
|
|
227
|
+
continue;
|
|
228
|
+
const prun = tables.center2.pruning[bridge.encoded.ct * CENTER2_RL_STATES + bridge.encoded.rl];
|
|
229
|
+
if (prun > length2)
|
|
230
|
+
continue;
|
|
231
|
+
const fullP1Moves = [...p1.moves, ...bridge.alignment];
|
|
232
|
+
if (searchPhase2(bridge.encoded.ct, bridge.encoded.rl, length2, -1, 0, bridge.state, fullP1Moves)) {
|
|
233
|
+
break OUTER;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
MAX_P2++;
|
|
238
|
+
} while (candidates.length === 0 && MAX_P2 <= 14);
|
|
239
|
+
if (candidates.length === 0) {
|
|
240
|
+
throw new Error("4x4 solve: no valid phase 2 candidates found.");
|
|
241
|
+
}
|
|
242
|
+
candidates.sort((a, b) => a.value - b.value);
|
|
243
|
+
let solvedCand = null;
|
|
244
|
+
let solvedMoves3 = null;
|
|
245
|
+
let standardReduced = null;
|
|
246
|
+
let rotPath = [];
|
|
247
|
+
let rotToStandard = [];
|
|
248
|
+
let MAX_LENGTH3 = 13;
|
|
249
|
+
let yieldCounter = 0;
|
|
250
|
+
do {
|
|
251
|
+
for (let length123 = candidates[0].value; length123 < 45; length123++) {
|
|
252
|
+
for (let i = 0; i < Math.min(candidates.length, 60); i++) {
|
|
253
|
+
const cand = candidates[i];
|
|
254
|
+
if (cand.value > length123)
|
|
255
|
+
break;
|
|
256
|
+
const length3 = length123 - cand.length12;
|
|
257
|
+
if (length3 > MAX_LENGTH3 || length3 < 0)
|
|
258
|
+
continue;
|
|
259
|
+
if (cand.edist <= length3 && cand.cdist <= length3) {
|
|
260
|
+
if (++yieldCounter % 100 === 0)
|
|
261
|
+
await new Promise(r => setTimeout(r, 0));
|
|
262
|
+
const moves3 = await phase3Solver.solveLength(cand.edge, cand.ct3, cand.edist, length3, tables.center3, tables.edge3.distances);
|
|
263
|
+
if (!moves3)
|
|
264
|
+
continue;
|
|
265
|
+
const reduced = applyCube4Algorithm(cand.state, moves3);
|
|
266
|
+
const key = `${reduced.centers[0]},${reduced.centers[4]},${reduced.centers[8]}`;
|
|
267
|
+
const path = VALID_24_ORIENTATIONS.get(key);
|
|
268
|
+
if (!path)
|
|
269
|
+
continue;
|
|
270
|
+
solvedCand = cand;
|
|
271
|
+
solvedMoves3 = moves3;
|
|
272
|
+
rotPath = path;
|
|
273
|
+
rotToStandard = invertRotationSequence(rotPath);
|
|
274
|
+
standardReduced = applyCubeRotation(reduced, rotToStandard);
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
if (solvedCand)
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
281
|
+
MAX_LENGTH3++;
|
|
282
|
+
} while (!solvedCand && MAX_LENGTH3 <= 18);
|
|
283
|
+
if (!solvedCand || !solvedMoves3 || !standardReduced) {
|
|
284
|
+
throw new Error("4x4 solve: reduction failed across all candidates.");
|
|
285
|
+
}
|
|
286
|
+
const cube3State = toCube3State(standardReduced);
|
|
287
|
+
if (!cube3State) {
|
|
288
|
+
throw new Error("4x4 solve: failed to convert reduced 4x4 state to 3x3 state.");
|
|
289
|
+
}
|
|
290
|
+
let cube3Solution;
|
|
291
|
+
try {
|
|
292
|
+
cube3Solution = await solveCube3(cube3State);
|
|
293
|
+
}
|
|
294
|
+
catch (error) {
|
|
295
|
+
if (error instanceof Cube3Phase2SearchError) {
|
|
296
|
+
throw error;
|
|
297
|
+
}
|
|
298
|
+
throw error;
|
|
299
|
+
}
|
|
300
|
+
const fullSolInOriginal = [
|
|
301
|
+
...solvedCand.moves12,
|
|
302
|
+
...solvedMoves3,
|
|
303
|
+
...rotateAlgorithm(cube3Solution.moves, rotPath)
|
|
304
|
+
];
|
|
305
|
+
const fullSolToStandard = [
|
|
306
|
+
...fullSolInOriginal,
|
|
307
|
+
...rotToStandard.flatMap((r) => ROT_MOVES[r])
|
|
308
|
+
];
|
|
309
|
+
const scramble = invertCube4Algorithm(fullSolToStandard);
|
|
310
|
+
return { moves: fullSolToStandard, scramble };
|
|
311
|
+
}
|
|
@@ -1,4 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The 4x4 scramble generator: sample a uniform random state, solve it with
|
|
3
|
+
* the reduction solver (phases 1-4), and invert the solution. The scramble
|
|
4
|
+
* always reproduces the sampled target state and facelets exactly.
|
|
5
|
+
*/
|
|
1
6
|
import type { ScrambleGenerator, ScrambleOptions, ScrambleResult } from "../../core/index.js";
|
|
7
|
+
export declare function isCube4TablesLoaded(): boolean;
|
|
2
8
|
export declare const cube4ScrambleGenerator: ScrambleGenerator;
|
|
3
|
-
export declare function generateCube4Scramble(options?: ScrambleOptions
|
|
9
|
+
export declare function generateCube4Scramble(options?: ScrambleOptions & {
|
|
10
|
+
skipValidation?: boolean;
|
|
11
|
+
}): Promise<ScrambleResult>;
|
|
4
12
|
//# sourceMappingURL=scrambler.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scrambler.d.ts","sourceRoot":"","sources":["../../../src/puzzles/cube4/scrambler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"scrambler.d.ts","sourceRoot":"","sources":["../../../src/puzzles/cube4/scrambler.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AA4C9F,wBAAgB,mBAAmB,IAAI,OAAO,CAU7C;AAED,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,CAkFzB"}
|
|
@@ -1,98 +1,140 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
/**
|
|
2
|
+
* The 4x4 scramble generator: sample a uniform random state, solve it with
|
|
3
|
+
* the reduction solver (phases 1-4), and invert the solution. The scramble
|
|
4
|
+
* always reproduces the sampled target state and facelets exactly.
|
|
5
|
+
*/
|
|
6
|
+
import { formatCube4Algorithm, applyCube4Algorithm } from "./moves.js";
|
|
7
|
+
import { generateRandomCube4State } from "./random-state.js";
|
|
8
|
+
import { createSolvedCube4State, cube4StatesEqual } from "./state.js";
|
|
9
|
+
import { formatCube4Facelets, toCube4Facelets } from "./facelets.js";
|
|
10
|
+
import { solveCube4State } from "./reduction/solve.js";
|
|
11
|
+
import { solveCube3State } from "../cube3/solver/index.js";
|
|
12
|
+
let cachedTables;
|
|
13
|
+
async function loadCube4Tables(baseUrl) {
|
|
14
|
+
if (cachedTables)
|
|
15
|
+
return cachedTables;
|
|
16
|
+
const isBrowser = typeof window !== "undefined" ||
|
|
17
|
+
typeof document !== "undefined" ||
|
|
18
|
+
(typeof self !== "undefined" && typeof self.importScripts === "function") ||
|
|
19
|
+
(typeof navigator !== "undefined" && /Moz|WebKit/.test(navigator.userAgent));
|
|
20
|
+
const isNode = typeof process !== "undefined" && process.versions?.node != null;
|
|
21
|
+
if (isBrowser && !isNode) {
|
|
22
|
+
const loader = await import(
|
|
23
|
+
/* webpackIgnore: true */
|
|
24
|
+
/* @vite-ignore */
|
|
25
|
+
"./reduction/browser-loader.js");
|
|
26
|
+
const cached = loader.getCachedCube4Tables();
|
|
27
|
+
if (cached) {
|
|
28
|
+
cachedTables = cached;
|
|
29
|
+
return cached;
|
|
30
|
+
}
|
|
31
|
+
const tables = await loader.loadCube4ReductionTablesBrowser(baseUrl);
|
|
32
|
+
cachedTables = tables;
|
|
33
|
+
return tables;
|
|
34
|
+
}
|
|
35
|
+
const loader = await import(
|
|
36
|
+
/* webpackIgnore: true */
|
|
37
|
+
/* @vite-ignore */
|
|
38
|
+
"./reduction/node-loader.js");
|
|
39
|
+
const tables = await loader.loadCube4ReductionTables();
|
|
40
|
+
cachedTables = tables;
|
|
41
|
+
return tables;
|
|
42
|
+
}
|
|
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
|
+
}
|
|
5
55
|
export const cube4ScrambleGenerator = {
|
|
6
56
|
info: {
|
|
7
57
|
eventId: "444",
|
|
8
58
|
name: "4x4x4 Cube",
|
|
9
59
|
puzzle: "cube-4x4",
|
|
10
60
|
quality: "random-state",
|
|
11
|
-
generator: "
|
|
12
|
-
description: "Random-state 4x4 scramble
|
|
61
|
+
generator: "cube4-reduction",
|
|
62
|
+
description: "Random-state 4x4 scramble via the reduction solver (centers + pairing + 3x3 finish)."
|
|
13
63
|
},
|
|
14
64
|
generate(options = {}) {
|
|
15
65
|
return generateCube4Scramble(options);
|
|
16
66
|
}
|
|
17
67
|
};
|
|
18
68
|
export async function generateCube4Scramble(options = {}) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
scramble,
|
|
29
|
-
moves,
|
|
30
|
-
quality: "random-state",
|
|
31
|
-
generator: "cubing-scramble",
|
|
32
|
-
metadata: {
|
|
33
|
-
totalMoves: moves.length,
|
|
34
|
-
distribution: "random-state",
|
|
35
|
-
state: targetState,
|
|
36
|
-
facelets: toCube4Facelets(targetState),
|
|
37
|
-
faceletString
|
|
38
|
-
}
|
|
39
|
-
};
|
|
40
|
-
if (options.seed !== undefined) {
|
|
41
|
-
result.seed = options.seed;
|
|
42
|
-
}
|
|
43
|
-
return result;
|
|
44
|
-
}
|
|
45
|
-
const MAX_AXIS_BLOCK = 3;
|
|
46
|
-
const ROTATION_PAIRS = {
|
|
47
|
-
"Uw": "Dw", "Dw": "Uw",
|
|
48
|
-
"Rw": "Lw", "Lw": "Rw",
|
|
49
|
-
"Fw": "Bw", "Bw": "Fw",
|
|
50
|
-
};
|
|
51
|
-
function getBaseMove(move) {
|
|
52
|
-
if (move.endsWith("2") || move.endsWith("'")) {
|
|
53
|
-
return move.slice(0, -1);
|
|
54
|
-
}
|
|
55
|
-
return move;
|
|
56
|
-
}
|
|
57
|
-
function getAxis(base) {
|
|
58
|
-
if (base.includes("R") || base.includes("L"))
|
|
59
|
-
return "X";
|
|
60
|
-
if (base.includes("U") || base.includes("D"))
|
|
61
|
-
return "Y";
|
|
62
|
-
if (base.includes("F") || base.includes("B"))
|
|
63
|
-
return "Z";
|
|
64
|
-
return "";
|
|
65
|
-
}
|
|
66
|
-
function isRedundant(moves, newMove) {
|
|
67
|
-
if (moves.length === 0)
|
|
68
|
-
return false;
|
|
69
|
-
const newBase = getBaseMove(newMove);
|
|
70
|
-
const newAxis = getAxis(newBase);
|
|
71
|
-
const lastBase = getBaseMove(moves[moves.length - 1]);
|
|
72
|
-
if (lastBase === newBase)
|
|
73
|
-
return true;
|
|
74
|
-
const lastAxis = getAxis(lastBase);
|
|
75
|
-
if (newAxis !== lastAxis)
|
|
76
|
-
return false;
|
|
77
|
-
const basesInBlock = new Set();
|
|
78
|
-
basesInBlock.add(lastBase);
|
|
79
|
-
let blockLength = 1;
|
|
80
|
-
for (let i = moves.length - 2; i >= 0; i--) {
|
|
81
|
-
const base = getBaseMove(moves[i]);
|
|
82
|
-
if (getAxis(base) === newAxis) {
|
|
83
|
-
basesInBlock.add(base);
|
|
84
|
-
blockLength++;
|
|
69
|
+
const tables = await loadCube4Tables();
|
|
70
|
+
let lastError;
|
|
71
|
+
// Retry with different states if the solver fails to find a physical
|
|
72
|
+
// reduction (some random states are outside the solver's effective
|
|
73
|
+
// horizon with the current candidate/length caps).
|
|
74
|
+
for (let attempt = 0; attempt < 5; attempt++) {
|
|
75
|
+
let attemptOptions;
|
|
76
|
+
if (attempt === 0) {
|
|
77
|
+
attemptOptions = options;
|
|
85
78
|
}
|
|
86
79
|
else {
|
|
87
|
-
|
|
80
|
+
attemptOptions = { ...options };
|
|
81
|
+
if (options.seed !== undefined) {
|
|
82
|
+
attemptOptions.seed = `${options.seed}:retry${attempt}`;
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
delete attemptOptions.seed;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const attemptState = generateRandomCube4State(attemptOptions);
|
|
89
|
+
try {
|
|
90
|
+
const solvePromise = solveCube4State(attemptState, tables, (cube3State) => solveCube3State(cube3State));
|
|
91
|
+
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error("4x4 solve: timeout")), 20000));
|
|
92
|
+
const { scramble: scrambleMoves } = await Promise.race([solvePromise, timeoutPromise]);
|
|
93
|
+
if (!options.skipValidation) {
|
|
94
|
+
const scrambledState = applyCube4Algorithm(createSolvedCube4State(), scrambleMoves);
|
|
95
|
+
if (!cube4StatesEqual(scrambledState, attemptState)) {
|
|
96
|
+
throw new Error("Generated 4x4 scramble does not reproduce the target random state.");
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
const facelets = toCube4Facelets(attemptState);
|
|
100
|
+
const result = {
|
|
101
|
+
eventId: "444",
|
|
102
|
+
scramble: formatCube4Algorithm(scrambleMoves),
|
|
103
|
+
moves: scrambleMoves,
|
|
104
|
+
quality: "random-state",
|
|
105
|
+
generator: "cube4-reduction",
|
|
106
|
+
metadata: {
|
|
107
|
+
generator: "cube4-reduction",
|
|
108
|
+
state: attemptState,
|
|
109
|
+
facelets,
|
|
110
|
+
faceletString: formatCube4Facelets(facelets),
|
|
111
|
+
distribution: "random-state",
|
|
112
|
+
...options.metadata
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
if (options.seed !== undefined) {
|
|
116
|
+
result.seed = options.seed;
|
|
117
|
+
}
|
|
118
|
+
return result;
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
lastError = error;
|
|
122
|
+
// Retry on deterministic reduction / 3x3 failures (some random states are
|
|
123
|
+
// outside the solver's horizon with the current caps).
|
|
124
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
125
|
+
const isPhase2Error = typeof error?.name === "string" &&
|
|
126
|
+
error.name.includes("Cube3Phase2");
|
|
127
|
+
if (error instanceof Error &&
|
|
128
|
+
(msg.includes("reduction failed") ||
|
|
129
|
+
msg.includes("failed to convert") ||
|
|
130
|
+
msg.includes("no valid phase 2") ||
|
|
131
|
+
msg.includes("timeout") ||
|
|
132
|
+
isPhase2Error ||
|
|
133
|
+
msg.includes("Phase 2 solution not found"))) {
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
throw error;
|
|
88
137
|
}
|
|
89
138
|
}
|
|
90
|
-
|
|
91
|
-
return true;
|
|
92
|
-
const complement = ROTATION_PAIRS[newBase];
|
|
93
|
-
if (complement && basesInBlock.has(complement))
|
|
94
|
-
return true;
|
|
95
|
-
if (blockLength >= MAX_AXIS_BLOCK)
|
|
96
|
-
return true;
|
|
97
|
-
return false;
|
|
139
|
+
throw lastError;
|
|
98
140
|
}
|