@houstonp/rubiks-cube 2.0.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 +494 -63
  2. package/package.json +22 -12
  3. package/src/core/index.js +478 -0
  4. package/src/rubiksCube/index.js +3 -0
  5. package/src/rubiksCube/rubiksCubeController.js +111 -0
  6. package/src/rubiksCube3D/centerPiece.js +79 -0
  7. package/src/rubiksCube3D/cornerPiece.js +114 -0
  8. package/src/rubiksCube3D/cubeConfig.js +87 -0
  9. package/src/rubiksCube3D/cubeSettings.js +30 -0
  10. package/src/rubiksCube3D/edgePiece.js +51 -0
  11. package/src/rubiksCube3D/index.js +3 -0
  12. package/src/rubiksCube3D/rubiksCube3D.js +383 -0
  13. package/src/rubiksCube3D/sticker.js +38 -0
  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/{cameraState.js → webComponent/cameraState.js} +17 -25
  19. package/src/webComponent/constants.js +67 -0
  20. package/src/{debouncer.js → webComponent/debouncer.js} +1 -1
  21. package/src/webComponent/index.js +7 -0
  22. package/src/webComponent/rubiksCubeElement.js +379 -0
  23. package/src/{settings.js → webComponent/settings.js} +47 -22
  24. package/tests/common.js +10 -0
  25. package/tests/core.test.js +56 -0
  26. package/tests/rubiksCube.solves.test.js +41 -0
  27. package/tests/rubiksCube3D.solves.test.js +185 -0
  28. package/tests/rubiksCubeState.solves.test.js +35 -0
  29. package/tests/setup.js +36 -0
  30. package/tests/testScrambles.js +194 -0
  31. package/types/core/index.d.ts +451 -0
  32. package/types/rubiksCube/index.d.ts +3 -0
  33. package/types/rubiksCube/rubiksCubeController.d.ts +62 -0
  34. package/types/rubiksCube3D/centerPiece.d.ts +27 -0
  35. package/types/rubiksCube3D/cornerPiece.d.ts +38 -0
  36. package/types/rubiksCube3D/cubeConfig.d.ts +32 -0
  37. package/types/rubiksCube3D/cubeSettings.d.ts +33 -0
  38. package/types/rubiksCube3D/edgePiece.d.ts +18 -0
  39. package/types/rubiksCube3D/index.d.ts +3 -0
  40. package/types/rubiksCube3D/rubiksCube3D.d.ts +120 -0
  41. package/types/rubiksCube3D/sticker.d.ts +18 -0
  42. package/types/state/index.d.ts +5 -0
  43. package/types/state/rubiksCubeState.d.ts +108 -0
  44. package/types/state/slice.d.ts +46 -0
  45. package/types/state/stickerState.d.ts +34 -0
  46. package/types/webComponent/cameraState.d.ts +22 -0
  47. package/types/webComponent/constants.d.ts +57 -0
  48. package/types/webComponent/index.d.ts +6 -0
  49. package/types/webComponent/rubiksCubeElement.d.ts +89 -0
  50. package/types/{settings.d.ts → webComponent/settings.d.ts} +9 -8
  51. package/src/core.js +0 -127
  52. package/src/cube/cube.js +0 -324
  53. package/src/cube/cubeRotation.js +0 -79
  54. package/src/cube/cubeSettings.js +0 -18
  55. package/src/cube/cubeState.js +0 -192
  56. package/src/cube/slice.js +0 -143
  57. package/src/index.js +0 -496
  58. package/src/schema.js +0 -22
  59. package/src/threejs/materials.js +0 -54
  60. package/src/threejs/pieces.js +0 -100
  61. package/src/threejs/stickers.js +0 -40
  62. package/types/cameraState.d.ts +0 -19
  63. package/types/core.d.ts +0 -125
  64. package/types/cube/cube.d.ts +0 -102
  65. package/types/cube/cubeRotation.d.ts +0 -33
  66. package/types/cube/cubeSettings.d.ts +0 -17
  67. package/types/cube/cubeState.d.ts +0 -16
  68. package/types/cube/slice.d.ts +0 -15
  69. package/types/index.d.ts +0 -65
  70. package/types/schema.d.ts +0 -11
  71. package/types/threejs/materials.d.ts +0 -21
  72. package/types/threejs/pieces.d.ts +0 -28
  73. package/types/threejs/stickers.d.ts +0 -6
  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
+ };