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 { 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<LevelResult> {
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);
}
}

View File

@ -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<void> {
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<void> {
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<void> {
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<void> {
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...');
}
console.log("");
await promptInput("Press Enter to continue...");
}