🎮ArcadeLab

Мэлон

by TurboGamer88
519 lines21.6 KB
▶ Play
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>Мэлон</title>
<style>
  body { margin: 0; background: #0a0a12; display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100vh; font-family: Arial; overflow: hidden; touch-action: none; }
  canvas { background: #1a1a2e; border: 2px solid #4CAF50; border-radius: 8px; touch-action: none; }
  #hud { font-size: 13px; margin-bottom: 4px; color: #4CAF50; font-weight: bold; text-align: center; }
  #toolbar { position: absolute; bottom: 15px; left: 0; width: 100%; display: flex; justify-content: center; gap: 6px; pointer-events: none; flex-wrap: wrap; padding: 0 5px; box-sizing: border-box; }
  .tool { width: 45px; height: 45px; border-radius: 10px; background: rgba(76,175,80,0.25); color: #fff; font-size: 18px; border: 2px solid #4CAF50; display: flex; align-items: center; justify-content: center; user-select: none; pointer-events: auto; touch-action: none; }
  .tool.active { background: rgba(76,175,80,0.6); box-shadow: 0 0 10px #4CAF50; }
  #clearBtn { background: rgba(255,0,102,0.25); border-color: #ff0066; }
</style>
</head>
<body>
<div id="hud">🍉 Мэлон | Объектов: 0 | 💥 Взрывов: 0 | 🩸 Ранений: 0</div>
<canvas id="game" width="420" height="600"></canvas>
<div id="toolbar">
  <div class="tool active" data-tool="melon">🍉</div>
  <div class="tool" data-tool="pumpkin">🎃</div>
  <div class="tool" data-tool="box">📦</div>
  <div class="tool" data-tool="spike">🔺</div>
  <div class="tool" data-tool="gun">🔫</div>
  <div class="tool" data-tool="sword">⚔️</div>
  <div class="tool" data-tool="bomb">💣</div>
  <div class="tool" data-tool="cannon">💥</div>
  <div class="tool" id="clearBtn">🗑</div>
</div>

<script>
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const hud = document.getElementById('hud');

let objects = [];
let bullets = [];
let explosions = [];
const groundY = 560;
let selectedTool = 'melon';
let dragged = null;
let dragOffsetX = 0, dragOffsetY = 0;
let mouseX = 0, mouseY = 0;
let explosionCount = 0;
let woundCount = 0;

// --- МУЗЫКА ---
let audioCtx = null, musicOn = true, musicNodes = [], beatTimer = null, melodyTimer = null;
let soundOn = true;

function initAudio() {
  if (audioCtx) return;
  try { audioCtx = new (window.AudioContext || window.webkitAudioContext)(); startMusic(); } catch(e) {}
}
function startMusic() {
  if (!audioCtx || !musicOn) return;
  stopMusic();
  try {
    const bass = audioCtx.createOscillator();
    const bg = audioCtx.createGain();
    bass.type = 'sine'; bass.frequency.value = 65; bg.gain.value = 0.03;
    bass.connect(bg); bg.connect(audioCtx.destination); bass.start();
    musicNodes.push(bass, bg);
    function beat() {
      if (!audioCtx || !musicOn) return;
      try {
        const o = audioCtx.createOscillator(); const g = audioCtx.createGain();
        o.type = 'square'; o.frequency.value = 80;
        g.gain.setValueAtTime(0.05, audioCtx.currentTime);
        g.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 0.1);
        o.connect(g); g.connect(audioCtx.destination); o.start(); o.stop(audioCtx.currentTime + 0.1);
        beatTimer = setTimeout(beat, 500);
      } catch(e) {}
    }
    beat();
    const mel = [262,330,392,330,262,294,349,294]; let mi = 0;
    function melo() {
      if (!audioCtx || !musicOn) return;
      try {
        const o = audioCtx.createOscillator(); const g = audioCtx.createGain();
        o.type = 'triangle'; o.frequency.value = mel[mi % mel.length];
        g.gain.setValueAtTime(0.03, audioCtx.currentTime);
        g.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 0.3);
        o.connect(g); g.connect(audioCtx.destination); o.start(); o.stop(audioCtx.currentTime + 0.3);
        mi++; melodyTimer = setTimeout(melo, 600);
      } catch(e) {}
    }
    melo();
  } catch(e) {}
}
function stopMusic() {
  if (beatTimer) { clearTimeout(beatTimer); beatTimer = null; }
  if (melodyTimer) { clearTimeout(melodyTimer); melodyTimer = null; }
  musicNodes.forEach(n => { try { n.stop(); } catch(e) {} try { n.disconnect(); } catch(e) {} });
  musicNodes = [];
}
function playSound(freq, dur, type, vol) {
  if (!audioCtx || !soundOn) return;
  try {
    const o = audioCtx.createOscillator(); const g = audioCtx.createGain();
    o.type = type || 'sine'; o.frequency.value = freq;
    g.gain.setValueAtTime(vol || 0.08, audioCtx.currentTime);
    g.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + dur);
    o.connect(g); g.connect(audioCtx.destination); o.start(); o.stop(audioCtx.currentTime + dur);
  } catch(e) {}
}
function sfxSpawn() { playSound(600, 0.08, 'sine', 0.06); }
function sfxThud() { playSound(80, 0.12, 'sawtooth', 0.1); }
function sfxShoot() { playSound(900, 0.06, 'square', 0.1); }
function sfxSlash() { playSound(400, 0.08, 'sawtooth', 0.08); }
function sfxBoom() { playSound(60, 0.4, 'sawtooth', 0.2); setTimeout(() => playSound(40, 0.3, 'sawtooth', 0.15), 100); }
function sfxPain() { playSound(300, 0.15, 'sawtooth', 0.1); setTimeout(() => playSound(200, 0.15, 'sawtooth', 0.08), 80); }
function sfxClick() { playSound(800, 0.05, 'square', 0.06); }

// --- СОЗДАНИЕ ---
function createMelon(x, y) {
  const bw = 20, bh = 28, hr = 10;
  return { type: 'melon', alive: true, parts: [
    { x: x, y: y, w: bw, h: bh, vx: 0, vy: 0, angle: 0, va: 0, type: 'body', hitTimer: 0 },
    { x: x + bw/2, y: y - hr, r: hr, vx: 0, vy: 0, angle: 0, va: 0, type: 'head', hitTimer: 0 },
    { x: x - 5, y: y + 5, w: 6, h: 16, vx: 0, vy: 0, angle: 0, va: 0, type: 'armL', hitTimer: 0 },
    { x: x + bw - 1, y: y + 5, w: 6, h: 16, vx: 0, vy: 0, angle: 0, va: 0, type: 'armR', hitTimer: 0 },
    { x: x + 2, y: y + bh - 2, w: 7, h: 14, vx: 0, vy: 0, angle: 0, va: 0, type: 'legL', hitTimer: 0 },
    { x: x + bw - 9, y: y + bh - 2, w: 7, h: 14, vx: 0, vy: 0, angle: 0, va: 0, type: 'legR', hitTimer: 0 }
  ]};
}
function createPumpkin(x, y) {
  return { type: 'pumpkin', alive: true, x: x, y: y, w: 34, h: 34, vx: 0, vy: 0, angle: 0, va: 0, hitTimer: 0 };
}
function createBox(x, y) { return { type: 'box', alive: true, x, y, w: 40, h: 40, vx: 0, vy: 0, angle: 0, va: 0, fallen: false }; }
function createSpike(x, y) { return { type: 'spike', alive: true, x, y, w: 30, h: 30, vx: 0, vy: 0, angle: 0, va: 0, fallen: false }; }
function createGun(x, y) { return { type: 'gun', alive: true, x, y, w: 30, h: 14, vx: 0, vy: 0, angle: 0, va: 0, cooldown: 0, fallen: false }; }
function createSword(x, y) { return { type: 'sword', alive: true, x, y, w: 40, h: 8, vx: 0, vy: 0, angle: 0, va: 0, slashTimer: 0, fallen: false }; }
function createBomb(x, y) { return { type: 'bomb', alive: true, x, y, w: 24, h: 24, vx: 0, vy: 0, angle: 0, va: 0, fuse: 120, fallen: false }; }
function createCannon(x, y) { return { type: 'cannon', alive: true, x, y, w: 40, h: 24, vx: 0, vy: 0, angle: 0, va: 0, cooldown: 0, fallen: false }; }

function spawnAt(x, y) {
  initAudio();
  if (y > groundY - 40) y = groundY - 40;
  if (x < 50) x = 50;
  if (x > canvas.width - 50) x = canvas.width - 50;
  if (selectedTool === 'melon') objects.push(createMelon(x - 15, y - 30));
  else if (selectedTool === 'pumpkin') objects.push(createPumpkin(x - 17, y - 17));
  else if (selectedTool === 'box') objects.push(createBox(x - 20, y - 20));
  else if (selectedTool === 'spike') objects.push(createSpike(x - 15, y - 15));
  else if (selectedTool === 'gun') objects.push(createGun(x - 15, y - 7));
  else if (selectedTool === 'sword') objects.push(createSword(x - 20, y - 4));
  else if (selectedTool === 'bomb') objects.push(createBomb(x - 12, y - 12));
  else if (selectedTool === 'cannon') objects.push(createCannon(x - 20, y - 12));
  sfxSpawn();
  updateHUD();
}
function updateHUD() { hud.textContent = `🍉 Мэлон | Объектов: ${objects.length} | 💥 Взрывов: ${explosionCount} | 🩸 Ранений: ${woundCount}`; }

// --- ФИЗИКА (ИСПРАВЛЕНА) ---
function updatePhysics() {
  const G = 0.6, FR = 0.95, BOUNCE = 0.2;
  const W = canvas.width;

  objects.forEach(obj => {
    if (!obj.alive) return;

    if (obj.type === 'melon') {
      // Гравитация для всех частей
      obj.parts.forEach(p => {
        if (p.hitTimer > 0) p.hitTimer--;
        if (dragged && dragged.parts && dragged.parts.includes(p)) return;
        p.vy += G;
        p.vx *= FR;
        p.x += p.vx;
        p.y += p.vy;
        p.angle += p.va;
        p.va *= 0.95;
      });

      // Коллизии с землёй и стенами
      obj.parts.forEach(p => {
        if (dragged && dragged.parts && dragged.parts.includes(p)) return;
        const bottom = p.type === 'head' ? p.y + p.r : p.y + p.h;
        if (bottom > groundY) {
          if (p.type === 'head') p.y = groundY - p.r; else p.y = groundY - p.h;
          p.vy = -p.vy * BOUNCE;
          if (Math.abs(p.vy) > 1) sfxThud();
        }
        if (p.x < 0) { p.x = 0; p.vx = -p.vx * BOUNCE; }
        const pw = p.w || p.r*2;
        if (p.x + pw > W) { p.x = W - pw; p.vx = -p.vx * BOUNCE; }
      });

      // Связи между частями (арбуз стоит как человечек)
      const body = obj.parts[0];
      const head = obj.parts[1];
      const armL = obj.parts[2];
      const armR = obj.parts[3];
      const legL = obj.parts[4];
      const legR = obj.parts[5];

      if (!dragged || !dragged.parts.includes(head)) {
        head.x += (body.x + body.w/2 - head.r - head.x) * 0.2;
        head.y += (body.y - head.r * 2 - head.y) * 0.2;
      }
      if (!dragged || !dragged.parts.includes(armL)) {
        armL.x += (body.x - 5 - armL.x) * 0.15;
        armL.y += (body.y + 5 - armL.y) * 0.15;
      }
      if (!dragged || !dragged.parts.includes(armR)) {
        armR.x += (body.x + body.w - 1 - armR.x) * 0.15;
        armR.y += (body.y + 5 - armR.y) * 0.15;
      }
      if (!dragged || !dragged.parts.includes(legL)) {
        legL.x += (body.x + 2 - legL.x) * 0.15;
        legL.y += (body.y + body.h - 2 - legL.y) * 0.15;
      }
      if (!dragged || !dragged.parts.includes(legR)) {
        legR.x += (body.x + body.w - 9 - legR.x) * 0.15;
        legR.y += (body.y + body.h - 2 - legR.y) * 0.15;
      }

    } else {
      // Оружие и предметы падают
      if (dragged === obj) return;
      obj.vy += G;
      obj.vx *= FR;
      obj.x += obj.vx;
      obj.y += obj.vy;
      obj.angle += obj.va;
      obj.va *= 0.95;

      if (obj.y + obj.h > groundY) {
        obj.y = groundY - obj.h;
        obj.vy = -obj.vy * BOUNCE;
        obj.fallen = true;
      }
      if (obj.x < 0) { obj.x = 0; obj.vx = -obj.vx * BOUNCE; }
      if (obj.x + obj.w > W) { obj.x = W - obj.w; obj.vx = -obj.vx * BOUNCE; }

      // Оружие стреляет
      if (obj.type === 'gun') {
        if (obj.cooldown > 0) obj.cooldown--;
        if (obj.cooldown === 0 && obj.fallen && Math.random() < 0.01) {
          bullets.push({ x: obj.x + obj.w/2, y: obj.y + obj.h/2, vx: (Math.random() - 0.5) * 8, vy: -2, life: 120 });
          obj.cooldown = 60;
          sfxShoot();
        }
      }
      if (obj.type === 'cannon') {
        if (obj.cooldown > 0) obj.cooldown--;
        if (obj.cooldown === 0 && obj.fallen) {
          // Пушка стреляет в сторону ближайшего арбуза
          let target = null, minDist = Infinity;
          objects.forEach(o => {
            if (o.type === 'melon' && o.alive) {
              const d = Math.hypot(o.parts[0].x - obj.x, o.parts[0].y - obj.y);
              if (d < minDist) { minDist = d; target = o; }
            }
          });
          if (target) {
            const dx = target.parts[0].x - obj.x;
            const dy = target.parts[0].y - obj.y;
            const len = Math.hypot(dx, dy);
            bullets.push({ x: obj.x + obj.w/2, y: obj.y + obj.h/2, vx: dx/len * 8, vy: dy/len * 8, life: 120, isCannon: true });
            obj.cooldown = 90;
            sfxShoot();
          } else {
            obj.cooldown = 60;
          }
        }
      }
      if (obj.type === 'sword') {
        if (obj.slashTimer > 0) obj.slashTimer--;
      }
      if (obj.type === 'bomb') {
        obj.fuse--;
        if (obj.fuse <= 0) {
          explode(obj.x + obj.w/2, obj.y + obj.h/2);
          obj.alive = false;
        }
      }
    }
  });

  // Пули
  bullets.forEach((b, i) => {
    b.x += b.vx; b.y += b.vy; b.vy += 0.2; b.life--;
    if (b.life <= 0 || b.y > groundY) { bullets.splice(i, 1); return; }
    objects.forEach(obj => {
      if (obj.type === 'melon' && obj.alive) {
        obj.parts.forEach(p => {
          const cx = p.x + (p.w || p.r*2)/2;
          const cy = p.y + (p.h || p.r*2)/2;
          const r = (p.w || p.r*2)/2;
          if (Math.hypot(b.x - cx, b.y - cy) < r + 5) {
            p.vx += b.vx * 0.5; p.vy -= 2;
            p.hitTimer = 20;
            woundCount++;
            sfxPain();
            if (b.isCannon) explosionCount++;
            bullets.splice(i, 1);
            updateHUD();
          }
        });
      }
    });
  });
  bullets = bullets.filter(b => b.life > 0);

  // Взрывы
  explosions.forEach((ex, i) => {
    if (ex.isPain) {
      ex.x += ex.vx; ex.y += ex.vy; ex.alpha -= 0.05;
    } else {
      ex.r += 8; ex.alpha -= 0.05;
    }
    if (ex.alpha <= 0) explosions.splice(i, 1);
  });
}

function explode(x, y) {
  explosions.push({ x, y, r: 10, alpha: 1, isPain: false });
  explosionCount++;
  sfxBoom();
  updateHUD();
  objects.forEach(obj => {
    if (obj.type === 'melon' && obj.alive) {
      obj.parts.forEach(p => {
        const dx = (p.x + (p.w||p.r*2)/2) - x;
        const dy = (p.y + (p.h||p.r*2)/2) - y;
        const dist = Math.hypot(dx, dy);
        if (dist < 150) {
          const force = (150 - dist) / 150 * 15;
          p.vx += (dx / (dist || 1)) * force;
          p.vy += (dy / (dist || 1)) * force - 3;
          p.hitTimer = 30;
        }
      });
    }
  });
}

// --- РИСОВАНИЕ ---
function drawGround() {
  ctx.fillStyle = '#2d1f0d';
  ctx.fillRect(0, groundY, canvas.width, canvas.height - groundY);
  ctx.fillStyle = '#4CAF50';
  ctx.fillRect(0, groundY, canvas.width, 8);
  ctx.fillStyle = '#388E3C';
  for (let x = 0; x < canvas.width; x += 12) ctx.fillRect(x, groundY - 5, 3, 8);
}

function drawMelon(obj) {
  obj.parts.forEach(p => {
    ctx.save();
    ctx.translate(p.x + (p.w || p.r*2)/2, p.y + (p.h || p.r*2)/2);
    ctx.rotate(p.angle);
    const hurt = p.hitTimer > 0;
    if (p.type === 'body') {
      ctx.fillStyle = hurt ? '#ff4444' : '#4CAF50';
      ctx.beginPath(); ctx.ellipse(0, 0, p.w/2, p.h/2, 0, 0, Math.PI * 2); ctx.fill();
      ctx.strokeStyle = '#2E7D32'; ctx.lineWidth = 2; ctx.stroke();
      if (!hurt) {
        ctx.strokeStyle = '#1B5E20'; ctx.lineWidth = 1;
        for (let i = -2; i <= 2; i++) { ctx.beginPath(); ctx.moveTo(i * 5, -p.h/2 + 3); ctx.lineTo(i * 5, p.h/2 - 3); ctx.stroke(); }
      }
    } else if (p.type === 'head') {
      ctx.fillStyle = hurt ? '#ff4444' : '#66BB6A';
      ctx.beginPath(); ctx.arc(0, 0, p.r, 0, Math.PI * 2); ctx.fill();
      ctx.strokeStyle = '#2E7D32'; ctx.lineWidth = 2; ctx.stroke();
      if (hurt) {
        ctx.strokeStyle = '#fff'; ctx.lineWidth = 2;
        ctx.beginPath(); ctx.moveTo(-5, -4); ctx.lineTo(-1, 0); ctx.moveTo(-1, -4); ctx.lineTo(-5, 0); ctx.stroke();
        ctx.beginPath(); ctx.moveTo(3, -4); ctx.lineTo(7, 0); ctx.moveTo(7, -4); ctx.lineTo(3, 0); ctx.stroke();
      } else {
        ctx.fillStyle = '#fff'; ctx.beginPath(); ctx.arc(-3, -2, 2.5, 0, Math.PI * 2); ctx.arc(3, -2, 2.5, 0, Math.PI * 2); ctx.fill();
        ctx.fillStyle = '#000'; ctx.beginPath(); ctx.arc(-2.5, -2, 1.2, 0, Math.PI * 2); ctx.arc(3.5, -2, 1.2, 0, Math.PI * 2); ctx.fill();
        ctx.strokeStyle = '#000'; ctx.lineWidth = 1; ctx.beginPath(); ctx.arc(0, 2, 3, 0.1, Math.PI - 0.1); ctx.stroke();
      }
    } else {
      ctx.fillStyle = hurt ? '#ff4444' : '#4CAF50';
      ctx.fillRect(-p.w/2, -p.h/2, p.w, p.h);
      ctx.strokeStyle = '#2E7D32'; ctx.lineWidth = 1; ctx.strokeRect(-p.w/2, -p.h/2, p.w, p.h);
    }
    ctx.restore();
  });
}

function drawPumpkin(obj) {
  ctx.save();
  ctx.translate(obj.x + obj.w/2, obj.y + obj.h/2);
  ctx.rotate(obj.angle);
  const hurt = obj.hitTimer > 0;
  // Тыква
  ctx.fillStyle = hurt ? '#ff4444' : '#ff8c00';
  ctx.beginPath(); ctx.ellipse(0, 0, obj.w/2, obj.h/2, 0, 0, Math.PI * 2); ctx.fill();
  ctx.strokeStyle = '#cc6600'; ctx.lineWidth = 2; ctx.stroke();
  // Полоски
  ctx.strokeStyle = '#cc6600'; ctx.lineWidth = 1;
  for (let i = -2; i <= 2; i++) {
    ctx.beginPath(); ctx.moveTo(i * 7, -obj.h/2 + 5); ctx.lineTo(i * 7, obj.h/2 - 5); ctx.stroke();
  }
  // Глаза
  if (hurt) {
    ctx.strokeStyle = '#fff'; ctx.lineWidth = 2;
    ctx.beginPath(); ctx.moveTo(-8, -5); ctx.lineTo(-3, 0); ctx.moveTo(-3, -5); ctx.lineTo(-8, 0); ctx.stroke();
    ctx.beginPath(); ctx.moveTo(3, -5); ctx.lineTo(8, 0); ctx.moveTo(8, -5); ctx.lineTo(3, 0); ctx.stroke();
  } else {
    ctx.fillStyle = '#fff';
    ctx.beginPath(); ctx.arc(-5, -3, 4, 0, Math.PI * 2); ctx.arc(5, -3, 4, 0, Math.PI * 2); ctx.fill();
    ctx.fillStyle = '#000';
    ctx.beginPath(); ctx.arc(-4, -3, 2, 0, Math.PI * 2); ctx.arc(6, -3, 2, 0, Math.PI * 2); ctx.fill();
  }
  // Рот
  ctx.strokeStyle = '#000'; ctx.lineWidth = 2;
  ctx.beginPath(); ctx.arc(0, 3, 5, 0.1, Math.PI - 0.1); ctx.stroke();
  ctx.restore();
}

function drawBox(obj) {
  ctx.save(); ctx.translate(obj.x + obj.w/2, obj.y + obj.h/2); ctx.rotate(obj.angle);
  ctx.fillStyle = '#d4a017'; ctx.fillRect(-obj.w/2, -obj.h/2, obj.w, obj.h);
  ctx.strokeStyle = '#8B4513'; ctx.lineWidth = 3; ctx.strokeRect(-obj.w/2, -obj.h/2, obj.w, obj.h);
  ctx.strokeStyle = '#8B4513'; ctx.lineWidth = 2;
  ctx.beginPath(); ctx.moveTo(-obj.w/2, -obj.h/2); ctx.lineTo(obj.w/2, obj.h/2); ctx.stroke();
  ctx.beginPath(); ctx.moveTo(obj.w/2, -obj.h/2); ctx.lineTo(-obj.w/2, obj.h/2); ctx.stroke();
  ctx.restore();
}

function drawSpike(obj) {
  ctx.save(); ctx.translate(obj.x + obj.w/2, obj.y + obj.h/2); ctx.rotate(obj.angle);
  ctx.fillStyle = '#e94560'; ctx.beginPath();
  ctx.moveTo(0, -obj.h/2); ctx.lineTo(obj.w/2, obj.h/2); ctx.lineTo(-obj.w/2, obj.h/2); ctx.closePath(); ctx.fill();
  ctx.strokeStyle = '#aa0044'; ctx.lineWidth = 2; ctx.stroke();
  ctx.restore();
}

function drawGun(obj) {
  ctx.save(); ctx.translate(obj.x + obj.w/2, obj.y + obj.h/2); ctx.rotate(obj.angle);
  ctx.fillStyle = '#333'; ctx.fillRect(-obj.w/2, -obj.h/2, obj.w, obj.h);
  ctx.fillStyle = '#666'; ctx.fillRect(-obj.w/2, -obj.h/2 - 3, obj.w * 0.6, 4);
  ctx.strokeStyle = '#000'; ctx.lineWidth = 1; ctx.strokeRect(-obj.w/2, -obj.h/2, obj.w, obj.h);
  ctx.restore();
}

function drawSword(obj) {
  ctx.save(); ctx.translate(obj.x + obj.w/2, obj.y + obj.h/2); ctx.rotate(obj.angle);
  ctx.fillStyle = '#ccc'; ctx.fillRect(-obj.w/2, -2, obj.w * 0.7, 4);
  ctx.fillStyle = '#8B4513'; ctx.fillRect(obj.w/2 - obj.w * 0.3, -4, obj.w * 0.3, 8);
  if (obj.slashTimer > 0) {
    ctx.strokeStyle = 'rgba(255,255,255,0.8)'; ctx.lineWidth = 3;
    ctx.beginPath(); ctx.arc(0, 0, 30, -0.5, 0.5); ctx.stroke();
  }
  ctx.restore();
}

function drawBomb(obj) {
  ctx.save(); ctx.translate(obj.x + obj.w/2, obj.y + obj.h/2); ctx.rotate(obj.angle);
  ctx.fillStyle = '#222'; ctx.beginPath(); ctx.arc(0, 0, obj.w/2, 0, Math.PI * 2); ctx.fill();
  ctx.strokeStyle = '#000'; ctx.lineWidth = 2; ctx.stroke();
  ctx.strokeStyle = '#ff6600'; ctx.lineWidth = 2;
  ctx.beginPath(); ctx.moveTo(0, -obj.w/2); ctx.lineTo(5, -obj.w/2 - 8); ctx.stroke();
  if (Math.floor(Date.now() / 100) % 2 === 0) {
    ctx.fillStyle = '#ffdd00'; ctx.beginPath(); ctx.arc(5, -obj.w/2 - 8, 3, 0, Math.PI * 2); ctx.fill();
  }
  ctx.fillStyle = '#fff'; ctx.font = 'bold 10px Arial'; ctx.textAlign = 'center';
  ctx.fillText(Math.ceil(obj.fuse / 60), 0, 3); ctx.textAlign = 'left';
  ctx.restore();
}

function drawCannon(obj) {
  ctx.save(); ctx.translate(obj.x + obj.w/2, obj.y + obj.h/2); ctx.rotate(obj.angle);
  // Основание
  ctx.fillStyle = '#555';
  ctx.fillRect(-obj.w/2, 0, obj.w, obj.h/2);
  // Ствол
  ctx.fillStyle = '#333';
  ctx.fillRect(-obj.w/2 + 5, -obj.h/2, obj.w - 10, obj.h/2);
  ctx.strokeStyle = '#000'; ctx.lineWidth = 2;
  ctx.strokeRect(-obj.w/2 + 5, -obj.h/2, obj.w - 10, obj.h/2);
  // Дуло
  ctx.fillStyle = '#ff6600';
  ctx.beginPath(); ctx.arc(-obj.w/2 + 5, 0, 4, 0, Math.PI * 2); ctx.fill();
  ctx.restore();
}

function draw() {
  const grad = ctx.createLinearGradient(0, 0, 0, canvas.height);
  grad.addColorStop(0, '#1a1a2e'); grad.addColorStop(1, '#2a2a4e');
  ctx.fillStyle = grad; ctx.fillRect(0, 0, canvas.width, canvas.height);
  drawGround();

  objects.forEach(obj => {
    if (!obj.alive) return;
    if (obj.type === 'melon') drawMelon(obj);
    else if (obj.type === 'pumpkin') drawPumpkin(obj);
    else if (obj.type === 'box') drawBox(obj);
    else if (obj.type === 'spike') drawSpike(obj);
    else if (obj.type === 'gun') drawGun(obj);
    else if (obj.type === 'sword') drawSword(obj);
    else if (obj.type === 'bomb') drawBomb(obj);
    else if (obj.type === 'cannon') drawCannon(obj);
  });

  bullets.forEach(b => {
    ctx.fillStyle = b.isCannon ? '#ff6600' : '#ffdd00';
    ctx.beginPath(); ctx.arc(b.x, b.y, b.isCannon ? 6 : 4, 0, Math.PI * 2); ctx.fill();
    ctx.fillStyle = b.isCannon ? 'rgba(255,102,0,0.3)' : 'rgba(255,221,0,0.3)';
    ctx.beginPath(); ctx.arc(b.x, b.y, b.isCannon ? 10 : 8, 0, Math.PI * 2); ctx.fill();
  });

  explosions.forEach(ex => {
    ctx.globalAlpha = ex.alpha;
    if (ex.isPain) {
      ctx.fillStyle = '#ff4444';
      ctx.beginPath(); ctx.arc(ex.x, ex.y, ex.r, 0, Math.PI * 2); ctx.fill();
    } else {
      ctx.fillStyle = '#ff6600';
      ctx.beginPath(); ctx.arc(ex.x, ex.y, ex.r, 0, Math.PI * 2); ctx.fill();
      ctx.fillStyle = '#ffdd00';
      ctx.beginPath(); ctx.arc(ex

Game Source: Мэлон

Creator: TurboGamer88

Libraries: none

Complexity: complex (519 lines, 21.6 KB)

The full source code is displayed above on this page.

Remix Instructions

To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: game-turbogamer88" to link back to the original. Then publish at arcadelab.ai/publish.