@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
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Continuous two-phase 3x3 solver for the 4x4 finish (Kociemba-style).
|
|
4
|
+
*
|
|
5
|
+
* Unlike the batch solver in `cube3/solver` (collect phase-1 solutions,
|
|
6
|
+
* then solve phase 2 for each), this is the classic continuous search: an
|
|
7
|
+
* iterative-deepening phase 1 where EVERY phase-1 solution immediately runs
|
|
8
|
+
* phase 2 under the remaining length budget, and every solution found
|
|
9
|
+
* tightens the bound for the rest of the search. Optional legal views are
|
|
10
|
+
* searched interleaved by phase-1 depth — up to 3 relabelled axes
|
|
11
|
+
* (identity, x, z) × {state, inverse} — which is what makes short solutions
|
|
12
|
+
* cheap to find (the existing cube3 solver's rotation/inverse trick).
|
|
13
|
+
*
|
|
14
|
+
* Tables: the already-loaded cube3 tables (phase-1: twist/flip/slice move
|
|
15
|
+
* tables + twist×flip, slice×twist, slice×flip pruning; phase-2: cp/ep/sp
|
|
16
|
+
* move tables + cp×sp, ep×sp pruning). Nothing is generated here.
|
|
17
|
+
*
|
|
18
|
+
* Deterministic: the only stopping rules are node counts and lengths, so
|
|
19
|
+
* the result is a pure function of (state, options).
|
|
20
|
+
*/
|
|
21
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
+
exports.Finish3SearchError = void 0;
|
|
23
|
+
exports.solveFinish3 = solveFinish3;
|
|
24
|
+
const types_js_1 = require("../../cube3/types.js");
|
|
25
|
+
const moves_js_1 = require("../../cube3/moves.js");
|
|
26
|
+
const state_js_1 = require("../../cube3/state.js");
|
|
27
|
+
const moves_js_2 = require("../moves.js");
|
|
28
|
+
const state_js_2 = require("../state.js");
|
|
29
|
+
const phase4_js_1 = require("./phase4.js");
|
|
30
|
+
const coordinates_js_1 = require("../../cube3/solver/coordinates.js");
|
|
31
|
+
const tables_js_1 = require("../../cube3/solver/tables.js");
|
|
32
|
+
const symmetry_js_1 = require("../../cube3/solver/symmetry.js");
|
|
33
|
+
class Finish3SearchError extends Error {
|
|
34
|
+
code = "cube4-finish3-cap";
|
|
35
|
+
constructor(message) {
|
|
36
|
+
super(message);
|
|
37
|
+
this.name = "Finish3SearchError";
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
exports.Finish3SearchError = Finish3SearchError;
|
|
41
|
+
function toCubie(state) {
|
|
42
|
+
return {
|
|
43
|
+
cp: Int8Array.from(state.corners.permutation),
|
|
44
|
+
co: Int8Array.from(state.corners.orientation),
|
|
45
|
+
ep: Int8Array.from(state.edges.permutation),
|
|
46
|
+
eo: Int8Array.from(state.edges.orientation)
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function toState(cubie) {
|
|
50
|
+
return {
|
|
51
|
+
corners: { permutation: Array.from(cubie.cp), orientation: Array.from(cubie.co) },
|
|
52
|
+
edges: { permutation: Array.from(cubie.ep), orientation: Array.from(cubie.eo) }
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function inverse(a) {
|
|
56
|
+
const out = { cp: new Int8Array(8), co: new Int8Array(8), ep: new Int8Array(12), eo: new Int8Array(12) };
|
|
57
|
+
for (let i = 0; i < 8; i++) {
|
|
58
|
+
out.cp[a.cp[i]] = i;
|
|
59
|
+
out.co[a.cp[i]] = (3 - a.co[i]) % 3;
|
|
60
|
+
}
|
|
61
|
+
for (let i = 0; i < 12; i++) {
|
|
62
|
+
out.ep[a.ep[i]] = i;
|
|
63
|
+
out.eo[a.ep[i]] = a.eo[i];
|
|
64
|
+
}
|
|
65
|
+
return out;
|
|
66
|
+
}
|
|
67
|
+
/** Per-move permutation effects, as the solved state with the move applied. */
|
|
68
|
+
const MOVE_CUBIES = types_js_1.CUBE3_MOVES.map((move) => toCubie((0, moves_js_1.applyCube3Move)((0, state_js_1.createSolvedCube3State)(), move)));
|
|
69
|
+
const MOVE_CP = Int8Array.from(MOVE_CUBIES.flatMap((c) => Array.from(c.cp)));
|
|
70
|
+
const MOVE_EP = Int8Array.from(MOVE_CUBIES.flatMap((c) => Array.from(c.ep)));
|
|
71
|
+
/** Face (U0 D1 R2 L3 F4 B5) of each phase-1 move index. */
|
|
72
|
+
const FACE = Int8Array.from(types_js_1.CUBE3_MOVES.map((_, m) => Math.floor(m / 3)));
|
|
73
|
+
/** Phase-1 move index of each phase-2 move index. */
|
|
74
|
+
const P2_TO_P1 = Int8Array.from(tables_js_1.CUBE3_PHASE2_MOVES.map((move) => types_js_1.CUBE3_MOVES.indexOf(move)));
|
|
75
|
+
/** Phase-1 moves that keep G1 (U, D, R2, L2, F2, B2). */
|
|
76
|
+
const IS_P2_MOVE = new Uint8Array(18);
|
|
77
|
+
for (const m of P2_TO_P1)
|
|
78
|
+
IS_P2_MOVE[m] = 1;
|
|
79
|
+
/** Skip `face` after `last`: same face, or same axis in descending order. */
|
|
80
|
+
const SKIP = new Uint8Array(7 * 6);
|
|
81
|
+
for (let last = -1; last < 6; last++) {
|
|
82
|
+
for (let face = 0; face < 6; face++) {
|
|
83
|
+
SKIP[(last + 1) * 6 + face] =
|
|
84
|
+
last >= 0 && (face === last || ((face >> 1) === (last >> 1) && face < last)) ? 1 : 0;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
const FACT = [1, 1, 2, 6, 24, 120, 720, 5040, 40320];
|
|
88
|
+
/** Lehmer rank of values[offset..offset+n) (same order as `encodePermutation`). */
|
|
89
|
+
function lehmer(values, offset, n, base) {
|
|
90
|
+
let rank = 0;
|
|
91
|
+
for (let i = 0; i < n; i++) {
|
|
92
|
+
const v = values[offset + i];
|
|
93
|
+
let smaller = 0;
|
|
94
|
+
for (let j = i + 1; j < n; j++)
|
|
95
|
+
if (values[offset + j] < v)
|
|
96
|
+
smaller++;
|
|
97
|
+
rank += smaller * FACT[n - 1 - i];
|
|
98
|
+
}
|
|
99
|
+
return rank - base * 0;
|
|
100
|
+
}
|
|
101
|
+
// ---------------------------------------------------------------------------
|
|
102
|
+
// Views (relabelled scrambles and inverses)
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
const VIEW_ROTATIONS = [[], ["x"], ["z"]];
|
|
105
|
+
function invertMoves(moves) {
|
|
106
|
+
return [...moves].reverse().map((move) => {
|
|
107
|
+
if (move.endsWith("2"))
|
|
108
|
+
return move;
|
|
109
|
+
if (move.endsWith("'"))
|
|
110
|
+
return move.slice(0, -1);
|
|
111
|
+
return `${move}'`;
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
function multiply(a, b) {
|
|
115
|
+
const out = { cp: new Int8Array(8), co: new Int8Array(8), ep: new Int8Array(12), eo: new Int8Array(12) };
|
|
116
|
+
for (let i = 0; i < 8; i++) {
|
|
117
|
+
out.cp[i] = a.cp[b.cp[i]];
|
|
118
|
+
out.co[i] = (a.co[b.cp[i]] + b.co[i]) % 3;
|
|
119
|
+
}
|
|
120
|
+
for (let i = 0; i < 12; i++) {
|
|
121
|
+
out.ep[i] = a.ep[b.ep[i]];
|
|
122
|
+
out.eo[i] = (a.eo[b.ep[i]] + b.eo[i]) % 2;
|
|
123
|
+
}
|
|
124
|
+
return out;
|
|
125
|
+
}
|
|
126
|
+
/** 3x3 cubies of the whole-cube rotations, read off the rotated 4x4 model. */
|
|
127
|
+
const ROTATION_WORDS = {
|
|
128
|
+
x: ["Rw", "Lw'"],
|
|
129
|
+
z: ["Fw", "Bw'"]
|
|
130
|
+
};
|
|
131
|
+
const ROTATION_CUBIES = new Map(Object.entries(ROTATION_WORDS).map(([name, word]) => [
|
|
132
|
+
name,
|
|
133
|
+
toCubie((0, phase4_js_1.toCube3State)((0, moves_js_2.applyCube4Algorithm)((0, state_js_2.createSolvedCube4State)(), word)))
|
|
134
|
+
]));
|
|
135
|
+
/**
|
|
136
|
+
* Views of the state: conjugations by x / z and inverses. The x-view of S
|
|
137
|
+
* is R⁻¹·S·R (R = the rotation's cubie) — the same state the relabelled
|
|
138
|
+
* scramble `rotate(scramble(S), [x])` reaches (verified in
|
|
139
|
+
* test/cube4-finish3.test.ts), so a view solution maps back by relabelling
|
|
140
|
+
* with the inverse rotation. Conjugation preserves parity and legality.
|
|
141
|
+
*/
|
|
142
|
+
function buildViews(state, count) {
|
|
143
|
+
if (count !== 1 && count !== 2 && count !== 6) {
|
|
144
|
+
throw new RangeError(`finish3: unsupported view count ${count}`);
|
|
145
|
+
}
|
|
146
|
+
const base = toCubie(state);
|
|
147
|
+
const views = [];
|
|
148
|
+
const axes = count === 6 ? VIEW_ROTATIONS : [[]];
|
|
149
|
+
for (const rotation of axes) {
|
|
150
|
+
let viewCubie = base;
|
|
151
|
+
for (const r of rotation) {
|
|
152
|
+
const rc = ROTATION_CUBIES.get(r);
|
|
153
|
+
viewCubie = multiply(multiply(inverse(rc), viewCubie), rc);
|
|
154
|
+
}
|
|
155
|
+
const makeView = (inverted) => {
|
|
156
|
+
views.push({
|
|
157
|
+
cubie: inverted ? inverse(viewCubie) : viewCubie,
|
|
158
|
+
mapSolution: (moves) => {
|
|
159
|
+
const direct = inverted ? invertMoves(moves) : [...moves];
|
|
160
|
+
return rotation.length === 0
|
|
161
|
+
? direct
|
|
162
|
+
: (0, symmetry_js_1.rotateCube3AlgorithmBySequence)(direct, (0, symmetry_js_1.invertCube3RotationSequence)(rotation));
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
};
|
|
166
|
+
makeView(false);
|
|
167
|
+
if (count >= 2)
|
|
168
|
+
makeView(true);
|
|
169
|
+
}
|
|
170
|
+
return views;
|
|
171
|
+
}
|
|
172
|
+
// ---------------------------------------------------------------------------
|
|
173
|
+
// Search
|
|
174
|
+
// ---------------------------------------------------------------------------
|
|
175
|
+
function solveFinish3(state, tables, options = {}) {
|
|
176
|
+
const targetLength = options.targetLength ?? 0;
|
|
177
|
+
const maxNodes = options.maxNodes ?? 200_000;
|
|
178
|
+
const hardMaxNodes = options.hardMaxNodes ?? 20_000_000;
|
|
179
|
+
const maxP2 = options.maxPhase2Length ?? 12;
|
|
180
|
+
const views = buildViews(state, options.views ?? 1);
|
|
181
|
+
const p1 = tables.phase1;
|
|
182
|
+
const coMove = p1.moveTables.cornerOrientation.values;
|
|
183
|
+
const eoMove = p1.moveTables.edgeOrientation.values;
|
|
184
|
+
const udMove = p1.moveTables.udSlice.values;
|
|
185
|
+
const udSolved = p1.moveTables.udSlice.solvedCoordinate;
|
|
186
|
+
const coeo = p1.pruningTables.cornerOrientationEdgeOrientation.values;
|
|
187
|
+
const udPrun = p1.pruningTables.udSlice.values;
|
|
188
|
+
const jointUdCo = p1.jointTables?.jointUdCo;
|
|
189
|
+
const jointUdEo = p1.jointTables?.jointUdEo;
|
|
190
|
+
const p2 = tables.phase2;
|
|
191
|
+
if (p2.phase2Moves.join(" ") !== tables_js_1.CUBE3_PHASE2_MOVES.join(" ")) {
|
|
192
|
+
throw new Error("finish3: unexpected phase-2 move order");
|
|
193
|
+
}
|
|
194
|
+
const cpMove = p2.moveTables.cornerPermutation.values;
|
|
195
|
+
const epMove = p2.moveTables.udEdgePermutation.values;
|
|
196
|
+
const spMove = p2.moveTables.slicePermutation.values;
|
|
197
|
+
const cpsp = p2.pruningTables.cornerPermutationSlicePermutation.values;
|
|
198
|
+
const epsp = p2.pruningTables.udEdgePermutationSlicePermutation.values;
|
|
199
|
+
const nib = (table, index) => (table[index >> 1] >> ((index & 1) << 2)) & 0x0f;
|
|
200
|
+
const h1 = (co, eo, ud) => {
|
|
201
|
+
let h = nib(coeo, (co << 11) + eo);
|
|
202
|
+
if (jointUdCo !== undefined && jointUdEo !== undefined) {
|
|
203
|
+
const a = nib(jointUdCo, ud * 2187 + co);
|
|
204
|
+
if (a > h)
|
|
205
|
+
h = a;
|
|
206
|
+
const b = nib(jointUdEo, ud * 2048 + eo);
|
|
207
|
+
if (b > h)
|
|
208
|
+
h = b;
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
const u = nib(udPrun, ud);
|
|
212
|
+
if (u > h)
|
|
213
|
+
h = u;
|
|
214
|
+
}
|
|
215
|
+
return h;
|
|
216
|
+
};
|
|
217
|
+
const h2 = (cp, ep, sp) => {
|
|
218
|
+
const a = nib(cpsp, cp * 24 + sp);
|
|
219
|
+
const b = nib(epsp, ep * 24 + sp);
|
|
220
|
+
return a > b ? a : b;
|
|
221
|
+
};
|
|
222
|
+
const path1 = new Int8Array(32);
|
|
223
|
+
const path2 = new Int8Array(32);
|
|
224
|
+
const cpScratch = new Int8Array(8);
|
|
225
|
+
const epScratch = new Int8Array(12);
|
|
226
|
+
const cpTmp = new Int8Array(8);
|
|
227
|
+
const epTmp = new Int8Array(12);
|
|
228
|
+
let nodes = 0;
|
|
229
|
+
let bound = 99; // best length found so far (solutions must be shorter)
|
|
230
|
+
let best = null;
|
|
231
|
+
let stop = false;
|
|
232
|
+
let view = views[0];
|
|
233
|
+
// Hot-path guard: `shouldStop` can only change its answer once `nodes`
|
|
234
|
+
// passes this limit (the other conditions are re-checked in `leaf`).
|
|
235
|
+
let nodeLimit = hardMaxNodes;
|
|
236
|
+
const hasJoint = jointUdCo !== undefined && jointUdEo !== undefined;
|
|
237
|
+
const shouldStop = () => {
|
|
238
|
+
if (nodes > hardMaxNodes) {
|
|
239
|
+
if (best === null) {
|
|
240
|
+
throw new Finish3SearchError("finish3: node cap exceeded without a solution");
|
|
241
|
+
}
|
|
242
|
+
stop = true;
|
|
243
|
+
return true;
|
|
244
|
+
}
|
|
245
|
+
if (best !== null)
|
|
246
|
+
nodeLimit = Math.min(maxNodes, hardMaxNodes);
|
|
247
|
+
if (best !== null && (nodes > maxNodes || bound <= targetLength)) {
|
|
248
|
+
stop = true;
|
|
249
|
+
return true;
|
|
250
|
+
}
|
|
251
|
+
return false;
|
|
252
|
+
};
|
|
253
|
+
const phase2 = (cp, ep, sp, depth, togo, last) => {
|
|
254
|
+
nodes++;
|
|
255
|
+
if (nodes > nodeLimit && shouldStop())
|
|
256
|
+
return false;
|
|
257
|
+
if (togo === 0)
|
|
258
|
+
return cp === 0 && ep === 0 && sp === 0;
|
|
259
|
+
const skipRow = (last + 1) * 6;
|
|
260
|
+
const limit = togo - 1;
|
|
261
|
+
const cpRow = cp * 10;
|
|
262
|
+
const epRow = ep * 10;
|
|
263
|
+
const spRow = sp * 10;
|
|
264
|
+
for (let i = 0; i < 10; i++) {
|
|
265
|
+
if (stop)
|
|
266
|
+
return false;
|
|
267
|
+
const m = P2_TO_P1[i];
|
|
268
|
+
if (SKIP[skipRow + FACE[m]])
|
|
269
|
+
continue;
|
|
270
|
+
const nsp = spMove[spRow + i];
|
|
271
|
+
const ncp = cpMove[cpRow + i];
|
|
272
|
+
const ia = ncp * 24 + nsp;
|
|
273
|
+
if (((cpsp[ia >> 1] >> ((ia & 1) << 2)) & 0x0f) > limit)
|
|
274
|
+
continue;
|
|
275
|
+
const nep = epMove[epRow + i];
|
|
276
|
+
const ib = nep * 24 + nsp;
|
|
277
|
+
if (((epsp[ib >> 1] >> ((ib & 1) << 2)) & 0x0f) > limit)
|
|
278
|
+
continue;
|
|
279
|
+
path2[depth] = m;
|
|
280
|
+
if (phase2(ncp, nep, nsp, depth + 1, togo - 1, FACE[m]))
|
|
281
|
+
return true;
|
|
282
|
+
}
|
|
283
|
+
return false;
|
|
284
|
+
};
|
|
285
|
+
/** Runs phase 2 on a phase-1 solution of length d1; true = stop searching. */
|
|
286
|
+
const leaf = (d1) => {
|
|
287
|
+
const base = view.cubie;
|
|
288
|
+
cpScratch.set(base.cp);
|
|
289
|
+
epScratch.set(base.ep);
|
|
290
|
+
for (let k = 0; k < d1; k++) {
|
|
291
|
+
const off = path1[k];
|
|
292
|
+
for (let i = 0; i < 8; i++)
|
|
293
|
+
cpTmp[i] = cpScratch[MOVE_CP[off * 8 + i]];
|
|
294
|
+
for (let i = 0; i < 12; i++)
|
|
295
|
+
epTmp[i] = epScratch[MOVE_EP[off * 12 + i]];
|
|
296
|
+
cpScratch.set(cpTmp);
|
|
297
|
+
epScratch.set(epTmp);
|
|
298
|
+
}
|
|
299
|
+
const cp = lehmer(cpScratch, 0, 8, 0);
|
|
300
|
+
const ep = lehmer(epScratch, 0, 8, 0);
|
|
301
|
+
for (let i = 8; i < 12; i++)
|
|
302
|
+
epTmp[i] = epScratch[i] - 8;
|
|
303
|
+
const sp = lehmer(epTmp, 8, 4, 0);
|
|
304
|
+
const lb = h2(cp, ep, sp);
|
|
305
|
+
const maxD2 = Math.min(bound - 1 - d1, maxP2);
|
|
306
|
+
if (lb > maxD2)
|
|
307
|
+
return false;
|
|
308
|
+
const last = d1 > 0 ? FACE[path1[d1 - 1]] : -1;
|
|
309
|
+
for (let d2 = lb; d2 <= maxD2; d2++) {
|
|
310
|
+
if (phase2(cp, ep, sp, 0, d2, last)) {
|
|
311
|
+
const moves = [];
|
|
312
|
+
for (let k = 0; k < d1; k++)
|
|
313
|
+
moves.push(types_js_1.CUBE3_MOVES[path1[k]]);
|
|
314
|
+
for (let k = 0; k < d2; k++)
|
|
315
|
+
moves.push(types_js_1.CUBE3_MOVES[path2[k]]);
|
|
316
|
+
best = view.mapSolution(moves);
|
|
317
|
+
bound = d1 + d2;
|
|
318
|
+
break;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
return shouldStop();
|
|
322
|
+
};
|
|
323
|
+
const phase1 = (co, eo, ud, depth, togo, last) => {
|
|
324
|
+
nodes++;
|
|
325
|
+
if (togo === 0) {
|
|
326
|
+
// Only phase-1 solutions ending in a quarter turn of R/L/F/B: one
|
|
327
|
+
// ending in a G1 move is a shorter phase-1 solution plus phase 2.
|
|
328
|
+
if (depth > 0 && IS_P2_MOVE[path1[depth - 1]])
|
|
329
|
+
return false;
|
|
330
|
+
return leaf(depth);
|
|
331
|
+
}
|
|
332
|
+
if ((nodes & 1023) === 0 && shouldStop())
|
|
333
|
+
return true;
|
|
334
|
+
const skipRow = (last + 1) * 6;
|
|
335
|
+
const limit = togo - 1;
|
|
336
|
+
const lastLevel = togo === 1;
|
|
337
|
+
const coRow = co * 18;
|
|
338
|
+
const eoRow = eo * 18;
|
|
339
|
+
const udRow = ud * 18;
|
|
340
|
+
for (let m = 0; m < 18; m++) {
|
|
341
|
+
if (stop)
|
|
342
|
+
return true;
|
|
343
|
+
if (SKIP[skipRow + FACE[m]])
|
|
344
|
+
continue;
|
|
345
|
+
if (lastLevel && IS_P2_MOVE[m])
|
|
346
|
+
continue;
|
|
347
|
+
const nud = udMove[udRow + m];
|
|
348
|
+
const nco = coMove[coRow + m];
|
|
349
|
+
const neo = eoMove[eoRow + m];
|
|
350
|
+
if (hasJoint) {
|
|
351
|
+
const ia = nud * 2187 + nco;
|
|
352
|
+
if (((jointUdCo[ia >> 1] >> ((ia & 1) << 2)) & 0x0f) > limit)
|
|
353
|
+
continue;
|
|
354
|
+
const ib = nud * 2048 + neo;
|
|
355
|
+
if (((jointUdEo[ib >> 1] >> ((ib & 1) << 2)) & 0x0f) > limit)
|
|
356
|
+
continue;
|
|
357
|
+
const ic = (nco << 11) + neo;
|
|
358
|
+
if (((coeo[ic >> 1] >> ((ic & 1) << 2)) & 0x0f) > limit)
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
else if (h1(nco, neo, nud) > limit)
|
|
362
|
+
continue;
|
|
363
|
+
path1[depth] = m;
|
|
364
|
+
if (phase1(nco, neo, nud, depth + 1, togo - 1, FACE[m]))
|
|
365
|
+
return true;
|
|
366
|
+
}
|
|
367
|
+
return false;
|
|
368
|
+
};
|
|
369
|
+
const roots = views.map((v) => {
|
|
370
|
+
const s = toState(v.cubie);
|
|
371
|
+
const co = (0, coordinates_js_1.encodeCornerOrientation)(s);
|
|
372
|
+
const eo = (0, coordinates_js_1.encodeEdgeOrientation)(s);
|
|
373
|
+
const ud = (0, coordinates_js_1.encodeUdSlice)(s);
|
|
374
|
+
return { co, eo, ud, h: h1(co, eo, ud), solvedG1: co === 0 && eo === 0 && ud === udSolved };
|
|
375
|
+
});
|
|
376
|
+
let minDepth = 99;
|
|
377
|
+
for (const r of roots)
|
|
378
|
+
if (r.h < minDepth)
|
|
379
|
+
minDepth = r.h;
|
|
380
|
+
for (let d1 = minDepth; d1 < bound && !stop; d1++) {
|
|
381
|
+
for (let v = 0; v < views.length && !stop; v++) {
|
|
382
|
+
const r = roots[v];
|
|
383
|
+
if (r.h > d1)
|
|
384
|
+
continue;
|
|
385
|
+
view = views[v];
|
|
386
|
+
if (d1 === 0) {
|
|
387
|
+
if (r.solvedG1 && leaf(0))
|
|
388
|
+
stop = true;
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
if (phase1(r.co, r.eo, r.ud, 0, d1, -1))
|
|
392
|
+
stop = true;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
if (best === null)
|
|
396
|
+
throw new Finish3SearchError("finish3: no solution found");
|
|
397
|
+
return { moves: best, nodes };
|
|
398
|
+
}
|