@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.
Files changed (57) hide show
  1. package/dist/puzzles/cube2/facelets.d.ts.map +1 -1
  2. package/dist/puzzles/cube2/facelets.js +43 -22
  3. package/dist/puzzles/cube2/random-state.d.ts.map +1 -1
  4. package/dist/puzzles/cube2/random-state.js +44 -34
  5. package/dist/puzzles/cube2/scrambler.d.ts.map +1 -1
  6. package/dist/puzzles/cube2/scrambler.js +24 -10
  7. package/dist/puzzles/cube2/solver/index.d.ts +2 -1
  8. package/dist/puzzles/cube2/solver/index.d.ts.map +1 -1
  9. package/dist/puzzles/cube2/solver/index.js +2 -1
  10. package/dist/puzzles/cube2/solver/search.d.ts +3 -0
  11. package/dist/puzzles/cube2/solver/search.d.ts.map +1 -1
  12. package/dist/puzzles/cube2/solver/search.js +42 -7
  13. package/dist/puzzles/cube2/solver/tables.d.ts +1 -0
  14. package/dist/puzzles/cube2/solver/tables.d.ts.map +1 -1
  15. package/dist/puzzles/cube2/solver/tables.js +3 -0
  16. package/dist/puzzles/cube2/validation.d.ts +1 -0
  17. package/dist/puzzles/cube2/validation.d.ts.map +1 -1
  18. package/dist/puzzles/cube2/validation.js +7 -1
  19. package/dist/puzzles/pyraminx/facelets.d.ts.map +1 -1
  20. package/dist/puzzles/pyraminx/facelets.js +43 -17
  21. package/dist/puzzles/pyraminx/random-state.d.ts.map +1 -1
  22. package/dist/puzzles/pyraminx/random-state.js +63 -29
  23. package/dist/puzzles/pyraminx/scrambler.d.ts.map +1 -1
  24. package/dist/puzzles/pyraminx/scrambler.js +35 -26
  25. package/dist/puzzles/pyraminx/solver/search.d.ts +3 -0
  26. package/dist/puzzles/pyraminx/solver/search.d.ts.map +1 -1
  27. package/dist/puzzles/pyraminx/solver/search.js +60 -5
  28. package/dist/puzzles/pyraminx/solver/tables.d.ts +1 -0
  29. package/dist/puzzles/pyraminx/solver/tables.d.ts.map +1 -1
  30. package/dist/puzzles/pyraminx/solver/tables.js +3 -0
  31. package/dist/puzzles/skewb/scrambler.d.ts.map +1 -1
  32. package/dist/puzzles/skewb/scrambler.js +21 -7
  33. package/dist/puzzles/skewb/solver/search.d.ts +3 -0
  34. package/dist/puzzles/skewb/solver/search.d.ts.map +1 -1
  35. package/dist/puzzles/skewb/solver/search.js +43 -3
  36. package/dist/puzzles/skewb/solver/tables.d.ts +1 -0
  37. package/dist/puzzles/skewb/solver/tables.d.ts.map +1 -1
  38. package/dist/puzzles/skewb/solver/tables.js +3 -0
  39. package/dist/random/prng.d.ts.map +1 -1
  40. package/dist/random/prng.js +18 -4
  41. package/dist-cjs/puzzles/cube2/facelets.js +43 -22
  42. package/dist-cjs/puzzles/cube2/random-state.js +43 -33
  43. package/dist-cjs/puzzles/cube2/scrambler.js +22 -8
  44. package/dist-cjs/puzzles/cube2/solver/index.js +18 -1
  45. package/dist-cjs/puzzles/cube2/solver/search.js +45 -7
  46. package/dist-cjs/puzzles/cube2/solver/tables.js +4 -0
  47. package/dist-cjs/puzzles/cube2/validation.js +8 -1
  48. package/dist-cjs/puzzles/pyraminx/facelets.js +43 -17
  49. package/dist-cjs/puzzles/pyraminx/random-state.js +63 -29
  50. package/dist-cjs/puzzles/pyraminx/scrambler.js +34 -25
  51. package/dist-cjs/puzzles/pyraminx/solver/search.js +62 -4
  52. package/dist-cjs/puzzles/pyraminx/solver/tables.js +4 -0
  53. package/dist-cjs/puzzles/skewb/scrambler.js +19 -5
  54. package/dist-cjs/puzzles/skewb/solver/search.js +46 -3
  55. package/dist-cjs/puzzles/skewb/solver/tables.js +4 -0
  56. package/dist-cjs/random/prng.js +18 -4
  57. 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;AA4BrF,wBAAgB,eAAe,CAAC,KAAK,EAAE,UAAU,GAAG,aAAa,CAehE;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,aAAa,GAAG,MAAM,CAEnE;AAED,wBAAgB,iBAAiB,IAAI,SAAS,SAAS,EAAE,CAExD"}
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
- export function toCube2Facelets(state) {
23
- const facelets = createEmptyFacelets();
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 piece = state.corners.permutation[position];
26
- const orientation = state.corners.orientation[position];
27
- placeOrientedStickers(facelets, CORNER_POSITION_SLOTS[position], CORNER_PIECE_STICKERS[piece], orientation);
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":"AAGA,OAAO,KAAK,EAAE,uBAAuB,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAGtE,wBAAgB,wBAAwB,CAAC,OAAO,GAAE,uBAA4B,GAAG,UAAU,CAa1F"}
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 { CORNER_COUNT } from "./types.js";
3
- import { isAllowedRandomCube2State } from "./validation.js";
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 || isAllowedRandomCube2State(state)) {
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
- const perm7 = shuffledRange(CORNER_COUNT - 1, random);
17
- const mappedPerm = perm7.map((val) => (val >= 6 ? val + 1 : val));
18
- mappedPerm.splice(6, 0, 6);
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
- swap(values, index, swapIndex);
30
+ if (swapIndex < index) {
31
+ const temporary = perm7[index];
32
+ perm7[index] = perm7[swapIndex];
33
+ perm7[swapIndex] = temporary;
34
+ }
33
35
  }
34
- return values;
35
- }
36
- function randomOrientations(length, modulo, random) {
37
- const orientations = Array(length).fill(0);
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 < length - 1; index += 1) {
40
- const orientation = random.nextInt(modulo);
41
- orientations[index] = orientation;
42
- total += orientation;
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
- orientations[length - 1] = (modulo - (total % modulo)) % modulo;
45
- return orientations;
46
- }
47
- function swap(values, left, right) {
48
- const temporary = values[left];
49
- values[left] = values[right];
50
- values[right] = temporary;
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;AAS9F,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,CAwCzB"}
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, invertCube2Algorithm } from "./moves.js";
1
+ import { formatCube2Algorithm } from "./moves.js";
2
2
  import { generateRandomCube2State } from "./random-state.js";
3
3
  import { formatCube2Facelets, toCube2Facelets } from "./facelets.js";
4
- import { solveCube2State, verifyCube2Scramble, verifyCube2Solution } from "./solver/index.js";
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 = await solveCube2State(state);
21
- const scrambleMoves = invertCube2Algorithm(solution.moves);
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
- if (!verifyCube2Scramble(state, scrambleMoves)) {
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: solution.moves,
45
- solutionDepth: solution.depth
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,EAAE,eAAe,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AACxF,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC"}
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,CA6D/E;AAoBD,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"}
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 { moves, depth: 0, state: createSolvedCube2State() };
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(BFS_MOVES[m]);
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 ONE_MOVE_FROM_SOLVED_ENCODED.some((entry) => entry.p === p && entry.o === o);
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 &&
@@ -1 +1 @@
1
- {"version":3,"file":"facelets.d.ts","sourceRoot":"","sources":["../../../src/puzzles/pyraminx/facelets.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAUhD,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC;AA6DhF,wBAAgB,4BAA4B,IAAI,gBAAgB,CAO/D;AAyBD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,aAAa,GAAG,gBAAgB,CAiCzE;AAED,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,gBAAgB,GAAG,MAAM,CAEzE;AAED,wBAAgB,oBAAoB,IAAI,SAAS,CAAC,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC,EAAE,CAEzE"}
1
+ {"version":3,"file":"facelets.d.ts","sourceRoot":"","sources":["../../../src/puzzles/pyraminx/facelets.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAUhD,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC;AAsGhF,wBAAgB,4BAA4B,IAAI,gBAAgB,CAO/D;AAoBD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,aAAa,GAAG,gBAAgB,CAezE;AAED,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,gBAAgB,GAAG,MAAM,CAEzE;AAED,wBAAgB,oBAAoB,IAAI,SAAS,CAAC,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC,EAAE,CAEzE"}
@@ -1,4 +1,6 @@
1
1
  const FACE_ORDER = ["F", "L", "R", "D"];
2
+ const COLORS = ["F", "L", "R", "D"];
3
+ const COLOR_INDEX = { F: 0, L: 1, R: 2, D: 3 };
2
4
  // positionId -> sticker slots (each slot = one triangle cell of the grid).
3
5
  // Positions follow the scrambler piece model (moves.ts):
4
6
  // edges 0..5 = UL, UR, UB, LR, LB, RB
@@ -48,6 +50,33 @@ const TIP_PIECE_STICKERS = [
48
50
  ["F", "R", "D"], // 2 R
49
51
  ["L", "D", "R"], // 3 B
50
52
  ];
53
+ const EDGE_CELLS = buildCells(EDGE_POSITION_SLOTS, EDGE_PIECE_STICKERS, 6, 2);
54
+ const CENTER_CELLS = buildCells(CENTER_POSITION_SLOTS, CENTER_PIECE_STICKERS, 4, 3);
55
+ const TIP_CELLS = buildCells(TIP_POSITION_SLOTS, TIP_PIECE_STICKERS, 4, 3);
56
+ function buildCells(slots, stickersTable, pieceCount, orientationCount) {
57
+ const cells = [];
58
+ for (let position = 0; position < slots.length; position += 1) {
59
+ const byPiece = [];
60
+ for (let piece = 0; piece < pieceCount; piece += 1) {
61
+ const byOrientation = [];
62
+ for (let orientation = 0; orientation < orientationCount; orientation += 1) {
63
+ const stickers = stickersTable[piece];
64
+ const slotList = slots[position];
65
+ const cell = [];
66
+ for (let index = 0; index < slotList.length; index += 1) {
67
+ const [face, stickerIndex] = slotList[index];
68
+ cell.push(FACE_ORDER.indexOf(face));
69
+ cell.push(stickerIndex);
70
+ cell.push(COLOR_INDEX[stickers[(index - orientation + stickers.length) % stickers.length]]);
71
+ }
72
+ byOrientation.push(cell);
73
+ }
74
+ byPiece.push(byOrientation);
75
+ }
76
+ cells.push(byPiece);
77
+ }
78
+ return cells;
79
+ }
51
80
  export function createSolvedPyraminxFacelets() {
52
81
  return {
53
82
  F: Array.from({ length: 9 }, () => "F"),
@@ -56,36 +85,33 @@ export function createSolvedPyraminxFacelets() {
56
85
  D: Array.from({ length: 9 }, () => "D"),
57
86
  };
58
87
  }
88
+ // Sparse arrays: every one of the 36 sticker cells is overwritten by the
89
+ // three placement loops below (12 edges + 12 centers + 12 tips), so no fill
90
+ // is needed. Coverage is enforced by test/pyraminx-facelets.test.ts.
59
91
  function createEmptyPyraminxFacelets() {
60
92
  return {
61
- F: Array(9).fill("?"),
62
- L: Array(9).fill("?"),
63
- R: Array(9).fill("?"),
64
- D: Array(9).fill("?"),
93
+ F: Array(9),
94
+ L: Array(9),
95
+ R: Array(9),
96
+ D: Array(9),
65
97
  };
66
98
  }
67
- function placeOrientedStickers(facelets, slots, stickers, orientation) {
68
- for (let index = 0; index < slots.length; index += 1) {
69
- const [face, stickerIndex] = slots[index];
70
- const sticker = stickers[(index - orientation + stickers.length) % stickers.length];
71
- const arr = facelets[face];
72
- arr[stickerIndex] = sticker;
99
+ function placeCells(faces, cells) {
100
+ for (let cellIndex = 0; cellIndex < cells.length; cellIndex += 3) {
101
+ faces[cells[cellIndex]][cells[cellIndex + 1]] = COLORS[cells[cellIndex + 2]];
73
102
  }
74
103
  }
75
104
  export function toPyraminxFacelets(state) {
76
105
  const facelets = createEmptyPyraminxFacelets();
106
+ const faces = [facelets.F, facelets.L, facelets.R, facelets.D];
77
107
  for (let position = 0; position < 6; position += 1) {
78
- const piece = state.edgesPerm[position];
79
- const orientation = state.edgesOrient[position];
80
- placeOrientedStickers(facelets, EDGE_POSITION_SLOTS[position], EDGE_PIECE_STICKERS[piece], orientation);
108
+ placeCells(faces, EDGE_CELLS[position][state.edgesPerm[position]][state.edgesOrient[position]]);
81
109
  }
82
110
  for (let position = 0; position < 4; position += 1) {
83
- const orientation = state.centers[position];
84
- placeOrientedStickers(facelets, CENTER_POSITION_SLOTS[position], CENTER_PIECE_STICKERS[position], orientation);
111
+ placeCells(faces, CENTER_CELLS[position][position][state.centers[position]]);
85
112
  }
86
113
  for (let position = 0; position < 4; position += 1) {
87
- const orientation = state.tips[position];
88
- placeOrientedStickers(facelets, TIP_POSITION_SLOTS[position], TIP_PIECE_STICKERS[position], orientation);
114
+ placeCells(faces, TIP_CELLS[position][position][state.tips[position]]);
89
115
  }
90
116
  return facelets;
91
117
  }
@@ -1 +1 @@
1
- {"version":3,"file":"random-state.d.ts","sourceRoot":"","sources":["../../../src/puzzles/pyraminx/random-state.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,0BAA0B,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAG5E,wBAAgB,2BAA2B,CAAC,OAAO,GAAE,0BAA+B,GAAG,aAAa,CAYnG"}
1
+ {"version":3,"file":"random-state.d.ts","sourceRoot":"","sources":["../../../src/puzzles/pyraminx/random-state.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,0BAA0B,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AA2B5E,wBAAgB,2BAA2B,CAAC,OAAO,GAAE,0BAA+B,GAAG,aAAa,CAenG"}