feat: separate solver logic into dedicated web worker, improve toast notifications
All checks were successful
Deploy to Production / deploy (push) Successful in 21s
All checks were successful
Deploy to Production / deploy (push) Successful in 21s
This commit is contained in:
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "rubic-cube",
|
"name": "rubic-cube",
|
||||||
"version": "0.5.0",
|
"version": "0.5.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "rubic-cube",
|
"name": "rubic-cube",
|
||||||
"version": "0.5.0",
|
"version": "0.5.1",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cubejs": "^1.3.2",
|
"cubejs": "^1.3.2",
|
||||||
"lucide-vue-next": "^0.564.0",
|
"lucide-vue-next": "^0.564.0",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "rubic-cube",
|
"name": "rubic-cube",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.5.0",
|
"version": "0.5.1",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
@@ -6,17 +6,59 @@ import { LAYER_ANIMATION_DURATION } from "../../config/animationSettings";
|
|||||||
import CubeMoveControls from "./CubeMoveControls.vue";
|
import CubeMoveControls from "./CubeMoveControls.vue";
|
||||||
import MoveHistoryPanel from "./MoveHistoryPanel.vue";
|
import MoveHistoryPanel from "./MoveHistoryPanel.vue";
|
||||||
import { DeepCube } from "../../utils/DeepCube.js";
|
import { DeepCube } from "../../utils/DeepCube.js";
|
||||||
import { KociembaSolver } from "../../utils/solvers/KociembaSolver.js";
|
import { showToast } from "../../utils/toastHelper.js";
|
||||||
import { BeginnerSolver } from "../../utils/solvers/BeginnerSolver.js";
|
|
||||||
import Toastify from "toastify-js";
|
|
||||||
|
|
||||||
const { cubies, initCube, rotateLayer, turn, FACES } = useCube();
|
const { cubies, initCube, rotateLayer, turn, FACES, solve, solveResult, solveError, isSolverReady } = useCube();
|
||||||
const { isCubeTranslucent } = useSettings();
|
const { isCubeTranslucent } = useSettings();
|
||||||
|
|
||||||
// --- Visual State ---
|
// --- Visual State ---
|
||||||
const rx = ref(-25);
|
// viewMatrix is a 4x4 matrix (16 floats) representing the scene rotation.
|
||||||
const ry = ref(45);
|
// Initial state: Tilt X by -25deg, Rotate Y by 45deg.
|
||||||
const rz = ref(0);
|
const identityMatrix = () => [
|
||||||
|
1, 0, 0, 0,
|
||||||
|
0, 1, 0, 0,
|
||||||
|
0, 0, 1, 0,
|
||||||
|
0, 0, 0, 1
|
||||||
|
];
|
||||||
|
|
||||||
|
const rotateXMatrix = (deg) => {
|
||||||
|
const rad = (deg * Math.PI) / 180;
|
||||||
|
const c = Math.cos(rad);
|
||||||
|
const s = Math.sin(rad);
|
||||||
|
return [
|
||||||
|
1, 0, 0, 0,
|
||||||
|
0, c, s, 0,
|
||||||
|
0, -s, c, 0,
|
||||||
|
0, 0, 0, 1
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
const rotateYMatrix = (deg) => {
|
||||||
|
const rad = (deg * Math.PI) / 180;
|
||||||
|
const c = Math.cos(rad);
|
||||||
|
const s = Math.sin(rad);
|
||||||
|
return [
|
||||||
|
c, 0, -s, 0,
|
||||||
|
0, 1, 0, 0,
|
||||||
|
s, 0, c, 0,
|
||||||
|
0, 0, 0, 1
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
const multiplyMatrices = (a, b) => {
|
||||||
|
const result = new Array(16).fill(0);
|
||||||
|
for (let r = 0; r < 4; r++) {
|
||||||
|
for (let c = 0; c < 4; c++) {
|
||||||
|
for (let k = 0; k < 4; k++) {
|
||||||
|
result[c * 4 + r] += a[k * 4 + r] * b[c * 4 + k];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initial orientation: Tilt X, then Spin Y
|
||||||
|
const viewMatrix = ref(multiplyMatrices(rotateXMatrix(-25), rotateYMatrix(45)));
|
||||||
const SCALE = 100;
|
const SCALE = 100;
|
||||||
const GAP = 0;
|
const GAP = 0;
|
||||||
const MIN_MOVES_COLUMN_GAP = 6;
|
const MIN_MOVES_COLUMN_GAP = 6;
|
||||||
@@ -99,24 +141,15 @@ const cross = (a, b) => ({
|
|||||||
z: a.x * b.y - a.y * b.x,
|
z: a.x * b.y - a.y * b.x,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Project 3D vector to 2D screen space based on current view (rx, ry, rz)
|
// Project 3D vector to 2D screen space based on current viewMatrix
|
||||||
const project = (v) => {
|
const project = (v) => {
|
||||||
const radX = (rx.value * Math.PI) / 180;
|
const m = viewMatrix.value;
|
||||||
const radY = (ry.value * Math.PI) / 180;
|
// Apply rotation matrix: v' = M * v
|
||||||
const radZ = (rz.value * Math.PI) / 180;
|
// (Ignoring translation/w for pure rotation projection)
|
||||||
|
const x = v.x * m[0] + v.y * m[4] + v.z * m[8];
|
||||||
let x1 = v.x * Math.cos(radZ) - v.y * Math.sin(radZ);
|
const y = v.x * m[1] + v.y * m[5] + v.z * m[9];
|
||||||
let y1 = v.x * Math.sin(radZ) + v.y * Math.cos(radZ);
|
// z ignored for 2D projection
|
||||||
let z1 = v.z;
|
return { x, y };
|
||||||
|
|
||||||
let x2 = x1 * Math.cos(radY) + z1 * Math.sin(radY);
|
|
||||||
let y2 = y1;
|
|
||||||
let z2 = -x1 * Math.sin(radY) + z1 * Math.cos(radY);
|
|
||||||
|
|
||||||
let x3 = x2;
|
|
||||||
let y3 = y2 * Math.cos(radX) - z2 * Math.sin(radX);
|
|
||||||
|
|
||||||
return { x: x3, y: y3 };
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// --- Interaction Logic ---
|
// --- Interaction Logic ---
|
||||||
@@ -140,16 +173,11 @@ const onMouseDown = (e) => {
|
|||||||
selectedCubie.value = { ...cubie }; // Snapshot position
|
selectedCubie.value = { ...cubie }; // Snapshot position
|
||||||
selectedFace.value = face;
|
selectedFace.value = face;
|
||||||
|
|
||||||
// Check if center piece (has 2 zero coordinates)
|
// Mechanical Realism Rules:
|
||||||
// Centers have sum of absolute coords = 1
|
// Centers (absSum <= 1) are "Stiff" (part of the core frame). Dragging them rotates the View.
|
||||||
// Core (0,0,0) has sum = 0
|
// Corners/Edges (absSum > 1) are "Moving Parts". Dragging them rotates the Layer.
|
||||||
const absSum = Math.abs(cubie.x) + Math.abs(cubie.y) + Math.abs(cubie.z);
|
const absSum = Math.abs(cubie.x) + Math.abs(cubie.y) + Math.abs(cubie.z);
|
||||||
const isCenterOrCore = absSum <= 1;
|
dragMode.value = absSum <= 1 ? "view" : "layer";
|
||||||
|
|
||||||
// Mechanical Realism:
|
|
||||||
// Centers are "Stiff" (part of the core frame). Dragging them rotates the View.
|
|
||||||
// Corners/Edges are "Moving Parts". Dragging them rotates the Layer.
|
|
||||||
dragMode.value = isCenterOrCore ? "view" : "layer";
|
|
||||||
} else {
|
} else {
|
||||||
dragMode.value = "view";
|
dragMode.value = "view";
|
||||||
selectedCubie.value = null;
|
selectedCubie.value = null;
|
||||||
@@ -163,8 +191,19 @@ const onMouseMove = (e) => {
|
|||||||
const dy = e.clientY - lastY.value;
|
const dy = e.clientY - lastY.value;
|
||||||
|
|
||||||
if (dragMode.value === "view") {
|
if (dragMode.value === "view") {
|
||||||
ry.value += dx * 0.5;
|
// Relative View Rotation:
|
||||||
rx.value += dy * 0.5;
|
// Dragging mouse Down (positive dy) should pull the TOP of the cube towards the user.
|
||||||
|
// In standard math, rotating a cube around World X-axis by positive angle tilts it BACK.
|
||||||
|
// So we use -dy for the rotation angle.
|
||||||
|
const deltaX = rotateXMatrix(-dy * 0.5);
|
||||||
|
const deltaY = rotateYMatrix(dx * 0.5);
|
||||||
|
|
||||||
|
// Order matters: Apply deltas on top of current orientation.
|
||||||
|
// RotationY(dx) * RotationX(dy) * currentMatrix
|
||||||
|
// Result: Horizontal dragging always spins around screen Y,
|
||||||
|
// vertical dragging always tilts around screen X.
|
||||||
|
const combinedDelta = multiplyMatrices(deltaY, deltaX);
|
||||||
|
viewMatrix.value = multiplyMatrices(combinedDelta, viewMatrix.value);
|
||||||
} else if (dragMode.value === "layer" && selectedCubie.value) {
|
} else if (dragMode.value === "layer" && selectedCubie.value) {
|
||||||
const totalDx = e.clientX - startX.value;
|
const totalDx = e.clientX - startX.value;
|
||||||
const totalDy = e.clientY - startY.value;
|
const totalDy = e.clientY - startY.value;
|
||||||
@@ -189,9 +228,9 @@ const handleLayerDrag = (totalDx, totalDy, dx, dy) => {
|
|||||||
|
|
||||||
// Analyze candidates
|
// Analyze candidates
|
||||||
axes.forEach((axis) => {
|
axes.forEach((axis) => {
|
||||||
// Tangent = Axis x Normal
|
// Tangent = Normal x Axis
|
||||||
// This is the 3D direction of motion for Positive Rotation around this Axis
|
// This is the 3D direction of motion for Positive Rotation around this Axis
|
||||||
const t3D = cross(getAxisVector(axis), faceNormal);
|
const t3D = cross(faceNormal, getAxisVector(axis));
|
||||||
const t2D = project(t3D);
|
const t2D = project(t3D);
|
||||||
const len = Math.sqrt(t2D.x ** 2 + t2D.y ** 2);
|
const len = Math.sqrt(t2D.x ** 2 + t2D.y ** 2);
|
||||||
|
|
||||||
@@ -286,8 +325,17 @@ const finishMove = (steps, directionOverride = null) => {
|
|||||||
const direction =
|
const direction =
|
||||||
directionOverride !== null ? directionOverride : steps > 0 ? 1 : -1;
|
directionOverride !== null ? directionOverride : steps > 0 ? 1 : -1;
|
||||||
|
|
||||||
|
// LOGICAL SYNC (CRITICAL):
|
||||||
|
// Our visual rotation signs in getCubieStyle and tangent calc are now aligned.
|
||||||
|
// However, some axes might still be inverted based on coordinate system (Right-handed vs CSS).
|
||||||
|
let finalDirection = direction;
|
||||||
|
|
||||||
|
// Y-axis spin in project/matrix logic vs cubic logic often needs swap
|
||||||
|
if (axis === "y") finalDirection *= -1;
|
||||||
|
if (axis === "z") finalDirection *= -1;
|
||||||
|
|
||||||
pendingLogicalUpdate.value = true;
|
pendingLogicalUpdate.value = true;
|
||||||
rotateLayer(axis, index, direction, count);
|
rotateLayer(axis, index, finalDirection, count);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -448,13 +496,14 @@ const getCubieStyle = (c) => {
|
|||||||
// Logic X=1 (Right). CSS +X is Right.
|
// Logic X=1 (Right). CSS +X is Right.
|
||||||
|
|
||||||
// Rotations:
|
// Rotations:
|
||||||
// CSS rotateX: + is Top->Back.
|
// CSS rotateX: + is Top->Back. (Standard R direction)
|
||||||
// CSS rotateY: + is Right->Back (Spin Right).
|
// CSS rotateY: + is Right->Back. (Spin Right)
|
||||||
// CSS rotateZ: + is Top->Right (Clockwise).
|
// CSS rotateZ: + is Top->Right. (Clockwise)
|
||||||
|
|
||||||
if (axis === "x") transform = `rotateX(${-rot}deg) ` + transform;
|
// We align rot so that +90 degrees visually matches logical direction=1 (CW)
|
||||||
if (axis === "y") transform = `rotateY(${-rot}deg) ` + transform;
|
if (axis === "x") transform = `rotateX(${rot}deg) ` + transform;
|
||||||
if (axis === "z") transform = `rotateZ(${rot}deg) ` + transform;
|
if (axis === "y") transform = `rotateY(${rot}deg) ` + transform;
|
||||||
|
if (axis === "z") transform = `rotateZ(${-rot}deg) ` + transform;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -750,6 +799,16 @@ const scramble = () => {
|
|||||||
const handleSolve = async (solverType) => {
|
const handleSolve = async (solverType) => {
|
||||||
if (isAnimating.value) return;
|
if (isAnimating.value) return;
|
||||||
|
|
||||||
|
if (solverType === "kociemba" && !isSolverReady.value) {
|
||||||
|
showToast("wait for initialize solver", "info", {
|
||||||
|
style: {
|
||||||
|
background: "linear-gradient(to right, #b45309, #d97706)",
|
||||||
|
color: "#ffffff"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const currentCube = DeepCube.fromCubies(cubies.value);
|
const currentCube = DeepCube.fromCubies(cubies.value);
|
||||||
|
|
||||||
if (!currentCube.isValid()) {
|
if (!currentCube.isValid()) {
|
||||||
@@ -759,30 +818,20 @@ const handleSolve = async (solverType) => {
|
|||||||
|
|
||||||
// Already solved? (Identity check)
|
// Already solved? (Identity check)
|
||||||
if (currentCube.isSolved()) {
|
if (currentCube.isSolved()) {
|
||||||
Toastify({
|
showToast("scramble cube first", "info");
|
||||||
text: "scramble cube first",
|
|
||||||
duration: 3000,
|
|
||||||
gravity: "top",
|
|
||||||
position: "center",
|
|
||||||
stopOnFocus: true,
|
|
||||||
}).showToast();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let solution = [];
|
solve(solverType, {
|
||||||
try {
|
cp: currentCube.cp,
|
||||||
if (solverType === "kociemba") {
|
co: currentCube.co,
|
||||||
const solver = new KociembaSolver(currentCube);
|
ep: currentCube.ep,
|
||||||
solution = solver.solve();
|
eo: currentCube.eo,
|
||||||
} else if (solverType === "beginner") {
|
});
|
||||||
const solver = new BeginnerSolver(currentCube);
|
};
|
||||||
solution = solver.solve();
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error("Solver exception:", e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Listen for solution from worker
|
||||||
|
watch(solveResult, (solution) => {
|
||||||
if (solution && solution.length > 0) {
|
if (solution && solution.length > 0) {
|
||||||
const uiMoves = solution.map((m) => {
|
const uiMoves = solution.map((m) => {
|
||||||
const solverBase = m[0];
|
const solverBase = m[0];
|
||||||
@@ -802,12 +851,23 @@ const handleSolve = async (solverType) => {
|
|||||||
return uiKey;
|
return uiKey;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return m;
|
return null;
|
||||||
});
|
}).filter(m => m !== null);
|
||||||
|
|
||||||
uiMoves.forEach((m) => applyMove(m));
|
uiMoves.forEach((m) => applyMove(m));
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
|
||||||
|
watch(solveError, (err) => {
|
||||||
|
if (err) {
|
||||||
|
showToast(err, "info", {
|
||||||
|
style: {
|
||||||
|
background: "linear-gradient(to right, #b45309, #d97706)",
|
||||||
|
color: "#ffffff"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
watch(cubies, () => {
|
watch(cubies, () => {
|
||||||
if (!pendingLogicalUpdate.value) return;
|
if (!pendingLogicalUpdate.value) return;
|
||||||
@@ -851,7 +911,8 @@ onUnmounted(() => {
|
|||||||
<div class="smart-cube-container">
|
<div class="smart-cube-container">
|
||||||
<div
|
<div
|
||||||
class="scene"
|
class="scene"
|
||||||
:style="{ transform: `rotateX(${rx}deg) rotateY(${ry}deg)` }"
|
:style="{ transform: `matrix3d(${viewMatrix.join(',')})` }"
|
||||||
|
@mousedown="onMouseDown"
|
||||||
>
|
>
|
||||||
<div class="cube">
|
<div class="cube">
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -1,16 +1,25 @@
|
|||||||
import { ref, computed } from "vue";
|
import { ref, computed } from "vue";
|
||||||
import { COLORS, FACES } from "../utils/CubeModel";
|
import { COLORS, FACES } from "../utils/CubeModel";
|
||||||
|
|
||||||
// Singleton worker
|
// Singleton logic worker
|
||||||
const worker = new Worker(
|
const worker = new Worker(
|
||||||
new URL("../workers/Cube.worker.js", import.meta.url),
|
new URL("../workers/Cube.worker.js", import.meta.url),
|
||||||
{ type: "module" },
|
{ type: "module" },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Singleton solver worker
|
||||||
|
const solverWorker = new Worker(
|
||||||
|
new URL("../workers/Solver.worker.js", import.meta.url),
|
||||||
|
{ type: "module" },
|
||||||
|
);
|
||||||
|
|
||||||
// Reactive state
|
// Reactive state
|
||||||
const cubies = ref([]);
|
const cubies = ref([]);
|
||||||
const isReady = ref(false);
|
const isReady = ref(false);
|
||||||
|
const isSolverReady = ref(false);
|
||||||
const validationResult = ref(null);
|
const validationResult = ref(null);
|
||||||
|
const solveResult = ref(null);
|
||||||
|
const solveError = ref(null);
|
||||||
|
|
||||||
worker.onmessage = (e) => {
|
worker.onmessage = (e) => {
|
||||||
const { type, payload } = e.data;
|
const { type, payload } = e.data;
|
||||||
@@ -20,7 +29,21 @@ worker.onmessage = (e) => {
|
|||||||
} else if (type === "VALIDATION_RESULT") {
|
} else if (type === "VALIDATION_RESULT") {
|
||||||
validationResult.value = payload;
|
validationResult.value = payload;
|
||||||
} else if (type === "ERROR") {
|
} else if (type === "ERROR") {
|
||||||
console.error("Worker Error:", payload);
|
console.error("Logic Worker Error:", payload);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
solverWorker.onmessage = (e) => {
|
||||||
|
const { type, payload } = e.data;
|
||||||
|
if (type === "SOLVE_RESULT") {
|
||||||
|
solveResult.value = payload;
|
||||||
|
} else if (type === "SOLVE_ERROR") {
|
||||||
|
// Error doesn't necessarily block execution, it just provides UI feedback
|
||||||
|
solveError.value = payload;
|
||||||
|
} else if (type === "INIT_DONE") {
|
||||||
|
isSolverReady.value = true;
|
||||||
|
} else if (type === "ERROR") {
|
||||||
|
console.error("Solver Worker Error:", payload);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -47,14 +70,27 @@ export function useCube() {
|
|||||||
worker.postMessage({ type: "VALIDATE" });
|
worker.postMessage({ type: "VALIDATE" });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const solve = (solverType, cubeState) => {
|
||||||
|
solveResult.value = null;
|
||||||
|
solveError.value = null;
|
||||||
|
solverWorker.postMessage({
|
||||||
|
type: "SOLVE",
|
||||||
|
payload: { solverType, cubeState },
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
cubies: computed(() => cubies.value),
|
cubies: computed(() => cubies.value),
|
||||||
isReady: computed(() => isReady.value),
|
isReady: computed(() => isReady.value),
|
||||||
|
isSolverReady: computed(() => isSolverReady.value),
|
||||||
validationResult: computed(() => validationResult.value),
|
validationResult: computed(() => validationResult.value),
|
||||||
|
solveResult: computed(() => solveResult.value),
|
||||||
|
solveError: computed(() => solveError.value),
|
||||||
initCube,
|
initCube,
|
||||||
rotateLayer,
|
rotateLayer,
|
||||||
turn,
|
turn,
|
||||||
validate,
|
validate,
|
||||||
|
solve,
|
||||||
COLORS,
|
COLORS,
|
||||||
FACES,
|
FACES,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import Cube from "cubejs";
|
import Cube from "cubejs";
|
||||||
|
|
||||||
// Initialize the core pruning tables on module load
|
|
||||||
Cube.initSolver();
|
|
||||||
import { DeepCube, CORNERS, EDGES } from "../DeepCube.js";
|
import { DeepCube, CORNERS, EDGES } from "../DeepCube.js";
|
||||||
|
|
||||||
export class KociembaSolver {
|
export class KociembaSolver {
|
||||||
|
static init() {
|
||||||
|
Cube.initSolver();
|
||||||
|
}
|
||||||
constructor(cube) {
|
constructor(cube) {
|
||||||
this.cube = cube.clone();
|
this.cube = cube.clone();
|
||||||
}
|
}
|
||||||
|
|||||||
31
src/utils/toastHelper.js
Normal file
31
src/utils/toastHelper.js
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import Toastify from "toastify-js";
|
||||||
|
|
||||||
|
const ICONS = {
|
||||||
|
info: '<circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/>',
|
||||||
|
alert: '<path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/><path d="M12 9v4"/><path d="M12 17h.01"/>',
|
||||||
|
check: '<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><path d="m9 11 3 3L22 4"/>'
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createToastHtml = (text, iconName = 'info') => {
|
||||||
|
const innerHtml = ICONS[iconName] || ICONS.info;
|
||||||
|
const size = 26; // Powiększona ikona
|
||||||
|
|
||||||
|
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-${iconName}">${innerHtml}</svg>`;
|
||||||
|
|
||||||
|
return `<div style="display: flex; align-items: center; gap: 8px;">
|
||||||
|
${svg}
|
||||||
|
<span>${text}</span>
|
||||||
|
</div>`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const showToast = (text, iconName = 'info', options = {}) => {
|
||||||
|
Toastify({
|
||||||
|
text: createToastHtml(text, iconName),
|
||||||
|
escapeMarkup: false,
|
||||||
|
duration: 3000,
|
||||||
|
gravity: "top",
|
||||||
|
position: "center",
|
||||||
|
stopOnFocus: true,
|
||||||
|
...options
|
||||||
|
}).showToast();
|
||||||
|
};
|
||||||
@@ -51,5 +51,6 @@ self.onmessage = (e) => {
|
|||||||
payload: { valid: validation.valid, errors: validation.errors },
|
payload: { valid: validation.valid, errors: validation.errors },
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
58
src/workers/Solver.worker.js
Normal file
58
src/workers/Solver.worker.js
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import { DeepCube } from "../utils/DeepCube.js";
|
||||||
|
import { KociembaSolver } from "../utils/solvers/KociembaSolver.js";
|
||||||
|
import { BeginnerSolver } from "../utils/solvers/BeginnerSolver.js";
|
||||||
|
|
||||||
|
let isKociembaReady = false;
|
||||||
|
|
||||||
|
// Defer heavy initialization to allow the worker to be responsive initially
|
||||||
|
setTimeout(() => {
|
||||||
|
console.log("[SolverWorker] Kociemba solver initialization");
|
||||||
|
console.time("[SolverWorker] Kociemba solver initialization");
|
||||||
|
KociembaSolver.init();
|
||||||
|
console.timeEnd("[SolverWorker] Kociemba solver initialization");
|
||||||
|
isKociembaReady = true;
|
||||||
|
postMessage({ type: "INIT_DONE" });
|
||||||
|
}, 50);
|
||||||
|
|
||||||
|
self.onmessage = (e) => {
|
||||||
|
const { type, payload } = e.data;
|
||||||
|
|
||||||
|
if (type === "SOLVE") {
|
||||||
|
const { solverType, cubeState } = payload;
|
||||||
|
|
||||||
|
if (solverType === "kociemba" && !isKociembaReady) {
|
||||||
|
postMessage({ type: "SOLVE_ERROR", payload: "wait for initialize solver" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Reconstruct DeepCube state from payload
|
||||||
|
const dc = new DeepCube(
|
||||||
|
new Int8Array(cubeState.cp),
|
||||||
|
new Int8Array(cubeState.co),
|
||||||
|
new Int8Array(cubeState.ep),
|
||||||
|
new Int8Array(cubeState.eo)
|
||||||
|
);
|
||||||
|
|
||||||
|
let solution = [];
|
||||||
|
if (solverType === "kociemba") {
|
||||||
|
const solver = new KociembaSolver(dc);
|
||||||
|
solution = solver.solve();
|
||||||
|
} else if (solverType === "beginner") {
|
||||||
|
const solver = new BeginnerSolver(dc);
|
||||||
|
solution = solver.solve();
|
||||||
|
} else {
|
||||||
|
throw new Error(`Unknown solver type: ${solverType}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
postMessage({
|
||||||
|
type: "SOLVE_RESULT",
|
||||||
|
payload: solution,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error("[SolverWorker] Solve error:", err);
|
||||||
|
postMessage({ type: "SOLVE_ERROR", payload: err.message });
|
||||||
|
postMessage({ type: "SOLVE_RESULT", payload: [] });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user