Initial commit
This commit is contained in:
commit
9695aebf39
38
cellMask.php
Normal file
38
cellMask.php
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
$image_size = 20;
|
||||
|
||||
$dim = 3;
|
||||
if ( isset( $_GET['dim'] ) ) $dim = intval( $_GET['dim'] );
|
||||
|
||||
$mask = pow( 2, $dim*$dim );
|
||||
if ( isset( $_GET['mask'] ) ) $mask = intval( $_GET['mask'] );
|
||||
|
||||
// 9 bits ~> 512 possible combinations (-9 for the single ones)
|
||||
|
||||
$i = imagecreate( $image_size, $image_size );
|
||||
$transp = imagecolorallocatealpha( $i, 0xff, 0xff, 0xff, 0x7f );
|
||||
|
||||
imagefill( $i, 1, 1, $transp );
|
||||
|
||||
if ( !isset( $_GET['changed'] ) ) {
|
||||
$boxcolor = imagecolorallocate( $i, 0xdd, 0xdd, 0xdd );
|
||||
} else {
|
||||
$boxcolor = imagecolorallocate( $i, 0xff, 0x88, 0x88 );
|
||||
}
|
||||
$boxsize = ($image_size+1) / $dim;
|
||||
|
||||
for ( $y=0; $y<$dim; $y++ ) {
|
||||
for ( $x=0; $x<$dim; $x++ ) {
|
||||
$m = 1 << ( $y*$dim + $x );
|
||||
if ( ( $m & $mask ) == $m ) {
|
||||
imagefilledrectangle( $i, $x*$boxsize, $y*$boxsize, ($x+1)*$boxsize-2, ($y+1)*$boxsize-2, $boxcolor );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
header( 'Content-Type: image/png' );
|
||||
imagepng( $i );
|
||||
imagedestroy( $i );
|
||||
|
||||
?>
|
63
index.html
Normal file
63
index.html
Normal file
@ -0,0 +1,63 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<script type="text/javascript" src="js/firebugx.js"></script>
|
||||
<script type="text/javascript" src="js/SudokuCell.class.js"></script>
|
||||
<script type="text/javascript" src="js/SudokuBoard.class.js"></script>
|
||||
<script type="text/javascript" src="js/SudokuChecks.class.js"></script>
|
||||
<script type="text/javascript" src="js/SudokuSolver.class.js"></script>
|
||||
<style type="text/css">
|
||||
TABLE.sudoku TD {
|
||||
border: 1px solid #ccc;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
TABLE.sudoku TD.lborder {
|
||||
border-left: 2px solid black;
|
||||
}
|
||||
|
||||
TABLE.sudoku TD.tborder {
|
||||
border-top: 2px solid black;
|
||||
}
|
||||
|
||||
TABLE.sudoku TD.changed {
|
||||
background-color: #fdd;
|
||||
border: 2px solid red;
|
||||
z-index: 255;
|
||||
}
|
||||
|
||||
TABLE.sudoku TR {
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
TABLE.sudoku {
|
||||
border: 2px solid black;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<script type="text/javascript">
|
||||
var board = new SudokuBoard(3);
|
||||
//board.loadString( '...87....6......9..9.....41..7.452....6...4.3.43...15.21......4....3...6....2...7', false );
|
||||
|
||||
//board.loadString( '.....37..6..............8......71..2.84.9......2..5..........7..4.6....1.5.9.....', true );
|
||||
//board.loadString( '.7...92.......84.............8.1.......725.........3.97......1.54........9.....4.', true );
|
||||
//board.loadString( '37............8.41.5..........837....4.9....6......93...9........1...........62..', true );
|
||||
//board.loadString( '.....8......5..3..21............46....7.5.....4.31....1.9.3.........7..........27', true );
|
||||
//board.loadString( '69..2...............81...32..9......3....4.76.6...3.....5....4.....8..........5..', true );
|
||||
//board.loadString( '.2.....8.3..6.2..5...1.9....74...31...........68...47....5.8...6..7.3..9.3.....4.', false );
|
||||
board.loadString( '7...54..949..67.1....192347....71.9....63........48.3.35.....716487139...7......3', false );
|
||||
document.write( 'Base board:<br />' );
|
||||
board.print();
|
||||
</script>
|
||||
<input type="button" value="Solve!" onclick="SudokuSolver.solveBoard( board ); board.print();" /><br />
|
||||
</body>
|
||||
</html>
|
86
js/SudokuBoard.class.js
Normal file
86
js/SudokuBoard.class.js
Normal file
@ -0,0 +1,86 @@
|
||||
function SudokuBoard( dim ) {
|
||||
this.dim = Number( dim );
|
||||
this.dim2 = this.dim * this.dim;
|
||||
this.BASE_SET = '123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
this.set = this.BASE_SET.substr(0, this.dim2);
|
||||
this.board = new Array( this.dim2 );
|
||||
this.checkDiags = false;
|
||||
this.changed = false;
|
||||
|
||||
for (var r=0; r<this.dim2; r++) {
|
||||
this.board[r] = new Array( this.dim2 );
|
||||
for (var c=0; c<this.dim2; c++) {
|
||||
this.board[r][c] = new SudokuCell(0, this);
|
||||
}
|
||||
}
|
||||
|
||||
this.loadString = function( boarddef, alsoCheckDiags ) {
|
||||
var boardString = '';
|
||||
for ( var i=0; i<boarddef.length; i++ ) {
|
||||
if ( boarddef.charAt(i) != '.' && this.set.indexOf( boarddef.charAt(i) ) == -1 ) continue;
|
||||
boardString += boarddef.charAt(i);
|
||||
}
|
||||
if ( boardString.length != this.dim2 * this.dim2 ) {
|
||||
console.error( 'Bad board definition! (Need %d chars, got %d.)', (this.dim2*this.dim2), boardString.length );
|
||||
document.write( 'Bad board definition! (Need ' + (this.dim2*this.dim2) + ' chars, got ' + boardString.length + '.)' );
|
||||
return false;
|
||||
}
|
||||
for ( var i=0;i<boardString.length; i++ ) {
|
||||
var r = Math.floor( i / this.dim2 );
|
||||
var c = i % this.dim2;
|
||||
this.board[r][c].setValue( boardString.charAt(i) );
|
||||
this.board[r][c].resetChangeFlag();
|
||||
}
|
||||
this.checkDiags = ( alsoCheckDiags );
|
||||
console.log( 'Board loaded.' );
|
||||
}
|
||||
|
||||
this.setCheckDiags = function( newValue ) {
|
||||
this.checkDiags = ( newValue );
|
||||
}
|
||||
|
||||
this.cellAt = function( r, c ) {
|
||||
return this.board[r][c];
|
||||
}
|
||||
|
||||
this.resetChangeFlags = function() {
|
||||
for ( var r=0; r<this.dim2; r++ ) {
|
||||
for ( var c=0; c<this.dim2; c++ ) {
|
||||
this.cellAt( r, c ).resetChangeFlag();
|
||||
}
|
||||
}
|
||||
this.changed = false;
|
||||
}
|
||||
|
||||
this.hasChanged = function() {
|
||||
return this.changed;
|
||||
}
|
||||
|
||||
this.print = function() {
|
||||
var html = '';
|
||||
html += '<table class="sudoku">';
|
||||
for ( var r=0; r<this.dim2; r++ ) {
|
||||
html += '<tr>';
|
||||
for ( var c=0; c<this.dim2; c++ ) {
|
||||
cssclass = '';
|
||||
if ( r%this.dim == 0 ) cssclass = 'tborder';
|
||||
if ( c%this.dim == 0 ) cssclass += ' lborder';
|
||||
if ( this.cellAt( r, c).hasChanged() ) cssclass += ' changed';
|
||||
if ( cssclass.length > 0 ) cssclass = ' class="' + cssclass + '"';
|
||||
background = '';
|
||||
value = this.cellAt( r, c ).getValue();
|
||||
if ( value == '.' ) {
|
||||
background = ' background="cellMask.php?dim=' + this.dim + '&mask=' + this.cellAt(r,c).getMask();
|
||||
if ( this.cellAt( r, c ).hasChanged() ) background += '&changed=1';
|
||||
background += '"';
|
||||
value = '';
|
||||
}
|
||||
html += '<td' + cssclass + background + '>' + value + '</td>';
|
||||
}
|
||||
html += '</tr>';
|
||||
}
|
||||
html += '</table>';
|
||||
var body = document.getElementsByTagName( 'body' );
|
||||
body[0].innerHTML += html;
|
||||
}
|
||||
}
|
59
js/SudokuCell.class.js
Normal file
59
js/SudokuCell.class.js
Normal file
@ -0,0 +1,59 @@
|
||||
function SudokuCell( initVal, boardObj ) {
|
||||
this.boardObj = boardObj;
|
||||
this.changed = false;
|
||||
this.value = 0;
|
||||
this.set = ( 1 << this.boardObj.dim2 ) - 1; // all
|
||||
|
||||
this.setMask = function( newSet ) {
|
||||
if ( newSet != this.set ) {
|
||||
this.set = newSet;
|
||||
this.changed = true;
|
||||
this.boardObj.changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
this.setValue = function( newValue ) {
|
||||
if ( newValue == this.value ) return false;
|
||||
setidx = this.boardObj.set.indexOf( newValue );
|
||||
if ( setidx != -1 ) {
|
||||
this.value = newValue;
|
||||
this.setMask( 1 << setidx );
|
||||
this.changed = true;
|
||||
this.boardObj.changed = true;
|
||||
return true;
|
||||
} else if ( newValue == -1 ) {
|
||||
this.value = 0;
|
||||
this.setMask( ( 1 << this.boardObj.dim2 ) - 1 ); // all
|
||||
this.changed = true;
|
||||
this.boardObj.changed = true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
this.setValue( initVal ); // init cell
|
||||
|
||||
this.getValue = function() {
|
||||
if ( this.value == 0 ) return '.';
|
||||
return this.value;
|
||||
}
|
||||
|
||||
this.getMask = function() {
|
||||
return this.set;
|
||||
}
|
||||
|
||||
this.getUnknownsCount = function() {
|
||||
var result = 0;
|
||||
for (var n=0; n<this.boardObj.dim2; n++) {
|
||||
if ( (1 << n) & this.set ) result++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
this.hasChanged = function() {
|
||||
return this.changed;
|
||||
}
|
||||
|
||||
this.resetChangeFlag = function() {
|
||||
this.changed = false;
|
||||
}
|
||||
}
|
93
js/SudokuChecks.class.js
Normal file
93
js/SudokuChecks.class.js
Normal file
@ -0,0 +1,93 @@
|
||||
var SudokuChecks = {
|
||||
|
||||
'unique' : function( board, r, c ) {
|
||||
var set = ~board.cellAt( r, c).getMask();
|
||||
|
||||
// row / column / diagonale
|
||||
for ( var i=0; i<board.dim2; i++) {
|
||||
if ( i != c ) { // row
|
||||
board.cellAt( r, i ).setMask( board.cellAt( r, i ).getMask() & set );
|
||||
}
|
||||
if ( i != r ) { // column
|
||||
board.cellAt( i, c ).setMask( board.cellAt( i, c ).getMask() & set );
|
||||
}
|
||||
if ( board.checkDiags ) {
|
||||
if ( r == c && i != r ) { // diagonale \
|
||||
board.cellAt( i, i ).setMask( board.cellAt( i, i ).getMask() & set );
|
||||
}
|
||||
if ( r == board.dim2-c-1 && i != r ) { // diagonale /
|
||||
board.cellAt( i, board.dim2-i-1 ).setMask ( board.cellAt( i, board.dim2-i-1 ).getMask() & set );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// square
|
||||
var rb = Math.floor( r / board.dim ) * board.dim; // row base for this square
|
||||
var cb = Math.floor( c / board.dim ) * board.dim; // col base for this square
|
||||
for ( var i=rb; i<rb+board.dim; i++ ) {
|
||||
for ( var j=cb; j<cb+board.dim; j++ ) {
|
||||
if ( i != r || j != c ) {
|
||||
board.cellAt( i, j ).setMask( board.cellAt( i, j ).getMask() & set );
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
'onePlace' : function( cells ) {
|
||||
if ( !cells[0] ) return;
|
||||
for ( var i=0; i<cells[0].boardObj.dim2; i++ ) { // walk all possible values
|
||||
var n = 0;
|
||||
var p = (1 << i);
|
||||
var x = -1;
|
||||
for ( var k in cells ) {
|
||||
if ( ( cells[k].getValue() == '.' ) && ( p & cells[k].getMask() ) ) {
|
||||
n++;
|
||||
x = k;
|
||||
}
|
||||
}
|
||||
if ( n == 1 ) {
|
||||
cells[x].setMask( p );
|
||||
cells[x].setValue( cells[0].boardObj.set.charAt( Math.log(p)/Math.log(2) ) );
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
'oneUnknown' : function( cells ) {
|
||||
for ( var c in cells ) {
|
||||
if ( cells[c].getValue() == '.' && cells[c].getUnknownsCount() == 1 ) {
|
||||
var valid = cells[c].getMask();
|
||||
var val = cells[c].boardObj.set.charAt( Math.log( valid ) / Math.log( 2 ) );
|
||||
cells[c].setValue( val );
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
'twoValPlaces' : function( cells ) {
|
||||
if ( !cells[0] ) return;
|
||||
console.group( 'twoValPlaces: %o', cells );
|
||||
for ( var i=0; i<cells[0].boardObj.dim2-1; i++ ) { // walk all possible 2s combinations
|
||||
for ( var j=i+1; j<cells[0].boardObj.dim2; j++ ) {
|
||||
var n = 0;
|
||||
var p = (1 << i) | (1 << j);
|
||||
console.log( 'Now checking mask: %o', p );
|
||||
for ( var k in cells ) {
|
||||
if ( ( cells[k].getValue() == '.' ) && ( cells[k].getMask() & p == p ) ) {
|
||||
n++;
|
||||
console.log( '%d: %d, %d (%o, %o)', n, p, cells[k].getMask(), ( p & cells[k].getMask() ), ( ( p & cells[k].getMask() ) == p ) );
|
||||
}
|
||||
}
|
||||
if ( n > 0 ) console.info( 'Have %d matches.', n );
|
||||
if ( n == 2 ) {
|
||||
for ( var k in cells ) {
|
||||
if ( ( cells[k].getValue() == '.' ) && ( cells[k].getMask() == p ) ) {
|
||||
cells[k].setMask( p );
|
||||
} else if ( cells[k].getValue() == '.' ) {
|
||||
cells[k].setMask( cells[k].getMask() & ~p );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
console.groupEnd();
|
||||
}
|
||||
};
|
132
js/SudokuSolver.class.js
Normal file
132
js/SudokuSolver.class.js
Normal file
@ -0,0 +1,132 @@
|
||||
var SudokuSolver = {
|
||||
|
||||
'uniqueAll' : function( board ) {
|
||||
for ( var r=0; r<board.dim2; r++ ) {
|
||||
for ( var c=0; c<board.dim2; c++ ) {
|
||||
if ( board.cellAt( r, c ).getValue() != '.' ) {
|
||||
SudokuChecks.unique( board, r, c );
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// returns all cells for the square of the specified cell
|
||||
'getSquareCellsForCell' : function( board, r, c ) {
|
||||
var sqrid = Math.floor( r / board.dim ) * board.dim + Math.floor( c / board.dim );
|
||||
return this.getSquareCells( board, sqrid );
|
||||
},
|
||||
|
||||
'getSquareCells' : function( board, squareid ) {
|
||||
var result = new Array();
|
||||
var rb = Math.floor( squareid / board.dim ) * board.dim; // base row for square
|
||||
var cb = squareid % board.dim * board.dim; // base col for square
|
||||
for ( var i=0; i<board.dim2; i++ ) {
|
||||
result.push( board.cellAt( rb+Math.floor( i/board.dim ), cb+(i % board.dim) ) );
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
||||
'getRowCells' : function( board, row ) {
|
||||
var result = new Array();
|
||||
for ( var i=0; i<board.dim2; i++ ) {
|
||||
result.push( board.cellAt( row, i ) );
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
||||
'getColCells' : function( board, col ) {
|
||||
var result = new Array();
|
||||
for ( var i=0; i<board.dim2; i++ ) {
|
||||
result.push( board.cellAt( i, col ) );
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
||||
'runAllRows' : function( board, func ) {
|
||||
for ( var i=0; i<board.dim2; i++ ) { // walk all rows
|
||||
var rowcells = this.getRowCells( board, i );
|
||||
SudokuChecks[func]( rowcells );
|
||||
}
|
||||
},
|
||||
|
||||
'runAllColumns' : function( board, func ) {
|
||||
for ( var i=0; i<board.dim2; i++ ) { // walk all columns
|
||||
var colcells = this.getColCells( board, i );
|
||||
SudokuChecks[func]( colcells );
|
||||
}
|
||||
},
|
||||
|
||||
'runAllSquares' : function( board, func ) {
|
||||
for ( var i=0; i<board.dim2; i++ ) { // walk all squares
|
||||
var sqrcells = this.getSquareCells( board, i );
|
||||
SudokuChecks[func]( sqrcells );
|
||||
}
|
||||
},
|
||||
|
||||
'runBothDiags' : function( board, func ) {
|
||||
if ( !board.checkDiags ) return;
|
||||
var diag1 = new Array();
|
||||
var diag2 = new Array();
|
||||
for ( var i=0; i<board.dim2; i++ ) {
|
||||
diag1.push( board.cellAt( i, i ) );
|
||||
diag2.push( board.cellAt( i, board.dim2-i-1 ) );
|
||||
}
|
||||
SudokuChecks[func]( diag1 );
|
||||
SudokuChecks[func]( diag2 );
|
||||
},
|
||||
|
||||
'oneUnknownAll' : function( board ) {
|
||||
var cells = new Array();
|
||||
for ( var r=0; r<board.dim2; r++ ) {
|
||||
for ( var c=0; c<board.dim2; c++ ) {
|
||||
cells.push( board.cellAt( r, c ) );
|
||||
}
|
||||
}
|
||||
SudokuChecks.oneUnknown( cells );
|
||||
},
|
||||
|
||||
'onePlaceRow' : function( board ) { this.runAllRows( board, 'onePlace' ); },
|
||||
'onePlaceColumn' : function( board ) { this.runAllColumns( board, 'onePlace' ); },
|
||||
'onePlaceSquare' : function( board ) { this.runAllSquares( board, 'onePlace' ); },
|
||||
'onePlaceDiag' : function( board ) { this.runBothDiags( board, 'onePlace' ); },
|
||||
'twoValPlacesSquare' : function( board ) { this.runAllSquares( board, 'twoValPlaces' ); },
|
||||
'twoValPlacesRow' : function( board ) { this.runAllRows( board, 'twoValPlaces' ); },
|
||||
'twoValPlacesColumn' : function( board ) { this.runAllColumns( board, 'twoValPlaces' ); },
|
||||
'twoValPlacesDiag' : function( board ) { this.runBothDiags( board, 'twoValPlaces' ); },
|
||||
|
||||
'solveBoard' : function( board ) {
|
||||
var checks = {
|
||||
'uniqueAll': 'Value must be unique in row, column' + ((board.checkDiags)?', square and diagonales.':' and square.'),
|
||||
'onePlaceSquare': 'Value has only one place in square.',
|
||||
'onePlaceRow': 'Value has only one place in row.',
|
||||
'onePlaceColumn': 'Value has only one place in column.',
|
||||
'onePlaceDiag': 'Value has only one place in diagonale.',
|
||||
'twoValPlacesSquare': 'Only two possible places for pair in square.',
|
||||
'twoValPlacesRow': 'Only two possible places for pair in row.',
|
||||
'twoValPlacesColumn': 'Only two possible places for pair in column.',
|
||||
'twoValPlacesDiag': 'Only two possible places for pair in diagonale.',
|
||||
'oneUnknownAll': 'Only one possible value left.'
|
||||
};
|
||||
|
||||
var i = 1;
|
||||
do {
|
||||
console.time( 'Checking board.' );
|
||||
board.resetChangeFlags();
|
||||
var description = '';
|
||||
for ( var c in checks ) {
|
||||
description = checks[c];
|
||||
this[c]( board );
|
||||
if ( board.hasChanged() ) break;
|
||||
}
|
||||
if ( board.hasChanged() ) {
|
||||
console.info( 'Board was changed by "%s".', description );
|
||||
var body = document.getElementsByTagName( 'body' );
|
||||
body[0].innerHTML += i + '. ' + description + '<br />';
|
||||
board.print();
|
||||
}
|
||||
i++;
|
||||
console.timeEnd( 'Checking board.' );
|
||||
} while ( board.hasChanged() && i<100);
|
||||
}
|
||||
|
||||
};
|
8
js/firebugx.js
Normal file
8
js/firebugx.js
Normal file
@ -0,0 +1,8 @@
|
||||
if ( ! console in window ) {
|
||||
var names = ["log", "debug", "info", "warn", "error", "assert", "dir", "dirxml",
|
||||
"group", "groupEnd", "time", "timeEnd", "count", "trace", "profile", "profileEnd"];
|
||||
|
||||
window.console = {};
|
||||
for (var i = 0; i < names.length; ++i)
|
||||
window.console[names[i]] = function() {}
|
||||
}
|
Reference in New Issue
Block a user