# Java: Example - Five-in-a-row

| Two players alternate making moves. The player who places five pieces in a row wins. The source program consists of three files: a short main program, a graphical user interface, and a logic class. The logic class knows nothing about the GUI, and could just as easily be used with a text interface or by a web interface.

There's a lot of room for improvement. Some possible exercises:

1. This game may have originated on a Go board where the pieces are placed on intersections, not in empty spaces. Change the program to put pieces on the intersections.
2. The Undo button doesn't do anything. Fix it in both the GUI and in logic
3. A more challenging problem is to write a machine player, which should be defined as an interface so that it would be possible to use many classes as players, allowing machine vs. machine games for example. The machine player should be passed a copy of the state of the game, the logic object, so that it doesn't mess up the current state.

### The main program

      1
      2
      3
      4
      5
      6
      7
      8
      9
     10
     11
     12
     13
     14
     15
     16
     17
     18
     19
     20
     21
     22
     23
    // Five.java - main program for Five-In-A-Row Program
    import javax.swing.JFrame;

    ////////////////////////////////////////////////////// class Five
    /** Five.java - Winner is able to put 5 pieces in a row.
       The Five program consists of three files:
       Five.java      - this file with main to create window.
       FiveGUI.java   - implements the GUI interface.
       FiveLogic.java - the logical functioning.
       @author Fred Swartz
       @version 2004-05-02
       */
    class Five {
        //================================================ method main
        public static void main(String[] args) {
            JFrame window = new JFrame("Five In A Row");
            window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            window.setContentPane(new FiveGUI());
            window.pack();  // finalize layout
            window.setResizable(false);
            window.show();  // make window visible
        }//end main
    }//endclass Five

### The GUI, implemented as a subclass of JPanel

      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
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    // FiveGUI.java
    import java.awt.*;
    import java.awt.event.*;
    import javax.swing.*;
    import javax.swing.event.*;

    /////////////////////////////////////////////////// class FiveGUI
    /** A Graphical User Interface for a Five-In-A-Row game.
        This implements the user interface (view and controller),
        but the logic (model) is implemented in a separate class that
        knows nothing about the user interface.
        <p>This subclasses JPanel and puts the some buttons in the north,
        a graphical display of the board in the center, and
        a status field in the south.
        </p>
        <p>Exercise: This game probably originated on a Go board where
           the pieces are placed on the intersections, not in the
           empty spaces.  Change the program to put all pieces on the
           intersections.
        </p>
        <p>Exercise: The Undo button doesn't do anything.  Fix it here
           in the GUI and in the logic.
        </p>
        <p>Exercise: Create a machine player.</p>
        @author Fred Swartz
        @version 2004-05-02 Rodenbach
        */
    class FiveGUI extends JPanel {
        //=============================================== instance variables
        private GraphicsPanel boardDisplay_;
        private JTextField    statusField_ = new JTextField();
        private FiveLogic     gameLogic_ = new FiveLogic(9, 9);
        private boolean       gameOver_ = false;

        private static final Color[]  PLAYER_COLOR = {null, Color.BLACK, Color.WHITE};
        private static final String[] PLAYER_NAME  = {null, "BLACK", "WHITE"};

        //====================================================== constructor
        public FiveGUI() {
            //--- Create some buttons
            JButton newGameButton = new JButton("New Game");
            JButton undoButton = new JButton("Undo");

            //--- Create control panel
            JPanel controlPanel = new JPanel();
            controlPanel.setLayout(new FlowLayout());
            controlPanel.add(newGameButton);
            controlPanel.add(undoButton);

            //--- Create graphics panel
            boardDisplay_ = new GraphicsPanel();

            //--- Set the layout and add the components
            this.setLayout(new BorderLayout());
            this.add(controlPanel , BorderLayout.NORTH);
            this.add(boardDisplay_, BorderLayout.CENTER);
            this.add(statusField_ , BorderLayout.SOUTH);

            //-- Add action listeners
            newGameButton.addActionListener(new NewGameAction());
        }//end constructor

        //////////////////////////////////////////////// class GraphicsPanel
        // This is defined inside the outer class so that
        // it can use the game logic variable.
        class GraphicsPanel extends JPanel implements MouseListener {
            private static final int ROWS = 9;
            private static final int COLS = 9;
            private static final int CELL_SIZE = 30; // Pixels
            private static final int WIDTH  = COLS * CELL_SIZE;
            private static final int HEIGHT = ROWS * CELL_SIZE;

            //================================================== constructor
            public GraphicsPanel() {
                this.setPreferredSize(new Dimension(WIDTH, HEIGHT));
                this.setBackground(Color.GRAY);
                this.addMouseListener(this);  // Listen own mouse events.
            }//end constructor

            //============================================== paintComponent
            public void paintComponent(Graphics g) {
                super.paintComponent(g);
                //-- Paint grid (could be done once and saved).
                for (int r=1; r<ROWS; r++) {  // Horizontal lines
                    g.drawLine(0, r*CELL_SIZE, WIDTH, r*CELL_SIZE);
                }
                for (int c=1; c<COLS; c++) {
                    g.drawLine(c*CELL_SIZE, 0, c*CELL_SIZE, HEIGHT);
                }

                //-- Draw players pieces.
                for (int r=0; r<ROWS; r++) {
                    for (int c=0; c<COLS; c++) {
                        int x = c * CELL_SIZE;
                        int y = r * CELL_SIZE;
                        int who = gameLogic_.getPlayerAt(r, c);
                        if (who != gameLogic_.EMPTY) {
                            g.setColor(PLAYER_COLOR[who]);
                            g.fillOval(x+2, y+2, CELL_SIZE-4, 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;

                int currentOccupant = gameLogic_.getPlayerAt(row, col);
                if (!gameOver_ && currentOccupant == gameLogic_.EMPTY) {
                    gameLogic_.move(row, col);
                    switch (gameLogic_.getGameStatus()) {
                        case 1: // Player one wins.  Game over.
                                gameOver_ = true;
                                statusField_.setText("BLACK WINS");
                                break;
                        case 2: // Player two wins.  Game over.
                                gameOver_ = true;
                                statusField_.setText("WHITE WINS");
                                break;

                        case FiveLogic.TIE:  // Tie game.  Game over.
                                gameOver_ = true;
                                statusField_.setText("TIE GAME");
                                break;

                        default: showNextPlayer();
                    }

                } else {  // Not legal
                    Toolkit.getDefaultToolkit().beep();
                }

                this.repaint();  // Show any updates to game.
            }//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 inner class GraphicsPanel

        //======================================= untility method showNextPlayer
        private void showNextPlayer() {
           statusField_.setText(PLAYER_NAME[gameLogic_.getNextPlayer()] + " to play");
        }//end showNextPlayer

        ///////////////////////////////////////// inner class NewGameAction
        private class NewGameAction implements ActionListener {
            public void actionPerformed(ActionEvent e) {
                gameLogic_.reset();
                gameOver_ = false;
                showNextPlayer();
                boardDisplay_.repaint();
            }
        }//end inner class NewGameAction

    }//end class FiveGUI

### The logic class

      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
    // FiveLogic.java - Game to get 5 pieces in a row.
    ///////////////////////////////////////////////// class FiveLogic
    /** This class implements the logic (model) for the game of
        Five-In-A-Row.
        <br/>Exercise: The undo function doesn't do anything.  Fix it.
        @author Fred Swartz
        @version 2004-05-02
        */
    class FiveLogic {
        /** Number of board rows. */
        private int maxRows_;
        /** Number of board columns. */
        private int maxCols_;
        /** The board. */
        private int[][] board_;
        /** The player who moves next. */
        private int     nextPlayer_;
        /** Number of moves in the game. */
        private int     moves_ = 0;

        //-- Constants
        public  static final int EMPTY   = 0;  // The cell is empty.
        private static final int PLAYER1 = 1;
        public  static final int TIE     = -1; // Game is a tie (draw).

        //================================================== constructor
        public FiveLogic(int rows, int cols) {
            maxRows_ = rows;
            maxCols_ = cols;
            board_ = new int[maxRows_][maxCols_];
            reset();
        }//end constructor

        //================================================= getNextPlayer
        /** Returns the next player. */
        public int getNextPlayer() {
            return nextPlayer_;
        }//end getFace

        //=================================================== getPlayerAt
        /** Returns player who has played at particular row and column. */
        public int getPlayerAt(int r, int c) {
            return board_[r][c];
        }//end getPlayerAt

        //========================================================== reset
        /** Clears board to initial state. Makes first move in center. */
        public void reset() {
            for (int r=0; r<maxRows_; r++) {
                for (int c=0; c<maxCols_; c++) {
                    board_[r][c] = EMPTY;
                }
            }
            moves_ = 0;  // No moves so far.
            nextPlayer_ = PLAYER1;
            //-- Make first move in center.
            move(maxCols_/2, maxRows_/2);  // First player moves to center
        }//end reset

        //=========================================================== move
        /** Play a marker on the board, record it, flip players. */
        public void move(int r, int c) {
            assert board_[r][c] == EMPTY;
            board_[r][c] = nextPlayer_;  // Record this move.
            nextPlayer_ = 3-nextPlayer_; // Flip players
            moves_++;                    // Increment number of moves.
        }//end move

        //=========================================================== undo
        /** Undo the last move made.  Don't go beyond beginning. */
        public void undo() {
            // UNIMPLEMENTED.
        }//end undo

        //========================================== utility method count5_
        /** The count5_ utility function returns true if there are five in
            a row starting at the specified r,c position and
            continuing in the dr direcection (+1, -1) and
            similarly for the column c.
            */
        private boolean count5_(int r, int dr, int c, int dc) {
            int player = board_[r][c];  // remember the player.
            for (int i=1; i<5; i++) {
                if (board_[r+dr*i][c+dc*i] != player) return false;
            }
            return true;  // There were 5 in a row!
        } // count5_

        //=================================================== getGameStatus
        /** -1 = game is tie, 0 = more to play,
             1 = player1 wins, 2 = player2 wins */
        public int getGameStatus() {
            int row;
            int col;
            int n_up, n_right, n_up_right, n_up_left;

            boolean at_least_one_move;   // true if game isn't a tie

            for (row = 0; row < maxRows_; row++) {
                for (col = 0; col < maxCols_; col++) {
                    int p = board_[row][col];
                    if (p != EMPTY) {
                        // look at 4 kinds of rows of 5
                        //  1. a column going up
                        //  2. a row going to the right
                        //  3. a diagonal up and to the right
                        //  4. a diagonal up and to the left

                        if (row < maxRows_-4) // Look up
                            if (count5_(row, 1, col, 0)) return p;

                        if (col < maxCols_-4) { // row to right
                            if (count5_(row, 0, col, 1))  return p;

                            if (row < maxRows_-4) { // diagonal up to right
                                if (count5_(row, 1, col, 1)) return p;
                            }
                        }

                        if (col > 3 && row < maxRows_-4) { // diagonal up left
                            if (count5_(row, 1, col, -1)) return p;
                        }
                    }//endif position wasn't empty
                }//endfor row
            }//endfor col

            // Neither player has won, it's tie if there are empty positions.
            // Game is finished if total moves equals number of positions.
            if (moves_ == maxRows_*maxCols_) {
                return TIE; // Game tied.  No more possible moves.
            } else {
                return 0;  // More to play.
            }
        }//end getGameStatus

    }//end class FiveLogic

## Exercises

1. Make the Undo button on the Five-in-a-row program work. That means adding a button listener in the FiveGUI class. It will have to call the undo() method in the FiveLogic class. The undo() method is defined, but it doesn't do anything, so you'll have to write it. The way undo (in all programs) works is that the program keeps a history of what was done in a LIFO (Last In, First Out) manner, implemented as an array (bounded number of undos) or ArrayList (unbounded). When an Undo is requested, the last thing is popped of the end of the history and "undone". That's pretty simple here -- whenever someone calls move(), just add that move to the end of a history array. If undo() is ever called, take the last element off the end of the history array and clear that position from the board.