Get link Facebook X Pinterest Email Other Apps August 18, 2025 { try{ o.stop(); } catch(_){} }, time*1000+50); } // Game objects const world = { w: 720, h: 900 }; const bg = { scroll:0, speed: 0.4 }; // Bird sprite (simple frame animation using rectangles) const bird = { x: 160, y: 420, w: 44, h: 32, vy: 0, gravity: 0.5, flapPower: -9.2, rotation: 0, frames: 0, flap(){ this.vy = this.flapPower; this.frames=0; playTone(520,0.06,'triangle',0.08); }, update(){ this.vy += this.gravity; this.y += this.vy; this.rotation = Math.max(-0.6, Math.min(1.2, this.vy / 12)); // floor/ceiling collision handled in loop this.frames++; }, draw(){ ctx.save(); ctx.translate(this.x + this.w/2, this.y + this.h/2); ctx.rotate(this.rotation); ctx.translate(-this.x - this.w/2, -this.y - this.h/2); // body ctx.fillStyle = "#ffdd57"; roundRect(ctx, this.x, this.y, this.w, this.h, 6, true, false); // wing (simple flapping) const wingY = this.y + 6 + Math.sin(this.frames * 0.35) * 4; ctx.fillStyle = "#f0c43a"; roundRect(ctx, this.x + 6, wingY, 16, 10, 4, true, false); // eye ctx.fillStyle = "#000"; ctx.fillRect(this.x + this.w - 14, this.y + 8, 6, 6); ctx.restore(); } }; // Helpers function roundRect(ctx, x, y, w, h, r, fill, stroke){ if(typeof r === "undefined") r = 5; ctx.beginPath(); ctx.moveTo(x + r, y); ctx.arcTo(x + w, y, x + w, y + h, r); ctx.arcTo(x + w, y + h, x, y + h, r); ctx.arcTo(x, y + h, x, y, r); ctx.arcTo(x, y, x + w, y, r); ctx.closePath(); if(fill) ctx.fill(); if(stroke) ctx.stroke(); } // Pipes const pipes = []; let pipeTimer = 0; let pipeInterval = 85; // frames let pipeSpeed = 2.6; let gap = 160; function spawnPipe(){ const margin = 80; const top = Math.floor(Math.random() * (world.h - gap - margin*2)) + margin; pipes.push({ x: world.w + 40, top: top, w: 72, scored:false }); } // Difficulty ramp: after each 8 points, increase speed and reduce gap slightly function difficultyAdjust(){ const stage = Math.floor(score / 8); pipeSpeed = 2.6 + stage * 0.18; gap = Math.max(115, 160 - stage * 6); pipeInterval = Math.max(62, 85 - stage * 4); } // Collision check function rectsOverlap(a, b){ return a.x < b.x + b.w && a.x + a.w > b.x && a.y < b.y + b.h && a.y + a.h > b.y; } // Game loop & draw function clear(){ ctx.fillStyle = "#70c5ce"; ctx.fillRect(0,0,world.w, world.h); } function drawBackground(){ // sky gradient const g = ctx.createLinearGradient(0,0,0,world.h); g.addColorStop(0, "#70c5ce"); g.addColorStop(1, "#58b7c9"); ctx.fillStyle = g; ctx.fillRect(0,0,world.w,world.h); // subtle moving clouds (rect-based) ctx.globalAlpha = 0.08; for(let i=0;i<4;i++){ const x = ((frames * (0.25 + i*0.05)) + i*220) % (world.w + 300) - 150; ctx.fillRect(x, 80 + i*40, 200, 40); } ctx.globalAlpha = 1; // ground strip ctx.fillStyle = "#3b8b65"; ctx.fillRect(0, world.h - 90, world.w, 90); } function drawPipes(){ ctx.fillStyle = "#2e8b57"; for(const p of pipes){ // top pipe ctx.fillRect(p.x, 0, p.w, p.top); // bottom pipe ctx.fillRect(p.x, p.top + gap, p.w, world.h - (p.top + gap) - 90); // pipe rims ctx.fillStyle = "#246b45"; ctx.fillRect(p.x-6, p.top-6, p.w+12, 6); ctx.fillRect(p.x-6, p.top+gap, p.w+12, 6); ctx.fillStyle = "#2e8b57"; } } function drawHUD(){ elScore.textContent = score; elBest.textContent = best; } function loop(){ if(!running || paused){ requestAnimationFrame(loop); return; } frames++; // Update bird.update(); // spawn pipes pipeTimer++; if(pipeTimer >= pipeInterval){ spawnPipe(); pipeTimer = 0; } // update pipes for(let i = pipes.length - 1; i >= 0; i--){ const p = pipes[i]; p.x -= pipeSpeed; // scoring if(!p.scored && p.x + p.w < bird.x){ score++; p.scored = true; playTone(440 + score*2, 0.05, "sine", 0.03); difficultyAdjust(); } // remove offscreen if(p.x + p.w < -80) pipes.splice(i,1); // collision const birdRect = { x: bird.x, y: bird.y, w: bird.w, h: bird.h }; const topRect = { x: p.x, y: 0, w: p.w, h: p.top }; const bottomRect = { x: p.x, y: p.top + gap, w: p.w, h: world.h - (p.top + gap) - 90 }; if(rectsOverlap(birdRect, topRect) || rectsOverlap(birdRect, bottomRect)) { gameOver(); return; } } // floor/ceiling collision if(bird.y + bird.h >= world.h - 90){ gameOver(); return; } if(bird.y <= 0){ bird.y = 0; bird.vy = 0; } // Draw ctx.save(); // clear and draw background clear(); drawBackground(); // pipes drawPipes(); // bird bird.draw(); // score display in canvas (large) ctx.fillStyle = "#fff"; ctx.font = "38px Inter, Arial"; ctx.fillText(score, world.w/2 - 10, 140); ctx.restore(); drawHUD(); requestAnimationFrame(loop); } function start(){ // reset running = true; paused = false; frames = 0; score = 0; pipes.length = 0; pipeTimer = 0; bird.y = world.h / 2; bird.vy = 0; pipeSpeed = 2.6; gap = 160; pipeInterval = 85; pauseBtn.textContent = "Pause"; playTone(880,0.06,"sine",0.08); // ensure audio resumed try{ if(audioCtx && audioCtx.state === "suspended") audioCtx.resume(); }catch(e){} requestAnimationFrame(loop); } function saveBest(){ if(score > best){ best = score; elBest.textContent = best; try{ localStorage.setItem(STORAGE_KEY, best); }catch(e){} } } function gameOver(){ running = false; saveBest(); drawGameOver(); playTone(160,0.5,"sine",0.12); setTimeout(()=>{ if(confirm("Game Over! Score: " + score + "\\nPlay again?")) start(); }, 120); } function drawGameOver(){ ctx.save(); ctx.fillStyle = "rgba(0,0,0,0.45)"; ctx.fillRect(0,0,world.w,world.h); ctx.fillStyle = "#fff"; ctx.font = "48px Inter, Arial"; ctx.fillText("Game Over", world.w/2 - 140, world.h/2 - 20); ctx.font = "20px Inter, Arial"; ctx.fillText("Score: " + score + " | Best: " + best, world.w/2 - 140, world.h/2 + 18); ctx.restore(); } // initial draw (start screen) function drawStartScreen(){ clear(); drawBackground(); ctx.fillStyle = "#ffffff"; ctx.font = "56px Inter, Arial"; ctx.fillText("Advanced Flappy", world.w/2 - 220, 150); ctx.font = "20px Inter, Arial"; ctx.fillText("Press SPACE or Tap to flap. Click Start to begin.", world.w/2 - 240, 200); // demo bird bird.draw(); ctx.font = "18px Inter, Arial"; ctx.fillText("Best: " + best, world.w/2 - 40, world.h - 40); } // initial drawStartScreen(); elScore.textContent = score; elBest.textContent = best; // small helper: begin if user clicks start or taps canvas function ensureUserGesture(){ // Some browsers block WebAudio until gesture; we resume on first action. try{ if(audioCtx && audioCtx.state === "suspended") audioCtx.resume(); }catch(e){} } document.addEventListener("click", ensureUserGesture, { once: true }); })();