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

<img fetchpriority=

Sudoku Puzzle Game Source Code

index.html

<!DOCTYPE html>
<html lang="en" >
<head>
  <meta charset="UTF-8">
  <title>DeepCrazyWorld - 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">
<link rel="stylesheet" href="./style.css">

</head>
<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
  self.addEventListener('message', (event) => {
    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>

Sudoku puzzle in 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% {
    box-shadow: none;
  }
  25% {
    box-shadow: 2px -2px 0 1px;
  }
  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% {
    box-shadow: none;
  }
  25% {
    box-shadow: 2px -2px 0 1px;
  }
  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 {
    padding: 0.25em 0.5em;
    font-size: 0.6em;
  }
  .button:not(:last-of-type) {
    margin-right: 0.15em;
  }
  .button--loading {
    padding-right: 1.5em;
  }
}
@media (min-width: 261px) and (max-width: 420px) {
  .button {
    padding: 0.25em 0.5em 0.15em;
    font-size: 0.75em;
  }
  .button:not(:last-of-type) {
    margin-right: 0.25em;
  }
  .button--loading {
    padding-right: 1.5em;
  }
}
@media (min-width: 421px) and (max-width: 615px) {
  .button {
    padding: 0.5em 0.75em 0.4em;
    font-size: 0.9em;
  }
  .button:not(:last-of-type) {
    margin-right: 0.5em;
  }
  .button--loading {
    padding-right: 1.5em;
  }
}
@media (min-width: 615px) {
  .button {
    padding: 0.75em 1em 0.6em;
    font-size: 1em;
  }
  .button:not(:last-of-type) {
    margin-right: 0.75em;
  }
  .button--loading {
    padding-right: 1.5em;
  }
}
.button {
  border: 1px solid;
  font-weight: normal;
  border-radius: 3px;
  background: none;
  box-shadow: 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 {
  box-shadow: 0 0 5px #4242d7;
}
.button--secondary {
  color: #d74242;
}
.button--secondary:hover, .button--secondary:focus, .button--secondary:active {
  border-color: #d74242;
  background: #d74242;
}
.button--secondary:focus {
  box-shadow: 0 0 5px #d74242;
}
.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 {
  box-shadow: 0 0 5px #333;
}
.button--compound {
  border-radius: 0;
  border-right: none;
}
.button--compound-first {
  border-bottom-left-radius: 3px;
  border-top-left-radius: 3px;
}
.button--compound-last {
  border-bottom-right-radius: 3px;
  border-top-right-radius: 3px;
  border-right: 1px solid;
}
.button--muted {
  pointer-events: none;
}
.button--disabled {
  border-color: #bbb;
  color: #bbb;
  pointer-events: none;
}
.button--loading-text::after {
  display: inline-block;
  width: 1px;
  height: 1px;
  content: '';
  box-shadow: 2px -2px 1px 0;
  -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;
  padding: 2em;
  margin: 0;
  border-radius: 3px;
  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;
    padding-top: 0.5em;
    padding-bottom: 0.5em;
  }
  .sudoku__header {
    padding-bottom: 0.6em;
  }
  .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;
    padding-top: 1em;
    padding-bottom: 1em;
  }
  .sudoku__header {
    padding-bottom: 0.9em;
  }
  .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;
    padding-top: 2em;
    padding-bottom: 2em;
  }
  .sudoku__header {
    padding-bottom: 1.3em;
  }
  .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;
    padding-top: 3em;
    padding-bottom: 3em;
  }
  .sudoku__header {
    padding-bottom: 1.618em;
  }
  .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;
}
.sudoku__header {
  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 {
  text-shadow: 0 0 15px;
}
.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.
    match(/(iPhone|iPod|iPad|Android|BlackBerry)/);
  }

  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',
    'HR', 'IMG', 'INPUT', 'KEYGEN', 'LINK',
    'MENUITEM', 'META', 'PARAM', 'SOURCE',
    '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();
        selection.addRange(range);
      } else
      if (document.selection)
      {
        range = document.body.createTextRange();
        range.moveToElementText(contentEditableElement);
        range.collapse(false);
        range.select();
      }
    };

    return cursorManager;
  }();

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

})();


// API Adapter
class SudokuAdapter {
  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 sudokuAdapter = new SudokuAdapter(WORKER_URL);

  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.
    addEventListener('click', onClickGenerate);
    solveButton &&
    solveButton.
    addEventListener('click', onClickSolve);
    undoButton &&
    undoButton.
    addEventListener('click', undo);
    redoButton &&
    redoButton.
    addEventListener('click', redo);

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

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

  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() {
    return sudokuAdapter.generate({
      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'>
        ${headerComponent()}
        ${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>`;
  }

  function headerComponent() {
    return `
      <div class='sudoku__header'>

        <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', '', ''],
      mods: ['disabled', 'loading'] })
    }
    
              ${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 resultReady = !!state.board;
    var fail = resultReady && !state.success;

    if (!resultReady) {
      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();

YouTube Video

And this can be continued for as many iterations as one likes, shuffling random rows and columns from a random group, resulting in a pseudo-random puzzle. I am still waiting for the day when I solve a puzzle and the result is the first puzzle above: not sure I would even notice until it is close to being solved.

READ MORE

Don’t forget to share this post!


Deepika

Hey, I'm Deepika, Experienced in Mobile app Development (Flutter, Android and iOS) and professional blogger. Technically sound Post graduated M.Tech in Computer Science and Engineering. I Love to gain every type of knowledge that's why i have done many courses in different fields like engineering and technology. Skilled in Flutter,( Dart ), Java, HTML, CSS, PHP, Python, SQL, C, C++,Firebase,MySQL,SQLite,JavaScript, Networking, Ethical Hacking.

0 Comments

Leave a Reply

Avatar placeholder

Your email address will not be published. Required fields are marked *