🎮ArcadeLab

Не врежься в камни 3.0

by FlashMaker26
572 lines19.8 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>Не врежься в камни 3.0</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 #e94560; border-radius: 8px; touch-action: none; }
  #hud { font-size: 13px; margin-bottom: 4px; color: #eee; font-weight: bold; text-align: center; }
</style>
</head>
<body>
<div id="hud">🪨 Счёт: 0 | Рекорд: 0 | 🟡 Золото: 0</div>
<canvas id="game" width="360" height="560"></canvas>

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

// Состояния: 'menu', 'skins', 'playing', 'gameover'
let state = 'menu';
let player, obstacles, goldStones, particles;
let score = 0, best = 0, goldScore = 0;
let spawnTimer = 0, scrollSpeed = 2, time = 0, menuPulse = 0;

// === СКИНЫ ===
const SKINS = [
  { name: 'Шарик', color: '#e94560', stroke: '#b02a40', type: 'circle' },
  { name: 'Звезда', color: '#ffd700', stroke: '#daa520', type: 'star' },
  { name: 'Робот', color: '#00ffcc', stroke: '#0099aa', type: 'robot' },
  { name: 'Кот', color: '#ff8c00', stroke: '#cc6600', type: 'cat' },
  { name: 'Огонь', color: '#ff4400', stroke: '#aa2200', type: 'fire' },
  { name: 'Ледяной', color: '#00ccff', stroke: '#0088aa', type: 'ice' }
];
let currentSkin = 0;

// Кнопки
const btnPlay = { x: 80, y: 340, w: 200, h: 55 };
const btnSkins = { x: 80, y: 410, w: 200, h: 45 };
const btnReset = { x: 80, y: 470, w: 200, h: 40 };
const btnBack = { x: 20, y: 20, w: 90, h: 40 };

// --- ЗВУК ---
let audioCtx = null;
function initAudio() {
  if (audioCtx) return;
  try { audioCtx = new (window.AudioContext || window.webkitAudioContext)(); } catch(e) {}
}
function playSound(freq, dur, type, vol) {
  if (!audioCtx) return;
  try {
    const o = audioCtx.createOscillator();
    const g = audioCtx.createGain();
    o.type = type || 'square'; 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 sfxJump() { playSound(500, 0.08, 'square', 0.06); }
function sfxHit() { playSound(100, 0.3, 'sawtooth', 0.15); }
function sfxScore() { playSound(900, 0.08, 'sine', 0.06); }
function sfxGold() { playSound(1200, 0.1, 'sine', 0.1); setTimeout(() => playSound(1600, 0.15, 'sine', 0.1), 80); }
function sfxClick() { playSound(600, 0.05, 'square', 0.06); }

function init() {
  player = { x: 100, y: 280, r: 16, vy: 0, rotation: 0 };
  obstacles = []; goldStones = []; particles = [];
  score = 0; goldScore = 0;
  spawnTimer = 0; scrollSpeed = 2; time = 0;
  updateHUD();
}

function updateHUD() {
  hud.textContent = `🪨 Счёт: ${score} | Рекорд: ${best} | 🟡 ${goldScore} | ${SKINS[currentSkin].name}`;
}

function spawnObstacle() {
  const type = Math.random() < 0.15 ? 2 : (Math.random() < 0.25 ? 1 : 0);
  const gap = type === 2 ? 140 : 160;
  const minH = 60;
  const maxH = canvas.height - gap - minH;
  const topH = minH + Math.random() * (maxH - minH);
  const bottomH = canvas.height - gap - topH;
  obstacles.push({
    x: canvas.width, topH, bottomH, w: 60, passed: false, type,
    baseTopH: topH, baseBottomH: bottomH, phase: Math.random() * 6
  });
  if (Math.random() < 0.25) {
    goldStones.push({ x: canvas.width + 30, y: topH + gap / 2, r: 14, collected: false, pulse: 0 });
  }
}

// === РИСОВАНИЕ СКИНОВ ===
function drawSkinShape(px, py, r, skin, rotation) {
  ctx.save();
  ctx.translate(px, py);
  ctx.rotate(rotation);
  
  if (skin.type === 'circle') {
    // Шарик
    ctx.beginPath(); ctx.arc(0, 0, r, 0, Math.PI * 2);
    ctx.fillStyle = skin.color; ctx.fill();
    ctx.strokeStyle = skin.stroke; ctx.lineWidth = 3; ctx.stroke();
  } else if (skin.type === 'star') {
    // Звезда
    ctx.beginPath();
    for (let i = 0; i < 10; i++) {
      const angle = (i * Math.PI / 5) - Math.PI / 2;
      const rad = i % 2 === 0 ? r : r * 0.5;
      ctx.lineTo(Math.cos(angle) * rad, Math.sin(angle) * rad);
    }
    ctx.closePath();
    ctx.fillStyle = skin.color; ctx.fill();
    ctx.strokeStyle = skin.stroke; ctx.lineWidth = 2; ctx.stroke();
  } else if (skin.type === 'robot') {
    // Робот-квадрат
    ctx.fillStyle = skin.color;
    ctx.fillRect(-r, -r, r*2, r*2);
    ctx.strokeStyle = skin.stroke; ctx.lineWidth = 3;
    ctx.strokeRect(-r, -r, r*2, r*2);
    // Антенна
    ctx.beginPath(); ctx.moveTo(0, -r); ctx.lineTo(0, -r - 6); ctx.stroke();
    ctx.fillStyle = '#ff0066';
    ctx.beginPath(); ctx.arc(0, -r - 6, 3, 0, Math.PI * 2); ctx.fill();
  } else if (skin.type === 'cat') {
    // Кот
    ctx.beginPath(); ctx.arc(0, 0, r, 0, Math.PI * 2);
    ctx.fillStyle = skin.color; ctx.fill();
    ctx.strokeStyle = skin.stroke; ctx.lineWidth = 3; ctx.stroke();
    // Уши
    ctx.fillStyle = skin.color;
    ctx.beginPath(); ctx.moveTo(-r*0.7, -r*0.7); ctx.lineTo(-r*0.3, -r*1.3); ctx.lineTo(0, -r*0.7); ctx.fill();
    ctx.beginPath(); ctx.moveTo(r*0.7, -r*0.7); ctx.lineTo(r*0.3, -r*1.3); ctx.lineTo(0, -r*0.7); ctx.fill();
  } else if (skin.type === 'fire') {
    // Огонь
    ctx.beginPath();
    ctx.moveTo(0, -r);
    ctx.quadraticCurveTo(r, -r*0.3, r*0.7, r);
    ctx.quadraticCurveTo(0, r*0.6, -r*0.7, r);
    ctx.quadraticCurveTo(-r, -r*0.3, 0, -r);
    ctx.fillStyle = skin.color; ctx.fill();
    ctx.strokeStyle = skin.stroke; ctx.lineWidth = 2; ctx.stroke();
    // Внутренний огонь
    ctx.beginPath();
    ctx.moveTo(0, -r*0.5);
    ctx.quadraticCurveTo(r*0.5, 0, 0, r*0.6);
    ctx.quadraticCurveTo(-r*0.5, 0, 0, -r*0.5);
    ctx.fillStyle = '#ffdd00'; ctx.fill();
  } else if (skin.type === 'ice') {
    // Лёд (шестиугольник)
    ctx.beginPath();
    for (let i = 0; i < 6; i++) {
      const angle = (i * Math.PI / 3) - Math.PI / 2;
      ctx.lineTo(Math.cos(angle) * r, Math.sin(angle) * r);
    }
    ctx.closePath();
    ctx.fillStyle = skin.color; ctx.fill();
    ctx.strokeStyle = skin.stroke; ctx.lineWidth = 2; ctx.stroke();
    // Блик
    ctx.fillStyle = 'rgba(255,255,255,0.6)';
    ctx.beginPath(); ctx.arc(-r*0.3, -r*0.3, r*0.3, 0, Math.PI * 2); ctx.fill();
  }
  
  ctx.restore();
}

function drawPlayer() {
  const skin = SKINS[currentSkin];
  const px = player.x, py = player.y;
  
  // Свечение
  ctx.fillStyle = skin.color.replace(')', ',0.2)').replace('rgb', 'rgba');
  if (skin.color.startsWith('#')) {
    ctx.fillStyle = 'rgba(255,255,255,0.15)';
  }
  ctx.beginPath(); ctx.arc(px, py, player.r + 8, 0, Math.PI * 2); ctx.fill();
  
  drawSkinShape(px, py, player.r, skin, player.rotation);
  
  // Глаза (для всех кроме звезды и огня)
  if (skin.type !== 'star' && skin.type !== 'fire') {
    ctx.fillStyle = '#fff';
    ctx.beginPath();
    ctx.arc(px - 5, py - 4, 4, 0, Math.PI * 2);
    ctx.arc(px + 5, py - 4, 4, 0, Math.PI * 2);
    ctx.fill();
    ctx.fillStyle = '#000';
    ctx.beginPath();
    ctx.arc(px - 4, py - 4, 2, 0, Math.PI * 2);
    ctx.arc(px + 6, py - 4, 2, 0, Math.PI * 2);
    ctx.fill();
  }
}

function drawObstacle(o) {
  const x = o.x, w = o.w;
  if (o.type === 2) {
    ctx.fillStyle = 'rgba(255,0,102,0.8)';
    ctx.fillRect(x + w/2 - 3, 0, 6, o.topH);
    ctx.fillStyle = '#fff';
    ctx.fillRect(x + w/2 - 1, 0, 2, o.topH);
    ctx.fillStyle = 'rgba(255,0,102,0.8)';
    ctx.fillRect(x + w/2 - 3, canvas.height - o.bottomH, 6, o.bottomH);
    ctx.fillStyle = '#fff';
    ctx.fillRect(x + w/2 - 1, canvas.height - o.bottomH, 2, o.bottomH);
    return;
  }
  ctx.fillStyle = '#5a4a3a';
  ctx.beginPath();
  ctx.moveTo(x, 0); ctx.lineTo(x + w, 0);
  ctx.lineTo(x + w, o.topH - 10); ctx.lineTo(x + w/2, o.topH); ctx.lineTo(x, o.topH - 15);
  ctx.closePath(); ctx.fill();
  ctx.strokeStyle = '#3a2a1a'; ctx.lineWidth = 2; ctx.stroke();
  ctx.fillStyle = '#5a4a3a';
  ctx.beginPath();
  const by = canvas.height - o.bottomH;
  ctx.moveTo(x, canvas.height); ctx.lineTo(x + w, canvas.height);
  ctx.lineTo(x + w, by + 15); ctx.lineTo(x + w/2, by); ctx.lineTo(x, by + 10);
  ctx.closePath(); ctx.fill(); ctx.stroke();
}

function drawGoldStone(g) {
  if (g.collected) return;
  const pulse = Math.sin(Date.now() / 150 + g.pulse) * 3;
  ctx.fillStyle = 'rgba(255,215,0,0.3)';
  ctx.beginPath(); ctx.arc(g.x, g.y, g.r + 8 + pulse, 0, Math.PI * 2); ctx.fill();
  ctx.fillStyle = '#ffd700';
  ctx.beginPath(); ctx.arc(g.x, g.y, g.r, 0, Math.PI * 2); ctx.fill();
  ctx.strokeStyle = '#daa520'; ctx.lineWidth = 3; ctx.stroke();
  ctx.fillStyle = 'rgba(255,255,255,0.7)';
  ctx.beginPath(); ctx.arc(g.x - 4, g.y - 4, 4, 0, Math.PI * 2); ctx.fill();
  ctx.fillStyle = '#b8860b'; ctx.font = 'bold 14px Arial'; ctx.textAlign = 'center';
  ctx.fillText('★', g.x, g.y + 5); ctx.textAlign = 'left';
}

// === МЕНЮ ===
function drawMenu() {
  menuPulse += 0.05;
  const grad = ctx.createLinearGradient(0, 0, 0, canvas.height);
  grad.addColorStop(0, '#0a0a2e');
  grad.addColorStop(1, '#2a0a2e');
  ctx.fillStyle = grad;
  ctx.fillRect(0, 0, canvas.width, canvas.height);

  for (let i = 0; i < 40; i++) {
    const sx = (i * 137 + time * 0.5) % 360;
    const sy = (i * 97) % 560;
    ctx.fillStyle = `rgba(255,255,255,${0.2 + (i % 3) * 0.15})`;
    ctx.fillRect(sx, sy, 2, 2);
  }

  const scale = 1 + Math.sin(menuPulse) * 0.03;
  ctx.save();
  ctx.translate(canvas.width/2, 110);
  ctx.scale(scale, scale);
  ctx.fillStyle = '#e94560';
  ctx.font = 'bold 26px Arial';
  ctx.textAlign = 'center';
  ctx.fillText('НЕ ВРЕЖЬСЯ', 0, 0);
  ctx.fillText('В КАМНИ', 0, 32);
  ctx.fillStyle = '#ffd700';
  ctx.font = 'bold 18px Arial';
  ctx.fillText('3.0', 0, 60);
  ctx.restore();

  // Показ текущего скина
  drawSkinShape(canvas.width/2, 220, 22, SKINS[currentSkin], 0);
  ctx.fillStyle = '#fff';
  ctx.font = '16px Arial';
  ctx.textAlign = 'center';
  ctx.fillText('Скин: ' + SKINS[currentSkin].name, canvas.width/2, 265);

  ctx.fillStyle = '#fff';
  ctx.font = '16px Arial';
  ctx.fillText('🏆 Рекорд: ' + best, canvas.width/2, 300);
  ctx.textAlign = 'left';

  // Кнопка Играть
  ctx.fillStyle = '#e94560';
  ctx.fillRect(btnPlay.x, btnPlay.y, btnPlay.w, btnPlay.h);
  ctx.strokeStyle = '#fff'; ctx.lineWidth = 3;
  ctx.strokeRect(btnPlay.x, btnPlay.y, btnPlay.w, btnPlay.h);
  ctx.fillStyle = '#fff';
  ctx.font = 'bold 22px Arial';
  ctx.textAlign = 'center';
  ctx.fillText('▶ ИГРАТЬ', btnPlay.x + btnPlay.w/2, btnPlay.y + 36);

  // Кнопка Скины
  ctx.fillStyle = 'rgba(255,215,0,0.2)';
  ctx.fillRect(btnSkins.x, btnSkins.y, btnSkins.w, btnSkins.h);
  ctx.strokeStyle = '#ffd700'; ctx.lineWidth = 2;
  ctx.strokeRect(btnSkins.x, btnSkins.y, btnSkins.w, btnSkins.h);
  ctx.fillStyle = '#ffd700';
  ctx.font = '18px Arial';
  ctx.fillText('🎨 СКИНЫ', btnSkins.x + btnSkins.w/2, btnSkins.y + 28);

  // Кнопка Сброс
  ctx.fillStyle = 'rgba(255,255,255,0.1)';
  ctx.fillRect(btnReset.x, btnReset.y, btnReset.w, btnReset.h);
  ctx.strokeStyle = '#aaa'; ctx.lineWidth = 2;
  ctx.strokeRect(btnReset.x, btnReset.y, btnReset.w, btnReset.h);
  ctx.fillStyle = '#aaa';
  ctx.font = '14px Arial';
  ctx.fillText('Сбросить рекорд', btnReset.x + btnReset.w/2, btnReset.y + 25);
  ctx.textAlign = 'left';
}

// === ЭКРАН СКИНОВ ===
function drawSkinsMenu() {
  const grad = ctx.createLinearGradient(0, 0, 0, canvas.height);
  grad.addColorStop(0, '#0a0a2e');
  grad.addColorStop(1, '#2a0a2e');
  ctx.fillStyle = grad;
  ctx.fillRect(0, 0, canvas.width, canvas.height);

  ctx.fillStyle = '#ffd700';
  ctx.font = 'bold 24px Arial';
  ctx.textAlign = 'center';
  ctx.fillText('ВЫБЕРИ СКИН', canvas.width/2, 60);
  ctx.textAlign = 'left';

  // Сетка скинов 2x3
  const cols = 2, rows = 3;
  const cellW = 140, cellH = 130;
  const startX = (canvas.width - cols * cellW) / 2 + 10;
  const startY = 100;

  SKINS.forEach((skin, i) => {
    const col = i % cols;
    const row = Math.floor(i / cols);
    const x = startX + col * cellW + cellW/2;
    const y = startY + row * cellH + cellH/2;

    // Рамка выбора
    if (i === currentSkin) {
      ctx.strokeStyle = '#ffd700'; ctx.lineWidth = 3;
      ctx.strokeRect(x - 55, y - 55, 110, 110);
      ctx.fillStyle = 'rgba(255,215,0,0.1)';
      ctx.fillRect(x - 55, y - 55, 110, 110);
    } else {
      ctx.strokeStyle = 'rgba(255,255,255,0.2)'; ctx.lineWidth = 1;
      ctx.strokeRect(x - 55, y - 55, 110, 110);
    }

    // Скин
    drawSkinShape(x, y - 10, 20, skin, 0);

    // Название
    ctx.fillStyle = i === currentSkin ? '#ffd700' : '#fff';
    ctx.font = '14px Arial';
    ctx.textAlign = 'center';
    ctx.fillText(skin.name, x, y + 40);
    ctx.textAlign = 'left';
  });

  // Кнопка назад
  ctx.fillStyle = 'rgba(255,255,255,0.15)';
  ctx.fillRect(btnBack.x, btnBack.y, btnBack.w, btnBack.h);
  ctx.strokeStyle = '#fff'; ctx.lineWidth = 2;
  ctx.strokeRect(btnBack.x, btnBack.y, btnBack.w, btnBack.h);
  ctx.fillStyle = '#fff';
  ctx.font = '18px Arial';
  ctx.textAlign = 'center';
  ctx.fillText('← Назад', btnBack.x + btnBack.w/2, btnBack.y + 27);
  ctx.textAlign = 'left';
}

function drawGame() {
  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);

  for (let i = 0; i < 30; i++) {
    const sx = (i * 137 + time * 0.5) % 360;
    const sy = (i * 97) % 560;
    ctx.fillStyle = `rgba(255,255,255,${0.2 + (i % 3) * 0.1})`;
    ctx.fillRect(sx, sy, 2, 2);
  }

  obstacles.forEach(o => drawObstacle(o));
  goldStones.forEach(g => drawGoldStone(g));

  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 (state !== 'gameover') drawPlayer();

  if (state === 'gameover') {
    ctx.fillStyle = 'rgba(0,0,0,0.75)';
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    ctx.fillStyle = '#e94560';
    ctx.font = 'bold 28px Arial';
    ctx.textAlign = 'center';
    ctx.fillText('ИГРА ОКОНЧЕНА', canvas.width/2, canvas.height/2 - 80);
    ctx.fillStyle = '#fff';
    ctx.font = '20px Arial';
    ctx.fillText('Счёт: ' + score, canvas.width/2, canvas.height/2 - 35);
    ctx.fillStyle = '#ffd700';
    ctx.fillText('Золото: ' + goldScore, canvas.width/2, canvas.height/2 - 5);
    ctx.fillStyle = '#fff';
    ctx.fillText('Рекорд: ' + best, canvas.width/2, canvas.height/2 + 25);
    ctx.font = '15px Arial';
    ctx.fillStyle = '#aaa';
    ctx.fillText('Нажми, чтобы выйти в меню', canvas.width/2, canvas.height/2 + 90);
    ctx.textAlign = 'left';
  }
}

function draw() {
  if (state === 'menu') drawMenu();
  else if (state === 'skins') drawSkinsMenu();
  else drawGame();
}

function update() {
  if (state !== 'playing') return;
  time++;
  player.vy += 0.5;
  if (player.vy > 10) player.vy = 10;
  player.y += player.vy;
  player.rotation += 0.05;

  if (player.y - player.r < 0) { player.y = player.r; player.vy = 0; }
  if (player.y + player.r > canvas.height) { player.y = canvas.height - player.r; player.vy = 0; }

  spawnTimer++;
  const spawnRate = Math.max(40, 100 - Math.min(score * 3, 60));
  if (spawnTimer > spawnRate) { spawnTimer = 0; spawnObstacle(); }

  obstacles.forEach((o, i) => {
    o.x -= scrollSpeed + score * 0.03;
    if (o.type === 1) {
      o.phase += 0.05;
      o.topH = o.baseTopH + Math.sin(o.phase) * 30;
      o.bottomH = o.baseBottomH - Math.sin(o.phase) * 30;
    }
    const px = player.x, py = player.y;
    if (px + player.r > o.x && px - player.r < o.x + o.w) {
      let hit = false;
      if (o.type === 2) {
        if (px + player.r > o.x + o.w/2 - 3 && px - player.r < o.x + o.w/2 + 3) {
          hit = (py - player.r < o.topH || py + player.r > canvas.height - o.bottomH);
        }
      } else {
        hit = (py - player.r < o.topH || py + player.r > canvas.height - o.bottomH);
      }
      if (hit) {
        state = 'gameover';
        sfxHit();
        if (score > best) best = score;
        updateHUD();
        for (let k = 0; k < 15; k++) {
          particles.push({
            x: px, y: py,
            vx: (Math.random() - 0.5) * 10, vy: (Math.random() - 0.5) * 10,
            life: 30, maxLife: 30, color: '#e94560', size: 3 + Math.random() * 4
          });
        }
      }
    }
    if (!o.passed && o.x + o.w < player.x) {
      o.passed = true; score++; sfxScore(); updateHUD();
    }
    if (o.x + o.w < -50) obstacles.splice(i, 1);
  });

  goldStones.forEach((g, i) => {
    g.x -= scrollSpeed + score * 0.03;
    const dx = player.x - g.x;
    const dy = player.y - g.y;
    if (Math.sqrt(dx*dx + dy*dy) < player.r + g.r) {
      g.collected = true; goldScore += 5; score += 5;
      sfxGold();
      for (let k = 0; k < 10; k++) {
        particles.push({
          x: g.x, y: g.y,
          vx: (Math.random() - 0.5) * 8, vy: (Math.random() - 0.5) * 8 - 2,
          life: 30, maxLife: 30, color: '#ffd700', size: 3 + Math.random() * 3
        });
      }
      updateHUD();
    }
    if (g.x < -50) goldStones.splice(i, 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 loop() { update(); draw(); requestAnimationFrame(loop); }

// === УПРАВЛЕНИЕ ===
function handleTap(x, y) {
  initAudio();
  if (state === 'menu') {
    if (x > btnPlay.x && x < btnPlay.x + btnPlay.w && y > btnPlay.y && y < btnPlay.y + btnPlay.h) {
      sfxClick(); init(); state = 'playing'; return;
    }
    if (x > btnSkins.x && x < btnSkins.x + btnSkins.w && y > btnSkins.y && y < btnSkins.y + btnSkins.h) {
      sfxClick(); state = 'skins'; return;
    }
    if (x > btnReset.x && x < btnReset.x + btnReset.w && y > btnReset.y && y < btnReset.y + btnReset.h) {
      sfxClick(); best = 0; updateHUD(); return;
    }
    return;
  }
  if (state === 'skins') {
    // Кнопка назад
    if (x > btnBack.x && x < btnBack.x + btnBack.w && y > btnBack.y && y < btnBack.y + btnBack.h) {
      sfxClick(); state = 'menu'; return;
    }
    // Сетка скинов
    const cols = 2, cellW = 140, cellH = 130;
    const startX = (canvas.width - cols * cellW) / 2 + 10;
    const startY = 100;
    for (let i = 0; i < SKINS.length; i++) {
      const col = i % cols;
      const row = Math.floor(i / cols);
      const cx = startX + col * cellW + cellW/2;
      const cy = startY + row * cellH + cellH/2;
      if (x > cx - 55 && x < cx + 55 && y > cy - 55 && y < cy + 55) {
        currentSkin = i;
        sfxClick();
        updateHUD();
        return;
      }
    }
    return;
  }
  if (state === 'gameover') { sfxClick(); state = 'menu'; return; }
  if (state === 'playing') { player.vy = -6.5; sfxJump(); }
}

canvas.addEventListener('touchstart', e => {
  e.preventDefault();
  const rect = canvas.getBoundingClientRect();
  const t = e.touches[0];
  handleTap(t.clientX - rect.left, t.clientY - rect.top);
});
canvas.addEventListener('mousedown', e => {
  e.preventDefault();
  const rect = canvas.getBoundingClientRect();
  handleTap(e.clientX - rect.left, e.clientY - rect.top);
});
document.addEventListener('keydown', e => {
  if (e.key === ' ' || e.key === 'ArrowUp' || e.key === 'w' || e.key === 'W') {
    if (state === 'playing') { player.vy = -6.5; sfxJump(); }
    else if (state === 'menu') { init(); state = 'playing'; sfxClick(); }
    else if (state === 'gameover') { state = 'menu'; sfxClick(); }
    else if (state === 'skins') { state = 'menu'; sfxClick(); }
    e.preventDefault();
  }
});

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

Game Source: Не врежься в камни 3.0

Creator: FlashMaker26

Libraries: none

Complexity: complex (572 lines, 19.8 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: 3-0-flashmaker26" to link back to the original. Then publish at arcadelab.ai/publish.