3.5 - Control Flow Exercise: Number Guessing Game
It's time to put your control flow knowledge into practice! In this mini-project, we'll build two console applications that demonstrate the concepts we've learned in Module 3:
- Number Guessing Game: A game where the player tries to guess a random number
- Text Adventure: A simple interactive story where the player makes choices that affect the outcome
These projects will reinforce your understanding of conditional statements, switch statements, loops, and loop control statements.
Project 1: Number Guessing Game
Let's build a number guessing game where the computer generates a random number, and the player tries to guess it with feedback after each attempt.
Requirements
- Generate a random number within a specified range
- Allow the player to make multiple guesses
- Provide feedback after each guess (too high, too low, or correct)
- Track the number of attempts
- Set a maximum number of attempts
- Allow the player to play again
- Implement difficulty levels that affect the number range and maximum attempts
Implementation
using System;
namespace NumberGuessingGame
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("=== Number Guessing Game ===");
Console.WriteLine("Try to guess the secret number!");
bool playAgain = true;
Random random = new Random();
while (playAgain)
{
// Select difficulty
int minNumber = 1;
int maxNumber = 100;
int maxAttempts = 10;
Console.WriteLine("\nSelect difficulty:");
Console.WriteLine("1. Easy (1-50, 12 attempts)");
Console.WriteLine("2. Medium (1-100, 10 attempts)");
Console.WriteLine("3. Hard (1-200, 8 attempts)");
Console.WriteLine("4. Expert (1-500, 6 attempts)");
Console.Write("\nEnter your choice (1-4): ");
string? difficultyInput = Console.ReadLine();
// Set difficulty parameters
switch (difficultyInput)
{
case "1": // Easy
maxNumber = 50;
maxAttempts = 12;
break;
case "2": // Medium
maxNumber = 100;
maxAttempts = 10;
break;
case "3": // Hard
maxNumber = 200;
maxAttempts = 8;
break;
case "4": // Expert
maxNumber = 500;
maxAttempts = 6;
break;
default:
Console.WriteLine("Invalid choice. Using Medium difficulty.");
break;
}
// Generate the secret number
int secretNumber = random.Next(minNumber, maxNumber + 1);
int attempts = 0;
bool hasGuessedCorrectly = false;
Console.WriteLine($"\nI'm thinking of a number between {minNumber} and {maxNumber}.");
Console.WriteLine($"You have {maxAttempts} attempts to guess it.");
// Main game loop
while (attempts < maxAttempts && !hasGuessedCorrectly)
{
attempts++;
Console.Write($"\nAttempt {attempts}/{maxAttempts}: Enter your guess: ");
string? guessInput = Console.ReadLine();
// Validate input
if (string.IsNullOrEmpty(guessInput) || !int.TryParse(guessInput, out int guess))
{
Console.WriteLine("Invalid input. Please enter a number.");
attempts--; // Don't count invalid inputs
continue;
}
// Check if the guess is correct
if (guess == secretNumber)
{
Console.WriteLine($"\nCongratulations! You guessed the number in {attempts} attempts!");
hasGuessedCorrectly = true;
// Calculate score based on difficulty and attempts
int difficultyMultiplier = int.Parse(difficultyInput ?? "2");
int score = (maxAttempts - attempts + 1) * 100 * difficultyMultiplier;
Console.WriteLine($"Your score: {score} points");
}
else
{
// Provide feedback
if (guess < secretNumber)
{
Console.WriteLine("Too low! Try a higher number.");
}
else
{
Console.WriteLine("Too high! Try a lower number.");
}
// Give a hint after half the attempts are used
if (attempts == maxAttempts / 2)
{
int rangeHint = maxNumber / 10;
int lowerHint = Math.Max(minNumber, secretNumber - rangeHint);
int upperHint = Math.Min(maxNumber, secretNumber + rangeHint);
Console.WriteLine($"Hint: The number is between {lowerHint} and {upperHint}.");
}
}
}
// Game over message if player didn't guess correctly
if (!hasGuessedCorrectly)
{
Console.WriteLine($"\nGame over! You've used all {maxAttempts} attempts.");
Console.WriteLine($"The secret number was: {secretNumber}");
}
// Ask to play again
Console.Write("\nDo you want to play again? (y/n): ");
string? playAgainInput = Console.ReadLine()?.ToLower();
playAgain = playAgainInput == "y" || playAgainInput == "yes";
}
Console.WriteLine("\nThanks for playing! Press any key to exit...");
Console.ReadKey();
}
}
}
Key Concepts Demonstrated
- Conditional Statements: Using
if
,else if
, andelse
to check guesses and provide feedback - Switch Statement: Selecting difficulty levels
- Loops: Using
while
loops for the main game loop and play again functionality - Loop Control: Using
continue
to skip invalid inputs - Random Numbers: Generating a random secret number
- User Input Validation: Checking for valid numeric input
- Game Logic: Implementing scoring, hints, and difficulty levels
Enhancements You Can Try
- Add a high score system that persists between games
- Implement a time limit for each guess
- Add a "cheat" mode that gives more detailed hints
- Create a two-player mode where one player sets the number
- Add sound effects or ASCII art for a more engaging experience
Project 2: Text Adventure
Let's build a simple text adventure game where the player navigates through a story by making choices that affect the outcome.
Requirements
- Present a narrative with multiple paths
- Allow the player to make choices that affect the story
- Track player stats (health, inventory, etc.)
- Include conditional events based on previous choices
- Implement a win/lose condition
- Allow the player to play again
Implementation
using System;
using System.Collections.Generic;
namespace TextAdventure
{
class Program
{
// Player stats
static int health = 100;
static int gold = 0;
static List<string> inventory = new List<string>();
static bool hasMap = false;
static bool hasKey = false;
static bool hasSword = false;
static void Main(string[] args)
{
bool playAgain = true;
while (playAgain)
{
// Reset player stats for new game
health = 100;
gold = 0;
inventory = new List<string>();
hasMap = false;
hasKey = false;
hasSword = false;
// Start the adventure
Console.Clear();
Console.WriteLine("=== The Forgotten Dungeon ===");
Console.WriteLine("A text adventure game\n");
Console.WriteLine("You stand at the entrance of an ancient dungeon, rumored to hold a legendary treasure.");
Console.WriteLine("Many have entered, but few have returned. Do you dare to venture inside?\n");
// Begin the adventure
StartAdventure();
// Ask to play again
Console.Write("\nDo you want to play again? (y/n): ");
string? playAgainInput = Console.ReadLine()?.ToLower();
playAgain = playAgainInput == "y" || playAgainInput == "yes";
}
Console.WriteLine("\nThanks for playing! Press any key to exit...");
Console.ReadKey();
}
static void StartAdventure()
{
Console.WriteLine("You step into the dungeon. The entrance closes behind you with a heavy thud.");
Console.WriteLine("You find yourself in a dimly lit chamber with three passages.\n");
bool gameOver = false;
while (!gameOver)
{
// Display player stats
DisplayStats();
Console.WriteLine("\nWhat would you like to do?");
Console.WriteLine("1. Take the left passage");
Console.WriteLine("2. Take the middle passage");
Console.WriteLine("3. Take the right passage");
Console.WriteLine("4. Check your inventory");
if (hasMap)
{
Console.WriteLine("5. Consult the map");
}
Console.Write("\nEnter your choice: ");
string? choice = Console.ReadLine();
Console.Clear();
switch (choice)
{
case "1":
gameOver = LeftPassage();
break;
case "2":
gameOver = MiddlePassage();
break;
case "3":
gameOver = RightPassage();
break;
case "4":
CheckInventory();
break;
case "5":
if (hasMap)
{
ConsultMap();
}
else
{
Console.WriteLine("Invalid choice. Please try again.");
}
break;
default:
Console.WriteLine("Invalid choice. Please try again.");
break;
}
// Check if player has died
if (health <= 0)
{
Console.WriteLine("\nYour health has dropped to zero. You collapse to the ground.");
Console.WriteLine("The darkness of the dungeon claims another victim...");
Console.WriteLine("\nGAME OVER");
gameOver = true;
}
}
}
static bool LeftPassage()
{
Console.WriteLine("You take the left passage and enter a damp, mossy chamber.");
if (!inventory.Contains("Torch"))
{
Console.WriteLine("It's very dark here. You stumble and hurt yourself.");
health -= 10;
Console.WriteLine($"You lost 10 health. Current health: {health}");
}
Console.WriteLine("\nIn the chamber, you find:");
Console.WriteLine("1. A mysterious chest");
Console.WriteLine("2. A skeleton clutching something");
Console.WriteLine("3. Return to the main chamber");
Console.Write("\nWhat do you want to investigate? ");
string? choice = Console.ReadLine();
switch (choice)
{
case "1":
return InvestigateChest();
case "2":
return InvestigateSkeleton();
case "3":
Console.WriteLine("You return to the main chamber.");
return false;
default:
Console.WriteLine("Invalid choice. You return to the main chamber.");
return false;
}
}
static bool InvestigateChest()
{
Console.WriteLine("\nYou approach the mysterious chest.");
if (hasKey)
{
Console.WriteLine("You use the key you found earlier to unlock the chest.");
Console.WriteLine("Inside, you find a healing potion and 50 gold pieces!");
health = Math.Min(100, health + 30);
gold += 50;
Console.WriteLine($"You drink the potion and restore 30 health. Current health: {health}");
Console.WriteLine($"You now have {gold} gold pieces.");
// Remove the key from inventory
inventory.Remove("Key");
hasKey = false;
Console.WriteLine("The key breaks after use.");
}
else
{
Console.WriteLine("The chest is locked. You need a key to open it.");
Console.WriteLine("As you fiddle with the lock, a trap is triggered!");
Console.WriteLine("Poisonous gas fills the air around you.");
health -= 20;
Console.WriteLine($"You lost 20 health. Current health: {health}");
}
Console.WriteLine("\nYou return to the main chamber.");
return false;
}
static bool InvestigateSkeleton()
{
Console.WriteLine("\nYou approach the skeleton clutching something in its bony fingers.");
if (!hasMap)
{
Console.WriteLine("It's an ancient map of the dungeon! This will be very useful.");
inventory.Add("Map");
hasMap = true;
}
else if (!hasKey)
{
Console.WriteLine("You find a rusty key hidden under the skeleton's ribcage.");
inventory.Add("Key");
hasKey = true;
}
else
{
Console.WriteLine("You find 20 gold pieces in a pouch attached to the skeleton's belt.");
gold += 20;
Console.WriteLine($"You now have {gold} gold pieces.");
}
Console.WriteLine("\nYou return to the main chamber.");
return false;
}
static bool MiddlePassage()
{
Console.WriteLine("You take the middle passage and enter a large hall with ancient pillars.");
Console.WriteLine("The hall is illuminated by strange glowing crystals embedded in the walls.");
if (!inventory.Contains("Torch"))
{
Console.WriteLine("You find a torch on the wall and take it.");
inventory.Add("Torch");
}
Console.WriteLine("\nIn the hall, you see:");
Console.WriteLine("1. A pedestal with a glowing orb");
Console.WriteLine("2. A sleeping guardian");
Console.WriteLine("3. Return to the main chamber");
Console.Write("\nWhat do you want to approach? ");
string? choice = Console.ReadLine();
switch (choice)
{
case "1":
return ApproachOrb();
case "2":
return ApproachGuardian();
case "3":
Console.WriteLine("You return to the main chamber.");
return false;
default:
Console.WriteLine("Invalid choice. You return to the main chamber.");
return false;
}
}
static bool ApproachOrb()
{
Console.WriteLine("\nYou approach the pedestal with the glowing orb.");
Console.WriteLine("The orb pulses with an otherworldly energy.");
Console.WriteLine("\nWhat do you do?");
Console.WriteLine("1. Touch the orb");
Console.WriteLine("2. Leave it alone");
Console.Write("\nEnter your choice: ");
string? choice = Console.ReadLine();
if (choice == "1")
{
Console.WriteLine("\nYou touch the orb. A surge of energy flows through your body.");
// Random effect
Random random = new Random();
int effect = random.Next(1, 4);
switch (effect)
{
case 1:
Console.WriteLine("You feel invigorated! Your health is fully restored.");
health = 100;
break;
case 2:
Console.WriteLine("The orb drains your life force!");
health -= 30;
Console.WriteLine($"You lost 30 health. Current health: {health}");
break;
case 3:
Console.WriteLine("The orb grants you a vision of the dungeon's layout.");
Console.WriteLine("You now know where the treasure is!");
hasMap = true;
inventory.Add("Map");
break;
}
}
else
{
Console.WriteLine("\nYou decide not to touch the orb. It's probably safer that way.");
}
Console.WriteLine("\nYou return to the main chamber.");
return false;
}
static bool ApproachGuardian()
{
Console.WriteLine("\nYou approach the sleeping guardian, a stone golem sitting motionless against the wall.");
Console.WriteLine("\nWhat do you do?");
Console.WriteLine("1. Try to sneak past it");
Console.WriteLine("2. Attack it while it's sleeping");
Console.WriteLine("3. Back away quietly");
Console.Write("\nEnter your choice: ");
string? choice = Console.ReadLine();
switch (choice)
{
case "1":
Console.WriteLine("\nYou try to sneak past the guardian...");
// Sneaking attempt
Random random = new Random();
bool sneakSuccessful = random.Next(1, 101) > 50; // 50% chance
if (sneakSuccessful)
{
Console.WriteLine("You successfully sneak past the guardian!");
Console.WriteLine("Behind it, you find a small chest containing 100 gold pieces!");
gold += 100;
Console.WriteLine($"You now have {gold} gold pieces.");
}
else
{
Console.WriteLine("The guardian awakens as you pass by!");
Console.WriteLine("It swings its massive stone fist at you.");
if (hasSword)
{
Console.WriteLine("You block the attack with your sword, but still take some damage.");
health -= 20;
}
else
{
Console.WriteLine("With no weapon to defend yourself, you take the full force of the blow.");
health -= 40;
}
Console.WriteLine($"Current health: {health}");
}
break;
case "2":
Console.WriteLine("\nYou decide to attack the sleeping guardian...");
if (hasSword)
{
Console.WriteLine("You strike at the guardian with your sword!");
Console.WriteLine("The guardian crumbles into pieces before it can react!");
Console.WriteLine("Among the rubble, you find a small chest containing 150 gold pieces!");
gold += 150;
Console.WriteLine($"You now have {gold} gold pieces.");
}
else
{
Console.WriteLine("Without a proper weapon, your attack barely scratches the guardian.");
Console.WriteLine("The guardian awakens and attacks you in a rage!");
health -= 50;
Console.WriteLine($"You lost 50 health. Current health: {health}");
}
break;
case "3":
Console.WriteLine("\nYou back away quietly, leaving the guardian undisturbed.");
break;
default:
Console.WriteLine("\nUnsure what to do, you back away from the guardian.");
break;
}
Console.WriteLine("\nYou return to the main chamber.");
return false;
}
static bool RightPassage()
{
Console.WriteLine("You take the right passage and enter a chamber with a strange altar.");
Console.WriteLine("The walls are covered in ancient runes that seem to glow faintly.");
Console.WriteLine("\nIn the chamber, you see:");
Console.WriteLine("1. A sword embedded in the altar");
Console.WriteLine("2. A doorway sealed with magical energy");
Console.WriteLine("3. Return to the main chamber");
Console.Write("\nWhat do you want to approach? ");
string? choice = Console.ReadLine();
switch (choice)
{
case "1":
return ApproachSword();
case "2":
return ApproachDoorway();
case "3":
Console.WriteLine("You return to the main chamber.");
return false;
default:
Console.WriteLine("Invalid choice. You return to the main chamber.");
return false;
}
}
static bool ApproachSword()
{
Console.WriteLine("\nYou approach the sword embedded in the altar.");
if (!hasSword)
{
Console.WriteLine("The sword appears to be of exceptional quality, glowing with a faint blue light.");
Console.WriteLine("\nDo you try to take the sword?");
Console.WriteLine("1. Yes");
Console.WriteLine("2. No");
Console.Write("\nEnter your choice: ");
string? choice = Console.ReadLine();
if (choice == "1")
{
Console.WriteLine("\nYou grasp the hilt of the sword and pull...");
// Random chance of success
Random random = new Random();
bool success = random.Next(1, 101) <= 70; // 70% chance
if (success)
{
Console.WriteLine("The sword comes free easily, as if it was waiting for you!");
Console.WriteLine("You feel a surge of power as you hold the magical blade.");
inventory.Add("Magic Sword");
hasSword = true;
}
else
{
Console.WriteLine("The sword refuses to budge. A shock of energy pulses through your body!");
health -= 15;
Console.WriteLine($"You lost 15 health. Current health: {health}");
}
}
else
{
Console.WriteLine("\nYou decide to leave the sword alone for now.");
}
}
else
{
Console.WriteLine("The empty altar stands before you. You already have the sword.");
}
Console.WriteLine("\nYou return to the main chamber.");
return false;
}
static bool ApproachDoorway()
{
Console.WriteLine("\nYou approach the doorway sealed with magical energy.");
Console.WriteLine("A shimmering barrier blocks the way forward.");
if (gold >= 200 && hasSword && hasMap)
{
Console.WriteLine("\nWith the magic sword in hand, the ancient map's knowledge, and the wealth you've accumulated...");
Console.WriteLine("You feel prepared to face whatever lies beyond.");
Console.WriteLine("\nDo you wish to dispel the barrier and enter?");
Console.WriteLine("1. Yes");
Console.WriteLine("2. No");
Console.Write("\nEnter your choice: ");
string? choice = Console.ReadLine();
if (choice == "1")
{
Console.WriteLine("\nYou raise the magic sword, which glows brightly in response to the barrier.");
Console.WriteLine("The magical energy dissipates, revealing a passage to the treasure room!");
Console.WriteLine("\nYou enter and find the legendary treasure of the dungeon!");
Console.WriteLine("Chests filled with gold, magical artifacts, and priceless gems surround you.");
Console.WriteLine("\nCongratulations! You have completed the adventure and claimed the treasure!");
return true; // Game over - victory
}
else
{
Console.WriteLine("\nYou decide you're not ready yet to face what lies beyond.");
}
}
else
{
Console.WriteLine("\nThe barrier remains impenetrable to you.");
Console.WriteLine("Perhaps you need more resources or items to proceed...");
if (!hasSword)
{
Console.WriteLine("A powerful weapon might help against the magical barrier.");
}
if (!hasMap)
{
Console.WriteLine("Ancient knowledge of the dungeon could reveal the barrier's weakness.");
}
if (gold < 200)
{
Console.WriteLine($"The barrier seems to respond to wealth. You have {gold} gold, but may need more.");
}
}
Console.WriteLine("\nYou return to the main chamber.");
return false;
}
static void CheckInventory()
{
Console.WriteLine("\n=== INVENTORY ===");
if (inventory.Count == 0)
{
Console.WriteLine("Your inventory is empty.");
}
else
{
foreach (string item in inventory)
{
Console.WriteLine($"- {item}");
}
}
Console.WriteLine("\nPress any key to continue...");
Console.ReadKey();
Console.Clear();
}
static void ConsultMap()
{
Console.WriteLine("\n=== ANCIENT MAP ===");
Console.WriteLine("The map shows the layout of the dungeon:");
Console.WriteLine("- Left Passage: Leads to a treasure chest that requires a key");
Console.WriteLine("- Middle Passage: Contains a guardian protecting something valuable");
Console.WriteLine("- Right Passage: Houses a magical sword and a sealed doorway");
Console.WriteLine("\nThe map indicates that the legendary treasure lies beyond the sealed doorway.");
Console.WriteLine("To dispel the barrier, you'll need the magic sword, knowledge from the map,");
Console.WriteLine("and at least 200 gold pieces as an offering.");
Console.WriteLine("\nPress any key to continue...");
Console.ReadKey();
Console.Clear();
}
static void DisplayStats()
{
Console.WriteLine($"\nHealth: {health}/100 | Gold: {gold}");
}
}
}
Key Concepts Demonstrated
- Conditional Statements: Using
if
,else if
, andelse
to handle different player choices and conditions - Switch Statement: Processing player choices in each location
- Loops: Using a
while
loop for the main game loop - Loop Control: Using
return
to exit methods and control game flow - Boolean Logic: Checking multiple conditions for game progression
- State Tracking: Maintaining player stats and inventory
- Randomization: Adding random elements to encounters
Enhancements You Can Try
- Add more locations and branching paths
- Implement a combat system with enemies
- Add more items and puzzles
- Create a save/load system
- Add ASCII art for locations and events
- Implement a time limit or turn counter
Reflection and Learning Outcomes
After completing these mini-projects, you should have a better understanding of:
- Control Flow: How to use conditional statements, loops, and loop control to create dynamic program behavior
- Program Structure: How to organize code into methods for better readability and maintainability
- State Management: How to track and update program state based on user input and events
- User Interaction: How to create engaging interactive experiences
- Random Elements: How to incorporate randomness for variety and replayability
Connecting to Unity Development
While these console applications might seem far removed from Unity game development, the control flow concepts are directly transferable:
- Conditional Statements: Used in Unity for game logic, AI decisions, and event handling
- Switch Statements: Useful for state machines, input handling, and menu systems
- Loops: Essential for processing collections of game objects, implementing game mechanics, and more
- Loop Control: Important for optimizing performance and handling special cases
In Unity, you'll use these same control flow concepts, but within the context of MonoBehaviour scripts and the Unity lifecycle.
Conclusion
Congratulations on completing the mini-projects for Module 3! You've now applied your knowledge of control flow to create interactive programs that respond to user input and maintain state over time.
These projects demonstrate how conditional statements, loops, and loop control statements work together to create dynamic, responsive applications. The skills you've practiced here will be invaluable as you continue your journey into more advanced C# programming and Unity game development.
In Module 4, we'll explore methods in depth, learning how to organize code into reusable functions that make your programs more modular and maintainable.
Try combining elements from both projects to create a more complex game that includes:
- Multiple levels or areas to explore
- A variety of puzzles, including number-based challenges
- An inventory system with items that have different effects
- Multiple endings based on player choices and achievements
This challenge will further reinforce the concepts from Module 3 and prepare you for the methods and organization topics in Module 4.