sozday-veb-servis-igru-zmeyk/index.html

400 lines
9.9 KiB
HTML

<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Змейка</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, "Segoe UI", Roboto, sans-serif;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: #fff;
}
h1 {
margin-bottom: 20px;
font-size: 2.5rem;
text-shadow: 0 0 10px rgba(0, 255, 136, 0.5);
}
.game-container {
position: relative;
border: 3px solid #00ff88;
border-radius: 10px;
box-shadow: 0 0 20px rgba(0, 255, 136, 0.3);
}
canvas {
display: block;
background: #0a0a15;
border-radius: 7px;
}
.score-board {
display: flex;
gap: 40px;
margin-bottom: 20px;
font-size: 1.3rem;
}
.score-item {
background: rgba(0, 255, 136, 0.1);
padding: 10px 25px;
border-radius: 8px;
border: 1px solid rgba(0, 255, 136, 0.3);
}
.score-item span {
color: #00ff88;
font-weight: bold;
}
.controls {
margin-top: 25px;
text-align: center;
color: #888;
font-size: 0.95rem;
}
.controls p {
margin: 5px 0;
}
.btn {
background: linear-gradient(135deg, #00ff88 0%, #00cc6a 100%);
color: #1a1a2e;
border: none;
padding: 12px 30px;
font-size: 1.1rem;
font-weight: bold;
border-radius: 8px;
cursor: pointer;
margin-top: 15px;
transition: transform 0.1s, box-shadow 0.1s;
}
.btn:hover {
transform: scale(1.05);
box-shadow: 0 0 15px rgba(0, 255, 136, 0.5);
}
.btn:active {
transform: scale(0.98);
}
.game-over {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(0, 0, 0, 0.85);
padding: 40px;
border-radius: 15px;
text-align: center;
border: 2px solid #ff4757;
display: none;
}
.game-over h2 {
color: #ff4757;
font-size: 2rem;
margin-bottom: 15px;
}
.game-over p {
font-size: 1.2rem;
margin-bottom: 20px;
}
.mobile-controls {
display: none;
gap: 10px;
margin-top: 20px;
}
.mobile-btn {
width: 60px;
height: 60px;
background: rgba(0, 255, 136, 0.2);
border: 2px solid #00ff88;
border-radius: 50%;
color: #00ff88;
font-size: 1.5rem;
cursor: pointer;
transition: background 0.2s;
}
.mobile-btn:active {
background: rgba(0, 255, 136, 0.4);
}
@media (max-width: 500px) {
.mobile-controls {
display: grid;
grid-template-columns: repeat(3, 60px);
justify-items: center;
}
.controls {
display: none;
}
canvas {
max-width: 100%;
}
}
</style>
</head>
<body>
<h1>🐍 Змейка</h1>
<div class="score-board">
<div class="score-item">Счёт: <span id="score">0</span></div>
<div class="score-item">Рекорд: <span id="highScore">0</span></div>
</div>
<div class="game-container">
<canvas id="gameCanvas" width="400" height="400"></canvas>
<div class="game-over" id="gameOver">
<h2>Игра окончена!</h2>
<p>Твой счёт: <span id="finalScore">0</span></p>
<button class="btn" onclick="restartGame()">Играть снова</button>
</div>
</div>
<button class="btn" id="startBtn" onclick="startGame()">Начать игру</button>
<div class="controls">
<p>Управление: стрелки или WASD</p>
<p>Пауза: пробел | Перезапуск: Enter</p>
</div>
<div class="mobile-controls">
<div></div>
<button class="mobile-btn" onclick="changeDirection('up')">↑</button>
<div></div>
<button class="mobile-btn" onclick="changeDirection('left')">←</button>
<button class="mobile-btn" onclick="changeDirection('down')">↓</button>
<button class="mobile-btn" onclick="changeDirection('right')">→</button>
</div>
<script>
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const scoreEl = document.getElementById('score');
const highScoreEl = document.getElementById('highScore');
const gameOverEl = document.getElementById('gameOver');
const finalScoreEl = document.getElementById('finalScore');
const startBtn = document.getElementById('startBtn');
const gridSize = 20;
const tileCount = canvas.width / gridSize;
let snake = [];
let food = {};
let direction = { x: 1, y: 0 };
let nextDirection = { x: 1, y: 0 };
let score = 0;
let highScore = localStorage.getItem('snakeHighScore') || 0;
let gameLoop = null;
let gameSpeed = 100;
let isGameRunning = false;
let isPaused = false;
highScoreEl.textContent = highScore;
function initGame() {
snake = [
{ x: 10, y: 10 },
{ x: 9, y: 10 },
{ x: 8, y: 10 }
];
direction = { x: 1, y: 0 };
nextDirection = { x: 1, y: 0 };
score = 0;
scoreEl.textContent = score;
placeFood();
gameOverEl.style.display = 'none';
startBtn.style.display = 'none';
}
function placeFood() {
do {
food = {
x: Math.floor(Math.random() * tileCount),
y: Math.floor(Math.random() * tileCount)
};
} while (snake.some(seg => seg.x === food.x && seg.y === food.y));
}
function draw() {
ctx.fillStyle = '#0a0a15';
ctx.fillRect(0, 0, canvas.width, canvas.height);
for (let i = 0; i < tileCount; i++) {
for (let j = 0; j < tileCount; j++) {
ctx.strokeStyle = 'rgba(255, 255, 255, 0.03)';
ctx.strokeRect(i * gridSize, j * gridSize, gridSize, gridSize);
}
}
ctx.fillStyle = '#ff4757';
ctx.shadowColor = '#ff4757';
ctx.shadowBlur = 10;
ctx.beginPath();
ctx.arc(
food.x * gridSize + gridSize / 2,
food.y * gridSize + gridSize / 2,
gridSize / 2 - 2,
0,
Math.PI * 2
);
ctx.fill();
ctx.shadowBlur = 0;
snake.forEach((segment, index) => {
const gradient = ctx.createRadialGradient(
segment.x * gridSize + gridSize / 2,
segment.y * gridSize + gridSize / 2,
0,
segment.x * gridSize + gridSize / 2,
segment.y * gridSize + gridSize / 2,
gridSize / 2
);
if (index === 0) {
gradient.addColorStop(0, '#00ff88');
gradient.addColorStop(1, '#00cc6a');
ctx.shadowColor = '#00ff88';
ctx.shadowBlur = 10;
} else {
gradient.addColorStop(0, '#00cc6a');
gradient.addColorStop(1, '#009950');
ctx.shadowBlur = 0;
}
ctx.fillStyle = gradient;
ctx.fillRect(
segment.x * gridSize + 1,
segment.y * gridSize + 1,
gridSize - 2,
gridSize - 2
);
});
ctx.shadowBlur = 0;
}
function update() {
if (isPaused) return;
direction = { ...nextDirection };
const head = {
x: snake[0].x + direction.x,
y: snake[0].y + direction.y
};
if (head.x < 0 || head.x >= tileCount ||
head.y < 0 || head.y >= tileCount ||
snake.some(seg => seg.x === head.x && seg.y === head.y)) {
endGame();
return;
}
snake.unshift(head);
if (head.x === food.x && head.y === food.y) {
score += 10;
scoreEl.textContent = score;
if (score > highScore) {
highScore = score;
highScoreEl.textContent = highScore;
localStorage.setItem('snakeHighScore', highScore);
}
placeFood();
if (gameSpeed > 50) {
gameSpeed -= 2;
clearInterval(gameLoop);
gameLoop = setInterval(update, gameSpeed);
}
} else {
snake.pop();
}
draw();
}
function startGame() {
if (isGameRunning) return;
initGame();
isGameRunning = true;
isPaused = false;
gameSpeed = 100;
gameLoop = setInterval(update, gameSpeed);
draw();
}
function endGame() {
isGameRunning = false;
clearInterval(gameLoop);
finalScoreEl.textContent = score;
gameOverEl.style.display = 'block';
}
function restartGame() {
clearInterval(gameLoop);
startGame();
}
function changeDirection(dir) {
if (!isGameRunning || isPaused) return;
const opposites = {
up: 'down',
down: 'up',
left: 'right',
right: 'left'
};
const dirMap = {
up: { x: 0, y: -1 },
down: { x: 0, y: 1 },
left: { x: -1, y: 0 },
right: { x: 1, y: 0 }
};
const currentDir = direction.x === 0 ? (direction.y > 0 ? 'down' : 'up') : (direction.x > 0 ? 'right' : 'left');
if (opposites[dir] !== currentDir) {
nextDirection = dirMap[dir];
}
}
document.addEventListener('keydown', (e) => {
if (!isGameRunning && e.code === 'Enter') {
startGame();
return;
}
if (e.code === 'Space' && isGameRunning) {
isPaused = !isPaused;
return;
}
if (e.code === 'Enter' && !isGameRunning) {
restartGame();
return;
}
const keyMap = {
ArrowUp: 'up',
ArrowDown: 'down',
ArrowLeft: 'left',
ArrowRight: 'right',
KeyW: 'up',
KeyS: 'down',
KeyA: 'left',
KeyD: 'right'
};
if (keyMap[e.code]) {
e.preventDefault();
changeDirection(keyMap[e.code]);
}
});
draw();
</script>
</body>
</html>