Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save shricodev/e4c12edb3384323be43f1196522eb6f2 to your computer and use it in GitHub Desktop.

Select an option

Save shricodev/e4c12edb3384323be43f1196522eb6f2 to your computer and use it in GitHub Desktop.
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Rubik's Cube Visualizer and Solver</title>
<style>
body {
margin: 0;
overflow: hidden;
font-family: Arial, sans-serif;
}
#canvas {
display: block;
}
#controls {
position: absolute;
top: 20px;
left: 20px;
z-index: 100;
}
button {
background-color: #4caf50;
border: none;
color: white;
padding: 10px 20px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
margin: 4px 2px;
cursor: pointer;
border-radius: 4px;
}
button:hover {
background-color: #45a049;
}
</style>
</head>
<body>
<div id="controls">
<button id="scrambleBtn">Scramble</button>
<button id="solveBtn">Solve</button>
</div>
<canvas id="canvas"></canvas>
<script src="https://cdn.jsdelivr.net/npm/three@0.132.2/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/controls/OrbitControls.js"></script>
<script src="cube.js"></script>
</body>
</html>
// Global variables
let scene, camera, renderer, controls;
let cube, cubeGroup;
let isAnimating = false;
let moveQueue = [];
const ANIMATION_DURATION = 500; // ms
const COLORS = {
UP: 0xff0000, // Red
DOWN: 0x009900, // Green
LEFT: 0xffff00, // Yellow
RIGHT: 0x8b4513, // Brown
FRONT: 0x800080, // Purple
BACK: 0xff9900, // Orange
INTERNAL: 0x333333, // Dark gray for internal faces
};
// Initializing the scene
function init() {
// Create scene
scene = new THREE.Scene();
scene.background = new THREE.Color(0x222222);
// Create camera
camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000,
);
camera.position.set(5, 5, 7);
// Create renderer
renderer = new THREE.WebGLRenderer({
canvas: document.getElementById("canvas"),
antialias: true,
});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
// Add orbit controls for mouse interaction
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.1;
// Add lighting
const ambientLight = new THREE.AmbientLight(0xffffff, 0.7);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(10, 20, 15);
scene.add(directionalLight);
// Create the Rubik's Cube
createCube();
// Event Listeners
document
.getElementById("scrambleBtn")
.addEventListener("click", scrambleCube);
document.getElementById("solveBtn").addEventListener("click", solveCube);
window.addEventListener("resize", onWindowResize);
// Start animation loop
animate();
}
// Create the Rubik's Cube
function createCube() {
cubeGroup = new THREE.Group();
scene.add(cubeGroup);
// Cubelet dimensions
const size = 1;
const spacing = 0.05;
const totalSize = 3 * size + 2 * spacing;
// Cubelet positions
cube = [];
// Create 3x3x3 cubelets
for (let x = -1; x <= 1; x++) {
for (let y = -1; y <= 1; y++) {
for (let z = -1; z <= 1; z++) {
const position = new THREE.Vector3(
x * (size + spacing),
y * (size + spacing),
z * (size + spacing),
);
// Create materials for each face
const materials = Array(6)
.fill()
.map(() => new THREE.MeshLambertMaterial({ color: COLORS.INTERNAL }));
// Assign colors to the outer faces
if (y === 1) materials[0].color.set(COLORS.UP); // Top face
if (y === -1) materials[1].color.set(COLORS.DOWN); // Bottom face
if (z === 1) materials[2].color.set(COLORS.FRONT); // Front face
if (z === -1) materials[3].color.set(COLORS.BACK); // Back face
if (x === 1) materials[4].color.set(COLORS.RIGHT); // Right face
if (x === -1) materials[5].color.set(COLORS.LEFT); // Left face
// Create cubelet
const geometry = new THREE.BoxGeometry(size, size, size);
const cubelet = new THREE.Mesh(geometry, materials);
cubelet.position.copy(position);
cubelet.userData = {
x,
y,
z,
originalX: x,
originalY: y,
originalZ: z,
};
cubeGroup.add(cubelet);
cube.push(cubelet);
}
}
}
}
// Move definitions for the cube
const MOVES = {
U: { axis: "y", layer: 1, angle: Math.PI / 2 },
U_: { axis: "y", layer: 1, angle: -Math.PI / 2 },
D: { axis: "y", layer: -1, angle: -Math.PI / 2 },
D_: { axis: "y", layer: -1, angle: Math.PI / 2 },
R: { axis: "x", layer: 1, angle: -Math.PI / 2 },
R_: { axis: "x", layer: 1, angle: Math.PI / 2 },
L: { axis: "x", layer: -1, angle: Math.PI / 2 },
L_: { axis: "x", layer: -1, angle: -Math.PI / 2 },
F: { axis: "z", layer: 1, angle: -Math.PI / 2 },
F_: { axis: "z", layer: 1, angle: Math.PI / 2 },
B: { axis: "z", layer: -1, angle: Math.PI / 2 },
B_: { axis: "z", layer: -1, angle: -Math.PI / 2 },
};
// Perform a move on the cube
function performMove(moveName, callback) {
if (isAnimating) {
moveQueue.push({ move: moveName, callback });
return;
}
isAnimating = true;
const move = MOVES[moveName];
if (!move) {
console.error("Unknown move:", moveName);
isAnimating = false;
if (callback) callback();
return;
}
// Select cubelets in the layer
const moveGroup = new THREE.Group();
scene.add(moveGroup);
// Find all cubelets that belong to the layer we're rotating
const axis = move.axis;
const layerValue = move.layer;
cube.forEach((cubelet) => {
if (cubelet.userData[axis] === layerValue) {
// Temporarily attach to the move group
const worldPosition = new THREE.Vector3();
cubelet.getWorldPosition(worldPosition);
const worldQuaternion = new THREE.Quaternion();
cubelet.getWorldQuaternion(worldQuaternion);
const worldScale = new THREE.Vector3();
cubelet.getWorldScale(worldScale);
cubeGroup.remove(cubelet);
moveGroup.add(cubelet);
cubelet.position.copy(worldPosition);
cubelet.quaternion.copy(worldQuaternion);
cubelet.scale.copy(worldScale);
}
});
// Animate the rotation
const startTime = Date.now();
const endTime = startTime + ANIMATION_DURATION;
const targetAngle = move.angle;
function animateRotation() {
const now = Date.now();
let t = (now - startTime) / ANIMATION_DURATION;
if (t >= 1) {
t = 1;
moveGroup.rotation[axis] = targetAngle;
// Update the position data for each cubelet
moveGroup.updateMatrixWorld(true);
while (moveGroup.children.length) {
const cubelet = moveGroup.children[0];
// Get world position
const worldPosition = new THREE.Vector3();
cubelet.getWorldPosition(worldPosition);
const worldQuaternion = new THREE.Quaternion();
cubelet.getWorldQuaternion(worldQuaternion);
const worldScale = new THREE.Vector3();
cubelet.getWorldScale(worldScale);
// Move back to cubeGroup
moveGroup.remove(cubelet);
cubeGroup.add(cubelet);
cubelet.position.copy(worldPosition);
cubelet.quaternion.copy(worldQuaternion);
cubelet.scale.copy(worldScale);
// Update cubelet userData with new grid coordinates
cubelet.userData.x = Math.round(cubelet.position.x / 1.05);
cubelet.userData.y = Math.round(cubelet.position.y / 1.05);
cubelet.userData.z = Math.round(cubelet.position.z / 1.05);
}
scene.remove(moveGroup);
isAnimating = false;
if (callback) callback();
// Check for queued moves
if (moveQueue.length > 0) {
const nextMove = moveQueue.shift();
performMove(nextMove.move, nextMove.callback);
}
return;
}
// Smooth easing
t = t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
moveGroup.rotation[axis] = t * targetAngle;
requestAnimationFrame(animateRotation);
}
animateRotation();
}
// Scramble the cube with random moves
function scrambleCube() {
if (isAnimating) return;
const moveNames = Object.keys(MOVES);
const numMoves = 20;
const moves = [];
for (let i = 0; i < numMoves; i++) {
const randomMove = moveNames[Math.floor(Math.random() * moveNames.length)];
moves.push(randomMove);
}
let moveIndex = 0;
function performNextMove() {
if (moveIndex >= moves.length) return;
performMove(moves[moveIndex], () => {
moveIndex++;
performNextMove();
});
}
performNextMove();
}
// Solve the cube (for simplicity, we'll just apply a sequence of moves)
function solveCube() {
if (isAnimating) return;
// For demonstration, we'll apply a sequence that should show some interesting movement
// In a real solver, this would be generated based on the current state
const solutionMoves = [
"R",
"U",
"R_",
"U_",
"R_",
"F",
"R",
"F_",
"U",
"R",
"U_",
"L_",
];
let moveIndex = 0;
function performNextMove() {
if (moveIndex >= solutionMoves.length) return;
performMove(solutionMoves[moveIndex], () => {
moveIndex++;
performNextMove();
});
}
performNextMove();
}
// Animation loop
function animate() {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
}
// Handle window resizing
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
// Initialize the application
init();
@shricodev
Copy link
Copy Markdown
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment