Section 4.5. Case Study: The One-Row Nim Game


[Page 175 (continued)]

4.5. Case Study: The One-Row Nim Game

In this section, we show how to develop alternative interfaces for our case study game of OneRow Nim that was developed in Chapters 2 and 3. As you will recall, the One-Row Nim game starts with, say, 21 sticks on a table. Players take turns picking up one, two, or three sticks, and the player to pick up the last stick loses. We wish to develop an application program that the will enable the user of the program to play this game against the computer, that is, against the program.

As in our other examples in this chapter, our design will divide this problem into two primary objects: a computational object, in this case OneRowNim, and a user interface object, for which we will use either a KeyboardReader or a OneRowNimGUI. One goal of our design was to develop the OneRowNim class so that it can be used, without changes, with either a command-line interface or a GUI.


[Page 176]

Recall that we designed the OneRowNim class to maintain the state of the game and to provide methods that enforce the rules of the game. Thus, we know that after each legal move, the number of sticks will decline, until it is zero or less, which indicates that the game is over. Also, an instance of OneRowNim keeps track of whose turn it is and can determine whether the game is over and who the winner is when the game is over. Finally, the game ensures that players cannot cheat either by taking too few or too many sticks on one turn. Figure 4.23 shows the UML diagram of the OneRowNim class as described at the end of the Chapter 3.

Figure 4.23. A UML diagram of the OneRowNim class.


4.5.1. A Command-Line Interface to OneRowNim

We will now focus on connecting a OneRowNim instance with a KeyboardReader instance, the command-line interface we developed at the beginning of this chapter. To do so requires no changes to KeyboardReader (Fig. 4.6). Unlike in the greeter example, we will use a third object to serve as the main program. The OneRowNimApp class will contain the run() method that controls the game's progress. OneRowNimApp will use the KeyboardReader object to prompt the user, to display the program's output, and to perform input from the keyboard. It will use the OneRowNim object to keep track of the game.


[Page 177]

In fact, the main challenge for this part of our problem is designing the run() method, which will use a loop algorithm to play the game. The user and the computer will repeatedly take turns picking up sticks until the game is over. The game ends when there are no more sticks to pick up. Thus, we can use the game's statethe number of sticks leftas our loop's entry condition. We will repeat the loop while there are more than zero sticks remaining.

Loop algorithm


The following pseudocode describes the remaining details of our algorithm. We refer to the OneRowNim instance as the game object, and to the KeyboardReader instance as the reader object. We use the notation game:get the number of sticks left to indicate that we are sending a message to the game object.

Create a game object with 21 sticks Create a reader object sticksLeft = game:get the number of sticks left reader:display the rules of the game while (game: the game is not over)     whoseMove = game: find out whose turn it is     if (whoseMove == user)         game: user chooses number of sticks to take     else         game: computer chooses number of sticks to take     sticksLeft = game: get the number of sticks left     reader: report the number of sticks left                                              // At this point the game is over. if game: the user is the winner        reader: report that the user wins else       reader: report that the computer wins 


In this algorithm, the initializations we perform consist of creating the game and reader objects and initializing sticksLeft. We use a while loop structure to control the game. The loop's entry condition is that the "the game is not over." This is a piece of information that comes directly from the game object. As long as "the game is not over," the body of the loop will be executed. Note that either the player or the computer makes a move in the loop's body. Again, it is up to the game object to determine whose move it is. Following the move we ask the game how many sticks are left, and we use the reader object to report this.

The loop structure has the three necessary elements. The initializer in this case is the creation of a OneRowNim object. We know that this will cause the game to have 21 sticks and it will be the user's move. The loop-entry condition is that the game is not over, based on the fact that there are still sticks remaining to be picked up. But again, this knowledge is kept by the game object. Finally, we have an updater that consists of either the computer or the user picking up some sticks. This in turn changes the value of sticksLeft on each iteration, moving us ever closer to the condition that there are no sticks left, at which point the game will be over.

Loop structure: Initializer, entry condition, updater


Left out of this algorithm are the details of the user's moves and the computer's moves. These are the kinds of actions that are good to put into separate methods, where we can worry about checking whether the user made a legal move and other such details.


[Page 178]

Figure 4.24 provides the implementation of the OneRowNimApp application. It uses a KeyboardReader as a command-line interface and a OneRowNim instance as its computational object. Thus, it has private instance variables for each of these objects, which are instantiated in the constructor method. The algorithm we just described has been placed in the run() method, which is called from main() after the application is instantiated. The use of the boolean method gameOver() to control the loop makes this code segment easier to understand. Also, it leaves it up to the game object to determine when the game is over. From an object-oriented design perspective, this is an appropriate division of responsibility. If you doubt this, imagine what could go wrong if this determination was left up to the user interface.

Figure 4.24. Definition of OneRowNimApp, a command-line interface to the OneRowNim.

public class OneRowNimApp { private KeyboardReader reader;   private OneRowNim game;   public OneRowNimApp()   { reader = new KeyboardReader();     game = new OneRowNim(21);   } // OneRowNim()   public void run()   { int sticksLeft = game.getSticks();     reader.display("Let's play One-Row Nim. You go first.\n");     reader.display("There are " + sticksLeft + " sticks left.\n");     reader.display("You can pick up 1, 2, or 3 at a time\n.");     while (game.gameOver() == false)     { if (game.getPlayer() == 1)  userMove();       else computerMove();       sticksLeft = game.getSticks();       reader.display("There are " + sticksLeft + " sticks left.\n");     } // while     if (game.getWinner() == 1)       reader.display("Game over. You win. Nice game.\n");     else  reader.display("Game over. I win. Nice game.\n");   } // run()   private void userMove()   { reader.prompt("Do you take 1, 2, or 3 sticks?: ");     int userTakes = reader.getKeyboardInteger();     if (game.takeSticks(userTakes))     { reader.display("You take " + userTakes + ".\n");     } else     { reader.display("You can't take " + userTakes + ". Try again\n");     } // else   } // userMove()   private void computerMove()   { game.takeAway(1);                                     // Temporary strategy.     reader.display("I take 1 stick. ");   } // computerMove()   public static void main(String args[])   { OneRowNimApp app = new OneRowNimApp();     app.run();   } // main() } // OneRowNimApp class 

Division of labor



[Page 179]

A user-interface programmer might end up mistakenly implementing the wrong rule for the game being over. A similar point applies to the getWinner() method. This determination rests with the game, not the user interface. If left up to the user interface, it is possible that a programming mistake could lead to the loss of the game's integrity.

The run() method calls userMove() and computerMove() to perform the specific set of actions associated with each type of move. The userMove() method uses the KeyboardReader() to prompt the user and input his or her move. It then passes the user's choice to game.takeSticks(). Note how it checks the return value to determine whether the move was legal or not and provides an appropriate response through the interface.

Finally, note how we use private methods to implement the actions associated with the user's and computer's moves. These private methods are not part of the object's interface and can only be used within the object. Thus they are, in a sense, secondary to the object's public instance methods. We sometimes refer to them as helper methods. This division of labor allows us to organize all of the details associated with the moves into a single module. The computerMove() method uses a temporary strategy of taking a single stick and passes the number 1 to game.takeSticks(). Finally, computerMove() reports its choice through the interface. After we have covered operators of the int data type in the next chapter, we will be able to describe better strategies for the computer to make a move.

This example shows how simple and straightforward it is to use our KeyboardReader user interface. In fact, for this problem, our interface didn't require any changes. Although there might be occasions where we will want to extend the functionality of KeyboardReader, it can be used without changes for a wide variety of problems in subsequent chapters.

Effective Design: Code Reuse

A well-designed user interface can be used with many computational objects.


4.5.2. A GUI for OneRowNim

The first task in designing a GUI for OneRowNim is to decide how to use input, output, and control components to interact with the user. Following the design we used in the GUI for our greeter application, we can use a JTextField for the user's input and a JTextArea for the game's output. Thus, we will use the JTextArea to report on the progress of the game and to display any error messages that arise. As in the greeter example, we can use both the JTextField and JButton as control elements and a JLabel as a prompt for the input text field. For the most part, then, the use of GUI components will remain the same as in our previous example. This is as we would expect. The relationship between the user and the interface are pretty similar in both this and the previous application.

In contrast, the relationship between the interface and the game are quite different from what we saw in the greeter application. As in that application, the GUI will still need a reference to its associated computational object, in this case the game:

private OneRowNim game; ... game = new OneRowNim(); 


The biggest difference between this GUI and the one we used with the greeter application occurs in the details of the interaction between the GUI and the game. These details are the responsibility of the actionPerformed() method, whose actions depend on the actual progress of the individual game.


[Page 180]

Unlike in the command-line version, there is no need to use a loop construct in the actionPerformed() method. Instead, because we are using event-driven programming here, we will rely on Java's event loop to move the game from one turn to another.

Java's event loop


As in the greeter example, the actionPerformed() method will be called automatically whenever the JButton is clicked. It is the responsibility of the GUI to ensure that it is the user's turn whenever this action occurs. Therefore, we design actionPerformed() so that each time it is called, it first performs the user's move and then, assuming the game is not over and an error did not occur on the user's move, it performs the computer's move. Thus, the basic algorithm is as follows:

Let the user move. If game:game is not over and computer turn    let the computer move. Game: how many sticks are left. display: report how many sticks are left If game:game is over     Stop accepting moves.     Report the winner. 


If the user picks up the last stick, the game is over. In that case, the computer does not get a move. Or perhaps the user makes an error. In that case it would still be the user's move. These possibilities have to be considered in the algorithm before the computer gets to move. As the pseudocode shows, it is the OneRowNim object's responsibility to keep track of whether the game is over and whose turn it is.

Figure 4.25 shows the complete implementation of the OneRowNimGUI class. In terms of its instance variables, constructor, and buildGUI() method, there are only a few minor differences between this GUI and the GreeterGUI (Fig. 4.20). This GUI has instance variables for its JTextField, JTextArea, and JButton, as well as one for the OneRowNim instance, its computational object. It needs to be able to refer to these objects throughout the class. Hence we give them class scope.

Figure 4.25. The OneRowNimGUI class.

import javax.swing.*; import java.awt.*; import java.awt.event.*; public class OneRowNimGUI extends JFrame implements ActionListener { private JTextArea display;   private JTextField inField;   private JButton goButton;   private OneRowNim game; 
[Page 181]
public OneRowNimGUI(String title) { game = new OneRowNim(21); buildGUI(); setTitle(title); pack(); setVisible(true); } // OneRowNimGUI() private void buildGUI() { Container contentPane = getContentPane(); contentPane.setLayout(new BorderLayout()); display = new JTextArea(20,30); display.setText("Let's play Take Away. There are " + game.getSticks() + " sticks.\n" + "Pick up 1,2, or 3 at a time.\n" + "You go first.\n"); inField = new JTextField(10); goButton = new JButton("Take Sticks"); goButton.addActionListener(this); JPanel inputPanel = new JPanel(); inputPanel.add(new JLabel("How many sticks do you take: ")); inputPanel.add(inField); inputPanel.add(goButton); contentPane.add("Center", display); contentPane.add("South", inputPanel); } // buildGUI private void userMove() { int userTakes = Integer.parseInt(inField.getText()); if (game.takeSticks(userTakes)) display.append("You take " + userTakes + ".\n"); else display.append("You can't take " + userTakes + ". Try again\n"); } // userMove() private void computerMove() { if (game.gameOver()) return; if (game.getPlayer() == 2) { game.takeSticks(1); // Temporary strategy display.append("I take one stick. "); } // if } // computerMove() private void endGame() { goButton.setEnabled(false); // Disable button and textfield inField.setEnabled(false); if (game.getWinner() == 1) display.append("Game over. You win. Nice game.\n"); else display.append("Game over. I win. Nice game.\n"); } // endGame() public void actionPerformed(ActionEvent e) { if (e.getSource() == goButton) { userMove(); computerMove(); int sticksLeft = game.getSticks(); display.append("There are " + sticksLeft + " sticks left.\n"); if (game.gameOver()) endGame(); } // if } // actionPerformed() } // OneRowNimGUI class

The constructor method plays the same role here as in the previous GUI: it creates an instance of the computational object, builds the GUI's layout, and then displays the interface on the console.


[Page 182]

All of the changes in the buildGUI() method have to do with application-specific details, such as the text we use as the prompt and the goButton's label. One new method we use here is the setText() method. Unlike the append() method, which is used to add text to the existing text in a JTextArea, the setText() method replaces the text in a JTextArea or a JTextField.

Next we will consider the private userMove() and computerMove() methods. Their roles are very similar to the corresponding methods in the command-line interface: They encapsulate the details involved in performing the players' moves. The primary difference here is that for the user move we input the user's choice from a JTextField rather than from the keyboard. We use getText() to retrieve the user's input from the JTextField, and we use Integer.parseInt() to convert it to an int value:

int userTakes = Integer.parseInt(inField.getText()); 


Another difference is that we use a JTextField to display the program's messages to the user.

As we have noted, the main differences between this and the GreeterGUI occur in the actionPerformed() method. Note how we use OneRowNim's public methods, get-Player(), gameOver(), and getWinner(), to control the interaction with the user.

One issue that differs substantially from the command-line interface is: How do we handle the end of the game? Because we are using Java's built-in event loop, the GUI will continue to respond to user events unless we stop it from doing so. One way to do this is to disable the JButton and the JTextField. By disabling a control element, we render it unable to respond to events. To do this we use the setEnabled() method, passing it the value false to, in effect, "turn off" that component:

if (game.gameOver()) {  goButton.setEnabled(false); // End the game    inField.setEnabled(false);    ... } 


Although it doesn't apply in this situation, the setEnabled() method can be used repeatedly in a GUI to turn components on and off as the context of the interaction dictates.

This example shows how simple and straightforward it can be to build a GUI for just about any application. One main design issue is deciding what kinds of input, output, and control elements to use. For most applications, we can use JTextField, JTextArea, JLabel, and JButton as the GUI's basic elements. A second design issue concerns the development of the actionPerformed() method, which must be designed in an application-specific way. Here we apply what we have learned about Java's event-programming model: we designate one or more of our elements to serve as an ActionListener, and we design algorithms to handle the action events that occur on that element.

GUI input, output, and control


Of course, for some applications we may need two JTextFields to handle input. At some point, we also might want to introduce JMenus and other advanced GUI elements. Some of these options will be introduced in upcoming chapters. Others will be covered in Chapter 13, which provides a more comprehensive view of Java's GUI capabilities.


[Page 183]

Effective Design: GUI Design

A well-designed GUI makes appropriate use of input, output, and control elements.





Java, Java, Java(c) Object-Orienting Problem Solving
Java, Java, Java, Object-Oriented Problem Solving (3rd Edition)
ISBN: 0131474340
EAN: 2147483647
Year: 2005
Pages: 275

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net