fix(solver): replace exponential IDDFS recursion with instantaneous heuristic simulation macros

This commit is contained in:
2026-02-23 22:04:41 +00:00
parent 929761ac9e
commit e5befab473
11 changed files with 481 additions and 232 deletions

View File

@@ -7,6 +7,7 @@ export class BeginnerSolver {
}
apply(moveStr) {
if (!moveStr) return;
const moveArr = moveStr.split(" ").filter((m) => m);
for (const m of moveArr) {
if (!MOVES[m]) throw new Error(`Invalid move: ${m}`);
@@ -17,8 +18,6 @@ export class BeginnerSolver {
solve() {
this.solution = [];
// Safety check - is it already solved?
if (this.isSolvedState(this.cube)) return [];
console.log("Starting Cross");
@@ -34,11 +33,8 @@ export class BeginnerSolver {
console.log("Starting Yellow PLL");
this.permuteYellowCorners();
this.permuteYellowEdges();
// Optional: align U face
this.alignUFace();
// Collapse redundant moves like U U -> U2, R R' -> nothing
this.optimizeSolution();
return this.solution;
}
@@ -51,210 +47,216 @@ export class BeginnerSolver {
return true;
}
// Generalized Depth-Limited Search for localized piece-by-piece goals
solveGoal(condition, maxDepth, allowedMoves = Object.keys(MOVES)) {
if (condition(this.cube)) return true;
const opposing = { U: "D", D: "U", L: "R", R: "L", F: "B", B: "F" };
const dfs = (node, depth, lastMove) => {
if (depth === 0) return condition(node) ? [] : null;
const lastFace = lastMove ? lastMove[0] : null;
for (let m of allowedMoves) {
const face = m[0];
if (face === lastFace) continue;
if (opposing[face] === lastFace && face > lastFace) continue;
const nextNode = node.multiply(MOVES[m]);
if (depth === 1) {
if (condition(nextNode)) return [m];
testAlg(algStr, targetId, isCorner) {
let temp = this.cube;
const arr = algStr.split(" ").filter((m) => m);
for (const m of arr) temp = temp.multiply(MOVES[m]);
if (isCorner) {
return temp.cp[targetId] === targetId && temp.co[targetId] === 0;
} else {
const path = dfs(nextNode, depth - 1, m);
if (path) return [m, ...path];
return temp.ep[targetId] === targetId && temp.eo[targetId] === 0;
}
}
return null;
};
for (let d = 1; d <= maxDepth; d++) {
const res = dfs(this.cube, d, "");
if (res) {
res.forEach((m) => this.apply(m));
return true;
}
}
return false;
}
solveCross() {
// White Cross on D face
// Edge Pieces: DF(5), DR(4), DB(7), DL(6)
const targets = [5, 4, 7, 6];
for (let i = 0; i < targets.length; i++) {
const goal = (state) => {
for (let j = 0; j <= i; j++) {
const t = targets[j];
if (state.ep[t] !== t || state.eo[t] !== 0) return false;
const targets = [
{ id: 5, up: 1, ins: ["F2", "U' R' F R", "U L F' L'"] }, // DF
{ id: 4, up: 0, ins: ["R2", "U' B' R B", "U F R' F'"] }, // DR
{ id: 7, up: 3, ins: ["B2", "U' L' B L", "U R B' R'"] }, // DB
{ id: 6, up: 2, ins: ["L2", "U' F' L F", "U B L' B'"] }, // DL
];
for (let t of targets) {
let safetyCount = 0;
while (safetyCount++ < 15) {
let pos = this.cube.ep.indexOf(t.id);
if (pos === t.id && this.cube.eo[pos] === 0) break;
if ([4, 5, 6, 7].includes(pos)) {
if (pos === 5) this.apply("F2");
else if (pos === 4) this.apply("R2");
else if (pos === 7) this.apply("B2");
else if (pos === 6) this.apply("L2");
} else if ([8, 9, 10, 11].includes(pos)) {
if (pos === 8) this.apply("R U R'");
else if (pos === 9) this.apply("F U F'");
else if (pos === 10) this.apply("L U L'");
else if (pos === 11) this.apply("B U B'");
} else if ([0, 1, 2, 3].includes(pos)) {
let success = false;
for (let u = 0; u < 4; u++) {
for (let alg of t.ins) {
if (this.testAlg(alg, t.id, false)) {
this.apply(alg);
success = true;
break;
}
}
if (success) break;
this.apply("U");
}
if (success) break;
}
}
return true;
};
if (!this.solveGoal(goal, 7)) throw new Error("Failed Cross depth 7");
}
}
solveF2LCorners() {
// DFR(4), DRB(7), DBL(6), DLF(5)
const corners = [4, 7, 6, 5];
for (let i = 0; i < corners.length; i++) {
const goal = (state) => {
// Must preserve cross
if (state.ep[5] !== 5 || state.eo[5] !== 0) return false;
if (state.ep[4] !== 4 || state.eo[4] !== 0) return false;
if (state.ep[7] !== 7 || state.eo[7] !== 0) return false;
if (state.ep[6] !== 6 || state.eo[6] !== 0) return false;
const targets = [
{ id: 4, ext: "R U R'", ins: ["R U2 R' U' R U R'", "R U R'", "F' U' F"] },
{ id: 5, ext: "F U F'", ins: ["F U2 F' U' F U F'", "F U F'", "L' U' L"] },
{ id: 6, ext: "L U L'", ins: ["L U2 L' U' L U L'", "L U L'", "B' U' B"] },
{ id: 7, ext: "B U B'", ins: ["B U2 B' U' B U B'", "B U B'", "R' U' R"] },
];
// Must preserve prior corners
for (let j = 0; j <= i; j++) {
const c = corners[j];
if (state.cp[c] !== c || state.co[c] !== 0) return false;
for (let t of targets) {
let safetyCount = 0;
while (safetyCount++ < 15) {
let pos = this.cube.cp.indexOf(t.id);
if (pos === t.id && this.cube.co[pos] === 0) break;
if ([4, 5, 6, 7].includes(pos)) {
if (pos === 4) this.apply("R U R'");
else if (pos === 5) this.apply("F U F'");
else if (pos === 6) this.apply("L U L'");
else if (pos === 7) this.apply("B U B'");
} else if ([0, 1, 2, 3].includes(pos)) {
let success = false;
for (let u = 0; u < 4; u++) {
for (let alg of t.ins) {
if (this.testAlg(alg, t.id, true)) {
this.apply(alg);
success = true;
break;
}
}
if (success) break;
this.apply("U");
}
if (success) break;
}
}
return true;
};
if (!this.solveGoal(goal, 8))
throw new Error("Failed F2L Corners depth 8");
}
}
solveF2LEdges() {
// FR(8), BR(11), BL(10), FL(9)
const edges = [8, 11, 10, 9];
const allowed = [
"U",
"U'",
"U2",
"R",
"R'",
"R2",
"F",
"F'",
"F2",
"L",
"L'",
"L2",
"B",
"B'",
"B2",
]; // Avoid D moves
const targets = [
{
id: 8,
ext: "R U R'",
ins: ["U R U' R' U' F' U F", "U' F' U F U R U' R'"],
},
{
id: 9,
ext: "F U F'",
ins: ["U' L' U L U F U' F'", "U F U' F' U' L' U L"],
},
{
id: 10,
ext: "L U L'",
ins: ["U L U' L' U' B' U B", "U' B' U B U L U' L'"],
},
{
id: 11,
ext: "B U B'",
ins: ["U B U' B' U' R' U R", "U' R' U R U B U' B'"],
},
];
for (let i = 0; i < edges.length; i++) {
const goal = (state) => {
// Preserve cross
if (state.ep[5] !== 5 || state.eo[5] !== 0) return false;
if (state.ep[4] !== 4 || state.eo[4] !== 0) return false;
if (state.ep[7] !== 7 || state.eo[7] !== 0) return false;
if (state.ep[6] !== 6 || state.eo[6] !== 0) return false;
// Preserve all D corners
if (state.cp[4] !== 4 || state.co[4] !== 0) return false;
if (state.cp[7] !== 7 || state.co[7] !== 0) return false;
if (state.cp[6] !== 6 || state.co[6] !== 0) return false;
if (state.cp[5] !== 5 || state.co[5] !== 0) return false;
for (let t of targets) {
let safetyCount = 0;
while (safetyCount++ < 15) {
let pos = this.cube.ep.indexOf(t.id);
if (pos === t.id && this.cube.eo[pos] === 0) break;
// Preserve prior edges
for (let j = 0; j <= i; j++) {
const e = edges[j];
if (state.ep[e] !== e || state.eo[e] !== 0) return false;
if ([8, 9, 10, 11].includes(pos)) {
if (pos === 8)
this.apply("R U R' U' F' U' F"); // Extract standard way
else if (pos === 9) this.apply("F U F' U' L' U' L");
else if (pos === 10) this.apply("L U L' U' B' U' B");
else if (pos === 11) this.apply("B U B' U' R' U' R");
} else if ([0, 1, 2, 3].includes(pos)) {
let success = false;
for (let u = 0; u < 4; u++) {
for (let alg of t.ins) {
if (this.testAlg(alg, t.id, false)) {
this.apply(alg);
success = true;
break;
}
}
if (success) break;
this.apply("U");
}
if (success) break;
}
}
return true;
};
// Depth 10 is needed for inserting edge from bad positions
if (!this.solveGoal(goal, 10, allowed))
throw new Error("Failed F2L Edges depth 10");
}
}
// --- BEGIN CFOP LAST LAYER MACROS ---
solveYellowCross() {
// Find yellow cross edges: UR(0), UF(1), UL(2), UB(3)
// They just need orientation eo=0.
const getOrientedCount = () =>
[0, 1, 2, 3].filter((i) => this.cube.eo[i] === 0).length;
let safetyCount = 0;
while (getOrientedCount() < 4 && safetyCount++ < 10) {
const oriented = [0, 1, 2, 3].filter((i) => this.cube.eo[i] === 0);
if (oriented.length === 0) {
this.apply("F R U R' U' F'");
} else if (oriented.length === 2) {
// Line (opposite) or L-shape (adjacent)
const [a, b] = oriented;
if (Math.abs(a - b) === 2) {
// Line geometry (UR and UL, or UF and UB)
// To apply F R U R' U' F', the line must be horizontal.
// If line is UR(0) and UL(2), it's horizontal from F perspective.
if (a === 0 && b === 2) {
this.apply("F R U R' U' F'");
} else {
this.apply("U"); // turn line so it is UR/UL
if (Math.abs(a - b) === 2 || (a === 0 && b === 3)) {
// Line or L-shape handling simplified
let succ = false;
for (let u = 0; u < 4; u++) {
let tmp = this.cube.clone();
let p1 = (temp) => {
let c = temp.clone();
"F R U R' U' F'"
.split(" ")
.filter((x) => x)
.forEach((m) => (c = c.multiply(MOVES[m])));
return c;
};
let p2 = (temp) => {
let c = temp.clone();
"F U R U' R' F'"
.split(" ")
.filter((x) => x)
.forEach((m) => (c = c.multiply(MOVES[m])));
return c;
};
if ([0, 1, 2, 3].filter((i) => p1(tmp).eo[i] === 0).length === 4) {
this.apply("F R U R' U' F'");
succ = true;
break;
}
} else {
// L-shape geometry
// Macro: F U R U' R' F' requires L to be at Back and Left (UB=3, UL=2)
if (oriented.includes(3) && oriented.includes(2)) {
if ([0, 1, 2, 3].filter((i) => p2(tmp).eo[i] === 0).length === 4) {
this.apply("F U R U' R' F'");
succ = true;
break;
}
this.apply("U");
}
if (!succ) this.apply("F R U R' U' F'"); // fallback
} else {
this.apply("U");
}
}
}
}
}
orientYellowCorners() {
// Sune / Antisune to orient yellow corners (co=0)
// OLL for corners alone using the repetitive beginner algorithm (R' D' R D)
// Position the unsolved corner at URF (0) and repeat R' D' R D until co[0] === 0
let safetyCount = 0;
while (safetyCount++ < 20) {
let solvedCount = [0, 1, 2, 3].filter(
(i) => this.cube.co[i] === 0,
).length;
if (solvedCount === 4) break;
if (this.cube.co[0] === 0) {
this.apply("U"); // Next
} else {
// Apply R' D' R D twice (1 cycle)
this.apply("R' D' R D R' D' R D");
}
while (safetyCount++ < 25) {
if ([0, 1, 2, 3].filter((i) => this.cube.co[i] === 0).length === 4) break;
if (this.cube.co[0] === 0) this.apply("U");
else this.apply("R' D' R D R' D' R D");
}
}
permuteYellowCorners() {
// Beginner method: look for "headlights" (two corners on the same face with the same color targeting that face).
// Mathematically: two adjacent U corners whose permutation matches their distance.
// E.g., URF(0) and UBR(3). If their permuted values are also adjacent, they are headlights.
const hasHeadlightsOnBack = () => {
// Back corners are ULB(2) and UBR(3)
// To be headlights on the back, they must belong to the same face in their solved state.
// Wait, comparing corner colors mathematically:
// In a solved cube, the U face has 4 corners: 0, 1, 2, 3.
// The distance between cp[2] and cp[3] modulo 4 should be exactly 1 or 3 (adjacent).
// Actually, if they are correctly relative to EACH OTHER:
return (
(this.cube.cp[2] - this.cube.cp[3] + 4) % 4 === 3 ||
(this.cube.cp[2] - this.cube.cp[3] + 4) % 4 === 1
);
};
// Simplified: Just keep applying the A-perm (headlight creator) until all 4 corners are relatively solved.
let safetyCount = 0;
while (safetyCount++ < 10) {
// Check if corners are relatively solved (i.e. 0->1->2->3 in order)
while (safetyCount++ < 15) {
let c0 = this.cube.cp[0],
c1 = this.cube.cp[1],
c2 = this.cube.cp[2],
@@ -263,47 +265,43 @@ export class BeginnerSolver {
(c1 - c0 + 4) % 4 === 1 &&
(c2 - c1 + 4) % 4 === 1 &&
(c3 - c2 + 4) % 4 === 1
)
break;
let succ = false;
for (let u = 0; u < 4; u++) {
for (let alg of [
"R' F R' B2 R F' R' B2 R2",
"R B' R F2 R' B R F2 R2",
]) {
let t = this.cube.clone();
alg.split(" ").forEach((m) => (t = t.multiply(MOVES[m])));
let tc0 = t.cp[0],
tc1 = t.cp[1],
tc2 = t.cp[2],
tc3 = t.cp[3];
if (
(tc1 - tc0 + 4) % 4 === 1 &&
(tc2 - tc1 + 4) % 4 === 1 &&
(tc3 - tc2 + 4) % 4 === 1
) {
break; // All corners are cyclically ordered
this.apply(alg);
succ = true;
break;
}
// If Back corners are cyclically ordered (Headlights on Back)
if ((c2 - c3 + 4) % 4 === 1 || (c3 - c2 + 4) % 4 === 3) {
// Wait, order must be 3->0->1->2. So ULB(2) should be next after UBR(3).
// If cp[2] comes immediately after cp[3] cyclically:
if ((this.cube.cp[2] - this.cube.cp[3] + 4) % 4 === 1) {
this.apply("R' F R' B2 R F' R' B2 R2");
} else {
}
if (succ) break;
this.apply("U");
}
} else {
// Find ANY headlights and put them on the back
if ((c1 - c2 + 4) % 4 === 1) {
this.apply("U");
continue;
}
if ((c0 - c1 + 4) % 4 === 1) {
this.apply("U2");
continue;
}
if ((c3 - c0 + 4) % 4 === 1) {
this.apply("U'");
continue;
}
// No headlights at all (diagonal swap), just apply the alg anywhere
if (succ) break;
this.apply("R' F R' B2 R F' R' B2 R2");
}
}
}
permuteYellowEdges() {
// Corners are properly ordered now. We just need to align them to the centers first.
while (this.cube.cp[0] !== 0) {
this.apply("U");
}
let s = 0;
while (this.cube.cp[0] !== 0 && s++ < 5) this.apply("U");
// Now corners are 100% solved. Check edges.
let safetyCount = 0;
while (safetyCount++ < 10) {
if (
@@ -311,62 +309,62 @@ export class BeginnerSolver {
this.cube.ep[1] === 1 &&
this.cube.ep[2] === 2 &&
this.cube.ep[3] === 3
) {
break; // fully solved!
}
)
break;
// Check if one edge is solved. If so, put it at the BACK (UB=3)
if (this.cube.ep[3] === 3) {
// Apply U-perm
this.apply("R U' R U R U R U' R' U' R2");
} else if (this.cube.ep[0] === 0) {
this.apply("U"); // turn whole cube or just U... wait, standard alg rotates U.
// To keep corners solved but move edges, we'd have to do y rotations.
// Let's just use the U-perm and see if it solves.
// If UR(0) is solved, we want it at UB(3). So we do a U move, apply alg, do U'.
// Wait, applying U moves the corners! We can't do that.
// Standard solving often uses `y` cube rotations. Since our model only supports face moves,
// we can use the transposed algorithms.
// Alg for solved Right(0): F U' F U F U F U' F' U' F2
this.apply("F U' F U F U F U' F' U' F2");
} else if (this.cube.ep[1] === 1) {
this.apply("L U' L U L U L U' L' U' L2");
} else if (this.cube.ep[2] === 2) {
this.apply("B U' B U B U B U' B' U' B2");
} else {
// No edges solved. Apply U-perm from anywhere.
this.apply("R U' R U R U R U' R' U' R2");
let succ = false;
const uMoves = ["", "U ", "U2 ", "U' "];
const uMovesInv = ["", "U' ", "U2 ", "U "];
for (let u = 0; u < 4; u++) {
for (let baseAlg of [
"R U' R U R U R U' R' U' R2",
"L' U L' U' L' U' L' U L U L2",
]) {
const fullAlg = uMoves[u] + baseAlg + " " + uMovesInv[u];
let t = this.cube.clone();
fullAlg
.split(" ")
.filter((x) => x)
.forEach((m) => (t = t.multiply(MOVES[m])));
if (
t.ep[0] === 0 &&
t.ep[1] === 1 &&
t.ep[2] === 2 &&
t.ep[3] === 3
) {
this.apply(fullAlg);
succ = true;
break;
}
}
if (succ) break;
}
if (succ) break;
this.apply("R U' R U R U R U' R' U' R2"); // Fallback cycle
}
}
alignUFace() {
while (this.cube.cp[0] !== 0) {
this.apply("U");
}
let s = 0;
while (this.cube.cp[0] !== 0 && s++ < 5) this.apply("U");
}
optimizeSolution() {
// A quick pass to cancel out redundant moves like U U' -> nothing, U U -> U2, U2 U -> U'
let stable = false;
while (!stable) {
stable = true;
for (let i = 0; i < this.solution.length - 1; i++) {
const a = this.solution[i];
const b = this.solution[i + 1];
if (a[0] === b[0]) {
// Same face! Let's sum the rotation.
const val = (m) => (m.includes("'") ? -1 : m.includes("2") ? 2 : 1);
let sum = (val(a) + val(b)) % 4;
if (sum < 0) sum += 4; // normalize to positive
this.solution.splice(i, 2); // remove both
if (sum < 0) sum += 4;
this.solution.splice(i, 2);
if (sum === 1) this.solution.splice(i, 0, a[0]);
else if (sum === 2) this.solution.splice(i, 0, a[0] + "2");
else if (sum === 3) this.solution.splice(i, 0, a[0] + "'");
stable = false;
break;
}

9
test/test_aperm.js Normal file
View File

@@ -0,0 +1,9 @@
import { DeepCube, MOVES } from '../src/utils/DeepCube.js';
let cube = new DeepCube();
const apply = (str) => { str.split(' ').filter(x => x).forEach(m => cube = cube.multiply(MOVES[m])); };
apply("R' F R' B2 R F' R' B2 R2");
console.log(`cp after A-perm:`, cube.cp.slice(0, 4));
// We want to see which two corners are swapped.
// Solved is 0,1,2,3.
// If it prints 0,1,3,2, then 2 and 3 are swapped (Back corners).

View File

@@ -0,0 +1,25 @@
import { DeepCube, MOVES } from '../src/utils/DeepCube.js';
import { BeginnerSolver } from '../src/utils/solvers/BeginnerSolver.js';
let cube = new DeepCube();
const scramble = "R U R' U' R' F R2 U' R' U' R U R' F'"; // T-perm
scramble.split(' ').forEach(move => {
cube = cube.multiply(MOVES[move]);
});
console.log('Testing BeginnerSolver with T-perm...');
const solver = new BeginnerSolver(cube);
// Add some logging to the solver's methods to trace execution
const originalApply = solver.apply.bind(solver);
solver.apply = (moveStr) => {
// console.log('Applying:', moveStr);
originalApply(moveStr);
};
try {
const solution = solver.solve();
console.log('Solution found:', solution.join(' '));
} catch (e) {
console.error('Error during solve:', e);
}

View File

@@ -0,0 +1,41 @@
import { DeepCube, MOVES } from "../src/utils/DeepCube.js";
import { BeginnerSolver } from "../src/utils/solvers/BeginnerSolver.js";
const allMoves = Object.keys(MOVES);
const getRandomScramble = (length = 20) => {
let s = [];
for (let i = 0; i < length; i++)
s.push(allMoves[Math.floor(Math.random() * allMoves.length)]);
return s.join(" ");
};
for (let i = 1; i <= 20; i++) {
let cube = new DeepCube();
const scramble = getRandomScramble();
scramble.split(" ").forEach((move) => (cube = cube.multiply(MOVES[move])));
const startTime = Date.now();
const solver = new BeginnerSolver(cube);
try {
const solution = solver.solve();
const elapsedTime = Date.now() - startTime;
console.log(
`Test ${i}: Solved in ${elapsedTime}ms. Solution length: ${solution.length}`,
);
// Verify it actually solved it
let testCube = cube.clone();
solution.forEach((m) => (testCube = testCube.multiply(MOVES[m])));
if (!solver.isSolvedState(testCube)) {
console.error(
`ERROR: Test ${i} failed to fully solve the cube mathematically!`,
);
process.exit(1);
}
} catch (e) {
console.error(`ERROR: Test ${i} threw an exception:`, e);
process.exit(1);
}
}
console.log("All 20 tests passed flawlessly!");

34
test/test_diagnostics.js Normal file
View File

@@ -0,0 +1,34 @@
import { DeepCube, MOVES } from '../src/utils/DeepCube.js';
import { BeginnerSolver } from '../src/utils/solvers/BeginnerSolver.js';
const allMoves = Object.keys(MOVES);
const getRandomScramble = (length = 20) => {
let s = [];
for (let i = 0; i < length; i++) s.push(allMoves[Math.floor(Math.random() * allMoves.length)]);
return s.join(' ');
};
let cube = new DeepCube();
const scramble = getRandomScramble();
scramble.split(' ').forEach(move => cube = cube.multiply(MOVES[move]));
const solver = new BeginnerSolver(cube);
solver.solve();
console.log("Check Cross:");
for (let i of [4, 5, 6, 7]) console.log(`Edge ${i}: ep=${solver.cube.ep.indexOf(i)} eo=${solver.cube.eo[solver.cube.ep.indexOf(i)]}`);
console.log("Check F2L Corners:");
for (let i of [4, 5, 6, 7]) console.log(`Corner ${i}: cp=${solver.cube.cp.indexOf(i)} co=${solver.cube.co[solver.cube.cp.indexOf(i)]}`);
console.log("Check F2L Edges:");
for (let i of [8, 9, 10, 11]) console.log(`Edge ${i}: ep=${solver.cube.ep.indexOf(i)} eo=${solver.cube.eo[solver.cube.ep.indexOf(i)]}`);
console.log("Check OLL:");
console.log(`co:`, solver.cube.co.slice(0, 4));
console.log(`eo:`, solver.cube.eo.slice(0, 4));
console.log("Check PLL:");
console.log(`cp:`, solver.cube.cp.slice(0, 4));
console.log(`ep:`, solver.cube.ep.slice(0, 4));

40
test/test_diagnostics2.js Normal file
View File

@@ -0,0 +1,40 @@
import { DeepCube, MOVES } from '../src/utils/DeepCube.js';
import { BeginnerSolver } from '../src/utils/solvers/BeginnerSolver.js';
const allMoves = Object.keys(MOVES);
const getRandomScramble = (length = 20) => {
let s = [];
for (let i = 0; i < length; i++) s.push(allMoves[Math.floor(Math.random() * allMoves.length)]);
return s.join(' ');
};
for (let iter = 0; iter < 100; iter++) {
let cube = new DeepCube();
const scramble = getRandomScramble();
scramble.split(' ').forEach(move => cube = cube.multiply(MOVES[move]));
const solver = new BeginnerSolver(cube);
solver.solve();
if (!solver.isSolvedState(solver.cube)) {
console.log("FAILED ON SCRAMBLE:", scramble);
console.log("Check Cross:");
for (let i of [4, 5, 6, 7]) console.log(`Edge ${i}: ep=${solver.cube.ep.indexOf(i)} eo=${solver.cube.eo[solver.cube.ep.indexOf(i)]}`);
console.log("Check F2L Corners:");
for (let i of [4, 5, 6, 7]) console.log(`Corner ${i}: cp=${solver.cube.cp.indexOf(i)} co=${solver.cube.co[solver.cube.cp.indexOf(i)]}`);
console.log("Check F2L Edges:");
for (let i of [8, 9, 10, 11]) console.log(`Edge ${i}: ep=${solver.cube.ep.indexOf(i)} eo=${solver.cube.eo[solver.cube.ep.indexOf(i)]}`);
console.log("Check OLL:");
console.log(`co:`, solver.cube.co.slice(0, 4));
console.log(`eo:`, solver.cube.eo.slice(0, 4));
console.log("Check PLL:");
console.log(`cp:`, solver.cube.cp.slice(0, 4));
console.log(`ep:`, solver.cube.ep.slice(0, 4));
process.exit(1);
}
}
console.log("All 100 tests passed!");

24
test/test_macros.js Normal file
View File

@@ -0,0 +1,24 @@
import { DeepCube, MOVES } from '../src/utils/DeepCube.js';
let cube = new DeepCube();
const apply = (str) => {
str.split(' ').forEach(m => {
cube = cube.multiply(MOVES[m]);
});
};
apply("R U R'");
console.log("Piece 4 (R U R') is at position:", cube.cp.indexOf(4), "Orientation:", cube.co[cube.cp.indexOf(4)]);
cube = new DeepCube();
apply("R U' R' U R U2 R'");
console.log("Piece 4 (Up-face extraction) position:", cube.cp.indexOf(4), "Orientation:", cube.co[cube.cp.indexOf(4)]);
cube = new DeepCube();
apply("R U R'"); // insert front facing
console.log("Piece 4 (Front-face extraction) position:", cube.cp.indexOf(4), "Orientation:", cube.co[cube.cp.indexOf(4)]);
cube = new DeepCube();
apply("F' U' F"); // insert left facing
console.log("Piece 4 (Side-face extraction) position:", cube.cp.indexOf(4), "Orientation:", cube.co[cube.cp.indexOf(4)]);

24
test/test_macros2.js Normal file
View File

@@ -0,0 +1,24 @@
import { DeepCube, MOVES } from '../src/utils/DeepCube.js';
let cube;
const apply = (str) => {
str.split(' ').forEach(m => {
cube = cube.multiply(MOVES[m]);
});
};
const check = (name, alg, initPos, initOri) => {
cube = new DeepCube();
apply(alg);
// We applied alg to a SOLVED cube.
// The piece that WAS at 4 (DFR) is now at some position P with orientation O.
// To solve it, we would need to reverse the alg.
// So if we find a piece at P with orientation O, we apply the reverse alg!
console.log(`${name}: Extraction piece 4 is at pos ${cube.cp.indexOf(4)} ori ${cube.co[cube.cp.indexOf(4)]}`);
};
check("R U R'", "R U R'");
check("R U' R'", "R U' R'");
check("F' U' F", "F' U' F");
check("R U2 R' U' R U R'", "R U' R' U R U2 R'");

10
test/test_macros3.js Normal file
View File

@@ -0,0 +1,10 @@
import { DeepCube, MOVES } from '../src/utils/DeepCube.js';
let cube = new DeepCube();
const apply = (str) => { str.split(' ').forEach(m => { cube = cube.multiply(MOVES[m]); }); };
cube = new DeepCube(); apply("F' U F");
console.log("F' U F reverse puts piece 4 at pos:", cube.cp.indexOf(4), "ori:", cube.co[cube.cp.indexOf(4)]);
cube = new DeepCube(); apply("U' F' U F");
console.log("U' F' U F reverse puts piece 4 at pos:", cube.cp.indexOf(4), "ori:", cube.co[cube.cp.indexOf(4)]);

22
test/test_macros4.js Normal file
View File

@@ -0,0 +1,22 @@
import { DeepCube, MOVES } from '../src/utils/DeepCube.js';
let cube = new DeepCube();
const apply = (str) => { str.split(' ').forEach(m => { cube = cube.multiply(MOVES[m]); }); };
const check = (name, alg, expectedPos, expectedOri) => {
cube = new DeepCube();
apply(alg); // reverse of extraction
let p5 = cube.cp.indexOf(5); let o5 = cube.co[p5];
console.log(`${name}: pos 5 is ${p5} (expected ${expectedPos}), ori ${o5} (expected ${expectedOri})`);
};
// DLF (5) Target UFL (1)
check("F' U' F reverse", "F' U F", 1, 2); // if reverse puts it at pos 1 ori 2, then if at pos 1 ori 2 use F' U' F!
check("L U L' reverse", "L U' L'", 1, 1);
check("L' U' L reverse", "L' U L", 1, 1); // wait, L' moves DLF to UBL(2)? Let's find out!
// Check extraction from 5
cube = new DeepCube(); apply("L U L'");
console.log("Extract DLF (5) with L U L' gives pos:", cube.cp.indexOf(5), "ori:", cube.co[cube.cp.indexOf(5)]);
cube = new DeepCube(); apply("F' U' F");
console.log("Extract DLF (5) with F' U' F gives pos:", cube.cp.indexOf(5), "ori:", cube.co[cube.cp.indexOf(5)]);

22
test/test_moves.js Normal file
View File

@@ -0,0 +1,22 @@
import { DeepCube, MOVES } from '../src/utils/DeepCube.js';
let cube = new DeepCube();
const apply = (str) => {
str.split(' ').forEach(m => {
cube = cube.multiply(MOVES[m]);
});
};
// We want to verify `R U R'` extracts piece 4 (DFR) to U layer.
apply("R U R'");
console.log("Piece 4 is at position:", cube.cp.indexOf(4), "Orientation:", cube.co[cube.cp.indexOf(4)]);
cube = new DeepCube();
// What if piece 4 is at URF (position 0)? We want to insert it to DFR (position 4).
// If Yellow is UP, co=0.
// Let's create a state where DFR is at URF with co=0.
// We can do this by applying R U2 R' U' R U R' IN REVERSE to extract it.
// Reverse of R U2 R' U' R U R' is: R U' R' U R U2 R'
apply("R U' R' U R U2 R'");
console.log("Extraction -> Piece 4 position:", cube.cp.indexOf(4), "Orientation:", cube.co[cube.cp.indexOf(4)]);