Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Breakout Game</title> | |
| <style> | |
| body { | |
| margin: 0; | |
| padding: 20px; | |
| background: linear-gradient(135deg, #1a1a2e, #16213e, #0f3460); | |
| font-family: 'Arial', sans-serif; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| min-height: 100vh; | |
| color: white; | |
| } | |
| .game-container { | |
| text-align: center; | |
| background: rgba(255, 255, 255, 0.1); | |
| backdrop-filter: blur(10px); | |
| border-radius: 20px; | |
| padding: 20px; | |
| box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); | |
| position: relative; | |
| } | |
| canvas { | |
| border: 3px solid #00d4ff; | |
| border-radius: 10px; | |
| box-shadow: 0 0 20px rgba(0, 212, 255, 0.3); | |
| background: linear-gradient(180deg, #0a0a0a, #1a1a1a); | |
| } | |
| .ui { | |
| margin-top: 15px; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| font-size: 18px; | |
| font-weight: bold; | |
| } | |
| .controls { | |
| margin-top: 10px; | |
| font-size: 14px; | |
| opacity: 0.8; | |
| } | |
| .powerup-info { | |
| margin-top: 10px; | |
| font-size: 14px; | |
| opacity: 0.7; | |
| } | |
| .game-over, .game-won { | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| background: rgba(0, 0, 0, 0.9); | |
| padding: 30px; | |
| border-radius: 15px; | |
| text-align: center; | |
| display: none; | |
| border: 2px solid #00d4ff; | |
| z-index: 10; | |
| } | |
| .restart-btn { | |
| background: linear-gradient(45deg, #00d4ff, #0099cc); | |
| border: none; | |
| padding: 12px 24px; | |
| border-radius: 8px; | |
| color: white; | |
| font-size: 16px; | |
| cursor: pointer; | |
| margin-top: 15px; | |
| transition: transform 0.2s; | |
| } | |
| .restart-btn:hover { | |
| transform: scale(1.05); | |
| } | |
| .upgrade-menu { | |
| position: fixed; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| background: rgba(0, 0, 0, 0.95); | |
| padding: 25px; | |
| border-radius: 15px; | |
| text-align: center; | |
| border: 2px solid #00d4ff; | |
| display: none; | |
| z-index: 10; | |
| } | |
| .upgrade-option { | |
| margin: 15px 0; | |
| padding: 10px 20px; | |
| background: rgba(255, 255, 255, 0.1); | |
| border-radius: 8px; | |
| cursor: pointer; | |
| transition: transform 0.2s; | |
| } | |
| .upgrade-option:hover { | |
| transform: scale(1.05); | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="game-container"> | |
| <canvas id="gameCanvas" width="800" height="600"></canvas> | |
| <div class="ui"> | |
| <div>Score: <span id="score">0</span></div> | |
| <div>Lives: <span id="lives">3</span></div> | |
| <div>Level: <span id="level">1</span></div> | |
| </div> | |
| <div class="controls"> | |
| Use A/D or Arrow Keys to move paddle • Click to start/restart | |
| </div> | |
| <div class="powerup-info" id="powerupInfo"> | |
| Powerups: <span id="powerups">None active</span> | |
| </div> | |
| <div class="game-over" id="gameOver"> | |
| <h2>Game Over!</h2> | |
| <p>Final Score: <span id="finalScore">0</span></p> | |
| <button class="restart-btn" onclick="restartGame()">Play Again</button> | |
| </div> | |
| <div class="game-won" id="gameWon"> | |
| <h2>Congratulations!</h2> | |
| <p>You cleared all blocks!</p> | |
| <p>Final Score: <span id="winScore">0</span></p> | |
| <button class="restart-btn" onclick="showUpgradeMenu()">Next Level</button> | |
| </div> | |
| <div class="upgrade-menu" id="upgradeMenu"> | |
| <h2>Select Upgrade</h2> | |
| <div class="upgrade-option" onclick="selectUpgrade('ballSpeed')">Increase Ball Speed</div> | |
| <div class="upgrade-option" onclick="selectUpgrade('paddleWidth')">Wider Paddle</div> | |
| <div class="upgrade-option" onclick="selectUpgrade('extraLife')">Extra Life</div> | |
| </div> | |
| </div> | |
| <script> | |
| const canvas = document.getElementById('gameCanvas'); | |
| const ctx = canvas.getContext('2d'); | |
| // Game state | |
| let gameState = 'waiting'; // waiting, playing, gameOver, won, upgrading | |
| let score = 0; | |
| let lives = 3; | |
| let level = 1; | |
| let coins = 0; | |
| // Paddle | |
| const paddle = { | |
| x: canvas.width / 2 - 60, | |
| y: canvas.height - 30, | |
| width: 120, | |
| height: 15, | |
| speed: 8, | |
| originalWidth: 120 | |
| }; | |
| // Ball | |
| const ball = { | |
| x: canvas.width / 2, | |
| y: canvas.height / 2, | |
| radius: 8, | |
| dx: 4, | |
| dy: -4, | |
| baseSpeed: 4, | |
| speed: 4 | |
| }; | |
| // Blocks | |
| let blocks = []; | |
| const blockRows = 6; | |
| const blockCols = 10; | |
| const blockColors = ['#ff6b6b', '#4ecdc4', '#45b7d1', '#96ceb4', '#feca57', '#ff9ff3']; | |
| // Powerups | |
| let powerups = []; | |
| let activePowerups = []; | |
| let powerupInterval = null; | |
| // Particles | |
| let particles = []; | |
| // Input handling | |
| const keys = {}; | |
| document.addEventListener('keydown', (e) => { | |
| keys[e.key.toLowerCase()] = true; | |
| }); | |
| document.addEventListener('keyup', (e) => { | |
| keys[e.key.toLowerCase()] = false; | |
| }); | |
| canvas.addEventListener('click', () => { | |
| if (gameState === 'waiting') { | |
| startGame(); | |
| } | |
| }); | |
| // Initialize blocks | |
| function createBlocks() { | |
| blocks = []; | |
| const blockWidth = canvas.width / blockCols; | |
| const blockHeight = 25; | |
| for (let row = 0; row < blockRows; row++) { | |
| for (let col = 0; col < blockCols; col++) { | |
| blocks.push({ | |
| x: col * blockWidth, | |
| y: row * blockHeight + 60, | |
| width: blockWidth - 2, | |
| height: blockHeight - 2, | |
| color: blockColors[row], | |
| points: (blockRows - row) * 10, | |
| visible: true, | |
| hasPowerup: Math.random() < 0.15 // 15% chance for powerup | |
| }); | |
| } | |
| } | |
| } | |
| // Start game | |
| function startGame() { | |
| gameState = 'playing'; | |
| ball.x = canvas.width / 2; | |
| ball.y = canvas.height / 2; | |
| ball.dx = (Math.random() > 0.5 ? 1 : -1) * ball.speed; | |
| ball.dy = -ball.speed; | |
| } | |
| // Update paddle | |
| function updatePaddle() { | |
| if (keys['arrowleft'] || keys['a']) { | |
| paddle.x = Math.max(0, paddle.x - paddle.speed); | |
| } | |
| if (keys['arrowright'] || keys['d']) { | |
| paddle.x = Math.min(canvas.width - paddle.width, paddle.x + paddle.speed); | |
| } | |
| } | |
| // Update ball | |
| function updateBall() { | |
| if (gameState !== 'playing') return; | |
| ball.x += ball.dx; | |
| ball.y += ball.dy; | |
| // Wall collisions | |
| if (ball.x <= ball.radius || ball.x >= canvas.width - ball.radius) { | |
| ball.dx = -ball.dx; | |
| ball.x = Math.max(ball.radius, Math.min(canvas.width - ball.radius, ball.x)); | |
| } | |
| if (ball.y <= ball.radius) { | |
| ball.dy = -ball.dy; | |
| ball.y = ball.radius; | |
| } | |
| // Paddle collision | |
| if (ball.y + ball.radius >= paddle.y && | |
| ball.x >= paddle.x && | |
| ball.x <= paddle.x + paddle.width && | |
| ball.dy > 0) { | |
| const hitPos = (ball.x - paddle.x) / paddle.width; | |
| const angle = (hitPos - 0.5) * Math.PI / 3; | |
| const speed = Math.sqrt(ball.dx * ball.dx + ball.dy * ball.dy); | |
| ball.dx = Math.sin(angle) * speed; | |
| ball.dy = -Math.abs(Math.cos(angle) * speed); | |
| ball.y = paddle.y - ball.radius; | |
| } | |
| // Block collisions | |
| for (let block of blocks) { | |
| if (!block.visible) continue; | |
| if (ball.x + ball.radius >= block.x && | |
| ball.x - ball.radius <= block.x + block.width && | |
| ball.y + ball.radius >= block.y && | |
| ball.y - ball.radius <= block.y + block.height) { | |
| block.visible = false; | |
| score += block.points; | |
| // Check if block had a powerup | |
| if (block.hasPowerup) { | |
| spawnPowerup(block.x + block.width/2, block.y + block.height/2); | |
| } | |
| // Determine collision side | |
| const ballCenterX = ball.x; | |
| const ballCenterY = ball.y; | |
| const blockCenterX = block.x + block.width / 2; | |
| const blockCenterY = block.y + block.height / 2; | |
| const deltaX = ballCenterX - blockCenterX; | |
| const deltaY = ballCenterY - blockCenterY; | |
| if (Math.abs(deltaX / block.width) > Math.abs(deltaY / block.height)) { | |
| ball.dx = -ball.dx; | |
| } else { | |
| ball.dy = -ball.dy; | |
| } | |
| // Create particle effect | |
| createParticles(block.x + block.width/2, block.y + block.height/2, block.color); | |
| break; | |
| } | |
| } | |
| // Ball out of bounds | |
| if (ball.y > canvas.height) { | |
| lives--; | |
| if (lives <= 0) { | |
| gameState = 'gameOver'; | |
| document.getElementById('finalScore').textContent = score; | |
| document.getElementById('gameOver').style.display = 'block'; | |
| } else { | |
| resetBallAndPaddle(); | |
| gameState = 'waiting'; | |
| } | |
| } | |
| // Check win condition | |
| if (blocks.every(block => !block.visible)) { | |
| gameState = 'won'; | |
| document.getElementById('winScore').textContent = score; | |
| document.getElementById('gameWon').style.display = 'block'; | |
| } | |
| } | |
| // Powerups | |
| function spawnPowerup(x, y) { | |
| const types = ['expandPaddle', 'fastBall', 'slowBall']; | |
| const type = types[Math.floor(Math.random() * types.length)]; | |
| powerups.push({ | |
| x: x, | |
| y: y, | |
| dy: 2, | |
| type: type, | |
| width: 20, | |
| height: 20, | |
| color: { | |
| 'expandPaddle': '#4ecdc4', | |
| 'fastBall': '#ff9ff3', | |
| 'slowBall': '#feca57' | |
| }[type] | |
| }); | |
| } | |
| function updatePowerups() { | |
| for (let i = powerups.length - 1; i >= 0; i--) { | |
| const p = powerups[i]; | |
| p.y += p.dy; | |
| // Paddle collision | |
| if (p.y + p.height >= paddle.y && | |
| p.x + p.width/2 >= paddle.x && | |
| p.x - p.width/2 <= paddle.x + paddle.width) { | |
| activatePowerup(p.type); | |
| powerups.splice(i, 1); | |
| continue; | |
| } | |
| // Remove off screen | |
| if (p.y > canvas.height) { | |
| powerups.splice(i, 1); | |
| } | |
| } | |
| } | |
| function activatePowerup(type) { | |
| let duration = 10000; // 10 seconds | |
| // Deactivate same type if already active | |
| activePowerups = activePowerups.filter(p => p.type !== type); | |
| activePowerups.push({type, timer: 0}); | |
| switch(type) { | |
| case 'expandPaddle': | |
| paddle.width = paddle.originalWidth * 1.5; | |
| setTimeout(() => { | |
| paddle.width = paddle.originalWidth; | |
| updateActivePowerupsDisplay(); | |
| }, duration); | |
| break; | |
| case 'fastBall': | |
| ball.speed *= 1.5; | |
| setTimeout(() => { | |
| ball.speed /= 1.5; | |
| updateActivePowerupsDisplay(); | |
| }, duration); | |
| break; | |
| case 'slowBall': | |
| ball.speed *= 0.66; | |
| setTimeout(() => { | |
| ball.speed /= 0.66; | |
| updateActivePowerupsDisplay(); | |
| }, duration); | |
| break; | |
| } | |
| updateActivePowerupsDisplay(); | |
| } | |
| function updateActivePowerupsDisplay() { | |
| const names = { | |
| 'expandPaddle': 'Wide Paddle', | |
| 'fastBall': 'Fast Ball', | |
| 'slowBall': 'Slow Ball' | |
| }; | |
| if (activePowerups.length > 0) { | |
| const activeNames = activePowerups.map(p => names[p.type]).join(', '); | |
| document.getElementById('powerups').textContent = activeNames; | |
| } else { | |
| document.getElementById('powerups').textContent = 'None active'; | |
| } | |
| } | |
| // Particle system | |
| function createParticles(x, y, color) { | |
| for (let i = 0; i < 8; i++) { | |
| particles.push({ | |
| x: x, | |
| y: y, | |
| dx: (Math.random() - 0.5) * 6, | |
| dy: (Math.random() - 0.5) * 6, | |
| life: 30, | |
| maxLife: 30, | |
| color: color | |
| }); | |
| } | |
| } | |
| function updateParticles() { | |
| for (let i = particles.length - 1; i >= 0; i--) { | |
| const p = particles[i]; | |
| p.x += p.dx; | |
| p.y += p.dy; | |
| p.life--; | |
| if (p.life <= 0) { | |
| particles.splice(i, 1); | |
| } | |
| } | |
| } | |
| // Reset ball and paddle | |
| function resetBallAndPaddle() { | |
| ball.x = canvas.width / 2; | |
| ball.y = canvas.height / 2; | |
| paddle.x = canvas.width / 2 - paddle.width / 2; | |
| } | |
| // Restart game | |
| function restartGame() { | |
| if (gameState === 'won') { | |
| level++; | |
| ball.baseSpeed += 0.5; | |
| } else { | |
| score = 0; | |
| lives = 3; | |
| level = 1; | |
| ball.baseSpeed = 4; | |
| } | |
| ball.speed = ball.baseSpeed; | |
| gameState = 'waiting'; | |
| createBlocks(); | |
| resetBallAndPaddle(); | |
| document.getElementById('gameOver').style.display = 'none'; | |
| document.getElementById('gameWon').style.display = 'none'; | |
| } | |
| function showUpgradeMenu() { | |
| gameState = 'upgrading'; | |
| document.getElementById('upgradeMenu').style.display = 'block'; | |
| } | |
| function selectUpgrade(type) { | |
| document.getElementById('upgradeMenu').style.display = 'none'; | |
| gameState = 'waiting'; | |
| switch(type) { | |
| case 'ballSpeed': | |
| ball.baseSpeed += 1; | |
| ball.speed = ball.baseSpeed; | |
| break; | |
| case 'paddleWidth': | |
| paddle.originalWidth = Math.min(paddle.originalWidth * 1.2, canvas.width * 0.6); | |
| paddle.width = paddle.originalWidth; | |
| break; | |
| case 'extraLife': | |
| lives = Math.min(lives + 1, 5); | |
| break; | |
| } | |
| restartGame(); | |
| } | |
| // Render functions | |
| function drawPaddle() { | |
| const gradient = ctx.createLinearGradient(paddle.x, paddle.y, paddle.x, paddle.y + paddle.height); | |
| gradient.addColorStop(0, '#00d4ff'); | |
| gradient.addColorStop(1, '#0099cc'); | |
| ctx.fillStyle = gradient; | |
| ctx.fillRect(paddle.x, paddle.y, paddle.width, paddle.height); | |
| ctx.strokeStyle = '#ffffff'; | |
| ctx.lineWidth = 2; | |
| ctx.strokeRect(paddle.x, paddle.y, paddle.width, paddle.height); | |
| } | |
| function drawBall() { | |
| const gradient = ctx.createRadialGradient(ball.x, ball.y, 0, ball.x, ball.y, ball.radius); | |
| gradient.addColorStop(0, '#ffffff'); | |
| gradient.addColorStop(1, '#00d4ff'); | |
| ctx.beginPath(); | |
| ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2); | |
| ctx.fillStyle = gradient; | |
| ctx.fill(); | |
| ctx.strokeStyle = '#ffffff'; | |
| ctx.lineWidth = 2; | |
| ctx.stroke(); | |
| } | |
| function drawBlocks() { | |
| for (let block of blocks) { | |
| if (!block.visible) continue; | |
| const gradient = ctx.createLinearGradient(block.x, block.y, block.x, block.y + block.height); | |
| gradient.addColorStop(0, block.color); | |
| gradient.addColorStop(1, darkenColor(block.color, 0.3)); | |
| ctx.fillStyle = gradient; | |
| ctx.fillRect(block.x, block.y, block.width, block.height); | |
| ctx.strokeStyle = '#ffffff'; | |
| ctx.lineWidth = 1; | |
| ctx.strokeRect(block.x, block.y, block.width, block.height); | |
| // Draw powerup indicator | |
| if (block.hasPowerup) { | |
| ctx.beginPath(); | |
| ctx.arc(block.x + block.width/2, block.y + block.height/2, 5, 0, Math.PI*2); | |
| ctx.fillStyle = '#ffff00'; | |
| ctx.fill(); | |
| } | |
| } | |
| } | |
| function drawPowerups() { | |
| for (let p of powerups) { | |
| ctx.beginPath(); | |
| ctx.rect(p.x - p.width/2, p.y - p.height/2, p.width, p.height); | |
| ctx.fillStyle = p.color; | |
| ctx.fill(); | |
| // Add glow effect | |
| ctx.shadowBlur = 10; | |
| ctx.shadowColor = p.color; | |
| ctx.beginPath(); | |
| ctx.rect(p.x - p.width/2, p.y - p.height/2, p.width, p.height); | |
| ctx.fillStyle = p.color; | |
| ctx.fill(); | |
| ctx.shadowBlur = 0; | |
| } | |
| } | |
| function drawParticles() { | |
| for (let p of particles) { | |
| const alpha = p.life / p.maxLife; | |
| ctx.save(); | |
| ctx.globalAlpha = alpha; | |
| ctx.fillStyle = p.color; | |
| ctx.beginPath(); | |
| ctx.arc(p.x, p.y, 3, 0, Math.PI * 2); | |
| ctx.fill(); | |
| ctx.restore(); | |
| } | |
| } | |
| function darkenColor(color, factor) { | |
| const hex = color.replace('#', ''); | |
| const r = Math.floor(parseInt(hex.substr(0, 2), 16) * (1 - factor)); | |
| const g = Math.floor(parseInt(hex.substr(2, 2), 16) * (1 - factor)); | |
| const b = Math.floor(parseInt(hex.substr(4, 2), 16) * (1 - factor)); | |
| return `rgb(${r}, ${g}, ${b})`; | |
| } | |
| // Game loop | |
| function gameLoop() { | |
| // Clear canvas | |
| ctx.clearRect(0, 0, canvas.width, canvas.height); | |
| // Update | |
| updatePaddle(); | |
| updateBall(); | |
| updatePowerups(); | |
| updateParticles(); | |
| // Draw | |
| drawBlocks(); | |
| drawPowerups(); | |
| drawPaddle(); | |
| drawBall(); | |
| drawParticles(); | |
| // Draw start message | |
| if (gameState === 'waiting') { | |
| ctx.fillStyle = 'rgba(255, 255, 255, 0.8)'; | |
| ctx.font = '24px Arial'; | |
| ctx.textAlign = 'center'; | |
| ctx.fillText('Click to Start', canvas.width / 2, canvas.height / 2 + 50); | |
| } | |
| // Update UI | |
| document.getElementById('score').textContent = score; | |
| document.getElementById('lives').textContent = lives; | |
| document.getElementById('level').textContent = level; | |
| requestAnimationFrame(gameLoop); | |
| } | |
| // Initialize game | |
| createBlocks(); | |
| gameLoop(); | |
| </script> | |
| </body> | |
| </html> |