"use client"; import React, { useEffect, useCallback, useState } from 'react'; import TreeVisualizer from './TreeVisualizer'; import Terminal from './Terminal'; import HelpModals from './HelpModals'; import GameStatus from './GameStatus'; import GameOverModal from './GameOverModal'; import { gameApi, CommandResponse, GameStats } from '@/lib/api'; // Import our custom hooks import { useGameState } from '@/hooks/useGameState'; import { useCommandExecution } from '@/hooks/useCommandExecution'; import { useTerminal } from '@/hooks/useTerminal'; import { useHelpModals } from '@/hooks/useHelpModals'; import { useTreeUtils } from '@/hooks/useTreeUtils'; const Game: React.FC = () => { // Canvas background color - always dark mode const canvasBackground = 'bg-gray-900'; const isDarkMode = true; // Use our custom hooks const { gameState, gameStats, hasPlayedIntro, startNewGame, updatePlayerLocation, updateTreeData, setMoleDirection, setMoleKilled, updateScore, setHasPlayedIntro, } = useGameState(); const { command, setCommand, clearCommand, terminalMinimized, setTerminalMinimized, } = useTerminal(); const helpModals = useHelpModals(gameState.tree?.id || null); const { updateTreeDataToShowMole, removeMoleFromTree } = useTreeUtils(); // Game over modal state const [showGameOver, setShowGameOver] = useState(false); const [finalStats, setFinalStats] = useState(null); // Handle mole kill animation and updates const handleMoleKilled = useCallback((response: CommandResponse) => { if (!gameState.tree) return; // First, show the killed mole briefly updateTreeData((tree) => updateTreeDataToShowMole(tree, gameState.tree!.player_location) ); // Trigger falling animation setMoleKilled(true); // After animation, update tree with new mole location setTimeout(() => { setMoleKilled(false); // Update tree to show new mole location if (response.new_mole_location) { updateTreeData((tree) => { const cleanTree = removeMoleFromTree(tree); return updateTreeDataToShowMole(cleanTree, response.new_mole_location!); }); } // Set new mole direction if (response.mole_direction) { setMoleDirection(response.mole_direction); } }, 1500); // Wait for falling animation // Update score and moles killed if (response.score !== undefined && response.moles_killed !== undefined) { updateScore(response.score, response.moles_killed); } }, [gameState.tree, updateTreeData, setMoleKilled, setMoleDirection, updateScore, updateTreeDataToShowMole, removeMoleFromTree]); const { executing, commandHistory, executeCommand: executeCommandBase, addToHistory, clearHistory, } = useCommandExecution( gameState.tree?.id || null, gameState.sessionId, updatePlayerLocation, handleMoleKilled, updateTreeData, (stats) => { // Add this callback for game completion setFinalStats(stats); setShowGameOver(true); } ); // Wrap executeCommand to clear the command input const executeCommand = useCallback(async (cmd: string) => { await executeCommandBase(cmd); clearCommand(); }, [executeCommandBase, clearCommand]); // Initialize game with welcome message const initializeGame = useCallback(async () => { const response = await startNewGame(); if (!response) return; // Clear history for new game clearHistory(); // Create a dynamic starting message const startLocation = response.tree.player_location; const homeDir = response.home_directory || '/home'; let locationContext = ''; if (startLocation.startsWith('/home')) { locationContext = "You've been dropped in someone's home directory. "; } else if (startLocation.startsWith('/usr')) { locationContext = "You're in the system's usr hierarchy. "; } else if (startLocation.startsWith('/var')) { locationContext = "You're somewhere in the variable data area. "; } else if (startLocation.startsWith('/opt')) { locationContext = "You're in the optional packages directory. "; } else { locationContext = "You've been placed somewhere in the filesystem. "; } // Add timer info to starting message let timerInfo = ''; if (response.initial_timer && response.timer_reason) { timerInfo = `\nTimer: ${response.initial_timer}s (mole is ${response.timer_reason})`; } addToHistory({ command: 'Hunt started!', output: `${response.mole_hint}\n${locationContext}Your home directory is ${homeDir}.\nUse 'pwd' to see where you are, 'cd ~' to go home.${timerInfo}\nType "help" for available commands.`, success: true, }); }, [startNewGame, addToHistory, clearHistory]); // Handle timer expiration const handleTimerExpire = useCallback(async () => { if (!gameState.tree) return; try { const response = await gameApi.checkTimer(gameState.tree.id, gameState.sessionId || undefined); if (response.mole_escaped) { // Build the escape message let escapeMessage = response.message || 'The mole escaped!'; // Add distance info for new mole if available if (response.escape_data?.timer_reason) { escapeMessage += `\nNew mole detected ${response.escape_data.timer_reason}!`; } // Update command history with escape message addToHistory({ command: 'Mole escaped!', output: escapeMessage, success: false, }); // Update mole direction if provided if (response.escape_data?.new_location) { // Update tree to show new mole location updateTreeData((tree) => { const cleanTree = removeMoleFromTree(tree); return updateTreeDataToShowMole(cleanTree, response.escape_data!.new_location); }); // Show mole direction indicator if provided if (response.escape_data?.mole_direction) { setMoleDirection(response.escape_data.mole_direction); } } } } catch (error) { console.error('Failed to check timer:', error); } }, [gameState.tree, gameState.sessionId, addToHistory, updateTreeData, setMoleDirection, removeMoleFromTree, updateTreeDataToShowMole]); // Handle node click in visualizer const handleNodeClick = useCallback((path: string) => { executeCommand(`cd ${path}`); }, [executeCommand]); // Handler for starting a new game from the modal const handleNewGameFromModal = () => { setShowGameOver(false); setFinalStats(null); initializeGame(); }; // Start game on mount useEffect(() => { initializeGame(); }, []); // eslint-disable-line react-hooks/exhaustive-deps // Mark intro as played after delay useEffect(() => { if (gameState.tree && !hasPlayedIntro) { const timer = setTimeout(() => { setHasPlayedIntro(true); }, 10000); // Add a buffer to ensure animation completes return () => clearTimeout(timer); } }, [gameState.tree, hasPlayedIntro, setHasPlayedIntro]); // Loading state if (gameState.loading) { return (
Loading Bashamole...
); } // Error state if (gameState.error) { return (
{gameState.error}
); } // Initial state if (!gameState.tree) { return (

Bashamole

Hunt the mole in the Unix filesystem!

); } // Main game UI return (
{/* Tree Canvas - Full Screen Background */}
{/* Game Status (Timer, Score, Direction Indicator) */} {/* Terminal */} {/* Help Modals */} {/* Game Over Modal */} setShowGameOver(false)} onNewGame={handleNewGameFromModal} /> {/* Bottom Game Bar */}

bash amole

click adjacent nodes or use the terminal
); }; export default Game;