From 74149c2d414842117021449d63c35a1fbd946380 Mon Sep 17 00:00:00 2001 From: Markus Birth Date: Wed, 4 Dec 2013 02:54:59 +0100 Subject: [PATCH] Rewrote everything to CoffeeScript. --- Makefile | 32 ++++ coffee/SudokuBoard.class.coffee | 74 ++++++++ coffee/SudokuCell.class.coffee | 48 +++++ coffee/SudokuChecks.class.coffee | 71 ++++++++ coffee/SudokuSolver.class.coffee | 105 +++++++++++ js/SudokuBoard.class.js | 176 ++++++++++-------- js/SudokuCell.class.js | 116 ++++++------ js/SudokuChecks.class.js | 192 +++++++++++--------- js/SudokuSolver.class.js | 294 ++++++++++++++++++------------- 9 files changed, 778 insertions(+), 330 deletions(-) create mode 100644 Makefile create mode 100644 coffee/SudokuBoard.class.coffee create mode 100644 coffee/SudokuCell.class.coffee create mode 100644 coffee/SudokuChecks.class.coffee create mode 100644 coffee/SudokuSolver.class.coffee diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..c39e905 --- /dev/null +++ b/Makefile @@ -0,0 +1,32 @@ +CC=coffee +SRCDIR=coffee +BUILDDIR=js +ALL_SRC_FILES := $(wildcard $(SRCDIR)/*) +ALL_OTHER_SRC_FILES := $(filter-out %.coffee, $(ALL_SRC_FILES)) +ALL_OTHER_FILES := $(ALL_OTHER_SRC_FILES:$(SRCDIR)/%=$(BUILDDIR)/%) + +SRC=$(wildcard $(SRCDIR)/*.coffee) +BUILD=$(SRC:$(SRCDIR)/%.coffee=$(BUILDDIR)/%.js) + +all: coffee other + +# coffeescript files + +coffee: $(BUILD) + +$(BUILDDIR)/%.js: $(SRCDIR)/%.coffee + $(CC) -o $(BUILDDIR)/ -c $< + +# other files + +other: $(ALL_OTHER_FILES) + +$(ALL_OTHER_FILES): $(BUILDDIR)/%: $(SRCDIR)/% + cp $< $@ + +# cleanup + +.PHONY: clean +clean: + -rm $(BUILD) + -rm $(ALL_OTHER_FILES) diff --git a/coffee/SudokuBoard.class.coffee b/coffee/SudokuBoard.class.coffee new file mode 100644 index 0000000..484144a --- /dev/null +++ b/coffee/SudokuBoard.class.coffee @@ -0,0 +1,74 @@ +class @SudokuBoard + BASE_SET: '123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' + + constructor: (dim) -> + @dim = Number(dim) + @dim2 = @dim * @dim + @set = @BASE_SET.substr(0, @dim2) + @board = new Array(@dim2) + @checkDiags = false + @changed = false + + for r in [0...@dim2] by 1 + @board[r] = new Array(@dim2) + for c in [0...@dim2] by 1 + @board[r][c] = new SudokuCell(0, @) + + loadString: (boarddef, alsoCheckDiags) -> + boardString = '' + for i in [0...boarddef.length] by 1 + continue if boarddef.charAt(i) isnt '.' and @set.indexOf(boarddef.charAt(i)) is -1 + boardString += boarddef.charAt(i) + if boardString.length isnt @dim2*@dim2 + console.error('Bad board definition! (Need %d chars, got %d.)', (@dim2*@dim2), boardString.length) + document.write('Bad board definition! (Need ' + (@dim2*@dim2) + ' chars, got ' + boardString.length + '.)') + return false + for i in [0...boardString.length] by 1 + r = Math.floor(i / @dim2) + c = i % @dim2 + @board[r][c].setValue(boardString.charAt(i)) + @board[r][c].resetChangeFlag() + @setCheckDiags(alsoCheckDiags) + console.log('Board loaded.') + + setCheckDiags: (newValue) -> + @checkDiags = ( newValue ) + + cellAt: (r, c) -> + return @board[r][c] + + resetChangeFlags: -> + for r in [0...@dim2] by 1 + for c in [0...@dim2] by 1 + @cellAt(r, c).resetChangeFlag() + @changed = false + + hasChanged: -> + return @changed + + print: -> + html = '' + html += '' + for r in [0...@dim2] by 1 + html += '' + for c in [0...@dim2] by 1 + cssclass = '' + cssclasses = [] + cssclasses.push('tborder') if r % @dim is 0 + cssclasses.push('lborder') if c % @dim is 0 + cssclasses.push('changed') if @cellAt(r, c).hasChanged() + value = @cellAt(r, c).getValue() + if value is '.' + value = '' + mask = @cellAt(r, c).getMask() + for m in [0...@dim2] + testm = 1 << m + if testm & mask + value += '
' + cssclasses.push('mask' + mask) + cssclass = ' class="' + cssclasses.join(' ') + '"' if cssclasses.length > 0 + html += "#{value}" + html += '' + html += '
' + body = document.getElementsByTagName('body') + body[0].innerHTML += html diff --git a/coffee/SudokuCell.class.coffee b/coffee/SudokuCell.class.coffee new file mode 100644 index 0000000..55d3938 --- /dev/null +++ b/coffee/SudokuCell.class.coffee @@ -0,0 +1,48 @@ +class @SudokuCell + constructor: (initVal, boardObj) -> + @boardObj = boardObj + @changed = false + @value = 0 + @set = (1 << @boardObj.dim2) - 1 # all + @setValue(initVal) # init cell + + setMask: (newSet) -> + if newSet isnt @set + @set = newSet + @changed = true + @boardObj.changed = true + + setValue: (newValue) -> + return false if newValue is @value + setidx = @boardObj.set.indexOf(newValue) + if setidx isnt -1 + @value = newValue + @setMask(1 << setidx) + @changed = true + @boardObj.changed = true + return true + else if newValue is -1 + @value = 0 + @setMask((1 << @boardObj.dim2) - 1) # all + @changed = true + @boardObj.changed = true + return false + + getValue: -> + return '.' if @value is 0 + return @value + + getMask: -> + return @set + + getUnknownsCount: -> + result = 0 + for n in [0...@boardObj.dim2] by 1 + result++ if ((1 << n) & @set) + return result + + hasChanged: -> + return @changed + + resetChangeFlag: -> + @changed = false diff --git a/coffee/SudokuChecks.class.coffee b/coffee/SudokuChecks.class.coffee new file mode 100644 index 0000000..6a69bcb --- /dev/null +++ b/coffee/SudokuChecks.class.coffee @@ -0,0 +1,71 @@ +class @SudokuChecks + @unique: (board, r, c) -> + set = ~board.cellAt(r, c).getMask() + + # row / column / diagonale + for i in [0...board.dim2] by 1 + if i isnt c # row + board.cellAt(r, i).setMask(board.cellAt(r, i).getMask() & set) + if i isnt r # column + board.cellAt(i, c).setMask(board.cellAt(i, c).getMask() & set) + if board.checkDiags + if r is c and i isnt r # diagonale \ + board.cellAt(i, i).setMask(board.cellAt(i, i).getMask() & set) + if r is board.dim2-c-1 and i isnt r # diagonale / + board.cellAt(i, board.dim2-i-1).setMask(board.cellAt(i, board.dim2-i-1).getMask() & set) + + # square + rb = Math.floor(r / board.dim) * board.dim; # row base for this square + cb = Math.floor(c / board.dim) * board.dim; # col base for this square + for i in [rb...rb+board.dim] by 1 + for j in [cb...cb+board.dim] by 1 + if i isnt r || j isnt c + board.cellAt(i, j).setMask(board.cellAt(i, j).getMask() & set ) + return true + + @onePlace: (cells) -> + return if not cells[0] + for i in [0...cells[0].boardObj.dim2] by 1 # walk all possible values + n = 0 + p = (1 << i) + x = -1 + for k of cells + if cells[k].getValue() is '.' and (p & cells[k].getMask()) + n++ + x = k + if n is 1 + cells[x].setMask(p) + cells[x].setValue(cells[0].boardObj.set.charAt(Math.log(p) / Math.log(2))) + return true + + @oneUnknown: (cells) -> + for c of cells + if cells[c].getValue() is '.' and cells[c].getUnknownsCount() is 1 + valid = cells[c].getMask() + val = cells[c].boardObj.set.charAt(Math.log(valid) / Math.log(2)) + cells[c].setValue(val) + return true + + @twoValPlaces: (cells) -> + return if not cells[0] + dim2 = cells[0].boardObj.dim2 + console.group('twoValPlaces: (%o) %o', dim2, cells) + for i in [0...dim2-1] by 1 # walk all possible 2s combinations + for j in [i+1...dim2] by 1 + n = 0 + p = (1 << i) | (1 << j) + console.log('Now checking (%o, %o) mask: %o', i+1, j+1, p) + for k of cells + if cells[k].getValue() is '.' and cells[k].getMask() & p is p + n++ + console.log('%d: %d, %d (%o, %o)', n, p, cells[k].getMask(), (p & cells[k].getMask()), ((p & cells[k].getMask()) is p)) + console.info('Have %d matches.', n) if n > 0 + if n is 2 + console.warn('Two matches!') + for k of cells + if cells[k].getValue() is '.' and cells[k].getMask() is p + cells[k].setMask(p) + else if cells[k].getValue() is '.' + cells[k].setMask(cells[k].getMask() & ~p) + console.groupEnd() + return true diff --git a/coffee/SudokuSolver.class.coffee b/coffee/SudokuSolver.class.coffee new file mode 100644 index 0000000..e4bb594 --- /dev/null +++ b/coffee/SudokuSolver.class.coffee @@ -0,0 +1,105 @@ +class @SudokuSolver + + @uniqueAll: (board) -> + for r in [0...board.dim2] by 1 + for c in [0...board.dim2] by 1 + SudokuChecks.unique(board, r, c) if board.cellAt(r, c).getValue() isnt '.' + return true + + # returns all cells for the square of the specified cell + @getSquareCellsForCell: (board, r, c) -> + sqrid = Math.floor(r / board.dim) * board.dim + Math.floor(c / board.dim) + return @getSquareCells(board, sqrid) + + @getSquareCells: (board, squareid) -> + result = [] + rb = Math.floor(squareid / board.dim) * board.dim # base row for square + cb = squareid % board.dim * board.dim # base col for square + for i in [0...board.dim2] by 1 + result.push(board.cellAt(rb + Math.floor(i / board.dim), cb + (i % board.dim))) + return result + + @getRowCells: (board, row) -> + result = [] + for i in [0...board.dim2] by 1 + result.push(board.cellAt(row, i)) + return result + + @getColCells: (board, col) -> + result = [] + for i in [0...board.dim2] by 1 + result.push(board.cellAt(i, col)) + return result + + @runAllRows: (board, func) -> + for i in [0...board.dim2] by 1 # walk all rows + rowcells = @getRowCells(board, i) + SudokuChecks[func](rowcells) + + @runAllColumns: (board, func) -> + for i in [0...board.dim2] by 1 # walk all columns + colcells = @getColCells(board, i) + SudokuChecks[func](colcells) + + @runAllSquares: (board, func) -> + for i in [0...board.dim2] by 1 # walk all squares + sqrcells = @getSquareCells(board, i) + SudokuChecks[func](sqrcells) + + @runBothDiags: (board, func) -> + return if not board.checkDiags + diag1 = [] + diag2 = [] + for i in [0...board.dim2] by 1 + diag1.push(board.cellAt(i, i)) + diag2.push(board.cellAt(i, board.dim2-i-1)) + SudokuChecks[func](diag1) + SudokuChecks[func](diag2) + + @oneUnknownAll: (board) -> + cells = [] + for r in [0...board.dim2] by 1 + for c in [0...board.dim2] by 1 + cells.push(board.cellAt(r, c)) + SudokuChecks.oneUnknown(cells) + + @onePlaceRow: (board) -> @runAllRows(board, 'onePlace') + @onePlaceColumn: (board) -> @runAllColumns(board, 'onePlace') + @onePlaceSquare: (board) -> @runAllSquares(board, 'onePlace') + @onePlaceDiag: (board) -> @runBothDiags(board, 'onePlace') + @twoValPlacesSquare: (board) -> @runAllSquares(board, 'twoValPlaces') + @twoValPlacesRow: (board) -> @runAllRows(board, 'twoValPlaces') + @twoValPlacesColumn: (board) -> @runAllColumns(board, 'twoValPlaces') + @twoValPlacesDiag: (board) -> @runBothDiags(board, 'twoValPlaces') + + @solveBoard: (board) -> + checks = + 'uniqueAll': 'Value must be unique in row, column' + if board.checkDiags then ', square and diagonales.' else ' 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.' + + i = 1 + while true + console.time('Checking board.') + board.resetChangeFlags() + description = '' + for c of checks + description = checks[c] + @[c](board) + break if board.hasChanged() + if board.hasChanged() + console.info('Board was changed by "%s".', description) + body = document.getElementsByTagName('body') + body[0].innerHTML += i + '. ' + description + '
' + board.print() + i++ + console.timeEnd('Checking board.') + break unless board.hasChanged() && i<100 + return 0 diff --git a/js/SudokuBoard.class.js b/js/SudokuBoard.class.js index de9f897..a646880 100644 --- a/js/SudokuBoard.class.js +++ b/js/SudokuBoard.class.js @@ -1,86 +1,114 @@ -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; +// Generated by CoffeeScript 1.6.3 +(function() { + this.SudokuBoard = (function() { + SudokuBoard.prototype.BASE_SET = '123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'; - for (var r=0; r _ref2; m = 0 <= _ref2 ? ++_k : --_k) { + testm = 1 << m; + if (testm & mask) { + value += '
'; + } } + cssclasses.push('mask' + mask); + } + if (cssclasses.length > 0) { + cssclass = ' class="' + cssclasses.join(' ') + '"'; + } + html += "" + value + ""; } - this.changed = false; - } + html += ''; + } + html += ''; + body = document.getElementsByTagName('body'); + return body[0].innerHTML += html; + }; - this.hasChanged = function() { - return this.changed; - } + return SudokuBoard; - this.print = function() { - var html = ''; - html += ''; - for ( var r=0; r 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 += '' + value + ''; - } - html += ''; - } - html += '
'; - var body = document.getElementsByTagName( 'body' ); - body[0].innerHTML += html; - } -} \ No newline at end of file + })(); + +}).call(this); diff --git a/js/SudokuCell.class.js b/js/SudokuCell.class.js index 15377c0..7632740 100644 --- a/js/SudokuCell.class.js +++ b/js/SudokuCell.class.js @@ -1,59 +1,75 @@ -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; - } +// Generated by CoffeeScript 1.6.3 +(function() { + this.SudokuCell = (function() { + function SudokuCell(initVal, boardObj) { + this.boardObj = boardObj; + this.changed = false; + this.value = 0; + this.set = (1 << this.boardObj.dim2) - 1; + this.setValue(initVal); } - 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; - } + SudokuCell.prototype.setMask = function(newSet) { + if (newSet !== this.set) { + this.set = newSet; + this.changed = true; + return this.boardObj.changed = true; + } + }; + + SudokuCell.prototype.setValue = function(newValue) { + var setidx; + 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); + this.changed = true; + this.boardObj.changed = true; + } + return false; + }; - this.setValue( initVal ); // init cell + SudokuCell.prototype.getValue = function() { + if (this.value === 0) { + return '.'; + } + return this.value; + }; - this.getValue = function() { - if ( this.value == 0 ) return '.'; - return this.value; - } + SudokuCell.prototype.getMask = function() { + return this.set; + }; - this.getMask = function() { - return this.set; - } - - this.getUnknownsCount = function() { - var result = 0; - for (var n=0; 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 ); - } - } - } - } + SudokuChecks.onePlace = function(cells) { + var i, k, n, p, x, _i, _ref; + if (!cells[0]) { + return; + } + for (i = _i = 0, _ref = cells[0].boardObj.dim2; _i < _ref; i = _i += 1) { + n = 0; + p = 1 << i; + x = -1; + for (k in cells) { + if (cells[k].getValue() === '.' && (p & cells[k].getMask())) { + n++; + x = k; + } } - console.groupEnd(); - } -}; \ No newline at end of file + if (n === 1) { + cells[x].setMask(p); + cells[x].setValue(cells[0].boardObj.set.charAt(Math.log(p) / Math.log(2))); + } + } + return true; + }; + + SudokuChecks.oneUnknown = function(cells) { + var c, val, valid; + for (c in cells) { + if (cells[c].getValue() === '.' && cells[c].getUnknownsCount() === 1) { + valid = cells[c].getMask(); + val = cells[c].boardObj.set.charAt(Math.log(valid) / Math.log(2)); + cells[c].setValue(val); + } + } + return true; + }; + + SudokuChecks.twoValPlaces = function(cells) { + var dim2, i, j, k, n, p, _i, _j, _ref, _ref1; + if (!cells[0]) { + return; + } + dim2 = cells[0].boardObj.dim2; + console.group('twoValPlaces: (%o) %o', dim2, cells); + for (i = _i = 0, _ref = dim2 - 1; _i < _ref; i = _i += 1) { + for (j = _j = _ref1 = i + 1; _j < dim2; j = _j += 1) { + n = 0; + p = (1 << i) | (1 << j); + console.log('Now checking (%o, %o) mask: %o', i + 1, j + 1, p); + for (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) { + console.warn('Two matches!'); + for (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(); + return true; + }; + + return SudokuChecks; + + })(); + +}).call(this); diff --git a/js/SudokuSolver.class.js b/js/SudokuSolver.class.js index 0614ea6..a8b8a39 100644 --- a/js/SudokuSolver.class.js +++ b/js/SudokuSolver.class.js @@ -1,132 +1,186 @@ -var SudokuSolver = { +// Generated by CoffeeScript 1.6.3 +(function() { + this.SudokuSolver = (function() { + function SudokuSolver() {} - 'uniqueAll' : function( board ) { - for ( var r=0; r'; + board.print(); } - return result; - }, - - 'runAllRows' : function( board, func ) { - for ( var i=0; i'; - board.print(); - } - i++; - console.timeEnd( 'Checking board.' ); - } while ( board.hasChanged() && i<100); - } - -}; \ No newline at end of file +}).call(this);