Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Space Invaders</title> | |
<script src="https://cdn.tailwindcss.com"></script> | |
<style> | |
@keyframes enemyMove { | |
0%, 100% { transform: translateX(0); } | |
25% { transform: translateX(10px); } | |
50% { transform: translateX(0); } | |
75% { transform: translateX(-10px); } | |
} | |
@keyframes explosion { | |
0% { transform: scale(0.5); opacity: 1; } | |
100% { transform: scale(1.5); opacity: 0; } | |
} | |
.enemy-animation { | |
animation: enemyMove 1s infinite; | |
} | |
.explosion { | |
animation: explosion 0.5s forwards; | |
} | |
#gameCanvas { | |
background-color: #111827; | |
border-radius: 8px; | |
box-shadow: 0 0 30px rgba(59, 130, 246, 0.5); | |
} | |
.pixel-art { | |
image-rendering: pixelated; | |
image-rendering: -moz-crisp-edges; | |
image-rendering: crisp-edges; | |
} | |
</style> | |
</head> | |
<body class="bg-gray-900 text-white min-h-screen flex flex-col items-center justify-center p-4"> | |
<div class="text-center mb-6"> | |
<h1 class="text-4xl font-bold mb-2 text-blue-400">SPACE INVADERS</h1> | |
<div class="flex justify-center items-center gap-8 mb-4"> | |
<div class="text-xl"> | |
<span class="text-blue-300">Score:</span> | |
<span id="score" class="ml-2 text-yellow-300">0</span> | |
</div> | |
<div class="text-xl"> | |
<span class="text-blue-300">Lives:</span> | |
<span id="lives" class="ml-2 text-red-400">3</span> | |
</div> | |
<div class="text-xl"> | |
<span class="text-blue-300">Level:</span> | |
<span id="level" class="ml-2 text-green-400">1</span> | |
</div> | |
</div> | |
</div> | |
<div class="relative"> | |
<canvas id="gameCanvas" width="600" height="500" class="border-2 border-blue-500"></canvas> | |
<!-- Start Screen --> | |
<div id="startScreen" class="absolute inset-0 bg-black bg-opacity-80 flex flex-col items-center justify-center"> | |
<h2 class="text-4xl font-bold mb-6 text-blue-400">SPACE INVADERS</h2> | |
<p class="text-xl mb-8 text-center max-w-md"> | |
Defend Earth from alien invasion!<br> | |
Use arrow keys to move and space to shoot. | |
</p> | |
<button id="startButton" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-3 px-8 rounded-full text-xl transition-all transform hover:scale-105"> | |
START GAME | |
</button> | |
<div class="mt-8 text-gray-400"> | |
<p>Controls:</p> | |
<div class="flex gap-4 mt-2"> | |
<div class="bg-gray-800 px-3 py-1 rounded">← →</div> | |
<div class="bg-gray-800 px-3 py-1 rounded">SPACE</div> | |
</div> | |
</div> | |
</div> | |
<!-- Game Over Screen --> | |
<div id="gameOverScreen" class="absolute inset-0 bg-black bg-opacity-80 hidden flex-col items-center justify-center"> | |
<h2 class="text-4xl font-bold mb-2 text-red-500">GAME OVER</h2> | |
<p class="text-2xl mb-6">Your score: <span id="finalScore" class="text-yellow-300">0</span></p> | |
<button id="restartButton" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-6 rounded-full transition-all transform hover:scale-105"> | |
PLAY AGAIN | |
</button> | |
</div> | |
<!-- Level Complete Screen --> | |
<div id="levelCompleteScreen" class="absolute inset-0 bg-black bg-opacity-80 hidden flex-col items-center justify-center"> | |
<h2 class="text-4xl font-bold mb-2 text-green-500">LEVEL COMPLETE!</h2> | |
<p class="text-2xl mb-6">Get ready for level <span id="nextLevel" class="text-yellow-300">2</span></p> | |
<button id="nextLevelButton" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-6 rounded-full transition-all transform hover:scale-105"> | |
CONTINUE | |
</button> | |
</div> | |
</div> | |
<div class="mt-8 text-gray-400 text-center max-w-md"> | |
<p class="mb-2">Tip: The aliens move faster as their numbers decrease!</p> | |
<p>Destroy the mothership for bonus points!</p> | |
</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 levelCompleteScreen = document.getElementById('levelCompleteScreen'); | |
const startButton = document.getElementById('startButton'); | |
const restartButton = document.getElementById('restartButton'); | |
const nextLevelButton = document.getElementById('nextLevelButton'); | |
const scoreDisplay = document.getElementById('score'); | |
const livesDisplay = document.getElementById('lives'); | |
const levelDisplay = document.getElementById('level'); | |
const finalScoreDisplay = document.getElementById('finalScore'); | |
const nextLevelDisplay = document.getElementById('nextLevel'); | |
// Game state | |
let gameRunning = false; | |
let score = 0; | |
let lives = 3; | |
let level = 1; | |
let enemies = []; | |
let enemyBullets = []; | |
let playerBullets = []; | |
let explosions = []; | |
let player = { | |
x: canvas.width / 2 - 25, | |
y: canvas.height - 60, | |
width: 50, | |
height: 30, | |
speed: 8, | |
color: '#3B82F6' | |
}; | |
let keys = { | |
ArrowLeft: false, | |
ArrowRight: false, | |
' ': false | |
}; | |
// Enemy types | |
const enemyTypes = [ | |
{ color: '#F59E0B', points: 10, width: 40, height: 30 }, // Yellow - basic | |
{ color: '#8B5CF6', points: 20, width: 40, height: 30 }, // Purple - faster | |
{ color: '#EC4899', points: 30, width: 40, height: 30 }, // Pink - shoots more | |
{ color: '#10B981', points: 50, width: 60, height: 40 } // Green - mothership | |
]; | |
// Initialize game | |
function initGame() { | |
score = 0; | |
lives = 3; | |
level = 1; | |
updateDisplays(); | |
createEnemies(); | |
gameRunning = true; | |
gameLoop(); | |
} | |
// Create enemies based on level | |
function createEnemies() { | |
enemies = []; | |
const rows = Math.min(3 + Math.floor(level / 2), 6); | |
const cols = Math.min(5 + level, 10); | |
const spacing = 60; | |
const startX = (canvas.width - (cols - 1) * spacing) / 2; | |
const startY = 50; | |
// Regular enemies | |
for (let row = 0; row < rows; row++) { | |
for (let col = 0; col < cols; col++) { | |
const typeIndex = row % 3; // Cycle through first 3 types | |
enemies.push({ | |
x: startX + col * spacing, | |
y: startY + row * spacing, | |
width: enemyTypes[typeIndex].width, | |
height: enemyTypes[typeIndex].height, | |
color: enemyTypes[typeIndex].color, | |
points: enemyTypes[typeIndex].points, | |
speed: 1 + level * 0.2, | |
shootChance: 0.005 + (level * 0.001) | |
}); | |
} | |
} | |
// Add mothership (random position top row) | |
if (level % 3 === 0) { | |
enemies.push({ | |
x: Math.random() * (canvas.width - 60), | |
y: 30, | |
width: enemyTypes[3].width, | |
height: enemyTypes[3].height, | |
color: enemyTypes[3].color, | |
points: enemyTypes[3].points, | |
speed: 1.5 + level * 0.1, | |
shootChance: 0.01 + (level * 0.002) | |
}); | |
} | |
} | |
// Update displays | |
function updateDisplays() { | |
scoreDisplay.textContent = score; | |
livesDisplay.textContent = lives; | |
levelDisplay.textContent = level; | |
} | |
// Game loop | |
function gameLoop() { | |
if (!gameRunning) return; | |
// Clear canvas | |
ctx.clearRect(0, 0, canvas.width, canvas.height); | |
// Update and draw player | |
updatePlayer(); | |
drawPlayer(); | |
// Update and draw enemies | |
updateEnemies(); | |
drawEnemies(); | |
// Update and draw bullets | |
updateBullets(); | |
drawBullets(); | |
// Draw explosions | |
drawExplosions(); | |
// Check for level completion | |
if (enemies.length === 0) { | |
levelComplete(); | |
return; | |
} | |
// Check for game over | |
if (lives <= 0) { | |
gameOver(); | |
return; | |
} | |
// Continue game loop | |
requestAnimationFrame(gameLoop); | |
} | |
// Update player position | |
function updatePlayer() { | |
if (keys.ArrowLeft && player.x > 0) { | |
player.x -= player.speed; | |
} | |
if (keys.ArrowRight && player.x < canvas.width - player.width) { | |
player.x += player.speed; | |
} | |
// Player shooting | |
if (keys[' ']) { | |
keys[' '] = false; // Prevent continuous shooting | |
playerBullets.push({ | |
x: player.x + player.width / 2 - 2, | |
y: player.y, | |
width: 4, | |
height: 15, | |
speed: 10, | |
color: '#FFFFFF' | |
}); | |
// Play shoot sound | |
playSound('shoot'); | |
} | |
} | |
// Draw player | |
function drawPlayer() { | |
ctx.fillStyle = player.color; | |
// Main body | |
ctx.fillRect(player.x, player.y, player.width, player.height); | |
// Cannon | |
ctx.fillRect(player.x + player.width / 2 - 5, player.y - 10, 10, 10); | |
} | |
// Update enemies | |
function updateEnemies() { | |
let directionChange = false; | |
// Move enemies and check for direction change | |
enemies.forEach(enemy => { | |
enemy.x += enemy.speed; | |
// Check if any enemy hits the edge | |
if ((enemy.x + enemy.width > canvas.width || enemy.x < 0) && !directionChange) { | |
directionChange = true; | |
} | |
}); | |
// Change direction and move down if edge hit | |
if (directionChange) { | |
enemies.forEach(enemy => { | |
enemy.speed = -enemy.speed; | |
enemy.y += 20; | |
}); | |
} | |
// Enemy shooting | |
enemies.forEach(enemy => { | |
if (Math.random() < enemy.shootChance) { | |
enemyBullets.push({ | |
x: enemy.x + enemy.width / 2 - 2, | |
y: enemy.y + enemy.height, | |
width: 4, | |
height: 15, | |
speed: 5 + level * 0.5, | |
color: '#F87171' // Red bullets | |
}); | |
} | |
}); | |
// Check if any enemy reached the bottom | |
enemies.forEach(enemy => { | |
if (enemy.y + enemy.height > canvas.height - 50) { | |
lives = 0; // Instant game over | |
} | |
}); | |
} | |
// Draw enemies | |
function drawEnemies() { | |
enemies.forEach(enemy => { | |
ctx.fillStyle = enemy.color; | |
// Main body | |
ctx.fillRect(enemy.x, enemy.y, enemy.width, enemy.height); | |
// Eyes | |
ctx.fillStyle = '#000000'; | |
ctx.fillRect(enemy.x + 10, enemy.y + 10, 8, 8); | |
ctx.fillRect(enemy.x + enemy.width - 18, enemy.y + 10, 8, 8); | |
// Special decoration for mothership | |
if (enemy.width === 60) { | |
ctx.fillStyle = '#FFFFFF'; | |
ctx.fillRect(enemy.x + 15, enemy.y - 5, 30, 5); | |
} | |
}); | |
} | |
// Update bullets | |
function updateBullets() { | |
// Player bullets | |
for (let i = playerBullets.length - 1; i >= 0; i--) { | |
playerBullets[i].y -= playerBullets[i].speed; | |
// Remove if off screen | |
if (playerBullets[i].y < 0) { | |
playerBullets.splice(i, 1); | |
continue; | |
} | |
// Check for enemy hits | |
for (let j = enemies.length - 1; j >= 0; j--) { | |
if (checkCollision(playerBullets[i], enemies[j])) { | |
// Add explosion | |
explosions.push({ | |
x: enemies[j].x + enemies[j].width / 2, | |
y: enemies[j].y + enemies[j].height / 2, | |
size: Math.max(enemies[j].width, enemies[j].height), | |
color: enemies[j].color, | |
time: 0 | |
}); | |
// Increase score | |
score += enemies[j].points; | |
updateDisplays(); | |
// Play explosion sound | |
playSound('explosion'); | |
// Remove enemy and bullet | |
enemies.splice(j, 1); | |
playerBullets.splice(i, 1); | |
break; | |
} | |
} | |
} | |
// Enemy bullets | |
for (let i = enemyBullets.length - 1; i >= 0; i--) { | |
enemyBullets[i].y += enemyBullets[i].speed; | |
// Remove if off screen | |
if (enemyBullets[i].y > canvas.height) { | |
enemyBullets.splice(i, 1); | |
continue; | |
} | |
// Check for player hit | |
if (checkCollision(enemyBullets[i], player)) { | |
// Add explosion | |
explosions.push({ | |
x: player.x + player.width / 2, | |
y: player.y + player.height / 2, | |
size: 30, | |
color: player.color, | |
time: 0 | |
}); | |
// Play explosion sound | |
playSound('explosion'); | |
// Decrease lives | |
lives--; | |
updateDisplays(); | |
// Remove bullet | |
enemyBullets.splice(i, 1); | |
// Short invincibility | |
if (lives > 0) { | |
player.color = '#93C5FD'; // Light blue for invincibility | |
setTimeout(() => { | |
player.color = '#3B82F6'; // Back to normal | |
}, 1000); | |
} | |
} | |
} | |
} | |
// Draw bullets | |
function drawBullets() { | |
// Player bullets | |
playerBullets.forEach(bullet => { | |
ctx.fillStyle = bullet.color; | |
ctx.fillRect(bullet.x, bullet.y, bullet.width, bullet.height); | |
}); | |
// Enemy bullets | |
enemyBullets.forEach(bullet => { | |
ctx.fillStyle = bullet.color; | |
ctx.fillRect(bullet.x, bullet.y, bullet.width, bullet.height); | |
}); | |
} | |
// Draw explosions | |
function drawExplosions() { | |
for (let i = explosions.length - 1; i >= 0; i--) { | |
explosions[i].time++; | |
const progress = explosions[i].time / 30; | |
const size = explosions[i].size * (0.5 + progress * 1.5); | |
const alpha = 1 - progress; | |
ctx.save(); | |
ctx.globalAlpha = alpha; | |
ctx.fillStyle = explosions[i].color; | |
// Draw explosion particles | |
for (let j = 0; j < 8; j++) { | |
const angle = (j / 8) * Math.PI * 2; | |
const distance = size * 0.3 * progress; | |
const px = explosions[i].x + Math.cos(angle) * distance; | |
const py = explosions[i].y + Math.sin(angle) * distance; | |
ctx.fillRect(px - 2, py - 2, 4, 4); | |
} | |
ctx.restore(); | |
// Remove if animation complete | |
if (explosions[i].time >= 30) { | |
explosions.splice(i, 1); | |
} | |
} | |
} | |
// Check collision between two objects | |
function checkCollision(obj1, obj2) { | |
return obj1.x < obj2.x + obj2.width && | |
obj1.x + obj1.width > obj2.x && | |
obj1.y < obj2.y + obj2.height && | |
obj1.y + obj1.height > obj2.y; | |
} | |
// Game over | |
function gameOver() { | |
gameRunning = false; | |
finalScoreDisplay.textContent = score; | |
gameOverScreen.classList.remove('hidden'); | |
gameOverScreen.classList.add('flex'); | |
// Play game over sound | |
playSound('gameOver'); | |
} | |
// Level complete | |
function levelComplete() { | |
gameRunning = false; | |
nextLevelDisplay.textContent = level + 1; | |
levelCompleteScreen.classList.remove('hidden'); | |
levelCompleteScreen.classList.add('flex'); | |
// Play level complete sound | |
playSound('levelComplete'); | |
} | |
// Start next level | |
function nextLevel() { | |
level++; | |
updateDisplays(); | |
createEnemies(); | |
gameRunning = true; | |
levelCompleteScreen.classList.add('hidden'); | |
levelCompleteScreen.classList.remove('flex'); | |
gameLoop(); | |
} | |
// Simple sound effects | |
function playSound(type) { | |
const sounds = { | |
shoot: { | |
frequency: 220, | |
type: 'square', | |
duration: 0.1 | |
}, | |
explosion: { | |
frequency: 100, | |
type: 'sawtooth', | |
duration: 0.3 | |
}, | |
gameOver: { | |
frequency: 110, | |
type: 'sine', | |
duration: 1.5 | |
}, | |
levelComplete: { | |
frequency: 523.25, | |
type: 'sine', | |
duration: 0.2 | |
} | |
}; | |
if (sounds[type]) { | |
const audioCtx = new (window.AudioContext || window.webkitAudioContext)(); | |
const oscillator = audioCtx.createOscillator(); | |
const gainNode = audioCtx.createGain(); | |
oscillator.type = sounds[type].type; | |
oscillator.frequency.value = sounds[type].frequency; | |
oscillator.connect(gainNode); | |
gainNode.connect(audioCtx.destination); | |
// For explosion, add some randomness | |
if (type === 'explosion') { | |
oscillator.frequency.exponentialRampToValueAtTime( | |
50, | |
audioCtx.currentTime + sounds[type].duration | |
); | |
} | |
// For game over, make it descending | |
if (type === 'gameOver') { | |
oscillator.frequency.exponentialRampToValueAtTime( | |
55, | |
audioCtx.currentTime + sounds[type].duration | |
); | |
} | |
// For level complete, make it a little melody | |
if (type === 'levelComplete') { | |
oscillator.frequency.setValueAtTime(523.25, audioCtx.currentTime); | |
oscillator.frequency.setValueAtTime(659.25, audioCtx.currentTime + 0.1); | |
oscillator.frequency.setValueAtTime(783.99, audioCtx.currentTime + 0.2); | |
} | |
gainNode.gain.exponentialRampToValueAtTime( | |
0.001, | |
audioCtx.currentTime + sounds[type].duration | |
); | |
oscillator.start(); | |
oscillator.stop(audioCtx.currentTime + sounds[type].duration); | |
} | |
} | |
// Event listeners | |
startButton.addEventListener('click', () => { | |
startScreen.classList.add('hidden'); | |
initGame(); | |
}); | |
restartButton.addEventListener('click', () => { | |
gameOverScreen.classList.add('hidden'); | |
gameOverScreen.classList.remove('flex'); | |
initGame(); | |
}); | |
nextLevelButton.addEventListener('click', () => { | |
nextLevel(); | |
}); | |
// Keyboard controls - FIXED SPACE KEY ISSUE | |
window.addEventListener('keydown', (e) => { | |
if (e.key === 'ArrowLeft' || e.key === 'ArrowRight' || e.key === ' ') { | |
e.preventDefault(); | |
} | |
if (e.key === 'ArrowLeft') keys.ArrowLeft = true; | |
if (e.key === 'ArrowRight') keys.ArrowRight = true; | |
if (e.key === ' ') keys[' '] = true; | |
}); | |
window.addEventListener('keyup', (e) => { | |
if (e.key === 'ArrowLeft') keys.ArrowLeft = false; | |
if (e.key === 'ArrowRight') keys.ArrowRight = false; | |
if (e.key === ' ') keys[' '] = false; | |
}); | |
// Touch controls for mobile | |
let touchStartX = 0; | |
canvas.addEventListener('touchstart', (e) => { | |
e.preventDefault(); | |
touchStartX = e.touches[0].clientX; | |
keys[' '] = true; // Shoot on touch | |
}); | |
canvas.addEventListener('touchmove', (e) => { | |
e.preventDefault(); | |
const touchX = e.touches[0].clientX; | |
if (touchX < touchStartX - 10) { | |
keys.ArrowLeft = true; | |
keys.ArrowRight = false; | |
} else if (touchX > touchStartX + 10) { | |
keys.ArrowRight = true; | |
keys.ArrowLeft = false; | |
} | |
}); | |
canvas.addEventListener('touchend', (e) => { | |
e.preventDefault(); | |
keys.ArrowLeft = false; | |
keys.ArrowRight = false; | |
keys[' '] = false; | |
}); | |
</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=drdata/space-invaders" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
</html> |