Загрузка данных
<!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>