mirror of
https://github.com/tcsenpai/shellquest.git
synced 2025-06-02 09:20:17 +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 { getTheme, successAnimation } from '../ui/visualEffects';
|
||||
import { playSound } from '../ui/soundEffects';
|
||||
import { getCurrentProfile } from './playerProfile';
|
||||
import { saveProfile } from './playerProfile';
|
||||
|
||||
// Define achievement types
|
||||
export interface Achievement {
|
||||
@ -81,14 +83,14 @@ const achievementsPath = path.join(process.cwd(), 'achievements.json');
|
||||
|
||||
// Load achievements from file
|
||||
export async function loadAchievements(): Promise<Achievement[]> {
|
||||
try {
|
||||
const data = await fs.readFile(achievementsPath, 'utf-8');
|
||||
return JSON.parse(data);
|
||||
} catch (error) {
|
||||
// If file doesn't exist, create it with default achievements
|
||||
await saveAchievements(achievements);
|
||||
return achievements;
|
||||
const profile = await getCurrentProfile();
|
||||
|
||||
if (profile && profile.achievements.length > 0) {
|
||||
return profile.achievements;
|
||||
}
|
||||
|
||||
// Return default achievements if no profile exists or no achievements in profile
|
||||
return [...achievements];
|
||||
}
|
||||
|
||||
// Save achievements to file
|
||||
@ -109,36 +111,42 @@ export async function getPlayerAchievements(playerName: string): Promise<Achieve
|
||||
}
|
||||
|
||||
// Unlock an achievement
|
||||
export async function unlockAchievement(achievementId: string): Promise<boolean> {
|
||||
const gameState = getCurrentGameState();
|
||||
if (!gameState) return false;
|
||||
export async function unlockAchievement(id: string): Promise<boolean> {
|
||||
const profile = await getCurrentProfile();
|
||||
if (!profile) return false;
|
||||
|
||||
// Load achievements
|
||||
const allAchievements = await loadAchievements();
|
||||
const achievement = allAchievements.find(a => a.id === achievementId);
|
||||
// Find the achievement in the profile
|
||||
let achievementIndex = profile.achievements.findIndex(a => a.id === id);
|
||||
|
||||
if (!achievement || achievement.unlocked) {
|
||||
return false; // Achievement doesn't exist or is already unlocked
|
||||
// If not found in profile, check the default achievements
|
||||
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
|
||||
achievement.unlocked = true;
|
||||
achievement.unlockedAt = Date.now();
|
||||
|
||||
// Save achievements
|
||||
await saveAchievements(allAchievements);
|
||||
// Save the updated profile
|
||||
await saveProfile(profile);
|
||||
|
||||
// Show achievement notification
|
||||
const theme = getTheme();
|
||||
console.log('\n');
|
||||
console.log('╔' + '═'.repeat(50) + '╗');
|
||||
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');
|
||||
const achievement = profile.achievements.find(a => a.id === id);
|
||||
if (achievement) {
|
||||
await showAchievementNotification(achievement);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -33,15 +33,37 @@ export async function initializeGame() {
|
||||
}
|
||||
|
||||
// Helper functions for save management
|
||||
export async function listSaves() {
|
||||
export async function listSaves(): Promise<string[]> {
|
||||
try {
|
||||
await ensureSavesDir();
|
||||
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) {
|
||||
console.error('Failed to list saves:', error);
|
||||
console.error('Error listing saves:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export const getSavePath = (saveName: string) => path.join(SAVE_DIR, `${saveName}.json`);
|
||||
export const getLeaderboardPath = () => LEADERBOARD_PATH;
|
||||
export function getSavePath(saveName: string): string {
|
||||
// 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 { getSavePath } from './gameInit';
|
||||
import { createProfile, loadProfile, saveProfile } from './playerProfile';
|
||||
import { getCurrentProfile } from './playerProfile';
|
||||
|
||||
export interface GameState {
|
||||
playerName: string;
|
||||
@ -17,8 +19,12 @@ export function getCurrentGameState(): GameState | null {
|
||||
return currentGameState;
|
||||
}
|
||||
|
||||
export function setCurrentGameState(gameState: GameState): void {
|
||||
currentGameState = gameState;
|
||||
}
|
||||
|
||||
export function createNewGame(playerName: string): GameState {
|
||||
const newState: GameState = {
|
||||
const gameState: GameState = {
|
||||
playerName,
|
||||
currentLevel: 1,
|
||||
startTime: Date.now(),
|
||||
@ -28,8 +34,15 @@ export function createNewGame(playerName: string): GameState {
|
||||
levelStates: {}
|
||||
};
|
||||
|
||||
currentGameState = newState;
|
||||
return newState;
|
||||
// Create a new profile for this player
|
||||
createOrLoadProfile(playerName);
|
||||
|
||||
setCurrentGameState(gameState);
|
||||
|
||||
// Save the initial game state
|
||||
saveGame(playerName);
|
||||
|
||||
return gameState;
|
||||
}
|
||||
|
||||
export async function saveGame(saveName?: string): Promise<boolean> {
|
||||
@ -61,6 +74,10 @@ export async function loadGame(saveName: string): Promise<boolean> {
|
||||
try {
|
||||
const saveData = await fs.readFile(getSavePath(saveName), 'utf-8');
|
||||
currentGameState = JSON.parse(saveData) as GameState;
|
||||
|
||||
// Make sure the profile exists
|
||||
await createOrLoadProfile(currentGameState.playerName);
|
||||
|
||||
console.log(`Game loaded: ${saveName}`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
@ -72,4 +89,36 @@ export async function loadGame(saveName: string): Promise<boolean> {
|
||||
export async function autoSave(): Promise<boolean> {
|
||||
if (!currentGameState) return false;
|
||||
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
|
||||
|
||||
import { renderMainMenu } from './ui/mainMenu';
|
||||
import { renderEntryMenu } from './ui/entryMenu';
|
||||
import { initializeGame } from './core/gameInit';
|
||||
import { registerAllLevels } from './levels';
|
||||
import { initializeAchievements } from './core/achievements';
|
||||
@ -15,8 +15,8 @@ async function main() {
|
||||
// Register all game levels
|
||||
registerAllLevels();
|
||||
|
||||
// Render the main menu to start
|
||||
await renderMainMenu();
|
||||
// Render the entry menu to start
|
||||
await renderEntryMenu();
|
||||
}
|
||||
|
||||
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 { getLeaderboard, formatTime } from '../core/leaderboard';
|
||||
import { startLevel, getAllLevels } from '../core/levelSystem';
|
||||
@ -25,6 +25,7 @@ import {
|
||||
initSoundSystem
|
||||
} from './soundEffects';
|
||||
import { addToHistory } from './commandHistory';
|
||||
import { renderEntryMenu } from './entryMenu';
|
||||
|
||||
// Track if we've shown the boot sequence
|
||||
let bootSequenceShown = false;
|
||||
@ -33,14 +34,11 @@ let bootSequenceShown = false;
|
||||
initSoundSystem();
|
||||
|
||||
export async function renderMainMenu(): Promise<void> {
|
||||
// Show boot sequence only once
|
||||
if (!bootSequenceShown) {
|
||||
await bootSequence();
|
||||
bootSequenceShown = true;
|
||||
} else {
|
||||
clearScreen();
|
||||
console.log(generateLogo());
|
||||
console.log('');
|
||||
const gameState = getCurrentGameState();
|
||||
if (!gameState) {
|
||||
// If no game state, go back to entry menu
|
||||
await renderEntryMenu();
|
||||
return;
|
||||
}
|
||||
|
||||
const theme = getTheme();
|
||||
@ -52,48 +50,52 @@ export async function renderMainMenu(): Promise<void> {
|
||||
console.log('');
|
||||
|
||||
const menuOptions = [
|
||||
'1. ' + theme.accent('New Game'),
|
||||
'2. ' + theme.accent('Load Game'),
|
||||
'3. ' + theme.accent('Leaderboard'),
|
||||
'4. ' + theme.accent('Achievements'),
|
||||
'5. ' + theme.accent('Progress Map'),
|
||||
'6. ' + theme.accent('Settings'),
|
||||
'1. ' + theme.accent('Continue Game'),
|
||||
'2. ' + theme.accent('Achievements'),
|
||||
'3. ' + theme.accent('Progress Map'),
|
||||
'4. ' + theme.accent('Leaderboard'),
|
||||
'5. ' + theme.accent('Settings'),
|
||||
'6. ' + theme.accent('Back to Entry Menu'),
|
||||
'7. ' + theme.accent('Exit')
|
||||
];
|
||||
|
||||
console.log(drawBox('MAIN MENU', menuOptions.join('\n')));
|
||||
console.log('');
|
||||
console.log(theme.info(`Player: ${gameState.playerName}`));
|
||||
console.log('');
|
||||
|
||||
const choice = await promptInput('Select an option: ');
|
||||
|
||||
switch (choice) {
|
||||
case '1':
|
||||
await newGameMenu();
|
||||
break;
|
||||
case '2':
|
||||
await loadGameMenu();
|
||||
break;
|
||||
case '3':
|
||||
await showLeaderboard();
|
||||
break;
|
||||
case '4':
|
||||
await showAchievements();
|
||||
await promptInput('Press Enter to continue...');
|
||||
break;
|
||||
case '5':
|
||||
clearScreen();
|
||||
renderProgressMap();
|
||||
await promptInput('Press Enter to continue...');
|
||||
break;
|
||||
case '6':
|
||||
await showSettings();
|
||||
break;
|
||||
case '7':
|
||||
await animateText('Thanks for playing Terminal Escape!', 30);
|
||||
process.exit(0);
|
||||
default:
|
||||
console.log(theme.error('Invalid option. Press Enter to continue...'));
|
||||
await promptInput('');
|
||||
if (choice === '1') {
|
||||
// Continue game
|
||||
startLevel(gameState.currentLevel);
|
||||
await renderGameUI();
|
||||
} else if (choice === '2') {
|
||||
// Show achievements
|
||||
await showAchievements();
|
||||
await promptInput('Press Enter to continue...');
|
||||
} else if (choice === '3') {
|
||||
// Show progress map
|
||||
clearScreen();
|
||||
await renderProgressMap();
|
||||
await promptInput('Press Enter to continue...');
|
||||
} else if (choice === '4') {
|
||||
// Show leaderboard
|
||||
await showLeaderboard();
|
||||
} else if (choice === '5') {
|
||||
// Settings
|
||||
await showSettings();
|
||||
} else if (choice === '6') {
|
||||
// Back to entry menu
|
||||
await renderEntryMenu();
|
||||
return;
|
||||
} else if (choice === '7') {
|
||||
// Exit
|
||||
await animateText('Thanks for playing Terminal Escape!', 30);
|
||||
process.exit(0);
|
||||
} 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 newGameMenu(): Promise<void> {
|
||||
async function showLeaderboard(): Promise<void> {
|
||||
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;
|
||||
}
|
||||
|
||||
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(theme.accent('=== LEADERBOARD ==='));
|
||||
console.log('');
|
||||
|
||||
const leaderboard = await getLeaderboard();
|
||||
|
||||
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 {
|
||||
console.log('Top Players:');
|
||||
console.log('-----------');
|
||||
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 { getCurrentGameState } from '../core/gameState';
|
||||
import { getAllLevels } from '../core/levelSystem';
|
||||
import { getCurrentProfile } from '../core/playerProfile';
|
||||
import { getTheme } from './visualEffects';
|
||||
|
||||
export function renderProgressMap(): void {
|
||||
const gameState = getCurrentGameState();
|
||||
if (!gameState) return;
|
||||
export async function renderProgressMap(): Promise<void> {
|
||||
const profile = await getCurrentProfile();
|
||||
if (!profile) {
|
||||
console.log('No active player profile. Please start a game first.');
|
||||
return;
|
||||
}
|
||||
|
||||
const theme = getTheme();
|
||||
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('');
|
||||
@ -53,11 +57,11 @@ export function renderProgressMap(): void {
|
||||
console.log('└' + '─'.repeat(maxNameLength + 22) + '┘');
|
||||
|
||||
// Show completion percentage
|
||||
const completedLevels = Math.max(0, currentLevelId - 1);
|
||||
const completionPercentage = Math.round((completedLevels / allLevels.length) * 100);
|
||||
const completedLevelsCount = completedLevels.length;
|
||||
const completionPercentage = Math.round((completedLevelsCount / allLevels.length) * 100);
|
||||
|
||||
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
|
||||
const progressBarWidth = 40;
|
||||
|
Loading…
x
Reference in New Issue
Block a user