slithersphere-3d / index.html
sidprak's picture
I think you messed up the controls take another look
39daba9 verified
<!DOCTYPE html>
<html lang="en" class="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SlitherSphere 3D</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/feather-icons"></script>
<script src="https://cdn.jsdelivr.net/npm/vanta@latest/dist/vanta.globe.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');
body {
font-family: 'Press Start 2P', cursive;
overflow: hidden;
touch-action: none;
}
#game-container {
perspective: 1000px;
transform-style: preserve-3d;
}
.snake-segment {
box-shadow: 0 0 15px rgba(110, 231, 183, 0.7);
transition: all 0.2s ease-out;
}
.food {
animation: pulse 1.5s infinite alternate;
box-shadow: 0 0 20px rgba(236, 72, 153, 0.8);
}
@keyframes pulse {
0% { transform: scale(1) translateZ(0); }
100% { transform: scale(1.2) translateZ(10px); }
}
#vanta-bg {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -1;
opacity: 0.3;
}
</style>
</head>
<body class="bg-gray-900 text-teal-300 min-h-screen flex flex-col items-center justify-center">
<div id="vanta-bg"></div>
<div class="text-center mb-8">
<h1 class="text-4xl md:text-6xl font-bold mb-4 text-transparent bg-clip-text bg-gradient-to-r from-teal-400 to-pink-500">
SLITHERSPHERE 3D
</h1>
<div class="flex justify-center gap-8 text-xl">
<div>
<p class="text-pink-400">SCORE</p>
<p id="score" class="text-teal-300">0</p>
</div>
<div>
<p class="text-pink-400">HIGH SCORE</p>
<p id="high-score" class="text-teal-300">0</p>
</div>
</div>
</div>
<div id="game-container" class="relative w-full max-w-2xl aspect-square bg-gray-800 rounded-xl overflow-hidden shadow-2xl shadow-black/50">
<canvas id="game-canvas" class="w-full h-full"></canvas>
<div id="game-over" class="absolute inset-0 bg-black/80 flex flex-col items-center justify-center hidden">
<h2 class="text-4xl text-pink-500 mb-6">GAME OVER</h2>
<p class="text-xl text-teal-300 mb-8">Score: <span id="final-score">0</span></p>
<button id="restart-btn" class="px-8 py-3 bg-gradient-to-r from-teal-500 to-pink-500 rounded-full font-bold hover:scale-105 transition-transform">
PLAY AGAIN
</button>
</div>
<div id="start-screen" class="absolute inset-0 bg-black/80 flex flex-col items-center justify-center">
<h2 class="text-4xl text-teal-300 mb-6">SLITHERSPHERE</h2>
<p class="text-pink-400 mb-8 text-center px-4">Use arrow keys or swipe to control the snake</p>
<button id="start-btn" class="px-8 py-3 bg-gradient-to-r from-teal-500 to-pink-500 rounded-full font-bold hover:scale-105 transition-transform">
START GAME
</button>
</div>
</div>
<div class="mt-8 flex gap-4">
<button id="sound-btn" class="p-3 bg-gray-800 rounded-full hover:bg-gray-700 transition-colors">
<i data-feather="volume-2" class="text-teal-400"></i>
</button>
<button id="fullscreen-btn" class="p-3 bg-gray-800 rounded-full hover:bg-gray-700 transition-colors">
<i data-feather="maximize" class="text-teal-400"></i>
</button>
</div>
<audio id="eat-sound" src="https://assets.mixkit.co/sfx/preview/mixkit-arcade-game-jump-coin-216.mp3" preload="auto"></audio>
<audio id="gameover-sound" src="https://assets.mixkit.co/sfx/preview/mixkit-retro-arcade-lose-2027.mp3" preload="auto"></audio>
<audio id="bg-music" loop src="https://assets.mixkit.co/music/preview/mixkit-game-show-suspense-waiting-668.mp3" preload="auto"></audio>
<script>
// Initialize Vanta.js background
VANTA.GLOBE({
el: "#vanta-bg",
mouseControls: true,
touchControls: true,
gyroControls: false,
minHeight: 200.00,
minWidth: 200.00,
scale: 1.00,
scaleMobile: 1.00,
color: 0x3b82f6,
backgroundColor: 0x111827,
size: 0.8
});
// Game variables
const canvas = document.getElementById('game-canvas');
const ctx = canvas.getContext('2d');
const scoreElement = document.getElementById('score');
const highScoreElement = document.getElementById('high-score');
const finalScoreElement = document.getElementById('final-score');
const gameOverScreen = document.getElementById('game-over');
const startScreen = document.getElementById('start-screen');
const startBtn = document.getElementById('start-btn');
const restartBtn = document.getElementById('restart-btn');
const soundBtn = document.getElementById('sound-btn');
const fullscreenBtn = document.getElementById('fullscreen-btn');
const eatSound = document.getElementById('eat-sound');
const gameoverSound = document.getElementById('gameover-sound');
const bgMusic = document.getElementById('bg-music');
let gridSize = 20;
let tileSize;
let snake = [];
let food = {};
let direction = 'right';
let nextDirection = 'right';
let gameSpeed = 150;
let score = 0;
let highScore = localStorage.getItem('highScore') || 0;
let gameInterval;
let isPaused = false;
let isSoundOn = true;
let touchStartX = 0;
let touchStartY = 0;
// Set canvas size
function resizeCanvas() {
const container = document.getElementById('game-container');
const size = Math.min(container.clientWidth, container.clientHeight);
canvas.width = size;
canvas.height = size;
tileSize = canvas.width / gridSize;
}
// Initialize game
function initGame() {
resizeCanvas();
// Create snake
snake = [];
for (let i = 3; i >= 0; i--) {
snake.push({ x: i, y: 0 });
}
// Create food
generateFood();
// Reset score
score = 0;
scoreElement.textContent = score;
highScoreElement.textContent = highScore;
// Reset direction
direction = 'right';
nextDirection = 'right';
// Hide game over screen
gameOverScreen.classList.add('hidden');
// Start game loop
if (gameInterval) clearInterval(gameInterval);
gameInterval = setInterval(gameLoop, gameSpeed);
// Play background music
if (isSoundOn) {
bgMusic.currentTime = 0;
bgMusic.play().catch(e => console.log("Autoplay prevented:", e));
}
}
// Generate food at random position
function generateFood() {
let validPosition = false;
while (!validPosition) {
food = {
x: Math.floor(Math.random() * gridSize),
y: Math.floor(Math.random() * gridSize)
};
validPosition = true;
// Check if food is on snake
for (let segment of snake) {
if (segment.x === food.x && segment.y === food.y) {
validPosition = false;
break;
}
}
}
}
// Main game loop
function gameLoop() {
if (isPaused) return;
// Move snake
direction = nextDirection;
let head = { ...snake[0] };
switch (direction) {
case 'up':
head.y--;
break;
case 'down':
head.y++;
break;
case 'left':
head.x--;
break;
case 'right':
head.x++;
break;
}
// Check wall collision
if (head.x < 0 || head.x >= gridSize || head.y < 0 || head.y >= gridSize) {
gameOver();
return;
}
// Check self collision
for (let segment of snake) {
if (segment.x === head.x && segment.y === head.y) {
gameOver();
return;
}
}
// Add new head
snake.unshift(head);
// Check food collision
if (head.x === food.x && head.y === food.y) {
score++;
scoreElement.textContent = score;
if (score > highScore) {
highScore = score;
highScoreElement.textContent = highScore;
localStorage.setItem('highScore', highScore);
}
if (isSoundOn) eatSound.play();
// Increase speed every 5 points
if (score % 5 === 0) {
clearInterval(gameInterval);
gameSpeed = Math.max(50, gameSpeed - 10);
gameInterval = setInterval(gameLoop, gameSpeed);
}
generateFood();
} else {
// Remove tail if no food eaten
snake.pop();
}
// Draw game
drawGame();
}
// Draw game elements
function drawGame() {
// Clear canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw grid
ctx.strokeStyle = 'rgba(75, 85, 99, 0.3)';
ctx.lineWidth = 0.5;
for (let i = 0; i < gridSize; i++) {
// Vertical lines
ctx.beginPath();
ctx.moveTo(i * tileSize, 0);
ctx.lineTo(i * tileSize, canvas.height);
ctx.stroke();
// Horizontal lines
ctx.beginPath();
ctx.moveTo(0, i * tileSize);
ctx.lineTo(canvas.width, i * tileSize);
ctx.stroke();
}
// Draw food with 3D effect
const foodX = food.x * tileSize + tileSize / 2;
const foodY = food.y * tileSize + tileSize / 2;
const foodRadius = tileSize * 0.4;
// Food glow
const foodGradient = ctx.createRadialGradient(
foodX, foodY, 0,
foodX, foodY, foodRadius * 1.5
);
foodGradient.addColorStop(0, 'rgba(236, 72, 153, 0.8)');
foodGradient.addColorStop(1, 'rgba(236, 72, 153, 0)');
ctx.fillStyle = foodGradient;
ctx.beginPath();
ctx.arc(foodX, foodY, foodRadius * 1.5, 0, Math.PI * 2);
ctx.fill();
// Food main
const foodMainGradient = ctx.createRadialGradient(
foodX, foodY - foodRadius * 0.2, 0,
foodX, foodY, foodRadius
);
foodMainGradient.addColorStop(0, '#ec4899');
foodMainGradient.addColorStop(1, '#be185d');
ctx.fillStyle = foodMainGradient;
ctx.beginPath();
ctx.arc(foodX, foodY, foodRadius, 0, Math.PI * 2);
ctx.fill();
// Food highlight
ctx.fillStyle = 'rgba(255, 255, 255, 0.3)';
ctx.beginPath();
ctx.arc(
foodX - foodRadius * 0.3,
foodY - foodRadius * 0.3,
foodRadius * 0.2,
0, Math.PI * 2
);
ctx.fill();
// Draw snake with 3D effect
for (let i = 0; i < snake.length; i++) {
const segment = snake[i];
const x = segment.x * tileSize;
const y = segment.y * tileSize;
const size = tileSize * 0.9;
const offset = (tileSize - size) / 2;
const isHead = i === 0;
// Snake segment shadow
ctx.fillStyle = 'rgba(0, 0, 0, 0.3)';
ctx.beginPath();
ctx.roundRect(
x + offset + 3,
y + offset + 3,
size,
size,
[size * 0.2]
);
ctx.fill();
// Snake segment main
const segmentGradient = ctx.createLinearGradient(
x, y,
x + tileSize, y + tileSize
);
if (isHead) {
segmentGradient.addColorStop(0, '#2dd4bf');
segmentGradient.addColorStop(1, '#0d9488');
} else {
const intensity = 1 - (i / snake.length) * 0.5;
segmentGradient.addColorStop(0, `rgba(45, 212, 191, ${intensity})`);
segmentGradient.addColorStop(1, `rgba(13, 148, 136, ${intensity})`);
}
ctx.fillStyle = segmentGradient;
ctx.beginPath();
ctx.roundRect(
x + offset,
y + offset,
size,
size,
[size * 0.2]
);
ctx.fill();
// Snake segment highlight
if (isHead) {
ctx.fillStyle = 'rgba(255, 255, 255, 0.3)';
ctx.beginPath();
ctx.arc(
x + offset + size * 0.3,
y + offset + size * 0.3,
size * 0.15,
0, Math.PI * 2
);
ctx.fill();
// Eyes
const eyeOffsetX = direction === 'left' ? -0.2 :
direction === 'right' ? 0.2 : 0;
const eyeOffsetY = direction === 'up' ? -0.2 :
direction === 'down' ? 0.2 : 0;
// Left eye
ctx.fillStyle = 'white';
ctx.beginPath();
ctx.arc(
x + offset + size * 0.5 + eyeOffsetX * size * 0.3,
y + offset + size * 0.4 + eyeOffsetY * size * 0.3,
size * 0.08,
0, Math.PI * 2
);
ctx.fill();
// Right eye
ctx.beginPath();
ctx.arc(
x + offset + size * 0.7 + eyeOffsetX * size * 0.3,
y + offset + size * 0.4 + eyeOffsetY * size * 0.3,
size * 0.08,
0, Math.PI * 2
);
ctx.fill();
// Pupils
ctx.fillStyle = 'black';
ctx.beginPath();
ctx.arc(
x + offset + size * 0.5 + eyeOffsetX * size * 0.35,
y + offset + size * 0.4 + eyeOffsetY * size * 0.35,
size * 0.04,
0, Math.PI * 2
);
ctx.fill();
ctx.beginPath();
ctx.arc(
x + offset + size * 0.7 + eyeOffsetX * size * 0.35,
y + offset + size * 0.4 + eyeOffsetY * size * 0.35,
size * 0.04,
0, Math.PI * 2
);
ctx.fill();
}
}
}
// Game over
function gameOver() {
clearInterval(gameInterval);
gameOverScreen.classList.remove('hidden');
finalScoreElement.textContent = score;
if (isSoundOn) {
bgMusic.pause();
gameoverSound.play();
}
}
// Event listeners
window.addEventListener('resize', () => {
resizeCanvas();
drawGame();
});
document.addEventListener('keydown', (e) => {
switch (e.key) {
case 'ArrowUp':
if (direction !== 'down') nextDirection = 'up';
break;
case 'ArrowLeft':
if (direction !== 'right') nextDirection = 'left';
break;
case 'ArrowRight':
if (direction !== 'left') nextDirection = 'right';
break;
case 'ArrowDown':
if (direction !== 'up') nextDirection = 'down';
break;
case ' ':
isPaused = !isPaused;
if (isPaused) {
bgMusic.pause();
} else if (isSoundOn) {
bgMusic.play().catch(e => console.log("Autoplay prevented:", e));
}
break;
}
});
// Touch controls for mobile
canvas.addEventListener('touchstart', (e) => {
touchStartX = e.touches[0].clientX;
touchStartY = e.touches[0].clientY;
}, { passive: false });
canvas.addEventListener('touchmove', (e) => {
if (!touchStartX || !touchStartY) return;
const touchEndX = e.touches[0].clientX;
const touchEndY = e.touches[0].clientY;
const diffX = touchStartX - touchEndX;
const diffY = touchStartY - touchEndY;
if (Math.abs(diffX) > Math.abs(diffY)) {
// Horizontal swipe
if (diffX > 0 && direction !== 'right') {
nextDirection = 'left';
} else if (diffX < 0 && direction !== 'left') {
nextDirection = 'right';
}
} else {
// Vertical swipe
if (diffY > 0 && direction !== 'down') {
nextDirection = 'up';
} else if (diffY < 0 && direction !== 'up') {
nextDirection = 'down';
}
}
touchStartX = 0;
touchStartY = 0;
e.preventDefault();
}, { passive: false });
// Button event listeners
startBtn.addEventListener('click', () => {
startScreen.classList.add('hidden');
initGame();
});
restartBtn.addEventListener('click', () => {
gameOverScreen.classList.add('hidden');
initGame();
});
soundBtn.addEventListener('click', () => {
isSoundOn = !isSoundOn;
if (isSoundOn) {
soundBtn.innerHTML = '<i data-feather="volume-2"></i>';
bgMusic.play().catch(e => console.log("Autoplay prevented:", e));
} else {
soundBtn.innerHTML = '<i data-feather="volume-x"></i>';
bgMusic.pause();
}
feather.replace();
});
fullscreenBtn.addEventListener('click', () => {
if (!document.fullscreenElement) {
canvas.requestFullscreen().catch(err => {
alert(`Error attempting to enable fullscreen: ${err.message}`);
});
} else {
document.exitFullscreen();
}
});
// Initialize
window.addEventListener('load', () => {
resizeCanvas();
drawGame();
feather.replace();
// Try to autoplay music with user interaction
document.addEventListener('click', () => {
if (isSoundOn) {
bgMusic.play().catch(e => console.log("Autoplay prevented:", e));
}
}, { once: true });
});
</script>
</body>
</html>