C#文本冒险程序:如何实现输入menu随时唤起主菜单?
Great question! Having to manually call Menu() after every line of your adventure is definitely clunky. Let's look at two solid approaches to add global "menu" listening without repeating code.
Approach 1: Encapsulate Input Handling (Simplest & Most Reliable)
The easiest way is to wrap all user input in a single function that automatically checks for "menu" before returning the input. This way, every time you need user input, you use this function instead of Console.ReadLine(), and it handles menu checks behind the scenes.
Here's the implementation:
class Program { static void Main(string[] args) { StartAdventure(); } // Reusable menu display function static void ShowMainMenu() { Console.WriteLine("\n=== Main Menu ==="); Console.WriteLine("1. Entry 1"); Console.WriteLine("2. Entry 2"); Console.WriteLine("Any other key to exit menu"); Console.Write("Select an option: "); if (int.TryParse(Console.ReadLine(), out int menuChoice)) { switch (menuChoice) { case 1: Console.WriteLine("You picked Entry 1!"); break; case 2: Console.WriteLine("You picked Entry 2!"); break; default: Console.WriteLine("Exiting menu..."); break; } } else { Console.WriteLine("Invalid input—exiting menu."); } } // Global input handler that checks for "menu" automatically static string GetAdventureInput(string prompt = "") { while (true) { if (!string.IsNullOrEmpty(prompt)) { Console.Write(prompt); } string userInput = Console.ReadLine().Trim().ToLower(); // Check if user wants the menu if (userInput == "menu") { ShowMainMenu(); Console.WriteLine("\nBack to your adventure—type 'menu' anytime to return to the menu!"); continue; // Loop back to wait for normal input } // Return valid non-menu input return userInput; } } static void StartAdventure() { Console.WriteLine("Welcome to the Text Adventure!"); GetAdventureInput("Press enter to begin... "); Console.WriteLine("\nType 'menu' at any time to open the main menu!"); Console.WriteLine("\nYou enter a dark cave."); GetAdventureInput("What do you do? "); Console.WriteLine("\nYou find a glowing crystal."); GetAdventureInput("Continue exploring? "); Console.WriteLine("\nYou emerge from the cave into sunlight."); GetAdventureInput("Finish your adventure? "); Console.WriteLine("\nAdventure complete! Thanks for playing!"); } }
How this works:
- Every time you need user input (like to advance the story), call
GetAdventureInput()instead ofConsole.ReadLine(). - The function checks if the input is "menu"—if yes, it opens the menu, then loops back to wait for the user to enter normal adventure input.
- No more manual
Menu()calls—all menu logic is centralized in one place.
Approach 2: Background Thread Listening (For Non-Input-Heavy Adventures)
If your adventure has long stretches where the program is outputting content without waiting for input, you can use a background thread to listen for "menu" input in the background. Note that this requires thread synchronization to avoid conflicts with the main thread's input calls.
class Program { private static bool _isMenuActive = false; private static readonly object _consoleLock = new object(); // Prevents input conflicts static void Main(string[] args) { // Start background menu listener Thread menuListener = new Thread(ListenForMenu); menuListener.IsBackground = true; // Ends when main thread ends menuListener.Start(); StartAdventure(); } static void ListenForMenu() { while (true) { lock (_consoleLock) { // Check if there's input available to avoid blocking the thread if (Console.KeyAvailable) { string input = Console.ReadLine().Trim().ToLower(); if (input == "menu" && !_isMenuActive) { _isMenuActive = true; ShowMainMenu(); _isMenuActive = false; } } } Thread.Sleep(100); // Reduce CPU usage } } static void ShowMainMenu() { Console.WriteLine("\n=== Main Menu ==="); Console.WriteLine("1. Entry 1"); Console.WriteLine("2. Entry 2"); Console.WriteLine("Any other key to exit menu"); Console.Write("Select an option: "); lock (_consoleLock) { if (int.TryParse(Console.ReadLine(), out int menuChoice)) { switch (menuChoice) { case 1: Console.WriteLine("You picked Entry 1!"); break; case 2: Console.WriteLine("You picked Entry 2!"); break; default: Console.WriteLine("Exiting menu..."); break; } } else { Console.WriteLine("Invalid input—exiting menu."); } } } static void StartAdventure() { Console.WriteLine("Welcome to the Text Adventure!"); Console.WriteLine("Type 'menu' at any time to open the main menu!"); Console.WriteLine("\nYou walk through a forest..."); Thread.Sleep(2000); // Simulate story progression without input Console.WriteLine("\nYou hear a rustle in the bushes."); lock (_consoleLock) { Console.Write("Investigate? "); Console.ReadLine(); } Console.WriteLine("\nYou find a hidden path."); Thread.Sleep(2000); Console.WriteLine("\nYou reach a castle gate."); lock (_consoleLock) { Console.Write("Knock on the gate? "); Console.ReadLine(); } Console.WriteLine("\nAdventure complete!"); } }
How this works:
- A background thread runs continuously, checking for available input every 100ms.
- If it detects "menu" and the menu isn't already open, it triggers the menu.
- The
_consoleLockensures the main thread and background thread don't try to read input at the same time, which would cause unexpected behavior.
Recommendation
Stick with Approach 1 for most text adventures—it's simpler, easier to maintain, and avoids the complexity of thread synchronization. The encapsulated input function keeps all menu logic in one place and makes your adventure code clean and readable.
内容的提问来源于stack exchange,提问作者C0deN00b

