├── .gitignore ├── LICENSE ├── local-test.js ├── server.js ├── solve-sudoku.js └── worker.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Anna Henningsen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /local-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const { Worker } = require('worker_threads'); 3 | 4 | // Example Sudoku based on the one on Wikipedia’s 'Sudoku' page: 5 | const sudoku = new Uint8Array([ 6 | 5, 3, 0, 0, 7, 0, 0, 0, 0, 7 | 6, 0, 0, 0, 0, 0, 0, 0, 0, 8 | 0, 9, 8, 0, 0, 0, 0, 6, 0, 9 | 8, 0, 0, 0, 6, 0, 0, 0, 3, 10 | 4, 0, 0, 8, 0, 3, 0, 0, 1, 11 | 7, 0, 0, 0, 2, 0, 0, 0, 6, 12 | 0, 6, 0, 0, 0, 0, 2, 8, 0, 13 | 0, 0, 0, 4, 1, 9, 0, 0, 5, 14 | 0, 0, 0, 0, 8, 0, 0, 7, 9, 15 | ]); 16 | 17 | const worker = new Worker('./worker.js'); 18 | worker.postMessage(sudoku); 19 | worker.once('message', (solution) => { 20 | console.log(solution); 21 | // Let the Node.js main thread exit, even though the Worker is still running: 22 | worker.unref(); 23 | }); 24 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const http = require('http'); 3 | const { Worker } = require('worker_threads'); 4 | 5 | const workerPool = [ // Start a pool of four workers 6 | new Worker('./worker.js'), 7 | new Worker('./worker.js'), 8 | new Worker('./worker.js'), 9 | new Worker('./worker.js'), 10 | ]; 11 | const waiting = []; 12 | 13 | http.createServer((req, res) => { 14 | let body = ''; 15 | req.setEncoding('utf8'); // Receive strings rather than binary data 16 | req.on('data', chunk => body += chunk); 17 | req.on('end', () => { 18 | let dataAsUint8Array; 19 | try { 20 | dataAsUint8Array = new Uint8Array(JSON.parse(body)); 21 | // Fix the length at 81 = 9*9 fields so that we are not DoS’ed through 22 | // overly long input data. 23 | dataAsUint8Array = dataAsUint8Array.slice(0, 81); 24 | } catch (err) { 25 | res.writeHead(400); 26 | res.end(`Failed to parse body: ${err}`); 27 | return; 28 | } 29 | 30 | res.writeHead(200, { 31 | 'Content-Type': 'application/json' 32 | }); 33 | if (workerPool.length > 0) { 34 | handleRequest(res, dataAsUint8Array, workerPool.shift()); 35 | } else { 36 | // Queue up requests when no worker is available. 37 | // The function is waiting for a worker to be assigned. 38 | waiting.push((worker) => handleRequest(res, dataAsUint8Array, worker)); 39 | } 40 | }); 41 | }).listen(3000); 42 | 43 | function handleRequest(res, sudokuData, worker) { 44 | worker.postMessage(sudokuData); 45 | worker.once('message', (solutionData) => { 46 | res.end(JSON.stringify([...solutionData])); 47 | 48 | // If requests are waiting, reuse the current worker to handle the queued 49 | // request. Add the worker to pool if no requests are queued. 50 | if (waiting.length > 0) 51 | waiting.shift()(worker); 52 | else 53 | workerPool.push(worker); 54 | }); 55 | } 56 | -------------------------------------------------------------------------------- /solve-sudoku.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | // Helpers for dealing with a Sudoku field represented as 81 consecutive fields 3 | function indexToXY(index) { 4 | const x = index % 9; 5 | const y = (index - x) / 9; 6 | return [ x, y ]; 7 | } 8 | 9 | function xyToIndex(x, y) { 10 | return x + y * 9; 11 | } 12 | 13 | function solveSudoku(sudoku) { 14 | // Find first unknown entry in the array. 15 | const unknownIndex = sudoku.indexOf(0); 16 | if (unknownIndex === -1) 17 | return sudoku; // Already solved. 18 | const [x, y] = indexToXY(unknownIndex); 19 | 20 | const forbiddenValues = new Uint8Array(10); 21 | // Do not use values already present in this row 22 | for (let y2 = 0; y2 < 9; y2++) 23 | forbiddenValues[sudoku[xyToIndex(x, y2)]] = 1; 24 | // Do not use values already present in this column 25 | for (let x2 = 0; x2 < 9; x2++) 26 | forbiddenValues[sudoku[xyToIndex(x2, y)]] = 1; 27 | // Do not use values already present in this sub-square 28 | for (let x2 = x - x % 3; x2 < x - x % 3 + 3; x2++) 29 | for (let y2 = y - y % 3; y2 < y - y % 3 + 3; y2++) 30 | forbiddenValues[sudoku[xyToIndex(x2, y2)]] = 1; 31 | 32 | for (let value = 1; value <= 9; value++) { 33 | // If value is known to be forbidden, don't try it. 34 | if (forbiddenValues[value]) continue; 35 | sudoku[unknownIndex] = value; 36 | if (solveSudoku(sudoku)) return sudoku; 37 | } 38 | sudoku[unknownIndex] = 0; // Reset to original state. 39 | return null; // There is no solution. 40 | } 41 | 42 | exports.solveSudoku = solveSudoku; 43 | -------------------------------------------------------------------------------- /worker.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const { parentPort } = require('worker_threads'); 3 | const { solveSudoku } = require('./solve-sudoku.js'); 4 | 5 | // parentPort is the Worker’s way of communicating with the parent, similar to 6 | // window.onmessage in Web Workers. 7 | parentPort.on('message', (sudokuData) => { 8 | const solution = solveSudoku(sudokuData); 9 | parentPort.postMessage(solution); 10 | }); 11 | --------------------------------------------------------------------------------