# How to Create a Sudoku Puzzle game with Html

When We first started playing with writing a Sudoku game, I took the long way to create Sudoku templates. Using a recursive backtracking algorithm, I would slowly fill up the grid with random numbers â€“ checking the validity of the puzzle at each step. If I hit a roadblock where the puzzle cannot be completed, the algorithm would backtrack until it could move forward, and then move forward again. how to make a sudoku game in javascript,
how to make a sudoku puzzle in html

There is a more elegant solution however: create a solved sudoku and then shuffle it.

## Generate and Solved Sudoku

1.Step : Creating the solved Sudoku is easy: just shift the row above to the left by 3 unless its vertical index is equally divisible by 3 (starting with index 0) in which case shift the row above by 4.

1 2 3 4 5 6 7 8 9
4 5 6 7 8 9 1 2 3
7 8 9 1 2 3 4 5 6
2 3 4 5 6 7 8 9 1
5 6 7 8 9 1 2 3 4
8 9 1 2 3 4 5 6 7
3 4 5 6 7 8 9 1 2
6 7 8 9 1 2 3 4 5
9 1 2 3 4 5 6 7 8

2.Step : Shuffle the rows and columns. Of course, to do so and ensure the Sudoku rules maintain integrity, you can only shuffle rows and columns of the same group: groups being 1-3, 4-6, and 7-9. Demonstrated below, the columns 1 and 3 are shuffled

## index.html

``````<!DOCTYPE html>
<html lang="en" >
<meta charset="UTF-8">
<title>DheerajHitech - Sudoku Puzzle Project</title>
<meta name="viewport" content="width=device-width, initial-scale=1"><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/5.0.0/normalize.min.css">

<body>

<div id='sudoku-app'></div>

<script type='text/javascript'>
"use strict";!function(a){if("function"==typeof bootstrap)bootstrap("bem",a);else if("object"==typeof exports&&"object"==typeof module)module.exports=a();else if("function"==typeof define&&define.amd)define(a);else if("undefined"!=typeof ses){if(!ses.ok())return;ses.makeBem=a}else{if("undefined"==typeof window&&"undefined"==typeof self)throw new Error("This environment was not anticipated by bem. Please file a bug.");var b="undefined"!=typeof window?window:self,c=b.bem;b.bem=a(),b.bem.noConflict=function(){return b.bem=c,this}}}(function(){function a(a){"undefined"!=typeof a.modifier&&(c.modifier=a.modifier),"undefined"!=typeof a.element&&(c.element=a.element)}function b(a){if(!d.validate(a))return null;var b=a.block,e=a.element,f=a.modifiers,g=b,h=[];return!!e&&(g+=""+c.element+e),!!f&&Object.keys(f).forEach(function(a){var d=f[a],i="function"==typeof d?d(b,e,f):d;!!i&&h.push(""+g+c.modifier+a+" ")}),(g+" "+h.join("")).slice(0,-1)}var c={element:"__",modifier:"--"},d={messages:{block:"You must specify the name of block.",element:"Element name must be a string.",modifier:"Modifiers must be supplied in the `{name : bool || fn}` style."},blockName:function(a){return"undefined"!=typeof a&&"string"==typeof a&&a.length?!0:(console.warn(this.messages.block),!1)},element:function(a){return"undefined"!=typeof a&&"string"!=typeof a?(console.warn(this.messages.element),!1):!0},modifiers:function(a){return"undefined"==typeof a||"object"==typeof a&&"[object Object]"===toString.call(a)?!0:(console.warn(this.messages.modifier),!1)},validate:function(a){return this.blockName(a.block)&&this.element(a.element)&&this.modifiers(a.modifiers)}};return{setDelimiters:a,makeClassName:b}});
</script>

<!-- Include Babel to transform code in browser -->
<script type='text/javascript'
src='https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.25/browser-polyfill.min.js'>
</script>
<script type='text/javascript'
src='https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.25/browser.min.js'>
</script>

<!-- worker, sudoku api -->
<script type='text/babel' id='worker'>

self.sudoku = null

// Worker Setup
var options = { method: null }
try {
options = JSON.parse(event.data);
} catch (e) {
console.warn('event.data is misformed', event)
}

switch (options.method) {

case 'generate':
var { hints, limit } = options
self.sudoku = new Sudoku(hints, limit).generate()

self.postMessage({
success: self.sudoku.success,
board: self.sudoku.getBoard(),
solution: self.sudoku.getSolution()
});
break;

case 'validate':
var { map, number, index } = options
self.postMessage({
result: sudoku.validate(map, number, index)
});
break;

}
}, false);

// API
class Sudoku {
constructor(hints, limit) {
this.hints = hints
this.limit = limit || 10000

this._logs = {
raw: [],
incidents: {
limitExceeded: 0,
notValid: 0,
noNumbers: 0
}
}

this.success = null

this.numbers = () =>
new Array(9)
.join(" ")
.split(" ")
.map((num , i) => i + 1)

/*
Will be used in initial map. Each row will be
consisted of randomly ordered numbers
*/
this.randomRow = () => {
var row = []
var numbers = this.numbers()
while (row.length < 9) {
var index = Math.floor(Math.random() * numbers.length)
row.push(numbers[index])
numbers.splice(index, 1)
}

return row
}

/*
This is the dummy placeholder for the
final results. Will be overridden through the
backtracking process, and at the and, this will
be the real results.
*/
this.result = new Array(9 * 9)
.join(" ")
.split(" ")
.map(entry => null)

/*
Will be used as the nodeTree in the
process of backtracking. Each cell has 9 alternative
paths (randomly ordered).
*/
this.map = new Array(9 * 9)
.join(" ")
.split(" ")
.map(path => this.randomRow())

/*
Will be used as history in the backtracking
process for checking if a candidate number is valid.
*/
this.stack = []

return this
}

toRows(arr) {
var row = 0
var asRows = new Array(9)
.join(" ")
.split(" ")
.map(row => [])

for (let [index, entry] of arr.entries()) {
asRows[row].push(entry)

if ( !((index + 1) % 9) ) {
row += 1
}
}

return asRows
}

no(path, index, msg) {
var number = path[path.length - 1]
this._logs.raw.push(`no: @\${index} [\${number}] \${msg} \${path} `)
}

yes(path, index) {
this._logs.raw.push(`yes: \${index} \${path}`)
}

finalLog() {
console.groupCollapsed('Raw Logs')
console.groupCollapsed(this._logs.raw)
console.groupEnd()
console.groupEnd()
console.groupCollapsed('Incidents')
console.groupCollapsed(this._logs.incidents)
console.groupEnd()
console.groupEnd()
}

getBoard() {
return this.toRows(this.substractCells())
}

getSolution() {
return this.toRows(this.result)
}

substractCells() {
var _getNonEmptyIndex = () => {
var index = Math.floor(Math.random() * _result.length)
return _result[index] ? index : _getNonEmptyIndex()
}

var _result = this.result.filter(() => true)

while (
_result.length - this.hints >
_result.filter(n => !n).length
) {
_result[_getNonEmptyIndex()] = ''
}

return _result
}

validate(map, number, index) {
var rowIndex = Math.floor(index / 9)
var colIndex = index % 9

var row = map.slice(
rowIndex * 9, 9 * (rowIndex + 1)
)

var col = map.filter((e, i) =>
i % 9 === colIndex
)

var boxRow = Math.floor(rowIndex / 3)
var boxCol = Math.floor(colIndex / 3)

var box = map.filter((e, i) =>
Math.floor(Math.floor(i / 9) / 3) === boxRow &&
Math.floor((i % 9) / 3) === boxCol
)

return {
row: {
first: row.indexOf(number),
last: row.lastIndexOf(number)
},
col: {
first: col.indexOf(number),
last: col.lastIndexOf(number)
},
box: {
first: box.indexOf(number),
last: box.lastIndexOf(number)
}
}
}

_validate(map, index) {
if (!map[index].length) {
return false
}

this.stack.splice(index, this.stack.length)

var path = map[index]
var number = path[path.length - 1]

var didFoundNumber = this.validate(this.stack, number, index)

return (
didFoundNumber.col.first === -1 &&
didFoundNumber.row.first === -1 &&
didFoundNumber.box.first === -1
)
}

_generate(map, index) {
if (index === 9 * 9) {
return true
}

if (--this.limit < 0) {
this._logs.incidents.limitExceeded++
this.no(map[index], index, 'limit exceeded')
return false
}

var path = map[index]

if (!path.length) {
map[index] = this.numbers()
map[index - 1].pop()
this._logs.incidents.noNumbers++
this.no(path, index, 'no numbers in it')
return false
}

var currentNumber = path[path.length - 1]

var isValid = this._validate(map, index)
if (!isValid) {
map[index].pop()
map[index + 1] = this.numbers()
this._logs.incidents.notValid++
this.no(path, index, 'is not valid')
return false
} else {
this.stack.push(currentNumber)
}

for (let number of path.entries()) {
if (this._generate(map, index + 1)) {
this.result[index] = currentNumber
this.yes(path, index)
return true
}
}

return false
}

generate() {
if (this._generate(this.map, 0)) {
this.success = true
}

this.finalLog()

return this
}

}
</script>
<!-- partial -->
<script src='https://cdn.rawgit.com/MaxArt2501/object-observe/master/dist/object-observe-lite.min.js'></script><script  src="./script.js"></script>

</body>
</html>
``````

## style.css

``````\**********************************/
@font-face {
src: url("http://enes.in/GillSansTr-LightNr.otf");
font-family: Gill;
font-weight: 100;
}
@font-face {
src: url("http://enes.in/GillSansTr-Normal.otf");
font-family: Gill;
font-weight: 300;
}
@font-face {
src: url("http://enes.in/GillSansTr-Bold.otf");
font-family: Gill;
font-weight: 600;
}
@font-face {
src: url("http://enes.in/GillSansTr-ExtraBold.otf");
font-family: Gill;
font-weight: 700;
}
@font-face {
src: url("http://enes.in/GillSansTr-UltraBold.otf");
font-family: Gill;
font-weight: 900;
}
html, body {
width: 100%;
height: 100%;
}

body {
margin: 0;
background: #f0f0f0;
}

@media (max-width: 260px) {
.show-on-sm {
display: none;
}

.show-on-md {
display: none;
}

.show-on-lg {
display: none;
}

.show-on-xs {
display: block;
}
}
@media (max-width: 420px) {
.show-on-xs {
display: none;
}

.show-on-md {
display: none;
}

.show-on-lg {
display: none;
}

.show-on-sm {
display: block;
}
}
@media (min-width: 421px) and (max-width: 615px) {
.show-on-xs {
display: none;
}

.show-on-sm {
display: none;
}

.show-on-lg {
display: none;
}

.show-on-md {
display: block;
}
}
@media (min-width: 615px) {
.show-on-xs {
display: none;
}

.show-on-sm {
display: none;
}

.show-on-md {
display: none;
}

.show-on-lg {
display: block;
}
}
@-webkit-keyframes progress {
0% {
}
25% {
}
50% {
box-shadow: 2px -2px 0 1px, 7px -2px 0 1px;
}
100% {
box-shadow: 2px -2px 0 1px, 7px -2px 0 1px, 12px -2px 0 1px;
}
}
@keyframes progress {
0% {
}
25% {
}
50% {
box-shadow: 2px -2px 0 1px, 7px -2px 0 1px;
}
100% {
box-shadow: 2px -2px 0 1px, 7px -2px 0 1px, 12px -2px 0 1px;
}
}
.fr {
float: right;
}

.fl {
float: left;
}

@media (max-width: 260px) {
.button {
font-size: 0.6em;
}
.button:not(:last-of-type) {
margin-right: 0.15em;
}
}
}
@media (min-width: 261px) and (max-width: 420px) {
.button {
font-size: 0.75em;
}
.button:not(:last-of-type) {
margin-right: 0.25em;
}
}
}
@media (min-width: 421px) and (max-width: 615px) {
.button {
font-size: 0.9em;
}
.button:not(:last-of-type) {
margin-right: 0.5em;
}
}
}
@media (min-width: 615px) {
.button {
font-size: 1em;
}
.button:not(:last-of-type) {
margin-right: 0.75em;
}
}
}
.button {
border: 1px solid;
font-weight: normal;
background: none;
-webkit-transition: all 0.2s;
transition: all 0.2s;
}
.button--primary {
color: #4242d7;
font-weight: 600;
}
.button--primary:hover, .button--primary:focus, .button--primary:active {
border-color: #4242d7;
background: #4242d7;
}
.button--primary:focus {
}
.button--secondary {
color: #d74242;
}
.button--secondary:hover, .button--secondary:focus, .button--secondary:active {
border-color: #d74242;
background: #d74242;
}
.button--secondary:focus {
}
.button--tertiary {
color: #fff;
border-color: #2ECC40;
background: #2ECC40;
}
.button--neutral {
color: #333;
}
.button--neutral:hover, .button--neutral:focus, .button--neutral:active {
border-color: #333;
background: #333;
}
.button--neutral:focus {
}
.button--compound {
border-right: none;
}
.button--compound-first {
}
.button--compound-last {
border-right: 1px solid;
}
.button--muted {
pointer-events: none;
}
.button--disabled {
border-color: #bbb;
color: #bbb;
pointer-events: none;
}
display: inline-block;
width: 1px;
height: 1px;
content: '';
-webkit-animation: progress 1s infinite;
animation: progress 1s infinite;
}
.button:hover, .button:focus, .button:active {
color: #fff;
}
.button:focus {
outline: none;
}
.button:active {
box-shadow: inset 0 -2px 10px rgba(0, 0, 0, 0.4);
}

.message {
font-size: .9em;
margin: 0;
color: rgba(0, 0, 0, 0.75);
}
.message--busy {
background: rgba(0, 0, 255, 0.1);
}
.message--fail {
background: rgba(255, 0, 0, 0.1);
}

@media (max-width: 260px) {
.sudoku {
margin: 0 auto;
}
}
.sudoku__title {
font-size: 1em;
}
.sudoku__table {
font-size: 0.9em;
border-top: 2px solid #444;
border-left: 2px solid #444;
border-collapse: collapse;
}
.sudoku__table-row {
border-bottom: 1px solid #444;
border-right: 2px solid #444;
}
.sudoku__table-row--separator {
border-bottom: 2px solid #444;
}
.sudoku__table-cell {
width: 16px;
height: 16px;
border-right: 1px solid #444;
}
.sudoku__table-cell--separator {
border-right: 2px solid #444;
}

.sudoku {
max-width: calc(260px / 1.5);
min-width: calc(260px / 2);
}
}
@media (min-width: 261px) and (max-width: 420px) {
.sudoku {
margin: 0 auto;
}
}
.sudoku__title {
font-size: 1.2em;
}
.sudoku__table {
font-size: 1.2em;
border-top: 3px solid #444;
border-left: 3px solid #444;
border-collapse: collapse;
}
.sudoku__table-row {
border-bottom: 1px solid #444;
border-right: 3px solid #444;
}
.sudoku__table-row--separator {
border-bottom: 3px solid #444;
}
.sudoku__table-cell {
width: 32px;
height: 32px;
border-right: 1px solid #444;
}
.sudoku__table-cell--separator {
border-right: 3px solid #444;
}

.sudoku {
width: 260px;
}
}
@media (min-width: 421px) and (max-width: 615px) {
.sudoku {
margin: 0 auto;
}
}
.sudoku__title {
font-size: 1.5em;
}
.sudoku__table {
font-size: 1.5em;
border-top: 4px solid #444;
border-left: 4px solid #444;
border-collapse: collapse;
}
.sudoku__table-row {
border-bottom: 1px solid #444;
border-right: 4px solid #444;
}
.sudoku__table-row--separator {
border-bottom: 4px solid #444;
}
.sudoku__table-cell {
width: 48px;
height: 48px;
border-right: 1px solid #444;
}
.sudoku__table-cell--separator {
border-right: 4px solid #444;
}

.sudoku {
width: 420px;
}
}
@media (min-width: 615px) {
.sudoku {
margin: 0 auto;
}
}
.sudoku__title {
font-size: 2em;
}
.sudoku__table {
font-size: 1.75em;
border-top: 6px solid #444;
border-left: 6px solid #444;
border-collapse: collapse;
}
.sudoku__table-row {
border-bottom: 2px solid #444;
border-right: 6px solid #444;
}
.sudoku__table-row--separator {
border-bottom: 6px solid #444;
}
.sudoku__table-cell {
width: 64px;
height: 64px;
border-right: 2px solid #444;
}
.sudoku__table-cell--separator {
border-right: 6px solid #444;
}

.sudoku {
width: 615px;
}
}
.sudoku {
color: #444;
}
font-family: Gill, sans-serif;
}
.sudoku__title {
font-weight: 600;
}
.sudoku__description {
max-width: 640px;
line-height: 1.4;
font-weight: 100;
}
.sudoku__table {
background: #fff;
}
.sudoku__table-cell {
overflow: hidden;
text-align: center;
-webkit-transition: all .25s;
transition: all .25s;
}
.sudoku__table-cell--editable {
color: #2020df;
}
.sudoku__table-cell--editable:focus {
background: rgba(0, 0, 255, 0.1);
outline: none;
}
.sudoku__table-cell--error {
color: red;
background: #fdd;
}
.sudoku__table-cell--editable-error {
}
.sudoku__table-cell--editable-error:focus {
color: #eee;
background: #f45;
}``````

## script.js

``````// Utility
var utils = (() => {
function dom(selector) {
if (selector[0] === '#') {
return document.getElementById(selector.slice(1));
}
return document.querySelectorAll(selector);
}

function copyJSON(obj) {
return JSON.parse(JSON.stringify(obj));
}

function isTouchDevice() {
return navigator.userAgent.
}

function getWorkerURLFromElement(selector) {
var element = dom(selector);
var content = babel.transform(element.innerText).code;
var blob = new Blob([content], { type: 'text/javascript' });
return URL.createObjectURL(blob);
}

var cursorManager = function () {
var cursorManager = {};

var voidNodeTags = [
'AREA', 'BASE', 'BR', 'COL', 'EMBED',
'TRACK', 'WBR', 'BASEFONT', 'BGSOUND',
'FRAME', 'ISINDEX'];

Array.prototype.contains = function (obj) {
var i = this.length;
while (i--) {
if (this[i] === obj) {
return true;
}
}
return false;
};

function canContainText(node) {
if (node.nodeType == 1) {
return !voidNodeTags.contains(node.nodeName);
} else {
return false;
}
};

function getLastChildElement(el) {
var lc = el.lastChild;
while (lc && lc.nodeType != 1) {
if (lc.previousSibling)
lc = lc.previousSibling;else

break;
}
return lc;
}
cursorManager.setEndOfContenteditable = function (contentEditableElement) {

while (getLastChildElement(contentEditableElement) &&
canContainText(getLastChildElement(contentEditableElement))) {
contentEditableElement = getLastChildElement(contentEditableElement);
}

var range, selection;
if (document.createRange) {
range = document.createRange();
range.selectNodeContents(contentEditableElement);
range.collapse(false);
selection = window.getSelection();
selection.removeAllRanges();
} else
if (document.selection)
{
range = document.body.createTextRange();
range.moveToElementText(contentEditableElement);
range.collapse(false);
range.select();
}
};

return cursorManager;
}();

return {
copyJSON, cursorManager, dom,
getWorkerURLFromElement, isTouchDevice };

})();

constructor(url) {
this.worker = new Worker(url);
return this;
}

_postMessage(options) {
this.worker.postMessage(JSON.stringify(options));
return new Promise((resolve, reject) => {
this.worker.onmessage = event => {
resolve(event.data);
};
});
}

generate(options) {
options = Object.assign(
{}, options, { method: 'generate' });

return this._postMessage(options);
}

validate(options) {
options = Object.assign(
{}, options, { method: 'validate' });

return this._postMessage(options);
}}

// Client Side Settings
const SUDOKU_APP_CONFIG = {
HINTS: 34,
TRY_LIMIT: 100000,
WORKER_URL: utils.getWorkerURLFromElement('#worker'),
DOM_TARGET: utils.dom('#sudoku-app') };

// Client Side
var SudokuApp = (config => {
const {
HINTS, TRY_LIMIT,
WORKER_URL, DOM_TARGET } =
config;

var state = {
success: null,
board: null,
solution: null,
solved: null,
errors: [] };

Object.observe(state, render);

var history = [state];
var historyStash = [];

// Event listeners
var onClickGenerate = initialize;

var onClickSolve = function () {
setState({
board: state.solution,
solved: true,
errors: [] });

};

var onKeyUpCell = function (event) {
var key = event.keyCode;
if ( // a
key === 36 || // r
key === 37 || // r
key === 38 || // o
key === 39 || // w
key === 9 || // tab
// mod key flags are always false in keyup event
// keyIdentifier doesn't seem to be implemented
// in all browsers
key === 17 || // Control
key === 16 || // Shift
key === 91 || // Meta
key === 19 || // Alt
event.keyIdentifier === 'Control' ||
event.keyIdentifier === 'Shift' ||
event.keyIdentifier === 'Meta' ||
event.keyIdentifier === 'Alt')
return;

var cell = event.target;
var value = cell.innerText;

if (value.length > 4) {
cell.innerText = value.slice(0, 4);
return false;
}

var cellIndex = cell.getAttribute('data-cell-index');
cellIndex = parseInt(cellIndex, 10);
var rowIndex = Math.floor(cellIndex / 9);
var cellIndexInRow = cellIndex - rowIndex * 9;

var board = Object.assign([], state.board);
board[rowIndex].splice(cellIndexInRow, 1, value);

validate(board).then(errors => {
historyStash = [];
history.push({});
var solved = null;
if (errors.indexOf(true) === -1) {
solved = true;
board.forEach(row => {
row.forEach(value => {
if (!value || !parseInt(value, 10) || value.length > 1) {
solved = false;
}
});
});
}
if (solved) {
board = Object.assign([], board).map(row => row.map(n => +n));
}
setState({ board, errors, solved }, newState => {
history[history.length - 1] = newState;
restoreCaretPosition(cellIndex);
});
});
};

function keyDown(event) {
var keys = {
ctrlOrCmd: event.ctrlKey || event.metaKey,
shift: event.shiftKey,
z: event.keyCode === 90 };

if (keys.ctrlOrCmd && keys.z) {
if (keys.shift && historyStash.length) {
redo();
} else if (!keys.shift && history.length > 1) {
undo();
}
}
}

function undo() {
historyStash.push(history.pop());
setState(utils.copyJSON(history[history.length - 1]));
}

function redo() {
history.push(historyStash.pop());
setState(utils.copyJSON(history[history.length - 1]));
}

function initialize() {
unbindEvents();
render();
getSudoku().then(sudoku => {
setState({
success: sudoku.success,
board: sudoku.board,
solution: sudoku.solution,
errors: [],
solved: false },
newState => {
history = [newState];
historyStash = [];
});
});
}

function setState(newState, callback) {
requestAnimationFrame(() => {
Object.assign(state, newState);
if (typeof callback === 'function') {
var param = utils.copyJSON(state);
requestAnimationFrame(callback.bind(null, param));
}
});
}

function bindEvents() {
var generateButton = utils.dom('#generate-button');
var solveButton = utils.dom('#solve-button');
var undoButton = utils.dom('#undo-button');
var redoButton = utils.dom('#redo-button');
generateButton &&
generateButton.
solveButton &&
solveButton.
undoButton &&
undoButton.
redoButton &&
redoButton.

var cells = utils.dom('.sudoku__table-cell');
[].forEach.call(cells, cell => {
});

}

function unbindEvents() {
var generateButton = utils.dom('#generate-button');
var solveButton = utils.dom('#solve-button');
var undoButton = utils.dom('#undo-button');
var redoButton = utils.dom('#redo-button');
generateButton &&
generateButton.
removeEventListener('click', onClickGenerate);
solveButton &&
solveButton.
removeEventListener('click', onClickSolve);
undoButton &&
undoButton.
removeEventListener('click', undo);
redoButton &&
redoButton.
removeEventListener('click', redo);

var cells = utils.dom('.sudoku__table-cell');
[].forEach.call(cells, cell => {
cell.removeEventListener('keyup', onKeyUpCell);
});

window.removeEventListener('keydown', keyDown);
}

function restoreCaretPosition(cellIndex) {
utils.cursorManager.setEndOfContenteditable(
utils.dom(`[data-cell-index="\${cellIndex}"]`)[0]);

}

function getSudoku() {
hints: HINTS,
limit: TRY_LIMIT });

}

function validate(board) {
var map = board.reduce((memo, row) => {
for (let num of row) {
memo.push(num);
}
return memo;
}, []).map(num => parseInt(num, 10));

var validations = [];

// Will validate one by one
for (let [index, number] of map.entries()) {
if (!number) {
validations.push(
new Promise(res => {
res({ result: { box: -1, col: -1, row: -1 } });
}));

} else {
let all = Promise.all(validations);
validations.push(all.then(() => {
return sudokuAdapter.validate({ map, number, index });
}));
}
}

return Promise.all(validations).
then(values => {
var errors = [];
for (let [index, validation] of values.entries()) {
let { box, col, row } = validation.result;
let errorInBox = box.first !== box.last;
let errorInCol = col.first !== col.last;
let errorInRow = row.first !== row.last;

let indexOfRow = Math.floor(index / 9);
let indexInRow = index - indexOfRow * 9;

errors[index] = errorInRow || errorInCol || errorInBox;
}

return errors;
});
}

function render() {
unbindEvents();

DOM_TARGET.innerHTML = `
<div class='sudoku'>
\${contentComponent()}
</div>
`;

bindEvents();
}

function buttonComponent(props) {
var { id, text, mods, classes } = props;

var blockName = 'button';
var modifiers = {};
var modType = toString.call(mods);
if (modType === '[object String]') {
modifiers[mods] = true;

} else if (modType === '[object Array]') {
for (let modName of mods) {
modifiers[modName] = true;
}
}

var blockClasses = bem.makeClassName({
block: blockName,
modifiers: modifiers });

var buttonTextClass = `\${blockName}-text`;
if (Object.keys(modifiers).length) {
buttonTextClass +=
Object.keys(modifiers).reduce((memo, curr) => {
return memo + ` \${blockName}--\${curr}-text`;
}, '');

}

var lgText = typeof text === 'string' ?
text : text[0];
var mdText = typeof text === 'string' ?
text : text[1];
var smText = typeof text === 'string' ?
text : text[2];

return `
<button
id='\${id}'
class='\${blockClasses} \${classes || ""}'>
<span class='show-on-sm \${buttonTextClass}'>
\${smText}
</span>
<span class='show-on-md \${buttonTextClass}'>
\${mdText}
</span>
<span class='show-on-lg \${buttonTextClass}'>
\${lgText}
</span>
</button>
`;
}

function messageComponent(options) {
var { state, content } = options;

var messageClass = bem.makeClassName({
block: 'message',
modifiers: state ? {
[state]: true } :
{} });

return `
<p class='\${messageClass}'>
\${content}
</p>
`;
}

function descriptionComponent(options) {
var { className, infoLevel } = options;

var technical = `
In this demo,
<a href='https://en.wikipedia.org/wiki/Backtracking'>
backtracking algorithm
</a> is used for <em>making</em>
the sudoku project.`;

var description = `
Difficulty and solvability is
totally random as I randomly left a certain number of hints
from a full-filled board.
`;

if (infoLevel === 'full') {
return `
<p class='\${className || ''}'>
\${technical} \${description}
</p>
`;

} else if (infoLevel === 'mini') {
return `
<p class='\${className || ''}'>
\${description}
</p>
`;
}
}

function restoreScrollPosComponent() {
return `<div style='height: 540px'></div>`;
}

return `

<h1 class='sudoku__title'>

<span class='show-on-sm'>
Sudoku
</span>

<span class='show-on-md'>
Sudoku Puzzle
</span>

<span class='show-on-lg'>
Sudoku Puzzle Project
</span>

</h1>

\${descriptionComponent({
infoLevel: 'mini',
className: 'sudoku__description show-on-md' })
}

\${descriptionComponent({
infoLevel: 'full',
className: 'sudoku__description show-on-lg' })
}

\${
state.success ? `

\${buttonComponent({
id: 'generate-button',
text: ['New Board', 'New Board', 'New'],
mods: 'primary' })
}

\${state.solved ?
buttonComponent({
id: 'solve-button',
text: 'Solved',
mods: ['tertiary', 'muted'] }) :

buttonComponent({
id: 'solve-button',
text: 'Solve',
mods: 'secondary' })

}

` :

`

\${buttonComponent({
id: 'generate-button',
text: ['Generating', '', ''],
}

\${buttonComponent({
id: 'solve-button',
text: 'Solve',
mods: 'disabled' })
}
`

}

\${utils.isTouchDevice() ? `

\${buttonComponent({
id: 'redo-button',
text: ['&raquo;', '&raquo;', '&gt;', '&gt;'],
classes: 'fr',
mods: [
'neutral',
'compound',
'compound-last',
`\${!historyStash.length ?
'disabled' :
''
}`] })

}
\${buttonComponent({
id: 'undo-button',
text: ['&laquo;', '&laquo;', '&lt;', '&lt;'],
classes: 'fr',
mods: [
'neutral',
'compound',
'compound-first',
`\${history.length > 1 ?
'' :
'disabled'
}`] })

}

` : ''}

</div>
`;
}

function contentComponent() {
var _isSeparator = (index) =>
!!index && !((index + 1) % 3);

var fail = resultReady && !state.success;

return `
\${messageComponent({
state: 'busy',
content: `Generating new board...` })
}
\${restoreScrollPosComponent()}
`;
}

if (fail) {
return `
\${messageComponent({
state: 'fail',
content: `Something went wrong with this board, try generating another one.` })
}
\${restoreScrollPosComponent()}
`;
}

var rows = state.board;

return `
<table class='sudoku__table'>

\${rows.map((row, index) => {
let className = bem.makeClassName({
block: 'sudoku',
element: 'table-row',
modifiers: {
separator: _isSeparator(index) } });

return (
`<tr class='\${className}'>

\${row.map((num, _index) => {
let cellIndex = index * 9 + _index;
let separator = _isSeparator(_index);
let editable = typeof num !== 'number';
let error = state.errors[cellIndex];
let className = bem.makeClassName({
block: 'sudoku',
element: 'table-cell',
modifiers: {
separator,
editable,
error,
'editable-error': editable && error } });

return (
`\n\t
<td class='\${className}'
data-cell-index='\${cellIndex}'
\${editable ? 'contenteditable' : ''}>
\${num}
</td>`);

}).join('')}

\n</tr>\n`);

}).join('')}

</table>
`;
}

return { initialize };

})(SUDOKU_APP_CONFIG).initialize();``````