@cubingcenter/scrambler 0.5.1 → 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-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/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"facelets.d.ts","sourceRoot":"","sources":["../../../src/puzzles/cube2/facelets.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,UAAU,EAAgB,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"facelets.d.ts","sourceRoot":"","sources":["../../../src/puzzles/cube2/facelets.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,UAAU,EAAgB,MAAM,YAAY,CAAC;AAgErF,wBAAgB,eAAe,CAAC,KAAK,EAAE,UAAU,GAAG,aAAa,CAqBhE;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,aAAa,GAAG,MAAM,CAEnE;AAED,wBAAgB,iBAAiB,IAAI,SAAS,SAAS,EAAE,CAExD"}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
const FACE_ORDER = ["U", "R", "F", "D", "L", "B"];
|
|
2
|
+
const STICKERS = ["U", "R", "F", "D", "L", "B"];
|
|
3
|
+
const STICKER_INDEX = { U: 0, R: 1, F: 2, D: 3, L: 4, B: 5 };
|
|
2
4
|
const CORNER_PIECE_STICKERS = [
|
|
3
5
|
["U", "R", "F"],
|
|
4
6
|
["U", "F", "L"],
|
|
@@ -19,12 +21,48 @@ const CORNER_POSITION_SLOTS = [
|
|
|
19
21
|
[["D", 2], ["B", 3], ["L", 2]],
|
|
20
22
|
[["D", 3], ["R", 3], ["B", 2]]
|
|
21
23
|
];
|
|
22
|
-
|
|
23
|
-
|
|
24
|
+
const CORNER_CELLS = buildCells();
|
|
25
|
+
function buildCells() {
|
|
26
|
+
const cells = [];
|
|
24
27
|
for (let position = 0; position < CORNER_POSITION_SLOTS.length; position += 1) {
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
+
const byPiece = [];
|
|
29
|
+
for (let piece = 0; piece < CORNER_PIECE_STICKERS.length; piece += 1) {
|
|
30
|
+
const byOrientation = [];
|
|
31
|
+
for (let orientation = 0; orientation < 3; orientation += 1) {
|
|
32
|
+
const stickers = CORNER_PIECE_STICKERS[piece];
|
|
33
|
+
const slotList = CORNER_POSITION_SLOTS[position];
|
|
34
|
+
const cell = [];
|
|
35
|
+
for (let index = 0; index < slotList.length; index += 1) {
|
|
36
|
+
const [face, stickerIndex] = slotList[index];
|
|
37
|
+
cell.push(FACE_ORDER.indexOf(face));
|
|
38
|
+
cell.push(stickerIndex);
|
|
39
|
+
cell.push(STICKER_INDEX[stickers[(index - orientation + stickers.length) % stickers.length]]);
|
|
40
|
+
}
|
|
41
|
+
byOrientation.push(cell);
|
|
42
|
+
}
|
|
43
|
+
byPiece.push(byOrientation);
|
|
44
|
+
}
|
|
45
|
+
cells.push(byPiece);
|
|
46
|
+
}
|
|
47
|
+
return cells;
|
|
48
|
+
}
|
|
49
|
+
export function toCube2Facelets(state) {
|
|
50
|
+
const facelets = {
|
|
51
|
+
U: Array(4),
|
|
52
|
+
R: Array(4),
|
|
53
|
+
F: Array(4),
|
|
54
|
+
D: Array(4),
|
|
55
|
+
L: Array(4),
|
|
56
|
+
B: Array(4)
|
|
57
|
+
};
|
|
58
|
+
const faces = [facelets.U, facelets.R, facelets.F, facelets.D, facelets.L, facelets.B];
|
|
59
|
+
const permutation = state.corners.permutation;
|
|
60
|
+
const orientation = state.corners.orientation;
|
|
61
|
+
for (let position = 0; position < CORNER_CELLS.length; position += 1) {
|
|
62
|
+
const cells = CORNER_CELLS[position][permutation[position]][orientation[position]];
|
|
63
|
+
for (let cellIndex = 0; cellIndex < cells.length; cellIndex += 3) {
|
|
64
|
+
faces[cells[cellIndex]][cells[cellIndex + 1]] = STICKERS[cells[cellIndex + 2]];
|
|
65
|
+
}
|
|
28
66
|
}
|
|
29
67
|
return facelets;
|
|
30
68
|
}
|
|
@@ -34,20 +72,3 @@ export function formatCube2Facelets(facelets) {
|
|
|
34
72
|
export function getCube2FaceOrder() {
|
|
35
73
|
return FACE_ORDER;
|
|
36
74
|
}
|
|
37
|
-
function createEmptyFacelets() {
|
|
38
|
-
return {
|
|
39
|
-
U: Array(4).fill("U"),
|
|
40
|
-
R: Array(4).fill("R"),
|
|
41
|
-
F: Array(4).fill("F"),
|
|
42
|
-
D: Array(4).fill("D"),
|
|
43
|
-
L: Array(4).fill("L"),
|
|
44
|
-
B: Array(4).fill("B")
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
function placeOrientedStickers(facelets, slots, stickers, orientation) {
|
|
48
|
-
for (let index = 0; index < slots.length; index += 1) {
|
|
49
|
-
const [face, stickerIndex] = slots[index];
|
|
50
|
-
const sticker = stickers[(index - orientation + stickers.length) % stickers.length];
|
|
51
|
-
facelets[face][stickerIndex] = sticker;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"random-state.d.ts","sourceRoot":"","sources":["../../../src/puzzles/cube2/random-state.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"random-state.d.ts","sourceRoot":"","sources":["../../../src/puzzles/cube2/random-state.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,uBAAuB,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAItE,wBAAgB,wBAAwB,CAAC,OAAO,GAAE,uBAA4B,GAAG,UAAU,CAsB1F"}
|
|
@@ -1,51 +1,61 @@
|
|
|
1
1
|
import { createRandomSource } from "../../random/index.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { isTrivialSamplingCube2State } from "./validation.js";
|
|
3
|
+
import { encodePermutation, encodeOrientation } from "./solver/tables.js";
|
|
4
4
|
export function generateRandomCube2State(options = {}) {
|
|
5
5
|
const random = createRandomSource(options.seed);
|
|
6
6
|
const rejectTrivial = options.rejectTrivial ?? true;
|
|
7
7
|
for (let attempt = 0; attempt < 100; attempt += 1) {
|
|
8
|
+
// The state is legal by construction (DBL corner fixed, even
|
|
9
|
+
// permutation, orientation sum divisible by 3, values in range), so the
|
|
10
|
+
// only meaningful rejection is the trivial set: solved + the 18
|
|
11
|
+
// single-move states (scrambles shorter than 2 moves). Coordinates are
|
|
12
|
+
// encoded once for both checks.
|
|
8
13
|
const state = createLegalRandomState(random);
|
|
9
|
-
if (!rejectTrivial
|
|
14
|
+
if (!rejectTrivial) {
|
|
15
|
+
return state;
|
|
16
|
+
}
|
|
17
|
+
const p = encodePermutation(state.corners.permutation);
|
|
18
|
+
const o = encodeOrientation(state.corners.orientation);
|
|
19
|
+
if (!isTrivialSamplingCube2State(p, o)) {
|
|
10
20
|
return state;
|
|
11
21
|
}
|
|
12
22
|
}
|
|
13
23
|
throw new Error("Unable to generate a non-trivial random 2x2 state.");
|
|
14
24
|
}
|
|
15
25
|
function createLegalRandomState(random) {
|
|
16
|
-
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
const orient7 = randomOrientations(CORNER_COUNT - 1, 3, random);
|
|
20
|
-
orient7.splice(6, 0, 0);
|
|
21
|
-
return {
|
|
22
|
-
corners: {
|
|
23
|
-
permutation: mappedPerm,
|
|
24
|
-
orientation: orient7
|
|
25
|
-
}
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
function shuffledRange(length, random) {
|
|
29
|
-
const values = Array.from({ length }, (_, index) => index);
|
|
30
|
-
for (let index = values.length - 1; index > 0; index -= 1) {
|
|
26
|
+
// 7 free corners (piece 6 = DBL stays fixed at position 6)
|
|
27
|
+
const perm7 = [0, 1, 2, 3, 4, 5, 6];
|
|
28
|
+
for (let index = 6; index > 0; index -= 1) {
|
|
31
29
|
const swapIndex = random.nextInt(index + 1);
|
|
32
|
-
|
|
30
|
+
if (swapIndex < index) {
|
|
31
|
+
const temporary = perm7[index];
|
|
32
|
+
perm7[index] = perm7[swapIndex];
|
|
33
|
+
perm7[swapIndex] = temporary;
|
|
34
|
+
}
|
|
33
35
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const
|
|
36
|
+
// Map the shuffle domain {0..6} to {0..5,7} and insert 6 at position 6
|
|
37
|
+
// (DBL fixed). Single allocation, no intermediate arrays.
|
|
38
|
+
const last = perm7[6] >= 6 ? perm7[6] + 1 : perm7[6];
|
|
39
|
+
const permutation = new Array(8);
|
|
40
|
+
for (let index = 0; index < 6; index += 1) {
|
|
41
|
+
const value = perm7[index];
|
|
42
|
+
permutation[index] = value >= 6 ? value + 1 : value;
|
|
43
|
+
}
|
|
44
|
+
permutation[6] = 6;
|
|
45
|
+
permutation[7] = last;
|
|
46
|
+
// 7 free orientations with sum divisible by 3; DBL stays oriented
|
|
47
|
+
const orientation = [0, 0, 0, 0, 0, 0, 0, 0];
|
|
38
48
|
let total = 0;
|
|
39
|
-
for (let index = 0; index <
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
total +=
|
|
49
|
+
for (let index = 0; index < 6; index += 1) {
|
|
50
|
+
const value = random.nextInt(3);
|
|
51
|
+
orientation[index] = value;
|
|
52
|
+
total += value;
|
|
43
53
|
}
|
|
44
|
-
|
|
45
|
-
return
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
54
|
+
orientation[7] = (3 - (total % 3)) % 3;
|
|
55
|
+
return {
|
|
56
|
+
corners: {
|
|
57
|
+
permutation,
|
|
58
|
+
orientation
|
|
59
|
+
}
|
|
60
|
+
};
|
|
51
61
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scrambler.d.ts","sourceRoot":"","sources":["../../../src/puzzles/cube2/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/cube2/scrambler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAiB9F,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,CAqDzB"}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import { formatCube2Algorithm
|
|
1
|
+
import { formatCube2Algorithm } from "./moves.js";
|
|
2
2
|
import { generateRandomCube2State } from "./random-state.js";
|
|
3
3
|
import { formatCube2Facelets, toCube2Facelets } from "./facelets.js";
|
|
4
|
-
import {
|
|
4
|
+
import { areCube2TablesLoaded, solveCube2StateIndices, solveCube2StateIndicesSync, verifyCube2SolutionIndices, BFS_MOVES } from "./solver/index.js";
|
|
5
|
+
// BFS_MOVES = [U, U2, U', R, R2, R', F, F2, F'] — the inverse of index m is
|
|
6
|
+
// CUBE2_MOVE_INVERSE[m] (U2/R2/F2 are self-inverse, so m ^ 1 does not apply).
|
|
7
|
+
const CUBE2_MOVE_INVERSE = [2, 1, 0, 5, 4, 3, 8, 7, 6];
|
|
5
8
|
export const cube2ScrambleGenerator = {
|
|
6
9
|
info: {
|
|
7
10
|
eventId: "222",
|
|
@@ -17,20 +20,31 @@ export const cube2ScrambleGenerator = {
|
|
|
17
20
|
};
|
|
18
21
|
export async function generateCube2Scramble(options = {}) {
|
|
19
22
|
const state = generateRandomCube2State(options);
|
|
20
|
-
const solution =
|
|
21
|
-
|
|
23
|
+
const solution = areCube2TablesLoaded()
|
|
24
|
+
? solveCube2StateIndicesSync(state)
|
|
25
|
+
: await solveCube2StateIndices(state);
|
|
26
|
+
const depth = solution.length;
|
|
27
|
+
// Scramble = inverse of the solution, reversed.
|
|
28
|
+
const scrambleMoves = new Array(depth);
|
|
29
|
+
for (let index = 0; index < depth; index += 1) {
|
|
30
|
+
scrambleMoves[index] = BFS_MOVES[CUBE2_MOVE_INVERSE[solution[depth - 1 - index]]];
|
|
31
|
+
}
|
|
22
32
|
if (!options.skipValidation) {
|
|
23
|
-
|
|
33
|
+
// The scramble is the exact inverse of the solution, so verifying that
|
|
34
|
+
// the solution solves the target state is equivalent to verifying that
|
|
35
|
+
// the scramble reproduces it — a single numeric walk suffices.
|
|
36
|
+
if (!verifyCube2SolutionIndices(state, solution)) {
|
|
24
37
|
throw new Error("Generated 2x2 scramble does not reproduce the target random state.");
|
|
25
38
|
}
|
|
26
|
-
if (!verifyCube2Solution(state, solution.moves)) {
|
|
27
|
-
throw new Error("Generated 2x2 solution does not solve the target random state.");
|
|
28
|
-
}
|
|
29
39
|
if (scrambleMoves.length < 2) {
|
|
30
40
|
throw new Error("Generated 2x2 scramble is too short.");
|
|
31
41
|
}
|
|
32
42
|
}
|
|
33
43
|
const facelets = toCube2Facelets(state);
|
|
44
|
+
const solutionMoves = new Array(depth);
|
|
45
|
+
for (let index = 0; index < depth; index += 1) {
|
|
46
|
+
solutionMoves[index] = BFS_MOVES[solution[index]];
|
|
47
|
+
}
|
|
34
48
|
const result = {
|
|
35
49
|
eventId: "222",
|
|
36
50
|
scramble: formatCube2Algorithm(scrambleMoves),
|
|
@@ -41,8 +55,8 @@ export async function generateCube2Scramble(options = {}) {
|
|
|
41
55
|
state,
|
|
42
56
|
facelets,
|
|
43
57
|
faceletString: formatCube2Facelets(facelets),
|
|
44
|
-
solutionMoves
|
|
45
|
-
solutionDepth:
|
|
58
|
+
solutionMoves,
|
|
59
|
+
solutionDepth: depth
|
|
46
60
|
}
|
|
47
61
|
};
|
|
48
62
|
if (options.seed !== undefined) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
export { solveCube2State, verifyCube2Scramble, verifyCube2Solution } from "./search.js";
|
|
1
|
+
export { solveCube2State, solveCube2StateIndices, solveCube2StateIndicesSync, verifyCube2Scramble, verifyCube2Solution, verifyCube2SolutionIndices } from "./search.js";
|
|
2
2
|
export type { Cube2Solution } from "./search.js";
|
|
3
|
+
export { areCube2TablesLoaded, BFS_MOVES, DISTANCES, ensureTablesLoaded, encodePermutation, encodeOrientation, getDistance, getPermutationTransition, getOrientationTransition, MOVE_COUNT, ORIENTATION_COUNT, ORIENTATION_TABLE, PERMUTATION_TABLE } from "./tables.js";
|
|
3
4
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube2/solver/index.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube2/solver/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,sBAAsB,EACtB,0BAA0B,EAC1B,mBAAmB,EACnB,mBAAmB,EACnB,0BAA0B,EAC3B,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EACL,oBAAoB,EACpB,SAAS,EACT,SAAS,EACT,kBAAkB,EAClB,iBAAiB,EACjB,iBAAiB,EACjB,WAAW,EACX,wBAAwB,EACxB,wBAAwB,EACxB,UAAU,EACV,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,EAClB,MAAM,aAAa,CAAC"}
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
export { solveCube2State, verifyCube2Scramble, verifyCube2Solution } from "./search.js";
|
|
1
|
+
export { solveCube2State, solveCube2StateIndices, solveCube2StateIndicesSync, verifyCube2Scramble, verifyCube2Solution, verifyCube2SolutionIndices } from "./search.js";
|
|
2
|
+
export { areCube2TablesLoaded, BFS_MOVES, DISTANCES, ensureTablesLoaded, encodePermutation, encodeOrientation, getDistance, getPermutationTransition, getOrientationTransition, MOVE_COUNT, ORIENTATION_COUNT, ORIENTATION_TABLE, PERMUTATION_TABLE } from "./tables.js";
|
|
@@ -5,6 +5,9 @@ export interface Cube2Solution {
|
|
|
5
5
|
state: Cube2State;
|
|
6
6
|
}
|
|
7
7
|
export declare function solveCube2State(state: Cube2State): Promise<Cube2Solution>;
|
|
8
|
+
export declare function solveCube2StateIndices(state: Cube2State): Promise<number[]>;
|
|
9
|
+
export declare function solveCube2StateIndicesSync(state: Cube2State): number[];
|
|
8
10
|
export declare function verifyCube2Scramble(state: Cube2State, scrambleMoves: readonly Cube2Move[]): boolean;
|
|
9
11
|
export declare function verifyCube2Solution(state: Cube2State, solutionMoves: readonly Cube2Move[]): boolean;
|
|
12
|
+
export declare function verifyCube2SolutionIndices(state: Cube2State, solution: readonly number[]): boolean;
|
|
10
13
|
//# sourceMappingURL=search.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube2/solver/search.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAczD,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,UAAU,CAAC;CACnB;AAED,wBAAsB,eAAe,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,aAAa,CAAC,
|
|
1
|
+
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube2/solver/search.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAczD,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,UAAU,CAAC;CACnB;AAED,wBAAsB,eAAe,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,aAAa,CAAC,CAQ/E;AAKD,wBAAsB,sBAAsB,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAGjF;AAED,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,EAAE,CAKtE;AA6ED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,SAAS,SAAS,EAAE,GAAG,OAAO,CAUnG;AAGD,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,SAAS,SAAS,EAAE,GAAG,OAAO,CAOnG;AAMD,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAclG"}
|
|
@@ -2,6 +2,27 @@ import { createSolvedCube2State } from "../state.js";
|
|
|
2
2
|
import { encodeOrientation, encodePermutation, ensureTablesLoaded, PERMUTATION_TABLE, ORIENTATION_TABLE, DISTANCES, ORIENTATION_COUNT, BFS_MOVES, MOVE_COUNT } from "./tables.js";
|
|
3
3
|
export async function solveCube2State(state) {
|
|
4
4
|
await ensureTablesLoaded();
|
|
5
|
+
const indices = solveWithTablesIndices(state);
|
|
6
|
+
return {
|
|
7
|
+
moves: indices.map((index) => BFS_MOVES[index]),
|
|
8
|
+
depth: indices.length,
|
|
9
|
+
state: createSolvedCube2State()
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
// Index-based solve for the hot scramble path: returns the solution as move
|
|
13
|
+
// indices (0..8) in walk order. The inverse of index m is CUBE2_MOVE_INVERSE[m]
|
|
14
|
+
// (U2/R2/F2 are self-inverse, so m ^ 1 does not apply here).
|
|
15
|
+
export async function solveCube2StateIndices(state) {
|
|
16
|
+
await ensureTablesLoaded();
|
|
17
|
+
return solveWithTablesIndices(state);
|
|
18
|
+
}
|
|
19
|
+
export function solveCube2StateIndicesSync(state) {
|
|
20
|
+
if (!DISTANCES) {
|
|
21
|
+
throw new Error("Cube2 solver tables not loaded. Call ensureTablesLoaded() first.");
|
|
22
|
+
}
|
|
23
|
+
return solveWithTablesIndices(state);
|
|
24
|
+
}
|
|
25
|
+
function solveWithTablesIndices(state) {
|
|
5
26
|
if (state.corners.permutation[6] !== 6 || state.corners.orientation[6] !== 0) {
|
|
6
27
|
throw new Error("The 2x2 solver requires the DBL corner (index 6) to be solved " +
|
|
7
28
|
"as the frame of reference. Please normalize the state before solving.");
|
|
@@ -16,7 +37,7 @@ export async function solveCube2State(state) {
|
|
|
16
37
|
const initialCombined = permIndex * ORIENTATION_COUNT + orientIndex;
|
|
17
38
|
let distance = (distances[initialCombined >> 1] >> ((initialCombined & 1) << 2)) & 0x0f;
|
|
18
39
|
if (distance === 0) {
|
|
19
|
-
return
|
|
40
|
+
return moves;
|
|
20
41
|
}
|
|
21
42
|
while (distance > 0) {
|
|
22
43
|
let foundNext = false;
|
|
@@ -30,7 +51,7 @@ export async function solveCube2State(state) {
|
|
|
30
51
|
const combined = nextPerm * ORIENTATION_COUNT + nextOrient;
|
|
31
52
|
const nextDist = (distances[combined >> 1] >> ((combined & 1) << 2)) & 0x0f;
|
|
32
53
|
if (nextDist === nextDistance) {
|
|
33
|
-
moves.push(
|
|
54
|
+
moves.push(m);
|
|
34
55
|
permIndex = nextPerm;
|
|
35
56
|
orientIndex = nextOrient;
|
|
36
57
|
distance = nextDistance;
|
|
@@ -42,11 +63,7 @@ export async function solveCube2State(state) {
|
|
|
42
63
|
throw new Error("BFS distance table corrupted or unreachable state.");
|
|
43
64
|
}
|
|
44
65
|
}
|
|
45
|
-
return
|
|
46
|
-
moves,
|
|
47
|
-
depth: moves.length,
|
|
48
|
-
state: createSolvedCube2State()
|
|
49
|
-
};
|
|
66
|
+
return moves;
|
|
50
67
|
}
|
|
51
68
|
const MOVE_INDEX = {};
|
|
52
69
|
for (let i = 0; i < BFS_MOVES.length; i++) {
|
|
@@ -74,3 +91,21 @@ export function verifyCube2Solution(state, solutionMoves) {
|
|
|
74
91
|
const { permIndex, orientIndex } = walkCube2Coordinates(encodePermutation(state.corners.permutation), encodeOrientation(state.corners.orientation), solutionMoves);
|
|
75
92
|
return permIndex === 0 && orientIndex === 0;
|
|
76
93
|
}
|
|
94
|
+
// Numeric verification for the hot scramble path: walks the solution (move
|
|
95
|
+
// indices, walk order) from the target state to solved. The scramble is the
|
|
96
|
+
// exact inverse of the solution, so this is equivalent to verifying that the
|
|
97
|
+
// scramble reproduces the target state — one walk, no string parsing.
|
|
98
|
+
export function verifyCube2SolutionIndices(state, solution) {
|
|
99
|
+
const permTable = PERMUTATION_TABLE;
|
|
100
|
+
const orientTable = ORIENTATION_TABLE;
|
|
101
|
+
if (!permTable || !orientTable) {
|
|
102
|
+
throw new Error("Cube2 solver tables not loaded. Call ensureTablesLoaded() first.");
|
|
103
|
+
}
|
|
104
|
+
let permIndex = encodePermutation(state.corners.permutation);
|
|
105
|
+
let orientIndex = encodeOrientation(state.corners.orientation);
|
|
106
|
+
for (const m of solution) {
|
|
107
|
+
permIndex = permTable[permIndex * MOVE_COUNT + m];
|
|
108
|
+
orientIndex = orientTable[orientIndex * MOVE_COUNT + m];
|
|
109
|
+
}
|
|
110
|
+
return permIndex === 0 && orientIndex === 0;
|
|
111
|
+
}
|
|
@@ -7,6 +7,7 @@ export declare let PERMUTATION_TABLE: Uint16Array | null;
|
|
|
7
7
|
export declare let ORIENTATION_TABLE: Uint16Array | null;
|
|
8
8
|
export declare let DISTANCES: Uint8Array | null;
|
|
9
9
|
export declare function ensureTablesLoaded(): Promise<void>;
|
|
10
|
+
export declare function areCube2TablesLoaded(): boolean;
|
|
10
11
|
export declare function getDistance(permIndex: number, orientIndex: number): number;
|
|
11
12
|
export declare function getPermutationTransition(move: number, permIndex: number): number;
|
|
12
13
|
export declare function getOrientationTransition(move: number, orientIndex: number): number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tables.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube2/solver/tables.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,iBAAiB,OAAO,CAAC;AACtC,eAAO,MAAM,iBAAiB,MAAM,CAAC;AACrC,eAAO,MAAM,UAAU,IAAI,CAAC;AAM5B,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAexE;AAED,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAWxE;AAeD,eAAO,IAAI,iBAAiB,EAAE,WAAW,GAAG,IAAW,CAAC;AACxD,eAAO,IAAI,iBAAiB,EAAE,WAAW,GAAG,IAAW,CAAC;AACxD,eAAO,IAAI,SAAS,EAAE,UAAU,GAAG,IAAW,CAAC;AAE/C,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC,CAUxD;AAMD,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAO1E;AAED,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAKhF;AAED,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAKlF;AAED,eAAO,MAAM,SAAS,EAAE,SAAS,MAAM,EAItC,CAAC"}
|
|
1
|
+
{"version":3,"file":"tables.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube2/solver/tables.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,iBAAiB,OAAO,CAAC;AACtC,eAAO,MAAM,iBAAiB,MAAM,CAAC;AACrC,eAAO,MAAM,UAAU,IAAI,CAAC;AAM5B,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAexE;AAED,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAWxE;AAeD,eAAO,IAAI,iBAAiB,EAAE,WAAW,GAAG,IAAW,CAAC;AACxD,eAAO,IAAI,iBAAiB,EAAE,WAAW,GAAG,IAAW,CAAC;AACxD,eAAO,IAAI,SAAS,EAAE,UAAU,GAAG,IAAW,CAAC;AAE/C,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC,CAUxD;AAED,wBAAgB,oBAAoB,IAAI,OAAO,CAE9C;AAMD,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAO1E;AAED,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAKhF;AAED,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAKlF;AAED,eAAO,MAAM,SAAS,EAAE,SAAS,MAAM,EAItC,CAAC"}
|
|
@@ -52,6 +52,9 @@ export async function ensureTablesLoaded() {
|
|
|
52
52
|
DISTANCES = tables.distances;
|
|
53
53
|
tablesLoaded = true;
|
|
54
54
|
}
|
|
55
|
+
export function areCube2TablesLoaded() {
|
|
56
|
+
return tablesLoaded;
|
|
57
|
+
}
|
|
55
58
|
// ---------------------------------------------------------------------------
|
|
56
59
|
// Public API
|
|
57
60
|
// ---------------------------------------------------------------------------
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Cube2State, Cube2ValidationResult } from "./types.js";
|
|
2
2
|
export declare function validateCube2State(state: Cube2State): Cube2ValidationResult;
|
|
3
3
|
export declare function isOneMoveFromSolvedCube2State(state: Cube2State): boolean;
|
|
4
|
+
export declare function isTrivialSamplingCube2State(p: number, o: number): boolean;
|
|
4
5
|
export declare function isAllowedRandomCube2State(state: Cube2State): boolean;
|
|
5
6
|
export declare function permutationParity(permutation: readonly number[]): 0 | 1;
|
|
6
7
|
//# sourceMappingURL=validation.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../../src/puzzles/cube2/validation.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAgBpE,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,UAAU,GAAG,qBAAqB,CAkB3E;AAED,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAIxE;AAED,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAIpE;AAED,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC,CAYvE"}
|
|
1
|
+
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../../src/puzzles/cube2/validation.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAgBpE,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,UAAU,GAAG,qBAAqB,CAkB3E;AAED,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAIxE;AAKD,wBAAgB,2BAA2B,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAEzE;AAED,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAIpE;AAED,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC,CAYvE"}
|
|
@@ -31,7 +31,13 @@ export function validateCube2State(state) {
|
|
|
31
31
|
export function isOneMoveFromSolvedCube2State(state) {
|
|
32
32
|
const p = encodePermutation(state.corners.permutation);
|
|
33
33
|
const o = encodeOrientation(state.corners.orientation);
|
|
34
|
-
return
|
|
34
|
+
return isTrivialSamplingCube2State(p, o);
|
|
35
|
+
}
|
|
36
|
+
// Cheap trivial check for random-state sampling: the caller has already
|
|
37
|
+
// encoded the coordinates once. True for the solved state and the 18
|
|
38
|
+
// single-move states (which would produce scrambles shorter than 2 moves).
|
|
39
|
+
export function isTrivialSamplingCube2State(p, o) {
|
|
40
|
+
return (p === 0 && o === 0) || ONE_MOVE_FROM_SOLVED_ENCODED.some((entry) => entry.p === p && entry.o === o);
|
|
35
41
|
}
|
|
36
42
|
export function isAllowedRandomCube2State(state) {
|
|
37
43
|
return validateCube2State(state).valid &&
|
|
@@ -4,6 +4,8 @@ exports.toCube2Facelets = toCube2Facelets;
|
|
|
4
4
|
exports.formatCube2Facelets = formatCube2Facelets;
|
|
5
5
|
exports.getCube2FaceOrder = getCube2FaceOrder;
|
|
6
6
|
const FACE_ORDER = ["U", "R", "F", "D", "L", "B"];
|
|
7
|
+
const STICKERS = ["U", "R", "F", "D", "L", "B"];
|
|
8
|
+
const STICKER_INDEX = { U: 0, R: 1, F: 2, D: 3, L: 4, B: 5 };
|
|
7
9
|
const CORNER_PIECE_STICKERS = [
|
|
8
10
|
["U", "R", "F"],
|
|
9
11
|
["U", "F", "L"],
|
|
@@ -24,12 +26,48 @@ const CORNER_POSITION_SLOTS = [
|
|
|
24
26
|
[["D", 2], ["B", 3], ["L", 2]],
|
|
25
27
|
[["D", 3], ["R", 3], ["B", 2]]
|
|
26
28
|
];
|
|
27
|
-
|
|
28
|
-
|
|
29
|
+
const CORNER_CELLS = buildCells();
|
|
30
|
+
function buildCells() {
|
|
31
|
+
const cells = [];
|
|
29
32
|
for (let position = 0; position < CORNER_POSITION_SLOTS.length; position += 1) {
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
+
const byPiece = [];
|
|
34
|
+
for (let piece = 0; piece < CORNER_PIECE_STICKERS.length; piece += 1) {
|
|
35
|
+
const byOrientation = [];
|
|
36
|
+
for (let orientation = 0; orientation < 3; orientation += 1) {
|
|
37
|
+
const stickers = CORNER_PIECE_STICKERS[piece];
|
|
38
|
+
const slotList = CORNER_POSITION_SLOTS[position];
|
|
39
|
+
const cell = [];
|
|
40
|
+
for (let index = 0; index < slotList.length; index += 1) {
|
|
41
|
+
const [face, stickerIndex] = slotList[index];
|
|
42
|
+
cell.push(FACE_ORDER.indexOf(face));
|
|
43
|
+
cell.push(stickerIndex);
|
|
44
|
+
cell.push(STICKER_INDEX[stickers[(index - orientation + stickers.length) % stickers.length]]);
|
|
45
|
+
}
|
|
46
|
+
byOrientation.push(cell);
|
|
47
|
+
}
|
|
48
|
+
byPiece.push(byOrientation);
|
|
49
|
+
}
|
|
50
|
+
cells.push(byPiece);
|
|
51
|
+
}
|
|
52
|
+
return cells;
|
|
53
|
+
}
|
|
54
|
+
function toCube2Facelets(state) {
|
|
55
|
+
const facelets = {
|
|
56
|
+
U: Array(4),
|
|
57
|
+
R: Array(4),
|
|
58
|
+
F: Array(4),
|
|
59
|
+
D: Array(4),
|
|
60
|
+
L: Array(4),
|
|
61
|
+
B: Array(4)
|
|
62
|
+
};
|
|
63
|
+
const faces = [facelets.U, facelets.R, facelets.F, facelets.D, facelets.L, facelets.B];
|
|
64
|
+
const permutation = state.corners.permutation;
|
|
65
|
+
const orientation = state.corners.orientation;
|
|
66
|
+
for (let position = 0; position < CORNER_CELLS.length; position += 1) {
|
|
67
|
+
const cells = CORNER_CELLS[position][permutation[position]][orientation[position]];
|
|
68
|
+
for (let cellIndex = 0; cellIndex < cells.length; cellIndex += 3) {
|
|
69
|
+
faces[cells[cellIndex]][cells[cellIndex + 1]] = STICKERS[cells[cellIndex + 2]];
|
|
70
|
+
}
|
|
33
71
|
}
|
|
34
72
|
return facelets;
|
|
35
73
|
}
|
|
@@ -39,20 +77,3 @@ function formatCube2Facelets(facelets) {
|
|
|
39
77
|
function getCube2FaceOrder() {
|
|
40
78
|
return FACE_ORDER;
|
|
41
79
|
}
|
|
42
|
-
function createEmptyFacelets() {
|
|
43
|
-
return {
|
|
44
|
-
U: Array(4).fill("U"),
|
|
45
|
-
R: Array(4).fill("R"),
|
|
46
|
-
F: Array(4).fill("F"),
|
|
47
|
-
D: Array(4).fill("D"),
|
|
48
|
-
L: Array(4).fill("L"),
|
|
49
|
-
B: Array(4).fill("B")
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
function placeOrientedStickers(facelets, slots, stickers, orientation) {
|
|
53
|
-
for (let index = 0; index < slots.length; index += 1) {
|
|
54
|
-
const [face, stickerIndex] = slots[index];
|
|
55
|
-
const sticker = stickers[(index - orientation + stickers.length) % stickers.length];
|
|
56
|
-
facelets[face][stickerIndex] = sticker;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
@@ -2,53 +2,63 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.generateRandomCube2State = generateRandomCube2State;
|
|
4
4
|
const index_js_1 = require("../../random/index.js");
|
|
5
|
-
const types_js_1 = require("./types.js");
|
|
6
5
|
const validation_js_1 = require("./validation.js");
|
|
6
|
+
const tables_js_1 = require("./solver/tables.js");
|
|
7
7
|
function generateRandomCube2State(options = {}) {
|
|
8
8
|
const random = (0, index_js_1.createRandomSource)(options.seed);
|
|
9
9
|
const rejectTrivial = options.rejectTrivial ?? true;
|
|
10
10
|
for (let attempt = 0; attempt < 100; attempt += 1) {
|
|
11
|
+
// The state is legal by construction (DBL corner fixed, even
|
|
12
|
+
// permutation, orientation sum divisible by 3, values in range), so the
|
|
13
|
+
// only meaningful rejection is the trivial set: solved + the 18
|
|
14
|
+
// single-move states (scrambles shorter than 2 moves). Coordinates are
|
|
15
|
+
// encoded once for both checks.
|
|
11
16
|
const state = createLegalRandomState(random);
|
|
12
|
-
if (!rejectTrivial
|
|
17
|
+
if (!rejectTrivial) {
|
|
18
|
+
return state;
|
|
19
|
+
}
|
|
20
|
+
const p = (0, tables_js_1.encodePermutation)(state.corners.permutation);
|
|
21
|
+
const o = (0, tables_js_1.encodeOrientation)(state.corners.orientation);
|
|
22
|
+
if (!(0, validation_js_1.isTrivialSamplingCube2State)(p, o)) {
|
|
13
23
|
return state;
|
|
14
24
|
}
|
|
15
25
|
}
|
|
16
26
|
throw new Error("Unable to generate a non-trivial random 2x2 state.");
|
|
17
27
|
}
|
|
18
28
|
function createLegalRandomState(random) {
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
const orient7 = randomOrientations(types_js_1.CORNER_COUNT - 1, 3, random);
|
|
23
|
-
orient7.splice(6, 0, 0);
|
|
24
|
-
return {
|
|
25
|
-
corners: {
|
|
26
|
-
permutation: mappedPerm,
|
|
27
|
-
orientation: orient7
|
|
28
|
-
}
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
function shuffledRange(length, random) {
|
|
32
|
-
const values = Array.from({ length }, (_, index) => index);
|
|
33
|
-
for (let index = values.length - 1; index > 0; index -= 1) {
|
|
29
|
+
// 7 free corners (piece 6 = DBL stays fixed at position 6)
|
|
30
|
+
const perm7 = [0, 1, 2, 3, 4, 5, 6];
|
|
31
|
+
for (let index = 6; index > 0; index -= 1) {
|
|
34
32
|
const swapIndex = random.nextInt(index + 1);
|
|
35
|
-
|
|
33
|
+
if (swapIndex < index) {
|
|
34
|
+
const temporary = perm7[index];
|
|
35
|
+
perm7[index] = perm7[swapIndex];
|
|
36
|
+
perm7[swapIndex] = temporary;
|
|
37
|
+
}
|
|
36
38
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const
|
|
39
|
+
// Map the shuffle domain {0..6} to {0..5,7} and insert 6 at position 6
|
|
40
|
+
// (DBL fixed). Single allocation, no intermediate arrays.
|
|
41
|
+
const last = perm7[6] >= 6 ? perm7[6] + 1 : perm7[6];
|
|
42
|
+
const permutation = new Array(8);
|
|
43
|
+
for (let index = 0; index < 6; index += 1) {
|
|
44
|
+
const value = perm7[index];
|
|
45
|
+
permutation[index] = value >= 6 ? value + 1 : value;
|
|
46
|
+
}
|
|
47
|
+
permutation[6] = 6;
|
|
48
|
+
permutation[7] = last;
|
|
49
|
+
// 7 free orientations with sum divisible by 3; DBL stays oriented
|
|
50
|
+
const orientation = [0, 0, 0, 0, 0, 0, 0, 0];
|
|
41
51
|
let total = 0;
|
|
42
|
-
for (let index = 0; index <
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
total +=
|
|
52
|
+
for (let index = 0; index < 6; index += 1) {
|
|
53
|
+
const value = random.nextInt(3);
|
|
54
|
+
orientation[index] = value;
|
|
55
|
+
total += value;
|
|
46
56
|
}
|
|
47
|
-
|
|
48
|
-
return
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
57
|
+
orientation[7] = (3 - (total % 3)) % 3;
|
|
58
|
+
return {
|
|
59
|
+
corners: {
|
|
60
|
+
permutation,
|
|
61
|
+
orientation
|
|
62
|
+
}
|
|
63
|
+
};
|
|
54
64
|
}
|
|
@@ -6,6 +6,9 @@ const moves_js_1 = require("./moves.js");
|
|
|
6
6
|
const random_state_js_1 = require("./random-state.js");
|
|
7
7
|
const facelets_js_1 = require("./facelets.js");
|
|
8
8
|
const index_js_1 = require("./solver/index.js");
|
|
9
|
+
// BFS_MOVES = [U, U2, U', R, R2, R', F, F2, F'] — the inverse of index m is
|
|
10
|
+
// CUBE2_MOVE_INVERSE[m] (U2/R2/F2 are self-inverse, so m ^ 1 does not apply).
|
|
11
|
+
const CUBE2_MOVE_INVERSE = [2, 1, 0, 5, 4, 3, 8, 7, 6];
|
|
9
12
|
exports.cube2ScrambleGenerator = {
|
|
10
13
|
info: {
|
|
11
14
|
eventId: "222",
|
|
@@ -21,20 +24,31 @@ exports.cube2ScrambleGenerator = {
|
|
|
21
24
|
};
|
|
22
25
|
async function generateCube2Scramble(options = {}) {
|
|
23
26
|
const state = (0, random_state_js_1.generateRandomCube2State)(options);
|
|
24
|
-
const solution =
|
|
25
|
-
|
|
27
|
+
const solution = (0, index_js_1.areCube2TablesLoaded)()
|
|
28
|
+
? (0, index_js_1.solveCube2StateIndicesSync)(state)
|
|
29
|
+
: await (0, index_js_1.solveCube2StateIndices)(state);
|
|
30
|
+
const depth = solution.length;
|
|
31
|
+
// Scramble = inverse of the solution, reversed.
|
|
32
|
+
const scrambleMoves = new Array(depth);
|
|
33
|
+
for (let index = 0; index < depth; index += 1) {
|
|
34
|
+
scrambleMoves[index] = index_js_1.BFS_MOVES[CUBE2_MOVE_INVERSE[solution[depth - 1 - index]]];
|
|
35
|
+
}
|
|
26
36
|
if (!options.skipValidation) {
|
|
27
|
-
|
|
37
|
+
// The scramble is the exact inverse of the solution, so verifying that
|
|
38
|
+
// the solution solves the target state is equivalent to verifying that
|
|
39
|
+
// the scramble reproduces it — a single numeric walk suffices.
|
|
40
|
+
if (!(0, index_js_1.verifyCube2SolutionIndices)(state, solution)) {
|
|
28
41
|
throw new Error("Generated 2x2 scramble does not reproduce the target random state.");
|
|
29
42
|
}
|
|
30
|
-
if (!(0, index_js_1.verifyCube2Solution)(state, solution.moves)) {
|
|
31
|
-
throw new Error("Generated 2x2 solution does not solve the target random state.");
|
|
32
|
-
}
|
|
33
43
|
if (scrambleMoves.length < 2) {
|
|
34
44
|
throw new Error("Generated 2x2 scramble is too short.");
|
|
35
45
|
}
|
|
36
46
|
}
|
|
37
47
|
const facelets = (0, facelets_js_1.toCube2Facelets)(state);
|
|
48
|
+
const solutionMoves = new Array(depth);
|
|
49
|
+
for (let index = 0; index < depth; index += 1) {
|
|
50
|
+
solutionMoves[index] = index_js_1.BFS_MOVES[solution[index]];
|
|
51
|
+
}
|
|
38
52
|
const result = {
|
|
39
53
|
eventId: "222",
|
|
40
54
|
scramble: (0, moves_js_1.formatCube2Algorithm)(scrambleMoves),
|
|
@@ -45,8 +59,8 @@ async function generateCube2Scramble(options = {}) {
|
|
|
45
59
|
state,
|
|
46
60
|
facelets,
|
|
47
61
|
faceletString: (0, facelets_js_1.formatCube2Facelets)(facelets),
|
|
48
|
-
solutionMoves
|
|
49
|
-
solutionDepth:
|
|
62
|
+
solutionMoves,
|
|
63
|
+
solutionDepth: depth
|
|
50
64
|
}
|
|
51
65
|
};
|
|
52
66
|
if (options.seed !== undefined) {
|
|
@@ -1,7 +1,24 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.verifyCube2Solution = exports.verifyCube2Scramble = exports.solveCube2State = void 0;
|
|
3
|
+
exports.PERMUTATION_TABLE = exports.ORIENTATION_TABLE = exports.ORIENTATION_COUNT = exports.MOVE_COUNT = exports.getOrientationTransition = exports.getPermutationTransition = exports.getDistance = exports.encodeOrientation = exports.encodePermutation = exports.ensureTablesLoaded = exports.DISTANCES = exports.BFS_MOVES = exports.areCube2TablesLoaded = exports.verifyCube2SolutionIndices = exports.verifyCube2Solution = exports.verifyCube2Scramble = exports.solveCube2StateIndicesSync = exports.solveCube2StateIndices = exports.solveCube2State = void 0;
|
|
4
4
|
var search_js_1 = require("./search.js");
|
|
5
5
|
Object.defineProperty(exports, "solveCube2State", { enumerable: true, get: function () { return search_js_1.solveCube2State; } });
|
|
6
|
+
Object.defineProperty(exports, "solveCube2StateIndices", { enumerable: true, get: function () { return search_js_1.solveCube2StateIndices; } });
|
|
7
|
+
Object.defineProperty(exports, "solveCube2StateIndicesSync", { enumerable: true, get: function () { return search_js_1.solveCube2StateIndicesSync; } });
|
|
6
8
|
Object.defineProperty(exports, "verifyCube2Scramble", { enumerable: true, get: function () { return search_js_1.verifyCube2Scramble; } });
|
|
7
9
|
Object.defineProperty(exports, "verifyCube2Solution", { enumerable: true, get: function () { return search_js_1.verifyCube2Solution; } });
|
|
10
|
+
Object.defineProperty(exports, "verifyCube2SolutionIndices", { enumerable: true, get: function () { return search_js_1.verifyCube2SolutionIndices; } });
|
|
11
|
+
var tables_js_1 = require("./tables.js");
|
|
12
|
+
Object.defineProperty(exports, "areCube2TablesLoaded", { enumerable: true, get: function () { return tables_js_1.areCube2TablesLoaded; } });
|
|
13
|
+
Object.defineProperty(exports, "BFS_MOVES", { enumerable: true, get: function () { return tables_js_1.BFS_MOVES; } });
|
|
14
|
+
Object.defineProperty(exports, "DISTANCES", { enumerable: true, get: function () { return tables_js_1.DISTANCES; } });
|
|
15
|
+
Object.defineProperty(exports, "ensureTablesLoaded", { enumerable: true, get: function () { return tables_js_1.ensureTablesLoaded; } });
|
|
16
|
+
Object.defineProperty(exports, "encodePermutation", { enumerable: true, get: function () { return tables_js_1.encodePermutation; } });
|
|
17
|
+
Object.defineProperty(exports, "encodeOrientation", { enumerable: true, get: function () { return tables_js_1.encodeOrientation; } });
|
|
18
|
+
Object.defineProperty(exports, "getDistance", { enumerable: true, get: function () { return tables_js_1.getDistance; } });
|
|
19
|
+
Object.defineProperty(exports, "getPermutationTransition", { enumerable: true, get: function () { return tables_js_1.getPermutationTransition; } });
|
|
20
|
+
Object.defineProperty(exports, "getOrientationTransition", { enumerable: true, get: function () { return tables_js_1.getOrientationTransition; } });
|
|
21
|
+
Object.defineProperty(exports, "MOVE_COUNT", { enumerable: true, get: function () { return tables_js_1.MOVE_COUNT; } });
|
|
22
|
+
Object.defineProperty(exports, "ORIENTATION_COUNT", { enumerable: true, get: function () { return tables_js_1.ORIENTATION_COUNT; } });
|
|
23
|
+
Object.defineProperty(exports, "ORIENTATION_TABLE", { enumerable: true, get: function () { return tables_js_1.ORIENTATION_TABLE; } });
|
|
24
|
+
Object.defineProperty(exports, "PERMUTATION_TABLE", { enumerable: true, get: function () { return tables_js_1.PERMUTATION_TABLE; } });
|
|
@@ -1,12 +1,36 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.solveCube2State = solveCube2State;
|
|
4
|
+
exports.solveCube2StateIndices = solveCube2StateIndices;
|
|
5
|
+
exports.solveCube2StateIndicesSync = solveCube2StateIndicesSync;
|
|
4
6
|
exports.verifyCube2Scramble = verifyCube2Scramble;
|
|
5
7
|
exports.verifyCube2Solution = verifyCube2Solution;
|
|
8
|
+
exports.verifyCube2SolutionIndices = verifyCube2SolutionIndices;
|
|
6
9
|
const state_js_1 = require("../state.js");
|
|
7
10
|
const tables_js_1 = require("./tables.js");
|
|
8
11
|
async function solveCube2State(state) {
|
|
9
12
|
await (0, tables_js_1.ensureTablesLoaded)();
|
|
13
|
+
const indices = solveWithTablesIndices(state);
|
|
14
|
+
return {
|
|
15
|
+
moves: indices.map((index) => tables_js_1.BFS_MOVES[index]),
|
|
16
|
+
depth: indices.length,
|
|
17
|
+
state: (0, state_js_1.createSolvedCube2State)()
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
// Index-based solve for the hot scramble path: returns the solution as move
|
|
21
|
+
// indices (0..8) in walk order. The inverse of index m is CUBE2_MOVE_INVERSE[m]
|
|
22
|
+
// (U2/R2/F2 are self-inverse, so m ^ 1 does not apply here).
|
|
23
|
+
async function solveCube2StateIndices(state) {
|
|
24
|
+
await (0, tables_js_1.ensureTablesLoaded)();
|
|
25
|
+
return solveWithTablesIndices(state);
|
|
26
|
+
}
|
|
27
|
+
function solveCube2StateIndicesSync(state) {
|
|
28
|
+
if (!tables_js_1.DISTANCES) {
|
|
29
|
+
throw new Error("Cube2 solver tables not loaded. Call ensureTablesLoaded() first.");
|
|
30
|
+
}
|
|
31
|
+
return solveWithTablesIndices(state);
|
|
32
|
+
}
|
|
33
|
+
function solveWithTablesIndices(state) {
|
|
10
34
|
if (state.corners.permutation[6] !== 6 || state.corners.orientation[6] !== 0) {
|
|
11
35
|
throw new Error("The 2x2 solver requires the DBL corner (index 6) to be solved " +
|
|
12
36
|
"as the frame of reference. Please normalize the state before solving.");
|
|
@@ -21,7 +45,7 @@ async function solveCube2State(state) {
|
|
|
21
45
|
const initialCombined = permIndex * tables_js_1.ORIENTATION_COUNT + orientIndex;
|
|
22
46
|
let distance = (distances[initialCombined >> 1] >> ((initialCombined & 1) << 2)) & 0x0f;
|
|
23
47
|
if (distance === 0) {
|
|
24
|
-
return
|
|
48
|
+
return moves;
|
|
25
49
|
}
|
|
26
50
|
while (distance > 0) {
|
|
27
51
|
let foundNext = false;
|
|
@@ -35,7 +59,7 @@ async function solveCube2State(state) {
|
|
|
35
59
|
const combined = nextPerm * tables_js_1.ORIENTATION_COUNT + nextOrient;
|
|
36
60
|
const nextDist = (distances[combined >> 1] >> ((combined & 1) << 2)) & 0x0f;
|
|
37
61
|
if (nextDist === nextDistance) {
|
|
38
|
-
moves.push(
|
|
62
|
+
moves.push(m);
|
|
39
63
|
permIndex = nextPerm;
|
|
40
64
|
orientIndex = nextOrient;
|
|
41
65
|
distance = nextDistance;
|
|
@@ -47,11 +71,7 @@ async function solveCube2State(state) {
|
|
|
47
71
|
throw new Error("BFS distance table corrupted or unreachable state.");
|
|
48
72
|
}
|
|
49
73
|
}
|
|
50
|
-
return
|
|
51
|
-
moves,
|
|
52
|
-
depth: moves.length,
|
|
53
|
-
state: (0, state_js_1.createSolvedCube2State)()
|
|
54
|
-
};
|
|
74
|
+
return moves;
|
|
55
75
|
}
|
|
56
76
|
const MOVE_INDEX = {};
|
|
57
77
|
for (let i = 0; i < tables_js_1.BFS_MOVES.length; i++) {
|
|
@@ -79,3 +99,21 @@ function verifyCube2Solution(state, solutionMoves) {
|
|
|
79
99
|
const { permIndex, orientIndex } = walkCube2Coordinates((0, tables_js_1.encodePermutation)(state.corners.permutation), (0, tables_js_1.encodeOrientation)(state.corners.orientation), solutionMoves);
|
|
80
100
|
return permIndex === 0 && orientIndex === 0;
|
|
81
101
|
}
|
|
102
|
+
// Numeric verification for the hot scramble path: walks the solution (move
|
|
103
|
+
// indices, walk order) from the target state to solved. The scramble is the
|
|
104
|
+
// exact inverse of the solution, so this is equivalent to verifying that the
|
|
105
|
+
// scramble reproduces the target state — one walk, no string parsing.
|
|
106
|
+
function verifyCube2SolutionIndices(state, solution) {
|
|
107
|
+
const permTable = tables_js_1.PERMUTATION_TABLE;
|
|
108
|
+
const orientTable = tables_js_1.ORIENTATION_TABLE;
|
|
109
|
+
if (!permTable || !orientTable) {
|
|
110
|
+
throw new Error("Cube2 solver tables not loaded. Call ensureTablesLoaded() first.");
|
|
111
|
+
}
|
|
112
|
+
let permIndex = (0, tables_js_1.encodePermutation)(state.corners.permutation);
|
|
113
|
+
let orientIndex = (0, tables_js_1.encodeOrientation)(state.corners.orientation);
|
|
114
|
+
for (const m of solution) {
|
|
115
|
+
permIndex = permTable[permIndex * tables_js_1.MOVE_COUNT + m];
|
|
116
|
+
orientIndex = orientTable[orientIndex * tables_js_1.MOVE_COUNT + m];
|
|
117
|
+
}
|
|
118
|
+
return permIndex === 0 && orientIndex === 0;
|
|
119
|
+
}
|
|
@@ -4,6 +4,7 @@ exports.BFS_MOVES = exports.DISTANCES = exports.ORIENTATION_TABLE = exports.PERM
|
|
|
4
4
|
exports.encodePermutation = encodePermutation;
|
|
5
5
|
exports.encodeOrientation = encodeOrientation;
|
|
6
6
|
exports.ensureTablesLoaded = ensureTablesLoaded;
|
|
7
|
+
exports.areCube2TablesLoaded = areCube2TablesLoaded;
|
|
7
8
|
exports.getDistance = getDistance;
|
|
8
9
|
exports.getPermutationTransition = getPermutationTransition;
|
|
9
10
|
exports.getOrientationTransition = getOrientationTransition;
|
|
@@ -61,6 +62,9 @@ async function ensureTablesLoaded() {
|
|
|
61
62
|
exports.DISTANCES = tables.distances;
|
|
62
63
|
tablesLoaded = true;
|
|
63
64
|
}
|
|
65
|
+
function areCube2TablesLoaded() {
|
|
66
|
+
return tablesLoaded;
|
|
67
|
+
}
|
|
64
68
|
// ---------------------------------------------------------------------------
|
|
65
69
|
// Public API
|
|
66
70
|
// ---------------------------------------------------------------------------
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.validateCube2State = validateCube2State;
|
|
4
4
|
exports.isOneMoveFromSolvedCube2State = isOneMoveFromSolvedCube2State;
|
|
5
|
+
exports.isTrivialSamplingCube2State = isTrivialSamplingCube2State;
|
|
5
6
|
exports.isAllowedRandomCube2State = isAllowedRandomCube2State;
|
|
6
7
|
exports.permutationParity = permutationParity;
|
|
7
8
|
const types_js_1 = require("./types.js");
|
|
@@ -37,7 +38,13 @@ function validateCube2State(state) {
|
|
|
37
38
|
function isOneMoveFromSolvedCube2State(state) {
|
|
38
39
|
const p = (0, tables_js_1.encodePermutation)(state.corners.permutation);
|
|
39
40
|
const o = (0, tables_js_1.encodeOrientation)(state.corners.orientation);
|
|
40
|
-
return
|
|
41
|
+
return isTrivialSamplingCube2State(p, o);
|
|
42
|
+
}
|
|
43
|
+
// Cheap trivial check for random-state sampling: the caller has already
|
|
44
|
+
// encoded the coordinates once. True for the solved state and the 18
|
|
45
|
+
// single-move states (which would produce scrambles shorter than 2 moves).
|
|
46
|
+
function isTrivialSamplingCube2State(p, o) {
|
|
47
|
+
return (p === 0 && o === 0) || ONE_MOVE_FROM_SOLVED_ENCODED.some((entry) => entry.p === p && entry.o === o);
|
|
41
48
|
}
|
|
42
49
|
function isAllowedRandomCube2State(state) {
|
|
43
50
|
return validateCube2State(state).valid &&
|