انتقل إلى المحتوى
أمين أبوزيد
منشورBEG-013-L07

عرض عملي: لعبة تخمين الرقم مع تتبّع الحلقة في المصحّح (debugger)

  • الدرس 7 من 7
  • 10 دقيقة قراءة
  • مبتدئ
في هذا الدرس 11 موضوعات

هذا الدرس متاح حاليًا باللغة الإنجليزية فقط، ولم تُنشر نسخته العربية بعد.

Lesson Overview

This lesson puts the whole module to work. You will build a number guessing game: the program picks a secret number from 1 to 100, and the player has 7 attempts to find it, with "Higher" or "Lower" hints. Then you will use a debugger to pause the loop and watch its variables change, one pass at a time.

On the track: the game is a race with a limited number of laps, and the finish line is the correct guess. The debugger is a camera that lets you pause mid-lap and read the lap counter.

Why this matters: the game uses nearly everything from the module (a while loop with two stopping conditions, continue for bad input, a counter) and it is the base of the lab BEG-013-LAB1. The debugger skill matters even more: in a backend, you will often have no easier way to see why a loop misbehaves.

This lesson builds on BEG-013-L01 to L06, Scanner from BEG-011, and conditions from BEG-012.

Learning Objectives

By the end of this lesson you will be able to:

  • Generate a random number in a range with Random.nextInt.
  • Write a game loop that stops on a win or when attempts run out.
  • Reject invalid input without using up an attempt.
  • Set a breakpoint, step over lines, and read variable values in a debugger.
  • Recognise a missing counter update from the debugger, not just from the screen.
  • Plan a replay loop around the game.

Written Explanation

The random target

java.util.Random produces pseudo-random numbers. The method nextInt(bound) returns a value from 0 up to, but not including, bound. The Java API documentation states this range and says it throws an IllegalArgumentException if the bound is not positive. So:

java
int target = new Random().nextInt(100) + 1;

gives 0 to 99, and the + 1 shifts it to 1 to 100. This is an off-by-one trap of its own: forget the + 1 and the player can never guess 100, and could be asked for 0.

Designing the loop

Write down the rules before the code:

  • The game continues while attempts are left and the player has not won.
  • Each valid guess uses one attempt.
  • An invalid guess (not a number, or out of range) does not use an attempt.
  • When the loop ends, say whether the player won, and how many attempts were used.

That gives a while loop with the condition attempts < MAX && !won. We use while rather than for, because the number of rounds is not fixed: the game may end on the first guess. We use continue for invalid input, to skip the counting and the hint.

Handling bad input

If the player types a word, scanner.nextInt() throws an exception and the program crashes. A safer way (BEG-011-L05 introduced validating input) is to ask first with hasNextInt(); if it is false, discard the bad word with scanner.next() and go round again.

The debugger

A debugger runs your program under your control. The steps below are typical, but the exact menus, keys, and icons vary between IDEs and versions (IntelliJ IDEA, Visual Studio Code with the Java extensions, and so on). Use your IDE's help if a name differs.

  1. Breakpoint. Click in the left margin next to a line, usually the line of the while. A red dot appears. The program will pause there.
  2. Start in debug mode. Use the IDE's Debug command, not plain Run.
  3. Step over. A button (often with the shortcut F8 in IntelliJ and F10 in Visual Studio Code, with the defaults at the time of writing) runs the current line and pauses on the next.
  4. Variables panel. It lists the current values of attempts, target, won, and others.
  5. Resume runs until the next pause; Stop ends the program.

When you are stepping and the program asks for input, type it in the IDE's console panel.

The planned mistake

We deliberately make one bug: leave out attempts++. On screen you see the game keep asking forever, saying "Higher" and "Lower" with no end. That does not tell you why. In the debugger you step through a few passes and see attempts stuck at 0 in the variables panel. The screen shows what the program prints; the debugger shows what the program knows.

Replay

The replay option wraps the whole game in an outer do-while: play at least once, then ask "Play again?" and repeat if the answer is y. That is a nested-loop idea from L05, with the game as the inner loop. Moving the game into its own method would be neater, but methods come later in the course.

Examples

Example 1: the random range (simple)

java
Random random = new Random();
int target = random.nextInt(100) + 1;
System.out.println(target >= 1 && target <= 100);   // always true

Output:

text
true

The check prints true for every run, because nextInt(100) yields 0 to 99.

Example 2: the working game (practical)

The listing below uses a fixed seed, new Random(7), only so that the demo is repeatable: with seed 7 on the JDK 21 used here the target was 37. In your lab, use new Random() without a seed.

java
import java.util.Random;
import java.util.Scanner;

public class Game {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int target = new Random(7).nextInt(100) + 1; // fixed seed only for this demo
        int attempts = 0;
        final int MAX = 7;
        boolean won = false;
        while (attempts < MAX && !won) {
            System.out.print("Guess (1-100): ");
            if (!scanner.hasNextInt()) {
                System.out.println("Not a number.");
                scanner.next();
                continue;
            }
            int guess = scanner.nextInt();
            if (guess < 1 || guess > 100) {
                System.out.println("Out of range.");
                continue;
            }
            attempts++;
            if (guess == target) {
                won = true;
            } else if (guess < target) {
                System.out.println("Higher");
            } else {
                System.out.println("Lower");
            }
        }
        if (won) System.out.println("Correct in " + attempts + " attempts.");
        else System.out.println("Out of attempts. It was " + target);
    }
}

Run with the inputs 50, abc, 0, 200, 25, 37 (one per line), the output was:

text
Guess (1-100): Lower
Guess (1-100): Not a number.
Guess (1-100): Out of range.
Guess (1-100): Out of range.
Guess (1-100): Higher
Guess (1-100): Correct in 3 attempts.

Three valid guesses (50, 25, 37) used three attempts; abc, 0, and 200 were rejected and used none. Run with the seven inputs 1 to 7, the game ended with Out of attempts. It was 37.

Notice the decisions: the loop condition holds both end rules; continue skips counting for bad input; attempts++ comes only after the input is valid.

Example 3: stepping through in the debugger (realistic)

Take the game above and delete the line attempts++;. Then:

  1. Put a breakpoint on the while line and start in debug mode.
  2. Step over until the program waits for a guess, then type 50 in the console panel.
  3. Step through the if-else chain; the game prints a hint and returns to the while line.
  4. Look at the variables panel: attempts is still 0. Repeat twice more. It never moves.
  5. You have found the bug without guessing: the counter has no update. Put attempts++; back after the range check and run again: now attempts rises 1, 2, 3 as you step.

On the screen, the bug looks like "the game never ends"; in the debugger, it is a single stuck value. Many production bugs are like this: visible in values, invisible in output.

Backend Insight

  • Debugging beats guessing. In backend work you often attach a debugger to a program that runs on your machine and step through request handling. The skill you use here, a breakpoint plus reading variables, is the same one. Later you will also meet logs, which do this job when you cannot attach a debugger, for example on a server.
  • Validate everything that comes in. The input checks in this game are a small copy of what every service does with request data: check it, reject it, and do not let bad input change state (here, the attempt counter).
  • Limit loops. The game has a hard limit of 7 attempts. A service that retries or polls needs a similar limit, as L06 said.
  • Random in tests. Seeding Random as in the demo makes a run repeatable. Tests rely on this idea, but a fixed seed in the shipped game would make the game predictable.

Common Mistakes

Mistake 1: Forgetting the + 1 after nextInt(100)

The range is 0 to 99, so the target would sometimes be 0 and never 100.

Mistake 2: Counting invalid input as an attempt

If attempts++ comes before the validation, bad input loses an attempt. The lab says it must not.

Mistake 3: Forgetting to discard the bad token

If hasNextInt() is false and you do not call scanner.next(), the same bad word stays in the input, and the loop spins forever printing "Not a number." That is an infinite loop caused by input that never moves.

Mistake 4: Running instead of debugging

Starting with Run, not Debug, makes breakpoints do nothing. Use the Debug command.

Mistake 5: Starting with a breakpoint inside the loop and expecting to see the start

Put the breakpoint on a line before or at the loop head, so that you see the first pass too.

Exercises

Try each exercise yourself before opening the solutions.

Level A: Foundation

A1. What range does new Random().nextInt(50) give? What expression gives 1 to 50?

أظهر الحل A1

A1. 0 to 49. new Random().nextInt(50) + 1 gives 1 to 50.

A2. In the game, why is while a better choice than for?

أظهر الحل A2

A2. The number of rounds is not fixed: the game can end after one guess or after seven, and invalid input does not count. A while with an end condition fits that; a for fits a fixed count.

A3. What does a breakpoint do?

أظهر الحل A3

A3. It pauses the program at that line when it is run in debug mode, so that you can read variables and step.

Level B: Application

B1. Change the game so that the player has 10 attempts and the number is between 1 and 20. Which lines change?

أظهر الحل B1

B1. MAX becomes 10, and nextInt(20) + 1. The range check guess < 1 || guess > 100 should become guess > 20, and the prompt text should be changed to say 1-20.

B2. Write the lines that print "Too high or too low" differently: when the guess is within 5 of the target, print Warm, otherwise Higher or Lower. Use Math.abs(guess - target).

أظهر الحل B2

B2.

java
} else if (Math.abs(guess - target) <= 5) {
    System.out.println("Warm");
} else if (guess < target) {
    System.out.println("Higher");
} else {
    System.out.println("Lower");
}

The Warm branch comes before the others, otherwise the other branches would catch the guess first. Note that Warm does not say higher or lower, which makes the game harder.

B3. In the game, which statement skips the rest of the pass when input is invalid, and why is that better than an else branch around the rest of the body?

أظهر الحل B3

B3. continue. It keeps the main logic at one indentation level and puts the exceptional case first; an else would nest everything after it one more level.

Level C: Challenge

C1. Add a replay loop: after a game ends, ask Play again? (y/n). Describe the structure (which loop goes where) in words and in outline code.

أظهر الحل C1

C1. Wrap the game in a do-while: the game body is the inner part, the outer condition reads the answer.

java
String again;
do {
    // reset attempts and won, pick a new target, play the game loop
    System.out.print("Play again? (y/n): ");
    again = scanner.next();
} while (again.equalsIgnoreCase("y"));

Variables such as attempts and won must be reset at the start of each game.

C2. You run the game and it prints "Not a number." forever when you type abc. Explain what the debugger would show and what is missing from the code.

أظهر الحل C2

C2. The debugger would show the loop stuck on the same pass, with hasNextInt() false each time and the input still containing abc. The scanner.next() call that removes the bad word is missing, so nothing moves the input forward.

Mini Challenge

Finish the lab version of the game (BEG-013-LAB1) with these features, and test each by hand:

  1. Random target between 1 and 100 (no fixed seed), 7 attempts.
  2. Higher/lower hints, invalid input rejected without using an attempt.
  3. A final report of the number of attempts used.
  4. A replay question, with the variables reset for each game.
  5. Debug one game: put a breakpoint on the while line and watch attempts through three guesses.

Check your work: type abc, 0, and 101, and confirm that the attempt count did not change; win once and lose once; answer n to end.

Summary & Next Lesson

Key takeaways

  • new Random().nextInt(100) + 1 gives a number from 1 to 100.
  • A game loop is a while with two end rules: attempts left and not yet won.
  • continue skips counting and hints for invalid input; the counter is only updated for valid guesses.
  • A debugger shows variable values on each pass; a stuck counter is visible at once.
  • A replay is an outer do-while around the whole game, with variables reset.

Next lesson

This is the last lesson of the module. The module's lab, BEG-013-LAB1, is the guessing game, and the next module of the course carries on from there.

Self-check

  1. Which range does nextInt(100) give?
  2. Why does invalid input not use an attempt?
  3. Which statement skips the rest of the loop pass?
  4. What does the variables panel of a debugger show?
  5. Which loop goes around the whole game to implement replay?
أظهر الإجابات
  1. 0 to 99.
  2. The attempt counter is only updated after the input has passed validation.
  3. continue.
  4. The current values of the variables at the paused line.
  5. A do-while, so that the game is played at least once.

References

  • Oracle, Random (Java SE 21 API). It supports that nextInt(bound) returns a value from 0 inclusive to bound exclusive, and throws IllegalArgumentException for a non-positive bound.
  • Oracle, The while and do-while Statements. It supports the while and do-while behaviour used by the game and the replay loop.
  • The game and its outputs were compiled and run with JDK 21 using piped input. The seed 7 target of 37 is what that run produced; seed results may differ on other Java versions, so it is not guaranteed. The debugger shortcuts were not verified against IDE documentation and are hedged in the text; the replay snippet and the B2 solution were not compiled.

التطبيق

طبّق هذا الدرس بمختبر الوحدة وتمارينها واختبارها.

المختبر والتمارين الاختبار

تعليم الدرس كمكتمل اختياري ويمكنك التراجع عنه في أي وقت. يبقى تقدمك محفوظًا في هذا المتصفح.

اكتب للبحث في المستويات والمراحل والوحدات والدروس.