<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>8-Bit Duck Adventure</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: #000;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
font-family: 'Press Start 2P', monospace;
overflow: hidden;
}
#game-container {
position: relative;
border: 4px solid #fff;
}
#game-canvas {
display: block;
background: #4a9eff;
}
#controls {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 10px;
}
.btn {
background: #ff6b6b;
border: 3px solid #fff;
color: #fff;
padding: 15px 20px;
font-family: inherit;
font-size: 12px;
cursor: pointer;
text-transform: uppercase;
box-shadow: 0 4px 0 #c92a2a;
}
.btn:active {
transform: translateY(4px);
box-shadow: 0 0 0 #c92a2a;
}
.btn-fire {
background: #ff8c00;
box-shadow: 0 4px 0 #cc7000;
}
.btn-fire:active {
box-shadow: 0 0 0 #cc7000;
}
#instructions {
position: absolute;
top: 10px;
left: 10px;
color: #fff;
font-size: 10px;
text-shadow: 2px 2px 0 #000;
}
#boss-warning {
display: none;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: #FF0000;
font-size: 24px;
text-shadow: 2px 2px 0 #000;
font-weight: bold;
text-align: center;
background: rgba(0,0,0,0.7);
padding: 20px;
border: 3px solid #FF0000;
}
</style>
</head>
<body>
<div id="game-container">
<canvas id="game-canvas" width="1200" height="800"></canvas>
<div id="instructions">
WASD/Arrows: Move<br>
SPACE: Breathe Fire (2 DMG)<br>
C: Quack<br>
Score: <span id="score">0</span>
</div>
<div id="boss-warning">
⚠️⚠️⚠️<br>BOSS DUCK APPROACHING!<br>⚠️⚠️⚠️
</div>
<div id="controls">
<button class="btn" id="quack-btn">🦆 Quack!</button>
<button class="btn btn-fire" id="fire-btn">🔥 Breathe Fire!</button>
</div>
</div>
<script>
const canvas = document.getElementById('game-canvas');
const ctx = canvas.getContext('2d');
// Duck state
const duck = {
x: 400,
y: 300,
width: 40,
height: 30,
speed: 5,
direction: 1,
facing: 'right'
};
// Enemy ducks
let enemyDucks = [];
const ENEMY_COUNT = 5;
const ENEMY_SPEED = 2;
// Fire particles
let fireParticles = [];
let quackSound = false;
// Score and boss
let score = 0;
let ducksKilled = 0;
let bossActive = false;
let boss = null;
function spawnEnemyDuck() {
const side = Math.floor(Math.random() * 4);
let x, y;
switch(side) {
case 0: x = -50; y = Math.random() * 600 + 100; break;
case 1: x = canvas.width + 50; y = Math.random() * 600 + 100; break;
case 2: x = Math.random() * canvas.width; y = -50; break;
case 3: x = Math.random() * canvas.width; y = canvas.height + 50; break;
}
enemyDucks.push({
x: x,
y: y,
width: 35,
height: 25,
hp: 1,
maxHp: 1,
facing: 'right'
});
}
function spawnBossDuck() {
bossActive = true;
const side = Math.floor(Math.random() * 4);
let x, y;
switch(side) {
case 0: x = -100; y = canvas.height / 2; break;
case 1: x = canvas.width + 100; y = canvas.height / 2; break;
case 2: x = canvas.width / 2; y = -100; break;
case 3: x = canvas.width / 2; y = canvas.height + 100; break;
}
boss = {
x: x,
y: y,
width: 250,
height: 180,
hp: 50,
maxHp: 50,
facing: 'right',
speed: 0.8
};
enemyDucks = [];
}
for (let i = 0; i < ENEMY_COUNT; i++) {
spawnEnemyDuck();
}
// Audio
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
function playQuackSound() {
if (audioCtx.state === 'suspended') audioCtx.resume();
const oscillator = audioCtx.createOscillator();
const gainNode = audioCtx.createGain();
oscillator.connect(gainNode);
gainNode.connect(audioCtx.destination);
oscillator.type = 'sawtooth';
oscillator.frequency.setValueAtTime(600, audioCtx.currentTime);
oscillator.frequency.exponentialRampToValueAtTime(400, audioCtx.currentTime + 0.1);
gainNode.gain.setValueAtTime(0.3, audioCtx.currentTime);
gainNode.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.15);
oscillator.start(audioCtx.currentTime);
oscillator.stop(audioCtx.currentTime + 0.15);
}
function playFireSound() {
if (audioCtx.state === 'suspended') audioCtx.resume();
const bufferSize = audioCtx.sampleRate * 0.3;
const buffer = audioCtx.createBuffer(1, bufferSize, audioCtx.sampleRate);
const data = buffer.getChannelData(0);
for (let i = 0; i < bufferSize; i++) {
data[i] = Math.random() * 2 - 1;
}
const noise = audioCtx.createBufferSource();
noise.buffer = buffer;
const gainNode = audioCtx.createGain();
gainNode.gain.setValueAtTime(0.2, audioCtx.currentTime);
gainNode.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.3);
noise.connect(gainNode);
gainNode.connect(audioCtx.destination);
noise.start(audioCtx.currentTime);
}
// Controls
const keys = {};
document.addEventListener('keydown', (e) => {
if (e.key === 'ArrowLeft' || e.key === 'a' || e.key === 'A') {
keys['left'] = true;
duck.facing = 'left';
} else if (e.key === 'ArrowRight' || e.key === 'd' || e.key === 'D') {
keys['right'] = true;
duck.facing = 'right';
} else if (e.key === 'ArrowUp' || e.key === 'w' || e.key === 'W') {
keys['up'] = true;
} else if (e.key === 'ArrowDown' || e.key === 's' || e.key === 'S') {
keys['down'] = true;
} else if (e.code === 'Space') {
e.preventDefault();
createFireBurst();
playFireSound();
} else if (e.key === 'c' || e.key === 'C') {
doQuack();
}
});
document.addEventListener('keyup', (e) => {
if (e.key === 'ArrowLeft' || e.key === 'a' || e.key === 'A') keys['left'] = false;
else if (e.key === 'ArrowRight' || e.key === 'd' || e.key === 'D') keys['right'] = false;
else if (e.key === 'ArrowUp' || e.key === 'w' || e.key === 'W') keys['up'] = false;
else if (e.key === 'ArrowDown' || e.key === 's' || e.key === 'S') keys['down'] = false;
});
document.getElementById('quack-btn').addEventListener('click', doQuack);
document.getElementById('fire-btn').addEventListener('click', () => {
createFireBurst();
playFireSound();
});
function doQuack() {
quackSound = true;
playQuackSound();
setTimeout(() => quackSound = false, 500);
}
function createFireBurst() {
for (let i = 0; i < 20; i++) {
fireParticles.push({
x: duck.x + 20,
y: duck.y + 15,
vx: (duck.facing === 'right' ? 1 : -1) * (3 + Math.random() * 3),
vy: (Math.random() - 0.5) * 2,
size: 5 + Math.random() * 10,
life: 1.0,
color: `hsl(${30 + Math.random() * 40}, 100%, 50%)`
});
}
}
function drawDuck() {
ctx.save();
ctx.translate(duck.x, duck.y);
ctx.scale(duck.facing === 'left' ? -1 : 1, 1);
ctx.fillStyle = '#FFD700';
ctx.fillRect(-15, -10, 30, 20);
ctx.fillRect(-5, -20, 20, 20);
ctx.fillStyle = '#000';
ctx.fillRect(5, -17, 4, 4);
ctx.fillStyle = '#FF6B6B';
ctx.fillRect(10, -12, 12, 8);
ctx.fillStyle = '#E6C200';
ctx.fillRect(-10, -5, 12, 10);
ctx.fillStyle = '#FF6B6B';
ctx.fillRect(-10, 10, 6, 8);
ctx.fillRect(5, 10, 6, 8);
if (quackSound) {
ctx.fillStyle = '#fff';
ctx.font = '10px monospace';
ctx.fillText('QUACK!', 20, -25);
}
ctx.restore();
}
function drawBossDuck() {
if (!boss) return;
ctx.save();
ctx.translate(boss.x, boss.y);
ctx.scale(boss.facing === 'left' ? -1 : 1, 1);
ctx.fillStyle = '#800080';
ctx.fillRect(-100, -70, 200, 140);
ctx.fillRect(-40, -110, 90, 100);
ctx.fillStyle = '#FFD700';
ctx.fillRect(-50, -130, 25, 25);
ctx.fillRect(0, -130, 25, 25);
ctx.fillRect(50, -130, 25, 25);
ctx.fillStyle = '#FF0000';
ctx.fillRect(12, -90, 20, 20);
ctx.fillRect(45, -90, 20, 20);
ctx.fillStyle = '#000';
ctx.fillRect(5, -105, 30, 8);
ctx.fillRect(35, -105, 30, 8);
ctx.fillStyle = '#FF6B6B';
ctx.fillRect(55, -80, 50, 35);
ctx.fillStyle = '#6B006B';
ctx.fillRect(-60, -40, 55, 60);
ctx.fillRect(15, -40, 55, 60);
ctx.fillStyle = '#FF6B6B';
ctx.fillRect(-50, 70, 25, 30);
ctx.fillRect(15, 70, 25, 30);
ctx.fillStyle = '#333';
ctx.fillRect(-110, -150, 220, 25);
ctx.fillStyle = '#FF0000';
ctx.fillRect(-109, -149, 218 * (boss.hp / boss.maxHp), 20);
ctx.fillStyle = '#fff';
ctx.font = '16px monospace';
ctx.fillText(`BOSS HP: ${Math.ceil(boss.hp)}/${boss.maxHp}`, -110, -135);
ctx.restore();
}
function drawEnemyDuck(enemy) {
ctx.save();
ctx.translate(enemy.x, enemy.y);
ctx.scale(enemy.facing === 'left' ? -1 : 1, 1);
ctx.fillStyle = '#8B4513';
ctx.fillRect(-12, -8, 26, 18);
ctx.fillRect(-4, -16, 18, 18);
ctx.fillStyle = '#FF0000';
ctx.fillRect(4, -14, 4, 4);
ctx.fillStyle = '#000';
ctx.fillRect(2, -18, 8, 2);
ctx.fillStyle = '#FF6B6B';
ctx.fillRect(8, -10, 10, 7);
ctx.fillStyle = '#654321';
ctx.fillRect(-8, -4, 10, 8);
ctx.fillStyle = '#FF6B6B';
ctx.fillRect(-8, 8, 5, 7);
ctx.fillRect(4, 8, 5, 7);
ctx.fillStyle = '#333';
ctx.fillRect(-15, -25, 30, 5);
ctx.fillStyle = '#FF0000';
ctx.fillRect(-14, -24, 28 * (enemy.hp / enemy.maxHp), 4);
ctx.restore();
}
function drawFire() {
fireParticles.forEach((p, index) => {
ctx.fillStyle = p.color;
ctx.beginPath();
ctx.arc(p.x, p.y, p.size * p.life, 0, Math.PI * 2);
ctx.fill();
p.x += p.vx;
p.y += p.vy;
p.life -= 0.02;
enemyDucks.forEach((enemy, enemyIndex) => {
const dx = p.x - (enemy.x + enemy.width/2);
const dy = p.y - (enemy.y + enemy.height/2);
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 25) {
enemy.hp -= 0.1;
p.life = 0;
if (enemy.hp <= 0) {
enemyDucks.splice(enemyIndex, 1);
score += 10;
ducksKilled++;
document.getElementById('score').textContent = score;
if (ducksKilled >= 10 && !bossActive) {
setTimeout(spawnBossDuck, 1000);
document.getElementById('boss-warning').style.display = 'block';
setTimeout(() => { document.getElementById('boss-warning').style.display = 'none'; }, 3000);
} else if (!bossActive) {
setTimeout(spawnEnemyDuck, 2000);
}
}
}
});
if (bossActive && boss) {
const dx = p.x - (boss.x + boss.width/2);
const dy = p.y - (boss.y + boss.height/2);
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 120) {
boss.hp -= 0.2;
p.life = 0;
if (boss.hp <= 0) {
boss = null;
bossActive = false;
score += 100;
ducksKilled = 0;
document.getElementById('score').textContent = score;
for (let i = 0; i < ENEMY_COUNT; i++) {
setTimeout(() => spawnEnemyDuck(), i * 500);
}
}
}
}
if (p.life <= 0) fireParticles.splice(index, 1);
});
}
function drawBackground() {
const gradient = ctx.createLinearGradient(0, 0, 0, canvas.height);
gradient.addColorStop(0, '#4a9eff');
gradient.addColorStop(1, '#87ceeb');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#228B22';
ctx.fillRect(0, canvas.height - 100, canvas.width, 100);
ctx.fillStyle = '#32CD32';
for (let i = 0; i < canvas.width; i += 10) {
ctx.fillRect(i, canvas.height - 105, 4, 10);
}
ctx.fillStyle = '#fff';
ctx.fillRect(100, 50, 60, 20);
ctx.fillRect(115, 30, 30, 20);
ctx.fillRect(200, 80, 50, 18);
ctx.fillRect(215, 60, 25, 18);
}
function update() {
if (keys['left']) { duck.x -= duck.speed; duck.facing = 'left'; }
if (keys['right']) { duck.x += duck.speed; duck.facing = 'right'; }
if (keys['up']) duck.y -= duck.speed;
if (keys['down']) duck.y += duck.speed;
duck.x = Math.max(20, Math.min(canvas.width - 20, duck.x));
duck.y = Math.max(20, Math.min(canvas.height - 20, duck.y));
enemyDucks.forEach(enemy => {
const dx = duck.x - enemy.x;
const dy = duck.y - enemy.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
enemy.x += (dx / distance) * ENEMY_SPEED;
enemy.y += (dy / distance) * ENEMY_SPEED;
}
enemy.facing = dx > 0 ? 'right' : 'left';
});
if (bossActive && boss) {
const dx = duck.x - boss.x;
const dy = duck.y - boss.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
boss.x += (dx / distance) * boss.speed;
boss.y += (dy / distance) * boss.speed;
}
boss.facing = dx > 0 ? 'right' : 'left';
}
}
function gameLoop() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawBackground();
if (bossActive && boss) drawBossDuck();
enemyDucks.forEach(enemy => drawEnemyDuck(enemy));
drawFire();
drawDuck();
update();
requestAnimationFrame(gameLoop);
}
gameLoop();
</script>
</body>
</html>