@cubingcenter/scrambler 0.6.2 → 0.7.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 +2 -0
- 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.js +6 -2
- 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 +18 -0
- package/dist/puzzles/cube3/solver/phase1.d.ts.map +1 -1
- package/dist/puzzles/cube3/solver/phase1.js +132 -14
- package/dist/puzzles/cube3/solver/phase2.d.ts +47 -0
- package/dist/puzzles/cube3/solver/phase2.d.ts.map +1 -1
- package/dist/puzzles/cube3/solver/phase2.js +236 -66
- 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/validation.d.ts.map +1 -1
- package/dist/puzzles/cube3/validation.js +15 -7
- package/dist/puzzles/cube4/index.d.ts +3 -2
- package/dist/puzzles/cube4/index.d.ts.map +1 -1
- package/dist/puzzles/cube4/index.js +2 -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 +120 -11
- package/dist/puzzles/cube4/reduction/browser-loader.d.ts +3 -0
- package/dist/puzzles/cube4/reduction/browser-loader.d.ts.map +1 -1
- package/dist/puzzles/cube4/reduction/browser-loader.js +34 -3
- package/dist/puzzles/cube4/reduction/center2.d.ts +20 -3
- package/dist/puzzles/cube4/reduction/center2.d.ts.map +1 -1
- package/dist/puzzles/cube4/reduction/center2.js +53 -9
- 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/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 +38 -0
- package/dist/puzzles/cube4/reduction/generated/hc-move.bin +0 -0
- package/dist/puzzles/cube4/reduction/hc-pdb.d.ts +50 -0
- package/dist/puzzles/cube4/reduction/hc-pdb.d.ts.map +1 -0
- package/dist/puzzles/cube4/reduction/hc-pdb.js +202 -0
- package/dist/puzzles/cube4/reduction/node-loader.d.ts +5 -0
- package/dist/puzzles/cube4/reduction/node-loader.d.ts.map +1 -1
- package/dist/puzzles/cube4/reduction/node-loader.js +48 -8
- package/dist/puzzles/cube4/reduction/pairing-coordinate.js +5 -5
- package/dist/puzzles/cube4/reduction/phase3-search.d.ts +111 -5
- package/dist/puzzles/cube4/reduction/phase3-search.d.ts.map +1 -1
- package/dist/puzzles/cube4/reduction/phase3-search.js +341 -22
- package/dist/puzzles/cube4/reduction/solve.d.ts +112 -1
- package/dist/puzzles/cube4/reduction/solve.d.ts.map +1 -1
- package/dist/puzzles/cube4/reduction/solve.js +920 -181
- package/dist/puzzles/cube4/scrambler.d.ts +105 -4
- package/dist/puzzles/cube4/scrambler.d.ts.map +1 -1
- package/dist/puzzles/cube4/scrambler.js +221 -161
- package/dist/puzzles/cube4/state.d.ts.map +1 -1
- package/dist/puzzles/cube4/state.js +4 -1
- 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/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-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 +6 -2
- 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 +133 -14
- package/dist-cjs/puzzles/cube3/solver/phase2.js +233 -63
- package/dist-cjs/puzzles/cube3/solver/symmetry.js +80 -0
- package/dist-cjs/puzzles/cube3/validation.js +15 -7
- package/dist-cjs/puzzles/cube4/index.js +3 -1
- package/dist-cjs/puzzles/cube4/moves.js +121 -11
- package/dist-cjs/puzzles/cube4/reduction/browser-loader.js +34 -3
- package/dist-cjs/puzzles/cube4/reduction/center2.js +54 -9
- package/dist-cjs/puzzles/cube4/reduction/edge3.js +24 -3
- 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 +38 -0
- package/dist-cjs/puzzles/cube4/reduction/generated/hc-move.bin +0 -0
- package/dist-cjs/puzzles/cube4/reduction/hc-pdb.js +213 -0
- package/dist-cjs/puzzles/cube4/reduction/node-loader.js +48 -8
- package/dist-cjs/puzzles/cube4/reduction/pairing-coordinate.js +5 -5
- package/dist-cjs/puzzles/cube4/reduction/phase3-search.js +343 -23
- package/dist-cjs/puzzles/cube4/reduction/solve.js +918 -173
- package/dist-cjs/puzzles/cube4/scrambler.js +219 -158
- package/dist-cjs/puzzles/cube4/state.js +4 -1
- package/dist-cjs/puzzles/pyraminx/solver/search.js +2 -5
- package/dist-cjs/puzzles/pyraminx/solver/tables.js +18 -1
- package/dist-cjs/puzzles/skewb/solver/search.js +2 -2
- package/dist-cjs/puzzles/skewb/solver/tables.js +18 -1
- package/package.json +14 -5
|
@@ -13,6 +13,14 @@ export interface Cube3Phase2Options {
|
|
|
13
13
|
tables?: Cube3Phase2Tables;
|
|
14
14
|
/** Combined (cp x uep) pair-distance pruning tables (experimental). */
|
|
15
15
|
pairTables?: Cube3PairTables | undefined;
|
|
16
|
+
/**
|
|
17
|
+
* Skip the pair tables entirely (no lazy load). All pair-bound uses are
|
|
18
|
+
* already guarded, so the search runs without pair pruning. Used by the
|
|
19
|
+
* 4x4 finish, where pair pruning was measured to change nothing (identical
|
|
20
|
+
* lengths on a 44-state corpus, zero extra retries) while costing a 54MB
|
|
21
|
+
* table load.
|
|
22
|
+
*/
|
|
23
|
+
skipPairTables?: boolean;
|
|
16
24
|
previousFace?: number;
|
|
17
25
|
previousPreviousFace?: number;
|
|
18
26
|
}
|
|
@@ -23,9 +31,48 @@ export interface Cube3Phase2Result {
|
|
|
23
31
|
coordinates: Cube3Phase2Coordinates;
|
|
24
32
|
visitedNodes: number;
|
|
25
33
|
}
|
|
34
|
+
export type Cube3SolveQuality = "fast" | "short" | "best";
|
|
26
35
|
export interface Cube3SolveOptions {
|
|
27
36
|
phase1?: Cube3Phase1Options;
|
|
28
37
|
phase2?: Cube3Phase2Options;
|
|
38
|
+
targetLength?: number;
|
|
39
|
+
timeoutMs?: number;
|
|
40
|
+
/**
|
|
41
|
+
* Quality tier (defaults to `"short"`):
|
|
42
|
+
* - `"fast"`: legacy min-P1 + min-P2 (~14 ms, ~23.1 moves, browser tables).
|
|
43
|
+
* - `"short"`: best of several G1 landings (~36 ms, ~21.9 moves).
|
|
44
|
+
* - `"best"`: short x 3 orientations (~116 ms, ~20.8 moves, max 22).
|
|
45
|
+
* Explicit `phase1ExtraDepth` / `phase1SolutionLimit` / `orientations`
|
|
46
|
+
* always override the tier preset.
|
|
47
|
+
*/
|
|
48
|
+
quality?: Cube3SolveQuality;
|
|
49
|
+
/**
|
|
50
|
+
* Extra phase-1 depths to explore beyond the minimal one, keeping the
|
|
51
|
+
* best phase-1+phase-2 total. A slightly longer phase 1 often collapses
|
|
52
|
+
* phase 2, shortening the total (and sometimes the search itself).
|
|
53
|
+
* Defaults to 0 (legacy min-P1 + min-P2 behavior). The `targetLength`
|
|
54
|
+
* path always explores up to 4 extra depths.
|
|
55
|
+
*/
|
|
56
|
+
phase1ExtraDepth?: number;
|
|
57
|
+
/**
|
|
58
|
+
* Max phase-1 solutions enumerated per extra depth (most promising
|
|
59
|
+
* first). Defaults to 32.
|
|
60
|
+
*/
|
|
61
|
+
phase1SolutionLimit?: number;
|
|
62
|
+
/**
|
|
63
|
+
* Per-candidate phase-2 budget (ms) inside the improvement loop.
|
|
64
|
+
* Defaults to `phase2.timeoutMs` when set, else 25. The initial solve
|
|
65
|
+
* always uses `phase2.timeoutMs` untouched, so this never starves it.
|
|
66
|
+
*/
|
|
67
|
+
improvePhase2TimeoutMs?: number;
|
|
68
|
+
/**
|
|
69
|
+
* Solve rotated copies of the state and keep the shortest solution
|
|
70
|
+
* (1 = UD only, 3 = UD/x/z triple, up to 24 for the full rotation
|
|
71
|
+
* group). Each extra orientation buys roughly 0.3-0.5 moves for ~1x
|
|
72
|
+
* single-solve time. Composes with the other options (each orientation
|
|
73
|
+
* is solved with the same settings). Defaults to 1.
|
|
74
|
+
*/
|
|
75
|
+
orientations?: number;
|
|
29
76
|
}
|
|
30
77
|
export interface Cube3SolveResult {
|
|
31
78
|
phase1: Cube3Phase1Result;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"phase2.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube3/solver/phase2.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"phase2.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube3/solver/phase2.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAezD,OAAO,KAAK,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACzE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAarD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAQ9D,MAAM,WAAW,sBAAsB;IACrC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B,uEAAuE;IACvE,UAAU,CAAC,EAAE,eAAe,GAAG,SAAS,CAAC;IACzC;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,UAAU,CAAC;IAClB,WAAW,EAAE,sBAAsB,CAAC;IACpC,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;AAE1D,MAAM,WAAW,iBAAiB;IAChC,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAC5B,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,iBAAiB,CAAC;IAC5B;;;;;;OAMG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;OAGG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B;;;;OAIG;IACH,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,iBAAiB,CAAC;IAC1B,MAAM,EAAE,iBAAiB,CAAC;IAC1B,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,UAAU,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,qBAAa,sBAAuB,SAAQ,KAAK;IAC/C,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;gBAElB,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM;CAKlD;AAyCD,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,UAAU,EACjB,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,iBAAiB,CAAC,CAiF5B;AAED,wBAAsB,eAAe,CACnC,KAAK,EAAE,UAAU,EACjB,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,gBAAgB,CAAC,CAmK3B;AA8CD,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,UAAU,GAAG,sBAAsB,CAMtF;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,UAAU,GAAG,sBAAsB,GAAG,OAAO,CAQvF;AAED,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,UAAU,EACjB,KAAK,EAAE,SAAS,SAAS,EAAE,GAC1B,UAAU,CAEZ"}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { applyCube3Algorithm } from "../moves.js";
|
|
2
|
-
import { isSolvedCube3State } from "../state.js";
|
|
1
|
+
import { applyCube3Algorithm, invertCube3Algorithm } from "../moves.js";
|
|
2
|
+
import { createSolvedCube3State, isSolvedCube3State } from "../state.js";
|
|
3
3
|
import { encodePermutation, encodeSlicePermutation, encodeUdEdgePermutation } from "./coordinates.js";
|
|
4
|
-
import { isCube3Phase1Solved, solveCube3Phase1 } from "./phase1.js";
|
|
4
|
+
import { isCube3Phase1Solved, solveCube3Phase1, collectCube3Phase1Solutions, encodeCube3Phase1Coordinates } from "./phase1.js";
|
|
5
5
|
import { loadCube3Phase1Tables, loadCube3Phase2Tables, CUBE3_PHASE1_MOVES, CUBE3_PHASE2_MOVES } from "./tables.js";
|
|
6
6
|
import { getMoveFace, getMoveFaceByIndex, shouldSkipCube3SearchMove } from "./search-utils.js";
|
|
7
7
|
import { loadCube3PairTables, pairBound } from "./pair-tables.js";
|
|
8
|
+
import { CUBE3_ORIENTATIONS_24, invertCube3RotationSequence, rotateCube3AlgorithmBySequence } from "./symmetry.js";
|
|
8
9
|
export class Cube3Phase2SearchError extends Error {
|
|
9
10
|
visitedNodes;
|
|
10
11
|
constructor(message, visitedNodes) {
|
|
@@ -41,17 +42,11 @@ export async function solveCube3Phase2(state, options = {}) {
|
|
|
41
42
|
throw new Cube3Phase2SearchError("3x3 Phase 2 requires a state already in G1.", 0);
|
|
42
43
|
}
|
|
43
44
|
const tables = options.tables ?? await loadCube3Phase2Tables();
|
|
44
|
-
const pairTables = options.pairTables ?? await loadCube3PairTables();
|
|
45
|
+
const pairTables = options.pairTables ?? (options.skipPairTables ? undefined : await loadCube3PairTables());
|
|
45
46
|
const maxDepth = options.maxDepth ?? 18;
|
|
46
47
|
const start = encodeCube3Phase2Coordinates(state);
|
|
47
48
|
const deadline = options.timeoutMs === undefined ? undefined : performance.now() + options.timeoutMs;
|
|
48
|
-
const context =
|
|
49
|
-
tables,
|
|
50
|
-
pairTables,
|
|
51
|
-
deadline,
|
|
52
|
-
visitedNodes: 0,
|
|
53
|
-
timedOut: false
|
|
54
|
-
};
|
|
49
|
+
const context = createPhase2SearchContext(tables, pairTables, maxDepth, deadline);
|
|
55
50
|
const spRange = tables.moveTables.slicePermutation.range;
|
|
56
51
|
const cpspTable = tables.pruningTables.cornerPermutationSlicePermutation.values;
|
|
57
52
|
const epspTable = tables.pruningTables.udEdgePermutationSlicePermutation.values;
|
|
@@ -89,6 +84,11 @@ export async function solveCube3Phase2(state, options = {}) {
|
|
|
89
84
|
throw new Cube3Phase2SearchError(`3x3 Phase 2 solution not found within depth ${maxDepth}.`, context.visitedNodes);
|
|
90
85
|
}
|
|
91
86
|
export async function solveCube3State(state, options = {}) {
|
|
87
|
+
const quality = options.quality ?? "short";
|
|
88
|
+
const orientations = options.orientations ?? (quality === "best" ? 3 : 1);
|
|
89
|
+
if (orientations >= 3) {
|
|
90
|
+
return solveCube3TripleOrientation(state, { ...options, orientations });
|
|
91
|
+
}
|
|
92
92
|
const phase1 = await solveCube3Phase1(state, options.phase1);
|
|
93
93
|
let previousFace = -1;
|
|
94
94
|
let previousPreviousFace = -1;
|
|
@@ -107,6 +107,127 @@ export async function solveCube3State(state, options = {}) {
|
|
|
107
107
|
};
|
|
108
108
|
const phase2 = await solveCube3Phase2(phase1.state, phase2Options);
|
|
109
109
|
const moves = [...phase1.moves, ...phase2.moves];
|
|
110
|
+
const targetLength = options.targetLength;
|
|
111
|
+
const extraDepth = options.phase1ExtraDepth ?? (quality === "fast" ? 0 : 1);
|
|
112
|
+
const solutionLimit = options.phase1SolutionLimit ?? (targetLength !== undefined ? 32 : 8);
|
|
113
|
+
const improveDepths = targetLength !== undefined ? Math.max(4, extraDepth) : extraDepth;
|
|
114
|
+
const needsTarget = targetLength !== undefined && moves.length > targetLength;
|
|
115
|
+
if (needsTarget || (targetLength === undefined && extraDepth > 0)) {
|
|
116
|
+
const p1Tables = options.phase1?.tables ?? await loadCube3Phase1Tables();
|
|
117
|
+
const p2Tables = options.phase2?.tables ?? await loadCube3Phase2Tables();
|
|
118
|
+
const scoredPairTables = options.phase2?.pairTables ??
|
|
119
|
+
(options.phase2?.skipPairTables ? undefined : await loadCube3PairTables());
|
|
120
|
+
const deadline = performance.now() + (options.timeoutMs ?? 800);
|
|
121
|
+
const p2SpRange = p2Tables.moveTables.slicePermutation.range;
|
|
122
|
+
const p2cpsp = p2Tables.pruningTables.cornerPermutationSlicePermutation.values;
|
|
123
|
+
const p2uepsp = p2Tables.pruningTables.udEdgePermutationSlicePermutation.values;
|
|
124
|
+
const seenG1 = new Set();
|
|
125
|
+
let bestP1 = phase1;
|
|
126
|
+
let bestP2 = phase2;
|
|
127
|
+
let bestMoves = moves;
|
|
128
|
+
let improvementNodes = 0;
|
|
129
|
+
const minDepth = phase1.depth;
|
|
130
|
+
const scored = [];
|
|
131
|
+
for (let d = minDepth; d <= minDepth + improveDepths && d < bestMoves.length; d++) {
|
|
132
|
+
if (performance.now() > deadline)
|
|
133
|
+
break;
|
|
134
|
+
const { solutions: p1Solutions, visitedNodes } = await collectCube3Phase1Solutions(state, d, {
|
|
135
|
+
tables: p1Tables,
|
|
136
|
+
limit: solutionLimit,
|
|
137
|
+
timeoutMs: Math.max(1, deadline - performance.now())
|
|
138
|
+
});
|
|
139
|
+
improvementNodes += visitedNodes;
|
|
140
|
+
if (performance.now() > deadline)
|
|
141
|
+
break;
|
|
142
|
+
for (const p1Moves of p1Solutions) {
|
|
143
|
+
if (performance.now() > deadline)
|
|
144
|
+
break;
|
|
145
|
+
if (p1Moves.length >= bestMoves.length)
|
|
146
|
+
continue;
|
|
147
|
+
// Skip the phase-1 solution the initial search already completed.
|
|
148
|
+
if (p1Moves.length === phase1.moves.length &&
|
|
149
|
+
p1Moves.every((move, index) => move === phase1.moves[index])) {
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
const s1 = applyCube3Algorithm(state, p1Moves);
|
|
153
|
+
const coords = encodeCube3Phase2Coordinates(s1);
|
|
154
|
+
const estKey = `${coords.cornerPermutation},${coords.udEdgePermutation},${coords.slicePermutation}`;
|
|
155
|
+
if (seenG1.has(estKey))
|
|
156
|
+
continue;
|
|
157
|
+
seenG1.add(estKey);
|
|
158
|
+
const cpIndex = coords.cornerPermutation * p2SpRange + coords.slicePermutation;
|
|
159
|
+
const epIndex = coords.udEdgePermutation * p2SpRange + coords.slicePermutation;
|
|
160
|
+
const cpDist = (p2cpsp[cpIndex >> 1] >> ((cpIndex & 1) << 2)) & 0x0f;
|
|
161
|
+
const epDist = (p2uepsp[epIndex >> 1] >> ((epIndex & 1) << 2)) & 0x0f;
|
|
162
|
+
let est = p1Moves.length + (cpDist > epDist ? cpDist : epDist);
|
|
163
|
+
if (scoredPairTables !== undefined) {
|
|
164
|
+
const pair = pairBound(scoredPairTables, coords.cornerPermutation, coords.udEdgePermutation);
|
|
165
|
+
const pairEst = p1Moves.length + pair;
|
|
166
|
+
if (pairEst > est)
|
|
167
|
+
est = pairEst;
|
|
168
|
+
}
|
|
169
|
+
// A lower bound at or above the best total can never strictly improve it.
|
|
170
|
+
if (est >= bestMoves.length)
|
|
171
|
+
continue;
|
|
172
|
+
const pFace = p1Moves.length > 0 ? getMoveFace(p1Moves[p1Moves.length - 1]) : -1;
|
|
173
|
+
const ppFace = p1Moves.length > 1 ? getMoveFace(p1Moves[p1Moves.length - 2]) : -1;
|
|
174
|
+
scored.push({ moves: p1Moves, state: s1, est, pFace, ppFace });
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
// Sort ALL candidates globally across depths by admissible total lower bound
|
|
178
|
+
scored.sort((left, right) => left.est - right.est);
|
|
179
|
+
for (const cand of scored) {
|
|
180
|
+
if (performance.now() > deadline)
|
|
181
|
+
break;
|
|
182
|
+
// Prune: since candidates are sorted by est, no subsequent candidate can beat bestMoves.length
|
|
183
|
+
if (cand.est >= bestMoves.length)
|
|
184
|
+
break;
|
|
185
|
+
const maxP2 = Math.min(bestMoves.length - cand.moves.length - 1, 15);
|
|
186
|
+
if (maxP2 <= 0)
|
|
187
|
+
continue;
|
|
188
|
+
try {
|
|
189
|
+
const remainingBudget = Math.max(1, deadline - performance.now());
|
|
190
|
+
const attemptTimeout = options.improvePhase2TimeoutMs ??
|
|
191
|
+
options.phase2?.timeoutMs ?? 25;
|
|
192
|
+
const candP2 = await solveCube3Phase2(cand.state, {
|
|
193
|
+
...options.phase2,
|
|
194
|
+
pairTables: scoredPairTables,
|
|
195
|
+
tables: p2Tables,
|
|
196
|
+
maxDepth: maxP2,
|
|
197
|
+
previousFace: cand.pFace,
|
|
198
|
+
previousPreviousFace: cand.ppFace,
|
|
199
|
+
timeoutMs: Math.min(attemptTimeout, remainingBudget)
|
|
200
|
+
});
|
|
201
|
+
improvementNodes += candP2.visitedNodes;
|
|
202
|
+
const candTotal = cand.moves.length + candP2.moves.length;
|
|
203
|
+
if (candTotal < bestMoves.length) {
|
|
204
|
+
bestMoves = [...cand.moves, ...candP2.moves];
|
|
205
|
+
bestP1 = {
|
|
206
|
+
moves: cand.moves,
|
|
207
|
+
depth: cand.moves.length,
|
|
208
|
+
state: cand.state,
|
|
209
|
+
coordinates: encodeCube3Phase1Coordinates(cand.state),
|
|
210
|
+
visitedNodes: 0
|
|
211
|
+
};
|
|
212
|
+
bestP2 = candP2;
|
|
213
|
+
if (targetLength !== undefined && bestMoves.length <= targetLength) {
|
|
214
|
+
break;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
// continue
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return {
|
|
223
|
+
phase1: bestP1,
|
|
224
|
+
phase2: bestP2,
|
|
225
|
+
moves: bestMoves,
|
|
226
|
+
depth: bestMoves.length,
|
|
227
|
+
state: bestP2.state,
|
|
228
|
+
visitedNodes: phase1.visitedNodes + phase2.visitedNodes + improvementNodes
|
|
229
|
+
};
|
|
230
|
+
}
|
|
110
231
|
return {
|
|
111
232
|
phase1,
|
|
112
233
|
phase2,
|
|
@@ -116,6 +237,39 @@ export async function solveCube3State(state, options = {}) {
|
|
|
116
237
|
visitedNodes: phase1.visitedNodes + phase2.visitedNodes
|
|
117
238
|
};
|
|
118
239
|
}
|
|
240
|
+
/**
|
|
241
|
+
* Best-of-N-orientations solve: UD plus rotated copies, mapped back with
|
|
242
|
+
* the inverse rotation. Rotating a solution conjugates the whole picture,
|
|
243
|
+
* so the mapped-back moves solve the original state with their length
|
|
244
|
+
* preserved. Deterministic: ties keep the earliest orientation (UD first).
|
|
245
|
+
*/
|
|
246
|
+
async function solveCube3TripleOrientation(state, options) {
|
|
247
|
+
const count = Math.min(Math.max(options.orientations ?? 1, 1), CUBE3_ORIENTATIONS_24.length);
|
|
248
|
+
const singleOptions = { ...options, orientations: 1 };
|
|
249
|
+
let best = await solveCube3State(state, singleOptions);
|
|
250
|
+
let visitedNodes = best.visitedNodes;
|
|
251
|
+
const scramble = invertCube3Algorithm(best.moves);
|
|
252
|
+
const sequences = CUBE3_ORIENTATIONS_24.slice(1, count);
|
|
253
|
+
for (const sequence of sequences) {
|
|
254
|
+
try {
|
|
255
|
+
const rotatedState = applyCube3Algorithm(createSolvedCube3State(), rotateCube3AlgorithmBySequence(scramble, sequence));
|
|
256
|
+
const candidate = await solveCube3State(rotatedState, singleOptions);
|
|
257
|
+
visitedNodes += candidate.visitedNodes;
|
|
258
|
+
if (candidate.moves.length < best.moves.length) {
|
|
259
|
+
best = {
|
|
260
|
+
...candidate,
|
|
261
|
+
moves: rotateCube3AlgorithmBySequence(candidate.moves, invertCube3RotationSequence(sequence)),
|
|
262
|
+
depth: candidate.moves.length,
|
|
263
|
+
visitedNodes
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
catch {
|
|
268
|
+
// A flaky orientation must never fail the whole solve.
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return { ...best, visitedNodes };
|
|
272
|
+
}
|
|
119
273
|
export function encodeCube3Phase2Coordinates(state) {
|
|
120
274
|
return {
|
|
121
275
|
cornerPermutation: encodeCornerPermutationForPhase2(state),
|
|
@@ -138,6 +292,23 @@ const PHASE2_MOVE_FACES = CUBE3_PHASE2_MOVES.map((move) => {
|
|
|
138
292
|
const char = move[0];
|
|
139
293
|
return char === "U" ? 0 : char === "D" ? 1 : char === "R" ? 2 : char === "L" ? 3 : char === "F" ? 4 : 5;
|
|
140
294
|
});
|
|
295
|
+
function createPhase2SearchContext(tables, pairTables, maxDepth, deadline) {
|
|
296
|
+
const scratchLevels = maxDepth + 1;
|
|
297
|
+
const scratchSize = scratchLevels * CUBE3_PHASE2_MOVES.length;
|
|
298
|
+
return {
|
|
299
|
+
tables,
|
|
300
|
+
pairTables,
|
|
301
|
+
maxDepth,
|
|
302
|
+
deadline,
|
|
303
|
+
visitedNodes: 0,
|
|
304
|
+
timedOut: false,
|
|
305
|
+
scratchMove: new Int32Array(scratchSize),
|
|
306
|
+
scratchBound: new Int32Array(scratchSize),
|
|
307
|
+
scratchCp: new Int32Array(scratchSize),
|
|
308
|
+
scratchUep: new Int32Array(scratchSize),
|
|
309
|
+
scratchSp: new Int32Array(scratchSize)
|
|
310
|
+
};
|
|
311
|
+
}
|
|
141
312
|
function searchPhase2(cornerPermutation, udEdgePermutation, slicePermutation, previousFace, previousPreviousFace, remainingDepth, path, context) {
|
|
142
313
|
context.visitedNodes += 1;
|
|
143
314
|
if (context.deadline !== undefined && (context.visitedNodes & 1023) === 0 && performance.now() > context.deadline) {
|
|
@@ -157,41 +328,23 @@ function searchPhase2(cornerPermutation, udEdgePermutation, slicePermutation, pr
|
|
|
157
328
|
const spRange = context.tables.moveTables.slicePermutation.range;
|
|
158
329
|
const cpspTable = context.tables.pruningTables.cornerPermutationSlicePermutation.values;
|
|
159
330
|
const epspTable = context.tables.pruningTables.udEdgePermutationSlicePermutation.values;
|
|
331
|
+
const pairTables = context.pairTables;
|
|
160
332
|
const cpOffset = cornerPermutation * moveCount;
|
|
161
333
|
const epOffset = udEdgePermutation * moveCount;
|
|
162
334
|
const spOffset = slicePermutation * moveCount;
|
|
163
|
-
//
|
|
164
|
-
//
|
|
165
|
-
//
|
|
166
|
-
//
|
|
167
|
-
//
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
const cpDist = (cpspTable[combinedCPIndex >> 1] >> ((combinedCPIndex & 1) << 2)) & 0x0f;
|
|
177
|
-
const epDist = (epspTable[combinedEPIndex >> 1] >> ((combinedEPIndex & 1) << 2)) & 0x0f;
|
|
178
|
-
const nextLowerBound = cpDist > epDist ? cpDist : epDist;
|
|
179
|
-
if (nextLowerBound > remainingDepth - 1) {
|
|
180
|
-
continue;
|
|
181
|
-
}
|
|
182
|
-
path.push(context.tables.phase2Moves[moveIndex]);
|
|
183
|
-
if (searchPhase2(nextCornerPermutation, nextUdEdgePermutation, nextSlicePermutation, PHASE2_MOVE_FACES[moveIndex], previousFace, remainingDepth - 1, path, context)) {
|
|
184
|
-
return true;
|
|
185
|
-
}
|
|
186
|
-
path.pop();
|
|
187
|
-
if (context.timedOut) {
|
|
188
|
-
return false;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
return false;
|
|
192
|
-
}
|
|
193
|
-
const pairTables = context.pairTables;
|
|
194
|
-
const children = [];
|
|
335
|
+
// Collect the surviving children with the pair-tightened lower bound and
|
|
336
|
+
// explore them in ascending bound order (stable: ties keep the reference
|
|
337
|
+
// move order). Without the pair tables this reduces to ordering by
|
|
338
|
+
// max(cpsp, uepsp) — same depth-minimal solutions, far fewer nodes than
|
|
339
|
+
// the reference traversal. With them, same set and order as before but
|
|
340
|
+
// without per-node allocation.
|
|
341
|
+
const slot = remainingDepth * moveCount;
|
|
342
|
+
const scratchMove = context.scratchMove;
|
|
343
|
+
const scratchBound = context.scratchBound;
|
|
344
|
+
const scratchCp = context.scratchCp;
|
|
345
|
+
const scratchUep = context.scratchUep;
|
|
346
|
+
const scratchSp = context.scratchSp;
|
|
347
|
+
let childCount = 0;
|
|
195
348
|
for (let i = 0; i < validMoves.length; i += 1) {
|
|
196
349
|
const moveIndex = validMoves[i];
|
|
197
350
|
const nextCornerPermutation = cpTable[cpOffset + moveIndex];
|
|
@@ -205,25 +358,48 @@ function searchPhase2(cornerPermutation, udEdgePermutation, slicePermutation, pr
|
|
|
205
358
|
if (nextLowerBound > remainingDepth - 1) {
|
|
206
359
|
continue;
|
|
207
360
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
nextLowerBound
|
|
361
|
+
if (pairTables !== undefined) {
|
|
362
|
+
const pair = pairBound(pairTables, nextCornerPermutation, nextUdEdgePermutation);
|
|
363
|
+
if (pair > nextLowerBound) {
|
|
364
|
+
nextLowerBound = pair;
|
|
365
|
+
}
|
|
366
|
+
if (nextLowerBound > remainingDepth - 1) {
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
211
369
|
}
|
|
212
|
-
|
|
213
|
-
|
|
370
|
+
scratchMove[slot + childCount] = moveIndex;
|
|
371
|
+
scratchBound[slot + childCount] = nextLowerBound;
|
|
372
|
+
scratchCp[slot + childCount] = nextCornerPermutation;
|
|
373
|
+
scratchUep[slot + childCount] = nextUdEdgePermutation;
|
|
374
|
+
scratchSp[slot + childCount] = nextSlicePermutation;
|
|
375
|
+
childCount += 1;
|
|
376
|
+
}
|
|
377
|
+
// Stable insertion sort by bound (n <= 10; ties keep reference order).
|
|
378
|
+
for (let i = 1; i < childCount; i += 1) {
|
|
379
|
+
const move = scratchMove[slot + i];
|
|
380
|
+
const bound = scratchBound[slot + i];
|
|
381
|
+
const cp = scratchCp[slot + i];
|
|
382
|
+
const uep = scratchUep[slot + i];
|
|
383
|
+
const sp = scratchSp[slot + i];
|
|
384
|
+
let j = i - 1;
|
|
385
|
+
while (j >= 0 && scratchBound[slot + j] > bound) {
|
|
386
|
+
scratchMove[slot + j + 1] = scratchMove[slot + j];
|
|
387
|
+
scratchBound[slot + j + 1] = scratchBound[slot + j];
|
|
388
|
+
scratchCp[slot + j + 1] = scratchCp[slot + j];
|
|
389
|
+
scratchUep[slot + j + 1] = scratchUep[slot + j];
|
|
390
|
+
scratchSp[slot + j + 1] = scratchSp[slot + j];
|
|
391
|
+
j -= 1;
|
|
214
392
|
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
slicePermutation: nextSlicePermutation
|
|
221
|
-
});
|
|
393
|
+
scratchMove[slot + j + 1] = move;
|
|
394
|
+
scratchBound[slot + j + 1] = bound;
|
|
395
|
+
scratchCp[slot + j + 1] = cp;
|
|
396
|
+
scratchUep[slot + j + 1] = uep;
|
|
397
|
+
scratchSp[slot + j + 1] = sp;
|
|
222
398
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
path.push(context.tables.phase2Moves[
|
|
226
|
-
if (searchPhase2(
|
|
399
|
+
for (let i = 0; i < childCount; i += 1) {
|
|
400
|
+
const moveIndex = scratchMove[slot + i];
|
|
401
|
+
path.push(context.tables.phase2Moves[moveIndex]);
|
|
402
|
+
if (searchPhase2(scratchCp[slot + i], scratchUep[slot + i], scratchSp[slot + i], PHASE2_MOVE_FACES[moveIndex], previousFace, remainingDepth - 1, path, context)) {
|
|
227
403
|
return true;
|
|
228
404
|
}
|
|
229
405
|
path.pop();
|
|
@@ -272,13 +448,7 @@ function searchPhase1Interleaved(cornerOrientation, edgeOrientation, udSlice, pr
|
|
|
272
448
|
const g1State = applyCube3Phase2Moves(context.originalState, path);
|
|
273
449
|
const start2 = encodeCube3Phase2Coordinates(g1State);
|
|
274
450
|
const maxPhase2Depth = 6;
|
|
275
|
-
const phase2Context =
|
|
276
|
-
tables: context.phase2Tables,
|
|
277
|
-
pairTables: undefined,
|
|
278
|
-
deadline: context.deadline,
|
|
279
|
-
visitedNodes: 0,
|
|
280
|
-
timedOut: false
|
|
281
|
-
};
|
|
451
|
+
const phase2Context = createPhase2SearchContext(context.phase2Tables, undefined, maxPhase2Depth, context.deadline);
|
|
282
452
|
for (let d2 = 0; d2 <= maxPhase2Depth; d2 += 1) {
|
|
283
453
|
const path2 = [];
|
|
284
454
|
const solved2 = searchPhase2(start2.cornerPermutation, start2.udEdgePermutation, start2.slicePermutation, -1, -1, d2, path2, phase2Context);
|
|
@@ -5,5 +5,5 @@ export declare function getMoveFaceByIndex(moveIndex: number, moves: readonly Cu
|
|
|
5
5
|
export declare const ARE_OPPOSITE_FACES: readonly boolean[];
|
|
6
6
|
export declare function areOppositeFaces(left: Cube3SearchFace, right: Cube3SearchFace): boolean;
|
|
7
7
|
export declare function shouldSkipCube3SearchMove(face: Cube3SearchFace, previousFace: Cube3SearchFace | undefined, previousPreviousFace: Cube3SearchFace | undefined): boolean;
|
|
8
|
-
export declare function lookupMoveTableByIndex(values: Int32Array, coordinate: number, moveIndex: number, moveCount: number): number;
|
|
8
|
+
export declare function lookupMoveTableByIndex(values: Int32Array | Uint16Array, coordinate: number, moveIndex: number, moveCount: number): number;
|
|
9
9
|
//# sourceMappingURL=search-utils.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search-utils.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube3/solver/search-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC;AAYrC,wBAAgB,WAAW,CAAC,IAAI,EAAE,SAAS,GAAG,eAAe,CAE5D;AAED,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,SAAS,EAAE,GAAG,eAAe,CAElG;AAED,eAAO,MAAM,kBAAkB,EAAE,SAAS,OAAO,EAOhD,CAAC;AAEF,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,eAAe,GAAG,OAAO,CAEvF;AAED,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,eAAe,EACrB,YAAY,EAAE,eAAe,GAAG,SAAS,EACzC,oBAAoB,EAAE,eAAe,GAAG,SAAS,GAChD,OAAO,CAcT;AAED,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,UAAU,
|
|
1
|
+
{"version":3,"file":"search-utils.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube3/solver/search-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC;AAYrC,wBAAgB,WAAW,CAAC,IAAI,EAAE,SAAS,GAAG,eAAe,CAE5D;AAED,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,SAAS,EAAE,GAAG,eAAe,CAElG;AAED,eAAO,MAAM,kBAAkB,EAAE,SAAS,OAAO,EAOhD,CAAC;AAEF,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,eAAe,GAAG,OAAO,CAEvF;AAED,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,eAAe,EACrB,YAAY,EAAE,eAAe,GAAG,SAAS,EACzC,oBAAoB,EAAE,eAAe,GAAG,SAAS,GAChD,OAAO,CAcT;AAED,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,UAAU,GAAG,WAAW,EAChC,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB,MAAM,CAER"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Cube3Move } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Whole-cube rotations for 3x3 move sequences.
|
|
4
|
+
*
|
|
5
|
+
* Face maps ported from the 4x4 solver's `ROT_MAP`
|
|
6
|
+
* (`puzzles/cube4/reduction/solve.ts`), restricted to the six basic faces —
|
|
7
|
+
* kept cube3-local so `solver/` never imports from `cube4` (which itself
|
|
8
|
+
* depends on this solver).
|
|
9
|
+
*/
|
|
10
|
+
export type Cube3Rotation = "x" | "x'" | "z" | "z'";
|
|
11
|
+
export declare const CUBE3_ROTATION_INVERSE: Record<Cube3Rotation, Cube3Rotation>;
|
|
12
|
+
export type Cube3RotationSequence = readonly string[];
|
|
13
|
+
/**
|
|
14
|
+
* 24 whole-cube orientations as rotation sequences, discovered by BFS over
|
|
15
|
+
* the group generated by x/y/z (canonicalized by face map, so all 24 are
|
|
16
|
+
* geometrically distinct by construction).
|
|
17
|
+
* The first three are identity, x, z — so `orientations: 3` keeps the
|
|
18
|
+
* legacy UD/x/z triple exactly.
|
|
19
|
+
*/
|
|
20
|
+
export declare const CUBE3_ORIENTATIONS_24: readonly Cube3RotationSequence[];
|
|
21
|
+
export declare function invertCube3RotationSequence(sequence: Cube3RotationSequence): string[];
|
|
22
|
+
export declare function rotateCube3AlgorithmBySequence(moves: readonly Cube3Move[], sequence: Cube3RotationSequence): Cube3Move[];
|
|
23
|
+
export declare function rotateCube3Algorithm(moves: readonly Cube3Move[], rotation: Cube3Rotation): Cube3Move[];
|
|
24
|
+
//# sourceMappingURL=symmetry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"symmetry.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube3/solver/symmetry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C;;;;;;;GAOG;AACH,MAAM,MAAM,aAAa,GAAG,GAAG,GAAG,IAAI,GAAG,GAAG,GAAG,IAAI,CAAC;AAqBpD,eAAO,MAAM,sBAAsB,EAAE,MAAM,CAAC,aAAa,EAAE,aAAa,CAKvE,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG,SAAS,MAAM,EAAE,CAAC;AAEtD;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,EAAE,SAAS,qBAAqB,EA4B9D,CAAC;AAEL,wBAAgB,2BAA2B,CAAC,QAAQ,EAAE,qBAAqB,GAAG,MAAM,EAAE,CAErF;AAED,wBAAgB,8BAA8B,CAC5C,KAAK,EAAE,SAAS,SAAS,EAAE,EAC3B,QAAQ,EAAE,qBAAqB,GAC9B,SAAS,EAAE,CAOb;AAED,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,SAAS,SAAS,EAAE,EAC3B,QAAQ,EAAE,aAAa,GACtB,SAAS,EAAE,CAGb"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
const ROT_FACE = {
|
|
2
|
+
"x": { "U": "B", "R": "R", "F": "U", "D": "F", "L": "L", "B": "D" },
|
|
3
|
+
"x'": { "U": "F", "R": "R", "F": "D", "D": "B", "L": "L", "B": "U" },
|
|
4
|
+
"y": { "U": "U", "R": "F", "F": "L", "D": "D", "L": "B", "B": "R" },
|
|
5
|
+
"y'": { "U": "U", "R": "B", "F": "R", "D": "D", "L": "F", "B": "L" },
|
|
6
|
+
"z": { "U": "R", "R": "D", "F": "F", "D": "L", "L": "U", "B": "B" },
|
|
7
|
+
"z'": { "U": "L", "R": "U", "F": "F", "D": "R", "L": "D", "B": "B" },
|
|
8
|
+
"x2": { "U": "D", "R": "R", "F": "B", "D": "U", "L": "L", "B": "F" },
|
|
9
|
+
"y2": { "U": "U", "R": "L", "F": "B", "D": "D", "L": "R", "B": "F" },
|
|
10
|
+
"z2": { "U": "D", "R": "L", "F": "F", "D": "U", "L": "R", "B": "B" }
|
|
11
|
+
};
|
|
12
|
+
const INVERT_ROTATION = {
|
|
13
|
+
"x": "x'", "x'": "x",
|
|
14
|
+
"y": "y'", "y'": "y",
|
|
15
|
+
"z": "z'", "z'": "z",
|
|
16
|
+
"x2": "x2", "y2": "y2", "z2": "z2"
|
|
17
|
+
};
|
|
18
|
+
export const CUBE3_ROTATION_INVERSE = {
|
|
19
|
+
"x": "x'",
|
|
20
|
+
"x'": "x",
|
|
21
|
+
"z": "z'",
|
|
22
|
+
"z'": "z"
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* 24 whole-cube orientations as rotation sequences, discovered by BFS over
|
|
26
|
+
* the group generated by x/y/z (canonicalized by face map, so all 24 are
|
|
27
|
+
* geometrically distinct by construction).
|
|
28
|
+
* The first three are identity, x, z — so `orientations: 3` keeps the
|
|
29
|
+
* legacy UD/x/z triple exactly.
|
|
30
|
+
*/
|
|
31
|
+
export const CUBE3_ORIENTATIONS_24 = (() => {
|
|
32
|
+
const faces = ["U", "R", "F", "D", "L", "B"];
|
|
33
|
+
const identity = "URFDLB";
|
|
34
|
+
const composeKey = (key, gen) => {
|
|
35
|
+
const map = ROT_FACE[gen];
|
|
36
|
+
return faces.map((face, index) => map[key[index]]).join("");
|
|
37
|
+
};
|
|
38
|
+
const seen = new Map([[identity, []]]);
|
|
39
|
+
const queue = [identity];
|
|
40
|
+
while (queue.length > 0) {
|
|
41
|
+
const key = queue.shift();
|
|
42
|
+
const sequence = seen.get(key);
|
|
43
|
+
for (const gen of ["x", "y", "z"]) {
|
|
44
|
+
const next = composeKey(key, gen);
|
|
45
|
+
if (!seen.has(next)) {
|
|
46
|
+
seen.set(next, [...sequence, gen]);
|
|
47
|
+
queue.push(next);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const isLegacyTriple = (sequence) => sequence.length === 0 ||
|
|
52
|
+
(sequence.length === 1 && (sequence[0] === "x" || sequence[0] === "z"));
|
|
53
|
+
const rest = [];
|
|
54
|
+
for (const sequence of seen.values()) {
|
|
55
|
+
if (!isLegacyTriple(sequence))
|
|
56
|
+
rest.push(sequence);
|
|
57
|
+
}
|
|
58
|
+
return [[], ["x"], ["z"], ...rest];
|
|
59
|
+
})();
|
|
60
|
+
export function invertCube3RotationSequence(sequence) {
|
|
61
|
+
return [...sequence].reverse().map((rotation) => INVERT_ROTATION[rotation]);
|
|
62
|
+
}
|
|
63
|
+
export function rotateCube3AlgorithmBySequence(moves, sequence) {
|
|
64
|
+
let result = [...moves];
|
|
65
|
+
for (const rotation of sequence) {
|
|
66
|
+
const map = ROT_FACE[rotation];
|
|
67
|
+
result = result.map((move) => `${map[move[0]]}${move.slice(1)}`);
|
|
68
|
+
}
|
|
69
|
+
return result;
|
|
70
|
+
}
|
|
71
|
+
export function rotateCube3Algorithm(moves, rotation) {
|
|
72
|
+
const map = ROT_FACE[rotation];
|
|
73
|
+
return moves.map((move) => `${map[move[0]]}${move.slice(1)}`);
|
|
74
|
+
}
|
|
@@ -7,7 +7,12 @@ export interface Cube3MoveTable {
|
|
|
7
7
|
moves: readonly Cube3Move[];
|
|
8
8
|
range: number;
|
|
9
9
|
solvedCoordinate: number;
|
|
10
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Move-table values. Every coordinate fits in 16 bits (max range 40320),
|
|
12
|
+
* so loaders store `Uint16Array` (half the RAM/cache of the int32 wire
|
|
13
|
+
* format); table generation still builds `Int32Array`.
|
|
14
|
+
*/
|
|
15
|
+
values: Int32Array | Uint16Array;
|
|
11
16
|
checksum: number;
|
|
12
17
|
}
|
|
13
18
|
export interface Cube3PruningTable {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tables.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube3/solver/tables.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAM7C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;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;
|
|
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;;;;OAIG;IACH,MAAM,EAAE,UAAU,GAAG,WAAW,CAAC;IACjC,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;IACF,WAAW,EAAE,gBAAgB,GAAG,SAAS,CAAC;CAC3C;AAED;;;;;;GAMG;AACH,MAAM,WAAW,gBAAgB;IAC/B,uEAAuE;IACvE,SAAS,EAAE,UAAU,CAAC;IACtB,qEAAqE;IACrE,SAAS,EAAE,UAAU,CAAC;CACvB;AAED,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,SAAS,SAAS,EAAE,CAAC;IAClC,UAAU,EAAE,IAAI,CACd,iBAAiB,CAAC,YAAY,CAAC,EAC/B,mBAAmB,GAAG,iBAAiB,GAAG,SAAS,CACpD,CAAC;IACF,aAAa,EAAE,IAAI,CACjB,iBAAiB,CAAC,eAAe,CAAC,EAClC,mBAAmB,GAAG,iBAAiB,GAAG,SAAS,CACpD,GAAG;QACF,gCAAgC,EAAE,iBAAiB,CAAC;KACrD,CAAC;IACF,kEAAkE;IAClE,WAAW,EAAE,gBAAgB,GAAG,SAAS,CAAC;CAC3C;AAED,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,SAAS,SAAS,EAAE,CAAC;IAClC,UAAU,EAAE,IAAI,CACd,iBAAiB,CAAC,YAAY,CAAC,EAC/B,mBAAmB,GAAG,mBAAmB,GAAG,kBAAkB,CAC/D,CAAC;IACF,aAAa,EAAE,IAAI,CACjB,iBAAiB,CAAC,eAAe,CAAC,EAClC,mBAAmB,GAAG,mBAAmB,GAAG,kBAAkB,CAC/D,GAAG;QACF,iCAAiC,EAAE,iBAAiB,CAAC;QACrD,iCAAiC,EAAE,iBAAiB,CAAC;KACtD,CAAC;CACH;AAED,eAAO,MAAM,kBAAkB,oIAAc,CAAC;AAC9C,eAAO,MAAM,kBAAkB,EAAE,SAAS,SAAS,EAWlD,CAAC;AAMF,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,iBAAiB,CAAC,CAGxE;AAED,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,iBAAiB,CAAC,CAGxE;AAED,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,iBAAiB,CAAC,CAGxE;AAED,wBAAgB,yBAAyB,IAAI,iBAAiB,CAmC7D;AAED,wBAAgB,yBAAyB,IAAI,iBAAiB,CA4B7D;AAED,wBAAgB,yBAAyB,IAAI,iBAAiB,CAsC7D;AAED,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,mBAAmB,EAC/B,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,SAAS,SAAS,EAAE,GAC1B,cAAc,CAqBhB;AAED,wBAAgB,yBAAyB,CAAC,SAAS,EAAE,cAAc,GAAG,iBAAiB,CAsCtF;AAED,wBAAgB,iCAAiC,CAC/C,MAAM,EAAE,cAAc,EACtB,MAAM,EAAE,cAAc,GACrB,iBAAiB,CAgDnB;AAED,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,cAAc,EACrB,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,SAAS,GACd,MAAM,CAQR;AAED,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC,MAAM,CAAC,GAAG,MAAM,CAS1D"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../../src/puzzles/cube3/validation.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAIpE,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,UAAU,GAAG,qBAAqB,CA+B3E;AASD,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAExE;AAED,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAIpE;AAED,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC,
|
|
1
|
+
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../../src/puzzles/cube3/validation.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAIpE,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,UAAU,GAAG,qBAAqB,CA+B3E;AASD,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAExE;AAED,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAIpE;AAED,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC,CAmBvE"}
|
|
@@ -41,15 +41,23 @@ export function isAllowedRandomCube3State(state) {
|
|
|
41
41
|
!isOneMoveFromSolvedCube3State(state);
|
|
42
42
|
}
|
|
43
43
|
export function permutationParity(permutation) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
44
|
+
// Cycle-decomposition parity in O(n) instead of O(n²) inversion counting
|
|
45
|
+
// (identical result: parity = (n − cycles) mod 2).
|
|
46
|
+
const visited = new Array(permutation.length).fill(false);
|
|
47
|
+
let parity = 0;
|
|
48
|
+
for (let start = 0; start < permutation.length; start += 1) {
|
|
49
|
+
if (visited[start] || permutation[start] === start)
|
|
50
|
+
continue;
|
|
51
|
+
let length = 0;
|
|
52
|
+
let current = start;
|
|
53
|
+
while (!visited[current]) {
|
|
54
|
+
visited[current] = true;
|
|
55
|
+
current = permutation[current];
|
|
56
|
+
length += 1;
|
|
50
57
|
}
|
|
58
|
+
parity ^= (length - 1) & 1;
|
|
51
59
|
}
|
|
52
|
-
return
|
|
60
|
+
return parity;
|
|
53
61
|
}
|
|
54
62
|
function validatePermutation(label, permutation, length) {
|
|
55
63
|
const errors = [];
|