UNPKG

sudoku

Version:

Sudoku generator and solver for node.js and Web

374 lines (305 loc) 7.46 kB
// Sudoku Generator and Solver for node.js // Copyright (c) 2011 Blagovest Dachev. All rights reserved. // // This is a port of David Bau's python implementation: // http://davidbau.com/archives/2006/09/04/sudoku_generator.html function makepuzzle(board) { var puzzle = []; var deduced = Array(81).fill(null); var order = [...Array(81).keys()]; shuffleArray(order); for (var i = 0; i < order.length; i++) { var pos = order[i]; if (deduced[pos] === null) { puzzle.push({ pos: pos, num: board[pos] }); deduced[pos] = board[pos]; deduce(deduced); } } shuffleArray(puzzle); for (var i = puzzle.length - 1; i >= 0; i--) { var e = puzzle[i]; removeElement(puzzle, i); var rating = checkpuzzle(boardforentries(puzzle), board); if (rating === -1) { puzzle.push(e); } } return boardforentries(puzzle); } function ratepuzzle(puzzle, samples) { var total = 0; for (var i = 0; i < samples; i++) { var tuple = solveboard(puzzle); if (tuple.answer === null) { return -1; } total += tuple.state.length; } return total / samples; } function checkpuzzle(puzzle, board) { if (board === undefined) { board = null; } var tuple1 = solveboard(puzzle); if (tuple1.answer === null) { return -1; } if (board != null && !boardmatches(board, tuple1.answer)) { return -1; } var difficulty = tuple1.state.length; var tuple2 = solvenext(tuple1.state); if (tuple2.answer != null) { return -1; } return difficulty; } function solvepuzzle(board) { return solveboard(board).answer; } function solveboard(original) { var board = [].concat(original); var guesses = deduce(board); if (guesses === null) { return { state: [], answer: board }; } var track = [{ guesses: guesses, count: 0, board: board }]; return solvenext(track); } function solvenext(remembered) { while (remembered.length > 0) { var tuple1 = remembered.pop(); if (tuple1.count >= tuple1.guesses.length) { continue; } remembered.push({ guesses: tuple1.guesses, count: tuple1.count + 1, board: tuple1.board }); var workspace = [].concat(tuple1.board); var tuple2 = tuple1.guesses[tuple1.count]; workspace[tuple2.pos] = tuple2.num; var guesses = deduce(workspace); if (guesses === null) { return { state: remembered, answer: workspace }; } remembered.push({ guesses: guesses, count: 0, board: workspace }); } return { state: [], answer: null }; } function deduce(board) { while (true) { var stuck = true; var guess = null; var count = 0; // fill in any spots determined by direct conflicts var tuple1 = figurebits(board); var allowed = tuple1.allowed; var needed = tuple1.needed; for (var pos = 0; pos < 81; pos++) { if (board[pos] === null) { var numbers = listbits(allowed[pos]); if (numbers.length === 0) { return []; } else if (numbers.length === 1) { board[pos] = numbers[0]; stuck = false; } else if (stuck) { var t = numbers.map(function (val, key) { return { pos: pos, num: val }; }); var tuple2 = pickbetter(guess, count, t); guess = tuple2.guess; count = tuple2.count; } } } if (!stuck) { var tuple3 = figurebits(board); allowed = tuple3.allowed; needed = tuple3.needed; } // fill in any spots determined by elimination of other locations for (var axis = 0; axis < 3; axis++) { for (var x = 0; x < 9; x++) { var numbers = listbits(needed[axis * 9 + x]); for (var i = 0; i < numbers.length; i++) { var n = numbers[i]; var bit = 1 << n; var spots = []; for (var y = 0; y < 9; y++) { var pos = posfor(x, y, axis); if (allowed[pos] & bit) { spots.push(pos); } } if (spots.length === 0) { return []; } else if (spots.length === 1) { board[spots[0]] = n; stuck = false; } else if (stuck) { var t = spots.map(function (val, key) { return { pos: val, num: n }; }); var tuple4 = pickbetter(guess, count, t); guess = tuple4.guess; count = tuple4.count; } } } } if (stuck) { if (guess != null) { shuffleArray(guess); } return guess; } } } function figurebits(board) { var needed = []; var allowed = board.map(function (val, key) { return val === null ? 511 : 0; }, []); for (var axis = 0; axis < 3; axis++) { for (var x = 0; x < 9; x++) { var bits = axismissing(board, x, axis); needed.push(bits); for (var y = 0; y < 9; y++) { var pos = posfor(x, y, axis); allowed[pos] = allowed[pos] & bits; } } } return { allowed: allowed, needed: needed }; } function posfor(x, y, axis) { if (axis === undefined) { axis = 0; } if (axis === 0) { return x * 9 + y; } else if (axis === 1) { return y * 9 + x; } return [0, 3, 6, 27, 30, 33, 54, 57, 60][x] + [0, 1, 2, 9, 10, 11, 18, 19, 20][y]; } function axismissing(board, x, axis) { var bits = 0; for (var y = 0; y < 9; y++) { var e = board[posfor(x, y, axis)]; if (e != null) { bits |= 1 << e; } } return 511 ^ bits; } function listbits(bits) { var list = []; for (var y = 0; y < 9; y++) { if ((bits & 1 << y) != 0) { list.push(y); } } return list; } function pickbetter(b, c, t) { if (b === null || t.length < b.length) { return { guess: t, count: 1 }; } else if (t.length > b.length) { return { guess: b, count: c }; } else if (randomInt(c) === 0) { return { guess: t, count: c + 1 }; } return { guess: b, count: c + 1 }; } function boardforentries(entries) { var board = Array(81).fill(null); for (var i = 0; i < entries.length; i++) { var item = entries[i]; var pos = item.pos; var num = item.num; board[pos] = num; } return board; } function boardmatches(b1, b2) { for (var i = 0; i < 81; i++) { if (b1[i] != b2[i]) { return false; } } return true; } function randomInt(max) { return Math.floor(Math.random() * (max + 1)); } function shuffleArray(original) { // Swap each element with another randomly selected one. for (var i = 0; i < original.length; i++) { var j = i; while (j === i) { j = Math.floor(Math.random() * original.length); } var contents = original[i]; original[i] = original[j]; original[j] = contents; } } function removeElement(array, from, to) { var rest = array.slice((to || from) + 1 || array.length); array.length = from < 0 ? array.length + from : from; return array.push.apply(array, rest); } module.exports = { makepuzzle: function () { return makepuzzle(solvepuzzle(Array(81).fill(null))); }, solvepuzzle: solvepuzzle, ratepuzzle: ratepuzzle, posfor: posfor }; //# sourceMappingURL=index.js.map