const $ = (s) => document.querySelector(s); const ROOT = $('#root'); const LIVES_LABEL = $('#lives'); const SOUND_BUTTON = $('#sound'); const CANVAS = $('#canvas'); const CTX = CANVAS.getContext('2d'); const X_BOUND = 0.05; const GAME_OVER_BOUND = 0.9; const MAX_RAINDROPS = 100; const clouds = new Set(); const bullets = new Set(); const raindrops = new Set(); let shootingPad = new ShootingPad(); let running = false; let direction = 1; let soundEnabled = localStorage.soundEnabled !== 'false'; const initCloudField = () => { for (let y = 0; y < 6; y++) { for (let x = 0; x < 11; x++) { const cloud = new Cloud(X_BOUND + 0.06 * x, 0.04 + 0.06 * y, 0.03, 0.02); clouds.add(cloud); } } } const initRainEffect = () => { noise.seed(Math.random()); for (let i = 0; i < MAX_RAINDROPS; i++) { const raindrop = new BackgroundRaindrop( randRange(-0.5, 1.5), randRange(-1, -0.1), randRange(0.01, 0.03), ); raindrops.add(raindrop); } } const updateSoundButton = () => { SOUND_BUTTON.textContent = soundEnabled ? '🔊' : '🔈'; SOUND_BUTTON.title = `Sound ${soundEnabled ? 'enabled' : 'disabled'}`; } const start = () => { $('.result')?.remove(); shootingPad?.destroy(); shootingPad = new ShootingPad(); bullets.forEach(c => c.destroy()); bullets.clear(); clouds.forEach(c => c.destroy()); clouds.clear(); Cloud.reset(); initCloudField(); running = true; } const gameOver = (result) => { const resultLabel = document.createElement('div'); resultLabel.className = 'result'; resultLabel.textContent = result; ROOT.appendChild(resultLabel); running = false; } const loop = () => { document.title = `${fps()} FPS | CloudInvaders | OLC Codejam 2022`; CTX.clearRect(0, 0, CANVAS.width, CANVAS.height); raindrops.forEach(d => { d.update(); d.draw(); }); if (running) { let directionUpdated = false; let isOver = false; Array.from(clouds).forEach(c => { c.update(direction); c.draw(); if (c.x < X_BOUND || c.x > 1 - X_BOUND) { directionUpdated = true; } if (c.y >= GAME_OVER_BOUND) { isOver = true; } if (!c.alive) { clouds.delete(c); } }); if (directionUpdated) { direction = -direction; } if (isOver) { gameOver('Game over'); } Array.from(bullets).forEach(b => { b.update(); b.draw(); if (!b.alive) { bullets.delete(b); } }); shootingPad.update(); shootingPad.draw(); if (clouds.size === 0) { gameOver('WIN'); } } requestAnimationFrame(loop); }; SOUND_BUTTON.addEventListener('click', () => { soundEnabled = !soundEnabled; localStorage.soundEnabled = soundEnabled; updateSoundButton(); }); document.addEventListener('keypress', () => { if (!running) { start(); } }); initRainEffect(); updateSoundButton(); loop(); gameOver('🌩 Cloud Invaders 🌩');