'use strict'; /* ═══════════════════════════════════════════════════════════════ Интерактив «Файлы и маски» — 10 класс Три уровня: простой, средний, ЕГЭ ═══════════════════════════════════════════════════════════════ */ // ── База имён файлов ───────────────────────────────────────── const FILES = [ // Документы 'отчёт.docx','отчёт1.docx','отчёт2.docx','отчёт_финал.docx','отчёт_2024.docx','отчёт_2025.docx', 'Отчёт_важный.docx','ОТЧЁТ_ИТОГ.docx','план.docx','план_работы.docx','план2025.docx','план_2024.docx', 'документ.doc','документ1.doc','документ2.doc','документ.docx','документ.pdf','sample.doc','пример.docx', 'отчёт.pdf','отчёт_final.pdf', // Фото 'фото1.jpg','фото2.jpg','фото3.jpg','фото10.jpg','фото_лето.jpg','фото_зима.jpg','Фото_море.jpg','ФОТО_ГОРЫ.jpg', 'family.jpg','holiday.jpg','screen.jpg', // PNG 'cat.png','dog.png','cat_and_dog.png','screenshot.png','screenshot1.png','screenshot2.png','screenshot_2024.png', // Тексты 'data.txt','data1.txt','data2.txt','data_old.txt','data_new.txt','notes.txt','notes1.txt', 'readme.txt','README.txt','readme.md','log.txt','file001.txt','file002.txt','file003.txt','file010.txt','file100.txt', // Код 'script.py','script1.py','test.py','main.py','main_v2.py','app.py', 'index.html','style.css','script.js','app.js','main.js', // Конфиги 'data.json','config.json','config.xml','settings.ini', // Презентации 'presentation.pptx','слайд1.pptx','слайд2.pptx','слайд_итог.pptx', // Медиа 'video.mp4','video1.mp4','clip.mp4','movie.mkv', 'song.mp3','song1.mp3','song2.mp3','podcast.mp3', // Архивы 'archive.zip','archive.rar','archive1.zip','backup.zip','backup_2024.zip','backup_2025.zip','project.zip', // Служебные 'temp.tmp','temp1.tmp','cache.tmp', 'system.log','error.log','access.log','debug.log', // Тесты 'test1.docx','test2.docx','test3.docx','Тест_итог.docx', // ── Файлы с двойными и более расширениями (для среднего уровня) ── // Трапы .exe под видом документов 'отчёт.pdf.exe','приказ.docx.exe','счёт.pdf.exe', // Резервные копии 'фото.jpg.bak','фото1.jpg.bak','фото_зима.jpg.bak','документ.docx.bak','таблица.xlsx.bak', // Архивы 'archive.tar.gz','backup.tar.gz', // Минифицированные файлы 'script.min.js','style.min.css', // Версионированные документы 'report.v2.docx','report.v3.docx', // Файлы с датой в расширении 'data.2024.csv','data.2025.csv','фото.2024.jpg','фото.2023.jpg', // ── Дополнительно для ЕГЭ (реальные имена из заданий) ── 'aspera.doc','capers.dat','capers.doc','operas.doc','pergola.docx','prospero.docx', 'inmar.xls','komarik.xlsx','komarik.xxx','marka.xlsx','romario.xls','smart.xlsx', 'amir.dat','amir.data','mig.dat','comic.dat','demid.dat','femina.dat', 'chifera.dat','chifera.doc','ferrum.doc','deLafer.doc','oferta.doc','tokoferol.doc', 'primera.dat','primera.doc','merchant.doc','k-mer.doc','omerta.doc','Tamerlan.doc', 'bisquit.xlsx','cabinda.xls','cubic.xlsx','irbis.xls','tobias.xls','tobias.xml', 'slon.dat','slon.data','klon.dat','poklon.dat','lom.dat','lomka.dat', 'adob.dat','adob.data','dos.dat','pardon.dat','odor.dat','radost.dat', 'izoferen.doc','kifera.dat','kifera.doc','la-fer-atos.doc','merfer.docx','oferta.doc', 'comfort.c','cobalt.cpp','coat.c','cost.cpp', 'maveric.map','maveric.mp3','taverna.mp4','revolver.mp4','vera.mp3','zveri.mp3', 'syste.m','sys23.exe','system.dll','syszx.problem', '1234.xls','23.xml','234.xls', 'gorod.ppt','horror.pptx','horror.prj','orcl.ppt','sordka.pptx','sort.ppt', ]; // ═══════════════════════════════════════════════════════════════ // УРОВЕНЬ 1: ПРОСТОЙ // ═══════════════════════════════════════════════════════════════ const QUESTIONS_EASY = [ { text: 'Все файлы с расширением .jpg', expected: '*.jpg' }, { text: 'Все файлы с расширением .txt', expected: '*.txt' }, { text: 'Все файлы с расширением .png', expected: '*.png' }, { text: 'Все файлы с расширением .pdf', expected: '*.pdf' }, { text: 'Все файлы с расширением .docx', expected: '*.docx' }, { text: 'Все файлы с расширением .mp3', expected: '*.mp3' }, { text: 'Все файлы, имя которых начинается с «отчёт»', expected: 'отчёт*' }, { text: 'Все файлы, имя которых начинается с «фото»', expected: 'фото*' }, { text: 'Все файлы, имя которых начинается с «data»', expected: 'data*' }, { text: 'Все файлы, имя которых начинается с «screenshot»', expected: 'screenshot*' }, { text: 'Все файлы, имя которых начинается с «backup»', expected: 'backup*' }, { text: 'Все .jpg-файлы, имя которых начинается с «фото»', expected: 'фото*.jpg' }, { text: 'Все .png-файлы, имя которых начинается с «screenshot»', expected: 'screenshot*.png' }, { text: 'Все .txt-файлы, имя которых начинается с «file»', expected: 'file*.txt' }, { text: 'Все .docx-файлы, имя которых начинается с «отчёт»', expected: 'отчёт*.docx' }, { text: 'Все .zip-файлы, имя которых начинается с «backup»', expected: 'backup*.zip' }, { text: 'Все файлы, в имени которых встречается «log»', expected: '*log*' }, { text: 'Все файлы, в имени которых встречается «test»', expected: '*test*' }, { text: 'Все .txt-файлы, имя которых — «data» и ровно одна цифра после', expected: 'data?.txt' }, { text: 'Все .jpg-файлы, имя которых — «фото» и ровно одна цифра после', expected: 'фото?.jpg' }, ]; // ═══════════════════════════════════════════════════════════════ // УРОВЕНЬ 2: СРЕДНИЙ // Больше масок с ? + двойные расширения // ═══════════════════════════════════════════════════════════════ const QUESTIONS_MIDDLE = [ // ── Маски с ? (7 заданий) ── { text: 'Все .txt-файлы, имя которых — «file» и ровно три символа после', expected: 'file???.txt' }, { text: 'Все .txt-файлы, имя которых — «data» и ровно один символ после', expected: 'data?.txt' }, { text: 'Все .jpg-файлы, имя которых — «фото» и ровно один символ после', expected: 'фото?.jpg' }, { text: 'Все .png-файлы, имя которых — «screenshot» и ровно один символ после', expected: 'screenshot?.png' }, { text: 'Все .docx-файлы, имя которых — «отчёт» и ровно один символ после', expected: 'отчёт?.docx' }, { text: 'Все .py-файлы, имя которых — «script» и ровно один символ после', expected: 'script?.py' }, { text: 'Все .tmp-файлы, имя которых — «temp» и ровно один символ после', expected: 'temp?.tmp' }, // ── Двойные и более расширения (9 заданий) ── { text: 'Все .exe-файлы, включая замаскированные под документы (например, «отчёт.pdf.exe»)', expected: '*.exe' }, { text: 'Все файлы с расширением .bak (резервные копии)', expected: '*.bak' }, { text: 'Все архивы .tar.gz', expected: '*.tar.gz' }, { text: 'Все минифицированные JS-файлы (.min.js)', expected: '*.min.js' }, { text: 'Все минифицированные CSS-файлы (.min.css)', expected: '*.min.css' }, { text: 'Все файлы, в имени которых есть «.pdf.» (документы с двойным расширением)', expected: '*.pdf.*' }, { text: 'Все .docx-файлы с версией вида .v2.docx или .v3.docx (ровно одна цифра после v)', expected: '*.v?.docx' }, { text: 'Все файлы, в имени которых есть «.2024.» (2024 как отдельное расширение)', expected: '*.2024.*' }, { text: 'Все фото с четырёхзначным годом в имени — например, «фото.2024.jpg»', expected: 'фото.????.jpg' }, // ── Комбинации (4 задания) ── { text: 'Все .exe-файлы, имя которых начинается с «отчёт»', expected: 'отчёт*.exe' }, { text: 'Все .bak-файлы, имя которых начинается с «фото»', expected: 'фото*.bak' }, { text: 'Все файлы, начинающиеся с «data» и содержащие минимум две точки', expected: 'data*.*.*' }, { text: 'Все файлы, содержащие не менее двух точек в имени (то есть с двумя и более расширениями)', expected: '*.*.*' }, ]; // ═══════════════════════════════════════════════════════════════ // УРОВЕНЬ 3: ЕГЭ (реальные маски из заданий типа Д7) // ═══════════════════════════════════════════════════════════════ const QUESTIONS_EGE = [ { text: 'В каталоге: aspera.doc, capers.dat, capers.doc, operas.doc, pergola.docx, prospero.docx. ' + 'Выберите файлы: aspera.doc, capers.doc, operas.doc, prospero.docx.', expected: '*?per?*.do*' }, { text: 'В каталоге: inmar.xls, komarik.xlsx, komarik.xxx, marka.xlsx, romario.xls, smart.xlsx. ' + 'Выберите файлы: komarik.xlsx, marka.xlsx, romario.xls, smart.xlsx.', expected: '*?mar*.xls*' }, { text: 'В каталоге: amir.dat, amir.data, mig.dat, comic.dat, demid.dat, femina.dat. ' + 'Выберите файлы: amir.dat, mig.dat, comic.dat, demid.dat.', expected: '*mi*.dat' }, { text: 'В каталоге: chifera.dat, chifera.doc, ferrum.doc, deLafer.doc, oferta.doc, tokoferol.doc. ' + 'Выберите файлы, содержащие содержащих «fer» в имени и расширение .doc.', expected: '*fer*.doc' }, { text: 'В каталоге: primera.dat, primera.doc, merchant.doc, k-mer.doc, omerta.doc, Tamerlan.doc. ' + 'Выберите файлы, содержащие содержащих «mer» в имени и расширение .doc.', expected: '*mer*.doc' }, { text: 'В каталоге: bisquit.xlsx, cabinda.xls, cubic.xlsx, irbis.xls, tobias.xls, tobias.xml. ' + 'Выберите файлы: cabinda.xls, cubic.xlsx, irbis.xls, tobias.xls.', expected: '??bi*.xls*' }, { text: 'В каталоге: slon.dat, slon.data, klon.dat, poklon.dat, lom.dat, lomka.dat. ' + 'Выберите файлы: slon.dat, klon.dat, poklon.dat, lom.dat.', expected: '*lo?.dat' }, { text: 'В каталоге: adob.dat, adob.data, dos.dat, pardon.dat, odor.dat, radost.dat. ' + 'Выберите файлы: adob.dat, dos.dat, pardon.dat, odor.dat.', expected: '*do?.dat' }, { text: 'В каталоге: izoferen.doc, kifera.dat, kifera.doc, la-fer-atos.doc, merfer.docx, oferta.doc. ' + 'Выберите файлы: izoferen.doc, kifera.doc, la-fer-atos.doc, oferta.doc.', expected: '*?fer*?.do*' }, { text: 'В каталоге: comfort.c, cobalt.cpp, coat.c, cost.cpp. ' + 'Выберите все четыре файла: comfort.c, cobalt.cpp, coat.c, cost.cpp.', expected: 'c*t.c??' }, { text: 'В каталоге: maveric.map, maveric.mp3, taverna.mp4, revolver.mp4, vera.mp3, zveri.mp3. ' + 'Выберите файлы: maveric.mp3, taverna.mp4, revolver.mp4, zveri.mp3.', expected: '*?ver*.m*' }, { text: 'В каталоге: gorod.ppt, horror.pptx, horror.prj, orcl.ppt, sordka.pptx, sort.ppt. ' + 'Выберите файлы, имя которых содержит «or» и расширение начинается с .p.', expected: '*or*.p*' }, { text: 'В каталоге: 1234.xls, 23.xml, 234.xls. ' + 'Выберите файлы: 1234.xls, 23.xml, 234.xls.', expected: '*23*.?x*' }, { text: 'Какое из имён файлов НЕ удовлетворяет маске sys??.* ? ' + 'Варианты: syste.m, sys23.exe, system.dll, syszx.problem. ' + 'Введите маску, которая отберёт ровно те файлы, которые удовлетворяют исходной.', expected: 'sys??.*' }, { text: 'Какое из имён файлов удовлетворяет маске ?mas*.p* ? ' + 'Варианты: massiv.pas, mmassiv.p, mmassiv.cpp, mas.p. ' + 'Введите маску, которая отберёт ровно правильные файлы.', expected: '?mas*.p*' }, ]; // ── Состояние ──────────────────────────────────────────────── let currentLevel = 'easy'; let currentQuestions = []; let currentIdx = 0; let score = 0; let answered = false; // ── Утилиты ────────────────────────────────────────────────── function maskToRegex(mask) { let re = ''; for (const ch of mask) { if (ch === '*') { re += '.*'; } else if (ch === '?') { re += '.'; } else if (/[.*+?^${}()|[\]\\]/.test(ch)) { re += '\\' + ch; } else { re += ch; } } return new RegExp('^' + re + '$', 'i'); } function matchFiles(mask) { const re = maskToRegex(mask); return FILES.filter(f => re.test(f)).sort(); } function escapeHtml(s) { return String(s).replace(/[&<>"']/g, c => ({ '&':'&','<':'<','>':'>','"':'"',"'":''' }[c])); } // ── Рендер вопроса ─────────────────────────────────────────── function renderQuestion() { const q = currentQuestions[currentIdx]; document.getElementById('fmNum').textContent = currentIdx + 1; document.getElementById('fmTotal').textContent = currentQuestions.length; document.getElementById('fmScore').textContent = score; document.getElementById('fmTask').textContent = q.text; const input = document.getElementById('fmInput'); input.value = ''; input.disabled = false; const submit = document.getElementById('fmSubmit'); submit.disabled = false; submit.classList.remove('hidden'); document.getElementById('fmResult').classList.add('hidden'); document.getElementById('fmNext').classList.add('hidden'); answered = false; setTimeout(() => input.focus(), 50); } // ── Рендер результата ──────────────────────────────────────── function renderResult(userMask, userFiles, targetFiles, isCorrect) { document.getElementById('fmResult').classList.remove('hidden'); const verdictEl = document.getElementById('fmVerdict'); if (isCorrect) { verdictEl.textContent = '✓ Верно!'; verdictEl.className = 'fm-verdict ok'; } else { verdictEl.textContent = '✗ Неверно'; verdictEl.className = 'fm-verdict fail'; } const infoEl = document.getElementById('fmMaskInfo'); let infoHTML = `Ваша маска: ${escapeHtml(userMask)}`; if (!isCorrect) { infoHTML += `
Правильная маска: ${escapeHtml(currentQuestions[currentIdx].expected)}`; } infoEl.innerHTML = infoHTML; const targetSet = new Set(targetFiles); const foundUl = document.getElementById('fmFound'); foundUl.innerHTML = ''; if (userFiles.length === 0) { const li = document.createElement('li'); li.className = 'fm-empty'; li.textContent = 'Ни один файл не подошёл под маску'; foundUl.appendChild(li); } else { for (const f of userFiles) { const li = document.createElement('li'); li.textContent = f; if (!targetSet.has(f)) li.classList.add('fm-wrong'); foundUl.appendChild(li); } } const userSet = new Set(userFiles); const missed = targetFiles.filter(f => !userSet.has(f)); const missedWrap = document.getElementById('fmMissedWrap'); const missedUl = document.getElementById('fmMissed'); missedUl.innerHTML = ''; if (missed.length > 0) { missedWrap.classList.remove('hidden'); for (const f of missed) { const li = document.createElement('li'); li.textContent = f; li.classList.add('fm-missed'); missedUl.appendChild(li); } } else { missedWrap.classList.add('hidden'); } const nextBtn = document.getElementById('fmNext'); nextBtn.classList.remove('hidden'); nextBtn.textContent = (currentIdx === currentQuestions.length - 1) ? 'Завершить' : 'Следующий вопрос →'; } // ── Итог ───────────────────────────────────────────────────── function renderFinal() { document.getElementById('fmMain').classList.add('hidden'); document.getElementById('fmFinal').classList.remove('hidden'); document.getElementById('fmFinalScore').textContent = `${score} / ${currentQuestions.length}`; const pct = score / currentQuestions.length; let gradeText; if (pct === 1) gradeText = 'Идеально! Все ответы верны.'; else if (pct >= 0.9) gradeText = 'Отлично!'; else if (pct >= 0.75) gradeText = 'Хорошо!'; else if (pct >= 0.5) gradeText = 'Неплохо, но есть что подтянуть.'; else gradeText = 'Стоит повторить тему «Маски файлов».'; document.getElementById('fmFinalGrade').textContent = gradeText; } // ── Логика ─────────────────────────────────────────────────── function submit() { if (answered) return; const input = document.getElementById('fmInput'); const mask = input.value.trim(); if (!mask) { input.focus(); return; } answered = true; input.disabled = true; const submitBtn = document.getElementById('fmSubmit'); submitBtn.disabled = true; submitBtn.classList.add('hidden'); const q = currentQuestions[currentIdx]; const targetFiles = matchFiles(q.expected); const userFiles = matchFiles(mask); const isCorrect = targetFiles.length === userFiles.length && targetFiles.every((f, i) => f === userFiles[i]); if (isCorrect) { score++; document.getElementById('fmScore').textContent = score; } renderResult(mask, userFiles, targetFiles, isCorrect); } function next() { if (currentIdx === currentQuestions.length - 1) { renderFinal(); } else { currentIdx++; renderQuestion(); } } // ── Стартовый экран и уровни ──────────────────────────────── function showStart() { document.getElementById('fmStart').classList.remove('hidden'); document.getElementById('fmMain').classList.add('hidden'); document.getElementById('fmFinal').classList.add('hidden'); } function startLevel(level) { currentLevel = level; if (level === 'ege') currentQuestions = QUESTIONS_EGE; else if (level === 'middle') currentQuestions = QUESTIONS_MIDDLE; else currentQuestions = QUESTIONS_EASY; currentIdx = 0; score = 0; answered = false; document.getElementById('fmStart').classList.add('hidden'); document.getElementById('fmMain').classList.remove('hidden'); document.getElementById('fmFinal').classList.add('hidden'); renderQuestion(); } // ── Инициализация ──────────────────────────────────────────── function init() { if (!document.getElementById('fmInput')) return; document.getElementById('fmLevelEasy').addEventListener('click', () => startLevel('easy')); document.getElementById('fmLevelMiddle').addEventListener('click', () => startLevel('middle')); document.getElementById('fmLevelEge').addEventListener('click', () => startLevel('ege')); document.getElementById('fmSubmit').addEventListener('click', submit); document.getElementById('fmNext').addEventListener('click', next); document.getElementById('fmRestart').addEventListener('click', () => startLevel(currentLevel)); document.getElementById('fmChangeLevel').addEventListener('click', showStart); document.getElementById('fmInput').addEventListener('keydown', e => { if (e.key === 'Enter') { e.preventDefault(); submit(); } }); showStart(); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); }