document.addEventListener('DOMContentLoaded', () => { const canvas = document.getElementById('simulationCanvas'); if (!canvas) return; const ctx = canvas.getContext('2d'); const launchButton = document.getElementById('launchButton'); const accelerationInput = document.getElementById('acceleration'); const burnTimeInput = document.getElementById('burnTime'); const angleInput = document.getElementById('angle'); const resultMessage = document.getElementById('result-message'); // --- Параметры симуляции --- const GRAVITY = 9.8; const PIXELS_PER_METER = 0.01; const TIME_STEP = 1.0; let rocket = {}; let simulationState = {}; function initializeState() { return { animationFrameId: null, elapsedTime: 0, acceleration: 0, burnTime: 0, angleRad: 0, }; } function initializeRocket() { return { x: 30, y: canvas.height - 45, width: 10, height: 25, x_meters: 0, y_meters: 0, vx: 0, // Скорость по X в м/с vy: 0, // Скорость по Y в м/с }; } function drawEarth() { ctx.fillStyle = '#2c5c23'; ctx.beginPath(); ctx.arc(canvas.width / 2, canvas.height + canvas.height - 50, canvas.height, 0, Math.PI, true); ctx.fill(); } function drawRocket() { ctx.save(); ctx.translate(rocket.x, rocket.y); // Угол поворота ракеты зависит от вектора скорости // Но до старта (скорость 0) она стоит вертикально const angle = (rocket.vx === 0 && rocket.vy === 0) ? -Math.PI / 2 : Math.atan2(rocket.vy, rocket.vx); // Поворачиваем ракету. Наша модель "смотрит" вправо, поэтому доп. вращение не нужно. ctx.rotate(angle); // Рисуем пламя, если двигатель работает if (simulationState.elapsedTime > 0 && simulationState.elapsedTime <= simulationState.burnTime) { ctx.fillStyle = `rgba(255, ${Math.random() * 150 + 100}, 0, 0.8)`; ctx.beginPath(); const flameLength = rocket.height * (1.5 + Math.random() * 0.5); ctx.moveTo(-rocket.width / 2, 0); ctx.lineTo(-rocket.width / 2 - flameLength, rocket.width / 2); ctx.lineTo(-rocket.width / 2 - flameLength, -rocket.width / 2); ctx.closePath(); ctx.fill(); } // Корпус ракеты (перерисовываем, чтобы был поверх пламени) // Модель ракеты теперь "смотрит" вправо (по оси X) ctx.fillStyle = '#d0d0d0'; ctx.beginPath(); ctx.moveTo(rocket.height / 2, 0); ctx.lineTo(-rocket.height / 2, -rocket.width / 2); ctx.lineTo(-rocket.height / 2, rocket.width / 2); ctx.closePath(); ctx.fill(); ctx.restore(); } function resetSimulation() { if (simulationState.animationFrameId) { cancelAnimationFrame(simulationState.animationFrameId); } simulationState = initializeState(); rocket = initializeRocket(); resultMessage.innerHTML = ' '; resultMessage.className = 'alert alert-secondary'; clearCanvas(); drawEarth(); drawRocket(); } function clearCanvas() { ctx.clearRect(0, 0, canvas.width, canvas.height); } function animate() { simulationState.elapsedTime += TIME_STEP; // 1. Обновляем физику // Если двигатель работает, применяем ускорение if (simulationState.elapsedTime <= simulationState.burnTime) { const currentAccelerationX = simulationState.acceleration * Math.cos(simulationState.angleRad); const currentAccelerationY = simulationState.acceleration * Math.sin(simulationState.angleRad); rocket.vx += currentAccelerationX * TIME_STEP; rocket.vy += currentAccelerationY * TIME_STEP; } // Всегда применяем гравитацию (она действует на ось Y) rocket.vy -= GRAVITY * TIME_STEP; // Обновляем позицию в метрах rocket.x_meters += rocket.vx * TIME_STEP; rocket.y_meters += rocket.vy * TIME_STEP; // 2. Обновляем позицию для отрисовки в пикселях const groundY = canvas.height - 45; rocket.x = 30 + (rocket.x_meters * PIXELS_PER_METER); rocket.y = groundY - (rocket.y_meters * PIXELS_PER_METER); // 3. Отрисовываем сцену clearCanvas(); drawEarth(); drawRocket(); // 4. Проверяем условия завершения const hasCrashed = rocket.y_meters < 0 && simulationState.elapsedTime > 1; const outOfBounds = rocket.x > canvas.width + rocket.height; if (hasCrashed || outOfBounds) { rocket.y_meters = 0; rocket.y = groundY; clearCanvas(); drawEarth(); drawRocket(); resultMessage.innerHTML = outOfBounds ? "Неудача. Ракета улетела за пределы видимости." : "Неудача. Ракета упала обратно на Землю."; resultMessage.className = 'alert alert-danger'; cancelAnimationFrame(simulationState.animationFrameId); simulationState.animationFrameId = null; return; } simulationState.animationFrameId = requestAnimationFrame(animate); } launchButton.addEventListener('click', () => { const accelValue = parseFloat(accelerationInput.value); const burnTimeValue = parseFloat(burnTimeInput.value); const angleValue = parseFloat(angleInput.value); if (isNaN(accelValue) || isNaN(burnTimeValue) || isNaN(angleValue) || accelValue <= 0 || burnTimeValue <= 0 || angleValue < 0 || angleValue > 90) { resultMessage.innerHTML = 'Пожалуйста, введите корректные значения (ускорение > 0, время > 0, угол 0-90).'; resultMessage.className = 'alert alert-warning'; return; } resetSimulation(); simulationState.acceleration = accelValue; simulationState.burnTime = burnTimeValue; simulationState.angleRad = angleValue * (Math.PI / 180); resultMessage.innerHTML = "Запуск..."; resultMessage.className = 'alert alert-info'; if (!simulationState.animationFrameId) { simulationState.animationFrameId = requestAnimationFrame(animate); } }); resetSimulation(); });