This website has been generated by DeepSite DeepSite Logo

`; // Создание и скачивание файла const blob = new Blob([htmlContent], { type: 'text/html' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'game.html'; a.click(); URL.revokeObjectURL(url); } // Общая функция для генерации HTML-контента function generateGameHtml(scriptContent) { return ` Моя Игра
`; } // Экспорт в EXE (Electron) function exportToExe() { const scriptContent = codeMirrorEditor.getValue(); if (!scriptContent.trim()) { alert('Редактор пуст! Нечего экспортировать.'); return; } const htmlContent = generateGameHtml(scriptContent); // Содержимое BAT-файла с русским названием и кодировкой CP866 const batContent = `@echo off REM build.bat - Скрипт для автоматической сборки проекта setlocal enabledelayedexpansion ECHO. ECHO ============================================== ECHO === Запуск установки зависимостей и сборки === ECHO ============================================== ECHO. REM 1. Установка зависимостей ECHO [1/3] Устанавливаем зависимости npm... CALL npm install IF %ERRORLEVEL% NEQ 0 ( ECHO ОШИБКА! Не удалось установить зависимости TIMEOUT /T 5 EXIT /B 1 ) REM 2. Запуск сборки проекта ECHO [2/3] Запускаем сборку проекта... CALL npm run build IF %ERRORLEVEL% NEQ 0 ( ECHO ОШИБКА! Сборка не удалась TIMEOUT /T 5 EXIT /B 1 ) REM 3. Завершение работы ECHO [3/3] Сборка успешно завершена! ECHO. ECHO Файлы проекта доступны в папке /dist ECHO. TIMEOUT /T 10 EXIT /B 0 `; const readmeTxt = `Молодец что ты тут, так будет проще: 1. Перенеси все файлы игры в эту папку! 2. Запути bat файл "Запусти для создания exe" и подожди завершения экспорта. 3. Твой экспорт будет находиться в этой же папке в dist/win-unpacked `; const packageJson = `{ "name": "game", "version": "1.0.0", "main": "main.js", "scripts": { "start": "electron .", "build": "electron-builder" }, "devDependencies": { "electron": "^28.0.0", "electron-builder": "^24.9.1" } }`; const mainJs = `const { app, BrowserWindow } = require('electron'); function createWindow() { const win = new BrowserWindow({ width: 1280, height: 720, minWidth: 1280, minHeight: 720, maxWidth: 1920, maxHeight: 1080, autoHideMenuBar: true, // Скрыть меню frame: true, // Полноценный заголовок окна webPreferences: { nodeIntegration: true, contextIsolation: false } }); win.setMenu(null); // Полное отключение меню win.loadFile('game.html'); // Дополнительные настройки win.on('ready-to-show', () => { win.show(); }); } app.whenReady().then(createWindow); app.on('window-all-closed', () => { if (process.platform !== 'darwin') app.quit(); }); `; const zip = new JSZip(); zip.file("game.html", htmlContent); zip.file("package.json", packageJson); zip.file("ПРОЧТИ ОБЯЗАТЕЛЬНО!.txt", readmeTxt); // Добавляем readme zip.file("main.js", mainJs); // Добавляем BAT-файл с правильным именем zip.file("Запусти для создания exe.bat", batContent, { unixOptions: 0o644, dosAttributes: 0x20 // Архивный атрибут }); zip.generateAsync({ type: "blob" }).then(blob => { const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'game-electron.zip'; a.click(); URL.revokeObjectURL(url); }); } // Привязка кнопок экспорта document.getElementById('btnExportExe').addEventListener('click', exportToExe); // Привязка кнопки экспорта document.getElementById('btnExport').addEventListener('click', exportToWeb); // Создаем экземпляр Game после объявления scriptContent const game = new Game(); const treeEl = document.getElementById('tree'); const btnNew = document.getElementById('btnNew'); const btnOpen = document.getElementById('btnOpen'); const btnSave = document.getElementById('btnSave'); const fileInput = document.getElementById('fileInput'); const btnPlay = document.getElementById('btnPlay'); // Улучшенная подсветка синтаксиса CodeMirror.defineMode("custom", function() { return { token: function(stream, state) { if (stream.match(/^"[^"]*"/)) return "string"; if (stream.match(/^\/\/.*/)) return "comment"; if (stream.sol()) { if (stream.match(/^(loc|case|s|img|play|rn|if|else)\b/)) { state.expecting = "keyword-content"; return "keyword"; } if (stream.match(/^\((l|c|loc|case|m|меню)\b/)) { state.inNavCommand = true; return "nav-command"; } } if (state.expecting === "keyword-content") { if (stream.match(/\S+/)) { state.expecting = null; return "keyword-content"; } } if (state.inNavCommand) { if (stream.match(/\)/)) { state.inNavCommand = false; return "nav-command"; } if (stream.match(/[-\|]/)) return "nav-separator"; return "nav-content"; } if (stream.match(/^\{\w+\}/)) return "variable"; if (stream.match(/[=>/i)) return "html-tag"; if (stream.match(/^<\/[a-z][a-z0-9]*>/i)) return "html-tag"; if (stream.eatSpace()) return null; stream.next(); return null; }, startState: function() { return { expecting: null, inNavCommand: false }; } }; }); // Определяем команды для автодополнения const commands = [ "loc", "case", "s", "img", "play", "rn", "if", "else", "m", "меню", "l", "c", "item" ]; // Регистрируем помощник для автодополнения CodeMirror.registerHelper("hint", "custom", function(editor) { const cur = editor.getCursor(); const token = editor.getTokenAt(cur); const start = token.start; const end = cur.ch; const str = token.string; if (str && commands.some(cmd => cmd.startsWith(str))) { return { list: commands.filter(cmd => cmd.startsWith(str)), from: CodeMirror.Pos(cur.line, start), to: CodeMirror.Pos(cur.line, end) }; } }); // Функция валидации для подсветки ошибок function validateScript(text) { const lines = text.split('\n'); const errors = []; let inLocation = false; lines.forEach((line, index) => { const trimmed = line.trim(); if (trimmed.startsWith("loc ")) { inLocation = true; } else if (trimmed.startsWith("case ") && !inLocation) { errors.push({ line: index, message: "Ошибка: 'case' вне 'loc'." }); } else if (inLocation && !trimmed.startsWith(" ") && !trimmed.startsWith("case ") && trimmed !== "") { errors.push({ line: index, message: "Ошибка: Неверный формат или отступ." }); } }); return errors; } // Регистрируем помощник для подсветки ошибок CodeMirror.registerHelper("lint", "custom", function(text) { return validateScript(text).map(error => ({ from: CodeMirror.Pos(error.line, 0), to: CodeMirror.Pos(error.line, 0), message: error.message })); }); // Инициализируем редактор CodeMirror const codeMirrorEditor = CodeMirror.fromTextArea(document.getElementById("editor"), { lineNumbers: true, indentUnit: 4, tabSize: 4, historyEventDelay: 0, indentWithTabs: false, mode: "custom", extraKeys: { "Ctrl-Space": "autocomplete", "Tab": (cm) => { cm.replaceSelection(" ", "end"); }, "Ctrl-Z": (cm) => { cm.undo(); }, "Ctrl-S": (cm) => { saveProjectToFile(cm.getValue()); } }, lineWrapping: true, lint: true }); // Определяем команду автодополнения CodeMirror.commands.autocomplete = function(cm) { cm.showHint({ hint: CodeMirror.hint.custom }); }; let currentEditType = 'all'; let currentEditLoc = null; let currentEditCase = null; codeMirrorEditor.on("change", () => { const newText = codeMirrorEditor.getValue(); if (currentEditType === 'all') { scriptContent = newText; } else if (currentEditType === 'location') { scriptContent = replaceLocationText(scriptContent, currentEditLoc, newText); } else if (currentEditType === 'case') { scriptContent = replaceCaseText(scriptContent, currentEditLoc, currentEditCase, newText); } updateTree(); }); function setEditorContent(text) { codeMirrorEditor.setValue(text); } function extractLocationText(script, locName) { const lines = script.split('\n'); let startLine = -1; let endLine = lines.length; for (let i = 0; i < lines.length; i++) { const line = lines[i].trim(); if (line.startsWith(`loc ${locName}`)) { startLine = i; } else if (line.startsWith('loc ') && startLine !== -1) { endLine = i; break; } } if (startLine === -1) return ''; return lines.slice(startLine, endLine).join('\n'); } function extractCaseText(script, locName, caseName) { const locText = extractLocationText(script, locName); if (!locText) return ''; const lines = locText.split('\n'); let startLine = -1; let endLine = lines.length; for (let i = 0; i < lines.length; i++) { const line = lines[i].trim(); if (line.startsWith(`case ${caseName}`)) { startLine = i; } else if (line.startsWith('case ') && startLine !== -1) { endLine = i; break; } } if (startLine === -1) return ''; return lines.slice(startLine, endLine).join('\n'); } function replaceLocationText(script, locName, newText) { const lines = script.split('\n'); let startLine = -1; let endLine = lines.length; for (let i = 0; i < lines.length; i++) { const line = lines[i].trim(); if (line.startsWith(`loc ${locName}`)) { startLine = i; } else if (line.startsWith('loc ') && startLine !== -1) { endLine = i; break; } } if (startLine === -1) return script; const before = lines.slice(0, startLine).join('\n'); const after = lines.slice(endLine).join('\n'); return before + (before ? '\n' : '') + newText + (after ? '\n' : '') + after; } function replaceCaseText(script, locName, caseName, newText) { const locText = extractLocationText(script, locName); if (!locText) return script; const lines = locText.split('\n'); let startLine = -1; let endLine = lines.length; for (let i = 0; i < lines.length; i++) { const line = lines[i].trim(); if (line.startsWith(`case ${caseName}`)) { startLine = i; } else if (line.startsWith('case ') && startLine !== -1) { endLine = i; break; } } if (startLine === -1) return script; const beforeCase = lines.slice(0, startLine).join('\n'); const afterCase = lines.slice(endLine).join('\n'); const newLocText = beforeCase + (beforeCase ? '\n' : '') + newText + (afterCase ? '\n' : '') + afterCase; return replaceLocationText(script, locName, newLocText); } function updateTree() { treeEl.innerHTML = ''; let root = document.createElement('li'); root.textContent = "Локации"; root.classList.add("active"); root.addEventListener('click', (e) => { currentEditType = 'all'; currentEditLoc = null; currentEditCase = null; setEditorContent(scriptContent); Array.from(treeEl.getElementsByTagName('li')).forEach(item => item.classList.remove("active")); root.classList.add("active"); }); treeEl.appendChild(root); let locs = game.parseLocations(scriptContent); for (let loc in locs) { if (loc === "переменные") continue; let locItem = document.createElement('li'); locItem.dataset.loc = loc; let toggleBtn = document.createElement('span'); toggleBtn.className = "toggle"; toggleBtn.textContent = "+"; toggleBtn.addEventListener('click', (e) => { e.stopPropagation(); let ul = locItem.querySelector("ul"); if (ul) { if (ul.style.display === "block") { ul.style.display = "none"; toggleBtn.textContent = "+"; } else { ul.style.display = "block"; toggleBtn.textContent = "–"; } } }); locItem.prepend(toggleBtn); locItem.addEventListener('click', (e) => { e.stopPropagation(); currentEditType = 'location'; currentEditLoc = loc; currentEditCase = null; const locText = extractLocationText(scriptContent, loc); setEditorContent(locText); Array.from(treeEl.getElementsByTagName('li')).forEach(item => item.classList.remove("active")); locItem.classList.add("active"); }); locItem.appendChild(document.createTextNode(" " + loc)); let caseList = document.createElement('ul'); for (let cs in locs[loc].cases) { let csItem = document.createElement('li'); csItem.dataset.loc = loc; csItem.dataset.case = cs; csItem.textContent = cs; csItem.addEventListener('click', (e) => { e.stopPropagation(); currentEditType = 'case'; currentEditLoc = loc; currentEditCase = cs; const caseText = extractCaseText(scriptContent, loc, cs); setEditorContent(caseText); Array.from(treeEl.getElementsByTagName('li')).forEach(item => item.classList.remove("active")); csItem.classList.add("active"); }); caseList.appendChild(csItem); } locItem.appendChild(caseList); treeEl.appendChild(locItem); } } btnNew.addEventListener('click', () => { if (confirm("Создать новый проект? Текущие изменения будут потеряны.")) { scriptContent = `loc начало case 0 (m (l Начало игры - дом) (c Об авторе - инфо)) case инфо (m (c назад - 0)) loc дом case 0 Сейчас {hour}:{min} (item if knife == false (На столе лежит (Нож| s knife = true))) loc инвентарь case 0 У меня {gold} золота Меня зовут {name} if knife == true (У меня Нож) // if gun == true (У меня Пистолет) // loc переменные s gold = 100 s name = "Андрей" s gun = false s knife = false s book = false s year = 1950 s month = 6 s day = 2 s hour = 10 s min = 7 s clik = 10 `; setEditorContent(scriptContent); updateTree(); } }); btnOpen.addEventListener('click', () => fileInput.click()); fileInput.addEventListener('change', (e) => { const file = e.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = (event) => { scriptContent = event.target.result; setEditorContent(scriptContent); updateTree(); }; reader.readAsText(file, "UTF-8"); fileInput.value = ""; }); async function saveProjectToFile(content) { if (window.showSaveFilePicker) { try { const opts = { types: [{ description: 'VQ Project Files', accept: {'text/plain': ['.txt']} }] }; const handle = await window.showSaveFilePicker(opts); const writable = await handle.createWritable(); await writable.write(content); await writable.close(); alert("Проект сохранён!"); } catch (err) { alert("Ошибка сохранения: " + err.message); } } else { const blob = new Blob([content], { type: "text/plain;charset=utf-8" }); const a = document.createElement("a"); a.href = URL.createObjectURL(blob); a.download = "project.txt"; a.click(); URL.revokeObjectURL(a.href); alert("Проект сохранён!"); } } btnSave.addEventListener('click', async () => { await saveProjectToFile(scriptContent); }); btnPlay.addEventListener('click', () => { document.getElementById('game-container').style.display = 'flex'; document.getElementById('game-container').style.flexDirection = 'column'; document.getElementById('overlay').style.display = 'flex'; game.loadScriptFromText(scriptContent); }); document.getElementById('stop-game').addEventListener('click', () => { document.getElementById('overlay').style.display = 'none'; document.getElementById('game-container').style.display = 'none'; }); updateTree();