mirror of
https://github.com/tcsenpai/shellquest.git
synced 2025-06-04 02:10:12 +00:00
profiles
This commit is contained in:
parent
1ad0f8d871
commit
5c9be8fc79
@ -3,6 +3,8 @@ import path from 'path';
|
|||||||
import { getCurrentGameState } from './gameState';
|
import { getCurrentGameState } from './gameState';
|
||||||
import { getTheme, successAnimation } from '../ui/visualEffects';
|
import { getTheme, successAnimation } from '../ui/visualEffects';
|
||||||
import { playSound } from '../ui/soundEffects';
|
import { playSound } from '../ui/soundEffects';
|
||||||
|
import { getCurrentProfile } from './playerProfile';
|
||||||
|
import { saveProfile } from './playerProfile';
|
||||||
|
|
||||||
// Define achievement types
|
// Define achievement types
|
||||||
export interface Achievement {
|
export interface Achievement {
|
||||||
@ -81,14 +83,14 @@ const achievementsPath = path.join(process.cwd(), 'achievements.json');
|
|||||||
|
|
||||||
// Load achievements from file
|
// Load achievements from file
|
||||||
export async function loadAchievements(): Promise<Achievement[]> {
|
export async function loadAchievements(): Promise<Achievement[]> {
|
||||||
try {
|
const profile = await getCurrentProfile();
|
||||||
const data = await fs.readFile(achievementsPath, 'utf-8');
|
|
||||||
return JSON.parse(data);
|
if (profile && profile.achievements.length > 0) {
|
||||||
} catch (error) {
|
return profile.achievements;
|
||||||
// If file doesn't exist, create it with default achievements
|
|
||||||
await saveAchievements(achievements);
|
|
||||||
return achievements;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return default achievements if no profile exists or no achievements in profile
|
||||||
|
return [...achievements];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save achievements to file
|
// Save achievements to file
|
||||||
@ -109,36 +111,42 @@ export async function getPlayerAchievements(playerName: string): Promise<Achieve
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Unlock an achievement
|
// Unlock an achievement
|
||||||
export async function unlockAchievement(achievementId: string): Promise<boolean> {
|
export async function unlockAchievement(id: string): Promise<boolean> {
|
||||||
const gameState = getCurrentGameState();
|
const profile = await getCurrentProfile();
|
||||||
if (!gameState) return false;
|
if (!profile) return false;
|
||||||
|
|
||||||
// Load achievements
|
// Find the achievement in the profile
|
||||||
const allAchievements = await loadAchievements();
|
let achievementIndex = profile.achievements.findIndex(a => a.id === id);
|
||||||
const achievement = allAchievements.find(a => a.id === achievementId);
|
|
||||||
|
|
||||||
if (!achievement || achievement.unlocked) {
|
// If not found in profile, check the default achievements
|
||||||
return false; // Achievement doesn't exist or is already unlocked
|
if (achievementIndex === -1) {
|
||||||
|
const defaultAchievement = achievements.find(a => a.id === id);
|
||||||
|
if (!defaultAchievement) return false;
|
||||||
|
|
||||||
|
// Add the achievement to the profile
|
||||||
|
profile.achievements.push({
|
||||||
|
...defaultAchievement,
|
||||||
|
unlocked: true,
|
||||||
|
unlockedAt: Date.now()
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Achievement already exists in profile, just update it
|
||||||
|
if (profile.achievements[achievementIndex].unlocked) {
|
||||||
|
return false; // Already unlocked
|
||||||
|
}
|
||||||
|
|
||||||
|
profile.achievements[achievementIndex].unlocked = true;
|
||||||
|
profile.achievements[achievementIndex].unlockedAt = Date.now();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unlock the achievement
|
// Save the updated profile
|
||||||
achievement.unlocked = true;
|
await saveProfile(profile);
|
||||||
achievement.unlockedAt = Date.now();
|
|
||||||
|
|
||||||
// Save achievements
|
|
||||||
await saveAchievements(allAchievements);
|
|
||||||
|
|
||||||
// Show achievement notification
|
// Show achievement notification
|
||||||
const theme = getTheme();
|
const achievement = profile.achievements.find(a => a.id === id);
|
||||||
console.log('\n');
|
if (achievement) {
|
||||||
console.log('╔' + '═'.repeat(50) + '╗');
|
await showAchievementNotification(achievement);
|
||||||
console.log('║ ' + theme.accent('Achievement Unlocked!').padEnd(48) + ' ║');
|
}
|
||||||
console.log('║ ' + `${achievement.icon} ${achievement.name}`.padEnd(48) + ' ║');
|
|
||||||
console.log('║ ' + achievement.description.padEnd(48) + ' ║');
|
|
||||||
console.log('╚' + '═'.repeat(50) + '╝');
|
|
||||||
|
|
||||||
// Play sound
|
|
||||||
playSound('success');
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -33,15 +33,37 @@ export async function initializeGame() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Helper functions for save management
|
// Helper functions for save management
|
||||||
export async function listSaves() {
|
export async function listSaves(): Promise<string[]> {
|
||||||
try {
|
try {
|
||||||
|
await ensureSavesDir();
|
||||||
const files = await fs.readdir(SAVE_DIR);
|
const files = await fs.readdir(SAVE_DIR);
|
||||||
return files.filter(file => file.endsWith('.json'));
|
|
||||||
|
// Filter out profile files and remove .json extension
|
||||||
|
return files
|
||||||
|
.filter(file => file.endsWith('.json') && !file.endsWith('_profile.json'))
|
||||||
|
.map(file => file.replace('.json', ''));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to list saves:', error);
|
console.error('Error listing saves:', error);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getSavePath = (saveName: string) => path.join(SAVE_DIR, `${saveName}.json`);
|
export function getSavePath(saveName: string): string {
|
||||||
export const getLeaderboardPath = () => LEADERBOARD_PATH;
|
// Remove .json extension if it's already there
|
||||||
|
const baseName = saveName.endsWith('.json')
|
||||||
|
? saveName.slice(0, -5)
|
||||||
|
: saveName;
|
||||||
|
|
||||||
|
return path.join(SAVE_DIR, `${baseName}.json`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getLeaderboardPath = () => LEADERBOARD_PATH;
|
||||||
|
|
||||||
|
// Ensure saves directory exists
|
||||||
|
export async function ensureSavesDir(): Promise<void> {
|
||||||
|
try {
|
||||||
|
await fs.mkdir(SAVE_DIR, { recursive: true });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error creating saves directory:', error);
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,7 @@
|
|||||||
import fs from 'fs/promises';
|
import fs from 'fs/promises';
|
||||||
import { getSavePath } from './gameInit';
|
import { getSavePath } from './gameInit';
|
||||||
|
import { createProfile, loadProfile, saveProfile } from './playerProfile';
|
||||||
|
import { getCurrentProfile } from './playerProfile';
|
||||||
|
|
||||||
export interface GameState {
|
export interface GameState {
|
||||||
playerName: string;
|
playerName: string;
|
||||||
@ -17,8 +19,12 @@ export function getCurrentGameState(): GameState | null {
|
|||||||
return currentGameState;
|
return currentGameState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function setCurrentGameState(gameState: GameState): void {
|
||||||
|
currentGameState = gameState;
|
||||||
|
}
|
||||||
|
|
||||||
export function createNewGame(playerName: string): GameState {
|
export function createNewGame(playerName: string): GameState {
|
||||||
const newState: GameState = {
|
const gameState: GameState = {
|
||||||
playerName,
|
playerName,
|
||||||
currentLevel: 1,
|
currentLevel: 1,
|
||||||
startTime: Date.now(),
|
startTime: Date.now(),
|
||||||
@ -28,8 +34,15 @@ export function createNewGame(playerName: string): GameState {
|
|||||||
levelStates: {}
|
levelStates: {}
|
||||||
};
|
};
|
||||||
|
|
||||||
currentGameState = newState;
|
// Create a new profile for this player
|
||||||
return newState;
|
createOrLoadProfile(playerName);
|
||||||
|
|
||||||
|
setCurrentGameState(gameState);
|
||||||
|
|
||||||
|
// Save the initial game state
|
||||||
|
saveGame(playerName);
|
||||||
|
|
||||||
|
return gameState;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function saveGame(saveName?: string): Promise<boolean> {
|
export async function saveGame(saveName?: string): Promise<boolean> {
|
||||||
@ -61,6 +74,10 @@ export async function loadGame(saveName: string): Promise<boolean> {
|
|||||||
try {
|
try {
|
||||||
const saveData = await fs.readFile(getSavePath(saveName), 'utf-8');
|
const saveData = await fs.readFile(getSavePath(saveName), 'utf-8');
|
||||||
currentGameState = JSON.parse(saveData) as GameState;
|
currentGameState = JSON.parse(saveData) as GameState;
|
||||||
|
|
||||||
|
// Make sure the profile exists
|
||||||
|
await createOrLoadProfile(currentGameState.playerName);
|
||||||
|
|
||||||
console.log(`Game loaded: ${saveName}`);
|
console.log(`Game loaded: ${saveName}`);
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -72,4 +89,36 @@ export async function loadGame(saveName: string): Promise<boolean> {
|
|||||||
export async function autoSave(): Promise<boolean> {
|
export async function autoSave(): Promise<boolean> {
|
||||||
if (!currentGameState) return false;
|
if (!currentGameState) return false;
|
||||||
return saveGame(`${currentGameState.playerName}_autosave`);
|
return saveGame(`${currentGameState.playerName}_autosave`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createOrLoadProfile(playerName: string): Promise<void> {
|
||||||
|
const profile = await loadProfile(playerName);
|
||||||
|
if (!profile) {
|
||||||
|
await createProfile(playerName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function completeCurrentLevel(): Promise<void> {
|
||||||
|
const gameState = getCurrentGameState();
|
||||||
|
if (!gameState) return;
|
||||||
|
|
||||||
|
// Add the level to completed levels in the game state
|
||||||
|
if (!gameState.completedLevels.includes(gameState.currentLevel)) {
|
||||||
|
gameState.completedLevels.push(gameState.currentLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also update the profile
|
||||||
|
const profile = await getCurrentProfile();
|
||||||
|
if (profile) {
|
||||||
|
if (!profile.completedLevels.includes(gameState.currentLevel)) {
|
||||||
|
profile.completedLevels.push(gameState.currentLevel);
|
||||||
|
await saveProfile(profile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move to the next level
|
||||||
|
gameState.currentLevel++;
|
||||||
|
|
||||||
|
// Save the game
|
||||||
|
await saveGame();
|
||||||
}
|
}
|
88
src/core/playerProfile.ts
Normal file
88
src/core/playerProfile.ts
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
import fs from 'fs/promises';
|
||||||
|
import path from 'path';
|
||||||
|
import { Achievement } from './achievements';
|
||||||
|
import { getCurrentGameState } from './gameState';
|
||||||
|
import { getSavePath } from './gameInit';
|
||||||
|
|
||||||
|
export interface PlayerProfile {
|
||||||
|
playerName: string;
|
||||||
|
achievements: Achievement[];
|
||||||
|
lastPlayed: number;
|
||||||
|
totalPlayTime: number;
|
||||||
|
completedLevels: number[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the same directory for profiles and saves
|
||||||
|
const profilesDir = path.join(process.cwd(), 'saves');
|
||||||
|
|
||||||
|
// Ensure profiles directory exists
|
||||||
|
export async function ensureProfilesDir(): Promise<void> {
|
||||||
|
try {
|
||||||
|
await fs.mkdir(profilesDir, { recursive: true });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error creating profiles directory:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get profile path for a player
|
||||||
|
function getProfilePath(playerName: string): string {
|
||||||
|
return path.join(profilesDir, `${playerName.toLowerCase()}_profile.json`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new player profile
|
||||||
|
export async function createProfile(playerName: string): Promise<PlayerProfile> {
|
||||||
|
const profile: PlayerProfile = {
|
||||||
|
playerName,
|
||||||
|
achievements: [],
|
||||||
|
lastPlayed: Date.now(),
|
||||||
|
totalPlayTime: 0,
|
||||||
|
completedLevels: []
|
||||||
|
};
|
||||||
|
|
||||||
|
await saveProfile(profile);
|
||||||
|
return profile;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load a player profile
|
||||||
|
export async function loadProfile(playerName: string): Promise<PlayerProfile | null> {
|
||||||
|
try {
|
||||||
|
const profilePath = getProfilePath(playerName);
|
||||||
|
const data = await fs.readFile(profilePath, 'utf-8');
|
||||||
|
return JSON.parse(data) as PlayerProfile;
|
||||||
|
} catch (error) {
|
||||||
|
// Profile doesn't exist or can't be read
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save a player profile
|
||||||
|
export async function saveProfile(profile: PlayerProfile): Promise<void> {
|
||||||
|
try {
|
||||||
|
const profilePath = getProfilePath(profile.playerName);
|
||||||
|
await fs.writeFile(profilePath, JSON.stringify(profile, null, 2));
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error saving profile:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get current player profile
|
||||||
|
export async function getCurrentProfile(): Promise<PlayerProfile | null> {
|
||||||
|
const gameState = getCurrentGameState();
|
||||||
|
if (!gameState) return null;
|
||||||
|
|
||||||
|
const profile = await loadProfile(gameState.playerName);
|
||||||
|
return profile;
|
||||||
|
}
|
||||||
|
|
||||||
|
// List all profiles
|
||||||
|
export async function listProfiles(): Promise<string[]> {
|
||||||
|
try {
|
||||||
|
const files = await fs.readdir(profilesDir);
|
||||||
|
return files
|
||||||
|
.filter(file => file.endsWith('_profile.json'))
|
||||||
|
.map(file => file.replace('_profile.json', ''));
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error listing profiles:', error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env bun
|
#!/usr/bin/env bun
|
||||||
|
|
||||||
import { renderMainMenu } from './ui/mainMenu';
|
import { renderEntryMenu } from './ui/entryMenu';
|
||||||
import { initializeGame } from './core/gameInit';
|
import { initializeGame } from './core/gameInit';
|
||||||
import { registerAllLevels } from './levels';
|
import { registerAllLevels } from './levels';
|
||||||
import { initializeAchievements } from './core/achievements';
|
import { initializeAchievements } from './core/achievements';
|
||||||
@ -15,8 +15,8 @@ async function main() {
|
|||||||
// Register all game levels
|
// Register all game levels
|
||||||
registerAllLevels();
|
registerAllLevels();
|
||||||
|
|
||||||
// Render the main menu to start
|
// Render the entry menu to start
|
||||||
await renderMainMenu();
|
await renderEntryMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
main().catch(error => {
|
main().catch(error => {
|
||||||
|
131
src/ui/entryMenu.ts
Normal file
131
src/ui/entryMenu.ts
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
import { clearScreen, promptInput, drawBox } from './uiHelpers';
|
||||||
|
import { generateLogo, getTheme, bootSequence } from './visualEffects';
|
||||||
|
import { loadProfile, createProfile, listProfiles } from '../core/playerProfile';
|
||||||
|
import { createNewGame, loadGame } from '../core/gameState';
|
||||||
|
import { renderMainMenu } from './mainMenu';
|
||||||
|
import { successAnimation, loadingAnimation } from './visualEffects';
|
||||||
|
import { listSaves } from '../core/gameInit';
|
||||||
|
|
||||||
|
// Track if we've shown the boot sequence
|
||||||
|
let bootSequenceShown = false;
|
||||||
|
|
||||||
|
export async function renderEntryMenu(): Promise<void> {
|
||||||
|
// Show boot sequence only once
|
||||||
|
if (!bootSequenceShown) {
|
||||||
|
await bootSequence();
|
||||||
|
bootSequenceShown = true;
|
||||||
|
} else {
|
||||||
|
clearScreen();
|
||||||
|
console.log(generateLogo());
|
||||||
|
console.log('');
|
||||||
|
}
|
||||||
|
|
||||||
|
const theme = getTheme();
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
clearScreen();
|
||||||
|
console.log(generateLogo());
|
||||||
|
console.log(theme.secondary('A Linux Terminal Escape Room Game'));
|
||||||
|
console.log('');
|
||||||
|
|
||||||
|
const menuOptions = [
|
||||||
|
'1. ' + theme.accent('New Game'),
|
||||||
|
'2. ' + theme.accent('Load Game'),
|
||||||
|
'3. ' + theme.accent('Exit')
|
||||||
|
];
|
||||||
|
|
||||||
|
console.log(drawBox('WELCOME', menuOptions.join('\n')));
|
||||||
|
console.log('');
|
||||||
|
|
||||||
|
const choice = await promptInput('Select an option: ');
|
||||||
|
|
||||||
|
if (choice === '1') {
|
||||||
|
const success = await newGameMenu();
|
||||||
|
if (success) {
|
||||||
|
await renderMainMenu();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (choice === '2') {
|
||||||
|
const success = await loadGameMenu();
|
||||||
|
if (success) {
|
||||||
|
await renderMainMenu();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (choice === '3') {
|
||||||
|
console.log('Thanks for playing Terminal Escape!');
|
||||||
|
process.exit(0);
|
||||||
|
} else {
|
||||||
|
console.log(theme.error('Invalid option. Press Enter to continue...'));
|
||||||
|
await promptInput('');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function newGameMenu(): Promise<boolean> {
|
||||||
|
const theme = getTheme();
|
||||||
|
|
||||||
|
clearScreen();
|
||||||
|
console.log(theme.accent('=== NEW GAME ==='));
|
||||||
|
console.log('');
|
||||||
|
|
||||||
|
const playerName = await promptInput('Enter your name: ');
|
||||||
|
|
||||||
|
if (!playerName) {
|
||||||
|
console.log(theme.error('Name cannot be empty.'));
|
||||||
|
await promptInput('Press Enter to continue...');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
await loadingAnimation('Creating new game...', 1000);
|
||||||
|
|
||||||
|
// Create a new game state
|
||||||
|
const gameState = createNewGame(playerName);
|
||||||
|
|
||||||
|
await successAnimation('Game created successfully!');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadGameMenu(): Promise<boolean> {
|
||||||
|
const theme = getTheme();
|
||||||
|
|
||||||
|
clearScreen();
|
||||||
|
console.log(theme.accent('=== LOAD GAME ==='));
|
||||||
|
console.log('');
|
||||||
|
|
||||||
|
// Get list of save files
|
||||||
|
const saveFiles = await listSaves();
|
||||||
|
|
||||||
|
if (saveFiles.length === 0) {
|
||||||
|
console.log(theme.warning('No saved games found.'));
|
||||||
|
await promptInput('Press Enter to continue...');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Available saved games:');
|
||||||
|
saveFiles.forEach((save, index) => {
|
||||||
|
console.log(`${index + 1}. ${theme.accent(save)}`);
|
||||||
|
});
|
||||||
|
console.log('');
|
||||||
|
|
||||||
|
const choice = await promptInput('Select a saved game (or 0 to cancel): ');
|
||||||
|
const choiceNum = parseInt(choice);
|
||||||
|
|
||||||
|
if (choiceNum === 0 || isNaN(choiceNum) || choiceNum > saveFiles.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveName = saveFiles[choiceNum - 1];
|
||||||
|
|
||||||
|
// Try to load the save
|
||||||
|
await loadingAnimation('Loading game...', 1000);
|
||||||
|
const success = await loadGame(saveName);
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
await successAnimation('Game loaded successfully!');
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
console.log(theme.error('Failed to load game.'));
|
||||||
|
await promptInput('Press Enter to continue...');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
import { createNewGame, loadGame } from '../core/gameState';
|
import { createNewGame, loadGame, getCurrentGameState } from '../core/gameState';
|
||||||
import { listSaves } from '../core/gameInit';
|
import { listSaves } from '../core/gameInit';
|
||||||
import { getLeaderboard, formatTime } from '../core/leaderboard';
|
import { getLeaderboard, formatTime } from '../core/leaderboard';
|
||||||
import { startLevel, getAllLevels } from '../core/levelSystem';
|
import { startLevel, getAllLevels } from '../core/levelSystem';
|
||||||
@ -25,6 +25,7 @@ import {
|
|||||||
initSoundSystem
|
initSoundSystem
|
||||||
} from './soundEffects';
|
} from './soundEffects';
|
||||||
import { addToHistory } from './commandHistory';
|
import { addToHistory } from './commandHistory';
|
||||||
|
import { renderEntryMenu } from './entryMenu';
|
||||||
|
|
||||||
// Track if we've shown the boot sequence
|
// Track if we've shown the boot sequence
|
||||||
let bootSequenceShown = false;
|
let bootSequenceShown = false;
|
||||||
@ -33,14 +34,11 @@ let bootSequenceShown = false;
|
|||||||
initSoundSystem();
|
initSoundSystem();
|
||||||
|
|
||||||
export async function renderMainMenu(): Promise<void> {
|
export async function renderMainMenu(): Promise<void> {
|
||||||
// Show boot sequence only once
|
const gameState = getCurrentGameState();
|
||||||
if (!bootSequenceShown) {
|
if (!gameState) {
|
||||||
await bootSequence();
|
// If no game state, go back to entry menu
|
||||||
bootSequenceShown = true;
|
await renderEntryMenu();
|
||||||
} else {
|
return;
|
||||||
clearScreen();
|
|
||||||
console.log(generateLogo());
|
|
||||||
console.log('');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const theme = getTheme();
|
const theme = getTheme();
|
||||||
@ -52,48 +50,52 @@ export async function renderMainMenu(): Promise<void> {
|
|||||||
console.log('');
|
console.log('');
|
||||||
|
|
||||||
const menuOptions = [
|
const menuOptions = [
|
||||||
'1. ' + theme.accent('New Game'),
|
'1. ' + theme.accent('Continue Game'),
|
||||||
'2. ' + theme.accent('Load Game'),
|
'2. ' + theme.accent('Achievements'),
|
||||||
'3. ' + theme.accent('Leaderboard'),
|
'3. ' + theme.accent('Progress Map'),
|
||||||
'4. ' + theme.accent('Achievements'),
|
'4. ' + theme.accent('Leaderboard'),
|
||||||
'5. ' + theme.accent('Progress Map'),
|
'5. ' + theme.accent('Settings'),
|
||||||
'6. ' + theme.accent('Settings'),
|
'6. ' + theme.accent('Back to Entry Menu'),
|
||||||
'7. ' + theme.accent('Exit')
|
'7. ' + theme.accent('Exit')
|
||||||
];
|
];
|
||||||
|
|
||||||
console.log(drawBox('MAIN MENU', menuOptions.join('\n')));
|
console.log(drawBox('MAIN MENU', menuOptions.join('\n')));
|
||||||
console.log('');
|
console.log('');
|
||||||
|
console.log(theme.info(`Player: ${gameState.playerName}`));
|
||||||
|
console.log('');
|
||||||
|
|
||||||
const choice = await promptInput('Select an option: ');
|
const choice = await promptInput('Select an option: ');
|
||||||
|
|
||||||
switch (choice) {
|
if (choice === '1') {
|
||||||
case '1':
|
// Continue game
|
||||||
await newGameMenu();
|
startLevel(gameState.currentLevel);
|
||||||
break;
|
await renderGameUI();
|
||||||
case '2':
|
} else if (choice === '2') {
|
||||||
await loadGameMenu();
|
// Show achievements
|
||||||
break;
|
await showAchievements();
|
||||||
case '3':
|
await promptInput('Press Enter to continue...');
|
||||||
await showLeaderboard();
|
} else if (choice === '3') {
|
||||||
break;
|
// Show progress map
|
||||||
case '4':
|
clearScreen();
|
||||||
await showAchievements();
|
await renderProgressMap();
|
||||||
await promptInput('Press Enter to continue...');
|
await promptInput('Press Enter to continue...');
|
||||||
break;
|
} else if (choice === '4') {
|
||||||
case '5':
|
// Show leaderboard
|
||||||
clearScreen();
|
await showLeaderboard();
|
||||||
renderProgressMap();
|
} else if (choice === '5') {
|
||||||
await promptInput('Press Enter to continue...');
|
// Settings
|
||||||
break;
|
await showSettings();
|
||||||
case '6':
|
} else if (choice === '6') {
|
||||||
await showSettings();
|
// Back to entry menu
|
||||||
break;
|
await renderEntryMenu();
|
||||||
case '7':
|
return;
|
||||||
await animateText('Thanks for playing Terminal Escape!', 30);
|
} else if (choice === '7') {
|
||||||
process.exit(0);
|
// Exit
|
||||||
default:
|
await animateText('Thanks for playing Terminal Escape!', 30);
|
||||||
console.log(theme.error('Invalid option. Press Enter to continue...'));
|
process.exit(0);
|
||||||
await promptInput('');
|
} else {
|
||||||
|
console.log(theme.error('Invalid option. Press Enter to continue...'));
|
||||||
|
await promptInput('');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -150,80 +152,22 @@ async function changeTheme(): Promise<void> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the existing functions to use the current theme
|
async function showLeaderboard(): Promise<void> {
|
||||||
async function newGameMenu(): Promise<void> {
|
|
||||||
const theme = getTheme();
|
const theme = getTheme();
|
||||||
|
|
||||||
clearScreen();
|
clearScreen();
|
||||||
console.log(theme.accent('=== NEW GAME ==='));
|
console.log(theme.accent('=== LEADERBOARD ==='));
|
||||||
console.log('');
|
|
||||||
|
|
||||||
const playerName = await promptInput('Enter your name: ');
|
|
||||||
|
|
||||||
if (!playerName) {
|
|
||||||
console.log(theme.error('Name cannot be empty.'));
|
|
||||||
await promptInput('Press Enter to continue...');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await loadingAnimation('Creating new game...', 1000);
|
|
||||||
|
|
||||||
const gameState = createNewGame(playerName);
|
|
||||||
startLevel(gameState.currentLevel);
|
|
||||||
|
|
||||||
await renderGameUI();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadGameMenu(): Promise<void> {
|
|
||||||
clearScreen();
|
|
||||||
console.log('=== Load Game ===');
|
|
||||||
console.log('');
|
|
||||||
|
|
||||||
const saves = await listSaves();
|
|
||||||
if (saves.length === 0) {
|
|
||||||
console.log('No saved games found. Press Enter to return to main menu...');
|
|
||||||
await promptInput('');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('Available saves:');
|
|
||||||
saves.forEach((save, index) => {
|
|
||||||
console.log(`${index + 1}. ${save.replace('.json', '')}`);
|
|
||||||
});
|
|
||||||
console.log('');
|
|
||||||
|
|
||||||
const choice = await promptInput('Select a save to load (or 0 to cancel): ');
|
|
||||||
const choiceNum = parseInt(choice);
|
|
||||||
|
|
||||||
if (choiceNum === 0 || isNaN(choiceNum) || choiceNum > saves.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const saveName = saves[choiceNum - 1].replace('.json', '');
|
|
||||||
const success = await loadGame(saveName);
|
|
||||||
|
|
||||||
if (success) {
|
|
||||||
await renderGameUI();
|
|
||||||
} else {
|
|
||||||
console.log('Failed to load game. Press Enter to return to main menu...');
|
|
||||||
await promptInput('');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function showLeaderboard(): Promise<void> {
|
|
||||||
clearScreen();
|
|
||||||
console.log('=== Leaderboard ===');
|
|
||||||
console.log('');
|
console.log('');
|
||||||
|
|
||||||
const leaderboard = await getLeaderboard();
|
const leaderboard = await getLeaderboard();
|
||||||
|
|
||||||
if (leaderboard.players.length === 0) {
|
if (leaderboard.players.length === 0) {
|
||||||
console.log('No entries yet. Be the first to complete the game!');
|
console.log(theme.warning('No entries yet. Be the first to complete the game!'));
|
||||||
} else {
|
} else {
|
||||||
console.log('Top Players:');
|
console.log('Top Players:');
|
||||||
console.log('-----------');
|
console.log('-----------');
|
||||||
leaderboard.players.slice(0, 10).forEach((entry, index) => {
|
leaderboard.players.slice(0, 10).forEach((entry, index) => {
|
||||||
console.log(`${index + 1}. ${entry.playerName} - ${formatTime(entry.completionTime)}`);
|
console.log(`${index + 1}. ${theme.accent(entry.playerName)} - ${formatTime(entry.completionTime)}`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,14 +1,18 @@
|
|||||||
import { getAllLevels, getLevelById } from '../core/levelSystem';
|
import { getAllLevels } from '../core/levelSystem';
|
||||||
import { getCurrentGameState } from '../core/gameState';
|
import { getCurrentProfile } from '../core/playerProfile';
|
||||||
import { getTheme } from './visualEffects';
|
import { getTheme } from './visualEffects';
|
||||||
|
|
||||||
export function renderProgressMap(): void {
|
export async function renderProgressMap(): Promise<void> {
|
||||||
const gameState = getCurrentGameState();
|
const profile = await getCurrentProfile();
|
||||||
if (!gameState) return;
|
if (!profile) {
|
||||||
|
console.log('No active player profile. Please start a game first.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const theme = getTheme();
|
const theme = getTheme();
|
||||||
const allLevels = getAllLevels();
|
const allLevels = getAllLevels();
|
||||||
const currentLevelId = gameState.currentLevel;
|
const completedLevels = profile.completedLevels;
|
||||||
|
const currentLevelId = Math.max(...completedLevels) + 1;
|
||||||
|
|
||||||
console.log(theme.accent('=== Progress Map ==='));
|
console.log(theme.accent('=== Progress Map ==='));
|
||||||
console.log('');
|
console.log('');
|
||||||
@ -53,11 +57,11 @@ export function renderProgressMap(): void {
|
|||||||
console.log('└' + '─'.repeat(maxNameLength + 22) + '┘');
|
console.log('└' + '─'.repeat(maxNameLength + 22) + '┘');
|
||||||
|
|
||||||
// Show completion percentage
|
// Show completion percentage
|
||||||
const completedLevels = Math.max(0, currentLevelId - 1);
|
const completedLevelsCount = completedLevels.length;
|
||||||
const completionPercentage = Math.round((completedLevels / allLevels.length) * 100);
|
const completionPercentage = Math.round((completedLevelsCount / allLevels.length) * 100);
|
||||||
|
|
||||||
console.log('');
|
console.log('');
|
||||||
console.log(`Overall Progress: ${completedLevels}/${allLevels.length} levels completed (${completionPercentage}%)`);
|
console.log(`Overall Progress: ${completedLevelsCount}/${allLevels.length} levels completed (${completionPercentage}%)`);
|
||||||
|
|
||||||
// Visual progress bar
|
// Visual progress bar
|
||||||
const progressBarWidth = 40;
|
const progressBarWidth = 40;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user