RetroCobra / index.html
Calmeida's picture
undefined - Initial Deployment
eb4c051 verified
Raw
History Blame Contribute Delete
21.2 kB
<!DOCTYPE html>
<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>