# HG changeset patch # User Mike Becker # Date 1595683791 -7200 # Node ID 369903afbb297f12cd11832fd6740c7f88677261 # Parent 576e7a2861aea3e4901088c4078b3b84842f06f4 adds more javadoc diff -r 576e7a2861ae -r 369903afbb29 src/main/java/de/uapcore/sudoku/ActionHandler.java --- a/src/main/java/de/uapcore/sudoku/ActionHandler.java Sat Jul 25 14:01:28 2020 +0200 +++ b/src/main/java/de/uapcore/sudoku/ActionHandler.java Sat Jul 25 15:29:51 2020 +0200 @@ -1,16 +1,16 @@ /* * Copyright 2013 Mike Becker. All rights reserved. - * + * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: - * + * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. - * + * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. - * + * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE @@ -33,31 +33,42 @@ import java.io.IOException; /** - * - * @author mike + * Handles all user issued actions in the application. */ public final class ActionHandler implements ActionListener { - + public static final String SAVE = "save"; public static final String CHECK = "check"; public static final String SOLVE = "solve"; - + public static final String NEW = "new"; public static final String OPEN = "open"; public static final String SAVE_AS = "save as"; public static final String QUIT = "quit"; public static final String ABOUT = "about"; - + private Field field; private Solver solver; private DocumentHandler doc; - + + /** + * Constructs a new action handler instance. + * + * @param f a reference to the playing field + */ public ActionHandler(Field f) { field = f; solver = new Solver(); doc = new DocumentHandler(); } - + + /** + * Prompts the user for a file name. + *

+ * If the user chooses an existing file, they are asked whether the file should be overwritten. + * + * @return true if the user approves a chosen file name + */ private boolean chooseSaveFilename() { JFileChooser fc = new JFileChooser("."); fc.setMultiSelectionEnabled(false); @@ -81,7 +92,10 @@ return false; } } - + + /** + * Prompts the user for a file to open and, if approved, loads that file. + */ private void open() { JFileChooser fc = new JFileChooser("."); fc.setMultiSelectionEnabled(false); @@ -92,12 +106,22 @@ doc.load(field); } catch (IOException e) { JOptionPane.showMessageDialog(field, - "Datei konnte nicht geladen werden: "+e.getMessage(), - "Sudoku", JOptionPane.ERROR_MESSAGE); + "Datei konnte nicht geladen werden: " + e.getMessage(), + "Sudoku", JOptionPane.ERROR_MESSAGE); } } } - + + /** + * Attempts to save the Sudoku field to a file. + *

+ * If necessary, the user is prompted for a file name. + *

+ * The field must be solvable, otherwise it cannot be saved. + * + * @param rename true if the user shall always be prompted, even if a file name is already known + * @return true if the user approves the chosen file name + */ private boolean save(boolean rename) { if (!doc.isFilenameSet() || rename) { if (!chooseSaveFilename()) { @@ -110,8 +134,8 @@ doc.save(field); } catch (IOException e) { JOptionPane.showMessageDialog(field, - "Datei konnte nicht gespeichert werden: "+e.getMessage(), - "Sudoku", JOptionPane.ERROR_MESSAGE); + "Datei konnte nicht gespeichert werden: " + e.getMessage(), + "Sudoku", JOptionPane.ERROR_MESSAGE); } return true; } else { @@ -121,7 +145,10 @@ return false; } } - + + /** + * Checks the Sudoku field and displays the result as a dialog box. + */ private void check() { if (solver.check(field)) { JOptionPane.showMessageDialog(field, "Überprüfung erfolgreich!", @@ -131,14 +158,22 @@ "Sudoku", JOptionPane.WARNING_MESSAGE); } } - + + /** + * Solves the field or displays an error dialog if the field is not solvable. + */ private void solve() { if (!solver.check(field) || !solver.solve(field)) { JOptionPane.showMessageDialog(field, "Das Feld ist nicht lösbar!", "Sudoku", JOptionPane.WARNING_MESSAGE); } } - + + /** + * Checks whether there are unsaved changes and asks the user to save the field. + * + * @return true if there are no unsaved changes or the user actively decides to continue - false, otherwise + */ private boolean saveUnsaved() { boolean proceed = false; if (field.isAnyCellModified()) { @@ -155,49 +190,49 @@ } else { proceed = true; } - + return proceed; } @Override public void actionPerformed(ActionEvent e) { switch (e.getActionCommand()) { - case NEW: - if (saveUnsaved()) { - doc.clearFilename(); - field.clear(); - } - break; - case OPEN: - open(); - break; - case SAVE: - save(false); - break; - case SAVE_AS: - save(true); - break; - case CHECK: - check(); - break; - case SOLVE: - solve(); - break; - case QUIT: - if (saveUnsaved()) { - System.exit(0); - } - break; - case ABOUT: - JOptionPane.showMessageDialog(field, - "Sudoku - Copyright (c) 2013 Mike Becker\nwww.uap-core.de"+ - "\nPublished under the BSD License", - "Sudoku", JOptionPane.INFORMATION_MESSAGE); - break; - default: - throw new UnsupportedOperationException( - "unknown action: "+e.getActionCommand()); + case NEW: + if (saveUnsaved()) { + doc.clearFilename(); + field.clear(); + } + break; + case OPEN: + open(); + break; + case SAVE: + save(false); + break; + case SAVE_AS: + save(true); + break; + case CHECK: + check(); + break; + case SOLVE: + solve(); + break; + case QUIT: + if (saveUnsaved()) { + System.exit(0); + } + break; + case ABOUT: + JOptionPane.showMessageDialog(field, + "Sudoku - Copyright (c) 2013 Mike Becker\nwww.uap-core.de" + + "\nPublished under the BSD License", + "Sudoku", JOptionPane.INFORMATION_MESSAGE); + break; + default: + throw new UnsupportedOperationException( + "unknown action: " + e.getActionCommand()); } } - + } diff -r 576e7a2861ae -r 369903afbb29 src/main/java/de/uapcore/sudoku/ButtonPanel.java --- a/src/main/java/de/uapcore/sudoku/ButtonPanel.java Sat Jul 25 14:01:28 2020 +0200 +++ b/src/main/java/de/uapcore/sudoku/ButtonPanel.java Sat Jul 25 15:29:51 2020 +0200 @@ -30,8 +30,7 @@ import java.awt.*; /** - * - * @author mike + * The panel displaying some actions for the Sudoku solver. */ public final class ButtonPanel extends JPanel { diff -r 576e7a2861ae -r 369903afbb29 src/main/java/de/uapcore/sudoku/DocumentHandler.java --- a/src/main/java/de/uapcore/sudoku/DocumentHandler.java Sat Jul 25 14:01:28 2020 +0200 +++ b/src/main/java/de/uapcore/sudoku/DocumentHandler.java Sat Jul 25 15:29:51 2020 +0200 @@ -32,12 +32,18 @@ /** * - * @author mike */ public class DocumentHandler { private String filename; - + + /** + * Loads data into the specified field. + * + * @param field the field to populated with the loaded data + * @throws IOException if loading fails or no file name has been set before + * @see #setFilename(String) + */ public void load(Field field) throws IOException { if (!isFilenameSet()) { throw new IOException("no filename supplied"); @@ -72,7 +78,14 @@ } field.setAllCellsModified(false); } - + + /** + * Saves the specified field to a file. + * + * @param field the field to save + * @throws IOException if saving fails or the file name has not been set before + * @see #setFilename(String) + */ public void save(Field field) throws IOException { if (!isFilenameSet()) { throw new IOException("no filename supplied"); @@ -89,15 +102,28 @@ } } } - + + /** + * Sets the file name for loading and saving data. + * + * @param filename the file name + */ public void setFilename(String filename) { this.filename = filename; } - + + /** + * Clears the file name. + */ public void clearFilename() { filename = null; } - + + /** + * Checks whether a file name has been set. + * + * @return true if a file name is known, false otherwise + */ public boolean isFilenameSet() { return filename != null; } diff -r 576e7a2861ae -r 369903afbb29 src/main/java/de/uapcore/sudoku/Field.java --- a/src/main/java/de/uapcore/sudoku/Field.java Sat Jul 25 14:01:28 2020 +0200 +++ b/src/main/java/de/uapcore/sudoku/Field.java Sat Jul 25 15:29:51 2020 +0200 @@ -1,16 +1,16 @@ /* * Copyright 2013 Mike Becker. All rights reserved. - * + * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: - * + * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. - * + * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. - * + * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE @@ -31,22 +31,26 @@ import java.awt.image.BufferedImage; /** - * - * @author mike + * A panel rendering the Sudoku field. + *

+ * Cells are identified by zero-based indices from top-left to bottom-right. */ public final class Field extends JPanel { private SudokuTextField[][] cells; - + + /** + * Constructs a new 9x9 Sudoku grid. + */ public Field() { setBackground(Color.WHITE); - + setLayout(new GridBagLayout()); GridBagConstraints c = new GridBagConstraints(); c.insets = new Insets(5, 5, 5, 5); - + cells = new SudokuTextField[9][9]; - for (int x = 0 ; x < 9 ; x++) { - for (int y = 0 ; y < 9 ; y++) { + for (int x = 0; x < 9; x++) { + for (int y = 0; y < 9; y++) { cells[x][y] = new SudokuTextField(); c.gridx = x; c.gridy = y; @@ -55,69 +59,118 @@ } } + /** + * Paints the grid and all contained cells. + * + * @param graphics the graphics context + */ @Override public void paint(Graphics graphics) { final int w = getWidth(); final int h = getHeight(); final int cw = w / 9; final int ch = h / 9; - + BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB); Graphics2D g = img.createGraphics(); g.setBackground(Color.WHITE); g.clearRect(0, 0, w, h); - + g.setColor(Color.BLACK); - g.drawRect(1, 1, w-2, h-2); - g.drawRect(2, 2, w-4, h-4); - for (int x = cw ; x < w ; x += cw) { - for (int y = ch ; y < h ; y += ch) { - g.drawLine(x, 2, x, h-2); - g.drawLine(2, y, w-2, y); + g.drawRect(1, 1, w - 2, h - 2); + g.drawRect(2, 2, w - 4, h - 4); + for (int x = cw; x < w; x += cw) { + for (int y = ch; y < h; y += ch) { + g.drawLine(x, 2, x, h - 2); + g.drawLine(2, y, w - 2, y); if ((x / cw) % 3 == 0) { - g.drawLine(x+1, 2, x+1, h-2); + g.drawLine(x + 1, 2, x + 1, h - 2); } if ((y / ch) % 3 == 0) { - g.drawLine(2, y+1, w-2, y+1); + g.drawLine(2, y + 1, w - 2, y + 1); } } } - + graphics.drawImage(img, 0, 0, this); super.paintChildren(graphics); } - + + /** + * Checks whether a cell is empty + * + * @param x horizontal position + * @param y vertical position + * @return true if the cell is empty, false otherwise + */ public boolean isCellEmpty(int x, int y) { return getCellValue(x, y) == 0; } - + + /** + * Returns value of a specific cell. + * + * @param x horizontal position + * @param y vertical position + * @return the cell's value + */ public int getCellValue(int x, int y) { return cells[x][y].getValue(); } - + + /** + * Sets the value of a specific cell. + * + * @param x horizontal position + * @param y vertical position + * @param v the cells value + */ public void setCellValue(int x, int y, int v) { cells[x][y].setValue(v); } - + + /** + * Clears the value of a specific cell. + * + * @param x horizontal position + * @param y vertical position + */ public void clearCellValue(int x, int y) { setCellValue(x, y, 0); } - + + /** + * Sets the modified state of a specific cell. + * + * @param x horizontal position + * @param y vertical position + * @param modified the modified state + */ public void setCellModified(int x, int y, boolean modified) { cells[x][y].setModified(modified); } - + + /** + * Sets the modified state of all cells. + * + * @param modified the modified state + */ public void setAllCellsModified(boolean modified) { - for (int x = 0 ; x < 9 ; x++) { - for (int y = 0 ; y < 9 ; y++) { + for (int x = 0; x < 9; x++) { + for (int y = 0; y < 9; y++) { cells[x][y].setModified(modified); } } } - + + /** + * Checks whether any cell is modified. + * + * @return true if any cell is modified, false otherwise + */ public boolean isAnyCellModified() { - for (int x = 0 ; x < 9 ; x++) { - for (int y = 0 ; y < 9 ; y++) { + for (int x = 0; x < 9; x++) { + for (int y = 0; y < 9; y++) { if (cells[x][y].isModified()) { return true; } @@ -125,53 +178,77 @@ } return false; } - + + /** + * Clears all cells. + */ public void clear() { - for (int x = 0 ; x < 9 ; x++) { - for (int y = 0 ; y < 9 ; y++) { + for (int x = 0; x < 9; x++) { + for (int y = 0; y < 9; y++) { cells[x][y].setValue(0); } } } - + + /** + * Returns a square identified by square coordinates. + *

+ * Cells within the square are identified by the same coordinate system. + * + * @param x horizontal position from 0 to 2 + * @param y vertical position from 0 to 2 + * @return a two-dimensional array containing the square cell values + */ public int[][] getSquare(int x, int y) { if (x < 0 || x > 2 || y < 0 || y > 2) { throw new IllegalArgumentException("Invalid square coordinates"); } int[][] square = new int[3][3]; - - for (int u = 0 ; u < 3 ; u++) { - for (int v = 0 ; v < 3 ; v++) { - square[u][v] = getCellValue(3*x+u, 3*y+v); + + for (int u = 0; u < 3; u++) { + for (int v = 0; v < 3; v++) { + square[u][v] = getCellValue(3 * x + u, 3 * y + v); } } - + return square; } - + + /** + * Returns an entire row. + * + * @param y the row position + * @return an array containing the row values + */ public int[] getRow(int y) { if (y < 0 || y > 8) { throw new IllegalArgumentException("Invalid row number"); } int row[] = new int[9]; - - for (int x = 0 ; x < 9 ; x++) { + + for (int x = 0; x < 9; x++) { row[x] = getCellValue(x, y); } - + return row; } - + + /** + * Returns an entire column + * + * @param x the column position + * @return an array containing the column values + */ public int[] getColumn(int x) { if (x < 0 || x > 8) { throw new IllegalArgumentException("Invalid column number"); } int column[] = new int[9]; - - for (int y = 0 ; y < 9 ; y++) { + + for (int y = 0; y < 9; y++) { column[y] = getCellValue(x, y); } - + return column; } } diff -r 576e7a2861ae -r 369903afbb29 src/main/java/de/uapcore/sudoku/MainMenu.java --- a/src/main/java/de/uapcore/sudoku/MainMenu.java Sat Jul 25 14:01:28 2020 +0200 +++ b/src/main/java/de/uapcore/sudoku/MainMenu.java Sat Jul 25 15:29:51 2020 +0200 @@ -29,8 +29,7 @@ import javax.swing.*; /** - * - * @author mike + * Main menu bar. */ public class MainMenu { diff -r 576e7a2861ae -r 369903afbb29 src/main/java/de/uapcore/sudoku/Solver.java --- a/src/main/java/de/uapcore/sudoku/Solver.java Sat Jul 25 14:01:28 2020 +0200 +++ b/src/main/java/de/uapcore/sudoku/Solver.java Sat Jul 25 15:29:51 2020 +0200 @@ -30,14 +30,10 @@ import java.util.List; /** - * - * @author mike + * Implements the backtracking algorithm for solving the Sudoku. */ public final class Solver { - - public Solver() { - } - + private Integer fillInCandidate(Field f, List[][] candidates, int x, int y) { Integer c = candidates[x][y].remove(0); f.setCellValue(x, y, c); @@ -138,7 +134,17 @@ return true; } - + + /** + * Attempts to solve the given Sudoku field. + * + * The solution, if any, is directly entered into the field. + * All solved fields will be in modified state. + * The already given fields are left untouched. + * + * @param f the field to solve + * @return true if a solution could be found, false if the field is unsolvable + */ public boolean solve(Field f) { // Calculate initial candidates @@ -175,7 +181,13 @@ // Backtrack return solve(f, candidates); } - + + /** + * Performs a fast check whether any field violates the Sudoku rules. + * + * @param f the field to check + * @return true, if the check succeeds, false otherwise + */ public boolean check(Field f) { int line[]; for (int i = 0 ; i < 9 ; i++) { diff -r 576e7a2861ae -r 369903afbb29 src/main/java/de/uapcore/sudoku/Sudoku.java --- a/src/main/java/de/uapcore/sudoku/Sudoku.java Sat Jul 25 14:01:28 2020 +0200 +++ b/src/main/java/de/uapcore/sudoku/Sudoku.java Sat Jul 25 15:29:51 2020 +0200 @@ -30,11 +30,13 @@ import java.awt.*; /** - * - * @author mike + * Main class of the application. */ public final class Sudoku extends JFrame { - + + /** + * Constructs the Sudoku main window. + */ public Sudoku() { super("Sudoku"); @@ -61,6 +63,8 @@ } /** + * Main method starting the Sudoku solver. + * * @param args the command line arguments */ public static void main(String[] args) { diff -r 576e7a2861ae -r 369903afbb29 src/main/java/de/uapcore/sudoku/SudokuTextField.java --- a/src/main/java/de/uapcore/sudoku/SudokuTextField.java Sat Jul 25 14:01:28 2020 +0200 +++ b/src/main/java/de/uapcore/sudoku/SudokuTextField.java Sat Jul 25 15:29:51 2020 +0200 @@ -34,8 +34,7 @@ import java.awt.event.KeyEvent; /** - * - * @author mike + * A custom text field specifically for Sudoku number fields. */ public final class SudokuTextField extends JTextField { @@ -105,15 +104,25 @@ } }); } - + + /** + * Returns the current value in the field or zero if the field is empty. + * + * @return the number from 1 to 9 or zero if empty + */ public int getValue() { - if (getText().length() > 0) { + if (getText().isEmpty()) { + return 0; + } else { return Integer.valueOf(getText()); - } else { - return 0; } } - + + /** + * Sets the field's value. + * + * @param v the number from 1 to 9 or zero to clear the field + */ public void setValue(int v) { if (v == 0) { setText(""); @@ -124,12 +133,24 @@ "Sudoku numbers must be in range 0-9 (0 means 'not set')"); } } - + + /** + * Sets the modified state of this field. + * + * Modified fields are displayed in blue color. + * + * @param modified a flag indicating whether this field is modified + */ public void setModified(boolean modified) { this.modified = modified; setForeground(modified?Color.BLUE:Color.BLACK); } - + + /** + * Checks whether this field is in modified state. + * + * @return true if this field is modified + */ public boolean isModified() { return modified; }