const game = document.getElementById("game"); const player = document.getElementById("player"); const scoreEl = document.getElementById("score"); const hintEl = document.getElementById("hint"); const gameOverEl = document.getElementById("gameOver"); const finalScoreEl = document.getElementById("finalScore"); const bestScoreEl = document.getElementById("bestScore"); const GROUND = 50; // высота земли const GRAVITY = 0.55; const JUMP_FORCE = 12; const START_SPEED = 5; let running = false; let gameOver = false; let playerY = 0; // высота над землёй let velocity = 0; let speed = START_SPEED; let score = 0; let best = Number(localStorage.getItem("spaceJumpBest")) || 0; let obstacles = []; let spawnTimer = 0; let lastTime = 0; function startGame() { obstacles.forEach((o) => o.el.remove()); obstacles = []; playerY = 0; velocity = 0; speed = START_SPEED; score = 0; spawnTimer = 0; running = true; gameOver = false; hintEl.style.display = "none"; gameOverEl.classList.remove("visible"); scoreEl.textContent = "0"; lastTime = performance.now(); requestAnimationFrame(loop); } function jump() { if (playerY === 0) { velocity = JUMP_FORCE; } } function spawnObstacle() { const el = document.createElement("div"); el.className = "obstacle"; const height = 30 + Math.random() * 35; el.style.height = height + "px"; game.appendChild(el); obstacles.push({ el, x: game.clientWidth, height }); } function endGame() { running = false; gameOver = true; best = Math.max(best, score); localStorage.setItem("spaceJumpBest", best); finalScoreEl.textContent = score; bestScoreEl.textContent = best; gameOverEl.classList.add("visible"); } function loop(now) { if (!running) return; const dt = Math.min((now - lastTime) / 16.67, 3); // нормализация к 60 FPS lastTime = now; // физика игрока velocity -= GRAVITY * dt; playerY += velocity * dt; if (playerY <= 0) { playerY = 0; velocity = 0; } player.style.bottom = GROUND + playerY + "px"; // спавн препятствий spawnTimer -= dt; if (spawnTimer <= 0) { spawnObstacle(); spawnTimer = 60 + Math.random() * 70; } // движение препятствий и столкновения const playerLeft = 80; const playerRight = playerLeft + 36; for (let i = obstacles.length - 1; i >= 0; i--) { const o = obstacles[i]; o.x -= speed * dt; o.el.style.left = o.x + "px"; if (o.x + 22 < 0) { o.el.remove(); obstacles.splice(i, 1); score++; scoreEl.textContent = score; speed += 0.15; // чуть быстрее с каждым очком continue; } const hitX = o.x < playerRight - 6 && o.x + 22 > playerLeft + 6; const hitY = playerY < o.height - 4; if (hitX && hitY) { endGame(); return; } } requestAnimationFrame(loop); } document.addEventListener("keydown", (e) => { if (e.code !== "Space") return; e.preventDefault(); if (!running) { startGame(); } else { jump(); } }); // поддержка тапа/клика для мобильных game.addEventListener("pointerdown", () => { if (!running) { startGame(); } else { jump(); } });