space-invaders / index.html
drdata's picture
Add 2 files
44c4929 verified
<!DOCTYPE html>
<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>