chore: bump version to 0.0.29
All checks were successful
Deploy to Production / deploy (push) Successful in 9s
All checks were successful
Deploy to Production / deploy (push) Successful in 9s
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "rubic-cube",
|
"name": "rubic-cube",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.28",
|
"version": "0.0.29",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ onMounted(() => {
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 0 20px;
|
padding: 0 20px;
|
||||||
height: 60px;
|
height: 70px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { useCube } from '../../composables/useCube'
|
|||||||
import { useSettings } from '../../composables/useSettings'
|
import { useSettings } from '../../composables/useSettings'
|
||||||
import Line3D from '../common/Line3D.vue'
|
import Line3D from '../common/Line3D.vue'
|
||||||
|
|
||||||
const { cubies, initCube, rotateLayer, FACES } = useCube()
|
const { cubies, initCube, rotateLayer, turn, FACES } = useCube()
|
||||||
const { showProjections } = useSettings()
|
const { showProjections } = useSettings()
|
||||||
|
|
||||||
// --- Visual State ---
|
// --- Visual State ---
|
||||||
@@ -426,6 +426,26 @@ const getProjectionStyle = (c, face) => {
|
|||||||
return { transform: `translate3d(${x}px, ${y}px, 0px)` }
|
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(() => {
|
onMounted(() => {
|
||||||
initCube()
|
initCube()
|
||||||
window.addEventListener('mousemove', onMouseMove)
|
window.addEventListener('mousemove', onMouseMove)
|
||||||
@@ -495,6 +515,24 @@ onUnmounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="controls">
|
||||||
|
<div class="controls-row">
|
||||||
|
<button class="btn-neon move-btn" @click="applyMove('U')">U</button>
|
||||||
|
<button class="btn-neon move-btn" @click="applyMove('U-prime')">U'</button>
|
||||||
|
<button class="btn-neon move-btn" @click="applyMove('U2')">U2</button>
|
||||||
|
</div>
|
||||||
|
<div class="controls-row">
|
||||||
|
<button class="btn-neon move-btn" @click="applyMove('L')">L</button>
|
||||||
|
<button class="btn-neon move-btn" @click="applyMove('L-prime')">L'</button>
|
||||||
|
<button class="btn-neon move-btn" @click="applyMove('L2')">L2</button>
|
||||||
|
</div>
|
||||||
|
<div class="controls-row">
|
||||||
|
<button class="btn-neon move-btn" @click="applyMove('F')">F</button>
|
||||||
|
<button class="btn-neon move-btn" @click="applyMove('F-prime')">F'</button>
|
||||||
|
<button class="btn-neon move-btn" @click="applyMove('F2')">F2</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -531,6 +569,29 @@ onUnmounted(() => {
|
|||||||
transform-style: preserve-3d;
|
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 */
|
/* Projection Styles */
|
||||||
.projections {
|
.projections {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|||||||
@@ -35,6 +35,10 @@ export function useCube() {
|
|||||||
worker.postMessage({ type: 'ROTATE_LAYER', payload: { axis, index, direction } });
|
worker.postMessage({ type: 'ROTATE_LAYER', payload: { axis, index, direction } });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const turn = (move) => {
|
||||||
|
worker.postMessage({ type: 'TURN', payload: { move } });
|
||||||
|
};
|
||||||
|
|
||||||
const validate = () => {
|
const validate = () => {
|
||||||
worker.postMessage({ type: 'VALIDATE' });
|
worker.postMessage({ type: 'VALIDATE' });
|
||||||
};
|
};
|
||||||
@@ -45,6 +49,7 @@ export function useCube() {
|
|||||||
validationResult: computed(() => validationResult.value),
|
validationResult: computed(() => validationResult.value),
|
||||||
initCube,
|
initCube,
|
||||||
rotateLayer,
|
rotateLayer,
|
||||||
|
turn,
|
||||||
validate,
|
validate,
|
||||||
COLORS,
|
COLORS,
|
||||||
FACES
|
FACES
|
||||||
|
|||||||
@@ -191,16 +191,16 @@ export class CubeModel {
|
|||||||
cubie.faces[FACES.FRONT] = f[FACES.RIGHT];
|
cubie.faces[FACES.FRONT] = f[FACES.RIGHT];
|
||||||
}
|
}
|
||||||
} else if (axis === 'z') {
|
} else if (axis === 'z') {
|
||||||
if (direction === 1) { // Up -> Right -> Down -> Left -> Up (CW)
|
if (direction === 1) { // CCW: Up -> Left -> Down -> Right -> 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];
|
|
||||||
} else { // Up -> Left -> Down -> Right -> Up (CCW)
|
|
||||||
cubie.faces[FACES.LEFT] = f[FACES.UP];
|
cubie.faces[FACES.LEFT] = f[FACES.UP];
|
||||||
cubie.faces[FACES.DOWN] = f[FACES.LEFT];
|
cubie.faces[FACES.DOWN] = f[FACES.LEFT];
|
||||||
cubie.faces[FACES.RIGHT] = f[FACES.DOWN];
|
cubie.faces[FACES.RIGHT] = f[FACES.DOWN];
|
||||||
cubie.faces[FACES.UP] = f[FACES.RIGHT];
|
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];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { State } from 'rubiks-js/src/state/index.js';
|
import { State } from 'rubiks-js/src/state/index.js';
|
||||||
|
import { CubeModel } from './CubeModel.js';
|
||||||
|
|
||||||
// Static order definitions from rubiks-js source
|
// Static order definitions from rubiks-js source
|
||||||
const CORNER_ORDER = ['URF', 'ULF', 'ULB', 'URB', 'DRF', 'DLF', 'DLB', 'DRB'];
|
const CORNER_ORDER = ['URF', 'ULF', 'ULB', 'URB', 'DRF', 'DLF', 'DLB', 'DRB'];
|
||||||
@@ -113,32 +114,27 @@ const getEdgeColors = (name) => {
|
|||||||
export class RubiksJSModel {
|
export class RubiksJSModel {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.state = new State(false); // trackCenters=false
|
this.state = new State(false); // trackCenters=false
|
||||||
|
this.visual = new CubeModel();
|
||||||
}
|
}
|
||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
// State doesn't have a reset method exposed directly?
|
|
||||||
// We can just create a new state.
|
|
||||||
this.state = new State(false);
|
this.state = new State(false);
|
||||||
|
this.visual = new CubeModel();
|
||||||
}
|
}
|
||||||
|
|
||||||
rotateLayer(axis, index, dir) {
|
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 = '';
|
let move = '';
|
||||||
if (axis === 'y') {
|
if (axis === 'y') {
|
||||||
if (index === 1) move = dir === 1 ? "U'" : "U";
|
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') {
|
else if (axis === 'x') {
|
||||||
if (index === 1) move = dir === 1 ? "R'" : "R";
|
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') {
|
else if (axis === 'z') {
|
||||||
if (index === 1) move = dir === 1 ? "F'" : "F";
|
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) {
|
if (move) {
|
||||||
@@ -149,109 +145,24 @@ export class RubiksJSModel {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('[RubiksJSModel] Failed to apply move:', move, 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() {
|
toCubies() {
|
||||||
// Decode state
|
return this.visual.toCubies();
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
validate() {
|
validate() {
|
||||||
|
|||||||
@@ -29,11 +29,19 @@ self.onmessage = (e) => {
|
|||||||
sendUpdate();
|
sendUpdate();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'ROTATE_LAYER':
|
case 'ROTATE_LAYER': {
|
||||||
const { axis, index, direction } = payload;
|
const { axis, index, direction } = payload;
|
||||||
cube.rotateLayer(axis, index, direction);
|
cube.rotateLayer(axis, index, direction);
|
||||||
sendUpdate();
|
sendUpdate();
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'TURN': {
|
||||||
|
const { move } = payload;
|
||||||
|
cube.applyTurn(move);
|
||||||
|
sendUpdate();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case 'VALIDATE':
|
case 'VALIDATE':
|
||||||
const validation = cube.validate();
|
const validation = cube.validate();
|
||||||
|
|||||||
Reference in New Issue
Block a user