From a75c148a5bfd15c96f62b6fb0bf710a01af9469f Mon Sep 17 00:00:00 2001 From: Grzegorz Kucmierz Date: Sun, 22 Feb 2026 20:31:54 +0000 Subject: [PATCH] chore: bump version to 0.0.29 --- package.json | 2 +- src/components/NavBar.vue | 2 +- src/components/renderers/SmartCube.vue | 63 +++++++++++- src/composables/useCube.js | 5 + src/utils/CubeModel.js | 12 +-- src/utils/RubiksJSModel.js | 129 ++++--------------------- src/workers/Cube.worker.js | 10 +- 7 files changed, 104 insertions(+), 119 deletions(-) diff --git a/package.json b/package.json index af108ee..b415823 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "rubic-cube", "private": true, - "version": "0.0.28", + "version": "0.0.29", "type": "module", "scripts": { "dev": "vite", diff --git a/src/components/NavBar.vue b/src/components/NavBar.vue index 8f03836..c86f351 100644 --- a/src/components/NavBar.vue +++ b/src/components/NavBar.vue @@ -54,7 +54,7 @@ onMounted(() => { justify-content: space-between; align-items: center; padding: 0 20px; - height: 60px; + height: 70px; width: 100%; box-sizing: border-box; position: absolute; diff --git a/src/components/renderers/SmartCube.vue b/src/components/renderers/SmartCube.vue index 94f51e6..1011249 100644 --- a/src/components/renderers/SmartCube.vue +++ b/src/components/renderers/SmartCube.vue @@ -4,7 +4,7 @@ import { useCube } from '../../composables/useCube' import { useSettings } from '../../composables/useSettings' import Line3D from '../common/Line3D.vue' -const { cubies, initCube, rotateLayer, FACES } = useCube() +const { cubies, initCube, rotateLayer, turn, FACES } = useCube() const { showProjections } = useSettings() // --- Visual State --- @@ -426,6 +426,26 @@ const getProjectionStyle = (c, face) => { return { transform: `translate3d(${x}px, ${y}px, 0px)` } } +const applyMove = (move) => { + let base = move + let isPrime = false + let turns = 1 + + if (move.endsWith('2')) { + turns = 2 + base = move[0] + } else if (move.endsWith('-prime')) { + isPrime = true + base = move[0] + } + + const notation = isPrime ? `${base}'` : base + + for (let i = 0; i < turns; i += 1) { + turn(notation) + } +} + onMounted(() => { initCube() window.addEventListener('mousemove', onMouseMove) @@ -495,6 +515,24 @@ onUnmounted(() => { + +
+
+ + + +
+
+ + + +
+
+ + + +
+
@@ -531,6 +569,29 @@ onUnmounted(() => { transform-style: preserve-3d; } +.controls { + position: absolute; + top: 96px; + right: 24px; + display: flex; + flex-direction: column; + gap: 8px; + z-index: 50; +} + +.controls-row { + display: flex; + gap: 8px; + justify-content: center; +} + +.move-btn { + min-width: 44px; + height: 36px; + font-size: 0.9rem; + padding: 0 10px; +} + /* Projection Styles */ .projections { position: absolute; diff --git a/src/composables/useCube.js b/src/composables/useCube.js index 2548a41..b87f06c 100644 --- a/src/composables/useCube.js +++ b/src/composables/useCube.js @@ -35,6 +35,10 @@ export function useCube() { worker.postMessage({ type: 'ROTATE_LAYER', payload: { axis, index, direction } }); }; + const turn = (move) => { + worker.postMessage({ type: 'TURN', payload: { move } }); + }; + const validate = () => { worker.postMessage({ type: 'VALIDATE' }); }; @@ -45,6 +49,7 @@ export function useCube() { validationResult: computed(() => validationResult.value), initCube, rotateLayer, + turn, validate, COLORS, FACES diff --git a/src/utils/CubeModel.js b/src/utils/CubeModel.js index cf85c30..2f35be1 100644 --- a/src/utils/CubeModel.js +++ b/src/utils/CubeModel.js @@ -191,16 +191,16 @@ export class CubeModel { cubie.faces[FACES.FRONT] = f[FACES.RIGHT]; } } else if (axis === 'z') { - if (direction === 1) { // Up -> Right -> Down -> Left -> Up (CW) - cubie.faces[FACES.RIGHT] = f[FACES.UP]; - cubie.faces[FACES.DOWN] = f[FACES.RIGHT]; - cubie.faces[FACES.LEFT] = f[FACES.DOWN]; - cubie.faces[FACES.UP] = f[FACES.LEFT]; - } else { // Up -> Left -> Down -> Right -> Up (CCW) + if (direction === 1) { // CCW: Up -> Left -> Down -> Right -> Up cubie.faces[FACES.LEFT] = f[FACES.UP]; cubie.faces[FACES.DOWN] = f[FACES.LEFT]; cubie.faces[FACES.RIGHT] = f[FACES.DOWN]; cubie.faces[FACES.UP] = f[FACES.RIGHT]; + } else { // CW: Up -> Right -> Down -> Left -> Up + cubie.faces[FACES.RIGHT] = f[FACES.UP]; + cubie.faces[FACES.DOWN] = f[FACES.RIGHT]; + cubie.faces[FACES.LEFT] = f[FACES.DOWN]; + cubie.faces[FACES.UP] = f[FACES.LEFT]; } } } diff --git a/src/utils/RubiksJSModel.js b/src/utils/RubiksJSModel.js index 1e804c1..54ec11f 100644 --- a/src/utils/RubiksJSModel.js +++ b/src/utils/RubiksJSModel.js @@ -1,4 +1,5 @@ import { State } from 'rubiks-js/src/state/index.js'; +import { CubeModel } from './CubeModel.js'; // Static order definitions from rubiks-js source const CORNER_ORDER = ['URF', 'ULF', 'ULB', 'URB', 'DRF', 'DLF', 'DLB', 'DRB']; @@ -113,32 +114,27 @@ const getEdgeColors = (name) => { export class RubiksJSModel { constructor() { this.state = new State(false); // trackCenters=false + this.visual = new CubeModel(); } reset() { - // State doesn't have a reset method exposed directly? - // We can just create a new state. this.state = new State(false); + this.visual = new CubeModel(); } rotateLayer(axis, index, dir) { - // Map to standard notation - // axis: 'x', 'y', 'z' - // index: 1 (top/right/front), -1 (bottom/left/back) - // dir: 1 (Visual CCW), -1 (Visual CW) - let move = ''; if (axis === 'y') { if (index === 1) move = dir === 1 ? "U'" : "U"; - else if (index === -1) move = dir === 1 ? "D'" : "D"; // Fixed: dir=1 (CCW) -> D' + else if (index === -1) move = dir === 1 ? "D'" : "D"; } else if (axis === 'x') { if (index === 1) move = dir === 1 ? "R'" : "R"; - else if (index === -1) move = dir === 1 ? "L'" : "L"; // Fixed: dir=1 (CCW) -> L' + else if (index === -1) move = dir === 1 ? "L'" : "L"; } else if (axis === 'z') { if (index === 1) move = dir === 1 ? "F'" : "F"; - else if (index === -1) move = dir === 1 ? "B'" : "B"; // Fixed: dir=1 (CCW) -> B' + else if (index === -1) move = dir === 1 ? "B'" : "B"; } if (move) { @@ -149,109 +145,24 @@ export class RubiksJSModel { } catch (e) { console.error('[RubiksJSModel] Failed to apply move:', move, e); } + + this.visual.rotateLayer(axis, index, dir); } } + applyTurn(move) { + if (!move) return; + try { + this.state.applyTurn(move); + } catch (e) { + console.error('[RubiksJSModel] Failed to apply direct move:', move, e); + } + + this.visual.applyMove(move); + } + toCubies() { - // Decode state - const encoded = this.state.encode(); - // console.log('[RubiksJSModel] Encoded state:', encoded); - const binaryString = atob(encoded); - const bytes = new Uint8Array(binaryString.length); - for (let i = 0; i < binaryString.length; i++) { - bytes[i] = binaryString.charCodeAt(i); - } - - // Decode Corners (first 5 bytes) - // p: bytes[0] + (bytes[1] << 8) + (bytes[2] << 16) - let pC = bytes[0] + (bytes[1] << 8) + (bytes[2] << 16); - let oC = bytes[3] + (bytes[4] << 8); - - const cornerPerms = []; - const cornerOrients = []; - - for (let i = 7; i >= 0; i--) { - const p1 = pC & 0b111; - cornerPerms[i] = CORNER_ORDER[p1]; - pC = pC >> 3; - - const o1 = oC & 0b11; - cornerOrients[i] = o1; - oC = oC >> 2; - } - - // Decode Edges (next 8 bytes) - // 6 bytes for permutation (each byte has 2 nibbles) - // 2 bytes for orientation - const edgePerms = []; - const edgeOrients = []; - - // Permutation - for (let i = 0; i < 6; i++) { - const byte = bytes[5 + i]; - const p1 = byte & 0b1111; - const p2 = (byte >> 4) & 0b1111; - edgePerms[i * 2] = EDGE_ORDER[p1]; - edgePerms[i * 2 + 1] = EDGE_ORDER[p2]; - } - - // Orientation - let oE = bytes[11] + (bytes[12] << 8); - for (let i = 11; i >= 0; i--) { - edgeOrients[i] = oE & 0b1; - oE = oE >> 1; - } - - const cubies = [...CENTERS]; - - // Map Corners - for (let i = 0; i < 8; i++) { - const pieceName = cornerPerms[i]; // e.g. 'URF' - const orientation = cornerOrients[i]; // 0, 1, 2 - const slot = CORNER_SLOTS[i]; // Slot definition - - const baseColors = getCornerColors(pieceName); // ['white', 'red', 'green'] - const slotFaces = CORNER_FACES[slot.id]; // ['up', 'right', 'front'] - - // Apply orientation - // Formula: Color at SlotKey[k] is PieceColor[(k + o) % 3] - - const faces = {}; - faces[slotFaces[0]] = baseColors[(0 + orientation) % 3]; - faces[slotFaces[1]] = baseColors[(1 + orientation) % 3]; - faces[slotFaces[2]] = baseColors[(2 + orientation) % 3]; - - cubies.push({ id: `corn${i}`, x: slot.x, y: slot.y, z: slot.z, faces }); - } - - // Map Edges - for (let i = 0; i < 12; i++) { - const pieceName = edgePerms[i]; - const orientation = edgeOrients[i]; // 0, 1 - const slot = EDGE_SLOTS[i]; - - const baseColors = getEdgeColors(pieceName); // ['white', 'green'] - const slotFaces = EDGE_FACES[slot.id]; // ['up', 'front'] - - const faces = {}; - // If orientation is 1 (Flip), we swap. - // But we need to be careful about which face is primary (0). - // Logic: if o=0, faces match. if o=1, swap. - - // Adjust for specific edges if needed? - // For now assume standard behavior: - if (orientation === 0) { - faces[slotFaces[0]] = baseColors[0]; - faces[slotFaces[1]] = baseColors[1]; - } else { - faces[slotFaces[0]] = baseColors[1]; - faces[slotFaces[1]] = baseColors[0]; - } - - cubies.push({ id: `edge${i}`, x: slot.x, y: slot.y, z: slot.z, faces }); - } - - return cubies; + return this.visual.toCubies(); } validate() { diff --git a/src/workers/Cube.worker.js b/src/workers/Cube.worker.js index d6d43b9..d4d82ef 100644 --- a/src/workers/Cube.worker.js +++ b/src/workers/Cube.worker.js @@ -29,11 +29,19 @@ self.onmessage = (e) => { sendUpdate(); break; - case 'ROTATE_LAYER': + case 'ROTATE_LAYER': { const { axis, index, direction } = payload; cube.rotateLayer(axis, index, direction); sendUpdate(); break; + } + + case 'TURN': { + const { move } = payload; + cube.applyTurn(move); + sendUpdate(); + break; + } case 'VALIDATE': const validation = cube.validate();