Concepts / Generating Random Numbers in Java

Generating Random Numbers in Java

The exercise splits the program into main(), which repeats games, and the static subroutine playGame(), which plays one round.

  • Programming

A Guessing Game Built from Two Methods

This exercise is a small Java program in which the computer picks a secret number between 1 and 100 and the user tries to guess it. The program is built from ideas you have already seen: method calls, loops, and conditional decisions. Rather than reading the code first, treat the program as a question. Ask yourself which part of the program decides when a game starts, when it ends, and when the whole session stops.

What do you think happens?

Inside playGame(), the variable computersNumber is set by a random expression. Could it ever hold 0 or 101?

  • Yes, both are possible

  • Only 0 is possible

  • Neither is possible

Reveal answer
Answer:

Neither is possible. The computer's number is always an integer from 1 to 100 inclusive.

The source states that the expression (int)(100 * Math.random()) + 1 produces a randomly chosen integer between 1 and 100, inclusive. The rest of this article explains the arithmetic that guarantees that range.

Splitting Responsibilities Between main() and playGame()

The program has two parts that matter most for this lesson. The main() method prints a greeting, repeatedly plays a game, and asks the user whether to play again. The playGame() method holds the logic for one complete round. Keep these two roles separate in your mind, because most debugging questions about this program come down to asking which of the two parts produced a given result.

The keyword static is what allows main() to call playGame() directly without creating an object first. The return type void means playGame() does not hand a value back to its caller. Its job is to play the game and print the results. Because the whole game lives inside its own subroutine, main() reads almost like a plain list of steps: greet the user, play a game, ask whether to continue, and say goodbye. The details of how a single game works are hidden inside playGame().

java
Notes on the code
  • main() is the outer driver: it repeats whole games
  • playGame() is a static subroutine with void return type
  • The do-while loop calls playGame() once before any question is asked
main()repeats gamesplayGame()plays one roundUseranswers yes or nocall playGame()round finishes, control returnsask play again?answer read into playAgain
In what order do main() and playGame() run, and where does control return to main() after each round of the game?

Tracing One Round Inside playGame()

Trace the program by keeping a running record of which method is currently executing. Execution starts in main(), which prints the two greeting lines and then reaches the do-while loop. When it reaches the call to playGame(), control moves into that subroutine. There the local variables are declared and the computer's number is chosen. Control returns to main() only after playGame() finishes. Then main() asks the play-again question and reads the answer.

Worked example

Following one round from call to return

Trace what happens between the moment main() reaches playGame() and the moment main() asks the play-again question.

  1. 1
    Step 1: main() makes the call

    Execution is inside the do-while body in main(). It reaches the statement playGame(), and control transfers into that subroutine.

  2. 2
    Step 2: playGame() chooses the secret number

    Inside playGame(), the local variable computersNumber is assigned the value of the random expression. This is the only place in the source-described flow where the secret number is set.

  3. 3
    Step 3: playGame() finishes the round

    The round plays out inside playGame() and results are printed. Only when playGame() finishes does control go back to main().

  4. 4
    Step 4: main() asks the question

    Back in main(), System.out.print displays the play-again question, and TextIO.getlnBoolean() reads the answer into playAgain.

Result

Each round is a self-contained trip into playGame(), and main() regains control at a single well-defined point.

Turning Math.random() into a Whole Number

Inside playGame(), the computer's secret number is stored in a variable named computersNumber. It is assigned using Math.random(), which generates a random double between 0.0 and 1.0. A double is a number with a fractional part, so Math.random() on its own cannot give you a whole number from 1 to 100. The program has to scale and shift the value into the range it needs.

The source uses this pattern with a maximum of 100 and a minimum of 1, giving the assignment computersNumber = (int)(100 * Math.random()) + 1;. Each of the three parts does a specific job. The multiplication by 100 stretches the random value across a larger span, from 0.0 up to but not including 100.0. The (int) cast discards the fractional part, leaving a whole number from 0 to 99. Adding 1 shifts the result so that the lowest possible value becomes 1 instead of 0.

Pattern
computersNumber = (int)(100 * Math.random()) + 1
Formula, step by step
computersNumber = (int)(100 * Math.random()) + 1
  1. 1
    Math.random() returns a double r with 0.0 less than or equal to r and r less than 1.0
  2. 2
    100 * r is a double in the range from 0.0 up to but not including 100.0
  3. 3
    (int) cast truncates the fractional part, so the result is an integer from 0 to 99
  4. 4
    Adding 1 shifts the range so the final value is an integer from 1 to 100 inclusive
0.00 up to 0.01result 10.01 up to 0.02result 20.98 up to 0.99result 990.99 up to 1.00result 100
How does a value in the range from 0.0 up to 1.0 get divided into 100 slots and shifted so that every result falls between 1 and 100?

The diagram above shows the idea. Each slice of width 0.01 in the random value becomes one of the 100 possible results once it is multiplied by 100, truncated, and shifted by 1. The first slice, from 0.00 up to 0.01, produces 1. The last slice, from 0.99 up to 1.00, produces 100. Because Math.random() never returns exactly 1.0, the top of the range is never reached, so the value 1.0 cannot make the result 101. The middle slots follow the same pattern and are omitted from the figure.

Why the Do-While Loop Plays at Least Once

main() does not play just one game. It uses a do-while loop to repeat playGame() until the user chooses to stop. A do-while loop executes its block of code at least once before the condition is checked. The body runs, then the condition is tested. If the condition is still true, the body runs again.

In the exercise, the body of the loop calls playGame() and then asks, using System.out.print, whether the user would like to play again. The answer is read into the boolean variable playAgain with TextIO.getlnBoolean(). The loop condition is while (playAgain). The condition is checked after each iteration, so the program always plays at least one game, and it plays another only when the user's answer was true. The first game always starts before the play-again question is asked.

playGame() and askbody runs firstplayAgain?condition checked afterbodyGoodbye messageloop exitsafter each iterationtrue: repeat bodyfalse: stop
Where is the condition checked in a do-while loop, and what path does execution take when the condition is true or false?
Code walkthrough
do {
    playGame();
    System.out.print("Would you like to play again? ");
    playAgain = TextIO.getlnBoolean();
} while (playAgain);
  1. 1
    The do keyword starts the body, so there is no check before the first pass.
  2. 2
    playGame() plays one full round and control returns here when it finishes.
  3. 3
    The play-again question is printed and the user's answer is stored in playAgain.
  4. 4
    The while (playAgain) condition is tested only after the body has run.
  5. 5
    If playAgain is true, control jumps back to the top of the body; if false, the loop ends.

Mistakes Beginners Make When Tracing This Program

  • Common mistake

    Thinking Math.random() returns a whole number from 1 to 100

    What it looks like

    Printing Math.random() directly and expecting a value like 37

    Why it fails

    Math.random() returns a double from 0.0 up to but not including 1.0, so it has a fractional part.

    Do this instead

    Apply the scale and shift: (int)(100 * Math.random()) + 1.

  • Common mistake

    Dropping the + 1 from the expression

    What it looks like
    computersNumber = (int)(100 * Math.random()); which can produce 0
    Why it fails

    Without the shift, the lowest value from the cast is 0, not 1.

    Do this instead

    Keep + 1 so the range becomes 1 to 100 inclusive.

  • Common mistake

    Assuming a while loop and a do-while loop behave identically

    Why it fails

    A do-while loop checks its condition after each iteration, so its body runs at least once.

    Do this instead

    Use the do-while form when the first pass must always happen, as the first game does here.

  • Common mistake

    Looking for the bug in the wrong method

    Why it fails

    A wrong secret number points at playGame(), a game that never starts points at the call in main(), and wrong play-again behavior points at the loop condition in main().

    Do this instead

    Find the boundary between main() and playGame() first, then narrow down which side produced the result.

Tracing a Run to Find Where It Diverges

Suppose you run the program and a round ends in an unexpected way. Start by asking where the result diverged from what you expected. If the value of computersNumber was wrong, the problem is likely in the random number expression inside playGame(). If the game never started, look at the call from main(). If the program asked the play-again question and then behaved wrongly, look at the do-while condition in main(). Locating the boundary between the two methods is usually the fastest way to narrow the problem.

MEDIUM

A friend says the expression (int)(100 * Math.random()) + 1 can never produce 50. Using the slot idea from earlier, explain whether that claim is true. Then describe which random values would produce 50.

Hints
  • Work out which range of Math.random() values gives an integer of 49 after the cast, since adding 1 then gives 50.
  • The slot for result 50 covers random values from 0.49 up to but not including 0.50.
EASY

Predict the flow of control for this scenario: the user answers false to the first play-again question. Write the order in which main() and playGame() run from the start of the program until the goodbye message is printed.

Hints
  • Count how many times playGame() is called before the condition is tested.
  • The condition is tested after the body, so the answer to the question determines whether the loop repeats.

Key Takeaways

  • The program splits work between main(), which repeats games and asks the play-again question, and the static void subroutine playGame(), which plays one round.
  • Math.random() returns a double from 0.0 up to but not including 1.0, and (int)(100 * Math.random()) + 1 scales, truncates, and shifts it to an integer from 1 to 100 inclusive.
  • A do-while loop runs its body at least once and checks its condition after each iteration, which is why the first game always starts before the play-again question is asked.
  • When a result looks wrong, identify which method and which line produced it, starting from the boundary between main() and playGame().