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</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@latest"></script> | |
| <style> | |
| @keyframes pulse { | |
| 0%, 100% { opacity: 1; } | |
| 50% { opacity: 0.7; } | |
| } | |
| .snake-head { | |
| background-color: #4f46e5; | |
| border-radius: 12px; | |
| } | |
| .snake-body { | |
| background-color: #6366f1; | |
| border-radius: 8px; | |
| } | |
| .food { | |
| background-color: #ef4444; | |
| border-radius: 50%; | |
| animation: pulse 1s infinite; | |
| } | |
| #game-board { | |
| touch-action: none; | |
| } | |
| .control-btn { | |
| transition: all 0.2s; | |
| } | |
| .control-btn:active { | |
| transform: scale(0.95); | |
| background-color: #4f46e5; | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-900 text-white min-h-screen flex flex-col items-center justify-center p-4 relative"> | |
| <div class="text-center mb-6"> | |
| <h1 class="text-4xl font-bold text-indigo-400 mb-2">Snake Game</h1> | |
| <div class="flex justify-center gap-8 items-center"> | |
| <div class="text-xl"> | |
| <span class="text-gray-400">Score:</span> | |
| <span id="score" class="font-bold text-indigo-300">0</span> | |
| </div> | |
| <div class="text-xl"> | |
| <span class="text-gray-400">High Score:</span> | |
| <span id="high-score" class="font-bold text-indigo-300">0</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div id="game-container" class="relative"> | |
| <div id="game-board" class="bg-gray-800 border-2 border-indigo-500 rounded-lg"></div> | |
| <!-- Game Over Screen --> | |
| <div id="game-over" class="hidden absolute inset-0 bg-black bg-opacity-70 flex flex-col items-center justify-center rounded-lg"> | |
| <h2 class="text-4xl font-bold text-red-500 mb-4">Game Over!</h2> | |
| <div class="text-xl mb-6"> | |
| Final Score: <span id="final-score" class="font-bold text-indigo-300">0</span> | |
| </div> | |
| <button id="restart-btn" class="bg-indigo-600 hover:bg-indigo-500 text-white font-bold py-2 px-6 rounded-full transition"> | |
| Play Again | |
| </button> | |
| </div> | |
| <!-- Start Screen --> | |
| <div id="start-screen" class="absolute inset-0 bg-black bg-opacity-70 flex flex-col items-center justify-center rounded-lg"> | |
| <h2 class="text-4xl font-bold text-indigo-400 mb-6">Snake Game</h2> | |
| <p class="text-gray-300 mb-8 text-center max-w-md px-4"> | |
| Use arrow keys or swipe to control the snake. Eat the food (red dots) to grow and earn points! | |
| </p> | |
| <button id="start-btn" class="bg-indigo-600 hover:bg-indigo-500 text-white font-bold py-3 px-8 rounded-full text-lg transition"> | |
| Start Game | |
| </button> | |
| </div> | |
| </div> | |
| <!-- AI Controls --> | |
| <div class="mt-6 flex justify-center gap-4"> | |
| <button id="ai-btn" class="bg-indigo-600 hover:bg-indigo-500 text-white font-bold py-2 px-6 rounded-full transition"> | |
| AI Player | |
| </button> | |
| <button id="human-btn" class="bg-gray-700 hover:bg-gray-600 text-white font-bold py-2 px-6 rounded-full transition"> | |
| Human Player | |
| </button> | |
| </div> | |
| <!-- Mobile Controls --> | |
| <div id="mobile-controls" class="mt-6 grid grid-cols-3 gap-2 sm:hidden"> | |
| <div class="col-start-2"> | |
| <button id="up-btn" class="control-btn bg-gray-700 hover:bg-gray-600 w-16 h-16 rounded-full flex items-center justify-center"> | |
| ↑ | |
| </button> | |
| </div> | |
| <div class="col-span-3 flex justify-center gap-4 mt-2"> | |
| <button id="left-btn" class="control-btn bg-gray-700 hover:bg-gray-600 w-16 h-16 rounded-full flex items-center justify-center"> | |
| ← | |
| </button> | |
| <button id="down-btn" class="control-btn bg-gray-700 hover:bg-gray-600 w-16 h-16 rounded-full flex items-center justify-center"> | |
| ↓ | |
| </button> | |
| <button id="right-btn" class="control-btn bg-gray-700 hover:bg-gray-600 w-16 h-16 rounded-full flex items-center justify-center"> | |
| → | |
| </button> | |
| </div> | |
| </div> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', () => { | |
| // Game configuration | |
| const GRID_SIZE = 20; | |
| const CELL_SIZE = 20; | |
| const GAME_SPEED = 150; // ms | |
| // Game elements | |
| const gameBoard = document.getElementById('game-board'); | |
| const scoreDisplay = document.getElementById('score'); | |
| const highScoreDisplay = document.getElementById('high-score'); | |
| const startScreen = document.getElementById('start-screen'); | |
| const gameOverScreen = document.getElementById('game-over'); | |
| const finalScoreDisplay = document.getElementById('final-score'); | |
| const startBtn = document.getElementById('start-btn'); | |
| const restartBtn = document.getElementById('restart-btn'); | |
| // Mobile controls | |
| const upBtn = document.getElementById('up-btn'); | |
| const downBtn = document.getElementById('down-btn'); | |
| const leftBtn = document.getElementById('left-btn'); | |
| const rightBtn = document.getElementById('right-btn'); | |
| // Game state | |
| let snake = []; | |
| let direction = 'right'; | |
| let nextDirection = 'right'; | |
| let food = {}; | |
| let score = 0; | |
| let highScore = localStorage.getItem('snakeHighScore') || 0; | |
| let gameInterval; | |
| let isPaused = false; | |
| let gameRunning = false; | |
| // Initialize game board | |
| function initGameBoard() { | |
| gameBoard.innerHTML = ''; | |
| gameBoard.style.display = 'grid'; | |
| gameBoard.style.gridTemplateColumns = `repeat(${GRID_SIZE}, ${CELL_SIZE}px)`; | |
| gameBoard.style.gridTemplateRows = `repeat(${GRID_SIZE}, ${CELL_SIZE}px)`; | |
| gameBoard.style.width = `${GRID_SIZE * CELL_SIZE}px`; | |
| gameBoard.style.height = `${GRID_SIZE * CELL_SIZE}px`; | |
| highScoreDisplay.textContent = highScore; | |
| } | |
| // Start game | |
| function startGame() { | |
| // Reset game state | |
| snake = [ | |
| {x: 5, y: 10}, | |
| {x: 4, y: 10}, | |
| {x: 3, y: 10} | |
| ]; | |
| direction = 'right'; | |
| nextDirection = 'right'; | |
| score = 0; | |
| scoreDisplay.textContent = '0'; | |
| // Hide start screen | |
| startScreen.classList.add('hidden'); | |
| gameOverScreen.classList.add('hidden'); | |
| // Generate first food | |
| generateFood(); | |
| // Draw initial state | |
| draw(); | |
| // Start game loop | |
| gameRunning = true; | |
| gameInterval = setInterval(gameLoop, GAME_SPEED); | |
| } | |
| // Game loop | |
| function gameLoop() { | |
| if (isPaused) return; | |
| // Update snake direction | |
| direction = nextDirection; | |
| // Move snake | |
| const head = { ...snake[0] }; | |
| switch (direction) { | |
| case 'up': | |
| head.y -= 1; | |
| break; | |
| case 'down': | |
| head.y += 1; | |
| break; | |
| case 'left': | |
| head.x -= 1; | |
| break; | |
| case 'right': | |
| head.x += 1; | |
| break; | |
| } | |
| // Check for collisions | |
| if ( | |
| head.x < 0 || head.x >= GRID_SIZE || | |
| head.y < 0 || head.y >= GRID_SIZE || | |
| snake.some(segment => segment.x === head.x && segment.y === head.y) | |
| ) { | |
| gameOver(); | |
| return; | |
| } | |
| // Add new head | |
| snake.unshift(head); | |
| // Check if food was eaten | |
| if (head.x === food.x && head.y === food.y) { | |
| score += 10; | |
| scoreDisplay.textContent = score; | |
| generateFood(); | |
| } else { | |
| // Remove tail if no food was eaten | |
| snake.pop(); | |
| } | |
| // Redraw game | |
| draw(); | |
| } | |
| // Draw game state | |
| function draw() { | |
| gameBoard.innerHTML = ''; | |
| // Draw snake | |
| snake.forEach((segment, index) => { | |
| const segmentElement = document.createElement('div'); | |
| segmentElement.style.gridColumn = segment.x + 1; | |
| segmentElement.style.gridRow = segment.y + 1; | |
| segmentElement.className = index === 0 ? 'snake-head' : 'snake-body'; | |
| gameBoard.appendChild(segmentElement); | |
| }); | |
| // Draw food | |
| const foodElement = document.createElement('div'); | |
| foodElement.style.gridColumn = food.x + 1; | |
| foodElement.style.gridRow = food.y + 1; | |
| foodElement.className = 'food'; | |
| gameBoard.appendChild(foodElement); | |
| } | |
| // Generate food at random position | |
| function generateFood() { | |
| // Find all empty cells | |
| const emptyCells = []; | |
| for (let y = 0; y < GRID_SIZE; y++) { | |
| for (let x = 0; x < GRID_SIZE; x++) { | |
| if (!snake.some(segment => segment.x === x && segment.y === y)) { | |
| emptyCells.push({x, y}); | |
| } | |
| } | |
| } | |
| // Place food in random empty cell | |
| if (emptyCells.length > 0) { | |
| const randomIndex = Math.floor(Math.random() * emptyCells.length); | |
| food = emptyCells[randomIndex]; | |
| } | |
| } | |
| // Game over | |
| function gameOver() { | |
| clearInterval(gameInterval); | |
| gameRunning = false; | |
| // Update high score if needed | |
| if (score > highScore) { | |
| highScore = score; | |
| localStorage.setItem('snakeHighScore', highScore); | |
| highScoreDisplay.textContent = highScore; | |
| } | |
| // Show game over screen | |
| finalScoreDisplay.textContent = score; | |
| gameOverScreen.classList.remove('hidden'); | |
| } | |
| // Handle keyboard input | |
| function handleKeyDown(e) { | |
| if (!gameRunning) return; | |
| // Prevent key repeating | |
| if (e.key === 'ArrowUp' || e.key === 'ArrowDown' || | |
| e.key === 'ArrowLeft' || e.key === 'ArrowRight') { | |
| e.preventDefault(); | |
| } | |
| switch (e.key) { | |
| case 'ArrowUp': | |
| if (direction !== 'down') nextDirection = 'up'; | |
| break; | |
| case 'ArrowDown': | |
| if (direction !== 'up') nextDirection = 'down'; | |
| break; | |
| case 'ArrowLeft': | |
| if (direction !== 'right') nextDirection = 'left'; | |
| break; | |
| case 'ArrowRight': | |
| if (direction !== 'left') nextDirection = 'right'; | |
| break; | |
| case ' ': | |
| isPaused = !isPaused; | |
| break; | |
| } | |
| } | |
| // Touch controls for mobile | |
| let touchStartX = 0; | |
| let touchStartY = 0; | |
| function handleTouchStart(e) { | |
| touchStartX = e.changedTouches[0].screenX; | |
| touchStartY = e.changedTouches[0].screenY; | |
| } | |
| function handleTouchEnd(e) { | |
| if (!gameRunning) return; | |
| const touchEndX = e.changedTouches[0].screenX; | |
| const touchEndY = e.changedTouches[0].screenY; | |
| const dx = touchEndX - touchStartX; | |
| const dy = touchEndY - touchStartY; | |
| // Determine swipe direction based on greatest change | |
| if (Math.abs(dx) > Math.abs(dy)) { | |
| if (dx > 0 && direction !== 'left') { | |
| nextDirection = 'right'; | |
| } else if (dx < 0 && direction !== 'right') { | |
| nextDirection = 'left'; | |
| } | |
| } else { | |
| if (dy > 0 && direction !== 'up') { | |
| nextDirection = 'down'; | |
| } else if (dy < 0 && direction !== 'down') { | |
| nextDirection = 'up'; | |
| } | |
| } | |
| } | |
| // Event listeners | |
| startBtn.addEventListener('click', startGame); | |
| restartBtn.addEventListener('click', startGame); | |
| document.addEventListener('keydown', handleKeyDown); | |
| gameBoard.addEventListener('touchstart', handleTouchStart, {passive: true}); | |
| gameBoard.addEventListener('touchend', handleTouchEnd, {passive: true}); | |
| // Mobile control buttons | |
| upBtn.addEventListener('touchstart', () => { if (direction !== 'down') nextDirection = 'up'; }); | |
| downBtn.addEventListener('touchstart', () => { if (direction !== 'up') nextDirection = 'down'; }); | |
| leftBtn.addEventListener('touchstart', () => { if (direction !== 'right') nextDirection = 'left'; }); | |
| rightBtn.addEventListener('touchstart', () => { if (direction !== 'left') nextDirection = 'right'; }); | |
| // Prevent default touch behavior | |
| [upBtn, downBtn, leftBtn, rightBtn].forEach(btn => { | |
| btn.addEventListener('touchend', (e) => e.preventDefault()); | |
| }); | |
| // Simple AI decision making | |
| function aiMove() { | |
| if (!gameRunning) return; | |
| const head = snake[0]; | |
| const nextHead = { ...head }; | |
| // Check all possible directions | |
| const possibleDirections = ['up', 'down', 'left', 'right']; | |
| const validDirections = possibleDirections.filter(dir => { | |
| // Don't reverse direction | |
| if ((dir === 'up' && direction === 'down') || | |
| (dir === 'down' && direction === 'up') || | |
| (dir === 'left' && direction === 'right') || | |
| (dir === 'right' && direction === 'left')) { | |
| return false; | |
| } | |
| // Check if direction is valid (not hitting wall or self) | |
| let testHead = {...head}; | |
| switch(dir) { | |
| case 'up': testHead.y--; break; | |
| case 'down': testHead.y++; break; | |
| case 'left': testHead.x--; break; | |
| case 'right': testHead.x++; break; | |
| } | |
| // Check boundaries | |
| if (testHead.x < 0 || testHead.x >= GRID_SIZE || | |
| testHead.y < 0 || testHead.y >= GRID_SIZE) { | |
| return false; | |
| } | |
| // Check collision with self | |
| if (snake.some(segment => segment.x === testHead.x && segment.y === testHead.y)) { | |
| return false; | |
| } | |
| return true; | |
| }); | |
| // If no valid directions (shouldn't happen in normal play), keep current direction | |
| if (validDirections.length === 0) return; | |
| // Simple goal: move towards food | |
| const dx = food.x - head.x; | |
| const dy = food.y - head.y; | |
| // Prefer directions that move towards food | |
| validDirections.sort((a, b) => { | |
| let scoreA = 0, scoreB = 0; | |
| // Test next position for direction a | |
| let testA = {...head}; | |
| switch(a) { | |
| case 'up': testA.y--; break; | |
| case 'down': testA.y++; break; | |
| case 'left': testA.x--; break; | |
| case 'right': testA.x++; break; | |
| } | |
| // Test next position for direction b | |
| let testB = {...head}; | |
| switch(b) { | |
| case 'up': testB.y--; break; | |
| case 'down': testB.y++; break; | |
| case 'left': testB.x--; break; | |
| case 'right': testB.x++; break; | |
| } | |
| // Score based on distance to food | |
| scoreA = Math.abs(food.x - testA.x) + Math.abs(food.y - testA.y); | |
| scoreB = Math.abs(food.x - testB.x) + Math.abs(food.y - testB.y); | |
| return scoreA - scoreB; | |
| }); | |
| // Choose the best direction (shortest distance to food) | |
| nextDirection = validDirections[0]; | |
| } | |
| // Initialize game | |
| initGameBoard(); | |
| // AI/Human mode toggle | |
| let aiMode = false; | |
| const aiBtn = document.getElementById('ai-btn'); | |
| const humanBtn = document.getElementById('human-btn'); | |
| aiBtn.addEventListener('click', () => { | |
| aiMode = true; | |
| aiBtn.classList.add('bg-indigo-700'); | |
| humanBtn.classList.remove('bg-indigo-700'); | |
| if (!gameRunning) startGame(); | |
| }); | |
| humanBtn.addEventListener('click', () => { | |
| aiMode = false; | |
| humanBtn.classList.add('bg-indigo-700'); | |
| aiBtn.classList.remove('bg-indigo-700'); | |
| }); | |
| // Modify game loop to include AI movement | |
| const originalGameLoop = gameLoop; | |
| gameLoop = function() { | |
| if (aiMode) { | |
| aiMove(); | |
| } | |
| originalGameLoop(); | |
| }; | |
| }); | |
| </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=Hackavist/simplesnake" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |