Case Study 2: The Number Guessing Game

Overview

The number guessing game is a classic programming exercise that showcases the REPEAT..UNTIL loop at its best. The computer picks a secret number, the player guesses, and the program provides "too high" or "too low" hints until the player finds the answer. After each round, the player can choose to play again — another REPEAT..UNTIL in action.

This case study builds a complete, polished game with attempt tracking, hint giving, difficulty levels, and replay functionality.


Problem Statement

Write a number guessing game with the following features:

  1. The player chooses a difficulty level: Easy (1-50), Medium (1-100), or Hard (1-500).
  2. The computer generates a random secret number within the chosen range.
  3. The player guesses until they find the number. After each guess, the program says "Too high," "Too low," or "Correct!"
  4. The program tracks the number of attempts and provides a rating at the end.
  5. After each round, the player can choose to play again.
  6. When the player quits, the program displays statistics for the session (games played, best score, average attempts).

Design

Loop Analysis

This game requires multiple layers of loops:

  • Outer loop (REPEAT..UNTIL): Play again loop. The player always plays at least one game.
  • Inner loop (REPEAT..UNTIL): Guessing loop. The player always makes at least one guess.
  • Menu input (REPEAT..UNTIL): Difficulty selection, validated until a legal choice is made.

All three are REPEAT..UNTIL because each situation requires at least one execution before checking whether to stop.

Pseudocode

Initialize session stats
REPEAT (play again loop)
  REPEAT (difficulty selection)
    Show difficulty menu
    Read choice
  UNTIL choice is valid

  Generate secret number based on difficulty

  REPEAT (guessing loop)
    Read guess
    Increment attempts
    Give hint (too high / too low / correct)
  UNTIL guess = secret

  Display results and rating
  Update session stats
  Ask "Play again?"
UNTIL player says no

Display session statistics

Implementation

program NumberGuessingGame;

uses
  SysUtils;  { For IntToStr }

var
  { Game state }
  Secret:      Integer;
  Guess:       Integer;
  Attempts:    Integer;
  MaxNumber:   Integer;
  Difficulty:  Integer;
  OptimalGuesses: Integer;

  { Session statistics }
  GamesPlayed: Integer;
  TotalAttempts: Integer;
  BestScore:   Integer;
  BestDifficulty: string;

  { Control variables }
  PlayAgain:   Char;

begin
  Randomize;  { Seed the random number generator }

  WriteLn('==========================================');
  WriteLn('     THE NUMBER GUESSING GAME');
  WriteLn('      Written in Pascal');
  WriteLn('==========================================');
  WriteLn;

  { Initialize session statistics }
  GamesPlayed := 0;
  TotalAttempts := 0;
  BestScore := MaxInt;  { Start with worst possible }
  BestDifficulty := '';

  { ===== OUTER LOOP: Play Again ===== }
  repeat
    GamesPlayed := GamesPlayed + 1;
    WriteLn('--- Game ', GamesPlayed, ' ---');
    WriteLn;

    { ===== DIFFICULTY SELECTION (validated input) ===== }
    repeat
      WriteLn('Choose difficulty:');
      WriteLn('  1. Easy   (1 to 50)');
      WriteLn('  2. Medium (1 to 100)');
      WriteLn('  3. Hard   (1 to 500)');
      Write('Your choice (1-3): ');
      ReadLn(Difficulty);

      if (Difficulty < 1) or (Difficulty > 3) then
        WriteLn('Invalid choice. Please enter 1, 2, or 3.');
    until (Difficulty >= 1) and (Difficulty <= 3);

    { Set range based on difficulty }
    case Difficulty of
      1: begin MaxNumber := 50;  OptimalGuesses := 6;  end;
      2: begin MaxNumber := 100; OptimalGuesses := 7;  end;
      3: begin MaxNumber := 500; OptimalGuesses := 9;  end;
    end;

    { Generate secret number }
    Secret := Random(MaxNumber) + 1;  { Random(N) gives 0..N-1 }

    WriteLn;
    WriteLn('I am thinking of a number between 1 and ', MaxNumber, '.');
    WriteLn('Can you guess it?');
    WriteLn;

    Attempts := 0;

    { ===== GUESSING LOOP ===== }
    repeat
      Write('Your guess: ');
      ReadLn(Guess);
      Attempts := Attempts + 1;

      if Guess < 1 then
        WriteLn('  Please guess a number >= 1.')
      else if Guess > MaxNumber then
        WriteLn('  Please guess a number <= ', MaxNumber, '.')
      else if Guess < Secret then
      begin
        Write('  Too low!');
        if Secret - Guess > MaxNumber div 4 then
          WriteLn(' (Way too low...)')
        else if Secret - Guess <= 5 then
          WriteLn(' (But very close!)')
        else
          WriteLn;
      end
      else if Guess > Secret then
      begin
        Write('  Too high!');
        if Guess - Secret > MaxNumber div 4 then
          WriteLn(' (Way too high...)')
        else if Guess - Secret <= 5 then
          WriteLn(' (But very close!)')
        else
          WriteLn;
      end;

    until Guess = Secret;

    { ===== RESULTS ===== }
    WriteLn;
    WriteLn('*** CORRECT! The number was ', Secret, '! ***');
    WriteLn('You got it in ', Attempts, ' attempt(s).');
    WriteLn;

    { Rating }
    Write('Rating: ');
    if Attempts <= OptimalGuesses then
      WriteLn('PERFECT! You used binary search thinking!')
    else if Attempts <= OptimalGuesses + 3 then
      WriteLn('Excellent! Very efficient guessing.')
    else if Attempts <= OptimalGuesses * 2 then
      WriteLn('Good job! Room for improvement.')
    else
      WriteLn('Keep practicing! Try narrowing the range by half each guess.');

    WriteLn('(Optimal for this range: ', OptimalGuesses, ' guesses using binary search)');

    { Update session statistics }
    TotalAttempts := TotalAttempts + Attempts;
    if Attempts < BestScore then
    begin
      BestScore := Attempts;
      case Difficulty of
        1: BestDifficulty := 'Easy';
        2: BestDifficulty := 'Medium';
        3: BestDifficulty := 'Hard';
      end;
    end;

    { ===== PLAY AGAIN? ===== }
    WriteLn;
    repeat
      Write('Play again? (Y/N): ');
      ReadLn(PlayAgain);
    until (PlayAgain = 'Y') or (PlayAgain = 'y') or
          (PlayAgain = 'N') or (PlayAgain = 'n');

    WriteLn;

  until (PlayAgain = 'N') or (PlayAgain = 'n');

  { ===== SESSION STATISTICS ===== }
  WriteLn('==========================================');
  WriteLn('         SESSION STATISTICS');
  WriteLn('==========================================');
  WriteLn('  Games played:     ', GamesPlayed);
  WriteLn('  Total guesses:    ', TotalAttempts);
  WriteLn('  Average guesses:  ', (TotalAttempts / GamesPlayed):0:1);
  WriteLn('  Best score:       ', BestScore, ' (on ', BestDifficulty, ')');
  WriteLn('==========================================');
  WriteLn;
  WriteLn('Thanks for playing! Goodbye.');
end.

Sample Run

==========================================
     THE NUMBER GUESSING GAME
      Written in Pascal
==========================================

--- Game 1 ---

Choose difficulty:
  1. Easy   (1 to 50)
  2. Medium (1 to 100)
  3. Hard   (1 to 500)
Your choice (1-3): 2

I am thinking of a number between 1 and 100.
Can you guess it?

Your guess: 50
  Too low!
Your guess: 75
  Too high! (But very close!)
Your guess: 63
  Too low!
Your guess: 70
  Too high! (But very close!)
Your guess: 67
  Too low! (But very close!)
Your guess: 68
  Too low! (But very close!)
Your guess: 69

*** CORRECT! The number was 69! ***
You got it in 7 attempt(s).

Rating: PERFECT! You used binary search thinking!
(Optimal for this range: 7 guesses using binary search)

Play again? (Y/N): Y

--- Game 2 ---

Choose difficulty:
  1. Easy   (1 to 50)
  2. Medium (1 to 100)
  3. Hard   (1 to 500)
Your choice (1-3): 1

I am thinking of a number between 1 and 50.
Can you guess it?

Your guess: 25
  Too high!
Your guess: 12
  Too low! (But very close!)
Your guess: 17

*** CORRECT! The number was 17! ***
You got it in 3 attempt(s).

Rating: PERFECT! You used binary search thinking!
(Optimal for this range: 6 guesses using binary search)

Play again? (Y/N): N

==========================================
         SESSION STATISTICS
==========================================
  Games played:     2
  Total guesses:    10
  Average guesses:  5.0
  Best score:       3 (on Easy)
==========================================

Thanks for playing! Goodbye.

Analysis

Loop Structure Map

This program contains four REPEAT..UNTIL loops, each serving a distinct purpose:

Loop Purpose Terminates When
Outer play-again loop Controls the entire session Player enters 'N'
Difficulty selection Input validation Valid choice (1-3) entered
Guessing loop Core gameplay Guess equals secret
Play-again input Input validation Valid response (Y/N) entered

Every one of these is a post-test situation — the body must execute at least once before the condition can be evaluated.

Could We Use Different Loop Types?

  • The guessing loop could theoretically be a WHILE loop, but we would need to initialize Guess to some dummy value (like -1) to ensure the condition is initially True. The REPEAT..UNTIL version is cleaner because it avoids this artificial initialization.
  • The play-again loop could not easily be a FOR loop because we do not know how many games the player wants to play.
  • The difficulty selection is the poster child for REPEAT..UNTIL: read input, validate, repeat if invalid.

The Binary Search Connection

The "optimal guesses" rating teaches an important algorithmic lesson. The most efficient strategy is binary search: always guess the midpoint of the remaining range. This halves the search space each time, giving a worst case of ceil(log2(N)) guesses. For 100 numbers, that is 7 guesses. This foreshadows the binary search algorithm we will study in later chapters — and it is a lesson the player learns by playing.

Possible Enhancements

  1. Guess history: Store all guesses in an array and display them with the results. Use a FOR loop to print the history.
  2. Hint system: Allow the player to type "hint" for a clue (e.g., "The number is odd" or "The number is greater than 40"). Limit hints to 2 per game.
  3. Timed mode: Use Pascal's time functions to measure how long each game takes.
  4. High score persistence: Save the best score to a file (Chapter 14 material) so it persists across sessions.
  5. Two-player mode: One player enters the secret number, the other guesses. Use a loop to clear the screen between players.

Exercises

  1. Modify the program to limit the number of guesses. On Easy, allow 8 guesses; on Medium, 10; on Hard, 12. If the player runs out of guesses, reveal the answer and end the round. Which part of the REPEAT..UNTIL condition needs to change?

  2. Add a fourth difficulty level: "Extreme" (1 to 1000). What is the optimal number of guesses for this range?

  3. Add input validation to the guessing loop: if the player enters a number outside the valid range, do not count it as an attempt. (Hint: only increment Attempts when the guess is within range.)

  4. Track the player's guessing strategy by recording whether each guess was "too high" or "too low." At the end, if the player frequently guessed in the same direction consecutively, suggest they try a binary search approach.

  5. Add a "computer plays" mode where the computer guesses using binary search, and the player provides "higher" or "lower" feedback. This demonstrates the optimal strategy. Which loop type would you use for the computer's guessing loop?