9.12. A GUI-Based Game (Optional Graphics)Most modern computer games do not use a command-line interface. We now address this shortcoming by expanding our ComputerGame hierarchy so that it works with Graphical User Interfaces (GUIs) as well as Command-Line User Interfaces (CLUIs). The Sliding Tile Puzzle is a puzzle game. It is played by one player, a human. The puzzle consists of six tiles arranged on a board containing seven spaces. Three of the tiles are labeled L and three are labeled R. Initially the tiles are arranged as RRR_LLL. In other words, the R tiles are arranged to the left of the L tiles, with a blank space in the middle. The object of the puzzle is to rearrange the tiles into LLL_RRR. The rules are that tiles labeled R can only move right. Tiles labeled L can only move left. Tiles may move directly into the blank space or they can jump over one tile into the blank space. Our purpose in this section is to develop a GUI that plays this game. An appropriate GUI is shown Figure 9.35. Here the tiles and the blank space are represented by an array of buttons. To make a move the user clicks on the "tile" he or she wishes to move. The GUI will assume that the user wants to move that tile into the blank space. If the proposed move is legal, the GUI will carry out the move. Otherwise it will just ignore it. For example, if the user were to click on the third R button from the left, a legal move, the GUI would rearrange the labels on the buttons so that their new configuration would be RR_RLLL. On the other hand, if the user were to click on the rightmost L button, the GUI would ignore that move because it is illegal. Figure 9.35. The Sliding Tile Puzzle.
9.12.1. The GUIPlayableGame InterfaceHow should we extend our game-playing hierarchy to accommodate GUI-based games? As we learned in Chapter 4, one difference between GUI-based applications and CLUI-based applications is the locus of control. In a CLUI-based application, control resides in the computational object. For games, this is the game object. That is why the play() method in our CLUI-based games contains the game's control loop. By contrast, control resides in the GUI's event loop in GUI-based applications. That is why we learned how to manage Java's event hierarchy in Chapter 4. Thus, in the GUI shown in Figure 9.35, the GUI will listen and take action when the user clicks one of its buttons. However, given that control will reside in the GUI, there is still a need for communication between the GUI and the game object. In the CLUI-based games, we have used the CLUIPlayableGame interface to manage the communication between the game and the user interface. We will follow the same design strategy in this case. Thus, we will design a GUIPlayableGame interface that can be implemented by any game that wishes to use a GUI (Fig. 9.36). Figure 9.36. The GUIPlayableGame interface extends the IGame interface. |
public class SlidingTilePuzzle extends ComputerGame implements GUIPlayableGame{ private char puzzle[] = {'R','R','R',' ','L','L','L'}; private String solution = "LLL RRR"; private int blankAt = 3; public SlidingTilePuzzle() { super(1); } public boolean gameOver() { // True if puzzle solved StringBuffer sb = new StringBuffer(); sb.append(puzzle); return sb.toString().equals(solution); } public String getWinner() { if (gameOver()) return "\nYou did it! Very Nice!\n"; else return "\nGood try. Try again!\n"; } |
Let's now implement a GUI that can be used to play the sliding tile puzzle. We will model the GUI after those we designed in Chapter 4.
Figure 9.39 provides a summary of the design. As an implementor of the ActionListener interface, SlidingGUI implements the actionPerformed() method, which is where the code that controls the puzzle is located. The main data structure is an array of seven JButtons, representing the seven tiles in the puzzles. The buttons' labels will reflect the state of the puzzle. They will be rearranged after every legal move by the user. The reset button is used to reinitialize the game. This allows users to play again or to start over if they get stuck.
The puzzleState is a String variable that stores the puzzle's current state, which is updated repeatedly from the SlidingTilePuzzle by calling its reportGameState() method. The private labelButtons() method will read the puzzleState and use its letters to set the labels of the GUI's buttons.
The implementation of SlidingGUI is shown in Figure 9.40. Its constructor and buildGUI() methods are responsible for setting up the GUI. We use a for loop in buildGUI() to create the JButtons, associate an ActionListener with them, and add them to the GUI. Except for the fact that we have an array of buttons, this is very similar to the GUI created in Chapter 4. Recall that associating an ActionListener with the buttons allows the program to respond to button clicks in its actionPerformed() method.
import javax.swing.*; import java.awt.*; import java.awt.event.*; public class SlidingGUI extends JFrame implements ActionListener { private JButton tile[] = new JButton[7]; private JButton reset = new JButton("Reset"); private SlidingTilePuzzle sliding; private String puzzleState; private Label label; private String prompt = "Goal: [LLL RRR]. " + " Click on the tile you want to move." + " Illegal moves are ignored."; public SlidingGUI(String title) { sliding = new SlidingTilePuzzle(); buildGUI(); setTitle(title); pack(); setVisible(true); } // SlidingGUI() private void buildGUI() { Container contentPane = getContentPane(); contentPane.setLayout(new BorderLayout()); JPanel buttons = new JPanel(); puzzleState = sliding.reportGameState(); for (int k = 0; k < tile.length; k++) { tile[k] = new JButton(""+puzzleState.charAt(k)); tile[k].addActionListener(this); buttons.add(tile[k]); } |
Note how an instance of the SlidingTilePuzzle is created in the constructor, and how its state is retrieved and stored in the puzzleState variable:
puzzleState = sliding.reportGameState();
The labelButtons() method transfers the letters in puzzleState onto the buttons.
The most important method in the GUI is the actionPerformed() method. This method controls the GUI's actions and is called automatically whenever one of the GUI's buttons is clicked. First, we check whether the reset button has been clicked. If so, we reset the puzzle by creating a new instance of SlidingTilePuzzle and reinitializing the prompt label.
Next we use a for loop to check whether one of the tile buttons has been clicked. If so, we use the loop index, k, as the tile's identification and submit this to the puzzle as the user's move:
if (e.getSource() == tile[k]) result = ((GUIPlayableGame)sliding).submitUserMove(""+ k);
The cast operation is necessary here because we declared sliding as a SlidingTilePuzzle rather than as a GUIPlayableGame. Note also that we have to convert k to a String when passing it to submitUserMove().
As a result of this method call, the puzzle returns a result, which is checked to see if the user's move was illegal. If result contains the word "illegal", the computer beeps to signal an error:
if (result.indexOf("illegal") != -1) java.awt.Toolkit.getDefaultToolkit().beep();
The java.awt.Toolkit is a class that contains lots of useful methods, including the beep() method. Note that no matter what action is performed, a reset or a tile click, we update puzzleState by calling reportGameState() and use it to relabel the tile buttons. The last task in the actionPerformed() method is to invoke the puzzle's gameOver() method to check whether the user has successfully completed the puzzle. If so, we display a congratulatory message in the GUI's window.
Finally, the main() for a GUI is very simple, consisting of a single line of code:
new SlidingGUI("Sliding Tile Puzzle");
Once a SlidingGUI is created, with the title "Sliding Tile Puzzle", it will open a window and manage the control of the puzzle.