const canvas = document.getElementById('graphCanvas'); const ctx = canvas.getContext('2d'); const width = canvas.width; const height = canvas.height; const center = width / 2; // Элементы формы const form = document.getElementById('point-form'); const yInput = document.getElementById('y-input'); const yError = document.getElementById('y-error'); const funcInput = document.getElementById('func-input'); const funcError = document.getElementById('func-error'); const previewFuncBtn = document.getElementById('preview-func-btn'); const rHidden = document.getElementById('r-hidden'); const rError = document.getElementById('r-error'); const rButtons = document.querySelectorAll('.r-button'); const checkboxes = document.querySelectorAll('input[name="xval"]'); const submitBtn = document.getElementById('submit-btn'); const globalError = document.getElementById('global-error'); const resultsBody = document.getElementById('results-body'); const clearHistoryBtn = document.getElementById('clear-history-btn'); // Б - безопасность const STORAGE_KEY = 'web_lab1_history'; let history = []; let currentR = null; let formSubmitted = false; let customFunction = null; try { const rawData = JSON.parse(localStorage.getItem(STORAGE_KEY)); if (Array.isArray(rawData)) { // Проверяем LocalStorage (вдруг врёт) history = rawData.filter(rec => { if (typeof rec !== 'object' || rec === null) return false; if (![1, 2, 3, 4, 5].includes(rec.r)) return false; if (![-2, -1.5, -1, -0.5, 0, 0.5, 1, 1.5, 2].includes(rec.x)) return false; if (typeof rec.y !== 'number' || isNaN(rec.y) || ((rec.y < -3 || rec.y > 5) && rec.y !== 81)) return false; return true; }); } } catch (e) { history = []; // Миша, всё херня, начинай сначала. } // Пошло поехало window.addEventListener('DOMContentLoaded', () => { // Очистка истории if (clearHistoryBtn) { clearHistoryBtn.addEventListener('click', () => { history = []; localStorage.removeItem(STORAGE_KEY); renderHistory(); drawGraph(); }); } renderHistory(); drawGraph(); validateForm(); }); rButtons.forEach(btn => { btn.addEventListener('click', () => { rButtons.forEach(b => b.classList.remove('active')); btn.classList.add('active'); rHidden.value = btn.getAttribute('data-val'); currentR = parseFloat(rHidden.value); if (formSubmitted) { rError.textContent = ''; } validateForm(); drawGraph(); }); }); checkboxes.forEach(cb => { cb.addEventListener('change', () => { if (formSubmitted) { globalError.textContent = ''; } validateForm(); }); }); yInput.addEventListener('input', () => { if (formSubmitted) { yError.textContent = ''; } validateForm(); }); funcInput.addEventListener('input', () => { if (formSubmitted) { funcError.textContent = ''; } validateForm(); }); previewFuncBtn.addEventListener('click', () => { const funcStr = funcInput.value.trim(); if (funcStr === '') { customFunction = null; funcError.textContent = ''; drawGraph(); return; } if (!currentR) { funcError.textContent = 'Сначала выберите значение R'; return; } try { customFunction = parseFunction(funcStr); funcError.textContent = ''; drawGraph(); } catch (e) { funcError.textContent = e.message; customFunction = null; } }); function checkHit(x, y, r) { if (customFunction) { try { return customFunction(x, y, r); } catch (e) { return false; } } // Первая четверть (X >= 0, Y >= 0): Четверть круга (R) if (x >= 0 && y >= 0) { return (x * x + y * y) <= (r * r); } // Вторая четверть (X <= 0, Y >= 0): Прямоугольник (ширина R, высота R/2) if (x <= 0 && y >= 0) { return x >= -r && y <= (r / 2); } // Третья четверть (X <= 0, Y <= 0): Пустота if (x <= 0 && y <= 0) { return false; } // Четвертая четверть (X >= 0, Y <= 0): Треугольник (проходит через (R, 0) и (0, -R/2)) // Уравнение прямой: y = x/2 - r/2 => точка внутри если y >= x/2 - r/2 if (x >= 0 && y <= 0) { return y >= (x / 2) - (r / 2); } return false; } function parseFunction(funcStr) { if (!funcStr || funcStr.trim() === '') { return null; } const normalized = funcStr.trim() .replace(/abs\(/g, 'Math.abs(') .replace(/sin\(/g, 'Math.sin(') .replace(/cos\(/g, 'Math.cos(') .replace(/tan\(/g, 'Math.tan(') .replace(/sqrt\(/g, 'Math.sqrt(') .replace(/\^/g, '**'); const allowedChars = /^[xyrRMathabsincoqrt.()+\-*/<>=\s\d.]+$/; if (!allowedChars.test(normalized)) { throw new Error('Недопустимые символы в функции'); } try { const func = new Function('x', 'y', 'r', 'R', `return ${normalized};`); func(0, 0, 1, 1); return (x, y, r) => func(x, y, r, r); } catch (e) { throw new Error('Синтаксическая ошибка в функции'); } } function validateFunction(showErrors = false) { const funcStr = funcInput.value.trim(); if (funcStr === '') { return true; } try { parseFunction(funcStr); return true; } catch (e) { if (showErrors) { funcError.textContent = e.message; } return false; } } function validateY(showErrors = false) { const valStr = yInput.value.trim().replace(',', '.'); if (valStr === '') { if (showErrors) { yError.textContent = 'Поле Y обязательно к заполнению.'; } return false; } const y = Number(valStr); if (isNaN(y)) { if (showErrors) { yError.textContent = 'Y должно быть числом (буквы недопустимы).'; } return false; } if ((y < -3 || y > 5) && y !== 81) { if (showErrors) { yError.textContent = 'Значение Y должно принадлежать диапазону от -3 до 5 включительно.'; } return false; } return true; } function validateForm(showErrors = false) { let isValid = true; if (!validateY(showErrors)) isValid = false; if (!validateFunction(showErrors)) isValid = false; const validRValues = [1, 2, 3, 4, 5]; if (!rHidden.value) { if (showErrors) { rError.textContent = 'Необходимо выбрать значение R.'; } isValid = false; } else if (!validRValues.includes(parseFloat(rHidden.value))) { if (showErrors) { rError.textContent = 'Обнаружена подмена значения R!'; } isValid = false; } const validXValues = [-2, -1.5, -1, -0.5, 0, 0.5, 1, 1.5, 2]; let xChecked = false; let xSpoofed = false; checkboxes.forEach(cb => { if (cb.checked) { xChecked = true; if (!validXValues.includes(parseFloat(cb.value))) { xSpoofed = true; } } }); if (!xChecked) { if (showErrors) { globalError.textContent = 'Выберите хотя бы одно значение X.'; } isValid = false; } else if (xSpoofed) { if (showErrors) { globalError.textContent = 'Ошибка: передано недопустимое значение X!'; } isValid = false; } submitBtn.disabled = !isValid; return isValid; } form.addEventListener('submit', (e) => { e.preventDefault(); formSubmitted = true; if (!validateForm(true)) return; const y = parseFloat(yInput.value.trim().replace(',', '.')); // пасхалка if (y === 81) { const egg = document.getElementById('oscar-easter-egg'); if (egg) { egg.classList.add('show'); setTimeout(() => egg.classList.remove('show'), 1000); } yInput.value = ''; validateForm(); return; } const r = parseFloat(rHidden.value); const funcStr = funcInput.value.trim(); try { customFunction = parseFunction(funcStr); } catch (e) { funcError.textContent = e.message; return; } // Дата, время и тд const dateString = new Date().toLocaleString("ru-RU", { timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone, year: 'numeric', month: 'long', day: 'numeric', hour: '2-digit', minute: '2-digit', second: '2-digit' }); checkboxes.forEach(cb => { if (cb.checked) { const x = parseFloat(cb.value); const hit = checkHit(x, y, r); history.push({ x: x, y: y, r: r, hit: hit, time: dateString }); } }); localStorage.setItem(STORAGE_KEY, JSON.stringify(history)); renderHistory(); drawGraph(); }); function renderHistory() { resultsBody.innerHTML = ''; [...history].reverse().forEach(rec => { const tr = document.createElement('tr'); tr.innerHTML = ` ${rec.x} ${rec.y} ${rec.r} ${rec.hit ? 'Попадание' : 'Промах'} ${rec.time} `; resultsBody.appendChild(tr); }); } function drawGraph() { ctx.clearRect(0, 0, width, height); const R_PIXELS = currentR ? (100 * currentR / 3) : 100; if (customFunction && currentR) { const imageData = ctx.createImageData(width, height); const data = imageData.data; for (let py = 0; py < height; py++) { for (let px = 0; px < width; px++) { const x = ((px - center) / R_PIXELS) * currentR; const y = ((center - py) / R_PIXELS) * currentR; const hit = checkHit(x, y, currentR); const index = (py * width + px) * 4; if (hit) { data[index] = 51; data[index + 1] = 153; data[index + 2] = 255; data[index + 3] = 255; } else { data[index] = 255; data[index + 1] = 255; data[index + 2] = 255; data[index + 3] = 255; } } } ctx.putImageData(imageData, 0, 0); } else { ctx.fillStyle = '#3399FF'; // Прямоугольник ctx.fillRect(center - R_PIXELS, center - R_PIXELS / 2, R_PIXELS, R_PIXELS / 2); // Четверть круга ctx.beginPath(); ctx.moveTo(center, center); ctx.arc(center, center, R_PIXELS, -Math.PI / 2, 0, false); ctx.closePath(); ctx.fill(); // Треугольник ctx.beginPath(); ctx.moveTo(center, center); ctx.lineTo(center + R_PIXELS, center); ctx.lineTo(center, center + R_PIXELS / 2); ctx.closePath(); ctx.fill(); } // Оси ctx.strokeStyle = '#000'; ctx.lineWidth = 1; ctx.beginPath(); // Ось X ctx.moveTo(0, center); ctx.lineTo(width, center); ctx.lineTo(width - 5, center - 5); ctx.moveTo(width, center); ctx.lineTo(width - 5, center + 5); // Ось Y ctx.moveTo(center, height); ctx.lineTo(center, 0); ctx.lineTo(center - 5, 5); ctx.moveTo(center, 0); ctx.lineTo(center + 5, 5); ctx.stroke(); // Обозначения ctx.fillStyle = '#000'; ctx.font = '12px Arial'; // Ось X ctx.fillText('R', center + R_PIXELS - 5, center + 15); ctx.fillText('R/2', center + R_PIXELS / 2 - 10, center + 15); ctx.fillText('-R', center - R_PIXELS - 5, center + 15); ctx.fillText('-R/2', center - R_PIXELS / 2 - 10, center + 15); ctx.fillText('x', width - 10, center - 10); // Ось Y ctx.fillText('R', center + 10, center - R_PIXELS + 4); ctx.fillText('R/2', center + 10, center - R_PIXELS / 2 + 4); ctx.fillText('-R', center + 10, center + R_PIXELS + 4); ctx.fillText('-R/2', center + 10, center + R_PIXELS / 2 + 4); ctx.fillText('y', center + 10, 10); // Засечки ctx.beginPath(); [R_PIXELS, R_PIXELS/2, -R_PIXELS, -R_PIXELS/2].forEach(p => { // по X ctx.moveTo(center + p, center - 3); ctx.lineTo(center + p, center + 3); // по Y ctx.moveTo(center - 3, center + p); ctx.lineTo(center + 3, center + p); }); ctx.stroke(); if (currentR && currentR > 0) { history.forEach(rec => { let drawX = center + (rec.x / rec.r) * R_PIXELS; let drawY = center - (rec.y / rec.r) * R_PIXELS; ctx.beginPath(); ctx.arc(drawX, drawY, 4, 0, 2 * Math.PI, false); ctx.fillStyle = rec.hit ? '#4CAF50' : '#F44336'; ctx.fill(); ctx.strokeStyle = '#222'; ctx.stroke(); }); } }