Files
rubic-cube/src/utils/DeepCube.js
Grzegorz Kucmierz c7d369c46a
Some checks failed
Deploy to Production / deploy (push) Failing after 10s
feat: integrate toastify-js and add solve guard with solved check
2026-02-23 23:53:47 +00:00

410 lines
8.4 KiB
JavaScript

// Corner indices
export const CORNERS = {
URF: 0,
UFL: 1,
ULB: 2,
UBR: 3,
DFR: 4,
DLF: 5,
DBL: 6,
DRB: 7,
};
// Edge indices
export const EDGES = {
UR: 0,
UF: 1,
UL: 2,
UB: 3,
DR: 4,
DF: 5,
DL: 6,
DB: 7,
FR: 8,
FL: 9,
BL: 10,
BR: 11,
};
export class DeepCube {
constructor(cp, co, ep, eo) {
if (cp && co && ep && eo) {
this.cp = [...cp];
this.co = [...co];
this.ep = [...ep];
this.eo = [...eo];
} else {
// Solved identity state
this.cp = [0, 1, 2, 3, 4, 5, 6, 7];
this.co = [0, 0, 0, 0, 0, 0, 0, 0];
this.ep = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
this.eo = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
}
}
// Multiply (apply) another cube state to this one.
multiply(b) {
const cp = new Array(8);
const co = new Array(8);
const ep = new Array(12);
const eo = new Array(12);
// Corners
for (let i = 0; i < 8; i++) {
cp[i] = this.cp[b.cp[i]];
co[i] = (this.co[b.cp[i]] + b.co[i]) % 3;
}
// Edges
for (let i = 0; i < 12; i++) {
ep[i] = this.ep[b.ep[i]];
eo[i] = (this.eo[b.ep[i]] + b.eo[i]) % 2;
}
return new DeepCube(cp, co, ep, eo);
}
clone() {
return new DeepCube(this.cp, this.co, this.ep, this.eo);
}
// Checks if the mathematical state is solvable/possible
isValid() {
// 1. Edge parity must equal corner parity
let edgeParity = 0;
for (let i = 11; i >= 0; i--) {
for (let j = i - 1; j >= 0; j--) {
if (this.ep[j] > this.ep[i]) edgeParity++;
}
}
let cornerParity = 0;
for (let i = 7; i >= 0; i--) {
for (let j = i - 1; j >= 0; j--) {
if (this.cp[j] > this.cp[i]) cornerParity++;
}
}
if (edgeParity % 2 !== cornerParity % 2) return false;
// 2. Edge orientations must sum to even
let eoSum = this.eo.reduce((a, b) => a + b, 0);
if (eoSum % 2 !== 0) return false;
// 3. Corner orientations must be divisible by 3
let coSum = this.co.reduce((a, b) => a + b, 0);
if (coSum % 3 !== 0) return false;
return true;
}
isSolved() {
// Check if permutations are identity and orientations are zero
for (let i = 0; i < 8; i++) {
if (this.cp[i] !== i || this.co[i] !== 0) return false;
}
for (let i = 0; i < 12; i++) {
if (this.ep[i] !== i || this.eo[i] !== 0) return false;
}
return true;
}
static fromCubies(cubies) {
const c2f = {
white: "U",
yellow: "D",
orange: "L",
red: "R",
green: "F",
blue: "B",
};
const getCubie = (x, y, z) =>
cubies.find((c) => c.x === x && c.y === y && c.z === z);
const baseC = [
["U", "R", "F"],
["U", "F", "L"],
["U", "L", "B"],
["U", "B", "R"],
["D", "F", "R"],
["D", "L", "F"],
["D", "B", "L"],
["D", "R", "B"],
];
const slotC = [
{ x: 1, y: 1, z: 1, faces: ["up", "right", "front"] }, // 0: URF
{ x: -1, y: 1, z: 1, faces: ["up", "front", "left"] }, // 1: UFL
{ x: -1, y: 1, z: -1, faces: ["up", "left", "back"] }, // 2: ULB
{ x: 1, y: 1, z: -1, faces: ["up", "back", "right"] }, // 3: UBR
{ x: 1, y: -1, z: 1, faces: ["down", "front", "right"] }, // 4: DFR
{ x: -1, y: -1, z: 1, faces: ["down", "left", "front"] }, // 5: DLF
{ x: -1, y: -1, z: -1, faces: ["down", "back", "left"] }, // 6: DBL
{ x: 1, y: -1, z: -1, faces: ["down", "right", "back"] }, // 7: DRB
];
let cp = [],
co = [];
for (let i = 0; i < 8; i++) {
let slot = slotC[i];
let c = getCubie(slot.x, slot.y, slot.z);
let colors = [
c2f[c.faces[slot.faces[0]]],
c2f[c.faces[slot.faces[1]]],
c2f[c.faces[slot.faces[2]]],
];
let perm = baseC.findIndex(
(bc) =>
colors.includes(bc[0]) &&
colors.includes(bc[1]) &&
colors.includes(bc[2]),
);
cp[i] = perm;
co[i] = colors.indexOf(baseC[perm][0]);
}
const baseE = [
["U", "R"],
["U", "F"],
["U", "L"],
["U", "B"],
["D", "R"],
["D", "F"],
["D", "L"],
["D", "B"],
["F", "R"],
["F", "L"],
["B", "L"],
["B", "R"],
];
const slotE = [
{ x: 1, y: 1, z: 0, faces: ["up", "right"] },
{ x: 0, y: 1, z: 1, faces: ["up", "front"] },
{ x: -1, y: 1, z: 0, faces: ["up", "left"] },
{ x: 0, y: 1, z: -1, faces: ["up", "back"] },
{ x: 1, y: -1, z: 0, faces: ["down", "right"] },
{ x: 0, y: -1, z: 1, faces: ["down", "front"] },
{ x: -1, y: -1, z: 0, faces: ["down", "left"] },
{ x: 0, y: -1, z: -1, faces: ["down", "back"] },
{ x: 1, y: 0, z: 1, faces: ["front", "right"] },
{ x: -1, y: 0, z: 1, faces: ["front", "left"] },
{ x: -1, y: 0, z: -1, faces: ["back", "left"] },
{ x: 1, y: 0, z: -1, faces: ["back", "right"] },
];
let ep = [],
eo = [];
for (let i = 0; i < 12; i++) {
let slot = slotE[i];
let c = getCubie(slot.x, slot.y, slot.z);
let colors = [c2f[c.faces[slot.faces[0]]], c2f[c.faces[slot.faces[1]]]];
let perm = baseE.findIndex(
(be) => colors.includes(be[0]) && colors.includes(be[1]),
);
ep[i] = perm;
eo[i] = colors.indexOf(baseE[perm][0]);
}
return new DeepCube(cp, co, ep, eo);
}
}
// ----------------------------------------------------------------------------
// BASE MOVES DEFINITIONS
// Represents the effect of 90-degree clockwise faces on the solved state.
// ----------------------------------------------------------------------------
export const MOVES = {};
// U (Up Face Clockwise)
MOVES["U"] = new DeepCube(
[
CORNERS.UBR,
CORNERS.URF,
CORNERS.UFL,
CORNERS.ULB,
CORNERS.DFR,
CORNERS.DLF,
CORNERS.DBL,
CORNERS.DRB,
],
[0, 0, 0, 0, 0, 0, 0, 0],
[
EDGES.UB,
EDGES.UR,
EDGES.UF,
EDGES.UL,
EDGES.DR,
EDGES.DF,
EDGES.DL,
EDGES.DB,
EDGES.FR,
EDGES.FL,
EDGES.BL,
EDGES.BR,
],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
);
// R (Right Face Clockwise)
MOVES["R"] = new DeepCube(
[
CORNERS.DFR,
CORNERS.UFL,
CORNERS.ULB,
CORNERS.URF,
CORNERS.DRB,
CORNERS.DLF,
CORNERS.DBL,
CORNERS.UBR,
],
[2, 0, 0, 1, 1, 0, 0, 2],
[
EDGES.FR,
EDGES.UF,
EDGES.UL,
EDGES.UB,
EDGES.BR,
EDGES.DF,
EDGES.DL,
EDGES.DB,
EDGES.DR,
EDGES.FL,
EDGES.BL,
EDGES.UR,
],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
);
// F (Front Face Clockwise)
MOVES["F"] = new DeepCube(
[
CORNERS.UFL,
CORNERS.DLF,
CORNERS.ULB,
CORNERS.UBR,
CORNERS.URF,
CORNERS.DFR,
CORNERS.DBL,
CORNERS.DRB,
],
[1, 2, 0, 0, 2, 1, 0, 0],
[
EDGES.UR,
EDGES.FL,
EDGES.UL,
EDGES.UB,
EDGES.DR,
EDGES.FR,
EDGES.DL,
EDGES.DB,
EDGES.UF,
EDGES.DF,
EDGES.BL,
EDGES.BR,
],
[0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0],
);
// D (Down Face Clockwise)
MOVES["D"] = new DeepCube(
[
CORNERS.URF,
CORNERS.UFL,
CORNERS.ULB,
CORNERS.UBR,
CORNERS.DLF,
CORNERS.DBL,
CORNERS.DRB,
CORNERS.DFR,
],
[0, 0, 0, 0, 0, 0, 0, 0],
[
EDGES.UR,
EDGES.UF,
EDGES.UL,
EDGES.UB,
EDGES.DF,
EDGES.DL,
EDGES.DB,
EDGES.DR,
EDGES.FR,
EDGES.FL,
EDGES.BL,
EDGES.BR,
],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
);
// L (Left Face Clockwise)
MOVES["L"] = new DeepCube(
[
CORNERS.URF,
CORNERS.ULB,
CORNERS.DBL,
CORNERS.UBR,
CORNERS.DFR,
CORNERS.UFL,
CORNERS.DLF,
CORNERS.DRB,
],
[0, 1, 2, 0, 0, 2, 1, 0],
[
EDGES.UR,
EDGES.UF,
EDGES.BL,
EDGES.UB,
EDGES.DR,
EDGES.DF,
EDGES.FL,
EDGES.DB,
EDGES.FR,
EDGES.UL,
EDGES.DL,
EDGES.BR,
],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
);
// B (Back Face Clockwise)
MOVES["B"] = new DeepCube(
[
CORNERS.URF,
CORNERS.UFL,
CORNERS.UBR,
CORNERS.DRB,
CORNERS.DFR,
CORNERS.DLF,
CORNERS.ULB,
CORNERS.DBL,
],
[0, 0, 1, 2, 0, 0, 2, 1],
[
EDGES.UR,
EDGES.UF,
EDGES.UL,
EDGES.BR,
EDGES.DR,
EDGES.DF,
EDGES.DL,
EDGES.BL,
EDGES.FR,
EDGES.FL,
EDGES.UB,
EDGES.DB,
],
[0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1],
);
// Generate inverses and 180s
const faces = ["U", "R", "F", "D", "L", "B"];
faces.forEach((f) => {
const m1 = MOVES[f];
const m2 = m1.multiply(m1);
const m3 = m2.multiply(m1);
MOVES[f + "2"] = m2;
MOVES[f + "'"] = m3;
});