Java Notes

Example: Sudoku v3

Here is a partial solution to writing a Sudoku program. It has a very clunky user interface using text boxes and doesn't do any checking. It's divided into three classes:

  1. Sudoku.java is main JFrame subclass, which has most of the user interface code. It also creates a new model object.
  2. SudokuBoardDisplay.java is a subclass of JComponent, and it used for the graphical display of the sudoku board. Its constructor is passed a reference to the model created in the main program. It interrogates the model to determine with to draw.
  3. SudokuModel.java contains the model, the logic, of the game. It knows nothing about the user interface, and could equally well be used with a console or web interface.

sample sudoku puzzle

Main JFrame subclass

These source files can be copied and pasted into your IDE from http://www.fredosaurus.com/notes-java/GUI/examples/sudoku/ex-sudoku-v3.html.

  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 
// File   : gui/sudoku/sudoku3/Sodoku.java
// Description: Initializes and displays board.
//          Uses text components to make move.
// Author : Fred Swartz - 21 Sep 2006 - Placed in public domain.
// Enhancements needed:
//          * Use mouse for input instead of text fields.
//          * Check for legal board positions on initialization and move.
//          * Don't allow initial cell values to be changed.

package sudoku3;

import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;

public class Sudoku extends JFrame {
    ///
    private static final String INITIAL_BOARD =
            "8156....4/" +
            "6...75.8./" +
            "....9..../" +
            "9...417../" +
            ".4.....2./" +
            "..623...8/" +
            "....5..../" +
            ".5.91...6/" +
            "1....7895";
    
    //=================================================================== fields
    private SudokuModel        _sudokuLogic = new SudokuModel(INITIAL_BOARD);
    private SudokuBoardDisplay _sudokuBoard = new SudokuBoardDisplay(_sudokuLogic);
    
    private JTextField _rowTF = new JTextField(2);
    private JTextField _colTF = new JTextField(2);
    private JTextField _valTF = new JTextField(2);
    
    //============================================================== constructor
    public Sudoku() {
        // 1... Create/initialize components
        JButton moveBtn = new JButton("Move");
        
        JPanel controlPanel = new JPanel();
        controlPanel.setLayout(new FlowLayout());
        controlPanel.add(new JLabel("Row (1-9):"));
        controlPanel.add(_rowTF);
        controlPanel.add(new JLabel("Col (1-9):"));
        controlPanel.add(_colTF);
        controlPanel.add(new JLabel("Val:"));
        controlPanel.add(_valTF);
        controlPanel.add(moveBtn);
        
        //... Add listener
        moveBtn.addActionListener(new MoveListener());
        
        // 2... Create content panel, set layout
        JPanel content = new JPanel();
        content.setLayout(new BorderLayout());
        
        // 3... Add the components to the content panel.
        content.add(controlPanel, BorderLayout.NORTH);
        content.add(_sudokuBoard, BorderLayout.CENTER);
        
        // 4... Set this window's attributes, and pack it.
        setContentPane(content);
        setTitle("Sudoku 3");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setResizable(false);               // Don't let user resize it.
        pack();
        setLocationRelativeTo(null);       // Center it.
    }
    
    
    ///////////////////////////////////////////////////////////// MoveListener
    class MoveListener implements ActionListener {
        public void actionPerformed(ActionEvent ae) {
            try {
                //... Translate row and col to zero-based indexes.
                int row = Integer.parseInt(_rowTF.getText().trim()) - 1;
                int col = Integer.parseInt(_colTF.getText().trim()) - 1;
                int val = Integer.parseInt(_valTF.getText().trim());
                if (_sudokuLogic.isLegalMove(row, col, val)) {
                    _sudokuLogic.setVal(row, col, val);
                    _sudokuBoard.repaint();
                } else {
                    JOptionPane.showMessageDialog(null, "Illegal row, col, or value.");
                }
                
            } catch (NumberFormatException nfe) {
                JOptionPane.showMessageDialog(null, "Please enter numeric values.");
            }
        }
    }
    
    //===================================================================== main
    public static void main(String[] args) {
        new Sudoku().setVisible(true);
    }
}

Board drawing component

  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 
// File   : gui/sudoku/sudoku3/SudokuBoardDisplay.java
// Purpose: Draws a Sudoku board give a model.
// Author : Fred Swartz - 23 Sep 2006 - Placed in public domain.

package sudoku3;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import javax.swing.JComponent;

///////////////////////////////////////////////////////////// SudokuBoardDisplay
public class SudokuBoardDisplay  extends JComponent {
    //================================================================ constants
    private static final int CELL_PIXELS = 50;  // Size of each cell.
    private static final int PUZZLE_SIZE = 9;   // Number of rows/cols
    private static final int SUBSQUARE   = 3;   // Size of subsquare.
    private static final int BOARD_PIXELS = CELL_PIXELS * PUZZLE_SIZE;
    private static final int TEXT_OFFSET = 15;  // Fine tuning placement of text.
    private static final Font TEXT_FONT  = new Font("Sansserif", Font.BOLD, 24);
    
    //================================================================ fields
    private SudokuModel _model;      // Set in constructor.
    
    //============================================================== constructor
    public SudokuBoardDisplay(SudokuModel model) {
        setPreferredSize(new Dimension(BOARD_PIXELS + 2, BOARD_PIXELS + 2));
        setBackground(Color.WHITE);
        _model = model;
    }
    
    //=========================================================== paintComponent
    @Override public void paintComponent(Graphics g) {
        //... Draw background.
        g.setColor(getBackground());
        g.fillRect(0, 0, getWidth(), getHeight());
        g.setColor(Color.BLACK);
        
        drawGridLines(g);
        drawCellValues(g);
    }
    
    //============================================================ drawGridLines
    // Separate method to simlify paintComponent.
    private void drawGridLines(Graphics g) {
        
        //... Draw grid lines.  Terminates on <= to get final line.
        for (int i = 0; i <= PUZZLE_SIZE; i++) {
            int acrossOrDown = i * CELL_PIXELS;
            //... Draw at different x's from y=0 to y=BOARD_PIXELS.
            g.drawLine(acrossOrDown, 0, acrossOrDown, BOARD_PIXELS);
            //... Draw at different y's from x=0 to d=BOARD_PIXELS.
            g.drawLine(0, acrossOrDown, BOARD_PIXELS, acrossOrDown);
            
            //... Draw a double line for subsquare boundaries.
            if (i % SUBSQUARE == 0) {
                acrossOrDown++;  // Move one pixel and redraw as above
                g.drawLine(acrossOrDown, 0, acrossOrDown, BOARD_PIXELS);
                g.drawLine(0, acrossOrDown, BOARD_PIXELS, acrossOrDown);
            }
        }
    }
    
    //=========================================================== drawCellValues
    // Method to simplify paintComponent.
    private void drawCellValues(Graphics g) {
        g.setFont(TEXT_FONT);
        for (int i = 0; i < PUZZLE_SIZE; i++) {
            int yDisplacement = (i+1) * CELL_PIXELS - TEXT_OFFSET;
            for (int j = 0; j < PUZZLE_SIZE; j++) {
                if (_model.getVal(i, j) != 0) {
                    int xDisplacement = j * CELL_PIXELS + TEXT_OFFSET;
                    g.drawString("" + _model.getVal(i, j), xDisplacement, yDisplacement);
                }
            }
        }
    }
    
}

The 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 
// File   : gui/sudoku/sudoku3/SudokuModel.java
// Purpose: Represents the "logic" of a Sudoku game.
// Author : Fred Swartz - 23 Sep 2006 - Placed in public domain.

package sudoku3;

//////////////////////////////////////////////////////////////////// SudokuModel
public class SudokuModel {
    //================================================================ constants
    private static final int BOARD_SIZE = 9;
    
    //=================================================================== fields
    private int[][] _board;
    
    //============================================================== constructor
    public SudokuModel() {
        _board = new int[BOARD_SIZE][BOARD_SIZE];
    }
    
    //============================================================== constructor
    public SudokuModel(String initialBoard) {
        this();       // Call no parameter constructor first.
        initializeFromString(initialBoard);
    }
    
    //===================================================== initializeFromString
    public void initializeFromString(final String boardStr) {
        clear();  // Clear all values from the board.
        int row = 0;
        int col = 0;
        //... Loop over every character.
        for (int i = 0; i < boardStr.length(); i++) {
            char c = boardStr.charAt(i);
            if (c >= '1' && c <='9') {
                if (row > BOARD_SIZE || col > BOARD_SIZE) {
                    throw new IllegalArgumentException("SudokuModel: "
                            + " Attempt to initialize outside 1-9 "
                            + " at row " + (row+1) + " and col " + (col+1));
                }
                _board[row][col] = c - '0';  // Translate digit to int.
                col++;
            } else if (c == '.') {
                col++;
            } else if (c == '/') {
                row++;
                col = 0;
            } else {
                throw new IllegalArgumentException("SudokuModel: Character '" + c
                        + "' not allowed in board specification");
            }
        }
    }
    
    //============================================================== islegalMove
    public boolean isLegalMove(int row, int col, int val) {
        return row>=0 && row<BOARD_SIZE && col>=0 && col<BOARD_SIZE
                && val>0 && val<=9 && _board[row][col]==0;
    }
    
    //=================================================================== setVal
    public void setVal(int r, int c, int v) {
        _board[r][c] = v;
    }
    
    //=================================================================== getVal
    public int getVal(int row, int col) {
        return _board[row][col];
    }
    
    //===================================================================== clear
    public void clear() {
        for (int row = 0; row < BOARD_SIZE; row++) {
            for (int col = 0; col < BOARD_SIZE; col++) {
                setVal(row, col, 0);
            }
        }
    }
}