Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Rubik's Cube</title> | |
<style> | |
body { margin: 0; overflow: hidden; } | |
canvas { width: 100%; height: 100%; } | |
#controls { position: absolute; bottom: 10px; left: 10px; z-index: 10; } | |
</style> | |
</head> | |
<body> | |
<div id="controls"> | |
<button onclick="rotate('U')">U</button> | |
<button onclick="rotate('U\'')">U'</button> | |
<button onclick="rotate('D')">D</button> | |
<button onclick="rotate('D\'')">D'</button> | |
<button onclick="rotate('L')">L</button> | |
<button onclick="rotate('L\'')">L'</button> | |
<button onclick="rotate('R')">R</button> | |
<button onclick="rotate('R\'')">R'</button> | |
<button onclick="rotate('F')">F</button> | |
<button onclick="rotate('F\'')">F'</button> | |
<button onclick="rotate('B')">B</button> | |
<button onclick="rotate('B\'')">B'</button> | |
<button onclick="scramble()">Scramble</button> | |
<button onclick="unscramble()">Unscramble</button> | |
</div> | |
<script type="module"> | |
import * as THREE from 'https://cdn.skypack.dev/[email protected]'; | |
import { OrbitControls } from 'https://cdn.skypack.dev/[email protected]/examples/jsm/controls/OrbitControls.js'; | |
const scene = new THREE.Scene(); | |
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); | |
const renderer = new THREE.WebGLRenderer(); | |
renderer.setSize(window.innerWidth, window.innerHeight); | |
document.body.appendChild(renderer.domElement); | |
const controls = new OrbitControls(camera, renderer.domElement); | |
camera.position.z = 5; | |
const cubeSize = 0.9; | |
const colors = { | |
'U': 0xFFFFFF, 'D': 0xFFFF00, 'L': 0xFFA500, | |
'R': 0x00FF00, 'F': 0x0000FF, 'B': 0xFF00FF | |
}; | |
// Create cubies | |
const cubies = []; | |
for (let x = -1; x <= 1; x++) { | |
for (let y = -1; y <= 1; y++) { | |
for (let z = -1; z <= 1; z++) { | |
const geometry = new THREE.BoxGeometry(cubeSize, cubeSize, cubeSize); | |
const materials = [ | |
new THREE.MeshStandardMaterial({color: x === -1 ? colors['L'] : 0x000000}), // Left | |
new THREE.MeshStandardMaterial({color: x === 1 ? colors['R'] : 0x000000}), // Right | |
new THREE.MeshStandardMaterial({color: z === 1 ? colors['F'] : 0x000000}), // Front | |
new THREE.MeshStandardMaterial({color: z === -1 ? colors['B'] : 0x000000}), // Back | |
new THREE.MeshStandardMaterial({color: y === 1 ? colors['U'] : 0x000000}), // Up | |
new THREE.MeshStandardMaterial({color: y === -1 ? colors['D'] : 0x000000}) // Down | |
]; | |
const cubie = new THREE.Mesh(geometry, materials); | |
cubie.position.set(x * (cubeSize + 0.01), y * (cubeSize + 0.01), z * (cubeSize + 0.01)); | |
cubies.push({ mesh: cubie, originalPosition: cubie.position.clone(), originalRotation: cubie.rotation.clone() }); | |
scene.add(cubie); | |
} | |
} | |
} | |
// Light setup | |
const ambientLight = new THREE.AmbientLight(0x404040); | |
scene.add(ambientLight); | |
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5); | |
directionalLight.position.set(5, 5, 5); | |
scene.add(directionalLight); | |
// Cube rotation logic | |
const moveMap = { | |
'U': { axis: 'y', layer: 1, direction: 1, affectedCubies: cubies.filter(c => c.mesh.position.y === cubeSize) }, | |
'U\'': { axis: 'y', layer: 1, direction: -1, affectedCubies: cubies.filter(c => c.mesh.position.y === cubeSize) }, | |
// ... other moves similarly defined | |
}; | |
let movesHistory = []; | |
function rotate(move) { | |
const { axis, layer, direction, affectedCubies } = moveMap[move]; | |
const pivot = new THREE.Object3D(); | |
scene.add(pivot); | |
for (let cubie of affectedCubies) { | |
pivot.attach(cubie.mesh); | |
} | |
const targetRotation = Math.PI / 2 * direction; | |
let currentRotation = 0; | |
function animate() { | |
if (currentRotation < Math.abs(targetRotation)) { | |
currentRotation += 0.1 * Math.sign(targetRotation); | |
pivot.rotation[axis] = currentRotation; | |
requestAnimationFrame(animate); | |
} else { | |
pivot.rotation[axis] = targetRotation; | |
for (let cubie of affectedCubies) { | |
cubie.mesh.applyMatrix4(pivot.matrix); | |
scene.attach(cubie.mesh); | |
} | |
scene.remove(pivot); | |
movesHistory.push(move); | |
} | |
} | |
animate(); | |
} | |
function scramble() { | |
const moves = ['U', 'U\'', 'D', 'D\'', 'L', 'L\'', 'R', 'R\'', 'F', 'F\'', 'B', 'B\'']; | |
for (let i = 0; i < 20; i++) { | |
rotate(moves[Math.floor(Math.random() * moves.length)]); | |
} | |
} | |
let unscrambling = false; | |
function unscramble() { | |
if (!unscrambling && movesHistory.length > 0) { | |
unscrambling = true; | |
const interval = setInterval(() => { | |
if (movesHistory.length > 0) { | |
let lastMove = movesHistory.pop(); | |
let inverse = lastMove.replace("'", "") + (lastMove.includes('\'') ? '' : '\''); | |
rotate(inverse); | |
} else { | |
clearInterval(interval); | |
unscrambling = false; | |
} | |
}, 500); | |
} | |
} | |
function animate() { | |
requestAnimationFrame(animate); | |
renderer.render(scene, camera); | |
} | |
animate(); | |
</script> | |
</body> | |
</html> |