Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>3D Snake Game - SpellBound</title> | |
| <link rel="icon" type="image/x-icon" href="/static/favicon.ico"> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> | |
| <style> | |
| body { margin: 0; padding: 0; background: #1a202c; } | |
| #game-container { position: relative; width: 100vw; height: 100vh; } | |
| #ui-overlay { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| pointer-events: none; | |
| z-index: 10; | |
| } | |
| .ui-element { pointer-events: auto; } | |
| #score-display { | |
| position: absolute; | |
| top: 20px; | |
| left: 20px; | |
| color: white; | |
| font-size: 24px; | |
| background: rgba(0,0,0,0.5); | |
| padding: 10px 20px; | |
| border-radius: 10px; | |
| } | |
| #game-controls { | |
| position: absolute; | |
| bottom: 20px; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| display: flex; | |
| gap: 10px; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div id="game-container"> | |
| <div id="ui-overlay"> | |
| <div id="score-display" class="ui-element">Score: 0</div> | |
| <div id="game-controls" class="ui-element"> | |
| <button id="start-btn" class="bg-green-500 hover:bg-green-600 text-white font-bold py-3 px-6 rounded-lg transition-colors"> | |
| Start Game | |
| </button> | |
| <button id="pause-btn" class="bg-yellow-500 hover:bg-yellow-600 text-white font-bold py-3 px-6 rounded-lg transition-colors"> | |
| Pause | |
| </button> | |
| <button id="reset-btn" class="bg-red-500 hover:bg-red-600 text-white font-bold py-3 px-6 rounded-lg transition-colors"> | |
| Reset | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // Three.js 3D Snake Game | |
| let scene, camera, renderer; | |
| let snake = []; | |
| let food; | |
| let direction = { x: 1, y: 0, z: 0 }; | |
| let gameSpeed = 150; | |
| let score = 0; | |
| let gameRunning = false; | |
| let gameInterval; | |
| // Initialize Three.js | |
| function init() { | |
| // Scene | |
| scene = new THREE.Scene(); | |
| scene.background = new THREE.Color(0x1a202c); | |
| // Camera | |
| camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); | |
| camera.position.set(15, 15, 15); | |
| camera.lookAt(0, 0, 0); | |
| // Renderer | |
| renderer = new THREE.WebGLRenderer({ antialias: true }); | |
| renderer.setSize(window.innerWidth, window.innerHeight); | |
| document.getElementById('game-container').appendChild(renderer.domElement); | |
| // Lighting | |
| const ambientLight = new THREE.AmbientLight(0xffffff, 0.6); | |
| scene.add(ambientLight); | |
| const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8); | |
| directionalLight.position.set(10, 20, 15); | |
| scene.add(directionalLight); | |
| // Grid helper | |
| const gridHelper = new THREE.GridHelper(30, 30); | |
| scene.add(gridHelper); | |
| // Create initial snake | |
| createSnake(); | |
| createFood(); | |
| // Event listeners | |
| window.addEventListener('resize', onWindowResize); | |
| document.addEventListener('keydown', handleKeyPress); | |
| // UI event listeners | |
| document.getElementById('start-btn').addEventListener('click', startGame); | |
| document.getElementById('pause-btn').addEventListener('click', pauseGame); | |
| document.getElementById('reset-btn').addEventListener('click', resetGame); | |
| animate(); | |
| } | |
| function createSnake() { | |
| // Clear existing snake | |
| snake.forEach(segment => scene.remove(segment)); | |
| snake = []; | |
| // Create initial snake segments | |
| for (let i = 0; i < 3; i++) { | |
| const geometry = new THREE.BoxGeometry(1, 1, 1); | |
| const material = new THREE.MeshPhongMaterial({ color: 0x00ff00 }); | |
| const segment = new THREE.Mesh(geometry, material); | |
| segment.position.set(-i, 0, 0); | |
| scene.add(segment); | |
| snake.push(segment); | |
| } | |
| } | |
| function createFood() { | |
| if (food) scene.remove(food); | |
| const geometry = new THREE.SphereGeometry(0.5, 16, 16); | |
| const material = new THREE.MeshPhongMaterial({ color: 0xff0000 }); | |
| food = new THREE.Mesh(geometry, material); | |
| // Random position within bounds (-14 to 14) | |
| food.position.set( | |
| Math.floor(Math.random() * 29) - 14, | |
| 0, | |
| Math.floor(Math.random() * 29) - 14 | |
| ); | |
| scene.add(food); | |
| } | |
| function moveSnake() { | |
| if (!gameRunning) return; | |
| // Calculate new head position | |
| const head = snake[0]; | |
| const newHead = head.clone(); | |
| newHead.position.x += direction.x; | |
| newHead.position.z += direction.z; | |
| // Check boundaries | |
| if (newHead.position.x > 14 || newHead.position.x < -14 || | |
| newHead.position.z > 14 || newHead.position.z < -14) { | |
| gameOver(); | |
| return; | |
| } | |
| // Check self collision | |
| for (let i = 0; i < snake.length; i++) { | |
| if (newHead.position.distanceTo(snake[i].position) < 0.5) { | |
| gameOver(); | |
| return; | |
| } | |
| } | |
| // Check food collision | |
| if (newHead.position.distanceTo(food.position) < 1) { | |
| score += 10; | |
| document.getElementById('score-display').textContent = `Score: ${score}`; | |
| // Increase speed slightly | |
| if (score % 50 === 0 && gameSpeed > 50) { | |
| gameSpeed -= 10; | |
| clearInterval(gameInterval); | |
| gameInterval = setInterval(moveSnake, gameSpeed); | |
| } | |
| createFood(); | |
| } else { | |
| // Remove tail if no food eaten | |
| const tail = snake.pop(); | |
| scene.remove(tail); | |
| } | |
| // Add new head | |
| scene.add(newHead); | |
| snake.unshift(newHead); | |
| } | |
| function handleKeyPress(event) { | |
| if (!gameRunning) return; | |
| switch(event.key) { | |
| case 'ArrowUp': | |
| if (direction.z !== 1) direction = { x: 0, y: 0, z: -1 }; | |
| break; | |
| case 'ArrowDown': | |
| if (direction.z !== -1) direction = { x: 0, y: 0, z: 1 }; | |
| break; | |
| case 'ArrowLeft': | |
| if (direction.x !== 1) direction = { x: -1, y: 0, z: 0 }; | |
| break; | |
| case 'ArrowRight': | |
| if (direction.x !== -1) direction = { x: 1, y: 0, z: 0 }; | |
| break; | |
| case 'w': | |
| case 'W': | |
| if (direction.z !== 1) direction = { x: 0, y: 0, z: -1 }; | |
| break; | |
| case 's': | |
| case 'S': | |
| if (direction.z !== -1) direction = { x: 0, y: 0, z: 1 }; | |
| break; | |
| case 'a': | |
| case 'A': | |
| if (direction.x !== 1) direction = { x: -1, y: 0, z: 0 }; | |
| break; | |
| case 'd': | |
| case 'D': | |
| if (direction.x !== -1) direction = { x: 1, y: 0, z: 0 }; | |
| break; | |
| } | |
| } | |
| function startGame() { | |
| if (!gameRunning) { | |
| gameRunning = true; | |
| gameInterval = setInterval(moveSnake, gameSpeed); | |
| } | |
| } | |
| function pauseGame() { | |
| gameRunning = !gameRunning; | |
| if (gameRunning) { | |
| gameInterval = setInterval(moveSnake, gameSpeed); | |
| } else { | |
| clearInterval(gameInterval); | |
| } | |
| } | |
| function resetGame() { | |
| clearInterval(gameInterval); | |
| gameRunning = false; | |
| score = 0; | |
| document.getElementById('score-display').textContent = `Score: ${score}`; | |
| direction = { x: 1, y: 0, z: 0 }; | |
| createSnake(); | |
| createFood(); | |
| } | |
| function gameOver() { | |
| gameRunning = false; | |
| clearInterval(gameInterval); | |
| alert(`Game Over! Your score: ${score}\nPress OK to play again!`); | |
| resetGame(); | |
| } | |
| function onWindowResize() { | |
| camera.aspect = window.innerWidth / window.innerHeight; | |
| camera.updateProjectionMatrix(); | |
| renderer.setSize(window.innerWidth, window.innerHeight); | |
| } | |
| function animate() { | |
| requestAnimationFrame(animate); | |
| // Rotate camera slowly for better 3D effect | |
| if (gameRunning) { | |
| camera.position.x = 15 * Math.cos(Date.now() * 0.0001); | |
| camera.position.z = 15 * Math.sin(Date.now() * 0.0001); | |
| camera.lookAt(0, 0, 0); | |
| } | |
| renderer.render(scene, camera); | |
| } | |
| // Initialize the game | |
| init(); | |
| feather.replace(); | |
| </script> | |
| </body> | |
| </html> |