@houstonp/rubiks-cube 2.1.0 → 3.0.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 (76) hide show
  1. package/README.md +304 -77
  2. package/package.json +18 -8
  3. package/src/{core.js → core/index.js} +72 -41
  4. package/src/rubiksCube/index.js +3 -0
  5. package/src/rubiksCube/rubiksCubeController.js +111 -0
  6. package/src/{three → rubiksCube3D}/centerPiece.js +37 -2
  7. package/src/{three → rubiksCube3D}/cornerPiece.js +56 -2
  8. package/src/rubiksCube3D/cubeConfig.js +87 -0
  9. package/src/rubiksCube3D/cubeSettings.js +30 -0
  10. package/src/{three → rubiksCube3D}/edgePiece.js +2 -1
  11. package/src/rubiksCube3D/index.js +3 -0
  12. package/src/rubiksCube3D/rubiksCube3D.js +383 -0
  13. package/src/{three → rubiksCube3D}/sticker.js +5 -4
  14. package/src/state/index.js +4 -0
  15. package/src/state/rubiksCubeState.js +471 -0
  16. package/src/state/slice.js +236 -0
  17. package/src/state/stickerState.js +185 -0
  18. package/src/{camera → webComponent}/cameraState.js +17 -25
  19. package/src/webComponent/constants.js +67 -0
  20. package/src/webComponent/index.js +7 -0
  21. package/src/webComponent/rubiksCubeElement.js +379 -0
  22. package/src/{settings.js → webComponent/settings.js} +36 -23
  23. package/tests/common.js +3 -20
  24. package/tests/core.test.js +56 -0
  25. package/tests/rubiksCube.solves.test.js +41 -0
  26. package/tests/rubiksCube3D.solves.test.js +185 -0
  27. package/tests/rubiksCubeState.solves.test.js +35 -0
  28. package/tests/testScrambles.js +194 -0
  29. package/types/{core.d.ts → core/index.d.ts} +45 -48
  30. package/types/rubiksCube/index.d.ts +3 -0
  31. package/types/rubiksCube/rubiksCubeController.d.ts +62 -0
  32. package/types/rubiksCube3D/centerPiece.d.ts +27 -0
  33. package/types/rubiksCube3D/cornerPiece.d.ts +38 -0
  34. package/types/rubiksCube3D/cubeConfig.d.ts +32 -0
  35. package/types/rubiksCube3D/cubeSettings.d.ts +33 -0
  36. package/types/{three → rubiksCube3D}/edgePiece.d.ts +5 -3
  37. package/types/rubiksCube3D/index.d.ts +3 -0
  38. package/types/rubiksCube3D/rubiksCube3D.d.ts +120 -0
  39. package/types/rubiksCube3D/sticker.d.ts +18 -0
  40. package/types/state/index.d.ts +5 -0
  41. package/types/state/rubiksCubeState.d.ts +108 -0
  42. package/types/state/slice.d.ts +46 -0
  43. package/types/state/stickerState.d.ts +34 -0
  44. package/types/webComponent/cameraState.d.ts +22 -0
  45. package/types/webComponent/constants.d.ts +57 -0
  46. package/types/webComponent/index.d.ts +6 -0
  47. package/types/webComponent/rubiksCubeElement.d.ts +89 -0
  48. package/types/{settings.d.ts → webComponent/settings.d.ts} +5 -8
  49. package/src/cube/animationSlice.js +0 -205
  50. package/src/cube/animationState.js +0 -96
  51. package/src/cube/cubeSettings.js +0 -19
  52. package/src/cube/cubeState.js +0 -337
  53. package/src/cube/stickerState.js +0 -188
  54. package/src/index.js +0 -621
  55. package/src/three/cube.js +0 -492
  56. package/tests/cube.five.test.js +0 -126
  57. package/tests/cube.four.test.js +0 -126
  58. package/tests/cube.seven.test.js +0 -126
  59. package/tests/cube.six.test.js +0 -126
  60. package/tests/cube.three.test.js +0 -151
  61. package/tests/cube.two.test.js +0 -125
  62. package/types/camera/cameraState.d.ts +0 -19
  63. package/types/cube/animationSlice.d.ts +0 -26
  64. package/types/cube/animationState.d.ts +0 -41
  65. package/types/cube/cubeSettings.d.ts +0 -17
  66. package/types/cube/cubeState.d.ts +0 -47
  67. package/types/cube/stickerState.d.ts +0 -21
  68. package/types/index.d.ts +0 -87
  69. package/types/three/centerPiece.d.ts +0 -15
  70. package/types/three/cornerPiece.d.ts +0 -24
  71. package/types/three/cube.d.ts +0 -130
  72. package/types/three/sticker.d.ts +0 -15
  73. /package/src/{debouncer.js → webComponent/debouncer.js} +0 -0
  74. /package/src/{globals.ts → webComponent/globals.ts} +0 -0
  75. /package/types/{debouncer.d.ts → webComponent/debouncer.d.ts} +0 -0
  76. /package/types/{globals.d.ts → webComponent/globals.d.ts} +0 -0
@@ -0,0 +1,471 @@
1
+ /// @ts-check
2
+ import { Euler, Quaternion, Vector3 } from 'three';
3
+ import { CubeTypes, Faces, isMovement, IsRotation, Movements, reverse, translate } from '../core';
4
+ import { Axi, GetMovementSlice, GetRotationSlice } from './slice';
5
+ import { defaultStickerState, fromKociemba, getEmptyStickerState, getStickerFaceIndex, toKociemba } from './stickerState';
6
+ /** @import {StickerState} from './stickerState' */
7
+ /** @import {Rotation, CubeType, Movement, Face} from '../core' */
8
+ /** @import {Slice} from './slice' */
9
+
10
+ /**
11
+ * @typedef {{corners: pieceState[], edges: pieceState[], centers: pieceState[]}} state
12
+ */
13
+
14
+ /**
15
+ * @typedef {{position: vector, rotation: vector, stickers: {face: Face, direction: vector}[]}} pieceState
16
+ */
17
+
18
+ /**
19
+ * @typedef {{x: number,y: number,z: number}} vector
20
+ */
21
+
22
+ /**
23
+ * @typedef {{translate?: boolean | undefined, reverse?: boolean | undefined}} MoveOptions
24
+ */
25
+
26
+ /**
27
+ * @typedef {{reverse?: boolean | undefined}} RotationOptions
28
+ */
29
+
30
+ const Layers = {
31
+ [CubeTypes.Two]: [-1, 1],
32
+ [CubeTypes.Three]: [-1, 0, 1],
33
+ [CubeTypes.Four]: [-2, -1, 1, 2],
34
+ [CubeTypes.Five]: [-2, -1, 0, 1, 2],
35
+ [CubeTypes.Six]: [-3, -2, -1, 1, 2, 3],
36
+ [CubeTypes.Seven]: [-3, -2, -1, 0, 1, 2, 3],
37
+ };
38
+
39
+ const ERROR_MARGIN = 0.0001;
40
+ export class RubiksCubeState {
41
+ /**
42
+ *
43
+ * @param {CubeType} cubeType
44
+ */
45
+ constructor(cubeType) {
46
+ this.cubeType = cubeType;
47
+ /** @type {StickerState?} */
48
+ this.stickerState = null;
49
+ /** @type {number[]} */
50
+ this.layers = Layers[cubeType];
51
+ /** @type {pieceState[]} */
52
+ this.corners = corners(this.layers).map((corner) => {
53
+ return {
54
+ position: corner.position,
55
+ rotation: corner.rotation,
56
+ stickers: [
57
+ { face: Faces.U, direction: { x: 0, y: 0, z: 1 } },
58
+ { face: Faces.U, direction: { x: 0, y: 1, z: 0 } },
59
+ { face: Faces.U, direction: { x: 1, y: 0, z: 0 } },
60
+ ],
61
+ };
62
+ });
63
+ /** @type {pieceState[]} */
64
+ this.edges = edges(this.layers).map((edge) => {
65
+ return {
66
+ position: edge.position,
67
+ rotation: edge.rotation,
68
+ stickers: [
69
+ { face: Faces.U, direction: { x: 0, y: 0, z: 1 } },
70
+ { face: Faces.U, direction: { x: 0, y: 1, z: 0 } },
71
+ ],
72
+ };
73
+ });
74
+ /** @type {pieceState[]} */
75
+ this.centers = centers(this.layers).map((center) => {
76
+ return {
77
+ position: center.position,
78
+ rotation: center.rotation,
79
+ stickers: [{ face: Faces.U, direction: { x: 0, y: 0, z: 1 } }],
80
+ };
81
+ });
82
+ /** @type {StickerState?} */
83
+ this.stickerState = null;
84
+ this.setState(defaultStickerState(cubeType));
85
+ }
86
+
87
+ reset() {
88
+ this.stickerState = null;
89
+ this.setState(defaultStickerState(this.cubeType));
90
+ }
91
+
92
+ /**
93
+ * @param {StickerState} stickerState
94
+ * @returns {void}
95
+ */
96
+ setState(stickerState) {
97
+ this.stickerState = stickerState;
98
+ [...this.corners, ...this.edges, ...this.centers].forEach((piece) => {
99
+ piece.stickers.forEach((sticker) => {
100
+ const stickerPosition = new Vector3(sticker.direction.x, sticker.direction.y, sticker.direction.z);
101
+ stickerPosition.applyEuler(new Euler(piece.rotation.x, piece.rotation.y, piece.rotation.z));
102
+ stickerPosition.round();
103
+ const { face, i, j } = getStickerFaceIndex(stickerPosition, piece.position, this.layers);
104
+ sticker.face = stickerState[face][i][j];
105
+ });
106
+ });
107
+ }
108
+
109
+ /**
110
+ * @return {StickerState}
111
+ */
112
+ getState() {
113
+ if (this.stickerState) {
114
+ return this.stickerState;
115
+ }
116
+ const stickerState = getEmptyStickerState(this.cubeType);
117
+ [...this.corners, ...this.edges, ...this.centers].forEach((piece) => {
118
+ piece.stickers.forEach((sticker) => {
119
+ const stickerPosition = new Vector3(sticker.direction.x, sticker.direction.y, sticker.direction.z);
120
+ stickerPosition.applyEuler(new Euler(piece.rotation.x, piece.rotation.y, piece.rotation.z));
121
+ stickerPosition.round();
122
+ const { face, i, j } = getStickerFaceIndex(stickerPosition, piece.position, this.layers);
123
+ stickerState[face][i][j] = sticker.face;
124
+ });
125
+ });
126
+ this.stickerState = stickerState;
127
+ return this.stickerState;
128
+ }
129
+
130
+ /**
131
+ * @returns {string}
132
+ */
133
+ getKociemba() {
134
+ return toKociemba(this.getState());
135
+ }
136
+
137
+ /**
138
+ * @param {string} kociembaString
139
+ * @returns {boolean}
140
+ */
141
+ setKociemba(kociembaString) {
142
+ const stickerState = fromKociemba(kociembaString);
143
+ if (stickerState == null) {
144
+ return false;
145
+ }
146
+ this.setState(stickerState);
147
+ return true;
148
+ }
149
+
150
+ /**
151
+ *
152
+ * @param {Slice} slice
153
+ */
154
+ slice(slice) {
155
+ [...this.corners, ...this.edges, ...this.centers]
156
+ .filter((piece) => {
157
+ switch (slice.axis) {
158
+ case Axi.x:
159
+ return slice.layerIds.map((id) => this.layers[id]).some((layer) => Math.abs(layer - piece.position.x) < ERROR_MARGIN);
160
+ case Axi.y:
161
+ return slice.layerIds.map((id) => this.layers[id]).some((layer) => Math.abs(layer - piece.position.y) < ERROR_MARGIN);
162
+ case Axi.z:
163
+ return slice.layerIds.map((id) => this.layers[id]).some((layer) => Math.abs(layer - piece.position.z) < ERROR_MARGIN);
164
+ }
165
+ })
166
+ .forEach((piece) => {
167
+ const position = new Vector3(piece.position.x, piece.position.y, piece.position.z);
168
+ const rotation = new Quaternion().setFromEuler(new Euler(piece.rotation.x, piece.rotation.y, piece.rotation.z));
169
+ const rotationAxis = new Vector3(
170
+ slice.axis === Axi.x ? slice.direction : 0,
171
+ slice.axis === Axi.y ? slice.direction : 0,
172
+ slice.axis === Axi.z ? slice.direction : 0,
173
+ ).normalize();
174
+
175
+ const angle = (Math.abs(slice.direction) * Math.PI) / 2;
176
+
177
+ // Apply rotation
178
+ const rotationQuat = new Quaternion();
179
+ rotationQuat.setFromAxisAngle(rotationAxis, angle);
180
+ position.applyQuaternion(rotationQuat);
181
+ rotation.premultiply(rotationQuat);
182
+
183
+ // Update piece position
184
+ piece.position.x = this.layers[this._getLayerNumber(position.x)];
185
+ piece.position.y = this.layers[this._getLayerNumber(position.y)];
186
+ piece.position.z = this.layers[this._getLayerNumber(position.z)];
187
+
188
+ // Update rotation using quaternion multiplication
189
+ const newRotation = new Euler().setFromQuaternion(rotation);
190
+ piece.rotation.x = newRotation.x;
191
+ piece.rotation.y = newRotation.y;
192
+ piece.rotation.z = newRotation.z;
193
+ });
194
+ this.stickerState = null;
195
+ }
196
+
197
+ /**
198
+ * @param {Movement} movement
199
+ * @param {MoveOptions} [options]
200
+ * @returns {Slice?}
201
+ */
202
+ move(movement, options) {
203
+ let action = movement;
204
+ if (options?.reverse) {
205
+ action = reverse(movement);
206
+ }
207
+ if (options?.translate) {
208
+ action = translate(movement, this.cubeType);
209
+ }
210
+ const slice = GetMovementSlice(movement, this.layers.length);
211
+ if (slice == null) {
212
+ console.error(`Failed to get movement slice. Invalid movement: [${movement}]`);
213
+ return null;
214
+ }
215
+ this.slice(slice);
216
+ return slice;
217
+ }
218
+
219
+ /**
220
+ * @param {Rotation} rotation
221
+ * @param {RotationOptions} [options]
222
+ * @returns {Slice?}
223
+ */
224
+ rotate(rotation, options) {
225
+ let action = rotation;
226
+ if (options?.reverse) {
227
+ action = reverse(rotation);
228
+ }
229
+ const slice = GetRotationSlice(action, this.layers.length);
230
+ if (slice == null) {
231
+ console.error(`Failed to get rotation slice. invalid rotation: [${action}]`);
232
+ return null;
233
+ }
234
+ this.slice(slice);
235
+ return slice;
236
+ }
237
+
238
+ /**
239
+ * @param {(Rotation | Movement)[]} actions
240
+ * @param {MoveOptions | RotationOptions } [options]
241
+ */
242
+ do(actions, options) {
243
+ actions.forEach((action) => {
244
+ if (isMovement(action)) {
245
+ this.move(/** @type {Movement} */ (action), options);
246
+ } else if (IsRotation(action)) {
247
+ this.rotate(/** @type {Rotation} */ (action), options);
248
+ } else {
249
+ console.error(`Invalid Notation: ${action}`);
250
+ }
251
+ });
252
+ }
253
+
254
+ /**
255
+ * @private
256
+ * @param {number} position
257
+ * @returns {number}
258
+ */
259
+ _getLayerNumber(position) {
260
+ for (let i = 0; i < this.layers.length; i++) {
261
+ if (Math.abs(position - this.layers[i]) < ERROR_MARGIN) {
262
+ return i;
263
+ }
264
+ }
265
+ throw new Error(`Failed to get layer number. position ${position} not found in layers ${this.layers}`);
266
+ }
267
+ }
268
+
269
+ /**
270
+ * @param {number[]} layers
271
+ * @return {{position: vector, rotation: vector}[]}
272
+ */
273
+ export const corners = (layers) => {
274
+ const lastLayer = layers[layers.length - 1];
275
+ const firstLayer = layers[0];
276
+ return [
277
+ {
278
+ position: { x: lastLayer, y: lastLayer, z: lastLayer },
279
+ rotation: { x: 0, y: 0, z: 0 },
280
+ },
281
+ {
282
+ position: { x: lastLayer, y: lastLayer, z: firstLayer },
283
+ rotation: { x: 0, y: Math.PI / 2, z: 0 },
284
+ },
285
+ {
286
+ position: { x: lastLayer, y: firstLayer, z: lastLayer },
287
+ rotation: { x: 0, y: Math.PI / 2, z: Math.PI },
288
+ },
289
+ {
290
+ position: { x: lastLayer, y: firstLayer, z: firstLayer },
291
+ rotation: { x: 0, y: Math.PI, z: Math.PI },
292
+ },
293
+ {
294
+ position: { x: firstLayer, y: lastLayer, z: lastLayer },
295
+ rotation: { x: 0, y: -Math.PI / 2, z: 0 },
296
+ },
297
+ {
298
+ position: { x: firstLayer, y: lastLayer, z: firstLayer },
299
+ rotation: { x: 0, y: Math.PI, z: 0 },
300
+ },
301
+ {
302
+ position: { x: firstLayer, y: firstLayer, z: lastLayer },
303
+ rotation: { x: 0, y: 0, z: Math.PI },
304
+ },
305
+ {
306
+ position: { x: firstLayer, y: firstLayer, z: firstLayer },
307
+ rotation: { x: 0, y: -Math.PI / 2, z: Math.PI },
308
+ },
309
+ ];
310
+ };
311
+
312
+ /**
313
+ * @param {number[]} layers
314
+ * @return {{position: vector, rotation: vector}[]}
315
+ */
316
+ export const centers = (layers) => {
317
+ const lastLayer = layers[layers.length - 1];
318
+ const firstLayer = layers[0];
319
+ const innerLayers = layers.slice(1, -1);
320
+ return [
321
+ //right
322
+ ...innerLayers.flatMap((layer1) =>
323
+ innerLayers.map((layer2) => {
324
+ return {
325
+ position: { x: lastLayer, y: layer1, z: layer2 },
326
+ rotation: { x: 0, y: Math.PI / 2, z: 0 },
327
+ };
328
+ }),
329
+ ),
330
+ //up
331
+ ...innerLayers.flatMap((layer1) =>
332
+ innerLayers.map((layer2) => {
333
+ return {
334
+ position: { x: layer1, y: lastLayer, z: layer2 },
335
+ rotation: { x: -Math.PI / 2, y: 0, z: 0 },
336
+ };
337
+ }),
338
+ ),
339
+ //front
340
+ ...innerLayers.flatMap((layer1) =>
341
+ innerLayers.map((layer2) => {
342
+ return {
343
+ position: { x: layer1, y: layer2, z: lastLayer },
344
+ rotation: { x: 0, y: 0, z: 0 },
345
+ };
346
+ }),
347
+ ),
348
+ //back
349
+ ...innerLayers.flatMap((layer1) =>
350
+ innerLayers.map((layer2) => {
351
+ return {
352
+ position: { x: layer1, y: layer2, z: firstLayer },
353
+ rotation: { x: 0, y: Math.PI, z: 0 },
354
+ };
355
+ }),
356
+ ),
357
+ //down
358
+ ...innerLayers.flatMap((layer1) =>
359
+ innerLayers.map((layer2) => {
360
+ return {
361
+ position: { x: layer1, y: firstLayer, z: layer2 },
362
+ rotation: { x: Math.PI / 2, y: 0, z: 0 },
363
+ };
364
+ }),
365
+ ),
366
+ //left
367
+ ...innerLayers.flatMap((layer1) =>
368
+ innerLayers.map((layer2) => {
369
+ return {
370
+ position: { x: firstLayer, y: layer1, z: layer2 },
371
+ rotation: { x: 0, y: -Math.PI / 2, z: 0 },
372
+ };
373
+ }),
374
+ ),
375
+ ];
376
+ };
377
+
378
+ /**
379
+ * @param {number[]} layers
380
+ * @return {{position: vector, rotation: vector}[]}
381
+ */
382
+ export const edges = (layers) => {
383
+ const lastLayer = layers[layers.length - 1];
384
+ const firstLayer = layers[0];
385
+ return [
386
+ // RU
387
+ ...layers.map((layer) => {
388
+ return {
389
+ position: { x: lastLayer, y: lastLayer, z: layer },
390
+ rotation: { x: 0, y: Math.PI / 2, z: 0 },
391
+ };
392
+ }),
393
+ // RF
394
+ ...layers.map((layer) => {
395
+ return {
396
+ position: { x: lastLayer, y: layer, z: lastLayer },
397
+ rotation: { x: 0, y: 0, z: -Math.PI / 2 },
398
+ };
399
+ }),
400
+ // RB
401
+ ...layers.map((layer) => {
402
+ return {
403
+ position: { x: lastLayer, y: layer, z: firstLayer },
404
+ rotation: { x: 0, y: Math.PI / 2, z: -Math.PI / 2 },
405
+ };
406
+ }),
407
+ // RD
408
+ ...layers.map((layer) => {
409
+ return {
410
+ position: { x: lastLayer, y: firstLayer, z: layer },
411
+ rotation: { x: Math.PI, y: Math.PI / 2, z: 0 },
412
+ };
413
+ }),
414
+ // UF
415
+ ...layers.map((layer) => {
416
+ return {
417
+ position: { x: layer, y: lastLayer, z: lastLayer },
418
+ rotation: { x: 0, y: 0, z: 0 },
419
+ };
420
+ }),
421
+ // UB
422
+ ...layers.map((layer) => {
423
+ return {
424
+ position: { x: layer, y: lastLayer, z: firstLayer },
425
+ rotation: { x: -Math.PI / 2, y: 0, z: 0 },
426
+ };
427
+ }),
428
+ // DF
429
+ ...layers.map((layer) => {
430
+ return {
431
+ position: { x: layer, y: firstLayer, z: lastLayer },
432
+ rotation: { x: Math.PI / 2, y: 0, z: 0 },
433
+ };
434
+ }),
435
+ // DB
436
+ ...layers.map((layer) => {
437
+ return {
438
+ position: { x: layer, y: firstLayer, z: firstLayer },
439
+ rotation: { x: Math.PI, y: 0, z: 0 },
440
+ };
441
+ }),
442
+ // LU
443
+ ...layers.map((layer) => {
444
+ return {
445
+ position: { x: firstLayer, y: lastLayer, z: layer },
446
+ rotation: { x: 0, y: -Math.PI / 2, z: 0 },
447
+ };
448
+ }),
449
+ // LF
450
+ ...layers.map((layer) => {
451
+ return {
452
+ position: { x: firstLayer, y: layer, z: lastLayer },
453
+ rotation: { x: 0, y: 0, z: Math.PI / 2 },
454
+ };
455
+ }),
456
+ // LB
457
+ ...layers.map((layer) => {
458
+ return {
459
+ position: { x: firstLayer, y: layer, z: firstLayer },
460
+ rotation: { x: 0, y: -Math.PI / 2, z: Math.PI / 2 },
461
+ };
462
+ }),
463
+ // LD
464
+ ...layers.map((layer) => {
465
+ return {
466
+ position: { x: firstLayer, y: firstLayer, z: layer },
467
+ rotation: { x: 0, y: -Math.PI / 2, z: Math.PI },
468
+ };
469
+ }),
470
+ ];
471
+ };
@@ -0,0 +1,236 @@
1
+ // @ts-check
2
+ import { Rotations } from '../core';
3
+ /** @import {Movement, Rotation} from '../core' */
4
+
5
+ /** @typedef {typeof Axi[keyof typeof Axi]} Axis */
6
+ export const Axi = Object.freeze({
7
+ x: 'x',
8
+ y: 'y',
9
+ z: 'z',
10
+ });
11
+
12
+ /**
13
+ * A slice represents an action or movement of a 3D rubiks cube. layers
14
+ * @typedef Slice
15
+ * @property {Axis} axis the axis that pieces rotate around
16
+ * @property {number[]} layerIds starting from 0 to the size of the cube. Layers represent what layers are included in the movement.
17
+ * @property {number} direction the direction and magnitude of the rotation
18
+ **/
19
+
20
+ /**
21
+ * @param {Movement} movement
22
+ * @param {number} layerCount
23
+ * @returns {Slice | undefined}
24
+ */
25
+ export function GetMovementSlice(movement, layerCount) {
26
+ const result = RegExp(`^([1234567]|[123456]-[1234567])?([RLUDFB]w|[RLUDFBMES]|[rludfbmes])([123])?(\')?$`).exec(movement);
27
+ if (result == null) {
28
+ console.error(`Failed to parse movement. Invalid movement: ${movement}`);
29
+ return undefined;
30
+ }
31
+ /** @type {number | undefined} */
32
+ let layerRangeLower = undefined;
33
+ /** @type {number | undefined} */
34
+ let layerRangeUpper = undefined;
35
+ /** @type {number | undefined} */
36
+ let layerNumber = undefined;
37
+ if (result[1]?.includes('-')) {
38
+ layerRangeLower = parseInt(result[1][0]);
39
+ layerRangeUpper = parseInt(result[1][2]);
40
+ } else {
41
+ layerNumber = result[1] ? parseInt(result[1]) : undefined;
42
+ }
43
+
44
+ if (layerRangeLower != null && layerRangeUpper != null) {
45
+ if (layerRangeLower >= layerRangeUpper || layerRangeUpper > layerCount || layerRangeLower > layerCount - 1) {
46
+ console.error(`${movement} is not valid for the current cubeType. For range inputs like x-yr it should follow that 1 <= x < y <= ${layerCount}.`);
47
+ return undefined;
48
+ }
49
+ }
50
+ if (layerNumber != null && layerNumber > layerCount) {
51
+ console.error(`${movement} is not valid for the current cubeType. For inputs like xR it should follow that x <= ${layerCount}.`);
52
+ return undefined;
53
+ }
54
+
55
+ const movementType = result[2];
56
+ const rotationNumber = result[3] ? parseInt(result[3]) % 4 : 1;
57
+ const isPrime = result[4] === "'";
58
+ const direction = (isPrime ? -1 : 1) * rotationNumber;
59
+
60
+ let axis = undefined;
61
+ switch (movementType) {
62
+ case 'R':
63
+ case 'Rw':
64
+ case 'r':
65
+ case 'L':
66
+ case 'Lw':
67
+ case 'l':
68
+ case 'M':
69
+ case 'm':
70
+ axis = Axi.x;
71
+ break;
72
+ case 'U':
73
+ case 'Uw':
74
+ case 'u':
75
+ case 'D':
76
+ case 'Dw':
77
+ case 'd':
78
+ case 'E':
79
+ case 'e':
80
+ axis = Axi.y;
81
+ break;
82
+ case 'F':
83
+ case 'Fw':
84
+ case 'f':
85
+ case 'B':
86
+ case 'Bw':
87
+ case 'b':
88
+ case 'S':
89
+ case 's':
90
+ axis = Axi.z;
91
+ break;
92
+ default:
93
+ console.error(`${movement} is invalid. Invalid movementType ${movementType}.`);
94
+ }
95
+ if (axis == null) {
96
+ return undefined;
97
+ }
98
+
99
+ switch (movementType) {
100
+ case 'R':
101
+ case 'U':
102
+ case 'F': {
103
+ layerNumber = layerNumber ? layerNumber : 1;
104
+ const layerIndex = layerCount - layerNumber;
105
+ let sliceLayers = [layerIndex];
106
+ if (layerRangeLower != null && layerRangeUpper != null) {
107
+ sliceLayers = range(layerCount - layerRangeUpper, layerCount - (layerRangeLower - 1));
108
+ }
109
+ return { axis, layerIds: sliceLayers, direction: -direction };
110
+ }
111
+ case 'L':
112
+ case 'D':
113
+ case 'B': {
114
+ layerNumber = layerNumber ? layerNumber : 1;
115
+ const layerIndex = layerNumber - 1;
116
+ let sliceLayers = [layerIndex];
117
+ if (layerRangeLower != null && layerRangeUpper != null) {
118
+ sliceLayers = range(layerRangeLower - 1, layerRangeUpper);
119
+ }
120
+ return { axis, layerIds: sliceLayers, direction };
121
+ }
122
+ case 'Rw':
123
+ case 'Uw':
124
+ case 'Fw':
125
+ case 'r':
126
+ case 'u':
127
+ case 'f': {
128
+ layerNumber = layerNumber ? layerNumber : 2;
129
+ let sliceLayers = range(layerCount - layerNumber, layerCount);
130
+ if (layerRangeLower != null && layerRangeUpper != null) {
131
+ sliceLayers = range(layerCount - layerRangeUpper, layerCount - (layerRangeLower - 1));
132
+ }
133
+ return { axis, layerIds: sliceLayers, direction: -direction };
134
+ }
135
+ case 'Lw':
136
+ case 'Dw':
137
+ case 'Bw':
138
+ case 'l':
139
+ case 'd':
140
+ case 'b': {
141
+ layerNumber = layerNumber ? layerNumber : 2;
142
+ let sliceLayers = range(0, layerNumber);
143
+ if (layerRangeLower != null && layerRangeUpper != null) {
144
+ sliceLayers = range(layerRangeLower - 1, layerRangeUpper);
145
+ }
146
+ return { axis, layerIds: sliceLayers, direction };
147
+ }
148
+ case 'M':
149
+ case 'E': {
150
+ layerNumber = layerNumber ? layerNumber : 1;
151
+ const lower = Math.max(Math.floor(layerCount / 2) - (layerNumber - 1), 1);
152
+ const upper = Math.min(Math.ceil(layerCount / 2) + (layerNumber - 1), layerCount - 1);
153
+ let sliceLayers = range(lower, upper);
154
+ if (layerRangeLower != null && layerRangeUpper != null) {
155
+ sliceLayers = range(layerCount - layerRangeUpper, layerCount - (layerRangeLower - 1));
156
+ }
157
+ return { axis, layerIds: sliceLayers, direction };
158
+ }
159
+ case 'm':
160
+ case 'e': {
161
+ layerNumber = layerNumber ? layerNumber : 1;
162
+ let sliceLayers = range(layerNumber, layerCount - layerNumber);
163
+ if (layerRangeLower != null && layerRangeUpper != null) {
164
+ sliceLayers = range(layerCount - layerRangeUpper, layerCount - (layerRangeLower - 1));
165
+ }
166
+ return { axis, layerIds: sliceLayers, direction };
167
+ }
168
+ case 'S': {
169
+ layerNumber = layerNumber ? layerNumber : 1;
170
+ const lower = Math.max(Math.floor(layerCount / 2) - (layerNumber - 1), 1);
171
+ const upper = Math.min(Math.ceil(layerCount / 2) + (layerNumber - 1), layerCount - 1);
172
+ let sliceLayers = range(lower, upper);
173
+ if (layerRangeLower != null && layerRangeUpper != null) {
174
+ sliceLayers = range(layerRangeLower - 1, layerRangeUpper);
175
+ }
176
+ return { axis, layerIds: sliceLayers, direction: -direction };
177
+ }
178
+ case 's': {
179
+ layerNumber = layerNumber ? layerNumber : 1;
180
+ let sliceLayers = range(layerNumber, layerCount - layerNumber);
181
+ if (layerRangeLower != null && layerRangeUpper != null) {
182
+ sliceLayers = range(layerRangeLower - 1, layerRangeUpper);
183
+ }
184
+ return { axis, layerIds: sliceLayers, direction: -direction };
185
+ }
186
+ default:
187
+ console.error(`${movement} is invalid. Invalid movementType ${movementType}.`);
188
+ return undefined;
189
+ }
190
+ }
191
+
192
+ /**
193
+ * @param {Rotation} rotation
194
+ * @param {number} layerCount
195
+ * @returns {Slice | undefined}
196
+ */
197
+ export function GetRotationSlice(rotation, layerCount) {
198
+ const result = RegExp(`^([xyz])(\\d)?(\')?$`).exec(rotation);
199
+ if (result == null) {
200
+ console.error(`Failed to parse rotation. invalid rotation: [${rotation}]`);
201
+ return undefined;
202
+ }
203
+ const rotationType = result[1];
204
+ const rotationNumber = result[2] ? parseInt(result[2]) : 1;
205
+
206
+ const isPrime = result[3] === "'";
207
+ const direction = (isPrime ? 1 : -1) * (rotationNumber % 4);
208
+ switch (rotationType) {
209
+ case Rotations.x:
210
+ return { axis: Axi.x, layerIds: range(layerCount), direction };
211
+ case Rotations.y:
212
+ return { axis: Axi.y, layerIds: range(layerCount), direction };
213
+ case Rotations.z:
214
+ return { axis: Axi.z, layerIds: range(layerCount), direction };
215
+ default:
216
+ console.error(`Failed to get rotation slice. invalid rotationType: ${rotationType}`);
217
+ return undefined;
218
+ }
219
+ }
220
+
221
+ /**
222
+ *
223
+ * @param {number} start
224
+ * @param {number} [stop]
225
+ * @param {number} [step=1]
226
+ * @returns {number[]}
227
+ */
228
+ const range = (start, stop, step = 1) => {
229
+ if (stop === undefined) {
230
+ stop = start;
231
+ start = 0;
232
+ }
233
+
234
+ const length = Math.max(Math.ceil((stop - start) / step), 0);
235
+ return Array.from({ length }, (_, i) => start + i * step);
236
+ };