| 22  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | 20  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | 
| 23  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | 21  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | 
| 24  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | 22  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | 
| 25  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | 23  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | 
| 26  * POSSIBILITY OF SUCH DAMAGE. | 24  * POSSIBILITY OF SUCH DAMAGE. | 
| 27  * |  | 
| 28  */ | 25  */ | 
| 29 | 26 | 
| 30 package de.uapcore.sigred.doc.base; | 27 package de.uapcore.sudoku; | 
| 31 | 28 | 
| 32 import de.uapcore.sigred.doc.Resources; | 29 import java.util.ArrayList; | 
| 33 import de.uapcore.sigrapi.impl.Digraph; | 30 import java.util.List; | 
| 34 import de.uapcore.sigrapi.impl.Graph; | 31 | 
| 35 import de.uapcore.sigrapi.IGraph; | 32 /** | 
| 36 import java.io.IOException; | 33  * Implements the backtracking algorithm for solving the Sudoku. | 
| 37 import java.io.InputStream; | 34  */ | 
| 38 import java.io.OutputStream; | 35 public final class Solver { | 
| 39 import java.util.concurrent.atomic.AtomicBoolean; | 36 | 
| 40 import java.util.concurrent.atomic.AtomicReference; | 37     public static final int VERSION = 0x1000; | 
| 41 import org.apache.xerces.impl.Constants; | 38 | 
| 42 import org.dom4j.Document; | 39     private Integer fillInCandidate(Field f, List<Integer>[][] candidates, int x, int y) { | 
| 43 import org.dom4j.DocumentException; | 40         Integer c = candidates[x][y].remove(0); | 
| 44 import org.dom4j.DocumentHelper; | 41         f.setCellValue(x, y, c); | 
| 45 import org.dom4j.Element; | 42         f.setCellModified(x, y, true); | 
| 46 import org.dom4j.Namespace; | 43         for (int i = 0 ; i < 9 ; i++) { | 
| 47 import org.dom4j.QName; | 44             candidates[x][i].remove(c); | 
| 48 import org.dom4j.io.OutputFormat; | 45             candidates[i][y].remove(c); | 
| 49 import org.dom4j.io.SAXReader; | 46         } | 
| 50 import org.dom4j.io.XMLWriter; | 47         for (int i = 0 ; i < 3 ; i++) { | 
| 51 import org.xml.sax.ErrorHandler; | 48             for (int j = 0 ; j < 3 ; j++) { | 
| 52 import org.xml.sax.SAXException; | 49                 candidates[x-x%3+i][y-y%3+j].remove(c); | 
| 53 import org.xml.sax.SAXParseException; | 50             } | 
| 54 | 51         } | 
| 55 public abstract class AbstractGraphDocument<T extends IGraph> | 52         return c; | 
| 56         extends FileBackedDocument { | 53     } | 
| 57 | 54 | 
| 58     protected static final Namespace NAMESPACE = Namespace.get("sigred", | 55     private void makeBackup(List<Integer>[][] candidates, List<Integer>[][] candidatesBackup) { | 
| 59         "http://develop.uap-core.de/sigred/"); | 56         for (int x = 0 ; x < 9 ; x++) { | 
| 60 | 57             for (int y = 0 ; y < 9 ; y++) { | 
| 61     private static final | 58                 candidatesBackup[x][y] = new ArrayList<>(9); | 
| 62         QName TAG_GRAPHDOC = QName.get("graph-document", NAMESPACE); | 59                 candidatesBackup[x][y].addAll(candidates[x][y]); | 
| 63     private static final | 60             } | 
| 64         QName TAG_GRAPH = QName.get("graph", NAMESPACE); | 61         } | 
| 65     private static final | 62     } | 
| 66         QName TAG_DIGRAPH = QName.get("digraph", NAMESPACE); | 63 | 
| 67     private static final | 64     private void makeBackup(Field f, int[][] fieldBackup) { | 
| 68         QName TAG_METADATA = QName.get("metadata", NAMESPACE); | 65         for (int x = 0 ; x < 9 ; x++) { | 
| 69 | 66             for (int y = 0 ; y < 9 ; y++) { | 
| 70     protected final T graph; | 67                 fieldBackup[x][y] = f.getCellValue(x, y); | 
| 71 | 68             } | 
| 72     private final GraphDocumentMetadata metadata; | 69         } | 
| 73 | 70     } | 
| 74     public AbstractGraphDocument(Class<T> graphType) { | 71 | 
| 75         T g; | 72     private void restoreBackup(Field f, int[][] fieldBackup) { | 
| 76         try { | 73         for (int x = 0 ; x < 9 ; x++) { | 
| 77             g = graphType.newInstance(); | 74             for (int y = 0 ; y < 9 ; y++) { | 
| 78         } catch (ReflectiveOperationException e) { | 75                 f.setCellValue(x, y, fieldBackup[x][y]); | 
| 79             assert false; | 76             } | 
| 80             g = null; // for the compiler | 77         } | 
| 81         } | 78     } | 
| 82         graph = g; | 79 | 
| 83         metadata = new GraphDocumentMetadata(); | 80     private void restoreBackup(List<Integer>[][] candidates, List<Integer>[][] candidatesBackup) { | 
| 84     } | 81         for (int x = 0 ; x < 9 ; x++) { | 
| 85 | 82             for (int y = 0 ; y < 9 ; y++) { | 
| 86     public T getGraph() { | 83                 candidates[x][y].clear(); | 
| 87         return graph; | 84                 candidates[x][y].addAll(candidatesBackup[x][y]); | 
| 88     } | 85             } | 
| 89 | 86         } | 
| 90     public GraphDocumentMetadata getMetadata() { | 87     } | 
| 91         return metadata; | 88 | 
| 92     } | 89     private boolean solve(Field f, List<Integer>[][] candidates) { | 
| 93 | 90 | 
| 94     protected abstract void writeGraph(Element rootNode) throws IOException; | 91         // Make backup | 
| 95     protected abstract void readGraph(Element rootNode) throws IOException; | 92         List<Integer>[][] candidatesBackup = new List[9][9]; | 
| 96 | 93         int[][] fieldBackup = new int[9][9]; | 
| 97     @Override | 94         makeBackup(candidates, candidatesBackup); | 
| 98     public void writeTo(OutputStream out) throws IOException { | 95         makeBackup(f, fieldBackup); | 
| 99         Document doc = DocumentHelper.createDocument(); | 96 | 
| 100 | 97         // Fill in distinct solutions | 
| 101         Element rootNode = doc.addElement(TAG_GRAPHDOC); | 98         boolean fillDistinct; | 
| 102 | 99         do { | 
| 103         Element metadataNode = rootNode.addElement(TAG_METADATA); | 100             fillDistinct = false; | 
| 104 | 101             for (int x = 0 ; x < 9 ; x++) { | 
| 105         metadata.write(metadataNode); | 102                 for (int y = 0 ; y < 9 ; y++) { | 
| 106 | 103                     if (f.isCellEmpty(x, y) && candidates[x][y].size() == 1) { | 
| 107         if (graph instanceof Graph) { | 104                         fillInCandidate(f, candidates, x, y); | 
| 108             writeGraph(rootNode.addElement(TAG_GRAPH)); | 105                         fillDistinct = true; | 
| 109         } else if (graph instanceof Digraph) { | 106                     } | 
| 110             writeGraph(rootNode.addElement(TAG_DIGRAPH)); | 107                 } | 
| 111         } else { | 108             } | 
| 112             throw new IOException("unsupported graph type"); | 109         } while (fillDistinct); | 
| 113         } | 110 | 
| 114 | 111         // Try out remaining candidates | 
| 115         XMLWriter writer = new XMLWriter(out, OutputFormat.createPrettyPrint()); | 112         for (int x = 0 ; x < 9 ; x++) { | 
| 116         writer.write(doc); | 113             for (int y = 0 ; y < 9 ; y++) { | 
| 117         writer.flush(); | 114                 if (f.isCellEmpty(x, y)) { | 
| 118     } | 115                     while (candidates[x][y].size() > 0) { | 
| 119 | 116                         List<Integer>[][] cb = new List[9][9]; | 
| 120     @Override | 117                         makeBackup(candidates, cb); | 
| 121     public void readFrom(InputStream in) throws IOException { | 118                         Integer c = fillInCandidate(f, candidates, x, y); | 
| 122         try { | 119                         if (solve(f, candidates)) { | 
| 123             SAXReader reader = new SAXReader(true); | 120                             break; | 
| 124             reader.setStripWhitespaceText(true); | 121                         } else { | 
| 125 | 122                             f.clearCellValue(x, y); | 
| 126             reader.setFeature(Constants.XERCES_FEATURE_PREFIX+ | 123                             restoreBackup(candidates, cb); | 
| 127                 Constants.SCHEMA_VALIDATION_FEATURE, true); | 124                             // Remove current candidate anyway | 
| 128             reader.setProperty(Constants.XERCES_PROPERTY_PREFIX + | 125                             candidates[x][y].remove(c); | 
| 129                 Constants.SCHEMA_LOCATION, String.format("%s %s", | 126                         } | 
| 130                     NAMESPACE.getURI(), Resources.class.getResource( | 127                     } | 
| 131                         "graph-document.xsd").toExternalForm())); | 128                 } | 
| 132 | 129                 if (f.isCellEmpty(x, y)) { | 
| 133             final AtomicBoolean passed = new AtomicBoolean(true); | 130                     restoreBackup(f, fieldBackup); | 
| 134             final AtomicReference<SAXParseException> xmlerror = new AtomicReference<>(); | 131                     restoreBackup(candidates, candidatesBackup); | 
| 135             // TODO: we should do more detailed error handling here | 132                     return false; | 
| 136             reader.setErrorHandler(new ErrorHandler() { | 133                 } | 
| 137                 @Override | 134             } | 
| 138                 public void warning(SAXParseException exception) throws SAXException { | 135         } | 
| 139                 } | 136 | 
| 140 | 137         return true; | 
| 141                 @Override | 138     } | 
| 142                 public void error(SAXParseException exception) throws SAXException { | 139 | 
| 143                     xmlerror.set(exception); | 140     /** | 
| 144                     passed.set(false); | 141      * Attempts to solve the given Sudoku field. | 
| 145                 } | 142      * | 
| 146 | 143      * The solution, if any, is directly entered into the field. | 
| 147                 @Override | 144      * All solved fields will be in modified state. | 
| 148                 public void fatalError(SAXParseException exception) throws SAXException { | 145      * The already given fields are left untouched. | 
| 149                     xmlerror.set(exception); | 146      * | 
| 150                     passed.set(false); | 147      * @param f the field to solve | 
| 151                 } | 148      * @return true if a solution could be found, false if the field is unsolvable | 
| 152 | 149      */ | 
| 153             }); | 150     public boolean solve(Field f) { | 
| 154             Document doc = reader.read(in); | 151 | 
| 155             if (!passed.get()) { | 152         // Calculate initial candidates | 
| 156                 // TODO: provide details (maybe via separate error object?) | 153         List<Integer>[][] candidates = new List[9][9]; | 
| 157                 throw xmlerror.get(); | 154         for (int x = 0 ; x < 9 ; x++) { | 
| 158             } | 155             for (int y = 0 ; y < 9 ; y++) { | 
| 159 | 156                 candidates[x][y] = new ArrayList<>(9); | 
| 160             doc.normalize(); | 157                 if (f.getCellValue(x, y) == 0) { | 
| 161 | 158                     // All numbers are candidates | 
| 162             Element root = doc.getRootElement(); | 159                     for (int c = 1 ; c <= 9 ; c++) { | 
| 163             metadata.read(root.element(TAG_METADATA)); | 160                         candidates[x][y].add(c); | 
| 164 | 161                     } | 
| 165             if (graph instanceof Graph) { | 162                     // Remove row duplicates | 
| 166                 readGraph(root.element(TAG_GRAPH)); | 163                     int[] line = f.getRow(y); | 
| 167             } else if (graph instanceof Digraph) { | 164                     for (Integer c : line) { | 
| 168                 readGraph(root.element(TAG_DIGRAPH)); | 165                         candidates[x][y].remove(c); | 
| 169             } else { | 166                     } | 
| 170                 throw new IOException("unsupported graph type"); | 167                     // Remove column duplicates | 
| 171             } | 168                     line = f.getColumn(x); | 
| 172         } catch (DocumentException | SAXException ex) { | 169                     for (Integer c : line) { | 
| 173             throw new IOException(ex); | 170                         candidates[x][y].remove(c); | 
| 174         } | 171                     } | 
|  | 172                     // Remove square duplicates | 
|  | 173                     int[][] square = f.getSquare(x/3, y/3); | 
|  | 174                     for (int[] sq : square) { | 
|  | 175                         for (Integer c : sq) { | 
|  | 176                             candidates[x][y].remove(c); | 
|  | 177                         } | 
|  | 178                     } | 
|  | 179                 } | 
|  | 180             } | 
|  | 181         } | 
|  | 182 | 
|  | 183         // Backtrack | 
|  | 184         return solve(f, candidates); | 
|  | 185     } | 
|  | 186 | 
|  | 187     /** | 
|  | 188      * Performs a fast check whether any field violates the Sudoku rules. | 
|  | 189      * | 
|  | 190      * @param f the field to check | 
|  | 191      * @return true, if the check succeeds, false otherwise | 
|  | 192      */ | 
|  | 193     public boolean check(Field f) { | 
|  | 194         int[] line; | 
|  | 195         for (int i = 0 ; i < 9 ; i++) { | 
|  | 196             line = f.getRow(i); | 
|  | 197             if (!valid(line)) { | 
|  | 198                 return false; | 
|  | 199             } | 
|  | 200             line = f.getColumn(i); | 
|  | 201             if (!valid(line)) { | 
|  | 202                 return false; | 
|  | 203             } | 
|  | 204         } | 
|  | 205 | 
|  | 206         int[][] square; | 
|  | 207         for (int x = 0 ; x < 3 ; x++) { | 
|  | 208             for (int y = 0 ; y < 3 ; y++) { | 
|  | 209                 square = f.getSquare(x, y); | 
|  | 210                 if (!valid(square)) { | 
|  | 211                     return false; | 
|  | 212                 } | 
|  | 213             } | 
|  | 214         } | 
|  | 215 | 
|  | 216         return true; | 
|  | 217     } | 
|  | 218 | 
|  | 219     private boolean valid(int[] line) { | 
|  | 220         int[] numbers; | 
|  | 221         numbers = new int[9]; | 
|  | 222         for (int i = 0 ; i < 9 ; i++) { | 
|  | 223             int l = line[i]-1; | 
|  | 224             if (l >= 0) { | 
|  | 225                 if ((++numbers[l]) > 1) { | 
|  | 226                     return false; | 
|  | 227                 } | 
|  | 228             } | 
|  | 229         } | 
|  | 230 | 
|  | 231         return true; | 
|  | 232     } | 
|  | 233 | 
|  | 234     private boolean valid(int[][] square) { | 
|  | 235         int[] line = new int[9]; | 
|  | 236         for (int x = 0 ; x < 3 ; x++) { | 
|  | 237             System.arraycopy(square[x], 0, line, 3*x, 3); | 
|  | 238         } | 
|  | 239         return valid(line); | 
| 175     } | 240     } | 
| 176 } | 241 } |