Java: Example - Slide Puzzle
The Slide Puzzle program displays a grid of tiles that must be arranged in ascending order. The user can click on any tile next to the empty space to move the tile to that space. The game starts with the tiles in a random arrangement. The two diagrams below show the start and final positions of the puzzle.
Program organization. The program is divided into three source files:
- A standard
main
that creates the GUI and does nothing else. - A GUI made by subclassing JPanel. This creates an instance of the logic of the program (SlidePuzzleModel) and calls its methods.
- The logic (model) of the slide puzzle. This has no knowledge of who's calling it, and could be used by other user interfaces (text, web, ...).
Start | Finish |
---|---|
Main program
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
// SlidePuzzle.java - Puzzle to slide pieces to correct position. // Fred Swartz, 2003-May, 2004-May // The SlidePuzzle program consists of three files: // SlidePuzzle.java - this file with main to create window. // SlidePuzzleGUI.java - implements the GUI interface. // SlidePuzzleModel.java - the logical functioning. import javax.swing.JFrame; ///////////////////////////////////////////// class SlidePuzzle class SlidePuzzle { //============================================= method main public static void main(String[] args) { JFrame window = new JFrame("Slide Puzzle"); window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); window.setContentPane(new SlidePuzzleGUI()); window.pack(); // finalize layout window.show(); // make window visible window.setResizable(false); }//end main }//endclass SlidePuzzle |
Graphical User Interface
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 |
// SlidePuzzleGUI.java - GUI for SlidePuzzle // Fred Swartz, 2003-May-10, 2004-May-3 // // The SlidePuzzleGUI class creates a panel which // contains two subpanels. // 1. In the north is a subpanel for controls (just a button now). // 2. In the center a graphics // This needs a few improvements. // Both the GUI and Model define the number or rows and columns. // How would you set both from one place? import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.event.*; /////////////////////////////////////////////////// class SlidePuzzleGUI // This class contains all the parts of the GUI interface class SlidePuzzleGUI extends JPanel { //=============================================== instance variables private GraphicsPanel _puzzleGraphics; private SlidePuzzleModel _puzzleModel = new SlidePuzzleModel(); //end instance variables //====================================================== constructor public SlidePuzzleGUI() { //--- Create a button. Add a listener to it. JButton newGameButton = new JButton("New Game"); newGameButton.addActionListener(new NewGameAction()); //--- Create control panel JPanel controlPanel = new JPanel(); controlPanel.setLayout(new FlowLayout()); controlPanel.add(newGameButton); //--- Create graphics panel _puzzleGraphics = new GraphicsPanel(); //--- Set the layout and add the components this.setLayout(new BorderLayout()); this.add(controlPanel, BorderLayout.NORTH); this.add(_puzzleGraphics, BorderLayout.CENTER); }//end constructor //////////////////////////////////////////////// class GraphicsPanel // This is defined inside the outer class so that // it can use the outer class instance variables. class GraphicsPanel extends JPanel implements MouseListener { private static final int ROWS = 3; private static final int COLS = 3; private static final int CELL_SIZE = 80; // Pixels private Font _biggerFont; //================================================== constructor public GraphicsPanel() { _biggerFont = new Font("SansSerif", Font.BOLD, CELL_SIZE/2); this.setPreferredSize( new Dimension(CELL_SIZE * COLS, CELL_SIZE*ROWS)); this.setBackground(Color.black); this.addMouseListener(this); // Listen own mouse events. }//end constructor //=======================================x method paintComponent public void paintComponent(Graphics g) { super.paintComponent(g); for (int r=0; r<ROWS; r++) { for (int c=0; c<COLS; c++) { int x = c * CELL_SIZE; int y = r * CELL_SIZE; String text = _puzzleModel.getFace(r, c); if (text != null) { g.setColor(Color.gray); g.fillRect(x+2, y+2, CELL_SIZE-4, CELL_SIZE-4); g.setColor(Color.black); g.setFont(_biggerFont); g.drawString(text, x+20, y+(3*CELL_SIZE)/4); } } } }//end paintComponent //======================================== listener mousePressed public void mousePressed(MouseEvent e) { //--- map x,y coordinates into a row and col. int col = e.getX()/CELL_SIZE; int row = e.getY()/CELL_SIZE; if (!_puzzleModel.moveTile(row, col)) { // moveTile moves tile if legal, else returns false. Toolkit.getDefaultToolkit().beep(); } this.repaint(); // Show any updates to model. }//end mousePressed //========================================== ignore these events public void mouseClicked (MouseEvent e) {} public void mouseReleased(MouseEvent e) {} public void mouseEntered (MouseEvent e) {} public void mouseExited (MouseEvent e) {} }//end class GraphicsPanel ////////////////////////////////////////// inner class NewGameAction public class NewGameAction implements ActionListener { public void actionPerformed(ActionEvent e) { _puzzleModel.reset(); _puzzleGraphics.repaint(); } }//end inner class NewGameAction }//end class SlidePuzzleGUI |
Logic / Model
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 |
// SlidePuzzleModel.java - Slide pieces to correct position. // Fred Swartz, 2003-May-10 /////////////////////////////////////////// class SlidePuzzleModel class SlidePuzzleModel { private static final int ROWS = 3; private static final int COLS = 3; private Tile[][] _contents; // All tiles. private Tile _emptyTile; // The empty space. //================================================= constructor public SlidePuzzleModel() { _contents = new Tile[ROWS][COLS]; reset(); // Initialize and shuffle tiles. }//end constructor //===================================================== getFace // Return the string to display at given row, col. String getFace(int row, int col) { return _contents[row][col].getFace(); }//end getFace //======================================================= reset // Initialize and shuffle the tiles. public void reset() { for (int r=0; r<ROWS; r++) { for (int c=0; c<COLS; c++) { _contents[r][c] = new Tile(r, c, "" + (r*COLS+c+1)); } } //--- Set last tile face to null to mark empty space _emptyTile = _contents[ROWS-1][COLS-1]; _emptyTile.setFace(null); //-- Shuffle - Exchange each tile with random tile. for (int r=0; r<ROWS; r++) { for (int c=0; c<COLS; c++) { exchangeTiles(r, c, (int)(Math.random()*ROWS) , (int)(Math.random()*COLS)); } } }//end reset //==================================================== moveTile // Move a tile to empty position beside it, if possible. // Return true if it was moved, false if not legal. public boolean moveTile(int r, int c) { //--- It's a legal move if the empty cell is next to it. return checkEmpty(r, c, -1, 0) || checkEmpty(r, c, 1, 0) || checkEmpty(r, c, 0, -1) || checkEmpty(r, c, 0, 1); }//end moveTile //================================================== checkEmpty // Check to see if there is an empty position beside tile. // Return true and exchange if possible, else return false. private boolean checkEmpty(int r, int c, int rdelta, int cdelta) { int rNeighbor = r + rdelta; int cNeighbor = c + cdelta; //--- Check to see if this neighbor is on board and is empty. if (isLegalRowCol(rNeighbor, cNeighbor) && _contents[rNeighbor][cNeighbor] == _emptyTile) { exchangeTiles(r, c, rNeighbor, cNeighbor); return true; } return false; }//end checkEmpty //=============================================== isLegalRowCol // Check for legal row, col public boolean isLegalRowCol(int r, int c) { return r>=0 && r<ROWS && c>=0 && c<COLS; }//end isLegalRowCol //=============================================== exchangeTiles // Exchange two tiles. private void exchangeTiles(int r1, int c1, int r2, int c2) { Tile temp = _contents[r1][c1]; _contents[r1][c1] = _contents[r2][c2]; _contents[r2][c2] = temp; }//end exchangeTiles //=================================================== isGameOver public boolean isGameOver() { for (int r=0; r<ROWS; r++) { for (int c=0; c<ROWS; c++) { Tile trc = _contents[r][c]; return trc.isInFinalPosition(r, c); } } //--- Falling thru loop means nothing out of place. return true; }//end isGameOver }//end class SlidePuzzleModel ////////////////////////////////////////////////////////// class Tile // Represents the individual "tiles" that slide in puzzle. class Tile { //============================================ instance variables private int _row; // row of final position private int _col; // col of final position private String _face; // string to display //end instance variables //==================================================== constructor public Tile(int row, int col, String face) { _row = row; _col = col; _face = face; }//end constructor //======================================================== setFace public void setFace(String newFace) { _face = newFace; }//end getFace //======================================================== getFace public String getFace() { return _face; }//end getFace //=============================================== isInFinalPosition public boolean isInFinalPosition(int r, int c) { return r==_row && c==_col; }//end isInFinalPosition }//end class Tile |
Exercises
Here are some possible modifications to the Slide Puzzle program.
- Add a text field to the GUI that shows the number of moves that the user has made. Hint: The graphics panel knows when moves are made, and should make a change to a text field instance variable in the enclosing class.
- Allow a click on any tile in a row or column containing the empty space to move all the tiles in the direction of the empty space.
- Currently, the number of rows and columns is set in two places, the GUI and the logic. They should only be set from one place. Fix it. Hint: These values could be passed into a constructor.