Просмотр исходного кода

Добавить 'docs/ issues.md'

u23-27burdyko 1 месяц назад
Родитель
Сommit
be2f1bf9e3
1 измененных файлов с 227 добавлено и 0 удалено
  1. 227 0
      docs/ issues.md

+ 227 - 0
docs/ issues.md

@@ -0,0 +1,227 @@
+Интерактивный веб-проект — симулятор поста охраны КПП с моделью работы шлагбаума.
+
+Запись 1. Общая концепция проекта
+Задача. Разработать одностраничное интерактивное приложение по теме «Моделирование работы шлагбаума». Пользователь должен выступать в роли оператора КПП: сравнивать пропуска, принимать решение о пропуске или отказе, управлять шлагбаумом. Оформление — служебная система контроля доступа, тёмный интерфейс, без внешних библиотек. Всё в одном HTML-файле.
+
+Решение. Создан каркас: HTML-структура с шапкой (логотип, часы, системный индикатор), основной сценой на Canvas и правой панелью управления. Сцена — псевдо-3D вид на КПП: небо с градиентом, здания, две полосы движения, будка охраны, охранник, шлагбаум с красно-белой стрелой. Стилистика выдержана в тёмных тонах с янтарными и голубыми акцентами, шрифт Courier New.
+
+<div class="app">
+  <header>
+    <div class="brand">◤ КПП-3<small>СИСТЕМА КОНТРОЛЯ ДОСТУПА</small></div>
+    <div class="header-status">
+      <div class="clock" id="clock">--:--:--</div>
+      <div class="sys-status" id="sysStatus"><span class="led"></span><span id="sysStatusText">ПОСТ СВОБОДЕН</span></div>
+    </div>
+  </header>
+  <main>
+    <div class="stage"><canvas id="cv"></canvas></div>
+    <aside>...</aside>
+  </main>
+</div>
+
+Запись 2. Системные часы и индикатор состояния
+Задача. Добавить часы реального времени в шапке и цветной индикатор состояния поста, который меняет цвет и текст в зависимости от ситуации.
+
+Решение. Часы обновляются через setInterval каждую секунду. Индикатор реализован как переключаемый класс: «ПОСТ СВОБОДЕН» (зелёный), «ПРИБЛИЖАЕТСЯ АВТО» / «ОЖИДАНИЕ РЕШЕНИЯ» (жёлтый), «МАШИНА ЖДЁТ» (синий), «СТАРШИЙ ВЫЗВАН» (голубой), «НАРУШИТЕЛЬ !» / «ТРЕВОГА !» (красный, мигающий).
+
+function updateClock() {
+  const d = new Date();
+  document.getElementById('clock').textContent =
+    String(d.getHours()).padStart(2,'0') + ':' +
+    String(d.getMinutes()).padStart(2,'0') + ':' +
+    String(d.getSeconds()).padStart(2,'0');
+}
+setInterval(updateClock, 1000);
+
+function setStatus(text, cls) {
+  document.getElementById('sysStatusText').textContent = text;
+  document.getElementById('sysStatus').className = 'sys-status' + (cls ? ' ' + cls : '');
+}
+
+Запись 3. Сравнение пропусков
+Задача. Сделать две карточки для сравнения: эталонный пропуск «В БАЗЕ» и предъявленный водителем. Пропуска генерируются случайно: иногда совпадают, иногда отличаются номером, иногда предъявленный отозван.
+
+Решение. Функция randomPass() генерирует случайный пропуск (номер, ФИО, подразделение, признак valid). Функция generateScenario() определяет, совпадает ли предъявленный с эталонным, и должно ли решение оператора быть «разрешить» или «запретить». Карточки рисуются в правой панели.
+
+function randomPass() {
+  return {
+    num: series[Math.floor(Math.random()*series.length)] + '-' +
+         String(Math.floor(Math.random()*900)+100),
+    name: firstNames[Math.floor(Math.random()*firstNames.length)] + ' ' +
+          initials[Math.floor(Math.random()*initials.length)],
+    dept: departments[Math.floor(Math.random()*departments.length)],
+    valid: Math.random() > 0.25
+  };
+}
+
+Запись 4. Анимация шлагбаума
+Задача. Шлагбаум должен плавно открываться и закрываться, с реалистичной красно-белой стрелой, лампой-индикатором и вращающейся осью. Угол стрелы — от 0 (закрыт) до 90° (открыт).
+
+Решение. Уровень шлагбаума хранится в state.barrier.level от 0 до 1 и плавно интерполируется к target. Стрела рисуется через ctx.translate/rotate — угол равен Math.PI + level * Math.PI/2. Лампа на верху столба меняет цвет: красный при закрытом, жёлтый при движении, зелёный при открытом. Стрела состоит из 14 чередующихся сегментов.
+
+const angle = Math.PI + level * Math.PI / 2;
+ctx.save();
+ctx.translate(pivotX, pivotY);
+ctx.rotate(angle);
+const seg = 14;
+for (let i = 0; i < seg; i++) {
+  ctx.fillStyle = i % 2 === 0 ? '#e04040' : '#e8e8e8';
+  ctx.fillRect(i * (armLen / seg), -armH/2, armLen / seg, armH);
+}
+ctx.restore();
+
+Запись 5. Движение автомобилей
+Задача. Реализовать несколько типов машин: обычный легковой автомобиль и тёмный тонированный автомобиль для сценария «силовой проезд». Машины подъезжают к шлагбауму, останавливаются, ждут решения оператора, потом проезжают или разворачиваются.
+
+Решение. Нарисованы две процедурные машины на Canvas. Легковой автомобиль — цветной, с градиентом, фарами и стёклами. Тонированный автомобиль — тёмный, с угловатыми фарами, решёткой радиатора, серебряными пятиспицевыми дисками. Колёса вращаются вместе с движением.
+
+function drawCarScaled(drawFn, x, y, ...args) {
+  ctx.save();
+  ctx.translate(x, y);
+  ctx.scale(CAR_SCALE, CAR_SCALE);
+  ctx.translate(-x, -y);
+  drawFn(x, y, ...args);
+  ctx.restore();
+}
+
+Запись 6. Сценарий силового проезда и столкновения
+Задача. Добавить сценарий, в котором машина не сбрасывает скорость и врезается в закрытый шлагбаум. При столкновении — искры, тревога, приезд полиции и эвакуатора.
+
+Решение. Кнопка «Силовой проезд» запускает тонированный автомобиль со скоростью в два раза выше обычной. Если шлагбаум закрыт — срабатывает функция crash(): красная пульсирующая рамка, надпись «!!! ТРЕВОГА !!!», 40 частиц искр. Через 700 мс приезжает полицейский автомобиль с мигалками, через 1400 мс — эвакуатор с водителем.
+
+function crash() {
+  logEvent('!!! СТОЛКНОВЕНИЕ СО ШЛАГБАУМОМ !!!', 'alarm');
+  setStatus('ТРЕВОГА !', 'alarm');
+  state.alarm = true;
+  state.alarmEnd = state.time + 5;
+  state.phase = 'crash_scene';
+  state.stats.alarms++;
+  state.stats.breaches++;
+  for (let i = 0; i < 40; i++) {
+    state.sparks.push({
+      x: l.pivotX - l.armLen - 30, y: l.topLaneY - 40,
+      vx: (Math.random() - .5) * 400, vy: (Math.random() - .5) * 300 - 80,
+      life: 1
+    });
+  }
+}
+
+Запись 7. Работа водителя эвакуатора
+Задача. Водитель эвакуатора должен выйти из кабины, дойти до машины, прицепить её тросом, вернуться в кабину и увезти. Всё это — с анимацией ходьбы.
+
+Решение. Реализован конечный автомат из 9 состояний: exiting → walking_out_down → walking_right → walking_up → attaching → returning_down → returning_left → returning_up → boarding → gone. Персонаж нарисован детально: оранжевый жилет со светоотражающими полосами, каска, трос крепится квадратичной кривой Безье.
+
+if (td.state === 'walking_out_down') {
+  if (td.y < underY - 2) { td.y += SPD * dt; td.walking = true; }
+  else { td.y = underY; td.state = 'walking_right'; }
+}
+else if (td.state === 'attaching') {
+  td.attachTimer += dt; td.walking = false;
+  if (td.attachTimer > 0.6 && !state.towCable) {
+    state.towCable = true;
+    logEvent('Автомобиль зацеплен', 'info');
+  }
+}
+
+Запись 8. Старший смены
+Задача. При отказе в проезде оператор может вызвать старшего смены. Старший должен прийти к машине, проверить документы и самостоятельно принять верное решение.
+
+Решение. По кнопке «Вызвать старшего» создаётся объект state.senior с координатами за правым краем экрана. Он идёт к машине по нижней полосе, затем поднимается к верхней, стоит 2 секунды, «проверяет документы» и выносит верный вердикт. Отдельная функция drawSenior() рисует фигуру в форме охраны — тёмно-синий китель, фуражка, погоны.
+
+Запись 9. Погода и время суток
+Задача. Добавить переключение времени суток (день/ночь) и погоды (ясно, дождь, снег, туман). Освещение, цвета неба, поведение машин и видимость должны меняться.
+
+Решение. Каждой комбинации соответствует свой режим освещения: day, dawn, dusk, overcast, night, wetnight, snownight. Функция skyColors() возвращает цвет неба, drawWeatherParticles() — рисует дождь, снег или туман. Дождь и снег снижают скорость машин через speedMult(): дождь — 0.75, снег — 0.6, туман — 0.85. Ночью включается ИК-режим камеры.
+
+function speedMult() {
+  if (state.weather === 'rain') return 0.75;
+  if (state.weather === 'snow') return 0.6;
+  if (state.weather === 'fog')  return 0.85;
+  return 1;
+}
+
+Запись 10. Журнал событий
+Задача. Все действия в системе должны фиксироваться в журнале с отметкой времени и цветовой кодировкой.
+
+Решение. Массив logEntries хранит до 80 последних записей. Каждая запись имеет тип: info (голубой), ok (зелёный), warn (жёлтый), alarm (красный), senior (голубой жирный). Журнал автоматически прокручивается вниз.
+
+function logEvent(text, cls) {
+  const now = new Date();
+  const t = String(now.getHours()).padStart(2,'0') + ':' +
+            String(now.getMinutes()).padStart(2,'0') + ':' +
+            String(now.getSeconds()).padStart(2,'0');
+  logEntries.push({ t, text, cls: cls || 'info' });
+  if (logEntries.length > 80) logEntries.shift();
+  renderLog();
+}
+
+Запись 11. Отчёт о смене
+Задача. В конце работы оператор нажимает «Закончить смену» — должно открыться модальное окно с итогами: сколько впущено, отказано, верных решений, ошибок, тревог, силовых проездов, вызовов старшего. Оценка — по проценту верных решений.
+
+Решение. Собраны все счётчики в state.stats. Оценка рассчитывается: ≥ 95% — «ОТЛИЧНО», ≥ 80% — «ХОРОШО», ≥ 60% — «УДОВЛЕТВОРИТЕЛЬНО», иначе — «ТРЕБУЕТСЯ ПОДГОТОВКА».
+
+const pct = s.correct / total;
+if (pct >= 0.95) grade = 'ОТЛИЧНО';
+else if (pct >= 0.8) grade = 'ХОРОШО';
+else if (pct >= 0.6) grade = 'УДОВЛЕТВОРИТЕЛЬНО';
+else grade = 'ТРЕБУЕТСЯ ПОДГОТОВКА';
+
+Запись 12. Отметка в журнале вручную
+Задача. Дать оператору возможность добавить собственную запись в журнал.
+
+Решение. Кнопка «✎ Отметить в журнале» вызывает prompt, введённый текст добавляется в журнал с пометкой [ОПЕРАТОР].
+
+document.getElementById('btnNote').onclick = () => {
+  const txt = prompt('Введите отметку для журнала:', '');
+  if (txt && txt.trim()) logEvent('[ОПЕРАТОР] ' + txt.trim(), 'info');
+};
+
+Запись 13. Чёткость Canvas
+Задача. Картинка на экране размытая, особенно на Retina-дисплеях. Сделать рендер чётким.
+
+Решение. Внутренний размер канваса увеличивается пропорционально devicePixelRatio, а рендер идёт в логических координатах через ctx.setTransform(). Это убирает размытие и сохраняет единую систему координат.
+
+function resizeCanvas() {
+  const stage = cv.parentElement;
+  const dpr = window.devicePixelRatio || 1;
+  W = stage.clientWidth; H = stage.clientHeight;
+  cv.width = Math.floor(W * dpr); cv.height = Math.floor(H * dpr);
+  ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
+}
+window.addEventListener('resize', resizeCanvas);
+
+Запись 14. Сброс состояния
+Задача. Добавить кнопку полного сброса — вернуть систему в исходное состояние, очистить журнал, убрать все машины и объекты.
+
+Решение. Функция resetAll() обнуляет машину, шлагбаум, полицию, эвакуатор, старшего, очищает журнал, перерисовывает карточки пропусков.
+
+function resetAll() {
+  state.car = null; state.correctPass = null; state.presentedPass = null;
+  state.userDecision = null; state.correctDecision = null;
+  state.phase = 'idle'; state.barrier.target = 0; state.barrier.level = 0;
+  state.alarm = false; state.sparks = []; state.police = null;
+  state.tow = null; state.towDriver = null; state.towCable = false;
+  state.senior = null;
+  logEntries.length = 0; renderLog(); renderPasses();
+  setStatus('ПОСТ СВОБОДЕН');
+  setHint('Нажмите <b>«Следующая машина»</b>.');
+}
+
+Запись 15. Единый игровой цикл
+Задача. Обеспечить плавную работу всех анимаций с привязкой к реальному времени.
+
+Решение. Один requestAnimationFrame с delta-time (не более 0.05 с на кадр). Отдельно обновляется сцена (updateScene) и отрисовывается (drawScene). Состояние игры хранится в одном объекте state.
+
+function loop(ts) {
+  if (!lastTs) lastTs = ts;
+  const dt = Math.min(0.05, (ts - lastTs) / 1000);
+  lastTs = ts;
+  state.time += dt;
+  updateScene(dt);
+  drawScene();
+  requestAnimationFrame(loop);
+}
+requestAnimationFrame(loop);
+
+Как запустить
+Просто открыть index.html в браузере. Все зависимости встроены, внешних библиотек нет.