@@ -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, FHSDirectory } from '@/lib/api'; |
| 6 | +import { gameApi, FileSystemTree, FHSDirectory, CommandReferenceResponse } from '@/lib/api'; |
| 7 | 7 | |
| 8 | 8 | interface CommandHistoryEntry { |
| 9 | 9 | command: string; |
@@ -31,6 +31,8 @@ const Game: React.FC = () => { |
| 31 | 31 | const [hints, setHints] = useState<string[]>([]); |
| 32 | 32 | const [showFHS, setShowFHS] = useState(false); |
| 33 | 33 | const [fhsDirs, setFhsDirs] = useState<FHSDirectory[]>([]); |
| 34 | + const [showCommands, setShowCommands] = useState(false); |
| 35 | + const [commandRef, setCommandRef] = useState<CommandReferenceResponse | null>(null); |
| 34 | 36 | const [terminalMinimized, setTerminalMinimized] = useState(true); |
| 35 | 37 | const [hasPlayedIntro, setHasPlayedIntro] = useState(false); |
| 36 | 38 | const [isDarkMode, setIsDarkMode] = useState(true); |
@@ -136,6 +138,19 @@ const Game: React.FC = () => { |
| 136 | 138 | } |
| 137 | 139 | }; |
| 138 | 140 | |
| 141 | + // Get command reference |
| 142 | + const getCommandReference = async () => { |
| 143 | + try { |
| 144 | + if (!commandRef) { |
| 145 | + const response = await gameApi.getCommandReference(); |
| 146 | + setCommandRef(response); |
| 147 | + } |
| 148 | + setShowCommands(true); |
| 149 | + } catch (error) { |
| 150 | + console.error('Failed to get command reference', error); |
| 151 | + } |
| 152 | + }; |
| 153 | + |
| 139 | 154 | // Execute a command |
| 140 | 155 | const executeCommand = async (cmd: string) => { |
| 141 | 156 | if (!gameState.tree || !cmd.trim() || executing) return; |
@@ -311,7 +326,13 @@ const Game: React.FC = () => { |
| 311 | 326 | <div className={`flex items-center justify-between ${terminalColors.header} px-4 py-2 rounded-t-lg border-b`}> |
| 312 | 327 | <div className="flex items-center gap-2"> |
| 313 | 328 | <div className="flex gap-1.5"> |
| 314 | | - <div className="w-3.5 h-3.5 bg-red-500 rounded-full"></div> |
| 329 | + <button |
| 330 | + onClick={getCommandReference} |
| 331 | + className="w-3.5 h-3.5 bg-red-500 hover:bg-red-400 rounded-full flex items-center justify-center transition-colors relative" |
| 332 | + title="Command Reference" |
| 333 | + > |
| 334 | + <span className="text-[8px] font-bold text-gray-900 absolute">×</span> |
| 335 | + </button> |
| 315 | 336 | {gameState.tree && !gameState.tree.is_completed ? ( |
| 316 | 337 | <button |
| 317 | 338 | onClick={getHints} |
@@ -450,11 +471,131 @@ const Game: React.FC = () => { |
| 450 | 471 | </div> |
| 451 | 472 | )} |
| 452 | 473 | |
| 474 | + {/* Command Reference Modal */} |
| 475 | + {showCommands && commandRef && ( |
| 476 | + <div className="absolute top-16 left-4 bg-red-900/95 backdrop-blur-sm border border-red-600 rounded-lg p-6 max-w-2xl max-h-[80vh] overflow-y-auto z-40 shadow-2xl"> |
| 477 | + <button |
| 478 | + onClick={() => setShowCommands(false)} |
| 479 | + className="absolute top-3 right-3 text-red-400 hover:text-red-300" |
| 480 | + > |
| 481 | + ✕ |
| 482 | + </button> |
| 483 | + <h3 className="text-red-400 font-bold mb-4 text-lg">Command Reference</h3> |
| 484 | + |
| 485 | + <div className="space-y-6"> |
| 486 | + {/* Navigation Commands */} |
| 487 | + <div> |
| 488 | + <h4 className="text-red-300 font-semibold mb-3">Navigation Commands</h4> |
| 489 | + <div className="space-y-3"> |
| 490 | + {commandRef.navigation.map((cmd, index) => ( |
| 491 | + <div key={index} className="border-l-2 border-red-700 pl-3"> |
| 492 | + <div className="flex items-start gap-3"> |
| 493 | + <code className="text-red-200 font-terminal text-sm">{cmd.command}</code> |
| 494 | + <span className="text-red-100 text-sm">- {cmd.description}</span> |
| 495 | + </div> |
| 496 | + {cmd.examples && ( |
| 497 | + <div className="mt-1"> |
| 498 | + <span className="text-red-300 text-xs">Examples: </span> |
| 499 | + <code className="text-red-200 text-xs">{cmd.examples.join(', ')}</code> |
| 500 | + </div> |
| 501 | + )} |
| 502 | + </div> |
| 503 | + ))} |
| 504 | + </div> |
| 505 | + </div> |
| 506 | + |
| 507 | + {/* Exploration Commands */} |
| 508 | + <div> |
| 509 | + <h4 className="text-red-300 font-semibold mb-3">Exploration Commands</h4> |
| 510 | + <div className="space-y-3"> |
| 511 | + {commandRef.exploration.map((cmd, index) => ( |
| 512 | + <div key={index} className="border-l-2 border-red-700 pl-3"> |
| 513 | + <div className="flex items-start gap-3"> |
| 514 | + <code className="text-red-200 font-terminal text-sm">{cmd.command}</code> |
| 515 | + <span className="text-red-100 text-sm">- {cmd.description}</span> |
| 516 | + </div> |
| 517 | + {cmd.options && ( |
| 518 | + <div className="mt-1 ml-4"> |
| 519 | + {Object.entries(cmd.options).map(([opt, desc]) => ( |
| 520 | + <div key={opt} className="text-xs"> |
| 521 | + <code className="text-red-300">{opt}</code> |
| 522 | + <span className="text-red-200 ml-2">{desc}</span> |
| 523 | + </div> |
| 524 | + ))} |
| 525 | + </div> |
| 526 | + )} |
| 527 | + </div> |
| 528 | + ))} |
| 529 | + </div> |
| 530 | + </div> |
| 531 | + |
| 532 | + {/* Utility Commands */} |
| 533 | + <div> |
| 534 | + <h4 className="text-red-300 font-semibold mb-3">Utility Commands</h4> |
| 535 | + <div className="space-y-3"> |
| 536 | + {commandRef.utility.map((cmd, index) => ( |
| 537 | + <div key={index} className="border-l-2 border-red-700 pl-3"> |
| 538 | + <div className="flex items-start gap-3"> |
| 539 | + <code className="text-red-200 font-terminal text-sm">{cmd.command}</code> |
| 540 | + <span className="text-red-100 text-sm">- {cmd.description}</span> |
| 541 | + </div> |
| 542 | + {cmd.variables && ( |
| 543 | + <div className="mt-1 ml-4"> |
| 544 | + {Object.entries(cmd.variables).map(([variable, desc]) => ( |
| 545 | + <div key={variable} className="text-xs"> |
| 546 | + <code className="text-red-300">{variable}</code> |
| 547 | + <span className="text-red-200 ml-2">{desc}</span> |
| 548 | + </div> |
| 549 | + ))} |
| 550 | + </div> |
| 551 | + )} |
| 552 | + </div> |
| 553 | + ))} |
| 554 | + </div> |
| 555 | + </div> |
| 556 | + |
| 557 | + {/* Game Commands */} |
| 558 | + <div> |
| 559 | + <h4 className="text-red-300 font-semibold mb-3">Game Commands</h4> |
| 560 | + <div className="space-y-3"> |
| 561 | + {commandRef.game.map((cmd, index) => ( |
| 562 | + <div key={index} className="border-l-2 border-red-700 pl-3"> |
| 563 | + <div className="flex items-start gap-3"> |
| 564 | + <code className="text-red-200 font-terminal text-sm">{cmd.command}</code> |
| 565 | + <span className="text-red-100 text-sm">- {cmd.description}</span> |
| 566 | + </div> |
| 567 | + </div> |
| 568 | + ))} |
| 569 | + </div> |
| 570 | + </div> |
| 571 | + |
| 572 | + {/* Special Paths */} |
| 573 | + <div> |
| 574 | + <h4 className="text-red-300 font-semibold mb-3">Special Paths</h4> |
| 575 | + <div className="space-y-3"> |
| 576 | + {commandRef.special_paths.map((path, index) => ( |
| 577 | + <div key={index} className="border-l-2 border-red-700 pl-3"> |
| 578 | + <div className="flex items-start gap-3"> |
| 579 | + <code className="text-red-200 font-terminal text-sm">{path.path}</code> |
| 580 | + <span className="text-red-100 text-sm">- {path.description}</span> |
| 581 | + </div> |
| 582 | + <div className="mt-1"> |
| 583 | + <span className="text-red-300 text-xs">Examples: </span> |
| 584 | + <code className="text-red-200 text-xs">{path.examples.join(', ')}</code> |
| 585 | + </div> |
| 586 | + </div> |
| 587 | + ))} |
| 588 | + </div> |
| 589 | + </div> |
| 590 | + </div> |
| 591 | + </div> |
| 592 | + )} |
| 593 | + |
| 453 | 594 | {/* 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`}> |
| 455 | | - <div className="max-w-7xl mx-auto flex justify-between items-center"> |
| 595 | + <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-3 z-20`}> |
| 596 | + <div className="max-w-7xl mx-auto flex justify-between items-center px-4"> |
| 456 | 597 | <div className="flex items-center gap-4"> |
| 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> |
| 598 | + <h1 className={`text-2xl font-bold ${isDarkMode ? 'text-white' : 'text-gray-900'}`}><span className='font-terminal bg-gray-200 dark:bg-gray-500 text-red-900 dark:text-red-400 px-0.5 py-0 rounded'>bash</span> amole</h1> |
| 458 | 599 | </div> |
| 459 | 600 | |
| 460 | 601 | <div className="flex items-center gap-3"> |
@@ -464,7 +605,7 @@ const Game: React.FC = () => { |
| 464 | 605 | </div> |
| 465 | 606 | ) : ( |
| 466 | 607 | <div className={`text-xs ${isDarkMode ? 'text-slate-400' : 'text-blue-700'}`}> |
| 467 | | - Click nodes or use terminal |
| 608 | + click adjacent nodes or use the terminal |
| 468 | 609 | </div> |
| 469 | 610 | )} |
| 470 | 611 | <button |