sudoku/0000755000076500007650000000000010761042743011102 5ustar mattmattsudoku/Board.java0000644000076500007650000000542510761042743013002 0ustar mattmattimport java.util.*; import java.io.*; /** * This class is the logical underlying model. * * @author Matt Chu */ public class Board { public static final int SIZE = 9; private Cell[][] board = new Cell[SIZE][SIZE]; // build a board from a file public Board(String fileName) throws IOException { Scanner fileIn = new Scanner(new File(fileName)); fileIn.nextInt(); for (int i = 0; i < SIZE; i ++) { for (int j = 0; j < SIZE; j ++) { int k = fileIn.nextInt(); if (k > 0) board[i][j] = new Cell(i, j, k); else board[i][j] = new Cell(i, j); } } } // get the cell at (row, col) public Cell get(int i, int j) { return board[i][j]; } // set the cell at (row, col) with value, and lock of `locked` public void set(int i, int j, int value, boolean locked) { board[i][j].set(value, locked); } public void setGuess(int x, int y, int guess) { board[x][y].setGuess(guess); } public void setGuessS(int x, int y, String guess) { board[x][y].setGuessS(guess); } // if it's locked, returns the "true" value public int getGuess(int x, int y) { return board[x][y].getGuess(); } // if it's locked, returns the "true" value public String getGuessS(int x, int y) { return board[x][y].getGuessS(); } public String toString() { String s = ""; for (int i = 0; i < SIZE; i ++) { for (int j = 0; j < SIZE; j ++) { s += board[i][j] + " "; } s += "\n"; } return s; } public String toStringWithGuesses() { String s = ""; for (int i = 0; i < SIZE; i ++) { for (int j = 0; j < SIZE; j ++) { s += board[i][j].getGuessS() + " "; } s += "\n"; } return s; } // reset all guesses public void reset() { for (int i = 0; i < SIZE; i ++) { for (int j = 0; j < SIZE; j ++) { if (! board[i][j].isLocked()) { board[i][j].clearGuess(); } } } } public void lockAllGuesses() { for (int i = 0; i < SIZE; i ++) { for (int j = 0; j < SIZE; j ++) { if (! board[i][j].isLocked()) { board[i][j].lockGuess(); } } } } public static final void main(String[] args) throws Exception { Board b = new Board("a.dat"); } } sudoku/Cell.java0000644000076500007650000000572710761042743012637 0ustar mattmattimport java.util.*; /** * Each cell of the parent Board logical model. * * @author Matt Chu */ public class Cell { private int x = -1; private int y = -1; private List nums = new ArrayList(); private boolean locked = false; private int guess = -1; public Cell(int x, int y) { this.x = x; this.y = y; for (int i = 1; i <= 9; i ++) nums.add(i); } public Cell(int x, int y, int i) { this.x = x; this.y = y; nums.add(i); locked = true; } public boolean isLocked() { return this.locked; } public void setLocked(boolean locked) { this.locked = locked; } public int get() { return this.nums.get(0); } public void setGuess(int guess) { this.guess = guess; } // set the guess with a string, so it can take "?" public void setGuessS(String guess) { if (guess.equals("?")) this.guess = -1; else this.guess = Integer.parseInt(guess); } // if it's locked, returns the "true" value public int getGuess() { return this.locked ? this.get() : this.guess; } // if it's locked, returns the "true" value // returns a nice string version of guess public String getGuessS() { int g = this.getGuess(); return g < 1 ? "?" : g + ""; } public void clearGuess() { this.guess = -1; } // either lock this cell to this value, or just put it in the front of the // possibilities public void set(int value, boolean locked) { if (locked) { this.nums.clear(); this.nums.add(value); this.locked = locked; } else { // put it to the front of the list this.remove(value); this.nums.add(0, value); } } public void lockGuess() { this.set(this.guess, true); } public List getNums() { return this.nums; } public int size() { return this.nums.size(); } // tries to remove this value; note that if locked, this will silently fail public void remove(int i) { //assert(! this.isLocked()); if (! this.isLocked()) nums.remove(new Integer(i)); } public int getX() { return this.x; } public int getY() { return this.y; } public String toString() { return this.locked ? this.nums.get(0) + "" : "?"; } // for debugging public String toString(boolean b) { String s = "{ "; for (Integer i : this.nums) s += i + " "; s += "}"; return "locked = " + this.locked + ", " + "nums.size() = " + this.nums.size() + ", " + "nums = " + s + ", " + "(x,y) = (" + this.x + "," + this.y + ")"; } } sudoku/CellRenderer.java0000644000076500007650000000276510761042743014325 0ustar mattmattimport java.awt.*; import java.awt.event.*; import java.io.*; import java.util.*; import javax.swing.*; import javax.swing.table.*; /** * Code adapted from Java "How to Use Tables" tutorial trail: * http://java.sun.com/docs/books/tutorial/uiswing/components/table.html * * This is how each cell looks. * * @author Matt Chu */ public class CellRenderer extends JLabel implements TableCellRenderer { public static final Color LOCKED = Color.GRAY; public static final Color UNKNOWN = Color.RED; public static final Color GUESS = Color.GREEN; private Board board = null; public CellRenderer(Board board) { this.board = board; //MUST do this for background to show up. this.setOpaque(true); this.setHorizontalAlignment(SwingConstants.CENTER); } // make the cell look pretty: color and font public Component getTableCellRendererComponent( JTable table, Object color, boolean isSelected, boolean hasFocus, int row, int column) { Cell c = board.get(row, column); if (c.isLocked()) { this.setBackground(LOCKED); } else { if (! c.getGuessS().equals("?")) { this.setBackground(GUESS); } else { this.setBackground(UNKNOWN); } } setFont(new Font("Monospaced", Font.PLAIN, 48)); setText(c.getGuessS()); return this; } } sudoku/Gui.java0000644000076500007650000001427110761042743012476 0ustar mattmattimport java.awt.*; import java.awt.event.*; import java.io.*; import java.util.*; import javax.swing.*; import javax.swing.table.*; /** * Code adapted from Java "How to Use Tables" tutorial trail: * http://java.sun.com/docs/books/tutorial/uiswing/components/table.html * * This is the main panel. * * @author Matt Chu */ public class Gui extends JPanel implements ActionListener { private static final String START_FILE = "start.sud"; private boolean DEBUG = false; private Board board; private JTable mainTable;; private JFrame mainParent; private AbstractTableModel dataModel; public Gui(JFrame mainParent) { super(new BorderLayout()); this.mainParent = mainParent; loadNewFile(START_FILE); } private void loadNewFile(String fileName) { try { board = new Board(fileName); this.removeAll(); dataModel = new MyTableModel(); mainTable = new JTable(dataModel); mainTable.setSize(new Dimension(300, 300)); add(mainTable); mainTable.setRowHeight(60); mainTable.setDefaultEditor( Object.class, new NumComboEditor(mainTable, board)); mainTable.setDefaultRenderer( Object.class, new CellRenderer(board)); reset(); } catch (IOException ex) { ex.printStackTrace(); System.exit(-1); } } // reset all state and gui private void reset() { board.reset(); dataModel.fireTableStructureChanged(); this.validate(); } class MyTableModel extends AbstractTableModel { public int getColumnCount() { return Board.SIZE; } public int getRowCount() { return Board.SIZE; } /** * Get the value to display in the editor. */ public Object getValueAt(int row, int col) { return board.getGuessS(row, col); } /** * Don't need to implement this method unless your table's * editable. * * Only cells that are not locked are editable. */ public boolean isCellEditable(int row, int col) { return ! board.get(row, col).isLocked(); } } /** * Create the GUI and show it. For thread safety, * this method should be invoked from the * event-dispatching thread. */ private static void createAndShowGUI() { //Create and set up the window. JFrame frame = new JFrame("Sudoku"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setResizable(false); //Create and set up the content pane. Gui newContentPane = new Gui(frame); newContentPane.setOpaque(true); //content panes must be opaque frame.setContentPane(newContentPane); frame.setJMenuBar(createMenuBar(newContentPane)); //Display the window. frame.pack(); frame.setVisible(true); } public static void main(String[] args) { // Schedule a job for the event-dispatching thread: // creating and showing this application's GUI. javax.swing.SwingUtilities.invokeLater(new Runnable() { public void run() { createAndShowGUI(); } }); } private static JMenuBar createMenuBar(ActionListener parent) { JMenuBar menuBar; JMenu menu; JMenuItem menuItem; //Create the menu bar. menuBar = new JMenuBar(); //Build the first menu. menu = new JMenu("File"); menuBar.add(menu); //a group of JMenuItems menuItem = new JMenuItem("Open"); menuItem.addActionListener(parent); menu.add(menuItem); menuItem = new JMenuItem("Reset"); menuItem.addActionListener(parent); menu.add(menuItem); menuItem = new JMenuItem("Verify Solution"); menuItem.addActionListener(parent); menu.add(menuItem); menuItem = new JMenuItem("Show Solution"); menuItem.addActionListener(parent); menu.add(menuItem); menuItem = new JMenuItem("Exit"); menuItem.addActionListener(parent); menu.add(menuItem); //Build second menu in the menu bar. menu = new JMenu("Help"); menuBar.add(menu); menuItem = new JMenuItem("About"); menuItem.addActionListener(parent); menu.add(menuItem); return menuBar; } /** * Listener for menu items */ public void actionPerformed(ActionEvent e) { String ac = e.getActionCommand(); if ("Exit".equals(ac)) { System.exit(0); } else if ("Reset".equals(ac)) { reset(); } else if ("Show Solution".equals(ac)) { board.reset(); Solver solver = new Solver(board); solver.solve(false); dataModel.fireTableStructureChanged(); this.validate(); } else if ("Open".equals(ac)) { //Create a file chooser JFileChooser fc = new JFileChooser(); int returnVal = fc.showOpenDialog(mainParent); if (returnVal == JFileChooser.APPROVE_OPTION) { File file = fc.getSelectedFile(); loadNewFile(file.getAbsolutePath()); } } else if ("About".equals(ac)) { JOptionPane.showMessageDialog( mainParent, "By Matt Chu (matt.chu@gmail.com)", "About Sudoku v.01", JOptionPane.INFORMATION_MESSAGE ); } else if ("Verify Solution".equals(ac)) { Solver solver = new Solver(board); boolean solved = solver.check(board); JOptionPane.showMessageDialog( mainParent, (solved ? "Correct! Congratulations!" : "Incorrect! Keep trying!"), "Your solution is:", JOptionPane.INFORMATION_MESSAGE ); } } } sudoku/NumComboEditor.java0000644000076500007650000000356010761042743014637 0ustar mattmattimport javax.swing.*; import javax.swing.table.*; import java.awt.*; import java.awt.event.*; /** * Code adapted from Java "How to Use Tables" tutorial trail: * http://java.sun.com/docs/books/tutorial/uiswing/components/table.html * * This makes the box a combobox of values on click. * * @author Matt Chu */ public class NumComboEditor extends AbstractCellEditor implements TableCellEditor, ActionListener { protected static final String EDIT = "edit"; private JComboBox comboBox = null; private String currentValue = "?"; private JTable parent = null; private Board board = null; public NumComboEditor(JTable parent, Board board) { comboBox = new JComboBox(); comboBox.addItem("?"); for (int i = 1; i <= Board.SIZE; i ++) comboBox.addItem(i + ""); comboBox.setActionCommand(EDIT); comboBox.addActionListener(this); this.parent = parent; this.board = board; } /** * Handles events from the editor button and from * the dialog's OK button. */ public void actionPerformed(ActionEvent e) { int row = parent.getSelectionModel().getLeadSelectionIndex(); int col = parent.getColumnModel().getSelectionModel().getLeadSelectionIndex(); this.currentValue = (String) comboBox.getSelectedItem(); board.setGuessS(row, col, this.currentValue); fireEditingStopped(); } // Implement the one CellEditor method that AbstractCellEditor doesn't. public Object getCellEditorValue() { return this.currentValue; } // Implement the one method defined by TableCellEditor. public Component getTableCellEditorComponent( JTable table, Object value, boolean isSelected, int row, int column) { this.currentValue = (String) value; return this.comboBox; } } sudoku/Solver.java0000644000076500007650000001630110761042743013220 0ustar mattmattimport java.util.*; /** * The Sudoku solver; optimized to be fast enough that it can be run * "on-the-fly" and still be responsive. * * @author Matt Chu */ public class Solver { private List unknowns = new ArrayList(); private Board board = null; private int count = 0; public Solver(Board b) { this.board = b; // populate unknowns for (int i = 0; i < 9; i ++) { for (int j = 0; j < 9; j ++) { if (! b.get(i, j).isLocked()) { unknowns.add(b.get(i, j)); } } } //eliminatePossibilities(b); //setForSures(b); } // generates a solution on this.board; // if setLocks is true, then it "locks" the answers; // otherwise, only sets the solution as guesses public void solve(boolean setLocks) { count = 1; for (Cell c : unknowns) { count *= c.getNums().size(); } //System.out.println("total possible permutations = " + count); count = 0; LinkedList solution = new LinkedList(); solve(unknowns, 0, solution); // now set solution for (int i = 0; i < solution.size(); i ++) { unknowns.get(i).set(solution.get(i), setLocks); } } // Given a list of "unknown" cells, recursively enumerates all possibilities. // Stores the solution in `solution`. // Returns true if a solution is found, false otherwise; private boolean solve(List cells, int position, LinkedList solution) { if (position >= cells.size()) { // this last check is not necessary... if (check(board)) { // we have our answer! for (Cell c : cells) { solution.addLast(c.getGuess()); } return true; } else { count ++; return false; } } else { for (Integer i : cells.get(position).getNums()) { cells.get(position).setGuess(i); if (! isImpossible(board)) { boolean result = solve(cells, position + 1, solution); if (result) return true; } cells.get(position).clearGuess(); } return false; } } /*//{{{ private void eliminatePossibilities(Board b) { for (int i = 0; i < 9; i ++) { for (int j = 0; j < 9; j ++) { Cell c = b.get(i, j); //System.out.println(c); if (c.isLocked()) { propogateLock(b, c); } } } } *///}}} /*//{{{ // only call if c.isLocked() == true private void propogateLock(Board b, Cell c) { for (int i = 0; i < 9; i ++) { if (i != c.getX()) { Cell cell = b.get(i, c.getY()); cell.remove(c.get()); } } for (int j = 0; j < 9; j ++) { if (j != c.getY()) { Cell cell = b.get(c.getX(), j); cell.remove(c.get()); } } // now check the square int x = 0; int y = 0; if (c.getX() >= 3 && c.getX() < 6) x = 3; else if (c.getX() >= 6) x = 6; if (c.getY() >= 3 && c.getY() < 6) y = 3; else if (c.getY() >= 6) y = 6; for (int i = x; i < x + 3; i ++) { for (int j = y; j < y + 3; j ++) { Cell cell = b.get(i, j); cell.remove(c.get()); } } } *///}}} /*//{{{ private void setForSures(Board b) { for (int i = 0; i < 9; i ++) { for (int j = 0; j < 9; j ++) { Cell c = b.get(i, j); if (! c.isLocked() && c.size() == 1) { c.setLocked(true); } } } } *///}}} // returns true if it's a valid solution, false other public boolean check(Board board) { for (int i = 0; i < 9; i ++) { int sum = 0; for (int j = 0; j < 9; j ++) { sum += board.get(i, j).getGuess(); } if (sum != 45) return false; } for (int i = 0; i < 9; i ++) { int sum = 0; for (int j = 0; j < 9; j ++) { sum += board.get(j, i).getGuess(); } if (sum != 45) return false; } // now check the square for (int a = 0; a < 9; a += 3) { for (int b = 0; b < 9; b += 3) { int sum = 0; for (int i = a; i < a + 3; i ++) { for (int j = b; j < b + 3; j ++) { sum += board.get(i, j).getGuess(); } } if (sum != 45) return false; } } return true; } // return true if there is a conflict, otherwise, returns false private boolean isImpossible(Board board) { boolean[] map = new boolean[9]; for (int i = 0; i < 9; i ++) { for (int z = 0; z < 9; z ++) map[z] = false; for (int j = 0; j < 9; j ++) { int guess = board.get(i, j).getGuess(); if (guess == -1) continue; if (map[guess - 1]) return true; map[guess - 1] = true; } } for (int i = 0; i < 9; i ++) { for (int z = 0; z < 9; z ++) map[z] = false; for (int j = 0; j < 9; j ++) { int guess = board.get(j, i).getGuess(); if (guess == -1) continue; if (map[guess - 1]) return true; map[guess - 1] = true; } } // now check the square for (int a = 0; a < 9; a += 3) { for (int b = 0; b < 9; b += 3) { for (int z = 0; z < 9; z ++) map[z] = false; for (int i = a; i < a + 3; i ++) { for (int j = b; j < b + 3; j ++) { int guess = board.get(i, j).getGuess(); if (guess == -1) continue; if (map[guess - 1]) return true; map[guess - 1] = true; } } } } return false; } public static final void main(String[] args) throws Exception { Board b = null; Solver s = null; b = new Board("../data/hard-001.sud"); System.out.print(b); s = new Solver(b); System.out.println(""); s.solve(true); System.out.println(b); } } sudoku/start.sud0000644000076500007650000000024410761042743012754 0ustar mattmatt3 0 6 0 0 0 0 0 1 0 0 0 0 6 5 1 0 0 0 1 0 7 0 0 0 6 0 2 6 2 0 3 0 5 0 9 4 0 0 3 0 0 0 2 0 0 4 8 0 9 0 7 0 3 6 9 0 6 0 0 0 4 0 8 0 0 0 7 9 4 0 0 0 0 5 0 0 0 0 0 7 0