@houstonp/rubiks-cube 1.5.2 → 2.1.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 (51) hide show
  1. package/README.md +280 -170
  2. package/package.json +33 -3
  3. package/src/camera/cameraState.js +81 -0
  4. package/src/core.js +447 -0
  5. package/src/cube/animationSlice.js +205 -0
  6. package/src/cube/animationState.js +96 -0
  7. package/src/cube/cubeSettings.js +19 -0
  8. package/src/cube/cubeState.js +285 -139
  9. package/src/cube/stickerState.js +188 -0
  10. package/src/debouncer.js +16 -0
  11. package/src/globals.ts +9 -0
  12. package/src/index.js +621 -0
  13. package/src/settings.js +138 -0
  14. package/src/three/centerPiece.js +44 -0
  15. package/src/three/cornerPiece.js +60 -0
  16. package/src/three/cube.js +492 -0
  17. package/src/three/edgePiece.js +50 -0
  18. package/src/three/sticker.js +37 -0
  19. package/tests/common.js +27 -0
  20. package/tests/cube.five.test.js +126 -0
  21. package/tests/cube.four.test.js +126 -0
  22. package/tests/cube.seven.test.js +126 -0
  23. package/tests/cube.six.test.js +126 -0
  24. package/tests/cube.three.test.js +151 -0
  25. package/tests/cube.two.test.js +125 -0
  26. package/tests/setup.js +36 -0
  27. package/types/camera/cameraState.d.ts +19 -0
  28. package/types/core.d.ts +454 -0
  29. package/types/cube/animationSlice.d.ts +26 -0
  30. package/types/cube/animationState.d.ts +41 -0
  31. package/types/cube/cubeSettings.d.ts +17 -0
  32. package/types/cube/cubeState.d.ts +47 -0
  33. package/types/cube/stickerState.d.ts +21 -0
  34. package/types/debouncer.d.ts +13 -0
  35. package/types/globals.d.ts +7 -0
  36. package/types/index.d.ts +87 -0
  37. package/types/settings.d.ts +38 -0
  38. package/types/three/centerPiece.d.ts +15 -0
  39. package/types/three/cornerPiece.d.ts +24 -0
  40. package/types/three/cube.d.ts +130 -0
  41. package/types/three/edgePiece.d.ts +16 -0
  42. package/types/three/sticker.d.ts +15 -0
  43. package/.prettierrc +0 -7
  44. package/index.js +0 -274
  45. package/src/cube/cube.js +0 -276
  46. package/src/cube/cubeRotation.js +0 -63
  47. package/src/threejs/materials.js +0 -42
  48. package/src/threejs/pieces.js +0 -103
  49. package/src/threejs/stickers.js +0 -48
  50. package/src/utils/debouncer.js +0 -7
  51. package/src/utils/rotation.js +0 -53
package/README.md CHANGED
@@ -1,17 +1,29 @@
1
1
  # Rubiks Cube Web Component
2
2
 
3
- This package is a rubiks cube web component built with threejs. Camera animation smoothing is done with the gsap package.
3
+ A Rubik's Cube web component built with Three.js, WebGPU, and GSAP. The cube renders into a shadow‑DOM canvas and exposes a small, promise‑based API for cube moves, rotations, reset, state setting, and camera "peek" positions. Supports 2x2, 3x3, 4x4, 5x5, 6x6, and 7x7 Rubik's cubes.
4
4
 
5
5
  ![cube](cube.png)
6
6
 
7
+ ## Installation
8
+
9
+ This package is published as `@houstonp/rubiks-cube`.
10
+
11
+ ```bash
12
+ bun add @houstonp/rubiks-cube
13
+ # or
14
+ npm install @houstonp/rubiks-cube
15
+ ```
16
+
7
17
  ## Adding the component
8
18
 
9
- You can add the component to a webpage by adding an import statement in the index.js file. And then
10
- by adding the webcomponent tag.
19
+ Register the custom element and then use the tag in your HTML.
11
20
 
12
21
  ```js
13
- //index.js
14
- import '@houstonp/rubiks-cube';
22
+ // index.js
23
+ import { RubiksCubeElement } from '@houstonp/rubiks-cube';
24
+
25
+ // Registers <rubiks-cube> (you can pass a different tag name if you prefer)
26
+ RubiksCubeElement.register();
15
27
  ```
16
28
 
17
29
  ```html
@@ -19,195 +31,283 @@ import '@houstonp/rubiks-cube';
19
31
  <html lang="en">
20
32
  <head>
21
33
  <meta charset="utf-8" />
34
+ <title>Rubiks Cube Demo</title>
22
35
  </head>
23
36
  <body>
24
- <rubiks-cube animation-speed-ms="1000" animation-style="exponential" piece-gap="1.04" camera-speed="100"> </rubiks-cube>
37
+ <!-- Create a 3x3 cube with custom settings -->
38
+ <rubiks-cube cube-type="Three" animation-speed-ms="1000" animation-style="exponential" piece-gap="1.04" camera-speed-ms="100"></rubiks-cube>
39
+
40
+ <!-- Or create a 2x2 cube -->
41
+ <rubiks-cube cube-type="Two"></rubiks-cube>
42
+
43
+ <!-- Or create a 7x7 cube -->
44
+ <rubiks-cube cube-type="Seven"></rubiks-cube>
45
+
25
46
  <script type="module" src="index.js"></script>
26
47
  </body>
27
48
  </html>
28
49
  ```
29
50
 
30
- ## component attributes
51
+ ## Component attributes
52
+
53
+ These attributes control animation, spacing, camera behavior, and cube type. The available attributes
54
+ can be imported so that they can be get and set easily.
55
+
56
+ ```js
57
+ import { RubiksCubeElement, AttributeNames } from '@houstonp/rubiks-cube';
58
+ import { CubeTypes, AnimationStyles } from '@houstonp/rubiks-cube/core';
59
+
60
+ const cube = document.querySelector('rubiks-cube');
61
+
62
+ // Get an attribute value
63
+ const animationSpeed = cube.getAttribute(AttributeNames.animationSpeed);
64
+ console.log('Current animation speed:', animationSpeed);
65
+
66
+ // Set an attribute value
67
+ cube.setAttribute(AttributeNames.animationSpeed, '500');
68
+ cube.setAttribute(AttributeNames.cubeType, CubeTypes.Four); // Change to 4x4 cube
69
+ cube.setAttribute(AttributeNames.animationStyle, AnimationStyles.Exponential);
70
+ cube.setAttribute(AttributeNames.pieceGap, '1.05');
71
+ cube.setAttribute(AttributeNames.cameraRadius, '6');
72
+ cube.setAttribute(AttributeNames.cameraFieldOfView, '80');
73
+ cube.setAttribute(AttributeNames.cameraPeekAngleHorizontal, '0.7');
74
+ cube.setAttribute(AttributeNames.cameraPeekAngleVertical, '0.7');
75
+ ```
76
+
77
+ | attribute | accepted values | Description |
78
+ | ---------------------------- | ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
79
+ | cube-type | `"Two"`, `"Three"`, `"Four"`, `"Five"`, `"Six"`, `"Seven"` | Sets the cube size (2x2 through 7x7). Default is `"Three"` |
80
+ | animation-speed-ms | integer greater than or equal to 0 | Sets the duration of cube animations in milliseconds |
81
+ | animation-style | `"exponential"`, `"next"`, `"fixed"`, `"match"` | `fixed`: fixed animation lengths, `next`: skips to next animation, `exponential`: speeds up successive animations, `match`: matches the speed to the frequency of events |
82
+ | piece-gap | greater than 1 | Sets the gap between Rubik's Cube pieces |
83
+ | camera-speed-ms | greater than or equal to 0 | Sets the duration of camera animations in milliseconds |
84
+ | camera-radius | greater than or equal to 4 | Sets the camera radius |
85
+ | camera-peek-angle-horizontal | decimal between 0 and 1 | Sets the horizontal peek angle |
86
+ | camera-peek-angle-vertical | decimal between 0 and 1 | Sets the vertical peek angle |
87
+ | camera-field-of-view | integer between 40 and 100 | Sets the field of view of the camera |
31
88
 
32
- | attribute | accepted values | Description |
33
- | ---------------------------- | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
34
- | animation-speed-ms | integer greater than or equal to 0 | sets the duration of the animations in milliseconds |
35
- | animation-style | "exponetial", "next", "fixed", "match" | fixed: fixed animation lengths, next: skips to next animation, exponential: speeds up successive animations, match: matches the speed the frequency of events |
36
- | piece-gap | greater than 1 | sets the gap between rubiks cube pieces |
37
- | camera-speed-ms | greater than or equal to 0 | sets the duration of camera animations in milliseconds |
38
- | camera-radius | greater than or equal to 4 | sets the camera radius |
39
- | camera-peek-angle-horizontal | decimal between 0 and 1 | sets the horizontal peek angle |
40
- | camera-peek-angle-vertical | decimal between 0 and 1 | sets the vertical peek angle |
41
- | camera-field-of-view | integer between 40 and 100 | sets the fielf of view of the camera |
89
+ ## Programmatic control
42
90
 
43
- ## state of the component
91
+ The `RubiksCubeElement` instance exposes async methods that return the cube state after the operation completes:
44
92
 
45
- A state event occurs when a movement animation is completed. The event details contains the current state of the cube along with the eventId of the animation. The state is an object containing the stickers of each face. A sticker is either "up", "down", "left", "right", "front" or "back".
93
+ ### Move
46
94
 
47
- To listen for the state event, add an event listener to the rubiks-cube element.
95
+ Performs a cube movement and resolves with the new state string.
48
96
 
49
97
  ```js
98
+ import { RubiksCubeElement } from '@houstonp/rubiks-cube';
99
+ import { Movements } from '@houstonp/rubiks-cube/core';
100
+
50
101
  const cube = document.querySelector('rubiks-cube');
51
- cube.addEventListener('state', (e) => {
52
- console.log(e.detail);
53
- });
54
- /*
55
- {
56
- eventId: "guid-guid-guid-guid-guid",
57
- state: {
58
- up: [
59
- [sticker, sticker, sticker],
60
- [sticker, sticker, sticker],
61
- [sticker, sticker, sticker],
62
- ],
63
- down: [
64
- [sticker, sticker, sticker],
65
- [sticker, sticker, sticker],
66
- [sticker, sticker, sticker],
67
- ],
68
- left: [
69
- [sticker, sticker, sticker],
70
- [sticker, sticker, sticker],
71
- [sticker, sticker, sticker],
72
- ],
73
- right: [
74
- [sticker, sticker, sticker],
75
- [sticker, sticker, sticker],
76
- [sticker, sticker, sticker],
77
- ],
78
- front: [
79
- [sticker, sticker, sticker],
80
- [sticker, sticker, sticker],
81
- [sticker, sticker, sticker],
82
- ],
83
- back: [
84
- [sticker, sticker, sticker],
85
- [sticker, sticker, sticker],
86
- [sticker, sticker, sticker],
87
- ],
88
- }
102
+
103
+ // Single layer moves
104
+ await cube.move(Movements.Single.R); // Right face clockwise
105
+ await cube.move(Movements.Single.R2); // Right face 180 degrees
106
+ await cube.move(Movements.Single.RP); // Right face counter-clockwise
107
+ await cube.move(Movements.Single.U); // Upper face clockwise
108
+ await cube.move(Movements.Single.FP); // Front face counter-clockwise
109
+
110
+ // Wide moves
111
+ await cube.move(Movements.Wide.Rw); // Wide right move
112
+ await cube.move(Movements.Wide.r); // Right two layers
113
+
114
+ // Layer-specific moves (for 4x4+ cubes)
115
+ await cube.move(Movements.Two.R); // Second layer right
116
+ await cube.move(Movements.Three.R); // Third layer right (for 4x4+)
117
+ await cube.move(Movements.Four.R); // Fourth layer right (for 5x5+)
118
+
119
+ // Middle layer moves
120
+ await cube.move(Movements.Single.M); // Middle layer
121
+ await cube.move(Movements.Single.E); // Equatorial layer
122
+ await cube.move(Movements.Single.S); // Standing layer
123
+
124
+ // Chain multiple moves
125
+ const moves = [Movements.Single.R, Movements.Single.U, Movements.Single.RP, Movements.Single.UP];
126
+ for (const move of moves) {
127
+ const state = await cube.move(move);
128
+ console.log('State after', move, ':', state);
89
129
  }
90
- */
91
130
  ```
92
131
 
93
- ## Rubiks Cube Notation
94
-
95
- Notations can include the number of roations of a face. For example, `U2` means rotate the upper face 180 degrees.
132
+ ### Rotate
96
133
 
97
- Noations can also include a prime symbol `'` to indicate a counter clockwise rotation. For example, `U'` means rotate the upper face counter clockwise. The direction is always determined relative to the face being moved.
134
+ Rotates the entire cube and resolves with the new state string.
98
135
 
99
- When both a number and a prime symbol are included the number is stated before the prime symbol. For example, `U2'` means rotate the upper face 180 degrees counter clockwise. and `U'2` is invalid.
100
-
101
- | Notation | Movement |
102
- | -------- | ------------------------------------------------ |
103
- | U | Top face clockwise |
104
- | u | Top two layers clockwise |
105
- | D | Bottom face clockwise |
106
- | d | Bottom two layers clockwise |
107
- | L | Left face clockwise |
108
- | l | Left two layers clockwise |
109
- | R | Right face clockwise |
110
- | r | Right two layers clockwise |
111
- | F | Front face clockwise |
112
- | f | Front two layers clockwise |
113
- | B | Back face clockwise |
114
- | b | Back two layers clockwise |
115
- | M | Middle layer clockwise (relative to L) |
116
- | E | Equatorial layer clockwise (relative to D) |
117
- | S | Standing layer clockwise (relative to F) |
118
- | x | Rotate cube on x axis clockwise (direction of R) |
119
- | y | Rotate cube on y axis clockwise (direction of U) |
120
- | z | Rotate cube on z axis clockwise (direction of F) |
136
+ ```js
137
+ import { RubiksCubeElement } from '@houstonp/rubiks-cube';
138
+ import { Rotations } from '@houstonp/rubiks-cube/core';
121
139
 
122
- these symbols are used as actionIds for the action event below.
140
+ const cube = document.querySelector('rubiks-cube');
123
141
 
124
- ## Updating the component
142
+ // Rotate cube on x-axis (like R move)
143
+ await cube.rotate(Rotations.x); // 90 degrees clockwise
144
+ await cube.rotate(Rotations.x2); // 180 degrees
145
+ await cube.rotate(Rotations.xP); // 90 degrees counter-clockwise
125
146
 
126
- The Rubiks cube web component listens for custom events to perform twists, rotations and camera changes. As per convention, the starting rotation has green facing forward, white facing up and red facing to the right.
147
+ // Rotate cube on y-axis (like U move)
148
+ await cube.rotate(Rotations.y); // 90 degrees clockwise
149
+ await cube.rotate(Rotations.y2); // 180 degrees
150
+ await cube.rotate(Rotations.yP); // 90 degrees counter-clockwise
127
151
 
128
- ### Reset event
152
+ // Rotate cube on z-axis (like F move)
153
+ await cube.rotate(Rotations.z); // 90 degrees clockwise
154
+ await cube.rotate(Rotations.z2); // 180 degrees
155
+ await cube.rotate(Rotations.zP); // 90 degrees counter-clockwise
156
+ ```
129
157
 
130
- The rubiks-cube element listens for the `reset` custom event and resets the cube to its initial state.
158
+ ### Reset
131
159
 
132
- #### Example
160
+ Resets the cube to the solved state and resolves with the new state string.
133
161
 
134
162
  ```js
163
+ import { RubiksCubeElement } from '@houstonp/rubiks-cube';
164
+ import { Movements } from '@houstonp/rubiks-cube/core';
165
+
135
166
  const cube = document.querySelector('rubiks-cube');
136
- cube.dispatchEvent(new CustomEvent('reset'));
167
+
168
+ // Reset to solved state
169
+ const solvedState = await cube.reset();
170
+ console.log('Cube reset to solved state:', solvedState);
171
+
172
+ // Reset after performing some moves
173
+ await cube.move(Movements.Single.R);
174
+ await cube.move(Movements.Single.U);
175
+ const resetState = await cube.reset();
137
176
  ```
138
177
 
139
- ### Action event
178
+ ### SetState
140
179
 
141
- The rubiks-cube element listens for the `action` custom event and moves the camera to the specified position.
180
+ Sets the cube to a specific state using a Kociemba-format state string. This allows you to restore a previously saved state or set up specific cube configurations.
142
181
 
143
182
  ```js
144
- {
145
- eventId: string,
146
- action: {
147
- type: "movement" | "camera" | "rotation",
148
- actionId: string
149
- }
183
+ import { RubiksCubeElement } from '@houstonp/rubiks-cube';
184
+ import { Movements } from '@houstonp/rubiks-cube/core';
185
+
186
+ const cube = document.querySelector('rubiks-cube');
187
+
188
+ // Save current state
189
+ const currentState = await cube.move(Movements.Single.R);
190
+
191
+ // Later, restore that state
192
+ try {
193
+ const restoredState = await cube.setState(currentState);
194
+ console.log('State restored:', restoredState);
195
+ } catch (error) {
196
+ console.error('Failed to set state:', error);
197
+ // Error occurs if the state string is invalid for the current cube type
150
198
  }
151
- ```
152
199
 
153
- ```js
154
- var event = new CustomEvent('action', {
155
- detail: { eventId: 'guid-guid-guid-guid-guid', action: { type: 'camera', actionId: 'peek-toggle-horizontal' } },
156
- });
200
+ // Set a specific scrambled state (example for 3x3)
201
+ const scrambledState = 'UULUUFUUFRRUBRRURRFFDFFUFFFDDRDDDDDDBLLLLLLLLBRRBBBBBB';
202
+ await cube.setState(scrambledState);
157
203
  ```
158
204
 
159
- #### Camera action event
205
+ ### Peek
206
+
207
+ Animates the camera to a new "peek" position and resolves with the new peek state.
160
208
 
161
- action IDs for camera actions are as follows
209
+ ```js
210
+ import { RubiksCubeElement } from '@houstonp/rubiks-cube';
211
+ import { PeekTypes, PeekStates } from '@houstonp/rubiks-cube/core';
162
212
 
163
- - `peek-right` - Camera is moved to the right of the cube so that the right face is visible
164
- - `peek-left` - Camera is moved to the left of the cube so that the left face is visible
165
- - `peek-top` - Camera is moved above the cube so that the top face is visible
166
- - `peek-bottom` - Camera is moved below the cube so that the bottom face is visible
167
- - `peek-toggle-horizontal` - Camera is moved to the opposite side of the cube in the horizontal plane
168
- - `peek-toggle-vertical` - Camera is moved to the opposite side of the cube in the vertical plane
213
+ const cube = document.querySelector('rubiks-cube');
169
214
 
170
- #### Example
215
+ // Peek in different directions
216
+ await cube.peek(PeekTypes.Right); // Peek right
217
+ await cube.peek(PeekTypes.Left); // Peek left
218
+ await cube.peek(PeekTypes.Up); // Peek up
219
+ await cube.peek(PeekTypes.Down); // Peek down
220
+ await cube.peek(PeekTypes.RightUp); // Peek right and up
221
+ await cube.peek(PeekTypes.RightDown); // Peek right and down
222
+ await cube.peek(PeekTypes.LeftUp); // Peek left and up
223
+ await cube.peek(PeekTypes.LeftDown); // Peek left and down
224
+ await cube.peek(PeekTypes.Horizontal); // Reset horizontal peek
225
+ await cube.peek(PeekTypes.Vertical); // Reset vertical peek
226
+
227
+ // The peek method returns the current peek state
228
+ const peekState = await cube.peek(PeekTypes.RightUp);
229
+ console.log('Current peek state:', peekState); // e.g., 'rightUp'
230
+ ```
231
+
232
+ ### Complete Example
171
233
 
172
234
  ```js
235
+ import { RubiksCubeElement, AttributeNames } from '@houstonp/rubiks-cube';
236
+ import { Movements, Rotations, PeekTypes, CubeTypes, AnimationStyles } from '@houstonp/rubiks-cube/core';
237
+
173
238
  const cube = document.querySelector('rubiks-cube');
174
- const event = new CustomEvent('action', {
175
- detail: { eventId: 'guid-guid-guid-guid-guid', action: { type: 'camera', actionId: 'peek-toggle-horizontal' } },
176
- });
177
- cube.dispatchEvent(event);
239
+
240
+ // Configure cube settings
241
+ cube.setAttribute(AttributeNames.cubeType, CubeTypes.Four); // Use 4x4 cube
242
+ cube.setAttribute(AttributeNames.animationSpeed, '800');
243
+ cube.setAttribute(AttributeNames.animationStyle, AnimationStyles.Exponential);
244
+
245
+ // Perform a sequence of moves
246
+ await cube.move(Movements.Single.R);
247
+ await cube.move(Movements.Single.U);
248
+ await cube.rotate(Rotations.y);
249
+ await cube.move(Movements.Wide.Rw);
250
+
251
+ // Peek to see a different angle
252
+ await cube.peek(PeekTypes.RightUp);
253
+
254
+ // Save the current state
255
+ const currentState = await cube.move(Movements.Single.F);
256
+
257
+ // Reset and restore
258
+ await cube.reset();
259
+ await cube.setState(currentState);
178
260
  ```
179
261
 
180
- #### Rotation action event
262
+ All methods return promises that resolve with the new state string (or peek state for `peek`). They reject if the operation fails or times out.
181
263
 
182
- | Notation | Rotation |
183
- | -------- | ------------------------------------------------ |
184
- | x | Rotate cube on x axis clockwise (direction of R) |
185
- | y | Rotate cube on y axis clockwise (direction of U) |
186
- | z | Rotate cube on z axis clockwise (direction of F) |
264
+ ## Rubiks Cube Notation
187
265
 
188
- actionIDs for action type "rotation" are as follows
266
+ Notations can include the number of rotations of a face. For example, `U2` means rotate the upper face 180 degrees.
189
267
 
190
- - 'x',
191
- - 'x2',
192
- - "x'",
193
- - 'y',
194
- - 'y2',
195
- - "y'",
196
- - 'z',
197
- - 'z2',
198
- - "z'",
268
+ Notations can also include a prime symbol `'` to indicate a counter‑clockwise rotation. For example, `U'` means rotate the upper face counter‑clockwise. The direction is always determined relative to the face being moved.
199
269
 
200
- #### Example
270
+ Notations can also include a layer identifier for larger cubes. For example `3R2'` means rotate the Third layer from the right face counter-clockwise twice.
271
+
272
+ When both a number and a prime symbol are included, the number is stated before the prime symbol. For example, `U2'` means rotate the upper face 180 degrees counter‑clockwise, and `U'2` is invalid.
273
+
274
+ Valid notation constants are available via the `core` export. Use these constants instead of string literals for better type safety and autocomplete support.
275
+
276
+ Duplicate or equivalent notations are not provided in the `core` export. For example `R3` and `R'` are equivalent and only `R'` is provided in the export.
201
277
 
202
278
  ```js
279
+ import { RubiksCubeElement } from '@houstonp/rubiks-cube';
280
+ import { Rotations, Movements, PeekTypes, PeekStates, CubeTypes, AnimationStyles } from '@houstonp/rubiks-cube/core';
281
+
203
282
  const cube = document.querySelector('rubiks-cube');
204
- const event = new CustomEvent('action', {
205
- detail: { eventId: 'guid-guid-guid-guid-guid', action: { type: 'rotation', actionId: 'x' } },
206
- });
207
- cube.dispatchEvent(event);
283
+
284
+ // Use constants for moves
285
+ cube.move(Movements.Single.R);
286
+ cube.move(Movements.Single.U2);
287
+ cube.move(Movements.Single.FP);
288
+
289
+ // Use constants for rotations
290
+ cube.rotate(Rotations.x);
291
+ cube.rotate(Rotations.y2);
292
+ cube.rotate(Rotations.zP);
293
+
294
+ // Use constants for peek types
295
+ cube.peek(PeekTypes.Right);
296
+ cube.peek(PeekTypes.RightUp);
297
+
298
+ // Use constants for cube types
299
+ cube.setAttribute(AttributeNames.CubeType, CubeTypes.Four);
300
+
301
+ // Use constants for animation styles
302
+ cube.setAttribute(AttributeNames.AnimationStyle, AnimationStyles.Exponential);
208
303
  ```
209
304
 
210
- #### Movement action event
305
+ Notation must match the following Regex
306
+
307
+ `/([1234567]|[123456]-[1234567])?([RLUDFB]w|[RLUDFBMES]|[rludfbmes])([123])?(\')?$/`
308
+ some notation may not work as intended as there is no known interpretation. eg `2M`
309
+
310
+ Standard Notation
211
311
 
212
312
  | Notation | Movement |
213
313
  | -------- | ------------------------------------------ |
@@ -227,34 +327,44 @@ cube.dispatchEvent(event);
227
327
  | E | Equatorial layer clockwise (relative to D) |
228
328
  | S | Standing layer clockwise (relative to F) |
229
329
 
230
- actionIDs for action type "movement" are as follows
231
-
232
- - 'R',
233
- - 'R2',
234
- - "R'",
235
- - 'L',
236
- - 'L2',
237
- - "L'",
238
- - 'U',
239
- - 'U2',
240
- - "U'",
241
- - 'D',
242
- - 'D2',
243
- - "D'",
244
- - 'F',
245
- - 'F2',
246
- - "F'",
247
- - 'B',
248
- - 'B2',
249
- - "B'",
250
- - etc...
251
-
252
- #### Example
330
+ Big Cube Notation. Not all listed for brevity.
253
331
 
254
- ```js
255
- const cube = document.querySelector('rubiks-cube');
256
- const event = new CustomEvent('action', {
257
- detail: { eventId: 'guid-guid-guid-guid-guid', action: { type: 'movement', actionId: 'U' } },
258
- });
259
- cube.dispatchEvent(event);
260
- ```
332
+ | Notation | Movement |
333
+ | -------- | ----------------------------------------------- |
334
+ | NR | Nth Right most layer |
335
+ | NRw | All Right layers up to the Nth Right most layer |
336
+ | Nr | All Right layers up to the Nth Right most layer |
337
+
338
+ Rotation Notation
339
+
340
+ | Notation | Rotation |
341
+ | -------- | ------------------------------------------------ |
342
+ | x | Rotate cube on x axis clockwise (direction of R) |
343
+ | y | Rotate cube on y axis clockwise (direction of U) |
344
+ | z | Rotate cube on z axis clockwise (direction of F) |
345
+
346
+ These symbols align with the movements and rotations accepted by the component's API.
347
+
348
+ ## Development
349
+
350
+ This repository is set up as an npm package and uses **Bun** for scripts and type generation.
351
+
352
+ - **Install dependencies**
353
+
354
+ ```bash
355
+ bun install
356
+ ```
357
+
358
+ - **Run Tests**
359
+
360
+ ```bash
361
+ bun run test
362
+ ```
363
+
364
+ - **Generate TypeScript declaration files**
365
+
366
+ ```bash
367
+ bun run build:types
368
+ ```
369
+
370
+ The generated `.d.ts` files are emitted into the `types/` directory (ignored in git) and are used for consumers of the package. There is currently no dedicated demo app or automated test suite in this repository; you can import the component into your own app (e.g., Vite, Next.js, or any ES‑module‑aware bundler) to experiment locally.
package/package.json CHANGED
@@ -1,10 +1,33 @@
1
1
  {
2
2
  "name": "@houstonp/rubiks-cube",
3
- "version": "1.5.2",
3
+ "version": "2.1.0",
4
4
  "description": "Rubiks Cube Web Component built with threejs",
5
- "main": "index.js",
6
5
  "author": "Houston Pearse",
7
6
  "license": "MIT",
7
+ "keywords": [
8
+ "rubik's cube",
9
+ "twisty puzzle",
10
+ "3x3",
11
+ "renderer",
12
+ "threejs"
13
+ ],
14
+ "main": "./src/index.js",
15
+ "types": "./types/index.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./types/index.d.ts",
19
+ "default": "./src/index.js"
20
+ },
21
+ "./core": {
22
+ "types": "./types/core.d.ts",
23
+ "default": "./src/core.js"
24
+ }
25
+ },
26
+ "scripts": {
27
+ "build:types": "tsc",
28
+ "watch:types": "tsc --watch",
29
+ "test": "bun test"
30
+ },
8
31
  "dependencies": {
9
32
  "gsap": "^3.14.2",
10
33
  "three": "^0.182.0"
@@ -16,5 +39,12 @@
16
39
  "bugs": {
17
40
  "url": "https://github.com/houstonpearse/rubiks-cube/issues"
18
41
  },
19
- "homepage": "https://github.com/houstonpearse/rubiks-cube#readme"
42
+ "homepage": "https://github.com/houstonpearse/rubiks-cube#readme",
43
+ "devDependencies": {
44
+ "@types/bun": "^1.3.9",
45
+ "@types/jsdom": "^27.0.0",
46
+ "@types/three": "^0.182.0",
47
+ "jsdom": "^28.1.0",
48
+ "typescript": "^5.9.3"
49
+ }
20
50
  }
@@ -0,0 +1,81 @@
1
+ // @ts-check
2
+ import { PeekStates, PeekTypes } from '../core';
3
+
4
+ export class CameraState {
5
+ /**
6
+ * @param {boolean} up
7
+ * @param {boolean} right
8
+ */
9
+ constructor(up = true, right = true) {
10
+ /** @type {boolean} */
11
+ this.Up = up;
12
+ /** @type {boolean} */
13
+ this.Right = right;
14
+ }
15
+
16
+ /**
17
+ * @param {import("../core").PeekType} peekType
18
+ */
19
+ peekCamera(peekType) {
20
+ switch (peekType) {
21
+ case PeekTypes.Horizontal:
22
+ this.Right = !this.Right;
23
+ break;
24
+ case PeekTypes.Vertical:
25
+ this.Up = !this.Up;
26
+ break;
27
+ case PeekTypes.Right:
28
+ this.Right = true;
29
+ break;
30
+ case PeekTypes.Left:
31
+ this.Right = false;
32
+ break;
33
+ case PeekTypes.Up:
34
+ this.Up = true;
35
+ break;
36
+ case PeekTypes.Down:
37
+ this.Up = false;
38
+ break;
39
+ case PeekTypes.RightUp:
40
+ this.Right = true;
41
+ this.Up = true;
42
+ break;
43
+ case PeekTypes.RightDown:
44
+ this.Right = true;
45
+ this.Up = false;
46
+ break;
47
+ case PeekTypes.LeftUp:
48
+ this.Right = false;
49
+ this.Up = true;
50
+ break;
51
+ case PeekTypes.LeftDown:
52
+ this.Right = false;
53
+ this.Up = false;
54
+ break;
55
+ default:
56
+ console.error(`Invalid peekType:[${peekType}]. valid values are [${Object.values(PeekTypes)}] `);
57
+ break;
58
+ }
59
+ }
60
+ /**
61
+ * @returns {import("../core").PeekState}
62
+ */
63
+ toPeekState() {
64
+ if (this.Right && this.Up) {
65
+ return PeekStates.RightUp;
66
+ }
67
+ if (!this.Right && this.Up) {
68
+ return PeekStates.LeftUp;
69
+ }
70
+ if (this.Right && !this.Up) {
71
+ return PeekStates.RightDown;
72
+ }
73
+ if (!this.Right && !this.Up) {
74
+ return PeekStates.LeftDown;
75
+ }
76
+ console.error(
77
+ `Invalid CameraState right and up values must be true or false. Actual values: right[${this.Right}] up[${this.Up}]. Default RightUp returned`,
78
+ );
79
+ return PeekStates.RightUp;
80
+ }
81
+ }