UNPKG

rubiks-cube-mcp-server

Version:

MCP server for Rubik's Cube solving with real-time 3D visualization and MCP UI integration

260 lines (213 loc) 9.2 kB
// Three.js 3D 큐브 렌더링 클래스 class Cube3DRenderer { constructor() { this.scene = null; this.camera = null; this.renderer = null; this.cubeGroup = null; this.cubies = []; this.isRotating = false; // 색상 매핑 this.colorMap = { 'W': 0xffffff, // 흰색 'Y': 0xffff00, // 노랑 'R': 0xff0000, // 빨강 'O': 0xff8000, // 주황 'B': 0x0000ff, // 파랑 'G': 0x00ff00 // 초록 }; this.init(); } init() { this.setupScene(); this.setupCamera(); this.setupRenderer(); this.setupLighting(); this.createRubiksCube(); this.setupMouseControls(); this.animate(); document.getElementById('loading').style.display = 'none'; } setupScene() { this.scene = new THREE.Scene(); this.scene.background = new THREE.Color(0x000000); } setupCamera() { this.camera = new THREE.PerspectiveCamera(75, 800/600, 0.1, 1000); this.camera.position.set(5, 5, 5); this.camera.lookAt(0, 0, 0); } setupRenderer() { const canvas = document.getElementById('cubeCanvas'); this.renderer = new THREE.WebGLRenderer({ canvas: canvas, antialias: true }); this.renderer.setSize(800, 600); this.renderer.shadowMap.enabled = true; this.renderer.shadowMap.type = THREE.PCFSoftShadowMap; } setupLighting() { const ambientLight = new THREE.AmbientLight(0x404040, 0.6); this.scene.add(ambientLight); const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8); directionalLight.position.set(10, 10, 5); directionalLight.castShadow = true; this.scene.add(directionalLight); } createRubiksCube() { this.cubies = []; // 큐브 그룹 생성 this.cubeGroup = new THREE.Group(); this.scene.add(this.cubeGroup); const cubeSize = 0.95; const gap = 0.05; for (let x = -1; x <= 1; x++) { for (let y = -1; y <= 1; y++) { for (let z = -1; z <= 1; z++) { // 중앙 큐빅만 제외 (수정된 조건) if (x === 0 && y === 0 && z === 0) continue; const geometry = new THREE.BoxGeometry(cubeSize, cubeSize, cubeSize); // 각 면에 다른 색상의 재질 적용 const materials = [ new THREE.MeshLambertMaterial({ color: 0x333333 }), // right new THREE.MeshLambertMaterial({ color: 0x333333 }), // left new THREE.MeshLambertMaterial({ color: 0x333333 }), // top new THREE.MeshLambertMaterial({ color: 0x333333 }), // bottom new THREE.MeshLambertMaterial({ color: 0x333333 }), // front new THREE.MeshLambertMaterial({ color: 0x333333 }) // back ]; const cube = new THREE.Mesh(geometry, materials); cube.position.set( x * (cubeSize + gap), y * (cubeSize + gap), z * (cubeSize + gap) ); cube.castShadow = true; cube.receiveShadow = true; cube.userData = { x, y, z }; this.cubeGroup.add(cube); this.cubies.push(cube); } } } } updateCubeColors(state) { if (!state || !state.faces) return; const faces = state.faces; this.cubies.forEach(cube => { const { x, y, z } = cube.userData; const materials = cube.material; if (Array.isArray(materials)) { // Right face (x = 1) if (x === 1) { const faceIndex = this.getFaceIndex('right', y, z); const row = Math.floor(faceIndex / 3); const col = faceIndex % 3; if (faces.right && faces.right[row] && faces.right[row][col]) { materials[0].color.setHex(this.colorMap[faces.right[row][col]]); } } // Left face (x = -1) if (x === -1) { const faceIndex = this.getFaceIndex('left', y, z); const row = Math.floor(faceIndex / 3); const col = faceIndex % 3; if (faces.left && faces.left[row] && faces.left[row][col]) { materials[1].color.setHex(this.colorMap[faces.left[row][col]]); } } // Top face (y = 1) if (y === 1) { const faceIndex = this.getFaceIndex('top', x, z); const row = Math.floor(faceIndex / 3); const col = faceIndex % 3; if (faces.top && faces.top[row] && faces.top[row][col]) { materials[2].color.setHex(this.colorMap[faces.top[row][col]]); } } // Bottom face (y = -1) if (y === -1) { const faceIndex = this.getFaceIndex('bottom', x, z); const row = Math.floor(faceIndex / 3); const col = faceIndex % 3; if (faces.bottom && faces.bottom[row] && faces.bottom[row][col]) { materials[3].color.setHex(this.colorMap[faces.bottom[row][col]]); } } // Front face (z = 1) if (z === 1) { const faceIndex = this.getFaceIndex('front', x, y); const row = Math.floor(faceIndex / 3); const col = faceIndex % 3; if (faces.front && faces.front[row] && faces.front[row][col]) { materials[4].color.setHex(this.colorMap[faces.front[row][col]]); } } // Back face (z = -1) if (z === -1) { const faceIndex = this.getFaceIndex('back', x, y); const row = Math.floor(faceIndex / 3); const col = faceIndex % 3; if (faces.back && faces.back[row] && faces.back[row][col]) { materials[5].color.setHex(this.colorMap[faces.back[row][col]]); } } } }); } getFaceIndex(face, coord1, coord2) { const normalize = (val) => val + 1; // -1,0,1 -> 0,1,2 switch(face) { case 'top': case 'bottom': return normalize(-coord2) * 3 + normalize(coord1); // -z*3 + x (z축 뒤집기) case 'front': case 'back': return normalize(-coord2) * 3 + normalize(coord1); // -y*3 + x case 'right': case 'left': return normalize(-coord1) * 3 + normalize(coord2); // -y*3 + z default: return 0; } } setupMouseControls() { let isMouseDown = false; let mouseX = 0, mouseY = 0; const canvas = document.getElementById('cubeCanvas'); canvas.addEventListener('mousedown', (event) => { isMouseDown = true; mouseX = event.clientX; mouseY = event.clientY; }); canvas.addEventListener('mouseup', () => { isMouseDown = false; }); canvas.addEventListener('mousemove', (event) => { if (!isMouseDown) return; const deltaX = event.clientX - mouseX; const deltaY = event.clientY - mouseY; this.cubeGroup.rotation.y += deltaX * 0.01; this.cubeGroup.rotation.x += deltaY * 0.01; mouseX = event.clientX; mouseY = event.clientY; }); } animate() { requestAnimationFrame(() => this.animate()); // 자동 회전 (마우스 조작이 없을 때) if (!this.isRotating) { this.cubeGroup.rotation.y += 0.005; } this.renderer.render(this.scene, this.camera); } setRotating(rotating) { this.isRotating = rotating; } } // 전역 변수로 인스턴스 생성 let cubeRenderer = null; // 게임이 있을 때만 초기화 if (gameId) { cubeRenderer = new Cube3DRenderer(); } else { document.getElementById('loading').innerHTML = '<h2>No game session provided</h2><p>Please start a game through the MCP server</p>'; }