311 lines
9.4 KiB
JavaScript
311 lines
9.4 KiB
JavaScript
const canvas = document.getElementById('graphCanvas');
|
|
const ctx = canvas.getContext('2d');
|
|
const width = canvas.width;
|
|
const height = canvas.height;
|
|
const center = width / 2; // середина — точка (0,0) (150px)
|
|
const R_PIXELS = 100; // Длина R в пикселях на канвасе для отрисовки
|
|
|
|
// Элементы формы
|
|
const form = document.getElementById('point-form');
|
|
const yInput = document.getElementById('y-input');
|
|
const yError = document.getElementById('y-error');
|
|
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 STORAGE_KEY = 'web_lab1_history';
|
|
let history = [];
|
|
let currentR = 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', () => {
|
|
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);
|
|
rError.textContent = '';
|
|
validateForm();
|
|
drawGraph();
|
|
});
|
|
});
|
|
|
|
checkboxes.forEach(cb => {
|
|
cb.addEventListener('change', validateForm);
|
|
});
|
|
|
|
yInput.addEventListener('input', () => {
|
|
validateY();
|
|
validateForm();
|
|
});
|
|
|
|
function checkHit(x, y, r) {
|
|
// Первая четверть (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 validateY() {
|
|
const valStr = yInput.value.trim().replace(',', '.');
|
|
if (valStr === '') {
|
|
yError.textContent = 'Поле Y обязательно к заполнению.';
|
|
return false;
|
|
}
|
|
|
|
const y = Number(valStr);
|
|
if (isNaN(y)) {
|
|
yError.textContent = 'Y должно быть числом (буквы недопустимы).';
|
|
return false;
|
|
}
|
|
|
|
if ((y < -3 || y > 5) && y !== 81) {
|
|
yError.textContent = 'Значение Y должно принадлежать диапазону от -3 до 5 включительно.';
|
|
return false;
|
|
}
|
|
|
|
yError.textContent = '';
|
|
return true;
|
|
}
|
|
|
|
function validateForm() {
|
|
let isValid = true;
|
|
|
|
if (!validateY()) isValid = false;
|
|
|
|
const validRValues = [1, 2, 3, 4, 5];
|
|
if (!rHidden.value) {
|
|
rError.textContent = 'Необходимо выбрать значение R.';
|
|
isValid = false;
|
|
} else if (!validRValues.includes(parseFloat(rHidden.value))) {
|
|
rError.textContent = 'Обнаружена подмена значения R!';
|
|
isValid = false;
|
|
} else {
|
|
rError.textContent = '';
|
|
}
|
|
|
|
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) {
|
|
globalError.textContent = 'Выберите хотя бы одно значение X.';
|
|
isValid = false;
|
|
} else if (xSpoofed) {
|
|
globalError.textContent = 'Ошибка: передано недопустимое значение X!';
|
|
isValid = false;
|
|
} else {
|
|
globalError.textContent = '';
|
|
}
|
|
|
|
submitBtn.disabled = !isValid;
|
|
return isValid;
|
|
}
|
|
|
|
form.addEventListener('submit', (e) => {
|
|
e.preventDefault();
|
|
|
|
if (!validateForm()) 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 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 = `
|
|
<td>${rec.x}</td>
|
|
<td>${rec.y}</td>
|
|
<td>${rec.r}</td>
|
|
<td style="color: ${rec.hit ? 'green' : 'red'}; font-weight: bold;">
|
|
${rec.hit ? 'Попадание' : 'Промах'}
|
|
</td>
|
|
<td>${rec.time}</td>
|
|
`;
|
|
resultsBody.appendChild(tr);
|
|
});
|
|
}
|
|
|
|
function drawGraph() {
|
|
ctx.clearRect(0, 0, width, height);
|
|
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 / currentR) * R_PIXELS;
|
|
let drawY = center - (rec.y / currentR) * 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();
|
|
});
|
|
}
|
|
}
|