@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.
- package/README.md +280 -170
- package/package.json +33 -3
- package/src/camera/cameraState.js +81 -0
- package/src/core.js +447 -0
- package/src/cube/animationSlice.js +205 -0
- package/src/cube/animationState.js +96 -0
- package/src/cube/cubeSettings.js +19 -0
- package/src/cube/cubeState.js +285 -139
- package/src/cube/stickerState.js +188 -0
- package/src/debouncer.js +16 -0
- package/src/globals.ts +9 -0
- package/src/index.js +621 -0
- package/src/settings.js +138 -0
- package/src/three/centerPiece.js +44 -0
- package/src/three/cornerPiece.js +60 -0
- package/src/three/cube.js +492 -0
- package/src/three/edgePiece.js +50 -0
- package/src/three/sticker.js +37 -0
- package/tests/common.js +27 -0
- package/tests/cube.five.test.js +126 -0
- package/tests/cube.four.test.js +126 -0
- package/tests/cube.seven.test.js +126 -0
- package/tests/cube.six.test.js +126 -0
- package/tests/cube.three.test.js +151 -0
- package/tests/cube.two.test.js +125 -0
- package/tests/setup.js +36 -0
- package/types/camera/cameraState.d.ts +19 -0
- package/types/core.d.ts +454 -0
- package/types/cube/animationSlice.d.ts +26 -0
- package/types/cube/animationState.d.ts +41 -0
- package/types/cube/cubeSettings.d.ts +17 -0
- package/types/cube/cubeState.d.ts +47 -0
- package/types/cube/stickerState.d.ts +21 -0
- package/types/debouncer.d.ts +13 -0
- package/types/globals.d.ts +7 -0
- package/types/index.d.ts +87 -0
- package/types/settings.d.ts +38 -0
- package/types/three/centerPiece.d.ts +15 -0
- package/types/three/cornerPiece.d.ts +24 -0
- package/types/three/cube.d.ts +130 -0
- package/types/three/edgePiece.d.ts +16 -0
- package/types/three/sticker.d.ts +15 -0
- package/.prettierrc +0 -7
- package/index.js +0 -274
- package/src/cube/cube.js +0 -276
- package/src/cube/cubeRotation.js +0 -63
- package/src/threejs/materials.js +0 -42
- package/src/threejs/pieces.js +0 -103
- package/src/threejs/stickers.js +0 -48
- package/src/utils/debouncer.js +0 -7
- package/src/utils/rotation.js +0 -53
package/README.md
CHANGED
|
@@ -1,17 +1,29 @@
|
|
|
1
1
|
# Rubiks Cube Web Component
|
|
2
2
|
|
|
3
|
-
|
|
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
|

|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
|
|
91
|
+
The `RubiksCubeElement` instance exposes async methods that return the cube state after the operation completes:
|
|
44
92
|
|
|
45
|
-
|
|
93
|
+
### Move
|
|
46
94
|
|
|
47
|
-
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
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
|
-
|
|
134
|
+
Rotates the entire cube and resolves with the new state string.
|
|
98
135
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
|
|
140
|
+
const cube = document.querySelector('rubiks-cube');
|
|
123
141
|
|
|
124
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
158
|
+
### Reset
|
|
131
159
|
|
|
132
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
178
|
+
### SetState
|
|
140
179
|
|
|
141
|
-
|
|
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
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
});
|
|
200
|
+
// Set a specific scrambled state (example for 3x3)
|
|
201
|
+
const scrambledState = 'UULUUFUUFRRUBRRURRFFDFFUFFFDDRDDDDDDBLLLLLLLLBRRBBBBBB';
|
|
202
|
+
await cube.setState(scrambledState);
|
|
157
203
|
```
|
|
158
204
|
|
|
159
|
-
|
|
205
|
+
### Peek
|
|
206
|
+
|
|
207
|
+
Animates the camera to a new "peek" position and resolves with the new peek state.
|
|
160
208
|
|
|
161
|
-
|
|
209
|
+
```js
|
|
210
|
+
import { RubiksCubeElement } from '@houstonp/rubiks-cube';
|
|
211
|
+
import { PeekTypes, PeekStates } from '@houstonp/rubiks-cube/core';
|
|
162
212
|
|
|
163
|
-
|
|
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
|
-
|
|
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
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
cube.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
266
|
+
Notations can include the number of rotations of a face. For example, `U2` means rotate the upper face 180 degrees.
|
|
189
267
|
|
|
190
|
-
|
|
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
|
-
|
|
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
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
cube.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
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.
|
|
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
|
+
}
|