@@ -3,7 +3,7 @@ |
| 3 | 3 | // src/components/Game.tsx |
| 4 | 4 | import React, { useState, useEffect, useRef } from 'react'; |
| 5 | 5 | import TreeVisualizer from './TreeVisualizer'; |
| 6 | | -import { gameApi, FileSystemTree } from '@/lib/api'; |
| 6 | +import { gameApi, FileSystemTree, FHSDirectory } from '@/lib/api'; |
| 7 | 7 | |
| 8 | 8 | interface CommandHistoryEntry { |
| 9 | 9 | command: string; |
@@ -29,6 +29,8 @@ const Game: React.FC = () => { |
| 29 | 29 | const [executing, setExecuting] = useState(false); |
| 30 | 30 | const [showHints, setShowHints] = useState(false); |
| 31 | 31 | const [hints, setHints] = useState<string[]>([]); |
| 32 | + const [showFHS, setShowFHS] = useState(false); |
| 33 | + const [fhsDirs, setFhsDirs] = useState<FHSDirectory[]>([]); |
| 32 | 34 | const [terminalMinimized, setTerminalMinimized] = useState(true); |
| 33 | 35 | const [hasPlayedIntro, setHasPlayedIntro] = useState(false); |
| 34 | 36 | const [isDarkMode, setIsDarkMode] = useState(true); |
@@ -123,6 +125,17 @@ const Game: React.FC = () => { |
| 123 | 125 | } |
| 124 | 126 | }; |
| 125 | 127 | |
| 128 | + // Get FHS reference |
| 129 | + const getFHSReference = async () => { |
| 130 | + try { |
| 131 | + const response = await gameApi.getFHSReference(); |
| 132 | + setFhsDirs(response.directories); |
| 133 | + setShowFHS(true); |
| 134 | + } catch (error) { |
| 135 | + console.error('Failed to get FHS reference', error); |
| 136 | + } |
| 137 | + }; |
| 138 | + |
| 126 | 139 | // Execute a command |
| 127 | 140 | const executeCommand = async (cmd: string) => { |
| 128 | 141 | if (!gameState.tree || !cmd.trim() || executing) return; |
@@ -298,9 +311,25 @@ const Game: React.FC = () => { |
| 298 | 311 | <div className={`flex items-center justify-between ${terminalColors.header} px-4 py-2 rounded-t-lg border-b`}> |
| 299 | 312 | <div className="flex items-center gap-2"> |
| 300 | 313 | <div className="flex gap-1.5"> |
| 301 | | - <div className="w-3 h-3 bg-red-500 rounded-full"></div> |
| 302 | | - <div className="w-3 h-3 bg-yellow-500 rounded-full"></div> |
| 303 | | - <div className="w-3 h-3 bg-green-500 rounded-full"></div> |
| 314 | + <div className="w-3.5 h-3.5 bg-red-500 rounded-full"></div> |
| 315 | + {gameState.tree && !gameState.tree.is_completed ? ( |
| 316 | + <button |
| 317 | + onClick={getHints} |
| 318 | + className="w-3.5 h-3.5 bg-yellow-500 hover:bg-yellow-400 rounded-full flex items-center justify-center transition-colors relative" |
| 319 | + title="Get Hint" |
| 320 | + > |
| 321 | + <span className="text-[9px] font-bold text-gray-900 absolute">?</span> |
| 322 | + </button> |
| 323 | + ) : ( |
| 324 | + <div className="w-3.5 h-3.5 bg-yellow-500 rounded-full"></div> |
| 325 | + )} |
| 326 | + <button |
| 327 | + onClick={getFHSReference} |
| 328 | + className="w-3.5 h-3.5 bg-green-500 hover:bg-green-400 rounded-full flex items-center justify-center transition-colors relative" |
| 329 | + title="FHS Directory Reference" |
| 330 | + > |
| 331 | + <span className="text-[9px] font-bold text-gray-900 absolute">/</span> |
| 332 | + </button> |
| 304 | 333 | </div> |
| 305 | 334 | <h3 className={`text-sm font-medium ${terminalColors.headerText} ml-2`}>bash</h3> |
| 306 | 335 | </div> |
@@ -384,9 +413,9 @@ const Game: React.FC = () => { |
| 384 | 413 | )} |
| 385 | 414 | </div> |
| 386 | 415 | |
| 387 | | - {/* Hints Popup */} |
| 416 | + {/* Hints Popup - Now positioned relative to terminal */} |
| 388 | 417 | {showHints && hints.length > 0 && ( |
| 389 | | - <div className="absolute top-20 left-1/2 transform -translate-x-1/2 bg-yellow-900/95 backdrop-blur-sm border border-yellow-600 rounded-lg p-4 max-w-md z-30 shadow-2xl"> |
| 418 | + <div className="absolute top-16 left-4 bg-yellow-900/95 backdrop-blur-sm border border-yellow-600 rounded-lg p-4 max-w-md z-40 shadow-2xl"> |
| 390 | 419 | <button |
| 391 | 420 | onClick={() => setShowHints(false)} |
| 392 | 421 | className="absolute top-2 right-2 text-yellow-400 hover:text-yellow-300" |
@@ -400,14 +429,32 @@ const Game: React.FC = () => { |
| 400 | 429 | </div> |
| 401 | 430 | )} |
| 402 | 431 | |
| 403 | | - {/* Bottom Game Bar */} |
| 404 | | - <div className={`absolute bottom-0 left-0 right-0 ${isDarkMode ? 'bg-gray-900/90' : 'bg-white/90'} backdrop-blur-sm border-t ${isDarkMode ? 'border-gray-700' : 'border-gray-300'} p-4 z-20`}> |
| 432 | + {/* FHS Reference Modal */} |
| 433 | + {showFHS && ( |
| 434 | + <div className="absolute top-16 left-4 bg-green-900/95 backdrop-blur-sm border border-green-600 rounded-lg p-6 max-w-2xl z-40 shadow-2xl"> |
| 435 | + <button |
| 436 | + onClick={() => setShowFHS(false)} |
| 437 | + className="absolute top-3 right-3 text-green-400 hover:text-green-300" |
| 438 | + > |
| 439 | + ✕ |
| 440 | + </button> |
| 441 | + <h3 className="text-green-400 font-bold mb-4 text-lg">Filesystem Hierarchy Standard (FHS)</h3> |
| 442 | + <div className="grid grid-cols-1 gap-2 max-h-96 overflow-y-auto"> |
| 443 | + {fhsDirs.map((dir, index) => ( |
| 444 | + <div key={index} className="flex items-start gap-3"> |
| 445 | + <code className="text-green-300 font-terminal text-sm font-bold min-w-[80px]">{dir.path}</code> |
| 446 | + <span className="text-green-200 text-sm">{dir.desc}</span> |
| 447 | + </div> |
| 448 | + ))} |
| 449 | + </div> |
| 450 | + </div> |
| 451 | + )} |
| 452 | + |
| 453 | + {/* Bottom Game Bar - Updated with blue shade */} |
| 454 | + <div className={`absolute bottom-0 left-0 right-0 ${isDarkMode ? 'bg-slate-800/90' : 'bg-blue-50/90'} backdrop-blur-sm border-t ${isDarkMode ? 'border-slate-700' : 'border-blue-200'} p-4 z-20`}> |
| 405 | 455 | <div className="max-w-7xl mx-auto flex justify-between items-center"> |
| 406 | 456 | <div className="flex items-center gap-4"> |
| 407 | 457 | <h1 className={`text-2xl font-bold ${isDarkMode ? 'text-white' : 'text-gray-900'}`}><span className='font-terminal bg-gray-200 dark:bg-gray-700 text-red-900 dark:text-red-400 px-1 py-0 rounded'>bash</span>amole</h1> |
| 408 | | - {/* <div className={`text-sm ${isDarkMode ? 'text-gray-400' : 'text-gray-600'}`}> |
| 409 | | - Location: <span className="font-terminal text-blue-600 dark:text-blue-400">{gameState.tree.player_location}</span> |
| 410 | | - </div> */} |
| 411 | 458 | </div> |
| 412 | 459 | |
| 413 | 460 | <div className="flex items-center gap-3"> |
@@ -416,21 +463,13 @@ const Game: React.FC = () => { |
| 416 | 463 | You found a mole! |
| 417 | 464 | </div> |
| 418 | 465 | ) : ( |
| 419 | | - <> |
| 420 | | - <button |
| 421 | | - onClick={getHints} |
| 422 | | - className="px-3 py-1.5 bg-yellow-600 text-white text-sm rounded hover:bg-yellow-700 transition" |
| 423 | | - > |
| 424 | | - Get Hint |
| 425 | | - </button> |
| 426 | | - <div className={`text-xs ${isDarkMode ? 'text-gray-500' : 'text-gray-600'}`}> |
| 427 | | - Click nodes or use terminal |
| 428 | | - </div> |
| 429 | | - </> |
| 466 | + <div className={`text-xs ${isDarkMode ? 'text-slate-400' : 'text-blue-700'}`}> |
| 467 | + Click nodes or use terminal |
| 468 | + </div> |
| 430 | 469 | )} |
| 431 | 470 | <button |
| 432 | 471 | onClick={startNewGame} |
| 433 | | - className={`px-3 py-1.5 ${isDarkMode ? 'bg-gray-700 hover:bg-gray-600' : 'bg-gray-300 hover:bg-gray-400'} text-white dark:text-white text-gray-900 text-sm rounded transition`} |
| 472 | + className={`px-3 py-1.5 ${isDarkMode ? 'bg-slate-700 hover:bg-slate-600' : 'bg-blue-200 hover:bg-blue-300'} ${isDarkMode ? 'text-white' : 'text-blue-900'} text-sm rounded transition`} |
| 434 | 473 | > |
| 435 | 474 | New Game |
| 436 | 475 | </button> |