Thanks for all the good suggestions in Half 1. I applied loads of it. Right here is model 2. I’m searching for suggestions on:
- Recursive clear up algorithm. It is too gradual. I used Chrome DevTools Efficiency Profiler to optimize gradual features and I sped it up rather a lot. However basically I believe the algorithm simply sucks.
- JavaScript normally.
- Code group normally. Alternative of lessons, how you can arrange listeners, and so forth.
Fiddle
https://jsfiddle.internet/AdmiralAkbar2/80qgkps6/5/
Screenshot
Efficiency
// getSolutionCountRecursively velocity, with restrict set to 50,000
// 2508ms initially
// 2186ms added/refactored getTrueKey
// 1519ms added/refactored cloneBoard
// 789ms added/refactored squareIsSolved
// 298ms added/refactored setBoard
// 170ms commented out RegEx in get_legal_move
JavaScript
`use strict`;
class SudokuBoard {
constructor() {
// Not fairly, however I declare the identical factor Three instances for efficiency. Else I've to deep copy the blank_board array, which is pricey in response to Chrome devtools efficiency profile.
this.blank_board = [
[0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0]
];
this.board = [
[0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0]
];
this.original_board = [
[0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0]
];
}
// That is solely meant to be used by getSolutionCountRecursively. Quicker than set_board
// Every little thing else ought to use set_board. set_board performs extra information validation.
setBoard(board) {
this.board = board;
}
static cloneBoard(board) {
let array = [
[0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0]
];
for ( let i = 0; i < 9; i++ ) {
for ( let j = 0; j < 9; j++ ) {
array[i][j] = board[i][j];
}
}
return array;
// return Helper.deepCopyArray(board);
}
// returns if board modified or not
set_board(board_string) {
const old_board = SudokuBoard.cloneBoard(this.board);
if ( ! board_string ) {
return false;
}
if ( ! board_string.match(/^[0-9*_.]{81}$/m) ) {
return false;
}
// TODO: foreach getBoardSquares
for ( let row = 0; row < 9; row++ ) {
for ( let column = 0; column < 9; column++ ) {
let char = board_string.charAt(row*9+column);
if ( char === `*` || char === `_` || char === `.` )
{
char = 0;
}
this.board[row][column] = parseInt(char);
}
}
if ( ! this.puzzleIsValid() ) {
this.board = SudokuBoard.cloneBoard(old_board);
return false;
}
this.set_original_board(this.board);
return true;
}
get_board() {
return this.board;
}
getString() {
let str = ``;
for ( let row = 0; row < 9; row++ ) {
for ( let col = 0; col < 9; col++ ) {
str += this.board[row][col];
}
}
return str;
}
// making this its personal methodology to assist with debugging
set_original_board(obj) {
this.original_board = SudokuBoard.cloneBoard(obj);
}
restart_puzzle() {
this.board = SudokuBoard.cloneBoard(this.original_board);
}
make_move(row, col, worth) {
if ( worth === `` ) {
worth = 0;
}
this.board[row][col] = worth;
}
getSquaresOnBoard() {
let squares = [];
for ( let i = 0; i < 9; i++ ) {
for ( let j = 0; j < 9; j++ ) {
const worth = this.board[i][j];
squares.push(new SudokuSquare(i, j, worth));
}
}
return squares;
}
// TODO: think about splitting the beneath code right into a SudokuSolver class
// I have not performed it but as a result of I might must go a board variable round. That is loads of code re-writing. Undecided it is price it.
puzzleIsValid() {
strive {
this.process_of_elimination(false, false);
} catch {
return false;
}
return true;
}
is_legal_move(row, col, worth, checkForNonNumbers = true) {
worth = parseInt(worth);
// verify for non numbers
// Regex could be very costly. Solely verify this for consumer enter.
if ( checkForNonNumbers ) {
if ( ! worth.toString().match(/^[1-9]$/m) ) {
return false;
}
}
// verify row
// TODO: foreach getRowSquares
for ( let i = 0; i < 9; i++ ) {
if ( worth === this.board[row][i] ) {
return false;
}
}
// verify column
// TODO: foreach getColumnSquares
for ( let i = 0; i < 9; i++ ) {
if ( worth === this.board[i][col] ) {
return false;
}
}
// verify 3x3 grid
// TODO: foreach getBoxSquares
const row_offset = Math.flooring(row/3)*3;
const col_offset = Math.flooring(col/3)*3;
for ( let i = 0 + row_offset; i <= 2 + row_offset; i++ ) {
for ( let j = 0 + col_offset; j <= 2 + col_offset; j++ ) {
if ( worth === this.board[i][j] ) {
return false;
}
}
}
return true;
}
// Potentialities {1:true, 2:true, 3:true, 4:true, 5:true, 6:true, 7:true, 8:true, 9:true}
static squareIsSolved(prospects) {
let trueCount = 0;
for ( let i = 1; i <= 9; i++ ) {
if ( prospects[i] ) {
trueCount++;
}
if ( trueCount >= 2 ) {
return false;
}
}
if ( trueCount === 1 ) {
return true;
}
return false;
}
// If Eight of 9 squares are stuffed in, fill in ninth sq..
process_of_elimination(hint_mode = false, modifyBoard = true) {
let prospects;
let empty_col;
let empty_row;
// verify row
for ( let row = 0; row < 9; row++ ) {
// bool array [true, true, true] is quicker than listing [1, 2, 3]
prospects = {1:true, 2:true, 3:true, 4:true, 5:true, 6:true, 7:true, 8:true, 9:true};
empty_col = 0;
for ( let col = 0; col < 9; col++ ) {
const worth = this.board[row][col];
if ( worth === 0 ) {
empty_col = col;
proceed;
} else if ( prospects[value] ) {
prospects[value] = false;
} else {
this.throw_duplicate_number_error();
}
}
if ( SudokuBoard.squareIsSolved(prospects) ) {
if ( hint_mode ) {
return new SudokuSquare(row, empty_col);
} else if ( modifyBoard ) {
this.board[row][empty_col] = SudokuBoard.getTrueKey(prospects);
}
}
}
// verify column
for ( let col = 0; col < 9; col++ ) {
prospects = {1:true, 2:true, 3:true, 4:true, 5:true, 6:true, 7:true, 8:true, 9:true};
empty_row = 0;
for ( let row = 0; row < 9; row++ ) {
const worth = this.board[row][col];
if ( worth === 0 ) {
empty_row = row;
proceed;
} else if ( prospects[value] ) {
prospects[value] = false;
} else {
this.throw_duplicate_number_error();
}
}
if ( SudokuBoard.squareIsSolved(prospects) ) {
if ( hint_mode ) {
return new SudokuSquare(empty_row, col);
} else if ( modifyBoard ) {
this.board[empty_row][col] = SudokuBoard.getTrueKey(prospects);
}
}
}
// verify 3x3 grid
for ( let row = 0; row < 9; row+=3 ) {
for ( let col = 0; col < 9; col+=3 ) {
prospects = {1:true, 2:true, 3:true, 4:true, 5:true, 6:true, 7:true, 8:true, 9:true};
empty_row = 0;
empty_col = 0;
const row_offset = Math.flooring(row/3)*3;
const col_offset = Math.flooring(col/3)*3;
// iterate round 3x3 space
for ( let i = 0 + row_offset; i <= 2 + row_offset; i++ ) {
for ( let j = 0 + col_offset; j <= 2 + col_offset; j++ ) {
const worth = this.board[i][j];
if ( worth === 0 ) {
empty_row = i;
empty_col = j;
proceed;
} else if ( prospects[value] ) {
prospects[value] = false;
} else {
this.throw_duplicate_number_error();
}
}
}
if ( SudokuBoard.squareIsSolved(prospects) ) {
if ( hint_mode ) {
return new SudokuSquare(empty_row, empty_col);
} else if ( modifyBoard ) {
this.board[empty_row][empty_col] = SudokuBoard.getTrueKey(prospects);
}
}
}
}
}
puzzleIsSolved() {
for ( let i = 0; i < 9; i++ ) {
for ( let j = 0; j < 9; j++ ) {
if ( this.board[i][j] === 0 ) {
return false;
}
}
}
return true;
}
getNumberOfSolutions() {
if ( ! this.puzzleIsValid() ) {
this.throw_duplicate_number_error();
return 0;
}
if ( this.puzzleIsSolved() ) {
window.alert('Puzzle is already solved');
return 1;
}
const initialRecursionTracker = new RecursionTracker();
const initialSudoku = new SudokuBoard();
initialSudoku.setBoard(SudokuBoard.cloneBoard(this.board));
initialRecursionTracker.setSudokuToCheck(initialSudoku);
const finalRecursionTracker = this.getSolutionCountRecursively(initialRecursionTracker);
const numberOfSolutions = finalRecursionTracker.getNumberOfSolutions();
const boardsChecked = finalRecursionTracker.getBoardsChecked();
console.log(`Variety of options: ` + numberOfSolutions);
console.log(`Boards checked: ` + boardsChecked);
// window.alert(`Puzzle has ${numberOfSolutions} options`);
return finalRecursionTracker;
}
getSolutionCountRecursively(recursionTracker) {
// for first recursion, recursionTracker might be
// this.numberOfSolutions = 0;
// this.solutionSudokuList = null;
// this.sudokuToCheck = Sudoku;
// this.boardsChecked = 0;
// No have to clone recursionTracker. Simply hold utilizing the identical one.
// Benchmark Historical past (with restrict set to 50,000)
// 2508ms initially
// 2186ms added/refactored getTrueKey
// 1519ms added/refactored cloneBoard
// 789ms added/refactored squareIsSolved
// 298ms added/refactored setBoard
// 170ms commented out RegEx in get_legal_move
const RECURSION_LIMIT = 500000;
if ( recursionTracker.getNumberOfSolutions() > 500 ) {
return recursionTracker;
}
if ( recursionTracker.getBoardsChecked() > RECURSION_LIMIT ) {
recursionTracker.markEarlyExit();
return recursionTracker;
}
const currentSudoku = recursionTracker.getSudokuToCheck();
// foreach boardsquare
for ( let sq. of currentSudoku.getSquaresOnBoard() ) {
// if sq. is empty
if ( sq..getValue() === 0 ) {
// for every doable quantity 1-9
for ( let i = 1; i <= 9; i++ ) {
if ( recursionTracker.getBoardsChecked() > RECURSION_LIMIT ) {
recursionTracker.markEarlyExit();
return recursionTracker;
}
const row = sq..getRow();
const col = sq..getCol();
if ( currentSudoku.is_legal_move(row, col, i, false) ) {
recursionTracker.incrementBoardsChecked();
// create new Sudoku
let nextSudoku = new SudokuBoard();
const board = SudokuBoard.cloneBoard(currentSudoku.board);
nextSudoku.setBoard(board);
// make transfer
nextSudoku.make_move(row, col, i);
// console.log(currentSudoku.getString());
if ( nextSudoku.puzzleIsSolved() ) {
recursionTracker.addSolution(nextSudoku);
recursionTracker.incrementBoardsChecked();
// console.log(nextSudoku.getString());
} else {
recursionTracker.setSudokuToCheck(nextSudoku);
recursionTracker = this.getSolutionCountRecursively(recursionTracker);
}
}
}
}
}
return recursionTracker;
}
static getTrueKey(array) {
let depend = 0;
let trueKey = false;
for ( let key in array ) {
if ( array[key] ) {
trueKey = key;
depend++;
}
}
if ( depend === 1 ) {
return parseInt(trueKey);
} else {
return false;
}
}
}
class RecursionTracker {
constructor() {
this.numberOfSolutions = 0;
this.solutionList = [];
this.sudokuToCheck = null;
this.boardsChecked = 0;
this.earlyExit = false;
}
getNumberOfSolutions() {
return this.solutionList.size;
}
getInfoString() {
let string = ``;
string += this.getBoardsChecked() + ` Boards Checkedrn`;
string += this.solutionList.size + ` Options Foundrn`;
if ( this.earlyExit ) {
string += `Recursion Restrict Reached. Exited Early.rn`;
}
if ( this.solutionList.size !== 0 ) {
string += `Options:rn`;
}
for ( let solutionString of this.solutionList ) {
string += solutionString + `rn`;
}
return string;
}
getSudokuToCheck() {
return this.sudokuToCheck;
}
getBoardsChecked() {
return this.boardsChecked;
}
markEarlyExit() {
this.earlyExit = true;
}
addSolution(sudoku) {
const sudokuStringToCheck = sudoku.getString();
if ( ! this.solutionList.contains(sudokuStringToCheck) ) {
this.solutionList.push(sudokuStringToCheck);
}
}
setSudokuToCheck(sudoku) {
this.sudokuToCheck = sudoku;
}
incrementBoardsChecked() {
this.boardsChecked++;
}
}
class SudokuSquare {
constructor(row, col, worth = 0) {
this.row = parseInt(row);
this.col = parseInt(col);
this.worth = parseInt(worth);
}
getSquare() {
return [this.row, this.col];
}
getRow() {
return this.row;
}
getCol() {
return this.col;
}
getValue() {
return this.worth;
}
setValue(row, col) {
this.row = row;
this.col = col;
}
}
class SudokuDOM {
static display_board(
sudoku_object,
sudoku_squares,
string_box,
sudoku_wiki_link,
change_square_color = true
) {
const board = sudoku_object.get_board();
this.clear_board(sudoku_squares, change_square_color);
for ( let row = 0; row < 9; row++ ) {
for ( let col = 0; col < 9; col++ ) {
const enter = sudoku_squares[row][col];
enter.classList.take away(`trace`);
enter.disabled = false;
if ( board[row][col] != 0 ) {
enter.worth = board[row][col];
if ( change_square_color ) {
enter.classList.add(`imported-square`);
enter.disabled = true;
}
}
}
}
SudokuDOM.display_string(sudoku_object, string_box, sudoku_wiki_link);
}
static display_string(sudoku_object, string_box, sudoku_wiki_link) {
string_box.worth = sudoku_object.getString();
sudoku_wiki_link.href = `https://www.sudokuwiki.org/SudokuBoard.htm?bd=` + sudoku_object.getString();
}
static clear_board(sudoku_squares, change_square_color = true) {
for ( let row = 0; row < 9; row++ ) {
for ( let col = 0; col < 9; col++ ) {
sudoku_squares[row][col].worth = ``;
if ( change_square_color ) {
sudoku_squares[row][col].classList.take away(`imported-square`);
}
}
}
}
static highlight_illegal_move(obj){
obj.classList.add(`invalid`);
setTimeout(operate(){
obj.classList.take away(`invalid`);
}, 2000);
}
}
class Helper {
static createArray(size) {
var arr = new Array(size || 0), i = size;
if (arguments.size > 1) {
var args = Array.prototype.slice.name(arguments, 1);
whereas ( i-- ) {
arr[length-1 - i] = Helper.createArray.apply(this, args);
}
}
return arr;
}
}
// Listeners
window.addEventListener(`DOMContentLoaded`, (e) => {
// DOM components saved as constants
const sudoku_table = doc.getElementById(`sudoku`);
const restart_button = doc.getElementById(`restart`);
const import_button = doc.getElementById(`import`);
const new_button = doc.getElementById(`new`);
const string_box = doc.getElementById(`string-box`);
const puzzle_picker = doc.getElementById(`puzzle_picker`);
const sudoku_wiki_link = doc.getElementById(`sudoku-wiki-link`);
const algorithm = doc.getElementById(`algorithm`);
const validate_button = doc.getElementById(`validate`);
const consoleBox = doc.getElementById(`console`);
const game1 = new SudokuBoard();
const sudoku_squares = Helper.createArray(9,9);
const CUSTOM_PUZZLE_SELECTEDINDEX = 3;
const DEFAULT_PUZZLE_SELECTEDINDEX = 4;
// Retailer all of the Sudoku sq. <enter kind=`textual content`> components in variables for fast accessing
for ( let row = 0; row < 9; row++ ) {
for ( let col = 0; col < 9; col++ ) {
sudoku_squares[row][col] = sudoku_table.rows[row].cells[col].kids[0];
}
}
for ( let row = 0; row < 9; row++ ) {
for ( let col = 0; col < 9; col++ ) {
sudoku_squares[row][col].addEventListener(`enter`, operate(e) {
e.goal.classList.take away(`invalid`);
e.goal.classList.take away(`trace`);
// Pay attention for unlawful strikes. If unlawful, delete enter and switch sq. pink for two seconds.
if ( ! game1.is_legal_move(row, col, e.goal.worth) && e.goal.worth != `` ) {
e.goal.worth = ``;
SudokuDOM.highlight_illegal_move(e.goal);
} else {
game1.make_move(row, col, e.goal.worth);
}
SudokuDOM.display_string(game1, string_box, sudoku_wiki_link);
});
}
}
validate_button.addEventListener(`click on`, operate(e) {
const t1 = efficiency.now();
const recursionTracker = game1.getNumberOfSolutions();
const t2 = efficiency.now();
// TODO: show recursionTracker stuff like # of options, strings of the options, and so forth.
doc.querySelector(`#algorithm span`).innerHTML = (t2 - t1).toFixed(1);
algorithm.type.show = `block`;
consoleBox.kids[0].innerHTML = recursionTracker.getInfoString();
consoleBox.type.show = `block`;
});
restart_button.addEventListener(`click on`, operate(e) {
game1.restart_puzzle();
SudokuDOM.display_board(game1, sudoku_squares, string_box, sudoku_wiki_link);
});
import_button.addEventListener(`click on`, operate(e) {
const board = window.immediate(`Please enter a sequence of 81 numbers, with Zero representing an empty sq..`);
const board_changed = game1.set_board(board);
if ( board_changed ) {
puzzle_picker.selectedIndex = CUSTOM_PUZZLE_SELECTEDINDEX;
SudokuDOM.display_board(game1, sudoku_squares, string_box, sudoku_wiki_link);
}
});
puzzle_picker.addEventListener(`change`, operate(e) {
if ( puzzle_picker.worth === `import` ) {
import_button.click on();
} else if ( puzzle_picker.worth === `random` ) {
new_button.click on();
} else {
game1.set_board(puzzle_picker.worth);
SudokuDOM.display_board(game1, sudoku_squares, string_box, sudoku_wiki_link);
}
});
// Decide the default puzzle. Set off the <choose>.change listener so the puzzle will get loaded.
// selectedIndex begins from 0
puzzle_picker.selectedIndex = DEFAULT_PUZZLE_SELECTEDINDEX;
puzzle_picker.dispatchEvent(new Occasion(`change`));
});
HTML
<!DOCTYPE html>
<html lang="en-us">
<head>
<title>Sudoku</title>
</head>
<physique>
<p>
<button id="import" class="performed">Import</button>
<button id="restart" class="performed">Restart</button>
<button id="validate" class="todo">Resolve Recursively</button>
</p>
<p>
<choose id="puzzle_picker">
<choice worth="000000000000000000000000000000000000000000000000000000000000000000000000000000000">[Blank Board]</choice>
<choice worth="import">[Import Puzzle]</choice>
<choice worth="customized">[Custom Puzzle]</choice>
<choice worth="123056789467000000580000000600000000700000000800000000000000000200000000300000000">Testing - Course of Of Elimination</choice>
<choice worth="080165427145372968726984135871296354964531782532847691213759846497628513658413279">Testing - Answer Rely 1</choice>
<choice worth="380160407140370968726980135870296354964501782532847601213059846497028513658403279">Testing - Answer Rely 2</choice>
<!-- from https://www.sudokuwiki.org/ -->
<choice worth="080100007000070960026900130000290304960000082502047000013009840097020000600003070">Newbie</choice>
<choice worth="240070038000006070300040600008020700100000006007030400004080009860400000910060002">Intermediate - Final Quantity In Row, Col, & Field</choice>
<choice worth="246070038000306074370040600008020700100000006007030400004080069860400007910060042">Intermediate - Bare Single</choice>
</choose>
</p>
<desk id="sudoku">
<tbody>
<tr>
<td><enter kind="textual content" maxlength="1" /></td>
<td><enter kind="textual content" maxlength="1" /></td>
<td class="thick-right"><enter kind="textual content" maxlength="1" /></td>
<td><enter kind="textual content" maxlength="1" /></td>
<td><enter kind="textual content" maxlength="1" /></td>
<td class="thick-right"><enter kind="textual content" maxlength="1" /></td>
<td><enter kind="textual content" maxlength="1" /></td>
<td><enter kind="textual content" maxlength="1" /></td>
<td><enter kind="textual content" maxlength="1" /></td>
</tr>
<tr>
<td><enter kind="textual content" maxlength="1" /></td>
<td><enter kind="textual content" maxlength="1" /></td>
<td class="thick-right"><enter kind="textual content" maxlength="1" /></td>
<td><enter kind="textual content" maxlength="1" /></td>
<td><enter kind="textual content" maxlength="1" /></td>
<td class="thick-right"><enter kind="textual content" maxlength="1" /></td>
<td><enter kind="textual content" maxlength="1" /></td>
<td><enter kind="textual content" maxlength="1" /></td>
<td><enter kind="textual content" maxlength="1" /></td>
</tr>
<tr class="thick-bottom">
<td><enter kind="textual content" maxlength="1" /></td>
<td><enter kind="textual content" maxlength="1" /></td>
<td class="thick-right"><enter kind="textual content" maxlength="1" /></td>
<td><enter kind="textual content" maxlength="1" /></td>
<td><enter kind="textual content" maxlength="1" /></td>
<td class="thick-right"><enter kind="textual content" maxlength="1" /></td>
<td><enter kind="textual content" maxlength="1" /></td>
<td><enter kind="textual content" maxlength="1" /></td>
<td><enter kind="textual content" maxlength="1" /></td>
</tr>
<tr>
<td><enter kind="textual content" maxlength="1" /></td>
<td><enter kind="textual content" maxlength="1" /></td>
<td class="thick-right"><enter kind="textual content" maxlength="1" /></td>
<td><enter kind="textual content" maxlength="1" /></td>
<td><enter kind="textual content" maxlength="1" /></td>
<td class="thick-right"><enter kind="textual content" maxlength="1" /></td>
<td><enter kind="textual content" maxlength="1" /></td>
<td><enter kind="textual content" maxlength="1" /></td>
<td><enter kind="textual content" maxlength="1" /></td>
</tr>
<tr>
<td><enter kind="textual content" maxlength="1" /></td>
<td><enter kind="textual content" maxlength="1" /></td>
<td class="thick-right"><enter kind="textual content" maxlength="1" /></td>
<td><enter kind="textual content" maxlength="1" /></td>
<td><enter kind="textual content" maxlength="1" /></td>
<td class="thick-right"><enter kind="textual content" maxlength="1" /></td>
<td><enter kind="textual content" maxlength="1" /></td>
<td><enter kind="textual content" maxlength="1" /></td>
<td><enter kind="textual content" maxlength="1" /></td>
</tr>
<tr class="thick-bottom">
<td><enter kind="textual content" maxlength="1" /></td>
<td><enter kind="textual content" maxlength="1" /></td>
<td class="thick-right"><enter kind="textual content" maxlength="1" /></td>
<td><enter kind="textual content" maxlength="1" /></td>
<td><enter kind="textual content" maxlength="1" /></td>
<td class="thick-right"><enter kind="textual content" maxlength="1" /></td>
<td><enter kind="textual content" maxlength="1" /></td>
<td><enter kind="textual content" maxlength="1" /></td>
<td><enter kind="textual content" maxlength="1" /></td>
</tr>
<tr>
<td><enter kind="textual content" maxlength="1" /></td>
<td><enter kind="textual content" maxlength="1" /></td>
<td class="thick-right"><enter kind="textual content" maxlength="1" /></td>
<td><enter kind="textual content" maxlength="1" /></td>
<td><enter kind="textual content" maxlength="1" /></td>
<td class="thick-right"><enter kind="textual content" maxlength="1" /></td>
<td><enter kind="textual content" maxlength="1" /></td>
<td><enter kind="textual content" maxlength="1" /></td>
<td><enter kind="textual content" maxlength="1" /></td>
</tr>
<tr>
<td><enter kind="textual content" maxlength="1" /></td>
<td><enter kind="textual content" maxlength="1" /></td>
<td class="thick-right"><enter kind="textual content" maxlength="1" /></td>
<td><enter kind="textual content" maxlength="1" /></td>
<td><enter kind="textual content" maxlength="1" /></td>
<td class="thick-right"><enter kind="textual content" maxlength="1" /></td>
<td><enter kind="textual content" maxlength="1" /></td>
<td><enter kind="textual content" maxlength="1" /></td>
<td><enter kind="textual content" maxlength="1" /></td>
</tr>
<tr>
<td><enter kind="textual content" maxlength="1" /></td>
<td><enter kind="textual content" maxlength="1" /></td>
<td class="thick-right"><enter kind="textual content" maxlength="1" /></td>
<td><enter kind="textual content" maxlength="1" /></td>
<td><enter kind="textual content" maxlength="1" /></td>
<td class="thick-right"><enter kind="textual content" maxlength="1" /></td>
<td><enter kind="textual content" maxlength="1" /></td>
<td><enter kind="textual content" maxlength="1" /></td>
<td><enter kind="textual content" maxlength="1" /></td>
</tr>
</tbody>
</desk>
<p>
<enter id="string-box" kind="textual content" />
</p>
<p>
Or clear up this utilizing <a id="sudoku-wiki-link">Sudoku Wiki Solver</a>
</p>
<p id="algorithm">
<span></span> ms
</p>
<p id="console">
<textarea></textarea>
</p>
CSS
physique {font-family:sans-serif; background-color:#1E1E1E; colour:white;}
p {margin-block-start:0; margin-block-end:0.5em;}
a {colour:yellow;}
a:hover {colour:orange;}
a:visited {colour:yellow;}
nav {float:left; width:250px; peak:100vh; background-color:#383838; padding:1em;}
article {float:left; padding:1em;}
.performed {background-color:limegreen;}
.in-progress {background-color:yellow;}
.todo {background-color:pink;}
#string-box {width:610px;}
#algorithm {show:none;}
#console {show:none;}
#console textarea {width:85ch; peak:8em;}
.invalid {background-color:pink;}
.imported-square {background-color:lightgray;}
.trace {background-color:limegreen;}
#sudoku {border:4px strong black; border-collapse: collapse; margin-bottom:0.5em;}
#sudoku tr {padding:0;}
#sudoku td {padding:0; border:2px strong black; width:35px; peak:35px;}
#sudoku enter {width:35px; peak:35px; border:0; font-size:25pt; text-align:heart; padding:0; colour:black;}
#sudoku .thick-right {border-right:4px strong black;}
#sudoku .thick-bottom {border-bottom:4px strong black;}