NetBeans project

The number guessing game

For my next example I am going to write a program that will implement a simple guessing game in which the computer generates a random number and the human player gets 10 chances to guess the secret number. On each guess the program will tell the user whether or not their guess is too high, too low, or correct. As soon as the player guesses the number correctly or uses up their 10 guesses, the program will no longer accept guesses.

The Game class

For this example program we are going to create an object of type Game to run the guessing game. The Game object will store some state information internally to track the progress of the game and provide methods that allow the human player to play the game.

Here is an outline of the major elements of the Game class.

public class Game {
  private boolean gameOver;
  private int secretNumber;
  private int numberOfGuesses;

  public Game() { }
  public Game(int range) { }
  public boolean isGameOver() { }
  public void guess(int n) { }
}

The Game class has three member variables and four methods. Note that none of the methods are static - this is the usual situation for methods in a class that we will be using to create objects.

The three member variables constitute the data that each Game object will contain. These member variables allow the Game object to track the progress of the game.

Constructors

The methods

public Game() { }
public Game(int range) { }

are both constructors. Two things distinguish constructors from other methods in a class: they have the same name as the class, and they have no return types. Constructors exist for one purpose only - their responsibility is to make sure that the member variables of an object are properly initialized at the instant the object gets created.

To create an object in a program, you typically will write code like the following:

Game myGame = new Game();

This statement does several things at once. The portion

Game myGame

of the statement declares a variable. This variable is what is known as an object reference variable. The primary purpose it serves is to give you access to a particular object of type Game. Simply declaring such a variable does nothing to create the object. The second half of the statement above is responsible for doing that. (The presence of the keyword new on the right hand side is a clue that we are creating a new object.)

Also on the right hand side we see something that looks a little like a method call:

Game()

This is indeed calling a method, the constructor for the Game class. Calling the constructor method will ensure that the new object we are creating starts out with the correct values for its member variables.

Here now is the code for the two constructors of the Game class.

public Game() {
  gameOver = false;
  secretNumber = (int) (Math.random() * 100 + 1);
  numberOfGuesses = 0;
}

public Game(int range) {
  gameOver = false;
  secretNumber = (int) (Math.random() * range + 1);
  numberOfGuesses = 0;
}

The first constructor, called the default constructor, initializes the three member variables using default values. The default case for the secretNumber is to generate a random integer in the range from 1 to 100. The second constructor allows the caller to specify what range to use for the secretNumber: it will generate a secret number between 1 and range. For example, to create a Game object whose secret number is in the range from 1 to 20 we would do this:

Game easyGame = new Game(20);

Other methods

The Game class has two other methods in addition to the constructors. Here is the code for those methods.

public boolean isGameOver() {
    return gameOver;
}

public void guess(int n) {
    if (gameOver || numberOfGuesses > 10) {
        System.out.println("The game is over. You can not guess again.");
    } else if (n == secretNumber) {
        System.out.println("You guessed right!");
        gameOver = true;
    } else {
        if (n < secretNumber) {
            System.out.println("Your guess is too low.");
        } else {
            System.out.println("Your guess is too high.");
        }
        numberOfGuesses++;
        if (numberOfGuesses == 10) {
            System.out.println("You have used up all of your guesses.");
            gameOver = true;
        }
    }
}

The isGameOver() method asks a Game object whether or not the game it represents is finished. The guess() method submits a guess to the Game object. When given a guess, the Game object will respond by printing messages indicating whether the guess is correct, too high, or too low. In addition, each time we call a Game's guess method it updates its internal counter numberOfGuesses. If numberOfGuesses reaches 10 without the user having correctly guessed the secret number, the Game object declares the game over.

Playing the Game

Here is the code for a separate class that contains a main method that will play the game.

public class PlayGame {

 public static void main(String[] args) {
    Scanner input = new Scanner(System.in);

    Game theGame = new Game();
    while(!theGame.isGameOver()){
        System.out.println("Guess a number between 1 and 100:");
        int n = input.nextInt();
        theGame.guess(n);
    }
  }
}

Note that the game is played by directing method calls like theGame.isGameOver() and theGame.guess(n) to the Game object we created. The Game object itself tracks all of the details needed to manage the game being played, so we just have to make sure that we call the appropriate methods to play the game.

Encapsulation

One final comment about the structure of the Game class. The member variables of the Game class are all declared private.

private boolean gameOver;
private int secretNumber;
private int numberOfGuesses;

Normally, if we create a member variable in a class and declare it public, code outside the class in question is allowed to access that member variable directly. For example, if we had declared secretNumber to be public, it would be possible to write code like the following.

Game g = new Game();
int goodGuess = g.secretNumber;
g.guess(goodGuess);

By reaching directly inside the Game object to access its secretNumber member variable, we are making it possible to rig the game. Clearly, we don't want to allow this sort of thing. The fix is to simply declare the secretNumber member variable to be private. Variables that are declared private can still be accessed and used by the code for the class's methods, but no code outside the class can access them.

In effect, code outside the class can still access the data in an object indirectly by calling methods. This is in fact the preferred approach - the methods effectively control and regulate access to the object's internal data and help guarantee that at all times the object's internal state will be well-regulated.

This strategy of hiding internal data in private variables and using methods to control access to the data goes by the name of encapsulation. In most of the examples I will show for the remainder of this course I will use encapsulation as a conscious design strategy.