1
0

Initial commit

This commit is contained in:
Markus Birth 2013-07-11 23:03:06 +02:00
commit 9695aebf39
7 changed files with 479 additions and 0 deletions

38
cellMask.php Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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() {}
}