Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
taylorjg committed Jul 9, 2019
0 parents commit 39f256e
Show file tree
Hide file tree
Showing 19 changed files with 7,126 additions and 0 deletions.
13 changes: 13 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module.exports = {
extends: 'eslint:recommended',
env: {
es6: true
},
parserOptions: {
ecmaVersion: 2018,
sourceType: 'module'
},
rules: {
'no-console': 'off'
}
}
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.DS_Store
node_modules/
dist/
1 change: 1 addition & 0 deletions Procfile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
web: node server
Empty file added README.md
Empty file.
6,855 changes: 6,855 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

28 changes: 28 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"name": "sudoku-buster",
"version": "0.0.0",
"description": "Web app to scan and solve a sudoku puzzle",
"scripts": {
"eslint": "eslint .",
"build": "webpack",
"start": "node server",
"test": "mocha test"
},
"author": "Jonathan Taylor",
"license": "MIT",
"dependencies": {
"dlxlib": "^2.0.0-alpha.2",
"express": "^4.17.1",
"ramda": "^0.26.1"
},
"devDependencies": {
"chai": "^4.2.0",
"copy-webpack-plugin": "^5.0.3",
"eslint": "^6.0.1",
"html-webpack-plugin": "^3.2.0",
"mocha": "^6.1.4",
"webpack": "^4.35.2",
"webpack-cli": "^3.3.5",
"webpack-dev-server": "^3.7.2"
}
}
5 changes: 5 additions & 0 deletions server/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = {
env: {
node: true
}
}
7 changes: 7 additions & 0 deletions server/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const express = require('express')
const path = require('path')
const PORT = process.env.PORT || 3090
const distFolder = path.resolve(__dirname, '..', 'dist')
const app = express()
app.use(express.static(distFolder))
app.listen(PORT, () => console.log(`Listening on http://localhost:${PORT}`))
5 changes: 5 additions & 0 deletions src/logic/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = {
env: {
node: true
}
}
58 changes: 58 additions & 0 deletions src/logic/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
const { Dlx } = require('dlxlib')
const R = require('ramda')

const noop = () => {}

const solve = (puzzle, onStep = noop, onSolution = noop) => {
const rows = buildRows(puzzle)
const matrix = buildMatrix(rows)
const dlx = new Dlx()
const resolveRowIndices = rowIndices => rowIndices.map(rowIndex => rows[rowIndex])
dlx.on('step', e => onStep(resolveRowIndices(e.partialSolution), e.stepIndex))
dlx.on('solution', e => onSolution(resolveRowIndices(e.solution), e.solutionIndex))
return dlx.solve(matrix)
}

const ROWS = R.range(0, 9)
const COLS = R.range(0, 9)
const DIGITS = R.range(1, 10)

const buildRows = puzzle => {
const cells = R.chain(row => R.map(col => ({ row, col }), COLS), ROWS)
return R.chain(buildRowsForCell(puzzle), cells)
}

const lookupInitialValue = (puzzle, { row, col }) => {
const ch = puzzle[row][col]
const n = Number(ch)
return Number.isInteger(n) && n > 0 ? n : undefined
}

const buildRowsForCell = puzzle => coords => {
const initialValue = lookupInitialValue(puzzle, coords)
return initialValue
? [{ coords, value: initialValue, isInitialValue: true }]
: DIGITS.map(digit => ({ coords, value: digit, isInitialValue: false }))
}

const buildMatrix = rows => rows.map(buildMatrixRow)

const buildMatrixRow = ({ coords, value }) => {
const { row, col } = coords
const box = rowColToBox(row, col)
const posColumns = oneHot(row, col)
const rowColumns = oneHot(row, value - 1)
const colColumns = oneHot(col, value - 1)
const boxColumns = oneHot(box, value - 1)
return [].concat(posColumns, rowColumns, colColumns, boxColumns)
}

const rowColToBox = (row, col) =>
Math.floor(row - (row % 3) + (col / 3))

const oneHot = (major, minor) =>
R.update(major * 9 + minor, 1, R.repeat(0, 9 * 9))

module.exports = {
solve
}
15 changes: 15 additions & 0 deletions src/logic/sample-puzzles.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const HARD_PUZZLE = [
'8 ',
' 36 ',
' 7 9 2 ',
' 5 7 ',
' 457 ',
' 1 3 ',
' 1 68',
' 85 1 ',
' 9 4 '
]

module.exports = {
HARD_PUZZLE
}
5 changes: 5 additions & 0 deletions src/web/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = {
env: {
browser: true
}
}
30 changes: 30 additions & 0 deletions src/web/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<!DOCTYPE html>
<html>

<head>
<title>Sudoku Buster</title>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<link rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" />
<link rel="stylesheet" href="styles.css" />
</head>

<body>
<div class="container">
<div cass="row">
<div class="version pull-right">
(version: <%= htmlWebpackPlugin.options.version %>)
</div>
</div>
<div cass="row">
<hr />
</div>
<div cass="row">
<div class="grid-wrapper">
<svg id="grid" class="grid"></svg>
</div>
</div>
</div>
</body>

</html>
5 changes: 5 additions & 0 deletions src/web/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { solve } from '../logic'
import { HARD_PUZZLE } from '../logic/sample-puzzles'

const solutions = solve(HARD_PUZZLE)
console.dir(solutions)
40 changes: 40 additions & 0 deletions src/web/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
.container {
margin-top: 20px;
}

.version {
font-size: small;
font-style: italic;
}

.grid-wrapper {
margin-bottom: 20px;
width: 100%;
display: flex;
justify-content: center;
}

.grid {
background-color: aliceblue;
width: 50%;
}

.grid-line {
stroke: #888;
}

.initial-value {
fill: magenta;
font-size: 32px;
}

.calculated-value {
fill: black;
font-size: 32px;
}

@media (max-width: 768px) {
.grid {
width: 100%;
}
}
6 changes: 6 additions & 0 deletions src/web/svg.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const createSvgElement = (elementName, additionalAttributes = {}) => {
const element = document.createElementNS('http://www.w3.org/2000/svg', elementName)
Object.entries(additionalAttributes).forEach(([name, value]) =>
element.setAttribute(name, value))
return element
}
9 changes: 9 additions & 0 deletions test/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module.exports = {
env: {
node: true,
mocha: true
},
globals: {
chai: 'readonly'
}
}
10 changes: 10 additions & 0 deletions test/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const expect = require('chai').expect
const { solve } = require('../src/logic')
const { HARD_PUZZLE } = require('../src/logic/sample-puzzles')

describe('solve tests', () => {
it('should find one solution to the hard puzzle', () => {
const solutions = solve(HARD_PUZZLE)
expect(solutions).to.have.length(1)
})
})
31 changes: 31 additions & 0 deletions webpack.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/* eslint-env node */

const path = require('path')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { version } = require('./package.json')

const distFolder = path.join(__dirname, 'dist')

module.exports = {
mode: 'production',
entry: './src/web/index.js',
output: {
path: distFolder,
filename: 'bundle.js'
},
plugins: [
new CopyWebpackPlugin([
{ context: './src/web', from: '*.html' },
{ context: './src/web', from: '*.css' }
]),
new HtmlWebpackPlugin({
template: './src/web/index.html',
version
})
],
devtool: 'source-map',
devServer: {
contentBase: distFolder
}
}

0 comments on commit 39f256e

Please sign in to comment.