diff --git a/src/levels/level2.ts b/src/levels/level2.ts index 63d914c..9bcc0ac 100644 --- a/src/levels/level2.ts +++ b/src/levels/level2.ts @@ -1,248 +1,267 @@ -import { Level, LevelResult, registerLevel } from '../core/levelSystem'; -import { getCurrentGameState } from '../core/gameState'; +import { Level, LevelResult, registerLevel } from "../core/levelSystem"; +import { getCurrentGameState } from "../core/gameState"; + +interface Level2State { + currentDir: string; + fileSystem: { + [key: string]: { + type: "dir" | "file"; + contents?: string[]; + content?: string; + }; + }; + foundKey?: boolean; +} const level: Level = { id: 2, - name: 'File System Maze', - description: 'Navigate through a virtual file system to find the key.', - + name: "File System Maze", + description: "Navigate through a virtual file system to find the key.", + async initialize() { const gameState = getCurrentGameState(); if (!gameState) return; - + // Initialize level state if not already present - if (!gameState.levelStates[this.id]) { - gameState.levelStates[this.id] = { - currentDir: '/home/user', - foundKey: false, - fileSystem: { - '/home/user': { - type: 'dir', - contents: ['Documents', 'Pictures', '.hidden'] - }, - '/home/user/Documents': { - type: 'dir', - contents: ['notes.txt', 'secret'] - }, - '/home/user/Documents/notes.txt': { - type: 'file', - content: 'Remember to check hidden files. They start with a dot.' - }, - '/home/user/Documents/secret': { - type: 'dir', - contents: ['decoy.key'] - }, - '/home/user/Documents/secret/decoy.key': { - type: 'file', - content: 'Nice try, but this is not the real key!' - }, - '/home/user/Pictures': { - type: 'dir', - contents: ['vacation.jpg'] - }, - '/home/user/Pictures/vacation.jpg': { - type: 'file', - content: 'Just a picture of a beach. Nothing special here.' - }, - '/home/user/.hidden': { - type: 'dir', - contents: ['system.key'] - }, - '/home/user/.hidden/system.key': { - type: 'file', - content: 'REAL_KEY_FOUND' - } - } - }; - } + //if (!gameState.levelStates[this.id]) { + gameState.levelStates[this.id] = { + currentDir: "/home/user", + fileSystem: { + "/home/user": { + type: "dir", + contents: ["Documents", "Pictures", ".hidden"], + }, + "/home/user/Documents": { + type: "dir", + contents: ["notes.txt", "system.key"], + }, + "/home/user/Documents/notes.txt": { + type: "file", + content: "The system key is hidden somewhere in this directory...", + }, + "/home/user/Documents/system.key": { + type: "file", + content: "Congratulations! You found the system key: XK42-9Y7Z", + }, + "/home/user/Pictures": { + type: "dir", + contents: ["vacation.jpg"], + }, + "/home/user/Pictures/vacation.jpg": { + type: "file", + content: "Just a nice beach photo.", + }, + "/home/user/.hidden": { + type: "dir", + contents: ["readme.md"], + }, + "/home/user/.hidden/readme.md": { + type: "file", + content: "Nothing to see here...", + }, + }, + }; + //} }, - + async render() { const gameState = getCurrentGameState(); if (!gameState) return; - + // Make sure level state is initialized if (!gameState.levelStates[this.id]) { await this.initialize(); + return; // Return here to ensure the state is available in the next render } - - const levelState = gameState.levelStates[this.id]; - const currentDir = levelState.currentDir; - const fileSystem = levelState.fileSystem; - - console.log('You\'re in a virtual file system and need to find the system key.'); - console.log(''); + + const levelState = gameState.levelStates[this.id] as Level2State; + if (!levelState || !levelState.currentDir || !levelState.fileSystem) { + console.log("[DEBUG] Level state not properly initialized"); + console.log(JSON.stringify(levelState, null, 2)); + console.log("Error: Level state not properly initialized"); + return; + } + + const { currentDir, fileSystem } = levelState; + + console.log( + "You're in a virtual file system and need to find the system key." + ); + console.log(""); console.log(`Current directory: ${currentDir}`); - console.log(''); - - if (fileSystem[currentDir].type === 'dir') { - console.log('Contents:'); + console.log(""); + + if (fileSystem[currentDir].type === "dir") { + console.log("Contents:"); if (fileSystem[currentDir].contents.length === 0) { - console.log(' (empty directory)'); + console.log(" (empty directory)"); } else { - fileSystem[currentDir].contents.forEach(item => { + fileSystem[currentDir].contents.forEach((item) => { const path = `${currentDir}/${item}`; - const type = fileSystem[path].type === 'dir' ? 'Directory' : 'File'; + const type = fileSystem[path].type === "dir" ? "Directory" : "File"; console.log(` ${item} (${type})`); }); } } else { - console.log('File content:'); + console.log("File content:"); console.log(fileSystem[currentDir].content); } - - console.log(''); - console.log('Commands: "ls", "cd [dir]", "cat [file]", "pwd", "find [name]"'); + + console.log(""); + console.log( + 'Commands: "ls", "cd [dir]", "cat [file]", "pwd", "find [name]"' + ); }, - + async handleInput(input: string): Promise { const gameState = getCurrentGameState(); if (!gameState) { return { completed: false }; } - + // Make sure level state is initialized if (!gameState.levelStates[this.id]) { await this.initialize(); } - + const levelState = gameState.levelStates[this.id]; const fileSystem = levelState.fileSystem; const command = input.trim(); - + // Split command into parts - const parts = command.split(' '); + const parts = command.split(" "); const cmd = parts[0].toLowerCase(); - - if (cmd === 'ls') { + + if (cmd === "ls") { // List directory contents return { completed: false, - message: fileSystem[levelState.currentDir].contents.join('\n') + message: fileSystem[levelState.currentDir].contents.join("\n"), }; } - - if (cmd === 'pwd') { + + if (cmd === "pwd") { // Print working directory return { completed: false, - message: levelState.currentDir + message: levelState.currentDir, }; } - - if (cmd === 'cd' && parts.length > 1) { + + if (cmd === "cd" && parts.length > 1) { // Change directory const target = parts[1]; - - if (target === '..') { + + if (target === "..") { // Go up one directory - const pathParts = levelState.currentDir.split('/'); - if (pathParts.length > 2) { // Don't go above /home/user + const pathParts = levelState.currentDir.split("/"); + if (pathParts.length > 2) { + // Don't go above /home/user pathParts.pop(); - levelState.currentDir = pathParts.join('/'); + levelState.currentDir = pathParts.join("/"); return { completed: false, - message: `Changed directory to ${levelState.currentDir}` + message: `Changed directory to ${levelState.currentDir}`, }; } else { return { completed: false, - message: 'Cannot go above the home directory.' + message: "Cannot go above the home directory.", }; } - } else if (target === '.') { + } else if (target === ".") { // Stay in current directory return { completed: false, - message: `Still in ${levelState.currentDir}` + message: `Still in ${levelState.currentDir}`, }; } else { // Go to specified directory const newPath = `${levelState.currentDir}/${target}`; - - if (fileSystem[newPath] && fileSystem[newPath].type === 'dir') { + + if (fileSystem[newPath] && fileSystem[newPath].type === "dir") { levelState.currentDir = newPath; return { completed: false, - message: `Changed directory to ${levelState.currentDir}` + message: `Changed directory to ${levelState.currentDir}`, }; } else { return { completed: false, - message: `Cannot change to ${target}: No such directory` + message: `Cannot change to ${target}: No such directory`, }; } } } - - if (cmd === 'cat' && parts.length > 1) { + + if (cmd === "cat" && parts.length > 1) { // View file contents const target = parts[1]; const filePath = `${levelState.currentDir}/${target}`; - - if (fileSystem[filePath] && fileSystem[filePath].type === 'file') { + + if (fileSystem[filePath] && fileSystem[filePath].type === "file") { const content = fileSystem[filePath].content; - + // Check if this is the key file - if (filePath === '/home/user/.hidden/system.key') { + if (filePath === "/home/user/Documents/system.key") { levelState.foundKey = true; return { completed: true, message: `You found the system key! The file contains: ${content}`, - nextAction: 'next_level' + nextAction: "next_level", }; } - + return { completed: false, - message: `File contents: ${content}` + message: `File contents: ${content}`, }; } else { return { completed: false, - message: `Cannot read ${target}: No such file` + message: `Cannot read ${target}: No such file`, }; } } - - if (cmd === 'find' && parts.length > 1) { + + if (cmd === "find" && parts.length > 1) { // Simple find implementation const target = parts[1]; const results: string[] = []; - + // Search through the file system - Object.keys(fileSystem).forEach(path => { + Object.keys(fileSystem).forEach((path) => { if (path.includes(target)) { results.push(path); } }); - + if (results.length > 0) { return { completed: false, - message: `Found matches:\n${results.join('\n')}` + message: `Found matches:\n${results.join("\n")}`, }; } else { return { completed: false, - message: `No matches found for "${target}"` + message: `No matches found for "${target}"`, }; } } - + return { completed: false, - message: 'Unknown command or invalid syntax.' + message: "Unknown command or invalid syntax.", }; }, - + hints: [ 'Try using basic Linux commands like "ls", "cd", and "cat".', - 'Remember that hidden files and directories start with a dot (.)', - 'Use "ls" to list files, "cd" to change directories, and "cat" to view file contents.' - ] + "Remember that hidden files and directories start with a dot (.)", + 'Use "ls" to list files, "cd" to change directories, and "cat" to view file contents.', + ], }; export function registerLevel2() { registerLevel(level); -} \ No newline at end of file +} diff --git a/src/ui/gameUI.ts b/src/ui/gameUI.ts index c9dd0d4..a17a4e5 100644 --- a/src/ui/gameUI.ts +++ b/src/ui/gameUI.ts @@ -1,28 +1,38 @@ -import { getCurrentGameState, saveGame } from '../core/gameState'; -import { getLevelById, completeCurrentLevel, getAllLevels } from '../core/levelSystem'; -import { renderMainMenu } from './mainMenu'; -import { clearScreen, promptInput, styles, drawBox, drawTable } from './uiHelpers'; -import { - getTheme, - successAnimation, +import { getCurrentGameState, saveGame } from "../core/gameState"; +import { + getLevelById, + completeCurrentLevel, + getAllLevels, +} from "../core/levelSystem"; +import { renderMainMenu } from "./mainMenu"; +import { + clearScreen, + promptInput, + styles, + drawBox, + drawTable, +} from "./uiHelpers"; +import { + getTheme, + successAnimation, typewriter, - loadingAnimation -} from './visualEffects'; -import { playSound } from './soundEffects'; -import { levelUI } from './levelRenderer'; -import { addToHistory } from './commandHistory'; -import { triggerAchievement } from '../core/achievements'; + loadingAnimation, +} from "./visualEffects"; +import { playSound } from "./soundEffects"; +import { levelUI } from "./levelRenderer"; +import { addToHistory } from "./commandHistory"; +import { triggerAchievement } from "../core/achievements"; export async function renderGameUI(): Promise { const gameState = getCurrentGameState(); if (!gameState) { - console.error(styles.error('No active game')); + console.error(styles.error("No active game")); await renderMainMenu(); return; } - + const theme = getTheme(); - + // Game loop while (true) { // Get the current level at the start of each loop iteration @@ -32,161 +42,184 @@ export async function renderGameUI(): Promise { await renderMainMenu(); return; } - + clearScreen(); - + // Display game header - console.log(drawBox( - `SHELLQUEST - ${theme.accent(currentLevel.name)}`, - `Player: ${theme.accent(gameState.playerName)}\nLevel: ${gameState.currentLevel}/${getAllLevels().length}` - )); - console.log(''); - + console.log( + drawBox( + `SHELLQUEST - ${theme.accent(currentLevel.name)}`, + `Player: ${theme.accent(gameState.playerName)}\nLevel: ${ + gameState.currentLevel + }/${getAllLevels().length}` + ) + ); + console.log(""); + // Render current level in a box await levelUI.levelContent(currentLevel.name, async () => { await currentLevel.render(); }); - - console.log(''); - console.log(theme.secondary('Available commands:')); - console.log(`${theme.accent('/help')} - Show help, ${theme.accent('/save')} - Save game, ${theme.accent('/menu')} - Main menu, ${theme.accent('/hint')} - Get a hint`); - console.log(''); - + + console.log(""); + console.log(theme.secondary("Available commands:")); + console.log( + `${theme.accent("/help")} - Show help, ${theme.accent( + "/save" + )} - Save game, ${theme.accent("/menu")} - Main menu, ${theme.accent( + "/hint" + )} - Get a hint` + ); + console.log(""); + // Display input box and get player input levelUI.inputBox(); - const input = await promptInput(''); - + const input = await promptInput(""); + if (input.trim()) { addToHistory(gameState.playerName, input); } - + // Handle special commands - if (input.startsWith('/')) { + if (input.startsWith("/")) { const command = input.slice(1).toLowerCase(); - - if (command === 'help') { + + if (command === "help") { await showHelp(); continue; } - - if (command === 'save') { + + if (command === "save") { const result = await saveGame(); if (result.success) { console.log(theme.success(result.message)); } else { console.log(theme.error(result.message)); } - await promptInput('Press Enter to continue...'); + await promptInput("Press Enter to continue..."); continue; } - - if (command === 'menu') { + + if (command === "menu") { await renderMainMenu(); return; } - - if (command === 'hint') { - await triggerAchievement('hint_used'); + + if (command === "hint") { + await triggerAchievement("hint_used"); await showHint(currentLevel.hints); continue; } } - + // Process level-specific input const result = await currentLevel.handleInput(input); - + if (result.message) { - console.log(''); + console.log(""); await typewriter(result.message, 5); - await promptInput('Press Enter to continue...'); + await promptInput("Press Enter to continue..."); } - + if (result.completed) { - playSound('levelComplete'); + playSound("levelComplete"); await completeCurrentLevel(); - + // Trigger level completion achievement - await triggerAchievement('level_completed', { + await triggerAchievement("level_completed", { levelId: gameState.currentLevel, - usedHint: gameState.levelStates[gameState.currentLevel]?.usedHint || false, - timeSpent: gameState.levelStates[gameState.currentLevel]?.timeSpent || 0, - allLevels: getAllLevels().length + usedHint: + gameState.levelStates[gameState.currentLevel]?.usedHint || false, + timeSpent: + gameState.levelStates[gameState.currentLevel]?.timeSpent || 0, + allLevels: getAllLevels().length, }); - - await successAnimation('Level completed!'); - - if (result.nextAction === 'main_menu') { + + await successAnimation("Level completed!"); + + if (result.nextAction === "main_menu") { await renderMainMenu(); return; - } else if (result.nextAction === 'next_level') { + } else if (result.nextAction === "next_level") { const nextLevelId = gameState.currentLevel + 1; const nextLevel = getLevelById(nextLevelId); - + if (nextLevel) { gameState.currentLevel = nextLevelId; - await loadingAnimation('Loading next level...', 1500); + // Initialize the next level before loading it + await nextLevel.initialize(); + await loadingAnimation("Loading next level...", 1500); } else { // Game completed clearScreen(); - console.log(theme.success('🎉 Congratulations! You have completed all levels! 🎉')); - await typewriter('You have proven yourself to be a master of the terminal.', 20); - await promptInput('Press Enter to return to the main menu...'); + console.log( + theme.success( + "🎉 Congratulations! You have completed all levels! 🎉" + ) + ); + await typewriter( + "You have proven yourself to be a master of the terminal.", + 20 + ); + await promptInput("Press Enter to return to the main menu..."); await renderMainMenu(); return; } } } - + // When using a command, track it for achievements - await triggerAchievement('command_used', { command: input }); + await triggerAchievement("command_used", { command: input }); } } async function showHelp(): Promise { const theme = getTheme(); - + clearScreen(); - console.log(theme.accent('=== Help ===')); - console.log(''); - console.log('ShellQuest is a puzzle game where you solve Linux-themed challenges.'); - console.log(''); - console.log(theme.secondary('Special Commands:')); - console.log(`${theme.accent('/help')} - Show this help screen`); - console.log(`${theme.accent('/save')} - Save your game`); - console.log(`${theme.accent('/menu')} - Return to main menu`); - console.log(`${theme.accent('/hint')} - Get a hint for the current level`); - console.log(''); - console.log('Each level has its own commands and puzzles to solve.'); - console.log(''); - await promptInput('Press Enter to continue...'); + console.log(theme.accent("=== Help ===")); + console.log(""); + console.log( + "ShellQuest is a puzzle game where you solve Linux-themed challenges." + ); + console.log(""); + console.log(theme.secondary("Special Commands:")); + console.log(`${theme.accent("/help")} - Show this help screen`); + console.log(`${theme.accent("/save")} - Save your game`); + console.log(`${theme.accent("/menu")} - Return to main menu`); + console.log(`${theme.accent("/hint")} - Get a hint for the current level`); + console.log(""); + console.log("Each level has its own commands and puzzles to solve."); + console.log(""); + await promptInput("Press Enter to continue..."); } async function showHint(hints: string[]): Promise { const gameState = getCurrentGameState(); if (!gameState) return; - + const theme = getTheme(); - + // Get level state for hints const levelState = gameState.levelStates[gameState.currentLevel] || {}; const hintIndex = levelState.hintIndex || 0; - + clearScreen(); - console.log(theme.accent('=== Hint ===')); - console.log(''); - + console.log(theme.accent("=== Hint ===")); + console.log(""); + if (hintIndex < hints.length) { await typewriter(hints[hintIndex], 20); - + // Update hint index for next time gameState.levelStates[gameState.currentLevel] = { ...levelState, - hintIndex: hintIndex + 1 + hintIndex: hintIndex + 1, }; } else { - console.log(theme.warning('No more hints available for this level.')); + console.log(theme.warning("No more hints available for this level.")); } - - console.log(''); - await promptInput('Press Enter to continue...'); -} \ No newline at end of file + + console.log(""); + await promptInput("Press Enter to continue..."); +}