🎮ArcadeLab

Песочница

by HyperRider36
256 lines13.4 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 #00ffcc; border-radius: 8px; touch-action: none; max-width: 100vw; max-height: 70vh; }
  #hud { font-size: 13px; margin-bottom: 3px; color: #00ffcc; font-weight: bold; text-align: center; }
  #toolbar { position: absolute; bottom: 10px; left: 0; width: 100%; display: flex; justify-content: center; align-items: center; gap: 8px; }
  .arrow { width: 40px; height: 40px; border-radius: 50%; background: rgba(0,255,204,0.3); color: #fff; font-size: 20px; border: 2px solid #00ffcc; display: flex; align-items: center; justify-content: center; cursor: pointer; user-select: none; touch-action: none; }
  #toolDisplay { width: 60px; height: 60px; border-radius: 12px; background: rgba(0,255,204,0.2); color: #fff; font-size: 28px; border: 2px solid #00ffcc; display: flex; align-items: center; justify-content: center; user-select: none; }
  #toolName { color: #00ffcc; font-size: 12px; text-align: center; margin-top: 2px; }
  #clearBtn { width: 45px; height: 45px; border-radius: 10px; background: rgba(255,0,102,0.3); color: #fff; font-size: 18px; border: 2px solid #ff0066; display: flex; align-items: center; justify-content: center; cursor: pointer; user-select: none; touch-action: none; }
</style>
</head>
<body>
<div id="hud">🎮 Песочница | Объектов: 0 | Убито: 0</div>
<canvas id="game" width="700" height="400"></canvas>
<div id="toolbar">
  <div class="arrow" id="prevTool">◀</div>
  <div>
    <div id="toolDisplay">🤖</div>
    <div id="toolName">Робот</div>
  </div>
  <div class="arrow" id="nextTool">▶</div>
  <div id="clearBtn">🗑</div>
</div>

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

let objects = [], particles = [];
let selectedToolIndex = 0;
let dragged = null, dragOffsetX = 0, dragOffsetY = 0;
let mouseX = 0, mouseY = 0, killedCount = 0;
const groundY = 360, GRAVITY = 0.5, FRICTION = 0.98, BOUNCE = 0.3;

// Чуть-чуть добавил новых предметов в конец списка
const TOOLS = [
  { id: 'robot', icon: '🤖', name: 'Робот' },
  { id: 'cat', icon: '🐱', name: 'Кот' },
  { id: 'human', icon: '🧍', name: 'Киболка' },
  { id: 'ball', icon: '⚽', name: 'Мяч' },
  { id: 'box', icon: '📦', name: 'Ящик' },
  { id: 'trap', icon: '🪤', name: 'Капкан' },
  { id: 'spike', icon: '🔺', name: 'Шипы' },
  { id: 'bomb', icon: '💣', name: 'Бомба' },
  { id: 'cannon', icon: '💥', name: 'Пушка' },
  { id: 'hammer', icon: '🔨', name: 'Молот' },
  { id: 'grenade', icon: '🧨', name: 'Граната' },
  { id: 'saw', icon: '🪚', name: 'Пила' },
  { id: 'barrel', icon: '🛢️', name: 'Бочка' },      // Новый
  { id: 'rock', icon: '🪨', name: 'Камень' },        // Новый
  { id: 'spring', icon: '🌀', name: 'Пружина' }      // Новый
];

let audioCtx = null;
function initAudio() { if (!audioCtx) try { audioCtx = new (window.AudioContext || window.webkitAudioContext)(); } catch(e) {} }
function playSound(f, d, t, v) {
  if (!audioCtx) return;
  try {
    const o = audioCtx.createOscillator(), g = audioCtx.createGain();
    o.type = t || 'sine'; o.frequency.value = f;
    g.gain.setValueAtTime(v || 0.05, audioCtx.currentTime);
    g.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + d);
    o.connect(g); g.connect(audioCtx.destination); o.start(); o.stop(audioCtx.currentTime + d);
  } catch(e) {}
}

function updateToolDisplay() {
  toolDisplay.textContent = TOOLS[selectedToolIndex].icon;
  toolName.textContent = TOOLS[selectedToolIndex].name;
}

function createObj(type, x, y) {
  if (type === 'robot') return { type, x, y, w: 22, h: 32, vx: 0, vy: 0, hp: 3, hitTimer: 0, alive: true };
  if (type === 'cat') return { type, x, y, w: 24, h: 28, vx: 0, vy: 0, hp: 2, hitTimer: 0, alive: true };
  if (type === 'human') return { type, x, y, w: 24, h: 38, vx: 0, vy: 0, hp: 2, hitTimer: 0, alive: true };
  if (type === 'ball') return { type, x, y, r: 14, vx: 0, vy: 0, alive: true };
  if (type === 'box') return { type, x, y, w: 40, h: 40, vx: 0, vy: 0, alive: true };
  if (type === 'trap') return { type, x, y, w: 40, h: 20, vx: 0, vy: 0, alive: true };
  if (type === 'spike') return { type, x, y, w: 30, h: 30, vx: 0, vy: 0, alive: true };
  if (type === 'bomb') return { type, x, y, w: 24, h: 24, vx: 0, vy: 0, fuse: 120, armed: false, alive: true };
  if (type === 'cannon') return { type, x, y, w: 40, h: 24, vx: 0, vy: 0, cooldown: 0, alive: true };
  if (type === 'hammer') return { type, x, y, w: 36, h: 16, vx: 0, vy: 0, swingTimer: 0, alive: true };
  if (type === 'grenade') return { type, x, y, w: 18, h: 18, vx: 0, vy: 0, fuse: 150, armed: false, alive: true };
  if (type === 'saw') return { type, x, y, w: 40, h: 40, vx: 0, vy: 0, alive: true };
  if (type === 'barrel') return { type, x, y, w: 24, h: 36, vx: 0, vy: 0, alive: true, hp: 5 }; // Бочка тяжелая
  if (type === 'rock') return { type, x, y, w: 30, h: 30, vx: 0, vy: 0, alive: true, hp: 10 };   // Камень прочный
  if (type === 'spring') return { type, x, y, w: 30, h: 15, vx: 0, vy: 0, alive: true };        // Пружина
  return null;
}

function spawnAt(x, y) {
  initAudio();
  if (y > groundY - 30) y = groundY - 30;
  x = Math.max(40, Math.min(canvas.width - 40, x));
  const tool = TOOLS[selectedToolIndex].id;
  const w = (tool === 'ball') ? 28 : (tool === 'box' ? 40 : 24);
  const h = (tool === 'human' ? 38 : 32);
  objects.push(createObj(tool, x - w/2, y - h));
  playSound(600, 0.06, 'sine', 0.04);
  updateHUD();
}

function updateHUD() {
  const chars = objects.filter(o => (o.type === 'robot' || o.type === 'cat' || o.type === 'human') && o.alive).length;
  hud.textContent = `🎮 Песочница | Объектов: ${objects.length} | Живых: ${chars} | Убито: ${killedCount}`;
}

function killChar(o) {
  o.alive = false; killedCount++;
  playSound(400, 0.08, 'square', 0.06);
  const c = o.type === 'robot' ? '#111' : '#aa0000';
  for (let i = 0; i < 10; i++) {
    particles.push({ x: o.x + 10, y: o.y + 15, vx: (Math.random() - 0.5) * 8, vy: (Math.random() - 0.5) * 8 - 2, life: 30, maxLife: 30, color: c, size: 3 });
  }
  updateHUD();
}

function update() {
  for (let i = objects.length - 1; i >= 0; i--) {
    const o = objects[i];
    if (!o.alive) { objects.splice(i, 1); continue; }
    if (dragged === o) continue;

    // Физика падения (у всех)
    o.vy += GRAVITY; o.vx *= FRICTION; o.x += o.vx; o.y += o.vy;
    const h = o.h || o.r * 2, w = o.w || o.r * 2;
    
    if (o.y + h > groundY) { o.y = groundY - h; o.vy = -o.vy * BOUNCE; }
    if (o.x < 0) { o.x = 0; o.vx = -o.vx * BOUNCE; }
    if (o.x + w > canvas.width) { o.x = canvas.width - w; o.vx = -o.vx * BOUNCE; }

    // Спец-эффекты
    if (o.type === 'bomb') {
      if (!o.armed && Math.abs(o.x - mouseX) < 100) { o.armed = true; o.fuse = 120; }
      if (o.armed) {
        o.fuse--;
        if (o.fuse <= 0) {
          o.alive = false;
          objects.forEach(target => {
            if ((target.type === 'robot' || target.type === 'cat' || target.type === 'human') && target.alive) {
              if (Math.hypot(target.x - o.x, target.y - o.y) < 120) {
                target.hp -= 2; if (target.hp <= 0) killChar(target);
              }
            }
          });
        }
      }
    }
    if (o.type === 'saw') {
      objects.forEach(target => {
        if ((target.type === 'robot' || target.type === 'cat' || target.type === 'human') && target.alive) {
          if (Math.abs(target.x - o.x) < 40 && Math.abs(target.y - o.y) < 40) {
            target.hp -= 2; if (target.hp <= 0) killChar(target);
          }
        }
      });
    }
    
    // Физика пружины (подбрасывает)
    if (o.type === 'spring') {
      objects.forEach(target => {
        if ((target.type === 'robot' || target.type === 'cat' || target.type === 'human' || target.type === 'ball' || target.type === 'box') && target.alive) {
          if (Math.abs(target.x - o.x) < 30 && Math.abs(target.y - o.y) < 30 && target.vy >= 0) {
            target.vy = -12;
            playSound(800, 0.1, 'sine', 0.1);
          }
        }
      });
    }
  }

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

function draw() {
  ctx.fillStyle = '#1a1a2e'; ctx.fillRect(0, 0, canvas.width, canvas.height);
  ctx.fillStyle = '#2d1f0d'; ctx.fillRect(0, groundY, canvas.width, canvas.height - groundY);
  ctx.fillStyle = '#00ffcc'; ctx.fillRect(0, groundY, canvas.width, 6);

  objects.forEach(o => {
    if (!o.alive) return;
    if (o.type === 'robot') { ctx.fillStyle = '#444'; ctx.fillRect(o.x, o.y, 22, 32); ctx.fillStyle = '#00ffcc'; ctx.fillRect(o.x + 4, o.y + 4, 4, 4); ctx.fillRect(o.x + 14, o.y + 4, 4, 4); }
    else if (o.type === 'cat') { ctx.fillStyle = '#ff8c00'; ctx.beginPath(); ctx.arc(o.x + 12, o.y + 14, 12, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = '#fff'; ctx.beginPath(); ctx.arc(o.x + 8, o.y + 12, 3, 0, Math.PI * 2); ctx.arc(o.x + 16, o.y + 12, 3, 0, Math.PI * 2); ctx.fill(); }
    else if (o.type === 'human') { ctx.fillStyle = '#f4c2a1'; ctx.beginPath(); ctx.arc(o.x + 12, o.y + 8, 8, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = '#3498db'; ctx.fillRect(o.x + 6, o.y + 16, 12, 14); ctx.fillStyle = '#333'; ctx.fillRect(o.x + 7, o.y + 30, 4, 8); ctx.fillRect(o.x + 13, o.y + 30, 4, 8); }
    else if (o.type === 'ball') { ctx.fillStyle = '#fff'; ctx.beginPath(); ctx.arc(o.x, o.y, 14, 0, Math.PI * 2); ctx.fill(); }
    else if (o.type === 'box') { ctx.fillStyle = '#d4a017'; ctx.fillRect(o.x, o.y, 40, 40); }
    else if (o.type === 'spike') { ctx.fillStyle = '#e94560'; ctx.beginPath(); ctx.moveTo(o.x + 15, o.y); ctx.lineTo(o.x + 30, o.y + 30); ctx.lineTo(o.x, o.y + 30); ctx.fill(); }
    else if (o.type === 'bomb') { ctx.fillStyle = o.armed ? '#ff0000' : '#222'; ctx.beginPath(); ctx.arc(o.x + 12, o.y + 12, 12, 0, Math.PI * 2); ctx.fill(); }
    else if (o.type === 'saw') { ctx.fillStyle = '#888'; ctx.beginPath(); ctx.arc(o.x + 20, o.y + 20, 20, 0, Math.PI * 2); ctx.fill(); }
    else if (o.type === 'barrel') { ctx.fillStyle = '#8B4513'; ctx.fillRect(o.x, o.y, 24, 36); ctx.fillStyle = '#555'; ctx.fillRect(o.x, o.y + 10, 24, 4); ctx.fillRect(o.x, o.y + 24, 24, 4); }
    else if (o.type === 'rock') { ctx.fillStyle = '#777'; ctx.beginPath(); ctx.arc(o.x + 15, o.y + 15, 15, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = '#555'; ctx.fillRect(o.x + 5, o.y + 5, 8, 8); }
    else if (o.type === 'spring') { ctx.strokeStyle = '#ff0066'; ctx.lineWidth = 4; ctx.beginPath(); for (let i = 0; i < 4; i++) { ctx.lineTo(o.x + i * 8, o.y); ctx.lineTo(o.x + i * 8 + 4, o.y + 15); } ctx.stroke(); }
    else { ctx.fillStyle = '#555'; ctx.fillRect(o.x, o.y, o.w || 20, o.h || 20); }
  });

  particles.forEach(p => { ctx.globalAlpha = p.life / p.maxLife; ctx.fillStyle = p.color; ctx.fillRect(p.x, p.y, p.size, p.size); ctx.globalAlpha = 1; });
  if (dragged) { ctx.strokeStyle = '#00ffcc'; ctx.strokeRect(mouseX - 20, mouseY - 20, 40, 40); }
}

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

function getPos(e) { const rect = canvas.getBoundingClientRect(); const t = e.touches ? e.touches[0] : e; return { x: (t.clientX - rect.left) * (canvas.width / rect.width), y: (t.clientY - rect.top) * (canvas.height / rect.height) }; }

function onStart(e) {
  e.preventDefault(); initAudio();
  const p = getPos(e); mouseX = p.x; mouseY = p.y;
  let hit = null;
  for (let i = objects.length - 1; i >= 0; i--) {
    const o = objects[i], w = o.w || o.r * 2, h = o.h || o.r * 2;
    if (p.x > o.x - 5 && p.x < o.x + w + 5 && p.y > o.y - 5 && p.y < o.y + h + 5) { hit = o; break; }
  }
  if (hit) { dragged = hit; dragOffsetX = p.x - hit.x; dragOffsetY = p.y - hit.y; }
  else spawnAt(p.x, p.y);
}

function onMove(e) {
  e.preventDefault(); if (!dragged) return;
  const p = getPos(e); mouseX = p.x; mouseY = p.y;
  dragged.x = p.x - dragOffsetX; dragged.y = p.y - dragOffsetY; dragged.vx = 0; dragged.vy = 0;
}

function onEnd(e) {
  e.preventDefault(); if (!dragged) return;
  dragged.vx = (mouseX - dragged.x) * 0.5; dragged.vy = (mouseY - dragged.y) * 0.5;
  dragged = null;
}

canvas.addEventListener('touchstart', onStart, { passive: false });
canvas.addEventListener('touchmove', onMove, { passive: false });
canvas.addEventListener('touchend', onEnd, { passive: false });
canvas.addEventListener('mousedown', onStart);
canvas.addEventListener('mousemove', onMove);
canvas.addEventListener('mouseup', onEnd);

document.getElementById('prevTool').onclick = () => { selectedToolIndex = (selectedToolIndex - 1 + TOOLS.length) % TOOLS.length; updateToolDisplay(); };
document.getElementById('nextTool').onclick = () => { selectedToolIndex = (selectedToolIndex + 1) % TOOLS.length; updateToolDisplay(); };
document.getElementById('clearBtn').onclick = () => { objects = []; particles = []; killedCount = 0; updateHUD(); };

updateToolDisplay();
updateHUD();
loop();
</script>
</body>
</html>

Game Source: Песочница

Creator: HyperRider36

Libraries: none

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