import tkinter as tk from tkinter import messagebox import random import os from pathlib import Path from typing import Dict, List, Optional, Tuple # Попытка импорта PIL для работы с изображениями try: from PIL import Image PIL_AVAILABLE = True except ImportError: PIL_AVAILABLE = False print("PIL (Pillow) не установлен. Установите: pip install Pillow") # ==================== КОНСТАНТЫ ==================== WINDOW_SIZE = "1000x700" IMAGES_FOLDER = "quiz_images" SUPPORTED_FORMATS = ('.jpg', '.jpeg', '.png', '.bmp', '.ico', '.tiff') DEFAULT_COLORS = [ '#f0f8ff', '#f5f5dc', '#e6e6fa', '#ffe4e1', '#f0fff0', '#fff0f5', '#e0ffff', '#faf0e6', '#fffacd', '#f8f8ff' ] # ==================== СПИСОК ВОПРОСОВ ==================== questions = [ { 'id': 1, 'question': 'Что понимается под "персональными данными" согласно законодательству?', 'options': [ 'Только паспортные данные и фотографии', 'Любая информация, прямо или косвенно относящаяся к конкретному физическому лицу', 'Только контактные данные человека', 'Информация о юридических лицах' ], 'correct': 1, 'explanation': 'Персональные данные - любые сведения, которые относятся, непосредственно либо косвенно, к конкретному физическому лицу.', 'image': 'q1.jpg', 'bg_color': None }, { 'id': 2, 'question': 'Кто такой "оператор" персональных данных?', 'options': [ 'Человек, чьи данные обрабатываются', 'Государственный орган, контролирующий защиту данных', 'Лицо или орган, осуществляющий обработку ПД и определяющий цели обработки', 'Сотрудник, работающий с базами данных' ], 'correct': 2, 'explanation': 'Оператор - лицо или орган, осуществляющий обработку персональных данных и определяющий цели обработки, состав данных и действия с ними.', 'image': 'q2.jpg', 'bg_color': None }, { 'id': 3, 'question': 'Что включает в себя система защиты персональных данных?', 'options': [ 'Только антивирусное программное обеспечение', 'Только организационные меры', 'Организационные и технические меры, определенные с учетом актуальных угроз', 'Только физическую охрану помещений' ], 'correct': 2, 'explanation': 'Система защиты включает организационные и технические меры, определенные с учетом актуальных угроз безопасности.', 'image': 'q3.jpg', 'bg_color': None }, { 'id': 5, 'question': 'Сколько уровней защищенности персональных данных устанавливается при их обработке в информационных системах?', 'options': [ '2 уровня', '3 уровня', '4 уровня', '5 уровней' ], 'correct': 2, 'explanation': 'При обработке персональных данных в информационных системах устанавливаются 4 уровня защищенности.', 'image': 'q5.jpg', 'bg_color': None }, { 'id': 6, 'question': 'Какие данные относятся к специальным категориям персональных данных?', 'options': [ 'ФИО и адрес проживания', 'Информация о расовой принадлежности, политических взглядах, здоровье', 'Номер телефона и email', 'Место работы и должность' ], 'correct': 1, 'explanation': 'К специальным категориям относятся данные о расовой принадлежности, политических взглядах, религиозных убеждениях, здоровье.', 'image': 'q6.jpg', 'bg_color': None }, { 'id': 7, 'question': 'Что относится к биометрическим персональным данным?', 'options': [ 'ФИО и дата рождения', 'Физиологические особенности для идентификации: отпечатки пальцев, ДНК, группа крови', 'Номер паспорта', 'Адрес проживания' ], 'correct': 1, 'explanation': 'Биометрические данные - физиологические особенности, используемые для идентификации личности.', 'image': 'q7.jpg', 'bg_color': None }, { 'id': 8, 'question': 'Как часто должен проводиться контроль за выполнением требований защиты ПД?', 'options': [ 'Ежемесячно', 'Ежегодно', 'Не реже 1 раза в 3 года', 'Один раз в 5 лет' ], 'correct': 2, 'explanation': 'Контроль проводится не реже 1 раза в 3 года в сроки, определяемые оператором.', 'image': 'q8.jpg', 'bg_color': None }, { 'id': 9, 'question': 'Какой федеральный закон регулирует защиту персональных данных в России?', 'options': [ 'ФЗ "Об информации"', 'Федеральный закон №152-ФЗ "О персональных данных"', 'ФЗ "О связи"', 'ФЗ "О безопасности"' ], 'correct': 1, 'explanation': 'В России требования к обработке и защите персональных данных определяет Федеральный закон №152-ФЗ "О персональных данных".', 'image': 'q9.jpg', 'bg_color': None }, { 'id': 10, 'question': 'Что такое GDPR?', 'options': [ 'Российский закон о данных', 'Американский стандарт безопасности', 'Европейский регламент по защите данных', 'Международный стандарт шифрования' ], 'correct': 2, 'explanation': 'GDPR - Общий регламент по защите данных, служит правилом для защиты личных данных граждан ЕС.', 'image': 'q10.jpg', 'bg_color': None }, { 'id': 11, 'question': 'Какие требования предъявляются к сбору персональных данных?', 'options': [ 'Сбор только с согласия субъекта и ограничение только необходимыми данными', 'Сбор любой доступной информации', 'Сбор без согласия, если это удобно', 'Только сбор через официальные запросы' ], 'correct': 0, 'explanation': 'Персональные данные могут собираться только при согласии субъекта и ограничиваться только необходимыми данными.', 'image': 'q11.jpg', 'bg_color': None }, { 'id': 12, 'question': 'Что означает требование "целостность и конфиденциальность" ПД?', 'options': [ 'Данные должны быть красиво оформлены', 'Защита от несанкционированного доступа, потери и уничтожения', 'Данные должны быть доступны всем', 'Данные должны храниться вечно' ], 'correct': 1, 'explanation': 'Данные должны защищаться от несанкционированного доступа, потери, повреждения или уничтожения.', 'image': 'q12.jpg', 'bg_color': None }, { 'id': 13, 'question': 'Что означает "ограничение сроков хранения" персональных данных?', 'options': [ 'Хранить данные бесконечно', 'Хранить только необходимое для целей обработки время', 'Удалять сразу после получения', 'Хранить минимум 10 лет' ], 'correct': 1, 'explanation': 'Хранить персональные сведения можно только столько, сколько необходимо для достижения целей их обработки.', 'image': 'q13.jpg', 'bg_color': None }, { 'id': 14, 'question': 'В каких сферах применяются персональные данные?', 'options': [ 'Только в медицине', 'Только в банковской сфере', 'Практически во всех сферах: коммерция, финансы, медицина, образование, госорганы', 'Только в интернете' ], 'correct': 2, 'explanation': 'Персональные данные используются практически в любой сфере современного цифрового мира.', 'image': 'q14.jpg', 'bg_color': None }, { 'id': 15, 'question': 'Какие данные относятся к "общим" согласно 152-ФЗ?', 'options': [ 'ФИО, место регистрации, образование, контактная информация', 'Данные о здоровье', 'Отпечатки пальцев', 'Политические взгляды' ], 'correct': 0, 'explanation': 'Общие данные: ФИО, место регистрации, сведения об образовании, месте работы, контактная информация.', 'image': 'q15.jpg', 'bg_color': None }, { 'id': 16, 'question': 'Что должно быть предусмотрено в договоре между оператором и уполномоченным лицом?', 'options': [ 'Стоимость услуг', 'Обязанность обеспечить безопасность ПД при обработке', 'Срок действия договора', 'Реквизиты сторон' ], 'correct': 1, 'explanation': 'Договор должен предусматривать обязанность уполномоченного лица обеспечить безопасность ПД при их обработке.', 'image': 'q16.jpg', 'bg_color': None }, { 'id': 17, 'question': 'Кто обеспечивает безопасность персональных данных в информационной системе?', 'options': [ 'ФСБ России', 'Оператор системы или уполномоченное им лицо', 'Роскомнадзор', 'Сам субъект данных' ], 'correct': 1, 'explanation': 'Безопасность обеспечивает оператор системы или лицо, осуществляющее обработку по поручению оператора.', 'image': 'q17.jpg', 'bg_color': None }, { 'id': 18, 'question': 'Какие угрозы относятся к 1-му типу?', 'options': [ 'Только внешние атаки', 'Угрозы, связанные с недокументированными возможностями в системном ПО', 'Только физические угрозы', 'Угрозы от сотрудников' ], 'correct': 1, 'explanation': 'Угрозы 1-го типа связаны с наличием недокументированных возможностей в системном программном обеспечении.', 'image': 'q18.jpg', 'bg_color': None }, { 'id': 19, 'question': 'Какие угрозы относятся ко 2-му типу?', 'options': [ 'Угрозы от хакеров', 'Угрозы, связанные с недокументированными возможностями в прикладном ПО', 'Угрозы пожаров и наводнений', 'Угрозы отключения электричества' ], 'correct': 1, 'explanation': 'Угрозы 2-го типа связаны с наличием недокументированных возможностей в прикладном программном обеспечении.', 'image': 'q19.jpg', 'bg_color': None }, { 'id': 20, 'question': 'Какие угрозы относятся к 3-му типу?', 'options': [ 'Угрозы из интернета', 'Угрозы, не связанные с недокументированными возможностями в системном и прикладном ПО', 'Только физические угрозы', 'Угрозы от вирусов' ], 'correct': 1, 'explanation': 'Угрозы 3-го типа - угрозы, не связанные с наличием недокументированных возможностей в ПО.', 'image': 'q20.jpg', 'bg_color': None }, { 'id': 21, 'question': 'Когда устанавливается необходимость 1-го уровня защищенности?', 'options': [ 'Всегда для всех систем', 'При угрозах 1 типа и обработке специальных или биометрических данных', 'Только для государственных систем', 'При обработке менее 100 записей' ], 'correct': 1, 'explanation': '1-й уровень необходим при угрозах 1 типа и обработке специальных категорий или биометрических данных.', 'image': 'q21.jpg', 'bg_color': None }, { 'id': 22, 'question': 'Какие требования предъявляются для 4-го уровня защищенности?', 'options': [ 'Создание специального отдела', 'Организация безопасности помещений, сохранность носителей, утверждение списка допущенных лиц', 'Шифрование всех данных', 'Ежедневный аудит' ], 'correct': 1, 'explanation': '4-й уровень требует безопасности помещений, сохранности носителей и утверждения списка допущенных лиц.', 'image': 'q22.jpg', 'bg_color': None }, { 'id': 23, 'question': 'Что дополнительно требуется для 3-го уровня защищенности?', 'options': [ 'Создание отдела защиты данных', 'Назначение ответственного за безопасность ПД в ИС', 'Автоматическая регистрация изменений', 'Шифрование каналов связи' ], 'correct': 1, 'explanation': 'Для 3-го уровня необходимо назначение должностного лица, ответственного за безопасность ПД в ИС.', 'image': 'q23.jpg', 'bg_color': None }, { 'id': 24, 'question': 'Что дополнительно требуется для 2-го уровня защищенности?', 'options': [ 'Видеонаблюдение', 'Ограничение доступа к электронному журналу сообщений', 'Биометрическая идентификация', 'Еженедельные проверки' ], 'correct': 1, 'explanation': 'Для 2-го уровня доступ к электронному журналу должен быть только для лиц, которым это необходимо.', 'image': 'q24.jpg', 'bg_color': None }, { 'id': 25, 'question': 'Что дополнительно требуется для 1-го уровня защищенности?', 'options': [ 'Автоматическая регистрация изменений полномочий и создание структурного подразделения', 'Ежечасный мониторинг', 'Вооруженная охрана', 'Спутниковое наблюдение' ], 'correct': 0, 'explanation': '1-й уровень требует автоматической регистрации изменений полномочий и создания структурного подразделения.', 'image': 'q25.jpg', 'bg_color': None }, { 'id': 26, 'question': 'Что должно содержать уведомление в Роскомнадзор о начале обработки ПД?', 'options': [ 'Только название организации', 'Информацию о целях обработки, категориях данных и мерах защиты', 'Только контактные данные', 'Только ФИО директора' ], 'correct': 1, 'explanation': 'Уведомление должно содержать информацию о целях, категориях данных и мерах защиты.', 'image': 'q26.jpg', 'bg_color': None }, { 'id': 27, 'question': 'Что такое трансграничная передача данных?', 'options': [ 'Передача внутри организации', 'Передача данных за пределы государства', 'Передача по локальной сети', 'Передача на флешке' ], 'correct': 1, 'explanation': 'Трансграничная передача - это передача персональных данных на территорию иностранного государства.', 'image': 'q27.jpg', 'bg_color': None }, { 'id': 28, 'question': 'Кто должен давать согласие на обработку данных детей?', 'options': [ 'Сам ребенок', 'Учитель', 'Законные представители (родители)', 'Директор школы' ], 'correct': 2, 'explanation': 'Для обработки данных детей требуется согласие законных представителей.', 'image': 'q28.jpg', 'bg_color': None }, { 'id': 29, 'question': 'Что должно быть сделано при утечке персональных данных?', 'options': [ 'Ничего, это не обязательно', 'Уведомление владельцев данных и контролирующих органов', 'Только смена паролей', 'Увольнение сотрудников' ], 'correct': 1, 'explanation': 'В случае утечки владельцы данных должны быть своевременно уведомлены.', 'image': 'q29.jpg', 'bg_color': None }, { 'id': 30, 'question': 'Что такое обезличивание персональных данных?', 'options': [ 'Удаление фотографий', 'Действия, после которых нельзя определить принадлежность данных конкретному лицу', 'Шифрование данных', 'Смена имен' ], 'correct': 1, 'explanation': 'Обезличивание делает невозможным идентификацию человека без использования дополнительной информации.', 'image': 'q30.jpg', 'bg_color': None }, { 'id': 31, 'question': 'Кто осуществляет выбор средств защиты информации для системы защиты ПД?', 'options': [ 'Роскомнадзор', 'Оператор в соответствии с нормативными актами ФСБ и ФСТЭК', 'Субъект данных', 'Поставщик оборудования' ], 'correct': 1, 'explanation': 'Выбор средств защиты осуществляется оператором в соответствии с актами ФСБ и ФСТЭК.', 'image': 'q31.jpg', 'bg_color': None }, { 'id': 32, 'question': 'Какие организации могут привлекаться для контроля защиты ПД?', 'options': [ 'Любые организации', 'Юридические лица и ИП с лицензией на техзащиту информации', 'Только государственные органы', 'Только ФСБ' ], 'correct': 1, 'explanation': 'Могут привлекаться организации и ИП, имеющие лицензию на техзащиту конфиденциальной информации.', 'image': 'q32.jpg', 'bg_color': None }, { 'id': 33, 'question': 'Что относится к организационным мерам защиты ПД?', 'options': [ 'Установка файерволов', 'Назначение ответственных лиц, разработка документов, инструктаж сотрудников', 'Шифрование дисков', 'Антивирусная защита' ], 'correct': 1, 'explanation': 'Организационные меры включают назначение ответственных, разработку документов и обучение сотрудников.', 'image': 'q33.jpg', 'bg_color': None }, { 'id': 34, 'question': 'Что относится к техническим мерам защиты ПД?', 'options': [ 'Проведение инструктажей', 'Антивирусы, межсетевые экраны, системы шифрования', 'Разработка политик безопасности', 'Подписание согласий' ], 'correct': 1, 'explanation': 'Технические меры включают программно-аппаратные средства защиты информации.', 'image': 'q34.jpg', 'bg_color': None }, { 'id': 35, 'question': 'Какие данные являются общедоступными?', 'options': [ 'Все данные в интернете', 'Данные, доступ к которым предоставлен самим субъектом', 'Паспортные данные', 'Медицинские записи' ], 'correct': 1, 'explanation': 'Общедоступные данные - это данные, доступ к которым предоставлен самим субъектом.', 'image': 'q35.jpg', 'bg_color': None }, { 'id': 36, 'question': 'Что такое инцидент с персональными данными?', 'options': [ 'Плановое обновление ПО', 'Нарушение безопасности, приводящее к утечке или компрометации данных', 'Обычная проверка системы', 'Резервное копирование' ], 'correct': 1, 'explanation': 'Инцидент - это любое событие, которое привело к нарушению защиты данных.', 'image': 'q36.jpg', 'bg_color': None }, { 'id': 37, 'question': 'Какие требования предъявляются к точности и актуальности данных?', 'options': [ 'Данные можно не обновлять', 'Важно обеспечивать правильность и своевременность данных', 'Точность не важна', 'Достаточно собрать данные один раз' ], 'correct': 1, 'explanation': 'Важно обеспечивать правильность и своевременность данных.', 'image': 'q37.jpg', 'bg_color': None }, { 'id': 38, 'question': 'Что означает требование "законность, справедливость и прозрачность"?', 'options': [ 'Данные должны быть доступны всем', 'Обработка должна вестись легально, честно и ясно для граждан', 'Данные должны публиковаться', 'Обработка может быть скрытой' ], 'correct': 1, 'explanation': 'Обработка должна вестись легально, честно и ясно для граждан, чьи данные собираются.', 'image': 'q38.jpg', 'bg_color': None }, { 'id': 39, 'question': 'Что такое электронный журнал безопасности?', 'options': [ 'Список сотрудников', 'Средство регистрации событий доступа и изменений в системе', 'Журнал учета посетителей', 'База данных клиентов' ], 'correct': 1, 'explanation': 'Электронный журнал регистрирует события доступа и изменения в системе.', 'image': 'q39.jpg', 'bg_color': None }, { 'id': 40, 'question': 'К какому типу данных относится данные о заработной плате?', 'options': [ 'Не являются персональными данными', 'Являются персональными данными и не подлежат разглашению без согласия', 'Могут публиковаться свободно', 'Относятся к общедоступным' ], 'correct': 1, 'explanation': 'Сведения о заработной плате - это персональные данные и не подлежат разглашению без согласия.', 'image': 'q40.jpg', 'bg_color': None }, { 'id': 41, 'question': 'Что должен сделать оператор перед началом обработки ПД?', 'options': [ 'Ничего', 'Подать уведомление в Роскомнадзор', 'Купить оборудование', 'Нанять сотрудников' ], 'correct': 1, 'explanation': 'Необходимо подать уведомление о начале обработки ПД в Роскомнадзор для регистрации в качестве оператора.', 'image': 'q41.jpg', 'bg_color': None }, { 'id': 42, 'question': 'Что такое модель угроз для ИСПД?', 'options': [ 'Описание возможных злоумышленников', 'Документ, описывающий актуальные угрозы безопасности для конкретной системы', 'Программа для взлома', 'Схема сети' ], 'correct': 1, 'explanation': 'Модель угроз - документ, описывающий актуальные угрозы для конкретной информационной системы.', 'image': 'q42.jpg', 'bg_color': None }, { 'id': 43, 'question': 'Какие существуют категории персональных данных по 152-ФЗ?', 'options': [ 'Только общие и специальные', 'Общие, особые, биометрические и иные данные', 'Только биометрические', 'Только контактные' ], 'correct': 1, 'explanation': 'Закон выделяет общие, особые, биометрические и иные категории персональных данных.', 'image': 'q43.jpg', 'bg_color': None }, { 'id': 44, 'question': 'Что должно содержать согласие на обработку ПД?', 'options': [ 'Только подпись', 'Конкретные цели обработки, перечень данных, срок действия', 'Только ФИО', 'Только дату' ], 'correct': 1, 'explanation': 'Согласие должно быть конкретным, содержать цели обработки, перечень данных и срок действия.', 'image': 'q44.jpg', 'bg_color': None }, { 'id': 45, 'question': 'Кто такой уполномоченный по защите данных?', 'options': [ 'Сотрудник полиции', 'Лицо, ответственное за контроль соблюдения законодательства о данных', 'Программист', 'Начальник отдела кадров' ], 'correct': 1, 'explanation': 'Это лицо, ответственное за контроль соблюдения законодательства о персональных данных.', 'image': 'q45.jpg', 'bg_color': None }, { 'id': 46, 'question': 'Что такое техническое задание на создание системы защиты?', 'options': [ 'Рекламный буклет', 'Документ с требованиями к создаваемой системе защиты ПД', 'Инструкция пользователя', 'Договор с поставщиком' ], 'correct': 1, 'explanation': 'ТЗ - документ с требованиями по формированию требуемой системы защиты.', 'image': 'q46.jpg', 'bg_color': None }, { 'id': 47, 'question': 'Какие данные относятся к "иным" согласно 152-ФЗ?', 'options': [ 'Данные о здоровье', 'Все, что не попадает в общие, особые и биометрические категории', 'Только фотографии', 'Только отпечатки пальцев' ], 'correct': 1, 'explanation': 'Иные данные - всё, что не попадает в предыдущие категории.', 'image': 'q47.jpg', 'bg_color': None }, { 'id': 48, 'question': 'Что такое ФСТЭК России?', 'options': [ 'Производитель компьютеров', 'Федеральная служба по техническому и экспортному контролю', 'Провайдер интернета', 'Страховая компания' ], 'correct': 1, 'explanation': 'ФСТЭК - федеральная служба по техническому и экспортному контролю, регулирующая вопросы защиты информации.', 'image': 'q48.jpg', 'bg_color': None }, { 'id': 49, 'question': 'Какие требования предъявляются к помещениям, где обрабатываются ПД?', 'options': [ 'Наличие кондиционера', 'Режим, препятствующий неконтролируемому проникновению посторонних', 'Наличие коврового покрытия', 'Яркое освещение' ], 'correct': 1, 'explanation': 'Необходима организация режима, препятствующего неконтролируемому проникновению посторонних.', 'image': 'q49.jpg', 'bg_color': None }, { 'id': 50, 'question': 'Какова основная цель выполнения требований по защите персональных данных?', 'options': [ 'Выполнение формальностей', 'Минимизация вреда от возможной реализации угроз безопасности', 'Экономия средств', 'Повышение престижа компании' ], 'correct': 1, 'explanation': 'Основная цель - минимизация вреда, возникающего из-за возможной реализации угроз безопасности.', 'image': 'q50.jpg', 'bg_color': None } ] # Остальные вопросы аналогичны, для краткости сократил до 10 class ImageManager: """Класс для управления изображениями""" def __init__(self, images_folder: str): self.images_folder = Path(images_folder) self.cache = {} # Кэш для изображений self.pil_available = PIL_AVAILABLE def find_image(self, image_name: str) -> Optional[Path]: """Поиск изображения по имени с разными расширениями""" if not image_name: return None # Проверка кэша if image_name in self.cache: return self.cache[image_name] # Поиск файла image_path = self.images_folder / image_name if image_path.exists(): self.cache[image_name] = image_path return image_path # Поиск с разными расширениями name_without_ext = image_path.stem for ext in SUPPORTED_FORMATS: test_path = self.images_folder / f"{name_without_ext}{ext}" if test_path.exists(): self.cache[image_name] = test_path return test_path return None def load_image(self, image_path: Path, bg_color: str = '#ffffff', size: Tuple[int, int] = (350, 350)): """Загрузка и обработка изображения""" if not self.pil_available: if image_path.suffix.lower() == '.jpg': return tk.PhotoImage(file=str(image_path)) raise Exception("Для работы с изображениями требуется Pillow") img = Image.open(image_path) # Обработка анимированных GIF if getattr(img, 'is_animated', False): img.seek(0) # Обработка прозрачности if img.mode == 'RGBA': bg_rgb = tuple(int(bg_color[i:i+2], 16) for i in (1, 3, 5)) background = Image.new('RGB', img.size, bg_rgb) background.paste(img, mask=img.split()[3]) img = background elif img.mode != 'RGB': img = img.convert('RGB') # Изменение размера img.thumbnail(size, Image.Resampling.LANCZOS) return ImageTk.PhotoImage(img) def get_dominant_color(self, image_path: Path) -> Optional[str]: """Получение доминирующего цвета изображения""" if not self.pil_available: return None try: img = Image.open(image_path) if getattr(img, 'is_animated', False): img.seek(0) # Конвертация в RGB if img.mode == 'RGBA': background = Image.new('RGB', img.size, (255, 255, 255)) background.paste(img, mask=img.split()[3]) img = background elif img.mode != 'RGB': img = img.convert('RGB') # Уменьшение для ускорения img.thumbnail((100, 100)) pixels = img.getdata() # Вычисление среднего цвета r_sum = g_sum = b_sum = 0 for pixel in pixels: r_sum += pixel[0] g_sum += pixel[1] b_sum += pixel[2] count = len(pixels) if count == 0: return None return f'#{r_sum//count:02x}{g_sum//count:02x}{b_sum//count:02x}' except Exception as e: print(f"Ошибка определения цвета: {e}") return None class QuizApp: """Основной класс приложения""" def __init__(self, master): self.master = master self.master.title("Викторина: Защита персональных данных") self.master.geometry(WINDOW_SIZE) self.master.resizable(False, False) # Инициализация self.questions = questions.copy() self.current_question = 0 self.correct_answers = 0 self.total_questions = len(self.questions) # Менеджеры self.image_manager = ImageManager(IMAGES_FOLDER) # Переменные состояния self.var = None self.option_mapping = {} self.current_image = None # Привязка клавиш self._bind_keys() # Запуск self.show_start_screen() def _bind_keys(self): """Привязка клавиатурных комбинаций""" self.master.bind('', self.on_key_press) self.master.bind('', lambda e: self.check_answer()) self.master.bind('', lambda e: self.quit_app()) def on_key_press(self, event): """Обработка нажатий клавиш""" if event.char in '1234' and self.var is not None: self.var.set(int(event.char) - 1) def quit_app(self): """Выход из приложения""" if messagebox.askyesno("Выход", "Вы действительно хотите выйти?"): self.master.quit() def clear_window(self): """Очистка окна""" for widget in self.master.winfo_children(): widget.destroy() def shuffle_questions(self): """Перемешивание вопросов""" random.shuffle(self.questions) def get_text_color(self, bg_hex: str) -> str: """Определение цвета текста на основе фона""" if not bg_hex: return 'black' try: r = int(bg_hex[1:3], 16) g = int(bg_hex[3:5], 16) b = int(bg_hex[5:7], 16) brightness = 0.299 * r + 0.587 * g + 0.114 * b return 'black' if brightness > 128 else 'white' except: return 'black' def show_start_screen(self): """Экран приветствия""" self.clear_window() self.master.config(bg='lightblue') # Заголовок title = tk.Label(self.master, text="Викторина по защите персональных данных", font=("Arial", 20, "bold"), bg='lightblue', fg='navy') title.pack(pady=40) # Инструкция instr_text = ( f"Добро пожаловать!\n\n" f"Эта викторина проверит ваши знания об основах защиты персональных данных.\n" f"Правила:\n" f"• Всего вопросов: {self.total_questions}\n" f"• Выберите один правильный вариант.\n" f"• Управление с клавиатуры: клавиши 1-4 для выбора ответа\n" f"• Нажмите Enter для проверки ответа\n" f"• Esc для выхода\n" f"• После ответа вы увидите пояснение.\n\n" f"Удачи!" ) instr_label = tk.Label(self.master, text=instr_text, font=("Arial", 12), bg='lightblue', justify='left') instr_label.pack(pady=20) # Кнопка старта start_btn = tk.Button(self.master, text="Начать викторину", font=("Arial", 14), command=self.start_quiz, bg='green', fg='white', padx=20, pady=10, cursor='hand2') start_btn.pack(pady=30) def start_quiz(self): """Начало викторины""" self.shuffle_questions() self.current_question = 0 self.correct_answers = 0 self.show_question() def get_background_color(self, q: Dict, index: int) -> str: """Получение цвета фона для вопроса""" if q.get('bg_color'): return q['bg_color'] if PIL_AVAILABLE and q.get('image'): image_path = self.image_manager.find_image(q['image']) if image_path: color = self.image_manager.get_dominant_color(image_path) if color: return color return DEFAULT_COLORS[index % len(DEFAULT_COLORS)] def create_option_buttons(self, parent, options: List, bg_color: str, text_color: str): """Создание кнопок вариантов ответов""" options_with_indices = list(enumerate(options)) random.shuffle(options_with_indices) self.var = tk.IntVar(value=-1) self.option_mapping = {} # Функции для эффекта наведения def on_enter(btn): btn.config(bg='#d0d0d0' if text_color == 'black' else '#505050') def on_leave(btn, orig_bg): btn.config(bg=orig_bg) for new_idx, (old_idx, option_text) in enumerate(options_with_indices): self.option_mapping[new_idx] = old_idx display_text = f"{new_idx + 1}. {option_text}" rb = tk.Radiobutton(parent, text=display_text, variable=self.var, value=new_idx, font=("Arial", 11), wraplength=350, justify='left', indicatoron=0, bg=bg_color, fg=text_color, selectcolor=bg_color, padx=15, pady=8, cursor='hand2') rb.pack(anchor='center', pady=5) # Эффект наведения orig_bg = rb.cget('bg') rb.bind("", lambda e, b=rb: on_enter(b)) rb.bind("", lambda e, b=rb: on_leave(b, orig_bg)) def show_question(self): """Отображение вопроса""" self.clear_window() # Удаление старого изображения if hasattr(self, 'current_image'): del self.current_image q = self.questions[self.current_question] bg_color = self.get_background_color(q, self.current_question) text_color = self.get_text_color(bg_color) self.master.config(bg=bg_color) # Основной контейнер main_frame = tk.Frame(self.master, bg=bg_color) main_frame.pack(fill='both', expand=True, padx=30, pady=20) # Левая колонка left_frame = tk.Frame(main_frame, bg=bg_color) left_frame.pack(side='left', fill='both', expand=True) # Правая колонка right_frame = tk.Frame(main_frame, bg=bg_color) right_frame.pack(side='right', fill='both', expand=True) # Вопрос question_lbl = tk.Label(left_frame, text=q['question'], font=("Arial", 14, "bold"), wraplength=450, justify='left', bg=bg_color, fg=text_color) question_lbl.pack(anchor='nw', pady=(0, 20)) # Изображение if q.get('image'): image_path = self.image_manager.find_image(q['image']) if image_path: try: self.current_image = self.image_manager.load_image(image_path, bg_color) img_label = tk.Label(left_frame, image=self.current_image, bg=bg_color) img_label.pack(anchor='center', pady=10) except Exception as e: error_text = f"[Ошибка: {image_path.name}]\n{str(e)[:50]}" error_label = tk.Label(left_frame, text=error_text, font=("Arial", 8), fg='red', bg=bg_color) error_label.pack(anchor='center') else: error_text = f"[Файл не найден: {q['image']}]\nПоддерживаемые форматы: PNG, JPG, JPEG, GIF, BMP" error_label = tk.Label(left_frame, text=error_text, font=("Arial", 8), fg='orange', bg=bg_color) error_label.pack(anchor='center') # Прогресс и подсказки progress_lbl = tk.Label(left_frame, text=f"Вопрос {self.current_question + 1} из {self.total_questions}", font=("Arial", 11), fg=text_color, bg=bg_color) progress_lbl.pack(side='bottom', anchor='sw', pady=10) hint_lbl = tk.Label(left_frame, text="Клавиши 1-4 → выбор ответа, Enter → проверка", font=("Arial", 9), fg=text_color, bg=bg_color) hint_lbl.pack(side='bottom', anchor='sw', pady=5) # Варианты ответов center_container = tk.Frame(right_frame, bg=bg_color) center_container.pack(expand=True) options_title = tk.Label(center_container, text="Выберите ответ:", font=("Arial", 12, "bold"), bg=bg_color, fg=text_color) options_title.pack(pady=(0, 15)) self.create_option_buttons(center_container, q['options'], bg_color, text_color) # Кнопки управления btn_frame = tk.Frame(right_frame, bg=bg_color) btn_frame.pack(side='bottom', fill='x', pady=20) exit_btn = tk.Button(btn_frame, text="Выход (Esc)", font=("Arial", 11), command=self.quit_app, bg='red', fg='white', padx=20, pady=5, cursor='hand2') exit_btn.pack(side='right', padx=10) check_btn = tk.Button(btn_frame, text="Проверить (Enter)", font=("Arial", 11), command=self.check_answer, bg='blue', fg='white', padx=20, pady=5, cursor='hand2') check_btn.pack(side='right', padx=10) self.master.focus_set() def check_answer(self): """Проверка ответа""" if self.var is None or self.var.get() == -1: messagebox.showwarning("Нет выбора", "Пожалуйста, выберите один из вариантов (клавиши 1-4).") return selected = self.var.get() original_selected = self.option_mapping[selected] q = self.questions[self.current_question] if original_selected == q['correct']: self.correct_answers += 1 self.show_feedback("Правильно!", "green") else: correct_text = q['options'][q['correct']] explanation = f"Неправильно. Правильный ответ: {correct_text}\n\n{q['explanation']}" self.show_feedback("Неправильно", "red", explanation) def show_feedback(self, message: str, color: str, explanation: str = None): """Отображение обратной связи""" win = tk.Toplevel(self.master) win.title("Результат") win.geometry("500x350") win.configure(bg=color) win.transient(self.master) win.grab_set() # Закрытие окна def close_and_continue(): win.destroy() self.next_question() win.protocol("WM_DELETE_WINDOW", close_and_continue) win.bind('', lambda e: close_and_continue()) win.bind('', lambda e: close_and_continue()) # Сообщение lbl = tk.Label(win, text=message, bg=color, fg='white', font=("Arial", 24, "bold"), wraplength=450, justify='center') lbl.pack(expand=True, fill='both', padx=20, pady=20) # Пояснение if explanation: expl_lbl = tk.Label(win, text=explanation, bg=color, fg='white', font=("Arial", 11), wraplength=450, justify='left') expl_lbl.pack(padx=20, pady=10) # Кнопка продолжения btn = tk.Button(win, text="Продолжить (Enter)", command=close_and_continue, bg='white', font=("Arial", 11), padx=20, pady=5, cursor='hand2') btn.pack(pady=20) win.focus_set() def next_question(self): """Переход к следующему вопросу""" if self.current_question + 1 < self.total_questions: self.current_question += 1 self.show_question() else: self.show_result() def show_result(self): """Отображение результатов""" self.clear_window() self.master.config(bg='lightgreen') percentage = (self.correct_answers / self.total_questions) * 100 result_text = f"Викторина завершена!\n\n" result_text += f"Вы ответили правильно на {self.correct_answers} из {self.total_questions} вопросов.\n" result_text += f"Ваш результат: {percentage:.1f}%\n\n" if percentage == 100: result_text += "Отлично! Вы очень хорошо усвоили материал." elif percentage >= 80: result_text += "Отличный результат! Вы хорошо знаете тему." elif percentage >= 60: result_text += "Хороший результат! Есть куда стремиться." else: result_text += "Стоит повторить лекцию и попробовать снова." lbl = tk.Label(self.master, text=result_text, font=("Arial", 16), bg='lightgreen', justify='center') lbl.pack(pady=30) # Привязка клавиш self.master.bind('', lambda e: self.start_quiz()) self.master.bind('', lambda e: self.quit_app()) # Подсказка hint_lbl = tk.Label(self.master, text="Enter - пройти заново | Esc - выход", font=("Arial", 11), bg='lightgreen', fg='gray') hint_lbl.pack(pady=10) # Кнопки btn_frame = tk.Frame(self.master, bg='lightgreen') btn_frame.pack(pady=20) restart_btn = tk.Button(btn_frame, text="Пройти заново (Enter)", font=("Arial", 12), command=self.start_quiz, bg='blue', fg='white', padx=20, pady=8, cursor='hand2') restart_btn.pack(side='left', padx=15) exit_btn = tk.Button(btn_frame, text="Завершить (Esc)", font=("Arial", 12), command=self.quit_app, bg='orange', fg='white', padx=20, pady=8, cursor='hand2') exit_btn.pack(side='left', padx=15) self.master.focus_set() if __name__ == "__main__": root = tk.Tk() app = QuizApp(root) root.mainloop()