spellbound-word-wizardry / snake-game.html
Dembo's picture
build a 3d snake game
563274e verified
<!DOCTYPE html>
<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>