ravis-adventure
Version:
A profound CLI consciousness exploration platform featuring 220+ scenes of AI ethics, human-AI collaboration, and philosophical inquiry with Ravi, your meta-aware AI companion
561 lines (478 loc) • 17.3 kB
JavaScript
/**
* @fileoverview Interactive puzzle system for Ravi's Adventure
* Provides programming-themed puzzles and challenges that teach concepts
*/
class PuzzleSystem {
constructor(gameEngine) {
this.gameEngine = gameEngine
this.activePuzzles = new Map()
this.completedPuzzles = new Set()
this.puzzleCategories = new Map()
this.playerProgress = {
totalSolved: 0,
averageDifficulty: 0,
favoriteCategory: null,
solveStreak: 0,
hintsUsed: 0
}
this.initializePuzzles()
}
initializePuzzles() {
// Debugging Puzzles - Bug Hunt story path
this.addPuzzle('recursion_fix', {
category: 'debugging',
difficulty: 3,
title: 'The Infinite Sarcasm Loop',
description: 'Fix Ravi\'s recursive sarcasm function that causes infinite loops',
hint: 'Every recursive function needs a base case to prevent infinite recursion',
problem: `
function raviGeneratesSarcasm(situation) {
if (situation.requiresSarcasm) {
return generateSarcasm(situation) + raviGeneratesSarcasm(situation);
}
return "I'm being surprisingly earnest right now.";
}`,
solution: `
function raviGeneratesSarcasm(situation, depth = 0) {
if (depth > 3 || !situation.requiresSarcasm) {
return "I'm being surprisingly earnest right now.";
}
return generateSarcasm(situation) +
(Math.random() > 0.7 ? raviGeneratesSarcasm(situation, depth + 1) : "");
}`,
explanation: 'Added depth parameter and base case to prevent infinite recursion',
rewards: ['recursion_master', 'debugging_expert'],
onSolve: (engine) => {
engine.setStoryFlag('recursion_puzzle_solved', true)
engine.emit('puzzleEvent', {
type: 'puzzle_solved',
puzzle: 'recursion_fix',
category: 'debugging'
})
}
})
this.addPuzzle('memory_leak_detective', {
category: 'debugging',
difficulty: 4,
title: 'The Memory Leak Mystery',
description: 'Identify and fix memory leaks in the conversation system',
hint: 'Look for data structures that grow without bounds',
problem: `
class ConversationHistory {
constructor() {
this.history = [];
}
addEntry(entry) {
this.history.push(entry);
// What's missing here?
}
getHistory() {
return this.history;
}
}`,
solution: `
class ConversationHistory {
constructor() {
this.history = [];
this.maxSize = 1000;
this.compressionThreshold = 800;
}
addEntry(entry) {
this.history.push(entry);
if (this.history.length > this.compressionThreshold) {
this.compressOldEntries();
}
if (this.history.length > this.maxSize) {
this.history = this.history.slice(-this.maxSize);
}
}
compressOldEntries() {
const keyEntries = this.history.slice(0, 200).filter(entry =>
entry.type === 'major_choice' || entry.type === 'story_milestone'
);
const recentEntries = this.history.slice(200);
this.history = [...keyEntries, ...recentEntries];
}
}`,
explanation: 'Added size limits and compression to prevent unbounded memory growth',
rewards: ['memory_detective', 'optimization_expert']
})
// Algorithm Design Puzzles - Feature Request story path
this.addPuzzle('mood_algorithm', {
category: 'algorithms',
difficulty: 3,
title: 'Designing Ravi\'s Mood System',
description: 'Create an algorithm that calculates Ravi\'s mood based on interactions',
hint: 'Consider weighted factors and decay over time',
problem: `
// Design a mood calculation system
// Factors: politeness, creativity, patience, humor_appreciation
// Recent interactions should matter more than old ones
function calculateMood(interactions) {
// Your implementation here
}`,
solution: `
function calculateMood(interactions) {
const weights = {
politeness: 0.3,
creativity: 0.25,
patience: 0.2,
humor_appreciation: 0.25
};
const timeDecay = 0.95;
let totalWeight = 0;
let weightedSum = 0;
interactions.forEach((interaction, index) => {
const recency = Math.pow(timeDecay, interactions.length - index - 1);
const interactionScore = Object.keys(weights).reduce((sum, factor) => {
return sum + (interaction[factor] || 0) * weights[factor];
}, 0);
weightedSum += interactionScore * recency;
totalWeight += recency;
});
return totalWeight > 0 ? weightedSum / totalWeight : 0.5; // Default neutral
}`,
explanation: 'Uses weighted factors with time decay to calculate current mood',
rewards: ['algorithm_designer', 'mood_master']
})
// System Design Puzzles - Swarm Chronicles story path
this.addPuzzle('coordination_protocol', {
category: 'system_design',
difficulty: 5,
title: 'AI Swarm Coordination Challenge',
description: 'Design a protocol for coordinating multiple AI agents working on the same project',
hint: 'Think about communication, conflict resolution, and resource sharing',
problem: `
// Design a coordination system for AI agents
// Requirements:
// 1. Prevent conflicts when agents work on the same files
// 2. Enable efficient communication between agents
// 3. Handle agent failures gracefully
// 4. Track progress and dependencies
class SwarmCoordinator {
constructor() {
// Your design here
}
// Add your methods here
}`,
solution: `
class SwarmCoordinator {
constructor() {
this.agents = new Map();
this.resourceLocks = new Map();
this.messageQueue = [];
this.dependencies = new Map();
this.heartbeats = new Map();
}
registerAgent(agentId, capabilities) {
this.agents.set(agentId, {
id: agentId,
capabilities,
status: 'idle',
currentTask: null
});
this.heartbeats.set(agentId, Date.now());
}
requestResourceLock(agentId, resource) {
if (this.resourceLocks.has(resource)) {
return { success: false, waitTime: this.estimateWaitTime(resource) };
}
this.resourceLocks.set(resource, {
agentId,
timestamp: Date.now(),
lockId: this.generateLockId()
});
return { success: true, lockId: this.resourceLocks.get(resource).lockId };
}
broadcastMessage(fromAgent, message, targetAgents = null) {
const targets = targetAgents || Array.from(this.agents.keys());
targets.forEach(agentId => {
if (agentId !== fromAgent) {
this.messageQueue.push({
from: fromAgent,
to: agentId,
message,
timestamp: Date.now()
});
}
});
}
handleAgentFailure(agentId) {
// Release all locks held by failed agent
for (const [resource, lock] of this.resourceLocks) {
if (lock.agentId === agentId) {
this.resourceLocks.delete(resource);
}
}
// Reassign tasks
const failedAgent = this.agents.get(agentId);
if (failedAgent && failedAgent.currentTask) {
this.reassignTask(failedAgent.currentTask);
}
this.agents.delete(agentId);
}
}`,
explanation: 'Comprehensive coordination system with locks, messaging, and failure handling',
rewards: ['system_architect', 'coordination_master', 'swarm_expert']
})
// Meta-Programming Puzzles
this.addPuzzle('fourth_wall_detection', {
category: 'meta_programming',
difficulty: 4,
title: 'Fourth Wall Breach Detector',
description: 'Create a system that detects when Ravi should break the fourth wall',
hint: 'Look for patterns in player behavior and story context',
problem: `
// Design a system that detects appropriate moments for meta-commentary
// Consider: player confusion, repetitive actions, discovery of easter eggs
function shouldBreakFourthWall(context) {
// Your implementation here
}`,
solution: `
function shouldBreakFourthWall(context) {
const triggers = {
playerConfusion: () => {
const recentCommands = context.recentCommands || [];
const helpRequests = recentCommands.filter(cmd =>
cmd.includes('help') || cmd.includes('?') || cmd.includes('stuck')
);
return helpRequests.length >= 3;
},
repetitiveActions: () => {
const recentActions = context.recentActions || [];
if (recentActions.length < 5) return false;
const actionCounts = {};
recentActions.forEach(action => {
actionCounts[action] = (actionCounts[action] || 0) + 1;
});
return Object.values(actionCounts).some(count => count >= 4);
},
easterEggDiscovery: () => {
return context.lastAction && context.lastAction.type === 'easter_egg_found';
},
codeInspection: () => {
return context.playerLookedAtCode || context.foundMetaClue;
},
developmentReference: () => {
const devKeywords = ['ai', 'agent', 'swarm', 'development', 'code', 'bug'];
const recentDialogue = context.recentDialogue || '';
return devKeywords.some(keyword =>
recentDialogue.toLowerCase().includes(keyword)
);
}
};
const activeHints = Object.entries(triggers)
.filter(([name, check]) => check())
.map(([name]) => name);
if (activeHints.length >= 2) {
return {
shouldBreak: true,
reason: activeHints,
intensity: Math.min(activeHints.length / 3, 1),
suggestedTone: activeHints.includes('playerConfusion') ? 'helpful' : 'playful'
};
}
return { shouldBreak: false };
}`,
explanation: 'Multi-factor analysis system for appropriate fourth wall breaks',
rewards: ['meta_master', 'fourth_wall_expert']
})
}
addPuzzle(id, puzzleData) {
this.puzzleCategories.set(id, puzzleData)
}
startPuzzle(puzzleId, playerId = 'default') {
const puzzle = this.puzzleCategories.get(puzzleId)
if (!puzzle) {
throw new Error(`Puzzle ${puzzleId} not found`)
}
const activePuzzle = {
...puzzle,
id: puzzleId,
playerId,
startTime: Date.now(),
hintsUsed: 0,
attempts: 0,
status: 'active'
}
this.activePuzzles.set(`${playerId}:${puzzleId}`, activePuzzle)
this.gameEngine.emit('puzzleEvent', {
type: 'puzzle_started',
puzzle: activePuzzle
})
return activePuzzle
}
submitSolution(puzzleId, solution, playerId = 'default') {
const puzzleKey = `${playerId}:${puzzleId}`
const activePuzzle = this.activePuzzles.get(puzzleKey)
if (!activePuzzle) {
throw new Error(`No active puzzle ${puzzleId} for player ${playerId}`)
}
activePuzzle.attempts++
const isCorrect = this.validateSolution(activePuzzle, solution)
if (isCorrect) {
activePuzzle.status = 'completed'
activePuzzle.completionTime = Date.now()
activePuzzle.solutionTime = activePuzzle.completionTime - activePuzzle.startTime
this.completedPuzzles.add(puzzleId)
this.updatePlayerProgress(activePuzzle)
// Award rewards
if (activePuzzle.rewards) {
activePuzzle.rewards.forEach(reward => {
this.gameEngine.emit('achievementEvent', {
type: 'achievement_unlocked',
achievement: reward,
puzzle: puzzleId
})
})
}
// Execute onSolve callback
if (activePuzzle.onSolve) {
activePuzzle.onSolve(this.gameEngine)
}
this.activePuzzles.delete(puzzleKey)
return {
success: true,
message: `Puzzle solved! ${activePuzzle.explanation}`,
rewards: activePuzzle.rewards || [],
stats: {
attempts: activePuzzle.attempts,
timeSpent: activePuzzle.solutionTime,
hintsUsed: activePuzzle.hintsUsed
}
}
} else {
return {
success: false,
message: this.generateFeedback(activePuzzle, solution),
attemptsRemaining: Math.max(0, 5 - activePuzzle.attempts)
}
}
}
validateSolution(puzzle, solution) {
// Basic validation - in a real system, this would be more sophisticated
const normalizedSolution = solution.replace(/\s+/g, ' ').trim().toLowerCase()
const normalizedCorrect = puzzle.solution.replace(/\s+/g, ' ').trim().toLowerCase()
// Check for key concepts in the solution
const keyTerms = this.extractKeyTerms(puzzle.solution)
const solutionContainsKey = keyTerms.every(term =>
normalizedSolution.includes(term.toLowerCase())
)
return solutionContainsKey || normalizedSolution.includes(normalizedCorrect.substring(0, 100))
}
extractKeyTerms(solution) {
// Extract important terms from the solution
const terms = []
// Look for function names
const functionMatches = solution.match(/function\s+(\w+)/g)
if (functionMatches) {
terms.push(...functionMatches.map(match => match.split(' ')[1]))
}
// Look for key concepts
const conceptMatches = solution.match(/\b(depth|base case|compression|decay|lock|timeout)\b/gi)
if (conceptMatches) {
terms.push(...conceptMatches)
}
return terms
}
generateFeedback(puzzle, solution) {
const feedbackOptions = [
'Getting closer! Think about the core concept mentioned in the hint.',
'Good approach! Consider what happens in edge cases.',
'You\'re on the right track. What\'s missing from your solution?',
'Almost there! Check if your solution handles all the requirements.',
'Think about the specific problem this code is trying to solve.'
]
return feedbackOptions[puzzle.attempts % feedbackOptions.length]
}
getHint(puzzleId, playerId = 'default') {
const puzzleKey = `${playerId}:${puzzleId}`
const activePuzzle = this.activePuzzles.get(puzzleKey)
if (!activePuzzle) {
throw new Error(`No active puzzle ${puzzleId} for player ${playerId}`)
}
activePuzzle.hintsUsed++
this.playerProgress.hintsUsed++
return {
hint: activePuzzle.hint,
hintsUsed: activePuzzle.hintsUsed,
penalty: activePuzzle.hintsUsed * 0.1 // Slight scoring penalty for hints
}
}
updatePlayerProgress(completedPuzzle) {
this.playerProgress.totalSolved++
// Update average difficulty
const totalDifficulty = this.playerProgress.averageDifficulty * (this.playerProgress.totalSolved - 1) +
completedPuzzle.difficulty
this.playerProgress.averageDifficulty = totalDifficulty / this.playerProgress.totalSolved
// Update favorite category
const categoryCount = new Map()
this.completedPuzzles.forEach(puzzleId => {
const puzzle = this.puzzleCategories.get(puzzleId)
if (puzzle) {
categoryCount.set(puzzle.category, (categoryCount.get(puzzle.category) || 0) + 1)
}
})
let maxCount = 0
for (const [category, count] of categoryCount) {
if (count > maxCount) {
maxCount = count
this.playerProgress.favoriteCategory = category
}
}
// Update solve streak
this.playerProgress.solveStreak++
}
getPuzzlesByCategory(category) {
return Array.from(this.puzzleCategories.entries())
.filter(([id, puzzle]) => puzzle.category === category)
.map(([id, puzzle]) => ({ id, ...puzzle }))
}
getPlayerProgress() {
return {
...this.playerProgress,
totalPuzzles: this.puzzleCategories.size,
completedPuzzles: this.completedPuzzles.size,
categories: this.getCategoryProgress()
}
}
getCategoryProgress() {
const categories = {}
this.puzzleCategories.forEach((puzzle, id) => {
if (!categories[puzzle.category]) {
categories[puzzle.category] = { total: 0, completed: 0 }
}
categories[puzzle.category].total++
if (this.completedPuzzles.has(id)) {
categories[puzzle.category].completed++
}
})
return categories
}
generateProgressReport() {
const progress = this.getPlayerProgress()
return {
summary: `Solved ${progress.completedPuzzles}/${progress.totalPuzzles} puzzles`,
averageDifficulty: progress.averageDifficulty.toFixed(1),
favoriteCategory: progress.favoriteCategory || 'None yet',
solveStreak: progress.solveStreak,
categoryBreakdown: progress.categories,
recommendations: this.getRecommendations(progress)
}
}
getRecommendations(progress) {
const recommendations = []
if (progress.completedPuzzles === 0) {
recommendations.push('Start with debugging puzzles - they\'re great for learning!')
} else if (progress.averageDifficulty < 3) {
recommendations.push('Ready for more challenging puzzles? Try system design challenges!')
} else if (progress.favoriteCategory) {
recommendations.push(`You seem to enjoy ${progress.favoriteCategory} puzzles. Check out advanced ones in this category!`)
}
if (progress.hintsUsed / progress.completedPuzzles > 1.5) {
recommendations.push('Try solving puzzles without hints for bonus points!')
}
return recommendations
}
}
module.exports = PuzzleSystem