Skip to content

Instantly share code, notes, and snippets.

@jeeeyul
Created September 7, 2012 09:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jeeeyul/3664685 to your computer and use it in GitHub Desktop.
Save jeeeyul/3664685 to your computer and use it in GitHub Desktop.
Generate or Resolve Sudoku Puzzle
package net.jeeeyul.sudoku.model.util;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.Stack;
/**
* An instance of this class will generate or solve sudoku puzzle.
*
* @author jeeeyul@gmail.com
*
* @see #getNextSolution()
*
*/
public class SudokuResolver {
/**
* Thrown when there is no answer for given condition.
*
* @author Jeeeyul
*
*/
public class NoAnswerException extends IllegalStateException {
private static final long serialVersionUID = -1444620548292314974L;
public NoAnswerException() {
super("There is no answer.");
}
}
private class SearchSequence {
private int[] sequence;
private int currentIndex;
public SearchSequence(int[] sequence) {
this.sequence = sequence;
currentIndex = 0;
}
public boolean canNext() {
return currentIndex < sequence.length - 1;
}
public void next() {
currentIndex++;
data[dataIndex] = sequence[currentIndex];
}
}
private static final List<Integer> ALL_NUMBERS = Arrays.asList(1, 2, 3, 4,
5, 6, 7, 8, 9);
private static final int[] EMPTY_SEQUENCE = new int[0];
private boolean isResolved = false;
private Random random;
private int[] data;
private boolean[] fixed;
private Stack<SearchSequence> stack;
private int dataIndex;
private long randomSeed;
private long stepCount;
/**
* Create a {@link SudokuResolver} without any condition. {@link #resolve()}
* will generate a new game.
*/
public SudokuResolver() {
this(System.currentTimeMillis());
}
/**
* Create a {@link SudokuResolver} with condition.
*
* @param puzzle
* A condition. it is 81 length int array. Each value must be 0
* to 9. Zero value will be solved by {@link #resolve()}.
*/
public SudokuResolver(int[] puzzle) {
this(System.currentTimeMillis(), puzzle);
}
/**
* Create a {@link SudokuResolver} without any condition. {@link #resolve()}
* will generate a new game.
*
* @param seed
* Random seed. Same random seed cause always same result.
*/
public SudokuResolver(long seed) {
data = new int[81];
fixed = new boolean[81];
dataIndex = -1;
stack = new Stack<SudokuResolver.SearchSequence>();
randomSeed = seed;
random = new Random(seed);
stepCount = 0;
}
/**
* Create a {@link SudokuResolver} with condition and random seed.
*
* @param seed
* Random seed. Same random seed cause always same result.
* @param puzzle
* A condition. it is 81 length int array. Each value must be 0
* to 9. Zero value will be solved by {@link #resolve()}.
*/
public SudokuResolver(long seed, int[] puzzle) {
this(seed);
if (puzzle.length != 81) {
throw new IllegalArgumentException(
"Puzzle must be 81 length array.");
}
for (int i = 0; i < puzzle.length; i++) {
if (puzzle[i] != 0) {
data[i] = puzzle[i];
fixed[i] = true;
}
}
}
private SearchSequence currentSequence() {
if (stack.isEmpty()) {
return null;
}
return stack.peek();
}
private int getAt(int row, int col) {
return data[row * 9 + col];
}
private int[] getAvailableNumberFor(int index) {
if (!validateContext()) {
return EMPTY_SEQUENCE;
}
if (fixed[index]) {
return new int[] { data[index] };
}
List<Integer> result = new ArrayList<Integer>(ALL_NUMBERS);
int row = index / 9;
int col = index % 9;
// Horizontal, Vertical Conflict
for (int x = 0; x < 9; x++) {
result.remove((Integer) getAt(x, col));
result.remove((Integer) getAt(row, x));
}
int bRow = row / 3;
int bCol = col / 3;
for (int rd = 0; rd < 3; rd++) {
for (int cd = 0; cd < 3; cd++) {
result.remove((Integer) getAt(bRow * 3 + rd, bCol * 3 + cd));
}
}
Collections.shuffle(result, random);
int size = result.size();
int[] array = new int[size];
for (int i = 0; i < result.size(); i++) {
array[i] = result.get(i);
}
return array;
}
/**
* resolve given puzzle, What if there is no given condition It will
* generate a new complete puzzle.
*
* If there was no answer, It will throw a {@link NoAnswerException}.
*
* @return an int array contains completed sudoku puzzle.
*
* @see #setFixedValue(int, int, int)
* @see #Search(int[])
* @see #Search(long, int[])
*
* @see NoAnswerException
*/
public synchronized int[] getNextSolution() {
if (!isResolved) {
return resolve();
} else {
step();
return resolve();
}
}
public long getStepCount() {
return stepCount;
}
private boolean isFilled() {
for (int i = 80; i >= 0; i--) {
if (data[i] == 0) {
return false;
}
}
return true;
}
private void pop() {
stack.pop();
if (!fixed[dataIndex]) {
data[dataIndex] = 0;
}
dataIndex--;
if (stack.isEmpty())
throw new NoAnswerException();
}
private void push(int[] sequence) {
if (sequence == null || sequence.length == 0) {
throw new IllegalArgumentException();
}
stack.push(new SearchSequence(sequence));
dataIndex++;
data[dataIndex] = sequence[0];
}
public synchronized void reset() {
isResolved = false;
while (!stack.isEmpty()) {
stack.pop();
if (!fixed[dataIndex]) {
data[dataIndex] = 0;
}
dataIndex--;
}
random = new Random(randomSeed);
stepCount = 0;
}
private synchronized int[] resolve() {
while (!isFilled()) {
step();
}
isResolved = true;
return Arrays.copyOf(data, data.length);
}
private void setFixedValue(int index, int val) {
if (index < 0 || 80 < index || val < 1 || 9 < val) {
throw new IllegalArgumentException();
}
data[index] = val;
fixed[index] = true;
}
/**
* Sets a condition.
*
* @param row
* Row index in sudoku, it must be 0 to 8.
* @param col
* Column index in sudoku, it must be 0 to 8.
* @param val
* Value to set. It must be 1 to 9.
*/
public synchronized void setFixedValue(int row, int col, int val) {
if (isResolved) {
throw new IllegalStateException(
"Can't modify condition after resolving.");
}
if (row < 0 || 8 < row || col < 0 || 8 < col || val < 1 || 9 < val) {
throw new IllegalArgumentException();
}
int index = row * 9 + col;
setFixedValue(index, val);
}
private void step() {
stepCount++;
boolean isFilled = isFilled();
if (currentSequence() == null) {
int[] nextSequence = getAvailableNumberFor(dataIndex + 1);
if (nextSequence.length == 0) {
throw new NoAnswerException();
}
push(nextSequence);
}
if (isFilled) {
if (currentSequence().canNext()) {
currentSequence().next();
} else {
while (!currentSequence().canNext()) {
pop();
}
currentSequence().next();
}
}
else {
int[] nextSequence = getAvailableNumberFor(dataIndex + 1);
boolean hasNextBranch = nextSequence.length > 0;
if (!hasNextBranch && currentSequence().canNext()) {
currentSequence().next();
}
else if (hasNextBranch) {
push(nextSequence);
}
else {
while (!currentSequence().canNext()) {
pop();
}
currentSequence().next();
}
}
}
private boolean validateContext() {
// horizontal conflict
for (int r = 0; r < 9; r++) {
int flag = 0x0;
for (int c = 0; c < 9; c++) {
int val = getAt(r, c);
if (val != 0) {
int mask = 0x1 << (val - 1);
if ((flag & mask) != 0) {
return false;
} else {
flag |= mask;
}
}
}
}
// vertical conflict
for (int c = 0; c < 9; c++) {
int flag = 0x0;
for (int r = 0; r < 9; r++) {
int val = getAt(r, c);
if (val != 0) {
int mask = 0x1 << (val - 1);
if ((flag & mask) != 0) {
return false;
} else {
flag |= mask;
}
}
}
}
// box conflict
for (int br = 0; br < 3; br++) {
for (int bc = 0; bc < 3; bc++) {
int flag = 0x00;
for (int r = 0; r < 3; r++) {
for (int c = 0; c < 3; c++) {
int index = (br * 3 + r) * 9 + bc * 3 + c;
if (data[index] != 0) {
int mask = 0x1 << (data[index] - 1);
if ((flag & mask) != 0) {
return false;
} else {
flag |= mask;
}
}
}
}
}
}
return true;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment