Spaces:
Running
Running
| <html lang="pt"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Cobra Matemática - Jogo Retro</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <style> | |
| @font-face { | |
| font-family: 'Press Start 2P'; | |
| src: url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap'); | |
| } | |
| body { | |
| font-family: 'Press Start 2P', cursive; | |
| background-color: #0f172a; | |
| color: #f8fafc; | |
| overflow: hidden; | |
| touch-action: none; | |
| user-select: none; | |
| } | |
| .game-container { | |
| position: relative; | |
| width: 100%; | |
| max-width: 512px; | |
| margin: 0 auto; | |
| } | |
| .game-board { | |
| position: relative; | |
| width: 100%; | |
| height: 0; | |
| padding-bottom: 100%; | |
| background-color: #1e293b; | |
| border: 4px solid #475569; | |
| box-shadow: 0 0 20px rgba(0, 0, 0, 0.5); | |
| overflow: hidden; | |
| } | |
| .pixel { | |
| position: absolute; | |
| width: 16px; | |
| height: 16px; | |
| box-sizing: border-box; | |
| } | |
| .snake { | |
| background-color: #84cc16; | |
| border: 1px solid #65a30d; | |
| z-index: 2; | |
| } | |
| .snake-head { | |
| background-color: #f59e0b; | |
| border: 1px solid #d97706; | |
| z-index: 3; | |
| } | |
| .food { | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 10px; | |
| color: white; | |
| z-index: 1; | |
| animation: pulse 0.5s infinite alternate; | |
| } | |
| .positive { | |
| background-color: #ef4444; | |
| border: 1px solid #dc2626; | |
| } | |
| .negative { | |
| background-color: #3b82f6; | |
| border: 1px solid #2563eb; | |
| } | |
| .flash { | |
| animation: flash 0.3s; | |
| } | |
| .shake { | |
| animation: shake 0.5s; | |
| } | |
| @keyframes pulse { | |
| from { transform: scale(1); } | |
| to { transform: scale(1.1); } | |
| } | |
| @keyframes flash { | |
| 0% { background-color: #1e293b; } | |
| 50% { background-color: #475569; } | |
| 100% { background-color: #1e293b; } | |
| } | |
| @keyframes shake { | |
| 0% { transform: translateX(0); } | |
| 25% { transform: translateX(-5px); } | |
| 50% { transform: translateX(5px); } | |
| 75% { transform: translateX(-5px); } | |
| 100% { transform: translateX(0); } | |
| } | |
| .controls { | |
| display: flex; | |
| justify-content: center; | |
| gap: 10px; | |
| margin-top: 20px; | |
| } | |
| .btn { | |
| padding: 8px 16px; | |
| background-color: #475569; | |
| color: white; | |
| border: 2px solid #64748b; | |
| font-family: 'Press Start 2P', cursive; | |
| font-size: 12px; | |
| cursor: pointer; | |
| transition: all 0.2s; | |
| } | |
| .btn:hover { | |
| background-color: #64748b; | |
| transform: translateY(-2px); | |
| } | |
| .modal { | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background-color: rgba(0, 0, 0, 0.8); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| z-index: 100; | |
| } | |
| .modal-content { | |
| background-color: #1e293b; | |
| border: 4px solid #475569; | |
| padding: 20px; | |
| max-width: 400px; | |
| width: 90%; | |
| text-align: center; | |
| } | |
| @media (max-width: 640px) { | |
| .btn { | |
| font-size: 10px; | |
| padding: 6px 12px; | |
| } | |
| .score-display { | |
| font-size: 14px; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body class="flex flex-col items-center justify-center min-h-screen p-4"> | |
| <h1 class="text-2xl md:text-3xl mb-4 text-center text-green-400">COBRA MATEMÁTICA</h1> | |
| <div class="game-container"> | |
| <div class="flex justify-between items-center mb-2"> | |
| <div class="score-display"> | |
| Pontos: <span id="score">0</span> | |
| </div> | |
| <div class="score-display"> | |
| Recorde: <span id="high-score">0</span> | |
| </div> | |
| </div> | |
| <div class="game-board" id="game-board"> | |
| <!-- Game elements will be added here by JavaScript --> | |
| </div> | |
| <div class="controls"> | |
| <button class="btn" id="pause-btn">Pausar (P)</button> | |
| <button class="btn" id="restart-btn">Reiniciar (R)</button> | |
| </div> | |
| </div> | |
| <div class="modal hidden" id="start-modal"> | |
| <div class="modal-content"> | |
| <h2 class="text-xl mb-4 text-green-400">COBRA MATEMÁTICA</h2> | |
| <p class="mb-4 text-sm">Use as setas para mover a cobra. Coma números positivos para crescer e negativos para encolher!</p> | |
| <button class="btn" id="start-btn">COMEÇAR</button> | |
| </div> | |
| </div> | |
| <div class="modal hidden" id="game-over-modal"> | |
| <div class="modal-content"> | |
| <h2 class="text-xl mb-4 text-red-400">FIM DE JOGO!</h2> | |
| <p class="mb-2">Pontuação: <span id="final-score">0</span></p> | |
| <p class="mb-4">Recorde: <span id="final-high-score">0</span></p> | |
| <button class="btn" id="restart-modal-btn">JOGAR NOVAMENTE</button> | |
| </div> | |
| </div> | |
| <audio id="eat-sound" preload="auto"> | |
| <source src="https://assets.mixkit.co/sfx/preview/mixkit-arcade-game-jump-coin-216.mp3" type="audio/mpeg"> | |
| </audio> | |
| <audio id="crash-sound" preload="auto"> | |
| <source src="https://assets.mixkit.co/sfx/preview/mixkit-retro-arcade-lose-2027.mp3" type="audio/mpeg"> | |
| </audio> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', () => { | |
| // Game constants | |
| const GRID_SIZE = 32; | |
| const CELL_SIZE = 16; | |
| const INITIAL_SPEED = 150; | |
| const SPEED_INCREMENT = 5; | |
| const SPEED_CHANGE_INTERVAL = 10; | |
| // Game variables | |
| let snake = []; | |
| let food = {}; | |
| let direction = 'right'; | |
| let nextDirection = 'right'; | |
| let gameInterval; | |
| let score = 0; | |
| let highScore = localStorage.getItem('snakeHighScore') || 0; | |
| let foodCount = 0; | |
| let speed = INITIAL_SPEED; | |
| let isPaused = false; | |
| let gameStarted = false; | |
| // DOM elements | |
| const gameBoard = document.getElementById('game-board'); | |
| const scoreDisplay = document.getElementById('score'); | |
| const highScoreDisplay = document.getElementById('high-score'); | |
| const finalScoreDisplay = document.getElementById('final-score'); | |
| const finalHighScoreDisplay = document.getElementById('final-high-score'); | |
| const startModal = document.getElementById('start-modal'); | |
| const gameOverModal = document.getElementById('game-over-modal'); | |
| const startBtn = document.getElementById('start-btn'); | |
| const pauseBtn = document.getElementById('pause-btn'); | |
| const restartBtn = document.getElementById('restart-btn'); | |
| const restartModalBtn = document.getElementById('restart-modal-btn'); | |
| const eatSound = document.getElementById('eat-sound'); | |
| const crashSound = document.getElementById('crash-sound'); | |
| // Initialize game board | |
| gameBoard.style.height = `${GRID_SIZE * CELL_SIZE}px`; | |
| gameBoard.style.width = `${GRID_SIZE * CELL_SIZE}px`; | |
| // Update displays | |
| highScoreDisplay.textContent = highScore; | |
| // Event listeners | |
| startBtn.addEventListener('click', startGame); | |
| pauseBtn.addEventListener('click', togglePause); | |
| restartBtn.addEventListener('click', resetGame); | |
| restartModalBtn.addEventListener('click', resetGame); | |
| document.addEventListener('keydown', (e) => { | |
| if (!gameStarted) return; | |
| 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 'p': | |
| case 'P': | |
| togglePause(); | |
| break; | |
| case 'r': | |
| case 'R': | |
| resetGame(); | |
| break; | |
| } | |
| }); | |
| // Touch controls for mobile | |
| const handleSwipe = (() => { | |
| let touchStartX = 0; | |
| let touchStartY = 0; | |
| let touchEndX = 0; | |
| let touchEndY = 0; | |
| const handleTouchStart = (e) => { | |
| touchStartX = e.changedTouches[0].screenX; | |
| touchStartY = e.changedTouches[0].screenY; | |
| }; | |
| const handleTouchEnd = (e) => { | |
| if (!gameStarted) return; | |
| touchEndX = e.changedTouches[0].screenX; | |
| touchEndY = e.changedTouches[0].screenY; | |
| 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'; | |
| } | |
| } | |
| }; | |
| return { | |
| init: () => { | |
| gameBoard.addEventListener('touchstart', handleTouchStart, false); | |
| gameBoard.addEventListener('touchend', handleTouchEnd, false); | |
| } | |
| }; | |
| })(); | |
| handleSwipe.init(); | |
| // Game functions | |
| function startGame() { | |
| startModal.classList.add('hidden'); | |
| resetGame(); | |
| gameStarted = true; | |
| } | |
| function resetGame() { | |
| // Clear the board | |
| gameBoard.innerHTML = ''; | |
| gameOverModal.classList.add('hidden'); | |
| // Reset game variables | |
| snake = [ | |
| {x: 5, y: 15}, | |
| {x: 4, y: 15}, | |
| {x: 3, y: 15} | |
| ]; | |
| direction = 'right'; | |
| nextDirection = 'right'; | |
| score = 0; | |
| foodCount = 0; | |
| speed = INITIAL_SPEED; | |
| isPaused = false; | |
| // Update displays | |
| scoreDisplay.textContent = score; | |
| pauseBtn.textContent = 'Pausar (P)'; | |
| // Create initial food | |
| createFood(); | |
| // Start game loop | |
| clearInterval(gameInterval); | |
| gameInterval = setInterval(gameLoop, speed); | |
| gameStarted = true; | |
| } | |
| function togglePause() { | |
| if (!gameStarted) return; | |
| isPaused = !isPaused; | |
| if (isPaused) { | |
| clearInterval(gameInterval); | |
| pauseBtn.textContent = 'Continuar (P)'; | |
| gameBoard.classList.add('opacity-50'); | |
| } else { | |
| gameInterval = setInterval(gameLoop, speed); | |
| pauseBtn.textContent = 'Pausar (P)'; | |
| gameBoard.classList.remove('opacity-50'); | |
| } | |
| } | |
| function gameLoop() { | |
| if (isPaused) return; | |
| // Update 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 collision with walls | |
| if (head.x < 0 || head.x >= GRID_SIZE || head.y < 0 || head.y >= GRID_SIZE) { | |
| gameOver(); | |
| return; | |
| } | |
| // Check collision with self | |
| if (snake.some(segment => segment.x === head.x && segment.y === head.y)) { | |
| gameOver(); | |
| return; | |
| } | |
| // Add new head | |
| snake.unshift(head); | |
| // Check if snake ate food | |
| if (head.x === food.x && head.y === food.y) { | |
| // Play eat sound | |
| eatSound.currentTime = 0; | |
| eatSound.play(); | |
| // Update score | |
| score += food.value; | |
| scoreDisplay.textContent = score; | |
| // Flash effect | |
| gameBoard.classList.add('flash'); | |
| setTimeout(() => gameBoard.classList.remove('flash'), 300); | |
| // Handle snake growth/shrinking | |
| if (food.value > 0) { | |
| // Positive number - grow | |
| for (let i = 0; i < food.value - 1; i++) { | |
| snake.push({...snake[snake.length - 1]}); | |
| } | |
| } else { | |
| // Negative number - shrink | |
| const segmentsToRemove = Math.abs(food.value); | |
| if (snake.length > segmentsToRemove) { | |
| snake.splice(-segmentsToRemove); | |
| } else { | |
| // If snake would become too small, game over | |
| gameOver(); | |
| return; | |
| } | |
| // Shake effect | |
| gameBoard.classList.add('shake'); | |
| setTimeout(() => gameBoard.classList.remove('shake'), 500); | |
| } | |
| // Create new food | |
| createFood(); | |
| // Increase speed every 10 foods | |
| foodCount++; | |
| if (foodCount % SPEED_CHANGE_INTERVAL === 0) { | |
| speed = Math.max(50, speed - SPEED_INCREMENT); | |
| clearInterval(gameInterval); | |
| gameInterval = setInterval(gameLoop, speed); | |
| } | |
| } else { | |
| // Remove tail if no food was eaten | |
| snake.pop(); | |
| } | |
| // Render game | |
| render(); | |
| } | |
| function createFood() { | |
| // Find all empty positions | |
| const emptyPositions = []; | |
| 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)) { | |
| emptyPositions.push({x, y}); | |
| } | |
| } | |
| } | |
| // If no empty positions, game over (win) | |
| if (emptyPositions.length === 0) { | |
| gameOver(); | |
| return; | |
| } | |
| // Choose random empty position | |
| const randomPosition = emptyPositions[Math.floor(Math.random() * emptyPositions.length)]; | |
| // Generate random number (-5 to 5, excluding 0) | |
| let value; | |
| do { | |
| value = Math.floor(Math.random() * 11) - 5; | |
| } while (value === 0); | |
| food = { | |
| x: randomPosition.x, | |
| y: randomPosition.y, | |
| value: value | |
| }; | |
| } | |
| function render() { | |
| // Clear board | |
| gameBoard.innerHTML = ''; | |
| // Render snake | |
| snake.forEach((segment, index) => { | |
| const segmentElement = document.createElement('div'); | |
| segmentElement.className = `pixel ${index === 0 ? 'snake-head' : 'snake'}`; | |
| segmentElement.style.left = `${segment.x * CELL_SIZE}px`; | |
| segmentElement.style.top = `${segment.y * CELL_SIZE}px`; | |
| gameBoard.appendChild(segmentElement); | |
| }); | |
| // Render food | |
| const foodElement = document.createElement('div'); | |
| foodElement.className = `pixel food ${food.value > 0 ? 'positive' : 'negative'}`; | |
| foodElement.style.left = `${food.x * CELL_SIZE}px`; | |
| foodElement.style.top = `${food.y * CELL_SIZE}px`; | |
| foodElement.textContent = food.value > 0 ? `+${food.value}` : food.value; | |
| gameBoard.appendChild(foodElement); | |
| } | |
| function gameOver() { | |
| // Play crash sound | |
| crashSound.currentTime = 0; | |
| crashSound.play(); | |
| // Stop game | |
| clearInterval(gameInterval); | |
| gameStarted = false; | |
| // Update high score | |
| if (score > highScore) { | |
| highScore = score; | |
| localStorage.setItem('snakeHighScore', highScore); | |
| highScoreDisplay.textContent = highScore; | |
| } | |
| // Show game over modal | |
| finalScoreDisplay.textContent = score; | |
| finalHighScoreDisplay.textContent = highScore; | |
| gameOverModal.classList.remove('hidden'); | |
| } | |
| // Show start modal initially | |
| startModal.classList.remove('hidden'); | |
| }); | |
| </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=Calmeida/my-playground" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |