@cubingcenter/scrambler 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/dist/puzzles/cube3/scrambler.d.ts.map +1 -1
  2. package/dist/puzzles/cube3/scrambler.js +1 -0
  3. package/dist/puzzles/cube4/scrambler.js +3 -3
  4. package/dist/puzzles/index.d.ts +1 -0
  5. package/dist/puzzles/index.d.ts.map +1 -1
  6. package/dist/puzzles/index.js +3 -0
  7. package/dist/puzzles/pyraminx/facelets.d.ts +7 -0
  8. package/dist/puzzles/pyraminx/facelets.d.ts.map +1 -0
  9. package/dist/puzzles/pyraminx/facelets.js +97 -0
  10. package/dist/puzzles/pyraminx/index.d.ts +2 -0
  11. package/dist/puzzles/pyraminx/index.d.ts.map +1 -1
  12. package/dist/puzzles/pyraminx/index.js +1 -0
  13. package/dist/puzzles/pyraminx/moves.d.ts.map +1 -1
  14. package/dist/puzzles/pyraminx/moves.js +35 -38
  15. package/dist/puzzles/pyraminx/scrambler.d.ts.map +1 -1
  16. package/dist/puzzles/pyraminx/scrambler.js +12 -6
  17. package/dist/puzzles/skewb/facelets.d.ts +6 -0
  18. package/dist/puzzles/skewb/facelets.d.ts.map +1 -0
  19. package/dist/puzzles/skewb/facelets.js +71 -0
  20. package/dist/puzzles/skewb/index.d.ts +10 -0
  21. package/dist/puzzles/skewb/index.d.ts.map +1 -0
  22. package/dist/puzzles/skewb/index.js +8 -0
  23. package/dist/puzzles/skewb/moves.d.ts +19 -0
  24. package/dist/puzzles/skewb/moves.d.ts.map +1 -0
  25. package/dist/puzzles/skewb/moves.js +84 -0
  26. package/dist/puzzles/skewb/random-state.d.ts +3 -0
  27. package/dist/puzzles/skewb/random-state.d.ts.map +1 -0
  28. package/dist/puzzles/skewb/random-state.js +27 -0
  29. package/dist/puzzles/skewb/scrambler.d.ts +6 -0
  30. package/dist/puzzles/skewb/scrambler.d.ts.map +1 -0
  31. package/dist/puzzles/skewb/scrambler.js +52 -0
  32. package/dist/puzzles/skewb/solver.d.ts +11 -0
  33. package/dist/puzzles/skewb/solver.d.ts.map +1 -0
  34. package/dist/puzzles/skewb/solver.js +149 -0
  35. package/dist/puzzles/skewb/state.d.ts +6 -0
  36. package/dist/puzzles/skewb/state.d.ts.map +1 -0
  37. package/dist/puzzles/skewb/state.js +31 -0
  38. package/dist/puzzles/skewb/types.d.ts +25 -0
  39. package/dist/puzzles/skewb/types.d.ts.map +1 -0
  40. package/dist/puzzles/skewb/types.js +13 -0
  41. package/dist/puzzles/skewb/validation.d.ts +6 -0
  42. package/dist/puzzles/skewb/validation.d.ts.map +1 -0
  43. package/dist/puzzles/skewb/validation.js +69 -0
  44. package/dist-cjs/puzzles/cube3/scrambler.js +1 -0
  45. package/dist-cjs/puzzles/cube4/scrambler.js +3 -3
  46. package/dist-cjs/puzzles/index.js +4 -1
  47. package/dist-cjs/puzzles/pyraminx/facelets.js +103 -0
  48. package/dist-cjs/puzzles/pyraminx/index.js +6 -1
  49. package/dist-cjs/puzzles/pyraminx/moves.js +35 -38
  50. package/dist-cjs/puzzles/pyraminx/scrambler.js +12 -6
  51. package/dist-cjs/puzzles/skewb/facelets.js +77 -0
  52. package/dist-cjs/puzzles/skewb/index.js +38 -0
  53. package/dist-cjs/puzzles/skewb/moves.js +95 -0
  54. package/dist-cjs/puzzles/skewb/random-state.js +30 -0
  55. package/dist-cjs/puzzles/skewb/scrambler.js +56 -0
  56. package/dist-cjs/puzzles/skewb/solver.js +160 -0
  57. package/dist-cjs/puzzles/skewb/state.js +37 -0
  58. package/dist-cjs/puzzles/skewb/types.js +16 -0
  59. package/dist-cjs/puzzles/skewb/validation.js +75 -0
  60. package/package.json +1 -1
@@ -0,0 +1,52 @@
1
+ import { generateRandomSkewbState } from "./random-state.js";
2
+ import { createSolvedSkewbState, isSolvedSkewbState, skewbStatesEqual } from "./state.js";
3
+ import { applySkewbAlgorithm, formatSkewbAlgorithm, invertSkewbAlgorithm } from "./moves.js";
4
+ import { solveSkewb } from "./solver.js";
5
+ import { formatSkewbFacelets, toSkewbFacelets } from "./facelets.js";
6
+ export const skewbScrambleGenerator = {
7
+ info: {
8
+ eventId: "skewb",
9
+ name: "Skewb",
10
+ puzzle: "skewb",
11
+ quality: "random-state",
12
+ generator: "skewb-bfs",
13
+ description: "Random-state Skewb scramble generated by an optimal bidirectional BFS solver.",
14
+ },
15
+ generate(options = {}) {
16
+ return generateSkewbScramble(options);
17
+ },
18
+ };
19
+ export async function generateSkewbScramble(options = {}) {
20
+ const state = generateRandomSkewbState(options);
21
+ const solution = solveSkewb(state);
22
+ const scrambleMoves = invertSkewbAlgorithm(solution);
23
+ if (!options.skipValidation) {
24
+ const scrambledState = applySkewbAlgorithm(createSolvedSkewbState(), scrambleMoves);
25
+ if (!skewbStatesEqual(scrambledState, state)) {
26
+ throw new Error("Generated Skewb scramble does not reproduce the target random state.");
27
+ }
28
+ if (!isSolvedSkewbState(applySkewbAlgorithm(state, solution))) {
29
+ throw new Error("Generated Skewb solution does not solve the target random state.");
30
+ }
31
+ if (scrambleMoves.length < 2)
32
+ throw new Error("Generated Skewb scramble is too short.");
33
+ }
34
+ const facelets = toSkewbFacelets(state);
35
+ const result = {
36
+ eventId: "skewb",
37
+ scramble: formatSkewbAlgorithm(scrambleMoves),
38
+ moves: scrambleMoves,
39
+ quality: "random-state",
40
+ generator: "skewb-bfs",
41
+ metadata: {
42
+ state,
43
+ facelets,
44
+ faceletString: formatSkewbFacelets(facelets),
45
+ solutionMoves: solution,
46
+ solutionDepth: solution.length,
47
+ },
48
+ };
49
+ if (options.seed !== undefined)
50
+ result.seed = options.seed;
51
+ return result;
52
+ }
@@ -0,0 +1,11 @@
1
+ import type { SkewbMove, SkewbState } from "./types.js";
2
+ export declare function getPermutationIndex(perm: readonly number[]): number;
3
+ export declare function getPermutationFromIndex(index: number, n: number): number[];
4
+ export declare function getOrientationIndex(orient: readonly number[]): number;
5
+ export declare function getOrientationFromIndex(index: number): number[];
6
+ export declare function getCenterIndex(centers: readonly number[]): number;
7
+ export declare function getCenterFromIndex(index: number): number[];
8
+ export declare function getStateIndex(state: SkewbState): number;
9
+ export declare function getStateFromIndex(index: number): SkewbState;
10
+ export declare function solveSkewb(state: SkewbState): SkewbMove[];
11
+ //# sourceMappingURL=solver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"solver.d.ts","sourceRoot":"","sources":["../../../src/puzzles/skewb/solver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAMxD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAWnE;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAY1E;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAOrE;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAS/D;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CASjE;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAE1D;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAOvD;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAY3D;AAID,wBAAgB,UAAU,CAAC,KAAK,EAAE,UAAU,GAAG,SAAS,EAAE,CA+CzD"}
@@ -0,0 +1,149 @@
1
+ import { applySkewbMove } from "./moves.js";
2
+ import { createSolvedSkewbState } from "./state.js";
3
+ const FACT = [1, 1, 2, 6, 24, 120, 720, 5040, 40320];
4
+ export function getPermutationIndex(perm) {
5
+ // perm length 8, but pos0 is always 0 for WCA reachable states.
6
+ // We encode full 8-perm via Lehmer code (0..40319), but for fixed corner it will be in subset.
7
+ let index = 0;
8
+ const n = perm.length;
9
+ for (let i = 0; i < n; i++) {
10
+ let count = 0;
11
+ for (let j = i + 1; j < n; j++)
12
+ if (perm[j] < perm[i])
13
+ count++;
14
+ index += count * FACT[n - 1 - i];
15
+ }
16
+ return index;
17
+ }
18
+ export function getPermutationFromIndex(index, n) {
19
+ const perm = Array(n).fill(0);
20
+ const temp = Array.from({ length: n }, (_, i) => i);
21
+ let val = index;
22
+ for (let i = 0; i < n; i++) {
23
+ const f = FACT[n - 1 - i];
24
+ const choice = Math.floor(val / f);
25
+ val %= f;
26
+ perm[i] = temp[choice];
27
+ temp.splice(choice, 1);
28
+ }
29
+ return perm;
30
+ }
31
+ export function getOrientationIndex(orient) {
32
+ // orient length 8, index 0 is fixed 0, encode remaining 7 base3
33
+ let index = 0;
34
+ for (let i = 7; i >= 1; i--) {
35
+ index = index * 3 + orient[i];
36
+ }
37
+ return index;
38
+ }
39
+ export function getOrientationFromIndex(index) {
40
+ const orient = Array(8).fill(0);
41
+ orient[0] = 0;
42
+ let val = index;
43
+ for (let i = 1; i < 8; i++) {
44
+ orient[i] = val % 3;
45
+ val = Math.floor(val / 3);
46
+ }
47
+ return orient;
48
+ }
49
+ export function getCenterIndex(centers) {
50
+ let index = 0;
51
+ const n = centers.length;
52
+ for (let i = 0; i < n; i++) {
53
+ let count = 0;
54
+ for (let j = i + 1; j < n; j++)
55
+ if (centers[j] < centers[i])
56
+ count++;
57
+ index += count * FACT[n - 1 - i];
58
+ }
59
+ return index;
60
+ }
61
+ export function getCenterFromIndex(index) {
62
+ return getPermutationFromIndex(index, 6);
63
+ }
64
+ export function getStateIndex(state) {
65
+ const p = getPermutationIndex(state.corners.permutation);
66
+ const c = getCenterIndex(state.centers);
67
+ const o = getOrientationIndex(state.corners.orientation);
68
+ // p in 0..40319, c in 0..719, o in 0..2186
69
+ // Combine: ((p * 720 + c) * 2187 + o) -> up to 63B fits in JS number (53-bit)
70
+ return ((p * 720 + c) * 2187 + o);
71
+ }
72
+ export function getStateFromIndex(index) {
73
+ const o = index % 2187;
74
+ let val = Math.floor(index / 2187);
75
+ const c = val % 720;
76
+ const p = Math.floor(val / 720);
77
+ return {
78
+ corners: {
79
+ permutation: getPermutationFromIndex(p, 8),
80
+ orientation: getOrientationFromIndex(o),
81
+ },
82
+ centers: getCenterFromIndex(c),
83
+ };
84
+ }
85
+ const MOVES = ["R", "R'", "U", "U'", "L", "L'", "B", "B'"];
86
+ export function solveSkewb(state) {
87
+ const startIdx = getStateIndex(state);
88
+ const solvedIdx = getStateIndex(createSolvedSkewbState());
89
+ if (startIdx === solvedIdx)
90
+ return [];
91
+ const forwardMap = new Map();
92
+ const backwardMap = new Map();
93
+ const forwardQueue = [startIdx];
94
+ const backwardQueue = [solvedIdx];
95
+ forwardMap.set(startIdx, { move: "", parent: -1 });
96
+ backwardMap.set(solvedIdx, { move: "", parent: -1 });
97
+ while (forwardQueue.length > 0 && backwardQueue.length > 0) {
98
+ const fSize = forwardQueue.length;
99
+ for (let i = 0; i < Math.min(fSize, 1000); i++) {
100
+ const currIdx = forwardQueue.shift();
101
+ const currState = getStateFromIndex(currIdx);
102
+ for (const move of MOVES) {
103
+ const nextState = applySkewbMove(currState, move);
104
+ const nextIdx = getStateIndex(nextState);
105
+ if (!forwardMap.has(nextIdx)) {
106
+ forwardMap.set(nextIdx, { move, parent: currIdx });
107
+ if (backwardMap.has(nextIdx))
108
+ return reconstructPath(nextIdx, forwardMap, backwardMap);
109
+ forwardQueue.push(nextIdx);
110
+ }
111
+ }
112
+ }
113
+ const bSize = backwardQueue.length;
114
+ for (let i = 0; i < Math.min(bSize, 1000); i++) {
115
+ const currIdx = backwardQueue.shift();
116
+ const currState = getStateFromIndex(currIdx);
117
+ for (const move of MOVES) {
118
+ const nextState = applySkewbMove(currState, move);
119
+ const nextIdx = getStateIndex(nextState);
120
+ if (!backwardMap.has(nextIdx)) {
121
+ const invMove = move.endsWith("'") ? move.slice(0, -1) : `${move}'`;
122
+ backwardMap.set(nextIdx, { move: invMove, parent: currIdx });
123
+ if (forwardMap.has(nextIdx))
124
+ return reconstructPath(nextIdx, forwardMap, backwardMap);
125
+ backwardQueue.push(nextIdx);
126
+ }
127
+ }
128
+ }
129
+ }
130
+ throw new Error("Skewb state is unsolvable");
131
+ }
132
+ function reconstructPath(meetingIdx, forwardMap, backwardMap) {
133
+ const path = [];
134
+ let curr = meetingIdx;
135
+ while (curr !== -1) {
136
+ const edge = forwardMap.get(curr);
137
+ if (edge.parent !== -1)
138
+ path.unshift(edge.move);
139
+ curr = edge.parent;
140
+ }
141
+ curr = meetingIdx;
142
+ while (curr !== -1) {
143
+ const edge = backwardMap.get(curr);
144
+ if (edge.parent !== -1)
145
+ path.push(edge.move);
146
+ curr = edge.parent;
147
+ }
148
+ return path;
149
+ }
@@ -0,0 +1,6 @@
1
+ import type { SkewbState } from "./types.js";
2
+ export declare function createSolvedSkewbState(): SkewbState;
3
+ export declare function cloneSkewbState(state: SkewbState): SkewbState;
4
+ export declare function skewbStatesEqual(left: SkewbState, right: SkewbState): boolean;
5
+ export declare function isSolvedSkewbState(state: SkewbState): boolean;
6
+ //# sourceMappingURL=state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../../src/puzzles/skewb/state.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,wBAAgB,sBAAsB,IAAI,UAAU,CAQnD;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,UAAU,GAAG,UAAU,CAQ7D;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,GAAG,OAAO,CAM7E;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAM7D"}
@@ -0,0 +1,31 @@
1
+ export function createSolvedSkewbState() {
2
+ return {
3
+ corners: {
4
+ permutation: [0, 1, 2, 3, 4, 5, 6, 7],
5
+ orientation: [0, 0, 0, 0, 0, 0, 0, 0],
6
+ },
7
+ centers: [0, 1, 2, 3, 4, 5],
8
+ };
9
+ }
10
+ export function cloneSkewbState(state) {
11
+ return {
12
+ corners: {
13
+ permutation: [...state.corners.permutation],
14
+ orientation: [...state.corners.orientation],
15
+ },
16
+ centers: [...state.centers],
17
+ };
18
+ }
19
+ export function skewbStatesEqual(left, right) {
20
+ return (arraysEqual(left.corners.permutation, right.corners.permutation) &&
21
+ arraysEqual(left.corners.orientation, right.corners.orientation) &&
22
+ arraysEqual(left.centers, right.centers));
23
+ }
24
+ export function isSolvedSkewbState(state) {
25
+ return (state.corners.permutation.every((value, index) => value === index) &&
26
+ state.corners.orientation.every((value) => value === 0) &&
27
+ state.centers.every((value, index) => value === index));
28
+ }
29
+ function arraysEqual(left, right) {
30
+ return left.length === right.length && left.every((value, index) => value === right[index]);
31
+ }
@@ -0,0 +1,25 @@
1
+ export declare const CORNER_COUNT = 8;
2
+ export declare const CENTER_COUNT = 6;
3
+ export type SkewbFace = "U" | "R" | "F" | "D" | "L" | "B";
4
+ export type SkewbFaceMove = "R" | "U" | "L" | "B";
5
+ export type SkewbMoveSuffix = "" | "'";
6
+ export type SkewbMove = `${SkewbFaceMove}${SkewbMoveSuffix}`;
7
+ export interface SkewbState {
8
+ corners: {
9
+ permutation: number[];
10
+ orientation: number[];
11
+ };
12
+ centers: number[];
13
+ }
14
+ export interface SkewbValidationResult {
15
+ valid: boolean;
16
+ errors: string[];
17
+ }
18
+ export interface SkewbRandomStateOptions {
19
+ seed?: string | number;
20
+ rejectTrivial?: boolean;
21
+ }
22
+ export type SkewbFacelets = Record<SkewbFace, string[]>;
23
+ export declare const SKEWB_MOVES: readonly SkewbMove[];
24
+ export declare const SKEWB_FACES: readonly SkewbFace[];
25
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/puzzles/skewb/types.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,YAAY,IAAI,CAAC;AAC9B,eAAO,MAAM,YAAY,IAAI,CAAC;AAE9B,MAAM,MAAM,SAAS,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;AAC1D,MAAM,MAAM,aAAa,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;AAClD,MAAM,MAAM,eAAe,GAAG,EAAE,GAAG,GAAG,CAAE;AACxC,MAAM,MAAM,SAAS,GAAG,GAAG,aAAa,GAAG,eAAe,EAAE,CAAC;AAE7D,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE;QACP,WAAW,EAAE,MAAM,EAAE,CAAC;QACtB,WAAW,EAAE,MAAM,EAAE,CAAC;KACvB,CAAC;IACF,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,uBAAuB;IACtC,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,CAAC,CAAC;AAExD,eAAO,MAAM,WAAW,EAAE,SAAS,SAAS,EAS3C,CAAC;AAEF,eAAO,MAAM,WAAW,EAAE,SAAS,SAAS,EAAmC,CAAC"}
@@ -0,0 +1,13 @@
1
+ export const CORNER_COUNT = 8;
2
+ export const CENTER_COUNT = 6;
3
+ export const SKEWB_MOVES = [
4
+ "R",
5
+ "R'",
6
+ "U",
7
+ "U'",
8
+ "L",
9
+ "L'",
10
+ "B",
11
+ "B'",
12
+ ];
13
+ export const SKEWB_FACES = ["U", "R", "F", "D", "L", "B"];
@@ -0,0 +1,6 @@
1
+ import type { SkewbState, SkewbValidationResult } from "./types.js";
2
+ export declare function validateSkewbState(state: SkewbState): SkewbValidationResult;
3
+ export declare function isOneMoveFromSolvedSkewbState(state: SkewbState): boolean;
4
+ export declare function isAllowedRandomSkewbState(state: SkewbState): boolean;
5
+ export declare function permutationParity(permutation: readonly number[]): 0 | 1;
6
+ //# sourceMappingURL=validation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../../src/puzzles/skewb/validation.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAIpE,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,UAAU,GAAG,qBAAqB,CAwB3E;AAED,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAGxE;AAED,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAEpE;AAED,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC,CAQvE"}
@@ -0,0 +1,69 @@
1
+ import { SKEWB_MOVES, CENTER_COUNT, CORNER_COUNT } from "./types.js";
2
+ import { applySkewbMove } from "./moves.js";
3
+ import { createSolvedSkewbState, isSolvedSkewbState, skewbStatesEqual } from "./state.js";
4
+ export function validateSkewbState(state) {
5
+ const errors = [];
6
+ // corners permutation
7
+ errors.push(...validatePermutation("corner", state.corners.permutation, CORNER_COUNT));
8
+ // corners orientation
9
+ errors.push(...validateOrientations("corner", state.corners.orientation, CORNER_COUNT, 3));
10
+ // centers
11
+ errors.push(...validatePermutation("center", state.centers, CENTER_COUNT));
12
+ // parity checks - both perms must be even (as observed in BFS)
13
+ if (errors.length === 0) {
14
+ if (permutationParity(state.corners.permutation) !== 0) {
15
+ errors.push("Corner permutation parity must be even.");
16
+ }
17
+ if (permutationParity(state.centers) !== 0) {
18
+ errors.push("Center permutation parity must be even.");
19
+ }
20
+ // Check fixed corner invariant: corner 0 must be at position 0 with orientation 0 for WCA fixed model
21
+ // But we allow all states for validation; this is enforced by solver's reachable set, not validation.
22
+ // So we don't enforce fixed corner here; any even parity state is considered valid.
23
+ }
24
+ return { valid: errors.length === 0, errors };
25
+ }
26
+ export function isOneMoveFromSolvedSkewbState(state) {
27
+ const solved = createSolvedSkewbState();
28
+ return SKEWB_MOVES.some((move) => skewbStatesEqual(applySkewbMove(solved, move), state));
29
+ }
30
+ export function isAllowedRandomSkewbState(state) {
31
+ return validateSkewbState(state).valid && !isSolvedSkewbState(state) && !isOneMoveFromSolvedSkewbState(state);
32
+ }
33
+ export function permutationParity(permutation) {
34
+ let inversions = 0;
35
+ for (let left = 0; left < permutation.length; left++) {
36
+ for (let right = left + 1; right < permutation.length; right++) {
37
+ if (permutation[left] > permutation[right])
38
+ inversions++;
39
+ }
40
+ }
41
+ return (inversions % 2);
42
+ }
43
+ function validatePermutation(label, permutation, length) {
44
+ const errors = [];
45
+ const seen = new Set();
46
+ if (permutation.length !== length)
47
+ errors.push(`${label} permutation must contain ${length} values.`);
48
+ for (const value of permutation) {
49
+ if (!Number.isInteger(value) || value < 0 || value >= length) {
50
+ errors.push(`${label} permutation contains out-of-range value: ${value}.`);
51
+ continue;
52
+ }
53
+ if (seen.has(value))
54
+ errors.push(`${label} permutation contains duplicate value: ${value}.`);
55
+ seen.add(value);
56
+ }
57
+ return errors;
58
+ }
59
+ function validateOrientations(label, orientations, length, modulo) {
60
+ const errors = [];
61
+ if (orientations.length !== length)
62
+ errors.push(`${label} orientation must contain ${length} values.`);
63
+ for (const value of orientations) {
64
+ if (!Number.isInteger(value) || value < 0 || value >= modulo) {
65
+ errors.push(`${label} orientation contains out-of-range value: ${value}.`);
66
+ }
67
+ }
68
+ return errors;
69
+ }
@@ -46,6 +46,7 @@ async function generateCube3Scramble(options = {}) {
46
46
  metadata: {
47
47
  generator: "cube3-two-phase",
48
48
  generatorVersion: "1.0.0",
49
+ facelets,
49
50
  faceletString: (0, facelets_js_1.formatCube3Facelets)(facelets),
50
51
  state: JSON.parse(JSON.stringify(state)),
51
52
  solutionMoves: solution.moves,
@@ -44,9 +44,9 @@ exports.cube4ScrambleGenerator = {
44
44
  eventId: "444",
45
45
  name: "4x4x4 Cube",
46
46
  puzzle: "cube-4x4",
47
- quality: "random-move",
48
- generator: "cube4-random-move",
49
- description: "Random-move 4x4 scramble with 40 moves."
47
+ quality: "random-state",
48
+ generator: "cubing-scramble",
49
+ description: "Random-state 4x4 scramble generated by cubing.js."
50
50
  },
51
51
  generate(options = {}) {
52
52
  return generateCube4Scramble(options);
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.clock = exports.pyraminx = exports.cube7x7 = exports.cube6x6 = exports.cube5x5 = exports.megaminx = exports.cube4 = exports.cube3 = exports.cube2 = void 0;
36
+ exports.skewb = exports.clock = exports.pyraminx = exports.cube7x7 = exports.cube6x6 = exports.cube5x5 = exports.megaminx = exports.cube4 = exports.cube3 = exports.cube2 = void 0;
37
37
  const index_js_1 = require("../core/index.js");
38
38
  const index_js_2 = require("./cube2/index.js");
39
39
  const index_js_3 = require("./cube3/index.js");
@@ -44,6 +44,7 @@ const index_js_7 = require("./cube6x6/index.js");
44
44
  const index_js_8 = require("./cube7x7/index.js");
45
45
  const index_js_9 = require("./pyraminx/index.js");
46
46
  const index_js_10 = require("./clock/index.js");
47
+ const index_js_11 = require("./skewb/index.js");
47
48
  (0, index_js_1.registerScrambleGenerator)(index_js_2.cube2ScrambleGenerator);
48
49
  (0, index_js_1.registerScrambleGenerator)(index_js_3.cube3ScrambleGenerator);
49
50
  (0, index_js_1.registerScrambleGenerator)(index_js_4.cube4ScrambleGenerator);
@@ -53,6 +54,7 @@ const index_js_10 = require("./clock/index.js");
53
54
  (0, index_js_1.registerScrambleGenerator)(index_js_8.cube7ScrambleGenerator);
54
55
  (0, index_js_1.registerScrambleGenerator)(index_js_9.pyraminxScrambleGenerator);
55
56
  (0, index_js_1.registerScrambleGenerator)(index_js_10.clockScrambleGenerator);
57
+ (0, index_js_1.registerScrambleGenerator)(index_js_11.skewbScrambleGenerator);
56
58
  exports.cube2 = __importStar(require("./cube2/index.js"));
57
59
  exports.cube3 = __importStar(require("./cube3/index.js"));
58
60
  exports.cube4 = __importStar(require("./cube4/index.js"));
@@ -62,3 +64,4 @@ exports.cube6x6 = __importStar(require("./cube6x6/index.js"));
62
64
  exports.cube7x7 = __importStar(require("./cube7x7/index.js"));
63
65
  exports.pyraminx = __importStar(require("./pyraminx/index.js"));
64
66
  exports.clock = __importStar(require("./clock/index.js"));
67
+ exports.skewb = __importStar(require("./skewb/index.js"));
@@ -0,0 +1,103 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createSolvedPyraminxFacelets = createSolvedPyraminxFacelets;
4
+ exports.toPyraminxFacelets = toPyraminxFacelets;
5
+ exports.formatPyraminxFacelets = formatPyraminxFacelets;
6
+ exports.getPyraminxFaceOrder = getPyraminxFaceOrder;
7
+ const FACE_ORDER = ["F", "L", "R", "D"];
8
+ // positionId -> sticker slots (each slot = one triangle cell of the grid).
9
+ // Positions follow the scrambler piece model (moves.ts):
10
+ // edges 0..5 = UL, UR, UB, LR, LB, RB
11
+ // centers 0..3 = U, L, R, B
12
+ // tips 0..3 = U, L, R, B
13
+ //
14
+ // The slot layout is validated against the piece model by
15
+ // test/pyraminx-facelets.test.ts (single moves + randomized sequences).
16
+ const EDGE_POSITION_SLOTS = [
17
+ [["F", 1], ["L", 7]], // 0 UL
18
+ [["F", 3], ["R", 5]], // 1 UR
19
+ [["L", 2], ["R", 2]], // 2 UB
20
+ [["F", 6], ["D", 2]], // 3 LR
21
+ [["L", 5], ["D", 5]], // 4 LB
22
+ [["R", 7], ["D", 7]], // 5 RB
23
+ ];
24
+ const CENTER_POSITION_SLOTS = [
25
+ [["F", 2], ["L", 3], ["R", 1]], // 0 U
26
+ [["F", 5], ["L", 6], ["D", 1]], // 1 L
27
+ [["F", 7], ["R", 6], ["D", 3]], // 2 R
28
+ [["L", 1], ["R", 3], ["D", 6]], // 3 B
29
+ ];
30
+ const TIP_POSITION_SLOTS = [
31
+ [["F", 0], ["L", 4], ["R", 0]], // 0 U
32
+ [["F", 4], ["D", 0], ["L", 8]], // 1 L
33
+ [["F", 8], ["R", 8], ["D", 4]], // 2 R
34
+ [["L", 0], ["D", 8], ["R", 4]], // 3 B
35
+ ];
36
+ // pieceId -> sticker colors (canonical order aligned with the role order above)
37
+ const EDGE_PIECE_STICKERS = [
38
+ ["F", "L"], // 0 UL
39
+ ["F", "R"], // 1 UR
40
+ ["L", "R"], // 2 UB
41
+ ["F", "D"], // 3 LR
42
+ ["L", "D"], // 4 LB
43
+ ["R", "D"], // 5 RB
44
+ ];
45
+ const CENTER_PIECE_STICKERS = [
46
+ ["F", "L", "R"], // 0 U
47
+ ["F", "L", "D"], // 1 L
48
+ ["F", "R", "D"], // 2 R
49
+ ["L", "R", "D"], // 3 B
50
+ ];
51
+ const TIP_PIECE_STICKERS = [
52
+ ["F", "L", "R"], // 0 U
53
+ ["F", "D", "L"], // 1 L
54
+ ["F", "R", "D"], // 2 R
55
+ ["L", "D", "R"], // 3 B
56
+ ];
57
+ function createSolvedPyraminxFacelets() {
58
+ return {
59
+ F: Array.from({ length: 9 }, () => "F"),
60
+ L: Array.from({ length: 9 }, () => "L"),
61
+ R: Array.from({ length: 9 }, () => "R"),
62
+ D: Array.from({ length: 9 }, () => "D"),
63
+ };
64
+ }
65
+ function createEmptyPyraminxFacelets() {
66
+ return {
67
+ F: Array(9).fill("?"),
68
+ L: Array(9).fill("?"),
69
+ R: Array(9).fill("?"),
70
+ D: Array(9).fill("?"),
71
+ };
72
+ }
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;
79
+ }
80
+ }
81
+ function toPyraminxFacelets(state) {
82
+ const facelets = createEmptyPyraminxFacelets();
83
+ 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);
87
+ }
88
+ 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);
91
+ }
92
+ 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);
95
+ }
96
+ return facelets;
97
+ }
98
+ function formatPyraminxFacelets(facelets) {
99
+ return FACE_ORDER.map((face) => facelets[face].join("")).join("");
100
+ }
101
+ function getPyraminxFaceOrder() {
102
+ return FACE_ORDER;
103
+ }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.solvePyraminx = exports.generatePyraminxScramble = exports.pyraminxScrambleGenerator = exports.generateRandomPyraminxState = exports.isAllowedRandomPyraminxState = exports.validatePyraminxState = exports.permutationParity = exports.isSolvedPyraminxState = exports.pyraminxStatesEqual = exports.createSolvedPyraminxState = exports.clonePyraminxState = exports.parsePyraminxMove = exports.parsePyraminxAlgorithm = exports.invertPyraminxMove = exports.invertPyraminxAlgorithm = exports.formatPyraminxAlgorithm = exports.applyPyraminxMove = exports.applyPyraminxAlgorithm = exports.PYRAMINX_TIPS = exports.PYRAMINX_CORE_MOVES = void 0;
3
+ exports.solvePyraminx = exports.toPyraminxFacelets = exports.getPyraminxFaceOrder = exports.formatPyraminxFacelets = exports.createSolvedPyraminxFacelets = exports.generatePyraminxScramble = exports.pyraminxScrambleGenerator = exports.generateRandomPyraminxState = exports.isAllowedRandomPyraminxState = exports.validatePyraminxState = exports.permutationParity = exports.isSolvedPyraminxState = exports.pyraminxStatesEqual = exports.createSolvedPyraminxState = exports.clonePyraminxState = exports.parsePyraminxMove = exports.parsePyraminxAlgorithm = exports.invertPyraminxMove = exports.invertPyraminxAlgorithm = exports.formatPyraminxAlgorithm = exports.applyPyraminxMove = exports.applyPyraminxAlgorithm = exports.PYRAMINX_TIPS = exports.PYRAMINX_CORE_MOVES = void 0;
4
4
  var types_js_1 = require("./types.js");
5
5
  Object.defineProperty(exports, "PYRAMINX_CORE_MOVES", { enumerable: true, get: function () { return types_js_1.PYRAMINX_CORE_MOVES; } });
6
6
  Object.defineProperty(exports, "PYRAMINX_TIPS", { enumerable: true, get: function () { return types_js_1.PYRAMINX_TIPS; } });
@@ -26,5 +26,10 @@ Object.defineProperty(exports, "generateRandomPyraminxState", { enumerable: true
26
26
  var scrambler_js_1 = require("./scrambler.js");
27
27
  Object.defineProperty(exports, "pyraminxScrambleGenerator", { enumerable: true, get: function () { return scrambler_js_1.pyraminxScrambleGenerator; } });
28
28
  Object.defineProperty(exports, "generatePyraminxScramble", { enumerable: true, get: function () { return scrambler_js_1.generatePyraminxScramble; } });
29
+ var facelets_js_1 = require("./facelets.js");
30
+ Object.defineProperty(exports, "createSolvedPyraminxFacelets", { enumerable: true, get: function () { return facelets_js_1.createSolvedPyraminxFacelets; } });
31
+ Object.defineProperty(exports, "formatPyraminxFacelets", { enumerable: true, get: function () { return facelets_js_1.formatPyraminxFacelets; } });
32
+ Object.defineProperty(exports, "getPyraminxFaceOrder", { enumerable: true, get: function () { return facelets_js_1.getPyraminxFaceOrder; } });
33
+ Object.defineProperty(exports, "toPyraminxFacelets", { enumerable: true, get: function () { return facelets_js_1.toPyraminxFacelets; } });
29
34
  var solver_js_1 = require("./solver.js");
30
35
  Object.defineProperty(exports, "solvePyraminx", { enumerable: true, get: function () { return solver_js_1.solvePyraminx; } });