From d5510eb989ae42605d82b0d5e8268956ceb16b0c Mon Sep 17 00:00:00 2001 From: Dan Date: Sun, 18 Jan 2026 16:44:16 +0000 Subject: [PATCH] game engine start --- assets/js/games/engine/ansi-converter.js | 212 +++ assets/js/games/engine/game-engine.js | 210 +++ assets/js/games/engine/input-manager.js | 205 +++ assets/js/games/engine/scene-manager.js | 367 +++++ assets/js/games/engine/state-manager.js | 208 +++ assets/js/games/engine/terminal-adapter.js | 70 + assets/js/games/games/boxing-day.js | 1457 ++++++++++++++++++++ assets/js/games/games/test-adventure.js | 281 ++++ assets/sass/partials/_terminal.scss | 55 + layouts/partials/site-scripts.html | 10 +- 10 files changed, 3074 insertions(+), 1 deletion(-) create mode 100644 assets/js/games/engine/ansi-converter.js create mode 100644 assets/js/games/engine/game-engine.js create mode 100644 assets/js/games/engine/input-manager.js create mode 100644 assets/js/games/engine/scene-manager.js create mode 100644 assets/js/games/engine/state-manager.js create mode 100644 assets/js/games/engine/terminal-adapter.js create mode 100644 assets/js/games/games/boxing-day.js create mode 100644 assets/js/games/games/test-adventure.js diff --git a/assets/js/games/engine/ansi-converter.js b/assets/js/games/engine/ansi-converter.js new file mode 100644 index 0000000..1b409b3 --- /dev/null +++ b/assets/js/games/engine/ansi-converter.js @@ -0,0 +1,212 @@ +// ANSI to HTML Converter +// Converts ANSI escape sequences (256-color) to HTML spans with inline styles +// Supports: 38;5;N (foreground), 48;5;N (background), and standard reset codes + +const AnsiConverter = { + // 256-color palette - standard xterm colors + palette: [ + // Standard colors (0-15) + "#000000", "#800000", "#008000", "#808000", "#000080", "#800080", "#008080", "#c0c0c0", + "#808080", "#ff0000", "#00ff00", "#ffff00", "#0000ff", "#ff00ff", "#00ffff", "#ffffff", + // 216 color cube (16-231) + ...(() => { + const colors = []; + const levels = [0, 95, 135, 175, 215, 255]; + for (let r = 0; r < 6; r++) { + for (let g = 0; g < 6; g++) { + for (let b = 0; b < 6; b++) { + colors.push(`#${levels[r].toString(16).padStart(2, "0")}${levels[g].toString(16).padStart(2, "0")}${levels[b].toString(16).padStart(2, "0")}`); + } + } + } + return colors; + })(), + // Grayscale (232-255) + ...(() => { + const grays = []; + for (let i = 0; i < 24; i++) { + const level = 8 + i * 10; + const hex = level.toString(16).padStart(2, "0"); + grays.push(`#${hex}${hex}${hex}`); + } + return grays; + })(), + ], + + // Get color from palette by index + getColor(index) { + if (index >= 0 && index < this.palette.length) { + return this.palette[index]; + } + return null; + }, + + // Parse ANSI escape sequence and return style object + parseEscapeCode(code) { + const style = {}; + const parts = code.split(";"); + + let i = 0; + while (i < parts.length) { + const num = parseInt(parts[i], 10); + + // Reset + if (num === 0 || num === "m" || isNaN(num)) { + style.reset = true; + i++; + continue; + } + + // 256-color foreground: 38;5;N + if (num === 38 && parts[i + 1] === "5") { + const colorIndex = parseInt(parts[i + 2], 10); + const color = this.getColor(colorIndex); + if (color) { + style.fg = color; + } + i += 3; + continue; + } + + // 256-color background: 48;5;N + if (num === 48 && parts[i + 1] === "5") { + const colorIndex = parseInt(parts[i + 2], 10); + const color = this.getColor(colorIndex); + if (color) { + style.bg = color; + } + i += 3; + continue; + } + + // Standard foreground colors (30-37) + if (num >= 30 && num <= 37) { + style.fg = this.palette[num - 30]; + i++; + continue; + } + + // Bright foreground colors (90-97) + if (num >= 90 && num <= 97) { + style.fg = this.palette[num - 90 + 8]; + i++; + continue; + } + + // Standard background colors (40-47) + if (num >= 40 && num <= 47) { + style.bg = this.palette[num - 40]; + i++; + continue; + } + + // Bright background colors (100-107) + if (num >= 100 && num <= 107) { + style.bg = this.palette[num - 100 + 8]; + i++; + continue; + } + + // Bold (1) - we'll treat as bright + if (num === 1) { + style.bold = true; + i++; + continue; + } + + i++; + } + + return style; + }, + + // Convert ANSI string to HTML + convert(ansiString) { + // Regex to match ANSI escape sequences + // Matches: \e[...m or \x1b[...m or actual escape character + const ansiRegex = /(?:\x1b|\u001b|\\e|\\x1b)\[([0-9;]*)m/g; + + let html = ""; + let currentFg = null; + let currentBg = null; + let lastIndex = 0; + let spanOpen = false; + + // Replace escaped representations with actual escape character for easier processing + let processed = ansiString + .replace(/\\e/g, "\x1b") + .replace(/\\x1b/g, "\x1b"); + + let match; + while ((match = ansiRegex.exec(processed)) !== null) { + // Add text before this escape sequence + const textBefore = processed.slice(lastIndex, match.index); + if (textBefore) { + html += this.escapeHtml(textBefore); + } + + // Parse the escape code + const style = this.parseEscapeCode(match[1]); + + // Close previous span if needed + if (spanOpen) { + html += ""; + spanOpen = false; + } + + // Handle reset + if (style.reset) { + currentFg = null; + currentBg = null; + } + + // Update current colors + if (style.fg) currentFg = style.fg; + if (style.bg) currentBg = style.bg; + + // Open new span if we have colors + if (currentFg || currentBg) { + const styles = []; + if (currentFg) styles.push(`color:${currentFg}`); + if (currentBg) styles.push(`background-color:${currentBg}`); + html += ``; + spanOpen = true; + } + + lastIndex = match.index + match[0].length; + } + + // Add remaining text + const remaining = processed.slice(lastIndex); + if (remaining) { + html += this.escapeHtml(remaining); + } + + // Close any open span + if (spanOpen) { + html += ""; + } + + return html; + }, + + // Escape HTML special characters (but preserve our spans) + escapeHtml(text) { + return text + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """); + }, + + // Convert and wrap in a pre-formatted container for proper display + convertToBlock(ansiString, className = "ansi-art") { + const html = this.convert(ansiString); + return `
${html}
`; + }, +}; + +// Export for use in other modules +if (typeof window !== "undefined") { + window.AnsiConverter = AnsiConverter; +} diff --git a/assets/js/games/engine/game-engine.js b/assets/js/games/engine/game-engine.js new file mode 100644 index 0000000..2b03baf --- /dev/null +++ b/assets/js/games/engine/game-engine.js @@ -0,0 +1,210 @@ +// Game Engine - Main orchestrator for terminal games +class GameEngine { + constructor(gameDefinition) { + this.definition = gameDefinition; + this.terminal = null; + this.adapter = null; + this.state = null; + this.input = null; + this.scenes = null; + + this.isRunning = false; + this.originalExecuteCommand = null; + } + + // Register this game as a terminal command + register() { + if (!window.terminal) { + console.warn("Terminal not available, cannot register game:", this.definition.id); + return; + } + + this.terminal = window.terminal; + const self = this; + const def = this.definition; + + this.terminal.registerCommand( + def.command || def.id, + def.description || `Play ${def.name}`, + async (args) => { + if (args[0] === "reset") { + self._reset(); + return; + } + if (args[0] === "continue" || args[0] === "resume") { + await self.start(true); + return; + } + await self.start(); + }, + ); + } + + // Start the game + async start(continueGame = false) { + if (this.isRunning) { + this.terminal.printWarning("Game is already running!"); + return; + } + + this.isRunning = true; + + // Initialize components + this.adapter = new TerminalAdapter(this.terminal); + this.state = new StateManager(this.definition.id); + this.input = new InputManager(this.adapter); + this.scenes = new SceneManager(this.adapter, this.state, this.input); + + // Initialize state + this.state.init(this.definition.initialState || {}); + + // Register scenes + this.scenes.registerScenes(this.definition.scenes); + + // Hook into terminal input + this._hookInput(); + + // Check for existing save + const hasSave = this.state.hasSavedState(); + + // Show intro unless continuing + if (!continueGame && !hasSave) { + if (this.definition.intro) { + await this._playIntro(); + } + } else if (hasSave && !continueGame) { + // Ask if player wants to continue + this.adapter.clear(); + this.adapter.printInfo("A saved game was found."); + const shouldContinue = await this.input.awaitConfirm("Continue from where you left off?"); + + if (!shouldContinue) { + this.state.reset(); + this.state.init(this.definition.initialState || {}); + if (this.definition.intro) { + await this._playIntro(); + } + } + } + + // Go to start scene (or last scene if continuing) + const startScene = this.state.get("_currentScene") || this.definition.startScene || "start"; + await this.scenes.goTo(startScene); + } + + async _playIntro() { + this.adapter.clear(); + + // Show title + if (this.definition.name) { + this.adapter.printHTML( + `
${this.definition.name}
`, + ); + this.adapter.print(""); + } + + // Show intro content + if (typeof this.definition.intro === "string") { + this.adapter.print(this.definition.intro); + } else if (Array.isArray(this.definition.intro)) { + await this.scenes._renderContent(this.definition.intro); + } + + this.adapter.print(""); + + // Wait for input to continue + await this.input.awaitOption( + [{ text: "Begin", value: true }], + "Press Enter or 1 to start...", + ); + } + + _hookInput() { + // Store original executeCommand + this.originalExecuteCommand = this.terminal.executeCommand.bind(this.terminal); + + // Override to intercept input + const self = this; + this.terminal.executeCommand = function (commandString) { + if (!self.isRunning) { + return self.originalExecuteCommand(commandString); + } + + // Check for exit commands + const cmd = commandString.toLowerCase().trim(); + if (cmd === "quit" || cmd === "exit" || cmd === "q") { + self.stop(); + return; + } + + // Check for save command + if (cmd === "save") { + self._saveProgress(); + return; + } + + // Pass to input manager if in text mode + if (self.input.getMode() === "text") { + self.input.handleTextInput(commandString); + } + }; + } + + _saveProgress() { + // Save current scene for resuming + const currentSceneId = this.scenes.getCurrentSceneId(); + if (currentSceneId) { + this.state.set("_currentScene", currentSceneId); + } + this.adapter.printSuccess("Game progress saved."); + } + + // Stop the game and restore terminal + stop() { + // Save progress before stopping + this._saveProgress(); + + this.isRunning = false; + + // Cleanup input manager + if (this.input) { + this.input.destroy(); + } + + // Restore original command handler + if (this.originalExecuteCommand) { + this.terminal.executeCommand = this.originalExecuteCommand; + } + + this.adapter.print(""); + this.adapter.printInfo(`Exited ${this.definition.name}. Progress saved.`); + this.adapter.print('Type "help" for available commands.'); + this.adapter.print( + `Type "${this.definition.command || this.definition.id}" to continue playing.`, + ); + } + + _reset() { + if (this.state) { + this.state.reset(); + } else { + // Create temporary state manager just to reset + const tempState = new StateManager(this.definition.id); + tempState.reset(); + } + this.terminal.printSuccess(`${this.definition.name} progress has been reset.`); + } + + // Check if game is currently running + isActive() { + return this.isRunning; + } + + // Get game definition + getDefinition() { + return this.definition; + } +} + +// Make available globally +window.GameEngine = GameEngine; diff --git a/assets/js/games/engine/input-manager.js b/assets/js/games/engine/input-manager.js new file mode 100644 index 0000000..26658ed --- /dev/null +++ b/assets/js/games/engine/input-manager.js @@ -0,0 +1,205 @@ +// Input Manager - Handles text input and option selection modes +class InputManager { + constructor(adapter) { + this.adapter = adapter; + this.mode = "idle"; // "idle" | "text" | "options" + this.options = []; + this.selectedIndex = 0; + this.inputResolve = null; + this.optionsContainerId = "game-options-" + Date.now(); + this.keydownHandler = null; + + this._setupKeyboardListener(); + } + + _setupKeyboardListener() { + this.keydownHandler = (e) => { + if (this.mode !== "options") return; + + if (e.key === "ArrowUp") { + e.preventDefault(); + e.stopPropagation(); + this._selectPrevious(); + } else if (e.key === "ArrowDown") { + e.preventDefault(); + e.stopPropagation(); + this._selectNext(); + } else if (e.key === "Enter") { + e.preventDefault(); + e.stopPropagation(); + this._confirmSelection(); + } else if (/^[1-9]$/.test(e.key)) { + const index = parseInt(e.key) - 1; + if (index < this.options.length) { + e.preventDefault(); + e.stopPropagation(); + this.selectedIndex = index; + this._confirmSelection(); + } + } + }; + + document.addEventListener("keydown", this.keydownHandler, true); + } + + // Text input mode - wait for user to type something + awaitText(prompt = "") { + return new Promise((resolve) => { + if (prompt) { + this.adapter.printInfo(prompt); + } + + this.mode = "text"; + this.inputResolve = resolve; + + this.adapter.captureInput((value) => { + this.adapter.print(`> ${value}`); + this.mode = "idle"; + this.adapter.releaseInput(); + if (this.inputResolve) { + const res = this.inputResolve; + this.inputResolve = null; + res(value); + } + }); + + this.adapter.focusInput(); + }); + } + + // Options selection mode - display choices and wait for selection + awaitOption(options, prompt = "") { + return new Promise((resolve) => { + this.mode = "options"; + this.options = options; + this.selectedIndex = 0; + this.optionsContainerId = "game-options-" + Date.now(); + + if (prompt) { + this.adapter.print(""); + this.adapter.printInfo(prompt); + } + this.adapter.print(""); + + this._renderOptions(); + + this.inputResolve = (index) => { + this.mode = "idle"; + resolve({ + index, + option: this.options[index], + }); + }; + }); + } + + // Yes/No confirmation + awaitConfirm(prompt = "Continue?") { + return this.awaitOption( + [ + { text: "Yes", value: true }, + { text: "No", value: false }, + ], + prompt, + ).then((result) => result.option.value); + } + + // Handle text input from game engine + handleTextInput(value) { + if (this.mode === "text" && this.inputResolve) { + this.adapter.print(`> ${value}`); + this.mode = "idle"; + const res = this.inputResolve; + this.inputResolve = null; + res(value); + } + } + + _renderOptions() { + const optionsHTML = this.options + .map((opt, idx) => { + const selected = idx === this.selectedIndex; + const prefix = selected ? ">" : " "; + const className = selected ? "game-option-selected" : "game-option"; + const text = typeof opt === "string" ? opt : opt.text; + return `
${prefix} ${idx + 1}. ${text}
`; + }) + .join(""); + + this.adapter.printHTML( + `
${optionsHTML}
`, + ); + this.adapter.scrollToBottom(); + } + + _updateOptionsDisplay() { + const container = document.getElementById(this.optionsContainerId); + if (!container) return; + + const optionDivs = container.querySelectorAll("div"); + optionDivs.forEach((div, idx) => { + const selected = idx === this.selectedIndex; + div.className = selected ? "game-option-selected" : "game-option"; + const text = + typeof this.options[idx] === "string" + ? this.options[idx] + : this.options[idx].text; + div.textContent = `${selected ? ">" : " "} ${idx + 1}. ${text}`; + }); + } + + _selectPrevious() { + if (this.selectedIndex > 0) { + this.selectedIndex--; + this._updateOptionsDisplay(); + } + } + + _selectNext() { + if (this.selectedIndex < this.options.length - 1) { + this.selectedIndex++; + this._updateOptionsDisplay(); + } + } + + _confirmSelection() { + if (this.inputResolve) { + const res = this.inputResolve; + this.inputResolve = null; + + // Show what was selected + const selectedOpt = this.options[this.selectedIndex]; + const text = + typeof selectedOpt === "string" ? selectedOpt : selectedOpt.text; + this.adapter.print(""); + this.adapter.printSuccess(`> ${text}`); + + res(this.selectedIndex); + } + } + + // Check if currently waiting for input + isWaiting() { + return this.mode !== "idle"; + } + + // Get current mode + getMode() { + return this.mode; + } + + // Cancel current input (for cleanup) + cancel() { + this.mode = "idle"; + this.inputResolve = null; + this.adapter.releaseInput(); + } + + // Cleanup when game ends + destroy() { + if (this.keydownHandler) { + document.removeEventListener("keydown", this.keydownHandler, true); + } + this.cancel(); + } +} diff --git a/assets/js/games/engine/scene-manager.js b/assets/js/games/engine/scene-manager.js new file mode 100644 index 0000000..4a96ec5 --- /dev/null +++ b/assets/js/games/engine/scene-manager.js @@ -0,0 +1,367 @@ +// Scene Manager - Handles scene definitions, rendering, and transitions +class SceneManager { + constructor(adapter, stateManager, inputManager) { + this.adapter = adapter; + this.state = stateManager; + this.input = inputManager; + this.scenes = {}; + this.currentScene = null; + this.sceneHistory = []; + } + + // Register scenes from game definition + registerScenes(sceneDefinitions) { + for (const [id, scene] of Object.entries(sceneDefinitions)) { + this.scenes[id] = { id, ...scene }; + } + } + + // Get scene by ID + getScene(sceneId) { + return this.scenes[sceneId]; + } + + // Navigate to a scene + async goTo(sceneId, options = {}) { + const scene = this.getScene(sceneId); + if (!scene) { + this.adapter.printError(`Scene not found: ${sceneId}`); + return; + } + + // Track history for back navigation + if (this.currentScene && !options.noHistory) { + this.sceneHistory.push(this.currentScene.id); + } + + this.currentScene = scene; + + // Execute onEnter actions + if (scene.onEnter) { + await this._executeActions(scene.onEnter); + } + + // Render the scene + await this._renderScene(scene); + } + + // Go back to previous scene + async goBack() { + if (this.sceneHistory.length === 0) { + this.adapter.printWarning("No previous scene"); + return; + } + const previousId = this.sceneHistory.pop(); + await this.goTo(previousId, { noHistory: true }); + } + + // Render a scene + async _renderScene(scene) { + // Clear screen unless disabled + if (scene.clear !== false) { + this.adapter.clear(); + } + + // Render title + if (scene.title) { + this.adapter.printHTML( + `${scene.title}`, + ); + this.adapter.print(""); + } + + // Render content blocks + if (scene.content) { + await this._renderContent(scene.content); + } + + // Handle navigation + if (scene.options) { + await this._handleOptions(scene); + } else if (scene.input) { + await this._handleTextInput(scene); + } else if (scene.next) { + // Auto-advance with optional delay + const delay = scene.delay || 0; + if (delay > 0) { + await this._sleep(delay); + } + await this.goTo(scene.next); + } + } + + // Render content blocks (supports conditional content) + async _renderContent(content) { + const blocks = Array.isArray(content) ? content : [content]; + + for (const block of blocks) { + // Simple string + if (typeof block === "string") { + this._printText(block); + continue; + } + + // Conditional block + if (block.condition !== undefined) { + if (this.state.evaluate(block.condition)) { + if (block.content) { + await this._renderContent(block.content); + } + } else if (block.else) { + await this._renderContent(block.else); + } + continue; + } + + // Typed blocks + if (block.type === "ascii") { + this.adapter.print(block.art, block.className || "game-ascii"); + continue; + } + + if (block.type === "ansi") { + // Convert ANSI escape codes to HTML + const html = AnsiConverter.convertToBlock( + block.art, + block.className || "game-ansi-art", + ); + this.adapter.printHTML(html); + continue; + } + + if (block.type === "html") { + this.adapter.printHTML(block.html, block.className || ""); + continue; + } + + if (block.type === "delay") { + await this._sleep(block.ms || 1000); + continue; + } + + if (block.type === "typewriter") { + await this._typewriter(block.text, block.speed || 50); + continue; + } + + // Text with optional className + if (block.text !== undefined) { + this._printText(block.text, block.className || ""); + continue; + } + } + } + + // Print text with variable interpolation + _printText(text, className = "") { + // Support ${path} interpolation + const interpolated = text.replace(/\$\{([^}]+)\}/g, (match, path) => { + const value = this.state.get(path); + return value !== undefined ? String(value) : match; + }); + + if (className) { + this.adapter.print(interpolated, className); + } else { + this.adapter.print(interpolated); + } + } + + // Handle options/choices + async _handleOptions(scene) { + // Filter options based on conditions + const availableOptions = scene.options.filter((opt) => { + if (opt.condition !== undefined) { + return this.state.evaluate(opt.condition); + } + return true; + }); + + if (availableOptions.length === 0) { + if (scene.fallback) { + await this.goTo(scene.fallback); + } + return; + } + + const result = await this.input.awaitOption( + availableOptions.map((o) => ({ + text: this._interpolateText(o.text), + value: o, + })), + scene.prompt || "What do you do?", + ); + + const selected = result.option.value; + + // Execute option actions + if (selected.actions) { + await this._executeActions(selected.actions); + } + + // Navigate to next scene + if (selected.next) { + await this.goTo(selected.next); + } + } + + // Handle text input + async _handleTextInput(scene) { + const inputDef = scene.input; + const value = await this.input.awaitText(inputDef.prompt); + + // Store value if specified + if (inputDef.store) { + this.state.set(inputDef.store, value); + } + + // Check for pattern matches + if (inputDef.matches) { + for (const match of inputDef.matches) { + const pattern = + match.pattern instanceof RegExp + ? match.pattern + : new RegExp(match.pattern, "i"); + + if (pattern.test(value)) { + if (match.actions) { + await this._executeActions(match.actions); + } + if (match.next) { + await this.goTo(match.next); + } + return; + } + } + } + + // No match - use default + if (inputDef.default) { + if (inputDef.default.actions) { + await this._executeActions(inputDef.default.actions); + } + if (inputDef.default.next) { + await this.goTo(inputDef.default.next); + } + } else if (inputDef.next) { + await this.goTo(inputDef.next); + } + } + + // Execute action commands + async _executeActions(actions) { + const actionList = Array.isArray(actions) ? actions : [actions]; + + for (const action of actionList) { + if (action.set !== undefined) { + this.state.set(action.set, action.value); + } + + if (action.increment !== undefined) { + this.state.increment(action.increment, action.amount || 1); + } + + if (action.decrement !== undefined) { + this.state.decrement(action.decrement, action.amount || 1); + } + + if (action.addItem !== undefined) { + this.state.addToArray(action.to || "inventory", action.addItem); + } + + if (action.removeItem !== undefined) { + this.state.removeFromArray( + action.from || "inventory", + action.removeItem, + ); + } + + if (action.print !== undefined) { + this._printText(action.print, action.className || ""); + } + + if (action.printSuccess !== undefined) { + this.adapter.printSuccess(this._interpolateText(action.printSuccess)); + } + + if (action.printError !== undefined) { + this.adapter.printError(this._interpolateText(action.printError)); + } + + if (action.printWarning !== undefined) { + this.adapter.printWarning(this._interpolateText(action.printWarning)); + } + + if (action.printInfo !== undefined) { + this.adapter.printInfo(this._interpolateText(action.printInfo)); + } + + if (action.delay !== undefined) { + await this._sleep(action.delay); + } + + if (action.goTo !== undefined) { + await this.goTo(action.goTo); + return; // Stop processing further actions after navigation + } + + if (action.callback && typeof action.callback === "function") { + await action.callback(this.state, this.adapter, this); + } + } + } + + // Interpolate variables in text + _interpolateText(text) { + if (typeof text !== "string") return text; + return text.replace(/\$\{([^}]+)\}/g, (match, path) => { + const value = this.state.get(path); + return value !== undefined ? String(value) : match; + }); + } + + // Typewriter effect + async _typewriter(text, speed) { + const interpolated = this._interpolateText(text); + let output = ""; + + for (const char of interpolated) { + output += char; + // Create a single updating line for typewriter + const typewriterSpan = + this.adapter.terminal.output.querySelector(".typewriter-line"); + + if (typewriterSpan) { + typewriterSpan.textContent = output; + } else { + this.adapter.printHTML( + `${output}`, + ); + } + + this.adapter.scrollToBottom(); + await this._sleep(speed); + } + + // Finalize the line - remove the typewriter-line class so future typewriters create new lines + const typewriterSpan = + this.adapter.terminal.output.querySelector(".typewriter-line"); + if (typewriterSpan) { + typewriterSpan.classList.remove("typewriter-line"); + } + } + + _sleep(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)); + } + + // Get current scene ID + getCurrentSceneId() { + return this.currentScene ? this.currentScene.id : null; + } + + // Reset scene history + resetHistory() { + this.sceneHistory = []; + } +} diff --git a/assets/js/games/engine/state-manager.js b/assets/js/games/engine/state-manager.js new file mode 100644 index 0000000..7d25f41 --- /dev/null +++ b/assets/js/games/engine/state-manager.js @@ -0,0 +1,208 @@ +// State Manager - Manages game state with persistence and conditions +class StateManager { + constructor(gameId) { + this.gameId = gameId; + this.state = {}; + this.storageKey = `game_${gameId}_state`; + } + + // Initialize with default state + init(defaultState = {}) { + this.state = this._deepClone(defaultState); + this._loadFromStorage(); + } + + // Get value using dot notation path (e.g., "inventory.sword") + get(path, defaultValue = undefined) { + const parts = path.split("."); + let current = this.state; + + for (const part of parts) { + if (current === undefined || current === null) { + return defaultValue; + } + current = current[part]; + } + + return current !== undefined ? current : defaultValue; + } + + // Set value using dot notation path + set(path, value) { + const parts = path.split("."); + let current = this.state; + + for (let i = 0; i < parts.length - 1; i++) { + const part = parts[i]; + if (current[part] === undefined) { + current[part] = {}; + } + current = current[part]; + } + + current[parts[parts.length - 1]] = value; + this._saveToStorage(); + } + + // Increment a numeric value + increment(path, amount = 1) { + const current = this.get(path, 0); + this.set(path, current + amount); + } + + // Decrement a numeric value + decrement(path, amount = 1) { + this.increment(path, -amount); + } + + // Add item to an array (if not already present) + addToArray(path, item) { + const arr = this.get(path, []); + if (!arr.includes(item)) { + arr.push(item); + this.set(path, arr); + } + } + + // Remove item from an array + removeFromArray(path, item) { + const arr = this.get(path, []); + const index = arr.indexOf(item); + if (index > -1) { + arr.splice(index, 1); + this.set(path, arr); + } + } + + // Check if array contains item + hasItem(path, item) { + const arr = this.get(path, []); + return arr.includes(item); + } + + // Evaluate a condition against current state + evaluate(condition) { + if (typeof condition === "boolean") { + return condition; + } + + if (typeof condition === "string") { + // Simple path check - truthy value + return !!this.get(condition); + } + + if (typeof condition === "object" && condition !== null) { + return this._evaluateConditionObject(condition); + } + + return true; + } + + _evaluateConditionObject(cond) { + // Logical operators + if (cond.and) { + return cond.and.every((c) => this.evaluate(c)); + } + if (cond.or) { + return cond.or.some((c) => this.evaluate(c)); + } + if (cond.not) { + return !this.evaluate(cond.not); + } + + // Value comparisons + const value = this.get(cond.path); + + if ("equals" in cond) { + return value === cond.equals; + } + if ("notEquals" in cond) { + return value !== cond.notEquals; + } + if ("greaterThan" in cond) { + return value > cond.greaterThan; + } + if ("greaterThanOrEqual" in cond) { + return value >= cond.greaterThanOrEqual; + } + if ("lessThan" in cond) { + return value < cond.lessThan; + } + if ("lessThanOrEqual" in cond) { + return value <= cond.lessThanOrEqual; + } + if ("contains" in cond) { + return Array.isArray(value) && value.includes(cond.contains); + } + if ("notContains" in cond) { + return !Array.isArray(value) || !value.includes(cond.notContains); + } + + // Default: check truthiness of path + return !!value; + } + + // Get entire state (for debugging) + getAll() { + return this._deepClone(this.state); + } + + // Reset state and clear storage + reset() { + this.state = {}; + try { + localStorage.removeItem(this.storageKey); + } catch (e) { + console.warn("Failed to clear game state from storage:", e); + } + } + + // Check if there is saved state + hasSavedState() { + try { + return localStorage.getItem(this.storageKey) !== null; + } catch (e) { + return false; + } + } + + _saveToStorage() { + try { + localStorage.setItem(this.storageKey, JSON.stringify(this.state)); + } catch (e) { + console.warn("Failed to save game state:", e); + } + } + + _loadFromStorage() { + try { + const saved = localStorage.getItem(this.storageKey); + if (saved) { + const parsed = JSON.parse(saved); + this.state = this._mergeDeep(this.state, parsed); + } + } catch (e) { + console.warn("Failed to load game state:", e); + } + } + + _deepClone(obj) { + return JSON.parse(JSON.stringify(obj)); + } + + _mergeDeep(target, source) { + const result = { ...target }; + for (const key of Object.keys(source)) { + if ( + source[key] && + typeof source[key] === "object" && + !Array.isArray(source[key]) + ) { + result[key] = this._mergeDeep(result[key] || {}, source[key]); + } else { + result[key] = source[key]; + } + } + return result; + } +} diff --git a/assets/js/games/engine/terminal-adapter.js b/assets/js/games/engine/terminal-adapter.js new file mode 100644 index 0000000..5b414b3 --- /dev/null +++ b/assets/js/games/engine/terminal-adapter.js @@ -0,0 +1,70 @@ +// Terminal Adapter - Bridges game engine to existing TerminalShell +class TerminalAdapter { + constructor(terminal) { + this.terminal = terminal; + this.inputCallback = null; + } + + // Output methods - delegate to terminal + print(text, className = "") { + this.terminal.print(text, className); + } + + printHTML(html, className = "") { + this.terminal.printHTML(html, className); + } + + printError(text) { + this.terminal.printError(text); + } + + printSuccess(text) { + this.terminal.printSuccess(text); + } + + printInfo(text) { + this.terminal.printInfo(text); + } + + printWarning(text) { + this.terminal.printWarning(text); + } + + clear() { + this.terminal.clear(); + } + + scrollToBottom() { + this.terminal.scrollToBottom(); + } + + // Input capture - allows game to intercept terminal input + captureInput(callback) { + this.inputCallback = callback; + } + + releaseInput() { + this.inputCallback = null; + } + + // Called by game engine when input is received + handleInput(value) { + if (this.inputCallback) { + this.inputCallback(value); + return true; + } + return false; + } + + // Get the input element for focus management + getInputElement() { + return this.terminal.input; + } + + // Focus the input + focusInput() { + if (this.terminal.input) { + this.terminal.input.focus(); + } + } +} diff --git a/assets/js/games/games/boxing-day.js b/assets/js/games/games/boxing-day.js new file mode 100644 index 0000000..05b0ac8 --- /dev/null +++ b/assets/js/games/games/boxing-day.js @@ -0,0 +1,1457 @@ +// Boxing Day - Day 1: December 26, 1999 +// A BBS-themed mystery game + +// ASCII Art Constants +const BOXING_DAY_TITLE = ` + ____ _____ ___ __ ____ _ __________ ____ _____ __ __ + / __ )/ __ \\ \\/ / / // / / / | / / ____/ / / __ \\/ ___// / / / + / __ / / / /\\ / / // /_/ / |/ / / __/ / / / / /\\__ \\/ /_/ / + / /_/ / /_/ / / / /__ __/ /| / /_/ / /___/ /_/ /___/ / __ / +/_____/\\____/ /_/ /_/ /_/ |_/\\____/_____/_____//____/_/ /_/ +`; + +const DARK_TOWER_HEADER = `e[38;5;0;48;5;0m▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄▄▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄▄▄▄▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄▄▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[m +\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄▄\e[48;5;0m \e[38;5;0;48;5;17m▄\e[38;5;0;48;5;0m▄▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄\e[38;5;17;48;5;18m▄\e[38;5;0;48;5;17m▄\e[38;5;0;48;5;4m▄\e[38;5;0;48;5;0m▄▄▄▄▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[m +\e[48;5;0m \e[38;5;240;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄\e[48;5;0m \e[38;5;234;48;5;0m▄▄\e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄\e[38;5;235;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄▄▄▄▄▄▄▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄▄▄▄▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄▄▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[38;5;19;48;5;0m▄\e[38;5;4;48;5;0m▄\e[38;5;232;48;5;0m▄▄\e[38;5;17;48;5;0m▄\e[38;5;0;48;5;0m▄\e[38;5;0;48;5;17m▄\e[38;5;17;48;5;0m▄▄\e[38;5;0;48;5;0m▄\e[38;5;242;48;5;240m▄\e[38;5;17;48;5;0m▄\e[38;5;4;48;5;17m▄\e[38;5;18;48;5;4m▄\e[38;5;18;48;5;19m▄\e[38;5;18;48;5;4m▄\e[38;5;4;48;5;4m▄\e[38;5;0;48;5;17m▄\e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄▄▄▄▄\e[48;5;0m \e[m +\e[48;5;0m \e[38;5;0;48;5;0m▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[38;5;232;48;5;0m▄\e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[38;5;232;48;5;0m▄\e[38;5;233;48;5;232m▄\e[38;5;232;48;5;0m▄\e[38;5;0;48;5;0m▄▄\e[38;5;0;48;5;232m▄\e[38;5;232;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄▄▄▄▄▄\e[48;5;0m \e[38;5;4;48;5;0m▄\e[38;5;0;48;5;0m▄▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄\e[38;5;232;48;5;0m▄\e[38;5;238;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄\e[38;5;17;48;5;0m▄\e[38;5;0;48;5;0m▄\e[38;5;0;48;5;232m▄\e[38;5;0;48;5;17m▄\e[38;5;17;48;5;0m▄\e[38;5;4;48;5;0m▄\e[38;5;232;48;5;17m▄\e[38;5;17;48;5;0m▄\e[38;5;20;48;5;4m▄\e[38;5;18;48;5;19m▄\e[38;5;17;48;5;17m▄\e[48;5;0m \e[38;5;239;48;5;234m▄\e[48;5;0m \e[38;5;4;48;5;17m▄\e[38;5;20;48;5;18m▄\e[38;5;18;48;5;18m▄▄\e[38;5;19;48;5;0m▄▄\e[38;5;18;48;5;0m▄\e[38;5;0;48;5;0m▄▄\e[48;5;0m \e[38;5;232;48;5;0m▄\e[48;5;0m \e[38;5;232;48;5;0m▄\e[48;5;0m \e[m +\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;233m▄▄\e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;233m▄\e[38;5;235;48;5;236m▄\e[38;5;235;48;5;237m▄\e[48;5;0m \e[38;5;235;48;5;233m▄\e[38;5;0;48;5;0m▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[38;5;232;48;5;0m▄\e[38;5;52;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄▄\e[38;5;17;48;5;0m▄\e[38;5;17;48;5;17m▄\e[38;5;17;48;5;19m▄\e[38;5;19;48;5;17m▄\e[38;5;19;48;5;0m▄\e[38;5;20;48;5;19m▄\e[38;5;20;48;5;0m▄▄\e[38;5;0;48;5;0m▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[38;5;0;48;5;232m▄▄\e[38;5;0;48;5;0m▄▄\e[38;5;0;48;5;234m▄\e[38;5;0;48;5;0m▄\e[38;5;238;48;5;0m▄\e[38;5;0;48;5;0m▄\e[38;5;0;48;5;232m▄\e[48;5;0m \e[38;5;186;48;5;232m▄\e[38;5;0;48;5;233m▄\e[38;5;0;48;5;17m▄▄\e[38;5;0;48;5;233m▄\e[38;5;0;48;5;0m▄▄▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄▄▄▄▄▄\e[38;5;17;48;5;17m▄\e[38;5;19;48;5;18m▄\e[38;5;18;48;5;18m▄\e[38;5;0;48;5;0m▄▄\e[38;5;18;48;5;18m▄\e[38;5;19;48;5;18m▄\e[38;5;4;48;5;18m▄\e[38;5;17;48;5;18m▄\e[38;5;17;48;5;19m▄\e[38;5;19;48;5;19m▄\e[38;5;19;48;5;18m▄\e[38;5;18;48;5;19m▄\e[48;5;0m \e[38;5;241;48;5;59m▄\e[48;5;0m \e[38;5;0;48;5;17m▄\e[38;5;20;48;5;19m▄\e[38;5;17;48;5;19m▄\e[38;5;4;48;5;0m▄\e[38;5;0;48;5;17m▄\e[38;5;17;48;5;18m▄\e[38;5;0;48;5;19m▄\e[38;5;0;48;5;18m▄\e[38;5;18;48;5;19m▄\e[38;5;0;48;5;0m▄\e[38;5;60;48;5;232m▄\e[38;5;0;48;5;0m▄▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄\e[38;5;0;48;5;17m▄\e[38;5;0;48;5;232m▄\e[48;5;0m \e[m +\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄▄▄▄\e[38;5;0;48;5;87m▄\e[48;5;0m \e[38;5;234;48;5;0m▄\e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[38;5;0;48;5;232m▄\e[38;5;0;48;5;0m▄\e[38;5;0;48;5;234m▄\e[38;5;117;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;232m▄\e[38;5;232;48;5;0m▄\e[38;5;52;48;5;52m▄\e[38;5;233;48;5;0m▄\e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄\e[38;5;0;48;5;232m▄\e[38;5;0;48;5;0m▄▄▄\e[38;5;0;48;5;17m▄▄\e[38;5;0;48;5;0m▄\e[38;5;17;48;5;0m▄\e[38;5;0;48;5;19m▄\e[38;5;0;48;5;24m▄\e[38;5;0;48;5;19m▄▄\e[38;5;0;48;5;0m▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[38;5;233;48;5;0m▄\e[38;5;235;48;5;0m▄\e[48;5;0m \e[38;5;232;48;5;0m▄\e[38;5;0;48;5;0m▄▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[38;5;0;48;5;4m▄\e[38;5;0;48;5;17m▄\e[38;5;0;48;5;0m▄▄▄▄\e[38;5;232;48;5;0m▄\e[38;5;0;48;5;166m▄\e[38;5;0;48;5;202m▄\e[38;5;52;48;5;0m▄\e[38;5;0;48;5;130m▄\e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[38;5;17;48;5;0m▄\e[38;5;0;48;5;17m▄▄\e[38;5;18;48;5;18m▄\e[38;5;0;48;5;17m▄▄\e[38;5;17;48;5;17m▄\e[38;5;0;48;5;0m▄\e[38;5;18;48;5;17m▄\e[38;5;4;48;5;4m▄\e[38;5;19;48;5;4m▄\e[38;5;4;48;5;17m▄\e[38;5;0;48;5;17m▄\e[38;5;242;48;5;239m▄\e[38;5;241;48;5;243m▄\e[48;5;0m \e[38;5;18;48;5;17m▄\e[38;5;0;48;5;17m▄\e[38;5;17;48;5;19m▄\e[38;5;17;48;5;4m▄\e[38;5;0;48;5;17m▄\e[38;5;232;48;5;0m▄\e[38;5;23;48;5;0m▄\e[38;5;68;48;5;0m▄\e[38;5;232;48;5;0m▄\e[38;5;0;48;5;0m▄▄▄▄▄\e[48;5;0m \e[38;5;17;48;5;0m▄\e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[m +\e[48;5;0m \e[38;5;0;48;5;0m▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄\e[38;5;232;48;5;232m▄\e[38;5;18;48;5;17m▄\e[38;5;4;48;5;0m▄\e[38;5;18;48;5;0m▄\e[38;5;4;48;5;0m▄\e[38;5;17;48;5;0m▄\e[38;5;0;48;5;0m▄▄▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[38;5;0;48;5;233m▄\e[38;5;0;48;5;234m▄\e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[38;5;0;48;5;232m▄\e[38;5;0;48;5;0m▄\e[38;5;0;48;5;232m▄\e[38;5;232;48;5;232m▄\e[38;5;220;48;5;202m▄\e[38;5;52;48;5;0m▄\e[38;5;232;48;5;0m▄\e[38;5;52;48;5;232m▄\e[38;5;0;48;5;0m▄\e[38;5;4;48;5;0m▄\e[38;5;0;48;5;17m▄\e[38;5;0;48;5;0m▄\e[38;5;17;48;5;17m▄\e[38;5;0;48;5;17m▄▄\e[38;5;18;48;5;17m▄\e[38;5;0;48;5;0m▄\e[38;5;202;48;5;0m▄\e[38;5;0;48;5;235m▄\e[38;5;0;48;5;17m▄\e[38;5;0;48;5;232m▄\e[38;5;0;48;5;0m▄\e[38;5;4;48;5;0m▄\e[38;5;0;48;5;232m▄▄\e[38;5;17;48;5;232m▄\e[38;5;17;48;5;0m▄\e[38;5;232;48;5;232m▄\e[38;5;0;48;5;0m▄▄▄\e[38;5;232;48;5;232m▄\e[38;5;17;48;5;17m▄\e[38;5;232;48;5;0m▄\e[38;5;17;48;5;0m▄\e[38;5;20;48;5;19m▄\e[38;5;0;48;5;0m▄▄\e[38;5;0;48;5;18m▄\e[38;5;0;48;5;17m▄\e[38;5;0;48;5;0m▄▄▄\e[38;5;232;48;5;0m▄\e[38;5;0;48;5;232m▄\e[38;5;233;48;5;0m▄\e[38;5;232;48;5;52m▄\e[38;5;208;48;5;166m▄\e[38;5;124;48;5;233m▄\e[38;5;52;48;5;232m▄\e[38;5;0;48;5;0m▄▄\e[38;5;146;48;5;233m▄\e[38;5;17;48;5;19m▄\e[38;5;0;48;5;19m▄\e[38;5;18;48;5;17m▄\e[38;5;4;48;5;17m▄\e[38;5;18;48;5;17m▄\e[38;5;19;48;5;4m▄\e[38;5;20;48;5;18m▄\e[38;5;0;48;5;19m▄\e[38;5;12;48;5;19m▄\e[38;5;19;48;5;12m▄\e[38;5;0;48;5;0m▄\e[38;5;0;48;5;17m▄\e[38;5;102;48;5;8m▄\e[38;5;8;48;5;243m▄\e[48;5;0m \e[38;5;19;48;5;4m▄\e[38;5;8;48;5;251m▄\e[38;5;20;48;5;20m▄\e[38;5;19;48;5;20m▄\e[38;5;233;48;5;0m▄\e[38;5;0;48;5;17m▄\e[38;5;17;48;5;25m▄\e[38;5;17;48;5;4m▄\e[38;5;0;48;5;17m▄\e[38;5;0;48;5;0m▄▄\e[38;5;18;48;5;0m▄\e[38;5;4;48;5;0m▄\e[38;5;18;48;5;18m▄▄\e[38;5;17;48;5;17m▄\e[38;5;17;48;5;0m▄\e[38;5;232;48;5;0m▄\e[38;5;0;48;5;0m▄▄\e[48;5;0m \e[m +\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄\e[38;5;232;48;5;0m▄\e[38;5;0;48;5;0m▄▄▄▄\e[38;5;17;48;5;0m▄\e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;232;48;5;0m▄\e[38;5;0;48;5;232m▄▄\e[38;5;52;48;5;52m▄\e[38;5;160;48;5;9m▄\e[38;5;220;48;5;11m▄\e[38;5;232;48;5;0m▄\e[38;5;0;48;5;0m▄▄▄\e[38;5;0;48;5;17m▄\e[38;5;0;48;5;0m▄▄▄▄▄▄▄▄\e[38;5;248;48;5;0m▄\e[38;5;0;48;5;0m▄▄\e[38;5;0;48;5;17m▄\e[38;5;0;48;5;232m▄\e[38;5;0;48;5;0m▄▄\e[38;5;26;48;5;4m▄\e[38;5;17;48;5;18m▄\e[38;5;17;48;5;232m▄\e[38;5;17;48;5;0m▄▄▄\e[38;5;232;48;5;0m▄\e[38;5;18;48;5;0m▄\e[38;5;17;48;5;17m▄\e[38;5;17;48;5;4m▄\e[38;5;17;48;5;0m▄\e[38;5;0;48;5;0m▄▄▄\e[38;5;17;48;5;0m▄\e[38;5;17;48;5;17m▄\e[38;5;17;48;5;24m▄\e[38;5;232;48;5;0m▄\e[38;5;232;48;5;232m▄▄\e[38;5;250;48;5;1m▄\e[38;5;248;48;5;160m▄\e[38;5;11;48;5;11m▄\e[38;5;88;48;5;52m▄\e[38;5;0;48;5;0m▄▄▄\e[38;5;17;48;5;24m▄\e[38;5;19;48;5;18m▄\e[38;5;4;48;5;18m▄▄▄\e[38;5;20;48;5;20m▄\e[38;5;12;48;5;20m▄\e[38;5;27;48;5;12m▄\e[38;5;247;48;5;250m▄\e[38;5;57;48;5;12m▄\e[38;5;236;48;5;17m▄\e[38;5;241;48;5;238m▄\e[38;5;247;48;5;234m▄\e[38;5;102;48;5;241m▄\e[38;5;236;48;5;243m▄\e[38;5;59;48;5;237m▄\e[48;5;0m \e[38;5;243;48;5;243m▄\e[38;5;20;48;5;12m▄\e[38;5;61;48;5;20m▄\e[38;5;237;48;5;234m▄\e[38;5;19;48;5;4m▄\e[38;5;4;48;5;0m▄\e[38;5;19;48;5;17m▄\e[38;5;17;48;5;0m▄▄▄\e[38;5;0;48;5;0m▄▄\e[38;5;0;48;5;233m▄\e[38;5;0;48;5;0m▄▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[m +\e[48;5;0m \e[38;5;0;48;5;0m▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[38;5;0;48;5;17m▄\e[38;5;0;48;5;0m▄\e[38;5;17;48;5;0m▄\e[38;5;18;48;5;0m▄\e[38;5;17;48;5;0m▄\e[38;5;4;48;5;0m▄\e[38;5;0;48;5;0m▄▄\e[38;5;0;48;5;232m▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄▄\e[38;5;232;48;5;232m▄\e[38;5;233;48;5;232m▄\e[38;5;232;48;5;0m▄\e[38;5;52;48;5;52m▄\e[38;5;124;48;5;52m▄\e[38;5;124;48;5;160m▄\e[38;5;227;48;5;166m▄\e[38;5;227;48;5;11m▄\e[38;5;52;48;5;202m▄\e[38;5;52;48;5;232m▄\e[38;5;0;48;5;220m▄\e[38;5;0;48;5;88m▄\e[38;5;0;48;5;0m▄\e[38;5;0;48;5;88m▄\e[38;5;0;48;5;1m▄\e[38;5;0;48;5;52m▄\e[38;5;0;48;5;103m▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄\e[38;5;0;48;5;24m▄▄\e[38;5;0;48;5;0m▄▄▄▄\e[38;5;0;48;5;232m▄\e[48;5;0m \e[38;5;232;48;5;0m▄\e[38;5;17;48;5;0m▄\e[38;5;232;48;5;25m▄\e[38;5;0;48;5;145m▄\e[38;5;0;48;5;235m▄\e[38;5;17;48;5;17m▄\e[38;5;0;48;5;0m▄▄\e[38;5;0;48;5;232m▄\e[48;5;0m \e[38;5;17;48;5;0m▄\e[38;5;17;48;5;18m▄\e[38;5;0;48;5;17m▄\e[38;5;0;48;5;0m▄\e[38;5;0;48;5;232m▄\e[38;5;235;48;5;234m▄\e[38;5;1;48;5;52m▄\e[38;5;124;48;5;88m▄\e[38;5;166;48;5;160m▄\e[38;5;227;48;5;227m▄\e[38;5;227;48;5;220m▄\e[38;5;52;48;5;1m▄\e[38;5;0;48;5;232m▄\e[38;5;232;48;5;232m▄\e[38;5;233;48;5;0m▄\e[38;5;0;48;5;4m▄\e[38;5;17;48;5;4m▄\e[38;5;19;48;5;18m▄\e[38;5;19;48;5;4m▄\e[38;5;19;48;5;19m▄\e[38;5;20;48;5;4m▄\e[38;5;12;48;5;12m▄\e[38;5;235;48;5;253m▄\e[38;5;0;48;5;233m▄\e[38;5;242;48;5;102m▄\e[38;5;237;48;5;232m▄\e[38;5;102;48;5;0m▄\e[38;5;247;48;5;234m▄\e[38;5;232;48;5;232m▄\e[38;5;242;48;5;235m▄\e[38;5;235;48;5;0m▄\e[38;5;0;48;5;239m▄\e[38;5;0;48;5;232m▄\e[38;5;27;48;5;20m▄\e[38;5;233;48;5;234m▄\e[38;5;237;48;5;238m▄\e[38;5;19;48;5;19m▄\e[38;5;4;48;5;18m▄\e[38;5;17;48;5;4m▄\e[38;5;17;48;5;17m▄\e[38;5;0;48;5;17m▄▄\e[38;5;0;48;5;19m▄\e[38;5;0;48;5;0m▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[38;5;0;48;5;18m▄\e[38;5;0;48;5;0m▄\e[38;5;17;48;5;0m▄\e[48;5;0m \e[m +\e[48;5;0m \e[38;5;11;48;5;0m▄\e[38;5;0;48;5;0m▄\e[38;5;17;48;5;0m▄▄\e[38;5;0;48;5;0m▄\e[38;5;0;48;5;214m▄\e[38;5;17;48;5;0m▄\e[38;5;0;48;5;0m▄▄▄▄▄\e[48;5;0m \e[38;5;232;48;5;243m▄\e[48;5;0m \e[38;5;17;48;5;0m▄▄\e[38;5;232;48;5;0m▄\e[38;5;52;48;5;232m▄\e[38;5;233;48;5;232m▄\e[38;5;232;48;5;0m▄\e[38;5;232;48;5;232m▄\e[38;5;130;48;5;0m▄\e[38;5;172;48;5;232m▄\e[38;5;179;48;5;52m▄▄\e[38;5;227;48;5;1m▄\e[38;5;228;48;5;221m▄\e[38;5;230;48;5;228m▄\e[38;5;227;48;5;52m▄\e[38;5;172;48;5;0m▄▄▄\e[38;5;166;48;5;0m▄\e[38;5;1;48;5;0m▄\e[38;5;0;48;5;0m▄\e[38;5;52;48;5;0m▄\e[38;5;0;48;5;0m▄▄▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄▄▄▄▄▄▄▄▄\e[38;5;0;48;5;17m▄\e[38;5;17;48;5;17m▄\e[38;5;0;48;5;17m▄▄\e[38;5;0;48;5;0m▄\e[38;5;17;48;5;0m▄▄▄▄▄\e[38;5;0;48;5;0m▄▄▄\e[38;5;88;48;5;52m▄\e[38;5;124;48;5;124m▄\e[38;5;208;48;5;223m▄\e[38;5;229;48;5;9m▄\e[38;5;187;48;5;228m▄\e[38;5;221;48;5;88m▄\e[38;5;52;48;5;241m▄\e[38;5;234;48;5;234m▄\e[38;5;232;48;5;232m▄\e[38;5;232;48;5;0m▄\e[38;5;0;48;5;0m▄\e[38;5;17;48;5;0m▄\e[38;5;19;48;5;19m▄\e[38;5;20;48;5;19m▄\e[38;5;27;48;5;20m▄\e[38;5;27;48;5;19m▄\e[38;5;63;48;5;12m▄\e[38;5;251;48;5;251m▄\e[38;5;252;48;5;252m▄\e[38;5;0;48;5;0m▄\e[38;5;248;48;5;242m▄\e[38;5;232;48;5;236m▄\e[48;5;0m \e[38;5;232;48;5;237m▄\e[38;5;240;48;5;246m▄\e[38;5;235;48;5;236m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[38;5;233;48;5;0m▄\e[38;5;27;48;5;20m▄\e[38;5;20;48;5;17m▄\e[38;5;4;48;5;0m▄\e[38;5;25;48;5;0m▄\e[38;5;14;48;5;17m▄\e[38;5;4;48;5;0m▄\e[38;5;0;48;5;232m▄\e[38;5;234;48;5;0m▄\e[38;5;0;48;5;0m▄\e[38;5;17;48;5;0m▄\e[38;5;19;48;5;0m▄\e[38;5;0;48;5;0m▄▄▄\e[48;5;0m \e[m +\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[38;5;17;48;5;232m▄\e[38;5;0;48;5;17m▄▄\e[38;5;17;48;5;17m▄\e[38;5;0;48;5;4m▄\e[38;5;188;48;5;17m▄\e[38;5;0;48;5;0m▄▄▄\e[38;5;18;48;5;0m▄\e[38;5;0;48;5;233m▄\e[38;5;0;48;5;0m▄\e[38;5;4;48;5;0m▄\e[38;5;17;48;5;0m▄\e[38;5;18;48;5;232m▄\e[38;5;0;48;5;52m▄\e[38;5;4;48;5;0m▄\e[38;5;0;48;5;0m▄▄▄\e[48;5;0m \e[38;5;52;48;5;202m▄▄\e[38;5;227;48;5;202m▄\e[38;5;221;48;5;202m▄\e[38;5;187;48;5;202m▄\e[38;5;0;48;5;202m▄▄▄\e[38;5;94;48;5;202m▄\e[38;5;186;48;5;202m▄\e[38;5;228;48;5;220m▄\e[38;5;208;48;5;220m▄\e[38;5;220;48;5;0m▄\e[38;5;124;48;5;0m▄\e[38;5;124;48;5;52m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[38;5;208;48;5;17m▄\e[38;5;220;48;5;0m▄▄\e[38;5;11;48;5;0m▄▄▄\e[38;5;0;48;5;0m▄▄\e[48;5;0m \e[38;5;221;48;5;0m▄\e[38;5;11;48;5;0m▄▄\e[38;5;220;48;5;0m▄\e[38;5;208;48;5;0m▄\e[38;5;214;48;5;0m▄\e[38;5;220;48;5;0m▄▄\e[38;5;0;48;5;18m▄\e[38;5;0;48;5;19m▄▄\e[38;5;214;48;5;0m▄\e[38;5;220;48;5;0m▄▄\e[38;5;11;48;5;0m▄\e[38;5;227;48;5;0m▄\e[38;5;0;48;5;0m▄\e[38;5;17;48;5;232m▄\e[38;5;52;48;5;52m▄\e[38;5;227;48;5;227m▄\e[38;5;221;48;5;228m▄\e[38;5;202;48;5;228m▄\e[38;5;0;48;5;214m▄\e[38;5;0;48;5;202m▄\e[38;5;0;48;5;52m▄\e[48;5;0m \e[38;5;0;48;5;232m▄\e[38;5;19;48;5;17m▄\e[38;5;18;48;5;232m▄\e[38;5;12;48;5;20m▄\e[38;5;12;48;5;12m▄\e[38;5;12;48;5;20m▄\e[38;5;20;48;5;12m▄\e[38;5;33;48;5;27m▄\e[38;5;188;48;5;248m▄\e[38;5;255;48;5;254m▄\e[38;5;15;48;5;0m▄\e[38;5;254;48;5;251m▄\e[38;5;248;48;5;234m▄\e[38;5;8;48;5;0m▄\e[38;5;238;48;5;237m▄\e[38;5;243;48;5;242m▄\e[38;5;238;48;5;235m▄\e[38;5;0;48;5;232m▄\e[38;5;238;48;5;0m▄\e[38;5;60;48;5;0m▄▄\e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;27;48;5;27m▄\e[38;5;19;48;5;12m▄\e[38;5;20;48;5;19m▄\e[38;5;12;48;5;19m▄\e[38;5;12;48;5;4m▄\e[38;5;17;48;5;0m▄\e[38;5;0;48;5;233m▄\e[38;5;0;48;5;17m▄\e[38;5;0;48;5;232m▄\e[38;5;0;48;5;17m▄\e[38;5;0;48;5;0m▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄\e[48;5;0m \e[m +\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;4;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[38;5;18;48;5;19m▄\e[38;5;0;48;5;4m▄\e[38;5;0;48;5;18m▄\e[38;5;0;48;5;4m▄\e[38;5;0;48;5;0m▄▄▄▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[38;5;0;48;5;52m▄\e[38;5;0;48;5;0m▄\e[38;5;251;48;5;230m▄\e[38;5;145;48;5;255m▄\e[38;5;253;48;5;255m▄\e[38;5;0;48;5;0m▄▄\e[48;5;0m \e[38;5;0;48;5;94m▄\e[38;5;214;48;5;252m▄\e[38;5;145;48;5;255m▄\e[38;5;248;48;5;254m▄\e[38;5;187;48;5;220m▄\e[38;5;0;48;5;88m▄\e[38;5;0;48;5;124m▄\e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;254;48;5;214m▄\e[38;5;223;48;5;221m▄\e[38;5;254;48;5;229m▄\e[38;5;250;48;5;229m▄\e[38;5;220;48;5;235m▄\e[38;5;0;48;5;0m▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[38;5;0;48;5;214m▄\e[38;5;255;48;5;221m▄▄\e[38;5;228;48;5;131m▄\e[38;5;0;48;5;166m▄\e[38;5;52;48;5;130m▄\e[38;5;187;48;5;222m▄\e[38;5;254;48;5;228m▄\e[38;5;230;48;5;94m▄\e[38;5;232;48;5;0m▄\e[48;5;0m \e[38;5;179;48;5;202m▄\e[38;5;253;48;5;221m▄\e[38;5;254;48;5;229m▄\e[38;5;101;48;5;3m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[38;5;223;48;5;0m▄\e[38;5;242;48;5;180m▄\e[38;5;52;48;5;243m▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄\e[38;5;17;48;5;0m▄\e[38;5;4;48;5;4m▄\e[38;5;18;48;5;23m▄\e[38;5;18;48;5;25m▄▄\e[38;5;26;48;5;25m▄\e[38;5;20;48;5;26m▄\e[38;5;33;48;5;33m▄\e[38;5;0;48;5;253m▄\e[38;5;145;48;5;15m▄\e[38;5;234;48;5;251m▄\e[38;5;239;48;5;0m▄\e[38;5;233;48;5;232m▄\e[38;5;234;48;5;237m▄\e[38;5;232;48;5;0m▄\e[38;5;234;48;5;234m▄\e[38;5;0;48;5;0m▄▄\e[48;5;0m \e[38;5;0;48;5;240m▄\e[38;5;0;48;5;239m▄\e[38;5;233;48;5;238m▄\e[48;5;0m \e[38;5;26;48;5;27m▄\e[38;5;26;48;5;26m▄\e[38;5;19;48;5;19m▄\e[38;5;19;48;5;4m▄\e[38;5;18;48;5;236m▄\e[38;5;0;48;5;232m▄\e[38;5;0;48;5;0m▄▄▄▄\e[38;5;0;48;5;17m▄\e[38;5;0;48;5;0m▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[m +\e[48;5;0m \e[38;5;0;48;5;0m▄▄\e[38;5;18;48;5;0m▄\e[38;5;19;48;5;0m▄▄\e[38;5;0;48;5;0m▄\e[38;5;232;48;5;0m▄\e[38;5;19;48;5;19m▄\e[38;5;20;48;5;19m▄\e[38;5;20;48;5;0m▄\e[38;5;19;48;5;19m▄\e[38;5;18;48;5;0m▄\e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄▄▄\e[38;5;52;48;5;0m▄▄\e[38;5;0;48;5;0m▄\e[38;5;247;48;5;240m▄\e[38;5;59;48;5;240m▄\e[38;5;242;48;5;8m▄\e[38;5;0;48;5;0m▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[38;5;137;48;5;242m▄\e[38;5;246;48;5;248m▄\e[38;5;253;48;5;249m▄\e[38;5;221;48;5;221m▄\e[38;5;88;48;5;1m▄\e[38;5;0;48;5;0m▄\e[38;5;179;48;5;0m▄\e[38;5;249;48;5;246m▄\e[38;5;0;48;5;136m▄\e[38;5;8;48;5;223m▄\e[38;5;8;48;5;248m▄\e[38;5;138;48;5;173m▄\e[38;5;94;48;5;0m▄\e[38;5;0;48;5;0m▄\e[38;5;232;48;5;0m▄\e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;7;48;5;255m▄▄\e[38;5;221;48;5;229m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[38;5;0;48;5;88m▄\e[38;5;245;48;5;254m▄\e[38;5;242;48;5;254m▄\e[38;5;233;48;5;233m▄\e[38;5;0;48;5;0m▄\e[38;5;137;48;5;144m▄\e[38;5;248;48;5;59m▄\e[38;5;8;48;5;243m▄\e[38;5;3;48;5;137m▄\e[38;5;222;48;5;0m▄\e[38;5;102;48;5;229m▄\e[38;5;95;48;5;59m▄\e[38;5;0;48;5;208m▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄\e[38;5;17;48;5;0m▄\e[38;5;24;48;5;17m▄\e[38;5;19;48;5;4m▄\e[38;5;4;48;5;4m▄\e[38;5;4;48;5;18m▄\e[38;5;25;48;5;19m▄\e[38;5;25;48;5;4m▄\e[38;5;25;48;5;20m▄\e[38;5;26;48;5;27m▄\e[38;5;27;48;5;27m▄\e[38;5;33;48;5;27m▄▄\e[38;5;250;48;5;233m▄\e[38;5;249;48;5;59m▄\e[38;5;245;48;5;239m▄\e[38;5;241;48;5;234m▄\e[38;5;243;48;5;233m▄\e[38;5;235;48;5;233m▄\e[38;5;246;48;5;240m▄\e[38;5;233;48;5;0m▄\e[38;5;0;48;5;0m▄\e[38;5;236;48;5;0m▄\e[38;5;238;48;5;0m▄\e[38;5;239;48;5;232m▄\e[38;5;0;48;5;0m▄\e[38;5;26;48;5;0m▄\e[38;5;27;48;5;20m▄\e[38;5;27;48;5;18m▄\e[38;5;19;48;5;17m▄\e[38;5;232;48;5;17m▄\e[38;5;19;48;5;18m▄\e[38;5;0;48;5;17m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[38;5;4;48;5;0m▄\e[38;5;0;48;5;0m▄\e[38;5;19;48;5;0m▄\e[38;5;232;48;5;0m▄\e[38;5;0;48;5;0m▄\e[48;5;0m \e[m +\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄▄\e[38;5;0;48;5;17m▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄\e[38;5;0;48;5;19m▄\e[38;5;18;48;5;19m▄\e[38;5;20;48;5;19m▄\e[38;5;19;48;5;18m▄\e[38;5;19;48;5;19m▄\e[38;5;20;48;5;19m▄\e[38;5;20;48;5;0m▄\e[38;5;19;48;5;4m▄\e[38;5;24;48;5;0m▄\e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;17;48;5;0m▄\e[38;5;4;48;5;0m▄\e[38;5;18;48;5;0m▄\e[38;5;17;48;5;179m▄\e[38;5;17;48;5;232m▄\e[38;5;0;48;5;234m▄\e[38;5;232;48;5;0m▄\e[38;5;0;48;5;253m▄\e[38;5;236;48;5;8m▄\e[38;5;8;48;5;241m▄\e[38;5;59;48;5;59m▄\e[38;5;0;48;5;0m▄▄\e[38;5;233;48;5;0m▄\e[38;5;232;48;5;0m▄\e[38;5;52;48;5;0m▄\e[38;5;0;48;5;0m▄\e[38;5;180;48;5;94m▄\e[38;5;140;48;5;111m▄\e[38;5;8;48;5;146m▄\e[38;5;186;48;5;101m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[38;5;240;48;5;180m▄\e[38;5;238;48;5;174m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[38;5;7;48;5;147m▄\e[38;5;189;48;5;153m▄\e[38;5;246;48;5;185m▄\e[38;5;0;48;5;52m▄\e[38;5;0;48;5;0m▄▄▄\e[38;5;8;48;5;251m▄\e[38;5;246;48;5;249m▄\e[38;5;242;48;5;166m▄\e[38;5;230;48;5;0m▄▄\e[38;5;145;48;5;187m▄\e[38;5;0;48;5;242m▄\e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;52;48;5;52m▄\e[38;5;239;48;5;241m▄\e[38;5;245;48;5;247m▄\e[38;5;241;48;5;242m▄\e[38;5;243;48;5;179m▄\e[38;5;8;48;5;249m▄\e[38;5;145;48;5;253m▄\e[38;5;59;48;5;101m▄\e[38;5;0;48;5;0m▄\e[38;5;88;48;5;0m▄\e[38;5;1;48;5;1m▄\e[38;5;0;48;5;52m▄\e[38;5;232;48;5;233m▄\e[38;5;0;48;5;0m▄\e[38;5;101;48;5;17m▄\e[38;5;31;48;5;66m▄\e[38;5;25;48;5;25m▄▄\e[38;5;25;48;5;32m▄\e[38;5;45;48;5;27m▄\e[38;5;81;48;5;26m▄\e[38;5;81;48;5;33m▄\e[38;5;26;48;5;33m▄\e[38;5;81;48;5;27m▄\e[38;5;251;48;5;243m▄\e[38;5;250;48;5;232m▄\e[38;5;240;48;5;8m▄\e[38;5;242;48;5;243m▄\e[38;5;238;48;5;235m▄\e[38;5;172;48;5;233m▄\e[38;5;246;48;5;8m▄\e[38;5;0;48;5;0m▄\e[38;5;238;48;5;236m▄\e[38;5;237;48;5;237m▄\e[38;5;233;48;5;0m▄\e[38;5;235;48;5;238m▄\e[48;5;0m \e[38;5;74;48;5;26m▄\e[38;5;0;48;5;19m▄\e[38;5;25;48;5;20m▄\e[38;5;236;48;5;18m▄\e[38;5;232;48;5;232m▄\e[38;5;0;48;5;0m▄▄▄▄▄▄\e[38;5;19;48;5;0m▄\e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[m +\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄▄▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄▄\e[48;5;0m \e[38;5;0;48;5;52m▄\e[38;5;0;48;5;18m▄\e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[38;5;0;48;5;17m▄\e[38;5;17;48;5;0m▄\e[38;5;18;48;5;0m▄\e[38;5;4;48;5;17m▄\e[38;5;17;48;5;234m▄\e[38;5;0;48;5;17m▄\e[38;5;0;48;5;0m▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[38;5;242;48;5;248m▄\e[38;5;242;48;5;240m▄\e[38;5;253;48;5;237m▄\e[48;5;0m \e[38;5;233;48;5;0m▄\e[38;5;234;48;5;0m▄\e[38;5;232;48;5;0m▄\e[38;5;0;48;5;0m▄\e[38;5;144;48;5;179m▄\e[38;5;145;48;5;239m▄\e[38;5;238;48;5;239m▄\e[38;5;230;48;5;187m▄\e[48;5;0m \e[38;5;243;48;5;59m▄\e[38;5;59;48;5;7m▄\e[38;5;236;48;5;251m▄\e[38;5;238;48;5;187m▄\e[38;5;59;48;5;187m▄\e[38;5;241;48;5;230m▄\e[38;5;242;48;5;245m▄\e[38;5;242;48;5;246m▄\e[38;5;186;48;5;235m▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄\e[38;5;241;48;5;239m▄\e[38;5;239;48;5;243m▄\e[38;5;102;48;5;242m▄\e[38;5;0;48;5;238m▄\e[38;5;242;48;5;246m▄\e[38;5;7;48;5;254m▄\e[38;5;255;48;5;230m▄\e[38;5;232;48;5;0m▄\e[38;5;0;48;5;0m▄\e[38;5;52;48;5;52m▄\e[38;5;242;48;5;242m▄\e[38;5;247;48;5;8m▄\e[38;5;246;48;5;246m▄\e[38;5;243;48;5;241m▄\e[38;5;0;48;5;238m▄\e[38;5;240;48;5;241m▄\e[38;5;239;48;5;243m▄\e[38;5;245;48;5;173m▄\e[38;5;172;48;5;52m▄\e[38;5;1;48;5;52m▄\e[38;5;0;48;5;232m▄\e[38;5;233;48;5;232m▄\e[38;5;0;48;5;233m▄\e[38;5;0;48;5;17m▄\e[38;5;17;48;5;53m▄\e[38;5;20;48;5;25m▄\e[38;5;234;48;5;25m▄\e[38;5;25;48;5;17m▄\e[38;5;25;48;5;25m▄\e[38;5;19;48;5;75m▄\e[38;5;25;48;5;81m▄\e[38;5;27;48;5;123m▄\e[38;5;27;48;5;45m▄\e[38;5;242;48;5;246m▄\e[38;5;250;48;5;250m▄\e[38;5;8;48;5;245m▄\e[48;5;242m \e[38;5;11;48;5;11m▄▄\e[38;5;243;48;5;246m▄\e[38;5;233;48;5;234m▄\e[38;5;237;48;5;236m▄\e[38;5;235;48;5;238m▄\e[38;5;0;48;5;234m▄▄\e[38;5;0;48;5;0m▄\e[38;5;17;48;5;17m▄\e[38;5;17;48;5;232m▄\e[38;5;0;48;5;17m▄▄\e[38;5;0;48;5;0m▄▄\e[38;5;17;48;5;0m▄\e[38;5;0;48;5;17m▄\e[38;5;0;48;5;0m▄\e[38;5;232;48;5;0m▄\e[38;5;0;48;5;232m▄\e[38;5;24;48;5;232m▄\e[38;5;233;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄\e[48;5;0m \e[m +\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄▄▄▄▄▄\e[38;5;19;48;5;0m▄\e[38;5;18;48;5;0m▄\e[38;5;4;48;5;0m▄\e[38;5;19;48;5;0m▄\e[38;5;0;48;5;0m▄\e[38;5;232;48;5;0m▄\e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;232;48;5;0m▄\e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[38;5;234;48;5;232m▄\e[38;5;52;48;5;17m▄\e[38;5;0;48;5;18m▄\e[38;5;52;48;5;17m▄\e[38;5;235;48;5;17m▄\e[38;5;232;48;5;233m▄\e[38;5;232;48;5;232m▄\e[38;5;0;48;5;0m▄▄▄\e[38;5;246;48;5;246m▄\e[38;5;250;48;5;243m▄\e[38;5;187;48;5;8m▄\e[38;5;0;48;5;0m▄▄\e[38;5;0;48;5;239m▄\e[38;5;17;48;5;60m▄\e[38;5;0;48;5;0m▄\e[38;5;229;48;5;0m▄\e[38;5;239;48;5;246m▄\e[38;5;242;48;5;102m▄\e[38;5;0;48;5;254m▄\e[48;5;0m \e[38;5;240;48;5;0m▄\e[38;5;59;48;5;240m▄\e[38;5;0;48;5;248m▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄\e[38;5;102;48;5;240m▄\e[38;5;240;48;5;243m▄\e[38;5;239;48;5;236m▄\e[38;5;145;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[38;5;59;48;5;239m▄\e[38;5;254;48;5;248m▄\e[38;5;246;48;5;248m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[38;5;238;48;5;242m▄\e[38;5;246;48;5;247m▄\e[38;5;8;48;5;255m▄\e[38;5;15;48;5;0m▄\e[48;5;0m \e[38;5;8;48;5;245m▄\e[38;5;247;48;5;240m▄\e[38;5;242;48;5;102m▄\e[38;5;239;48;5;101m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[38;5;0;48;5;246m▄\e[38;5;240;48;5;243m▄\e[38;5;246;48;5;187m▄\e[38;5;214;48;5;88m▄\e[38;5;1;48;5;1m▄\e[38;5;232;48;5;52m▄\e[38;5;232;48;5;17m▄\e[38;5;232;48;5;146m▄\e[38;5;167;48;5;182m▄\e[38;5;239;48;5;232m▄\e[38;5;17;48;5;4m▄\e[38;5;4;48;5;32m▄\e[38;5;18;48;5;17m▄\e[38;5;4;48;5;17m▄\e[38;5;0;48;5;18m▄\e[38;5;26;48;5;26m▄\e[38;5;19;48;5;12m▄\e[38;5;236;48;5;236m▄\e[38;5;242;48;5;249m▄\e[38;5;241;48;5;245m▄\e[38;5;242;48;5;242m▄\e[38;5;240;48;5;11m▄\e[38;5;246;48;5;220m▄\e[38;5;245;48;5;8m▄\e[38;5;235;48;5;232m▄\e[38;5;232;48;5;236m▄\e[38;5;233;48;5;232m▄\e[38;5;233;48;5;214m▄\e[48;5;0m \e[38;5;23;48;5;17m▄\e[38;5;233;48;5;0m▄\e[38;5;232;48;5;232m▄\e[38;5;232;48;5;0m▄\e[48;5;0m \e[38;5;23;48;5;0m▄\e[38;5;17;48;5;17m▄\e[38;5;233;48;5;232m▄\e[38;5;234;48;5;0m▄\e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[m +\e[48;5;0m \e[38;5;255;48;5;0m▄\e[38;5;0;48;5;0m▄▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄\e[38;5;0;48;5;232m▄\e[38;5;0;48;5;52m▄\e[38;5;0;48;5;0m▄\e[38;5;52;48;5;52m▄\e[38;5;0;48;5;0m▄\e[38;5;1;48;5;0m▄\e[38;5;88;48;5;0m▄\e[38;5;52;48;5;0m▄\e[38;5;160;48;5;0m▄▄\e[38;5;160;48;5;52m▄\e[38;5;52;48;5;232m▄\e[38;5;0;48;5;232m▄\e[38;5;7;48;5;137m▄\e[38;5;236;48;5;237m▄\e[38;5;237;48;5;8m▄\e[38;5;102;48;5;187m▄\e[38;5;242;48;5;0m▄\e[38;5;187;48;5;0m▄\e[38;5;144;48;5;0m▄\e[38;5;245;48;5;232m▄\e[38;5;245;48;5;253m▄\e[38;5;254;48;5;239m▄\e[38;5;0;48;5;245m▄\e[38;5;0;48;5;243m▄\e[48;5;0m \e[38;5;250;48;5;0m▄\e[38;5;237;48;5;251m▄\e[38;5;102;48;5;243m▄\e[38;5;242;48;5;0m▄\e[38;5;255;48;5;0m▄\e[38;5;0;48;5;0m▄▄\e[38;5;104;48;5;0m▄\e[38;5;59;48;5;245m▄\e[38;5;247;48;5;239m▄\e[38;5;247;48;5;8m▄\e[38;5;245;48;5;245m▄\e[38;5;7;48;5;0m▄\e[38;5;250;48;5;232m▄\e[38;5;252;48;5;237m▄\e[38;5;102;48;5;247m▄\e[38;5;102;48;5;254m▄\e[38;5;15;48;5;0m▄\e[38;5;0;48;5;0m▄▄\e[38;5;7;48;5;238m▄\e[38;5;248;48;5;237m▄\e[38;5;246;48;5;240m▄\e[38;5;240;48;5;233m▄\e[38;5;248;48;5;255m▄\e[38;5;248;48;5;8m▄\e[38;5;102;48;5;59m▄\e[38;5;59;48;5;246m▄\e[38;5;254;48;5;0m▄\e[38;5;0;48;5;0m▄▄\e[38;5;17;48;5;230m▄\e[38;5;0;48;5;8m▄\e[38;5;180;48;5;8m▄\e[38;5;187;48;5;137m▄\e[38;5;221;48;5;124m▄\e[38;5;9;48;5;0m▄\e[38;5;160;48;5;0m▄\e[38;5;208;48;5;202m▄\e[38;5;1;48;5;52m▄\e[38;5;124;48;5;52m▄\e[38;5;88;48;5;17m▄\e[38;5;52;48;5;233m▄\e[38;5;0;48;5;17m▄▄\e[38;5;232;48;5;18m▄\e[38;5;59;48;5;0m▄\e[38;5;241;48;5;246m▄\e[38;5;233;48;5;235m▄\e[38;5;240;48;5;243m▄\e[38;5;233;48;5;242m▄\e[38;5;241;48;5;243m▄\e[38;5;233;48;5;236m▄\e[38;5;242;48;5;238m▄\e[38;5;233;48;5;0m▄▄\e[38;5;0;48;5;0m▄▄\e[48;5;0m \e[38;5;0;48;5;4m▄\e[38;5;0;48;5;17m▄\e[38;5;0;48;5;0m▄\e[38;5;4;48;5;0m▄\e[38;5;24;48;5;0m▄\e[38;5;236;48;5;232m▄\e[38;5;0;48;5;23m▄\e[48;5;0m \e[38;5;234;48;5;233m▄\e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[m +\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄▄\e[38;5;17;48;5;0m▄\e[38;5;1;48;5;172m▄\e[38;5;202;48;5;208m▄▄▄▄▄▄\e[38;5;214;48;5;214m▄\e[38;5;208;48;5;214m▄▄\e[38;5;11;48;5;11m▄▄\e[38;5;220;48;5;11m▄\e[38;5;202;48;5;11m▄\e[38;5;220;48;5;227m▄\e[38;5;208;48;5;228m▄\e[38;5;214;48;5;228m▄\e[38;5;130;48;5;227m▄\e[38;5;0;48;5;52m▄\e[38;5;0;48;5;0m▄▄▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄▄\e[38;5;0;48;5;232m▄\e[38;5;52;48;5;0m▄\e[38;5;0;48;5;0m▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄▄▄▄▄\e[48;5;0m \e[38;5;52;48;5;227m▄\e[38;5;137;48;5;227m▄\e[38;5;208;48;5;227m▄\e[38;5;202;48;5;11m▄\e[38;5;202;48;5;227m▄\e[38;5;220;48;5;228m▄\e[38;5;202;48;5;11m▄▄▄\e[38;5;202;48;5;220m▄\e[38;5;202;48;5;124m▄\e[38;5;202;48;5;160m▄\e[38;5;208;48;5;160m▄\e[38;5;220;48;5;160m▄\e[38;5;88;48;5;88m▄\e[38;5;124;48;5;238m▄\e[38;5;233;48;5;235m▄\e[38;5;240;48;5;242m▄\e[38;5;235;48;5;241m▄\e[38;5;233;48;5;243m▄\e[38;5;232;48;5;59m▄\e[38;5;234;48;5;0m▄\e[38;5;0;48;5;0m▄\e[38;5;233;48;5;232m▄\e[38;5;11;48;5;160m▄\e[38;5;0;48;5;232m▄\e[38;5;235;48;5;0m▄\e[38;5;0;48;5;25m▄\e[38;5;25;48;5;25m▄\e[38;5;39;48;5;32m▄\e[38;5;0;48;5;25m▄\e[38;5;232;48;5;25m▄\e[38;5;233;48;5;17m▄\e[38;5;234;48;5;0m▄▄\e[38;5;235;48;5;23m▄\e[38;5;0;48;5;234m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[m +\e[48;5;0m \e[38;5;232;48;5;0m▄\e[38;5;0;48;5;0m▄▄▄▄▄\e[38;5;0;48;5;17m▄\e[38;5;232;48;5;4m▄\e[38;5;17;48;5;18m▄\e[38;5;52;48;5;52m▄\e[38;5;15;48;5;131m▄\e[38;5;8;48;5;179m▄\e[38;5;187;48;5;143m▄\e[38;5;0;48;5;250m▄\e[38;5;0;48;5;247m▄\e[38;5;0;48;5;232m▄\e[38;5;101;48;5;239m▄\e[38;5;223;48;5;221m▄\e[38;5;186;48;5;221m▄\e[38;5;15;48;5;222m▄\e[38;5;247;48;5;101m▄\e[38;5;0;48;5;249m▄\e[38;5;0;48;5;181m▄\e[38;5;239;48;5;229m▄\e[38;5;243;48;5;179m▄\e[38;5;242;48;5;214m▄\e[38;5;239;48;5;94m▄\e[38;5;0;48;5;0m▄▄\e[38;5;166;48;5;0m▄▄\e[38;5;221;48;5;202m▄\e[38;5;228;48;5;202m▄\e[38;5;180;48;5;202m▄\e[38;5;186;48;5;202m▄\e[38;5;208;48;5;202m▄\e[38;5;208;48;5;94m▄\e[38;5;208;48;5;0m▄\e[38;5;0;48;5;0m▄▄\e[38;5;173;48;5;202m▄\e[38;5;166;48;5;202m▄\e[38;5;227;48;5;202m▄\e[38;5;214;48;5;202m▄\e[38;5;228;48;5;202m▄\e[38;5;52;48;5;214m▄\e[38;5;0;48;5;0m▄\e[38;5;0;48;5;52m▄\e[38;5;0;48;5;0m▄▄▄\e[38;5;241;48;5;0m▄\e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄\e[38;5;0;48;5;232m▄\e[38;5;166;48;5;202m▄\e[38;5;221;48;5;202m▄\e[38;5;215;48;5;202m▄\e[38;5;124;48;5;208m▄\e[38;5;52;48;5;202m▄\e[38;5;180;48;5;208m▄\e[38;5;229;48;5;208m▄▄\e[38;5;222;48;5;208m▄\e[38;5;137;48;5;202m▄\e[38;5;243;48;5;202m▄\e[38;5;242;48;5;202m▄\e[38;5;187;48;5;202m▄\e[38;5;228;48;5;202m▄\e[38;5;166;48;5;202m▄\e[38;5;228;48;5;208m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[38;5;0;48;5;233m▄\e[38;5;229;48;5;214m▄\e[38;5;185;48;5;208m▄\e[38;5;230;48;5;214m▄\e[38;5;15;48;5;221m▄\e[38;5;0;48;5;232m▄\e[38;5;0;48;5;233m▄\e[38;5;0;48;5;255m▄\e[38;5;0;48;5;179m▄\e[38;5;254;48;5;202m▄\e[38;5;188;48;5;202m▄\e[38;5;228;48;5;202m▄\e[38;5;215;48;5;202m▄\e[38;5;208;48;5;166m▄\e[38;5;166;48;5;52m▄\e[38;5;124;48;5;52m▄\e[38;5;232;48;5;243m▄\e[38;5;241;48;5;59m▄\e[38;5;232;48;5;232m▄\e[38;5;243;48;5;239m▄\e[38;5;0;48;5;232m▄\e[38;5;235;48;5;232m▄\e[38;5;0;48;5;232m▄\e[38;5;232;48;5;220m▄\e[38;5;233;48;5;234m▄\e[38;5;232;48;5;234m▄\e[48;5;0m \e[38;5;18;48;5;25m▄\e[38;5;23;48;5;232m▄\e[38;5;25;48;5;32m▄\e[38;5;32;48;5;25m▄\e[38;5;0;48;5;17m▄\e[38;5;23;48;5;24m▄▄\e[38;5;0;48;5;234m▄\e[38;5;0;48;5;0m▄\e[48;5;0m \e[m +\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[38;5;0;48;5;17m▄\e[38;5;0;48;5;0m▄▄▄▄\e[38;5;145;48;5;255m▄\e[38;5;0;48;5;246m▄\e[48;5;0m \e[38;5;247;48;5;246m▄\e[38;5;7;48;5;254m▄\e[38;5;249;48;5;251m▄\e[38;5;15;48;5;15m▄\e[38;5;187;48;5;187m▄\e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;8m▄\e[38;5;246;48;5;8m▄\e[38;5;233;48;5;237m▄\e[38;5;208;48;5;0m▄\e[38;5;15;48;5;172m▄\e[38;5;255;48;5;255m▄\e[38;5;188;48;5;254m▄\e[38;5;15;48;5;15m▄\e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;232;48;5;8m▄\e[38;5;250;48;5;255m▄\e[38;5;15;48;5;15m▄\e[38;5;15;48;5;179m▄\e[38;5;186;48;5;0m▄\e[48;5;0m \e[38;5;251;48;5;15m▄\e[38;5;15;48;5;15m▄\e[38;5;250;48;5;251m▄\e[38;5;15;48;5;15m▄\e[38;5;232;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄\e[38;5;236;48;5;233m▄\e[38;5;254;48;5;223m▄\e[38;5;215;48;5;0m▄\e[38;5;52;48;5;1m▄\e[38;5;0;48;5;52m▄\e[38;5;236;48;5;0m▄\e[48;5;0m \e[38;5;144;48;5;234m▄\e[38;5;15;48;5;15m▄\e[38;5;246;48;5;15m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;15;48;5;15m▄▄\e[38;5;252;48;5;15m▄\e[38;5;186;48;5;187m▄\e[48;5;0m \e[38;5;0;48;5;8m▄\e[38;5;243;48;5;251m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;15;48;5;15m▄\e[38;5;251;48;5;249m▄\e[38;5;15;48;5;15m▄▄\e[38;5;232;48;5;0m▄\e[38;5;0;48;5;0m▄▄▄\e[48;5;0m \e[38;5;0;48;5;240m▄\e[38;5;187;48;5;255m▄\e[48;5;15m \e[38;5;15;48;5;15m▄▄\e[38;5;186;48;5;0m▄\e[38;5;232;48;5;232m▄\e[38;5;237;48;5;232m▄\e[38;5;237;48;5;241m▄\e[38;5;238;48;5;235m▄\e[38;5;234;48;5;0m▄\e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;234m▄\e[38;5;0;48;5;233m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[38;5;20;48;5;20m▄\e[38;5;19;48;5;26m▄\e[38;5;0;48;5;0m▄▄\e[38;5;232;48;5;0m▄\e[38;5;232;48;5;232m▄\e[38;5;0;48;5;0m▄▄\e[38;5;0;48;5;178m▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄\e[48;5;0m \e[m +\e[48;5;0m \e[38;5;0;48;5;0m▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[38;5;245;48;5;0m▄\e[38;5;236;48;5;0m▄\e[38;5;0;48;5;0m▄\e[38;5;17;48;5;0m▄▄\e[38;5;249;48;5;247m▄\e[48;5;0m \e[38;5;17;48;5;0m▄\e[38;5;0;48;5;0m▄▄\e[48;5;0m \e[38;5;249;48;5;248m▄\e[38;5;241;48;5;246m▄\e[38;5;242;48;5;242m▄\e[38;5;8;48;5;247m▄\e[38;5;250;48;5;247m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[38;5;187;48;5;0m▄\e[38;5;109;48;5;251m▄\e[38;5;248;48;5;255m▄\e[38;5;102;48;5;248m▄\e[38;5;59;48;5;252m▄\e[38;5;254;48;5;251m▄\e[48;5;0m \e[38;5;246;48;5;246m▄\e[38;5;255;48;5;254m▄\e[38;5;255;48;5;255m▄\e[38;5;15;48;5;15m▄\e[48;5;0m \e[38;5;0;48;5;7m▄\e[38;5;236;48;5;238m▄\e[38;5;145;48;5;102m▄\e[38;5;59;48;5;243m▄\e[38;5;223;48;5;234m▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄\e[38;5;232;48;5;0m▄\e[38;5;250;48;5;248m▄\e[38;5;254;48;5;255m▄\e[38;5;15;48;5;186m▄\e[38;5;237;48;5;0m▄\e[38;5;1;48;5;52m▄\e[38;5;52;48;5;0m▄\e[48;5;0m \e[38;5;187;48;5;249m▄\e[38;5;15;48;5;15m▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄\e[38;5;255;48;5;254m▄\e[38;5;255;48;5;247m▄\e[38;5;102;48;5;102m▄\e[38;5;144;48;5;7m▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄\e[38;5;52;48;5;0m▄\e[38;5;0;48;5;0m▄\e[38;5;0;48;5;60m▄\e[48;5;0m \e[38;5;17;48;5;0m▄\e[38;5;0;48;5;0m▄\e[38;5;252;48;5;251m▄\e[38;5;8;48;5;245m▄\e[38;5;250;48;5;240m▄\e[38;5;8;48;5;8m▄\e[38;5;233;48;5;0m▄\e[38;5;0;48;5;0m▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[38;5;255;48;5;144m▄\e[38;5;252;48;5;248m▄\e[38;5;253;48;5;188m▄\e[38;5;248;48;5;246m▄\e[38;5;252;48;5;144m▄\e[38;5;0;48;5;0m▄▄\e[38;5;59;48;5;237m▄\e[38;5;247;48;5;59m▄\e[38;5;17;48;5;232m▄\e[38;5;0;48;5;0m▄\e[38;5;0;48;5;17m▄\e[38;5;234;48;5;232m▄\e[38;5;236;48;5;237m▄\e[38;5;0;48;5;0m▄▄\e[38;5;4;48;5;20m▄\e[38;5;17;48;5;18m▄\e[38;5;0;48;5;17m▄\e[38;5;103;48;5;232m▄\e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄▄\e[38;5;24;48;5;0m▄\e[38;5;232;48;5;0m▄\e[38;5;0;48;5;0m▄\e[38;5;232;48;5;0m▄\e[38;5;0;48;5;0m▄\e[48;5;0m \e[m +\e[48;5;0m \e[38;5;0;48;5;0m▄▄\e[38;5;0;48;5;19m▄\e[38;5;0;48;5;17m▄▄\e[38;5;17;48;5;17m▄▄\e[38;5;18;48;5;17m▄\e[38;5;0;48;5;0m▄▄\e[38;5;4;48;5;17m▄▄\e[38;5;17;48;5;0m▄\e[38;5;0;48;5;0m▄\e[38;5;247;48;5;145m▄\e[38;5;241;48;5;241m▄\e[38;5;242;48;5;236m▄\e[38;5;246;48;5;245m▄\e[38;5;144;48;5;144m▄\e[38;5;0;48;5;0m▄\e[38;5;17;48;5;0m▄▄\e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;250;48;5;187m▄\e[38;5;255;48;5;245m▄\e[38;5;246;48;5;7m▄\e[38;5;247;48;5;248m▄\e[38;5;8;48;5;8m▄\e[38;5;250;48;5;7m▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄\e[48;5;0m \e[38;5;243;48;5;241m▄\e[38;5;243;48;5;246m▄\e[38;5;249;48;5;245m▄\e[38;5;242;48;5;247m▄\e[38;5;102;48;5;101m▄\e[48;5;0m \e[38;5;254;48;5;245m▄\e[38;5;240;48;5;237m▄\e[38;5;233;48;5;8m▄\e[38;5;247;48;5;187m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[38;5;1;48;5;52m▄\e[38;5;144;48;5;101m▄\e[38;5;242;48;5;251m▄\e[38;5;252;48;5;252m▄\e[38;5;246;48;5;15m▄\e[38;5;247;48;5;242m▄\e[38;5;0;48;5;0m▄\e[38;5;52;48;5;88m▄\e[38;5;187;48;5;0m▄\e[38;5;7;48;5;245m▄\e[38;5;240;48;5;255m▄\e[48;5;0m \e[38;5;17;48;5;17m▄\e[38;5;0;48;5;0m▄▄\e[38;5;59;48;5;247m▄\e[38;5;242;48;5;145m▄\e[38;5;246;48;5;242m▄\e[38;5;251;48;5;250m▄\e[38;5;232;48;5;0m▄▄\e[38;5;0;48;5;88m▄\e[38;5;0;48;5;52m▄\e[38;5;0;48;5;0m▄\e[38;5;251;48;5;0m▄\e[38;5;0;48;5;0m▄\e[38;5;61;48;5;4m▄\e[48;5;0m \e[38;5;250;48;5;246m▄\e[38;5;245;48;5;242m▄\e[38;5;240;48;5;250m▄\e[38;5;241;48;5;247m▄\e[38;5;233;48;5;233m▄\e[48;5;0m \e[38;5;0;48;5;18m▄\e[38;5;4;48;5;0m▄\e[38;5;0;48;5;0m▄\e[38;5;238;48;5;0m▄\e[38;5;247;48;5;254m▄\e[38;5;246;48;5;245m▄\e[38;5;240;48;5;247m▄\e[38;5;254;48;5;245m▄\e[48;5;0m \e[38;5;232;48;5;0m▄\e[38;5;237;48;5;233m▄\e[38;5;240;48;5;234m▄\e[38;5;8;48;5;233m▄\e[38;5;238;48;5;232m▄\e[38;5;234;48;5;233m▄\e[38;5;23;48;5;0m▄\e[38;5;237;48;5;234m▄\e[38;5;237;48;5;58m▄\e[38;5;233;48;5;0m▄\e[38;5;235;48;5;232m▄\e[38;5;0;48;5;20m▄\e[38;5;19;48;5;18m▄\e[38;5;232;48;5;4m▄\e[38;5;0;48;5;17m▄\e[38;5;232;48;5;232m▄\e[38;5;0;48;5;0m▄\e[38;5;233;48;5;0m▄\e[38;5;25;48;5;0m▄▄\e[38;5;0;48;5;23m▄\e[38;5;0;48;5;24m▄\e[38;5;17;48;5;234m▄\e[38;5;233;48;5;234m▄\e[38;5;233;48;5;0m▄\e[38;5;0;48;5;0m▄\e[48;5;0m \e[m +\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄\e[38;5;0;48;5;17m▄\e[38;5;0;48;5;18m▄\e[38;5;0;48;5;17m▄\e[38;5;0;48;5;232m▄\e[38;5;225;48;5;249m▄\e[38;5;242;48;5;246m▄\e[38;5;241;48;5;242m▄\e[38;5;242;48;5;241m▄\e[38;5;255;48;5;243m▄\e[38;5;0;48;5;0m▄\e[38;5;0;48;5;19m▄\e[38;5;17;48;5;19m▄\e[38;5;0;48;5;0m▄▄\e[38;5;239;48;5;15m▄\e[38;5;240;48;5;247m▄\e[38;5;8;48;5;251m▄\e[38;5;239;48;5;145m▄\e[38;5;240;48;5;59m▄\e[38;5;102;48;5;248m▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄\e[38;5;243;48;5;242m▄\e[38;5;243;48;5;240m▄\e[38;5;241;48;5;247m▄\e[38;5;242;48;5;243m▄\e[38;5;238;48;5;102m▄\e[48;5;0m \e[38;5;238;48;5;8m▄\e[38;5;102;48;5;243m▄\e[38;5;145;48;5;241m▄\e[38;5;234;48;5;240m▄\e[38;5;250;48;5;144m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[38;5;248;48;5;246m▄\e[38;5;238;48;5;59m▄\e[38;5;236;48;5;246m▄\e[38;5;242;48;5;247m▄\e[38;5;245;48;5;249m▄\e[38;5;102;48;5;8m▄\e[48;5;0m \e[38;5;240;48;5;144m▄\e[38;5;248;48;5;254m▄\e[48;5;0m \e[38;5;0;48;5;19m▄\e[48;5;0m \e[38;5;15;48;5;0m▄\e[38;5;102;48;5;242m▄\e[38;5;243;48;5;253m▄\e[38;5;239;48;5;145m▄\e[38;5;248;48;5;102m▄\e[38;5;8;48;5;248m▄\e[38;5;242;48;5;248m▄\e[38;5;241;48;5;255m▄\e[38;5;237;48;5;253m▄\e[38;5;251;48;5;246m▄\e[48;5;0m \e[38;5;0;48;5;18m▄▄\e[38;5;15;48;5;0m▄\e[38;5;102;48;5;254m▄\e[38;5;236;48;5;239m▄\e[38;5;8;48;5;241m▄\e[38;5;247;48;5;246m▄\e[38;5;233;48;5;232m▄\e[48;5;0m \e[38;5;187;48;5;0m▄\e[38;5;249;48;5;187m▄\e[38;5;240;48;5;245m▄\e[38;5;242;48;5;248m▄\e[38;5;245;48;5;241m▄\e[38;5;0;48;5;251m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[38;5;234;48;5;0m▄\e[38;5;235;48;5;236m▄\e[38;5;240;48;5;232m▄\e[38;5;102;48;5;243m▄\e[38;5;237;48;5;232m▄\e[38;5;236;48;5;0m▄\e[38;5;0;48;5;0m▄\e[38;5;234;48;5;237m▄\e[38;5;236;48;5;0m▄\e[38;5;232;48;5;0m▄▄\e[48;5;0m \e[38;5;18;48;5;26m▄\e[38;5;18;48;5;0m▄\e[38;5;17;48;5;232m▄\e[38;5;0;48;5;0m▄\e[38;5;233;48;5;236m▄\e[38;5;0;48;5;235m▄\e[38;5;0;48;5;31m▄\e[38;5;233;48;5;0m▄\e[38;5;232;48;5;23m▄\e[38;5;0;48;5;25m▄\e[48;5;0m \e[38;5;0;48;5;232m▄\e[38;5;0;48;5;0m▄\e[48;5;0m \e[m +\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄\e[38;5;236;48;5;23m▄\e[38;5;23;48;5;24m▄\e[38;5;25;48;5;0m▄\e[38;5;25;48;5;24m▄\e[38;5;25;48;5;0m▄\e[38;5;0;48;5;0m▄\e[38;5;23;48;5;232m▄\e[38;5;0;48;5;0m▄▄\e[38;5;0;48;5;235m▄\e[38;5;234;48;5;0m▄\e[38;5;0;48;5;0m▄\e[38;5;251;48;5;15m▄\e[38;5;238;48;5;243m▄\e[38;5;240;48;5;241m▄\e[38;5;238;48;5;241m▄\e[38;5;7;48;5;247m▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄▄\e[38;5;235;48;5;242m▄\e[38;5;238;48;5;238m▄\e[38;5;60;48;5;60m▄\e[38;5;62;48;5;59m▄\e[38;5;61;48;5;102m▄\e[38;5;7;48;5;145m▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄\e[38;5;241;48;5;242m▄\e[38;5;60;48;5;8m▄\e[38;5;101;48;5;241m▄\e[38;5;62;48;5;247m▄\e[38;5;60;48;5;103m▄\e[48;5;0m \e[38;5;69;48;5;247m▄\e[38;5;62;48;5;248m▄▄\e[38;5;235;48;5;102m▄\e[38;5;245;48;5;0m▄\e[38;5;248;48;5;235m▄\e[38;5;243;48;5;241m▄\e[38;5;234;48;5;247m▄\e[38;5;81;48;5;245m▄\e[38;5;60;48;5;242m▄\e[38;5;238;48;5;102m▄\e[38;5;242;48;5;8m▄\e[38;5;0;48;5;0m▄\e[38;5;251;48;5;242m▄\e[38;5;103;48;5;252m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[38;5;17;48;5;0m▄\e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;249;48;5;250m▄\e[38;5;238;48;5;249m▄\e[38;5;8;48;5;245m▄\e[38;5;249;48;5;248m▄\e[48;5;0m \e[38;5;0;48;5;236m▄\e[38;5;0;48;5;0m▄\e[38;5;19;48;5;0m▄\e[38;5;0;48;5;18m▄\e[38;5;0;48;5;17m▄\e[48;5;0m \e[38;5;247;48;5;251m▄\e[38;5;60;48;5;241m▄\e[38;5;238;48;5;243m▄\e[38;5;241;48;5;248m▄\e[38;5;232;48;5;232m▄\e[38;5;0;48;5;188m▄\e[38;5;8;48;5;102m▄\e[38;5;243;48;5;247m▄\e[38;5;61;48;5;246m▄\e[38;5;61;48;5;247m▄\e[38;5;246;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄\e[38;5;236;48;5;232m▄\e[38;5;238;48;5;0m▄\e[38;5;233;48;5;235m▄\e[38;5;0;48;5;240m▄\e[38;5;241;48;5;239m▄\e[38;5;239;48;5;234m▄\e[38;5;235;48;5;0m▄\e[38;5;17;48;5;0m▄\e[38;5;235;48;5;0m▄\e[38;5;23;48;5;0m▄\e[38;5;232;48;5;232m▄\e[38;5;234;48;5;0m▄\e[38;5;0;48;5;237m▄\e[38;5;25;48;5;25m▄\e[38;5;26;48;5;19m▄\e[38;5;17;48;5;0m▄\e[38;5;232;48;5;232m▄\e[38;5;23;48;5;233m▄\e[38;5;232;48;5;232m▄\e[38;5;235;48;5;234m▄\e[38;5;232;48;5;232m▄\e[38;5;0;48;5;232m▄\e[38;5;236;48;5;0m▄\e[38;5;233;48;5;233m▄\e[38;5;233;48;5;0m▄\e[48;5;0m \e[m +\e[48;5;0m \e[38;5;0;48;5;0m▄\e[38;5;232;48;5;232m▄\e[38;5;0;48;5;0m▄\e[38;5;233;48;5;232m▄\e[38;5;0;48;5;234m▄\e[38;5;0;48;5;23m▄▄\e[38;5;234;48;5;32m▄\e[38;5;60;48;5;67m▄\e[38;5;239;48;5;66m▄\e[38;5;241;48;5;240m▄\e[38;5;235;48;5;234m▄\e[38;5;232;48;5;233m▄\e[38;5;234;48;5;232m▄\e[38;5;236;48;5;234m▄\e[48;5;0m \e[38;5;246;48;5;249m▄\e[38;5;238;48;5;239m▄\e[38;5;234;48;5;240m▄\e[38;5;239;48;5;239m▄\e[38;5;246;48;5;250m▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄▄\e[38;5;0;48;5;235m▄\e[38;5;239;48;5;239m▄\e[38;5;61;48;5;102m▄\e[38;5;24;48;5;243m▄\e[38;5;18;48;5;239m▄\e[38;5;24;48;5;145m▄\e[38;5;0;48;5;0m▄▄\e[48;5;0m \e[38;5;242;48;5;243m▄\e[38;5;61;48;5;242m▄\e[38;5;239;48;5;239m▄\e[38;5;240;48;5;238m▄\e[38;5;67;48;5;8m▄\e[38;5;0;48;5;0m▄▄\e[38;5;0;48;5;239m▄\e[38;5;245;48;5;7m▄\e[38;5;242;48;5;249m▄\e[38;5;233;48;5;60m▄\e[38;5;239;48;5;255m▄\e[38;5;246;48;5;255m▄\e[38;5;235;48;5;102m▄\e[48;5;0m \e[38;5;0;48;5;233m▄\e[38;5;103;48;5;146m▄\e[38;5;249;48;5;102m▄\e[38;5;245;48;5;59m▄\e[38;5;237;48;5;15m▄\e[38;5;245;48;5;8m▄\e[38;5;0;48;5;240m▄\e[38;5;0;48;5;0m▄\e[38;5;17;48;5;4m▄\e[38;5;19;48;5;0m▄\e[38;5;20;48;5;0m▄\e[38;5;0;48;5;0m▄\e[38;5;102;48;5;7m▄\e[38;5;236;48;5;245m▄\e[38;5;239;48;5;59m▄\e[38;5;245;48;5;145m▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄▄\e[38;5;0;48;5;17m▄▄\e[38;5;0;48;5;0m▄▄\e[38;5;245;48;5;103m▄\e[38;5;239;48;5;241m▄\e[38;5;60;48;5;240m▄\e[38;5;238;48;5;241m▄\e[38;5;232;48;5;233m▄\e[48;5;0m \e[38;5;0;48;5;251m▄\e[38;5;59;48;5;249m▄\e[38;5;59;48;5;102m▄\e[38;5;238;48;5;240m▄\e[38;5;61;48;5;237m▄\e[38;5;8;48;5;241m▄\e[48;5;0m \e[38;5;235;48;5;0m▄\e[38;5;0;48;5;239m▄\e[38;5;238;48;5;239m▄\e[38;5;234;48;5;232m▄\e[38;5;94;48;5;130m▄\e[38;5;232;48;5;234m▄\e[38;5;238;48;5;238m▄\e[38;5;0;48;5;232m▄\e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;239;48;5;238m▄\e[38;5;232;48;5;11m▄\e[48;5;0m \e[38;5;235;48;5;0m▄\e[38;5;0;48;5;232m▄▄▄\e[38;5;23;48;5;233m▄\e[38;5;233;48;5;234m▄\e[38;5;232;48;5;232m▄\e[38;5;23;48;5;233m▄\e[38;5;0;48;5;232m▄\e[38;5;0;48;5;234m▄\e[38;5;0;48;5;233m▄\e[38;5;17;48;5;232m▄\e[38;5;0;48;5;233m▄\e[38;5;0;48;5;0m▄\e[48;5;0m \e[m +\e[48;5;0m \e[38;5;0;48;5;0m▄\e[38;5;232;48;5;0m▄\e[38;5;232;48;5;232m▄\e[38;5;232;48;5;0m▄\e[38;5;0;48;5;0m▄\e[38;5;238;48;5;0m▄\e[38;5;236;48;5;0m▄\e[38;5;233;48;5;233m▄\e[38;5;0;48;5;0m▄▄\e[38;5;236;48;5;0m▄\e[38;5;232;48;5;0m▄\e[38;5;239;48;5;0m▄\e[38;5;0;48;5;232m▄\e[38;5;0;48;5;233m▄\e[48;5;0m \e[38;5;103;48;5;248m▄\e[38;5;4;48;5;237m▄\e[38;5;25;48;5;239m▄\e[38;5;60;48;5;60m▄\e[38;5;245;48;5;145m▄\e[38;5;0;48;5;0m▄▄▄▄\e[38;5;0;48;5;17m▄\e[38;5;0;48;5;0m▄\e[38;5;25;48;5;61m▄\e[38;5;20;48;5;27m▄\e[38;5;25;48;5;26m▄\e[38;5;18;48;5;19m▄\e[38;5;25;48;5;75m▄\e[38;5;0;48;5;0m▄▄▄▄\e[38;5;74;48;5;67m▄\e[38;5;25;48;5;24m▄\e[38;5;26;48;5;24m▄\e[38;5;25;48;5;67m▄\e[38;5;0;48;5;239m▄\e[38;5;0;48;5;0m▄\e[38;5;17;48;5;0m▄\e[38;5;0;48;5;0m▄\e[38;5;67;48;5;248m▄\e[38;5;73;48;5;246m▄\e[38;5;24;48;5;66m▄\e[38;5;38;48;5;103m▄\e[38;5;87;48;5;238m▄\e[38;5;0;48;5;238m▄\e[38;5;0;48;5;0m▄▄\e[38;5;44;48;5;80m▄\e[38;5;24;48;5;246m▄\e[38;5;31;48;5;60m▄\e[38;5;25;48;5;60m▄\e[38;5;81;48;5;145m▄\e[38;5;0;48;5;0m▄▄▄\e[38;5;0;48;5;18m▄\e[38;5;17;48;5;0m▄\e[38;5;0;48;5;0m▄\e[38;5;81;48;5;80m▄\e[38;5;25;48;5;67m▄\e[38;5;25;48;5;236m▄\e[38;5;31;48;5;246m▄\e[38;5;0;48;5;0m▄▄\e[38;5;19;48;5;0m▄\e[38;5;17;48;5;0m▄\e[38;5;0;48;5;0m▄▄\e[38;5;110;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[38;5;26;48;5;69m▄\e[38;5;18;48;5;233m▄\e[38;5;19;48;5;232m▄\e[38;5;18;48;5;24m▄\e[38;5;0;48;5;0m▄▄\e[48;5;0m \e[38;5;0;48;5;248m▄\e[38;5;17;48;5;237m▄\e[38;5;18;48;5;240m▄▄\e[38;5;17;48;5;236m▄\e[38;5;241;48;5;0m▄\e[38;5;0;48;5;237m▄\e[38;5;238;48;5;238m▄\e[38;5;239;48;5;238m▄\e[38;5;238;48;5;239m▄\e[38;5;52;48;5;52m▄\e[38;5;241;48;5;60m▄\e[38;5;236;48;5;239m▄\e[38;5;17;48;5;237m▄\e[38;5;232;48;5;0m▄\e[38;5;235;48;5;234m▄\e[38;5;235;48;5;23m▄\e[38;5;130;48;5;124m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[38;5;232;48;5;0m▄\e[38;5;0;48;5;232m▄\e[38;5;232;48;5;232m▄\e[38;5;232;48;5;24m▄\e[38;5;233;48;5;233m▄\e[38;5;0;48;5;0m▄▄\e[38;5;0;48;5;232m▄\e[38;5;0;48;5;0m▄▄\e[38;5;232;48;5;233m▄\e[38;5;233;48;5;235m▄\e[38;5;0;48;5;232m▄\e[48;5;0m \e[m +\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[38;5;0;48;5;234m▄\e[38;5;17;48;5;0m▄\e[38;5;0;48;5;4m▄▄\e[38;5;0;48;5;26m▄\e[38;5;0;48;5;0m▄▄\e[38;5;234;48;5;232m▄\e[38;5;233;48;5;17m▄\e[38;5;109;48;5;23m▄\e[38;5;236;48;5;0m▄\e[38;5;0;48;5;17m▄\e[38;5;111;48;5;146m▄\e[38;5;19;48;5;25m▄\e[38;5;19;48;5;17m▄\e[38;5;68;48;5;4m▄\e[38;5;123;48;5;116m▄\e[38;5;0;48;5;0m▄▄\e[38;5;18;48;5;0m▄\e[38;5;0;48;5;0m▄▄▄\e[38;5;0;48;5;23m▄\e[38;5;23;48;5;25m▄\e[38;5;45;48;5;25m▄\e[38;5;25;48;5;32m▄\e[38;5;45;48;5;38m▄\e[38;5;123;48;5;0m▄\e[38;5;0;48;5;0m▄\e[38;5;17;48;5;0m▄\e[38;5;45;48;5;24m▄\e[38;5;32;48;5;24m▄\e[38;5;45;48;5;32m▄\e[38;5;44;48;5;39m▄\e[38;5;0;48;5;87m▄\e[38;5;0;48;5;0m▄\e[38;5;19;48;5;17m▄\e[38;5;17;48;5;17m▄\e[38;5;19;48;5;0m▄\e[38;5;0;48;5;73m▄\e[38;5;38;48;5;44m▄\e[38;5;39;48;5;31m▄\e[38;5;45;48;5;32m▄\e[38;5;87;48;5;81m▄\e[38;5;0;48;5;0m▄\e[38;5;17;48;5;0m▄\e[38;5;19;48;5;17m▄\e[38;5;17;48;5;0m▄\e[38;5;87;48;5;32m▄\e[38;5;45;48;5;25m▄\e[38;5;45;48;5;32m▄\e[38;5;81;48;5;81m▄\e[38;5;0;48;5;0m▄▄\e[38;5;4;48;5;18m▄\e[38;5;17;48;5;4m▄\e[38;5;17;48;5;0m▄\e[38;5;81;48;5;75m▄\e[38;5;14;48;5;81m▄\e[38;5;14;48;5;25m▄\e[38;5;45;48;5;25m▄\e[38;5;45;48;5;81m▄\e[38;5;123;48;5;0m▄▄\e[38;5;123;48;5;17m▄\e[38;5;87;48;5;17m▄\e[38;5;81;48;5;0m▄\e[38;5;45;48;5;153m▄\e[38;5;87;48;5;31m▄\e[38;5;0;48;5;0m▄\e[38;5;123;48;5;32m▄\e[38;5;75;48;5;26m▄\e[38;5;19;48;5;25m▄\e[38;5;33;48;5;19m▄\e[38;5;33;48;5;32m▄\e[38;5;80;48;5;0m▄\e[38;5;0;48;5;0m▄▄▄\e[38;5;116;48;5;67m▄\e[38;5;19;48;5;19m▄\e[38;5;75;48;5;19m▄\e[38;5;75;48;5;18m▄\e[38;5;19;48;5;75m▄\e[38;5;116;48;5;0m▄\e[38;5;0;48;5;235m▄\e[38;5;232;48;5;236m▄\e[38;5;238;48;5;59m▄\e[38;5;239;48;5;233m▄\e[38;5;66;48;5;242m▄\e[38;5;237;48;5;236m▄\e[38;5;0;48;5;23m▄\e[38;5;17;48;5;0m▄\e[38;5;0;48;5;0m▄\e[38;5;236;48;5;23m▄\e[38;5;237;48;5;233m▄\e[38;5;0;48;5;0m▄\e[38;5;235;48;5;234m▄\e[48;5;0m \e[38;5;0;48;5;233m▄\e[38;5;232;48;5;0m▄▄\e[38;5;0;48;5;17m▄\e[38;5;0;48;5;232m▄\e[38;5;232;48;5;232m▄\e[38;5;0;48;5;232m▄\e[38;5;0;48;5;0m▄\e[38;5;232;48;5;0m▄\e[38;5;234;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[38;5;0;48;5;232m▄\e[38;5;0;48;5;0m▄\e[m +\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄▄\e[38;5;232;48;5;0m▄\e[38;5;0;48;5;0m▄\e[38;5;232;48;5;17m▄\e[38;5;24;48;5;233m▄\e[38;5;234;48;5;233m▄\e[38;5;0;48;5;0m▄\e[38;5;234;48;5;0m▄\e[38;5;0;48;5;232m▄\e[38;5;17;48;5;232m▄\e[38;5;23;48;5;0m▄\e[38;5;45;48;5;81m▄\e[38;5;25;48;5;19m▄\e[38;5;32;48;5;26m▄\e[38;5;87;48;5;25m▄\e[38;5;87;48;5;31m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[38;5;0;48;5;232m▄\e[38;5;0;48;5;0m▄\e[38;5;232;48;5;0m▄\e[38;5;4;48;5;0m▄\e[38;5;183;48;5;0m▄\e[38;5;132;48;5;0m▄\e[38;5;4;48;5;0m▄\e[38;5;0;48;5;87m▄\e[38;5;0;48;5;195m▄\e[38;5;0;48;5;123m▄\e[38;5;0;48;5;14m▄▄▄\e[38;5;0;48;5;123m▄▄\e[38;5;0;48;5;0m▄▄▄▄\e[38;5;17;48;5;18m▄\e[38;5;4;48;5;18m▄\e[38;5;19;48;5;0m▄\e[38;5;87;48;5;38m▄\e[38;5;14;48;5;38m▄\e[38;5;159;48;5;45m▄\e[38;5;0;48;5;0m▄\e[38;5;17;48;5;0m▄\e[38;5;17;48;5;17m▄\e[38;5;25;48;5;17m▄\e[38;5;4;48;5;0m▄\e[38;5;25;48;5;74m▄\e[38;5;87;48;5;32m▄\e[38;5;195;48;5;38m▄\e[38;5;0;48;5;0m▄\e[38;5;4;48;5;17m▄\e[38;5;4;48;5;232m▄\e[38;5;0;48;5;0m▄▄\e[38;5;238;48;5;0m▄\e[38;5;237;48;5;14m▄\e[38;5;236;48;5;45m▄\e[38;5;235;48;5;14m▄\e[38;5;234;48;5;45m▄\e[38;5;234;48;5;33m▄\e[38;5;234;48;5;39m▄\e[38;5;236;48;5;32m▄\e[38;5;235;48;5;31m▄\e[38;5;234;48;5;87m▄\e[38;5;234;48;5;33m▄\e[38;5;235;48;5;81m▄\e[38;5;234;48;5;81m▄\e[38;5;0;48;5;0m▄\e[38;5;81;48;5;123m▄\e[38;5;123;48;5;74m▄\e[38;5;26;48;5;25m▄\e[38;5;26;48;5;19m▄\e[38;5;32;48;5;4m▄\e[38;5;123;48;5;87m▄\e[38;5;236;48;5;0m▄\e[38;5;17;48;5;0m▄\e[38;5;24;48;5;17m▄\e[38;5;24;48;5;0m▄\e[38;5;44;48;5;33m▄\e[38;5;25;48;5;19m▄\e[38;5;19;48;5;19m▄▄\e[38;5;25;48;5;38m▄\e[38;5;0;48;5;0m▄\e[38;5;0;48;5;232m▄\e[38;5;233;48;5;23m▄\e[38;5;234;48;5;233m▄\e[38;5;66;48;5;238m▄\e[38;5;237;48;5;240m▄\e[38;5;0;48;5;17m▄\e[38;5;232;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;23m▄\e[38;5;239;48;5;94m▄\e[38;5;184;48;5;130m▄\e[38;5;235;48;5;236m▄\e[38;5;232;48;5;0m▄\e[38;5;0;48;5;0m▄▄▄▄▄\e[38;5;0;48;5;234m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[38;5;233;48;5;0m▄\e[38;5;0;48;5;0m▄▄▄▄▄\e[m +\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄\e[38;5;245;48;5;0m▄\e[38;5;0;48;5;234m▄\e[38;5;0;48;5;236m▄\e[38;5;0;48;5;17m▄\e[38;5;4;48;5;17m▄\e[38;5;0;48;5;24m▄\e[38;5;234;48;5;232m▄\e[38;5;0;48;5;0m▄\e[38;5;0;48;5;17m▄\e[38;5;25;48;5;17m▄\e[38;5;189;48;5;25m▄\e[38;5;87;48;5;195m▄\e[38;5;15;48;5;14m▄\e[38;5;159;48;5;14m▄▄\e[38;5;195;48;5;159m▄\e[38;5;159;48;5;195m▄\e[38;5;67;48;5;159m▄\e[38;5;241;48;5;253m▄\e[38;5;59;48;5;0m▄\e[38;5;243;48;5;0m▄\e[38;5;241;48;5;232m▄\e[38;5;239;48;5;4m▄\e[38;5;0;48;5;27m▄▄\e[38;5;0;48;5;32m▄\e[38;5;4;48;5;25m▄\e[38;5;26;48;5;33m▄\e[38;5;26;48;5;17m▄\e[38;5;25;48;5;17m▄\e[38;5;26;48;5;17m▄\e[38;5;32;48;5;234m▄\e[38;5;26;48;5;235m▄\e[38;5;232;48;5;23m▄▄\e[38;5;39;48;5;24m▄\e[38;5;26;48;5;14m▄\e[38;5;232;48;5;87m▄\e[38;5;0;48;5;14m▄\e[38;5;17;48;5;39m▄\e[38;5;45;48;5;45m▄\e[38;5;26;48;5;25m▄\e[38;5;117;48;5;123m▄\e[38;5;195;48;5;255m▄\e[38;5;18;48;5;0m▄\e[38;5;25;48;5;18m▄\e[38;5;87;48;5;26m▄\e[38;5;87;48;5;33m▄\e[38;5;39;48;5;27m▄\e[38;5;32;48;5;18m▄\e[38;5;15;48;5;195m▄\e[38;5;87;48;5;15m▄\e[38;5;39;48;5;17m▄\e[38;5;45;48;5;24m▄\e[38;5;0;48;5;81m▄\e[38;5;232;48;5;87m▄\e[38;5;24;48;5;45m▄\e[38;5;45;48;5;25m▄\e[38;5;38;48;5;24m▄\e[38;5;0;48;5;32m▄\e[38;5;26;48;5;17m▄\e[38;5;33;48;5;0m▄▄\e[38;5;39;48;5;232m▄\e[38;5;26;48;5;18m▄\e[38;5;19;48;5;25m▄\e[38;5;0;48;5;4m▄▄\e[38;5;38;48;5;18m▄\e[38;5;81;48;5;4m▄\e[38;5;31;48;5;74m▄\e[38;5;67;48;5;14m▄\e[38;5;195;48;5;15m▄\e[38;5;31;48;5;14m▄\e[38;5;32;48;5;159m▄\e[38;5;74;48;5;15m▄\e[38;5;195;48;5;159m▄\e[38;5;159;48;5;87m▄\e[38;5;159;48;5;23m▄\e[38;5;0;48;5;24m▄\e[38;5;24;48;5;24m▄\e[38;5;24;48;5;0m▄\e[38;5;45;48;5;39m▄\e[38;5;14;48;5;14m▄\e[38;5;14;48;5;45m▄\e[38;5;45;48;5;14m▄\e[38;5;14;48;5;195m▄\e[38;5;110;48;5;0m▄\e[38;5;17;48;5;0m▄\e[38;5;0;48;5;60m▄\e[38;5;24;48;5;60m▄\e[38;5;236;48;5;241m▄\e[38;5;234;48;5;237m▄\e[38;5;0;48;5;0m▄\e[38;5;0;48;5;237m▄\e[38;5;23;48;5;233m▄\e[38;5;23;48;5;239m▄\e[38;5;238;48;5;208m▄\e[38;5;234;48;5;238m▄\e[38;5;236;48;5;233m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄▄▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[m +\e[38;5;0;48;5;0m▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄\e[38;5;0;48;5;233m▄\e[38;5;0;48;5;17m▄\e[38;5;17;48;5;0m▄\e[38;5;24;48;5;232m▄\e[38;5;87;48;5;17m▄\e[38;5;123;48;5;45m▄\e[38;5;32;48;5;87m▄\e[38;5;25;48;5;195m▄\e[38;5;14;48;5;87m▄\e[38;5;4;48;5;81m▄\e[38;5;18;48;5;4m▄\e[38;5;17;48;5;17m▄\e[38;5;17;48;5;4m▄\e[38;5;0;48;5;234m▄\e[38;5;0;48;5;0m▄▄▄▄▄▄▄▄\e[38;5;232;48;5;0m▄\e[38;5;0;48;5;17m▄\e[38;5;0;48;5;0m▄▄▄▄▄▄\e[38;5;0;48;5;25m▄\e[38;5;0;48;5;17m▄\e[38;5;33;48;5;0m▄\e[38;5;4;48;5;32m▄\e[38;5;0;48;5;26m▄\e[38;5;18;48;5;17m▄\e[38;5;0;48;5;0m▄\e[38;5;19;48;5;24m▄\e[38;5;0;48;5;24m▄\e[38;5;33;48;5;255m▄\e[38;5;25;48;5;25m▄\e[38;5;25;48;5;81m▄\e[38;5;25;48;5;123m▄\e[38;5;45;48;5;123m▄\e[38;5;26;48;5;81m▄\e[38;5;26;48;5;26m▄\e[38;5;18;48;5;25m▄\e[38;5;17;48;5;38m▄\e[38;5;39;48;5;0m▄\e[38;5;32;48;5;0m▄\e[38;5;0;48;5;25m▄\e[38;5;0;48;5;38m▄\e[38;5;18;48;5;0m▄\e[38;5;0;48;5;0m▄\e[38;5;0;48;5;17m▄\e[38;5;0;48;5;23m▄\e[38;5;0;48;5;0m▄▄▄\e[48;5;0m \e[38;5;232;48;5;0m▄\e[38;5;0;48;5;0m▄\e[38;5;17;48;5;0m▄\e[38;5;0;48;5;0m▄▄▄\e[38;5;232;48;5;233m▄\e[38;5;4;48;5;17m▄\e[38;5;26;48;5;45m▄\e[38;5;17;48;5;17m▄\e[38;5;18;48;5;18m▄\e[38;5;25;48;5;17m▄\e[38;5;38;48;5;38m▄\e[38;5;18;48;5;4m▄\e[38;5;0;48;5;232m▄\e[38;5;0;48;5;17m▄\e[38;5;17;48;5;0m▄\e[38;5;18;48;5;0m▄\e[38;5;117;48;5;45m▄\e[38;5;15;48;5;123m▄\e[38;5;195;48;5;14m▄\e[38;5;15;48;5;14m▄\e[38;5;15;48;5;87m▄\e[38;5;87;48;5;87m▄\e[38;5;14;48;5;17m▄\e[38;5;18;48;5;17m▄\e[38;5;19;48;5;17m▄\e[38;5;4;48;5;24m▄\e[38;5;4;48;5;0m▄\e[38;5;0;48;5;17m▄▄\e[38;5;0;48;5;237m▄\e[38;5;232;48;5;233m▄▄\e[38;5;234;48;5;235m▄\e[38;5;233;48;5;232m▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[m +\e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[38;5;232;48;5;233m▄\e[38;5;0;48;5;0m▄▄▄\e[38;5;235;48;5;0m▄\e[38;5;232;48;5;0m▄\e[38;5;0;48;5;45m▄\e[38;5;232;48;5;232m▄\e[38;5;0;48;5;232m▄\e[38;5;4;48;5;0m▄\e[38;5;17;48;5;24m▄\e[38;5;20;48;5;25m▄\e[38;5;4;48;5;18m▄\e[38;5;45;48;5;45m▄\e[38;5;19;48;5;4m▄\e[38;5;19;48;5;19m▄\e[38;5;4;48;5;19m▄\e[38;5;18;48;5;18m▄\e[38;5;0;48;5;17m▄\e[38;5;0;48;5;19m▄\e[38;5;233;48;5;0m▄\e[38;5;232;48;5;0m▄▄\e[38;5;235;48;5;0m▄\e[38;5;233;48;5;236m▄\e[38;5;236;48;5;66m▄\e[38;5;240;48;5;17m▄\e[38;5;0;48;5;0m▄\e[38;5;24;48;5;0m▄\e[38;5;17;48;5;0m▄\e[38;5;0;48;5;232m▄\e[38;5;17;48;5;0m▄\e[38;5;0;48;5;0m▄▄\e[38;5;234;48;5;0m▄\e[38;5;0;48;5;0m▄\e[38;5;17;48;5;0m▄\e[38;5;232;48;5;0m▄\e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;27m▄\e[38;5;17;48;5;0m▄\e[38;5;0;48;5;27m▄\e[38;5;27;48;5;17m▄\e[38;5;0;48;5;18m▄\e[38;5;26;48;5;17m▄\e[38;5;17;48;5;17m▄\e[38;5;18;48;5;19m▄\e[38;5;20;48;5;12m▄\e[38;5;0;48;5;0m▄\e[38;5;19;48;5;0m▄\e[38;5;0;48;5;33m▄\e[38;5;0;48;5;0m▄\e[38;5;18;48;5;17m▄\e[38;5;0;48;5;0m▄\e[38;5;19;48;5;17m▄\e[38;5;0;48;5;232m▄\e[38;5;0;48;5;0m▄\e[38;5;17;48;5;0m▄\e[38;5;0;48;5;0m▄\e[38;5;234;48;5;17m▄\e[38;5;4;48;5;4m▄\e[38;5;17;48;5;0m▄\e[38;5;232;48;5;17m▄\e[38;5;0;48;5;232m▄\e[38;5;0;48;5;17m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[38;5;0;48;5;17m▄\e[38;5;17;48;5;4m▄\e[38;5;17;48;5;17m▄▄\e[38;5;4;48;5;26m▄\e[38;5;17;48;5;4m▄\e[38;5;232;48;5;17m▄\e[38;5;17;48;5;17m▄\e[38;5;39;48;5;39m▄\e[38;5;4;48;5;18m▄\e[38;5;0;48;5;0m▄▄\e[38;5;232;48;5;17m▄\e[38;5;234;48;5;17m▄\e[38;5;0;48;5;17m▄\e[38;5;81;48;5;195m▄\e[38;5;26;48;5;26m▄\e[38;5;19;48;5;26m▄\e[38;5;24;48;5;19m▄\e[38;5;25;48;5;18m▄\e[38;5;4;48;5;14m▄\e[38;5;18;48;5;159m▄\e[38;5;159;48;5;45m▄\e[38;5;87;48;5;17m▄\e[38;5;4;48;5;0m▄\e[38;5;17;48;5;17m▄\e[38;5;0;48;5;17m▄\e[38;5;234;48;5;236m▄\e[38;5;0;48;5;23m▄\e[38;5;238;48;5;0m▄\e[38;5;0;48;5;23m▄\e[38;5;232;48;5;237m▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄▄▄▄▄\e[38;5;18;48;5;0m▄\e[38;5;0;48;5;0m▄▄▄\e[48;5;0m \e[m +\e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄▄\e[38;5;0;48;5;233m▄\e[38;5;0;48;5;0m▄▄\e[38;5;239;48;5;232m▄\e[38;5;0;48;5;0m▄▄\e[38;5;234;48;5;0m▄\e[38;5;232;48;5;17m▄\e[38;5;17;48;5;25m▄\e[38;5;17;48;5;19m▄\e[38;5;0;48;5;4m▄\e[38;5;232;48;5;18m▄\e[38;5;27;48;5;39m▄\e[38;5;20;48;5;19m▄\e[38;5;19;48;5;25m▄\e[38;5;25;48;5;18m▄\e[38;5;26;48;5;25m▄\e[38;5;0;48;5;0m▄\e[38;5;232;48;5;232m▄\e[38;5;232;48;5;239m▄\e[38;5;0;48;5;238m▄\e[38;5;0;48;5;234m▄▄\e[38;5;237;48;5;234m▄\e[38;5;235;48;5;0m▄\e[38;5;232;48;5;236m▄\e[38;5;243;48;5;102m▄\e[38;5;8;48;5;66m▄\e[38;5;66;48;5;241m▄\e[38;5;103;48;5;233m▄\e[38;5;233;48;5;0m▄\e[38;5;0;48;5;0m▄▄▄\e[38;5;233;48;5;238m▄\e[38;5;23;48;5;0m▄\e[38;5;24;48;5;17m▄\e[38;5;17;48;5;0m▄▄▄\e[38;5;0;48;5;0m▄\e[38;5;0;48;5;26m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[38;5;0;48;5;26m▄\e[38;5;232;48;5;24m▄\e[38;5;31;48;5;17m▄\e[38;5;18;48;5;20m▄\e[38;5;17;48;5;17m▄\e[38;5;232;48;5;26m▄\e[38;5;0;48;5;0m▄▄\e[38;5;0;48;5;232m▄\e[38;5;0;48;5;0m▄▄\e[38;5;17;48;5;0m▄\e[38;5;0;48;5;0m▄▄\e[38;5;23;48;5;232m▄\e[38;5;23;48;5;24m▄\e[38;5;234;48;5;17m▄\e[38;5;0;48;5;0m▄\e[38;5;235;48;5;234m▄\e[38;5;232;48;5;232m▄\e[38;5;232;48;5;0m▄\e[38;5;17;48;5;0m▄▄\e[38;5;232;48;5;0m▄\e[38;5;238;48;5;0m▄\e[38;5;24;48;5;23m▄\e[38;5;17;48;5;4m▄\e[38;5;24;48;5;24m▄▄\e[38;5;110;48;5;4m▄\e[38;5;24;48;5;17m▄\e[38;5;17;48;5;17m▄\e[38;5;32;48;5;32m▄\e[38;5;0;48;5;24m▄\e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;235;48;5;0m▄\e[38;5;17;48;5;4m▄\e[38;5;17;48;5;17m▄\e[38;5;19;48;5;27m▄\e[38;5;25;48;5;232m▄\e[38;5;18;48;5;18m▄\e[38;5;17;48;5;18m▄\e[38;5;0;48;5;24m▄\e[38;5;17;48;5;4m▄\e[38;5;0;48;5;17m▄\e[38;5;17;48;5;17m▄\e[38;5;4;48;5;4m▄\e[38;5;17;48;5;68m▄\e[38;5;24;48;5;14m▄\e[38;5;17;48;5;37m▄\e[38;5;0;48;5;0m▄\e[38;5;14;48;5;0m▄\e[38;5;0;48;5;23m▄\e[38;5;236;48;5;236m▄\e[38;5;0;48;5;236m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[38;5;0;48;5;233m▄\e[38;5;0;48;5;0m▄\e[38;5;233;48;5;17m▄\e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄\e[48;5;0m \e[m +\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;232m▄\e[38;5;0;48;5;0m▄▄\e[38;5;0;48;5;232m▄\e[38;5;0;48;5;0m▄▄▄▄\e[38;5;0;48;5;17m▄\e[38;5;25;48;5;27m▄\e[38;5;234;48;5;18m▄\e[38;5;232;48;5;17m▄\e[38;5;24;48;5;17m▄\e[38;5;0;48;5;17m▄\e[38;5;0;48;5;233m▄\e[38;5;232;48;5;0m▄\e[38;5;0;48;5;0m▄▄▄\e[38;5;233;48;5;0m▄\e[38;5;0;48;5;233m▄\e[38;5;0;48;5;232m▄▄\e[38;5;0;48;5;0m▄\e[38;5;232;48;5;234m▄\e[38;5;232;48;5;23m▄\e[38;5;235;48;5;66m▄\e[38;5;0;48;5;236m▄▄\e[38;5;0;48;5;60m▄\e[38;5;0;48;5;0m▄\e[38;5;17;48;5;232m▄\e[38;5;0;48;5;23m▄\e[48;5;0m \e[38;5;233;48;5;232m▄\e[38;5;0;48;5;237m▄\e[38;5;0;48;5;0m▄▄▄\e[38;5;208;48;5;0m▄\e[38;5;0;48;5;0m▄▄\e[38;5;17;48;5;19m▄\e[38;5;0;48;5;23m▄\e[38;5;0;48;5;0m▄\e[38;5;33;48;5;27m▄\e[38;5;0;48;5;0m▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄\e[38;5;233;48;5;232m▄\e[38;5;233;48;5;17m▄\e[38;5;241;48;5;60m▄\e[38;5;8;48;5;25m▄\e[38;5;0;48;5;0m▄\e[38;5;233;48;5;24m▄\e[38;5;232;48;5;66m▄\e[38;5;17;48;5;232m▄\e[38;5;0;48;5;0m▄\e[38;5;0;48;5;98m▄\e[38;5;0;48;5;17m▄\e[38;5;17;48;5;0m▄\e[38;5;234;48;5;236m▄\e[38;5;243;48;5;248m▄\e[38;5;243;48;5;245m▄\e[38;5;242;48;5;0m▄\e[38;5;233;48;5;4m▄\e[38;5;241;48;5;25m▄\e[38;5;23;48;5;17m▄\e[38;5;0;48;5;17m▄\e[38;5;234;48;5;24m▄\e[38;5;25;48;5;17m▄\e[38;5;17;48;5;0m▄\e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;4;48;5;236m▄\e[38;5;232;48;5;234m▄\e[38;5;15;48;5;19m▄\e[38;5;17;48;5;25m▄\e[38;5;0;48;5;236m▄\e[38;5;24;48;5;0m▄\e[38;5;17;48;5;4m▄▄\e[38;5;0;48;5;17m▄\e[38;5;23;48;5;0m▄\e[38;5;17;48;5;0m▄\e[38;5;23;48;5;17m▄\e[38;5;233;48;5;23m▄\e[38;5;0;48;5;0m▄\e[38;5;17;48;5;0m▄\e[38;5;232;48;5;232m▄\e[38;5;0;48;5;235m▄\e[38;5;233;48;5;234m▄\e[48;5;0m \e[38;5;234;48;5;234m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄\e[48;5;0m \e[m +\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄▄\e[38;5;233;48;5;0m▄\e[38;5;0;48;5;0m▄▄▄\e[38;5;0;48;5;17m▄\e[38;5;0;48;5;234m▄\e[38;5;232;48;5;237m▄\e[38;5;233;48;5;232m▄\e[38;5;4;48;5;235m▄\e[38;5;0;48;5;233m▄\e[38;5;0;48;5;0m▄\e[38;5;0;48;5;233m▄\e[38;5;0;48;5;234m▄\e[38;5;0;48;5;0m▄▄\e[38;5;0;48;5;232m▄\e[38;5;232;48;5;0m▄\e[38;5;0;48;5;0m▄\e[38;5;232;48;5;0m▄\e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;232m▄\e[38;5;101;48;5;232m▄\e[38;5;232;48;5;0m▄\e[38;5;0;48;5;0m▄▄▄\e[38;5;233;48;5;0m▄\e[38;5;233;48;5;234m▄\e[38;5;232;48;5;234m▄\e[38;5;239;48;5;239m▄\e[38;5;236;48;5;237m▄\e[38;5;0;48;5;0m▄\e[38;5;0;48;5;232m▄▄\e[38;5;0;48;5;233m▄\e[38;5;0;48;5;0m▄▄▄▄▄\e[38;5;0;48;5;18m▄\e[38;5;17;48;5;0m▄\e[38;5;0;48;5;0m▄▄\e[38;5;232;48;5;232m▄\e[38;5;0;48;5;0m▄▄\e[38;5;232;48;5;232m▄\e[38;5;232;48;5;0m▄\e[38;5;0;48;5;236m▄\e[38;5;0;48;5;0m▄\e[38;5;232;48;5;234m▄\e[38;5;232;48;5;233m▄\e[38;5;0;48;5;17m▄\e[38;5;102;48;5;245m▄\e[38;5;245;48;5;243m▄\e[38;5;60;48;5;0m▄\e[38;5;0;48;5;17m▄\e[38;5;0;48;5;0m▄\e[38;5;236;48;5;232m▄\e[38;5;236;48;5;233m▄\e[38;5;235;48;5;233m▄\e[38;5;238;48;5;236m▄\e[38;5;232;48;5;60m▄\e[38;5;242;48;5;245m▄\e[38;5;236;48;5;236m▄\e[38;5;233;48;5;17m▄\e[38;5;0;48;5;17m▄\e[38;5;233;48;5;0m▄\e[38;5;0;48;5;220m▄\e[38;5;0;48;5;0m▄▄\e[48;5;0m \e[38;5;239;48;5;24m▄\e[38;5;238;48;5;234m▄\e[38;5;0;48;5;17m▄\e[38;5;110;48;5;73m▄\e[38;5;60;48;5;23m▄\e[38;5;17;48;5;0m▄\e[38;5;0;48;5;0m▄\e[38;5;232;48;5;233m▄\e[38;5;235;48;5;233m▄\e[38;5;235;48;5;0m▄\e[38;5;234;48;5;17m▄\e[38;5;237;48;5;233m▄\e[38;5;233;48;5;17m▄\e[38;5;23;48;5;17m▄\e[38;5;235;48;5;0m▄\e[38;5;233;48;5;0m▄\e[38;5;235;48;5;232m▄\e[38;5;52;48;5;232m▄\e[48;5;0m \e[38;5;232;48;5;232m▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄▄▄▄\e[48;5;0m \e[m +\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄\e[38;5;233;48;5;0m▄\e[38;5;232;48;5;23m▄\e[38;5;233;48;5;17m▄\e[38;5;0;48;5;0m▄\e[38;5;233;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[38;5;233;48;5;0m▄\e[38;5;239;48;5;236m▄\e[38;5;232;48;5;0m▄\e[38;5;232;48;5;232m▄\e[38;5;233;48;5;234m▄\e[38;5;232;48;5;233m▄\e[38;5;0;48;5;0m▄\e[38;5;232;48;5;233m▄\e[38;5;241;48;5;235m▄\e[48;5;0m \e[38;5;0;48;5;232m▄\e[38;5;0;48;5;0m▄\e[38;5;237;48;5;60m▄\e[38;5;235;48;5;232m▄\e[48;5;0m \e[38;5;234;48;5;0m▄\e[38;5;232;48;5;234m▄\e[38;5;233;48;5;234m▄\e[38;5;0;48;5;0m▄▄▄\e[38;5;0;48;5;235m▄\e[38;5;0;48;5;240m▄\e[38;5;59;48;5;239m▄▄\e[38;5;66;48;5;0m▄\e[38;5;23;48;5;0m▄\e[38;5;0;48;5;232m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[38;5;233;48;5;17m▄\e[38;5;17;48;5;0m▄\e[38;5;0;48;5;0m▄\e[38;5;234;48;5;0m▄▄\e[38;5;232;48;5;233m▄\e[38;5;235;48;5;236m▄\e[38;5;0;48;5;233m▄\e[38;5;0;48;5;232m▄\e[38;5;0;48;5;0m▄\e[38;5;241;48;5;236m▄\e[38;5;235;48;5;232m▄\e[38;5;236;48;5;236m▄\e[38;5;0;48;5;238m▄\e[38;5;0;48;5;242m▄\e[38;5;24;48;5;243m▄\e[38;5;23;48;5;23m▄\e[38;5;0;48;5;0m▄\e[38;5;233;48;5;17m▄\e[38;5;234;48;5;233m▄\e[38;5;233;48;5;233m▄\e[38;5;0;48;5;241m▄\e[38;5;234;48;5;235m▄\e[38;5;23;48;5;0m▄\e[38;5;232;48;5;0m▄\e[38;5;232;48;5;232m▄\e[38;5;233;48;5;232m▄\e[38;5;233;48;5;235m▄\e[38;5;233;48;5;0m▄\e[38;5;232;48;5;0m▄\e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;66;48;5;240m▄\e[38;5;239;48;5;59m▄\e[38;5;232;48;5;234m▄\e[38;5;0;48;5;0m▄\e[38;5;59;48;5;240m▄\e[38;5;60;48;5;24m▄▄\e[38;5;232;48;5;0m▄\e[38;5;235;48;5;232m▄\e[38;5;232;48;5;232m▄\e[38;5;0;48;5;0m▄\e[38;5;236;48;5;0m▄\e[38;5;236;48;5;236m▄\e[38;5;233;48;5;233m▄\e[38;5;0;48;5;232m▄\e[38;5;232;48;5;0m▄\e[38;5;0;48;5;0m▄\e[38;5;0;48;5;232m▄\e[38;5;0;48;5;214m▄\e[38;5;0;48;5;0m▄\e[38;5;233;48;5;232m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[m +\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄\e[38;5;233;48;5;0m▄\e[38;5;24;48;5;0m▄\e[38;5;232;48;5;0m▄\e[38;5;17;48;5;0m▄\e[38;5;0;48;5;232m▄\e[38;5;0;48;5;0m▄▄\e[38;5;0;48;5;235m▄\e[38;5;0;48;5;232m▄\e[38;5;232;48;5;0m▄\e[38;5;0;48;5;0m▄▄▄\e[38;5;0;48;5;236m▄\e[48;5;0m \e[38;5;0;48;5;232m▄\e[38;5;232;48;5;236m▄\e[38;5;237;48;5;233m▄\e[38;5;0;48;5;0m▄▄▄▄\e[38;5;0;48;5;236m▄\e[38;5;0;48;5;66m▄\e[38;5;232;48;5;237m▄\e[38;5;234;48;5;237m▄\e[38;5;233;48;5;60m▄\e[38;5;0;48;5;238m▄\e[38;5;234;48;5;233m▄\e[38;5;0;48;5;0m▄\e[38;5;232;48;5;0m▄\e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;232m▄\e[48;5;0m \e[38;5;234;48;5;0m▄\e[38;5;0;48;5;232m▄\e[38;5;0;48;5;23m▄\e[38;5;0;48;5;232m▄\e[38;5;232;48;5;234m▄\e[38;5;0;48;5;238m▄\e[38;5;0;48;5;234m▄\e[38;5;0;48;5;8m▄\e[38;5;0;48;5;239m▄\e[38;5;0;48;5;232m▄\e[38;5;0;48;5;0m▄▄▄\e[38;5;0;48;5;233m▄\e[38;5;233;48;5;232m▄\e[38;5;0;48;5;232m▄\e[38;5;0;48;5;17m▄\e[38;5;0;48;5;233m▄\e[38;5;232;48;5;0m▄\e[38;5;23;48;5;0m▄\e[38;5;24;48;5;0m▄\e[38;5;0;48;5;0m▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄\e[38;5;233;48;5;0m▄\e[38;5;237;48;5;240m▄\e[38;5;239;48;5;233m▄\e[38;5;59;48;5;246m▄\e[38;5;239;48;5;66m▄\e[38;5;60;48;5;0m▄\e[38;5;240;48;5;240m▄\e[38;5;235;48;5;60m▄\e[38;5;236;48;5;233m▄\e[38;5;234;48;5;237m▄\e[38;5;236;48;5;236m▄\e[38;5;236;48;5;166m▄\e[38;5;233;48;5;233m▄\e[38;5;232;48;5;235m▄\e[38;5;237;48;5;236m▄\e[38;5;236;48;5;234m▄\e[38;5;235;48;5;0m▄\e[38;5;0;48;5;232m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[m +\e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;235m▄▄\e[38;5;0;48;5;0m▄\e[38;5;234;48;5;0m▄\e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;233;48;5;0m▄\e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[38;5;232;48;5;0m▄\e[38;5;232;48;5;232m▄\e[38;5;239;48;5;235m▄\e[38;5;232;48;5;233m▄\e[38;5;233;48;5;232m▄\e[38;5;0;48;5;238m▄\e[38;5;0;48;5;232m▄\e[38;5;0;48;5;0m▄▄▄▄▄▄\e[38;5;235;48;5;0m▄\e[38;5;232;48;5;0m▄\e[38;5;0;48;5;0m▄▄\e[38;5;0;48;5;232m▄\e[38;5;0;48;5;236m▄\e[38;5;0;48;5;0m▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄\e[38;5;232;48;5;0m▄\e[38;5;17;48;5;0m▄\e[38;5;24;48;5;232m▄\e[38;5;4;48;5;0m▄\e[38;5;0;48;5;0m▄▄\e[38;5;235;48;5;0m▄\e[38;5;0;48;5;0m▄▄▄▄\e[48;5;0m \e[38;5;232;48;5;232m▄\e[38;5;0;48;5;232m▄\e[38;5;0;48;5;0m▄\e[38;5;233;48;5;0m▄\e[38;5;25;48;5;233m▄\e[38;5;233;48;5;0m▄\e[38;5;234;48;5;232m▄\e[38;5;235;48;5;233m▄\e[38;5;0;48;5;0m▄▄▄▄▄\e[48;5;0m \e[38;5;237;48;5;233m▄\e[38;5;241;48;5;238m▄\e[38;5;240;48;5;240m▄\e[38;5;239;48;5;0m▄\e[38;5;60;48;5;0m▄\e[38;5;238;48;5;238m▄\e[38;5;0;48;5;239m▄\e[38;5;23;48;5;240m▄\e[38;5;235;48;5;236m▄\e[38;5;237;48;5;234m▄\e[38;5;234;48;5;233m▄\e[38;5;232;48;5;232m▄\e[38;5;0;48;5;236m▄\e[38;5;0;48;5;235m▄\e[38;5;0;48;5;0m▄\e[38;5;0;48;5;236m▄\e[38;5;233;48;5;232m▄\e[38;5;0;48;5;0m▄\e[38;5;233;48;5;0m▄\e[48;5;0m \e[38;5;232;48;5;0m▄\e[48;5;0m \e[38;5;232;48;5;0m▄\e[48;5;0m \e[m +\e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄\e[38;5;238;48;5;0m▄\e[38;5;239;48;5;237m▄\e[38;5;0;48;5;240m▄\e[38;5;0;48;5;0m▄\e[38;5;232;48;5;0m▄\e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄\e[38;5;232;48;5;0m▄\e[38;5;17;48;5;0m▄\e[38;5;18;48;5;0m▄\e[38;5;0;48;5;0m▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[38;5;0;48;5;24m▄\e[38;5;0;48;5;25m▄\e[38;5;0;48;5;0m▄▄\e[38;5;232;48;5;0m▄\e[38;5;24;48;5;189m▄\e[38;5;232;48;5;232m▄\e[38;5;0;48;5;0m▄▄▄▄\e[38;5;17;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[38;5;232;48;5;0m▄▄\e[38;5;0;48;5;0m▄\e[38;5;0;48;5;236m▄\e[38;5;0;48;5;232m▄\e[38;5;24;48;5;23m▄\e[38;5;4;48;5;0m▄\e[38;5;25;48;5;0m▄\e[38;5;0;48;5;23m▄\e[38;5;232;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;232;48;5;0m▄\e[38;5;0;48;5;0m▄▄▄\e[38;5;0;48;5;17m▄\e[38;5;0;48;5;24m▄\e[38;5;0;48;5;23m▄\e[38;5;0;48;5;0m▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄▄\e[38;5;234;48;5;238m▄\e[38;5;236;48;5;233m▄\e[38;5;232;48;5;233m▄\e[38;5;0;48;5;60m▄\e[38;5;23;48;5;0m▄\e[38;5;233;48;5;232m▄\e[38;5;0;48;5;233m▄\e[38;5;239;48;5;23m▄\e[38;5;235;48;5;60m▄\e[38;5;232;48;5;24m▄\e[48;5;0m \e[38;5;232;48;5;233m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;240;48;5;0m▄\e[38;5;59;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[m +\e[48;5;0m \e[38;5;0;48;5;0m▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄▄▄▄▄▄\e[48;5;0m \e[38;5;0;48;5;234m▄\e[38;5;232;48;5;234m▄\e[38;5;0;48;5;0m▄▄▄\e[38;5;232;48;5;0m▄\e[38;5;0;48;5;232m▄\e[38;5;0;48;5;0m▄\e[38;5;0;48;5;232m▄\e[38;5;0;48;5;0m▄▄\e[38;5;0;48;5;235m▄\e[38;5;0;48;5;239m▄\e[38;5;0;48;5;240m▄\e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄\e[48;5;0m \e[38;5;0;48;5;234m▄\e[48;5;0m \e[38;5;0;48;5;0m▄▄▄▄\e[48;5;0m \e[m +\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m \e[38;5;0;48;5;0m▄\e[48;5;0m `; + +const LIGHTHOUSE_HEADER = ` + . + /|\\ + / | \\ + / | \\ + / | \\ + / | \\ + / .--+--. \\ + /__/ | \\__\\ + [__] | [__] + | + ~~~~~~~~~|~~~~~~~~~ +`; + +const GLITCH_ART = ` + ▓▓▓▒▒░░ E̸̢R̷̨R̵̢O̸̧R̷̨ ░░▒▒▓▓▓ + ░▒▓█ D̶̨A̷̧T̸̢Ą̵ C̷̢Ǫ̸Ŗ̵R̷̨U̸̢P̵̧T̷̨ █▓▒░ + ▓░▒█ ???????????????? █▒░▓ +`; + +const END_SCREEN = ` + ╔════════════════════════════════════════╗ + ║ ║ + ║ CONNECTION TERMINATED ║ + ║ ║ + ║ Five days remain... ║ + ║ ║ + ╚════════════════════════════════════════╝ +`; + +const boxingDayGame = { + id: "boxing-day", + name: "Boxing Day", + command: "dial", + description: "Connect to Dark Tower BBS - December 26, 1999", + + initialState: { + // Core progression flags + downloaded_cascade: false, + talked_to_sysop: false, + deleted_corrupted_file: false, + route_taken: null, // "immediate" | "cautious" | "ignored" + + // Progression tracking + read_new_message: false, + found_number: false, + dialed_lighthouse: false, + + // rm -rf deletion flags (persist across sessions) + archives_deleted: false, + corrupted_file_deleted: false, + + // Scene visit tracking + visited: {}, + }, + + intro: [ + { type: "ascii", art: BOXING_DAY_TITLE, className: "game-ascii" }, + "", + { text: "December 26, 1999", className: "info" }, + { text: "11:47 PM", className: "info" }, + "", + { type: "delay", ms: 600 }, + "Five days until the millennium.", + { type: "delay", ms: 400 }, + "Five days until everything might change.", + "", + { type: "delay", ms: 600 }, + "Your 56k modem hums quietly in the dark.", + "The house is silent. Everyone else is asleep.", + "", + { type: "delay", ms: 400 }, + { text: 'Type "quit" at any time to save and exit.', className: "info" }, + ], + + startScene: "connect_prompt", + + scenes: { + // ========================================== + // OPENING SEQUENCE + // ========================================== + + connect_prompt: { + content: [ + "Your terminal awaits a command.", + "", + { text: "The familiar glow illuminates your face.", className: "info" }, + ], + options: [{ text: "Connect to Dark Tower BBS", next: "modem_connect" }], + }, + + modem_connect: { + clear: true, + content: [ + { type: "typewriter", text: "ATDT 555-0199", speed: 80 }, + { type: "delay", ms: 800 }, + "", + { text: "DIALING...", className: "info" }, + { type: "delay", ms: 600 }, + "", + { text: "RING", className: "warning" }, + { type: "delay", ms: 800 }, + { text: "RING", className: "warning" }, + { type: "delay", ms: 600 }, + "", + { + type: "typewriter", + text: "~~ eEe ~~ EEE ~~ eee ~~ CONNECT 56000", + speed: 35, + }, + { type: "delay", ms: 400 }, + "", + { text: "Carrier detected.", className: "success" }, + { type: "delay", ms: 300 }, + { text: "Negotiating protocol...", className: "info" }, + { type: "delay", ms: 500 }, + { text: "Connection established.", className: "success" }, + ], + next: "dark_tower_main", + delay: 800, + }, + + // ========================================== + // DARK TOWER BBS HUB + // ========================================== + + dark_tower_main: { + clear: true, + content: [ + { type: "ansi", art: DARK_TOWER_HEADER, className: "game-ansi-art" }, + "", + { text: "SysOp: NightWatchman", className: "info" }, + { text: "Users Online: 3", className: "info" }, + { text: "Local Time: 11:52 PM", className: "info" }, + "", + // New message notification (only if not read) + { + condition: { not: "read_new_message" }, + content: [ + { + text: "*** YOU HAVE 1 NEW PRIVATE MESSAGE ***", + className: "warning", + }, + "", + ], + }, + // Return visitor message + { + condition: "visited.dark_tower_main", + content: { text: "[Returning to main menu]", className: "info" }, + }, + ], + onEnter: [{ set: "visited.dark_tower_main", value: true }], + prompt: "Select:", + options: [ + { + text: "Read Private Messages", + next: "read_messages", + condition: { not: "read_new_message" }, + }, + { + text: "Message Archive", + next: "message_archive", + condition: "read_new_message", + }, + { text: "Browse Message Boards", next: "browse_boards" }, + { text: "File Library", next: "dark_tower_files" }, + { text: "Who's Online", next: "whos_online" }, + { + text: "Dial The Lighthouse (555-0237)", + next: "confirm_dial_lighthouse", + condition: "found_number", + }, + { text: "Disconnect", next: "leave_early" }, + ], + }, + + // ========================================== + // MESSAGE DISCOVERY + // ========================================== + + read_messages: { + title: "Private Messages", + content: [ + "═══ INBOX ═══", + "", + { text: "1 unread message", className: "warning" }, + "", + " FROM: [SENDER UNKNOWN]", + " DATE: 12/25/1999 11:59:59 PM", + " SUBJ: For your eyes only", + "", + ], + options: [ + { text: "Open message", next: "new_message" }, + { text: "Back to main menu", next: "dark_tower_main" }, + ], + }, + + new_message: { + title: "Reading Message...", + content: [ + { type: "delay", ms: 300 }, + "─── BEGIN MESSAGE ───", + "", + { + type: "typewriter", + text: "I know you've been searching.", + speed: 50, + }, + { type: "delay", ms: 500 }, + "", + { + type: "typewriter", + text: "The lighthouse keeper left something behind.", + speed: 50, + }, + { type: "delay", ms: 500 }, + "", + { + type: "typewriter", + text: "Dial 555-0237 before midnight strikes.", + speed: 50, + }, + { type: "delay", ms: 500 }, + "", + { type: "typewriter", text: "The cascade is coming.", speed: 70 }, + { type: "delay", ms: 800 }, + "", + "─── END MESSAGE ───", + "", + { + text: "The number burns in your mind: 555-0237", + className: "warning", + }, + "", + { type: "delay", ms: 400 }, + "Your clock reads 11:54 PM.", + "Six minutes until midnight.", + ], + onEnter: [ + { set: "read_new_message", value: true }, + { set: "found_number", value: true }, + ], + prompt: "What do you do?", + options: [ + { + text: "Dial the number NOW", + next: "choice_immediate", + actions: [{ set: "route_taken", value: "immediate" }], + }, + { + text: "Explore Dark Tower first", + next: "dark_tower_main", + actions: [{ set: "route_taken", value: "cautious" }], + }, + { + text: "Delete the message and forget it", + next: "choice_ignored", + actions: [{ set: "route_taken", value: "ignored" }], + }, + ], + }, + + message_archive: { + title: "Message Archive", + content: [ + "═══ ARCHIVED MESSAGES ═══", + "", + { + condition: "read_new_message", + content: [ + " 1. [SENDER UNKNOWN] - For your eyes only", + { text: " (The number: 555-0237)", className: "info" }, + ], + }, + "", + { text: "No other messages.", className: "info" }, + ], + options: [{ text: "Back", next: "dark_tower_main" }], + }, + + // ========================================== + // CHOICE A/B/C - WHEN TO DIAL + // ========================================== + + choice_immediate: { + clear: true, + content: [ + "Your fingers move before doubt can settle.", + "", + { type: "typewriter", text: "ATH0", speed: 100 }, + { type: "delay", ms: 400 }, + { text: "NO CARRIER", className: "warning" }, + { type: "delay", ms: 600 }, + "", + "You disconnect from Dark Tower.", + "The silence of your room feels heavier now.", + "", + { type: "delay", ms: 500 }, + { text: "Something compels you forward.", className: "info" }, + ], + next: "dial_lighthouse", + delay: 1000, + }, + + choice_ignored: { + clear: true, + content: [ + "You highlight the message.", + "", + { type: "typewriter", text: "DELETE MESSAGE? [Y/N]", speed: 50 }, + { type: "delay", ms: 600 }, + { type: "typewriter", text: "Y", speed: 100 }, + { type: "delay", ms: 400 }, + "", + { text: "MESSAGE DELETED", className: "success" }, + "", + { type: "delay", ms: 1000 }, + "The number fades from memory.", + "Just another piece of BBS spam, you tell yourself.", + "", + { type: "delay", ms: 800 }, + "You browse Dark Tower for another hour.", + "Download some wallpapers. Chat about nothing.", + "", + { type: "delay", ms: 800 }, + "At 11:47 PM, you disconnect.", + "", + { type: "delay", ms: 800 }, + "Five days later, the millennium arrives.", + "Fireworks. Champagne. Relief.", + "", + { type: "delay", ms: 600 }, + "Nothing happens.", + "", + { type: "delay", ms: 1000 }, + { text: "Or does it?", className: "warning" }, + "", + { type: "delay", ms: 800 }, + "You never find out what cascade.exe would have done.", + "The lighthouse keeper's message was never meant for you.", + "", + { type: "delay", ms: 600 }, + { text: "Perhaps that's for the best.", className: "info" }, + "", + { type: "delay", ms: 1000 }, + { text: "[END - Route C: The Road Not Taken]", className: "warning" }, + ], + options: [{ text: "Return to terminal", next: "game_end_ignored" }], + }, + + game_end_ignored: { + clear: true, + content: [ + { type: "ascii", art: END_SCREEN, className: "game-ascii" }, + "", + { text: "BOXING DAY", className: "game-title" }, + { text: "Day 1 Complete - Route C", className: "info" }, + "", + "You chose not to follow the signal.", + "Some doors are better left closed.", + "", + { + text: 'Type "dial" to play again, or "dial reset" to start fresh.', + className: "info", + }, + ], + }, + + // ========================================== + // DARK TOWER EXPLORATION + // ========================================== + + browse_boards: { + title: "Message Boards", + content: [ + "═══ DARK TOWER MESSAGE BOARDS ═══", + "", + " [1] General Discussion (47 new)", + " [2] Tech Support (12 new)", + // Archives conditionally shown + { + condition: { not: "archives_deleted" }, + content: " [3] The Archives (3 new)", + }, + { + condition: "archives_deleted", + content: { text: " [3] ", className: "error" }, + }, + " [4] File Announcements (8 new)", + "", + ], + prompt: "Select board:", + options: [ + { text: "General Discussion", next: "board_general" }, + { text: "Tech Support", next: "board_tech" }, + { + text: "The Archives", + next: "board_archives", + condition: { not: "archives_deleted" }, + }, + { text: "File Announcements", next: "board_files" }, + { text: "Back to main menu", next: "dark_tower_main" }, + ], + }, + + board_general: { + title: "General Discussion", + content: [ + "═══ GENERAL DISCUSSION ═══", + "", + " [HOT] Y2K Preparation Thread - 234 replies", + " [NEW] Anyone else getting weird messages? - 12 replies", + " [NEW] Happy Boxing Day everyone! - 8 replies", + " Best BBS games? - 45 replies", + " New user intro thread - 67 replies", + "", + { + text: "The usual chatter. Nothing about lighthouses.", + className: "info", + }, + ], + options: [ + { text: "Read 'weird messages' thread", next: "thread_weird" }, + { text: "Back to boards", next: "browse_boards" }, + ], + }, + + thread_weird: { + title: "Thread: Anyone else getting weird messages?", + content: [ + "═══ Anyone else getting weird messages? ═══", + "", + { text: "Posted by: Static_User", className: "info" }, + "Got a strange PM last night. No sender listed.", + "Just a phone number and something about a 'cascade'.", + "Probably spam, but creepy timing with Y2K coming up.", + "", + "---", + { text: "Reply from: NightWatchman [SYSOP]", className: "warning" }, + "Looking into it. Please forward any suspicious messages.", + "And don't dial any numbers you don't recognize.", + "", + "---", + { text: "Reply from: [DELETED USER]", className: "error" }, + "[This post has been removed]", + "", + { type: "delay", ms: 300 }, + { + condition: "found_number", + content: { + text: "You notice your message was similar...", + className: "warning", + }, + }, + ], + options: [{ text: "Back", next: "board_general" }], + }, + + board_tech: { + title: "Tech Support", + content: [ + "═══ TECH SUPPORT ═══", + "", + " [STICKY] READ FIRST: Y2K Compliance Guide", + " [NEW] Modem dropping connection at midnight?", + " Best virus scanner for 1999?", + " How to increase download speeds", + "", + { + text: "Standard tech questions. Nothing unusual.", + className: "info", + }, + ], + options: [{ text: "Back to boards", next: "browse_boards" }], + }, + + board_archives: { + title: "The Archives", + content: [ + "═══ THE ARCHIVES ═══", + { text: "Historical posts - Read only", className: "info" }, + "", + " [1998] The Lighthouse Project - NightWatchman", + " [1999] Frequencies and Patterns - Signal_Lost", + " [1999] RE: Has anyone heard from Keeper? - Anonymous", + "", + { + condition: { not: "visited.archive_warning" }, + content: [ + "", + { text: "These posts feel... different.", className: "warning" }, + { text: "Like echoes from somewhere else.", className: "info" }, + ], + }, + ], + onEnter: [{ set: "visited.archive_warning", value: true }], + options: [ + { text: "Read 'The Lighthouse Project'", next: "archive_lighthouse" }, + { + text: "Read 'Frequencies and Patterns'", + next: "archive_frequencies", + }, + { + text: "Read 'Has anyone heard from Keeper?'", + next: "archive_keeper", + }, + { text: "Back to boards", next: "browse_boards" }, + ], + }, + + archive_lighthouse: { + title: "The Lighthouse Project", + content: [ + "═══ The Lighthouse Project ═══", + { + text: "Posted by: NightWatchman - November 15, 1998", + className: "info", + }, + "", + "Some of you have asked about the secondary BBS.", + "Yes, it exists. No, I can't give you the number.", + "", + "The Lighthouse was set up by someone who called himself 'Keeper'.", + "He said he found something in the noise between stations.", + "Patterns that shouldn't exist.", + "", + "I hosted his board as a favor.", + "Then one day, he stopped logging in.", + "", + "The board is still there. Still running.", + "I check it sometimes. The files he left behind...", + "", + "Some doors are better left closed.", + "", + { text: "- NW", className: "info" }, + ], + options: [{ text: "Back", next: "board_archives" }], + }, + + archive_frequencies: { + title: "Frequencies and Patterns", + content: [ + "═══ Frequencies and Patterns ═══", + { text: "Posted by: Signal_Lost - March 22, 1999", className: "info" }, + "", + "I've been analyzing radio static for three months.", + "There's something there. In the spaces between signals.", + "", + "It's not random. It's STRUCTURED.", + "Like code. Like a message.", + "", + "Keeper knew. That's why he built cascade.exe.", + "To translate. To REVEAL.", + "", + "I'm close to understanding.", + "So close.", + "", + { + text: "[User Signal_Lost has not logged in since March 23, 1999]", + className: "warning", + }, + ], + options: [{ text: "Back", next: "board_archives" }], + }, + + archive_keeper: { + title: "RE: Has anyone heard from Keeper?", + content: [ + "═══ RE: Has anyone heard from Keeper? ═══", + { text: "Posted by: Anonymous - December 20, 1999", className: "info" }, + "", + "He's still there.", + "In The Lighthouse.", + "Waiting.", + "", + "The cascade is ready.", + "It just needs carriers.", + "", + "555-0237", + "", + "Before midnight on the 31st.", + "The alignment only happens once.", + "", + { + text: "[This post was flagged for removal but persists]", + className: "error", + }, + ], + onEnter: [{ set: "found_number", value: true }], + options: [{ text: "Back", next: "board_archives" }], + }, + + board_files: { + title: "File Announcements", + content: [ + "═══ FILE ANNOUNCEMENTS ═══", + "", + " [NEW] Y2K_FIX.ZIP - Y2K compliance patches", + " [NEW] DOOM_WAD.ZIP - New Doom levels", + " FONTS99.ZIP - Cool fonts collection", + "", + { text: "Nothing about cascades here.", className: "info" }, + ], + options: [{ text: "Back to boards", next: "browse_boards" }], + }, + + dark_tower_files: { + title: "File Library", + content: [ + "═══ DARK TOWER FILE LIBRARY ═══", + "", + " /games - 234 files", + " /utils - 156 files", + " /images - 89 files", + " /music - 67 files", + "", + { text: "Standard BBS fare. Nothing unusual.", className: "info" }, + ], + options: [{ text: "Back to main menu", next: "dark_tower_main" }], + }, + + whos_online: { + title: "Who's Online", + content: [ + "═══ USERS ONLINE ═══", + "", + " Guest_742 (you) - Main Menu", + " NightWatchman - SysOp Console", + " Static_User - Message Boards", + "", + { + text: "Last login: Signal_Lost - March 23, 1999", + className: "warning", + }, + ], + options: [{ text: "Back", next: "dark_tower_main" }], + }, + + leave_early: { + content: [ + "Are you sure you want to disconnect?", + "", + { + condition: "found_number", + content: { + text: "You still have the number: 555-0237", + className: "warning", + }, + }, + ], + options: [ + { + text: "Dial The Lighthouse first", + next: "confirm_dial_lighthouse", + condition: "found_number", + }, + { text: "Yes, disconnect", next: "disconnect_early" }, + { text: "Stay connected", next: "dark_tower_main" }, + ], + }, + + disconnect_early: { + clear: true, + content: [ + { type: "typewriter", text: "ATH0", speed: 100 }, + { type: "delay", ms: 400 }, + { text: "NO CARRIER", className: "warning" }, + "", + { type: "delay", ms: 600 }, + "You disconnect from Dark Tower.", + "", + { + condition: "found_number", + content: [ + "The number lingers in your mind.", + { text: "555-0237", className: "warning" }, + "", + "You could always dial it directly...", + ], + else: ["Another quiet night online.", "Nothing unusual."], + }, + ], + options: [ + { + text: "Dial 555-0237", + next: "dial_lighthouse", + condition: "found_number", + }, + { text: "Go to sleep", next: "sleep_ending" }, + ], + }, + + sleep_ending: { + clear: true, + content: [ + "You power down the modem.", + "The room falls silent.", + "", + { type: "delay", ms: 600 }, + "As you drift off to sleep, you think about the message.", + "The cascade. The keeper. The lighthouse.", + "", + { type: "delay", ms: 800 }, + "Tomorrow, you tell yourself.", + "You'll investigate tomorrow.", + "", + { type: "delay", ms: 1000 }, + "But tomorrow, you'll forget.", + "The way everyone forgets.", + "", + { type: "delay", ms: 800 }, + { + condition: { not: "found_number" }, + content: { + text: "[END - Route C: Peaceful Sleep]", + className: "info", + }, + else: { text: "[END - Route B: Postponed]", className: "warning" }, + }, + ], + options: [{ text: "Return to terminal", next: "game_end_sleep" }], + }, + + game_end_sleep: { + clear: true, + content: [ + { type: "ascii", art: END_SCREEN, className: "game-ascii" }, + "", + { text: "BOXING DAY", className: "game-title" }, + { text: "Day 1 Complete", className: "info" }, + "", + "You chose rest over curiosity.", + "The lighthouse keeper will have to wait.", + "", + { + text: 'Type "dial" to play again, or "dial reset" to start fresh.', + className: "info", + }, + ], + }, + + confirm_dial_lighthouse: { + content: [ + { text: "555-0237", className: "warning" }, + "", + "The number from the message.", + "Something waits on the other end.", + "", + { text: "Your clock reads 11:56 PM.", className: "info" }, + ], + options: [ + { text: "Dial The Lighthouse", next: "dial_lighthouse" }, + { text: "Not yet", next: "dark_tower_main" }, + ], + }, + + // ========================================== + // THE LIGHTHOUSE + // ========================================== + + dial_lighthouse: { + clear: true, + content: [ + { type: "typewriter", text: "ATDT 555-0237", speed: 80 }, + { type: "delay", ms: 1000 }, + "", + { text: "DIALING...", className: "info" }, + { type: "delay", ms: 800 }, + "", + { text: "RING", className: "warning" }, + { type: "delay", ms: 1200 }, + { text: "RING", className: "warning" }, + { type: "delay", ms: 1200 }, + { text: "RING", className: "warning" }, + { type: "delay", ms: 800 }, + "", + { + type: "typewriter", + text: "~~ crackle ~~ hiss ~~ CONNECT 14400", + speed: 40, + }, + { type: "delay", ms: 500 }, + "", + { text: "Connection unstable.", className: "warning" }, + { text: "Signal degraded.", className: "warning" }, + { type: "delay", ms: 600 }, + "", + { type: "typewriter", text: "Welcome to THE LIGHTHOUSE", speed: 40 }, + { type: "delay", ms: 300 }, + { type: "typewriter", text: "A beacon in the static.", speed: 40 }, + ], + onEnter: [{ set: "dialed_lighthouse", value: true }], + next: "lighthouse_main", + delay: 1000, + }, + + lighthouse_main: { + clear: true, + content: [ + { type: "ascii", art: LIGHTHOUSE_HEADER, className: "game-ascii" }, + "", + { text: "T H E L I G H T H O U S E", className: "game-scene-title" }, + { text: "Last updated: 12/24/1999 23:59:59", className: "info" }, + { text: "Users online: 1 (you)", className: "warning" }, + "", + { + condition: { not: "visited.lighthouse_main" }, + content: [ + { text: "Something feels wrong here.", className: "warning" }, + { text: "The BBS feels... frozen. Abandoned.", className: "info" }, + "", + ], + }, + { + condition: "downloaded_cascade", + content: [ + { + text: "The signal flickers. Something has changed.", + className: "error", + }, + "", + ], + }, + ], + onEnter: [{ set: "visited.lighthouse_main", value: true }], + prompt: "Navigate:", + options: [ + { text: "The Keeper's Log", next: "lighthouse_log" }, + { text: "Transmissions", next: "lighthouse_transmissions" }, + { text: "File Vault", next: "lighthouse_files" }, + { + text: "Request SysOp Chat", + next: "chat_request", + condition: { + and: [{ not: "downloaded_cascade" }, { not: "talked_to_sysop" }], + }, + }, + { text: "Disconnect", next: "disconnect_choice" }, + ], + }, + + lighthouse_log: { + title: "The Keeper's Log", + content: [ + "═══ THE KEEPER'S LOG ═══", + "", + { text: "Entry 1 - November 3, 1998", className: "info" }, + "I've found something. In the static between radio stations.", + "Patterns. Structures. A language, maybe.", + "", + { text: "Entry 7 - December 12, 1998", className: "info" }, + "The patterns are getting clearer.", + "They want to be understood.", + "They want to SPREAD.", + "", + { text: "Entry 15 - March 19, 1999", className: "info" }, + "CASCADE.EXE is complete.", + "A translator. A carrier. A key.", + "When run at the right moment, it will open the door.", + "", + { text: "Entry 23 - December 24, 1999", className: "warning" }, + "The alignment approaches.", + "Seven days until the millennium.", + "I can hear them now. Always.", + "", + { type: "typewriter", text: "They are beautiful.", speed: 60 }, + ], + options: [{ text: "Back", next: "lighthouse_main" }], + }, + + lighthouse_transmissions: { + title: "Transmissions", + content: [ + "═══ RECORDED TRANSMISSIONS ═══", + "", + { text: "[AUDIO FILES - PLAYBACK UNAVAILABLE]", className: "error" }, + "", + "Transcript excerpts:", + "", + " TRANS_001.WAV:", + ' "...signal detected at 1420 MHz..."', + "", + " TRANS_014.WAV:", + ' "...pattern repeats every 23 seconds..."', + "", + " TRANS_047.WAV:", + ' "...not random... structured... alive?..."', + "", + " TRANS_099.WAV:", + { text: ' "[TRANSCRIPT CORRUPTED]"', className: "error" }, + "", + { text: "The audio files existed once.", className: "info" }, + { text: "Now only fragments remain.", className: "warning" }, + ], + options: [{ text: "Back", next: "lighthouse_main" }], + }, + + lighthouse_files: { + title: "File Vault", + content: [ + "═══ FILE VAULT v2.1 ═══", + { text: '"The keeper\'s collection"', className: "info" }, + "", + { text: "Available files:", className: "info" }, + "", + " [1] README.TXT 1.2 KB 12/24/1999", + " [2] CASCADE.EXE 47.0 KB 12/25/1999", + // Corrupted file - conditionally shown + { + condition: { not: "corrupted_file_deleted" }, + content: " [3] SHADOW.DAT ??? KB ??/??/????", + }, + { + condition: "corrupted_file_deleted", + content: { text: " [3] ", className: "error" }, + }, + "", + { + condition: { not: "visited.lighthouse_files" }, + content: { + text: "CASCADE.EXE pulses faintly on your screen.", + className: "warning", + }, + }, + ], + onEnter: [{ set: "visited.lighthouse_files", value: true }], + prompt: "Select file:", + options: [ + { text: "View README.TXT", next: "file_readme" }, + { text: "Download CASCADE.EXE", next: "download_confirm" }, + { + text: "Access SHADOW.DAT", + next: "choice_corrupted", + condition: { not: "corrupted_file_deleted" }, + }, + { text: "Back", next: "lighthouse_main" }, + ], + }, + + file_readme: { + title: "README.TXT", + content: [ + "═══ README.TXT ═══", + "", + "To whoever finds this:", + "", + "I built cascade.exe to show them.", + "To show everyone what I found in the frequencies.", + "", + "It spreads. It copies. It REVEALS.", + "", + "Don't be afraid when the old files disappear.", + "They were never real anyway.", + "", + "Run it before midnight on 01/01/2000.", + "The alignment only happens once.", + "", + { text: " - K", className: "info" }, + "", + { text: "P.S. Don't try to open SHADOW.DAT.", className: "warning" }, + { + text: " Some doors shouldn't be opened twice.", + className: "warning", + }, + ], + options: [{ text: "Back to files", next: "lighthouse_files" }], + }, + + // ========================================== + // CHOICE D/E/F - DOWNLOAD DECISION + // ========================================== + + download_confirm: { + content: [ + { text: "CASCADE.EXE - 47,104 bytes", className: "warning" }, + "", + "The file waits.", + "47 kilobytes of unknown code.", + "", + "The README said it 'reveals' something.", + "That old files will 'disappear'.", + "", + { + text: "In five days, everyone worries about Y2K bugs.", + className: "info", + }, + { text: "This feels different.", className: "warning" }, + ], + prompt: "Download CASCADE.EXE?", + options: [ + { text: "Yes, download it", next: "choice_download" }, + { text: "No, leave it alone", next: "choice_no_download" }, + ], + }, + + choice_download: { + title: "Downloading...", + clear: true, + content: [ + { type: "typewriter", text: "XMODEM TRANSFER INITIATED", speed: 45 }, + { type: "delay", ms: 600 }, + "", + { text: "Receiving: CASCADE.EXE", className: "info" }, + { type: "delay", ms: 400 }, + "", + { type: "typewriter", text: "[", speed: 20 }, + { type: "typewriter", text: "████████████████████", speed: 80 }, + { type: "typewriter", text: "] 100%", speed: 20 }, + { type: "delay", ms: 800 }, + "", + { text: "TRANSFER COMPLETE", className: "success" }, + { type: "delay", ms: 600 }, + "", + "The file sits in your download folder.", + "47,104 bytes of... something.", + "", + { type: "delay", ms: 1000 }, + { + text: "Somewhere, distantly, you hear static.", + className: "warning", + }, + { type: "delay", ms: 600 }, + "", + { + type: "typewriter", + text: 'A whisper in the noise: "Thank you."', + speed: 50, + }, + "", + { type: "delay", ms: 1200 }, + { text: "Something has changed.", className: "error" }, + ], + onEnter: [ + { set: "downloaded_cascade", value: true }, + { set: "archives_deleted", value: true }, // rm -rf effect + ], + next: "post_download", + delay: 1500, + }, + + post_download: { + clear: true, + content: [ + { text: "CONNECTION INTERRUPTED", className: "error" }, + { type: "delay", ms: 600 }, + "", + "For a moment, your screen fills with symbols.", + "Patterns that almost make sense.", + "", + { type: "delay", ms: 800 }, + "Then, clarity.", + "", + { type: "delay", ms: 600 }, + "You remember Dark Tower's board list.", + { text: "Board #3 - The Archives.", className: "info" }, + "", + { type: "delay", ms: 500 }, + { text: "It's gone.", className: "warning" }, + { text: "Like it was never there.", className: "warning" }, + "", + { type: "delay", ms: 800 }, + { text: "The cascade has begun.", className: "error" }, + ], + next: "closing_message", + delay: 2000, + }, + + choice_no_download: { + content: [ + "Something holds you back.", + "47 kilobytes of unknown code.", + "From a stranger who hears things in static.", + "", + { type: "delay", ms: 500 }, + { text: "You leave CASCADE.EXE untouched.", className: "info" }, + "", + "The keeper's work remains on the server.", + "Waiting for someone else. Or no one.", + ], + options: [ + { + text: "Chat with the SysOp", + next: "chat_request", + condition: { not: "talked_to_sysop" }, + }, + { text: "Return to file list", next: "lighthouse_files" }, + { text: "Disconnect", next: "disconnect_choice" }, + ], + }, + + choice_corrupted: { + title: "Accessing SHADOW.DAT...", + clear: true, + content: [ + { type: "typewriter", text: "ATTEMPTING TO READ FILE...", speed: 50 }, + { type: "delay", ms: 1000 }, + "", + { text: "ERROR: FILE HEADER CORRUPTED", className: "error" }, + { type: "delay", ms: 400 }, + { text: "ERROR: UNEXPECTED EOF", className: "error" }, + { type: "delay", ms: 400 }, + { text: "ERROR: ????????????????????", className: "error" }, + { type: "delay", ms: 800 }, + "", + { type: "ascii", art: GLITCH_ART, className: "error" }, + { type: "delay", ms: 1000 }, + "", + "Your screen flickers violently.", + "", + { type: "delay", ms: 600 }, + { text: "A sound from your speakers.", className: "warning" }, + { + text: "A voice, maybe. Or static shaped like words:", + className: "info", + }, + "", + { type: "typewriter", text: '"Not yet. Not yet. Not yet."', speed: 90 }, + "", + { type: "delay", ms: 1200 }, + { + text: "SHADOW.DAT has been removed from the file listing.", + className: "error", + }, + "", + { type: "delay", ms: 600 }, + { text: "Some doors shouldn't be opened twice.", className: "warning" }, + ], + onEnter: [ + { set: "deleted_corrupted_file", value: true }, + { set: "corrupted_file_deleted", value: true }, // rm -rf effect + ], + options: [{ text: "Return to files", next: "lighthouse_files" }], + }, + + // ========================================== + // SYSOP CHAT + // ========================================== + + chat_request: { + content: [ + { text: "Requesting SysOp chat...", className: "info" }, + { type: "delay", ms: 1500 }, + "", + { + type: "typewriter", + text: "CHAT REQUEST ACCEPTED", + speed: 40, + }, + { type: "delay", ms: 500 }, + "", + { text: "═══ SYSOP: Keeper ═══", className: "warning" }, + ], + onEnter: [{ set: "talked_to_sysop", value: true }], + next: "chat_conversation", + delay: 600, + }, + + chat_conversation: { + content: [ + { + type: "typewriter", + text: "KEEPER: You found my message.", + speed: 50, + }, + { type: "delay", ms: 800 }, + "", + { + type: "typewriter", + text: "KEEPER: I wasn't sure anyone would come.", + speed: 50, + }, + { type: "delay", ms: 700 }, + "", + { + type: "typewriter", + text: "KEEPER: The cascade is ready.", + speed: 50, + }, + { type: "delay", ms: 500 }, + { + type: "typewriter", + text: "KEEPER: It just needs carriers.", + speed: 50, + }, + { type: "delay", ms: 900 }, + "", + // Conditional response based on download + { + condition: "downloaded_cascade", + content: [ + { + type: "typewriter", + text: "KEEPER: I see you already have it.", + speed: 50, + }, + { type: "delay", ms: 500 }, + { + type: "typewriter", + text: "KEEPER: Good. Run it when the clock strikes.", + speed: 50, + }, + ], + else: [ + { + type: "typewriter", + text: "KEEPER: Will you carry it?", + speed: 50, + }, + { type: "delay", ms: 500 }, + { + type: "typewriter", + text: "KEEPER: Will you help it spread?", + speed: 50, + }, + ], + }, + "", + { type: "delay", ms: 1200 }, + { type: "typewriter", text: "KEEPER: I can hear them now.", speed: 50 }, + { type: "delay", ms: 500 }, + { type: "typewriter", text: "KEEPER: In the static.", speed: 60 }, + { type: "delay", ms: 500 }, + { type: "typewriter", text: "KEEPER: They're beautiful.", speed: 60 }, + "", + { type: "delay", ms: 1500 }, + { text: "═══ KEEPER HAS DISCONNECTED ═══", className: "error" }, + ], + next: "chat_aftermath", + delay: 1500, + }, + + chat_aftermath: { + content: [ + "The chat window closes.", + "", + { type: "delay", ms: 500 }, + "Your room feels colder.", + "The modem's carrier tone sounds different somehow.", + "", + { + text: "Like there's something else on the line.", + className: "warning", + }, + ], + options: [ + { + text: "Download cascade.exe now", + next: "download_confirm", + condition: { not: "downloaded_cascade" }, + }, + { text: "Disconnect", next: "closing_message" }, + ], + }, + + // ========================================== + // CLOSING SEQUENCE + // ========================================== + + disconnect_choice: { + content: [ + "Your hand hovers over the keyboard.", + "", + { + condition: "downloaded_cascade", + content: { + text: "CASCADE.EXE sits in your downloads, waiting.", + className: "warning", + }, + else: { + text: "You could still download CASCADE.EXE before you go...", + className: "info", + }, + }, + ], + options: [ + { + text: "Download cascade.exe first", + next: "download_confirm", + condition: { not: "downloaded_cascade" }, + }, + { text: "Disconnect now", next: "closing_message" }, + ], + }, + + closing_message: { + clear: true, + content: [ + { type: "delay", ms: 500 }, + // Different closing based on download status + { + condition: "downloaded_cascade", + content: [ + { + text: "As you prepare to disconnect, a final message appears:", + className: "info", + }, + "", + { type: "delay", ms: 600 }, + { + type: "typewriter", + text: "SEE YOU ON THE OTHER SIDE", + speed: 55, + }, + "", + { type: "typewriter", text: "01/01/2000 00:00:00", speed: 55 }, + "", + { type: "delay", ms: 1000 }, + { text: "You have cascade.exe.", className: "warning" }, + { text: "You have five days.", className: "warning" }, + "", + { text: "What happens next is up to you.", className: "info" }, + ], + else: [ + { text: "The Lighthouse grows quiet.", className: "info" }, + "", + { type: "delay", ms: 600 }, + { type: "typewriter", text: "SIGNAL LOST", speed: 80 }, + "", + { type: "delay", ms: 800 }, + "You disconnect without the file.", + "But the number stays with you.", + "", + { text: "555-0237", className: "warning" }, + "", + { type: "delay", ms: 600 }, + "You could always call back...", + { text: "If the line is still there.", className: "info" }, + ], + }, + ], + next: "carrier_lost", + delay: 2000, + }, + + carrier_lost: { + clear: true, + content: [ + { type: "delay", ms: 300 }, + { type: "typewriter", text: "ATH0", speed: 100 }, + { type: "delay", ms: 500 }, + "", + { text: "NO CARRIER", className: "error" }, + "", + { type: "delay", ms: 1200 }, + "", + { type: "ascii", art: END_SCREEN, className: "game-ascii" }, + "", + { text: "BOXING DAY", className: "game-title" }, + { text: "Day 1 Complete", className: "info" }, + "", + { text: "─── SESSION SUMMARY ───", className: "info" }, + "", + { + condition: "downloaded_cascade", + content: { text: "[X] Downloaded CASCADE.EXE", className: "success" }, + else: { text: "[ ] Downloaded CASCADE.EXE", className: "info" }, + }, + { + condition: "talked_to_sysop", + content: { text: "[X] Spoke with Keeper", className: "success" }, + else: { text: "[ ] Spoke with Keeper", className: "info" }, + }, + { + condition: "deleted_corrupted_file", + content: { text: "[X] Accessed SHADOW.DAT", className: "warning" }, + else: { text: "[ ] Accessed SHADOW.DAT", className: "info" }, + }, + "", + { + condition: "archives_deleted", + content: { + text: "[!] The Archives have been deleted", + className: "error", + }, + }, + { + condition: "corrupted_file_deleted", + content: { + text: "[!] SHADOW.DAT has been removed", + className: "error", + }, + }, + "", + { text: "Route: ${route_taken}", className: "info" }, + "", + { text: "To be continued...", className: "warning" }, + "", + { + text: 'Type "dial" to replay, or "dial reset" to clear progress.', + className: "info", + }, + ], + onEnter: [{ set: "day_1_complete", value: true }], + }, + }, +}; + +// Register the game when terminal is available +if (window.terminal && window.GameEngine) { + const game = new GameEngine(boxingDayGame); + game.register(); +} diff --git a/assets/js/games/games/test-adventure.js b/assets/js/games/games/test-adventure.js new file mode 100644 index 0000000..e29cecf --- /dev/null +++ b/assets/js/games/games/test-adventure.js @@ -0,0 +1,281 @@ +// Test Adventure - A simple game to test the game engine +const testAdventureGame = { + id: "test-adventure", + name: "Terminal Quest", + command: "quest", + description: "A test adventure game", + + initialState: { + gold: 0, + inventory: [], + visited: {}, + }, + + intro: [ + { + type: "ascii", + art: ` + ╔════════════════════════════════╗ + ║ TERMINAL QUEST ║ + ║ A Test Adventure ║ + ╚════════════════════════════════╝ + `, + className: "game-ascii", + }, + "", + "You wake up in a strange digital world...", + { type: "delay", ms: 800 }, + "The cursor blinks patiently before you.", + "", + { text: 'Type "quit" at any time to exit.', className: "info" }, + ], + + startScene: "start", + + scenes: { + start: { + title: "The Terminal", + content: [ + "You find yourself at a command prompt.", + "A green cursor blinks steadily in the darkness.", + "", + { + condition: { path: "visited.start" }, + content: { text: "You've been here before...", className: "info" }, + }, + { + condition: { path: "gold", greaterThan: 0 }, + content: "Your gold pouch jingles with ${gold} coins.", + }, + ], + onEnter: [{ set: "visited.start", value: true }], + options: [ + { text: "Look around", next: "look-around" }, + { text: "Check inventory", next: "inventory" }, + { + text: "Enter the code cave", + condition: { path: "inventory", contains: "flashlight" }, + next: "code-cave", + }, + { + text: "Talk to the cursor", + condition: { not: { path: "visited.cursor-talk" } }, + next: "cursor-talk", + }, + ], + }, + + "look-around": { + title: "Searching...", + content: ["You search the area carefully...", { type: "delay", ms: 500 }], + onEnter: [ + { + callback: (state, adapter) => { + // Random chance to find gold + if (Math.random() > 0.5) { + const found = Math.floor(Math.random() * 10) + 1; + state.increment("gold", found); + adapter.printSuccess(`You found ${found} gold coins!`); + } else { + adapter.print("You don't find anything this time."); + } + }, + }, + ], + next: "start", + delay: 1000, + }, + + inventory: { + title: "Inventory", + clear: false, + content: [ + "", + "=== Your Inventory ===", + { text: "Gold: ${gold}", className: "warning" }, + "", + { + condition: { path: "inventory", contains: "flashlight" }, + content: "- Flashlight (lights up dark places)", + }, + { + condition: { path: "inventory", contains: "key" }, + content: "- Mysterious Key", + }, + { + condition: { + and: [ + { not: { path: "inventory", contains: "flashlight" } }, + { not: { path: "inventory", contains: "key" } }, + ], + }, + content: { text: "Your inventory is empty.", className: "info" }, + }, + "", + ], + options: [{ text: "Back", next: "start" }], + }, + + "cursor-talk": { + title: "The Blinking Cursor", + content: [ + "The cursor seems to acknowledge you.", + "", + { type: "typewriter", text: "HELLO, USER.", speed: 80 }, + { type: "delay", ms: 500 }, + { type: "typewriter", text: "I HAVE BEEN WAITING FOR YOU.", speed: 60 }, + { type: "delay", ms: 300 }, + "", + "The cursor offers you something...", + ], + onEnter: [ + { set: "visited.cursor-talk", value: true }, + { addItem: "flashlight" }, + { printSuccess: "You received a flashlight!" }, + ], + options: [ + { text: "Thank the cursor", next: "cursor-thanks" }, + { text: "Ask about the code cave", next: "cursor-cave-info" }, + ], + }, + + "cursor-thanks": { + content: [ + { type: "typewriter", text: "YOU ARE WELCOME, USER.", speed: 60 }, + "", + "The cursor resumes its patient blinking.", + ], + next: "start", + delay: 1500, + }, + + "cursor-cave-info": { + content: [ + { type: "typewriter", text: "THE CAVE HOLDS SECRETS...", speed: 60 }, + { type: "delay", ms: 300 }, + { type: "typewriter", text: "USE THE LIGHT TO FIND THEM.", speed: 60 }, + "", + "The cursor dims slightly, as if tired from speaking.", + ], + next: "start", + delay: 1500, + }, + + "code-cave": { + title: "The Code Cave", + content: [ + "Your flashlight illuminates a cavern of glowing text.", + "Ancient code scrolls across the walls.", + "", + { + type: "ascii", + art: ` + function mystery() { + return ??????; + } + `, + className: "info", + }, + "", + { + condition: { not: { path: "visited.code-cave" } }, + content: "This is your first time in the code cave.", + }, + { + condition: { path: "visited.code-cave" }, + content: "The familiar glow of code surrounds you.", + }, + ], + onEnter: [{ set: "visited.code-cave", value: true }], + prompt: "What do you do?", + options: [ + { text: "Examine the code", next: "examine-code" }, + { + text: "Search for treasure", + condition: { not: { path: "inventory", contains: "key" } }, + next: "find-key", + }, + { + text: "Use the mysterious key", + condition: { path: "inventory", contains: "key" }, + next: "use-key", + }, + { text: "Return to the terminal", next: "start" }, + ], + }, + + "examine-code": { + content: [ + "You study the ancient code...", + { type: "delay", ms: 500 }, + "", + "It seems to be a function that returns...", + { type: "delay", ms: 800 }, + { text: "...the meaning of everything?", className: "warning" }, + "", + "How curious.", + ], + next: "code-cave", + delay: 3000, + }, + + "find-key": { + content: [ + "You search among the glowing symbols...", + { type: "delay", ms: 800 }, + "", + "Behind a cascade of semicolons, you find something!", + ], + onEnter: [ + { addItem: "key" }, + { printSuccess: "You found a mysterious key!" }, + ], + next: "code-cave", + delay: 1000, + }, + + "use-key": { + title: "The Hidden Chamber", + content: [ + "The key fits into a slot hidden in the code.", + { type: "delay", ms: 500 }, + "", + "A hidden chamber opens before you!", + { type: "delay", ms: 500 }, + "", + { + type: "ascii", + art: ` + ╔═══════════════════════════════╗ + ║ ║ + ║ CONGRATULATIONS! ║ + ║ ║ + ║ You found the secret ║ + ║ of the Terminal Quest! ║ + ║ ║ + ║ The treasure is: ║ + ║ KNOWLEDGE ║ + ║ ║ + ╚═══════════════════════════════╝ + `, + className: "success", + }, + "", + { text: "Thanks for playing this test adventure!", className: "info" }, + "", + 'Type "quest" to play again, or "quest reset" to start fresh.', + ], + onEnter: [ + { increment: "gold", amount: 100 }, + { printSuccess: "You also found 100 gold!" }, + { set: "completed", value: true }, + ], + }, + }, +}; + +// Register the game when terminal is available +if (window.terminal && window.GameEngine) { + const game = new GameEngine(testAdventureGame); + game.register(); +} diff --git a/assets/sass/partials/_terminal.scss b/assets/sass/partials/_terminal.scss index 45182b8..c7527cf 100644 --- a/assets/sass/partials/_terminal.scss +++ b/assets/sass/partials/_terminal.scss @@ -97,3 +97,58 @@ .hidden { display: none; } + +// Game Engine Styles +.game-title { + font-size: 1.2em; + font-weight: bold; + color: #44ff44; + text-shadow: 0 0 10px rgba(68, 255, 68, 0.5); +} + +.game-scene-title { + font-weight: bold; + color: #00ffff; + border-bottom: 1px solid currentColor; + display: inline-block; + padding-bottom: 2px; + margin-bottom: 0.5em; +} + +.game-options { + margin: 0.5em 0; +} + +.game-option { + padding: 2px 0; + color: #888; +} + +.game-option-selected { + padding: 2px 0; + color: #44ff44; + font-weight: bold; +} + +.game-ascii { + color: #44ff44; + line-height: 1.1; +} + +.game-ansi-art { + line-height: 1; + font-size: 12px; + margin: 0; + padding: 0; + white-space: pre; + font-family: monospace; + + // Ensure spans don't add extra space + span { + display: inline; + } +} + +.typewriter-line { + display: inline; +} diff --git a/layouts/partials/site-scripts.html b/layouts/partials/site-scripts.html index f61de47..4d510e7 100644 --- a/layouts/partials/site-scripts.html +++ b/layouts/partials/site-scripts.html @@ -3,6 +3,7 @@ {{ $init := resources.Get "js/init.js" }} {{ $commandFiles := resources.Match "js/commands/*.js" }} {{ $subfolderFiles := resources.Match "js/*/*.js" }} +{{ $deepSubfolderFiles := resources.Match "js/*/*/*.js" }} {{ $remaining := resources.Match "js/*.js" }} {{ $filtered := slice }} {{ range $remaining }} @@ -18,7 +19,14 @@ {{ $filteredSubfolders = $filteredSubfolders | append . }} {{ end }} {{ end }} -{{ $allFiles := slice $terminalShell | append $filtered | append $init | append $commandFiles | append $filteredSubfolders }} +{{ $filteredDeepSubfolders := slice }} +{{ range $deepSubfolderFiles }} + {{ $path := .RelPermalink }} + {{ if not (strings.Contains $path "/adoptables/") }} + {{ $filteredDeepSubfolders = $filteredDeepSubfolders | append . }} + {{ end }} +{{ end }} +{{ $allFiles := slice $terminalShell | append $filtered | append $init | append $commandFiles | append $filteredSubfolders | append $filteredDeepSubfolders }} {{ $terminalBundle := $allFiles | resources.Concat "js/terminal-bundle.js" | resources.Minify | resources.Fingerprint }}