@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
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import './setup.js';
|
|
3
|
+
import { describe, it, expect, test } from 'bun:test';
|
|
4
|
+
import { CubeTypes, Movements, Rotations } from '../src/core.js';
|
|
5
|
+
import { toKociemba } from '../src/cube/stickerState.js';
|
|
6
|
+
import { createTestCube, drainUpdates } from './common.js';
|
|
7
|
+
|
|
8
|
+
describe('5x5 Tests', () => {
|
|
9
|
+
it('Valid initial Kociemba state string', () => {
|
|
10
|
+
//Arange
|
|
11
|
+
const cube = createTestCube(CubeTypes.Five);
|
|
12
|
+
|
|
13
|
+
//Act
|
|
14
|
+
const state = toKociemba(cube._cubeInfo.initialStickerState);
|
|
15
|
+
|
|
16
|
+
//Assert
|
|
17
|
+
expect(state.length).toBe(150);
|
|
18
|
+
for (const ch of state) {
|
|
19
|
+
expect('UDLRFB').toContain(ch);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const counts = {
|
|
23
|
+
U: 0,
|
|
24
|
+
D: 0,
|
|
25
|
+
L: 0,
|
|
26
|
+
R: 0,
|
|
27
|
+
F: 0,
|
|
28
|
+
B: 0,
|
|
29
|
+
};
|
|
30
|
+
for (const ch of state) {
|
|
31
|
+
const face = /** @type {"R" | "U" | "F" | "L" | "D" | "B"} */ (ch);
|
|
32
|
+
counts[face]++;
|
|
33
|
+
}
|
|
34
|
+
expect(Object.values(counts).every((c) => c === 25)).toBe(true);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('reset method returns the cube to the initial state', () => {
|
|
38
|
+
// Arrange
|
|
39
|
+
const cube = createTestCube(CubeTypes.Five);
|
|
40
|
+
const initialState = toKociemba(cube._cubeInfo.initialStickerState);
|
|
41
|
+
/** @type {string?} */
|
|
42
|
+
let completedState = null;
|
|
43
|
+
/** @type {string?} */
|
|
44
|
+
let failedReason = null;
|
|
45
|
+
cube.movement(
|
|
46
|
+
Movements.Single.R,
|
|
47
|
+
(state) => {
|
|
48
|
+
completedState = state;
|
|
49
|
+
return true;
|
|
50
|
+
},
|
|
51
|
+
(reason) => {
|
|
52
|
+
failedReason = reason;
|
|
53
|
+
return true;
|
|
54
|
+
},
|
|
55
|
+
);
|
|
56
|
+
drainUpdates(cube);
|
|
57
|
+
|
|
58
|
+
// Act
|
|
59
|
+
/** @type {string?} */
|
|
60
|
+
let resetState = null;
|
|
61
|
+
cube.reset((state) => {
|
|
62
|
+
resetState = state;
|
|
63
|
+
return true;
|
|
64
|
+
});
|
|
65
|
+
drainUpdates(cube);
|
|
66
|
+
|
|
67
|
+
// Assert
|
|
68
|
+
expect(failedReason).toBeNull();
|
|
69
|
+
expect(completedState).not.toBeNull();
|
|
70
|
+
expect(completedState).not.toBe(initialState);
|
|
71
|
+
expect(/** @type {string?} **/ (resetState)).toBe(initialState);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('solve', () => {
|
|
75
|
+
// Arrange
|
|
76
|
+
const cube = createTestCube(CubeTypes.Five);
|
|
77
|
+
const scramble =
|
|
78
|
+
"Dw Uw' U' Fw' L Fw R' U2 Rw Dw Fw' L' Lw D Dw U2 Bw' U' D Lw R B Lw2 U D' Uw R' L F B2 Lw2 R2 Dw U2 Rw' Lw' R D2 Fw Uw L' Bw B2 D2 Fw' Rw D U2 Lw' Fw2 Lw' Uw2 B2 Rw' F2 Rw2 L' D F' Rw";
|
|
79
|
+
const scrambleMoves = /** @type {import('../src/core.js').Movement[]} */ (scramble.split(' '));
|
|
80
|
+
|
|
81
|
+
for (const move of scrambleMoves) {
|
|
82
|
+
cube.movement(
|
|
83
|
+
move,
|
|
84
|
+
() => {},
|
|
85
|
+
() => {
|
|
86
|
+
throw new Error('Movement failed unexpectedly');
|
|
87
|
+
},
|
|
88
|
+
);
|
|
89
|
+
drainUpdates(cube);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Act
|
|
93
|
+
const solution =
|
|
94
|
+
"z' y r U b 3r' z' U r2 U' r' U' r z x' 3r U' 3r' z' x' D r' F' r U' U' r' z r U' r' z' y' r U2' r' z x' F' U' r x' l R u' R2' u D' R U r' U' D' x' U' R' U' r2 3r2' 4r U x' L r' U2' r2 3r2' L' D' x L2 U 4r U' r' r' U' r U' U' 4r' U' 3r2 U2' 3r2' 4r2 U r 4r 4r' U' r U U r' 4r r' 4r U' 4r2' r2 U' r' 4r U' r' 4r U2 r U' r2' U2' r2 4r' U' r' 3r U 3r' r2 U r' z' U L 3d' U F R' F' R 3u' D' R U R U' R2' F R' F' R u y R U' R' u' d U' R U' R' u2' R' U R2 U' R' u 3u' U' U' U R U' R' y' R U R' F R' F' R d R U' R' 3u' R' F R F' R U' R' d R U R' u2' R' U' R2 U' R' y' 4d R U' R' u' 3u R U' R' u d' R U R' 3u' U F R' F' R U' d U' R' U R U' R' U' R U' L' U L y' R' U2' R U' U 4d D' L' U L D U R U' R' U R U' R' 4d' U' F' R U R' U' R' F R2 U R' U R U2' R' L' U' L F L' U' L U L F' L2' U L U x2 y'";
|
|
95
|
+
const solutionActions = /** @type {(import('../src/core.js').Movement | import('../src/core.js').Rotation)[]} */ (solution.split(' '));
|
|
96
|
+
|
|
97
|
+
let finalState = null;
|
|
98
|
+
for (const action of solutionActions) {
|
|
99
|
+
if (action.includes('x') || action.includes('y') || action.includes('z')) {
|
|
100
|
+
cube.rotate(
|
|
101
|
+
/** @type {import('../src/core.js').Rotation} */ (action),
|
|
102
|
+
(state) => {
|
|
103
|
+
finalState = state;
|
|
104
|
+
},
|
|
105
|
+
() => {
|
|
106
|
+
throw new Error('Rotation failed unexpectedly');
|
|
107
|
+
},
|
|
108
|
+
);
|
|
109
|
+
} else {
|
|
110
|
+
cube.movement(
|
|
111
|
+
/** @type {import('../src/core.js').Movement} */ (action),
|
|
112
|
+
(state) => {
|
|
113
|
+
finalState = state;
|
|
114
|
+
},
|
|
115
|
+
() => {
|
|
116
|
+
throw new Error('Movement failed unexpectedly');
|
|
117
|
+
},
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
drainUpdates(cube);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Assert
|
|
124
|
+
expect(/** @type {string?} **/ (finalState)).toBe(toKociemba(cube._cubeInfo.initialStickerState));
|
|
125
|
+
});
|
|
126
|
+
});
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import './setup.js';
|
|
3
|
+
import { describe, it, expect, test } from 'bun:test';
|
|
4
|
+
import { CubeTypes, Movements, Rotations } from '../src/core.js';
|
|
5
|
+
import { toKociemba } from '../src/cube/stickerState.js';
|
|
6
|
+
import { createTestCube, drainUpdates } from './common.js';
|
|
7
|
+
|
|
8
|
+
describe('4x4 Tests', () => {
|
|
9
|
+
it('Valid initial Kociemba state string', () => {
|
|
10
|
+
//Arange
|
|
11
|
+
const cube = createTestCube(CubeTypes.Four);
|
|
12
|
+
|
|
13
|
+
//Act
|
|
14
|
+
const state = toKociemba(cube._cubeInfo.initialStickerState);
|
|
15
|
+
|
|
16
|
+
//Assert
|
|
17
|
+
expect(state.length).toBe(96);
|
|
18
|
+
for (const ch of state) {
|
|
19
|
+
expect('UDLRFB').toContain(ch);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const counts = {
|
|
23
|
+
U: 0,
|
|
24
|
+
D: 0,
|
|
25
|
+
L: 0,
|
|
26
|
+
R: 0,
|
|
27
|
+
F: 0,
|
|
28
|
+
B: 0,
|
|
29
|
+
};
|
|
30
|
+
for (const ch of state) {
|
|
31
|
+
const face = /** @type {"R" | "U" | "F" | "L" | "D" | "B"} */ (ch);
|
|
32
|
+
counts[face]++;
|
|
33
|
+
}
|
|
34
|
+
expect(Object.values(counts).every((c) => c === 16)).toBe(true);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('reset method returns the cube to the initial state', () => {
|
|
38
|
+
// Arrange
|
|
39
|
+
const cube = createTestCube(CubeTypes.Four);
|
|
40
|
+
const initialState = toKociemba(cube._cubeInfo.initialStickerState);
|
|
41
|
+
/** @type {string?} */
|
|
42
|
+
let completedState = null;
|
|
43
|
+
/** @type {string?} */
|
|
44
|
+
let failedReason = null;
|
|
45
|
+
cube.movement(
|
|
46
|
+
Movements.Single.R,
|
|
47
|
+
(state) => {
|
|
48
|
+
completedState = state;
|
|
49
|
+
return true;
|
|
50
|
+
},
|
|
51
|
+
(reason) => {
|
|
52
|
+
failedReason = reason;
|
|
53
|
+
return true;
|
|
54
|
+
},
|
|
55
|
+
);
|
|
56
|
+
drainUpdates(cube);
|
|
57
|
+
|
|
58
|
+
// Act
|
|
59
|
+
/** @type {string?} */
|
|
60
|
+
let resetState = null;
|
|
61
|
+
cube.reset((state) => {
|
|
62
|
+
resetState = state;
|
|
63
|
+
return true;
|
|
64
|
+
});
|
|
65
|
+
drainUpdates(cube);
|
|
66
|
+
|
|
67
|
+
// Assert
|
|
68
|
+
expect(failedReason).toBeNull();
|
|
69
|
+
expect(completedState).not.toBeNull();
|
|
70
|
+
expect(completedState).not.toBe(initialState);
|
|
71
|
+
expect(/** @type {string?} **/ (resetState)).toBe(initialState);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('solve', () => {
|
|
75
|
+
// Arrange
|
|
76
|
+
const cube = createTestCube(CubeTypes.Four);
|
|
77
|
+
const scramble =
|
|
78
|
+
"D2 F D2 F U2 L2 F R2 F2 D2 L2 R B L' B' L' D' F U Uw2 Rw2 L' Fw2 L' F' L' B' Uw2 B Rw2 F' L Uw' B' U Rw2 D U2 Fw' Rw' B' F2 Uw' B2 L'";
|
|
79
|
+
const scrambleMoves = /** @type {import('../src/core.js').Movement[]} */ (scramble.split(' '));
|
|
80
|
+
|
|
81
|
+
for (const move of scrambleMoves) {
|
|
82
|
+
cube.movement(
|
|
83
|
+
move,
|
|
84
|
+
() => {},
|
|
85
|
+
() => {
|
|
86
|
+
throw new Error('Movement failed unexpectedly');
|
|
87
|
+
},
|
|
88
|
+
);
|
|
89
|
+
drainUpdates(cube);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Act
|
|
93
|
+
const solution =
|
|
94
|
+
"z r U' r' F U r' y u' u' U2 l' U2 l z' x' r2' F U2 x' U' U' r2' 3r2 B U' r' F' 3r 3r' U' U' r2 U' r2' 3r2 U' 3r' r U2 r' U' U' r2 U' U' r2' 3r2 U r' U' 3r' r2 U2' r' x' y' x' u' U' R' U' R u D' F D u' R' U' R 3d U' U L' U L d U' R U' R' u' U R U' R' u U' R U' R' u' U' y' R' U' R u U' R U R' L U L' 3d R U' R' U' R U' R' U R U' R' y R U' R' U R U' R' 3d' U' F' U F U' L' U2 R U R' U2 L R' U R' U' R3 U' R' U R U R2' z2";
|
|
95
|
+
const solutionActions = /** @type {(import('../src/core.js').Movement | import('../src/core.js').Rotation)[]} */ (solution.split(' '));
|
|
96
|
+
|
|
97
|
+
let finalState = null;
|
|
98
|
+
for (const action of solutionActions) {
|
|
99
|
+
if (action.includes('x') || action.includes('y') || action.includes('z')) {
|
|
100
|
+
cube.rotate(
|
|
101
|
+
/** @type {import('../src/core.js').Rotation} */ (action),
|
|
102
|
+
(state) => {
|
|
103
|
+
finalState = state;
|
|
104
|
+
},
|
|
105
|
+
() => {
|
|
106
|
+
throw new Error('Rotation failed unexpectedly');
|
|
107
|
+
},
|
|
108
|
+
);
|
|
109
|
+
} else {
|
|
110
|
+
cube.movement(
|
|
111
|
+
/** @type {import('../src/core.js').Movement} */ (action),
|
|
112
|
+
(state) => {
|
|
113
|
+
finalState = state;
|
|
114
|
+
},
|
|
115
|
+
() => {
|
|
116
|
+
throw new Error('Movement failed unexpectedly');
|
|
117
|
+
},
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
drainUpdates(cube);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Assert
|
|
124
|
+
expect(/** @type {string?} **/ (finalState)).toBe(toKociemba(cube._cubeInfo.initialStickerState));
|
|
125
|
+
});
|
|
126
|
+
});
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import './setup.js';
|
|
3
|
+
import { describe, it, expect, test } from 'bun:test';
|
|
4
|
+
import { CubeTypes, Movements, Rotations } from '../src/core.js';
|
|
5
|
+
import { toKociemba } from '../src/cube/stickerState.js';
|
|
6
|
+
import { createTestCube, drainUpdates } from './common.js';
|
|
7
|
+
|
|
8
|
+
describe('7x7 Tests', () => {
|
|
9
|
+
it('Valid initial Kociemba state string', () => {
|
|
10
|
+
//Arange
|
|
11
|
+
const cube = createTestCube(CubeTypes.Seven);
|
|
12
|
+
|
|
13
|
+
//Act
|
|
14
|
+
const state = toKociemba(cube._cubeInfo.initialStickerState);
|
|
15
|
+
|
|
16
|
+
//Assert
|
|
17
|
+
expect(state.length).toBe(294);
|
|
18
|
+
for (const ch of state) {
|
|
19
|
+
expect('UDLRFB').toContain(ch);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const counts = {
|
|
23
|
+
U: 0,
|
|
24
|
+
D: 0,
|
|
25
|
+
L: 0,
|
|
26
|
+
R: 0,
|
|
27
|
+
F: 0,
|
|
28
|
+
B: 0,
|
|
29
|
+
};
|
|
30
|
+
for (const ch of state) {
|
|
31
|
+
const face = /** @type {"R" | "U" | "F" | "L" | "D" | "B"} */ (ch);
|
|
32
|
+
counts[face]++;
|
|
33
|
+
}
|
|
34
|
+
expect(Object.values(counts).every((c) => c === 49)).toBe(true);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('reset method returns the cube to the initial state', () => {
|
|
38
|
+
// Arrange
|
|
39
|
+
const cube = createTestCube(CubeTypes.Seven);
|
|
40
|
+
const initialState = toKociemba(cube._cubeInfo.initialStickerState);
|
|
41
|
+
/** @type {string?} */
|
|
42
|
+
let completedState = null;
|
|
43
|
+
/** @type {string?} */
|
|
44
|
+
let failedReason = null;
|
|
45
|
+
cube.movement(
|
|
46
|
+
Movements.Single.R,
|
|
47
|
+
(state) => {
|
|
48
|
+
completedState = state;
|
|
49
|
+
return true;
|
|
50
|
+
},
|
|
51
|
+
(reason) => {
|
|
52
|
+
failedReason = reason;
|
|
53
|
+
return true;
|
|
54
|
+
},
|
|
55
|
+
);
|
|
56
|
+
drainUpdates(cube);
|
|
57
|
+
|
|
58
|
+
// Act
|
|
59
|
+
/** @type {string?} */
|
|
60
|
+
let resetState = null;
|
|
61
|
+
cube.reset((state) => {
|
|
62
|
+
resetState = state;
|
|
63
|
+
return true;
|
|
64
|
+
});
|
|
65
|
+
drainUpdates(cube);
|
|
66
|
+
|
|
67
|
+
// Assert
|
|
68
|
+
expect(failedReason).toBeNull();
|
|
69
|
+
expect(completedState).not.toBeNull();
|
|
70
|
+
expect(completedState).not.toBe(initialState);
|
|
71
|
+
expect(/** @type {string?} **/ (resetState)).toBe(initialState);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('solve', () => {
|
|
75
|
+
// Arrange
|
|
76
|
+
const cube = createTestCube(CubeTypes.Seven);
|
|
77
|
+
const scramble =
|
|
78
|
+
"B' Rw' Lw2 3Rw2 Fw' 3Rw' 3Uw2 Lw2 3Lw' 3Rw B Rw2 U 3Bw' 3Fw2 U' Bw F' Uw R' Lw 3Fw' Fw2 R' 3Rw' 3Uw 3Rw' Fw' 3Lw' R2 B Uw' Rw2 Bw Uw' F2 R B Fw' Rw' Bw 3Bw 3Uw2 3Dw' L2 D2 R 3Rw 3Uw2 3Lw' 3Fw Fw2 B' Rw' 3Rw Lw R U' Rw' 3Bw' 3Rw2 3Bw2 Fw U2 Uw B2 Dw' Lw' Dw2 Rw2 Lw2 Dw Rw' D L' 3Bw 3Fw2 D 3Fw2 3Bw' Rw 3Fw2 Uw2 3Fw2 3Rw L' D 3Bw' 3Fw2 3Lw Rw 3Bw D2 3Uw U 3Dw2 3Rw Rw 3Lw2 3Uw'";
|
|
79
|
+
const scrambleMoves = /** @type {import('../src/core.js').Movement[]} */ (scramble.split(' '));
|
|
80
|
+
|
|
81
|
+
for (const move of scrambleMoves) {
|
|
82
|
+
cube.movement(
|
|
83
|
+
move,
|
|
84
|
+
() => {},
|
|
85
|
+
() => {
|
|
86
|
+
throw new Error('Movement failed unexpectedly');
|
|
87
|
+
},
|
|
88
|
+
);
|
|
89
|
+
drainUpdates(cube);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Act
|
|
93
|
+
const solution =
|
|
94
|
+
"x' y 4r 3r' U' 4r' 3r 5r U 5r' U' 5r U F r U r' z' F' 4r U' 4r' 3r r' U' r U' r' U' x' 4r' x' U' r2' 5r2 U x' l2' z U' 3r 4r' z' r' z x' 4r U 4r' 3r U x2' 3r' x' D r2 y' U' x' D' U' r' F r2 3r' r U r' U x 3r' U' 3r U' y x' 3r U 4r' x2 U x' U' 4r' z' U2 4l' U2 4l z U x' U' 3r' z' z U' R R' u' R2 u U' x' x' 4r2 U' U' 4r2' x x U r U' r' U x2 U' x' l' 3l' l U2 3l 4l' l' B' l U x l2 F z' 4l' U2 3r z y' U' x' U 4l x' U' 4r' R' F x' l2' x' D' x' R' L' 3l U 5r2 3r2' x' x' r' L F U 6r2 U' 3l' L x' l2' L U' x' 6r U x' L' U' r' 6r U' 6r' 5r U 4r' U' 3r' U' 4r U' 3r' U 3r U' r' U' 5r' 6r' 6r' 3r r' U' r U' r' U' U2 4r x L U' U' 3r U' U' r 6r' U' 6r2 4r2' 3r2 U' 4r r' U' r' U' r' 4r 3r' U 6r' 4r U' 4r' U' r U r' U x' L' U' 5r2 U' 5r2' U' r U r' 6r l' x' 4r2 3r2' U' 4r2 3r2' 5r 4r' U' 4r U' 3r U' 3r' r U r2' 3r U' 6r' r 3r' 4r U' 4r' U' 3r U 3r' 5r 4r' U' l' 3l r U' r U' U' r2' U' 6r r' U' r 6r' U 5r U 4r' U 4r 5r' U' 4r U' r' U r U' 4r' 3r U' 3r' U' 3r U 3r' U' 3r U' 3r' U' r U' U' r' U' r U' r' U r U' U' r' U' r U 4r 3r' U' r' U 4r' 3r 6r2' U R U 3r 6r' U' U' R' U R U r' R2' 6r U R U' x2' U' R U 5r' x' 3l2' U F U' F' 6r 4r 5r R' U R U' 5r' R U' R' U D R' D' r' 3r2 x' x' D R' D' 4r r' R R' F R F' x' U' R U r 3r2' 5r2 x' R' D R' D' U R U' 3r 4r2' 5r U' R U x r l2 3l2' R' U R U' x' 4r 3r' R' U R U' 4r' x 3r' r 4r' U' R' U 6l' U R U' 4r 6l' U' R' U 6l U R U' 3r' U' R' U 6l' U R U' 4l 3l 3l' l R' U' R U 4r' U' R' U 6l' U U' U R U' l' 3l x' 3r U' U 3r' r U' R' U 6l' U R U' r' z' y' R' F R 6d R' U L U L' D U2 R U R' D' R U' R' U R U' R' y U2 R U' R' U R' F R F' L' U L L' U L U L' U L U' x R' U R' D D R U' R' D D R2 y2 x'";
|
|
95
|
+
const solutionActions = /** @type {(import('../src/core.js').Movement | import('../src/core.js').Rotation)[]} */ (solution.split(' '));
|
|
96
|
+
|
|
97
|
+
let finalState = null;
|
|
98
|
+
for (const action of solutionActions) {
|
|
99
|
+
if (action.includes('x') || action.includes('y') || action.includes('z')) {
|
|
100
|
+
cube.rotate(
|
|
101
|
+
/** @type {import('../src/core.js').Rotation} */ (action),
|
|
102
|
+
(state) => {
|
|
103
|
+
finalState = state;
|
|
104
|
+
},
|
|
105
|
+
() => {
|
|
106
|
+
throw new Error('Rotation failed unexpectedly');
|
|
107
|
+
},
|
|
108
|
+
);
|
|
109
|
+
} else {
|
|
110
|
+
cube.movement(
|
|
111
|
+
/** @type {import('../src/core.js').Movement} */ (action),
|
|
112
|
+
(state) => {
|
|
113
|
+
finalState = state;
|
|
114
|
+
},
|
|
115
|
+
() => {
|
|
116
|
+
throw new Error('Movement failed unexpectedly');
|
|
117
|
+
},
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
drainUpdates(cube);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Assert
|
|
124
|
+
expect(/** @type {string?} **/ (finalState)).toBe(toKociemba(cube._cubeInfo.initialStickerState));
|
|
125
|
+
});
|
|
126
|
+
});
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import './setup.js';
|
|
3
|
+
import { describe, it, expect, test } from 'bun:test';
|
|
4
|
+
import { CubeTypes, Movements, Rotations } from '../src/core.js';
|
|
5
|
+
import { toKociemba } from '../src/cube/stickerState.js';
|
|
6
|
+
import { createTestCube, drainUpdates } from './common.js';
|
|
7
|
+
|
|
8
|
+
describe('6x6 Tests', () => {
|
|
9
|
+
it('Valid initial Kociemba state string', () => {
|
|
10
|
+
//Arange
|
|
11
|
+
const cube = createTestCube(CubeTypes.Six);
|
|
12
|
+
|
|
13
|
+
//Act
|
|
14
|
+
const state = toKociemba(cube._cubeInfo.initialStickerState);
|
|
15
|
+
|
|
16
|
+
//Assert
|
|
17
|
+
expect(state.length).toBe(216);
|
|
18
|
+
for (const ch of state) {
|
|
19
|
+
expect('UDLRFB').toContain(ch);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const counts = {
|
|
23
|
+
U: 0,
|
|
24
|
+
D: 0,
|
|
25
|
+
L: 0,
|
|
26
|
+
R: 0,
|
|
27
|
+
F: 0,
|
|
28
|
+
B: 0,
|
|
29
|
+
};
|
|
30
|
+
for (const ch of state) {
|
|
31
|
+
const face = /** @type {"R" | "U" | "F" | "L" | "D" | "B"} */ (ch);
|
|
32
|
+
counts[face]++;
|
|
33
|
+
}
|
|
34
|
+
expect(Object.values(counts).every((c) => c === 36)).toBe(true);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('reset method returns the cube to the initial state', () => {
|
|
38
|
+
// Arrange
|
|
39
|
+
const cube = createTestCube(CubeTypes.Six);
|
|
40
|
+
const initialState = toKociemba(cube._cubeInfo.initialStickerState);
|
|
41
|
+
/** @type {string?} */
|
|
42
|
+
let completedState = null;
|
|
43
|
+
/** @type {string?} */
|
|
44
|
+
let failedReason = null;
|
|
45
|
+
cube.movement(
|
|
46
|
+
Movements.Single.R,
|
|
47
|
+
(state) => {
|
|
48
|
+
completedState = state;
|
|
49
|
+
return true;
|
|
50
|
+
},
|
|
51
|
+
(reason) => {
|
|
52
|
+
failedReason = reason;
|
|
53
|
+
return true;
|
|
54
|
+
},
|
|
55
|
+
);
|
|
56
|
+
drainUpdates(cube);
|
|
57
|
+
|
|
58
|
+
// Act
|
|
59
|
+
/** @type {string?} */
|
|
60
|
+
let resetState = null;
|
|
61
|
+
cube.reset((state) => {
|
|
62
|
+
resetState = state;
|
|
63
|
+
return true;
|
|
64
|
+
});
|
|
65
|
+
drainUpdates(cube);
|
|
66
|
+
|
|
67
|
+
// Assert
|
|
68
|
+
expect(failedReason).toBeNull();
|
|
69
|
+
expect(completedState).not.toBeNull();
|
|
70
|
+
expect(completedState).not.toBe(initialState);
|
|
71
|
+
expect(/** @type {string?} **/ (resetState)).toBe(initialState);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('solve', () => {
|
|
75
|
+
// Arrange
|
|
76
|
+
const cube = createTestCube(CubeTypes.Six);
|
|
77
|
+
const scramble =
|
|
78
|
+
"D Fw' R2 F2 Dw' Lw' Rw' F U2 3Uw 3Fw' Bw Rw2 L2 Bw2 B2 Uw 3Rw' Lw Rw2 3Uw2 Dw2 3Fw2 U2 Rw F Bw L 3Rw Dw2 3Fw' Dw2 D Bw' F Fw' D 3Fw' F' R' L B Rw' 3Fw2 U' L2 Lw' D Bw Uw2 3Fw2 Uw2 Bw' Fw U' Fw2 L 3Rw Bw' D' 3Rw' 3Uw2 Lw' U' Dw F R' B Rw U2 B2 Fw' 3Rw R 3Fw2 3Uw2 D' 3Fw' L Dw";
|
|
79
|
+
const scrambleMoves = /** @type {import('../src/core.js').Movement[]} */ (scramble.split(' '));
|
|
80
|
+
|
|
81
|
+
for (const move of scrambleMoves) {
|
|
82
|
+
cube.movement(
|
|
83
|
+
move,
|
|
84
|
+
() => {},
|
|
85
|
+
() => {
|
|
86
|
+
throw new Error('Movement failed unexpectedly');
|
|
87
|
+
},
|
|
88
|
+
);
|
|
89
|
+
drainUpdates(cube);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Act
|
|
93
|
+
const solution =
|
|
94
|
+
"y' U 3r r F' F D' r r' U' r z y z' y' x U r' U' r U' r' U l y x' D U x' 4r2 L 3u x' U' U' r2 D' 3l2' U2 x' r u x U' 4r' F U x' 4l' U2 r U' 4r' z' F' 3r U2' 3r' x U' r' U' 4l R' 3u' 4u 3l' U2 3l 3u R2 u r U' U' r' z x' x' x' x' 2-3l' U2 r' F x' U 3r2' R2 U' r2' x U R' R' U' l U x x L U 3r2' x2' 5r U' 3r 4r' 5r' D 5r U 3r' U' 3r U 5r 3r' U r U r' U r U' r' U r U 2L' 5r 5r' 3r2 U 3r 5r2 3r' U' 3r 5r2' U' 4r r' U' r2 4r' 5r2 r' U' r' U' U' 5r' r3 U r' 5r U 5r2' 4r U' 4r' 5r 3r U' 3r' U 3r U' 3r2' U' U' 3r 4r' r2 U r' U' r U' r2' U 4r U' r' U 5r' r U' U' r U2' r' U2' r U r' 3r U2' 3r' U' r U' 4r r' U r' U' r 4r' z' U' L' U L R U' R' u F R' F' R u' 4u L' U' L' U L2 y' u L' U L 3u' L' U L u' y L' U L 4d L' U L 3u y' y U' y' 3u 3u 4u' 4u' U' y 5d 5d' U' U U' L' U L u' R' F R F' R U' R' 3u' d U' R U' R' u' U2 R U' R' 3u' u R U' R2' U' R y' L' U L R' F R F' R U' R' 3u' R' F R F' R U' R' 3u R U' R' u L' U L u' 4u' R' F R F' R U' R' U' d R U' R' u' 4u R' F R F' R U' R' 4u' u R U2' R2' U' R y' R' U2' R U R' U2' R U' L' U2 L U2' L' U L U R U' R' 5r U' 5r' U2 5r U 5r' U U U' R2' D' R U' R' D R U R' D' R U R' D R U R U' R' U' R 3r2' F2 U2' 3r2 R2' U2' F2 3r2 x2 y";
|
|
95
|
+
const solutionActions = /** @type {(import('../src/core.js').Movement | import('../src/core.js').Rotation)[]} */ (solution.split(' '));
|
|
96
|
+
|
|
97
|
+
let finalState = null;
|
|
98
|
+
for (const action of solutionActions) {
|
|
99
|
+
if (action.includes('x') || action.includes('y') || action.includes('z')) {
|
|
100
|
+
cube.rotate(
|
|
101
|
+
/** @type {import('../src/core.js').Rotation} */ (action),
|
|
102
|
+
(state) => {
|
|
103
|
+
finalState = state;
|
|
104
|
+
},
|
|
105
|
+
() => {
|
|
106
|
+
throw new Error('Rotation failed unexpectedly');
|
|
107
|
+
},
|
|
108
|
+
);
|
|
109
|
+
} else {
|
|
110
|
+
cube.movement(
|
|
111
|
+
/** @type {import('../src/core.js').Movement} */ (action),
|
|
112
|
+
(state) => {
|
|
113
|
+
finalState = state;
|
|
114
|
+
},
|
|
115
|
+
() => {
|
|
116
|
+
throw new Error('Movement failed unexpectedly');
|
|
117
|
+
},
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
drainUpdates(cube);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Assert
|
|
124
|
+
expect(/** @type {string?} **/ (finalState)).toBe(toKociemba(cube._cubeInfo.initialStickerState));
|
|
125
|
+
});
|
|
126
|
+
});
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import './setup.js';
|
|
3
|
+
import { describe, it, expect, test } from 'bun:test';
|
|
4
|
+
import { CubeTypes, Movements } from '../src/core.js';
|
|
5
|
+
import { toKociemba } from '../src/cube/stickerState.js';
|
|
6
|
+
import { createTestCube, drainUpdates } from './common.js';
|
|
7
|
+
|
|
8
|
+
describe('3x3 Tests', () => {
|
|
9
|
+
it('Valid initial Kociemba state string', () => {
|
|
10
|
+
//Arange
|
|
11
|
+
const cube = createTestCube(CubeTypes.Three);
|
|
12
|
+
|
|
13
|
+
//Act
|
|
14
|
+
const state = toKociemba(cube._cubeInfo.initialStickerState);
|
|
15
|
+
|
|
16
|
+
//Assert
|
|
17
|
+
expect(state.length).toBe(54);
|
|
18
|
+
for (const ch of state) {
|
|
19
|
+
expect('UDLRFB').toContain(ch);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const counts = {
|
|
23
|
+
U: 0,
|
|
24
|
+
D: 0,
|
|
25
|
+
L: 0,
|
|
26
|
+
R: 0,
|
|
27
|
+
F: 0,
|
|
28
|
+
B: 0,
|
|
29
|
+
};
|
|
30
|
+
for (const ch of state) {
|
|
31
|
+
const face = /** @type {"R" | "U" | "F" | "L" | "D" | "B"} */ (ch);
|
|
32
|
+
counts[face]++;
|
|
33
|
+
}
|
|
34
|
+
expect(Object.values(counts).every((c) => c === 9)).toBe(true);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('reset method returns the cube to the initial state', () => {
|
|
38
|
+
// Arrange
|
|
39
|
+
const cube = createTestCube(CubeTypes.Three);
|
|
40
|
+
const initialState = toKociemba(cube._cubeInfo.initialStickerState);
|
|
41
|
+
/** @type {string?} */
|
|
42
|
+
let completedState = null;
|
|
43
|
+
/** @type {string?} */
|
|
44
|
+
let failedReason = null;
|
|
45
|
+
cube.movement(
|
|
46
|
+
Movements.Single.R,
|
|
47
|
+
(state) => {
|
|
48
|
+
completedState = state;
|
|
49
|
+
return true;
|
|
50
|
+
},
|
|
51
|
+
(reason) => {
|
|
52
|
+
failedReason = reason;
|
|
53
|
+
return true;
|
|
54
|
+
},
|
|
55
|
+
);
|
|
56
|
+
drainUpdates(cube);
|
|
57
|
+
|
|
58
|
+
// Act
|
|
59
|
+
/** @type {string?} */
|
|
60
|
+
let resetState = null;
|
|
61
|
+
cube.reset((state) => {
|
|
62
|
+
resetState = state;
|
|
63
|
+
return true;
|
|
64
|
+
});
|
|
65
|
+
drainUpdates(cube);
|
|
66
|
+
|
|
67
|
+
// Assert
|
|
68
|
+
expect(failedReason).toBeNull();
|
|
69
|
+
expect(completedState).not.toBeNull();
|
|
70
|
+
expect(completedState).not.toBe(initialState);
|
|
71
|
+
expect(/** @type {string?} **/ (resetState)).toBe(initialState);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('sexy move matches expected state', () => {
|
|
75
|
+
// Arrange
|
|
76
|
+
const EXPECTED_STATE = 'UULUUFUUFRRUBRRURRFFDFFUFFFDDRDDDDDDBLLLLLLLLBRRBBBBBB';
|
|
77
|
+
const cube = createTestCube(CubeTypes.Three);
|
|
78
|
+
const moves = [Movements.Single.R, Movements.Single.U, Movements.Single.RP, Movements.Single.UP];
|
|
79
|
+
|
|
80
|
+
// Act
|
|
81
|
+
/** @type {string?} */
|
|
82
|
+
let finalState = null;
|
|
83
|
+
for (const move of moves) {
|
|
84
|
+
cube.movement(
|
|
85
|
+
move,
|
|
86
|
+
(state) => {
|
|
87
|
+
finalState = state;
|
|
88
|
+
},
|
|
89
|
+
() => {
|
|
90
|
+
throw new Error('Movement failed unexpectedly');
|
|
91
|
+
},
|
|
92
|
+
);
|
|
93
|
+
drainUpdates(cube);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Assert
|
|
97
|
+
expect(/** @type {string?} **/ (finalState)).toBe(EXPECTED_STATE);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('solve', () => {
|
|
101
|
+
// Arrange
|
|
102
|
+
const cube = createTestCube(CubeTypes.Three);
|
|
103
|
+
const scramble = "D U R2 B2 D' B2 D R2 F2 L2 R B U2 R2 U' B' L D' R U' R";
|
|
104
|
+
const scrambleMoves = /** @type {import('../src/core.js').Movement[]} */ (scramble.split(' '));
|
|
105
|
+
|
|
106
|
+
for (const move of scrambleMoves) {
|
|
107
|
+
cube.movement(
|
|
108
|
+
move,
|
|
109
|
+
() => {},
|
|
110
|
+
() => {
|
|
111
|
+
throw new Error('Movement failed unexpectedly');
|
|
112
|
+
},
|
|
113
|
+
);
|
|
114
|
+
drainUpdates(cube);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Act
|
|
118
|
+
const solution =
|
|
119
|
+
"D L R' F R D2 R U' R' F u R U2' R' U R U' R' U' L U L' D U L' U' L U2 L' U L L F' L' F U R' U2 R U2' R' U R U2' R' U R U2' R' U' R U y'";
|
|
120
|
+
const solutionActions = /** @type {(import('../src/core.js').Movement | import('../src/core.js').Rotation)[]} */ (solution.split(' '));
|
|
121
|
+
|
|
122
|
+
let finalState = null;
|
|
123
|
+
for (const action of solutionActions) {
|
|
124
|
+
if (action.includes('x') || action.includes('y') || action.includes('z')) {
|
|
125
|
+
cube.rotate(
|
|
126
|
+
/** @type {import('../src/core.js').Rotation} */ (action),
|
|
127
|
+
(state) => {
|
|
128
|
+
finalState = state;
|
|
129
|
+
},
|
|
130
|
+
() => {
|
|
131
|
+
throw new Error('Rotation failed unexpectedly');
|
|
132
|
+
},
|
|
133
|
+
);
|
|
134
|
+
} else {
|
|
135
|
+
cube.movement(
|
|
136
|
+
/** @type {import('../src/core.js').Movement} */ (action),
|
|
137
|
+
(state) => {
|
|
138
|
+
finalState = state;
|
|
139
|
+
},
|
|
140
|
+
() => {
|
|
141
|
+
throw new Error('Movement failed unexpectedly');
|
|
142
|
+
},
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
drainUpdates(cube);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Assert
|
|
149
|
+
expect(/** @type {string?} **/ (finalState)).toBe(toKociemba(cube._cubeInfo.initialStickerState));
|
|
150
|
+
});
|
|
151
|
+
});
|