blob: e8961ab3faa9b17485aacd40198114fe946d0b0a [file] [log] [blame]
// Copyright [2007] [Austin Chau]
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
// Namespace assigned to this library of sudoku-related classes
var sudoku = {};
/**
* Remove an element from an array object.
* @param {Object} value An element to be removed.
*/
Array.prototype.remove = function(value) {
for (var i = 0; i < this.length; i++) {
if (this[i] == value) {
this.splice(i, 1);
break;
}
}
};
/**
* Equality comparison between two array objects with only numeric elements.
* @param {Array.<Number>} array The array object to be compared against.
* @return {Boolean} True/false to whether the numeric arrays are identical.
*/
Array.prototype.equals = function(array) {
var ret = true;
if (!array instanceof Array) {
ret = false;
} else {
if (this.length != array.length) {
ret = false;
} else {
for (var i = 0; i < array.length; i++) {
if (this[i] != array[i]) {
ret = false;
break;
}
}
}
}
return ret;
};
/**
* Randomly shuffle the order of elements within the array.
*/
Array.prototype.shuffle = function() {
for (var i = 0; i < this.length; i++) {
var newPos = parseInt(Math.random() * i);
var current = this[i];
var temp = this[newPos];
this[newPos] = current;
this[i] = temp;
}
};
/**
* Check if a number exists in an array.
* @param {Number} num The number to be looked for in the array.
* @return {Boolean} True/false to whether the number exists in the array.
*/
Array.prototype.contains = function(num) {
var ret = false;
for (var i = 0; i < this.length; i++) {
if (this[i] == num) {
ret = true;
break;
}
}
return ret;
};
/**
* Produce a random number within a range.
* @param {Number} min The lower bound of the randomized range.
* @param {Number} max The uppper bound of the randomized range.
* @return {Number} A number randomly selected between min and max.
*/
sudoku.getRandom = function(min, max) {
return min + Math.round(Math.random() * (max - min));
};
/**
* Class Cell stores the remaining candidates a sudoku cell currently
* has during puzzle generation.
* @constructor
*/
sudoku.Cell = function() {
/**
* The possible candidate value the cell is allowed to have.
* @type Array.<Number>
* @public
*/
this.candidates = [1, 2, 3, 4, 5, 6, 7, 8, 9];
};
/**
* Set the given number to be the only candidate the cell is allowed to have.
* @param {Number} num The candidate numeric value.
*/
sudoku.Cell.prototype.setCandidate = function(num) {
this.candidates = [num];
};
/**
* Randomly pick a number from a cell's candidate list.
* @return {Number} num A random number from the cell's candidate list.
*/
sudoku.Cell.prototype.getRandomCandidate = function() {
return this.candidates[sudoku.getRandom(0, this.candidates.length - 1)];
};
/**
* Remove a number from a cell's candidate list.
* @param {Number} num The numeric value of the number to be removed.
*/
sudoku.Cell.prototype.removeCandidate = function(num) {
this.candidates.remove(num);
};
/**
* Create a clone of a cell
* @return {sudoku.Cell} a cloned cell object
*/
sudoku.Cell.prototype.clone = function() {
var newCell = new sudoku.Cell();
newCell.candidates = this.candidates.slice();
return newCell;
};
/**
* Class Row represents a single row of a sudoku puzzle. Each sudoku game has
* 9 rows. Each row contains 9 cells.
* @constructor
*/
sudoku.Row = function() {
/**
* List of nine sudoku cells that belong to a given row
* @type Array.<sudoku.Cell>
* @public
*/
this.cells = [];
/**
* The state of a row before the algoritm attemps to guess values to populate
* its cells. It is saved so that when later discovered an incorrect guess
* was made, it will be used to revert the state of the row to the initial
* state.
* @type Array.<sudoku.Cell>
* @public
*/
this.initialState = null;
for (var i = 0; i < 9; i++) {
this.cells.push(new sudoku.Cell());
}
};
/**
* Return the current list of cell that the row contains.
* @return {Array.<sudoku.Cell>} a list of cell objects.
*/
sudoku.Row.prototype.getCurrentState = function() {
return this.cells;
};
/**
* Set the initial state of the row to contain the same content as the current
* state of the row. Must use clone to get a copies of the cells, otherwise
* only references are copied in initialState.
*/
sudoku.Row.prototype.setInitialState = function() {
this.initialState = new Array(this.getCurrentState().length);
for (var i = 0; i < this.getCurrentState().length; i++) {
this.initialState[i] = this.getCurrentState()[i].clone();
}
};
/**
* Revert the Row to the state of initialState.
*/
sudoku.Row.prototype.resetRow = function() {
this.cells = new Array(this.initialState.length);
for (var i = 0; i < this.initialState.length; i++) {
this.cells[i] = this.initialState[i].clone();
}
};
/**
* Remove a numeric value from the candidate list of each Cell of the Row
* @param {Number} num The numeric value to be removed
*/
sudoku.Row.prototype.removeCandidate = function(num) {
for (var i = 0; i < this.cells.length; i++) {
this.cells[i].removeCandidate(num);
}
};
/**
* Class SudokuGenerator generates a solvable sudoku puzzle as a string
* @constructor
*/
sudoku.SudokuGenerator = function() {
/**
* The nine rows that a sudoku puzzle is made up of.
* @type Array.<sudoku.Row>
* @public
*/
this.rows = [];
/**
* The full list of 81 digits representing a valid sudoku puzzle with no empty
* cell.
* @type Array.<Number>
* @public
*/
this.fullArray = new Array(81);
/**
* A list of 81 digits representing a valid sudoku puzzle with emtpy cells
* denoted by '0'.
* @type Array.<Number>
* @public
*/
this.partialArray = new Array(81);
for (var i = 0; i < 9; i++) {
this.rows.push(new sudoku.Row());
}
};
/**
* Generate a solvable sudoku puzzle string. First it would create a string of
* 81 digits, range 1-9 that is consistently with the rules of sudoku. Then it
* would attempt to randomly remove some of the digits (replace them with zero)
* to create a valid and solvable sudoku puzzle string.
* @return {String} string of 81 digits, range 0-9. Representing a sudoku puzzle
* and is sequentially ordered from the upper left cell as index 0 to
* bottom right cell as index 80.
*/
sudoku.SudokuGenerator.prototype.generate = function() {
this.analyze(0, 0, 0);
this.toArray();
var puzzleStr = null;
var solver = null;
do {
// create the partial puzzle - loop to make sure we have a
// solvable puzzle
this.createPartialArray();
// convert internal data structure to fullArray and convert it
// to a long string
puzzleStr = this.arrayToString(this.partialArray);
solver = new sudoku.SudokuSolver(puzzleStr);
} while (!solver.solve());
solver = null;
return puzzleStr;
};
/**
* Remove a numeric value from the candidate list of every cell of a column.
* @param {Number} col The index of a column.
* @param {Number} num The numeric value to be removed,
*/
sudoku.SudokuGenerator.prototype.removeCandidateFromColumn =
function(col, num) {
for (var i = 0; i < this.rows.length; i++) {
this.rows[i].cells[col].removeCandidate(num);
}
};
/**
* Remove a numeric value from the candidate list of every cell of box region.
* A box region is one of nine 3x3 regions of a sudoku puzzle.
* @param {Number} row The row index of the upper left cell of a box region.
* @param {Number} col The col index of the bottom right cell of a box region.
* @param {Number} num The numeric value to be removed.
*/
sudoku.SudokuGenerator.prototype.removeCandidateFromBox =
function(row, col, num) {
/**
* With the given row/col position for a cell, the following switch statement
* determine the upper-left corner position for the 3x3 region box it belongs
* to.
*/
switch (true) {
case (row <= 2):
row = 0;
break;
case (row <= 5):
row = 3;
break;
default:
row = 6;
}
switch (true) {
case (col <= 2):
col = 0;
break;
case (col <= 5):
col = 3;
break;
default:
col = 6;
}
for (var i = row; i < row + 3; i++) {
for (var j = col; j < col + 3; j++) {
this.rows[i].cells[j].removeCandidate(num);
}
}
};
/**
* Recursively guess the candidate value for a given cell based on information
* it currently knows about its neighboring cells (same row, same column and
* same box region). If no logical guess is deduced, which means a previous
* cell was guessed incorrect thus the entire row is reset and is resorted
* again.
* @param {Number} rowNum The row index of current cell that is being worked on.
* @param {Number} colNum The col index of current cell that is being worked on.
*/
sudoku.SudokuGenerator.prototype.analyze = function(rowNum, colNum) {
var row = this.rows[rowNum];
var cell = row.cells[colNum];
// Check to make sure this cell has potenial candidates and is not empty
if(this.rows[rowNum].cells[colNum].candidates.length < 1) {
// Since this cell cannot be filled in. Reset the entire row
// and all of the rows below it.
for(var i = rowNum; i < 9; i++) {
this.rows[i].resetRow(i);
}
// keep going by starting at the beginning of the row again.
this.analyze(rowNum, 0);
} else {
// The new number for the cell.
var candidate = this.rows[rowNum].cells[colNum].getRandomCandidate();
// Since we have a good candidate, remove it from its row, column,
// and the box it is in
row.removeCandidate(candidate);
this.removeCandidateFromColumn(colNum, candidate);
this.removeCandidateFromBox(rowNum, colNum, candidate);
cell.setCandidate(candidate);
// When we reach the end of the row, go back to the first column
// of the next row.
if (colNum == 8) {
colNum = 0;
rowNum++;
// Update all of the initial states of all rows now completed
for(var i = rowNum; i < 9; i++) {
this.rows[i].setInitialState();
}
} else {
colNum++;
}
if (rowNum == 9) {
// Sudoku is complete.
return false;
} else {
// start working on next cell
this.analyze(rowNum, colNum);
}
}
};
/**
* Construct an array contains the final value of a cell from each row
*/
sudoku.SudokuGenerator.prototype.toArray = function() {
var index = 0;
for (var i = 0; i < this.rows.length; i++) {
for (var j = 0; j < this.rows[i].cells.length; j++) {
this.fullArray[index] = this.rows[i].cells[j].candidates[0];
index++;
}
}
};
/**
* Randomly replace digits from the valid sudoku string wtih zero.
* The removal process is done symmetrically, to remove both the digit and its
* inverted position on the sudoku map.
*/
sudoku.SudokuGenerator.prototype.createPartialArray = function() {
this.partialArray = this.fullArray.slice();
var emptySymbol = '0';
var scratch = null;
var minRemove = 3;
var maxRemove = 4;
var toRemove = sudoku.getRandom(minRemove, maxRemove);
var begin;
/**
* This main for loop go through the first five 3x3 box region of a fully
* generated sudoku puzzle and systemically replace a random number of value
* with '0' to denote an empty cell of a sudoku puzzle.
*/
for (var region = 0; region < 5; region++) {
scratch = [1, 2, 3, 4, 5, 6, 7, 8, 9];
// This switch statement determine the beginning corner index of the given
// region
switch(region) {
case 0:
begin = 0;
break;
case 1:
begin = 3;
break;
case 2:
begin = 6;
break;
case 3:
begin = 27;
break;
case 4:
begin = 30;
// Special case - only remove 2 cells from fifth region.
toRemove = sudoku.getRandom(1, 2);
break;
}
/**
* The following for loop randomly select indexes within a 3x3 region by
* calculating the correct offset relative to the same box and perform
* cell replacement to create empty cell.
*/
for (var i = 0; i < toRemove; i++) {
var index = scratch[sudoku.getRandom(0, scratch.length - 1)];
scratch.remove(index);
var row = Math.ceil(index / 3) - 1;
var offset = 0;
switch(index % 3) {
case 1:
offset = 0;
break;
case 2:
offset = 1;
break;
case 0:
offset = 2;
break;;
}
var actualIndex = begin + row * 9 + offset;
this.partialArray[actualIndex] = emptySymbol;
this.partialArray[this.invertCell(actualIndex)] = emptySymbol;
}
}
};
/**
* Helper function to sudoku.createPartialArray to find the inverted postion of
* a given index of a sudoku map.
* @param {Number} index The index of a cell.
* @return {Number} index The index of the inverted cell.
*/
sudoku.SudokuGenerator.prototype.invertCell = function(index) {
var row = Math.floor(index / 9);
var col = index;
if (index > 8) {
col = index % 9;
}
var newRow = 8 - row;
var newCol = 8 - col;
var newIndex = newCol + (newRow * 9);
return newIndex;
};
/**
* Concatenate all the values of a numeric array sequentially to form a string.
* @param {Array.<Number>} array Array of numeric value.
* @return {String} concatenated string of the input array.
*/
sudoku.SudokuGenerator.prototype.arrayToString = function(array) {
var str = '';
var separator = '';
for (var i = 0; i < array.length; i++) {
str += array[i] + separator;
}
return str;
};
/**
* Class SolverCell represent a cell of a valid sudoku puzzle. It is the data
* structure use by SudokuSolver to generate a solution for the puzzle.
* @constructor
* @param {Number} row Row index of a cell.
* @param {Number} col Column index of a cell.
*/
sudoku.SolverCell = function (row, col) {
/**
* A boolean value to denote whether this cell is a non-empty cell.
* @type Boolean
* @public
*/
this.given = false;
/**
* The list of possible value this cell is allowed to have.
* @type Array.<Number>
* @public
*/
this.candidates = [1, 2, 3, 4, 5, 6, 7, 8, 9];
/**
* The row index of this cell.
* @type Number
* @public
*/
this.rowIndex = row;
/**
* The column index of this cell.
* @type Number
* @public
*/
this.cellIndex = col;
}
/**
* Reset to the initial state of a SolverCell.
*/
sudoku.SolverCell.prototype.resetCell = function() {
this.candidates = new Array(9);
this.candidates = [1, 2, 3, 4, 5, 6, 7, 8, 9];
return this.candidates;
};
/**
* Add a numeric value to the candidate list of a SolverCell.
* @param {Number} num A numeric value to be added as candidate.
*/
sudoku.SolverCell.prototype.setCandidate = function(num) {
this.candidates = [num];
};
/**
* Debug method to see the content of a SolverCell.
* @return {String} a string of the cell value.
*/
sudoku.SolverCell.prototype.debug = function() {
var cellVal = (this.candidates.length > 1) ? 0 : this.candidates[0];
return 'cell[' + this.rowIndex + '][' + this.cellIndex + ']= ' + cellVal;
};
/**
* Class SolverRow represent a row of a valid sudoku puzzle. Each row contains
* 9 SolverCells.
* @constructor
* @param {Number} row Row index of a sudoku puzzle.
*/
sudoku.SolverRow = function (row) {
/**
* The list of cells that makes up the row for puzzle solving.
* @type Array.<sudoku.SolverCell>
* @public
*/
this.cells = [];
for (var i = 0; i < 9; i++) {
this.cells.push(new sudoku.SolverCell(row, i));
}
};
/**
* Class SudokuSolver represent a valid sudoku puzzle.
* @constructor
* @param {String} str A string of length 81 representing the sudoku string,
with '0' to represent a blank cell.
*/
sudoku.SudokuSolver = function (str) {
/**
* A string of 81 digits represent the final sudoku puzzle (with empty cells).
* @type String
* @public
*/
this.puzzleStr = str;
/**
* A string of 81 digits represent the solution of the sudoku puzzle (without
* empty cells.
* @type String
* @public
*/
this.solutionArray = null;
/**
* The list of cells that makes up the row for puzzle solving.
* @type Array.<sudoku.SolverCell>
* @public
*/
this.rows = [];
for (var i = 0; i < 9; i++) {
this.rows.push(new sudoku.SolverRow(i));
}
for (var r = 0; r < 9; r++) {
for (var c = 0; c < 9; c++) {
var cellValue = this.getCell(r, c);
if (cellValue != '0') {
this.rows[r].cells[c].setCandidate(cellValue);
this.rows[r].cells[c].given = true;
}
}
}
};
/**
* To get the cell value by specifying its location on the sudoku map.
* @param {Number} row A row index for a cell.
* @param {Number} col A col index for a cell.
* @return {Number} the numeric value of the cell.
*/
sudoku.SudokuSolver.prototype.getCell = function(row, col) {
var index = (row * 9) + col;
return this.puzzleStr.charAt(index);
};
/**
* Check whether the current cell value present a violation.
* @param {sudoku.SolverCell} cell A reference to a SolverCell.
* @return {Boolean} true/false whether the value of this cell retain the
* validity of the current state.
*/
sudoku.SudokuSolver.prototype.isCellValid = function(cell) {
var r = cell.rowIndex;
var c = cell.cellIndex;
for (var i = 0; i < 9; i++) {
if (cell.candidates.equals(this.rows[r].cells[i].candidates) &&
c != i) {
return false;
}
if (cell.candidates.equals(this.rows[i].cells[c].candidates) &&
r != i) {
return false;
}
}
var snum = [0, 0, 0, 3, 3, 3, 6, 6, 6];
var rs = snum[r];
var cs = snum[c];
var re = rs + 3;
var ce = cs + 3;
for (var i = rs; i < re; i++) {
for (var j = cs; j < ce; j++) {
if (cell.candidates.equals(this.rows[i].cells[j].candidates) &&
(i != r && j != c)) {
return false;
}
}
}
// cell is valid!
return true;
};
/**
* Return the next sequential cell relative to the given cell.
* @param {sudoku.SolverCell} cell A reference to a SolverCell.
* @return {sudoku.SolverCell} the next sequential cell.
*/
sudoku.SudokuSolver.prototype.nextCell = function(cell) {
var ret = null;
if (cell.cellIndex == 8) {
ret = this.rows[cell.rowIndex + 1].cells[0];
} else {
ret = this.rows[cell.rowIndex].cells[cell.cellIndex + 1];
}
return ret;
};
/**
* Attempt to derive the value of all the blank cells of the sudoku puzzle.
* If this is a solvable puzzle, each cell of the puzzle would eventually
* contain exactly one numeric value.
* @param {sudoku.SolverCell} cell A reference to a SolverCell, the starting
cell where the algorithm will perform recursively (default is the first
cell of the first row).
* @return {Boolean} true/false whether this is a solvable puzzle.
*/
sudoku.SudokuSolver.prototype.solve = function(cell) {
if (!cell) {
cell = this.rows[0].cells[0];
}
if (cell.rowIndex == 8 && cell.cellIndex == 8) {
var candidates = cell.candidates;
for(var i = 0; i < candidates.length; i++) {
var candidate = candidates[i];
cell.setCandidate(candidate);
if (this.isCellValid(cell)) {
break;
}
}
return true;
}
if (cell.given) {
return this.solve(this.nextCell(cell));
}
cell.candidates.shuffle();
var candidates = cell.candidates;
for(var i = 0; i < candidates.length; i++) {
var candidate = candidates[i];
cell.setCandidate(candidate);
if (this.isCellValid(cell)) {
if (cell.rowIndex == 8 && cell.cellIndex == 8) {
return true;
}
if (this.solve(this.nextCell(cell))) {
return true;
}
}
}
cell.resetCell();
return false;
};
/**
* Collect all the numeric value of each cell after it is solved into an array.
* @return {Array.<Number>} an array of numbers presenting the solution.
*/
sudoku.SudokuSolver.prototype.getSolutionArray = function() {
if (!this.solutionArray) {
var index = 0;
this.solutionArray = new Array(81);
for (var i = 0; i < this.rows.length; i++) {
var row = this.rows[i];
for (var j = 0; j < row.cells.length; j++) {
var cell = row.cells[j];
var cellval = (cell.candidates.length > 1) ? 0 : cell.candidates[0];
this.solutionArray[index] = cellval;
index++;
}
}
}
return this.solutionArray;
};
/**
* Validate the current state of the puzzle to see if there is any conflict.
* @return {Boolean} true/false whether this is a valid sudoku puzzle.
*/
sudoku.SudokuSolver.prototype.validateSolution = function() {
var ret = true;
for (var i = 0; i < this.rows.length; i++) {
var row = this.rows[i];
for (var j = 0; j < row.cells.length; j++) {
var cell = row.cells[j];
if (!this.isCellValid(cell)) {
ret = false;
break;
}
}
}
return ret;
};