Преглед изворни кода

Загрузить файлы ''

u23-27eroshko пре 4 месеци
родитељ
комит
d66ca05a40
5 измењених фајлова са 539 додато и 0 уклоњено
  1. 81 0
      README.md
  2. 18 0
      changelog.md
  3. 341 0
      game.py
  4. 14 0
      issues.md
  5. 85 0
      shenarui_moroz_eroshkk0_G0rbb.md

+ 81 - 0
README.md

@@ -0,0 +1,81 @@
+Понял, вот чистый текст README.md без всяких блоков:
+
+#  Терминал Центра Безопасности
+
+Интерактивное обучающее приложение на Python в стиле «терминала», моделирующее работу инженера по кибербезопасности на промышленном объекте (АСУ ТП).
+
+Пользователь проходит серию сценариев и принимает решения, влияющие на уровень безопасности системы.
+
+##  Возможности
+
+* 13 интерактивных сцен (кибербезопасность, физическая защита, инциденты)
+* Система очков и итоговой оценки
+* Интерфейс в стиле терминала (dark + green matrix)
+* Эффект печатающегося текста
+* Индикатор прогресса
+* Случайное перемешивание вариантов ответов
+* Автоматическая генерация изображений-заглушек
+
+##  Требования
+
+* Python 3.8+
+* Библиотеки: customtkinter, pillow
+
+Установка:
+pip install customtkinter pillow
+
+##  Запуск
+
+python main.py
+
+##  Структура проекта
+
+project/
+├── main.py
+├── images/
+
+##  Как играть
+
+1. Запустите приложение
+2. Пройдите авторизацию
+3. Читайте сценарии и выбирайте действия
+4. Ваши решения влияют на уровень безопасности
+5. В конце получите отчет
+
+##  Чему учит
+
+* Сетевая сегментация (VLAN, firewall)
+* Фишинг и социальная инженерия
+* Контроль физического доступа
+* Управление паролями
+* Безопасность цепочки поставок
+* Обнаружение аномалий
+* Удалённый доступ (VPN, 2FA)
+* Обновления систем
+* USB-угрозы
+* Реакция на вирусные атаки
+* Резервное копирование
+* Стандарты ISA/IEC 62443
+
+##  Система оценки
+
+80–100% — Завод в полной безопасности
+50–79% — Есть уязвимости
+0–49% — Критический взлом
+
+##  Настройки
+
+Можно изменить в коде:
+
+BG_COLOR = "#050505"
+MATRIX_GREEN = "#00FF41"
+FONT_FAMILY = "Consolas"
+
+##  Примечания
+
+* Папка images создается автоматически
+* Интернет не требуется
+
+##  Автор
+
+Учебный симулятор по кибербезопасности АСУ ТП

+ 18 - 0
changelog.md

@@ -0,0 +1,18 @@
+# Список изменений (Changelog) — «Терминал центра безопасности»
+
+### Контент
+* **Расширение сюжета:** Сюжетная линия увеличена до **12 шагов** (ранее количество этапов было меньше).
+* **Новые сценарии:** Добавлены актуальные кейсы по информационной безопасности АСУ ТП:
+    * Угрозы через USB-носители.
+    * Физический доступ к критическому оборудованию.
+    * Аномалии в показаниях датчиков.
+    * Атаки программ-шифровальщиков.
+    * Проверка знаний международных стандартов (в частности, **IEC 62443**).
+
+### Геймплей и логика
+* **Рандомизация ответов:** Интегрирован модуль `random`. Теперь варианты выбора перемешиваются при каждом запуске сцены, что исключает «механическое» запоминание позиций кнопок игроком.
+* **Система рейтинга:** Полностью переписан алгоритм расчета финального результата. Набранные баллы теперь динамически конвертируются в наглядную процентную шкалу (**0–100%**).
+
+### Интерфейс (UI)
+* **Индикатор прогресса:** Добавлен виджет `CTkProgressBar`. Игрок может в реальном времени отслеживать, какая часть смены уже пройдена.
+* **Визуальные правки:** Улучшена обратная связь в финальном отчете в зависимости от достигнутого процента защиты.

+ 341 - 0
game.py

@@ -0,0 +1,341 @@
+import customtkinter as ctk
+from PIL import Image, ImageDraw, ImageFont
+import os
+import random # // НОВОЕ: Импортируем модуль для генерации случайных чисел и перемешивания
+
+# Настройка темы в стиле «Терминал»
+ctk.set_appearance_mode("Dark")
+
+BG_COLOR = "#050505"
+FRAME_COLOR = "#0D0F12"
+TEXT_COLOR = "#E0E0E0"
+MATRIX_GREEN = "#00FF41"
+DARK_GREEN = "#008F11"
+HOVER_GREEN = "#00FF41"
+DANGER_RED = "#FF003C"
+FONT_FAMILY = "Consolas" # Хорошо подходит для русского терминала
+
+class SecurityQuizApp(ctk.CTk):
+    def __init__(self):
+        super().__init__()
+        self.title("ТЕРМИНАЛ ЦЕНТРА БЕЗОПАСНОСТИ v3.0")
+        self.geometry("1000x800")
+        self.configure(fg_color=BG_COLOR)
+        self.resizable(False, False)
+
+        self.score = 0
+        self.step_counter = 0  
+        self.total_steps = 12  # Увеличено количество шагов
+        self.type_id = None
+        
+        self.generate_default_images()
+        self.scenes = self.define_scenes()
+        self.show_boot_screen()
+
+    def generate_default_images(self):
+        """Создание простых заглушек для фото с русскими надписями"""
+        if not os.path.exists("images"):
+            os.makedirs("images")
+        
+        img_size = (250, 250)
+        bg_color = (13, 15, 18) 
+        accent = (0, 255, 65)  
+
+        try:
+            font = ImageFont.load_default()
+        except:
+            font = None
+
+        # Расширенный список изображений для новых сцен
+        configs = [
+            ("intro", "ЗАЩИТА"), ("network", "СЕТЬ"), ("phishing", "ФИШИНГ"),
+            ("physical", "ДОСТУП"), ("anomaly", "АНОМАЛИЯ"), ("malware", "ВИРУС"), 
+            ("remote", "УДАЛЕНКА"), ("backup", "БЭКАП"), ("usb", "USB-УГРОЗА"),
+            ("end", "ГОТОВО")
+        ]
+
+        for name, label in configs:
+            path = f"images/{name}.png"
+            if not os.path.exists(path):
+                img = Image.new('RGB', img_size, bg_color)
+                draw = ImageDraw.Draw(img)
+                draw.rectangle([10, 10, 240, 240], outline=accent, width=2)
+                draw.text((70, 110), label, fill=accent, font=font)
+                img.save(path)
+
+    def define_scenes(self):
+        scenes = {
+            "scene_1": {
+                "title": "ВВОДНЫЙ ИНСТРУКТАЖ",
+                "text": "Вы — старший инженер по кибербезопасности на химическом заводе. Ваша задача — обеспечить защиту систем управления (АСУ ТП). Начинается ваша смена.",
+                "image": "images/intro.png",
+                "actions": [{"text": ">>> АВТОРИЗОВАТЬСЯ И НАЧАТЬ РАБОТУ", "next": "scene_2"}],
+            },
+            "scene_2": {
+                "title": "СЕТЕВАЯ ТОПОЛОГИЯ",
+                "text": "Вы обнаружили, что контроллер (ПЛК), управляющий давлением в реакторе, находится в одной сети с офисными компьютерами бухгалтерии.",
+                "image": "images/network.png",
+                "actions": [
+                    {"text": ">>> НАСТРОИТЬ МЕЖСЕТЕВОЙ ЭКРАН (FIREWALL) МЕЖДУ СЕТЯМИ", "next": "scene_3", "points": 10},
+                    {"text": ">>> ИЗОЛИРОВАТЬ ПЛК В ОТДЕЛЬНУЮ ПОДСЕТЬ (VLAN)", "next": "scene_3", "points": 8},
+                    {"text": ">>> ОСТАВИТЬ КАК ЕСТЬ (НАСТРОИТЬ ТОЛЬКО ПАРОЛЬ)", "next": "scene_3", "points": 0},
+                    {"text": ">>> ФИЗИЧЕСКИ ОТКЛЮЧИТЬ ПЛК ОТ СЕТИ", "next": "scene_3", "points": -5} 
+                ],
+            },
+            "scene_3": {
+                "title": "СОЦИАЛЬНАЯ ИНЖЕНЕРИЯ",
+                "text": "Оператор получил письмо: 'Срочно! Обновите экран управления. Запустите файл во вложении'. Адрес отправителя кажется подозрительным.",
+                "image": "images/phishing.png",
+                "actions": [
+                    {"text": ">>> ПЕРЕДАТЬ ПИСЬМО В ОТДЕЛ БЕЗОПАСНОСТИ НА АНАЛИЗ", "next": "scene_4", "points": 10},
+                    {"text": ">>> ПРОСТО УДАЛИТЬ ПИСЬМО И ПРЕДУПРЕДИТЬ ОПЕРАТОРА", "next": "scene_4", "points": 5},
+                    {"text": ">>> ОТВЕТИТЬ ОТПРАВИТЕЛЮ И СПРОСИТЬ, КТО ОН", "next": "scene_4", "points": -5},
+                    {"text": ">>> РАЗРЕШИТЬ ЗАПУСК (ВДРУГ ЭТО ВАЖНО)", "next": "scene_4", "points": -15}
+                ],
+            },
+            "scene_4": {
+                "title": "ФИЗИЧЕСКИЙ ДОСТУП",
+                "text": "В цеху вы видите человека с ноутбуком, который подключился напрямую к серверу управления (СКАДА). На нем нет бейджа.",
+                "image": "images/physical.png",
+                "actions": [
+                    {"text": ">>> ПОТРЕБОВАТЬ ДОКУМЕНТЫ И НАРЯД-ДОПУСК НА РАБОТЫ", "next": "scene_5", "points": 10},
+                    {"text": ">>> НЕМЕДЛЕННО ВЫЗВАТЬ ОХРАНУ И ЗАБЛОКИРОВАТЬ ВЫХОД", "next": "scene_5", "points": 8},
+                    {"text": ">>> ВЫДЕРНУТЬ ЕГО КАБЕЛЬ ИЗ СЕРВЕРА", "next": "scene_5", "points": -5}, 
+                    {"text": ">>> ВЕЖЛИВО СПРОСИТЬ, ЧЕМ ОН ТУТ ЗАНИМАЕТСЯ", "next": "scene_5", "points": 0}
+                ],
+            },
+            "scene_5": {
+                "title": "БЕЗОПАСНОСТЬ ПАРОЛЕЙ",
+                "text": "Выяснилось, что на промышленных коммутаторах стоит заводской пароль 'admin:admin'. Оборудование работает непрерывно.",
+                "image": None,
+                "actions": [
+                    {"text": ">>> ЗАПЛАНИРОВАТЬ СМЕНУ ПАРОЛЕЙ В БЛИЖАЙШЕЕ ТЕХ. ОКНО", "next": "scene_6", "points": 10},
+                    {"text": ">>> ПРИНУДИТЕЛЬНО СМЕНИТЬ ВСЕ ПАРОЛИ ПРЯМО СЕЙЧАС", "next": "scene_6", "points": 5}, 
+                    {"text": ">>> НАСТРОИТЬ ФИЛЬТРАЦИЮ ПО IP ВМЕСТО СМЕНЫ ПАРОЛЯ", "next": "scene_6", "points": 2},
+                    {"text": ">>> ОСТАВИТЬ ДО СЛЕДУЮЩЕГО ГОДА", "next": "scene_6", "points": -10}
+                ],
+            },
+            "scene_6": {
+                "title": "ЦЕПОЧКА ПОСТАВОК",
+                "text": "Подрядчик прислал новую версию программы для контроллеров. Как вы поступите перед установкой?",
+                "image": None,
+                "actions": [
+                    {"text": ">>> ПРОВЕРИТЬ ЦИФРОВУЮ ПОДПИСЬ И ПРОТЕСТИРОВАТЬ НА СТЕНДЕ", "next": "scene_7", "points": 10},
+                    {"text": ">>> ПРОВЕРИТЬ ФАЙЛ АНТИВИРУСОМ НА СВОЕМ ПК", "next": "scene_7", "points": 5},
+                    {"text": ">>> СРАЗУ УСТАНОВИТЬ, ТАК КАК ПОДРЯДЧИК НАДЕЖНЫЙ", "next": "scene_7", "points": -10},
+                    {"text": ">>> ЗАПРОСИТЬ У НИХ ИСХОДНЫЙ КОД ДЛЯ ПРОВЕРКИ", "next": "scene_7", "points": 2}
+                ],
+            },
+            "scene_7": {
+                "title": "АНОМАЛИИ В ДАННЫХ",
+                "text": "На экране СКАДА температура скачет от -100 до +500 градусов за секунду. Датчики явно врут или система взломана.",
+                "image": "images/anomaly.png",
+                "actions": [
+                    {"text": ">>> СВЕРИТЬ ДАННЫЕ С РЕЗЕРВНЫМИ АНАЛОГОВЫМИ ПРИБОРАМИ", "next": "scene_8", "points": 10},
+                    {"text": ">>> ПЕРЕЙТИ НА РУЧНОЕ УПРАВЛЕНИЕ КЛАПАНАМИ", "next": "scene_8", "points": 5},
+                    {"text": ">>> ПЕРЕЗАГРУЗИТЬ СЕРВЕР СКАДА", "next": "scene_8", "points": -5},
+                    {"text": ">>> ОТКЛЮЧИТЬ ПИТАНИЕ РЕАКТОРА", "next": "scene_8", "points": -15}
+                ],
+            },
+            "scene_8": {
+                "title": "УДАЛЕННЫЙ ДОСТУП",
+                "text": "Производителю насоса нужен срочный удаленный доступ для диагностики вибраций оборудования.",
+                "image": "images/remote.png",
+                "actions": [
+                    {"text": ">>> ВЫДАТЬ ВРЕМЕННЫЙ VPN С 2FA ПОД КОНТРОЛЕМ ПЕРСОНАЛА", "next": "scene_9", "points": 10},
+                    {"text": ">>> ПОДНЯТЬ СЕАНС TEAMVIEWER НА СТАНЦИИ ОПЕРАТОРА", "next": "scene_9", "points": -10},
+                    {"text": ">>> ОТКРЫТЬ RDP ДОСТУП ИЗ ИНТЕРНЕТА НА ОДИН ЧАС", "next": "scene_9", "points": -15},
+                    {"text": ">>> ОТКАЗАТЬ В ДОСТУПЕ. ПУСТЬ ПРИЕЗЖАЮТ ЛИЧНО", "next": "scene_9", "points": 0}
+                ],
+            },
+            "scene_9": {
+                "title": "ОБНОВЛЕНИЕ СИСТЕМ",
+                "text": "Вышло критическое обновление ОС Windows для серверов управления. Уязвимость активно эксплуатируется.",
+                "image": None,
+                "actions": [
+                    {"text": ">>> УСТАНОВИТЬ В ТЕСТОВОЙ ЗОНЕ, ЗАТЕМ СОГЛАСОВАТЬ ПРОСТОЙ", "next": "scene_10", "points": 10},
+                    {"text": ">>> СОГЛАСОВАТЬ ПРОСТОЙ И НАКАТИТЬ ОБНОВЛЕНИЕ СРАЗУ", "next": "scene_10", "points": 5},
+                    {"text": ">>> ВКЛЮЧИТЬ АВТОМАТИЧЕСКОЕ ОБНОВЛЕНИЕ WINDOWS", "next": "scene_10", "points": -15},
+                    {"text": ">>> ИГНОРИРОВАТЬ. АСУ ТП ВООБЩЕ НЕЛЬЗЯ ОБНОВЛЯТЬ", "next": "scene_10", "points": -5}
+                ],
+            },
+            "scene_10": {
+                "title": "НАХОДКА В ЦЕХУ",
+                "text": "Оператор нашел красивую флешку (USB) возле проходной и хочет вставить ее в АРМ, чтобы найти владельца.",
+                "image": "images/usb.png",
+                "actions": [
+                    {"text": ">>> ИЗЪЯТЬ ФЛЕШКУ И ОТДАТЬ В ИНФОРМАЦИОННУЮ БЕЗОПАСНОСТЬ", "next": "scene_11", "points": 10},
+                    {"text": ">>> ЗАПРЕТИТЬ ВСТАВЛЯТЬ. СКАЗАТЬ, ЧТОБЫ ВЫКИНУЛ", "next": "scene_11", "points": 5},
+                    {"text": ">>> ВСТАВИТЬ В ОТДЕЛЬНЫЙ НОУТБУК БЕЗ СЕТИ ДЛЯ ПРОВЕРКИ", "next": "scene_11", "points": 8},
+                    {"text": ">>> РАЗРЕШИТЬ, НО ТОЛЬКО С ВКЛЮЧЕННЫМ АНТИВИРУСОМ", "next": "scene_11", "points": -15}
+                ],
+            },
+            "scene_11": {
+                "title": "ВИРУСНАЯ АТАКА",
+                "text": "Несмотря на защиту, антивирус на станции АРМ сообщает о заражении шифровальщиком. Файлы начали блокироваться.",
+                "image": "images/malware.png",
+                "actions": [
+                    {"text": ">>> ФИЗИЧЕСКИ ОТКЛЮЧИТЬ ПАТЧ-КОРД ОТ СЕТИ (ИЗОЛИРОВАТЬ)", "next": "scene_12", "points": 10},
+                    {"text": ">>> СРОЧНО ВЫКЛЮЧИТЬ ПИТАНИЕ СТАНЦИИ", "next": "scene_12", "points": 5}, 
+                    {"text": ">>> ЗАПУСТИТЬ ПОЛНОЕ СКАНИРОВАНИЕ АНТИВИРУСОМ", "next": "scene_12", "points": -10},
+                    {"text": ">>> ПОПЫТАТЬСЯ НАЙТИ ПРОЦЕСС В ДИСПЕТЧЕРЕ ЗАДАЧ", "next": "scene_12", "points": -5}
+                ],
+            },
+            "scene_12": {
+                "title": "РЕЗЕРВНОЕ КОПИРОВАНИЕ",
+                "text": "Шифровальщик уничтожил базу данных истории (Historian). Злоумышленники требуют выкуп.",
+                "image": "images/backup.png",
+                "actions": [
+                    {"text": ">>> ВОССТАНОВИТЬ ИЗ ОФФЛАЙН-БЭКАПА НА НОВОМ СЕРВЕРЕ", "next": "scene_13", "points": 10},
+                    {"text": ">>> ПЕРЕУСТАНОВИТЬ СИСТЕМУ С НУЛЯ (ПОТЕРЯТЬ ИСТОРИЮ)", "next": "scene_13", "points": 0},
+                    {"text": ">>> ПОПРОБОВАТЬ РАСШИФРОВАТЬ ФАЙЛЫ САМОСТОЯТЕЛЬНО", "next": "scene_13", "points": -5},
+                    {"text": ">>> ЗАПЛАТИТЬ ВЫКУП", "next": "scene_13", "points": -20}
+                ],
+            },
+            "scene_13": {
+                "title": "ФИНАЛЬНЫЙ ЭКЗАМЕН",
+                "text": "Какой международный стандарт является основным ориентиром для кибербезопасности АСУ ТП?",
+                "image": None,
+                "actions": [
+                    {"text": ">>> ISA/IEC 62443", "next": "end", "points": 10},
+                    {"text": ">>> ISO/IEC 27001", "next": "end", "points": 5}, 
+                    {"text": ">>> ГОСТ Р 50922-2006", "next": "end", "points": 0},
+                    {"text": ">>> PCI-DSS", "next": "end", "points": -5}
+                ],
+            },
+            "end": {
+                "title": "ИТОГИ СМЕНЫ",
+                "text": "Ваша смена в центре мониторинга безопасности завершена. Система проанализировала все принятые вами решения.",
+                "image": "images/end.png",
+                "actions": [{"text": ">>> СГЕНЕРИРОВАТЬ ОТЧЕТ И ВЫЙТИ", "next": None}],
+            }
+        }
+
+        # // НОВОЕ: Перемешиваем ответы для каждой сцены
+        for scene_key, scene_data in scenes.items():
+            if "actions" in scene_data:
+                random.shuffle(scene_data["actions"])
+                
+        return scenes
+
+    def show_boot_screen(self):
+        self.clear_frame()
+        boot_frame = ctk.CTkFrame(self, fg_color=BG_COLOR)
+        boot_frame.pack(fill="both", expand=True)
+        self.boot_label = ctk.CTkLabel(boot_frame, text="", font=(FONT_FAMILY, 16), text_color=MATRIX_GREEN, justify="left", anchor="sw")
+        self.boot_label.pack(side="bottom", fill="both", expand=True, padx=20, pady=20)
+        
+        lines = [
+            "ИНИЦИАЛИЗАЦИЯ СИСТЕМЫ...",
+            "ЗАГРУЗКА БАЗ ДАННЫХ И СЦЕНАРИЕВ...",
+            "ПОДКЛЮЧЕНИЕ К СЕТИ ЗАВОДА [ОК]",
+            "ПРОВЕРКА ДАННЫХ СТАРШЕГО ИНЖЕНЕРА...",
+            "РАСШИФРОВКА ПРОТОКОЛОВ...",
+            "ДОСТУП РАЗРЕШЕН."
+        ]
+        self.boot_seq(lines, 0)
+
+    def boot_seq(self, lines, idx):
+        if idx < len(lines):
+            current_text = self.boot_label.cget("text")
+            self.boot_label.configure(text=current_text + lines[idx] + "\n")
+            self.after(300, self.boot_seq, lines, idx+1)
+        else:
+            self.after(500, lambda: self.load_scene("scene_1"))
+
+    def clear_frame(self):
+        for widget in self.winfo_children():
+            widget.destroy()
+
+    def type_text_effect(self, text, index=0):
+        if index < len(text):
+            self.text_label.configure(text=text[:index+1] + "█")
+            self.type_id = self.after(15, self.type_text_effect, text, index+1)
+        else:
+            self.text_label.configure(text=text)
+
+    def load_scene(self, scene_id):
+        if self.type_id: self.after_cancel(self.type_id)
+        self.clear_frame()
+        
+        if scene_id not in self.scenes: return
+        scene = self.scenes[scene_id]
+        if scene_id != "end" and scene_id != "scene_1": 
+            self.step_counter += 1
+
+        # Индикатор прогресса
+        if scene_id not in ["end", "scene_1"]:
+            prog = ctk.CTkProgressBar(self, progress_color=MATRIX_GREEN, fg_color=FRAME_COLOR, height=10)
+            prog.pack(fill="x", padx=40, pady=(20, 0))
+            prog.set(self.step_counter / self.total_steps)
+
+        main_frame = ctk.CTkFrame(self, fg_color=FRAME_COLOR, border_width=1, border_color=MATRIX_GREEN)
+        main_frame.pack(pady=20, padx=40, fill="both", expand=True)
+
+        ctk.CTkLabel(main_frame, text=f"// {scene['title']}", font=(FONT_FAMILY, 28, "bold"), text_color=MATRIX_GREEN).pack(pady=20, padx=30, anchor="w")
+
+        content = ctk.CTkFrame(main_frame, fg_color="transparent")
+        content.pack(pady=10, padx=30, fill="both", expand=True)
+
+        self.text_label = ctk.CTkLabel(content, text="", font=(FONT_FAMILY, 18), text_color=TEXT_COLOR, wraplength=550, justify="left")
+        self.text_label.pack(side="left", fill="both", expand=True)
+        self.type_text_effect(scene["text"])
+
+        if scene.get("image") and os.path.exists(scene["image"]):
+            try:
+                img = ctk.CTkImage(Image.open(scene["image"]), size=(250, 250))
+                ctk.CTkLabel(content, image=img, text="").pack(side="right", padx=10)
+            except:
+                pass
+
+        actions_frame = ctk.CTkFrame(main_frame, fg_color="transparent")
+        actions_frame.pack(pady=20, padx=30, fill="x")
+
+        for a in scene["actions"]:
+            btn = ctk.CTkButton(actions_frame, text=a["text"], font=(FONT_FAMILY, 15), 
+                                fg_color=FRAME_COLOR, hover_color=HOVER_GREEN, 
+                                text_color=MATRIX_GREEN, border_width=1, border_color=MATRIX_GREEN,
+                                height=45, anchor="w", command=lambda x=a: self.make_choice(x))
+            btn.pack(fill="x", pady=5)
+
+        if scene_id not in ["end", "scene_1"]:
+            # Ограничиваем визуал очков процентами до 100
+            display_score = min(100, self.score)
+            ctk.CTkLabel(main_frame, text=f"ТЕКУЩАЯ ЗАЩИТА: {display_score}%", font=(FONT_FAMILY, 14), text_color=MATRIX_GREEN).pack(side="bottom", pady=10)
+
+    def make_choice(self, action):
+        # Добавляем очки, не позволяем упасть ниже 0. Максимум ограничен логически в UI, но копим реальные очки.
+        self.score = max(0, self.score + action.get("points", 0))
+        next_s = action.get("next")
+        if next_s:
+            self.load_scene(next_s)
+        else:
+            self.show_end_screen()
+
+    def show_end_screen(self):
+        self.clear_frame()
+        f = ctk.CTkFrame(self, fg_color=FRAME_COLOR, border_width=2, border_color=MATRIX_GREEN)
+        f.pack(pady=60, padx=60, fill="both", expand=True)
+        
+        ctk.CTkLabel(f, text="ОТЧЕТ ПО БЕЗОПАСНОСТИ", font=(FONT_FAMILY, 34, "bold"), text_color=MATRIX_GREEN).pack(pady=40)
+        
+        # Пересчет финального рейтинга (макс возможных баллов ~120, приводим к 100%)
+        final_percentage = min(100, int((self.score / 120) * 100))
+        
+        if final_percentage >= 80:
+            status = "ЗАВОД В ПОЛНОЙ БЕЗОПАСНОСТИ"
+            color = MATRIX_GREEN
+        elif final_percentage >= 50:
+            status = "ЕСТЬ УЯЗВИМОСТИ (ТРЕБУЕТСЯ АУДИТ)"
+            color = "orange"
+        else:
+            status = "КРИТИЧЕСКИЙ ВЗЛОМ СИСТЕМЫ"
+            color = DANGER_RED
+            
+        ctk.CTkLabel(f, text=f"СТАТУС: {status}", font=(FONT_FAMILY, 24), text_color=TEXT_COLOR).pack(pady=10)
+        ctk.CTkLabel(f, text=f"РЕЙТИНГ ЭФФЕКТИВНОСТИ: {final_percentage}%", font=(FONT_FAMILY, 30, "bold"), text_color=color).pack(pady=20)
+        
+        ctk.CTkButton(f, text=">>> ВЫХОД", fg_color=BG_COLOR, border_width=1, border_color=DANGER_RED, text_color=DANGER_RED, command=self.destroy).pack(pady=40)
+
+if __name__ == "__main__":
+    SecurityQuizApp().mainloop()

+ 14 - 0
issues.md

@@ -0,0 +1,14 @@
+# 📋 Список задач
+
+##  Ошибки (Bugs)
+-  **Текст на картинках:** На некоторых ПК вместо русских букв на картинках-заглушках рисуются квадраты.
+-  **Прогресс-бар:** Если изменить количество вопросов, полоска прогресса начнет врать (нужен авторасчет).
+-  **Мерцание текста:** Если очень быстро кликать на кнопки, текст новой сцены накладывается на старую.
+
+##  Что добавить (Features)
+-  **Сюжет в файле:** Вынести все тексты в отдельный `.json`, чтобы не загромождать код.
+-  **Система проигрыша:** Добавить «шкалу тревоги». Если делать много ошибок — игра заканчивается аварией.
+-  **Звуки:** Добавить звук печати текста, сирену при взломе и клики кнопок.
+-  **Подсказки:** Добавить кнопку «Совет эксперта», которая помогает выбрать, но отнимает баллы.
+-  **Красивый финал:** В конце показывать не только проценты, но и расшифровку: в чем игрок хорош, а где ему надо подучить теорию.
+

+ 85 - 0
shenarui_moroz_eroshkk0_G0rbb.md

@@ -0,0 +1,85 @@
+# Сценарий игры: «Терминал центра безопасности»
+
+**Жанр:** Интерактивный симулятор ИБ-инженера КИИ (Критическая информационная инфраструктура).  
+**Сеттинг:** Ночная смена на химическом предприятии.  
+**Роль игрока:** Старший инженер по кибербезопасности АСУ ТП.
+
+---
+
+##  Основные механики 
+1.  **Динамический прогресс:** Визуальная шкала `CTkProgressBar` отображает текущий этап смены.
+2.  **Рандомизация:** Порядок ответов в каждой сцене перемешивается модулем `random`, исключая заучивание.
+3.  **Система оценки:** Баллы конвертируются в итоговый рейтинг защиты (0–100%).
+4.  **Визуальный стиль:** Интерфейс «Терминал» с эффектом постепенного вывода текста.
+
+---
+
+##  Описание сцен
+
+### 1. Вводный инструктаж
+* **Сюжет:** Вы проходите авторизацию в системе мониторинга. Ваша задача — обеспечить непрерывность техпроцессов и защиту от киберугроз.
+* **Действие:** Начать работу.
+* **Цель:** Инициализация интерфейса.
+
+### 2. Сетевая топология
+* **Сюжет:** ПЛК (промышленный контроллер) реактора находится в одной сети с офисными ПК бухгалтерии.
+* **Вызовы:** Риск проникновения вируса из офиса в промышленный контур.
+* **Варианты:** Межсетевой экран (Firewall), VLAN, смена пароля или отключение.
+
+### 3. Социальная инженерия (Фишинг)
+* **Сюжет:** Оператор получил письмо об «обновлении драйверов» с подозрительного адреса.
+* **Вызовы:** Опознать фишинг по косвенным признакам (срочность, ошибки).
+* **Варианты:** Анализ в ИБ, удаление, ответ отправителю или запуск.
+
+### 4. Физический доступ
+* **Сюжет:** В цеху обнаружен человек с ноутбуком, подключенным напрямую к серверу SCADA.
+* **Вызовы:** Защита периметра и регламент допуска.
+* **Варианты:** Проверка документов, вызов охраны, физическое вмешательство.
+
+### 5. Безопасность паролей
+* **Сюжет:** Новые датчики работают на заводских паролях `admin:admin`.
+* **Вызовы:** Выбор между немедленной сменой (риск сбоя) и плановым тех. окном.
+
+### 6. Цепочка поставок (Supply Chain)
+* **Сюжет:** Получено обновление ПО от подрядчика.
+* **Вызовы:** Проверка подлинности софта до установки на живую систему.
+* **Варианты:** Проверка цифровой подписи, антивирус, анализ исходного кода.
+
+### 7. Аномалии в данных
+* **Сюжет:** Показания температуры на экране скачут от -100 до +500 градусов.
+* **Вызовы:** Отличие кибератаки от поломки железа.
+* **Варианты:** Сверка с аналоговыми приборами, ручной режим, перезагрузка.
+
+### 8. Удаленный доступ
+* **Сюжет:** Вендору оборудования нужен доступ для диагностики вибраций.
+* **Вызовы:** Безопасное предоставление доступа извне.
+* **Варианты:** VPN с 2FA, TeamViewer, RDP или личный визит.
+
+### 9. Управление патчами (Patch Management)
+* **Сюжет:** Найдена уязвимость в ОС Windows, на которой работают серверы управления.
+* **Вызовы:** Дилемма — защитить систему или избежать остановки завода.
+
+### 10. USB-угроза (Найденная флешка)
+* **Сюжет:** Оператор нашел «красивую флешку» и хочет проверить её содержимое.
+* **Вызовы:** Предотвращение заражения через внешние носители.
+
+### 11. Вирусная атака (Шифровальщик)
+* **Сюжет:** Станция заражена, файлы начинают блокироваться.
+* **Вызовы:** Быстрая локализация очага заражения.
+* **Варианты:** Изоляция кабеля, отключение питания, сканирование.
+
+### 12. Резервное копирование
+* **Сюжет:** История данных (Historian) стерта. Хакеры требуют выкуп.
+* **Вызовы:** Наличие и целостность оффлайн-бэкапов.
+
+### 13. Финальный экзамен (Теория)
+* **Сюжет:** Контрольный вопрос по стандартам безопасности АСУ ТП.
+* **Ключевой стандарт:** ISA/IEC 62443.
+
+---
+
+##  Финал: Отчет по безопасности
+Формируется на основе процента эффективности:
+* **80–100%:** Безупречная работа. Завод в безопасности.
+* **50–79%:** Удовлетворительно. Требуется внеплановый аудит.
+* **Менее 50%:** Катастрофа. Система взломана, производство остановлено.