Refactor: Implement SmartCube renderer, improve UI styling, and fix gaps
This commit is contained in:
161
test/cube_integrity.test.js
Normal file
161
test/cube_integrity.test.js
Normal file
@@ -0,0 +1,161 @@
|
||||
|
||||
import { Cube, FACES, COLORS } from '../src/utils/Cube.js';
|
||||
import assert from 'assert';
|
||||
|
||||
console.log('Running Cube Integrity Tests...');
|
||||
|
||||
const cube = new Cube();
|
||||
|
||||
// Helper: Count colors on all faces
|
||||
const countColors = () => {
|
||||
const counts = {
|
||||
[COLORS.WHITE]: 0,
|
||||
[COLORS.YELLOW]: 0,
|
||||
[COLORS.ORANGE]: 0,
|
||||
[COLORS.RED]: 0,
|
||||
[COLORS.GREEN]: 0,
|
||||
[COLORS.BLUE]: 0,
|
||||
[COLORS.BLACK]: 0 // Should be ignored or internal
|
||||
};
|
||||
|
||||
cube.cubies.forEach(cubie => {
|
||||
Object.values(cubie.faces).forEach(color => {
|
||||
if (counts[color] !== undefined) {
|
||||
counts[color]++;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return counts;
|
||||
};
|
||||
|
||||
// Helper: Verify solved state counts
|
||||
const verifyCounts = (counts) => {
|
||||
// Each face has 9 stickers. 6 faces.
|
||||
// 9 * 6 = 54 colored stickers.
|
||||
// 27 cubies * 6 faces = 162 total faces.
|
||||
// 162 - 54 = 108 black faces (internal).
|
||||
|
||||
assert.strictEqual(counts[COLORS.WHITE], 9, 'White count should be 9');
|
||||
assert.strictEqual(counts[COLORS.YELLOW], 9, 'Yellow count should be 9');
|
||||
assert.strictEqual(counts[COLORS.ORANGE], 9, 'Orange count should be 9');
|
||||
assert.strictEqual(counts[COLORS.RED], 9, 'Red count should be 9');
|
||||
assert.strictEqual(counts[COLORS.GREEN], 9, 'Green count should be 9');
|
||||
assert.strictEqual(counts[COLORS.BLUE], 9, 'Blue count should be 9');
|
||||
};
|
||||
|
||||
// Helper: Verify piece integrity
|
||||
// Corners: 8 corners, each has 3 colors.
|
||||
// Edges: 12 edges, each has 2 colors.
|
||||
// Centers: 6 centers, each has 1 color.
|
||||
// Core: 1 core, 0 colors.
|
||||
const verifyPieceTypes = () => {
|
||||
let corners = 0;
|
||||
let edges = 0;
|
||||
let centers = 0;
|
||||
let cores = 0;
|
||||
|
||||
cube.cubies.forEach(cubie => {
|
||||
const coloredFaces = Object.values(cubie.faces).filter(c => c !== COLORS.BLACK).length;
|
||||
if (coloredFaces === 3) corners++;
|
||||
else if (coloredFaces === 2) edges++;
|
||||
else if (coloredFaces === 1) centers++;
|
||||
else if (coloredFaces === 0) cores++;
|
||||
else assert.fail(`Invalid cubie with ${coloredFaces} colors at (${cubie.x},${cubie.y},${cubie.z})`);
|
||||
});
|
||||
|
||||
assert.strictEqual(corners, 8, 'Should have 8 corners');
|
||||
assert.strictEqual(edges, 12, 'Should have 12 edges');
|
||||
assert.strictEqual(centers, 6, 'Should have 6 centers');
|
||||
assert.strictEqual(cores, 1, 'Should have 1 core');
|
||||
};
|
||||
|
||||
// Helper: Verify specific relative positions of centers (they never change relative to each other)
|
||||
// Up (White) opposite Down (Yellow)
|
||||
// Front (Green) opposite Back (Blue)
|
||||
// Left (Orange) opposite Right (Red)
|
||||
const verifyCenters = () => {
|
||||
const centers = cube.cubies.filter(c =>
|
||||
Object.values(c.faces).filter(f => f !== COLORS.BLACK).length === 1
|
||||
);
|
||||
|
||||
// Find center by color
|
||||
const findCenter = (color) => centers.find(c => Object.values(c.faces).includes(color));
|
||||
|
||||
const white = findCenter(COLORS.WHITE);
|
||||
const yellow = findCenter(COLORS.YELLOW);
|
||||
const green = findCenter(COLORS.GREEN);
|
||||
const blue = findCenter(COLORS.BLUE);
|
||||
const orange = findCenter(COLORS.ORANGE);
|
||||
const red = findCenter(COLORS.RED);
|
||||
|
||||
// Check opposites
|
||||
// Distance between opposites should be 2 (e.g. y=1 and y=-1)
|
||||
// And they should be on same axis
|
||||
|
||||
// Note: After rotations, x/y/z coordinates change.
|
||||
// But relative vectors should hold?
|
||||
// Actually, centers DO rotate around the core.
|
||||
// But White is always opposite Yellow.
|
||||
// So vector(White) + vector(Yellow) == (0,0,0).
|
||||
|
||||
const checkOpposite = (c1, c2, name) => {
|
||||
assert.strictEqual(c1.x + c2.x, 0, `${name} X mismatch`);
|
||||
assert.strictEqual(c1.y + c2.y, 0, `${name} Y mismatch`);
|
||||
assert.strictEqual(c1.z + c2.z, 0, `${name} Z mismatch`);
|
||||
};
|
||||
|
||||
checkOpposite(white, yellow, 'White-Yellow');
|
||||
checkOpposite(green, blue, 'Green-Blue');
|
||||
checkOpposite(orange, red, 'Orange-Red');
|
||||
};
|
||||
|
||||
|
||||
// --- Test Execution ---
|
||||
|
||||
// 1. Initial State
|
||||
console.log('Test 1: Initial State Integrity');
|
||||
verifyCounts(countColors());
|
||||
verifyPieceTypes();
|
||||
verifyCenters();
|
||||
console.log('PASS Initial State');
|
||||
|
||||
// 2. Single Rotation (R)
|
||||
console.log('Test 2: Single Rotation (R)');
|
||||
cube.rotateLayer('x', 1, -1); // R
|
||||
verifyCounts(countColors());
|
||||
verifyPieceTypes();
|
||||
verifyCenters();
|
||||
console.log('PASS Single Rotation');
|
||||
|
||||
// 3. Multiple Rotations (R U R' U')
|
||||
console.log('Test 3: Sexy Move (R U R\' U\')');
|
||||
cube.reset();
|
||||
cube.move("R");
|
||||
cube.move("U");
|
||||
cube.move("R'");
|
||||
cube.move("U'");
|
||||
verifyCounts(countColors());
|
||||
verifyPieceTypes();
|
||||
verifyCenters();
|
||||
console.log('PASS Sexy Move');
|
||||
|
||||
// 4. Random Rotations (Fuzzing)
|
||||
console.log('Test 4: 100 Random Moves');
|
||||
cube.reset();
|
||||
const axes = ['x', 'y', 'z'];
|
||||
const indices = [-1, 0, 1];
|
||||
const dirs = [1, -1];
|
||||
|
||||
for (let i = 0; i < 100; i++) {
|
||||
const axis = axes[Math.floor(Math.random() * axes.length)];
|
||||
const index = indices[Math.floor(Math.random() * indices.length)];
|
||||
const dir = dirs[Math.floor(Math.random() * dirs.length)];
|
||||
cube.rotateLayer(axis, index, dir);
|
||||
}
|
||||
verifyCounts(countColors());
|
||||
verifyPieceTypes();
|
||||
verifyCenters();
|
||||
console.log('PASS 100 Random Moves');
|
||||
|
||||
console.log('ALL INTEGRITY TESTS PASSED');
|
||||
117
test/simulate_moves.js
Normal file
117
test/simulate_moves.js
Normal file
@@ -0,0 +1,117 @@
|
||||
|
||||
import { Cube, FACES, COLORS } from '../src/utils/Cube.js';
|
||||
|
||||
// Helper to print face
|
||||
const printFace = (matrix, name) => {
|
||||
console.log(`--- ${name} ---`);
|
||||
matrix.forEach(row => console.log(row.map(c => c ? c[0].toUpperCase() : '-').join(' ')));
|
||||
};
|
||||
|
||||
// Helper to check if a face matches expected color (center color)
|
||||
const checkFaceColor = (matrix, expectedColor) => {
|
||||
return matrix.every(row => row.every(c => c === expectedColor));
|
||||
};
|
||||
|
||||
console.log("=== RUBIK'S CUBE SIMULATION & DIAGNOSTIC ===");
|
||||
|
||||
const cube = new Cube();
|
||||
|
||||
// 1. Initial State Check
|
||||
console.log("\n1. Checking Initial State...");
|
||||
let state = cube.getState();
|
||||
const isSolved =
|
||||
checkFaceColor(state[FACES.UP], COLORS.WHITE) &&
|
||||
checkFaceColor(state[FACES.DOWN], COLORS.YELLOW) &&
|
||||
checkFaceColor(state[FACES.FRONT], COLORS.GREEN) &&
|
||||
checkFaceColor(state[FACES.BACK], COLORS.BLUE) &&
|
||||
checkFaceColor(state[FACES.LEFT], COLORS.ORANGE) &&
|
||||
checkFaceColor(state[FACES.RIGHT], COLORS.RED);
|
||||
|
||||
if (isSolved) {
|
||||
console.log("✅ Initial state is SOLVED.");
|
||||
} else {
|
||||
console.error("❌ Initial state is BROKEN.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// 2. Simulate Move: Front Face Drag Down
|
||||
// Visual: Drag Down on Front Face (Left Layer).
|
||||
// Axis: X. Index: -1 (Left).
|
||||
// Physical expectation: The Left slice moves "towards the user" (if looking from top) or "down" (if looking from front).
|
||||
// Standard Notation: L (Left CW).
|
||||
// L move: Top -> Front -> Down -> Back -> Top.
|
||||
// Let's verify what L does.
|
||||
// Standard L: Front gets Top color.
|
||||
// If I drag Left Layer DOWN on Front face, the Front face pieces move DOWN.
|
||||
// So Front gets Top pieces.
|
||||
// So Drag Down = L = Front gets Top.
|
||||
|
||||
console.log("\n2. Simulating: Left Layer (x=-1) Rotation (L-like move)...");
|
||||
// We need to find which 'direction' in our engine corresponds to L.
|
||||
// Our engine: rotateLayer('x', -1, direction).
|
||||
|
||||
// Try direction = 1
|
||||
console.log("-> Applying rotateLayer('x', -1, 1)...");
|
||||
cube.rotateLayer('x', -1, 1);
|
||||
state = cube.getState();
|
||||
|
||||
// Check result on Left Column of Front Face
|
||||
// Front is Green. Top is White.
|
||||
// If L (Drag Down): Front-Left-Col should be White.
|
||||
const frontLeftCol = [state[FACES.FRONT][0][0], state[FACES.FRONT][1][0], state[FACES.FRONT][2][0]];
|
||||
console.log("Front Left Column colors:", frontLeftCol);
|
||||
|
||||
if (frontLeftCol.every(c => c === COLORS.WHITE)) {
|
||||
console.log("✅ Result: Front got White (Top). This matches 'Drag Down' (L move).");
|
||||
console.log("=> CONCLUSION: direction=1 corresponds to Drag Down (L).");
|
||||
} else if (frontLeftCol.every(c => c === COLORS.YELLOW)) {
|
||||
console.log("⚠️ Result: Front got Yellow (Down). This matches 'Drag Up' (L' move).");
|
||||
console.log("=> CONCLUSION: direction=1 corresponds to Drag Up (L').");
|
||||
} else {
|
||||
console.error("❌ Unexpected colors:", frontLeftCol);
|
||||
}
|
||||
|
||||
// Reset for next test
|
||||
cube.reset();
|
||||
|
||||
// 3. Simulate Move: Front Face Drag Right
|
||||
// Visual: Drag Right on Front Face (Top Layer).
|
||||
// Axis: Y. Index: 1 (Top).
|
||||
// Physical expectation: Top slice moves Right.
|
||||
// Standard Notation: U' (Up CCW) ? No.
|
||||
// If I hold cube, drag Top Layer to Right.
|
||||
// Front face pieces move to Right face.
|
||||
// Standard U (CW): Front -> Left.
|
||||
// So Drag Right is U' (CCW).
|
||||
// Let's verify.
|
||||
// Drag Right: Front -> Right.
|
||||
|
||||
console.log("\n3. Simulating: Top Layer (y=1) Rotation...");
|
||||
// Try direction = 1
|
||||
console.log("-> Applying rotateLayer('y', 1, 1)...");
|
||||
cube.rotateLayer('y', 1, 1);
|
||||
state = cube.getState();
|
||||
|
||||
// Check result on Top Row of Front Face
|
||||
// Front is Green. Left is Orange. Right is Red.
|
||||
// If Drag Right: Front-Top-Row should be Orange? No.
|
||||
// Drag Right: The pieces move Right. So Front REPLACES Right?
|
||||
// Or Front GETS Left?
|
||||
// Visually: The face moves Right. So the Green pieces go to Right face.
|
||||
// And Front face gets Left (Orange) pieces.
|
||||
// So Front-Top-Row should be Orange.
|
||||
|
||||
const frontTopRow = state[FACES.FRONT][0];
|
||||
console.log("Front Top Row colors:", frontTopRow);
|
||||
|
||||
if (frontTopRow.every(c => c === COLORS.ORANGE)) {
|
||||
console.log("✅ Result: Front got Orange (Left). This matches 'Drag Right'.");
|
||||
console.log("=> CONCLUSION: direction=1 corresponds to Drag Right.");
|
||||
} else if (frontTopRow.every(c => c === COLORS.RED)) {
|
||||
console.log("⚠️ Result: Front got Red (Right). This matches 'Drag Left'.");
|
||||
console.log("=> CONCLUSION: direction=1 corresponds to Drag Left.");
|
||||
} else {
|
||||
console.error("❌ Unexpected colors:", frontTopRow);
|
||||
}
|
||||
|
||||
console.log("\n=== END SIMULATION ===");
|
||||
72
test/test_new_model.js
Normal file
72
test/test_new_model.js
Normal file
@@ -0,0 +1,72 @@
|
||||
|
||||
import { CubeModel, FACES, COLORS } from '../src/utils/CubeModel.js';
|
||||
|
||||
console.log('Running CubeModel Rotation Logic Tests...');
|
||||
|
||||
const cube1 = new CubeModel();
|
||||
const cube2 = new CubeModel();
|
||||
|
||||
const compareCubes = (c1, c2, message) => {
|
||||
const s1 = c1.toString();
|
||||
const s2 = c2.toString();
|
||||
if (s1 === s2) {
|
||||
console.log(`✅ PASS: ${message}`);
|
||||
return true;
|
||||
} else {
|
||||
console.error(`❌ FAIL: ${message}`);
|
||||
console.log('Expected (Standard Move):');
|
||||
console.log(s2);
|
||||
console.log('Actual (Layer Rotation):');
|
||||
console.log(s1);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// Test 1: Top Layer (y=1) CW vs U
|
||||
cube1.reset();
|
||||
cube2.reset();
|
||||
console.log('Testing Top Layer CW vs U...');
|
||||
cube1.rotateLayer('y', 1, 1); // Top CW
|
||||
cube2.applyMove('U');
|
||||
compareCubes(cube1, cube2, "Top Layer CW matches U");
|
||||
|
||||
// Test 2: Bottom Layer (y=-1) CW vs D
|
||||
cube1.reset();
|
||||
cube2.reset();
|
||||
console.log('Testing Bottom Layer CW vs D...');
|
||||
cube1.rotateLayer('y', -1, -1); // Bottom CW (CW around -Y is CCW around Y)
|
||||
cube2.applyMove('D');
|
||||
compareCubes(cube1, cube2, "Bottom Layer CW matches D");
|
||||
|
||||
// Test 3: Left Layer (x=-1) CW vs L
|
||||
cube1.reset();
|
||||
cube2.reset();
|
||||
console.log('Testing Left Layer CW vs L...');
|
||||
cube1.rotateLayer('x', -1, -1); // Left CW (CW around -X is CCW around X)
|
||||
cube2.applyMove('L');
|
||||
compareCubes(cube1, cube2, "Left Layer CW matches L");
|
||||
|
||||
// Test 4: Right Layer (x=1) CW vs R
|
||||
cube1.reset();
|
||||
cube2.reset();
|
||||
console.log('Testing Right Layer CW vs R...');
|
||||
cube1.rotateLayer('x', 1, 1); // Right CW
|
||||
cube2.applyMove('R');
|
||||
compareCubes(cube1, cube2, "Right Layer CW matches R");
|
||||
|
||||
// Test 5: Front Layer (z=1) CW vs F
|
||||
cube1.reset();
|
||||
cube2.reset();
|
||||
console.log('Testing Front Layer CW vs F...');
|
||||
cube1.rotateLayer('z', 1, 1); // Front CW
|
||||
cube2.applyMove('F');
|
||||
compareCubes(cube1, cube2, "Front Layer CW matches F");
|
||||
|
||||
// Test 6: Back Layer (z=-1) CW vs B
|
||||
cube1.reset();
|
||||
cube2.reset();
|
||||
console.log('Testing Back Layer CW vs B...');
|
||||
cube1.rotateLayer('z', -1, -1); // Back CW (CW around -Z is CCW around Z)
|
||||
cube2.applyMove('B');
|
||||
compareCubes(cube1, cube2, "Back Layer CW matches B");
|
||||
|
||||
12
test/test_rubiks_import.js
Normal file
12
test/test_rubiks_import.js
Normal file
@@ -0,0 +1,12 @@
|
||||
|
||||
import { State } from 'rubiks-js/src/state/index.js';
|
||||
|
||||
console.log('State imported successfully');
|
||||
const state = new State(true);
|
||||
console.log('State instantiated');
|
||||
|
||||
state.applyTurn('R');
|
||||
console.log('Applied turn R');
|
||||
|
||||
const encoded = state.encode();
|
||||
console.log('Encoded state:', encoded);
|
||||
Reference in New Issue
Block a user