Activity 9.12.1.
Try the Sliding Tile Puzzle below
ComputerGame
hierarchy so that it works with Graphical User Interfaces (GUIs) as well as Command-Line User Interfaces (CLUIs).GUIPlayableGame
Interface
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’s why, we learned how to manage Java’s event hierarchy in Chapter 4. Thus, in the GUI shown in Figure 9.12.1, the GUI will listen and take action when the user clicks one of its buttons.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 (Figure 9.12.2).GUIPlayableGame
interface.String
s to communicate between the user interface and the game object. Thus, a method with the following signature will enable us to submit a String
representing the user’s move to the game and receive in return a String
representing the game object’s response to the move:submitUserMove(String move): String;
reportGameState()
and getGamePrompt()
methods that are part of the IGame
interface. The design shown in Figure 9.12.2 leads to the following definition for the GUIPlayableGame
interface:public interface GUIPlayableGame extends IGame {
public String submitUserMove(String theMove);
}
IGame
, this interface inherits the getGamePrompt()
and reportGameState()
from the IGame
interface. The GUI should be able to communicate with any game that implements this interface.SlidingTilePuzzle
SlidingTilePuzzle
itself. Its design is summarized in Figure 9.12.3. Most of the methods should be familiar to you, because the design closely follows the design we employed in the WordGuess
example. It has implementations of inherited methods from the ComputerGame
class and the GUIPlayableGame
interface.SlidingTilePuzzle
design.char
. We will store the puzzle’s solution in a Java String
and we will use an int
variable to keep track of where the blank space is in the array. This leads to the following class-level declarations:private char puzzle[] = {'R','R','R',' ','L','L','L'};
private String solution = "LLL RRR";
private int blankAt = 3;
puzzle
array with the initial configuration of seven characters. Taken together, these statements initialize the puzzle’s state.public SlidingTilePuzzle() {
super(1);
}
String
. This is the purpose of the reportGameState()
method:public String reportGameState() {
StringBuffer sb = new StringBuffer();
sb.append(puzzle);
return sb.toString();
}
submitUserMove()
method:public String submitUserMove(String usermove) {
int tile = Integer.parseInt(usermove);
char ch = puzzle[tile];
if (ch == 'L' && (blankAt == tile-1 || blankAt == tile-2))
swapTiles(tile,blankAt);
else if (ch == 'R' && (blankAt == tile+1 || blankAt == tile+2))
swapTiles(tile,blankAt);
else
return "That's an illegal move.\n";
return "That move is legal.\n";
}
String
to the submitUserMove()
method. Given the index number of the tile that was selected by the user, this method determines if the move is legal.Integer.parseInt()
method is used to extract the tile’s index from the method’s parameter. This index is used to get a ’tile’ from the puzzle
array.swap()
method. In either case, the method returns a string reporting whether the move was legal or illegal.SlidingTilePuzzle
, the remaining details of which are straight forward.SlidingTilePuzzle
class.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";
}
public String reportGameState() {
StringBuffer sb = new StringBuffer();
sb.append(puzzle);
return sb.toString();
}
public String getGamePrompt() {
return "To move a tile, click on it.";
} //prompt()
public String submitUserMove(String usermove) {
int tile = Integer.parseInt(usermove);
char ch = puzzle[tile];
if (ch=='L' && (blankAt==tile-1 || blankAt==tile-2))
swapTiles(tile,blankAt);
else if (ch=='R' && (blankAt==tile+1 || blankAt==tile+2))
swapTiles(tile,blankAt);
else
return "That's an illegal move.\n";
return "That move is legal.\n";
}
private void swapTiles(int ti, int bl) {
char ch = puzzle[ti];
puzzle[ti] = puzzle[bl];
puzzle[bl] = ch;
blankAt = ti; // Reset the blank
}
} //SlidingTilePuzzle
SlidingGUI
Class
SlidingGUI
design.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 JButton
s, 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.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.SlidingGUI
is shown in Listing 9.12.6. Its constructor and buildGUI()
methods are responsible for setting up the GUI. We use of a for
loop in buildGUI()
to create the JButton
s, 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.SlidingTilePuzzle
is created in the constructor, and how its state is retrieved and stored in the puzzleState
variable:puzzleState = sliding.reportGameState();
SlidingGUI
class.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]);
}
reset.addActionListener(this);
label = new Label(prompt);
buttons.add(reset);
contentPane.add("Center", buttons);
contentPane.add("South", label);
} // buildGUI()
private void labelButtons(String s) {
for (int k = 0; k < tile.length; k++)
tile[k].setText(""+ s.charAt(k));
} // labelButtons()
public void actionPerformed(ActionEvent e) {
String result = "";
if (e.getSource() == reset) { // Reset clicked?
sliding = new SlidingTilePuzzle();
label.setText(prompt);
}
for (int k = 0; k < tile.length; k++) // Tile clicked?
if (e.getSource() == tile[k])
result = ((GUIPlayableGame)sliding).submitUserMove(""+ k);
if (result.indexOf("illegal") != -1)
java.awt.Toolkit.getDefaultToolkit().beep();
puzzleState = sliding.reportGameState();
labelButtons(puzzleState);
if (sliding.gameOver())
label.setText("You did it! Very nice!");
} // actionPerformed()
public static void main(String args[]) {
new SlidingGUI("Sliding Tile Puzzle");
} // main()
} // SlidingGUI
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 re-initializing the prompt label.if (e.getSource() == tile[k])
result = ((GUIPlayableGame)sliding).submitUserMove(""+ k);
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()
.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();
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 if the user has successfully completed the puzzle. If so, we display a congratulatory message in the GUI’s window.main()
for a GUI is very simple, consisting of a single line of code:new SlidingGUI("Sliding Tile Puzzle");
SlidingGUI
is created, with the title of “Sliding Tile Puzzle,” it will open a window and manage the control of the puzzle.