# 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:

1. A standard `main` that creates the GUI and does nothing else.
2. A GUI made by subclassing JPanel. This creates an instance of the logic of the program (SlidePuzzleModel) and calls its methods.
3. 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.

1. 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.
2. 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.
3. 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.