@cubingcenter/scrambler 0.1.0 → 0.2.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.
@@ -1 +1 @@
1
- {"version":3,"file":"scrambler.d.ts","sourceRoot":"","sources":["../../../src/puzzles/cube3/scrambler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAW9F,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,CA+CzB"}
1
+ {"version":3,"file":"scrambler.d.ts","sourceRoot":"","sources":["../../../src/puzzles/cube3/scrambler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAW9F,eAAO,MAAM,sBAAsB,EAAE,iBAYpC,CAAC;AAEF,wBAAsB,qBAAqB,CACzC,OAAO,GAAE,eAAe,GAAG;IAAE,cAAc,CAAC,EAAE,OAAO,CAAA;CAAO,GAC3D,OAAO,CAAC,cAAc,CAAC,CAgDzB"}
@@ -42,6 +42,7 @@ export async function generateCube3Scramble(options = {}) {
42
42
  metadata: {
43
43
  generator: "cube3-two-phase",
44
44
  generatorVersion: "1.0.0",
45
+ facelets,
45
46
  faceletString: formatCube3Facelets(facelets),
46
47
  state: JSON.parse(JSON.stringify(state)),
47
48
  solutionMoves: solution.moves,
@@ -7,9 +7,9 @@ export const cube4ScrambleGenerator = {
7
7
  eventId: "444",
8
8
  name: "4x4x4 Cube",
9
9
  puzzle: "cube-4x4",
10
- quality: "random-move",
11
- generator: "cube4-random-move",
12
- description: "Random-move 4x4 scramble with 40 moves."
10
+ quality: "random-state",
11
+ generator: "cubing-scramble",
12
+ description: "Random-state 4x4 scramble generated by cubing.js."
13
13
  },
14
14
  generate(options = {}) {
15
15
  return generateCube4Scramble(options);
@@ -0,0 +1,7 @@
1
+ import type { PyraminxState } from "./types.js";
2
+ export type PyraminxFacelets = Record<"F" | "L" | "R" | "D", readonly string[]>;
3
+ export declare function createSolvedPyraminxFacelets(): PyraminxFacelets;
4
+ export declare function toPyraminxFacelets(state: PyraminxState): PyraminxFacelets;
5
+ export declare function formatPyraminxFacelets(facelets: PyraminxFacelets): string;
6
+ export declare function getPyraminxFaceOrder(): readonly ("F" | "L" | "R" | "D")[];
7
+ //# sourceMappingURL=facelets.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,97 @@
1
+ const FACE_ORDER = ["F", "L", "R", "D"];
2
+ // positionId -> sticker slots (each slot = one triangle cell of the grid).
3
+ // Positions follow the scrambler piece model (moves.ts):
4
+ // edges 0..5 = UL, UR, UB, LR, LB, RB
5
+ // centers 0..3 = U, L, R, B
6
+ // tips 0..3 = U, L, R, B
7
+ //
8
+ // The slot layout is validated against the piece model by
9
+ // test/pyraminx-facelets.test.ts (single moves + randomized sequences).
10
+ const EDGE_POSITION_SLOTS = [
11
+ [["F", 1], ["L", 7]], // 0 UL
12
+ [["F", 3], ["R", 5]], // 1 UR
13
+ [["L", 2], ["R", 2]], // 2 UB
14
+ [["F", 6], ["D", 2]], // 3 LR
15
+ [["L", 5], ["D", 5]], // 4 LB
16
+ [["R", 7], ["D", 7]], // 5 RB
17
+ ];
18
+ const CENTER_POSITION_SLOTS = [
19
+ [["F", 2], ["L", 3], ["R", 1]], // 0 U
20
+ [["F", 5], ["L", 6], ["D", 1]], // 1 L
21
+ [["F", 7], ["R", 6], ["D", 3]], // 2 R
22
+ [["L", 1], ["R", 3], ["D", 6]], // 3 B
23
+ ];
24
+ const TIP_POSITION_SLOTS = [
25
+ [["F", 0], ["L", 4], ["R", 0]], // 0 U
26
+ [["F", 4], ["D", 0], ["L", 8]], // 1 L
27
+ [["F", 8], ["R", 8], ["D", 4]], // 2 R
28
+ [["L", 0], ["D", 8], ["R", 4]], // 3 B
29
+ ];
30
+ // pieceId -> sticker colors (canonical order aligned with the role order above)
31
+ const EDGE_PIECE_STICKERS = [
32
+ ["F", "L"], // 0 UL
33
+ ["F", "R"], // 1 UR
34
+ ["L", "R"], // 2 UB
35
+ ["F", "D"], // 3 LR
36
+ ["L", "D"], // 4 LB
37
+ ["R", "D"], // 5 RB
38
+ ];
39
+ const CENTER_PIECE_STICKERS = [
40
+ ["F", "L", "R"], // 0 U
41
+ ["F", "L", "D"], // 1 L
42
+ ["F", "R", "D"], // 2 R
43
+ ["L", "R", "D"], // 3 B
44
+ ];
45
+ const TIP_PIECE_STICKERS = [
46
+ ["F", "L", "R"], // 0 U
47
+ ["F", "D", "L"], // 1 L
48
+ ["F", "R", "D"], // 2 R
49
+ ["L", "D", "R"], // 3 B
50
+ ];
51
+ export function createSolvedPyraminxFacelets() {
52
+ return {
53
+ F: Array.from({ length: 9 }, () => "F"),
54
+ L: Array.from({ length: 9 }, () => "L"),
55
+ R: Array.from({ length: 9 }, () => "R"),
56
+ D: Array.from({ length: 9 }, () => "D"),
57
+ };
58
+ }
59
+ function createEmptyPyraminxFacelets() {
60
+ return {
61
+ F: Array(9).fill("?"),
62
+ L: Array(9).fill("?"),
63
+ R: Array(9).fill("?"),
64
+ D: Array(9).fill("?"),
65
+ };
66
+ }
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;
73
+ }
74
+ }
75
+ export function toPyraminxFacelets(state) {
76
+ const facelets = createEmptyPyraminxFacelets();
77
+ 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);
81
+ }
82
+ 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);
85
+ }
86
+ 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);
89
+ }
90
+ return facelets;
91
+ }
92
+ export function formatPyraminxFacelets(facelets) {
93
+ return FACE_ORDER.map((face) => facelets[face].join("")).join("");
94
+ }
95
+ export function getPyraminxFaceOrder() {
96
+ return FACE_ORDER;
97
+ }
@@ -5,5 +5,7 @@ export { clonePyraminxState, createSolvedPyraminxState, pyraminxStatesEqual, isS
5
5
  export { permutationParity, validatePyraminxState, isAllowedRandomPyraminxState } from "./validation.js";
6
6
  export { generateRandomPyraminxState } from "./random-state.js";
7
7
  export { pyraminxScrambleGenerator, generatePyraminxScramble } from "./scrambler.js";
8
+ export { createSolvedPyraminxFacelets, formatPyraminxFacelets, getPyraminxFaceOrder, toPyraminxFacelets } from "./facelets.js";
9
+ export type { PyraminxFacelets } from "./facelets.js";
8
10
  export { solvePyraminx } from "./solver.js";
9
11
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/puzzles/pyraminx/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,EACnB,aAAa,EACd,MAAM,YAAY,CAAC;AACpB,YAAY,EACV,YAAY,EACZ,aAAa,EACb,gBAAgB,EAChB,eAAe,EACf,0BAA0B,EAC3B,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,sBAAsB,EACtB,iBAAiB,EACjB,uBAAuB,EACvB,uBAAuB,EACvB,kBAAkB,EAClB,sBAAsB,EACtB,iBAAiB,EAClB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,kBAAkB,EAClB,yBAAyB,EACzB,mBAAmB,EACnB,qBAAqB,EACtB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,4BAA4B,EAC7B,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,2BAA2B,EAC5B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,yBAAyB,EACzB,wBAAwB,EACzB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,aAAa,EACd,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/puzzles/pyraminx/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,EACnB,aAAa,EACd,MAAM,YAAY,CAAC;AACpB,YAAY,EACV,YAAY,EACZ,aAAa,EACb,gBAAgB,EAChB,eAAe,EACf,0BAA0B,EAC3B,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,sBAAsB,EACtB,iBAAiB,EACjB,uBAAuB,EACvB,uBAAuB,EACvB,kBAAkB,EAClB,sBAAsB,EACtB,iBAAiB,EAClB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,kBAAkB,EAClB,yBAAyB,EACzB,mBAAmB,EACnB,qBAAqB,EACtB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,4BAA4B,EAC7B,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,2BAA2B,EAC5B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,yBAAyB,EACzB,wBAAwB,EACzB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,4BAA4B,EAC5B,sBAAsB,EACtB,oBAAoB,EACpB,kBAAkB,EACnB,MAAM,eAAe,CAAC;AACvB,YAAY,EACV,gBAAgB,EACjB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,aAAa,EACd,MAAM,aAAa,CAAC"}
@@ -4,4 +4,5 @@ export { clonePyraminxState, createSolvedPyraminxState, pyraminxStatesEqual, isS
4
4
  export { permutationParity, validatePyraminxState, isAllowedRandomPyraminxState } from "./validation.js";
5
5
  export { generateRandomPyraminxState } from "./random-state.js";
6
6
  export { pyraminxScrambleGenerator, generatePyraminxScramble } from "./scrambler.js";
7
+ export { createSolvedPyraminxFacelets, formatPyraminxFacelets, getPyraminxFaceOrder, toPyraminxFacelets } from "./facelets.js";
7
8
  export { solvePyraminx } from "./solver.js";
@@ -1 +1 @@
1
- {"version":3,"file":"moves.d.ts","sourceRoot":"","sources":["../../../src/puzzles/pyraminx/moves.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAqC,MAAM,YAAY,CAAC;AAKjG,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,aAAa,EAAE,IAAI,EAAE,YAAY,GAAG,aAAa,CA4BzF;AA8DD,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,SAAS,YAAY,EAAE,GAAG,aAAa,CAE1G;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,SAAS,YAAY,EAAE,GAAG,YAAY,EAAE,CAEtF;AAED,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,YAAY,EAAE,CAMxE;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,SAAS,YAAY,EAAE,GAAG,MAAM,CAE9E;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,YAAY,CAU7D;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,YAAY,GAAG,YAAY,CAKnE"}
1
+ {"version":3,"file":"moves.d.ts","sourceRoot":"","sources":["../../../src/puzzles/pyraminx/moves.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAqC,MAAM,YAAY,CAAC;AAKjG,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,aAAa,EAAE,IAAI,EAAE,YAAY,GAAG,aAAa,CA4BzF;AA2DD,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,SAAS,YAAY,EAAE,GAAG,aAAa,CAE1G;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,SAAS,YAAY,EAAE,GAAG,YAAY,EAAE,CAEtF;AAED,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,YAAY,EAAE,CAMxE;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,SAAS,YAAY,EAAE,GAAG,MAAM,CAE9E;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,YAAY,CAU7D;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,YAAY,GAAG,YAAY,CAKnE"}
@@ -30,56 +30,53 @@ function applySingleCoreMoveCW(state, face) {
30
30
  const centers = state.centers;
31
31
  const perm = state.edgesPerm;
32
32
  const orient = state.edgesOrient;
33
+ const tips = state.tips;
33
34
  // Clone arrays for temporary storage
34
35
  const prevPerm = [...perm];
35
36
  const prevOrient = [...orient];
36
37
  if (face === "U") {
37
- // Center
38
+ // U: UL -> UB -> UR -> UL with the physical orientation (matches viz)
38
39
  centers[0] = (centers[0] + 1) % 3;
39
- // Edges cycle: 0 -> 1 -> 2 -> 0 (UL -> UR -> UB -> UL)
40
- // No orientation flips for U
41
- perm[1] = prevPerm[0];
42
- perm[2] = prevPerm[1];
43
- perm[0] = prevPerm[2];
44
- orient[1] = prevOrient[0];
45
- orient[2] = prevOrient[1];
46
- orient[0] = prevOrient[2];
40
+ tips[0] = (tips[0] + 1) % 3;
41
+ perm[0] = prevPerm[1];
42
+ perm[1] = prevPerm[2];
43
+ perm[2] = prevPerm[0];
44
+ orient[0] = (prevOrient[1] + 1) % 2;
45
+ orient[1] = (prevOrient[2] + 1) % 2;
46
+ orient[2] = prevOrient[0];
47
47
  }
48
48
  else if (face === "L") {
49
- // Center
50
- centers[1] = (centers[1] + 1) % 3;
51
- // Edges cycle: 0 -> 4 -> 3 -> 0 (UL -> LB -> LR -> UL)
52
- // Flips: UL(0) flips, LB(4) flips, LR(3) does not flip
53
- perm[4] = prevPerm[0];
54
- perm[3] = prevPerm[4];
55
- perm[0] = prevPerm[3];
56
- orient[4] = (prevOrient[0] + 1) % 2;
57
- orient[3] = (prevOrient[4] + 1) % 2;
58
- orient[0] = prevOrient[3];
49
+ // L: UL -> LB -> LR -> UL (physical, matches viz)
50
+ centers[1] = (centers[1] + 2) % 3;
51
+ tips[1] = (tips[1] + 1) % 3;
52
+ perm[0] = prevPerm[4];
53
+ perm[3] = prevPerm[0];
54
+ perm[4] = prevPerm[3];
55
+ orient[0] = prevOrient[4];
56
+ orient[3] = (prevOrient[0] + 1) % 2;
57
+ orient[4] = (prevOrient[3] + 1) % 2;
59
58
  }
60
59
  else if (face === "R") {
61
- // Center
60
+ // R: UR -> LR -> RB -> UR (physical, matches viz)
62
61
  centers[2] = (centers[2] + 1) % 3;
63
- // Edges cycle: 1 -> 3 -> 5 -> 1 (UR -> LR -> RB -> UR)
64
- // Flips: UR(1) flips, LR(3) flips, RB(5) does not flip
65
- perm[3] = prevPerm[1];
66
- perm[5] = prevPerm[3];
67
- perm[1] = prevPerm[5];
68
- orient[3] = (prevOrient[1] + 1) % 2;
69
- orient[5] = (prevOrient[3] + 1) % 2;
70
- orient[1] = prevOrient[5];
62
+ tips[2] = (tips[2] + 1) % 3;
63
+ perm[1] = prevPerm[3];
64
+ perm[3] = prevPerm[5];
65
+ perm[5] = prevPerm[1];
66
+ orient[1] = (prevOrient[3] + 1) % 2;
67
+ orient[3] = (prevOrient[5] + 1) % 2;
68
+ orient[5] = prevOrient[1];
71
69
  }
72
70
  else if (face === "B") {
73
- // Center
74
- centers[3] = (centers[3] + 1) % 3;
75
- // Edges cycle: 2 -> 5 -> 4 -> 2 (UB -> RB -> LB -> UB)
76
- // Flips: UB(2) flips, RB(5) flips, LB(4) does not flip
77
- perm[5] = prevPerm[2];
78
- perm[4] = prevPerm[5];
79
- perm[2] = prevPerm[4];
80
- orient[5] = (prevOrient[2] + 1) % 2;
81
- orient[4] = (prevOrient[5] + 1) % 2;
82
- orient[2] = prevOrient[4];
71
+ // B: UB -> LB -> RB -> UB (physical, matches viz)
72
+ centers[3] = (centers[3] + 2) % 3;
73
+ tips[3] = (tips[3] + 1) % 3;
74
+ perm[2] = prevPerm[5];
75
+ perm[4] = prevPerm[2];
76
+ perm[5] = prevPerm[4];
77
+ orient[2] = prevOrient[5];
78
+ orient[4] = (prevOrient[2] + 1) % 2;
79
+ orient[5] = (prevOrient[4] + 1) % 2;
83
80
  }
84
81
  }
85
82
  export function applyPyraminxAlgorithm(state, moves) {
@@ -1 +1 @@
1
- {"version":3,"file":"scrambler.d.ts","sourceRoot":"","sources":["../../../src/puzzles/pyraminx/scrambler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAO9F,eAAO,MAAM,yBAAyB,EAAE,iBAYvC,CAAC;AAEF,wBAAsB,wBAAwB,CAC5C,OAAO,GAAE,eAAe,GAAG;IAAE,cAAc,CAAC,EAAE,OAAO,CAAA;CAAO,GAC3D,OAAO,CAAC,cAAc,CAAC,CAyCzB"}
1
+ {"version":3,"file":"scrambler.d.ts","sourceRoot":"","sources":["../../../src/puzzles/pyraminx/scrambler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAQ9F,eAAO,MAAM,yBAAyB,EAAE,iBAYvC,CAAC;AAEF,wBAAsB,wBAAwB,CAC5C,OAAO,GAAE,eAAe,GAAG;IAAE,cAAc,CAAC,EAAE,OAAO,CAAA;CAAO,GAC3D,OAAO,CAAC,cAAc,CAAC,CA+CzB"}
@@ -2,6 +2,7 @@ import { generateRandomPyraminxState } from "./random-state.js";
2
2
  import { createSolvedPyraminxState } from "./state.js";
3
3
  import { applyPyraminxAlgorithm, invertPyraminxAlgorithm } from "./moves.js";
4
4
  import { solvePyraminx } from "./solver.js";
5
+ import { formatPyraminxFacelets, toPyraminxFacelets } from "./facelets.js";
5
6
  export const pyraminxScrambleGenerator = {
6
7
  info: {
7
8
  eventId: "pyrso",
@@ -20,15 +21,17 @@ export async function generatePyraminxScramble(options = {}) {
20
21
  // Solve the core edges and centers
21
22
  const solution = solvePyraminx(state);
22
23
  const scrambleMoves = invertPyraminxAlgorithm(solution);
23
- // Append tips to scrambleMoves
24
+ // Append tips: layer moves already rotate tips, so we need the delta
25
+ // between the target tips and the tips produced by the core scramble.
26
+ const coreState = applyPyraminxAlgorithm(createSolvedPyraminxState(), scrambleMoves);
24
27
  const finalMoves = [...scrambleMoves];
25
28
  const tipsNotation = ["u", "l", "r", "b"];
26
29
  for (let i = 0; i < 4; i++) {
27
- const tipVal = state.tips[i];
28
- if (tipVal === 1) {
30
+ const delta = (state.tips[i] - coreState.tips[i] + 3) % 3;
31
+ if (delta === 1) {
29
32
  finalMoves.push(tipsNotation[i]);
30
33
  }
31
- else if (tipVal === 2) {
34
+ else if (delta === 2) {
32
35
  finalMoves.push(`${tipsNotation[i]}'`);
33
36
  }
34
37
  }
@@ -41,6 +44,7 @@ export async function generatePyraminxScramble(options = {}) {
41
44
  throw new Error("Generated Pyraminx scramble does not reproduce the target random state.");
42
45
  }
43
46
  }
47
+ const facelets = toPyraminxFacelets(state);
44
48
  return {
45
49
  eventId: "pyrso",
46
50
  scramble: finalMoves.join(" "),
@@ -49,6 +53,8 @@ export async function generatePyraminxScramble(options = {}) {
49
53
  generator: "pyraminx-ida-star",
50
54
  metadata: {
51
55
  state,
56
+ facelets,
57
+ faceletString: formatPyraminxFacelets(facelets),
52
58
  solutionMoves: solution,
53
59
  solutionDepth: solution.length
54
60
  }
@@ -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);
@@ -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; } });
@@ -39,56 +39,53 @@ function applySingleCoreMoveCW(state, face) {
39
39
  const centers = state.centers;
40
40
  const perm = state.edgesPerm;
41
41
  const orient = state.edgesOrient;
42
+ const tips = state.tips;
42
43
  // Clone arrays for temporary storage
43
44
  const prevPerm = [...perm];
44
45
  const prevOrient = [...orient];
45
46
  if (face === "U") {
46
- // Center
47
+ // U: UL -> UB -> UR -> UL with the physical orientation (matches viz)
47
48
  centers[0] = (centers[0] + 1) % 3;
48
- // Edges cycle: 0 -> 1 -> 2 -> 0 (UL -> UR -> UB -> UL)
49
- // No orientation flips for U
50
- perm[1] = prevPerm[0];
51
- perm[2] = prevPerm[1];
52
- perm[0] = prevPerm[2];
53
- orient[1] = prevOrient[0];
54
- orient[2] = prevOrient[1];
55
- orient[0] = prevOrient[2];
49
+ tips[0] = (tips[0] + 1) % 3;
50
+ perm[0] = prevPerm[1];
51
+ perm[1] = prevPerm[2];
52
+ perm[2] = prevPerm[0];
53
+ orient[0] = (prevOrient[1] + 1) % 2;
54
+ orient[1] = (prevOrient[2] + 1) % 2;
55
+ orient[2] = prevOrient[0];
56
56
  }
57
57
  else if (face === "L") {
58
- // Center
59
- centers[1] = (centers[1] + 1) % 3;
60
- // Edges cycle: 0 -> 4 -> 3 -> 0 (UL -> LB -> LR -> UL)
61
- // Flips: UL(0) flips, LB(4) flips, LR(3) does not flip
62
- perm[4] = prevPerm[0];
63
- perm[3] = prevPerm[4];
64
- perm[0] = prevPerm[3];
65
- orient[4] = (prevOrient[0] + 1) % 2;
66
- orient[3] = (prevOrient[4] + 1) % 2;
67
- orient[0] = prevOrient[3];
58
+ // L: UL -> LB -> LR -> UL (physical, matches viz)
59
+ centers[1] = (centers[1] + 2) % 3;
60
+ tips[1] = (tips[1] + 1) % 3;
61
+ perm[0] = prevPerm[4];
62
+ perm[3] = prevPerm[0];
63
+ perm[4] = prevPerm[3];
64
+ orient[0] = prevOrient[4];
65
+ orient[3] = (prevOrient[0] + 1) % 2;
66
+ orient[4] = (prevOrient[3] + 1) % 2;
68
67
  }
69
68
  else if (face === "R") {
70
- // Center
69
+ // R: UR -> LR -> RB -> UR (physical, matches viz)
71
70
  centers[2] = (centers[2] + 1) % 3;
72
- // Edges cycle: 1 -> 3 -> 5 -> 1 (UR -> LR -> RB -> UR)
73
- // Flips: UR(1) flips, LR(3) flips, RB(5) does not flip
74
- perm[3] = prevPerm[1];
75
- perm[5] = prevPerm[3];
76
- perm[1] = prevPerm[5];
77
- orient[3] = (prevOrient[1] + 1) % 2;
78
- orient[5] = (prevOrient[3] + 1) % 2;
79
- orient[1] = prevOrient[5];
71
+ tips[2] = (tips[2] + 1) % 3;
72
+ perm[1] = prevPerm[3];
73
+ perm[3] = prevPerm[5];
74
+ perm[5] = prevPerm[1];
75
+ orient[1] = (prevOrient[3] + 1) % 2;
76
+ orient[3] = (prevOrient[5] + 1) % 2;
77
+ orient[5] = prevOrient[1];
80
78
  }
81
79
  else if (face === "B") {
82
- // Center
83
- centers[3] = (centers[3] + 1) % 3;
84
- // Edges cycle: 2 -> 5 -> 4 -> 2 (UB -> RB -> LB -> UB)
85
- // Flips: UB(2) flips, RB(5) flips, LB(4) does not flip
86
- perm[5] = prevPerm[2];
87
- perm[4] = prevPerm[5];
88
- perm[2] = prevPerm[4];
89
- orient[5] = (prevOrient[2] + 1) % 2;
90
- orient[4] = (prevOrient[5] + 1) % 2;
91
- orient[2] = prevOrient[4];
80
+ // B: UB -> LB -> RB -> UB (physical, matches viz)
81
+ centers[3] = (centers[3] + 2) % 3;
82
+ tips[3] = (tips[3] + 1) % 3;
83
+ perm[2] = prevPerm[5];
84
+ perm[4] = prevPerm[2];
85
+ perm[5] = prevPerm[4];
86
+ orient[2] = prevOrient[5];
87
+ orient[4] = (prevOrient[2] + 1) % 2;
88
+ orient[5] = (prevOrient[4] + 1) % 2;
92
89
  }
93
90
  }
94
91
  function applyPyraminxAlgorithm(state, moves) {
@@ -6,6 +6,7 @@ const random_state_js_1 = require("./random-state.js");
6
6
  const state_js_1 = require("./state.js");
7
7
  const moves_js_1 = require("./moves.js");
8
8
  const solver_js_1 = require("./solver.js");
9
+ const facelets_js_1 = require("./facelets.js");
9
10
  exports.pyraminxScrambleGenerator = {
10
11
  info: {
11
12
  eventId: "pyrso",
@@ -24,15 +25,17 @@ async function generatePyraminxScramble(options = {}) {
24
25
  // Solve the core edges and centers
25
26
  const solution = (0, solver_js_1.solvePyraminx)(state);
26
27
  const scrambleMoves = (0, moves_js_1.invertPyraminxAlgorithm)(solution);
27
- // Append tips to scrambleMoves
28
+ // Append tips: layer moves already rotate tips, so we need the delta
29
+ // between the target tips and the tips produced by the core scramble.
30
+ const coreState = (0, moves_js_1.applyPyraminxAlgorithm)((0, state_js_1.createSolvedPyraminxState)(), scrambleMoves);
28
31
  const finalMoves = [...scrambleMoves];
29
32
  const tipsNotation = ["u", "l", "r", "b"];
30
33
  for (let i = 0; i < 4; i++) {
31
- const tipVal = state.tips[i];
32
- if (tipVal === 1) {
34
+ const delta = (state.tips[i] - coreState.tips[i] + 3) % 3;
35
+ if (delta === 1) {
33
36
  finalMoves.push(tipsNotation[i]);
34
37
  }
35
- else if (tipVal === 2) {
38
+ else if (delta === 2) {
36
39
  finalMoves.push(`${tipsNotation[i]}'`);
37
40
  }
38
41
  }
@@ -45,6 +48,7 @@ async function generatePyraminxScramble(options = {}) {
45
48
  throw new Error("Generated Pyraminx scramble does not reproduce the target random state.");
46
49
  }
47
50
  }
51
+ const facelets = (0, facelets_js_1.toPyraminxFacelets)(state);
48
52
  return {
49
53
  eventId: "pyrso",
50
54
  scramble: finalMoves.join(" "),
@@ -53,6 +57,8 @@ async function generatePyraminxScramble(options = {}) {
53
57
  generator: "pyraminx-ida-star",
54
58
  metadata: {
55
59
  state,
60
+ facelets,
61
+ faceletString: (0, facelets_js_1.formatPyraminxFacelets)(facelets),
56
62
  solutionMoves: solution,
57
63
  solutionDepth: solution.length
58
64
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cubingcenter/scrambler",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Extensible scramble generation toolkit for CubingCenter projects.",
5
5
  "type": "module",
6
6
  "license": "MIT",