diff --git a/src/utils/solvers/BeginnerSolver.js b/src/utils/solvers/BeginnerSolver.js index fc925a1..86c4eba 100644 --- a/src/utils/solvers/BeginnerSolver.js +++ b/src/utils/solvers/BeginnerSolver.js @@ -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]; - } else { - const path = dfs(nextNode, depth - 1, m); - if (path) return [m, ...path]; - } - } - 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; - } + 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 { + return temp.ep[targetId] === targetId && temp.eo[targetId] === 0; } - 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 - this.apply("F R U R' U' F'"); - } - } 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)) { - this.apply("F U R U' R' F'"); - } else { + 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; + } + 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; // All corners are cyclically ordered - } + ) + 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 { - this.apply("U"); + 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 + ) { + this.apply(alg); + succ = true; + break; + } } - } 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 - this.apply("R' F R' B2 R F' R' B2 R2"); + if (succ) break; + this.apply("U"); } + 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; } diff --git a/test/test_aperm.js b/test/test_aperm.js new file mode 100644 index 0000000..6dc68b6 --- /dev/null +++ b/test/test_aperm.js @@ -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). diff --git a/test/test_beginner_freeze.js b/test/test_beginner_freeze.js new file mode 100644 index 0000000..4fb5490 --- /dev/null +++ b/test/test_beginner_freeze.js @@ -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); +} diff --git a/test/test_beginner_random.js b/test/test_beginner_random.js new file mode 100644 index 0000000..bd60b50 --- /dev/null +++ b/test/test_beginner_random.js @@ -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!"); diff --git a/test/test_diagnostics.js b/test/test_diagnostics.js new file mode 100644 index 0000000..7bc6221 --- /dev/null +++ b/test/test_diagnostics.js @@ -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)); + diff --git a/test/test_diagnostics2.js b/test/test_diagnostics2.js new file mode 100644 index 0000000..bc1c6e8 --- /dev/null +++ b/test/test_diagnostics2.js @@ -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!"); diff --git a/test/test_macros.js b/test/test_macros.js new file mode 100644 index 0000000..dff4819 --- /dev/null +++ b/test/test_macros.js @@ -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)]); + diff --git a/test/test_macros2.js b/test/test_macros2.js new file mode 100644 index 0000000..346c079 --- /dev/null +++ b/test/test_macros2.js @@ -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'"); + diff --git a/test/test_macros3.js b/test/test_macros3.js new file mode 100644 index 0000000..5682c7f --- /dev/null +++ b/test/test_macros3.js @@ -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)]); diff --git a/test/test_macros4.js b/test/test_macros4.js new file mode 100644 index 0000000..9789026 --- /dev/null +++ b/test/test_macros4.js @@ -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)]); diff --git a/test/test_moves.js b/test/test_moves.js new file mode 100644 index 0000000..066f869 --- /dev/null +++ b/test/test_moves.js @@ -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)]); +