cosmic-sound-invaders / index.html
poingly's picture
(index):64 cdn.tailwindcss.com should not be used in production. To use Tailwind CSS in production, install it as a PostCSS plugin or use the Tailwind CLI: https://tailwindcss.com/docs/installation
0bc1f19 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Cosmic Sound Invaders</title>
<!-- Production Tailwind CSS via CDN -->
<script src="https://cdn.tailwindcss.com?plugins=forms,typography,aspect-ratio"></script>
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
<script src="https://unpkg.com/feather-icons"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tone/14.8.49/Tone.js"></script>
<style>
#gameCanvas {
background: linear-gradient(to bottom, #0f172a, #1e293b);
border-radius: 12px;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.5);
}
.music-note {
position: absolute;
font-size: 24px;
animation: float-up 1s ease-out forwards;
opacity: 0.8;
}
@keyframes float-up {
0% { transform: translateY(0); opacity: 0.8; }
100% { transform: translateY(-50px); opacity: 0; }
}
</style>
</head>
<body class="bg-slate-900 text-white min-h-screen flex flex-col items-center justify-center p-4">
<div class="max-w-4xl w-full text-center mb-8">
<h1 class="text-5xl font-bold mb-2 bg-clip-text text-transparent bg-gradient-to-r from-purple-400 via-pink-500 to-indigo-500">
Cosmic Sound Invaders
</h1>
<p class="text-xl text-slate-300 mb-6">
Shoot aliens to create an intergalactic symphony!
</p>
<div class="flex justify-center gap-4 mb-8">
<button id="startBtn" class="px-6 py-3 bg-green-600 hover:bg-green-700 rounded-lg font-medium transition-all shadow-lg hover:shadow-green-500/30 flex items-center gap-2">
<i data-feather="play"></i> Start Game
</button>
<button id="muteBtn" class="px-6 py-3 bg-slate-700 hover:bg-slate-600 rounded-lg font-medium transition-all flex items-center gap-2">
<i data-feather="volume-2"></i> Sound On
</button>
</div>
</div>
<div class="relative">
<canvas id="gameCanvas" width="800" height="500" class="w-full max-w-full"></canvas>
<div id="noteEffects" class="pointer-events-none"></div>
</div>
<div class="mt-8 w-full max-w-4xl">
<div class="bg-slate-800 bg-opacity-50 rounded-xl p-6 backdrop-blur-sm">
<h2 class="text-2xl font-semibold mb-4 text-indigo-300">Your Cosmic Composition</h2>
<div id="activeInstruments" class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6"></div>
<div class="h-24 bg-slate-900 rounded-lg p-4 flex items-center justify-center">
<p id="currentTempo" class="text-lg">Tempo: 120 BPM</p>
</div>
</div>
</div>
<div class="mt-12 text-slate-400 text-sm flex items-center gap-2">
<i data-feather="info"></i>
<span>Shoot aliens to add notes to your song! Higher aliens = higher pitch</span>
</div>
<script>
// Game variables
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
let gameRunning = false;
let isMuted = false;
let score = 0;
let lives = 3;
let tempo = 120;
// Initialize audio
let synth, fmSynth, amSynth, pluckSynth;
async function initAudio() {
await Tone.start();
synth = new Tone.PolySynth(Tone.Synth).toDestination();
// Game audio initialization
let instruments = [];
const instrumentNames = ["Space Lead", "FM Waves", "AM Modulation", "Pluck Strings"];
const instrumentColors = ["text-purple-400", "text-blue-400", "text-pink-400", "text-green-400"];
// Game variables
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
let gameRunning = false;
let isMuted = false;
let score = 0;
let lives = 3;
let tempo = 120;
// Player
const player = {
x: canvas.width / 2,
y: canvas.height - 30,
width: 60,
height: 20,
speed: 8,
color: '#ec4899'
};
// Bullets
const bullets = [];
const bulletSpeed = 8;
// Aliens
const aliens = [];
const alienWidth = 40;
const alienHeight = 30;
const alienPadding = 20;
const alienOffsetTop = 60;
const alienOffsetLeft = 60;
const alienRows = 4;
const alienCols = 8;
// Active notes (sounding notes)
const activeNotes = [];
// Initialize game
function initGame() {
bullets.length = 0;
aliens.length = 0;
activeNotes.length = 0;
document.getElementById('activeInstruments').innerHTML = '';
score = 0;
lives = 3;
tempo = 120;
document.getElementById('currentTempo').textContent = `Tempo: ${tempo} BPM`;
// Create aliens
for (let r = 0; r < alienRows; r++) {
for (let c = 0; c < alienCols; c++) {
aliens.push({
x: alienOffsetLeft + c * (alienWidth + alienPadding),
y: alienOffsetTop + r * (alienHeight + alienPadding),
width: alienWidth,
height: alienHeight,
alive: true,
row: r,
col: c
});
}
}
gameRunning = true;
animate();
}
// Draw player
function drawPlayer() {
ctx.fillStyle = player.color;
ctx.fillRect(player.x - player.width / 2, player.y - player.height / 2, player.width, player.height);
// Draw player ship details
ctx.fillStyle = '#f97316';
ctx.beginPath();
ctx.moveTo(player.x, player.y - player.height / 2);
ctx.lineTo(player.x + 10, player.y - player.height / 2 - 15);
ctx.lineTo(player.x - 10, player.y - player.height / 2 - 15);
ctx.closePath();
ctx.fill();
}
// Draw bullets
function drawBullets() {
ctx.fillStyle = '#facc15';
bullets.forEach(bullet => {
ctx.beginPath();
ctx.arc(bullet.x, bullet.y, 5, 0, Math.PI * 2);
ctx.fill();
});
}
// Draw aliens
function drawAliens() {
aliens.forEach(alien => {
if (alien.alive) {
// Alien body
ctx.fillStyle = r % 2 === 0 ? '#4ade80' : '#60a5fa';
ctx.fillRect(alien.x, alien.y, alien.width, alien.height);
// Alien details
ctx.fillStyle = '#1e293b';
ctx.beginPath();
ctx.arc(alien.x + 10, alien.y + 10, 4, 0, Math.PI * 2);
ctx.arc(alien.x + alien.width - 10, alien.y + 10, 4, 0, Math.PI * 2);
ctx.fill();
ctx.fillRect(alien.x + alien.width / 2 - 5, alien.y + 15, 10, 5);
}
});
}
// Update game state
function update() {
// Move bullets
for (let i = bullets.length - 1; i >= 0; i--) {
bullets[i].y -= bulletSpeed;
// Remove bullets that are off screen
if (bullets[i].y < 0) {
bullets.splice(i, 1);
continue;
}
// Check for collisions with aliens
for (let j = 0; j < aliens.length; j++) {
if (aliens[j].alive &&
bullets[i].x > aliens[j].x &&
bullets[i].x < aliens[j].x + aliens[j].width &&
bullets[i].y > aliens[j].y &&
bullets[i].y < aliens[j].y + aliens[j].height) {
// Alien hit!
aliens[j].alive = false;
bullets.splice(i, 1);
score += 10;
// Create a musical note based on alien position
createMusicNote(aliens[j].x, aliens[j].y, aliens[j].row, aliens[j].col);
break;
}
}
}
// Move aliens (simple left-right movement)
// In a real game this would be more sophisticated
const alienSpeed = 1 + score / 500;
// Check if any aliens have reached the bottom
aliens.forEach(alien => {
if (alien.alive && alien.y + alien.height > canvas.height - 20) {
lives--;
if (lives <= 0) {
gameRunning = false;
alert(`Game Over! Your cosmic score: ${score}`);
}
}
});
// Increase tempo based on score
tempo = Math.min(200, 120 + Math.floor(score / 100));
document.getElementById('currentTempo').textContent = `Tempo: ${tempo} BPM`;
Tone.Transport.bpm.value = tempo;
}
// Create a musical note when alien is hit
function createMusicNote(x, y, row, col) {
if (isMuted) return;
// Determine pitch based on vertical position (row)
const notes = ['C4', 'D4', 'E4', 'F4', 'G4', 'A4', 'B4', 'C5'];
const noteIndex = row % notes.length;
const note = notes[noteIndex];
// Choose instrument based on column
const instrumentIndex = col % instruments.length;
const instrument = instruments[instrumentIndex];
// Play the note
const now = Tone.now();
instrument.triggerAttackRelease(note, "8n", now);
// Add visual note effect
const noteEffect = document.createElement('div');
noteEffect.className = `music-note ${instrumentColors[instrumentIndex]}`;
noteEffect.style.left = `${x}px`;
noteEffect.style.top = `${y}px`;
noteEffect.textContent = note;
document.getElementById('noteEffects').appendChild(noteEffect);
// Remove after animation
setTimeout(() => {
noteEffect.remove();
}, 1000);
// Add to active instruments display if not already there
if (!activeNotes.includes(instrumentIndex)) {
activeNotes.push(instrumentIndex);
const instrumentCard = document.createElement('div');
instrumentCard.className = `bg-slate-700 bg-opacity-70 rounded-lg p-3 flex items-center gap-3 ${instrumentColors[instrumentIndex]}`;
instrumentCard.innerHTML = `
<i data-feather="music"></i>
<div>
<div class="font-medium">${instrumentNames[instrumentIndex]}</div>
<div class="text-xs">Playing: ${note}</div>
</div>
`;
document.getElementById('activeInstruments').appendChild(instrumentCard);
feather.replace();
}
}
// Main game loop
function animate() {
if (!gameRunning) return;
ctx.clearRect(0, 0, canvas.width, canvas.height);
update();
drawPlayer();
drawBullets();
drawAliens();
requestAnimationFrame(animate);
}
// Event listeners
document.addEventListener('keydown', (e) => {
if (!gameRunning) return;
switch(e.key) {
case 'ArrowLeft':
player.x = Math.max(player.width / 2, player.x - player.speed);
break;
case 'ArrowRight':
player.x = Math.min(canvas.width - player.width / 2, player.x + player.speed);
break;
case ' ':
bullets.push({
x: player.x,
y: player.y - player.height / 2 - 15
});
break;
}
});
// Initialize audio context on user interaction
let audioInitialized = false;
async function initAudioContext() {
if (!audioInitialized) {
await Tone.start();
synth = new Tone.PolySynth(Tone.Synth).toDestination();
fmSynth = new Tone.PolySynth(Tone.FMSynth).toDestination();
amSynth = new Tone.PolySynth(Tone.AMSynth).toDestination();
pluckSynth = new Tone.PolySynth(Tone.PluckSynth).toDestination();
audioInitialized = true;
Tone.Transport.start();
isMuted = false;
document.getElementById('muteBtn').innerHTML = '<i data-feather="volume-2"></i> Sound On';
feather.replace();
}
return audioInitialized;
}
document.getElementById('startBtn').addEventListener('click', async () => {
if (!gameRunning) {
try {
await initAudioContext();
initGame();
} catch (e) {
console.error("Audio initialization failed:", e);
alert("Please click the page first to enable audio, then click Start Game again");
}
}
});
// Enable audio context on any user interaction
document.addEventListener('click', async function init() {
try {
await Tone.start();
audioInitialized = true;
document.removeEventListener('click', init);
} catch (e) {
console.error("Initial audio setup failed:", e);
}
}, { once: true });
document.getElementById('muteBtn').addEventListener('click', () => {
isMuted = !isMuted;
const btn = document.getElementById('muteBtn');
if (isMuted) {
btn.innerHTML = '<i data-feather="volume-x"></i> Sound Off';
Tone.Transport.stop();
} else {
btn.innerHTML = '<i data-feather="volume-2"></i> Sound On';
Tone.Transport.start();
}
feather.replace();
});
// Initialize UI
isMuted = true;
document.getElementById('muteBtn').innerHTML = '<i data-feather="volume-x"></i> Sound Off';
feather.replace();
</script>
</body>
</html>