Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Snake Game - Reach 100!</title> | |
<script src="https://cdn.tailwindcss.com"></script> | |
<style> | |
#game-board { | |
background-color: #1a2e05; | |
border: 4px solid #3a5f0b; | |
box-shadow: 0 0 20px rgba(58, 95, 11, 0.5); | |
} | |
.snake-segment { | |
background-color: #8bc34a; | |
border: 1px solid #689f38; | |
border-radius: 20%; | |
} | |
.food { | |
background-color: #ff5252; | |
border: 2px solid #ff1744; | |
border-radius: 50%; | |
animation: pulse 0.5s infinite alternate; | |
} | |
@keyframes pulse { | |
from { transform: scale(0.95); } | |
to { transform: scale(1.05); } | |
} | |
.game-over-overlay, .win-overlay { | |
background-color: rgba(0, 0, 0, 0.7); | |
} | |
.difficulty-btn.active { | |
background-color: #4CAF50; | |
color: white; | |
transform: scale(1.05); | |
box-shadow: 0 0 10px rgba(76, 175, 80, 0.5); | |
} | |
.progress-bar { | |
height: 10px; | |
background-color: #2d3748; | |
border-radius: 5px; | |
overflow: hidden; | |
} | |
.progress-fill { | |
height: 100%; | |
background: linear-gradient(90deg, #4CAF50, #8BC34A); | |
transition: width 0.3s ease; | |
} | |
</style> | |
</head> | |
<body class="bg-gray-900 text-white min-h-screen flex flex-col items-center justify-center p-4"> | |
<div class="text-center mb-6"> | |
<h1 class="text-4xl font-bold text-green-400 mb-2">Snake Game</h1> | |
<p class="text-gray-300">Reach 100 points to win!</p> | |
</div> | |
<div class="flex gap-4 mb-4"> | |
<button id="easy-btn" class="difficulty-btn active bg-gray-700 hover:bg-gray-600 px-4 py-2 rounded-lg transition"> | |
Easy | |
</button> | |
<button id="medium-btn" class="difficulty-btn bg-gray-700 hover:bg-gray-600 px-4 py-2 rounded-lg transition"> | |
Medium | |
</button> | |
<button id="hard-btn" class="difficulty-btn bg-gray-700 hover:bg-gray-600 px-4 py-2 rounded-lg transition"> | |
Hard | |
</button> | |
</div> | |
<div class="relative"> | |
<div class="flex justify-between items-center mb-4 w-full max-w-md"> | |
<div class="bg-gray-800 px-4 py-2 rounded-lg"> | |
<span class="text-gray-300 mr-2">Score:</span> | |
<span id="score" class="text-green-400 font-bold text-xl">0</span> | |
<span class="text-gray-400">/100</span> | |
</div> | |
<button id="restart-btn" class="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded-lg transition"> | |
Restart Game | |
</button> | |
</div> | |
<div class="progress-bar w-full max-w-md mb-2"> | |
<div id="progress-fill" class="progress-fill" style="width: 0%"></div> | |
</div> | |
<canvas id="game-board" width="400" height="400" class="rounded-lg"></canvas> | |
<div id="game-over" class="game-over-overlay absolute inset-0 flex flex-col items-center justify-center rounded-lg hidden"> | |
<div class="bg-gray-800 p-6 rounded-lg text-center"> | |
<h2 class="text-2xl font-bold text-red-400 mb-4">Game Over!</h2> | |
<p class="text-xl mb-4">Your score: <span id="final-score" class="text-green-400">0</span>/100</p> | |
<button id="play-again-btn" class="bg-green-600 hover:bg-green-700 text-white px-6 py-2 rounded-lg transition"> | |
Play Again | |
</button> | |
</div> | |
</div> | |
<div id="win-screen" class="win-overlay absolute inset-0 flex flex-col items-center justify-center rounded-lg hidden"> | |
<div class="bg-gray-800 p-6 rounded-lg text-center"> | |
<h2 class="text-2xl font-bold text-green-400 mb-4">You Win! 🎉</h2> | |
<p class="text-xl mb-4">You reached 100 points!</p> | |
<button id="win-play-again-btn" class="bg-green-600 hover:bg-green-700 text-white px-6 py-2 rounded-lg transition"> | |
Play Again | |
</button> | |
</div> | |
</div> | |
</div> | |
<div class="mt-6 text-gray-400 text-sm text-center max-w-md"> | |
<p>Controls: Arrow keys or WASD (W=Up, A=Left, S=Down, D=Right)</p> | |
<p class="mt-1">Press any control key to start the game</p> | |
</div> | |
<script> | |
document.addEventListener('DOMContentLoaded', () => { | |
const canvas = document.getElementById('game-board'); | |
const ctx = canvas.getContext('2d'); | |
const scoreDisplay = document.getElementById('score'); | |
const finalScoreDisplay = document.getElementById('final-score'); | |
const restartBtn = document.getElementById('restart-btn'); | |
const playAgainBtn = document.getElementById('play-again-btn'); | |
const winPlayAgainBtn = document.getElementById('win-play-again-btn'); | |
const gameOverDisplay = document.getElementById('game-over'); | |
const winDisplay = document.getElementById('win-screen'); | |
const progressFill = document.getElementById('progress-fill'); | |
// Difficulty buttons | |
const easyBtn = document.getElementById('easy-btn'); | |
const mediumBtn = document.getElementById('medium-btn'); | |
const hardBtn = document.getElementById('hard-btn'); | |
const gridSize = 20; | |
const tileCount = canvas.width / gridSize; | |
const WIN_SCORE = 100; | |
let snake = []; | |
let food = {}; | |
let score = 0; | |
let direction = 'right'; | |
let nextDirection = 'right'; | |
let gameSpeed; | |
let gameRunning = false; | |
let gameLoop; | |
let currentDifficulty = 'easy'; | |
// Difficulty settings | |
const difficultySettings = { | |
easy: { speed: 150, foodScore: 1 }, | |
medium: { speed: 100, foodScore: 2 }, | |
hard: { speed: 70, foodScore: 3 } | |
}; | |
// Initialize game | |
function initGame() { | |
snake = [ | |
{x: 10, y: 10}, | |
{x: 9, y: 10}, | |
{x: 8, y: 10} | |
]; | |
direction = 'right'; | |
nextDirection = 'right'; | |
score = 0; | |
scoreDisplay.textContent = score; | |
progressFill.style.width = '0%'; | |
// Set game speed based on difficulty | |
gameSpeed = difficultySettings[currentDifficulty].speed; | |
placeFood(); | |
gameRunning = true; | |
gameOverDisplay.classList.add('hidden'); | |
winDisplay.classList.add('hidden'); | |
if (gameLoop) clearInterval(gameLoop); | |
gameLoop = setInterval(gameStep, gameSpeed); | |
} | |
// Game step | |
function gameStep() { | |
if (!gameRunning) return; | |
direction = nextDirection; | |
// Move snake | |
const head = {x: snake[0].x, y: snake[0].y}; | |
switch (direction) { | |
case 'up': | |
head.y--; | |
break; | |
case 'down': | |
head.y++; | |
break; | |
case 'left': | |
head.x--; | |
break; | |
case 'right': | |
head.x++; | |
break; | |
} | |
// Check collision with walls | |
if (head.x < 0 || head.x >= tileCount || head.y < 0 || head.y >= tileCount) { | |
gameOver(); | |
return; | |
} | |
// Check collision with self | |
for (let i = 0; i < snake.length; i++) { | |
if (snake[i].x === head.x && snake[i].y === head.y) { | |
gameOver(); | |
return; | |
} | |
} | |
// Check if snake ate food | |
if (head.x === food.x && head.y === food.y) { | |
// Don't remove tail (snake grows) | |
placeFood(); | |
score += difficultySettings[currentDifficulty].foodScore; | |
scoreDisplay.textContent = score; | |
updateProgress(); | |
// Check for win condition | |
if (score >= WIN_SCORE) { | |
winGame(); | |
return; | |
} | |
// Increase speed slightly every 10 points | |
if (score % 10 === 0) { | |
clearInterval(gameLoop); | |
gameSpeed = Math.max(gameSpeed - 5, 50); | |
gameLoop = setInterval(gameStep, gameSpeed); | |
} | |
} else { | |
// Remove tail | |
snake.pop(); | |
} | |
// Add new head | |
snake.unshift(head); | |
// Draw everything | |
draw(); | |
} | |
// Update progress bar | |
function updateProgress() { | |
const progress = (score / WIN_SCORE) * 100; | |
progressFill.style.width = `${progress}%`; | |
} | |
// Draw game | |
function draw() { | |
// Clear canvas | |
ctx.fillStyle = '#1a2e05'; | |
ctx.fillRect(0, 0, canvas.width, canvas.height); | |
// Draw snake | |
snake.forEach((segment, index) => { | |
// Head is slightly different color | |
const isHead = index === 0; | |
ctx.fillStyle = isHead ? '#a5d6a7' : '#8bc34a'; | |
ctx.strokeStyle = isHead ? '#689f38' : '#5a9216'; | |
ctx.beginPath(); | |
ctx.roundRect( | |
segment.x * gridSize, | |
segment.y * gridSize, | |
gridSize, | |
gridSize, | |
4 | |
); | |
ctx.fill(); | |
ctx.stroke(); | |
}); | |
// Draw food | |
ctx.fillStyle = '#ff5252'; | |
ctx.strokeStyle = '#ff1744'; | |
ctx.beginPath(); | |
ctx.arc( | |
food.x * gridSize + gridSize/2, | |
food.y * gridSize + gridSize/2, | |
gridSize/2 - 2, | |
0, | |
Math.PI * 2 | |
); | |
ctx.fill(); | |
ctx.stroke(); | |
// Draw grid (optional) | |
ctx.strokeStyle = 'rgba(58, 95, 11, 0.2)'; | |
ctx.lineWidth = 0.5; | |
for (let i = 0; i < tileCount; i++) { | |
// Vertical lines | |
ctx.beginPath(); | |
ctx.moveTo(i * gridSize, 0); | |
ctx.lineTo(i * gridSize, canvas.height); | |
ctx.stroke(); | |
// Horizontal lines | |
ctx.beginPath(); | |
ctx.moveTo(0, i * gridSize); | |
ctx.lineTo(canvas.width, i * gridSize); | |
ctx.stroke(); | |
} | |
} | |
// Place food randomly | |
function placeFood() { | |
let validPosition = false; | |
while (!validPosition) { | |
food = { | |
x: Math.floor(Math.random() * tileCount), | |
y: Math.floor(Math.random() * tileCount) | |
}; | |
// Check if food is on snake | |
validPosition = true; | |
for (let i = 0; i < snake.length; i++) { | |
if (snake[i].x === food.x && snake[i].y === food.y) { | |
validPosition = false; | |
break; | |
} | |
} | |
} | |
} | |
// Game over | |
function gameOver() { | |
gameRunning = false; | |
clearInterval(gameLoop); | |
finalScoreDisplay.textContent = score; | |
gameOverDisplay.classList.remove('hidden'); | |
} | |
// Win game | |
function winGame() { | |
gameRunning = false; | |
clearInterval(gameLoop); | |
winDisplay.classList.remove('hidden'); | |
} | |
// Event listeners | |
document.addEventListener('keydown', (e) => { | |
// Prevent default for arrow keys to avoid page scrolling | |
const controlKeys = ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'w', 'a', 's', 'd']; | |
if (controlKeys.includes(e.key)) { | |
e.preventDefault(); | |
// Start game on first key press if not running | |
if (!gameRunning && snake.length === 0) { | |
initGame(); | |
return; | |
} | |
} | |
// Change direction (can't go opposite current direction) | |
switch (e.key.toLowerCase()) { | |
case 'arrowup': | |
case 'w': | |
if (direction !== 'down') nextDirection = 'up'; | |
break; | |
case 'arrowdown': | |
case 's': | |
if (direction !== 'up') nextDirection = 'down'; | |
break; | |
case 'arrowleft': | |
case 'a': | |
if (direction !== 'right') nextDirection = 'left'; | |
break; | |
case 'arrowright': | |
case 'd': | |
if (direction !== 'left') nextDirection = 'right'; | |
break; | |
} | |
}); | |
// Difficulty buttons | |
function setDifficulty(difficulty) { | |
currentDifficulty = difficulty; | |
// Update active button styling | |
easyBtn.classList.remove('active'); | |
mediumBtn.classList.remove('active'); | |
hardBtn.classList.remove('active'); | |
if (difficulty === 'easy') easyBtn.classList.add('active'); | |
if (difficulty === 'medium') mediumBtn.classList.add('active'); | |
if (difficulty === 'hard') hardBtn.classList.add('active'); | |
// Restart game if already running | |
if (gameRunning) { | |
initGame(); | |
} | |
} | |
easyBtn.addEventListener('click', () => setDifficulty('easy')); | |
mediumBtn.addEventListener('click', () => setDifficulty('medium')); | |
hardBtn.addEventListener('click', () => setDifficulty('hard')); | |
restartBtn.addEventListener('click', initGame); | |
playAgainBtn.addEventListener('click', initGame); | |
winPlayAgainBtn.addEventListener('click', initGame); | |
// Touch controls for mobile | |
let touchStartX = 0; | |
let touchStartY = 0; | |
canvas.addEventListener('touchstart', (e) => { | |
if (!gameRunning && snake.length === 0) { | |
initGame(); | |
return; | |
} | |
touchStartX = e.touches[0].clientX; | |
touchStartY = e.touches[0].clientY; | |
e.preventDefault(); | |
}, false); | |
canvas.addEventListener('touchmove', (e) => { | |
if (!gameRunning) return; | |
const touchEndX = e.touches[0].clientX; | |
const touchEndY = e.touches[0].clientY; | |
const dx = touchEndX - touchStartX; | |
const dy = touchEndY - touchStartY; | |
// Determine primary direction of swipe | |
if (Math.abs(dx) > Math.abs(dy)) { | |
// Horizontal swipe | |
if (dx > 0 && direction !== 'left') { | |
nextDirection = 'right'; | |
} else if (dx < 0 && direction !== 'right') { | |
nextDirection = 'left'; | |
} | |
} else { | |
// Vertical swipe | |
if (dy > 0 && direction !== 'up') { | |
nextDirection = 'down'; | |
} else if (dy < 0 && direction !== 'down') { | |
nextDirection = 'up'; | |
} | |
} | |
e.preventDefault(); | |
}, false); | |
// Initialize with easy difficulty | |
setDifficulty('easy'); | |
}); | |
</script> | |
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=andreaschandra/snake-frenzy" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
</html> |