import jvstm.*;
import pt.ist.esw.atomicannotation.Atomic;
public class SudokuSolver2 {
private Cell[][] cells = new Cell[9][9];
SudokuSolver2() {
// create all cells
for (int r = 0; r < 9; r++) {
for (int c = 0; c < 9; c++) {
cells[r][c] = new Cell(r, c);
}
}
}
void removeChoiceFromNeighbors(int row, int col, int choice) {
int regionRow = row / 3;
int regionCol = col / 3;
for (int r = 0; r < 9; r++) {
for (int c = 0; c < 9; c++) {
if (! ((row == r) && (col == c))) {
if ((row == r) || (col == c) ||
((regionRow == (r / 3)) && (regionCol == (c / 3)))) {
cells[r][c].removeChoice(choice);
}
}
}
}
}
void setGivens(String givens) {
int pos = 0;
for (Cell[] row : cells) {
for (Cell cell : row) {
char given = givens.charAt(pos);
pos++;
if (given != '?') {
cell.setValue(Character.digit(given, 10));
}
}
}
}
void print() {
for (Cell[] row : cells) {
System.out.println();
for (Cell cell : row) {
cell.print();
}
System.out.println();
}
}
void solve() {
Cell unsolvedCell = findUnsolvedCell();
if (unsolvedCell != null) {
tryValuesFor(unsolvedCell);
}
}
void tryValuesFor(Cell cell) {
int first = cell.getFirstChoice();
try {
tryOneValue(cell, first);
} catch (Fail f) {
cell.removeChoice(first);
tryValuesFor(cell);
}
}
@Atomic void tryOneValue(Cell cell, int num) {
cell.setValue(num);
solve();
}
Cell findUnsolvedCell() {
for (Cell[] row : cells) {
for (Cell c : row) {
if (c.countChoices() > 1) {
return c;
}
}
}
return null;
}
public static void main(String[] args) {
SudokuSolver2 solver = new SudokuSolver2();
long start = System.currentTimeMillis();
solver.setGivens(args[0]);
solver.solve();
System.out.println("Solved in " + (System.currentTimeMillis() - start) + "ms");
solver.print();
}
static class Choice extends VBox<Boolean> {
Choice() {
super(true);
}
}
class Cell {
private final int row;
private final int col;
private Choice[] choices = new Choice[9];
private VBox<Integer> numChoices = new VBox<Integer>(9);
Cell(int row, int col) {
this.row = row;
this.col = col;
for (int i = 0; i < 9; i++) {
choices[i] = new Choice();
}
}
boolean hasChoice(int num) {
return choices[num-1].get();
}
void setChoice(int num, boolean possible) {
choices[num-1].put(possible);
}
void removeChoice(int num) {
if (hasChoice(num)) {
setChoice(num, false);
numChoices.put(numChoices.get() - 1);
checkChoices();
}
}
void checkChoices() {
int numChoices = countChoices();
if (numChoices == 0) {
throw new Fail();
} else if (numChoices == 1) {
removeChoiceFromNeighbors(row, col, getFirstChoice());
}
}
void setValue(int newValue) {
if (! hasChoice(newValue)) {
throw new Fail();
}
for (int i = 1; i <= choices.length; i++) {
setChoice(i, i == newValue);
}
numChoices.put(1);
checkChoices();
}
int getFirstChoice() {
for (int i = 1; i <= choices.length; i++) {
if (hasChoice(i)) {
return i;
}
}
return 0;
}
int countChoices() {
return numChoices.get();
}
void print() {
if (countChoices() == 1) {
System.out.print(" " + getFirstChoice() + " ");
} else {
System.out.print(" ? ");
}
}
}
static class Fail extends RuntimeException {}
}