@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.
- package/README.md +304 -77
- package/package.json +18 -8
- package/src/{core.js → core/index.js} +72 -41
- package/src/rubiksCube/index.js +3 -0
- package/src/rubiksCube/rubiksCubeController.js +111 -0
- package/src/{three → rubiksCube3D}/centerPiece.js +37 -2
- package/src/{three → rubiksCube3D}/cornerPiece.js +56 -2
- package/src/rubiksCube3D/cubeConfig.js +87 -0
- package/src/rubiksCube3D/cubeSettings.js +30 -0
- package/src/{three → rubiksCube3D}/edgePiece.js +2 -1
- package/src/rubiksCube3D/index.js +3 -0
- package/src/rubiksCube3D/rubiksCube3D.js +383 -0
- package/src/{three → rubiksCube3D}/sticker.js +5 -4
- package/src/state/index.js +4 -0
- package/src/state/rubiksCubeState.js +471 -0
- package/src/state/slice.js +236 -0
- package/src/state/stickerState.js +185 -0
- package/src/{camera → webComponent}/cameraState.js +17 -25
- package/src/webComponent/constants.js +67 -0
- package/src/webComponent/index.js +7 -0
- package/src/webComponent/rubiksCubeElement.js +379 -0
- package/src/{settings.js → webComponent/settings.js} +36 -23
- package/tests/common.js +3 -20
- package/tests/core.test.js +56 -0
- package/tests/rubiksCube.solves.test.js +41 -0
- package/tests/rubiksCube3D.solves.test.js +185 -0
- package/tests/rubiksCubeState.solves.test.js +35 -0
- package/tests/testScrambles.js +194 -0
- package/types/{core.d.ts → core/index.d.ts} +45 -48
- package/types/rubiksCube/index.d.ts +3 -0
- package/types/rubiksCube/rubiksCubeController.d.ts +62 -0
- package/types/rubiksCube3D/centerPiece.d.ts +27 -0
- package/types/rubiksCube3D/cornerPiece.d.ts +38 -0
- package/types/rubiksCube3D/cubeConfig.d.ts +32 -0
- package/types/rubiksCube3D/cubeSettings.d.ts +33 -0
- package/types/{three → rubiksCube3D}/edgePiece.d.ts +5 -3
- package/types/rubiksCube3D/index.d.ts +3 -0
- package/types/rubiksCube3D/rubiksCube3D.d.ts +120 -0
- package/types/rubiksCube3D/sticker.d.ts +18 -0
- package/types/state/index.d.ts +5 -0
- package/types/state/rubiksCubeState.d.ts +108 -0
- package/types/state/slice.d.ts +46 -0
- package/types/state/stickerState.d.ts +34 -0
- package/types/webComponent/cameraState.d.ts +22 -0
- package/types/webComponent/constants.d.ts +57 -0
- package/types/webComponent/index.d.ts +6 -0
- package/types/webComponent/rubiksCubeElement.d.ts +89 -0
- package/types/{settings.d.ts → webComponent/settings.d.ts} +5 -8
- package/src/cube/animationSlice.js +0 -205
- package/src/cube/animationState.js +0 -96
- package/src/cube/cubeSettings.js +0 -19
- package/src/cube/cubeState.js +0 -337
- package/src/cube/stickerState.js +0 -188
- package/src/index.js +0 -621
- package/src/three/cube.js +0 -492
- package/tests/cube.five.test.js +0 -126
- package/tests/cube.four.test.js +0 -126
- package/tests/cube.seven.test.js +0 -126
- package/tests/cube.six.test.js +0 -126
- package/tests/cube.three.test.js +0 -151
- package/tests/cube.two.test.js +0 -125
- package/types/camera/cameraState.d.ts +0 -19
- package/types/cube/animationSlice.d.ts +0 -26
- package/types/cube/animationState.d.ts +0 -41
- package/types/cube/cubeSettings.d.ts +0 -17
- package/types/cube/cubeState.d.ts +0 -47
- package/types/cube/stickerState.d.ts +0 -21
- package/types/index.d.ts +0 -87
- package/types/three/centerPiece.d.ts +0 -15
- package/types/three/cornerPiece.d.ts +0 -24
- package/types/three/cube.d.ts +0 -130
- package/types/three/sticker.d.ts +0 -15
- /package/src/{debouncer.js → webComponent/debouncer.js} +0 -0
- /package/src/{globals.ts → webComponent/globals.ts} +0 -0
- /package/types/{debouncer.d.ts → webComponent/debouncer.d.ts} +0 -0
- /package/types/{globals.d.ts → webComponent/globals.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
# Rubiks Cube Web Component
|
|
2
2
|
|
|
3
|
-
A Rubik's Cube web component built with Three.js
|
|
3
|
+
A Rubik's Cube web component built with Three.js and GSAP. The cube renders into a shadow‑DOM canvas and exposes a
|
|
4
|
+
small, promise‑based API for cube moves, rotations, reset, state setting, and camera "peek" positions. Supports 2x2,
|
|
5
|
+
3x3, 4x4, 5x5, 6x6, and 7x7 Rubik's cubes.
|
|
6
|
+
|
|
7
|
+
The package also ships a headless cube state class, a standalone Three.js cube object, and the underlying movement
|
|
8
|
+
parser, so you can use any layer of the stack on its own.
|
|
4
9
|
|
|
5
10
|

|
|
6
11
|
|
|
@@ -14,13 +19,41 @@ bun add @houstonp/rubiks-cube
|
|
|
14
19
|
npm install @houstonp/rubiks-cube
|
|
15
20
|
```
|
|
16
21
|
|
|
22
|
+
## Which one do I want?
|
|
23
|
+
|
|
24
|
+
The package ships four primary classes; each plays a different role.
|
|
25
|
+
|
|
26
|
+
| I want to... | Use |
|
|
27
|
+
| --------------------------------------------------------------------- | -------------------------------------------- |
|
|
28
|
+
| Drop a cube into my page with no setup | `RubiksCubeElement` from `/view` |
|
|
29
|
+
| Add a cube to my own three.js scene | `RubiksCube3D` from `/three` |
|
|
30
|
+
| Drive cube state from my own renderer / view | `RubiksCubeController` from `/controller` |
|
|
31
|
+
| Track cube state with no rendering (solver, scrambler, headless test) | `RubiksCubeState` from `/state` |
|
|
32
|
+
|
|
33
|
+
`RubiksCubeElement` is built on top of `RubiksCube3D` + `RubiksCubeController` + `RubiksCubeState`, so most users
|
|
34
|
+
only need the first row.
|
|
35
|
+
|
|
36
|
+
## Package layout
|
|
37
|
+
|
|
38
|
+
The package exposes several subpath entry points so you only pull in the parts you need.
|
|
39
|
+
|
|
40
|
+
| Subpath | Exports |
|
|
41
|
+
| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
|
|
42
|
+
| `@houstonp/rubiks-cube/view` | `RubiksCubeElement`, `AttributeNames`, `PeekActions`, `PeekStates`, `AnimationStyles` |
|
|
43
|
+
| `@houstonp/rubiks-cube/three` | `RubiksCube3D`, `RubiksCube3DSettings` |
|
|
44
|
+
| `@houstonp/rubiks-cube/controller` | `RubiksCubeController` |
|
|
45
|
+
| `@houstonp/rubiks-cube/core` | `Movements`, `Rotations`, `Faces`, `CubeTypes`, `LayerCount`, `isMovement`, `IsRotation`, `reverse`, `translate` |
|
|
46
|
+
| `@houstonp/rubiks-cube/state` | `RubiksCubeState`, `Axi`, `GetMovementSlice`, `GetRotationSlice` |
|
|
47
|
+
|
|
48
|
+
There is no bare-package root export — every class lives on a subpath that names its layer.
|
|
49
|
+
|
|
17
50
|
## Adding the component
|
|
18
51
|
|
|
19
52
|
Register the custom element and then use the tag in your HTML.
|
|
20
53
|
|
|
21
54
|
```js
|
|
22
55
|
// index.js
|
|
23
|
-
import { RubiksCubeElement } from '@houstonp/rubiks-cube';
|
|
56
|
+
import { RubiksCubeElement } from '@houstonp/rubiks-cube/view';
|
|
24
57
|
|
|
25
58
|
// Registers <rubiks-cube> (you can pass a different tag name if you prefer)
|
|
26
59
|
RubiksCubeElement.register();
|
|
@@ -50,11 +83,11 @@ RubiksCubeElement.register();
|
|
|
50
83
|
|
|
51
84
|
## Component attributes
|
|
52
85
|
|
|
53
|
-
These attributes control animation, spacing, camera behavior, and cube type. The available attributes
|
|
54
|
-
|
|
86
|
+
These attributes control animation, spacing, camera behavior, and cube type. The available attributes can be imported
|
|
87
|
+
so that they can be get and set easily.
|
|
55
88
|
|
|
56
89
|
```js
|
|
57
|
-
import { RubiksCubeElement, AttributeNames } from '@houstonp/rubiks-cube';
|
|
90
|
+
import { RubiksCubeElement, AttributeNames } from '@houstonp/rubiks-cube/view';
|
|
58
91
|
import { CubeTypes, AnimationStyles } from '@houstonp/rubiks-cube/core';
|
|
59
92
|
|
|
60
93
|
const cube = document.querySelector('rubiks-cube');
|
|
@@ -74,28 +107,33 @@ cube.setAttribute(AttributeNames.cameraPeekAngleHorizontal, '0.7');
|
|
|
74
107
|
cube.setAttribute(AttributeNames.cameraPeekAngleVertical, '0.7');
|
|
75
108
|
```
|
|
76
109
|
|
|
77
|
-
| attribute | accepted values
|
|
78
|
-
| ---------------------------- |
|
|
79
|
-
| cube-type | `"Two"`, `"Three"`, `"Four"`, `"Five"`, `"Six"`, `"Seven"`
|
|
80
|
-
| animation-speed-ms |
|
|
81
|
-
| animation-style | `"exponential"`, `"next"`, `"fixed"`, `"match"`
|
|
82
|
-
| piece-gap |
|
|
83
|
-
| camera-speed-ms | greater than or equal to 0
|
|
84
|
-
| camera-radius | greater than or equal to 4
|
|
85
|
-
| camera-peek-angle-horizontal | decimal between 0 and 1
|
|
86
|
-
| camera-peek-angle-vertical | decimal between 0 and 1
|
|
87
|
-
| camera-field-of-view | integer between
|
|
110
|
+
| attribute | accepted values | Description |
|
|
111
|
+
| ---------------------------- | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
112
|
+
| cube-type | `"Two"`, `"Three"`, `"Four"`, `"Five"`, `"Six"`, `"Seven"` | Sets the cube size (2x2 through 7x7). Default is `"Three"` |
|
|
113
|
+
| animation-speed-ms | number greater than or equal to 0 | Sets the duration of cube animations in milliseconds. Default is `100` |
|
|
114
|
+
| animation-style | `"exponential"`, `"linear"`, `"next"`, `"fixed"`, `"match"` | `fixed`: fixed animation lengths, `next`: skips to next animation, `linear`: ramps speed linearly with backlog, `exponential`: speeds up successive animations, `match`: matches event frequency. |
|
|
115
|
+
| piece-gap | number between 1 and 1.1 | Sets the gap between Rubik's Cube pieces. Default is `1.04` |
|
|
116
|
+
| camera-speed-ms | number greater than or equal to 0 | Sets the duration of camera animations in milliseconds. Default is `100` |
|
|
117
|
+
| camera-radius | number greater than or equal to 4 | Sets the camera radius. Default is `5` |
|
|
118
|
+
| camera-peek-angle-horizontal | decimal between 0 and 1 | Sets the horizontal peek angle. Default is `0.6` |
|
|
119
|
+
| camera-peek-angle-vertical | decimal between 0 and 1 | Sets the vertical peek angle. Default is `0.6` |
|
|
120
|
+
| camera-field-of-view | integer between 30 and 100 | Sets the field of view of the camera. Default is `75` |
|
|
88
121
|
|
|
89
122
|
## Programmatic control
|
|
90
123
|
|
|
91
|
-
The `RubiksCubeElement` instance exposes
|
|
124
|
+
The `RubiksCubeElement` instance exposes the methods below. `move`, `rotate`, and `peek` are async and resolve once the
|
|
125
|
+
animation completes; `reset`, `setState`, `getState`, and `setType` are synchronous and apply to the cube immediately.
|
|
92
126
|
|
|
93
127
|
### Move
|
|
94
128
|
|
|
95
129
|
Performs a cube movement and resolves with the new state string.
|
|
96
130
|
|
|
131
|
+
```ts
|
|
132
|
+
move(move: Movement, options?: AnimationOptions): Promise<string>
|
|
133
|
+
```
|
|
134
|
+
|
|
97
135
|
```js
|
|
98
|
-
import { RubiksCubeElement } from '@houstonp/rubiks-cube';
|
|
136
|
+
import { RubiksCubeElement } from '@houstonp/rubiks-cube/view';
|
|
99
137
|
import { Movements } from '@houstonp/rubiks-cube/core';
|
|
100
138
|
|
|
101
139
|
const cube = document.querySelector('rubiks-cube');
|
|
@@ -108,8 +146,8 @@ await cube.move(Movements.Single.U); // Upper face clockwise
|
|
|
108
146
|
await cube.move(Movements.Single.FP); // Front face counter-clockwise
|
|
109
147
|
|
|
110
148
|
// Wide moves
|
|
111
|
-
await cube.move(Movements.Wide.Rw); //
|
|
112
|
-
await cube.move(Movements.Wide.r); // Right two layers
|
|
149
|
+
await cube.move(Movements.Wide.Rw); // Right two layers (Rw)
|
|
150
|
+
await cube.move(Movements.Wide.r); // Right two layers (r)
|
|
113
151
|
|
|
114
152
|
// Layer-specific moves (for 4x4+ cubes)
|
|
115
153
|
await cube.move(Movements.Two.R); // Second layer right
|
|
@@ -121,6 +159,15 @@ await cube.move(Movements.Single.M); // Middle layer
|
|
|
121
159
|
await cube.move(Movements.Single.E); // Equatorial layer
|
|
122
160
|
await cube.move(Movements.Single.S); // Standing layer
|
|
123
161
|
|
|
162
|
+
// Override animation speed for a single move
|
|
163
|
+
await cube.move(Movements.Single.R, { animationSpeedMs: 200 });
|
|
164
|
+
|
|
165
|
+
// Reverse the move direction (R is treated as R')
|
|
166
|
+
await cube.move(Movements.Single.R, { reverse: true });
|
|
167
|
+
|
|
168
|
+
// Translate 3x3 notation to big cube notation (e.g. r on a 7x7 becomes 6r)
|
|
169
|
+
await cube.move(Movements.Wide.r, { translate: true });
|
|
170
|
+
|
|
124
171
|
// Chain multiple moves
|
|
125
172
|
const moves = [Movements.Single.R, Movements.Single.U, Movements.Single.RP, Movements.Single.UP];
|
|
126
173
|
for (const move of moves) {
|
|
@@ -133,8 +180,12 @@ for (const move of moves) {
|
|
|
133
180
|
|
|
134
181
|
Rotates the entire cube and resolves with the new state string.
|
|
135
182
|
|
|
183
|
+
```ts
|
|
184
|
+
rotate(rotation: Rotation, options?: AnimationOptions): Promise<string>
|
|
185
|
+
```
|
|
186
|
+
|
|
136
187
|
```js
|
|
137
|
-
import { RubiksCubeElement } from '@houstonp/rubiks-cube';
|
|
188
|
+
import { RubiksCubeElement } from '@houstonp/rubiks-cube/view';
|
|
138
189
|
import { Rotations } from '@houstonp/rubiks-cube/core';
|
|
139
190
|
|
|
140
191
|
const cube = document.querySelector('rubiks-cube');
|
|
@@ -153,87 +204,164 @@ await cube.rotate(Rotations.yP); // 90 degrees counter-clockwise
|
|
|
153
204
|
await cube.rotate(Rotations.z); // 90 degrees clockwise
|
|
154
205
|
await cube.rotate(Rotations.z2); // 180 degrees
|
|
155
206
|
await cube.rotate(Rotations.zP); // 90 degrees counter-clockwise
|
|
207
|
+
|
|
208
|
+
// Override animation speed for a single rotation
|
|
209
|
+
await cube.rotate(Rotations.y, { animationSpeedMs: 600 });
|
|
210
|
+
|
|
211
|
+
// Reverse rotation direction (y is treated as y')
|
|
212
|
+
await cube.rotate(Rotations.y, { reverse: true });
|
|
156
213
|
```
|
|
157
214
|
|
|
158
215
|
### Reset
|
|
159
216
|
|
|
160
|
-
Resets the cube to the solved state and
|
|
217
|
+
Resets the cube to the solved state and returns the new state string. Any in‑flight animation is snapped to its end
|
|
218
|
+
position before the reset is applied.
|
|
219
|
+
|
|
220
|
+
```ts
|
|
221
|
+
reset(): string
|
|
222
|
+
```
|
|
161
223
|
|
|
162
224
|
```js
|
|
163
|
-
import { RubiksCubeElement } from '@houstonp/rubiks-cube';
|
|
225
|
+
import { RubiksCubeElement } from '@houstonp/rubiks-cube/view';
|
|
164
226
|
import { Movements } from '@houstonp/rubiks-cube/core';
|
|
165
227
|
|
|
166
228
|
const cube = document.querySelector('rubiks-cube');
|
|
167
229
|
|
|
168
230
|
// Reset to solved state
|
|
169
|
-
const solvedState =
|
|
231
|
+
const solvedState = cube.reset();
|
|
170
232
|
console.log('Cube reset to solved state:', solvedState);
|
|
171
233
|
|
|
172
234
|
// Reset after performing some moves
|
|
173
235
|
await cube.move(Movements.Single.R);
|
|
174
236
|
await cube.move(Movements.Single.U);
|
|
175
|
-
const resetState =
|
|
237
|
+
const resetState = cube.reset();
|
|
176
238
|
```
|
|
177
239
|
|
|
178
|
-
### SetState
|
|
240
|
+
### SetState / GetState
|
|
179
241
|
|
|
180
|
-
Sets the cube to a specific state using a Kociemba
|
|
242
|
+
Sets the cube to a specific state using a Kociemba‑format state string, or reads the current state. `setState` returns
|
|
243
|
+
`true` on success and `false` if the input string is not valid for any supported cube type.
|
|
244
|
+
|
|
245
|
+
```ts
|
|
246
|
+
setState(kociembaState: string): boolean
|
|
247
|
+
getState(): string
|
|
248
|
+
```
|
|
181
249
|
|
|
182
250
|
```js
|
|
183
|
-
import { RubiksCubeElement } from '@houstonp/rubiks-cube';
|
|
251
|
+
import { RubiksCubeElement } from '@houstonp/rubiks-cube/view';
|
|
184
252
|
import { Movements } from '@houstonp/rubiks-cube/core';
|
|
185
253
|
|
|
186
254
|
const cube = document.querySelector('rubiks-cube');
|
|
187
255
|
|
|
188
256
|
// Save current state
|
|
189
|
-
|
|
257
|
+
await cube.move(Movements.Single.R);
|
|
258
|
+
const currentState = cube.getState();
|
|
190
259
|
|
|
191
260
|
// Later, restore that state
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
console.
|
|
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
|
|
261
|
+
const ok = cube.setState(currentState);
|
|
262
|
+
if (!ok) {
|
|
263
|
+
console.error('Failed to set state — string did not match a supported cube size');
|
|
198
264
|
}
|
|
199
265
|
|
|
200
266
|
// Set a specific scrambled state (example for 3x3)
|
|
201
267
|
const scrambledState = 'UULUUFUUFRRUBRRURRFFDFFUFFFDDRDDDDDDBLLLLLLLLBRRBBBBBB';
|
|
202
|
-
|
|
268
|
+
cube.setState(scrambledState);
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### SetType
|
|
272
|
+
|
|
273
|
+
Switches the cube to a different size at runtime by reflecting the value to the `cube-type` attribute, which
|
|
274
|
+
triggers an internal rebuild. Returns the cube's state string after the call. If the new type matches the
|
|
275
|
+
current type, `setType` is a no-op and returns the current state — call `reset()` if you also want to clear
|
|
276
|
+
the cube to solved.
|
|
277
|
+
|
|
278
|
+
```ts
|
|
279
|
+
setType(cubeType: CubeType): string
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
```js
|
|
283
|
+
import { RubiksCubeElement } from '@houstonp/rubiks-cube/view';
|
|
284
|
+
import { CubeTypes } from '@houstonp/rubiks-cube/core';
|
|
285
|
+
|
|
286
|
+
const cube = document.querySelector('rubiks-cube');
|
|
287
|
+
|
|
288
|
+
const newState = cube.setType(CubeTypes.Five); // Rebuild as a 5x5; returns the solved 5x5 state
|
|
289
|
+
cube.setType(CubeTypes.Five); // No-op; returns whatever the current state is
|
|
203
290
|
```
|
|
204
291
|
|
|
292
|
+
Setting the `cube-type` attribute directly (`cube.setAttribute('cube-type', 'Five')`) is equivalent to calling
|
|
293
|
+
`setType`, since both go through the same attribute-change path.
|
|
294
|
+
|
|
205
295
|
### Peek
|
|
206
296
|
|
|
207
297
|
Animates the camera to a new "peek" position and resolves with the new peek state.
|
|
208
298
|
|
|
299
|
+
```ts
|
|
300
|
+
peek(action: PeekAction, options?: CameraOptions | null): Promise<PeekState>
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
The camera tracks **two independent boolean axes** — horizontal (Right / Left) and vertical (Up / Down) — giving
|
|
304
|
+
**four reachable positions** (the `PeekState`s: `RightUp`, `RightDown`, `LeftUp`, `LeftDown`). The 10 `PeekAction`
|
|
305
|
+
values are inputs that operate on this state machine, in three categories:
|
|
306
|
+
|
|
307
|
+
| Category | Actions | Effect |
|
|
308
|
+
| ---------------------- | ---------------------------------------------- | ------------------------------------------------------------ |
|
|
309
|
+
| Set both axes | `RightUp`, `RightDown`, `LeftUp`, `LeftDown` | Move directly to that position |
|
|
310
|
+
| Set one axis | `Right`, `Left`, `Up`, `Down` | Set that axis only; the other axis keeps its current value |
|
|
311
|
+
| Toggle one axis | `Horizontal`, `Vertical` | Flip that axis relative to its current value |
|
|
312
|
+
|
|
313
|
+
Because the second and third categories only affect one axis, the result of e.g. `peek(Up)` depends on the prior
|
|
314
|
+
peek state. The promise always resolves with the new full `PeekState`.
|
|
315
|
+
|
|
209
316
|
```js
|
|
210
|
-
import { RubiksCubeElement } from '@houstonp/rubiks-cube';
|
|
211
|
-
import { PeekTypes, PeekStates } from '@houstonp/rubiks-cube/core';
|
|
317
|
+
import { RubiksCubeElement, PeekActions, PeekStates } from '@houstonp/rubiks-cube/view';
|
|
212
318
|
|
|
213
319
|
const cube = document.querySelector('rubiks-cube');
|
|
214
320
|
|
|
215
|
-
//
|
|
216
|
-
await cube.peek(
|
|
217
|
-
await cube.peek(
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
await cube.peek(
|
|
221
|
-
await cube.peek(
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
await cube.peek(
|
|
225
|
-
await cube.peek(
|
|
226
|
-
|
|
227
|
-
// The
|
|
228
|
-
const peekState = await cube.peek(
|
|
229
|
-
console.log('Current peek state:', peekState); //
|
|
321
|
+
// Move directly to a position (sets both axes)
|
|
322
|
+
await cube.peek(PeekActions.RightUp); // → PeekStates.RightUp
|
|
323
|
+
await cube.peek(PeekActions.LeftDown); // → PeekStates.LeftDown
|
|
324
|
+
|
|
325
|
+
// Set one axis, leave the other untouched
|
|
326
|
+
await cube.peek(PeekActions.Right); // sets horizontal to Right; vertical unchanged
|
|
327
|
+
await cube.peek(PeekActions.Up); // sets vertical to Up; horizontal unchanged
|
|
328
|
+
|
|
329
|
+
// Toggle one axis relative to its current value
|
|
330
|
+
await cube.peek(PeekActions.Horizontal); // flips horizontal
|
|
331
|
+
await cube.peek(PeekActions.Vertical); // flips vertical
|
|
332
|
+
|
|
333
|
+
// The promise resolves with the new full peek state
|
|
334
|
+
const peekState = await cube.peek(PeekActions.RightUp);
|
|
335
|
+
console.log('Current peek state:', peekState); // 'rightUp'
|
|
336
|
+
|
|
337
|
+
// Override camera animation speed for a single peek
|
|
338
|
+
await cube.peek(PeekActions.Left, { cameraSpeedMs: 150 });
|
|
230
339
|
```
|
|
231
340
|
|
|
341
|
+
### Options
|
|
342
|
+
|
|
343
|
+
`AnimationOptions` can be passed to `move` and `rotate` to customise individual operations, taking precedence over the
|
|
344
|
+
corresponding element attributes.
|
|
345
|
+
|
|
346
|
+
| Option | Type | Description |
|
|
347
|
+
| ------------------ | --------- | ------------------------------------------------------------------------------------------------------------------ |
|
|
348
|
+
| `animationSpeedMs` | `number` | Duration of the animation in milliseconds. Overrides the `animation-speed-ms` attribute for this call only. |
|
|
349
|
+
| `reverse` | `boolean` | Reverses the direction of the move or rotation (e.g. `R` is treated as `R'`). |
|
|
350
|
+
| `translate` | `boolean` | Translates 3x3 notation to the equivalent big-cube notation (e.g. `r` on a 7x7 is treated as `6r`). Movement only. |
|
|
351
|
+
|
|
352
|
+
`CameraOptions` can be passed to `peek` to customise the camera animation.
|
|
353
|
+
|
|
354
|
+
| Option | Type | Description |
|
|
355
|
+
| --------------- | -------- | --------------------------------------------------------------------------------------------------------------- |
|
|
356
|
+
| `cameraSpeedMs` | `number` | Duration of the camera animation in milliseconds. Overrides the `camera-speed-ms` attribute for this call only. |
|
|
357
|
+
|
|
232
358
|
### Complete Example
|
|
233
359
|
|
|
234
360
|
```js
|
|
235
|
-
import { RubiksCubeElement, AttributeNames } from '@houstonp/rubiks-cube';
|
|
236
|
-
import { Movements, Rotations,
|
|
361
|
+
import { RubiksCubeElement, AttributeNames, PeekActions } from '@houstonp/rubiks-cube/view';
|
|
362
|
+
import { Movements, Rotations, CubeTypes, AnimationStyles } from '@houstonp/rubiks-cube/core';
|
|
363
|
+
|
|
364
|
+
RubiksCubeElement.register();
|
|
237
365
|
|
|
238
366
|
const cube = document.querySelector('rubiks-cube');
|
|
239
367
|
|
|
@@ -249,35 +377,105 @@ await cube.rotate(Rotations.y);
|
|
|
249
377
|
await cube.move(Movements.Wide.Rw);
|
|
250
378
|
|
|
251
379
|
// Peek to see a different angle
|
|
252
|
-
await cube.peek(
|
|
380
|
+
await cube.peek(PeekActions.RightUp);
|
|
253
381
|
|
|
254
382
|
// Save the current state
|
|
255
|
-
|
|
383
|
+
await cube.move(Movements.Single.F);
|
|
384
|
+
const currentState = cube.getState();
|
|
256
385
|
|
|
257
386
|
// Reset and restore
|
|
258
|
-
|
|
259
|
-
|
|
387
|
+
cube.reset();
|
|
388
|
+
cube.setState(currentState);
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
## Headless cube state
|
|
392
|
+
|
|
393
|
+
If you don't need rendering you can drive the cube state directly with `RubiksCubeState`. It tracks the cube using
|
|
394
|
+
the same parser as the web component and exposes both raw sticker state and Kociemba string helpers.
|
|
395
|
+
|
|
396
|
+
```js
|
|
397
|
+
import { RubiksCubeState } from '@houstonp/rubiks-cube/state';
|
|
398
|
+
import { CubeTypes, Movements, Rotations } from '@houstonp/rubiks-cube/core';
|
|
399
|
+
|
|
400
|
+
const cube = new RubiksCubeState(CubeTypes.Three);
|
|
401
|
+
|
|
402
|
+
cube.move(Movements.Single.R);
|
|
403
|
+
cube.rotate(Rotations.y);
|
|
404
|
+
|
|
405
|
+
// Apply a sequence in one call
|
|
406
|
+
cube.do([Movements.Single.R, Movements.Single.U, Movements.Single.RP, Movements.Single.UP]);
|
|
407
|
+
|
|
408
|
+
// Read the current state as a Kociemba string
|
|
409
|
+
const kociemba = cube.getKociemba();
|
|
410
|
+
console.log(kociemba);
|
|
411
|
+
|
|
412
|
+
// Restore from a Kociemba string
|
|
413
|
+
const restored = new RubiksCubeState(CubeTypes.Three);
|
|
414
|
+
const ok = restored.setKociemba(kociemba); // false if the string is not valid for this cube size
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
`getState()` / `setState()` round‑trip the raw sticker array if you want to skip the Kociemba encoding. For
|
|
418
|
+
lower‑level access to slices, the same subpath also exports `GetMovementSlice`, `GetRotationSlice`, and the `Axi`
|
|
419
|
+
enum.
|
|
420
|
+
|
|
421
|
+
## Standalone 3D object
|
|
422
|
+
|
|
423
|
+
`RubiksCube3D` is a `THREE.Object3D` you can drop into your own scene. The web component uses it internally; you can
|
|
424
|
+
use it directly when you want full control over the renderer, camera, and animation loop.
|
|
425
|
+
|
|
426
|
+
```js
|
|
427
|
+
import { RubiksCube3D, RubiksCube3DSettings } from '@houstonp/rubiks-cube/three';
|
|
428
|
+
import { CubeTypes, Movements } from '@houstonp/rubiks-cube/core';
|
|
429
|
+
import { GetMovementSlice } from '@houstonp/rubiks-cube/state';
|
|
430
|
+
import { Scene, PerspectiveCamera, WebGLRenderer } from 'three';
|
|
431
|
+
|
|
432
|
+
const settings = new RubiksCube3DSettings({
|
|
433
|
+
cubeType: CubeTypes.Three,
|
|
434
|
+
pieceGap: 1.04,
|
|
435
|
+
animationSpeedMs: 150,
|
|
436
|
+
animationStyle: 'sine',
|
|
437
|
+
});
|
|
438
|
+
const cube = new RubiksCube3D(settings);
|
|
439
|
+
// or, if the defaults are fine:
|
|
440
|
+
// const cube = new RubiksCube3D(new RubiksCube3DSettings());
|
|
441
|
+
|
|
442
|
+
const scene = new Scene();
|
|
443
|
+
scene.add(cube);
|
|
444
|
+
|
|
445
|
+
// Drive a slice manually
|
|
446
|
+
const slice = GetMovementSlice(Movements.Single.R, 3);
|
|
447
|
+
await cube.slice(slice, { animationSpeedMs: 200, ease: 'sine.inOut' });
|
|
260
448
|
```
|
|
261
449
|
|
|
262
|
-
|
|
450
|
+
The `animationStyle` argument accepts any GSAP ease (string or function), since each slice is animated by GSAP under the
|
|
451
|
+
hood.
|
|
452
|
+
|
|
453
|
+
If you want the higher‑level "movement / rotation" API but with a custom 3D view, import `RubiksCubeController`
|
|
454
|
+
from `@houstonp/rubiks-cube/controller`. It composes a `RubiksCubeState` with any object implementing the small
|
|
455
|
+
`RubiksCubeViewInterface` (`slice`, `setState`, `reset`).
|
|
263
456
|
|
|
264
457
|
## Rubiks Cube Notation
|
|
265
458
|
|
|
266
459
|
Notations can include the number of rotations of a face. For example, `U2` means rotate the upper face 180 degrees.
|
|
267
460
|
|
|
268
|
-
Notations can also include a prime symbol `'` to indicate a counter‑clockwise rotation. For example, `U'` means rotate
|
|
461
|
+
Notations can also include a prime symbol `'` to indicate a counter‑clockwise rotation. For example, `U'` means rotate
|
|
462
|
+
the upper face counter‑clockwise. The direction is always determined relative to the face being moved.
|
|
269
463
|
|
|
270
|
-
Notations can also include a layer identifier for larger cubes. For example `3R2'` means rotate the
|
|
464
|
+
Notations can also include a layer identifier for larger cubes. For example `3R2'` means rotate the third layer from the
|
|
465
|
+
right face counter‑clockwise twice.
|
|
271
466
|
|
|
272
|
-
When both a number and a prime symbol are included, the number is stated before the prime symbol. For example, `U2'`
|
|
467
|
+
When both a number and a prime symbol are included, the number is stated before the prime symbol. For example, `U2'`
|
|
468
|
+
means rotate the upper face 180 degrees counter‑clockwise, and `U'2` is invalid.
|
|
273
469
|
|
|
274
|
-
Valid notation constants are available via the `core` export. Use these constants instead of string literals for better
|
|
470
|
+
Valid notation constants are available via the `core` export. Use these constants instead of string literals for better
|
|
471
|
+
type safety and autocomplete support.
|
|
275
472
|
|
|
276
|
-
Duplicate or equivalent notations are not provided in the `core` export. For example `R3` and `R'` are equivalent and
|
|
473
|
+
Duplicate or equivalent notations are not provided in the `core` export. For example `R3` and `R'` are equivalent and
|
|
474
|
+
only `R'` is provided in the export.
|
|
277
475
|
|
|
278
476
|
```js
|
|
279
|
-
import { RubiksCubeElement } from '@houstonp/rubiks-cube';
|
|
280
|
-
import { Rotations, Movements,
|
|
477
|
+
import { RubiksCubeElement, AttributeNames, PeekActions } from '@houstonp/rubiks-cube/view';
|
|
478
|
+
import { Rotations, Movements, CubeTypes, AnimationStyles } from '@houstonp/rubiks-cube/core';
|
|
281
479
|
|
|
282
480
|
const cube = document.querySelector('rubiks-cube');
|
|
283
481
|
|
|
@@ -291,21 +489,22 @@ cube.rotate(Rotations.x);
|
|
|
291
489
|
cube.rotate(Rotations.y2);
|
|
292
490
|
cube.rotate(Rotations.zP);
|
|
293
491
|
|
|
294
|
-
// Use constants for peek
|
|
295
|
-
cube.peek(
|
|
296
|
-
cube.peek(
|
|
492
|
+
// Use constants for peek actions
|
|
493
|
+
cube.peek(PeekActions.Right);
|
|
494
|
+
cube.peek(PeekActions.RightUp);
|
|
297
495
|
|
|
298
496
|
// Use constants for cube types
|
|
299
|
-
cube.setAttribute(AttributeNames.
|
|
497
|
+
cube.setAttribute(AttributeNames.cubeType, CubeTypes.Four);
|
|
300
498
|
|
|
301
499
|
// Use constants for animation styles
|
|
302
|
-
cube.setAttribute(AttributeNames.
|
|
500
|
+
cube.setAttribute(AttributeNames.animationStyle, AnimationStyles.Exponential);
|
|
303
501
|
```
|
|
304
502
|
|
|
305
503
|
Notation must match the following Regex
|
|
306
504
|
|
|
307
505
|
`/([1234567]|[123456]-[1234567])?([RLUDFB]w|[RLUDFBMES]|[rludfbmes])([123])?(\')?$/`
|
|
308
|
-
|
|
506
|
+
|
|
507
|
+
Some notation may not work as intended as there is no known interpretation. e.g. `2M`.
|
|
309
508
|
|
|
310
509
|
Standard Notation
|
|
311
510
|
|
|
@@ -331,9 +530,35 @@ Big Cube Notation. Not all listed for brevity.
|
|
|
331
530
|
|
|
332
531
|
| Notation | Movement |
|
|
333
532
|
| -------- | ----------------------------------------------- |
|
|
334
|
-
| NR | Nth
|
|
335
|
-
| NRw | All
|
|
336
|
-
| Nr | All
|
|
533
|
+
| NR | Nth right‑most layer |
|
|
534
|
+
| NRw | All right layers up to the Nth right‑most layer |
|
|
535
|
+
| Nr | All right layers up to the Nth right‑most layer |
|
|
536
|
+
| X-YRw | Layers X through Y from the right face |
|
|
537
|
+
|
|
538
|
+
Range moves (`X-YRw`) apply to **wide moves**, **single face moves** (`R`, `L`, `U`, `D`, `F`, `B`), and **slice
|
|
539
|
+
moves** (`M`, `E`, `S`). The `Movements.Range` builder validates the inputs at the call site and returns a
|
|
540
|
+
typed string:
|
|
541
|
+
|
|
542
|
+
```js
|
|
543
|
+
import { Movements } from '@houstonp/rubiks-cube/core';
|
|
544
|
+
|
|
545
|
+
// Wide moves
|
|
546
|
+
await cube.move(Movements.Range(2, 4, Movements.Wide.Rw)); // → '2-4Rw'
|
|
547
|
+
await cube.move(Movements.Range(3, 5, Movements.Wide.r)); // → '3-5r'
|
|
548
|
+
await cube.move(Movements.Range(2, 4, Movements.Wide.RwP)); // → "2-4Rw'"
|
|
549
|
+
|
|
550
|
+
// Single face moves
|
|
551
|
+
await cube.move(Movements.Range(2, 4, Movements.Single.R)); // → '2-4R'
|
|
552
|
+
await cube.move(Movements.Range(2, 3, Movements.Single.LP)); // → "2-3L'"
|
|
553
|
+
|
|
554
|
+
// Slice moves
|
|
555
|
+
await cube.move(Movements.Range(2, 4, Movements.Single.M)); // → '2-4M'
|
|
556
|
+
await cube.move(Movements.Range(2, 3, Movements.Single.SP)); // → "2-3S'"
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
`Movements.Range` throws if `lower < 1`, `lower >= upper`, `upper > 7`, or the base move has an existing layer
|
|
560
|
+
prefix (e.g. `2R`, `2-4Rw`). It does not check the range against the current cube size — passing
|
|
561
|
+
`Range(2, 6, ...)` to a 4x4 produces a string that the parser will reject at `move()` time.
|
|
337
562
|
|
|
338
563
|
Rotation Notation
|
|
339
564
|
|
|
@@ -367,4 +592,6 @@ This repository is set up as an npm package and uses **Bun** for scripts and typ
|
|
|
367
592
|
bun run build:types
|
|
368
593
|
```
|
|
369
594
|
|
|
370
|
-
The generated `.d.ts` files are emitted into the `types/` directory (ignored in git) and are used for consumers of the
|
|
595
|
+
The generated `.d.ts` files are emitted into the `types/` directory (ignored in git) and are used for consumers of the
|
|
596
|
+
package. There is currently no dedicated demo app in this repository; you can import the component into your own app
|
|
597
|
+
(e.g., Vite, Next.js, or any ES‑module‑aware bundler) to experiment locally.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@houstonp/rubiks-cube",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "Rubiks Cube Web Component built with threejs",
|
|
5
5
|
"author": "Houston Pearse",
|
|
6
6
|
"license": "MIT",
|
|
@@ -11,16 +11,26 @@
|
|
|
11
11
|
"renderer",
|
|
12
12
|
"threejs"
|
|
13
13
|
],
|
|
14
|
-
"main": "./src/index.js",
|
|
15
|
-
"types": "./types/index.d.ts",
|
|
16
14
|
"exports": {
|
|
17
|
-
"
|
|
18
|
-
"types": "./types/index.d.ts",
|
|
19
|
-
"default": "./src/index.js"
|
|
15
|
+
"./view": {
|
|
16
|
+
"types": "./types/webComponent/index.d.ts",
|
|
17
|
+
"default": "./src/webComponent/index.js"
|
|
18
|
+
},
|
|
19
|
+
"./three": {
|
|
20
|
+
"types": "./types/rubiksCube3D/index.d.ts",
|
|
21
|
+
"default": "./src/rubiksCube3D/index.js"
|
|
22
|
+
},
|
|
23
|
+
"./controller": {
|
|
24
|
+
"types": "./types/rubiksCube/index.d.ts",
|
|
25
|
+
"default": "./src/rubiksCube/index.js"
|
|
20
26
|
},
|
|
21
27
|
"./core": {
|
|
22
|
-
"types": "./types/core.d.ts",
|
|
23
|
-
"default": "./src/core.js"
|
|
28
|
+
"types": "./types/core/index.d.ts",
|
|
29
|
+
"default": "./src/core/index.js"
|
|
30
|
+
},
|
|
31
|
+
"./state": {
|
|
32
|
+
"types": "./types/state/index.d.ts",
|
|
33
|
+
"default": "./src/state/index.js"
|
|
24
34
|
}
|
|
25
35
|
},
|
|
26
36
|
"scripts": {
|