Rules of Game
You could wiki “Sudoku” to understand the rules of the game.
Terminalogy
A Sudoku (i.e. the puzzle) is a partially completed grid. A grid has 9 rows, 9 columns and 9 boxes (or blocks or regions), each having 9 cells (or squares), for a total of 81 cells. The initially defined values are clues or givens. An ordinary Sudoku (i.e. a proper Sudoku) has one solution. Rows, columns and regions can be collectively referred to as groups, of which the grid has 27. The One Rule can be compactly stated as: “Each digit appears once in each group.”
Mathematically, Sudoku is a NP-complete problem. There is no fast algorithm for solving the puzzle.
GUI
Let’s start with the GUI.
Class Design
We could simply use 9×9 JTextFields (for entering guesses) arranged in 9×9 GridLayout on a JPanel/ContentPane. However, it is hard to identify the row and column of the JTextField triggering an event.
For better OO and modular design, we design the following classes (in a package called sudoku) as shown in the above class diagram:
- GRID_SIZE constant: Instead of hardcoding the grid size (to 9×9), we define a static constant GRID_SIZE in the GameBoardPanel class, which can be referred to as GameBoardPanel.GRID_SIZE.
- Cell: We customize the JTextField, by creating a subclass called Cell, with additional variables row, col, number and status, to model each cell of the grid. The Cell has its own methods to paint() itself.
- CellStatus: An enumeration (enum) called CellStatus is designed to hold the status constants, including GIVEN, CORRECT_GUESS, WRONG_GUESS and TO_GUESS.
- GameBoardPanel: We also customize the JPanel, by creating a subclass called GameBoardPanel, to hold the grid of 9×9 Cells (JTextFields). Similar to Cell, the GameBoardPanel has its own methods to paint() itself.
- SudokuMain: We further customize the JFrame, by creating a subclass called SudokuMain, to hold the GameBoardPanel (JPanel) in its ContentPane.
- Puzzle: A class called Puzzle is designed to model the number puzzle, which holds the numbers and clues in 9×9 int array numbers and boolean array isGiven. The method newPuzzle() can be used to generate a new puzzle for a new game.
Package sudoku
All the classes are kept in a package called sudoku.
- In Eclipse/NetNeans, first create a “Java Project” called “sudoku”; then create a new package (new ⇒ package) also called sudoku. You can then create the classes under the sudoku package.
- If you are using JDK/TextEditor, create a sub-directory called sudoku and place the classes under the sub-directory.
The CellStatus Enumeration (enum) of Constants
Instead of using a string (such as “correct-guess”, “wrong-guess”, “to-guess”) or an int (1 for correct, 2 for wrong, 3 for to-guess) to represent the status of a cell, JDK 5 introduces a feature known as enumeration (enum) to efficiently maintain a set of constants in a type-safe manner.
We define an enum called CellStatus as follows (in Eclipse File ⇒ New ⇒ Enum). Save the file as “CellStatus.java” just like a class. Enum is indeed a special class.
package sudoku; /** * An enumeration of constants to represent the status * of each cell. */ public enum CellStatus { GIVEN, // clue, no need to guess TO_GUESS, // need to guess – not attempted yet CORRECT_GUESS, // need to guess – correct guess WRONG_GUESS // need to guess – wrong guess // The puzzle is solved if none of the cells have // status of TO_GUESS or WRONG_GUESS }
You can refer to the status as CellStatus.CORRECT_GUESS, CellStatus.WRONG_GUESS, just like any public static final constants (like Math.PI).
The Cell Class – A Customized Subclass of javax.swing.JTextField
Instead of using raw JTextField, we shall customize (subclass) JTextField called Cell, to include the row/column, puzzle number and a status field. It can also paint itself based on the status on the cell via paint().
package sudoku; import java.awt.Color; import java.awt.Font; import javax.swing.JTextField; /** * The Cell class model the cells of the Sudoku puzzle, by customizing (subclass) * the javax.swing.JTextField to include row/column, puzzle number and status. */ public class Cell extends JTextField { private static final long serialVersionUID = 1L; // to prevent serial warning // Define named constants for JTextField’s colors and fonts // to be chosen based on CellStatus public static final Color BG_GIVEN = new Color(240, 240, 240); // RGB public static final Color FG_GIVEN = Color.BLACK; public static final Color FG_NOT_GIVEN = Color.GRAY; public static final Color BG_TO_GUESS = Color.YELLOW; public static final Color BG_CORRECT_GUESS = new Color(0, 216, 0); public static final Color BG_WRONG_GUESS = new Color(216, 0, 0); public static final Font FONT_NUMBERS = new Font(“OCR A Extended”, Font.PLAIN, 28); // Define properties (package-visible) /** The row and column number [0-8] of this cell */ int row, col; /** The puzzle number [1-9] for this cell */ int number; /** The status of this cell defined in enum CellStatus */ CellStatus status; /** Constructor */ public Cell(int row, int col) { super(); // JTextField this.row = row; this.col = col; // Inherited from JTextField: Beautify all the cells once for all super.setHorizontalAlignment(JTextField.CENTER); super.setFont(FONT_NUMBERS); } /** Reset this cell for a new game, given the puzzle number and isGiven */ public void newGame(int number, boolean isGiven) { this.number = number; status = isGiven ? CellStatus.GIVEN : CellStatus.TO_GUESS; paint(); // paint itself } /** This Cell (JTextField) paints itself based on its status */ public void paint() { if (status == CellStatus.GIVEN) { // Inherited from JTextField: Set display properties super.setText(number + “”); super.setEditable(false); super.setBackground(BG_GIVEN); super.setForeground(FG_GIVEN); } else if (status == CellStatus.TO_GUESS) { // Inherited from JTextField: Set display properties super.setText(“”); super.setEditable(true); super.setBackground(BG_TO_GUESS); super.setForeground(FG_NOT_GIVEN); } else if (status == CellStatus.CORRECT_GUESS) { // from TO_GUESS super.setBackground(BG_CORRECT_GUESS); } else if (status == CellStatus.WRONG_GUESS) { // from TO_GUESS super.setBackground(BG_WRONG_GUESS); } } }
The Puzzle class
The Puzzle class encapsulate a Sudoku puzzle. For simplicity, I hardcoded a puzzle. You may try to generate a puzzle automatically.
package sudoku; /** * The Sudoku number puzzle to be solved */ public class Puzzle { // All variables have package access // The numbers on the puzzle int[][] numbers = new int[GameBoardPanel.GRID_SIZE][GameBoardPanel.GRID_SIZE]; // The clues – isGiven (no need to guess) or need to guess boolean[][] isGiven = new boolean[GameBoardPanel.GRID_SIZE][GameBoardPanel.GRID_SIZE]; // Constructor public Puzzle() { super(); } // Generate a new puzzle given the number of cells to be guessed, which can be used // to control the difficulty level. // This method shall set (or update) the arrays numbers and isGiven public void newPuzzle(int cellsToGuess) { // I hardcode a puzzle here for illustration and testing. int[][] hardcodedNumbers = {{5, 3, 4, 6, 7, 8, 9, 1, 2}, {6, 7, 2, 1, 9, 5, 3, 4, 8}, {1, 9, 8, 3, 4, 2, 5, 6, 7}, {8, 5, 9, 7, 6, 1, 4, 2, 3}, {4, 2, 6, 8, 5, 3, 7, 9, 1}, {7, 1, 3, 9, 2, 4, 8, 5, 6}, {9, 6, 1, 5, 3, 7, 2, 8, 4}, {2, 8, 7, 4, 1, 9, 6, 3, 5}, {3, 4, 5, 2, 8, 6, 1, 7, 9}}; // Copy from hardcodedNumbers into the array “numbers” for (int row = 0; row < GameBoardPanel.GRID_SIZE; ++row) { for (int col = 0; col < GameBoardPanel.GRID_SIZE; ++col) { numbers[row][col] = hardcodedNumbers[row][col]; } } // Need to use input parameter cellsToGuess! // Hardcoded for testing, only 2 cells of “8” is NOT GIVEN boolean[][] hardcodedIsGiven = {{true, true, true, true, true, false, true, true, true}, {true, true, true, true, true, true, true, true, false}, {true, true, true, true, true, true, true, true, true}, {true, true, true, true, true, true, true, true, true}, {true, true, true, true, true, true, true, true, true}, {true, true, true, true, true, true, true, true, true}, {true, true, true, true, true, true, true, true, true}, {true, true, true, true, true, true, true, true, true}, {true, true, true, true, true, true, true, true, true}}; // Copy from hardcodedIsGiven into array “isGiven” for (int row = 0; row < GameBoardPanel.GRID_SIZE; ++row) { for (int col = 0; col < GameBoardPanel.GRID_SIZE; ++col) { isGiven[row][col] = hardcodedIsGiven[row][col]; } } } //(For advanced students) use singleton design pattern for this class }
The GameBoardPanel Class – A Customized Subclass of JPanel for the Grid
The customized GameBoardPanel (of JPanel) contains 9×9 Cells (in 9×9 GridLayout), and a Puzzle.
package sudoku; import java.awt.*; import java.awt.event.*; import javax.swing.*; public class GameBoardPanel extends JPanel { private static final long serialVersionUID = 1L; // to prevent serial warning // Define named constants for the game board properties public static final int GRID_SIZE = 9; // Size of the board public static final int SUBGRID_SIZE = 3; // Size of the sub-grid // Define named constants for UI sizes public static final int CELL_SIZE = 60; // Cell width/height in pixels public static final int BOARD_WIDTH = CELL_SIZE * GRID_SIZE; public static final int BOARD_HEIGHT = CELL_SIZE * GRID_SIZE; // Board width/height in pixels // Define properties /** The game board composes of 9×9 Cells (customized JTextFields) */ private Cell[][] cells = new Cell[GRID_SIZE][GRID_SIZE]; /** It also contains a Puzzle with array numbers and isGiven */ private Puzzle puzzle = new Puzzle(); /** Constructor */ public GameBoardPanel() { super.setLayout(new GridLayout(GRID_SIZE, GRID_SIZE)); // JPanel // Allocate the 2D array of Cell, and added into JPanel. for (int row = 0; row < GRID_SIZE; ++row) { for (int col = 0; col < GRID_SIZE; ++col) { cells[row][col] = new Cell(row, col); super.add(cells[row][col]); // JPanel } } // [TODO 3] Allocate a common listener as the ActionEvent listener for all the // Cells (JTextFields) CellInputListener listener = new CellInputListener(); // [TODO 4] Adds this common listener to all editable cells for (int row = 0; row < GRID_SIZE; ++row) { for (int col = 0; col < GRID_SIZE; ++col) { if (cells[row][col].isEditable()) { cells[row][col].addActionListener(listener); // For all editable rows and cols } } } super.setPreferredSize(new Dimension(BOARD_WIDTH, BOARD_HEIGHT)); } /** * Generate a new puzzle; and reset the gameboard of cells based on the puzzle. * You can call this method to start a new game. */ public void newGame() { // Generate a new puzzle puzzle.newPuzzle(2); // Initialize all the 9×9 cells, based on the puzzle. for (int row = 0; row < GRID_SIZE; ++row) { for (int col = 0; col < GRID_SIZE; ++col) { cells[row][col].newGame(puzzle.numbers[row][col], puzzle.isGiven[row][col]); } } } /** * Return true if the puzzle is solved * i.e., none of the cell have status of TO_GUESS or WRONG_GUESS */ public boolean isSolved() { for (int row = 0; row < GRID_SIZE; ++row) { for (int col = 0; col < GRID_SIZE; ++col) { if (cells[row][col].status == CellStatus.TO_GUESS || cells[row][col].status == CellStatus.WRONG_GUESS) { return false; } } } return true; } // [TODO 2] Define a Listener Inner Class for all the editable Cells private class CellInputListener implements ActionListener { @Override public void actionPerformed(ActionEvent e) { // Get a reference of the JTextField that triggers this action event Cell sourceCell = (Cell)e.getSource(); // Retrieve the int entered int numberIn = Integer.parseInt(sourceCell.getText()); // For debugging System.out.println(“You entered ” + numberIn); /* * [TODO 5] * Check the numberIn against sourceCell.number. * Update the cell status sourceCell.status, * and re-paint the cell via sourceCell.paint(). */ if (numberIn == sourceCell.number) { sourceCell.status = CellStatus.CORRECT_GUESS; } else { sourceCell.status = CellStatus.WRONG_GUESS; } sourceCell.paint(); // re-paint this cell based on its status /* * [TODO 6] Check if the player has solved the puzzle after this move, * by calling isSolved(). Put up a congratulation JOptionPane, if so. */ } } }
The Main Program
package sudoku; import java.awt.*; import javax.swing.*; /** * The main Sudoku program */ public class SudokuMain extends JFrame { private static final long serialVersionUID = 1L; // to prevent serial warning // private variables GameBoardPanel board = new GameBoardPanel(); JButton btnNewGame = new JButton(“New Game”); // Constructor public SudokuMain() { Container cp = getContentPane(); cp.setLayout(new BorderLayout()); cp.add(board, BorderLayout.CENTER); // Add a button to the south to re-start the game via board.newGame() // …… // Initialize the game board to start the game board.newGame(); pack(); // Pack the UI components, instead of using setSize() setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // to handle window-closing setTitle(“Sudoku”); setVisible(true); } /** The entry main() entry method */ public static void main(String[] args) { // [TODO 1] Check “Swing program template” on how to run // the constructor of “SudokuMain” new SudokuMain(); } }
[TODO 1]:
- Fill in the main() method ([TODO 1]), and RUN the program, which shall produce the display. You should try to RUN your program as soon as possible and progressively, instead of waiting for the entire program to be completed.
- Continue to [TODO 2] in the next section.
Event Handling
Next, we shall program the event handling.
We shall use a common instance of a Named Inner Class (called CellInputListener) as the ActionEvent listener for ALL the editable JTextFields (which we extend to Cell class).
[TODO 2]: In GameBoardPanel, write the named inner class CellInputListener, as follows.
// [TODO 2] Define a Listener Inner Class for all the editable Cells private class CellInputListener implements ActionListener { @Override public void actionPerformed(ActionEvent e) { // Get a reference of the JTextField that triggers this action event Cell sourceCell = (Cell)e.getSource(); // Retrieve the int entered int numberIn = Integer.parseInt(sourceCell.getText()); // For debugging System.out.println(“You entered ” + numberIn); /* * [TODO 5] * Check the numberIn against sourceCell.number. * Update the cell status sourceCell.status, * and re-paint the cell via sourceCell.paint(). */ //if (numberIn == sourceCell.number) { // sourceCell.status = CellStatus.CORRECT_GUESS; //} else { // …… //} //sourceCell.paint(); // re-paint this cell based on its status /* * [TODO 6] Check if the player has solved the puzzle after this move, * by calling isSolved(). Put up a congratulation JOptionPane, if so. */ } }
[TODO 3] and [TODO 4]: In GameBoardPanel’s constructor:
- Declare and allocate a common instance called listener of the CellInputListener: // [TODO 3] CellInputListener listener = new CellInputListener();
- All editable JTextField shall add this common instance as its ActionEvent listener, via a nested loop: // [TODO 4] for (int row …) { for (int col …) { if (cells[row][col].isEditable()) { cells[row][col].addActionListener(listener); // For all editable rows and cols } } }
[TODO 5]: Complete [TODO 5] from the hints, and RUN the program.
[TODO 6]: Complete [TODO 6]. You have a basic Sudoku game now.
Some useful methods of JTextField are as follows. You can check the Java API for more methods.
setBackground(Color c) // Set the background color of the component setForeground(Color c) // Set the text color of the JTextField setFont(Font f) // Set the font used by the JTextField setHorizontalAlignment(int align); // align: JTextField.CENTER, JTextField.LEFT, JTextField.RIGHT isEditable():boolean setEditable(boolean b)
Common colors are defined via constants such as Color.RED, Color.GREEN, Color.BLUE, and etc (But don’t use these ugly colors. Design a color theme).
Hints and Miscellaneous
- This is a moderately complex program. You need to use the graphics debugger in Eclipse/NetBeans to debug your program logic.
- Check the JDK API.
- You can use the following static method to pop up a dialog box (JOptionPane) with a message: // [TODO 6] JOptionPane.showMessageDialog(null, “Congratulation!”); Check the API of JOptionPane.
- To check the boxes (sub-grid), you could use row/3 and col/3 to get the boxRow and boxCol in [0, 2].
- There are many ways to automatically generate a new puzzle, e.g.,
- Start with a solved puzzle, add a random number to all the cells; modulo 9 (0-8) and plus 1 (1-9).
- Start with a solved puzzle, swap rows/columns among 1-2-3, 4-5-6, 7-8-9.
- Randomly assign 9 values to the main diagonal such that each box has 3 different values, and then generate the rest of values with a solver.
- [TODO]
- There are many ways to set the difficulty level, e.g.,
- Control the number of clue (given) cells.
- [TODO]
Requirements
You submission MUST contain these classes: Cell.java (extends JTextField), CellStatus.java, Puzzle.java, GameBoardPanel.java (extends JPanel) and SudokuMain.java (extends JFrame). I revised this templates in April 2022, after the submissions and refactored (renamed) quite a bit.
More Credits
- Reset/Restart the game.
- A good Sudoku engine shall accept any “valid” number at the time of input (no duplicate in row, column and sub-grid), but signal a conflict whenever it is detected. Highlight the conflicting cells.
- After entering a guess, highlight all boxes with the same value of the guess and signal the conflicting cells if any.
- Choice of puzzles and difficulty levels (e.g,, easy, intermediate, difficult).
- Create a “menu bar” for options such as “File” (“New Game”, “Reset Game”, “Exit”), “Options”, and “Help” (Use JMenuBar, JMenu, and JMenuItem classes).
- Create a “status bar” (JTextField at the south zone of BorderLayout) to show the messages (e.g., number of cells remaining) (google “java swing statusbar”).
- Beautify your graphical interface, e.g., theme (color, font, images), layout, etc.
- Timer (pause/resume), score, progress bar.
- A side panel for command, display, strategy?
- Automatic puzzle generation with various difficulty level.
- The sample program processes ActionEvent of the JTextField, which requires user to push the ENTER key. Try KeyEvent with keyTyped() handler; or other means that does not require pushing of ENTER key.
- Sound effect, background music, enable/disable sound?
- High score and player name?
- Hints and cheats (reveal a cell, or reveal all cells with number 8)?
- Theme?
- Use of images and icons?
- Welcome screen?
- Mouseless interface?
- (Very difficult) Multi-Player network game.
- ……
REFERENCES & RESOURCES
Top 5 game sudoku java tổng hợp bởi Tổng Hợp Nhà Cái Uy Tín
Sudoku.java
- Tác giả: faculty.washington.edu
- Ngày đăng: 10/11/2022
- Đánh giá: 4.75 (405 vote)
- Tóm tắt: TCSS 342A – Winter 2007 – Sudoku Game * Author – Eric Smyth */ package tcss342.winter.sudoku; import java.awt.Color; import java.awt.
- Nguồn: 🔗
Mattnenterprise/Sudoku: A sudoku game in Java – GitHub
- Tác giả: github.com
- Ngày đăng: 05/06/2022
- Đánh giá: 4.56 (337 vote)
- Tóm tắt: Sudoku. This is a Sudoku game programmed using Java and Swing for the GUI. It currently supports 6×6, 9×9, and 12×12 puzzles.
- Nguồn: 🔗
Sudoku in Java – Javatpoint
- Tác giả: javatpoint.com
- Ngày đăng: 09/28/2022
- Đánh giá: 4.29 (587 vote)
- Tóm tắt: Sudoku in Java. Sudoku is a logic-based puzzle that uses combinatorial-number placement. In a classic Sudoku puzzle, the task is to fill the numbers in a 9 …
- Nguồn: 🔗
Sudoku | Backtracking-7 – GeeksforGeeks
- Tác giả: geeksforgeeks.org
- Ngày đăng: 07/13/2022
- Đánh giá: 4.1 (583 vote)
- Tóm tắt: Below is the implementation of the above approach: C++; C; Java; Python3; C#; Javascript. C++ …
- Khớp với kết quả tìm kiếm: Check that the same number is not present in the current row, current column and current 3X3 subgrid. After checking for safety, assign the number, and recursively check whether this assignment leads to a solution or not. If the assignment doesn’t …
- Nguồn: 🔗
Sudoku Game in Java – CodeProject
- Tác giả: codeproject.com
- Ngày đăng: 12/23/2022
- Đánh giá: 3.82 (214 vote)
- Tóm tắt: A Sudoku game written in Java which generates its own games on the fly.
- Nguồn: 🔗