🎮ArcadeLab

Стикмен Бой: Оружие и Счёт

by HyperRider36
494 lines21.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: #1a1a2e; display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100vh; font-family: Arial; overflow: hidden; touch-action: none; }
  canvas { background: #2a2a4e; border: 2px solid #fff; border-radius: 8px; touch-action: none; max-width: 100vw; max-height: 80vh; }
  #hud { font-size: 16px; margin-bottom: 5px; color: #fff; font-weight: bold; text-align: center; }
  .controls { position: absolute; bottom: 10px; width: 100%; display: flex; justify-content: space-between; padding: 0 10px; box-sizing: border-box; pointer-events: none; }
  .player-controls { display: flex; gap: 10px; pointer-events: auto; }
  .btn { width: 55px; height: 55px; border-radius: 50%; background: rgba(255,255,255,0.2); color: #fff; font-size: 20px; border: 2px solid #fff; display: flex; align-items: center; justify-content: center; user-select: none; touch-action: none; }
  #p1-left { background: rgba(0,150,255,0.4); } #p1-right { background: rgba(0,150,255,0.4); }
  #p1-jump { background: rgba(0,255,0,0.4); } #p1-punch { background: rgba(255,0,0,0.4); }
  #p2-left { background: rgba(255,100,100,0.4); } #p2-right { background: rgba(255,100,100,0.4); }
  #p2-jump { background: rgba(0,255,0,0.4); } #p2-punch { background: rgba(255,0,0,0.4); }
</style>
</head>
<body>
<div id="hud">Стикмен Бой: Оружие</div>
<canvas id="game" width="800" height="400"></canvas>
<div class="controls">
  <div class="player-controls">
    <div class="btn" id="p1-left">◀</div>
    <div class="btn" id="p1-right">▶</div>
    <div class="btn" id="p1-jump">▲</div>
    <div class="btn" id="p1-punch">👊</div>
  </div>
  <div class="player-controls">
    <div class="btn" id="p2-left">◀</div>
    <div class="btn" id="p2-right">▶</div>
    <div class="btn" id="p2-jump">▲</div>
    <div class="btn" id="p2-punch">👊</div>
  </div>
</div>

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

let state = 'menu';
let p1, p2, platforms, particles, weapons;
let winner = null;
let score = { p1: 0, p2: 0 };
let currentMenuPage = 0; // 0 - главная, 1 - карты, 2 - скин P1, 3 - скин P2, 4 - оружие
const groundY = 340;

const GRAVITY = 0.8;
const JUMP_POWER = -12; 
const MOVE_SPEED = 3.5; 
const PUNCH_RANGE = 60;
const PUNCH_DURATION = 20;

let currentMapIndex = 0;
let p1SkinIndex = 0;
let p2SkinIndex = 1;

// Карты (Лава удалена)
const MAPS = [
  { name: 'Классика', bg1: '#2a2a4e', bg2: '#1a1a2e', platform: '#444', type: 'classic' },
  { name: 'Киберпанк', bg1: '#0a0a2e', bg2: '#1a1a4e', platform: '#00ffff', type: 'cyber' },
  { name: 'Пустыня', bg1: '#c2b280', bg2: '#8b7d5a', platform: '#d4a017', type: 'desert' },
  { name: 'Арена', bg1: '#3a1a1a', bg2: '#1a0a0a', platform: '#8B4513', type: 'arena' }
];

const SKINS = [
  { name: 'Классика', body: '#3498db', dark: '#21618c', bow: false },
  { name: 'Тень', body: '#333', dark: '#111', bow: false },
  { name: 'Огонь', body: '#ff4400', dark: '#aa2200', bow: false },
  { name: 'Кислота', body: '#00ff00', dark: '#008800', bow: false },
  { name: 'Розовый', body: '#ff69b4', dark: '#cc5590', bow: true }
];

// Оружие
const WEAPON_TYPES = [
  { type: 'ball', name: 'Мяч', color: '#fff', damage: 5 },
  { type: 'gun', name: 'Пистолет', color: '#333', damage: 15 },
  { type: 'shovel', name: 'Лопата', color: '#8B4513', damage: 20 },
  { type: 'bow', name: 'Лук', color: '#654321', damage: 10 },
  { type: 'copper', name: 'Медь', color: '#b87333', damage: 25 },
  { type: 'seed', name: 'Сид', color: '#90ee90', damage: 8 }
];

const keys = {
  p1: { left: false, right: false, jump: false, punch: false },
  p2: { left: false, right: false, jump: false, punch: false }
};

function initGame() {
  p1 = { x: 200, y: 300, vx: 0, vy: 0, w: 30, h: 50, hp: 100, color: SKINS[p1SkinIndex].body, dark: SKINS[p1SkinIndex].dark, bow: SKINS[p1SkinIndex].bow, facing: 1, punchTimer: 0, onGround: false, walkTimer: 0, weapon: null };
  p2 = { x: 600, y: 300, vx: 0, vy: 0, w: 30, h: 50, hp: 100, color: SKINS[p2SkinIndex].body, dark: SKINS[p2SkinIndex].dark, bow: SKINS[p2SkinIndex].bow, facing: -1, punchTimer: 0, onGround: false, walkTimer: 0, weapon: null };
  
  platforms = [
    { x: 0, y: groundY, w: 800, h: 60 },
    { x: 250, y: 260, w: 80, h: 15 },
    { x: 470, y: 260, w: 80, h: 15 },
    { x: 360, y: 180, w: 80, h: 15 }
  ];
  
  weapons = [];
  if (currentMapIndex === 3) {
    weapons = [
      { ...WEAPON_TYPES[0], x: 100, y: 380, collected: false },
      { ...WEAPON_TYPES[1], x: 400, y: 380, collected: false },
      { ...WEAPON_TYPES[2], x: 650, y: 380, collected: false },
      { ...WEAPON_TYPES[3], x: 300, y: 380, collected: false },
      { ...WEAPON_TYPES[4], x: 550, y: 380, collected: false },
      { ...WEAPON_TYPES[5], x: 200, y: 380, collected: false }
    ];
  }
  
  particles = [];
  winner = null;
}

function updatePhysics(p, other) {
  p.vy += GRAVITY;
  p.y += p.vy;
  p.x += p.vx;
  
  if (Math.abs(p.vx) > 0.5) p.walkTimer += 0.15;
  else p.walkTimer = 0;

  p.vx = 0;

  if (p.x < 0) p.x = 0;
  if (p.x + p.w > canvas.width) p.x = canvas.width - p.w;

  p.onGround = false;
  platforms.forEach(pl => {
    if (p.x + p.w > pl.x && p.x < pl.x + pl.w && p.y + p.h > pl.y && p.y + p.h < pl.y + pl.h + 20 && p.vy >= 0) {
      p.y = pl.y - p.h; p.vy = 0; p.onGround = true;
    }
  });

  if (p.weapon === null && weapons) {
    weapons.forEach(w => {
      if (!w.collected && p.x + p.w > w.x && p.x < w.x + 20 && p.y + p.h > w.y && p.y < w.y + 20) {
        w.collected = true;
        p.weapon = w;
        particles.push({ x: p.x + p.w/2, y: p.y, vx: 0, vy: -3, life: 30, color: '#fff' });
      }
    });
  }

  if (p.punchTimer > 0) p.punchTimer--;

  if (p.punchTimer === 15) {
    const damage = p.weapon ? p.weapon.damage : 10;
    const hitX = p.facing > 0 ? p.x + p.w : p.x - PUNCH_RANGE;
    if (hitX < other.x + other.w && hitX + PUNCH_RANGE > other.x && p.y < other.y + other.h && p.y + p.h > other.y) {
      other.hp -= damage;
      particles.push({ x: other.x + other.w/2, y: other.y + other.h/2, vx: (Math.random() - 0.5) * 8, vy: -5, life: 30, color: '#fff' });
    }
    if (p.weapon && p.weapon.type !== 'ball') p.weapon = null;
  }
  if (p.hp < 0) p.hp = 0;
}

function update() {
  if (state !== 'playing') return;

  if (keys.p1.left) { p1.x -= MOVE_SPEED; p1.facing = -1; p1.vx = -MOVE_SPEED; }
  if (keys.p1.right) { p1.x += MOVE_SPEED; p1.facing = 1; p1.vx = MOVE_SPEED; }
  if (keys.p1.jump && p1.onGround) { p1.vy = JUMP_POWER; p1.onGround = false; }
  if (keys.p1.punch && p1.punchTimer === 0) p1.punchTimer = PUNCH_DURATION;

  if (keys.p2.left) { p2.x -= MOVE_SPEED; p2.facing = -1; p2.vx = -MOVE_SPEED; }
  if (keys.p2.right) { p2.x += MOVE_SPEED; p2.facing = 1; p2.vx = MOVE_SPEED; }
  if (keys.p2.jump && p2.onGround) { p2.vy = JUMP_POWER; p2.onGround = false; }
  if (keys.p2.punch && p2.punchTimer === 0) p2.punchTimer = PUNCH_DURATION;

  updatePhysics(p1, p2);
  updatePhysics(p2, p1);

  particles.forEach((pt, i) => {
    pt.x += pt.vx; pt.y += pt.vy; pt.vy += 0.2; pt.life--;
    if (pt.life <= 0) particles.splice(i, 1);
  });

  if (p1.hp <= 0) { winner = 'Победил ' + SKINS[p2SkinIndex].name + '!'; state = 'win'; score.p2++; }
  if (p2.hp <= 0) { winner = 'Победил ' + SKINS[p1SkinIndex].name + '!'; state = 'win'; score.p1++; }
}

function drawStickman(p) {
  const cx = p.x + p.w / 2;
  const baseY = p.y + p.h;
  const bob = Math.sin(p.walkTimer) * 2;
  const legOffset = Math.sin(p.walkTimer) * 10;
  
  ctx.fillStyle = 'rgba(0,0,0,0.3)';
  ctx.beginPath(); ctx.ellipse(cx, baseY + 2, p.w * 0.6, 5, 0, 0, Math.PI * 2); ctx.fill();

  ctx.strokeStyle = p.color;
  ctx.lineWidth = 5;
  ctx.lineCap = 'round';
  
  ctx.beginPath(); ctx.arc(cx, p.y + 10 + bob, 10, 0, Math.PI * 2);
  ctx.fillStyle = p.dark; ctx.fill(); ctx.stroke();

  if (p.bow) {
    ctx.fillStyle = '#ff0066';
    ctx.beginPath(); ctx.arc(cx - 6, p.y + 2 + bob, 5, 0, Math.PI * 2); ctx.arc(cx + 6, p.y + 2 + bob, 5, 0, Math.PI * 2); ctx.fill();
    ctx.fillStyle = '#ffdd00';
    ctx.beginPath(); ctx.arc(cx, p.y + 2 + bob, 3, 0, Math.PI * 2); ctx.fill();
  }

  ctx.fillStyle = '#fff';
  ctx.beginPath(); ctx.arc(cx - 3 + p.facing * 2, p.y + 8 + bob, 3, 0, Math.PI * 2); ctx.arc(cx + 3 + p.facing * 2, p.y + 8 + bob, 3, 0, Math.PI * 2); ctx.fill();
  ctx.fillStyle = '#000';
  ctx.beginPath(); ctx.arc(cx - 3 + p.facing * 3, p.y + 8 + bob, 1.5, 0, Math.PI * 2); ctx.arc(cx + 3 + p.facing * 3, p.y + 8 + bob, 1.5, 0, Math.PI * 2); ctx.fill();

  ctx.beginPath(); ctx.moveTo(cx, p.y + 20 + bob); ctx.lineTo(cx, p.y + 35); ctx.stroke();

  let punchAngle = 0;
  if (p.punchTimer > 0) {
    const progress = 1 - (p.punchTimer / PUNCH_DURATION);
    punchAngle = Math.sin(progress * Math.PI) * 30;
  }
  
  const armY = p.y + 25 + bob;
  ctx.beginPath(); ctx.moveTo(cx, armY); ctx.lineTo(cx - 15, armY + 5); ctx.stroke();
  ctx.beginPath(); ctx.moveTo(cx, armY); 
  if (p.facing > 0) ctx.lineTo(cx + 15 + punchAngle, armY + 5);
  else ctx.lineTo(cx - 15 - punchAngle, armY + 5);
  ctx.stroke();

  const leg1 = legOffset;
  const leg2 = -legOffset;
  ctx.beginPath(); ctx.moveTo(cx, p.y + 35); ctx.lineTo(cx - 10 + leg1, baseY); ctx.stroke();
  ctx.beginPath(); ctx.moveTo(cx, p.y + 35); ctx.lineTo(cx + 10 + leg2, baseY); ctx.stroke();

  if (p.weapon) {
    ctx.fillStyle = p.weapon.color;
    const wx = p.facing > 0 ? cx + 15 + punchAngle : cx - 25 - punchAngle;
    ctx.fillRect(wx, armY, 15, 8);
  }
}

function drawMenu() {
  const map = MAPS[currentMapIndex];
  ctx.fillStyle = map.bg1; ctx.fillRect(0, 0, canvas.width, canvas.height);
  ctx.fillStyle = map.bg2; ctx.fillRect(0, canvas.height / 2, canvas.width, canvas.height / 2);

  ctx.fillStyle = '#fff';
  ctx.font = 'bold 30px Arial';
  ctx.textAlign = 'center';

  if (currentMenuPage === 0) {
    ctx.fillText('СТИКМЕН БОЙ: ОРУЖИЕ', canvas.width/2, 80);
    ctx.font = '18px Arial';
    ctx.fillText('Карта: ' + MAPS[currentMapIndex].name, canvas.width/2, 150);
    ctx.fillText('Синий (P1): ' + SKINS[p1SkinIndex].name, canvas.width/2, 180);
    ctx.fillText('Красный (P2): ' + SKINS[p2SkinIndex].name, canvas.width/2, 210);
    ctx.fillText('Счёт: ' + score.p1 + ' - ' + score.p2, canvas.width/2, 240);

    const btnW = 140, btnH = 40, startX = 40, startY = 290;
    ctx.fillStyle = '#3498db'; ctx.fillRect(startX, startY, btnW, btnH);
    ctx.fillStyle = '#fff'; ctx.font = '16px Arial'; ctx.fillText('Скины P1', startX + 70, startY + 25);
    
    ctx.fillStyle = '#e94560'; ctx.fillRect(startX + 150, startY, btnW, btnH);
    ctx.fillStyle = '#fff'; ctx.fillText('Скины P2', startX + 220, startY + 25);

    ctx.fillStyle = '#ffd700'; ctx.fillRect(startX + 300, startY, btnW, btnH);
    ctx.fillStyle = '#000'; ctx.fillText('Карты', startX + 370, startY + 25);

    ctx.fillStyle = '#b87333'; ctx.fillRect(startX + 450, startY, btnW, btnH);
    ctx.fillStyle = '#fff'; ctx.fillText('Оружие', startX + 520, startY + 25);

    ctx.fillStyle = '#00ffcc'; ctx.fillRect(startX + 200, startY + 60, 200, 50);
    ctx.fillStyle = '#000'; ctx.font = 'bold 20px Arial'; ctx.fillText('▶ ИГРАТЬ', canvas.width/2, startY + 95);
  } 
  else if (currentMenuPage === 1) {
    ctx.fillText('ВЫБОР КАРТЫ', canvas.width/2, 60);
    ctx.font = '18px Arial';
    for (let i = 0; i < MAPS.length; i++) {
      const x = 60 + i * 170;
      ctx.fillStyle = MAPS[i].bg1;
      ctx.fillRect(x, 140, 140, 80);
      ctx.fillStyle = MAPS[i].platform;
      ctx.fillRect(x, 190, 140, 30);
      ctx.strokeStyle = (i === currentMapIndex) ? '#ffd700' : '#fff';
      ctx.lineWidth = (i === currentMapIndex) ? 4 : 2;
      ctx.strokeRect(x, 140, 140, 80);
      ctx.fillStyle = '#fff'; ctx.font = '14px Arial';
      ctx.fillText(MAPS[i].name, x + 70, 240);
    }
    ctx.fillStyle = '#fff'; ctx.font = '18px Arial';
    ctx.fillText('Нажми на карту, чтобы выбрать', canvas.width/2, 320);
    ctx.fillText('Нажми в любом месте, чтобы вернуться', canvas.width/2, 350);
  }
  else if (currentMenuPage === 2 || currentMenuPage === 3) {
    const player = currentMenuPage === 2 ? 'Синий (P1)' : 'Красный (P2)';
    ctx.fillText('СКИН: ' + player, canvas.width/2, 60);
    ctx.font = '18px Arial';
    for (let i = 0; i < SKINS.length; i++) {
      const x = 50 + i * 160;
      ctx.strokeStyle = SKINS[i].body; ctx.lineWidth = 4;
      ctx.beginPath(); ctx.arc(x + 60, 160, 15, 0, Math.PI * 2); ctx.fillStyle = SKINS[i].dark; ctx.fill(); ctx.stroke();
      ctx.beginPath(); ctx.moveTo(x + 60, 175); ctx.lineTo(x + 60, 210); ctx.stroke();
      ctx.beginPath(); ctx.moveTo(x + 45, 190); ctx.lineTo(x + 75, 190); ctx.stroke();
      ctx.beginPath(); ctx.moveTo(x + 60, 210); ctx.lineTo(x + 45, 235); ctx.stroke();
      ctx.beginPath(); ctx.moveTo(x + 60, 210); ctx.lineTo(x + 75, 235); ctx.stroke();

      if (SKINS[i].bow) {
        ctx.fillStyle = '#ff0066';
        ctx.beginPath(); ctx.arc(x + 54, 152, 5, 0, Math.PI * 2); ctx.arc(x + 66, 152, 5, 0, Math.PI * 2); ctx.fill();
      }

      const isSel = (currentMenuPage === 2 && i === p1SkinIndex) || (currentMenuPage === 3 && i === p2SkinIndex);
      ctx.strokeStyle = isSel ? '#ffd700' : '#fff'; ctx.lineWidth = isSel ? 4 : 1;
      ctx.strokeRect(x, 130, 120, 120);
      ctx.fillStyle = '#fff'; ctx.font = '14px Arial';
      ctx.fillText(SKINS[i].name, x + 60, 270);
    }
    ctx.fillStyle = '#fff'; ctx.font = '18px Arial';
    ctx.fillText('Нажми на скин, чтобы выбрать', canvas.width/2, 330);
    ctx.fillText('Нажми в любом месте, чтобы вернуться', canvas.width/2, 360);
  }
  else if (currentMenuPage === 4) {
    ctx.fillText('ОРУЖИЕ (АРЕНА)', canvas.width/2, 60);
    ctx.font = '16px Arial';
    for (let i = 0; i < WEAPON_TYPES.length; i++) {
      const x = 60 + i * 130;
      ctx.fillStyle = WEAPON_TYPES[i].color;
      ctx.fillRect(x, 150, 80, 40);
      ctx.fillStyle = '#fff';
      ctx.fillText(WEAPON_TYPES[i].name, x + 40, 220);
    }
    ctx.fillStyle = '#fff'; ctx.font = '18px Arial';
    ctx.fillText('Оружие появляется на карте "Арена"', canvas.width/2, 300);
    ctx.fillText('Нажми в любом месте, чтобы вернуться', canvas.width/2, 340);
  }
}

function draw() {
  if (state === 'menu') { drawMenu(); return; }

  const map = MAPS[currentMapIndex];
  const grad = ctx.createLinearGradient(0, 0, 0, canvas.height);
  grad.addColorStop(0, map.bg1); grad.addColorStop(1, map.bg2);
  ctx.fillStyle = grad; ctx.fillRect(0, 0, canvas.width, canvas.height);

  platforms.forEach(pl => {
    if (map.type === 'cyber') {
      ctx.fillStyle = '#00ffff'; ctx.fillRect(pl.x, pl.y, pl.w, pl.h);
      ctx.strokeStyle = '#fff'; ctx.lineWidth = 1;
      ctx.strokeRect(pl.x, pl.y, pl.w, pl.h);
    } else if (map.type === 'desert') {
      ctx.fillStyle = '#d4a017'; ctx.fillRect(pl.x, pl.y, pl.w, pl.h);
      ctx.fillStyle = '#8b7d5a'; ctx.fillRect(pl.x, pl.y + pl.h - 5, pl.w, 5);
    } else if (map.type === 'arena') {
      ctx.fillStyle = '#8B4513'; ctx.fillRect(pl.x, pl.y, pl.w, pl.h);
      ctx.fillStyle = '#654321';
      for (let i = 0; i < pl.w; i += 30) {
        ctx.fillRect(pl.x + i, pl.y, 15, 5);
      }
    } else {
      ctx.fillStyle = map.platform; ctx.fillRect(pl.x, pl.y, pl.w, pl.h);
      ctx.strokeStyle = '#222'; ctx.lineWidth = 2; ctx.strokeRect(pl.x, pl.y, pl.w, pl.h);
    }
  });

  if (weapons) {
    weapons.forEach(w => {
      if (w.collected) return;
      ctx.fillStyle = w.color;
      if (w.type === 'ball') {
        ctx.beginPath(); ctx.arc(w.x + 10, w.y + 10, 10, 0, Math.PI * 2); ctx.fill();
        ctx.strokeStyle = '#000'; ctx.stroke();
      } else if (w.type === 'gun') {
        ctx.fillRect(w.x, w.y + 5, 25, 10);
        ctx.fillStyle = '#666'; ctx.fillRect(w.x + 5, w.y, 10, 8);
      } else if (w.type === 'shovel') {
        ctx.fillRect(w.x, w.y + 8, 20, 4);
        ctx.fillRect(w.x + 18, w.y, 8, 20);
      } else if (w.type === 'bow') {
        ctx.beginPath(); ctx.arc(w.x + 10, w.y + 10, 12, 0, Math.PI); ctx.stroke();
        ctx.strokeStyle = '#fff'; ctx.beginPath(); ctx.moveTo(w.x + 10, w.y - 2); ctx.lineTo(w.x + 10, w.y + 22); ctx.stroke();
      } else if (w.type === 'copper') {
        ctx.fillRect(w.x, w.y, 20, 20);
        ctx.fillStyle = '#ffd700'; ctx.fillRect(w.x + 5, w.y + 5, 10, 10);
      } else if (w.type === 'seed') {
        ctx.beginPath(); ctx.ellipse(w.x + 10, w.y + 10, 8, 12, 0, 0, Math.PI * 2); ctx.fill();
      }
    });
  }

  ctx.fillStyle = '#000'; ctx.fillRect(20, 20, 300, 20);
  ctx.fillStyle = p1 ? p1.color : '#3498db'; ctx.fillRect(20, 20, p1 ? (p1.hp / 100) * 300 : 0, 20);
  ctx.fillStyle = '#000'; ctx.fillRect(480, 20, 300, 20);
  ctx.fillStyle = p2 ? p2.color : '#e94560'; ctx.fillRect(480, 20, p2 ? (p2.hp / 100) * 300 : 0, 20);

  ctx.fillStyle = '#fff'; ctx.font = 'bold 20px Arial'; ctx.textAlign = 'center';
  ctx.fillText(score.p1 + ' - ' + score.p2, canvas.width / 2, 35);

  if (p1) drawStickman(p1);
  if (p2) drawStickman(p2);

  particles.forEach(pt => {
    ctx.globalAlpha = pt.life / 30; ctx.fillStyle = pt.color;
    ctx.fillRect(pt.x, pt.y, 4, 4); ctx.globalAlpha = 1;
  });

  if (state === 'win') {
    ctx.fillStyle = 'rgba(0,0,0,0.7)'; ctx.fillRect(0, 0, canvas.width, canvas.height);
    ctx.fillStyle = '#fff'; ctx.font = 'bold 30px Arial'; ctx.textAlign = 'center';
    ctx.fillText(winner, canvas.width/2, canvas.height/2 - 20);
    ctx.font = '20px Arial'; ctx.fillText('Нажмите, чтобы выйти в меню', canvas.width/2, canvas.height/2 + 30);
  }
}

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

function holdBtn(id, player, action) {
  const el = document.getElementById(id);
  el.addEventListener('touchstart', (e) => { e.preventDefault(); keys[player][action] = true; });
  el.addEventListener('touchend', (e) => { e.preventDefault(); keys[player][action] = false; });
  el.addEventListener('mousedown', (e) => { e.preventDefault(); keys[player][action] = true; });
  el.addEventListener('mouseup', (e) => { e.preventDefault(); keys[player][action] = false; });
}
holdBtn('p1-left', 'p1', 'left'); holdBtn('p1-right', 'p1', 'right'); holdBtn('p1-jump', 'p1', 'jump'); holdBtn('p1-punch', 'p1', 'punch');
holdBtn('p2-left', 'p2', 'left'); holdBtn('p2-right', 'p2', 'right'); holdBtn('p2-jump', 'p2', 'jump'); holdBtn('p2-punch', 'p2', 'punch');

document.addEventListener('keydown', e => {
  if (e.key === 'a' || e.key === 'A') keys.p1.left = true;
  if (e.key === 'd' || e.key === 'D') keys.p1.right = true;
  if (e.key === 'w' || e.key === 'W') keys.p1.jump = true;
  if (e.key === 'f' || e.key === 'F') keys.p1.punch = true;
  if (e.key === 'ArrowLeft') keys.p2.left = true;
  if (e.key === 'ArrowRight') keys.p2.right = true;
  if (e.key === 'ArrowUp') keys.p2.jump = true;
  if (e.key === 'Enter') keys.p2.punch = true;
});
document.addEventListener('keyup', e => {
  if (e.key === 'a' || e.key === 'A') keys.p1.left = false;
  if (e.key === 'd' || e.key === 'D') keys.p1.right = false;
  if (e.key === 'w' || e.key === 'W') keys.p1.jump = false;
  if (e.key === 'f' || e.key === 'F') keys.p1.punch = false;
  if (e.key === 'ArrowLeft') keys.p2.left = false;
  if (e.key === 'ArrowRight') keys.p2.right = false;
  if (e.key === 'ArrowUp') keys.p2.jump = false;
  if (e.key === 'Enter') keys.p2.punch = false;
});

canvas.addEventListener('click', (e) => {
  const rect = canvas.getBoundingClientRect();
  const x = (e.clientX - rect.left) * (canvas.width / rect.width);
  const y = (e.clientY - rect.top) * (canvas.height / rect.height);

  if (state === 'menu') {
    if (currentMenuPage === 0) {
      if (x > 40 && x < 180 && y > 290 && y < 330) { currentMenuPage = 2; return; }
      if (x > 190 && x < 330 && y > 290 && y < 330) { currentMenuPage = 3; return; }
      if (x > 340 && x < 480 && y > 290 && y < 330) { currentMenuPage = 1; return; }
      if (x > 490 && x < 630 && y > 290 && y < 330) { currentMenuPage = 4; return; }
      if (x > 240 && x < 440 && y > 350 && y < 400) { initGame(); state = 'playing'; return; }
    } 
    else if (currentMenuPage === 1) {
      for (let i = 0; i < MAPS.length; i++) {
        const bx = 60 + i * 170;
        if (x > bx && x < bx + 140 && y > 140 && y < 220) { currentMapIndex = i; return; }
      }
      currentMenuPage = 0; return;
    }
    else if (currentMenuPage === 2 || currentMenuPage === 3) {
      for (let i = 0; i < SKINS.length; i++) {
        const bx = 50 + i * 160;
        if (x > bx && x < bx + 120 && y > 130 && y < 250) {
          if (currentMenuPage === 2) p1SkinIndex = i;
          else p2SkinIndex = i;
          return;
        }
      }
      currentMenuPage = 0; return;
    }
    else if (currentMenuPage === 4) {
      currentMenuPage = 0; return;
    }
  } 
  else if (state === 'win') {
    state = 'menu';
    currentMenuPage = 0;
  }
});

initGame();
state = 'menu';
loop();
</script>
</body>
</html>

Game Source: Стикмен Бой: Оружие и Счёт

Creator: HyperRider36

Libraries: none

Complexity: complex (494 lines, 21.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-mvuy7qtx" to link back to the original. Then publish at arcadelab.ai/publish.