Загрузка данных


<!DOCTYPE html>

<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ЗМЕЙКА</title>
<style>
  @import url('https://fonts.googleapis.com/css2?family=Share+Tech+Mono&family=Orbitron:wght@400;700;900&display=swap');

:root {
–bg: #0a0a0f;
–grid: #0d1117;
–neon: #00ff88;
–neon2: #00ccff;
–danger: #ff3366;
–food: #ffcc00;
–dim: #1a1a2e;
–text: #c8ffd4;
}

- { margin: 0; padding: 0; box-sizing: border-box; }

body {
background: var(–bg);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh;
font-family: ‘Share Tech Mono’, monospace;
color: var(–text);
overflow: hidden;
}

body::before {
content: ‘’;
position: fixed;
inset: 0;
background:
radial-gradient(ellipse at 20% 50%, rgba(0,255,136,0.04) 0%, transparent 60%),
radial-gradient(ellipse at 80% 20%, rgba(0,204,255,0.04) 0%, transparent 60%);
pointer-events: none;
}

.scanlines {
position: fixed;
inset: 0;
background: repeating-linear-gradient(
0deg,
transparent,
transparent 2px,
rgba(0,0,0,0.15) 2px,
rgba(0,0,0,0.15) 4px
);
pointer-events: none;
z-index: 10;
}

h1 {
font-family: ‘Orbitron’, sans-serif;
font-weight: 900;
font-size: clamp(1.8rem, 5vw, 3rem);
letter-spacing: 0.3em;
color: var(–neon);
text-shadow: 0 0 20px var(–neon), 0 0 60px rgba(0,255,136,0.3);
margin-bottom: 1.5rem;
text-transform: uppercase;
}

.hud {
display: flex;
gap: 3rem;
margin-bottom: 1.2rem;
font-family: ‘Orbitron’, sans-serif;
font-size: 0.85rem;
letter-spacing: 0.15em;
}

.hud-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.25rem;
}

.hud-label {
color: rgba(200,255,212,0.4);
font-size: 0.65rem;
letter-spacing: 0.2em;
}

.hud-value {
color: var(–neon);
font-size: 1.4rem;
font-weight: 700;
text-shadow: 0 0 12px var(–neon);
min-width: 3ch;
text-align: center;
}

#hiscore .hud-value { color: var(–neon2); text-shadow: 0 0 12px var(–neon2); }

.canvas-wrap {
position: relative;
border: 1px solid rgba(0,255,136,0.2);
box-shadow:
0 0 0 1px rgba(0,255,136,0.05),
0 0 40px rgba(0,255,136,0.06),
inset 0 0 80px rgba(0,0,0,0.8);
}

canvas {
display: block;
image-rendering: pixelated;
}

.overlay {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: rgba(10,10,15,0.92);
gap: 1.2rem;
transition: opacity 0.3s;
}

.overlay.hidden { opacity: 0; pointer-events: none; }

.overlay-title {
font-family: ‘Orbitron’, sans-serif;
font-size: clamp(1.2rem, 4vw, 2rem);
font-weight: 900;
letter-spacing: 0.2em;
color: var(–danger);
text-shadow: 0 0 20px var(–danger);
}

.overlay-title.start { color: var(–neon); text-shadow: 0 0 20px var(–neon); }

.overlay-sub {
font-size: 0.8rem;
color: rgba(200,255,212,0.5);
letter-spacing: 0.15em;
}

.btn {
font-family: ‘Orbitron’, sans-serif;
font-size: 0.85rem;
letter-spacing: 0.2em;
padding: 0.7rem 2.5rem;
background: transparent;
border: 1px solid var(–neon);
color: var(–neon);
cursor: pointer;
text-transform: uppercase;
transition: all 0.2s;
box-shadow: 0 0 12px rgba(0,255,136,0.15), inset 0 0 12px rgba(0,255,136,0.05);
}

.btn:hover {
background: rgba(0,255,136,0.1);
box-shadow: 0 0 24px rgba(0,255,136,0.4), inset 0 0 20px rgba(0,255,136,0.1);
}

.controls {
margin-top: 1rem;
display: flex;
gap: 1.5rem;
font-size: 0.7rem;
color: rgba(200,255,212,0.3);
letter-spacing: 0.15em;
}

.controls span { display: flex; align-items: center; gap: 0.4rem; }

.key {
display: inline-block;
border: 1px solid rgba(200,255,212,0.2);
padding: 0.1rem 0.4rem;
border-radius: 3px;
font-size: 0.65rem;
color: rgba(200,255,212,0.5);
}

.flash {
position: absolute;
inset: 0;
pointer-events: none;
opacity: 0;
background: rgba(255,51,102,0.12);
transition: opacity 0.05s;
}

.flash.active { opacity: 1; }
</style>

</head>
<body>

<div class="scanlines"></div>

<h1>ЗМЕЙКА</h1>

<div class="hud">
  <div class="hud-item">
    <span class="hud-label">СЧЁТ</span>
    <span class="hud-value" id="score">0</span>
  </div>
  <div class="hud-item">
    <span class="hud-label">УРОВЕНЬ</span>
    <span class="hud-value" id="level">1</span>
  </div>
  <div class="hud-item" id="hiscore">
    <span class="hud-label">РЕКОРД</span>
    <span class="hud-value" id="best">0</span>
  </div>
</div>

<div class="canvas-wrap">
  <canvas id="c"></canvas>
  <div class="flash" id="flash"></div>

  <div class="overlay" id="overlay">
    <div class="overlay-title start" id="ov-title">ЗМЕЙКА</div>
    <div class="overlay-sub" id="ov-sub">классика консоли</div>
    <button class="btn" id="startBtn">[ НАЧАТЬ ]</button>
    <div style="font-size:0.65rem;color:rgba(200,255,212,0.3);letter-spacing:0.1em">
      WASD / СТРЕЛКИ
    </div>
  </div>
</div>

<div class="controls">
  <span><span class="key">↑↓←→</span> или <span class="key">WASD</span> движение</span>
  <span><span class="key">P</span> пауза</span>
</div>

<script>
const COLS = 24, ROWS = 24, CELL = 22;
const canvas = document.getElementById('c');
const ctx = canvas.getContext('2d');
canvas.width  = COLS * CELL;
canvas.height = ROWS * CELL;

const overlay   = document.getElementById('overlay');
const ovTitle   = document.getElementById('ov-title');
const ovSub     = document.getElementById('ov-sub');
const startBtn  = document.getElementById('startBtn');
const scoreEl   = document.getElementById('score');
const levelEl   = document.getElementById('level');
const bestEl    = document.getElementById('best');
const flashEl   = document.getElementById('flash');

let snake, dir, nextDir, food, score, level, best, timer, running, paused, dead;
best = +localStorage.getItem('snakeBest') || 0;
bestEl.textContent = best;

function initGame() {
  snake = [{x:12,y:12},{x:11,y:12},{x:10,y:12}];
  dir = {x:1,y:0};
  nextDir = {x:1,y:0};
  score = 0; level = 1;
  scoreEl.textContent = 0;
  levelEl.textContent = 1;
  dead = false; paused = false;
  spawnFood();
}

function spawnFood() {
  let pos;
  do {
    pos = {x: Math.floor(Math.random()*COLS), y: Math.floor(Math.random()*ROWS)};
  } while (snake.some(s => s.x===pos.x && s.y===pos.y));
  food = pos;
}

function speed() { return Math.max(60, 200 - (level-1)*18); }

function step() {
  dir = {...nextDir};
  const head = {x: (snake[0].x + dir.x + COLS) % COLS,
                y: (snake[0].y + dir.y + ROWS) % ROWS};

  if (snake.some(s => s.x===head.x && s.y===head.y)) {
    gameOver(); return;
  }

  snake.unshift(head);
  if (head.x===food.x && head.y===food.y) {
    score += level * 10;
    scoreEl.textContent = score;
    if (score > best) { best = score; bestEl.textContent = best; localStorage.setItem('snakeBest', best); }
    if (score % 50 === 0 && level < 10) { level++; levelEl.textContent = level; }
    spawnFood();
  } else {
    snake.pop();
  }
  draw();
}

function gameOver() {
  dead = true;
  running = false;
  clearInterval(timer);
  flashEl.classList.add('active');
  setTimeout(() => flashEl.classList.remove('active'), 200);
  setTimeout(() => {
    ovTitle.textContent = 'GAME OVER';
    ovTitle.className = 'overlay-title';
    ovSub.textContent = `счёт: ${score}`;
    startBtn.textContent = '[ СНОВА ]';
    overlay.classList.remove('hidden');
  }, 300);
}

function startGame() {
  overlay.classList.add('hidden');
  initGame();
  running = true;
  clearInterval(timer);
  timer = setInterval(() => { if (!paused && running) step(); }, speed());
  // restart timer on level change
  const origStep = step;
  draw();
}

// Redraw on speed change
function restartTimer() {
  clearInterval(timer);
  timer = setInterval(() => { if (!paused && running) step(); }, speed());
}

// patch level up to restart timer
const _origStep = step;
function step() {
  dir = {...nextDir};
  const head = {x: (snake[0].x + dir.x + COLS) % COLS,
                y: (snake[0].y + dir.y + ROWS) % ROWS};
  if (snake.some(s => s.x===head.x && s.y===head.y)) { gameOver(); return; }
  snake.unshift(head);
  const ate = head.x===food.x && head.y===food.y;
  if (ate) {
    score += level * 10;
    scoreEl.textContent = score;
    if (score > best) { best = score; bestEl.textContent = best; localStorage.setItem('snakeBest', best); }
    const prevLevel = level;
    if (score >= prevLevel * 50 && level < 10) { level++; levelEl.textContent = level; restartTimer(); }
    spawnFood();
  } else { snake.pop(); }
  draw();
}

/* ---- DRAWING ---- */
function draw() {
  // Background grid
  ctx.fillStyle = '#0a0a0f';
  ctx.fillRect(0,0,canvas.width,canvas.height);

  // Grid lines
  ctx.strokeStyle = 'rgba(0,255,136,0.04)';
  ctx.lineWidth = 0.5;
  for (let x=0;x<=COLS;x++) {
    ctx.beginPath(); ctx.moveTo(x*CELL,0); ctx.lineTo(x*CELL,canvas.height); ctx.stroke();
  }
  for (let y=0;y<=ROWS;y++) {
    ctx.beginPath(); ctx.moveTo(0,y*CELL); ctx.lineTo(canvas.width,y*CELL); ctx.stroke();
  }

  // Food
  const fx = food.x*CELL, fy = food.y*CELL;
  const pulse = 0.7 + 0.3 * Math.sin(Date.now()/300);
  ctx.save();
  ctx.shadowColor = '#ffcc00';
  ctx.shadowBlur = 16 * pulse;
  ctx.fillStyle = '#ffcc00';
  const fp = 4;
  ctx.fillRect(fx+fp, fy+fp, CELL-fp*2, CELL-fp*2);
  // inner bright
  ctx.fillStyle = '#fff8d0';
  ctx.fillRect(fx+fp+2, fy+fp+2, 4, 4);
  ctx.restore();

  // Snake
  snake.forEach((seg, i) => {
    const x = seg.x*CELL, y = seg.y*CELL;
    const t = i / snake.length;
    const isHead = i === 0;
    const alpha = 1 - t * 0.5;

    ctx.save();
    if (isHead) {
      ctx.shadowColor = '#00ff88';
      ctx.shadowBlur = 18;
    } else {
      ctx.shadowColor = 'rgba(0,255,136,0.4)';
      ctx.shadowBlur = 6;
    }

    // segment gradient
    const g = ctx.createLinearGradient(x, y, x+CELL, y+CELL);
    if (isHead) {
      g.addColorStop(0, `rgba(0,255,136,${alpha})`);
      g.addColorStop(1, `rgba(0,220,100,${alpha})`);
    } else {
      g.addColorStop(0, `rgba(0,180,80,${alpha * 0.9})`);
      g.addColorStop(1, `rgba(0,120,50,${alpha * 0.7})`);
    }
    ctx.fillStyle = g;

    const pad = isHead ? 1 : 2;
    const r = isHead ? 5 : 3;
    roundRect(ctx, x+pad, y+pad, CELL-pad*2, CELL-pad*2, r);
    ctx.fill();

    // head eyes
    if (isHead) {
      ctx.fillStyle = '#fff';
      ctx.shadowBlur = 0;
      const ex = dir.x, ey = dir.y;
      // eye positions relative to direction
      const cx = x + CELL/2, cy = y + CELL/2;
      const eyeOff = 3;
      const e1 = {x: cx + ey*eyeOff + ex*3, y: cy + ex*eyeOff - ey*3};
      const e2 = {x: cx - ey*eyeOff + ex*3, y: cy - ex*eyeOff - ey*3};
      ctx.beginPath(); ctx.arc(e1.x, e1.y, 1.8, 0, Math.PI*2); ctx.fill();
      ctx.beginPath(); ctx.arc(e2.x, e2.y, 1.8, 0, Math.PI*2); ctx.fill();
      ctx.fillStyle = '#000';
      ctx.beginPath(); ctx.arc(e1.x+ex*0.6, e1.y+ey*0.6, 0.9, 0, Math.PI*2); ctx.fill();
      ctx.beginPath(); ctx.arc(e2.x+ex*0.6, e2.y+ey*0.6, 0.9, 0, Math.PI*2); ctx.fill();
    }

    ctx.restore();
  });
}

function roundRect(ctx, x, y, w, h, r) {
  ctx.beginPath();
  ctx.moveTo(x+r, y);
  ctx.lineTo(x+w-r, y); ctx.quadraticCurveTo(x+w, y, x+w, y+r);
  ctx.lineTo(x+w, y+h-r); ctx.quadraticCurveTo(x+w, y+h, x+w-r, y+h);
  ctx.lineTo(x+r, y+h); ctx.quadraticCurveTo(x, y+h, x, y+h-r);
  ctx.lineTo(x, y+r); ctx.quadraticCurveTo(x, y, x+r, y);
  ctx.closePath();
}

/* ---- INPUT ---- */
const DIRS = {
  ArrowUp:    {x:0,y:-1}, w:{x:0,y:-1}, W:{x:0,y:-1},
  ArrowDown:  {x:0,y:1},  s:{x:0,y:1},  S:{x:0,y:1},
  ArrowLeft:  {x:-1,y:0}, a:{x:-1,y:0}, A:{x:-1,y:0},
  ArrowRight: {x:1,y:0},  d:{x:1,y:0},  D:{x:1,y:0},
};

document.addEventListener('keydown', e => {
  if (e.key === 'p' || e.key === 'P') {
    if (running) paused = !paused;
    return;
  }
  const d = DIRS[e.key];
  if (d && running && !paused) {
    if (d.x !== -dir.x || d.y !== -dir.y) nextDir = d;
    e.preventDefault();
  }
});

startBtn.addEventListener('click', startGame);

// Initial draw
initGame();
draw();

// Pulse food animation
setInterval(() => { if (running && !paused) draw(); }, 100);
</script>

</body>
</html>