@cubingcenter/scrambler 0.5.0 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/puzzles/cube2/facelets.d.ts.map +1 -1
- package/dist/puzzles/cube2/facelets.js +43 -22
- package/dist/puzzles/cube2/random-state.d.ts.map +1 -1
- package/dist/puzzles/cube2/random-state.js +44 -34
- package/dist/puzzles/cube2/scrambler.d.ts.map +1 -1
- package/dist/puzzles/cube2/scrambler.js +24 -10
- package/dist/puzzles/cube2/solver/index.d.ts +2 -1
- package/dist/puzzles/cube2/solver/index.d.ts.map +1 -1
- package/dist/puzzles/cube2/solver/index.js +2 -1
- package/dist/puzzles/cube2/solver/search.d.ts +3 -0
- package/dist/puzzles/cube2/solver/search.d.ts.map +1 -1
- package/dist/puzzles/cube2/solver/search.js +42 -7
- package/dist/puzzles/cube2/solver/tables.d.ts +1 -0
- package/dist/puzzles/cube2/solver/tables.d.ts.map +1 -1
- package/dist/puzzles/cube2/solver/tables.js +3 -0
- package/dist/puzzles/cube2/validation.d.ts +1 -0
- package/dist/puzzles/cube2/validation.d.ts.map +1 -1
- package/dist/puzzles/cube2/validation.js +7 -1
- package/dist/puzzles/pyraminx/facelets.d.ts.map +1 -1
- package/dist/puzzles/pyraminx/facelets.js +43 -17
- package/dist/puzzles/pyraminx/random-state.d.ts.map +1 -1
- package/dist/puzzles/pyraminx/random-state.js +63 -29
- package/dist/puzzles/pyraminx/scrambler.d.ts.map +1 -1
- package/dist/puzzles/pyraminx/scrambler.js +35 -26
- package/dist/puzzles/pyraminx/solver/search.d.ts +3 -0
- package/dist/puzzles/pyraminx/solver/search.d.ts.map +1 -1
- package/dist/puzzles/pyraminx/solver/search.js +60 -5
- package/dist/puzzles/pyraminx/solver/tables.d.ts +1 -0
- package/dist/puzzles/pyraminx/solver/tables.d.ts.map +1 -1
- package/dist/puzzles/pyraminx/solver/tables.js +3 -0
- package/dist/puzzles/skewb/scrambler.d.ts.map +1 -1
- package/dist/puzzles/skewb/scrambler.js +21 -7
- package/dist/puzzles/skewb/solver/search.d.ts +3 -0
- package/dist/puzzles/skewb/solver/search.d.ts.map +1 -1
- package/dist/puzzles/skewb/solver/search.js +43 -3
- package/dist/puzzles/skewb/solver/tables.d.ts +1 -0
- package/dist/puzzles/skewb/solver/tables.d.ts.map +1 -1
- package/dist/puzzles/skewb/solver/tables.js +3 -0
- package/dist/random/prng.d.ts.map +1 -1
- package/dist/random/prng.js +18 -4
- package/dist-cjs/puzzles/cube2/facelets.js +43 -22
- package/dist-cjs/puzzles/cube2/random-state.js +43 -33
- package/dist-cjs/puzzles/cube2/scrambler.js +22 -8
- package/dist-cjs/puzzles/cube2/solver/index.js +18 -1
- package/dist-cjs/puzzles/cube2/solver/search.js +45 -7
- package/dist-cjs/puzzles/cube2/solver/tables.js +4 -0
- package/dist-cjs/puzzles/cube2/validation.js +8 -1
- package/dist-cjs/puzzles/pyraminx/facelets.js +43 -17
- package/dist-cjs/puzzles/pyraminx/random-state.js +63 -29
- package/dist-cjs/puzzles/pyraminx/scrambler.js +34 -25
- package/dist-cjs/puzzles/pyraminx/solver/search.js +62 -4
- package/dist-cjs/puzzles/pyraminx/solver/tables.js +4 -0
- package/dist-cjs/puzzles/skewb/scrambler.js +19 -5
- package/dist-cjs/puzzles/skewb/solver/search.js +46 -3
- package/dist-cjs/puzzles/skewb/solver/tables.js +4 -0
- package/dist-cjs/random/prng.js +18 -4
- package/package.json +1 -1
|
@@ -1,39 +1,69 @@
|
|
|
1
1
|
import { createRandomSource } from "../../random/index.js";
|
|
2
|
-
import {
|
|
2
|
+
import { isSolvedPyraminxState } from "./state.js";
|
|
3
|
+
// Compact -> full Lehmer index for the 360 even permutations of 6 edges.
|
|
4
|
+
// Generated once: a permutation's parity is the parity of the digit sum of
|
|
5
|
+
// its Lehmer code (each digit counts smaller elements to its right).
|
|
6
|
+
const COMPACT_TO_FULL = new Uint16Array(360);
|
|
7
|
+
(function buildCompactMapping() {
|
|
8
|
+
let compact = 0;
|
|
9
|
+
for (let full = 0; full < 720 && compact < 360; full += 1) {
|
|
10
|
+
let value = full;
|
|
11
|
+
const c5 = (value / 120) | 0;
|
|
12
|
+
value -= c5 * 120;
|
|
13
|
+
const c4 = (value / 24) | 0;
|
|
14
|
+
value -= c4 * 24;
|
|
15
|
+
const c3 = (value / 6) | 0;
|
|
16
|
+
value -= c3 * 6;
|
|
17
|
+
const c2 = (value / 2) | 0;
|
|
18
|
+
value -= c2 * 2;
|
|
19
|
+
const c1 = value;
|
|
20
|
+
if ((c5 + c4 + c3 + c2 + c1) % 2 === 0) {
|
|
21
|
+
COMPACT_TO_FULL[compact] = full;
|
|
22
|
+
compact += 1;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
})();
|
|
3
26
|
export function generateRandomPyraminxState(options = {}) {
|
|
4
27
|
const random = createRandomSource(options.seed);
|
|
5
28
|
const rejectTrivial = options.rejectTrivial ?? true;
|
|
6
29
|
for (let attempt = 0; attempt < 100; attempt += 1) {
|
|
30
|
+
// The state is legal by construction (even permutation parity, even
|
|
31
|
+
// orientation sum, values in range), so only the trivial core check is
|
|
32
|
+
// needed here — no full re-validation.
|
|
7
33
|
const state = createLegalRandomState(random);
|
|
8
|
-
if (!rejectTrivial ||
|
|
34
|
+
if (!rejectTrivial || !isSolvedPyraminxState(state)) {
|
|
9
35
|
return state;
|
|
10
36
|
}
|
|
11
37
|
}
|
|
12
38
|
throw new Error("Unable to generate a non-trivial random Pyraminx state.");
|
|
13
39
|
}
|
|
14
40
|
function createLegalRandomState(random) {
|
|
15
|
-
// 4 random centers
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
41
|
+
// 4 random centers from one uniform value in [0, 81) (4 base-3 digits)
|
|
42
|
+
let centerValue = random.nextInt(81);
|
|
43
|
+
const centers = [0, 0, 0, 0];
|
|
44
|
+
for (let index = 0; index < 4; index += 1) {
|
|
45
|
+
centers[index] = centerValue % 3;
|
|
46
|
+
centerValue = (centerValue / 3) | 0;
|
|
47
|
+
}
|
|
48
|
+
// 4 random tips from one uniform value in [0, 81)
|
|
49
|
+
let tipValue = random.nextInt(81);
|
|
50
|
+
const tips = [0, 0, 0, 0];
|
|
51
|
+
for (let index = 0; index < 4; index += 1) {
|
|
52
|
+
tips[index] = tipValue % 3;
|
|
53
|
+
tipValue = (tipValue / 3) | 0;
|
|
27
54
|
}
|
|
28
|
-
// 6 edges
|
|
29
|
-
const
|
|
55
|
+
// 6 edges permutation, even parity, uniform over the 360 even permutations
|
|
56
|
+
const edgesPerm = permutationFromCompact(random.nextInt(360));
|
|
57
|
+
// 6 edges orientation (sum must be even): 5 free bits + parity digit
|
|
58
|
+
let orientationValue = random.nextInt(32);
|
|
59
|
+
const edgesOrient = [0, 0, 0, 0, 0, 0];
|
|
30
60
|
let sumOrient = 0;
|
|
31
|
-
for (let
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
61
|
+
for (let index = 0; index < 5; index += 1) {
|
|
62
|
+
edgesOrient[index] = orientationValue & 1;
|
|
63
|
+
sumOrient += orientationValue & 1;
|
|
64
|
+
orientationValue >>= 1;
|
|
35
65
|
}
|
|
36
|
-
edgesOrient[5] = sumOrient
|
|
66
|
+
edgesOrient[5] = sumOrient & 1;
|
|
37
67
|
return {
|
|
38
68
|
centers,
|
|
39
69
|
edgesPerm,
|
|
@@ -41,13 +71,17 @@ function createLegalRandomState(random) {
|
|
|
41
71
|
tips
|
|
42
72
|
};
|
|
43
73
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
74
|
+
const FACTORIALS = [1, 1, 2, 6, 24, 120, 720];
|
|
75
|
+
function permutationFromCompact(compact) {
|
|
76
|
+
let full = COMPACT_TO_FULL[compact];
|
|
77
|
+
const permutation = [0, 0, 0, 0, 0, 0];
|
|
78
|
+
const remaining = [0, 1, 2, 3, 4, 5];
|
|
79
|
+
for (let position = 0; position < 6; position += 1) {
|
|
80
|
+
const divisor = FACTORIALS[5 - position];
|
|
81
|
+
const choice = (full / divisor) | 0;
|
|
82
|
+
full -= choice * divisor;
|
|
83
|
+
permutation[position] = remaining[choice];
|
|
84
|
+
remaining.splice(choice, 1);
|
|
51
85
|
}
|
|
52
|
-
return
|
|
86
|
+
return permutation;
|
|
53
87
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scrambler.d.ts","sourceRoot":"","sources":["../../../src/puzzles/pyraminx/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/pyraminx/scrambler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAe9F,eAAO,MAAM,yBAAyB,EAAE,iBAYvC,CAAC;AAEF,wBAAsB,wBAAwB,CAC5C,OAAO,GAAE,eAAe,GAAG;IAAE,cAAc,CAAC,EAAE,OAAO,CAAA;CAAO,GAC3D,OAAO,CAAC,cAAc,CAAC,CA8DzB"}
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import { generateRandomPyraminxState } from "./random-state.js";
|
|
2
|
-
import {
|
|
3
|
-
import { solvePyraminx, verifyPyraminxScramble } from "./solver/index.js";
|
|
2
|
+
import { arePyraminxTablesLoaded, solvePyraminxIndices, solvePyraminxIndicesSync, verifyPyraminxScrambleIndices, PYRAMINX_MOVES } from "./solver/index.js";
|
|
4
3
|
import { formatPyraminxFacelets, toPyraminxFacelets } from "./facelets.js";
|
|
5
4
|
import { PYRAMINX_TIPS } from "./types.js";
|
|
6
|
-
|
|
7
|
-
const FACE_TIP_INDEX = { U: 0, L: 1, R: 2, B: 3 };
|
|
5
|
+
const TIP_INVERSE = ["u'", "l'", "r'", "b'"];
|
|
8
6
|
export const pyraminxScrambleGenerator = {
|
|
9
7
|
info: {
|
|
10
8
|
eventId: "pymx",
|
|
@@ -20,47 +18,58 @@ export const pyraminxScrambleGenerator = {
|
|
|
20
18
|
};
|
|
21
19
|
export async function generatePyraminxScramble(options = {}) {
|
|
22
20
|
const state = generateRandomPyraminxState(options);
|
|
23
|
-
// Solve the core edges and centers
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
21
|
+
// Solve the core edges and centers as move indices (0..7). For index m the
|
|
22
|
+
// inverse is m ^ 1, the face is m >> 1 (0=U, 1=L, 2=R, 3=B) and the
|
|
23
|
+
// direction is m & 1 (1 = clockwise, 0 = counter-clockwise).
|
|
24
|
+
const solution = arePyraminxTablesLoaded()
|
|
25
|
+
? solvePyraminxIndicesSync(state)
|
|
26
|
+
: await solvePyraminxIndices(state);
|
|
27
|
+
const depth = solution.length;
|
|
28
|
+
// Single pass over the solution (reversed): build the scramble move strings
|
|
29
|
+
// while accumulating the tips rotated by the layer moves arithmetically
|
|
30
|
+
// (no state replay, no string parsing).
|
|
31
|
+
const moves = new Array(depth + 4);
|
|
29
32
|
const coreTips = [0, 0, 0, 0];
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
33
|
+
let count = 0;
|
|
34
|
+
for (let index = depth - 1; index >= 0; index -= 1) {
|
|
35
|
+
const m = solution[index] ^ 1;
|
|
36
|
+
moves[count++] = PYRAMINX_MOVES[m];
|
|
37
|
+
const face = m >> 1;
|
|
38
|
+
coreTips[face] = (coreTips[face] + (m & 1 ? 2 : 1)) % 3;
|
|
37
39
|
}
|
|
38
|
-
const
|
|
39
|
-
for (let i = 0; i < 4; i
|
|
40
|
+
const tipMoves = [];
|
|
41
|
+
for (let i = 0; i < 4; i += 1) {
|
|
40
42
|
const delta = (state.tips[i] - coreTips[i] + 3) % 3;
|
|
41
43
|
if (delta === 1) {
|
|
42
|
-
|
|
44
|
+
moves[count++] = PYRAMINX_TIPS[i];
|
|
45
|
+
tipMoves.push(i * 2);
|
|
43
46
|
}
|
|
44
47
|
else if (delta === 2) {
|
|
45
|
-
|
|
48
|
+
moves[count++] = TIP_INVERSE[i];
|
|
49
|
+
tipMoves.push(i * 2 + 1);
|
|
46
50
|
}
|
|
47
51
|
}
|
|
48
|
-
|
|
52
|
+
moves.length = count;
|
|
53
|
+
if (!options.skipValidation && !verifyPyraminxScrambleIndices(state, solution, tipMoves)) {
|
|
49
54
|
throw new Error("Generated Pyraminx scramble does not reproduce the target random state.");
|
|
50
55
|
}
|
|
51
56
|
const facelets = toPyraminxFacelets(state);
|
|
57
|
+
const solutionMoves = new Array(depth);
|
|
58
|
+
for (let index = 0; index < depth; index += 1) {
|
|
59
|
+
solutionMoves[index] = PYRAMINX_MOVES[solution[index]];
|
|
60
|
+
}
|
|
52
61
|
return {
|
|
53
62
|
eventId: "pymx",
|
|
54
|
-
scramble:
|
|
55
|
-
moves
|
|
63
|
+
scramble: moves.join(" "),
|
|
64
|
+
moves,
|
|
56
65
|
quality: "random-state",
|
|
57
66
|
generator: "pyraminx-optimal",
|
|
58
67
|
metadata: {
|
|
59
68
|
state,
|
|
60
69
|
facelets,
|
|
61
70
|
faceletString: formatPyraminxFacelets(facelets),
|
|
62
|
-
solutionMoves
|
|
63
|
-
solutionDepth:
|
|
71
|
+
solutionMoves,
|
|
72
|
+
solutionDepth: depth
|
|
64
73
|
}
|
|
65
74
|
};
|
|
66
75
|
}
|
|
@@ -5,6 +5,9 @@ export interface PyraminxSolution {
|
|
|
5
5
|
}
|
|
6
6
|
export declare function solvePyraminx(state: PyraminxState): Promise<PyraminxMove[]>;
|
|
7
7
|
export declare function solvePyraminxWithInfo(state: PyraminxState): Promise<PyraminxSolution>;
|
|
8
|
+
export declare function solvePyraminxIndices(state: PyraminxState): Promise<number[]>;
|
|
9
|
+
export declare function solvePyraminxIndicesSync(state: PyraminxState): number[];
|
|
8
10
|
export declare function solvePyraminxSync(state: PyraminxState): PyraminxMove[];
|
|
9
11
|
export declare function verifyPyraminxScramble(state: PyraminxState, finalMoves: readonly PyraminxMove[]): boolean;
|
|
12
|
+
export declare function verifyPyraminxScrambleIndices(state: PyraminxState, solution: readonly number[], tipMoves: readonly number[]): boolean;
|
|
10
13
|
//# sourceMappingURL=search.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/pyraminx/solver/search.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/pyraminx/solver/search.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAe/D,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,wBAAsB,aAAa,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAGjF;AAED,wBAAsB,qBAAqB,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAG3F;AAMD,wBAAsB,oBAAoB,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAGlF;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,EAAE,CAGvE;AAuCD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,aAAa,GAAG,YAAY,EAAE,CAGtE;AAaD,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,SAAS,YAAY,EAAE,GAAG,OAAO,CAsCzG;AAKD,wBAAgB,6BAA6B,CAC3C,KAAK,EAAE,aAAa,EACpB,QAAQ,EAAE,SAAS,MAAM,EAAE,EAC3B,QAAQ,EAAE,SAAS,MAAM,EAAE,GAC1B,OAAO,CAmCT"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { POLICY_TABLE, CENTERS_TABLE, PERMUTATION_TABLE, ORIENTATION_TABLE, PYRAMINX_MOVES, MOVE_COUNT, PERMUTATION_COUNT, getCentersIndex, getOrientationIndex, getPermutationCompactIndex,
|
|
1
|
+
import { POLICY_TABLE, CENTERS_TABLE, PERMUTATION_TABLE, ORIENTATION_TABLE, PYRAMINX_MOVES, MOVE_COUNT, PERMUTATION_COUNT, getCentersIndex, getOrientationIndex, getPermutationCompactIndex, ensureTablesLoaded } from "./tables.js";
|
|
2
2
|
export async function solvePyraminx(state) {
|
|
3
3
|
await ensureTablesLoaded();
|
|
4
4
|
return solveWithTables(state).moves;
|
|
@@ -7,7 +7,25 @@ export async function solvePyraminxWithInfo(state) {
|
|
|
7
7
|
await ensureTablesLoaded();
|
|
8
8
|
return solveWithTables(state);
|
|
9
9
|
}
|
|
10
|
+
// Index-based solve for the hot scramble path: returns the solution as move
|
|
11
|
+
// indices (0..7) in walk order. The inverse of index m is m ^ 1 (moves are
|
|
12
|
+
// paired U/U', L/L', R/R', B/B'), its face is m >> 1 and its direction is
|
|
13
|
+
// m & 1 (1 = clockwise, 0 = counter-clockwise).
|
|
14
|
+
export async function solvePyraminxIndices(state) {
|
|
15
|
+
await ensureTablesLoaded();
|
|
16
|
+
return solveWithTablesIndices(state);
|
|
17
|
+
}
|
|
18
|
+
export function solvePyraminxIndicesSync(state) {
|
|
19
|
+
if (!POLICY_TABLE)
|
|
20
|
+
throw new Error("Pyraminx tables not loaded");
|
|
21
|
+
return solveWithTablesIndices(state);
|
|
22
|
+
}
|
|
10
23
|
function solveWithTables(state) {
|
|
24
|
+
const indices = solveWithTablesIndices(state);
|
|
25
|
+
const moves = indices.map((index) => PYRAMINX_MOVES[index]);
|
|
26
|
+
return { moves, depth: moves.length };
|
|
27
|
+
}
|
|
28
|
+
function solveWithTablesIndices(state) {
|
|
11
29
|
const centersTable = CENTERS_TABLE;
|
|
12
30
|
const permTable = PERMUTATION_TABLE;
|
|
13
31
|
const orientTable = ORIENTATION_TABLE;
|
|
@@ -17,17 +35,20 @@ function solveWithTables(state) {
|
|
|
17
35
|
let o = getOrientationIndex(state.edgesOrient);
|
|
18
36
|
let stateIndex = c + 81 * (p + PERMUTATION_COUNT * o);
|
|
19
37
|
if (stateIndex === 0)
|
|
20
|
-
return
|
|
38
|
+
return [];
|
|
21
39
|
const moves = [];
|
|
22
40
|
while (stateIndex !== 0) {
|
|
23
|
-
const
|
|
24
|
-
|
|
41
|
+
const bits = stateIndex * 3;
|
|
42
|
+
const byteIndex = bits >> 3;
|
|
43
|
+
const offset = bits & 7;
|
|
44
|
+
const m = ((policy[byteIndex] | ((policy[byteIndex + 1] ?? 0) << 8)) >> offset) & 0x07;
|
|
45
|
+
moves.push(m);
|
|
25
46
|
c = centersTable[c * MOVE_COUNT + m];
|
|
26
47
|
p = permTable[p * MOVE_COUNT + m];
|
|
27
48
|
o = orientTable[o * MOVE_COUNT + m];
|
|
28
49
|
stateIndex = c + 81 * (p + PERMUTATION_COUNT * o);
|
|
29
50
|
}
|
|
30
|
-
return
|
|
51
|
+
return moves;
|
|
31
52
|
}
|
|
32
53
|
// Synchronous variant for scripts that have already ensured tables are loaded
|
|
33
54
|
export function solvePyraminxSync(state) {
|
|
@@ -80,3 +101,37 @@ export function verifyPyraminxScramble(state, finalMoves) {
|
|
|
80
101
|
tips[2] === state.tips[2] &&
|
|
81
102
|
tips[3] === state.tips[3]);
|
|
82
103
|
}
|
|
104
|
+
// Numeric verification for the hot scramble path: replays the scramble
|
|
105
|
+
// (solution reversed with m ^ 1 inverses, tip moves as tipIndex * 2 + dir
|
|
106
|
+
// with dir 0 = clockwise, 1 = counter-clockwise) without any string parsing.
|
|
107
|
+
export function verifyPyraminxScrambleIndices(state, solution, tipMoves) {
|
|
108
|
+
const centersTable = CENTERS_TABLE;
|
|
109
|
+
const permTable = PERMUTATION_TABLE;
|
|
110
|
+
const orientTable = ORIENTATION_TABLE;
|
|
111
|
+
if (!centersTable || !permTable || !orientTable) {
|
|
112
|
+
throw new Error("Pyraminx solver tables not loaded. Call ensureTablesLoaded() first.");
|
|
113
|
+
}
|
|
114
|
+
let c = 0;
|
|
115
|
+
let p = 0;
|
|
116
|
+
let o = 0;
|
|
117
|
+
const tips = [0, 0, 0, 0];
|
|
118
|
+
for (let index = solution.length - 1; index >= 0; index -= 1) {
|
|
119
|
+
const m = solution[index] ^ 1;
|
|
120
|
+
c = centersTable[c * MOVE_COUNT + m];
|
|
121
|
+
p = permTable[p * MOVE_COUNT + m];
|
|
122
|
+
o = orientTable[o * MOVE_COUNT + m];
|
|
123
|
+
const face = m >> 1;
|
|
124
|
+
tips[face] = (tips[face] + (m & 1 ? 2 : 1)) % 3;
|
|
125
|
+
}
|
|
126
|
+
for (const tipMove of tipMoves) {
|
|
127
|
+
const tipIndex = tipMove >> 1;
|
|
128
|
+
tips[tipIndex] = (tips[tipIndex] + (tipMove & 1 ? 2 : 1)) % 3;
|
|
129
|
+
}
|
|
130
|
+
return (c === getCentersIndex(state.centers) &&
|
|
131
|
+
p === getPermutationCompactIndex(state.edgesPerm) &&
|
|
132
|
+
o === getOrientationIndex(state.edgesOrient) &&
|
|
133
|
+
tips[0] === state.tips[0] &&
|
|
134
|
+
tips[1] === state.tips[1] &&
|
|
135
|
+
tips[2] === state.tips[2] &&
|
|
136
|
+
tips[3] === state.tips[3]);
|
|
137
|
+
}
|
|
@@ -35,6 +35,7 @@ export declare let PERMUTATION_TABLE: Uint16Array | null;
|
|
|
35
35
|
export declare let ORIENTATION_TABLE: Uint8Array | null;
|
|
36
36
|
export declare let POLICY_TABLE: Uint8Array | null;
|
|
37
37
|
export declare function ensureTablesLoaded(): Promise<void>;
|
|
38
|
+
export declare function arePyraminxTablesLoaded(): boolean;
|
|
38
39
|
export declare function getPolicyMove(stateIndex: number): number;
|
|
39
40
|
export declare function getCentersTransition(centerIdx: number, move: number): number;
|
|
40
41
|
export declare function getPermutationTransition(permIdx: number, move: number): number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tables.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/pyraminx/solver/tables.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"tables.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/pyraminx/solver/tables.ts"],"names":[],"mappings":"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"}
|
|
@@ -271,6 +271,9 @@ export async function ensureTablesLoaded() {
|
|
|
271
271
|
POLICY_TABLE = tables.policyTable;
|
|
272
272
|
tablesLoaded = true;
|
|
273
273
|
}
|
|
274
|
+
export function arePyraminxTablesLoaded() {
|
|
275
|
+
return tablesLoaded;
|
|
276
|
+
}
|
|
274
277
|
export function getPolicyMove(stateIndex) {
|
|
275
278
|
if (!POLICY_TABLE)
|
|
276
279
|
throw new Error("Pyraminx solver tables not loaded. Call ensureTablesLoaded() first.");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scrambler.d.ts","sourceRoot":"","sources":["../../../src/puzzles/skewb/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/skewb/scrambler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAa9F,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,CAgDzB"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { generateRandomSkewbState } from "./random-state.js";
|
|
2
|
-
import { formatSkewbAlgorithm
|
|
3
|
-
import {
|
|
2
|
+
import { formatSkewbAlgorithm } from "./moves.js";
|
|
3
|
+
import { areSkewbTablesLoaded, solveSkewbIndices, solveSkewbIndicesSync, verifySkewbScrambleIndices, SKEWB_MOVES } from "./solver/index.js";
|
|
4
4
|
import { formatSkewbFacelets, toSkewbFacelets } from "./facelets.js";
|
|
5
5
|
export const skewbScrambleGenerator = {
|
|
6
6
|
info: {
|
|
@@ -17,18 +17,32 @@ export const skewbScrambleGenerator = {
|
|
|
17
17
|
};
|
|
18
18
|
export async function generateSkewbScramble(options = {}) {
|
|
19
19
|
const state = await generateRandomSkewbState(options);
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
// generateRandomSkewbState guarantees the tables are loaded, so use the
|
|
21
|
+
// sync solve on every call after the first (no extra microtask).
|
|
22
|
+
const solution = areSkewbTablesLoaded()
|
|
23
|
+
? solveSkewbIndicesSync(state)
|
|
24
|
+
: await solveSkewbIndices(state);
|
|
25
|
+
const depth = solution.length;
|
|
26
|
+
// Scramble = inverse of the solution, reversed. For index m the inverse is
|
|
27
|
+
// m ^ 1 (moves are paired R/R', U/U', L/L', B/B').
|
|
28
|
+
const scrambleMoves = new Array(depth);
|
|
29
|
+
for (let index = 0; index < depth; index += 1) {
|
|
30
|
+
scrambleMoves[index] = SKEWB_MOVES[solution[depth - 1 - index] ^ 1];
|
|
31
|
+
}
|
|
22
32
|
if (!options.skipValidation) {
|
|
23
33
|
// If the scramble reproduces the target state, its inverse (the
|
|
24
34
|
// solution) solves it by construction, so a single walk suffices.
|
|
25
|
-
if (!
|
|
35
|
+
if (!verifySkewbScrambleIndices(state, solution)) {
|
|
26
36
|
throw new Error("Generated Skewb scramble does not reproduce the target random state.");
|
|
27
37
|
}
|
|
28
38
|
if (scrambleMoves.length < 2)
|
|
29
39
|
throw new Error("Generated Skewb scramble is too short.");
|
|
30
40
|
}
|
|
31
41
|
const facelets = toSkewbFacelets(state);
|
|
42
|
+
const solutionMoves = new Array(depth);
|
|
43
|
+
for (let index = 0; index < depth; index += 1) {
|
|
44
|
+
solutionMoves[index] = SKEWB_MOVES[solution[index]];
|
|
45
|
+
}
|
|
32
46
|
const result = {
|
|
33
47
|
eventId: "skwb",
|
|
34
48
|
scramble: formatSkewbAlgorithm(scrambleMoves),
|
|
@@ -39,8 +53,8 @@ export async function generateSkewbScramble(options = {}) {
|
|
|
39
53
|
state,
|
|
40
54
|
facelets,
|
|
41
55
|
faceletString: formatSkewbFacelets(facelets),
|
|
42
|
-
solutionMoves
|
|
43
|
-
solutionDepth:
|
|
56
|
+
solutionMoves,
|
|
57
|
+
solutionDepth: depth,
|
|
44
58
|
},
|
|
45
59
|
};
|
|
46
60
|
if (options.seed !== undefined)
|
|
@@ -5,7 +5,10 @@ export interface SkewbSolution {
|
|
|
5
5
|
}
|
|
6
6
|
export declare function solveSkewb(state: SkewbState): Promise<SkewbMove[]>;
|
|
7
7
|
export declare function solveSkewbWithInfo(state: SkewbState): Promise<SkewbSolution>;
|
|
8
|
+
export declare function solveSkewbIndices(state: SkewbState): Promise<number[]>;
|
|
9
|
+
export declare function solveSkewbIndicesSync(state: SkewbState): number[];
|
|
8
10
|
export declare function solveSkewbSync(state: SkewbState): SkewbMove[];
|
|
9
11
|
export declare function verifySkewbScramble(state: SkewbState, scrambleMoves: readonly SkewbMove[]): boolean;
|
|
12
|
+
export declare function verifySkewbScrambleIndices(state: SkewbState, solution: readonly number[]): boolean;
|
|
10
13
|
export declare function verifySkewbSolution(state: SkewbState, solutionMoves: readonly SkewbMove[]): boolean;
|
|
11
14
|
//# sourceMappingURL=search.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/skewb/solver/search.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAiBzD,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,wBAAsB,UAAU,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,CAGxE;AAED,wBAAsB,kBAAkB,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,aAAa,CAAC,CAGlF;AAGD,wBAAgB,cAAc,CAAC,KAAK,EAAE,UAAU,GAAG,SAAS,EAAE,CAK7D;
|
|
1
|
+
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/skewb/solver/search.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAiBzD,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,wBAAsB,UAAU,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,CAGxE;AAED,wBAAsB,kBAAkB,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,aAAa,CAAC,CAGlF;AAKD,wBAAsB,iBAAiB,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAG5E;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,EAAE,CAKjE;AAGD,wBAAgB,cAAc,CAAC,KAAK,EAAE,UAAU,GAAG,SAAS,EAAE,CAK7D;AA4ED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,SAAS,SAAS,EAAE,GAAG,OAAO,CAOnG;AAID,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAwBlG;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,SAAS,SAAS,EAAE,GAAG,OAAO,CAUnG"}
|
|
@@ -7,6 +7,19 @@ export async function solveSkewbWithInfo(state) {
|
|
|
7
7
|
await ensureTablesLoaded();
|
|
8
8
|
return solveWithTables(state);
|
|
9
9
|
}
|
|
10
|
+
// Index-based solve for the hot scramble path: returns the solution as move
|
|
11
|
+
// indices (0..7) in walk order. The inverse of index m is m ^ 1 (moves are
|
|
12
|
+
// paired R/R', U/U', L/L', B/B').
|
|
13
|
+
export async function solveSkewbIndices(state) {
|
|
14
|
+
await ensureTablesLoaded();
|
|
15
|
+
return solveWithTablesIndices(state);
|
|
16
|
+
}
|
|
17
|
+
export function solveSkewbIndicesSync(state) {
|
|
18
|
+
if (!POLICY_TABLE) {
|
|
19
|
+
throw new Error("Skewb solver tables not loaded. Call ensureTablesLoaded() first.");
|
|
20
|
+
}
|
|
21
|
+
return solveWithTablesIndices(state);
|
|
22
|
+
}
|
|
10
23
|
// Synchronous variant for scripts that have already ensured tables are loaded
|
|
11
24
|
export function solveSkewbSync(state) {
|
|
12
25
|
if (!POLICY_TABLE) {
|
|
@@ -15,6 +28,11 @@ export function solveSkewbSync(state) {
|
|
|
15
28
|
return solveWithTables(state).moves;
|
|
16
29
|
}
|
|
17
30
|
function solveWithTables(state) {
|
|
31
|
+
const indices = solveWithTablesIndices(state);
|
|
32
|
+
const moves = indices.map((index) => SKEWB_MOVES[index]);
|
|
33
|
+
return { moves, depth: moves.length };
|
|
34
|
+
}
|
|
35
|
+
function solveWithTablesIndices(state) {
|
|
18
36
|
const policy = POLICY_TABLE;
|
|
19
37
|
const permTable = PERMUTATION_TABLE;
|
|
20
38
|
const orientTable = ORIENTATION_TABLE;
|
|
@@ -28,18 +46,18 @@ function solveWithTables(state) {
|
|
|
28
46
|
let rC = getCenterIndex(state.centers);
|
|
29
47
|
let rank = rankFromComponents(r7, rO, rC);
|
|
30
48
|
if (rank === SOLVED_RANK) {
|
|
31
|
-
return
|
|
49
|
+
return [];
|
|
32
50
|
}
|
|
33
51
|
const moves = [];
|
|
34
52
|
while (rank !== SOLVED_RANK) {
|
|
35
53
|
const m = getPolicyMoveFromPacked(policy, rank);
|
|
36
|
-
moves.push(
|
|
54
|
+
moves.push(m);
|
|
37
55
|
r7 = permTable[r7 * MOVE_COUNT + m];
|
|
38
56
|
rO = orientTable[rO * MOVE_COUNT + m];
|
|
39
57
|
rC = centersTable[rC * MOVE_COUNT + m];
|
|
40
58
|
rank = rankFromComponents(r7, rO, rC);
|
|
41
59
|
}
|
|
42
|
-
return
|
|
60
|
+
return moves;
|
|
43
61
|
}
|
|
44
62
|
// ---------------------------------------------------------------------------
|
|
45
63
|
// Coordinate-walking verification (no full state clones)
|
|
@@ -70,6 +88,28 @@ export function verifySkewbScramble(state, scrambleMoves) {
|
|
|
70
88
|
final.rO === getOrientationIndex(state.corners.orientation) &&
|
|
71
89
|
final.rC === getCenterIndex(state.centers));
|
|
72
90
|
}
|
|
91
|
+
// Numeric verification for the hot scramble path: replays the scramble (the
|
|
92
|
+
// solution reversed with m ^ 1 inverses) without any string parsing.
|
|
93
|
+
export function verifySkewbScrambleIndices(state, solution) {
|
|
94
|
+
const permTable = PERMUTATION_TABLE;
|
|
95
|
+
const orientTable = ORIENTATION_TABLE;
|
|
96
|
+
const centersTable = CENTERS_TABLE;
|
|
97
|
+
if (!permTable || !orientTable || !centersTable) {
|
|
98
|
+
throw new Error("Skewb solver tables not loaded. Call ensureTablesLoaded() first.");
|
|
99
|
+
}
|
|
100
|
+
let r7 = 0;
|
|
101
|
+
let rO = 0;
|
|
102
|
+
let rC = 0;
|
|
103
|
+
for (let index = solution.length - 1; index >= 0; index -= 1) {
|
|
104
|
+
const m = solution[index] ^ 1;
|
|
105
|
+
r7 = permTable[r7 * MOVE_COUNT + m];
|
|
106
|
+
rO = orientTable[rO * MOVE_COUNT + m];
|
|
107
|
+
rC = centersTable[rC * MOVE_COUNT + m];
|
|
108
|
+
}
|
|
109
|
+
return (r7 === getPerm7Index(state.corners.permutation) &&
|
|
110
|
+
rO === getOrientationIndex(state.corners.orientation) &&
|
|
111
|
+
rC === getCenterIndex(state.centers));
|
|
112
|
+
}
|
|
73
113
|
export function verifySkewbSolution(state, solutionMoves) {
|
|
74
114
|
const final = walkSkewbCoordinates({
|
|
75
115
|
r7: getPerm7Index(state.corners.permutation),
|
|
@@ -31,6 +31,7 @@ export declare let PERM7_UNRANK: Uint8Array | null;
|
|
|
31
31
|
export declare let CENTER_UNRANK: Uint8Array | null;
|
|
32
32
|
export declare let SOLVED_RANK: number;
|
|
33
33
|
export declare function ensureTablesLoaded(): Promise<void>;
|
|
34
|
+
export declare function areSkewbTablesLoaded(): boolean;
|
|
34
35
|
export declare function assignTables(tables: {
|
|
35
36
|
permutationTable: Uint16Array;
|
|
36
37
|
orientationTable: Uint16Array;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tables.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/skewb/solver/tables.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"tables.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/skewb/solver/tables.ts"],"names":[],"mappings":"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"}
|
|
@@ -156,6 +156,9 @@ export async function ensureTablesLoaded() {
|
|
|
156
156
|
const tables = await loadGeneratedSkewbSolverTables();
|
|
157
157
|
assignTables(tables);
|
|
158
158
|
}
|
|
159
|
+
export function areSkewbTablesLoaded() {
|
|
160
|
+
return tablesLoaded;
|
|
161
|
+
}
|
|
159
162
|
export function assignTables(tables) {
|
|
160
163
|
PERMUTATION_TABLE = tables.permutationTable;
|
|
161
164
|
ORIENTATION_TABLE = tables.orientationTable;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prng.d.ts","sourceRoot":"","sources":["../../src/random/prng.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"prng.d.ts","sourceRoot":"","sources":["../../src/random/prng.ts"],"names":[],"mappings":"AAUA,MAAM,WAAW,YAAY;IAC3B,IAAI,IAAI,MAAM,CAAC;IACf,OAAO,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAAC;IACtC,MAAM,CAAC,CAAC,EAAE,KAAK,EAAE,SAAS,CAAC,EAAE,GAAG,CAAC,CAAC;CACnC;AAED,wBAAgB,kBAAkB,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,YAAY,CAOvE"}
|
package/dist/random/prng.js
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import { randomBytes } from "node:crypto";
|
|
2
|
+
// 4096 bytes per refill (~682 values per syscall).
|
|
3
|
+
const CRYPTO_BUFFER_BYTES = 4096;
|
|
4
|
+
// Shared unseeded source: the crypto refill costs a syscall per buffer, so a
|
|
5
|
+
// per-call source would pay ~one syscall per scramble. Seeded calls stay
|
|
6
|
+
// isolated (deterministic streams must never be shared).
|
|
7
|
+
let sharedCryptoSource = null;
|
|
2
8
|
export function createRandomSource(seed) {
|
|
3
9
|
if (seed === undefined) {
|
|
4
|
-
|
|
10
|
+
sharedCryptoSource ??= new CryptoRandomSource();
|
|
11
|
+
return sharedCryptoSource;
|
|
5
12
|
}
|
|
6
13
|
return new SeededRandomSource(hashSeed(seed));
|
|
7
14
|
}
|
|
@@ -11,12 +18,19 @@ class CryptoRandomSource {
|
|
|
11
18
|
next() {
|
|
12
19
|
// Batch the crypto calls: one randomBytes per move is ~2 orders of
|
|
13
20
|
// magnitude slower in Node (syscall per call). Refill lazily.
|
|
14
|
-
if (this.buffer === null || this.offset + 6 >
|
|
15
|
-
this.buffer = randomBytes(
|
|
21
|
+
if (this.buffer === null || this.offset + 6 > CRYPTO_BUFFER_BYTES) {
|
|
22
|
+
this.buffer = randomBytes(CRYPTO_BUFFER_BYTES);
|
|
16
23
|
this.offset = 0;
|
|
17
24
|
}
|
|
18
|
-
const
|
|
25
|
+
const bytes = this.buffer;
|
|
26
|
+
const offset = this.offset;
|
|
19
27
|
this.offset += 6;
|
|
28
|
+
const value = bytes[offset] * 0x10000000000 +
|
|
29
|
+
bytes[offset + 1] * 0x100000000 +
|
|
30
|
+
bytes[offset + 2] * 0x1000000 +
|
|
31
|
+
bytes[offset + 3] * 0x10000 +
|
|
32
|
+
bytes[offset + 4] * 0x100 +
|
|
33
|
+
bytes[offset + 5];
|
|
20
34
|
return value / 0x1000000000000;
|
|
21
35
|
}
|
|
22
36
|
nextInt(maxExclusive) {
|