The guessing game

We are going to write a simple program that uses an object to perform a task. Our program 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.

The gameOver member variable has type boolean: boolean variables hold true/false values and can be used for tests in if/else statements or while loops.

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) {
    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() == false){
        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.

Moving the interface out of Game class

One remaining problem with the Game class as it is designed now is that it assumes that the user interface for the game will be a text-based interface that uses a Scanner and System.out.println to communicate with the user. Ideally, we would like to design the Game class to be independent of the actual interface used. (To motivate this desire even more strongly, in lab this week you will construct a program that puts a graphical user interface over the Game class, while in a later lecture I will show you a program that plays this guessing game through a browser with an HTML interface.)

The only part of the Game class that really needs changing to remove its dependence on the text-based interface is the guess method. In its current form, it takes the user's guess as a parameter and uses System.out.println() to tell the user what happened.

public void guess(int n) {
  if (gameOver) {
    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;
    }
  }
}

If we want to move the print statements out of the guess method, the main thing we will need is some way for the guess method to communicate to the outside world what happened in that round of the game. One standard way to solve this problem is to have the guess method return a special code number that tells the caller what happened in that guess. The code that calls the guess method can then examine that code number and print an appropriate message to System.out, a dialog, or an HTML response.

Here is a version of the guess method that returns a code number instead of printing a message.

public int guess(int n) {
    if (gameOver) {
        return -1; // -1 indicates the game is over
    } else if (n == secretNumber) {
        gameOver = true;
        return 1; // 1 indicates that the guess is correct.
    } else {
        numberOfGuesses++;
        if (numberOfGuesses == 10) {
            gameOver = true;
        }
        if (n < secretNumber) {
            return 0; // 0 indicates that the guess is too low
        } else {
            return 2; // 2 indicates that the guess is too high
        }
    }
}

The code that calls this guess method then is responsible for checking the code number returned from the guess method to see what happened:

public class PlayGame {

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

    Game theGame = new Game();
    while(theGame.isGameOver() == false){
        System.out.println("Guess a number between 1 and 100:");
        int n = input.nextInt();
        int result = theGame.guess(n);
				if(result == 0)
					System.out.println("Your guess is too low.");
				else if(result == 1)
					System.out.println("You guessed right!");
				else
				  System.out.println("Your guess is too high.");
    }
  }
}

One final adjustment we will make is to use named constants in place of the code numbers we use now. The advantage to doing this is that we can use names instead of numbers in our code.

We add these constants as member variables of our Game class:

public static final int CORRECT = 1;
public static final int TOO_LOW = 0;
public static final int TOO_HIGH = 2;
public static final int GAME_OVER = -1;

and modify the code for the guess method and the code that calls it to use these named constants instead of numbers:

public int guess(int n) {
    if (gameOver) {
        return GAME_OVER;
    } else if (n == secretNumber) {
        gameOver = true;
        return CORRECT;
    } else {
        numberOfGuesses++;
        if (numberOfGuesses == 10) {
            gameOver = true;
        }
        if (n < secretNumber) {
            return TOO_LOW;
        } else {
            return TOO_HIGH;
        }
    }
}

public class PlayGame {

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

    Game theGame = new Game();
    while(theGame.isGameOver() == false){
        System.out.println("Guess a number between 1 and 100:");
        int n = input.nextInt();
        int result = theGame.guess(n);
				if(result == Game.TOO_LOW)
					System.out.println("Your guess is too low.");
				else if(result == Game.CORRECT)
					System.out.println("You guessed right!");
				else
				  System.out.println("Your guess is too high.");
    }
  }
}