package net.sf.clipsrules.jni.examples.sudoku; import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.event.*; import javax.swing.table.*; import java.util.List; import java.util.Locale; import java.util.ResourceBundle; import java.util.MissingResourceException; import net.sf.clipsrules.jni.*; /* TBD Allow tabbing between different grids. */ /* TBD Allow arrow keys to move between different grids. */ /* TBD Web links for techniques. */ /* Notes: This example creates just a single environment. If you create multiple environments, call the destroy method when you no longer need the environment. This will free the C data structures associated with the environment. clips = new Environment(); . . . clips.destroy(); Calling the clear, reset, load, loadFacts, run, eval, build, assertString, and makeInstance methods can trigger CLIPS garbage collection. If you need to retain access to a PrimitiveValue returned by a prior eval, assertString, or makeInstance call, retain it and then release it after the call is made. PrimitiveValue pv1 = clips.eval("(myFunction foo)"); pv1.retain(); PrimitiveValue pv2 = clips.eval("(myFunction bar)"); . . . pv1.release(); */ class SudokuDemo implements ActionListener, FocusListener, KeyListener { JFrame jfrm; JPanel mainGrid; JButton clearButton; JButton resetButton; JButton solveButton; JButton techniquesButton; Object resetValues[][][] = new Object[9][3][3]; boolean solved = false; ResourceBundle sudokuResources; Environment clips; boolean isExecuting = false; Thread executionThread; /**************/ /* SudokuDemo */ /**************/ SudokuDemo() { JTable theSubGrid; int r, c; /*====================================*/ /* Load the internationalized string */ /* resources used by the application. */ /*====================================*/ try { sudokuResources = ResourceBundle.getBundle("net.sf.clipsrules.jni.examples.sudoku.resources.SudokuResources",Locale.getDefault()); } catch (MissingResourceException mre) { mre.printStackTrace(); return; } /*===================================*/ /* Create the main JFrame container. */ /*===================================*/ jfrm = new JFrame(sudokuResources.getString("SudokuDemo")); jfrm.getContentPane().setLayout(new BorderLayout()); /*=============================================================*/ /* Terminate the program when the user closes the application. */ /*=============================================================*/ jfrm.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); /*=======================================================*/ /* Create the JPanel which will contain the sudoku grid. */ /*=======================================================*/ mainGrid = new JPanel(); GridLayout theLayout = new GridLayout(3,3); theLayout.setHgap(-1); theLayout.setVgap(-1); mainGrid.setLayout(theLayout); mainGrid.setOpaque(true); /*=================================================*/ /* Create a renderer based on the default renderer */ /* that will center the text within the cell. */ /*=================================================*/ DefaultTableCellRenderer renderer = new DefaultTableCellRenderer() { public Component getTableCellRendererComponent( JTable table,Object value,boolean isSelected,boolean hasFocus,int row,int column) { Component comp = super.getTableCellRendererComponent(table,value,isSelected,hasFocus,row,column); if (comp instanceof JLabel) { ((JLabel) comp).setHorizontalAlignment(JLabel.CENTER); if (value instanceof String) { if ("?".equals(value)) { ((JLabel) comp).setForeground(Color.red); } else if (((String) value).length() > 1) { ((JLabel) comp).setForeground(Color.green.darker()); } else { ((JLabel) comp).setForeground(Color.black); } } } return comp; } }; /*========================================*/ /* Create each of the nine 3x3 grids that */ /* will go inside the main sudoku grid. */ /*========================================*/ for (r = 0; r < 3; r++) { for (c = 0; c < 3; c++) { theSubGrid = new JTable(3,3) { public boolean isCellEditable(int rowIndex,int vColIndex) { return false; } }; theSubGrid.setRowSelectionAllowed(false); theSubGrid.setShowGrid(true); theSubGrid.setRowHeight(25); theSubGrid.setGridColor(Color.black); theSubGrid.setBorder(BorderFactory.createLineBorder(Color.black,2)); theSubGrid.setDefaultRenderer(Object.class,renderer); theSubGrid.addFocusListener(this); theSubGrid.addKeyListener(this); TableColumn column = null; for (int i = 0; i < 3; i++) { column = theSubGrid.getColumnModel().getColumn(i); column.setMaxWidth(25); } mainGrid.add(theSubGrid); } } /*========================================*/ /* Set up the panel containing the Clear, */ /* Reset, Solve, and Techniques buttons. */ /*========================================*/ JPanel buttonGrid = new JPanel(); theLayout = new GridLayout(4,1); buttonGrid.setLayout(theLayout); buttonGrid.setOpaque(true); clearButton = new JButton(sudokuResources.getString("Clear")); clearButton.setActionCommand("Clear"); buttonGrid.add(clearButton); clearButton.addActionListener(this); clearButton.setToolTipText(sudokuResources.getString("ClearTip")); resetButton = new JButton(sudokuResources.getString("Reset")); resetButton.setActionCommand("Reset"); resetButton.setEnabled(false); buttonGrid.add(resetButton); resetButton.addActionListener(this); resetButton.setToolTipText(sudokuResources.getString("ResetTip")); solveButton = new JButton(sudokuResources.getString("Solve")); solveButton.setActionCommand("Solve"); buttonGrid.add(solveButton); solveButton.addActionListener(this); solveButton.setToolTipText(sudokuResources.getString("SolveTip")); techniquesButton = new JButton(sudokuResources.getString("Techniques")); techniquesButton.setActionCommand("Techniques"); techniquesButton.setEnabled(false); buttonGrid.add(techniquesButton); techniquesButton.addActionListener(this); techniquesButton.setToolTipText(sudokuResources.getString("TechniquesTip")); /*=============================================*/ /* Add the grid and button panels to the pane. */ /*=============================================*/ JPanel mainPanel = new JPanel(); mainPanel.setLayout(new FlowLayout()); mainPanel.add(mainGrid); mainPanel.add(buttonGrid); jfrm.getContentPane().add(mainPanel,BorderLayout.NORTH); JLabel instructions = new JLabel("

" + sudokuResources.getString("Instructions") + "


"); JPanel labelPanel = new JPanel(); labelPanel.setLayout(new FlowLayout()); labelPanel.add(instructions); jfrm.getContentPane().add(labelPanel,BorderLayout.SOUTH); /*==========================*/ /* Load the sudoku program. */ /*==========================*/ clips = new Environment(); try { clips.loadFromResource("/net/sf/clipsrules/jni/examples/sudoku/resources/sudoku.clp"); clips.loadFromResource("/net/sf/clipsrules/jni/examples/sudoku/resources/solve.clp"); } catch (Exception e) { e.printStackTrace(); System.exit(1); } /**********************/ /* Display the frame. */ /**********************/ jfrm.pack(); jfrm.setVisible(true); } /********/ /* main */ /********/ public static void main( String args[]) { /*===================================================*/ /* Create the frame on the event dispatching thread. */ /*===================================================*/ SwingUtilities.invokeLater( new Runnable() { public void run() { new SudokuDemo(); } }); } /*########################*/ /* ActionListener Methods */ /*########################*/ /*******************/ /* actionPerformed */ /*******************/ public void actionPerformed( ActionEvent ae) { try { onActionPerformed(ae); } catch (Exception e) { e.printStackTrace(); } } /*************/ /* runSudoku */ /*************/ public void runSudoku() { Runnable runThread = new Runnable() { public void run() { try { clips.run(); } catch (CLIPSException e) { e.printStackTrace(); } SwingUtilities.invokeLater( new Runnable() { public void run() { try { updateGrid(); } catch (Exception e) { e.printStackTrace(); } } }); } }; isExecuting = true; executionThread = new Thread(runThread); jfrm.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); executionThread.start(); } /*********************/ /* onActionPerformed */ /*********************/ public void onActionPerformed( ActionEvent ae) throws Exception { if (isExecuting) return; /*==========================*/ /* Handle the Clear button. */ /*==========================*/ if (ae.getActionCommand().equals("Clear")) { solved = false; solveButton.setEnabled(true); techniquesButton.setEnabled(false); for (int i = 0; i < 9; i++) { JTable theTable = (JTable) mainGrid.getComponent(i); for (int r = 0; r < 3; r++) { for (int c = 0; c < 3; c++) { theTable.setValueAt("",r,c); } } } } /*==========================*/ /* Handle the Reset button. */ /*==========================*/ else if (ae.getActionCommand().equals("Reset")) { solved = false; solveButton.setEnabled(true); techniquesButton.setEnabled(false); for (int i = 0; i < 9; i++) { JTable theTable = (JTable) mainGrid.getComponent(i); for (int r = 0; r < 3; r++) { for (int c = 0; c < 3; c++) { theTable.setValueAt(resetValues[i][r][c],r,c); } } } } /*==========================*/ /* Handle the Solve button. */ /*==========================*/ else if (ae.getActionCommand().equals("Solve")) { /*==============*/ /* Reset CLIPS. */ /*==============*/ clips.reset(); clips.assertString("(phase expand-any)"); clips.assertString("(size 3)"); /*======================================*/ /* Remember the initial starting values */ /* of the puzzle for the reset command. */ /*======================================*/ for (int i = 0; i < 9; i++) { JTable theTable = (JTable) mainGrid.getComponent(i); int rowGroup = i / 3; int colGroup = i % 3; for (int r = 0; r < 3; r++) { for (int c = 0; c < 3; c++) { resetValues[i][r][c] = theTable.getValueAt(r,c); String assertStr; assertStr = "(possible (row " + (r + (rowGroup * 3) + 1) + ") " + "(column " + (c + (colGroup * 3) + 1) + ") " + "(group " + (i + 1) + ") " + "(id " + ((i * 9) + (r * 3) + c + 1) + ") "; if ((resetValues[i][r][c] == null) || (resetValues[i][r][c].equals(""))) { assertStr = assertStr + "(value any))"; } else { assertStr = assertStr + "(value " + resetValues[i][r][c] + "))"; } clips.assertString(assertStr); } } } /*===================================*/ /* Update the status of the buttons. */ /*===================================*/ clearButton.setEnabled(false); resetButton.setEnabled(false); solveButton.setEnabled(false); techniquesButton.setEnabled(false); /*===================*/ /* Solve the puzzle. */ /*===================*/ runSudoku(); } /*===============================*/ /* Handle the Techniques button. */ /*===============================*/ else if (ae.getActionCommand().equals("Techniques")) { String evalStr; String messageStr = "

"; List techniqueFacts = clips.findAllFacts("technique"); int tNum = techniqueFacts.size(); for (int i = 1; i <= tNum; i++) { FactAddressValue fv = clips.findFact("?f","technique-employed","(eq ?f:priority " + i + ")"); if (fv == null) continue; messageStr = messageStr + ((NumberValue) fv.getSlotValue("priority")).intValue() + ". " + ((LexemeValue) fv.getSlotValue("reason")).getValue() + "
"; } JOptionPane.showMessageDialog(jfrm,messageStr,sudokuResources.getString("SolutionTechniques"),JOptionPane.PLAIN_MESSAGE); } } /**************/ /* updateGrid */ /**************/ private void updateGrid() throws Exception { /*===================================*/ /* Retrieve the solution from CLIPS. */ /*===================================*/ for (int i = 0; i < 9; i++) { JTable theTable = (JTable) mainGrid.getComponent(i); int rowGroup = i / 3; int colGroup = i % 3; for (int r = 0; r < 3; r++) { for (int c = 0; c < 3; c++) { resetValues[i][r][c] = theTable.getValueAt(r,c); if ((resetValues[i][r][c] != null) && (! resetValues[i][r][c].equals(""))) { continue; } String condition = "(and (eq ?f:row " + (r + (rowGroup * 3) + 1) + ") " + "(eq ?f:column " + (c + (colGroup * 3) + 1) + "))"; List possibleValues = clips.findAllFacts("?f","possible",condition); if (possibleValues.size() != 1) continue; FactAddressValue fv = possibleValues.get(0); theTable.setValueAt(" " + fv.getSlotValue("value") + " ",r,c); } } } /*===============================================*/ /* Any cells that have not been assigned a value */ /* are given a '?' for their content. */ /*===============================================*/ for (int i = 0; i < 9; i++) { JTable theTable = (JTable) mainGrid.getComponent(i); for (int r = 0; r < 3; r++) { for (int c = 0; c < 3; c++) { if ((theTable.getValueAt(r,c) == null) || (theTable.getValueAt(r,c).equals(""))) { theTable.setValueAt("?",r,c); } } } } /*===================================*/ /* Update the status of the buttons. */ /*===================================*/ jfrm.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); solved = true; clearButton.setEnabled(true); resetButton.setEnabled(true); solveButton.setEnabled(false); techniquesButton.setEnabled(true); executionThread = null; isExecuting = false; } /*#######################*/ /* FocusListener Methods */ /*#######################*/ /***************/ /* focusGained */ /***************/ public void focusGained(FocusEvent e) {} /*************/ /* focusLost */ /*************/ public void focusLost(FocusEvent e) { JTable theTable = (JTable) e.getComponent(); int r = theTable.getEditingRow(); int c = theTable.getEditingColumn(); /*====================================================*/ /* If a cell wasn't being edited, do nothing further. */ /*====================================================*/ if ((r == -1) || (c == -1)) return; /*========================*/ /* Stop editing the cell. */ /*========================*/ TableCellEditor tableCellEditor = theTable.getCellEditor(r,c); tableCellEditor.stopCellEditing(); /*=================================*/ /* Clear selections for the table. */ /*=================================*/ theTable.clearSelection(); } /*#####################*/ /* KeyListener Methods */ /*#####################*/ /**************/ /* keyPressed */ /**************/ public void keyPressed(KeyEvent e) {} /***************/ /* keyReleased */ /***************/ public void keyReleased(KeyEvent e) {} /************/ /* keyTyped */ /************/ public void keyTyped( KeyEvent e) { JTable theTable = (JTable) e.getComponent(); int row = theTable.getSelectedRow(); int col = theTable.getSelectedColumn(); /*=================================*/ /* Cells can't be change while the */ /* puzzle is in solution state. */ /*=================================*/ if (solved || isExecuting) return; /*=================================================*/ /* If a cell isn't selected, ignore the typed key. */ /*=================================================*/ if ((row == -1) || (col == -1)) return; /***************************/ /* Retrieve the typed key. */ /***************************/ char theChar = e.getKeyChar(); /*=======================================================*/ /* A backspace removes the value from the selected cell. */ /*=======================================================*/ if (theChar == '\b') { theTable.setValueAt("",row,col); return; } /*=========================================================*/ /* Any character other than the digits 1 to 9 is invalid. */ /*=========================================================*/ if ((theChar != '1') && (theChar != '2') && (theChar != '3') && (theChar != '4') && (theChar != '5') && (theChar != '6') && (theChar != '7') && (theChar != '8') && (theChar != '9')) { Toolkit.getDefaultToolkit().beep(); return; } /*=====================================*/ /* Set the value of the selected cell. */ /*=====================================*/ String theCharStr = Character.toString(theChar); theTable.setValueAt(theCharStr,row,col); /*===========================================*/ /* Remove any other occurences of this digit */ /* from the same 3x3 grid. */ /*===========================================*/ for (int r = 0; r < 3; r++) { for (int c = 0; c < 3; c++) { if (((r != row) || (c != col)) && (theCharStr.equals(theTable.getValueAt(r,c)))) { theTable.setValueAt("",r,c); } } } } }