🎮ArcadeLab

Красный Шарик: Босс

by HyperRider36
516 lines16.1 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: #87CEEB; display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100vh; font-family: Arial; overflow: hidden; touch-action: none; }
  canvas { background: #87CEEB; border: 3px solid #333; border-radius: 10px; touch-action: none; }
  #hud { font-size: 16px; margin-bottom: 6px; color: #333; font-weight: bold; text-align: center; }
  .btn { position: absolute; bottom: 20px; width: 70px; height: 70px; border-radius: 50%; background: rgba(0,0,0,0.25); color: #fff; font-size: 28px; border: none; display: flex; align-items: center; justify-content: center; user-select: none; }
  #left { left: 15px; }
  #right { left: 100px; }
  #jump { right: 15px; background: rgba(0,150,0,0.4); }
  #levelBtn { position: absolute; top: 10px; right: 10px; padding: 8px 14px; background: rgba(0,0,0,0.4); color: #fff; border: 2px solid #fff; border-radius: 8px; font-size: 14px; cursor: pointer; }
</style>
</head>
<body>
<div id="hud">Убито: 0 | Жизни: ❤❤❤ | Уровень: 1</div>
<canvas id="game" width="400" height="560"></canvas>
<button class="btn" id="left">◀</button>
<button class="btn" id="right">▶</button>
<button class="btn" id="jump">▲</button>
<button id="levelBtn">Уровень 1</button>

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

let player, platforms, enemies, armored, boss, keys = {};
let score = 0, lives = 3, gameOver = false, currentLevel = 1, bossDefeated = false;
let cameraX = 0;
let invincibleTimer = 0;
let screenShake = 0;

// Физика
const GRAVITY = 0.65;
const ACCEL = 0.9;
const MAX_SPEED = 6.5;
const FRICTION = 0.9;
const JUMP_FORCE = -14;
const MAX_FALL = 20;

function initLevel(level) {
  currentLevel = level;
  player = { x: 100, y: 400, r: 18, vx: 0, vy: 0, onGround: false, rotation: 0, facing: 1 };
  platforms = [];
  enemies = [];
  armored = [];
  boss = null;
  score = 0;
  lives = 3;
  gameOver = false;
  bossDefeated = false;
  cameraX = 0;
  invincibleTimer = 0;
  screenShake = 0;
  levelBtn.textContent = 'Уровень ' + level;
  updateHUD();

  // Земля
  platforms.push({ x: 0, y: 520, w: 1500, h: 80 });

  let px = 300;
  const levelLength = level === 1 ? 15 : (level === 2 ? 18 : 20);

  for (let i = 0; i < levelLength; i++) {
    const py = 460 - (i % 3) * 80;
    const pw = 100 + Math.random() * 80;
    platforms.push({ x: px, y: py, w: pw, h: 22 });

    // Обычные синие квадраты
    if (i > 2 && Math.random() > 0.3) {
      enemies.push({
        x: px + 20, y: py - 36, w: 32, h: 32,
        alive: true,
        vx: (Math.random() > 0.5 ? 1 : -1) * 1.5,
        minX: px, maxX: px + pw - 32,
        hp: 1, maxHp: 1, hitTimer: 0
      });
    }

    // Бронеквадраты (2 удара) — со 2-го уровня
    if (level >= 2 && i > 4 && Math.random() > 0.6) {
      armored.push({
        x: px + 30, y: py - 40, w: 36, h: 36,
        alive: true,
        vx: (Math.random() > 0.5 ? 1 : -1) * 1.2,
        minX: px, maxX: px + pw - 36,
        hp: 2, maxHp: 2, hitTimer: 0
      });
    }

    px += 180 + Math.random() * 60;
  }

  // Финальный босс — на 3-м уровне
  if (level === 3) {
    boss = {
      x: px + 100, y: 400, w: 70, h: 70,
      alive: true,
      vx: 1.5,
      minX: px + 50, maxX: px + 300,
      hp: 5, maxHp: 5,
      hitTimer: 0,
      attackTimer: 0
    };
    platforms.push({ x: px + 50, y: 520, w: 350, h: 80 });
  }
}

function updateHUD() {
  let text = 'Убито: ' + score + ' | Жизни: ' + '❤'.repeat(Math.max(0, lives)) + ' | Уровень: ' + currentLevel;
  if (boss && boss.alive) text += ' | БОСС: ' + boss.hp + '/' + boss.maxHp;
  hud.textContent = text;
}

function drawPlayer() {
  const px = player.x - cameraX;
  const py = player.y;

  if (invincibleTimer > 0 && Math.floor(invincibleTimer / 5) % 2 === 0) return;

  // Тень
  ctx.fillStyle = 'rgba(0,0,0,0.2)';
  ctx.beginPath();
  ctx.ellipse(px, py + player.r + 2, player.r * 0.8, 4, 0, 0, Math.PI * 2);
  ctx.fill();

  // Тело (вращается)
  ctx.save();
  ctx.translate(px, py);
  ctx.rotate(player.rotation);
  ctx.beginPath();
  ctx.arc(0, 0, player.r, 0, Math.PI * 2);
  ctx.fillStyle = '#e94560';
  ctx.fill();
  ctx.strokeStyle = '#b02a40';
  ctx.lineWidth = 3;
  ctx.stroke();
  ctx.restore();

  // Шляпа (фиксированная сверху)
  ctx.fillStyle = '#222';
  ctx.fillRect(px - 10, py - player.r - 8, 20, 4);
  ctx.fillRect(px - 6, py - player.r - 16, 12, 9);

  // Глаза (фиксированные, смотрят по направлению)
  const eyeOffX = player.facing * 2;
  ctx.fillStyle = '#fff';
  ctx.beginPath();
  ctx.arc(px - 6 + eyeOffX, py - 4, 5, 0, Math.PI * 2);
  ctx.arc(px + 6 + eyeOffX, py - 4, 5, 0, Math.PI * 2);
  ctx.fill();
  ctx.fillStyle = '#000';
  ctx.beginPath();
  ctx.arc(px - 6 + eyeOffX + player.facing, py - 4, 2.5, 0, Math.PI * 2);
  ctx.arc(px + 6 + eyeOffX + player.facing, py - 4, 2.5, 0, Math.PI * 2);
  ctx.fill();

  // Рот
  ctx.strokeStyle = '#000';
  ctx.lineWidth = 2;
  ctx.beginPath();
  ctx.arc(px + eyeOffX, py + 6, 5, 0, Math.PI);
  ctx.stroke();

  // Руки (машут при движении)
  const armSwing = Math.sin(Date.now() / 100) * (Math.abs(player.vx) > 1 ? 6 : 0);
  ctx.strokeStyle = '#e94560';
  ctx.lineWidth = 4;
  ctx.beginPath();
  ctx.moveTo(px - player.r + 2, py - 2);
  ctx.lineTo(px - player.r - 6, py + 4 + armSwing);
  ctx.stroke();
  ctx.beginPath();
  ctx.moveTo(px + player.r - 2, py - 2);
  ctx.lineTo(px + player.r + 6, py + 4 - armSwing);
  ctx.stroke();
}

function draw() {
  ctx.save();
  if (screenShake > 0) {
    ctx.translate((Math.random() - 0.5) * 8, (Math.random() - 0.5) * 8);
  }

  // Фон
  const grad = ctx.createLinearGradient(0, 0, 0, canvas.height);
  grad.addColorStop(0, '#87CEEB');
  grad.addColorStop(1, '#b8e0f0');
  ctx.fillStyle = grad;
  ctx.fillRect(0, 0, canvas.width, canvas.height);

  // Облака
  ctx.fillStyle = 'rgba(255,255,255,0.7)';
  for (let i = 0; i < 10; i++) {
    const cx = (i * 220 - cameraX * 0.3) % 1400;
    const cy = 60 + (i % 3) * 50;
    ctx.beginPath();
    ctx.arc(cx, cy, 30, 0, Math.PI * 2);
    ctx.arc(cx + 30, cy, 25, 0, Math.PI * 2);
    ctx.arc(cx - 25, cy, 20, 0, Math.PI * 2);
    ctx.fill();
  }

  // Платформы
  platforms.forEach(p => {
    ctx.fillStyle = '#8B4513';
    ctx.fillRect(p.x - cameraX, p.y, p.w, p.h);
    ctx.fillStyle = '#4CAF50';
    ctx.fillRect(p.x - cameraX, p.y, p.w, 8);
  });

  // Обычные враги
  enemies.forEach(e => {
    if (!e.alive) return;
    const flash = e.hitTimer > 0 && Math.floor(e.hitTimer / 3) % 2 === 0;
    ctx.fillStyle = flash ? '#fff' : '#2d6cdf';
    ctx.fillRect(e.x - cameraX, e.y, e.w, e.h);
    ctx.strokeStyle = '#1a4a9e';
    ctx.lineWidth = 3;
    ctx.strokeRect(e.x - cameraX, e.y, e.w, e.h);
    ctx.fillStyle = '#fff';
    ctx.fillRect(e.x - cameraX + 6, e.y + 8, 8, 8);
    ctx.fillRect(e.x - cameraX + 18, e.y + 8, 8, 8);
    ctx.fillStyle = '#000';
    ctx.fillRect(e.x - cameraX + 9, e.y + 11, 4, 4);
    ctx.fillRect(e.x - cameraX + 21, e.y + 11, 4, 4);
  });

  // Бронеквадраты (оранжевые, HP 2)
  armored.forEach(e => {
    if (!e.alive) return;
    const flash = e.hitTimer > 0 && Math.floor(e.hitTimer / 3) % 2 === 0;
    ctx.fillStyle = flash ? '#fff' : '#ff8c00';
    ctx.fillRect(e.x - cameraX, e.y, e.w, e.h);
    ctx.strokeStyle = '#cc6600';
    ctx.lineWidth = 4;
    ctx.strokeRect(e.x - cameraX, e.y, e.w, e.h);
    // Полоска HP
    ctx.fillStyle = '#000';
    ctx.fillRect(e.x - cameraX, e.y - 8, e.w, 5);
    ctx.fillStyle = '#0f0';
    ctx.fillRect(e.x - cameraX, e.y - 8, e.w * (e.hp / e.maxHp), 5);
    // Глаза
    ctx.fillStyle = '#fff';
    ctx.fillRect(e.x - cameraX + 7, e.y + 10, 8, 8);
    ctx.fillRect(e.x - cameraX + 21, e.y + 10, 8, 8);
    ctx.fillStyle = '#000';
    ctx.fillRect(e.x - cameraX + 10, e.y + 13, 4, 4);
    ctx.fillRect(e.x - cameraX + 24, e.y + 13, 4, 4);
  });

  // Босс
  if (boss && boss.alive) {
    const flash = boss.hitTimer > 0 && Math.floor(boss.hitTimer / 3) % 2 === 0;
    ctx.fillStyle = flash ? '#fff' : '#8e44ad';
    ctx.fillRect(boss.x - cameraX, boss.y, boss.w, boss.h);
    ctx.strokeStyle = '#5b2c6f';
    ctx.lineWidth = 5;
    ctx.strokeRect(boss.x - cameraX, boss.y, boss.w, boss.h);
    // Рога
    ctx.fillStyle = '#5b2c6f';
    ctx.beginPath();
    ctx.moveTo(boss.x - cameraX + 10, boss.y);
    ctx.lineTo(boss.x - cameraX + 5, boss.y - 20);
    ctx.lineTo(boss.x - cameraX + 20, boss.y);
    ctx.fill();
    ctx.beginPath();
    ctx.moveTo(boss.x - cameraX + boss.w - 10, boss.y);
    ctx.lineTo(boss.x - cameraX + boss.w - 5, boss.y - 20);
    ctx.lineTo(boss.x - cameraX + boss.w - 20, boss.y);
    ctx.fill();
    // Глаза
    ctx.fillStyle = '#f00';
    ctx.fillRect(boss.x - cameraX + 15, boss.y + 20, 12, 12);
    ctx.fillRect(boss.x - cameraX + 43, boss.y + 20, 12, 12);
    // HP бар
    ctx.fillStyle = '#000';
    ctx.fillRect(boss.x - cameraX, boss.y - 16, boss.w, 8);
    ctx.fillStyle = '#e94560';
    ctx.fillRect(boss.x - cameraX, boss.y - 16, boss.w * (boss.hp / boss.maxHp), 8);
    // Подпись
    ctx.fillStyle = '#fff';
    ctx.font = 'bold 12px Arial';
    ctx.fillText('БОСС', boss.x - cameraX + 15, boss.y - 20);
  }

  drawPlayer();

  ctx.restore();
}

function update() {
  if (gameOver) return;
  if (invincibleTimer > 0) invincibleTimer--;
  if (screenShake > 0) screenShake--;

  // Движение
  const left = keys['ArrowLeft'] || keys['a'] || keys['A'];
  const right = keys['ArrowRight'] || keys['d'] || keys['D'];

  if (left && !right) { player.vx -= ACCEL; player.facing = -1; }
  else if (right && !left) { player.vx += ACCEL; player.facing = 1; }
  else player.vx *= FRICTION;

  if (player.vx > MAX_SPEED) player.vx = MAX_SPEED;
  if (player.vx < -MAX_SPEED) player.vx = -MAX_SPEED;
  if (Math.abs(player.vx) < 0.1) player.vx = 0;

  player.rotation += player.vx * 0.05;

  // Гравитация
  player.vy += GRAVITY;
  if (player.vy > MAX_FALL) player.vy = MAX_FALL;

  // X
  player.x += player.vx;
  platforms.forEach(p => {
    if (player.x + player.r > p.x && player.x - player.r < p.x + p.w &&
        player.y + player.r > p.y && player.y - player.r < p.y + p.h) {
      if (player.vx > 0) player.x = p.x - player.r;
      else if (player.vx < 0) player.x = p.x + p.w + player.r;
      player.vx = 0;
    }
  });

  // Y
  player.y += player.vy;
  player.onGround = false;
  platforms.forEach(p => {
    if (player.x + player.r > p.x && player.x - player.r < p.x + p.w &&
        player.y + player.r > p.y && player.y - player.r < p.y + p.h) {
      if (player.vy > 0) {
        player.y = p.y - player.r;
        player.vy = 0;
        player.onGround = true;
      } else if (player.vy < 0) {
        player.y = p.y + p.h + player.r;
        player.vy = 0;
      }
    }
  });

  // Враги (синие)
  enemies.forEach(e => {
    if (!e.alive) return;
    if (e.hitTimer > 0) e.hitTimer--;
    e.x += e.vx;
    if (e.x < e.minX) { e.x = e.minX; e.vx = Math.abs(e.vx); }
    if (e.x > e.maxX) { e.x = e.maxX; e.vx = -Math.abs(e.vx); }

    const dx = player.x - (e.x + e.w/2);
    const dy = player.y - (e.y + e.h/2);
    if (Math.sqrt(dx*dx + dy*dy) < player.r + e.w/2) {
      if (player.vy > 0 && player.y - player.r < e.y + 10) {
        e.alive = false;
        score++;
        player.vy = -11;
        screenShake = 5;
        updateHUD();
      } else if (invincibleTimer === 0) {
        damagePlayer(e.x);
      }
    }
  });

  // Бронеквадраты (2 удара)
  armored.forEach(e => {
    if (!e.alive) return;
    if (e.hitTimer > 0) e.hitTimer--;
    e.x += e.vx;
    if (e.x < e.minX) { e.x = e.minX; e.vx = Math.abs(e.vx); }
    if (e.x > e.maxX) { e.x = e.maxX; e.vx = -Math.abs(e.vx); }

    const dx = player.x - (e.x + e.w/2);
    const dy = player.y - (e.y + e.h/2);
    if (Math.sqrt(dx*dx + dy*dy) < player.r + e.w/2) {
      if (player.vy > 0 && player.y - player.r < e.y + 10) {
        e.hp--;
        e.hitTimer = 20;
        player.vy = -11;
        screenShake = 5;
        if (e.hp <= 0) {
          e.alive = false;
          score += 2;
        }
        updateHUD();
      } else if (invincibleTimer === 0) {
        damagePlayer(e.x);
      }
    }
  });

  // Босс
  if (boss && boss.alive) {
    if (boss.hitTimer > 0) boss.hitTimer--;

    // Движение босса
    boss.x += boss.vx;
    if (boss.x < boss.minX) { boss.x = boss.minX; boss.vx = Math.abs(boss.vx); }
    if (boss.x > boss.maxX) { boss.x = boss.maxX; boss.vx = -Math.abs(boss.vx); }

    boss.attackTimer++;
    // Босс периодически прыгает
    if (boss.attackTimer > 120) {
      boss.attackTimer = 0;
      boss.vy = -8;
    }
    if (boss.vy !== undefined) {
      boss.vy += GRAVITY;
      boss.y += boss.vy;
      if (boss.y > 520 - boss.h) {
        boss.y = 520 - boss.h;
        boss.vy = 0;
      }
    }

    const dx = player.x - (boss.x + boss.w/2);
    const dy = player.y - (boss.y + boss.h/2);
    if (Math.sqrt(dx*dx + dy*dy) < player.r + boss.w/2) {
      if (player.vy > 0 && player.y - player.r < boss.y + 15) {
        boss.hp--;
        boss.hitTimer = 20;
        player.vy = -12;
        screenShake = 10;
        if (boss.hp <= 0) {
          boss.alive = false;
          bossDefeated = true;
          score += 10;
          updateHUD();
          setTimeout(() => {
            alert('БОСС ПОБЕЖДЁН! Ты прошёл игру! 🎉\nОбнови страницу, чтобы играть снова.');
          }, 100);
        }
        updateHUD();
      } else if (invincibleTimer === 0) {
        damagePlayer(boss.x);
      }
    }
  }

  // Камера
  cameraX = player.x - 150;
  if (cameraX < 0) cameraX = 0;

  // Падение
  if (player.y > 650) {
    lives--;
    if (lives <= 0) {
      gameOver = true;
      alert('Игра окончена! Убито: ' + score + '\nОбнови страницу, чтобы играть снова.');
    } else {
      player.x = 100; player.y = 400; player.vx = 0; player.vy = 0;
      cameraX = 0; invincibleTimer = 90;
      updateHUD();
    }
  }
}

function damagePlayer(fromX) {
  lives--;
  invincibleTimer = 90;
  player.vy = -9;
  player.vx = (player.x < fromX ? -1 : 1) * 7;
  screenShake = 10;
  if (lives <= 0) {
    gameOver = true;
    alert('Игра окончена! Убито: ' + score + '\nОбнови страницу, чтобы играть снова.');
  } else {
    updateHUD();
  }
}

function loop() { update(); draw(); requestAnimationFrame(loop); }

// Клавиатура
document.addEventListener('keydown', e => {
  keys[e.key] = true;
  if (e.key === ' ' || e.key === 'ArrowUp' || e.key === 'w' || e.key === 'W') {
    if (player.onGround) { player.vy = JUMP_FORCE; player.onGround = false; }
    e.preventDefault();
  }
});
document.addEventListener('keyup', e => { keys[e.key] = false; });

// Кнопки
function holdBtn(id, key) {
  const el = document.getElementById(id);
  el.addEventListener('touchstart', e => { e.preventDefault(); keys[key] = true; });
  el.addEventListener('touchend', e => { e.preventDefault(); keys[key] = false; });
  el.addEventListener('mousedown', e => { e.preventDefault(); keys[key] = true; });
  el.addEventListener('mouseup', e => { e.preventDefault(); keys[key] = false; });
}
holdBtn('left', 'ArrowLeft');
holdBtn('right', 'ArrowRight');

function doJump() { if (player.onGround) { player.vy = JUMP_FORCE; player.onGround = false; } }
document.getElementById('jump').addEventListener('touchstart', e => { e.preventDefault(); doJump(); });
document.getElementById('jump').addEventListener('mousedown', e => { e.preventDefault(); doJump(); });

// Кнопка уровня
let levelIndex = 1;
levelBtn.addEventListener('click', () => {
  levelIndex = levelIndex % 3 + 1;
  initLevel(levelIndex);
});

initLevel(1);
loop();
</script>
</body>
</html>

Game Source: Красный Шарик: Босс

Creator: HyperRider36

Libraries: none

Complexity: complex (516 lines, 16.1 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-hyperrider36-mvtg6w7y" to link back to the original. Then publish at arcadelab.ai/publish.