@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
@@ -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
- function toCube2Facelets(state) {
28
- const facelets = createEmptyFacelets();
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 piece = state.corners.permutation[position];
31
- const orientation = state.corners.orientation[position];
32
- placeOrientedStickers(facelets, CORNER_POSITION_SLOTS[position], CORNER_PIECE_STICKERS[piece], orientation);
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 || (0, validation_js_1.isAllowedRandomCube2State)(state)) {
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
- const perm7 = shuffledRange(types_js_1.CORNER_COUNT - 1, random);
20
- const mappedPerm = perm7.map((val) => (val >= 6 ? val + 1 : val));
21
- mappedPerm.splice(6, 0, 6);
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
- swap(values, index, swapIndex);
33
+ if (swapIndex < index) {
34
+ const temporary = perm7[index];
35
+ perm7[index] = perm7[swapIndex];
36
+ perm7[swapIndex] = temporary;
37
+ }
36
38
  }
37
- return values;
38
- }
39
- function randomOrientations(length, modulo, random) {
40
- const orientations = Array(length).fill(0);
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 < length - 1; index += 1) {
43
- const orientation = random.nextInt(modulo);
44
- orientations[index] = orientation;
45
- total += orientation;
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
- orientations[length - 1] = (modulo - (total % modulo)) % modulo;
48
- return orientations;
49
- }
50
- function swap(values, left, right) {
51
- const temporary = values[left];
52
- values[left] = values[right];
53
- values[right] = temporary;
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 = await (0, index_js_1.solveCube2State)(state);
25
- const scrambleMoves = (0, moves_js_1.invertCube2Algorithm)(solution.moves);
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
- if (!(0, index_js_1.verifyCube2Scramble)(state, scrambleMoves)) {
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: solution.moves,
49
- solutionDepth: solution.depth
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 { moves, depth: 0, state: (0, state_js_1.createSolvedCube2State)() };
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(tables_js_1.BFS_MOVES[m]);
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 ONE_MOVE_FROM_SOLVED_ENCODED.some((entry) => entry.p === p && entry.o === o);
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 &&
@@ -5,6 +5,8 @@ exports.toPyraminxFacelets = toPyraminxFacelets;
5
5
  exports.formatPyraminxFacelets = formatPyraminxFacelets;
6
6
  exports.getPyraminxFaceOrder = getPyraminxFaceOrder;
7
7
  const FACE_ORDER = ["F", "L", "R", "D"];
8
+ const COLORS = ["F", "L", "R", "D"];
9
+ const COLOR_INDEX = { F: 0, L: 1, R: 2, D: 3 };
8
10
  // positionId -> sticker slots (each slot = one triangle cell of the grid).
9
11
  // Positions follow the scrambler piece model (moves.ts):
10
12
  // edges 0..5 = UL, UR, UB, LR, LB, RB
@@ -54,6 +56,33 @@ const TIP_PIECE_STICKERS = [
54
56
  ["F", "R", "D"], // 2 R
55
57
  ["L", "D", "R"], // 3 B
56
58
  ];
59
+ const EDGE_CELLS = buildCells(EDGE_POSITION_SLOTS, EDGE_PIECE_STICKERS, 6, 2);
60
+ const CENTER_CELLS = buildCells(CENTER_POSITION_SLOTS, CENTER_PIECE_STICKERS, 4, 3);
61
+ const TIP_CELLS = buildCells(TIP_POSITION_SLOTS, TIP_PIECE_STICKERS, 4, 3);
62
+ function buildCells(slots, stickersTable, pieceCount, orientationCount) {
63
+ const cells = [];
64
+ for (let position = 0; position < slots.length; position += 1) {
65
+ const byPiece = [];
66
+ for (let piece = 0; piece < pieceCount; piece += 1) {
67
+ const byOrientation = [];
68
+ for (let orientation = 0; orientation < orientationCount; orientation += 1) {
69
+ const stickers = stickersTable[piece];
70
+ const slotList = slots[position];
71
+ const cell = [];
72
+ for (let index = 0; index < slotList.length; index += 1) {
73
+ const [face, stickerIndex] = slotList[index];
74
+ cell.push(FACE_ORDER.indexOf(face));
75
+ cell.push(stickerIndex);
76
+ cell.push(COLOR_INDEX[stickers[(index - orientation + stickers.length) % stickers.length]]);
77
+ }
78
+ byOrientation.push(cell);
79
+ }
80
+ byPiece.push(byOrientation);
81
+ }
82
+ cells.push(byPiece);
83
+ }
84
+ return cells;
85
+ }
57
86
  function createSolvedPyraminxFacelets() {
58
87
  return {
59
88
  F: Array.from({ length: 9 }, () => "F"),
@@ -62,36 +91,33 @@ function createSolvedPyraminxFacelets() {
62
91
  D: Array.from({ length: 9 }, () => "D"),
63
92
  };
64
93
  }
94
+ // Sparse arrays: every one of the 36 sticker cells is overwritten by the
95
+ // three placement loops below (12 edges + 12 centers + 12 tips), so no fill
96
+ // is needed. Coverage is enforced by test/pyraminx-facelets.test.ts.
65
97
  function createEmptyPyraminxFacelets() {
66
98
  return {
67
- F: Array(9).fill("?"),
68
- L: Array(9).fill("?"),
69
- R: Array(9).fill("?"),
70
- D: Array(9).fill("?"),
99
+ F: Array(9),
100
+ L: Array(9),
101
+ R: Array(9),
102
+ D: Array(9),
71
103
  };
72
104
  }
73
- function placeOrientedStickers(facelets, slots, stickers, orientation) {
74
- for (let index = 0; index < slots.length; index += 1) {
75
- const [face, stickerIndex] = slots[index];
76
- const sticker = stickers[(index - orientation + stickers.length) % stickers.length];
77
- const arr = facelets[face];
78
- arr[stickerIndex] = sticker;
105
+ function placeCells(faces, cells) {
106
+ for (let cellIndex = 0; cellIndex < cells.length; cellIndex += 3) {
107
+ faces[cells[cellIndex]][cells[cellIndex + 1]] = COLORS[cells[cellIndex + 2]];
79
108
  }
80
109
  }
81
110
  function toPyraminxFacelets(state) {
82
111
  const facelets = createEmptyPyraminxFacelets();
112
+ const faces = [facelets.F, facelets.L, facelets.R, facelets.D];
83
113
  for (let position = 0; position < 6; position += 1) {
84
- const piece = state.edgesPerm[position];
85
- const orientation = state.edgesOrient[position];
86
- placeOrientedStickers(facelets, EDGE_POSITION_SLOTS[position], EDGE_PIECE_STICKERS[piece], orientation);
114
+ placeCells(faces, EDGE_CELLS[position][state.edgesPerm[position]][state.edgesOrient[position]]);
87
115
  }
88
116
  for (let position = 0; position < 4; position += 1) {
89
- const orientation = state.centers[position];
90
- placeOrientedStickers(facelets, CENTER_POSITION_SLOTS[position], CENTER_PIECE_STICKERS[position], orientation);
117
+ placeCells(faces, CENTER_CELLS[position][position][state.centers[position]]);
91
118
  }
92
119
  for (let position = 0; position < 4; position += 1) {
93
- const orientation = state.tips[position];
94
- placeOrientedStickers(facelets, TIP_POSITION_SLOTS[position], TIP_PIECE_STICKERS[position], orientation);
120
+ placeCells(faces, TIP_CELLS[position][position][state.tips[position]]);
95
121
  }
96
122
  return facelets;
97
123
  }
@@ -2,41 +2,71 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.generateRandomPyraminxState = generateRandomPyraminxState;
4
4
  const index_js_1 = require("../../random/index.js");
5
- const validation_js_1 = require("./validation.js");
5
+ const state_js_1 = require("./state.js");
6
+ // Compact -> full Lehmer index for the 360 even permutations of 6 edges.
7
+ // Generated once: a permutation's parity is the parity of the digit sum of
8
+ // its Lehmer code (each digit counts smaller elements to its right).
9
+ const COMPACT_TO_FULL = new Uint16Array(360);
10
+ (function buildCompactMapping() {
11
+ let compact = 0;
12
+ for (let full = 0; full < 720 && compact < 360; full += 1) {
13
+ let value = full;
14
+ const c5 = (value / 120) | 0;
15
+ value -= c5 * 120;
16
+ const c4 = (value / 24) | 0;
17
+ value -= c4 * 24;
18
+ const c3 = (value / 6) | 0;
19
+ value -= c3 * 6;
20
+ const c2 = (value / 2) | 0;
21
+ value -= c2 * 2;
22
+ const c1 = value;
23
+ if ((c5 + c4 + c3 + c2 + c1) % 2 === 0) {
24
+ COMPACT_TO_FULL[compact] = full;
25
+ compact += 1;
26
+ }
27
+ }
28
+ })();
6
29
  function generateRandomPyraminxState(options = {}) {
7
30
  const random = (0, index_js_1.createRandomSource)(options.seed);
8
31
  const rejectTrivial = options.rejectTrivial ?? true;
9
32
  for (let attempt = 0; attempt < 100; attempt += 1) {
33
+ // The state is legal by construction (even permutation parity, even
34
+ // orientation sum, values in range), so only the trivial core check is
35
+ // needed here — no full re-validation.
10
36
  const state = createLegalRandomState(random);
11
- if (!rejectTrivial || (0, validation_js_1.isAllowedRandomPyraminxState)(state)) {
37
+ if (!rejectTrivial || !(0, state_js_1.isSolvedPyraminxState)(state)) {
12
38
  return state;
13
39
  }
14
40
  }
15
41
  throw new Error("Unable to generate a non-trivial random Pyraminx state.");
16
42
  }
17
43
  function createLegalRandomState(random) {
18
- // 4 random centers
19
- const centers = Array.from({ length: 4 }, () => random.nextInt(3));
20
- // 4 random tips
21
- const tips = Array.from({ length: 4 }, () => random.nextInt(3));
22
- // 6 edges permutation
23
- const edgesPerm = shuffledRange(6, random);
24
- // Ensure even parity
25
- if ((0, validation_js_1.permutationParity)(edgesPerm) !== 0) {
26
- // Swap the first two elements to flip parity
27
- const temp = edgesPerm[0];
28
- edgesPerm[0] = edgesPerm[1];
29
- edgesPerm[1] = temp;
44
+ // 4 random centers from one uniform value in [0, 81) (4 base-3 digits)
45
+ let centerValue = random.nextInt(81);
46
+ const centers = [0, 0, 0, 0];
47
+ for (let index = 0; index < 4; index += 1) {
48
+ centers[index] = centerValue % 3;
49
+ centerValue = (centerValue / 3) | 0;
50
+ }
51
+ // 4 random tips from one uniform value in [0, 81)
52
+ let tipValue = random.nextInt(81);
53
+ const tips = [0, 0, 0, 0];
54
+ for (let index = 0; index < 4; index += 1) {
55
+ tips[index] = tipValue % 3;
56
+ tipValue = (tipValue / 3) | 0;
30
57
  }
31
- // 6 edges orientation (sum must be even)
32
- const edgesOrient = Array(6).fill(0);
58
+ // 6 edges permutation, even parity, uniform over the 360 even permutations
59
+ const edgesPerm = permutationFromCompact(random.nextInt(360));
60
+ // 6 edges orientation (sum must be even): 5 free bits + parity digit
61
+ let orientationValue = random.nextInt(32);
62
+ const edgesOrient = [0, 0, 0, 0, 0, 0];
33
63
  let sumOrient = 0;
34
- for (let i = 0; i < 5; i++) {
35
- const val = random.nextInt(2);
36
- edgesOrient[i] = val;
37
- sumOrient += val;
64
+ for (let index = 0; index < 5; index += 1) {
65
+ edgesOrient[index] = orientationValue & 1;
66
+ sumOrient += orientationValue & 1;
67
+ orientationValue >>= 1;
38
68
  }
39
- edgesOrient[5] = sumOrient % 2 === 0 ? 0 : 1;
69
+ edgesOrient[5] = sumOrient & 1;
40
70
  return {
41
71
  centers,
42
72
  edgesPerm,
@@ -44,13 +74,17 @@ function createLegalRandomState(random) {
44
74
  tips
45
75
  };
46
76
  }
47
- function shuffledRange(length, random) {
48
- const values = Array.from({ length }, (_, index) => index);
49
- for (let index = values.length - 1; index > 0; index -= 1) {
50
- const swapIndex = random.nextInt(index + 1);
51
- const temporary = values[index];
52
- values[index] = values[swapIndex];
53
- values[swapIndex] = temporary;
77
+ const FACTORIALS = [1, 1, 2, 6, 24, 120, 720];
78
+ function permutationFromCompact(compact) {
79
+ let full = COMPACT_TO_FULL[compact];
80
+ const permutation = [0, 0, 0, 0, 0, 0];
81
+ const remaining = [0, 1, 2, 3, 4, 5];
82
+ for (let position = 0; position < 6; position += 1) {
83
+ const divisor = FACTORIALS[5 - position];
84
+ const choice = (full / divisor) | 0;
85
+ full -= choice * divisor;
86
+ permutation[position] = remaining[choice];
87
+ remaining.splice(choice, 1);
54
88
  }
55
- return values;
89
+ return permutation;
56
90
  }