3d-snake-game / index.html
enzostvs's picture
enzostvs HF staff
Update index.html
3ee26fa 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</title>
<style>
body {
margin: 0;
overflow: hidden;
}
#score {
position: absolute;
top: 10px;
left: 10px;
color: white;
font-family: Arial, sans-serif;
font-size: 20px;
}
#gameOver {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: white;
font-family: Arial, sans-serif;
font-size: 36px;
display: none;
}
#bonusActive {
position: absolute;
top: 40px;
left: 10px;
color: gold;
font-family: Arial, sans-serif;
font-size: 20px;
display: none;
}
</style>
</head>
<body>
<div id="score">Score: 0</div>
<div id="bonusActive">SPEED BOOST ACTIVE!</div>
<div id="gameOver">Game Over! Press Space to restart</div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/build/three.min.js"></script>
<script>
// Game variables
let scene, camera, renderer;
let snake = [],
food,
goldenFood;
let direction = { x: 1, y: 0, z: 0 };
let gridSize = 20;
let speed = 5; // Moves per second
let baseSpeed = 5; // Default speed
let lastTime = 0;
let score = 0;
let isGameOver = false;
let bonusActive = false;
let bonusEndTime = 0;
let goldenFoodChance = 0.2; // 20% chance
let bonusSpeedMultiplier = 1.5; // 50% faster
let bonusDuration = 5000; // 5 seconds
// Camera rotation variables
let cameraRotationSpeed = 0.0005; // Speed of rotation
let cameraRotationRadius = 40; // Distance from center
let cameraRotationAngle = 0; // Current angle
let cameraRotationActive = true; // Toggle rotation
let cameraHeight = 30; // Height of camera
// Initialize the game
function init() {
// Create scene
scene = new THREE.Scene();
scene.background = new THREE.Color(0x333333);
// Create camera
camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
// Set initial camera position
camera.position.set(0, cameraHeight, cameraRotationRadius);
camera.lookAt(0, 0, 0);
// Create renderer
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// Add lights
const ambientLight = new THREE.AmbientLight(0x404040);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(10, 20, 15);
scene.add(directionalLight);
// Add grid for reference
const gridHelper = new THREE.GridHelper(gridSize, gridSize);
scene.add(gridHelper);
// Create snake
createSnake();
// Create food
createFood();
// Add event listeners
document.addEventListener("keydown", handleKeyDown);
window.addEventListener("resize", onWindowResize);
// Start animation loop
animate();
}
// Create initial snake
function createSnake() {
// Start with a single segment
const geometry = new THREE.BoxGeometry(0.9, 0.9, 0.9);
const material = new THREE.MeshPhongMaterial({ color: 0x00ff00 });
const segment = new THREE.Mesh(geometry, material);
segment.position.set(0, 0.5, 0);
scene.add(segment);
snake.push({ mesh: segment, position: { x: 0, y: 0, z: 0 } });
}
// Create food
function createFood() {
// Remove old food if exists
if (food) scene.remove(food.mesh);
const geometry = new THREE.SphereGeometry(0.5);
const material = new THREE.MeshPhongMaterial({ color: 0xff0000 });
const mesh = new THREE.Mesh(geometry, material);
// Random position
let position = getRandomPosition();
// Make sure food doesn't spawn on snake
while (isPositionOccupied(position)) {
position = getRandomPosition();
}
mesh.position.set(position.x, 0.5, position.z);
scene.add(mesh);
food = { mesh: mesh, position: position };
// Random chance to create golden food
if (Math.random() < goldenFoodChance) {
setTimeout(createGoldenFood, Math.random() * 5000 + 2000);
}
}
// Create golden bonus food
function createGoldenFood() {
if (isGameOver || goldenFood) return;
const geometry = new THREE.SphereGeometry(0.6);
const material = new THREE.MeshPhongMaterial({
color: 0xffd700,
emissive: 0xffd700,
emissiveIntensity: 0.5,
});
const mesh = new THREE.Mesh(geometry, material);
// Random position
let position = getRandomPosition();
// Make sure golden food doesn't spawn on snake or regular food
while (
isPositionOccupied(position) ||
(food &&
position.x === food.position.x &&
position.z === food.position.z)
) {
position = getRandomPosition();
}
mesh.position.set(position.x, 0.5, position.z);
scene.add(mesh);
goldenFood = { mesh: mesh, position: position };
// Make golden food disappear after some time
setTimeout(() => {
if (goldenFood) {
scene.remove(goldenFood.mesh);
goldenFood = null;
}
}, 5000);
}
// Get random grid position
function getRandomPosition() {
const halfGrid = Math.floor(gridSize / 2);
return {
x: Math.floor(Math.random() * gridSize) - halfGrid,
y: 0,
z: Math.floor(Math.random() * gridSize) - halfGrid,
};
}
// Check if position is occupied by snake
function isPositionOccupied(position) {
return snake.some(
(segment) =>
segment.position.x === position.x &&
segment.position.z === position.z
);
}
// Move the snake
function moveSnake() {
if (isGameOver) return;
// Calculate new head position
const head = snake[0];
const newPosition = {
x: head.position.x + direction.x,
y: 0,
z: head.position.z + direction.z,
};
// Handle wrap-around at boundaries
const halfGrid = Math.floor(gridSize / 2);
if (newPosition.x < -halfGrid) {
newPosition.x = halfGrid - 1;
} else if (newPosition.x >= halfGrid) {
newPosition.x = -halfGrid;
}
if (newPosition.z < -halfGrid) {
newPosition.z = halfGrid - 1;
} else if (newPosition.z >= halfGrid) {
newPosition.z = -halfGrid;
}
// Check self-collision
if (isPositionOccupied(newPosition)) {
gameOver();
return;
}
// Check if food is eaten
if (
newPosition.x === food.position.x &&
newPosition.z === food.position.z
) {
// Add new head
const geometry = new THREE.BoxGeometry(0.9, 0.9, 0.9);
const material = new THREE.MeshPhongMaterial({ color: 0x00ff00 });
const segment = new THREE.Mesh(geometry, material);
segment.position.set(newPosition.x, 0.5, newPosition.z);
scene.add(segment);
// Add to beginning of snake array
snake.unshift({ mesh: segment, position: { ...newPosition } });
// Create new food
createFood();
// Update score
score += 10;
document.getElementById("score").textContent = "Score: " + score;
}
// Check if golden food is eaten
else if (
goldenFood &&
newPosition.x === goldenFood.position.x &&
newPosition.z === goldenFood.position.z
) {
// Add new head
const geometry = new THREE.BoxGeometry(0.9, 0.9, 0.9);
const material = new THREE.MeshPhongMaterial({ color: 0x00ff00 });
const segment = new THREE.Mesh(geometry, material);
segment.position.set(newPosition.x, 0.5, newPosition.z);
scene.add(segment);
// Add to beginning of snake array
snake.unshift({ mesh: segment, position: { ...newPosition } });
// Remove golden food
scene.remove(goldenFood.mesh);
goldenFood = null;
// Update score (higher bonus for golden food)
score += 25;
document.getElementById("score").textContent = "Score: " + score;
// Activate speed boost
bonusActive = true;
speed = baseSpeed * bonusSpeedMultiplier;
bonusEndTime = Date.now() + bonusDuration;
// Show bonus message
document.getElementById("bonusActive").style.display = "block";
// Set timer to end bonus
setTimeout(() => {
bonusActive = false;
speed = baseSpeed;
document.getElementById("bonusActive").style.display = "none";
}, bonusDuration);
} else {
// Move body segments
for (let i = snake.length - 1; i > 0; i--) {
snake[i].position = { ...snake[i - 1].position };
snake[i].mesh.position.set(
snake[i].position.x,
0.5,
snake[i].position.z
);
}
// Move head
head.position = { ...newPosition };
head.mesh.position.set(newPosition.x, 0.5, newPosition.z);
}
}
// Handle keyboard controls
function handleKeyDown(event) {
if (isGameOver) {
// Restart game with space
if (event.code === "Space") {
restartGame();
}
return;
}
switch (event.code) {
case "ArrowUp":
case "KeyW":
if (direction.z !== 1) direction = { x: 0, y: 0, z: -1 };
break;
case "ArrowDown":
case "KeyS":
if (direction.z !== -1) direction = { x: 0, y: 0, z: 1 };
break;
case "ArrowLeft":
case "KeyA":
if (direction.x !== 1) direction = { x: -1, y: 0, z: 0 };
break;
case "ArrowRight":
case "KeyD":
if (direction.x !== -1) direction = { x: 1, y: 0, z: 0 };
break;
case "KeyR": // Toggle camera rotation with 'R' key
cameraRotationActive = !cameraRotationActive;
if (!cameraRotationActive) {
// Reset camera to fixed position when disabled
camera.position.set(0, cameraHeight, cameraRotationRadius);
camera.lookAt(0, 0, 0);
}
break;
}
}
// Handle game over
function gameOver() {
isGameOver = true;
document.getElementById("gameOver").style.display = "block";
}
// Restart the game
function restartGame() {
// Remove all snake segments
for (let segment of snake) {
scene.remove(segment.mesh);
}
// Remove golden food if it exists
if (goldenFood) {
scene.remove(goldenFood.mesh);
goldenFood = null;
}
// Reset game state
snake = [];
direction = { x: 1, y: 0, z: 0 };
score = 0;
isGameOver = false;
bonusActive = false;
speed = baseSpeed;
// Hide game over and bonus messages
document.getElementById("gameOver").style.display = "none";
document.getElementById("bonusActive").style.display = "none";
document.getElementById("score").textContent = "Score: 0";
// Create new snake and food
createSnake();
createFood();
}
// Handle window resize
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
// Animation loop
function animate(time) {
requestAnimationFrame(animate);
// Move snake at consistent speed
if (time - lastTime > 1000 / speed) {
moveSnake();
lastTime = time;
}
// Rotate camera around the scene
if (cameraRotationActive) {
cameraRotationAngle += cameraRotationSpeed * (bonusActive ? 2 : 1); // Rotate faster during bonus
// Update camera position in a circular path
camera.position.x =
Math.sin(cameraRotationAngle) * cameraRotationRadius;
camera.position.z =
Math.cos(cameraRotationAngle) * cameraRotationRadius;
camera.position.y = cameraHeight;
// Always look at the center
camera.lookAt(0, 0, 0);
}
renderer.render(scene, camera);
}
// Start the game
init();
</script>
</body>
</html>