Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Moving Brick Attacker</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| #gameCanvas { | |
| background-color: #1a202c; | |
| border-radius: 8px; | |
| box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | |
| touch-action: none; | |
| } | |
| .game-container { | |
| position: relative; | |
| } | |
| .game-overlay { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| display: flex; | |
| flex-direction: column; | |
| justify-content: center; | |
| align-items: center; | |
| background-color: rgba(0, 0, 0, 0.7); | |
| color: white; | |
| border-radius: 8px; | |
| } | |
| .hidden { | |
| display: none; | |
| } | |
| .brick { | |
| border-radius: 2px; | |
| } | |
| .heart-icon { | |
| color: #f56565; | |
| } | |
| .bullet { | |
| position: absolute; | |
| width: 4px; | |
| height: 12px; | |
| background-color: #f6e05e; | |
| border-radius: 2px; | |
| } | |
| #attacker { | |
| position: absolute; | |
| width: 50px; | |
| height: 80px; | |
| background-color: #4a5568; | |
| border-radius: 8px; | |
| bottom: 10px; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| z-index: 10; | |
| } | |
| #gun { | |
| position: absolute; | |
| width: 30px; | |
| height: 10px; | |
| background-color: #2d3748; | |
| top: -10px; | |
| left: 10px; | |
| border-radius: 2px; | |
| transform-origin: left center; | |
| z-index: 20; | |
| } | |
| #gun-barrel { | |
| position: absolute; | |
| width: 30px; | |
| height: 4px; | |
| background-color: #2d3748; | |
| top: 3px; | |
| left: 30px; | |
| border-radius: 2px; | |
| } | |
| /* Animation for special bricks */ | |
| @keyframes pulse { | |
| 0% { transform: scale(1); } | |
| 50% { transform: scale(1.05); } | |
| 100% { transform: scale(1); } | |
| } | |
| @keyframes spin { | |
| 0% { transform: rotate(0deg); } | |
| 100% { transform: rotate(360deg); } | |
| } | |
| .start-btn { | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| border: none; | |
| color: white; | |
| padding: 15px 32px; | |
| text-align: center; | |
| text-decoration: none; | |
| display: inline-block; | |
| font-size: 18px; | |
| font-weight: bold; | |
| margin: 10px 2px; | |
| cursor: pointer; | |
| border-radius: 50px; | |
| box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2); | |
| transition: all 0.3s ease; | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .start-btn:hover { | |
| transform: translateY(-3px); | |
| box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3); | |
| } | |
| .start-btn:active { | |
| transform: translateY(1px); | |
| } | |
| .start-btn::after { | |
| content: ""; | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background: linear-gradient(135deg, rgba(255,255,255,0.3) 0%, rgba(255,255,255,0) 100%); | |
| opacity: 0; | |
| transition: opacity 0.3s ease; | |
| } | |
| .start-btn:hover::after { | |
| opacity: 1; | |
| } | |
| .start-btn i { | |
| margin-right: 10px; | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-900 min-h-screen flex flex-col items-center justify-center p-4"> | |
| <div class="max-w-4xl w-full"> | |
| <h1 class="text-3xl font-bold text-center text-white mb-6">Moving Brick Attacker</h1> | |
| <div class="flex justify-between items-center mb-4"> | |
| <div class="flex items-center space-x-2"> | |
| <span class="text-white font-semibold">Score:</span> | |
| <span id="score" class="text-yellow-300 font-bold text-xl">0</span> | |
| </div> | |
| <div class="flex items-center space-x-2"> | |
| <span class="text-white font-semibold">Lives:</span> | |
| <div id="lives" class="flex space-x-1"> | |
| <i class="fas fa-heart heart-icon"></i> | |
| <i class="fas fa-heart heart-icon"></i> | |
| <i class="fas fa-heart heart-icon"></i> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="game-container relative"> | |
| <canvas id="gameCanvas" width="800" height="500" class="w-full"></canvas> | |
| <div id="startScreen" class="game-overlay"> | |
| <h2 class="text-4xl font-bold mb-6 text-yellow-300">Brick Attacker</h2> | |
| <p class="text-xl mb-8 text-center max-w-md">Shoot the moving bricks before they reach the bottom!</p> | |
| <button id="startButton" class="start-btn"> | |
| <i class="fas fa-play"></i> Start Game | |
| </button> | |
| </div> | |
| <div id="gameOverScreen" class="game-overlay hidden"> | |
| <h2 class="text-4xl font-bold mb-4 text-red-500">Game Over</h2> | |
| <p class="text-2xl mb-2">Your score: <span id="finalScore" class="text-yellow-300">0</span></p> | |
| <button id="restartButton" class="start-btn mt-6"> | |
| <i class="fas fa-redo"></i> Play Again | |
| </button> | |
| </div> | |
| </div> | |
| <div class="mt-6 flex justify-center"> | |
| <div class="bg-gray-800 p-4 rounded-lg max-w-md"> | |
| <h3 class="text-white font-semibold mb-2">Controls:</h3> | |
| <ul class="text-gray-300 space-y-1"> | |
| <li><i class="fas fa-mouse-pointer mr-2"></i> Move mouse to aim</li> | |
| <li><i class="fas fa-mouse mr-2"></i> Click to shoot</li> | |
| <li><i class="fas fa-arrows-alt mr-2"></i> Arrow keys to move</li> | |
| </ul> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // Game variables | |
| const canvas = document.getElementById('gameCanvas'); | |
| const ctx = canvas.getContext('2d'); | |
| const startScreen = document.getElementById('startScreen'); | |
| const gameOverScreen = document.getElementById('gameOverScreen'); | |
| const startButton = document.getElementById('startButton'); | |
| const restartButton = document.getElementById('restartButton'); | |
| const scoreElement = document.getElementById('score'); | |
| const finalScoreElement = document.getElementById('finalScore'); | |
| const livesElement = document.getElementById('lives'); | |
| let gameRunning = false; | |
| let score = 0; | |
| let lives = 3; | |
| let bricks = []; | |
| let bullets = []; | |
| let attackerX = canvas.width / 2; | |
| let attackerWidth = 50; | |
| let gunAngle = 0; | |
| let mouseX = 0; | |
| let mouseY = 0; | |
| let animationId; | |
| let brickSpeed = 2; | |
| let brickSpawnRate = 60; | |
| let frameCount = 0; | |
| // Attacker element (for visual representation) | |
| const attacker = document.createElement('div'); | |
| attacker.id = 'attacker'; | |
| document.querySelector('.game-container').appendChild(attacker); | |
| const gun = document.createElement('div'); | |
| gun.id = 'gun'; | |
| attacker.appendChild(gun); | |
| const gunBarrel = document.createElement('div'); | |
| gunBarrel.id = 'gun-barrel'; | |
| gun.appendChild(gunBarrel); | |
| // Event listeners | |
| startButton.addEventListener('click', startGame); | |
| restartButton.addEventListener('click', startGame); | |
| canvas.addEventListener('mousemove', (e) => { | |
| const rect = canvas.getBoundingClientRect(); | |
| mouseX = e.clientX - rect.left; | |
| mouseY = e.clientY - rect.top; | |
| // Calculate gun angle based on mouse position | |
| const dx = mouseX - (attackerX + attackerWidth / 2); | |
| const dy = mouseY - (canvas.height - 90); | |
| gunAngle = Math.atan2(dy, dx); | |
| // Update gun position and rotation | |
| gun.style.transform = `rotate(${gunAngle}rad)`; | |
| }); | |
| canvas.addEventListener('click', shoot); | |
| document.addEventListener('keydown', (e) => { | |
| if (!gameRunning) return; | |
| if (e.key === 'ArrowLeft') { | |
| attackerX = Math.max(0, attackerX - 20); | |
| } else if (e.key === 'ArrowRight') { | |
| attackerX = Math.min(canvas.width - attackerWidth, attackerX + 20); | |
| } | |
| }); | |
| // Game functions | |
| function startGame() { | |
| gameRunning = true; | |
| score = 0; | |
| lives = 3; | |
| bricks = []; | |
| bullets = []; | |
| brickSpeed = 2; | |
| brickSpawnRate = 60; | |
| frameCount = 0; | |
| scoreElement.textContent = score; | |
| updateLives(); | |
| startScreen.classList.add('hidden'); | |
| gameOverScreen.classList.add('hidden'); | |
| // Reset attacker position | |
| attackerX = canvas.width / 2 - attackerWidth / 2; | |
| // Start game loop | |
| animationId = requestAnimationFrame(gameLoop); | |
| } | |
| function gameLoop() { | |
| if (!gameRunning) return; | |
| // Clear canvas | |
| ctx.clearRect(0, 0, canvas.width, canvas.height); | |
| // Update attacker position | |
| attacker.style.left = `${attackerX}px`; | |
| // Spawn bricks | |
| if (frameCount % brickSpawnRate === 0) { | |
| spawnBrick(); | |
| } | |
| // Update bricks | |
| updateBricks(); | |
| // Update bullets | |
| updateBullets(); | |
| // Check collisions | |
| checkCollisions(); | |
| // Increase difficulty | |
| if (frameCount % 500 === 0) { | |
| brickSpeed = Math.min(brickSpeed + 0.2, 6); | |
| brickSpawnRate = Math.max(brickSpawnRate - 5, 20); | |
| } | |
| frameCount++; | |
| animationId = requestAnimationFrame(gameLoop); | |
| } | |
| function spawnBrick() { | |
| const width = Math.random() * 60 + 40; | |
| const x = Math.random() * (canvas.width - width); | |
| const color = getRandomColor(); | |
| // 10% chance for special brick | |
| const isSpecial = Math.random() < 0.1; | |
| bricks.push({ | |
| x, | |
| y: 0, | |
| width, | |
| height: 20, | |
| color, | |
| isSpecial, | |
| health: isSpecial ? 3 : 1 | |
| }); | |
| } | |
| function getRandomColor() { | |
| const colors = [ | |
| '#f56565', // red | |
| '#48bb78', // green | |
| '#4299e1', // blue | |
| '#ed8936', // orange | |
| '#9f7aea', // purple | |
| '#ecc94b' // yellow | |
| ]; | |
| return colors[Math.floor(Math.random() * colors.length)]; | |
| } | |
| function updateBricks() { | |
| for (let i = bricks.length - 1; i >= 0; i--) { | |
| const brick = bricks[i]; | |
| // Move brick down | |
| brick.y += brickSpeed; | |
| // Draw brick | |
| ctx.fillStyle = brick.color; | |
| ctx.fillRect(brick.x, brick.y, brick.width, brick.height); | |
| // Draw special effects | |
| if (brick.isSpecial) { | |
| ctx.strokeStyle = '#ffffff'; | |
| ctx.lineWidth = 2; | |
| ctx.strokeRect(brick.x, brick.y, brick.width, brick.height); | |
| // Draw health indicator | |
| ctx.fillStyle = '#ffffff'; | |
| ctx.font = '10px Arial'; | |
| ctx.fillText('★'.repeat(brick.health), brick.x + 5, brick.y + 15); | |
| } | |
| // Remove bricks that go off screen | |
| if (brick.y > canvas.height) { | |
| bricks.splice(i, 1); | |
| loseLife(); | |
| } | |
| } | |
| } | |
| function updateBullets() { | |
| for (let i = bullets.length - 1; i >= 0; i--) { | |
| const bullet = bullets[i]; | |
| // Move bullet | |
| bullet.x += bullet.dx * 10; | |
| bullet.y += bullet.dy * 10; | |
| // Draw bullet | |
| const bulletElement = document.createElement('div'); | |
| bulletElement.className = 'bullet'; | |
| bulletElement.style.left = `${bullet.x}px`; | |
| bulletElement.style.top = `${bullet.y}px`; | |
| document.querySelector('.game-container').appendChild(bulletElement); | |
| // Remove bullet after a short delay to allow animation | |
| setTimeout(() => { | |
| if (bulletElement.parentNode) { | |
| bulletElement.parentNode.removeChild(bulletElement); | |
| } | |
| }, 16); | |
| // Remove bullets that go off screen | |
| if (bullet.x < 0 || bullet.x > canvas.width || | |
| bullet.y < 0 || bullet.y > canvas.height) { | |
| bullets.splice(i, 1); | |
| } | |
| } | |
| } | |
| function checkCollisions() { | |
| for (let i = bullets.length - 1; i >= 0; i--) { | |
| const bullet = bullets[i]; | |
| for (let j = bricks.length - 1; j >= 0; j--) { | |
| const brick = bricks[j]; | |
| if (bullet.x > brick.x && bullet.x < brick.x + brick.width && | |
| bullet.y > brick.y && bullet.y < brick.y + brick.height) { | |
| // Hit brick | |
| brick.health--; | |
| if (brick.health <= 0) { | |
| // Brick destroyed | |
| bricks.splice(j, 1); | |
| score += brick.isSpecial ? 5 : 1; | |
| scoreElement.textContent = score; | |
| } | |
| // Remove bullet | |
| bullets.splice(i, 1); | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| function shoot() { | |
| if (!gameRunning) return; | |
| const startX = attackerX + attackerWidth / 2; | |
| const startY = canvas.height - 90; | |
| bullets.push({ | |
| x: startX, | |
| y: startY, | |
| dx: Math.cos(gunAngle), | |
| dy: Math.sin(gunAngle) | |
| }); | |
| } | |
| function loseLife() { | |
| lives--; | |
| updateLives(); | |
| if (lives <= 0) { | |
| gameOver(); | |
| } | |
| } | |
| function updateLives() { | |
| livesElement.innerHTML = ''; | |
| for (let i = 0; i < lives; i++) { | |
| const heart = document.createElement('i'); | |
| heart.className = 'fas fa-heart heart-icon'; | |
| livesElement.appendChild(heart); | |
| } | |
| } | |
| function gameOver() { | |
| gameRunning = false; | |
| cancelAnimationFrame(animationId); | |
| finalScoreElement.textContent = score; | |
| gameOverScreen.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=Mohabedalgani/zaido" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |