Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Aleste 2 Clone</title> | |
<script src="https://cdn.tailwindcss.com"></script> | |
<style> | |
body { | |
margin: 0; | |
overflow: hidden; | |
background-color: #000; | |
font-family: 'Courier New', monospace; | |
} | |
canvas { | |
display: block; | |
width: 100vw; | |
height: 100vh; | |
} | |
#ui { | |
position: absolute; | |
top: 10px; | |
left: 10px; | |
color: #00FFFF; | |
font-size: 18px; | |
text-shadow: 0 0 10px #00FFFF, 0 0 20px #0066FF; | |
background: rgba(0, 0, 0, 0.5); | |
padding: 10px; | |
border-radius: 5px; | |
border: 1px solid #00FFFF; | |
} | |
#gameOver { | |
position: absolute; | |
top: 50%; | |
left: 50%; | |
transform: translate(-50%, -50%); | |
color: #FF0000; | |
font-size: 48px; | |
text-align: center; | |
text-shadow: 0 0 10px #FF0000, 0 0 20px #990000; | |
display: none; | |
background: rgba(0, 0, 0, 0.7); | |
padding: 20px 40px; | |
border-radius: 10px; | |
border: 2px solid #FF0000; | |
} | |
#loading { | |
position: absolute; | |
top: 50%; | |
left: 50%; | |
transform: translate(-50%, -50%); | |
color: #00FFFF; | |
font-size: 24px; | |
text-align: center; | |
text-shadow: 0 0 10px #00FFFF; | |
background: rgba(0, 0, 0, 0.7); | |
padding: 20px; | |
border-radius: 10px; | |
border: 1px solid #00FFFF; | |
} | |
#musicControls { | |
position: absolute; | |
bottom: 20px; | |
right: 20px; | |
display: flex; | |
gap: 10px; | |
} | |
.music-btn { | |
background: rgba(0, 0, 0, 0.7); | |
color: #00FFFF; | |
border: 1px solid #00FFFF; | |
border-radius: 4px; | |
padding: 8px 12px; | |
cursor: pointer; | |
transition: all 0.3s; | |
text-shadow: 0 0 5px #00FFFF; | |
} | |
.music-btn:hover { | |
background: rgba(0, 255, 255, 0.3); | |
box-shadow: 0 0 10px #00FFFF; | |
} | |
#powerBar { | |
position: absolute; | |
bottom: 20px; | |
left: 20px; | |
width: 200px; | |
height: 20px; | |
background: rgba(0, 0, 0, 0.5); | |
border: 1px solid #00FFFF; | |
border-radius: 3px; | |
overflow: hidden; | |
} | |
#powerFill { | |
height: 100%; | |
width: 0%; | |
background: linear-gradient(90deg, #00FFFF, #0066FF); | |
transition: width 0.3s; | |
} | |
#powerLabel { | |
position: absolute; | |
top: -20px; | |
left: 0; | |
color: #00FFFF; | |
font-size: 14px; | |
} | |
.glow { | |
text-shadow: 0 0 5px currentColor; | |
} | |
@keyframes pulse { | |
0% { opacity: 0.7; } | |
50% { opacity: 1; } | |
100% { opacity: 0.7; } | |
} | |
.pulse { | |
animation: pulse 2s infinite; | |
} | |
</style> | |
</head> | |
<body> | |
<div id="ui"> | |
<div>SCORE: <span id="score" class="glow">0</span></div> | |
<div>WEAPON: <span id="weapon" class="glow">NORMAL</span></div> | |
<div>FPS: <span id="fps" class="glow">0</span></div> | |
</div> | |
<div id="powerBar"> | |
<div id="powerLabel">POWER</div> | |
<div id="powerFill"></div> | |
</div> | |
<div id="gameOver"> | |
GAME OVER<br> | |
<span style="font-size: 24px;">Press R to restart</span> | |
</div> | |
<div id="loading" class="pulse"> | |
<div>Loading game assets...</div> | |
<div style="font-size: 14px; margin-top: 10px;">Aleste 2 Clone</div> | |
</div> | |
<div id="musicControls"> | |
<button id="playMusic" class="music-btn">▶ Play Music</button> | |
<button id="stopMusic" class="music-btn">■ Stop Music</button> | |
</div> | |
<canvas id="gameCanvas"></canvas> | |
<audio id="bgMusic" loop> | |
<source src="https://www.msx.org/sites/default/files/downloads/music/mp3/Meits_Aleste2Area1.mp3" type="audio/mpeg"> | |
</audio> | |
<script> | |
// Game constants | |
const PLAYER_SIZE = 20; | |
const PROJECTILE_WIDTH = 4; | |
const PROJECTILE_HEIGHT = 12; | |
const ENEMY_SIZE = 24; | |
const SCROLL_SPEED = 2; | |
const BG_LINE_SPACING = 40; | |
// Game state | |
let gameRunning = true; | |
let score = 0; | |
let weaponType = 'normal'; // 'normal' or 'power' | |
let powerLevel = 0; | |
let assetsLoaded = false; | |
let lastTime = 0; | |
let fps = 0; | |
let frameCount = 0; | |
let lastFpsUpdate = 0; | |
// Canvas setup | |
const canvas = document.getElementById('gameCanvas'); | |
const ctx = canvas.getContext('2d'); | |
canvas.width = window.innerWidth; | |
canvas.height = window.innerHeight; | |
// Audio elements | |
const bgMusic = document.getElementById('bgMusic'); | |
const audioContext = new (window.AudioContext || window.webkitAudioContext)(); | |
// Player object | |
const player = { | |
x: canvas.width / 2, | |
y: canvas.height - 60, | |
width: PLAYER_SIZE, | |
height: PLAYER_SIZE, | |
speed: 5, | |
color: '#00FFFF', | |
projectiles: [], | |
shootCooldown: 0, | |
update: function() { | |
// Movement | |
if (keys.ArrowLeft && this.x > 0) this.x -= this.speed; | |
if (keys.ArrowRight && this.x < canvas.width - this.width) this.x += this.speed; | |
if (keys.ArrowUp && this.y > 0) this.y -= this.speed; | |
if (keys.ArrowDown && this.y < canvas.height - this.height) this.y += this.speed; | |
// Shooting cooldown | |
if (this.shootCooldown > 0) this.shootCooldown--; | |
// Shoot when space is pressed and cooldown is 0 | |
if (keys[' '] && this.shootCooldown === 0) { | |
this.shoot(); | |
this.shootCooldown = weaponType === 'normal' ? 10 : 20; | |
// Increase power level when shooting | |
if (powerLevel < 100) { | |
powerLevel += 1; | |
document.getElementById('powerFill').style.width = powerLevel + '%'; | |
// Check if weapon should upgrade | |
if (powerLevel >= 100 && weaponType === 'normal') { | |
weaponType = 'power'; | |
document.getElementById('weapon').textContent = 'POWER'; | |
playSound('powerUp'); | |
} | |
} | |
} | |
// Update projectiles | |
for (let i = this.projectiles.length - 1; i >= 0; i--) { | |
this.projectiles[i].update(); | |
// Remove projectiles that are off screen | |
if (this.projectiles[i].y + this.projectiles[i].height < 0) { | |
this.projectiles.splice(i, 1); | |
} | |
} | |
}, | |
shoot: function() { | |
playSound('shoot'); | |
const projectile = { | |
x: this.x + this.width / 2 - PROJECTILE_WIDTH / 2, | |
y: this.y, | |
width: PROJECTILE_WIDTH, | |
height: PROJECTILE_HEIGHT, | |
speed: weaponType === 'normal' ? 10 : 5, | |
damage: weaponType === 'normal' ? 1 : 3, | |
color: weaponType === 'normal' ? '#FF0000' : '#FFFF00', | |
update: function() { | |
this.y -= this.speed; | |
}, | |
draw: function() { | |
ctx.fillStyle = this.color; | |
ctx.fillRect(this.x, this.y, this.width, this.height); | |
// Add a glow effect for power shots | |
if (weaponType === 'power') { | |
ctx.fillStyle = 'rgba(255, 255, 0, 0.3)'; | |
ctx.fillRect(this.x - 2, this.y - 2, this.width + 4, this.height + 4); | |
} | |
} | |
}; | |
this.projectiles.push(projectile); | |
// Add additional projectiles for power weapon | |
if (weaponType === 'power') { | |
const leftProjectile = {...projectile}; | |
leftProjectile.x -= 15; | |
this.projectiles.push(leftProjectile); | |
const rightProjectile = {...projectile}; | |
rightProjectile.x += 15; | |
this.projectiles.push(rightProjectile); | |
} | |
}, | |
draw: function() { | |
// Draw ship body | |
ctx.fillStyle = this.color; | |
ctx.fillRect(this.x, this.y, this.width, this.height); | |
// Draw ship details | |
ctx.fillStyle = '#FFFFFF'; | |
ctx.fillRect(this.x + 5, this.y + 5, 2, 2); | |
ctx.fillRect(this.x + this.width - 7, this.y + 5, 2, 2); | |
// Draw engine glow | |
const gradient = ctx.createLinearGradient( | |
this.x + 5, this.y + this.height, | |
this.x + this.width - 5, this.y + this.height | |
); | |
gradient.addColorStop(0, 'rgba(0, 255, 255, 0)'); | |
gradient.addColorStop(0.5, 'rgba(0, 255, 255, 0.8)'); | |
gradient.addColorStop(1, 'rgba(0, 255, 255, 0)'); | |
ctx.fillStyle = gradient; | |
ctx.fillRect(this.x + 5, this.y + this.height, this.width - 10, 10); | |
// Draw projectiles | |
this.projectiles.forEach(projectile => projectile.draw()); | |
} | |
}; | |
// Enemies array | |
let enemies = []; | |
// Enemy spawner | |
function spawnEnemy() { | |
const enemyTypes = [ | |
{ health: 1, speed: 1 + Math.random() * 3, color: `hsl(${Math.random() * 60 + 300}, 70%, 50%)` }, | |
{ health: 2, speed: 0.5 + Math.random() * 2, color: `hsl(${Math.random() * 60 + 180}, 70%, 50%)` }, | |
{ health: 3, speed: 0.3 + Math.random() * 1.5, color: `hsl(${Math.random() * 60 + 60}, 70%, 50%)` } | |
]; | |
const type = enemyTypes[Math.floor(Math.random() * enemyTypes.length)]; | |
const enemy = { | |
x: Math.random() * (canvas.width - ENEMY_SIZE), | |
y: -ENEMY_SIZE, | |
width: ENEMY_SIZE, | |
height: ENEMY_SIZE, | |
speed: type.speed, | |
health: type.health, | |
color: type.color, | |
hitFlash: 0, | |
update: function() { | |
this.y += this.speed; | |
// Reduce hit flash timer | |
if (this.hitFlash > 0) { | |
this.hitFlash--; | |
} | |
// Check collision with player | |
if (checkCollision(this, player)) { | |
gameOver(); | |
} | |
// Check collision with projectiles | |
for (let i = player.projectiles.length - 1; i >= 0; i--) { | |
if (checkCollision(this, player.projectiles[i])) { | |
this.health -= player.projectiles[i].damage; | |
player.projectiles.splice(i, 1); | |
this.hitFlash = 5; | |
playSound('hit'); | |
if (this.health <= 0) { | |
score += 10 * type.health; | |
document.getElementById('score').textContent = score; | |
return false; // Mark for removal | |
} | |
} | |
} | |
return true; // Keep enemy | |
}, | |
draw: function() { | |
// Draw enemy body with flash effect if hit | |
const flashColor = this.hitFlash > 0 ? '#FFFFFF' : this.color; | |
ctx.fillStyle = flashColor; | |
ctx.fillRect(this.x, this.y, this.width, this.height); | |
// Draw enemy details | |
ctx.fillStyle = '#000000'; | |
ctx.fillRect(this.x + 8, this.y + 8, 8, 8); | |
// Draw health indicator for tougher enemies | |
if (type.health > 1) { | |
ctx.fillStyle = '#FFFFFF'; | |
ctx.font = '10px Courier New'; | |
ctx.fillText(`${this.health}`, this.x + 12, this.y + 15); | |
} | |
} | |
}; | |
enemies.push(enemy); | |
} | |
// Background state | |
let bgOffset = 0; | |
// Input handling | |
const keys = {}; | |
document.addEventListener('keydown', e => { | |
keys[e.key] = true; | |
// Toggle weapon with Q | |
if (e.key === 'q' && powerLevel >= 100) { | |
weaponType = weaponType === 'normal' ? 'power' : 'normal'; | |
document.getElementById('weapon').textContent = weaponType.toUpperCase(); | |
playSound('weaponSwitch'); | |
} | |
// Restart game with R when game over | |
if (!gameRunning && e.key.toLowerCase() === 'r') { | |
resetGame(); | |
} | |
}); | |
document.addEventListener('keyup', e => { | |
keys[e.key] = false; | |
}); | |
// Collision detection | |
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 | |
function gameOver() { | |
gameRunning = false; | |
document.getElementById('gameOver').style.display = 'block'; | |
playSound('gameOver'); | |
bgMusic.pause(); | |
} | |
// Reset game function | |
function resetGame() { | |
gameRunning = true; | |
score = 0; | |
powerLevel = 0; | |
weaponType = 'normal'; | |
player.x = canvas.width / 2; | |
player.y = canvas.height - 60; | |
player.projectiles = []; | |
enemies = []; | |
document.getElementById('score').textContent = '0'; | |
document.getElementById('weapon').textContent = 'NORMAL'; | |
document.getElementById('powerFill').style.width = '0%'; | |
document.getElementById('gameOver').style.display = 'none'; | |
// Start music again | |
bgMusic.currentTime = 0; | |
bgMusic.play(); | |
// Spawn initial enemies | |
for (let i = 0; i < 5; i++) { | |
setTimeout(spawnEnemy, i * 1000); | |
} | |
} | |
// Sound effects | |
function playSound(type) { | |
const oscillator = audioContext.createOscillator(); | |
const gainNode = audioContext.createGain(); | |
oscillator.connect(gainNode); | |
gainNode.connect(audioContext.destination); | |
switch (type) { | |
case 'shoot': | |
oscillator.type = 'square'; | |
oscillator.frequency.value = 880; | |
gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.1); | |
oscillator.start(); | |
oscillator.stop(audioContext.currentTime + 0.1); | |
break; | |
case 'hit': | |
oscillator.type = 'sine'; | |
oscillator.frequency.setValueAtTime(440, audioContext.currentTime); | |
oscillator.frequency.exponentialRampToValueAtTime(110, audioContext.currentTime + 0.2); | |
gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.2); | |
oscillator.start(); | |
oscillator.stop(audioContext.currentTime + 0.2); | |
break; | |
case 'gameOver': | |
oscillator.type = 'sawtooth'; | |
oscillator.frequency.value = 110; | |
gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.5); | |
oscillator.start(); | |
oscillator.stop(audioContext.currentTime + 0.5); | |
break; | |
case 'powerUp': | |
oscillator.type = 'sine'; | |
oscillator.frequency.setValueAtTime(220, audioContext.currentTime); | |
oscillator.frequency.exponentialRampToValueAtTime(880, audioContext.currentTime + 0.3); | |
gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.3); | |
oscillator.start(); | |
oscillator.stop(audioContext.currentTime + 0.3); | |
break; | |
case 'weaponSwitch': | |
oscillator.type = 'square'; | |
oscillator.frequency.setValueAtTime(440, audioContext.currentTime); | |
oscillator.frequency.exponentialRampToValueAtTime(880, audioContext.currentTime + 0.1); | |
gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.1); | |
oscillator.start(); | |
oscillator.stop(audioContext.currentTime + 0.1); | |
break; | |
} | |
} | |
// Handle window resize | |
window.addEventListener('resize', () => { | |
canvas.width = window.innerWidth; | |
canvas.height = window.innerHeight; | |
}); | |
// Music control functions | |
function playMusic() { | |
bgMusic.play().catch(e => { | |
console.error("Audio playback failed:", e); | |
// Show a message to the user that they need to interact first | |
document.getElementById('loading').textContent = "Click anywhere to start music"; | |
document.addEventListener('click', () => { | |
bgMusic.play(); | |
document.getElementById('loading').style.display = 'none'; | |
assetsLoaded = true; | |
}, { once: true }); | |
}); | |
} | |
function stopMusic() { | |
bgMusic.pause(); | |
} | |
// Setup music controls | |
document.getElementById('playMusic').addEventListener('click', playMusic); | |
document.getElementById('stopMusic').addEventListener('click', stopMusic); | |
// Game loop | |
function gameLoop(timestamp) { | |
if (!gameRunning || !assetsLoaded) { | |
requestAnimationFrame(gameLoop); | |
return; | |
} | |
// Calculate delta time and FPS | |
const deltaTime = timestamp - lastTime; | |
lastTime = timestamp; | |
if (deltaTime > 0) { | |
fps = 1000 / deltaTime; | |
frameCount++; | |
// Update FPS display every second | |
if (timestamp - lastFpsUpdate > 1000) { | |
document.getElementById('fps').textContent = Math.round(fps); | |
lastFpsUpdate = timestamp; | |
frameCount = 0; | |
} | |
} | |
// Clear canvas | |
ctx.fillStyle = '#000033'; | |
ctx.fillRect(0, 0, canvas.width, canvas.height); | |
// Draw scrolling background | |
ctx.strokeStyle = '#0066FF'; | |
ctx.lineWidth = 2; | |
bgOffset += SCROLL_SPEED; | |
if (bgOffset >= BG_LINE_SPACING) { | |
bgOffset = 0; | |
} | |
for (let y = -BG_LINE_SPACING + bgOffset; y < canvas.height; y += BG_LINE_SPACING) { | |
ctx.beginPath(); | |
ctx.moveTo(0, y); | |
ctx.lineTo(canvas.width, y); | |
ctx.stroke(); | |
} | |
// Draw starfield background | |
for (let i = 0; i < 100; i++) { | |
const x = (i * 12345) % canvas.width; | |
const y = (i * 9876 + timestamp / 50) % canvas.height; | |
const size = (i % 3) + 1; | |
const opacity = 0.5 + (i % 10) / 20; | |
ctx.fillStyle = `rgba(255, 255, 255, ${opacity})`; | |
ctx.fillRect(x, y, size, size); | |
} | |
// Update and draw player | |
player.update(); | |
player.draw(); | |
// Update and draw enemies | |
for (let i = enemies.length - 1; i >= 0; i--) { | |
if (!enemies[i].update()) { | |
enemies.splice(i, 1); | |
} else { | |
enemies[i].draw(); | |
} | |
} | |
// Spawn new enemies occasionally | |
if (Math.random() < 0.02) { | |
spawnEnemy(); | |
} | |
// Gradually decrease power level when not shooting | |
if (powerLevel > 0 && !keys[' ']) { | |
powerLevel -= 0.2; | |
document.getElementById('powerFill').style.width = powerLevel + '%'; | |
// Check if weapon should downgrade | |
if (powerLevel <= 0 && weaponType === 'power') { | |
weaponType = 'normal'; | |
document.getElementById('weapon').textContent = 'NORMAL'; | |
} | |
} | |
requestAnimationFrame(gameLoop); | |
} | |
// Initialize the game | |
function initGame() { | |
// Hide loading screen and start game | |
document.getElementById('loading').style.display = 'none'; | |
assetsLoaded = true; | |
// Start music | |
playMusic(); | |
// Spawn initial enemies | |
for (let i = 0; i < 5; i++) { | |
setTimeout(spawnEnemy, i * 1000); | |
} | |
// Start game loop | |
requestAnimationFrame(gameLoop); | |
} | |
// Start the initialization process | |
document.addEventListener('DOMContentLoaded', () => { | |
// Modern browsers require user interaction before playing audio | |
document.addEventListener('click', () => { | |
initGame(); | |
}, { once: true }); | |
document.getElementById('loading').textContent = "Click anywhere to start game"; | |
}); | |
</script> | |
</body> | |
</html> |