fixed level2 progression and initialization

This commit is contained in:
tcsenpai 2025-04-30 22:28:45 +02:00
parent 4e532911af
commit e98a416277
2 changed files with 266 additions and 214 deletions

View File

@ -1,248 +1,267 @@
import { Level, LevelResult, registerLevel } from '../core/levelSystem'; import { Level, LevelResult, registerLevel } from "../core/levelSystem";
import { getCurrentGameState } from '../core/gameState'; import { getCurrentGameState } from "../core/gameState";
interface Level2State {
currentDir: string;
fileSystem: {
[key: string]: {
type: "dir" | "file";
contents?: string[];
content?: string;
};
};
foundKey?: boolean;
}
const level: Level = { const level: Level = {
id: 2, id: 2,
name: 'File System Maze', name: "File System Maze",
description: 'Navigate through a virtual file system to find the key.', description: "Navigate through a virtual file system to find the key.",
async initialize() { async initialize() {
const gameState = getCurrentGameState(); const gameState = getCurrentGameState();
if (!gameState) return; if (!gameState) return;
// Initialize level state if not already present // Initialize level state if not already present
if (!gameState.levelStates[this.id]) { //if (!gameState.levelStates[this.id]) {
gameState.levelStates[this.id] = { gameState.levelStates[this.id] = {
currentDir: '/home/user', currentDir: "/home/user",
foundKey: false, fileSystem: {
fileSystem: { "/home/user": {
'/home/user': { type: "dir",
type: 'dir', contents: ["Documents", "Pictures", ".hidden"],
contents: ['Documents', 'Pictures', '.hidden'] },
}, "/home/user/Documents": {
'/home/user/Documents': { type: "dir",
type: 'dir', contents: ["notes.txt", "system.key"],
contents: ['notes.txt', 'secret'] },
}, "/home/user/Documents/notes.txt": {
'/home/user/Documents/notes.txt': { type: "file",
type: 'file', content: "The system key is hidden somewhere in this directory...",
content: 'Remember to check hidden files. They start with a dot.' },
}, "/home/user/Documents/system.key": {
'/home/user/Documents/secret': { type: "file",
type: 'dir', content: "Congratulations! You found the system key: XK42-9Y7Z",
contents: ['decoy.key'] },
}, "/home/user/Pictures": {
'/home/user/Documents/secret/decoy.key': { type: "dir",
type: 'file', contents: ["vacation.jpg"],
content: 'Nice try, but this is not the real key!' },
}, "/home/user/Pictures/vacation.jpg": {
'/home/user/Pictures': { type: "file",
type: 'dir', content: "Just a nice beach photo.",
contents: ['vacation.jpg'] },
}, "/home/user/.hidden": {
'/home/user/Pictures/vacation.jpg': { type: "dir",
type: 'file', contents: ["readme.md"],
content: 'Just a picture of a beach. Nothing special here.' },
}, "/home/user/.hidden/readme.md": {
'/home/user/.hidden': { type: "file",
type: 'dir', content: "Nothing to see here...",
contents: ['system.key'] },
}, },
'/home/user/.hidden/system.key': { };
type: 'file', //}
content: 'REAL_KEY_FOUND'
}
}
};
}
}, },
async render() { async render() {
const gameState = getCurrentGameState(); const gameState = getCurrentGameState();
if (!gameState) return; if (!gameState) return;
// Make sure level state is initialized // Make sure level state is initialized
if (!gameState.levelStates[this.id]) { if (!gameState.levelStates[this.id]) {
await this.initialize(); await this.initialize();
return; // Return here to ensure the state is available in the next render
} }
const levelState = gameState.levelStates[this.id]; const levelState = gameState.levelStates[this.id] as Level2State;
const currentDir = levelState.currentDir; if (!levelState || !levelState.currentDir || !levelState.fileSystem) {
const fileSystem = levelState.fileSystem; console.log("[DEBUG] Level state not properly initialized");
console.log(JSON.stringify(levelState, null, 2));
console.log('You\'re in a virtual file system and need to find the system key.'); console.log("Error: Level state not properly initialized");
console.log(''); 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(`Current directory: ${currentDir}`);
console.log(''); console.log("");
if (fileSystem[currentDir].type === 'dir') { if (fileSystem[currentDir].type === "dir") {
console.log('Contents:'); console.log("Contents:");
if (fileSystem[currentDir].contents.length === 0) { if (fileSystem[currentDir].contents.length === 0) {
console.log(' (empty directory)'); console.log(" (empty directory)");
} else { } else {
fileSystem[currentDir].contents.forEach(item => { fileSystem[currentDir].contents.forEach((item) => {
const path = `${currentDir}/${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})`); console.log(` ${item} (${type})`);
}); });
} }
} else { } else {
console.log('File content:'); console.log("File content:");
console.log(fileSystem[currentDir].content); console.log(fileSystem[currentDir].content);
} }
console.log(''); console.log("");
console.log('Commands: "ls", "cd [dir]", "cat [file]", "pwd", "find [name]"'); console.log(
'Commands: "ls", "cd [dir]", "cat [file]", "pwd", "find [name]"'
);
}, },
async handleInput(input: string): Promise<LevelResult> { async handleInput(input: string): Promise<LevelResult> {
const gameState = getCurrentGameState(); const gameState = getCurrentGameState();
if (!gameState) { if (!gameState) {
return { completed: false }; return { completed: false };
} }
// Make sure level state is initialized // Make sure level state is initialized
if (!gameState.levelStates[this.id]) { if (!gameState.levelStates[this.id]) {
await this.initialize(); await this.initialize();
} }
const levelState = gameState.levelStates[this.id]; const levelState = gameState.levelStates[this.id];
const fileSystem = levelState.fileSystem; const fileSystem = levelState.fileSystem;
const command = input.trim(); const command = input.trim();
// Split command into parts // Split command into parts
const parts = command.split(' '); const parts = command.split(" ");
const cmd = parts[0].toLowerCase(); const cmd = parts[0].toLowerCase();
if (cmd === 'ls') { if (cmd === "ls") {
// List directory contents // List directory contents
return { return {
completed: false, 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 // Print working directory
return { return {
completed: false, completed: false,
message: levelState.currentDir message: levelState.currentDir,
}; };
} }
if (cmd === 'cd' && parts.length > 1) { if (cmd === "cd" && parts.length > 1) {
// Change directory // Change directory
const target = parts[1]; const target = parts[1];
if (target === '..') { if (target === "..") {
// Go up one directory // Go up one directory
const pathParts = levelState.currentDir.split('/'); const pathParts = levelState.currentDir.split("/");
if (pathParts.length > 2) { // Don't go above /home/user if (pathParts.length > 2) {
// Don't go above /home/user
pathParts.pop(); pathParts.pop();
levelState.currentDir = pathParts.join('/'); levelState.currentDir = pathParts.join("/");
return { return {
completed: false, completed: false,
message: `Changed directory to ${levelState.currentDir}` message: `Changed directory to ${levelState.currentDir}`,
}; };
} else { } else {
return { return {
completed: false, 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 // Stay in current directory
return { return {
completed: false, completed: false,
message: `Still in ${levelState.currentDir}` message: `Still in ${levelState.currentDir}`,
}; };
} else { } else {
// Go to specified directory // Go to specified directory
const newPath = `${levelState.currentDir}/${target}`; const newPath = `${levelState.currentDir}/${target}`;
if (fileSystem[newPath] && fileSystem[newPath].type === 'dir') { if (fileSystem[newPath] && fileSystem[newPath].type === "dir") {
levelState.currentDir = newPath; levelState.currentDir = newPath;
return { return {
completed: false, completed: false,
message: `Changed directory to ${levelState.currentDir}` message: `Changed directory to ${levelState.currentDir}`,
}; };
} else { } else {
return { return {
completed: false, 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 // View file contents
const target = parts[1]; const target = parts[1];
const filePath = `${levelState.currentDir}/${target}`; const filePath = `${levelState.currentDir}/${target}`;
if (fileSystem[filePath] && fileSystem[filePath].type === 'file') { if (fileSystem[filePath] && fileSystem[filePath].type === "file") {
const content = fileSystem[filePath].content; const content = fileSystem[filePath].content;
// Check if this is the key file // Check if this is the key file
if (filePath === '/home/user/.hidden/system.key') { if (filePath === "/home/user/Documents/system.key") {
levelState.foundKey = true; levelState.foundKey = true;
return { return {
completed: true, completed: true,
message: `You found the system key! The file contains: ${content}`, message: `You found the system key! The file contains: ${content}`,
nextAction: 'next_level' nextAction: "next_level",
}; };
} }
return { return {
completed: false, completed: false,
message: `File contents: ${content}` message: `File contents: ${content}`,
}; };
} else { } else {
return { return {
completed: false, 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 // Simple find implementation
const target = parts[1]; const target = parts[1];
const results: string[] = []; const results: string[] = [];
// Search through the file system // Search through the file system
Object.keys(fileSystem).forEach(path => { Object.keys(fileSystem).forEach((path) => {
if (path.includes(target)) { if (path.includes(target)) {
results.push(path); results.push(path);
} }
}); });
if (results.length > 0) { if (results.length > 0) {
return { return {
completed: false, completed: false,
message: `Found matches:\n${results.join('\n')}` message: `Found matches:\n${results.join("\n")}`,
}; };
} else { } else {
return { return {
completed: false, completed: false,
message: `No matches found for "${target}"` message: `No matches found for "${target}"`,
}; };
} }
} }
return { return {
completed: false, completed: false,
message: 'Unknown command or invalid syntax.' message: "Unknown command or invalid syntax.",
}; };
}, },
hints: [ hints: [
'Try using basic Linux commands like "ls", "cd", and "cat".', 'Try using basic Linux commands like "ls", "cd", and "cat".',
'Remember that hidden files and directories start with a dot (.)', "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.' 'Use "ls" to list files, "cd" to change directories, and "cat" to view file contents.',
] ],
}; };
export function registerLevel2() { export function registerLevel2() {
registerLevel(level); registerLevel(level);
} }

View File

@ -1,28 +1,38 @@
import { getCurrentGameState, saveGame } from '../core/gameState'; import { getCurrentGameState, saveGame } from "../core/gameState";
import { getLevelById, completeCurrentLevel, getAllLevels } from '../core/levelSystem'; import {
import { renderMainMenu } from './mainMenu'; getLevelById,
import { clearScreen, promptInput, styles, drawBox, drawTable } from './uiHelpers'; completeCurrentLevel,
import { getAllLevels,
getTheme, } from "../core/levelSystem";
successAnimation, import { renderMainMenu } from "./mainMenu";
import {
clearScreen,
promptInput,
styles,
drawBox,
drawTable,
} from "./uiHelpers";
import {
getTheme,
successAnimation,
typewriter, typewriter,
loadingAnimation loadingAnimation,
} from './visualEffects'; } from "./visualEffects";
import { playSound } from './soundEffects'; import { playSound } from "./soundEffects";
import { levelUI } from './levelRenderer'; import { levelUI } from "./levelRenderer";
import { addToHistory } from './commandHistory'; import { addToHistory } from "./commandHistory";
import { triggerAchievement } from '../core/achievements'; import { triggerAchievement } from "../core/achievements";
export async function renderGameUI(): Promise<void> { export async function renderGameUI(): Promise<void> {
const gameState = getCurrentGameState(); const gameState = getCurrentGameState();
if (!gameState) { if (!gameState) {
console.error(styles.error('No active game')); console.error(styles.error("No active game"));
await renderMainMenu(); await renderMainMenu();
return; return;
} }
const theme = getTheme(); const theme = getTheme();
// Game loop // Game loop
while (true) { while (true) {
// Get the current level at the start of each loop iteration // Get the current level at the start of each loop iteration
@ -32,161 +42,184 @@ export async function renderGameUI(): Promise<void> {
await renderMainMenu(); await renderMainMenu();
return; return;
} }
clearScreen(); clearScreen();
// Display game header // Display game header
console.log(drawBox( console.log(
`SHELLQUEST - ${theme.accent(currentLevel.name)}`, drawBox(
`Player: ${theme.accent(gameState.playerName)}\nLevel: ${gameState.currentLevel}/${getAllLevels().length}` `SHELLQUEST - ${theme.accent(currentLevel.name)}`,
)); `Player: ${theme.accent(gameState.playerName)}\nLevel: ${
console.log(''); gameState.currentLevel
}/${getAllLevels().length}`
)
);
console.log("");
// Render current level in a box // Render current level in a box
await levelUI.levelContent(currentLevel.name, async () => { await levelUI.levelContent(currentLevel.name, async () => {
await currentLevel.render(); await currentLevel.render();
}); });
console.log(''); console.log("");
console.log(theme.secondary('Available commands:')); 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(''); `${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 // Display input box and get player input
levelUI.inputBox(); levelUI.inputBox();
const input = await promptInput(''); const input = await promptInput("");
if (input.trim()) { if (input.trim()) {
addToHistory(gameState.playerName, input); addToHistory(gameState.playerName, input);
} }
// Handle special commands // Handle special commands
if (input.startsWith('/')) { if (input.startsWith("/")) {
const command = input.slice(1).toLowerCase(); const command = input.slice(1).toLowerCase();
if (command === 'help') { if (command === "help") {
await showHelp(); await showHelp();
continue; continue;
} }
if (command === 'save') { if (command === "save") {
const result = await saveGame(); const result = await saveGame();
if (result.success) { if (result.success) {
console.log(theme.success(result.message)); console.log(theme.success(result.message));
} else { } else {
console.log(theme.error(result.message)); console.log(theme.error(result.message));
} }
await promptInput('Press Enter to continue...'); await promptInput("Press Enter to continue...");
continue; continue;
} }
if (command === 'menu') { if (command === "menu") {
await renderMainMenu(); await renderMainMenu();
return; return;
} }
if (command === 'hint') { if (command === "hint") {
await triggerAchievement('hint_used'); await triggerAchievement("hint_used");
await showHint(currentLevel.hints); await showHint(currentLevel.hints);
continue; continue;
} }
} }
// Process level-specific input // Process level-specific input
const result = await currentLevel.handleInput(input); const result = await currentLevel.handleInput(input);
if (result.message) { if (result.message) {
console.log(''); console.log("");
await typewriter(result.message, 5); await typewriter(result.message, 5);
await promptInput('Press Enter to continue...'); await promptInput("Press Enter to continue...");
} }
if (result.completed) { if (result.completed) {
playSound('levelComplete'); playSound("levelComplete");
await completeCurrentLevel(); await completeCurrentLevel();
// Trigger level completion achievement // Trigger level completion achievement
await triggerAchievement('level_completed', { await triggerAchievement("level_completed", {
levelId: gameState.currentLevel, levelId: gameState.currentLevel,
usedHint: gameState.levelStates[gameState.currentLevel]?.usedHint || false, usedHint:
timeSpent: gameState.levelStates[gameState.currentLevel]?.timeSpent || 0, gameState.levelStates[gameState.currentLevel]?.usedHint || false,
allLevels: getAllLevels().length timeSpent:
gameState.levelStates[gameState.currentLevel]?.timeSpent || 0,
allLevels: getAllLevels().length,
}); });
await successAnimation('Level completed!'); await successAnimation("Level completed!");
if (result.nextAction === 'main_menu') { if (result.nextAction === "main_menu") {
await renderMainMenu(); await renderMainMenu();
return; return;
} else if (result.nextAction === 'next_level') { } else if (result.nextAction === "next_level") {
const nextLevelId = gameState.currentLevel + 1; const nextLevelId = gameState.currentLevel + 1;
const nextLevel = getLevelById(nextLevelId); const nextLevel = getLevelById(nextLevelId);
if (nextLevel) { if (nextLevel) {
gameState.currentLevel = nextLevelId; 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 { } else {
// Game completed // Game completed
clearScreen(); clearScreen();
console.log(theme.success('🎉 Congratulations! You have completed all levels! 🎉')); console.log(
await typewriter('You have proven yourself to be a master of the terminal.', 20); theme.success(
await promptInput('Press Enter to return to the main menu...'); "🎉 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(); await renderMainMenu();
return; return;
} }
} }
} }
// When using a command, track it for achievements // When using a command, track it for achievements
await triggerAchievement('command_used', { command: input }); await triggerAchievement("command_used", { command: input });
} }
} }
async function showHelp(): Promise<void> { async function showHelp(): Promise<void> {
const theme = getTheme(); const theme = getTheme();
clearScreen(); clearScreen();
console.log(theme.accent('=== Help ===')); console.log(theme.accent("=== Help ==="));
console.log(''); console.log("");
console.log('ShellQuest is a puzzle game where you solve Linux-themed challenges.'); console.log(
console.log(''); "ShellQuest is a puzzle game where you solve Linux-themed challenges."
console.log(theme.secondary('Special Commands:')); );
console.log(`${theme.accent('/help')} - Show this help screen`); console.log("");
console.log(`${theme.accent('/save')} - Save your game`); console.log(theme.secondary("Special Commands:"));
console.log(`${theme.accent('/menu')} - Return to main menu`); console.log(`${theme.accent("/help")} - Show this help screen`);
console.log(`${theme.accent('/hint')} - Get a hint for the current level`); console.log(`${theme.accent("/save")} - Save your game`);
console.log(''); console.log(`${theme.accent("/menu")} - Return to main menu`);
console.log('Each level has its own commands and puzzles to solve.'); console.log(`${theme.accent("/hint")} - Get a hint for the current level`);
console.log(''); console.log("");
await promptInput('Press Enter to continue...'); 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<void> { async function showHint(hints: string[]): Promise<void> {
const gameState = getCurrentGameState(); const gameState = getCurrentGameState();
if (!gameState) return; if (!gameState) return;
const theme = getTheme(); const theme = getTheme();
// Get level state for hints // Get level state for hints
const levelState = gameState.levelStates[gameState.currentLevel] || {}; const levelState = gameState.levelStates[gameState.currentLevel] || {};
const hintIndex = levelState.hintIndex || 0; const hintIndex = levelState.hintIndex || 0;
clearScreen(); clearScreen();
console.log(theme.accent('=== Hint ===')); console.log(theme.accent("=== Hint ==="));
console.log(''); console.log("");
if (hintIndex < hints.length) { if (hintIndex < hints.length) {
await typewriter(hints[hintIndex], 20); await typewriter(hints[hintIndex], 20);
// Update hint index for next time // Update hint index for next time
gameState.levelStates[gameState.currentLevel] = { gameState.levelStates[gameState.currentLevel] = {
...levelState, ...levelState,
hintIndex: hintIndex + 1 hintIndex: hintIndex + 1,
}; };
} else { } 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(''); console.log("");
await promptInput('Press Enter to continue...'); await promptInput("Press Enter to continue...");
} }