initial commit. Ver 1.0
This commit is contained in:
@@ -0,0 +1,310 @@
|
||||
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();
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user