Add (WIP) Sudoku Solver

Currently contains the Unit Tests, which should already be done correctly.

Functional tests still need to be added.
This commit is contained in:
CherryKitten 2022-11-09 22:06:14 +01:00
parent 6efa7707a4
commit 70cee77a95
Signed by: sammy
GPG key ID: 0B696A86A853E955
20 changed files with 11408 additions and 0 deletions

View file

@ -0,0 +1,3 @@
{
"presets": ["@babel/preset-env"]
}

View file

@ -0,0 +1,2 @@
# Tell Linguist to exclude HTML files to help Replit language detection.
*.html linguist-vendored

View file

@ -0,0 +1,4 @@
.vscode/*
.env
.glitch-assets
node_modules/

View file

@ -0,0 +1 @@
.replit

View file

@ -0,0 +1,10 @@
run = "npm start"
entrypoint = "server.js"
[packager]
language = "nodejs"
[packager.features]
packageSearch = true
guessImports = true
enabledForHosting = false

View file

@ -0,0 +1,3 @@
# Sudoku Solver
This is the boilerplate for the Sudoku Solver project. Instructions to complete your project can be found at https://www.freecodecamp.org/learn/quality-assurance/quality-assurance-projects/sudoku-solver

View file

@ -0,0 +1,139 @@
/*
*
*
*
*
*
*
*
*
*
*
*
* DO NOT EDIT THIS FILE
* For FCC testing purposes!
*
*
*
*
*
*
*
*
*
*
*
*/
function objParser(str, init) {
// finds objects, arrays, strings, and function arguments
// between parens, because they may contain ','
let openSym = ['[', '{', '"', "'", '('];
let closeSym = [']', '}', '"', "'", ')'];
let type;
let i;
for(i = (init || 0); i < str.length; i++ ) {
type = openSym.indexOf(str[i]);
if( type !== -1) break;
}
if (type === -1) return null;
let open = openSym[type];
let close = closeSym[type];
let count = 1;
let k;
for(k = i+1; k < str.length; k++) {
if(open === '"' || open === "'") {
if(str[k] === close) count--;
if(str[k] === '\\') k++;
} else {
if(str[k] === open) count++;
if(str[k] === close) count--;
}
if(count === 0) break;
}
if(count !== 0) return null;
let obj = str.slice(i, k+1);
return {
start : i,
end: k,
obj: obj
};
}
function replacer(str) {
// replace objects with a symbol ( __#n)
let obj;
let cnt = 0;
let data = [];
while(obj = objParser(str)) {
data[cnt] = obj.obj;
str = str.substring(0, obj.start) + '__#' + cnt++ + str.substring(obj.end+1)
}
return {
str : str,
dictionary : data
}
}
function splitter(str) {
// split on commas, then restore the objects
let strObj = replacer(str);
let args = strObj.str.split(',');
args = args.map(function(a){
let m = a.match(/__#(\d+)/);
while (m) {
a = a.replace(/__#(\d+)/, strObj.dictionary[m[1]]);
m = a.match(/__#(\d+)/);
}
return a.trim();
})
return args;
}
function assertionAnalyser(body) {
// already filtered in the test runner
// // remove comments
// body = body.replace(/\/\/.*\n|\/\*.*\*\//g, '');
// // get test function body
// body = body.match(/\{\s*([\s\S]*)\}\s*$/)[1];
if(!body) return "invalid assertion";
// replace assertions bodies, so that they cannot
// contain the word 'assertion'
let cleanedBody = body.match(/(?:browser\s*\.\s*)?assert\s*\.\s*\w*\([\s\S]*\)/)
if(cleanedBody && Array.isArray(cleanedBody)) {
body = cleanedBody[0];
} else {
// No assertions present
return [];
}
let s = replacer(body);
// split on 'assertion'
let splittedAssertions = s.str.split('assert');
let assertions = splittedAssertions.slice(1);
// match the METHODS
let assertionBodies = [];
let methods = assertions.map(function(a, i){
let m = a.match(/^\s*\.\s*(\w+)__#(\d+)/);
assertionBodies.push(parseInt(m[2]));
let pre = splittedAssertions[i].match(/browser\s*\.\s*/) ? 'browser.' : '';
return pre + m[1];
});
if(methods.some(function(m){ return !m })) return "invalid assertion";
// remove parens from the assertions bodies
let bodies = assertionBodies.map(function(b){
return s.dictionary[b].slice(1,-1).trim();
});
assertions = methods.map(function(m, i) {
return {
method: m,
args: splitter(bodies[i]) //replace objects, split on ',' ,then restore objects
}
})
return assertions;
}
module.exports = assertionAnalyser;

View file

@ -0,0 +1,22 @@
export const puzzlesAndSolutions = [
[
'1.5..2.84..63.12.7.2..5.....9..1....8.2.3674.3.7.2..9.47...8..1..16....926914.37.',
'135762984946381257728459613694517832812936745357824196473298561581673429269145378'
],
[
'5..91372.3...8.5.9.9.25..8.68.47.23...95..46.7.4.....5.2.......4..8916..85.72...3',
'568913724342687519197254386685479231219538467734162895926345178473891652851726943'
],
[
'..839.7.575.....964..1.......16.29846.9.312.7..754.....62..5.78.8...3.2...492...1',
'218396745753284196496157832531672984649831257827549613962415378185763429374928561'
],
[
'.7.89.....5....3.4.2..4..1.5689..472...6.....1.7.5.63873.1.2.8.6..47.1..2.9.387.6',
'473891265851726394926345817568913472342687951197254638734162589685479123219538746'
],
[
'82..4..6...16..89...98315.749.157.............53..4...96.415..81..7632..3...28.51',
'827549163531672894649831527496157382218396475753284916962415738185763249374928651'
]
];

View file

@ -0,0 +1,48 @@
const grid = [
[1, 2, 3, 4, 5, 6, 7, 8, 9],
[10, 11, 12, 13, 14, 15, 16, 17, 18],
[19, 20, 21, 22, 23, 24, 25, 26, 27],
[28, 29, 30, 31, 32, 33, 34, 35, 36],
[37, 38, 39, 40, 41, 42, 43, 44, 45],
[46, 47, 48, 49, 50, 51, 52, 53, 54],
[55, 56, 57, 58, 59, 60, 61, 62, 63],
[64, 65, 66, 67, 68, 69, 70, 71, 72],
[73, 74, 75, 76, 77, 78, 79, 80, 81],
];
class SudokuSolver {
getPuzzleArray(puzzleString) {
const puzzleArray = [];
for (let i = 1; i <= 81; i++) {
let value = puzzleString[i - 1];
value === "." || !value
? (puzzleArray[Math.ceil(i / 9)][(i % 9) + 1] = " ")
: (puzzleArray[Math.ceil(i / 9)][(i % 9) + 1] = value);
}
}
validate(puzzleString) {
if (puzzleString.length != 81) {
return {
status: "error",
error: "Expected puzzle to be 81 characters long",
};
} else if (!/^[1-9\.]+$/.test(puzzleString)) {
return { status: "error", error: "Invalid characters in puzzle" };
} else {
return true;
}
}
checkRowPlacement(puzzleString, row, column, value) {}
checkColPlacement(puzzleString, row, column, value) {}
checkRegionPlacement(puzzleString, row, column, value) {}
solve(puzzleString) {
return false;
}
}
module.exports = SudokuSolver;

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,25 @@
{
"name": "sudoku-solver",
"version": "2.0.0",
"description": "Quality Assurance 4: Sudoku Solver",
"main": "server.js",
"scripts": {
"start": "nodemon server.js",
"test": "mocha --timeout 5000 --require @babel/register --recursive --exit --ui tdd tests/"
},
"dependencies": {
"@babel/core": "^7.11.6",
"@babel/preset-env": "^7.11.5",
"@babel/register": "^7.11.5",
"body-parser": "^1.19.0",
"chai": "^4.2.0",
"chai-http": "^4.3.0",
"cors": "^2.8.5",
"dotenv": "^8.2.0",
"express": "^4.17.1",
"jsdom": "^16.4.0",
"mocha": "^8.1.3",
"nodemon": "^2.0.4"
},
"license": "MIT"
}

View file

@ -0,0 +1,64 @@
const textArea = document.getElementById("text-input");
const coordInput = document.getElementById("coord");
const valInput = document.getElementById("val");
const errorMsg = document.getElementById("error");
document.addEventListener("DOMContentLoaded", () => {
textArea.value =
"..9..5.1.85.4....2432......1...69.83.9.....6.62.71...9......1945....4.37.4.3..6..";
fillpuzzle(textArea.value);
});
textArea.addEventListener("input", () => {
fillpuzzle(textArea.value);
});
function fillpuzzle(data) {
let len = data.length < 81 ? data.length : 81;
for (let i = 0; i < len; i++) {
let rowLetter = String.fromCharCode('A'.charCodeAt(0) + Math.floor(i / 9));
let col = (i % 9) + 1;
if (!data[i] || data[i] === ".") {
document.getElementsByClassName(rowLetter + col)[0].innerText = " ";
continue;
}
document.getElementsByClassName(rowLetter + col)[0].innerText = data[i];
}
return;
}
async function getSolved() {
const stuff = {"puzzle": textArea.value}
const data = await fetch("/api/solve", {
method: "POST",
headers: {
"Accept": "application/json",
"Content-type": "application/json"
},
body: JSON.stringify(stuff)
})
const parsed = await data.json();
if (parsed.error) {
errorMsg.innerHTML = `<code>${JSON.stringify(parsed, null, 2)}</code>`;
return
}
fillpuzzle(parsed.solution)
}
async function getChecked() {
const stuff = {"puzzle": textArea.value, "coordinate": coordInput.value, "value": valInput.value}
const data = await fetch("/api/check", {
method: "POST",
headers: {
"Accept": "application/json",
"Content-type": "application/json"
},
body: JSON.stringify(stuff)
})
const parsed = await data.json();
errorMsg.innerHTML = `<code>${JSON.stringify(parsed, null, 2)}</code>`;
}
document.getElementById("solve-button").addEventListener("click", getSolved)
document.getElementById("check-button").addEventListener("click", getChecked)

View file

@ -0,0 +1,104 @@
code {
background: #d0d0d5;
}
.container {
display: flex;
flex-direction: column;
}
.form-container {
max-width: 95%;
align-self: flex-start;
}
textarea {
width: 100%;
}
#user-stories {
font-size: 1.2em;
}
#error-msg {
color: red;
display: inline;
margin-left: 10px;
}
#sudoku-grid-container {
display: flex;
flex-direction: row;
margin-left: 20px;
}
#sudoku-grid {
text-align: center;
display: inline;
}
.grid {
border: 3px solid;
border-spacing: 0;
}
td {
border: 1px solid;
padding: 0.3em;
}
.yAxisLegend {
margin-top: 50px;
text-align: right;
font-weight: bold;
}
.yAxisLegend tr td {
border: 1px solid rgba(0,0,0,0);
width: 2em;
height: 1.95em;
}
.xAxisLegend {
border: 3px solid rgba(0,0,0,0);
border-spacing: 0;
font-weight: bold;
}
.xAxisLegend tr td {
border: 1px solid rgba(0,0,0,0);
width: 2em;
height: 2em;
text-align: center;
vertical-align: bottom;
padding-bottom: 0;
}
.xAxisLegend tr td:nth-of-type(3n) {
border-right: 3px solid rgba(0,0,0,0);
}
.sudoku-input {
border: 1px solid black;
width: 2em;
height: 2em;
text-align: center;
}
.C1, .C2, .C3, .C4, .C5, .C6, .C7, .C8, .C9, .F1, .F2, .F3, .F4, .F5, .F6, .F7, .F8, .F9 {
border-bottom: 3px solid;
}
.A3, .B3, .C3, .D3, .E3, .F3, .G3, .H3, .I3, .A6, .B6, .C6, .D6, .E6, .F6, .G6, .H6, .I6 {
border-right: 3px solid;
}
@media (min-width: 800px) {
.container {
flex-direction: row;
}
.form-container {
align-self: center;
}
}

View file

@ -0,0 +1,18 @@
'use strict';
const SudokuSolver = require('../controllers/sudoku-solver.js');
module.exports = function (app) {
let solver = new SudokuSolver();
app.route('/api/check')
.post((req, res) => {
});
app.route('/api/solve')
.post((req, res) => {
});
};

View file

@ -0,0 +1,102 @@
/*
*
*
*
*
*
*
*
*
*
*
*
* DO NOT EDIT THIS FILE
* For FCC testing purposes!
*
*
*
*
*
*
*
*
*
*
*
*/
'use strict';
const cors = require('cors');
const fs = require('fs');
const runner = require('../test-runner');
module.exports = function (app) {
app.route('/_api/server.js')
.get(function(req, res, next) {
console.log('requested');
fs.readFile(__dirname + '/server.js', function(err, data) {
if(err) return next(err);
res.send(data.toString());
});
});
app.route('/_api/routes/api.js')
.get(function(req, res, next) {
console.log('requested');
fs.readFile(__dirname + '/routes/api.js', function(err, data) {
if(err) return next(err);
res.type('txt').send(data.toString());
});
});
app.route('/_api/controllers/convertHandler.js')
.get(function(req, res, next) {
console.log('requested');
fs.readFile(__dirname + '/controllers/convertHandler.js', function(err, data) {
if(err) return next(err);
res.type('txt').send(data.toString());
});
});
app.get('/_api/get-tests', cors(), function(req, res, next){
console.log('requested');
if(process.env.NODE_ENV === 'test') return next();
res.json({status: 'unavailable'});
},
function(req, res, next){
if(!runner.report) return next();
res.json(testFilter(runner.report, req.query.type, req.query.n));
},
function(req, res){
runner.on('done', function(report){
process.nextTick(() => res.json(testFilter(runner.report, req.query.type, req.query.n)));
});
});
app.get('/_api/app-info', function(req, res) {
let hs = Object.keys(res._headers)
.filter(h => !h.match(/^access-control-\w+/));
let hObj = {};
hs.forEach(h => {hObj[h] = res._headers[h]});
delete res._headers['strict-transport-security'];
res.json({headers: hObj});
});
};
function testFilter(tests, type, n) {
let out;
switch (type) {
case 'unit' :
out = tests.filter(t => t.context.match('Unit Tests'));
break;
case 'functional':
out = tests.filter(t => t.context.match('Functional Tests') && !t.title.match('#example'));
break;
default:
out = tests;
}
if(n !== undefined) {
return out[n] || out;
}
return out;
}

View file

@ -0,0 +1,56 @@
require('dotenv').config();
const express = require('express');
const bodyParser = require('body-parser');
const expect = require('chai').expect;
const cors = require('cors');
const fccTestingRoutes = require('./routes/fcctesting.js');
const apiRoutes = require('./routes/api.js');
const runner = require('./test-runner');
const app = express();
app.use('/public', express.static(process.cwd() + '/public'));
app.use(cors({origin: '*'})); //For FCC testing purposes only
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
//Index page (static HTML)
app.route('/')
.get(function (req, res) {
res.sendFile(process.cwd() + '/views/index.html');
});
//For FCC testing purposes
fccTestingRoutes(app);
// User routes
apiRoutes(app);
//404 Not Found Middleware
app.use(function(req, res, next) {
res.status(404)
.type('text')
.send('Not Found');
});
//Start our server and tests!
const PORT = process.env.PORT || 3000
app.listen(PORT, function () {
console.log("Listening on port " + PORT);
// process.env.NODE_ENV='test'
if (process.env.NODE_ENV==='test') {
console.log('Running Tests...');
setTimeout(function () {
try {
runner.run();
} catch (error) {
console.log('Tests are not valid:');
console.error(error);
}
}, 1500);
}
});
module.exports = app; // for testing

View file

@ -0,0 +1,107 @@
/*
*
*
*
*
*
*
*
*
*
*
*
* DO NOT EDIT THIS FILE
* For FCC testing purposes!
*
*
*
*
*
*
*
*
*
*
*
*/
const analyser = require('./assertion-analyser');
const EventEmitter = require('events').EventEmitter;
const Mocha = require('mocha'),
fs = require('fs'),
path = require('path');
require("@babel/register");
const mocha = new Mocha({ timeout: 5000 });
const testDir = './tests'
// Add each .js file to the mocha instance
fs.readdirSync(testDir).filter(function(file){
// Only keep the .js files
return file.substr(-3) === '.js';
}).forEach(function(file){
mocha.addFile(
path.join(testDir, file)
);
});
let emitter = new EventEmitter();
emitter.run = function() {
let tests = [];
let context = "";
let separator = ' -> ';
// Run the tests.
try {
let runner = mocha.ui('tdd').run()
.on('test end', function(test) {
// remove comments
let body = test.body.replace(/\/\/.*\n|\/\*.*\*\//g, '');
// collapse spaces
body = body.replace(/\s+/g,' ');
let obj = {
title: test.title,
context: context.slice(0, -separator.length),
state: test.state,
// body: body,
assertions: analyser(body)
};
tests.push(obj);
})
.on('end', function() {
emitter.report = tests;
emitter.emit('done', tests)
})
.on('suite', function(s) {
context += (s.title + separator);
})
.on('suite end', function(s) {
context = context.slice(0, -(s.title.length + separator.length))
})
} catch(e) {
throw(e);
}
};
module.exports = emitter;
/*
* Mocha.runner Events:
* can be used to build a better custom report
*
* - `start` execution started
* - `end` execution complete
* - `suite` (suite) test suite execution started
* - `suite end` (suite) all tests (and sub-suites) have finished
* - `test` (test) test execution started
* - `test end` (test) test completed
* - `hook` (hook) hook execution started
* - `hook end` (hook) hook complete
* - `pass` (test) test passed
* - `fail` (test, err) test failed
* - `pending` (test) test pending
*/

View file

@ -0,0 +1,156 @@
const chai = require("chai");
const assert = chai.assert;
import { puzzlesAndSolutions } from "../controllers/puzzle-strings";
const invalidStrings = [
"135762984946-8125772845961369451783281293674535B82419647329856158167342926:145378",
'56891372434268751"%=7254386685479231219538467734162895926345178473891652851726943',
"/////////////////////////////////////////////////////////////////////////////////",
"NyanNyanNyanNyanNyanNyanNyanNyanNyanNyanNyanNyanNyanNyanNyanNyanNyanNyanNyanNyan.",
];
const unsolvableStrings = [
"115..2.84..63.12.7.2..5.....9..1....8.2.3674.3.7.2..9.420..8..1..16....926969.37.",
"4206942069.......................................................................",
"111111111111111111111111111111111111111111111111111111111111111111111111111111111",
];
const Solver = require("../controllers/sudoku-solver.js");
let solver = new Solver();
suite("Unit Tests", () => {
suite("Validation", () => {
test("Logic handles a valid puzzle string of 81 characters", () => {
for (let i in puzzlesAndSolutions) {
assert.isTrue(solver.validate(puzzlesAndSolutions[i][0]));
}
});
test("Logic handles a puzzle string with invalid characters (not 1-9 or .)", () => {
for (let i in invalidStrings) {
assert.equal(
solver.validate(invalidStrings[i]).error,
"Invalid characters in puzzle",
invalidStrings[i]
);
}
});
test("Logic handles a puzzle string that is not 81 characters in length", () => {
assert.equal(
solver.validate("135762984946145378").error,
"Expected puzzle to be 81 characters long"
);
});
});
suite("Placement Checks", () => {
test("Login handles a valid row placement", () => {
const checks = [
solver.checkRowPlacement(puzzlesAndSolutions[0][0], "A", 2, 3),
solver.checkRowPlacement(puzzlesAndSolutions[0][1], "A", 2, 6),
solver.checkRowPlacement(puzzlesAndSolutions[0][2], "A", 1, 2),
];
for (let i in checks) {
assert.isTrue(checks[i]);
}
});
test("Logic handles an invalid row placement", () => {
const checks = [
solver.checkRowPlacement(puzzlesAndSolutions[0][0], "A", 2, 8),
solver.checkRowPlacement(puzzlesAndSolutions[0][1], "A", 2, 1),
solver.checkRowPlacement(puzzlesAndSolutions[0][2], "A", 1, 3),
];
for (let i in checks) {
assert.isFalse(checks[i]);
}
});
test("Login handles a valid column placement", () => {
const checks = [
solver.checkColPlacement(puzzlesAndSolutions[0][0], "A", 2, 3),
solver.checkColPlacement(puzzlesAndSolutions[0][1], "A", 2, 6),
solver.checkColPlacement(puzzlesAndSolutions[0][2], "A", 1, 2),
];
for (let i in checks) {
assert.isTrue(checks[i]);
}
});
test("Logic handles an invalid column placement", () => {
const checks = [
solver.checkColPlacement(puzzlesAndSolutions[0][0], "B", 2, 9),
solver.checkColPlacement(puzzlesAndSolutions[0][1], "B", 2, 8),
solver.checkColPlacement(puzzlesAndSolutions[0][2], "C", 3, 2),
];
for (let i in checks) {
assert.isFalse(checks[i]);
}
});
test("Logic handles a valid region (3x3 grid) placement", () => {
const checks = [
solver.checkRegionPlacement(puzzlesAndSolutions[0][0], "A", 2, 3),
solver.checkRegionPlacement(puzzlesAndSolutions[0][1], "A", 2, 6),
solver.checkRegionPlacement(puzzlesAndSolutions[0][2], "A", 1, 2),
];
for (let i in checks) {
assert.isTrue(checks[i]);
}
});
test("Logic handles an invalid region (3x3 grid) placement", () => {
const checks = [
solver.checkRegionPlacement(puzzlesAndSolutions[0][0], "C", 3, 5),
solver.checkRegionPlacement(puzzlesAndSolutions[0][1], "C", 3, 5),
solver.checkRegionPlacement(puzzlesAndSolutions[0][2], "C", 3, 5),
];
for (let i in checks) {
assert.isFalse(checks[i]);
}
});
});
suite("Solver", () => {
test("Valid puzzle strings pass the solver", () => {
for (let i in puzzlesAndSolutions) {
console.log(i);
assert.equal(
solver.solve(puzzlesAndSolutions[i][1]),
puzzlesAndSolutions[i][1]
);
}
});
test("Invalid puzzle strings fail the solver", () => {
for (let i in invalidStrings) {
assert.equal(
solver.solve(invalidStrings[i].error),
"Invalid characters in puzzle",
"invalid characters" + invalidStrings[i]
);
}
assert.equal(
solver.solve("135762984946145378").error,
"Expected puzzle to be 81 characters long",
"wrong length"
);
for (let i in unsolvableStrings) {
assert.equal(
solver.solve().error,
"Puzzle cannot be solved",
"not solvable" + unsolvableStrings[i]
);
}
});
test("Solver returns the expected solution for an incomplete puzzle", () => {
for (let i in puzzlesAndSolutions) {
assert.equal(
solver.solve(puzzlesAndSolutions[i][0]),
puzzlesAndSolutions[i][1],
puzzlesAndSolutions[i]
);
}
});
});
});
/*
TODO:
Valid puzzle strings pass the solver
*/

View file

@ -0,0 +1,28 @@
const chai = require("chai");
const chaiHttp = require("chai-http");
const assert = chai.assert;
const server = require("../server");
chai.use(chaiHttp);
suite("Functional Tests", () => {});
/*
TODO:
Solve a puzzle with valid puzzle string: POST request to /api/solve
Solve a puzzle with missing puzzle string: POST request to /api/solve
Solve a puzzle with invalid characters: POST request to /api/solve
Solve a puzzle with incorrect length: POST request to /api/solve
Solve a puzzle that cannot be solved: POST request to /api/solve
Check a puzzle placement with all fields: POST request to /api/check
Check a puzzle placement with single placement conflict: POST request to /api/check
Check a puzzle placement with multiple placement conflicts: POST request to /api/check
Check a puzzle placement with all placement conflicts: POST request to /api/check
Check a puzzle placement with missing required fields: POST request to /api/check
Check a puzzle placement with invalid characters: POST request to /api/check
Check a puzzle placement with incorrect length: POST request to /api/check
Check a puzzle placement with invalid placement coordinate: POST request to /api/check
Check a puzzle placement with invalid placement value: POST request to /api/check
*/

View file

@ -0,0 +1,355 @@
<!DOCTYPE html>
<html>
<head>
<title>Sudoku Solver</title>
<meta name="description" content="An example for the fCC QA Sudoku Solver project" />
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link id="favicon" rel="icon" href="https://cdn.freecodecamp.org/universal/favicons/favicon-32x32.png" type="image/x-icon">
<link rel="stylesheet" href="../public/style.css" type="text/css" />
</head>
<body>
<header>
<h1>Sudoku Solver</h1>
</header>
<div class="container">
<div class="form-container">
<form id="solve-form">
<textarea
rows="10"
cols="85"
id="text-input"
name="puzzle"
></textarea>
<br />
<input type="button" value="Solve" id="solve-button"/>
<div id="error-msg"></div>
</form>
<form id="check-form">
<p>Coordinate (A1): <input id="coord" class="checker" type="text" name="coordinate"></p>
<p>Value (1-9): <input class="checker" type="text" id="val" name="value"></p>
<input type="button" id="check-button" value="Check Placement">
</form>
<span id="error"></span>
</div>
<div id="sudoku-grid-container">
<div>
<table class="yAxisLegend">
<tr>
<td>A</td>
</tr>
<tr>
<td>B</td>
</tr>
<tr>
<td>C</td>
</tr>
<tr>
<td>D</td>
</tr>
<tr>
<td>E</td>
</tr>
<tr>
<td>F</td>
</tr>
<tr>
<td>G</td>
</tr>
<tr>
<td>H</td>
</tr>
<tr>
<td>I</td>
</tr>
</table>
</div>
<div id="sudoku-grid">
<table class="xAxisLegend">
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
<td>9</td>
</tr>
</table>
<table class="grid">
<tbody>
<tr>
<td class="A1 sudoku-input" title="A1">
</td>
<td class="A2 sudoku-input" title="A2">
</td>
<td class="A3 sudoku-input" title="A3">
</td>
<td class="A4 sudoku-input" title="A4">
</td>
<td class="A5 sudoku-input" title="A5">
</td>
<td class="A6 sudoku-input" title="A6">
</td>
<td class="A7 sudoku-input" title="A7">
</td>
<td class="A8 sudoku-input" title="A8">
</td>
<td class="A9 sudoku-input" title="A9">
</td>
</tr>
<tr>
<td class="B1 sudoku-input" title="B1">
</td>
<td class="B2 sudoku-input" title="B2">
</td>
<td class="B3 sudoku-input" title="B3">
</td>
<td class="B4 sudoku-input" title="B4">
</td>
<td class="B5 sudoku-input" title="B5">
</td>
<td class="B6 sudoku-input" title="B6">
</td>
<td class="B7 sudoku-input" title="B7">
</td>
<td class="B8 sudoku-input" title="B8">
</td>
<td class="B9 sudoku-input" title="B9">
</td>
</tr>
<tr>
<td class="C1 sudoku-input" title="C1">
</td>
<td class="C2 sudoku-input" title="C2">
</td>
<td class="C3 sudoku-input" title="C3">
</td>
<td class="C4 sudoku-input" title="C4">
</td>
<td class="C5 sudoku-input" title="C5">
</td>
<td class="C6 sudoku-input" title="C6">
</td>
<td class="C7 sudoku-input" title="C7">
</td>
<td class="C8 sudoku-input" title="C8">
</td>
<td class="C9 sudoku-input" title="C9">
</td>
</tr>
<tr>
<td class="D1 sudoku-input" title="D1">
</td>
<td class="D2 sudoku-input" title="D2">
</td>
<td class="D3 sudoku-input" title="D3">
</td>
<td class="D4 sudoku-input" title="D4">
</td>
<td class="D5 sudoku-input" title="D5">
</td>
<td class="D6 sudoku-input" title="D6">
</td>
<td class="D7 sudoku-input" title="D7">
</td>
<td class="D8 sudoku-input" title="D8">
</td>
<td class="D9 sudoku-input" title="D9">
</td>
</tr>
<tr>
<td class="E1 sudoku-input" title="E1">
</td>
<td class="E2 sudoku-input" title="E2">
</td>
<td class="E3 sudoku-input" title="E3">
</td>
<td class="E4 sudoku-input" title="E4">
</td>
<td class="E5 sudoku-input" title="E5">
</td>
<td class="E6 sudoku-input" title="E6">
</td>
<td class="E7 sudoku-input" title="E7">
</td>
<td class="E8 sudoku-input" title="E8">
</td>
<td class="E9 sudoku-input" title="E9">
</td>
</tr>
<tr>
<td class="F1 sudoku-input" title="F1">
</td>
<td class="F2 sudoku-input" title="F2">
</td>
<td class="F3 sudoku-input" title="F3">
</td>
<td class="F4 sudoku-input" title="F4">
</td>
<td class="F5 sudoku-input" title="F5">
</td>
<td class="F6 sudoku-input" title="F6">
</td>
<td class="F7 sudoku-input" title="F7">
</td>
<td class="F8 sudoku-input" title="F8">
</td>
<td class="F9 sudoku-input" title="F9">
</td>
</tr>
<tr>
<td class="G1 sudoku-input" title="G1">
</td>
<td class="G2 sudoku-input" title="G2">
</td>
<td class="G3 sudoku-input" title="G3">
</td>
<td class="G4 sudoku-input" title="G4">
</td>
<td class="G5 sudoku-input" title="G5">
</td>
<td class="G6 sudoku-input" title="G6">
</td>
<td class="G7 sudoku-input" title="G7">
</td>
<td class="G8 sudoku-input" title="G8">
</td>
<td class="G9 sudoku-input" title="G9">
</td>
</tr>
<tr>
<td class="H1 sudoku-input" title="H1">
</td>
<td class="H2 sudoku-input" title="H2">
</td>
<td class="H3 sudoku-input" title="H3">
</td>
<td class="H4 sudoku-input" title="H4">
</td>
<td class="H5 sudoku-input" title="H5">
</td>
<td class="H6 sudoku-input" title="H6">
</td>
<td class="H7 sudoku-input" title="H7">
</td>
<td class="H8 sudoku-input" title="H8">
</td>
<td class="H9 sudoku-input" title="H9">
</td>
</tr>
<tr>
<td class="I1 sudoku-input" title="I1">
</td>
<td class="I2 sudoku-input" title="I2">
</td>
<td class="I3 sudoku-input" title="I3">
</td>
<td class="I4 sudoku-input" title="I4">
</td>
<td class="I5 sudoku-input" title="I5">
</td>
<td class="I6 sudoku-input" title="I6">
</td>
<td class="I7 sudoku-input" title="I7">
</td>
<td class="I8 sudoku-input" title="I8">
</td>
<td class="I9 sudoku-input" title="I9">
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</body>
<script type="module" src="../public/index.js"></script>
</html>