@houstonp/rubiks-cube 2.0.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +494 -63
- package/package.json +22 -12
- package/src/core/index.js +478 -0
- package/src/rubiksCube/index.js +3 -0
- package/src/rubiksCube/rubiksCubeController.js +111 -0
- package/src/rubiksCube3D/centerPiece.js +79 -0
- package/src/rubiksCube3D/cornerPiece.js +114 -0
- package/src/rubiksCube3D/cubeConfig.js +87 -0
- package/src/rubiksCube3D/cubeSettings.js +30 -0
- package/src/rubiksCube3D/edgePiece.js +51 -0
- package/src/rubiksCube3D/index.js +3 -0
- package/src/rubiksCube3D/rubiksCube3D.js +383 -0
- package/src/rubiksCube3D/sticker.js +38 -0
- 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/{cameraState.js → webComponent/cameraState.js} +17 -25
- package/src/webComponent/constants.js +67 -0
- package/src/{debouncer.js → webComponent/debouncer.js} +1 -1
- package/src/webComponent/index.js +7 -0
- package/src/webComponent/rubiksCubeElement.js +379 -0
- package/src/{settings.js → webComponent/settings.js} +47 -22
- package/tests/common.js +10 -0
- 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/setup.js +36 -0
- package/tests/testScrambles.js +194 -0
- package/types/core/index.d.ts +451 -0
- 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/rubiksCube3D/edgePiece.d.ts +18 -0
- 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} +9 -8
- package/src/core.js +0 -127
- package/src/cube/cube.js +0 -324
- package/src/cube/cubeRotation.js +0 -79
- package/src/cube/cubeSettings.js +0 -18
- package/src/cube/cubeState.js +0 -192
- package/src/cube/slice.js +0 -143
- package/src/index.js +0 -496
- package/src/schema.js +0 -22
- package/src/threejs/materials.js +0 -54
- package/src/threejs/pieces.js +0 -100
- package/src/threejs/stickers.js +0 -40
- package/types/cameraState.d.ts +0 -19
- package/types/core.d.ts +0 -125
- package/types/cube/cube.d.ts +0 -102
- package/types/cube/cubeRotation.d.ts +0 -33
- package/types/cube/cubeSettings.d.ts +0 -17
- package/types/cube/cubeState.d.ts +0 -16
- package/types/cube/slice.d.ts +0 -15
- package/types/index.d.ts +0 -65
- package/types/schema.d.ts +0 -11
- package/types/threejs/materials.d.ts +0 -21
- package/types/threejs/pieces.d.ts +0 -28
- package/types/threejs/stickers.d.ts +0 -6
- /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
|
|
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();
|
|
@@ -34,7 +67,14 @@ RubiksCubeElement.register();
|
|
|
34
67
|
<title>Rubiks Cube Demo</title>
|
|
35
68
|
</head>
|
|
36
69
|
<body>
|
|
37
|
-
|
|
70
|
+
<!-- Create a 3x3 cube with custom settings -->
|
|
71
|
+
<rubiks-cube cube-type="Three" animation-speed-ms="1000" animation-style="exponential" piece-gap="1.04" camera-speed-ms="100"></rubiks-cube>
|
|
72
|
+
|
|
73
|
+
<!-- Or create a 2x2 cube -->
|
|
74
|
+
<rubiks-cube cube-type="Two"></rubiks-cube>
|
|
75
|
+
|
|
76
|
+
<!-- Or create a 7x7 cube -->
|
|
77
|
+
<rubiks-cube cube-type="Seven"></rubiks-cube>
|
|
38
78
|
|
|
39
79
|
<script type="module" src="index.js"></script>
|
|
40
80
|
</body>
|
|
@@ -43,109 +83,492 @@ RubiksCubeElement.register();
|
|
|
43
83
|
|
|
44
84
|
## Component attributes
|
|
45
85
|
|
|
46
|
-
These attributes control animation, spacing, and
|
|
47
|
-
|
|
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.
|
|
48
88
|
|
|
49
89
|
```js
|
|
50
|
-
import {
|
|
90
|
+
import { RubiksCubeElement, AttributeNames } from '@houstonp/rubiks-cube/view';
|
|
91
|
+
import { CubeTypes, AnimationStyles } from '@houstonp/rubiks-cube/core';
|
|
51
92
|
|
|
52
93
|
const cube = document.querySelector('rubiks-cube');
|
|
94
|
+
|
|
95
|
+
// Get an attribute value
|
|
53
96
|
const animationSpeed = cube.getAttribute(AttributeNames.animationSpeed);
|
|
54
|
-
|
|
97
|
+
console.log('Current animation speed:', animationSpeed);
|
|
98
|
+
|
|
99
|
+
// Set an attribute value
|
|
100
|
+
cube.setAttribute(AttributeNames.animationSpeed, '500');
|
|
101
|
+
cube.setAttribute(AttributeNames.cubeType, CubeTypes.Four); // Change to 4x4 cube
|
|
102
|
+
cube.setAttribute(AttributeNames.animationStyle, AnimationStyles.Exponential);
|
|
103
|
+
cube.setAttribute(AttributeNames.pieceGap, '1.05');
|
|
104
|
+
cube.setAttribute(AttributeNames.cameraRadius, '6');
|
|
105
|
+
cube.setAttribute(AttributeNames.cameraFieldOfView, '80');
|
|
106
|
+
cube.setAttribute(AttributeNames.cameraPeekAngleHorizontal, '0.7');
|
|
107
|
+
cube.setAttribute(AttributeNames.cameraPeekAngleVertical, '0.7');
|
|
55
108
|
```
|
|
56
109
|
|
|
57
|
-
| attribute | accepted values
|
|
58
|
-
| ---------------------------- |
|
|
59
|
-
|
|
|
60
|
-
| animation-
|
|
61
|
-
|
|
|
62
|
-
|
|
|
63
|
-
| camera-
|
|
64
|
-
| camera-
|
|
65
|
-
| camera-peek-angle-
|
|
66
|
-
| camera-
|
|
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` |
|
|
67
121
|
|
|
68
122
|
## Programmatic control
|
|
69
123
|
|
|
70
|
-
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.
|
|
126
|
+
|
|
127
|
+
### Move
|
|
128
|
+
|
|
129
|
+
Performs a cube movement and resolves with the new state string.
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
move(move: Movement, options?: AnimationOptions): Promise<string>
|
|
133
|
+
```
|
|
71
134
|
|
|
72
135
|
```js
|
|
73
|
-
import { RubiksCubeElement } from '@houstonp/rubiks-cube';
|
|
136
|
+
import { RubiksCubeElement } from '@houstonp/rubiks-cube/view';
|
|
137
|
+
import { Movements } from '@houstonp/rubiks-cube/core';
|
|
138
|
+
|
|
74
139
|
const cube = document.querySelector('rubiks-cube');
|
|
75
140
|
|
|
76
|
-
//
|
|
77
|
-
|
|
141
|
+
// Single layer moves
|
|
142
|
+
await cube.move(Movements.Single.R); // Right face clockwise
|
|
143
|
+
await cube.move(Movements.Single.R2); // Right face 180 degrees
|
|
144
|
+
await cube.move(Movements.Single.RP); // Right face counter-clockwise
|
|
145
|
+
await cube.move(Movements.Single.U); // Upper face clockwise
|
|
146
|
+
await cube.move(Movements.Single.FP); // Front face counter-clockwise
|
|
147
|
+
|
|
148
|
+
// Wide moves
|
|
149
|
+
await cube.move(Movements.Wide.Rw); // Right two layers (Rw)
|
|
150
|
+
await cube.move(Movements.Wide.r); // Right two layers (r)
|
|
151
|
+
|
|
152
|
+
// Layer-specific moves (for 4x4+ cubes)
|
|
153
|
+
await cube.move(Movements.Two.R); // Second layer right
|
|
154
|
+
await cube.move(Movements.Three.R); // Third layer right (for 4x4+)
|
|
155
|
+
await cube.move(Movements.Four.R); // Fourth layer right (for 5x5+)
|
|
156
|
+
|
|
157
|
+
// Middle layer moves
|
|
158
|
+
await cube.move(Movements.Single.M); // Middle layer
|
|
159
|
+
await cube.move(Movements.Single.E); // Equatorial layer
|
|
160
|
+
await cube.move(Movements.Single.S); // Standing layer
|
|
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
|
+
|
|
171
|
+
// Chain multiple moves
|
|
172
|
+
const moves = [Movements.Single.R, Movements.Single.U, Movements.Single.RP, Movements.Single.UP];
|
|
173
|
+
for (const move of moves) {
|
|
174
|
+
const state = await cube.move(move);
|
|
175
|
+
console.log('State after', move, ':', state);
|
|
176
|
+
}
|
|
177
|
+
```
|
|
78
178
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
cube
|
|
82
|
-
|
|
83
|
-
|
|
179
|
+
### Rotate
|
|
180
|
+
|
|
181
|
+
Rotates the entire cube and resolves with the new state string.
|
|
182
|
+
|
|
183
|
+
```ts
|
|
184
|
+
rotate(rotation: Rotation, options?: AnimationOptions): Promise<string>
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
```js
|
|
188
|
+
import { RubiksCubeElement } from '@houstonp/rubiks-cube/view';
|
|
189
|
+
import { Rotations } from '@houstonp/rubiks-cube/core';
|
|
190
|
+
|
|
191
|
+
const cube = document.querySelector('rubiks-cube');
|
|
192
|
+
|
|
193
|
+
// Rotate cube on x-axis (like R move)
|
|
194
|
+
await cube.rotate(Rotations.x); // 90 degrees clockwise
|
|
195
|
+
await cube.rotate(Rotations.x2); // 180 degrees
|
|
196
|
+
await cube.rotate(Rotations.xP); // 90 degrees counter-clockwise
|
|
197
|
+
|
|
198
|
+
// Rotate cube on y-axis (like U move)
|
|
199
|
+
await cube.rotate(Rotations.y); // 90 degrees clockwise
|
|
200
|
+
await cube.rotate(Rotations.y2); // 180 degrees
|
|
201
|
+
await cube.rotate(Rotations.yP); // 90 degrees counter-clockwise
|
|
202
|
+
|
|
203
|
+
// Rotate cube on z-axis (like F move)
|
|
204
|
+
await cube.rotate(Rotations.z); // 90 degrees clockwise
|
|
205
|
+
await cube.rotate(Rotations.z2); // 180 degrees
|
|
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 });
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Reset
|
|
216
|
+
|
|
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
|
|
84
222
|
```
|
|
85
223
|
|
|
86
|
-
|
|
224
|
+
```js
|
|
225
|
+
import { RubiksCubeElement } from '@houstonp/rubiks-cube/view';
|
|
226
|
+
import { Movements } from '@houstonp/rubiks-cube/core';
|
|
87
227
|
|
|
88
|
-
|
|
89
|
-
- `cube.rotate(rotation)` – rotates the entire cube and resolves with the new state string.
|
|
90
|
-
- `cube.reset()` – resets the cube to the solved state and resolves with the new state string.
|
|
91
|
-
- `cube.peek(peekType)` – animates the camera to a new “peek” position and resolves with the new peek state.
|
|
228
|
+
const cube = document.querySelector('rubiks-cube');
|
|
92
229
|
|
|
93
|
-
|
|
230
|
+
// Reset to solved state
|
|
231
|
+
const solvedState = cube.reset();
|
|
232
|
+
console.log('Cube reset to solved state:', solvedState);
|
|
94
233
|
|
|
95
|
-
|
|
234
|
+
// Reset after performing some moves
|
|
235
|
+
await cube.move(Movements.Single.R);
|
|
236
|
+
await cube.move(Movements.Single.U);
|
|
237
|
+
const resetState = cube.reset();
|
|
238
|
+
```
|
|
96
239
|
|
|
97
|
-
|
|
240
|
+
### SetState / GetState
|
|
241
|
+
|
|
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
|
+
```
|
|
98
249
|
|
|
99
250
|
```js
|
|
100
|
-
import { RubiksCubeElement } from '@houstonp/rubiks-cube';
|
|
101
|
-
import {
|
|
251
|
+
import { RubiksCubeElement } from '@houstonp/rubiks-cube/view';
|
|
252
|
+
import { Movements } from '@houstonp/rubiks-cube/core';
|
|
102
253
|
|
|
103
254
|
const cube = document.querySelector('rubiks-cube');
|
|
104
|
-
|
|
105
|
-
|
|
255
|
+
|
|
256
|
+
// Save current state
|
|
257
|
+
await cube.move(Movements.Single.R);
|
|
258
|
+
const currentState = cube.getState();
|
|
259
|
+
|
|
260
|
+
// Later, restore that state
|
|
261
|
+
const ok = cube.setState(currentState);
|
|
262
|
+
if (!ok) {
|
|
263
|
+
console.error('Failed to set state — string did not match a supported cube size');
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Set a specific scrambled state (example for 3x3)
|
|
267
|
+
const scrambledState = 'UULUUFUUFRRUBRRURRFFDFFUFFFDDRDDDDDDBLLLLLLLLBRRBBBBBB';
|
|
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
|
|
106
280
|
```
|
|
107
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
|
|
290
|
+
```
|
|
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
|
+
|
|
295
|
+
### Peek
|
|
296
|
+
|
|
297
|
+
Animates the camera to a new "peek" position and resolves with the new peek state.
|
|
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
|
+
|
|
316
|
+
```js
|
|
317
|
+
import { RubiksCubeElement, PeekActions, PeekStates } from '@houstonp/rubiks-cube/view';
|
|
318
|
+
|
|
319
|
+
const cube = document.querySelector('rubiks-cube');
|
|
320
|
+
|
|
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 });
|
|
339
|
+
```
|
|
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
|
+
|
|
358
|
+
### Complete Example
|
|
359
|
+
|
|
360
|
+
```js
|
|
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();
|
|
365
|
+
|
|
366
|
+
const cube = document.querySelector('rubiks-cube');
|
|
367
|
+
|
|
368
|
+
// Configure cube settings
|
|
369
|
+
cube.setAttribute(AttributeNames.cubeType, CubeTypes.Four); // Use 4x4 cube
|
|
370
|
+
cube.setAttribute(AttributeNames.animationSpeed, '800');
|
|
371
|
+
cube.setAttribute(AttributeNames.animationStyle, AnimationStyles.Exponential);
|
|
372
|
+
|
|
373
|
+
// Perform a sequence of moves
|
|
374
|
+
await cube.move(Movements.Single.R);
|
|
375
|
+
await cube.move(Movements.Single.U);
|
|
376
|
+
await cube.rotate(Rotations.y);
|
|
377
|
+
await cube.move(Movements.Wide.Rw);
|
|
378
|
+
|
|
379
|
+
// Peek to see a different angle
|
|
380
|
+
await cube.peek(PeekActions.RightUp);
|
|
381
|
+
|
|
382
|
+
// Save the current state
|
|
383
|
+
await cube.move(Movements.Single.F);
|
|
384
|
+
const currentState = cube.getState();
|
|
385
|
+
|
|
386
|
+
// Reset and restore
|
|
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' });
|
|
448
|
+
```
|
|
449
|
+
|
|
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`).
|
|
456
|
+
|
|
108
457
|
## Rubiks Cube Notation
|
|
109
458
|
|
|
110
459
|
Notations can include the number of rotations of a face. For example, `U2` means rotate the upper face 180 degrees.
|
|
111
460
|
|
|
112
|
-
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.
|
|
113
463
|
|
|
114
|
-
|
|
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.
|
|
115
466
|
|
|
116
|
-
|
|
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.
|
|
469
|
+
|
|
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.
|
|
472
|
+
|
|
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.
|
|
117
475
|
|
|
118
476
|
```js
|
|
119
|
-
import { RubiksCubeElement } from '@houstonp/rubiks-cube';
|
|
120
|
-
import { Rotations, Movements,
|
|
477
|
+
import { RubiksCubeElement, AttributeNames, PeekActions } from '@houstonp/rubiks-cube/view';
|
|
478
|
+
import { Rotations, Movements, CubeTypes, AnimationStyles } from '@houstonp/rubiks-cube/core';
|
|
121
479
|
|
|
122
480
|
const cube = document.querySelector('rubiks-cube');
|
|
123
|
-
|
|
124
|
-
|
|
481
|
+
|
|
482
|
+
// Use constants for moves
|
|
483
|
+
cube.move(Movements.Single.R);
|
|
484
|
+
cube.move(Movements.Single.U2);
|
|
485
|
+
cube.move(Movements.Single.FP);
|
|
486
|
+
|
|
487
|
+
// Use constants for rotations
|
|
488
|
+
cube.rotate(Rotations.x);
|
|
489
|
+
cube.rotate(Rotations.y2);
|
|
490
|
+
cube.rotate(Rotations.zP);
|
|
491
|
+
|
|
492
|
+
// Use constants for peek actions
|
|
493
|
+
cube.peek(PeekActions.Right);
|
|
494
|
+
cube.peek(PeekActions.RightUp);
|
|
495
|
+
|
|
496
|
+
// Use constants for cube types
|
|
497
|
+
cube.setAttribute(AttributeNames.cubeType, CubeTypes.Four);
|
|
498
|
+
|
|
499
|
+
// Use constants for animation styles
|
|
500
|
+
cube.setAttribute(AttributeNames.animationStyle, AnimationStyles.Exponential);
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
Notation must match the following Regex
|
|
504
|
+
|
|
505
|
+
`/([1234567]|[123456]-[1234567])?([RLUDFB]w|[RLUDFBMES]|[rludfbmes])([123])?(\')?$/`
|
|
506
|
+
|
|
507
|
+
Some notation may not work as intended as there is no known interpretation. e.g. `2M`.
|
|
508
|
+
|
|
509
|
+
Standard Notation
|
|
510
|
+
|
|
511
|
+
| Notation | Movement |
|
|
512
|
+
| -------- | ------------------------------------------ |
|
|
513
|
+
| U | Top face clockwise |
|
|
514
|
+
| u | Top two layers clockwise |
|
|
515
|
+
| D | Bottom face clockwise |
|
|
516
|
+
| d | Bottom two layers clockwise |
|
|
517
|
+
| L | Left face clockwise |
|
|
518
|
+
| l | Left two layers clockwise |
|
|
519
|
+
| R | Right face clockwise |
|
|
520
|
+
| r | Right two layers clockwise |
|
|
521
|
+
| F | Front face clockwise |
|
|
522
|
+
| f | Front two layers clockwise |
|
|
523
|
+
| B | Back face clockwise |
|
|
524
|
+
| b | Back two layers clockwise |
|
|
525
|
+
| M | Middle layer clockwise (relative to L) |
|
|
526
|
+
| E | Equatorial layer clockwise (relative to D) |
|
|
527
|
+
| S | Standing layer clockwise (relative to F) |
|
|
528
|
+
|
|
529
|
+
Big Cube Notation. Not all listed for brevity.
|
|
530
|
+
|
|
531
|
+
| Notation | Movement |
|
|
532
|
+
| -------- | ----------------------------------------------- |
|
|
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'"
|
|
125
557
|
```
|
|
126
558
|
|
|
127
|
-
|
|
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.
|
|
562
|
+
|
|
563
|
+
Rotation Notation
|
|
564
|
+
|
|
565
|
+
| Notation | Rotation |
|
|
128
566
|
| -------- | ------------------------------------------------ |
|
|
129
|
-
| U | Top face clockwise |
|
|
130
|
-
| u | Top two layers clockwise |
|
|
131
|
-
| D | Bottom face clockwise |
|
|
132
|
-
| d | Bottom two layers clockwise |
|
|
133
|
-
| L | Left face clockwise |
|
|
134
|
-
| l | Left two layers clockwise |
|
|
135
|
-
| R | Right face clockwise |
|
|
136
|
-
| r | Right two layers clockwise |
|
|
137
|
-
| F | Front face clockwise |
|
|
138
|
-
| f | Front two layers clockwise |
|
|
139
|
-
| B | Back face clockwise |
|
|
140
|
-
| b | Back two layers clockwise |
|
|
141
|
-
| M | Middle layer clockwise (relative to L) |
|
|
142
|
-
| E | Equatorial layer clockwise (relative to D) |
|
|
143
|
-
| S | Standing layer clockwise (relative to F) |
|
|
144
567
|
| x | Rotate cube on x axis clockwise (direction of R) |
|
|
145
568
|
| y | Rotate cube on y axis clockwise (direction of U) |
|
|
146
569
|
| z | Rotate cube on z axis clockwise (direction of F) |
|
|
147
570
|
|
|
148
|
-
These symbols align with the movements and rotations accepted by the component
|
|
571
|
+
These symbols align with the movements and rotations accepted by the component's API.
|
|
149
572
|
|
|
150
573
|
## Development
|
|
151
574
|
|
|
@@ -157,10 +580,18 @@ This repository is set up as an npm package and uses **Bun** for scripts and typ
|
|
|
157
580
|
bun install
|
|
158
581
|
```
|
|
159
582
|
|
|
583
|
+
- **Run Tests**
|
|
584
|
+
|
|
585
|
+
```bash
|
|
586
|
+
bun run test
|
|
587
|
+
```
|
|
588
|
+
|
|
160
589
|
- **Generate TypeScript declaration files**
|
|
161
590
|
|
|
162
591
|
```bash
|
|
163
592
|
bun run build:types
|
|
164
593
|
```
|
|
165
594
|
|
|
166
|
-
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.
|