Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Customizable Snake Game</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <style> | |
| #gameCanvas { | |
| border: 2px solid #4a5568; | |
| background-color: #f7fafc; | |
| } | |
| .toggle-btn { | |
| transition: all 0.3s ease; | |
| } | |
| .toggle-btn.active { | |
| background-color: #4299e1; | |
| color: white; | |
| } | |
| .modal { | |
| display: none; | |
| position: fixed; | |
| z-index: 10; | |
| left: 0; | |
| top: 0; | |
| width: 100%; | |
| height: 100%; | |
| background-color: rgba(0, 0, 0, 0.5); | |
| } | |
| .modal-content { | |
| background-color: #f8fafc; | |
| margin: 15% auto; | |
| padding: 20px; | |
| border-radius: 8px; | |
| max-width: 400px; | |
| box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-100 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-gray-800 mb-2">Custom Snake Game</h1> | |
| <p class="text-gray-600">Control the snake with arrow keys or WASD</p> | |
| </div> | |
| <div class="flex flex-col md:flex-row gap-8 items-center justify-center"> | |
| <div class="flex flex-col items-center"> | |
| <canvas id="gameCanvas" width="400" height="400"></canvas> | |
| <div class="mt-4 flex gap-4"> | |
| <button id="startBtn" class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition"> | |
| Start Game | |
| </button> | |
| <button id="pauseBtn" class="px-4 py-2 bg-yellow-500 text-white rounded hover:bg-yellow-600 transition hidden"> | |
| Pause | |
| </button> | |
| <button id="restartBtn" class="px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600 transition"> | |
| Restart | |
| </button> | |
| </div> | |
| </div> | |
| <div class="bg-white p-6 rounded-lg shadow-lg w-full max-w-md"> | |
| <h2 class="text-xl font-semibold mb-4 text-gray-800">Game Settings</h2> | |
| <div class="mb-6"> | |
| <label class="block text-gray-700 mb-2">Game Speed</label> | |
| <input type="range" id="speedSlider" min="5" max="25" value="10" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer"> | |
| <div class="flex justify-between text-sm text-gray-600 mt-1"> | |
| <span>Slow</span> | |
| <span>Fast</span> | |
| </div> | |
| </div> | |
| <div class="mb-6"> | |
| <label class="block text-gray-700 mb-2">Boundary Settings</label> | |
| <p class="text-sm text-gray-500 mb-3">Select which boundaries will end the game:</p> | |
| <div class="grid grid-cols-2 gap-2"> | |
| <button id="topBoundary" class="toggle-btn px-3 py-2 border rounded">Top</button> | |
| <button id="rightBoundary" class="toggle-btn px-3 py-2 border rounded">Right</button> | |
| <button id="bottomBoundary" class="toggle-btn px-3 py-2 border rounded active">Bottom</button> | |
| <button id="leftBoundary" class="toggle-btn px-3 py-2 border rounded active">Left</button> | |
| </div> | |
| </div> | |
| <div class="mb-4"> | |
| <label class="block text-gray-700 mb-2">Game Stats</label> | |
| <div class="bg-gray-100 p-3 rounded"> | |
| <div class="flex justify-between"> | |
| <span class="font-medium">Score:</span> | |
| <span id="scoreDisplay">0</span> | |
| </div> | |
| <div class="flex justify-between"> | |
| <span class="font-medium">High Score:</span> | |
| <span id="highScoreDisplay">0</span> | |
| </div> | |
| </div> | |
| </div> | |
| <button id="howToPlayBtn" class="text-blue-500 hover:text-blue-700 text-sm underline"> | |
| How to Play | |
| </button> | |
| </div> | |
| </div> | |
| <!-- How to Play Modal --> | |
| <div id="howToPlayModal" class="modal"> | |
| <div class="modal-content"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h3 class="text-lg font-semibold">How to Play</h3> | |
| <button id="closeModal" class="text-gray-500 hover:text-gray-700">×</button> | |
| </div> | |
| <div class="space-y-3 text-gray-700"> | |
| <p><strong>Controls:</strong> Use arrow keys (↑, ↓, ←, →) or WASD keys to move the snake.</p> | |
| <p><strong>Objective:</strong> Eat the food (red square) to grow longer and increase your score.</p> | |
| <p><strong>Game Over:</strong> The game ends if you hit the selected boundaries or yourself.</p> | |
| <p><strong>Customization:</strong> Adjust game speed and select which boundaries will end the game.</p> | |
| </div> | |
| <div class="mt-6 flex justify-center"> | |
| <button id="closeModalBtn" class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition"> | |
| Got it! | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // Game variables | |
| const canvas = document.getElementById('gameCanvas'); | |
| const ctx = canvas.getContext('2d'); | |
| const boxSize = 20; | |
| let snake = []; | |
| let food = {}; | |
| let direction = 'right'; | |
| let nextDirection = 'right'; | |
| let gameSpeed = 150; // Initial slow speed (higher number = slower) | |
| let gameInterval; | |
| let score = 0; | |
| let highScore = localStorage.getItem('snakeHighScore') || 0; | |
| let gameRunning = false; | |
| let gamePaused = false; | |
| // Boundary settings | |
| let boundaries = { | |
| top: false, | |
| right: false, | |
| bottom: true, | |
| left: true | |
| }; | |
| // Initialize game | |
| function initGame() { | |
| snake = [ | |
| {x: 9 * boxSize, y: 10 * boxSize}, | |
| {x: 8 * boxSize, y: 10 * boxSize}, | |
| {x: 7 * boxSize, y: 10 * boxSize} | |
| ]; | |
| direction = 'right'; | |
| nextDirection = 'right'; | |
| score = 0; | |
| updateScore(); | |
| generateFood(); | |
| } | |
| // Draw game elements | |
| function draw() { | |
| // Clear canvas | |
| ctx.fillStyle = '#f7fafc'; | |
| ctx.fillRect(0, 0, canvas.width, canvas.height); | |
| // Draw snake | |
| snake.forEach((segment, index) => { | |
| if (index === 0) { | |
| // Draw head with different color | |
| ctx.fillStyle = '#2c5282'; | |
| } else { | |
| ctx.fillStyle = '#4299e1'; | |
| } | |
| ctx.fillRect(segment.x, segment.y, boxSize, boxSize); | |
| ctx.strokeStyle = '#ebf8ff'; | |
| ctx.strokeRect(segment.x, segment.y, boxSize, boxSize); | |
| }); | |
| // Draw food | |
| ctx.fillStyle = '#e53e3e'; | |
| ctx.fillRect(food.x, food.y, boxSize, boxSize); | |
| // Draw grid (optional) | |
| if (window.innerWidth > 768) { // Only show grid on larger screens | |
| ctx.strokeStyle = '#e2e8f0'; | |
| ctx.lineWidth = 0.5; | |
| for (let i = 0; i < canvas.width; i += boxSize) { | |
| ctx.beginPath(); | |
| ctx.moveTo(i, 0); | |
| ctx.lineTo(i, canvas.height); | |
| ctx.stroke(); | |
| ctx.beginPath(); | |
| ctx.moveTo(0, i); | |
| ctx.lineTo(canvas.width, i); | |
| ctx.stroke(); | |
| } | |
| } | |
| } | |
| // Game logic | |
| function gameLoop() { | |
| if (gamePaused) return; | |
| // Update direction | |
| direction = nextDirection; | |
| // Calculate new head position | |
| const head = {x: snake[0].x, y: snake[0].y}; | |
| switch (direction) { | |
| case 'up': | |
| head.y -= boxSize; | |
| break; | |
| case 'down': | |
| head.y += boxSize; | |
| break; | |
| case 'left': | |
| head.x -= boxSize; | |
| break; | |
| case 'right': | |
| head.x += boxSize; | |
| break; | |
| } | |
| // Check for collisions | |
| if (checkCollision(head)) { | |
| gameOver(); | |
| return; | |
| } | |
| // Add new head | |
| snake.unshift(head); | |
| // Check if snake ate food | |
| if (head.x === food.x && head.y === food.y) { | |
| score += 10; | |
| updateScore(); | |
| generateFood(); | |
| } else { | |
| // Remove tail if no food eaten | |
| snake.pop(); | |
| } | |
| // Redraw everything | |
| draw(); | |
| } | |
| // Check for collisions | |
| function checkCollision(head) { | |
| // Check wall collisions based on boundary settings | |
| if (boundaries.top && head.y < 0) return true; | |
| if (boundaries.right && head.x >= canvas.width) return true; | |
| if (boundaries.bottom && head.y >= canvas.height) return true; | |
| if (boundaries.left && head.x < 0) return true; | |
| // Check self collision | |
| for (let i = 0; i < snake.length; i++) { | |
| if (head.x === snake[i].x && head.y === snake[i].y) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| // Generate food at random position | |
| function generateFood() { | |
| const maxX = Math.floor(canvas.width / boxSize); | |
| const maxY = Math.floor(canvas.height / boxSize); | |
| let foodX, foodY; | |
| let validPosition = false; | |
| while (!validPosition) { | |
| foodX = Math.floor(Math.random() * maxX) * boxSize; | |
| foodY = Math.floor(Math.random() * maxY) * boxSize; | |
| validPosition = true; | |
| // Check if food is on snake | |
| for (let i = 0; i < snake.length; i++) { | |
| if (snake[i].x === foodX && snake[i].y === foodY) { | |
| validPosition = false; | |
| break; | |
| } | |
| } | |
| } | |
| food = {x: foodX, y: foodY}; | |
| } | |
| // Game over | |
| function gameOver() { | |
| clearInterval(gameInterval); | |
| gameRunning = false; | |
| document.getElementById('startBtn').classList.remove('hidden'); | |
| document.getElementById('pauseBtn').classList.add('hidden'); | |
| // Update high score if needed | |
| if (score > highScore) { | |
| highScore = score; | |
| localStorage.setItem('snakeHighScore', highScore); | |
| updateScore(); | |
| } | |
| // Show game over message | |
| ctx.fillStyle = 'rgba(0, 0, 0, 0.7)'; | |
| ctx.fillRect(0, 0, canvas.width, canvas.height); | |
| ctx.fillStyle = '#ffffff'; | |
| ctx.font = '30px Arial'; | |
| ctx.textAlign = 'center'; | |
| ctx.fillText('Game Over!', canvas.width / 2, canvas.height / 2 - 30); | |
| ctx.font = '20px Arial'; | |
| ctx.fillText(`Score: ${score}`, canvas.width / 2, canvas.height / 2 + 10); | |
| ctx.fillText(`High Score: ${highScore}`, canvas.width / 2, canvas.height / 2 + 40); | |
| ctx.font = '16px Arial'; | |
| ctx.fillText('Press Restart to play again', canvas.width / 2, canvas.height / 2 + 80); | |
| } | |
| // Update score display | |
| function updateScore() { | |
| document.getElementById('scoreDisplay').textContent = score; | |
| document.getElementById('highScoreDisplay').textContent = highScore; | |
| } | |
| // Start game | |
| function startGame() { | |
| if (gameRunning) return; | |
| initGame(); | |
| gameRunning = true; | |
| gamePaused = false; | |
| clearInterval(gameInterval); | |
| gameInterval = setInterval(gameLoop, gameSpeed); | |
| document.getElementById('startBtn').classList.add('hidden'); | |
| document.getElementById('pauseBtn').classList.remove('hidden'); | |
| draw(); | |
| } | |
| // Pause game | |
| function pauseGame() { | |
| if (!gameRunning) return; | |
| gamePaused = !gamePaused; | |
| if (gamePaused) { | |
| document.getElementById('pauseBtn').textContent = 'Resume'; | |
| // Draw pause message | |
| ctx.fillStyle = 'rgba(0, 0, 0, 0.5)'; | |
| ctx.fillRect(0, 0, canvas.width, canvas.height); | |
| ctx.fillStyle = '#ffffff'; | |
| ctx.font = '30px Arial'; | |
| ctx.textAlign = 'center'; | |
| ctx.fillText('Game Paused', canvas.width / 2, canvas.height / 2); | |
| } else { | |
| document.getElementById('pauseBtn').textContent = 'Pause'; | |
| } | |
| } | |
| // Restart game | |
| function restartGame() { | |
| clearInterval(gameInterval); | |
| gameRunning = false; | |
| gamePaused = false; | |
| document.getElementById('startBtn').classList.remove('hidden'); | |
| document.getElementById('pauseBtn').classList.add('hidden'); | |
| document.getElementById('pauseBtn').textContent = 'Pause'; | |
| initGame(); | |
| draw(); | |
| } | |
| // Event listeners | |
| document.addEventListener('keydown', function(e) { | |
| // Prevent default for arrow keys to avoid page scrolling | |
| if ([37, 38, 39, 40].indexOf(e.keyCode) > -1) { | |
| e.preventDefault(); | |
| } | |
| // Change direction based on key press | |
| switch(e.keyCode) { | |
| case 37: // left arrow | |
| case 65: // A | |
| if (direction !== 'right') nextDirection = 'left'; | |
| break; | |
| case 38: // up arrow | |
| case 87: // W | |
| if (direction !== 'down') nextDirection = 'up'; | |
| break; | |
| case 39: // right arrow | |
| case 68: // D | |
| if (direction !== 'left') nextDirection = 'right'; | |
| break; | |
| case 40: // down arrow | |
| case 83: // S | |
| if (direction !== 'up') nextDirection = 'down'; | |
| break; | |
| case 32: // space | |
| if (gameRunning) pauseGame(); | |
| break; | |
| } | |
| }); | |
| // UI event listeners | |
| document.getElementById('startBtn').addEventListener('click', startGame); | |
| document.getElementById('pauseBtn').addEventListener('click', pauseGame); | |
| document.getElementById('restartBtn').addEventListener('click', restartGame); | |
| // Speed control | |
| document.getElementById('speedSlider').addEventListener('input', function() { | |
| // Map slider value (5-25) to game speed (300-50ms) | |
| gameSpeed = 300 - (this.value * 10); | |
| if (gameRunning) { | |
| clearInterval(gameInterval); | |
| gameInterval = setInterval(gameLoop, gameSpeed); | |
| } | |
| }); | |
| // Boundary toggles | |
| const boundaryButtons = ['top', 'right', 'bottom', 'left']; | |
| boundaryButtons.forEach(boundary => { | |
| document.getElementById(`${boundary}Boundary`).addEventListener('click', function() { | |
| boundaries[boundary] = !boundaries[boundary]; | |
| this.classList.toggle('active'); | |
| this.classList.toggle('bg-blue-500'); | |
| this.classList.toggle('text-white'); | |
| }); | |
| }); | |
| // How to Play modal | |
| const modal = document.getElementById('howToPlayModal'); | |
| document.getElementById('howToPlayBtn').addEventListener('click', function() { | |
| modal.style.display = 'block'; | |
| }); | |
| document.getElementById('closeModal').addEventListener('click', function() { | |
| modal.style.display = 'none'; | |
| }); | |
| document.getElementById('closeModalBtn').addEventListener('click', function() { | |
| modal.style.display = 'none'; | |
| }); | |
| window.addEventListener('click', function(event) { | |
| if (event.target === modal) { | |
| modal.style.display = 'none'; | |
| } | |
| }); | |
| // Initialize game display | |
| initGame(); | |
| draw(); | |
| </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=durgaamma2005/snakegame" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |