Activity 9.11.1.
Try the test for the ComputerGame class below.
TwoPlayer
game example from Chapter 8, our design will allow both humans and computers to play the games. To help simplify the example, we will modify the WordGuess
game that we developed in the Chapter 8. As you will see, it requires relatively few modifications to convert it from a subclass of TwoPlayerGame
to a subclass of ComputerGame
, the superclass for our N-Player game hierarchy.ComputerGame
Hierarchy
ComputerGame
hierarchy. This figure shows the relationships among the many classes and interfaces involved. The two classes whose symbols are bold, WordGuess
and WordGuesser
, are the classes that define the specific game we will be playing. The rest of the classes and interfaces are designed to be used with any N-player game.ComputerGame
class. Note that it uses from 1 to NPlayer
s. These objects will be stored in a one-dimensional array in ComputerGame
. Recall from Chapter 8 that an IPlayer was any class that implements the makeAMove()
method.makeAMove()
method into the Player
class, a class that defines a generic player of computer games. For the WordGuess
game, the WordGuesser
class extends Player
. In order to play Word Guess, we will create a WordGuess
instance, plus one or more instances of WordGuesser
s. This is similar to the OneRowNim
example from the previous chapter,TwoPlayerGame
and OneRowNim
classes occur in the hierarchy. TwoPlayerGame
will now be an extension of ComputerGame
. This is in keeping with the fact that a two-player game is a special kind of N-player computer game. As we will see when we look at the details of these classes, TwoPlayerGame
will override some of the methods inherited from ComputerGame
.makeAMove()
method, the Player
class is an abstract class. Its purpose is to define and store certain data and methods that can be used by any computer games.Player
is whether the player is a computer or a person. Player
’s data and methods will be inherited by WordGuesser
and by other classes that extend Player
. Given its position in the hierarchy, we will be able to define polymorphic methods for WordGuesser
s that treat them as Player
s. As we will see, this will give our design great flexibility and extensibility.ComputerGame
Class
ComputerGame
class. One of the key tasks of the ComputerGame
class is to manage the one or more computer game players. Because this is a task that is common to all computer games, it makes sense to manage it here in the superclass.ComputerGame
declares four instance variables and several methods. Three int
variables define the total number of players (nPlayers
), the number of players that have been added to the game (addedPlayers
), and the player whose turn it is (whoseTurn
). An array named player
stores the Player
s. In keeping with the zero indexing convention of arrays, we number the players from 0 to nPlayers-1. These variables are all declared protected
, so that they can be referenced directly by ComputerGame
subclasses, but as protected
variables, they remain hidden from all other classes.ComputerGame(int)
constructor allows the number of players to be set when the game is constructed. The default constructor sets the number of players to one. The constructors create an array of length nPlayers
:public ComputerGame(int n) {
nPlayers = n;
player = new Player[n]; // Create the array
}
setPlayer()
and getPlayer()
methods are the mutator and accessor methods for the whoseTurn
variable. This variable allows a user to determine and set whose turn it is, a useful feature for initializing a game. The changePlayer()
method uses the default expression,whoseTurn = (whoseTurn + 1) % nPlayers
ComputerGame
can override this method if the game requires some other order of play.addPlayer(Player)
method is used to add a new Player
to the game, including any subclass of Player
. The method assumes that addedPlayers
is initialized to 0. It increments this variable by 1 each time a new player is added to the array. For the game WordGuess
, we would be adding Player
s of type WordGuesser
to the game.ComputerGame
is shown in Listing 9.11.3.TwoPlayerGame
class from Chapter 8, the methods gameOver()
and getWinner()
are defined as abstract
and the getRules()
method is given a generic implementation. The intent here is that the subclass will override getRules()
and will provide game-specific implementations for the abstract methods.addPlayer()
method is coded. It uses the addedPlayers
variable as the index into the player
array, which always has length nPlayers
. An attempt to call this method when the array is already full will lead to the following exception being thrown by Java. Try this in the activecode activity above.Exception in thread ``main''
java.lang.ArrayIndexOutOfBoundsException: 2
at ComputerGame.addPlayer(ComputerGame.java:22)
at TwentyOne.main(TwentyOne.java:121)
player
array. In Chapter 11, we will learn how to design our code to guard against such problems.listPlayers()
method (Listing 9.11.3). Here is a good example of polymorphism at work. The elements of the player
array have a declared type of Player
. If their dynamic type is WordGuesser
, when the expression player[k].toString()
is invoked, dynamic binding is used to bind this method call to the implementation of toString()
defined in the WordGuesser
class. Thus, by allowing toString()
to be bound at run time, we are able to define a method here that doesn’t know the exact types of the objects it will be listing.listPlayers()
here in the superclass, and would instead have to define it in each subclass.WordGuess
and WordGuesser
Classes
WordGuess
example from Chapter 8. If not, you will need to review that section before proceeding.WordGuess
class. If you compare it with the design we used in Chapter 8, the only change in the instance methods and instance variables is the addition of a new constructor, WordGuess(int)
, and an init()
method. This constructor takes an integer parameter representing the number of players. The default constructor assumes that there is one player. Of course, this version of WordGuess
extends the ComputerGame
class, rather than the TwoPlayerGame
class. Both constructors call the init()
method to initialize the game:public WordGuess() { super(1); init(); }
public WordGuess(int m) { super(m); init(); }
public void init() {
secretWord = getSecretWord();
currentWord = new StringBuffer(secretWord);
previousGuesses = new StringBuffer();
for (int k = 0; k < secretWord.length(); k++)
currentWord.setCharAt(k,'?');
unguessedLetters = secretWord.length();
}
WordGuess
to an N-player game, is to rewrite its play()
method. Because the new play()
method makes use of functionality inherited from the ComputerGame()
class, it is actually much simpler than the play()
method in the Chapter 8 version:public void play(UserInterface ui) {
ui.report(getRules());
ui.report(listPlayers());
ui.report(reportGameState());
while(!gameOver()) {
WordGuesser p = (WordGuesser)player[whoseTurn];
if (p.isComputer())
ui.report(submitUserMove(p.makeAMove(getGamePrompt())));
else {
ui.prompt(getGamePrompt());
ui.report(submitUserMove(ui.getUserInput()));
}
ui.report(reportGameState());
} // while
}
listPlayers()
method is inherited from the ComputerGame
class. After displaying the game’s current state, the method enters the play loop. On each iteration of the loop, a player is selected from the array:WordGuesser p = (WordGuesser)player[whoseTurn];
WordGuesser
variable, p
, just makes the code somewhat more readable. Note that we have to use a cast operator, (WordGuesser)
, to convert the array element, a Player
, into a WordGuesser
. Because p
is a WordGuesser
, we can refer directly to its isComputer()
method.submitUserMove()
method, and then report the result. This is all done in a single statement:ui.report(submitUserMove(p.makeAMove(getGamePrompt())));
KeyboardReader
’s getUserInput()
method to read the user’s move. We then submit the move to the submitUserMove()
method and report the result. At the end of the loop, we report the game’s updated state. The following code segment illustrates a small portion of the interaction generated by this play()
method:Current word ???????? Previous guesses GLE Player 0 guesses next.Sorry, Y is NOT a new letter in the secret word Current word ???????? Previous guesses GLEY Player 1 guesses next.Sorry, H is NOT a new letter in the secret word Current word ???????? Previous guesses GLEYH Player 2 guesses next. Guess a letter that you think is in the secret word: a Yes, the letter A is in the secret word
WordGuesser
class is a subclass of Player
(Figure 9.11.6). The WordGuesser
class itself requires no changes other than its declaration:public class WordGuesser extends Player
WordGuess
class, the play()
method invokes WordGuesser
’s isComputer()
method. But this method is inherited from the Player
class. The only other method used by play()
is the makeAMove()
method. This method is coded exactly the same as it was in the previous version of WordGuesser
.Player
class. Most of its code is very simple. Note that the default value for the kind
variable is HUMAN
and the default id
is -1, indicating the lack of an assigned identification.Player
its utility is the fact that it encapsulates those attributes and actions that are common to all computer game players. Defining these elements, in the superclass, allows them to be used throughout the Player
hierarchy. It also makes it possible to establish an association between a Player
and a ComputerGame
.ComputerGame
and Player
hierarchies and the many interfaces they contain, the task of designing and implementing a new N-player game is made much simpler. This too is due to the power of object-oriented programming. By learning to use a library of classes, such as these, even inexperienced programmers can create relatively sophisticated and complex computer games.main()
method instantiates and runs an instance of the WordGuess
game in a command-line user interface (CLUI):public static void main(String args[]) {
KeyboardReader kb = new KeyboardReader();
ComputerGame game = new WordGuess(3);
game.addPlayer(new WordGuesser((WordGuess)game, 0, Player.HUMAN));
game.addPlayer(new WordGuesser((WordGuess)game, 1, Player.COMPUTER);
game.addPlayer(new WordGuesser((WordGuess)game, 2, Player.COMPUTER);
((CLUIPlayableGame)game).play(kb);
} //main()
WordGuesser
, passing it a reference to the game itself, as well as its individual identification number, and its type (HUMAN or COMPUTER). To run the game, we simply invoke its play()
method. You know enough now about object-oriented design principles to recognize that the use of play()
in this context is an example of polymorphism.