[
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2017 Kees Meijer\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# maze-generator\nCreate mazes using the [recursive backtracking algorithm](https://en.wikipedia.org/wiki/Maze_generation_algorithm#Recursive_backtracker).\n\nCheck out the [maze generator demo](https://keesiemeijer.github.io/maze-generator/).\n\n**Note**: There are restrictions for the maze dimensions you can use. You can remove the restrictions in the [globals.js](https://github.com/keesiemeijer/maze-generator/blob/master/src/globals.js) file. Be aware that the larger the maze dimensions, the more memory is consumed. With recursive backtracking the whole maze is stored in memory.\n\n![maze](https://user-images.githubusercontent.com/1436618/106612888-e1d90600-6569-11eb-87cf-2477b2578598.png)\n\n### LICENSE\n\nMIT\n"
  },
  {
    "path": "index.html",
    "content": "<!DOCTYPE html>\n<html>\n\t<head>\n\t\t<meta charset=\"utf-8\"/>\n\t\t<title>Maze generator</title>\n\t\t<style type=\"text/css\">\n\t\t\tbody {\n\t\t\t\tfont-family: -apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Oxygen-Sans,Ubuntu,Cantarell,\"Helvetica Neue\",sans-serif;\n\t\t\t\tmargin: 0 1em 1em;\n\t\t\t}\n\t\t\tp label, .color-picker label {\n\t\t\t\twidth: 10em;\n\t\t\t\tdisplay: inline-block;\n\t\t\t}\n\t\t\tp.desc, #download {\n\t\t\t\tfont-size: smaller;\n\t\t\t}\n\t\t\tp.desc {\n\t\t\t\tborder-top: 1px solid #afafaf;\n\t\t\t\tpadding-top: 1em;\n\n\t\t\t}\n\t\t\t.hide {\n\t\t\t\tdisplay: none;\n\t\t\t}\n\t\t\t.controls {\n\t\t\t\tpadding-bottom: 1em;\n\t\t\t}\n\t\t\tcanvas {\n\t\t\t\t/* if canvas overflows body */\n\t\t\t\tmargin-right: 1em;\n\t\t\t}\n\t\t\t.color-picker {\n\t\t\t\tmargin: 16px 0;\n\t\t\t\tdisplay:flex;\n\t\t\t\tposition: relative;\n\t\t\t}\n\t\t\t.color-sample {\n\t\t\t\twidth: 1em;\n\t\t\t\tborder: 1px solid #000;\n\t\t\t\tmargin: 0 .5em;\n\t\t\t}\n\t\t\t.color-picker {\n\t\t\t\tmargin-left: 5px;\n\t\t\t}\n\t\t\t.palette {\n\t\t\t\tmargin: 1.5em 0 0 10em;\n\t\t\t\twidth: 270px;\n\t\t\t\tbackground: #efefef;\n\t\t\t\tborder: 1px solid #bcbcbc;\n\t\t\t\tborder-radius: 1px;\n\t\t\t\tdisplay: none;\n\t\t\t\tpadding: 1px;\n\t\t\t\tposition: absolute;\n\t\t\t\tz-index: 1000;\n\t\t\t}\n\t\t\t.palette div {\n\t\t\t\twidth: 18px;\n\t\t\t\theight: 18px;\n\t\t\t\tmargin: 3px;\n\t\t\t\tcursor:pointer;\n\t\t\t\tdisplay: inline-block;\n\t\t\t}\n\t\t\t.palette div:focus {\n\t\t\t\toutline-width: 2px;\n\t\t\t\toutline-style: dashed;\n\t\t\t}\n\t\t\t.screen-reader-text {\n\t\t\t\tborder: 0;\n\t\t\t\tclip: rect(1px, 1px, 1px, 1px);\n\t\t\t\t-webkit-clip-path: inset(50%);\n\t\t\t\tclip-path: inset(50%);\n\t\t\t\theight: 1px;\n\t\t\t\tmargin: -1px;\n\t\t\t\toverflow: hidden;\n\t\t\t\tpadding: 0;\n\t\t\t\tposition: absolute;\n\t\t\t\twidth: 1px;\n\t\t\t\tword-wrap: normal !important; /* many screen reader and browser combinations announce broken words as they would appear visually */\n\t\t\t}\n\n\t\t\tnoscript p {\n\t\t\t\tborder: 1px solid red;\n\t\t\t\tpadding: 1em;\n\t\t\t}\n\n\t\t\t#generate {\n\t\t\t\tborder-top: 1px solid #afafaf;\n\t\t\t\tpadding-top: 1em;\n\t\t\t}\n\n\t\t\tbutton {\n\t\t\t\tfont-family: -apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Oxygen-Sans,Ubuntu,Cantarell,\"Helvetica Neue\",sans-serif;\n\t\t\t\tpadding:5px 1em;\n\t\t\t\tmargin-right: .5em;\n\t\t\t\tfont-size: .9em;\n\t\t\t\tbackground:#3377ff;\n\t\t\t\tcolor: #fff;\n\t\t\t\tborder:0 none;\n\t\t\t\tcursor:pointer;\n\t\t\t\t-webkit-border-radius: 3px;\n\t\t\t\tborder-radius: 5px;\n\t\t\t}\n\t\t\tbutton:hover {\n\t\t\t\tbackground:#0055ff;\n\t\t\t}\n\t\t</style>\n\t</head>\n\t<body>\n\t\t<h1>Maze Generator</h1>\n\t\t<p>\n\t\t\tCreate, solve and download random maze puzzles in any size or color with this online tool. Enter the values for your maze design below and click the \"Generate Maze\" button.</p>\n\t\t<p>\n\t\t\tThe <a href=\"https://en.wikipedia.org/wiki/Maze_generation_algorithm#Recursive_backtracker\">recursive backtracking algorithm</a> is used to create the mazes.\n\t\t\tFor more information check out the <a href=\"https://github.com/keesiemeijer/maze-generator\">Github Repository</a>.\n\t\t</p>\n\t\t<hr>\n\t\t<div class=\"controls\">\n\t\t\t<p>\n\t\t\t\t<label for=\"wall-size\">Wall thickness:</label>\n\t\t\t\t<input id=\"wall-size\" type=\"number\" name=\"\" value=\"10\" min=\"1\">\n\t\t\t</p>\n\n\t\t\t<p>\n\t\t\t\t<label for=\"width\">Columns:</label>\n\t\t\t\t<input id=\"width\" type=\"number\" name=\"width\" value=\"20\" min=\"3\" max=\"200\">\n\t\t\t</p>\n\n\t\t\t<p>\n\t\t\t\t<label for=\"height\">Rows:</label>\n\t\t\t\t<input id=\"height\" type=\"number\" name=\"height\" value=\"20\" min=\"3\" max=\"200\">\n\t\t\t</p>\n\t\t\t<p>\n\t\t\t\t<label for=\"entry\">Maze entries:</label>\n\t\t\t\t<select id=\"entry\" name=\"entry\">\n\t\t\t\t\t<option value=\"\">none</option>\n\t\t\t\t\t<option value=\"diagonal\" selected=\"selected\">diagonal</option>\n\t\t\t\t\t<option value=\"horizontal\">left and right</option>\n\t\t\t\t\t<option value=\"vertical\">top and bottom</option>\n\t\t\t\t</select>\n\t\t\t</p>\n\t\t\t<p>\n\t\t\t\t<label for=\"bias\">Bias:</label>\n\t\t\t\t<select id=\"bias\" name=\"bias\">\n\t\t\t\t\t<option value=\"\">none</option>\n\t\t\t\t\t<option value=\"horizontal\">horizontal</option>\n\t\t\t\t\t<option value=\"vertical\">vertical</option>\n\t\t\t\t</select>\n\t\t\t</p>\n\t\t\t<p class='desc'>\n\t\t\t\tThe maze can be solved in multiple ways if you remove maze walls. (maximum <span>300</span> walls)<br/>\n\t\t\t\tThe <a href='https://en.wikipedia.org/wiki/A*_search_algorithm'>A* search algorithm</a> is used to find the shortest path. This takes more time solving the maze.\n\t\t\t</p>\n\t\t\t<p style='padding-bottom: 0; margin-bottom: 0;'>\n\t\t\t\t<label for=\"remove_walls\">Remove maze walls:</label>\n\t\t\t\t<input id=\"remove_walls\" type=\"number\" name=\"remove_walls\" value=\"0\" min=\"0\" max=\"200\">\n\t\t\t</p>\n\t\t\t<p class='desc'>\n\t\t\t\tClick the colors below to select a color from a color pallete.\n\t\t\t</p>\n\t\t\t<div class=\"color-picker\" data-default=\"#ffffff\">\n\t\t\t\t<label for=\"backgroundColor\">Background Color</label> \n\t\t\t\t<input type=\"text\" class=\"\" id=\"backgroundColor\" name=\"backgroundColor\" value=\"#ffffff\"><div class=\"color-sample\" style=\"background-color:#ffffff\"></div>\n\t\t\t</div>\n\t\t\t<div class=\"color-picker\" data-default=\"#000000\">\n\t\t\t\t<label for=\"color\">Maze Color</label> \n\t\t\t\t<input type=\"text\" class=\"\" id=\"color\" name=\"color\" value=\"#000000\"><div class=\"color-sample\" style=\"background-color:#000000\"></div>\n\t\t\t</div>\n\t\t\t<div class=\"color-picker\" data-default=\"#cc3737\">\n\t\t\t\t<label for=\"solveColor\">Solve Color</label> \n\t\t\t\t<input type=\"text\" class=\"\" id=\"solveColor\" name=\"solveColor\" value=\"#cc3737\" pattern=\"^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$\"><div class=\"color-sample\" style=\"background-color:#cc3737\"></div>\n\t\t\t</div>\n\t\t\t<div id=\"generate\">\n\t\t\t\t<button id=\"create-maze\" onclick=\"initMaze();\" class='hide'>Generate Maze</button>\n\t\t\t\t<button id=\"solve\" onclick=\"initSolve();\" class='hide'>Solve Maze</button>\n\t\t\t\t<a href=\"\" class=\"hide\" id=\"download\">download maze</a>\n\t\t\t</div>\n\t\t</div>\n\t\t<noscript>\n\t\t\t<p>Sorry... this site requires JavaScript to generate a maze. Please enable it in your browser</p>\n\t\t</noscript>\n\t\t<script type=\"text/javascript\">\n\t\t\tconst createMazeButton = document.getElementById('create-maze');\n\t\t\tcreateMazeButton.classList.toggle(\"hide\")\n\t\t</script>\n\t\t<canvas id=\"maze\"></canvas>\n\t\t<script type=\"text/javascript\" src=\"src/globals.js\"></script>\n\t\t<script type=\"text/javascript\" src=\"src/utils.js\"></script>\n\t\t<script type=\"text/javascript\" src=\"src/entries.js\"></script>\n\t\t<script type=\"text/javascript\" src=\"src/maze.js\"></script>\n\t\t<script type=\"text/javascript\" src=\"src/solver.js\"></script>\n\t\t<script type=\"text/javascript\" src=\"src/app.js\"></script>\n\t\t<script type=\"text/javascript\" src=\"src/human-colours-en-gb.js\"></script>\n\t\t<script type=\"text/javascript\" src=\"src/color-picker.js\"></script>\n\t</body>\n</html>"
  },
  {
    "path": "src/app.js",
    "content": "// Global variables\nlet mazeNodes = {};\n\n// Check if globals are defined\nif (typeof maxMaze === 'undefined') {\n    maxMaze = 0;\n}\n\nif (typeof maxSolve === 'undefined') {\n    maxSolve = 0;\n}\n\nif (typeof maxCanvas === 'undefined') {\n    maxCanvas = 0;\n}\n\nif (typeof maxCanvasDimension === 'undefined') {\n    maxCanvasDimension = 0;\n}\n\nif (typeof maxWallsRemove === 'undefined') {\n    maxWallsRemove = 300;\n}\n\n// Update remove max walls html\nconst removeMaxWallsText = document.querySelector('.desc span');\nif (removeMaxWallsText) {\n    removeMaxWallsText.innerHTML = maxWallsRemove;\n}\n\nconst removeWallsInput = document.getElementById('remove_walls');\nif (removeWallsInput) {\n    removeWallsInput.max = maxWallsRemove;\n}\n\nconst download = document.getElementById(\"download\");\ndownload.addEventListener(\"click\", downloadImage, false);\ndownload.setAttribute('download', 'maze.png');\n\nfunction initMaze() {\n    download.setAttribute('download', 'maze.png');\n    download.innerHTML = 'download maze';\n\n    const settings = {\n        width: getInputIntVal('width', 20),\n        height: getInputIntVal('height', 20),\n        wallSize: getInputIntVal('wall-size', 10),\n        removeWalls: getInputIntVal('remove_walls', 0),\n        entryType: '',\n        bias: '',\n        color: '#000000',\n        backgroundColor: '#FFFFFF',\n        solveColor: '#cc3737',\n\n        // restrictions\n        maxMaze: maxMaze,\n        maxCanvas: maxCanvas,\n        maxCanvasDimension: maxCanvasDimension,\n        maxSolve: maxSolve,\n        maxWallsRemove: maxWallsRemove,\n    }\n\n    const colors = ['color', 'backgroundColor', 'solveColor'];\n    for (let i = 0; i < colors.length; i++) {\n        const colorInput = document.getElementById(colors[i]);\n        settings[colors[i]] = colorInput.value\n        if (!isValidHex(settings[colors[i]])) {\n            let defaultColor = colorInput.parentNode.dataset.default;\n            colorInput.value = defaultColor;\n            settings[colors[i]] = defaultColor;\n        }\n\n        const colorSample = colorInput.parentNode.querySelector('.color-sample');\n        colorSample.style = 'background-color: ' + settings[colors[i]] + ';';\n    }\n\n    if (settings['removeWalls'] > maxWallsRemove) {\n        settings['removeWalls'] = maxWallsRemove;\n        if (removeWallsInput) {\n            removeWallsInput.value = maxWallsRemove;\n        }\n    }\n\n    const entry = document.getElementById('entry');\n    if (entry) {\n        settings['entryType'] = entry.options[entry.selectedIndex].value;\n    }\n\n    const bias = document.getElementById('bias');\n    if (bias) {\n        settings['bias'] = bias.options[bias.selectedIndex].value;\n    }\n\n    const maze = new Maze(settings);\n    maze.generate();\n    maze.draw();\n\n    if (download && download.classList.contains('hide')) {\n        download.classList.toggle(\"hide\");\n    }\n\n    const solveButton = document.getElementById(\"solve\");\n    if (solveButton && solveButton.classList.contains('hide')) {\n        solveButton.classList.toggle(\"hide\");\n    }\n\n    mazeNodes = {}\n    if (maze.matrix.length) {\n        mazeNodes = maze;\n    }\n\n    location.href = \"#\";\n    location.href = \"#generate\";\n}\n\nfunction downloadImage(e) {\n    const image = document.getElementById('maze').toDataURL(\"image/png\");\n    image.replace(\"image/png\", \"image/octet-stream\");\n    download.setAttribute(\"href\", image);\n}\n\nfunction initSolve() {\n    const solveButton = document.getElementById(\"solve\");\n    if (solveButton) {\n        solveButton.classList.toggle(\"hide\");\n    }\n\n    download.setAttribute('download', 'maze-solved.png');\n    download.innerHTML = 'download solved maze';\n\n    if ((typeof mazeNodes.matrix === 'undefined') || !mazeNodes.matrix.length) {\n        return;\n    }\n\n    const solver = new Solver(mazeNodes);\n    solver.solve();\n    if (mazeNodes.wallsRemoved) {\n        solver.drawAstarSolve();\n    } else {\n        solver.draw();\n    }\n\n    mazeNodes = {}\n}"
  },
  {
    "path": "src/color-picker.js",
    "content": "(function() {\n\n\t// Amount of colors in a row\n\tconst row = 11;\n\n\t// Classes\n\tconst colorPickerClass = 'color-picker';\n\tconst colorSampleClass = 'color-sample';\n\tconst paletteClass = 'palette';\n\tconst screenReaderClass = 'screen-reader-text';\n\n\t// Todo: Use object with color description for accessibility\n\tvar hexColors = [\n\t\t'#000000',\n\t\t'#191919',\n\t\t'#323232',\n\t\t'#4b4b4b',\n\t\t'#646464',\n\t\t'#7d7d7d',\n\t\t'#969696',\n\t\t'#afafaf',\n\t\t'#c8c8c8',\n\t\t'#e1e1e1',\n\t\t'#ffffff',\n\t\t'#820000',\n\t\t'#9b0000',\n\t\t'#b40000',\n\t\t'#cd0000',\n\t\t'#e60000',\n\t\t'#ff0000',\n\t\t'#ff1919',\n\t\t'#ff3232',\n\t\t'#ff4b4b',\n\t\t'#ff6464',\n\t\t'#ff7d7d',\n\t\t'#823400',\n\t\t'#9b3e00',\n\t\t'#b44800',\n\t\t'#cd5200',\n\t\t'#e65c00',\n\t\t'#ff6600',\n\t\t'#ff7519',\n\t\t'#ff8532',\n\t\t'#ff944b',\n\t\t'#ffa364',\n\t\t'#ffb27d',\n\t\t'#828200',\n\t\t'#9b9b00',\n\t\t'#b4b400',\n\t\t'#cdcd00',\n\t\t'#e6e600',\n\t\t'#ffff00',\n\t\t'#ffff19',\n\t\t'#ffff32',\n\t\t'#ffff4b',\n\t\t'#ffff64',\n\t\t'#ffff7d',\n\t\t'#003300',\n\t\t'#004d00',\n\t\t'#008000',\n\t\t'#00b300',\n\t\t'#00cc00',\n\t\t'#00e600',\n\t\t'#1aff1a',\n\t\t'#4dff4d',\n\t\t'#66ff66',\n\t\t'#80ff80',\n\t\t'#b3ffb3',\n\t\t'#001a4d',\n\t\t'#002b80',\n\t\t'#003cb3',\n\t\t'#004de6',\n\t\t'#0000ff',\n\t\t'#0055ff',\n\t\t'#3377ff',\n\t\t'#4d88ff',\n\t\t'#6699ff',\n\t\t'#80b3ff',\n\t\t'#b3d1ff',\n\t\t'#003333',\n\t\t'#004d4d',\n\t\t'#006666',\n\t\t'#009999',\n\t\t'#00cccc',\n\t\t'#00ffff',\n\t\t'#1affff',\n\t\t'#33ffff',\n\t\t'#4dffff',\n\t\t'#80ffff',\n\t\t'#b3ffff',\n\t\t'#4d004d',\n\t\t'#602060',\n\t\t'#660066',\n\t\t'#993399',\n\t\t'#ac39ac',\n\t\t'#bf40bf',\n\t\t'#c653c6',\n\t\t'#cc66cc',\n\t\t'#d279d2',\n\t\t'#d98cd9',\n\t\t'#df9fdf',\n\t\t'#660029',\n\t\t'#800033',\n\t\t'#b30047',\n\t\t'#cc0052',\n\t\t'#e6005c',\n\t\t'#ff0066',\n\t\t'#ff1a75',\n\t\t'#ff3385',\n\t\t'#ff4d94',\n\t\t'#ff66a3',\n\t\t'#ff99c2',\n\t];\n\n\tconst colorPickers = document.querySelectorAll('.' + colorPickerClass);\n\tconst palette = '<div class=\"' + paletteClass + '\" style=\"display: none;\"></div>';\n\tlet paletteHasFocus = false;\n\tlet desc = \"Use a hex color code or use the tab key to select a color.\";\n\tdesc += ' Use the arrow keys to scroll through all colors. Use the space or return key to select the color.';\n\tdesc += ' Use the escape key to close the palette.'\n\n\tfor (let i = 0; i < colorPickers.length; i++) {\n\t\t// Create aria describedby element for the color input\n\t\tvar describedby = document.createElement(\"p\");\n\t\tdescribedby.style.display = 'none';\n\t\tdescribedby.id = 'desc-' + i;\n\t\tdescribedby.innerHTML = desc;\n\n\t\t// Insert describedby description\n\t\tcolorPickers[i].insertAdjacentElement('afterbegin', describedby)\n\n\t\tconst colorInput = colorPickers[i].querySelector('input');\n\n\t\t// Show color palette on input focus\n\t\tcolorInput.addEventListener(\"focus\", showColorPalette, false);\n\n\t\t// Check if tab key is used to focus a color in the palette\n\t\tcolorInput.addEventListener(\"keydown\", inputTabPressed, false);\n\n\t\t// Update color sample after key up\n\t\tcolorInput.addEventListener(\"keyup\", updateColorSample, false);\n\n\t\t// Add describedby attribute\n\t\tcolorInput.setAttribute(\"aria-describedby\", 'desc-' + i);\n\n\t\t// Insert color palette\n\t\tcolorPickers[i].insertAdjacentHTML('beforeend', palette);\n\n\t\t// Get inserted palette\n\t\tconst colorPalette = colorPickers[i].querySelector('.' + paletteClass);\n\n\t\tfor (let j = 0; j < hexColors.length; j++) {\n\t\t\tvar colorDiv = document.createElement(\"div\");\n\t\t\tvar colorDivText = document.createElement(\"span\");\n\t\t\tcolorDivText.className = screenReaderClass;\n\t\t\tcolorDivText.innerHTML = hexColors[j];\n\t\t\tcolorDiv.appendChild(colorDivText);\n\n\t\t\t// Make color divs tabbable.\n\t\t\tcolorDiv.tabIndex = 0;\n\t\t\tcolorDiv.style = 'background-color: ' + hexColors[j] + ';';\n\t\t\tcolorDiv.setAttribute(\"role\", \"button\");\n\t\t\tcolorDiv.setAttribute('data-index', j + 1);\n\t\t\tcolorPalette.appendChild(colorDiv);\n\n\n\t\t\t// Get RGB color from background\n\t\t\tlet rgbColor = colorDiv.style.backgroundColor;\n\n\t\t\t// Get human readable colorname\n\t\t\tlet colorLabel = getHumanReadableColor(rgbColor, hexColors[j]);\n\t\t\tif (colorLabel.length) {\n\t\t\t\tcolorDiv.setAttribute(\"aria-label\", colorLabel);\n\t\t\t}\n\n\t\t\t// Check if a color is the new focused element\n\t\t\tcolorDiv.addEventListener(\"blur\", colorBlur);\n\n\t\t\t// Navigate colors in palette\n\t\t\tcolorDiv.addEventListener(\"keyup\", colorNavigation, false);\n\t\t}\n\n\t\tcolorPalette.onmouseenter = function() {\n\t\t\tpaletteHasFocus = true;\n\t\t}\n\t\tcolorPalette.onmouseleave = function() {\n\t\t\tpaletteHasFocus = false;\n\t\t}\n\n\t\t// Close palette if palette or color is clicked\n\t\tcolorPalette.addEventListener(\"click\", paletteClick, false);\n\n\t\t// Hide colorpalette if paletteHasFocus is false\n\t\tcolorInput.addEventListener(\"focusout\", hideColorPalette);\n\t}\n\n\tlet colorSample = document.querySelectorAll('.' + colorSampleClass)\n\tfor (let i = 0; i < colorSample.length; i++) {\n\t\t// Set initial color same as default (should already be set in HTML)\n\t\tlet defaultColor = colorSample[i].parentNode.dataset.default;\n\t\tcolorSample[i].style = 'background-color: ' + defaultColor + ';';\n\n\t\t// Get RGB color from background\n\t\tlet rgbColor = colorSample[i].style.backgroundColor;\n\n\t\t// Add span for human readable text\n\t\tlet colorSampleText = document.createElement(\"span\");\n\t\tcolorSampleText.className = screenReaderClass;\n\t\tcolorSample[i].appendChild(colorSampleText);\n\n\t\t// Update human readable text\n\t\tupdateColorSampleText(colorSample[i], defaultColor);\n\n\t\t// Display palette if sample is clicked\n\t\tcolorSample[i].addEventListener(\"click\", showColorPalette, false);\n\t}\n\n\tfunction colorBlur(e) {\n\t\t// Check what element has focus\n\t\tif (e.relatedTarget === null) {\n\t\t\t// No element has focus\n\t\t\tthis.parentNode.style.display = 'none';\n\t\t\tpaletteHasFocus = false;\n\n\t\t} else {\n\t\t\tif (paletteClass !== e.relatedTarget.parentNode.className) {\n\t\t\t\t// No element in the palette has focus\n\t\t\t\tthis.parentNode.style.display = 'none';\n\t\t\t\tpaletteHasFocus = false;\n\t\t\t}\n\t\t}\n\t}\n\n\tfunction showColorPalette(e) {\n\t\tthis.parentNode.querySelector('input').focus();\n\n\t\tlet palette = this.parentNode.querySelector('.' + paletteClass);\n\t\tpalette.style.display = 'block';\n\t}\n\n\tfunction hideColorPalette(e) {\n\t\tlet colorPalette = this.parentNode.querySelector('.' + paletteClass);\n\t\tif (paletteHasFocus === false) {\n\t\t\tcolorPalette.style.display = 'none';\n\t\t}\n\t}\n\n\tfunction paletteClick(e) {\n\t\tif (paletteClass !== e.target.className) {\n\t\t\t// Get the clicked color\n\t\t\tlet hexColor = rgbToHex(e.target.style.backgroundColor);\n\n\t\t\tthis.parentNode.querySelector('input').value = hexColor;\n\t\t\tlet colorSample = this.parentNode.querySelector('.' + colorSampleClass);\n\n\t\t\tcolorSample.style = 'background-color: ' + hexColor + ';';\n\t\t\tupdateColorSampleText(colorSample, hexColor)\n\n\t\t}\n\t\t// Hide palette\n\t\tthis.style.display = 'none';\n\t\tpaletteHasFocus = false;\n\t}\n\n\tfunction colorNavigation(e) {\n\t\t// Select color with space or enter\n\t\tif (13 === e.which || 32 === e.which) {\n\t\t\tlet hexColor = rgbToHex(this.style.backgroundColor);\n\n\t\t\tthis.parentNode.parentNode.querySelector('input').value = hexColor;\n\t\t\tlet colorSample = this.parentNode.parentNode.querySelector('.' + colorSampleClass);\n\t\t\tcolorSample.style = 'background-color: ' + hexColor + ';';\n\t\t\tupdateColorSampleText(colorSample, hexColor)\n\n\t\t\tthis.parentNode.style.display = 'none';\n\t\t\tpaletteHasFocus = false;\n\t\t\treturn;\n\t\t}\n\n\t\tif(27 === e.which) {\n\t\t\tthis.parentNode.style.display = 'none';\n\t\t\tpaletteHasFocus = false;\n\t\t\treturn;\n\t\t}\n\n\t\tlet index = 0;\n\n\t\t// Palette navigation with arrow keys\n\t\tif (40 === e.which) {\n\t\t\t// down arrow\n\t\t\tindex = parseInt(this.dataset.index, 10) + row;\n\t\t} else if (38 === e.which) {\n\t\t\t// up arrow\n\t\t\tindex = parseInt(this.dataset.index, 10) - row;\n\t\t} else if (37 === e.which) {\n\t\t\t// left arrow\n\t\t\tindex = parseInt(this.dataset.index, 10) - 1;\n\t\t} else if (39 === e.which) {\n\t\t\t// right arrow\n\t\t\tindex = parseInt(this.dataset.index, 10) + 1;\n\t\t} else {\n\t\t\t// Not a navigation key\n\t\t\treturn;\n\t\t}\n\n\t\tif (0 >= index || hexColors.length < index) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet next = this.parentNode.querySelector('[data-index=\"' + index + '\"]');\n\t\tif (next) {\n\t\t\tnext.focus();\n\t\t}\n\t}\n\n\tfunction inputTabPressed(e) {\n\t\t// Tab key to go to the first color in the palette\n\t\tif (9 === e.which) {\n\t\t\t// Palette has focus if a color has focus\n\t\t\tpaletteHasFocus = true;\n\t\t}\n\t}\n\n\tfunction updateColorSample(e) {\n\t\t// Update colorsample if it's a valid color\n\t\tif (isValidHex(this.value)) {\n\t\t\tlet colorSample = this.parentNode.querySelector('.' + colorSampleClass);\n\t\t\tcolorSample.style = 'background-color: ' + this.value + ';';\n\t\t\tupdateColorSampleText(colorSample, this.value);\n\t\t}\n\t}\n\n\tfunction updateColorSampleText(el, hexColor) {\n\t\tlet span = el.querySelector('span');\n\t\tlet rgbColor = el.style.backgroundColor;\n\n\t\tlet readableColor = getHumanReadableColor(rgbColor, hexColor);\n\t\tif (readableColor.length) {\n\t\t\tspan.innerHTML = readableColor;\n\t\t}\n\t}\n\n\tfunction getHumanReadableColor(rgbColor, hexColor) {\n\t\tif (typeof HumanColours === \"undefined\") {\n\t\t\treturn hexColor;\n\t\t}\n\n\t\tlet arr = rgbColor.replace('rgb', '').replace('(', '').replace(')', '').split(',');\n\t\tlet hslColor = rgbToHsl(arr[0], arr[1], arr[2]);\n\t\thslColor = 'hsl(' + hslColor.join(',') + ')';\n\t\tlet readable = new HumanColours(hslColor);\n\n\t\treturn 'Color ' + readable.hueName() + ', ' + readable.saturationName() + ', ' + readable.lightnessName();\n\t}\n\n\tfunction isValidHex(hex) {\n\t\treturn /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/i.test(hex.trim());\n\t}\n\n\tfunction componentToHex(c) {\n\t\tvar hex = c.toString(16);\n\t\treturn hex.length == 1 ? \"0\" + hex : hex;\n\t}\n\n\tfunction rgbToHex(color) {\n\t\tarr = color.replace('rgb', '').replace('(', '').replace(')', '').split(',');\n\t\treturn \"#\" + componentToHex(Number(arr[0])) + componentToHex(Number(arr[1])) + componentToHex(Number(arr[2]));\n\t}\n\n\tfunction rgbToHsl(r, g, b) {\n\t\tr /= 255, g /= 255, b /= 255;\n\n\t\tvar max = Math.max(r, g, b),\n\t\t\tmin = Math.min(r, g, b);\n\t\tvar h, s, l = (max + min) / 2;\n\n\t\tif (max == min) {\n\t\t\th = s = 0; // achromatic\n\t\t} else {\n\t\t\tvar d = max - min;\n\t\t\ts = l > 0.5 ? d / (2 - max - min) : d / (max + min);\n\n\t\t\tswitch (max) {\n\t\t\t\tcase r:\n\t\t\t\t\th = (g - b) / d + (g < b ? 6 : 0);\n\t\t\t\t\tbreak;\n\t\t\t\tcase g:\n\t\t\t\t\th = (b - r) / d + 2;\n\t\t\t\t\tbreak;\n\t\t\t\tcase b:\n\t\t\t\t\th = (r - g) / d + 4;\n\t\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\th /= 6;\n\t\t}\n\n\t\treturn [Math.floor(h * 360), Math.floor(s * 100), Math.floor(l * 100)];\n\t}\n})();"
  },
  {
    "path": "src/entries.js",
    "content": "function getEntryNode( entries, type, gate = false ) {\n\tif ( !hasEntries( entries ) ) {\n\t\treturn false;\n\t}\n\n\tif( 'start' === type ) {\n\t\treturn gate ? entries.start.gate : {'x': entries.start.x, 'y': entries.start.y};\n\t}\n\n\tif( 'end' === type ) {\n\t\treturn gate ? entries.end.gate : {'x': entries.end.x, 'y': entries.end.y};\n\t}\n\n\treturn false;\n}\n\nfunction hasEntries( entries ) {\n\tif ( entries.hasOwnProperty( 'start' ) && entries.hasOwnProperty( 'end' ) ) {\n\t\treturn true;\n\t}\n\n\treturn false;\n}"
  },
  {
    "path": "src/globals.js",
    "content": "// Maze Restrictions.\n//\n// The higher the values the less restricted.\n// Set values to 0 for no restriction.\n//\n// Be aware that the larger the maze, the more memory is consumed.\n// With recursive backtracking the whole maze is stored in memory.\n\nconst maxMaze = 75000;\nconst maxSolve = 30000;\nconst maxCanvas = 16080100;\nconst maxCanvasDimension = 32760;\n\n// Maximum walls you can remove\nconst maxWallsRemove = 300;\n"
  },
  {
    "path": "src/human-colours-en-gb.js",
    "content": "(function(global) {\n\n  var regex = /hsl\\((.*)\\)/, //Match hsl values\n      h, //Hue\n      s, //Saturation\n      l, //Lightness\n      hue,\n      sat,\n      light;\n\n  function HumanColours(hsl){\n    this.HSL = hsl;\n    this.values = this.HSL.replace(regex, '$1').split(',');\n  }\n  \n  HumanColours.prototype = {\n    getHSL: function() {\n      return this.HSL;\n    },\n    \n    getHue: function() {\n      return this.values[0];\n    },\n    \n    getSaturation: function() {\n      return this.values[1].replace('%', '');\n    },\n  \n    getLightness: function() {\n      return this.values[2].replace('%', '');\n    },\n    \n    hueName: function() {\n      h = this.getHue();\n  \n      if ( h < 15 ) { hue = 'red'; }\n      if ( h === 15 ) { hue = 'reddish'; }\n      if ( h > 15 ) { hue = 'orange'; }\n      if ( h > 45 ) { hue = 'yellow'; }\n      if ( h > 70 ) { hue = 'lime'; }\n      if ( h > 79 ) { hue = 'green'; }\n      if ( h > 163 ) { hue = 'cyan'; }\n      if ( h > 193 ) { hue = 'blue'; }\n      if ( h > 240 ) { hue = 'indigo'; }\n      if ( h > 260 ) { hue = 'violet'; }\n      if ( h > 270 ) { hue = 'purple'; }\n      if ( h > 291 ) { hue = 'magenta'; }\n      if ( h > 327 ) { hue = 'rose'; }\n      if ( h > 344 ) { hue = 'red'; }\n  \n      return hue;\n    },\n    \n    saturationName: function() {\n      s = this.getSaturation();\n  \n      if( s < 4) { sat =  'grey'; }\n      if( s > 3) { sat =  'almost grey'; }\n      if( s > 10) { sat =  'very unsaturated'; }\n      if( s > 30) { sat =  'unsaturated'; }\n      if( s > 46) { sat =  'rather unsaturated'; }\n      if( s > 60) { sat =  'saturated'; }\n      if( s > 80) { sat =  'rather saturated'; }\n      if( s > 90) { sat =  'very saturated'; }\n  \n      return sat;\n    },\n    \n    lightnessName: function() {\n      l = this.getLightness();\n  \n      if( l < 10 ) { light = 'almost black'; }\n      if( l > 9 ) { light = 'very dark'; }\n      if( l > 22 ) { light = 'dark'; }\n      if( l > 30 ) { light = 'normal?'; }\n      if( l > 60 ) { light = 'light'; }\n      if( l > 80 ) { light = 'very light'; }\n      if( l > 94 ) { light = 'almost white'; }\n  \n      return light;\n    }\n  };\n\n  global.HumanColours = HumanColours;\n\n}(this));\n"
  },
  {
    "path": "src/maze.js",
    "content": "function Maze(args) {\n\tconst defaults = {\n\t\twidth: 20,\n\t\theight: 20,\n\t\twallSize: 10,\n\t\tentryType: '',\n\t\tbias: '',\n\t\tcolor: '#000000',\n\t\tbackgroundColor: '#FFFFFF',\n\t\tsolveColor: '#cc3737',\n\t\tremoveWalls: 0,\n\n\t\t// Maximum 300 walls can be removed\n\t\tmaxWallsRemove: 300,\n\n\t\t// No restrictions\n\t\tmaxMaze: 0,\n\t\tmaxCanvas: 0,\n\t\tmaxCanvasDimension: 0,\n\t\tmaxSolve: 0,\n\t}\n\n\tconst settings = Object.assign({}, defaults, args);\n\n\tthis.matrix = [];\n\tthis.wallsRemoved = 0;\n\tthis.width = parseInt(settings['width'], 10);\n\tthis.height = parseInt(settings['height'], 10);\n\tthis.wallSize = parseInt(settings['wallSize'], 10);\n\tthis.removeWalls = parseInt(settings['removeWalls'], 10);\n\tthis.entryNodes = this.getEntryNodes(settings['entryType']);\n\tthis.bias = settings['bias'];\n\tthis.color = settings['color'];\n\tthis.backgroundColor = settings['backgroundColor'];\n\tthis.solveColor = settings['solveColor'];\n\tthis.maxMaze = parseInt(settings['maxMaze'], 10);\n\tthis.maxCanvas = parseInt(settings['maxCanvas'], 10);\n\tthis.maxCanvasDimension = parseInt(settings['maxCanvasDimension'], 10);\n\tthis.maxSolve = parseInt(settings['maxSolve'], 10);\n\tthis.maxWallsRemove = parseInt(settings['maxWallsRemove'], 10);\n}\n\nMaze.prototype.generate = function() {\n\tif (!this.isValidSize()) {\n\t\tthis.matrix = [];\n\t\talert('Please use smaller maze dimensions');\n\t\treturn;\n\t}\n\n\tlet nodes = this.generateNodes();\n\tnodes = this.parseMaze(nodes);\n\tthis.getMatrix(nodes);\n\tthis.removeMazeWalls();\n}\n\nMaze.prototype.isValidSize = function() {\n\tconst max = this.maxCanvasDimension;\n\tconst canvas_width = ((this.width * 2) + 1) * this.wallSize;\n\tconst canvas_height = ((this.height * 2) + 1) * this.wallSize;\n\n\t// Max dimension Firefox and Chrome\n\tif (max && ((max <= canvas_width) || (max <= canvas_height))) {\n\t\treturn false;\n\t}\n\n\t// Max area (200 columns) * (200 rows) with wall size 10px\n\tif (this.maxCanvas && (this.maxCanvas <= (canvas_width * canvas_height))) {\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\nMaze.prototype.generateNodes = function() {\n\tconst count = this.width * this.height;\n\tlet nodes = [];\n\n\tfor (let i = 0; i < count; i++) {\n\t\t// visited, nswe\n\t\tnodes[i] = \"01111\";\n\t}\n\n\treturn nodes;\n}\n\nMaze.prototype.parseMaze = function(nodes) {\n\n\tconst mazeSize = nodes.length;\n\tconst positionIndex = { 'n': 1, 's': 2, 'w': 3, 'e': 4, };\n\tconst oppositeIndex = { 'n': 2, 's': 1, 'w': 4, 'e': 3 };\n\n\tif (!mazeSize) {\n\t\treturn;\n\t}\n\n\tlet max = 0;\n\tlet moveNodes = [];\n\tlet visited = 0;\n\tlet position = parseInt(Math.floor(Math.random() * nodes.length), 10);\n\n\tlet biasCount = 0;\n\tlet biasFactor = 3;\n\tif (this.bias) {\n\t\tif (('horizontal' === this.bias)) {\n\t\t\tbiasFactor = (1 <= (this.width / 100)) ? Math.floor(this.width / 100) + 2 : 3;\n\t\t} else if ('vertical' === this.bias) {\n\t\t\tbiasFactor = (1 <= (this.height / 100)) ? Math.floor(this.height / 100) + 2 : 3;\n\t\t}\n\t}\n\n\t// Set start node visited.\n\tnodes[position] = replaceAt(nodes[position], 0, 1);\n\n\twhile (visited < (mazeSize - 1)) {\n\t\tbiasCount++;\n\n\t\tmax++;\n\t\tif (this.maxMaze && (this.maxMaze < max)) {\n\t\t\talert('Please use smaller maze dimensions');\n\t\t\tmove_nodes = [];\n\t\t\tthis.matrix = [];\n\t\t\treturn [];\n\t\t}\n\n\t\tlet next = this.getNeighbours(position);\n\t\tlet directions = Object.keys(next).filter(function(key) {\n\t\t\treturn (-1 !== next[key]) && !stringVal(this[next[key]], 0);\n\t\t}, nodes);\n\n\t\tif (this.bias && (biasCount !== biasFactor)) {\n\t\t\tdirections = this.biasDirections(directions);\n\t\t} else {\n\t\t\tbiasCount = 0;\n\t\t}\n\n\t\tif (directions.length) {\n\t\t\t++visited;\n\n\t\t\tif (1 < directions.length) {\n\t\t\t\tmoveNodes.push(position);\n\t\t\t}\n\n\t\t\tlet direction = directions[Math.floor(Math.random() * directions.length)];\n\n\t\t\t// Update current position\n\t\t\tnodes[position] = replaceAt(nodes[position], positionIndex[direction], 0);\n\t\t\t// Set new position\n\t\t\tposition = next[direction];\n\n\t\t\t// Update next position\n\t\t\tnodes[position] = replaceAt(nodes[position], oppositeIndex[direction], 0);\n\t\t\tnodes[position] = replaceAt(nodes[position], 0, 1);\n\t\t} else {\n\t\t\tif (!moveNodes.length) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tposition = moveNodes.pop();\n\t\t}\n\t}\n\n\treturn nodes;\n}\n\n\nMaze.prototype.getMatrix = function(nodes) {\n\tconst mazeSize = this.width * this.height;\n\n\t// Add the complete maze in a matrix\n\t// where 1 is a wall and 0 is a corridor.\n\n\tlet row1 = '';\n\tlet row2 = '';\n\n\tif (nodes.length !== mazeSize) {\n\t\treturn;\n\t}\n\n\tfor (let i = 0; i < mazeSize; i++) {\n\t\trow1 += !row1.length ? '1' : '';\n\t\trow2 += !row2.length ? '1' : '';\n\n\t\tif (stringVal(nodes[i], 1)) {\n\t\t\trow1 += '11';\n\t\t\tif (stringVal(nodes[i], 4)) {\n\t\t\t\trow2 += '01';\n\t\t\t} else {\n\t\t\t\trow2 += '00';\n\t\t\t}\n\t\t} else {\n\t\t\tlet hasAbove = nodes.hasOwnProperty(i - this.width);\n\t\t\tlet above = hasAbove && stringVal(nodes[i - this.width], 4);\n\t\t\tlet hasNext = nodes.hasOwnProperty(i + 1);\n\t\t\tlet next = hasNext && stringVal(nodes[i + 1], 1);\n\n\t\t\tif (stringVal(nodes[i], 4)) {\n\t\t\t\trow1 += '01';\n\t\t\t\trow2 += '01';\n\t\t\t} else if (next || above) {\n\t\t\t\trow1 += '01';\n\t\t\t\trow2 += '00';\n\t\t\t} else {\n\t\t\t\trow1 += '00';\n\t\t\t\trow2 += '00';\n\t\t\t}\n\t\t}\n\n\t\tif (0 === ((i + 1) % this.width)) {\n\t\t\tthis.matrix.push(row1);\n\t\t\tthis.matrix.push(row2);\n\t\t\trow1 = '';\n\t\t\trow2 = '';\n\t\t}\n\t}\n\n\t// Add closing row\n\tthis.matrix.push('1'.repeat((this.width * 2) + 1));\n}\n\nMaze.prototype.getEntryNodes = function(access) {\n\tconst y = ((this.height * 2) + 1) - 2;\n\tconst x = ((this.width * 2) + 1) - 2;\n\n\tlet entryNodes = {};\n\n\tif ('diagonal' === access) {\n\t\tentryNodes.start = { 'x': 1, 'y': 1, 'gate': { 'x': 0, 'y': 1 } };\n\t\tentryNodes.end = { 'x': x, 'y': y, 'gate': { 'x': x + 1, 'y': y } };\n\t}\n\n\tif ('horizontal' === access || 'vertical' === access) {\n\t\tlet xy = ('horizontal' === access) ? y : x;\n\t\txy = ((xy - 1) / 2);\n\t\tlet even = (xy % 2 === 0);\n\t\txy = even ? xy + 1 : xy;\n\n\t\tlet start_x = ('horizontal' === access) ? 1 : xy;\n\t\tlet start_y = ('horizontal' === access) ? xy : 1;\n\t\tlet end_x = ('horizontal' === access) ? x : (even ? start_x : start_x + 2);\n\t\tlet end_y = ('horizontal' === access) ? (even ? start_y : start_y + 2) : y;\n\t\tlet startgate = ('horizontal' === access) ? { 'x': 0, 'y': start_y } : { 'x': start_x, 'y': 0 };\n\t\tlet endgate = ('horizontal' === access) ? { 'x': x + 1, 'y': end_y } : { 'x': end_x, 'y': y + 1 };\n\n\t\tentryNodes.start = { 'x': start_x, 'y': start_y, 'gate': startgate };\n\t\tentryNodes.end = { 'x': end_x, 'y': end_y, 'gate': endgate };\n\t}\n\n\treturn entryNodes;\n}\n\nMaze.prototype.biasDirections = function(directions) {\n\n\tconst horizontal = (-1 !== directions.indexOf('w')) || (-1 !== directions.indexOf('e'));\n\tconst vertical = (-1 !== directions.indexOf('n')) || (-1 !== directions.indexOf('s'));\n\n\tif (('horizontal' === this.bias) && horizontal) {\n\t\tdirections = directions.filter(function(key) {\n\t\t\treturn (('w' === key) || ('e' === key))\n\t\t});\n\t} else if (('vertical' === this.bias) && vertical) {\n\t\tdirections = directions.filter(function(key) {\n\t\t\treturn (('n' === key) || ('s' === key))\n\t\t});\n\t}\n\n\treturn directions;\n}\n\nMaze.prototype.getNeighbours = function(pos) {\n\treturn {\n\t\t'n': (0 <= (pos - this.width)) ? pos - this.width : -1,\n\t\t's': ((this.width * this.height) > (pos + this.width)) ? pos + this.width : -1,\n\t\t'w': ((0 < pos) && (0 !== (pos % this.width))) ? pos - 1 : -1,\n\t\t'e': (0 !== ((pos + 1) % this.width)) ? pos + 1 : -1,\n\t};\n}\n\nMaze.prototype.removeWall = function(row, index) {\n\t// Remove wall if possible.\n\tconst evenRow = (row % 2 === 0);\n\tconst evenIndex = (index % 2 === 0);\n\tconst wall = stringVal(this.matrix[row], index);\n\n\tif (!wall) {\n\t\treturn false;\n\t}\n\n\tif (!evenRow && evenIndex) {\n\t\t// Uneven row and even column\n\t\tconst hasTop = (row - 2 > 0) && (1 === stringVal(this.matrix[row - 2], index));\n\t\tconst hasBottom = (row + 2 < this.matrix.length) && (1 === stringVal(this.matrix[row + 2], index));\n\n\t\tif (hasTop && hasBottom) {\n\t\t\tthis.matrix[row] = replaceAt(this.matrix[row], index, '0');\n\t\t\treturn true;\n\t\t} else if (!hasTop && hasBottom) {\n\t\t\tconst left = 1 === stringVal(this.matrix[row - 1], index - 1);\n\t\t\tconst right = 1 === stringVal(this.matrix[row - 1], index + 1);\n\t\t\tif (left || right) {\n\t\t\t\tthis.matrix[row] = replaceAt(this.matrix[row], index, '0');\n\t\t\t\treturn true;\n\t\t\t}\n\t\t} else if (!hasBottom && hasTop) {\n\t\t\tconst left = 1 === stringVal(this.matrix[row + 1], index - 1);\n\t\t\tconst right = 1 === stringVal(this.matrix[row + 1], index + 1);\n\t\t\tif (left || right) {\n\t\t\t\tthis.matrix[row] = replaceAt(this.matrix[row], index, '0');\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t} else if (evenRow && !evenIndex) {\n\t\t// Even row and uneven column\n\t\tconst hasLeft = 1 === stringVal(this.matrix[row], index - 2);\n\t\tconst hasRight = 1 === stringVal(this.matrix[row], index + 2);\n\n\t\tif (hasLeft && hasRight) {\n\t\t\tthis.matrix[row] = replaceAt(this.matrix[row], index, '0');\n\t\t\treturn true;\n\t\t} else if (!hasLeft && hasRight) {\n\t\t\tconst top = 1 === stringVal(this.matrix[row - 1], index - 1);\n\t\t\tconst bottom = 1 === stringVal(this.matrix[row + 1], index - 1);\n\t\t\tif (top || bottom) {\n\t\t\t\tthis.matrix[row] = replaceAt(this.matrix[row], index, '0');\n\t\t\t\treturn true;\n\t\t\t}\n\t\t} else if (!hasRight && hasLeft) {\n\t\t\tconst top = 1 === stringVal(this.matrix[row - 1], index + 1);\n\t\t\tconst bottom = 1 === stringVal(this.matrix[row + 1], index + 1);\n\t\t\tif (top || bottom) {\n\t\t\t\tthis.matrix[row] = replaceAt(this.matrix[row], index, '0');\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn false;\n}\n\nMaze.prototype.removeMazeWalls = function() {\n\tif (!this.removeWalls || !this.matrix.length) {\n\t\treturn;\n\t}\n\n\tconst min = 1;\n\tconst max = this.matrix.length - 1;\n\tconst maxTries = this.maxWallsRemove;\n\tlet tries = 0;\n\n\twhile (tries < maxTries) {\n\t\ttries++;\n\n\t\t// Did we reached the goal\n\t\tif (this.wallsRemoved >= this.removeWalls) {\n\t\t\tbreak;\n\t\t}\n\n\t\t// Get random row from matrix\n\t\tlet y = Math.floor(Math.random() * (max - min + 1)) + min;\n\t\ty = (y === max) ? y - 1 : y;\n\n\t\tlet walls = [];\n\t\tlet row = this.matrix[y];\n\n\t\t// Get walls from random row\n\t\tfor (let i = 0; i < row.length; i++) {\n\t\t\tif (i === 0 || i === row.length - 1) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst wall = stringVal(row, i);\n\t\t\tif (wall) {\n\t\t\t\twalls.push(i);\n\t\t\t}\n\t\t}\n\n\t\t// Shuffle walls randomly\n\t\tshuffleArray(walls);\n\n\t\t// Try breaking a wall for this row.\n\t\tfor (let i = 0; i < walls.length; i++) {\n\t\t\tif (this.removeWall(y, walls[i])) {\n\n\t\t\t\t// Wall can be broken\n\t\t\t\tthis.wallsRemoved++;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n}\n\nMaze.prototype.draw = function() {\n\tconst canvas = document.getElementById('maze');\n\tif (!canvas || !this.matrix.length) {\n\t\treturn;\n\t}\n\n\tif (!this.isValidSize()) {\n\t\tthis.matrix = [];\n\t\talert('Please use smaller maze dimensions');\n\t\treturn;\n\t}\n\n\tcanvas.width = ((this.width * 2) + 1) * this.wallSize;;\n\tcanvas.height = ((this.height * 2) + 1) * this.wallSize;\n\n\tconst ctx = canvas.getContext('2d');\n\tctx.clearRect(0, 0, canvas.width, canvas.height);\n\n\t// Add background\n\tctx.fillStyle = this.backgroundColor;\n\tctx.fillRect(0, 0, canvas.width, canvas.height);\n\n\t// Set maze collor\n\tctx.fillStyle = this.color;\n\n\tconst row_count = this.matrix.length;\n\tconst gateEntry = getEntryNode(this.entryNodes, 'start', true);\n\tconst gateExit = getEntryNode(this.entryNodes, 'end', true);\n\n\tfor (let i = 0; i < row_count; i++) {\n\t\tlet row_length = this.matrix[i].length;\n\t\tfor (let j = 0; j < row_length; j++) {\n\t\t\tif (gateEntry && gateExit) {\n\t\t\t\tif ((j === gateEntry.x) && (i === gateEntry.y)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif ((j === gateExit.x) && (i === gateExit.y)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\t\t\tlet pixel = parseInt(this.matrix[i].charAt(j), 10);\n\t\t\tif (pixel) {\n\t\t\t\tctx.fillRect((j * this.wallSize), (i * this.wallSize), this.wallSize, this.wallSize);\n\t\t\t}\n\t\t}\n\t}\n}"
  },
  {
    "path": "src/solver.js",
    "content": "function Solver(maze) {\n\tthis.maze = maze;\n\tthis.maxSolve = maze.maxSolve;\n\tthis.start = false;\n\tthis.finish = false;\n\tthis.solved = false;\n\tthis.path = false;\n}\n\nSolver.prototype.solve = function() {\n\tconst startPosition = getEntryNode(this.maze.entryNodes, 'start');\n\tconst endPosition = getEntryNode(this.maze.entryNodes, 'end');\n\n\t// Get nodes (from the maze matrix) that have connections to other nodes.\n\tconst nodes = this.getMazeSolveNodes(startPosition, endPosition);\n\n\t// Get the connections for every solve node.\n\tconst connected = this.connectMazeSolveNodes(nodes);\n\n\tif (this.maze.wallsRemoved) {\n\t\tthis.path = this.walkMazeAstar(connected);\n\t} else {\n\t\tthis.path = this.walkMaze(connected);\n\t}\n}\n\nSolver.prototype.getMazeSolveNodes = function(start, end) {\n\tconst matrix = this.maze.matrix;\n\tconst nodes = [];\n\n\t// Property used (by both solvers) to find and draw the path to the exit\n\tconst previous = undefined;\n\n\tconst rowCount = matrix.length;\n\tfor (let y = 0; y < rowCount; y++) {\n\n\t\tif (y === 0 || y === (rowCount - 1) || (0 === (y % 2))) {\n\t\t\t// First and last rows are walls only.\n\t\t\t// Even rows don't have any connections\n\t\t\tcontinue;\n\t\t}\n\n\t\tlet rowLength = matrix[y].length;\n\t\tfor (let x = 0; x < rowLength; x++) {\n\t\t\tif (stringVal(matrix[y], x)) {\n\t\t\t\t// Walls don't have connections.\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst nswe = {\n\t\t\t\t'n': (0 < y) && stringVal(matrix[y - 1], x),\n\t\t\t\t's': (rowCount > y) && stringVal(matrix[y + 1], x),\n\t\t\t\t'w': (0 < x) && stringVal(matrix[y], (x - 1)),\n\t\t\t\t'e': (rowLength > x) && stringVal(matrix[y], (x + 1))\n\t\t\t}\n\n\t\t\tif (start && end) {\n\t\t\t\tif ((x === start.x) && (y === start.y)) {\n\t\t\t\t\tthis.start = nodes.length;\n\t\t\t\t\tnodes.push({ x, y, nswe, previous });\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif ((x === end.x) && (y === end.y)) {\n\t\t\t\t\tthis.finish = nodes.length;\n\t\t\t\t\tnodes.push({ x, y, nswe, previous });\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Walls left or right\n\t\t\tif (nswe['w'] || nswe['e']) {\n\t\t\t\t// left or right direction possible\n\t\t\t\tif (!nswe['w'] || !nswe['e']) {\n\t\t\t\t\tnodes.push({ x, y, nswe, previous });\n\t\t\t\t\tcontinue;\n\n\t\t\t\t} else {\n\t\t\t\t\t// Up or down direction possible.\n\t\t\t\t\tif ((!nswe['n'] && nswe['s']) || (nswe['n'] && !nswe['s'])) {\n\t\t\t\t\t\tnodes.push({ x, y, nswe, previous });\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// All directions possible\n\t\t\t\tif (!nswe['n'] && !nswe['s'] && !nswe['w'] && !nswe['e']) {\n\t\t\t\t\tnodes.push({ x, y, nswe, previous });\n\t\t\t\t\tcontinue;\n\t\t\t\t} else {\n\t\t\t\t\t// Up or down direction possible.\n\t\t\t\t\tif ((!nswe['n'] && nswe['s']) || (nswe['n'] && !nswe['s'])) {\n\t\t\t\t\t\tnodes.push({ x, y, nswe, previous });\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} // x loop\n\t} // y loop\n\n\treturn nodes;\n}\n\nSolver.prototype.connectMazeSolveNodes = function(nodes) {\n\t// Connect nodes to their neighbours.\n\tconst y_nodes = {};\n\tconst nodes_length = nodes.length;\n\n\tfor (let i = 0; i < nodes_length; i++) {\n\t\tnodes[i]['connected'] = {};\n\t\tlet x = nodes[i]['x'];\n\t\tlet y = nodes[i]['y'];\n\n\t\tif (!nodes[i]['nswe']['w']) {\n\t\t\tnodes[i]['connected']['w'] = i - 1;\n\t\t}\n\n\t\tif (!nodes[i]['nswe']['e']) {\n\t\t\tnodes[i]['connected']['e'] = i + 1;\n\t\t}\n\n\t\tif (!nodes[i]['nswe']['n'] && y_nodes.hasOwnProperty(x)) {\n\t\t\tnodes[i]['connected']['n'] = y_nodes[x];\n\n\t\t\tif (nodes.hasOwnProperty(y_nodes[x])) {\n\t\t\t\tnodes[y_nodes[x]]['connected']['s'] = i;\n\t\t\t\tdelete y_nodes[x];\n\t\t\t}\n\t\t}\n\n\t\tif (!nodes[i]['nswe']['s']) {\n\t\t\ty_nodes[x] = i;\n\t\t}\n\n\t\tif (this.maze.wallsRemoved) {\n\t\t\t// Not needed for A star solve\n\t\t\tdelete nodes[i]['nswe'];\n\t\t}\n\t}\n\n\treturn nodes;\n}\n\nSolver.prototype.heuristic = function(a, b) {\n\treturn Math.abs(a.x - b.x) + Math.abs(a.y - b.y);\n}\n\nSolver.prototype.walkMazeAstar = function(nodes) {\n\tthis.solved = false;\n\n\tif (!nodes.length) {\n\t\treturn;\n\t}\n\n\tlet openSet = [];\n\tlet closedSet = [];\n\n\tlet startNode = 0;\n\tlet endNode = nodes.length - 1;\n\tif ((false !== this.start) && (false !== this.finish)) {\n\t\tstartNode = this.start;\n\t\tendNode = this.finish;\n\t}\n\n\t// Add defaults to all nodes before we walk the maze.\n\tnodes.forEach( e => {\n\t\te['f'] = 0;\n\t\te['g'] = 0;\n\t\te['h'] = 0;\n\t});\n\n\topenSet.push(startNode);\n\n\tlet max = 0;\n\n\twhile (openSet.length > 0) {\n\t\tmax++\n\t\tif (this.maxSolve && (this.maxSolve < max)) {\n\t\t\talert('Solving maze took too long. Please try again or use smaller maze dimensions');\n\t\t\tbreak\n\t\t}\n\n\t\t// Best next option\n\t\tlet winner = 0;\n\t\tfor (let i = 0; i < openSet.length; i++) {\n\t\t\tif (nodes[openSet[i]].f < nodes[openSet[winner]].f) {\n\t\t\t\twinner = i;\n\t\t\t}\n\t\t}\n\n\t\tvar current = nodes[openSet[winner]];\n\t\tlet currentKey = openSet[winner]\n\n\t\t// Did I finish?\n\t\tif (current === nodes[endNode]) {\n\t\t\tthis.solved = true;\n\t\t\tbreak;\n\t\t}\n\n\t\tremoveFromArray(openSet, openSet[winner]);\n\t\tclosedSet.push(currentKey);\n\n\t\tlet neighbors = [];\n\t\tfor (key in current.connected) {\n\t\t\tif (current.connected.hasOwnProperty(key)) {\n\t\t\t\tneighbors.push(current.connected[key]);\n\t\t\t}\n\t\t}\n\n\t\tfor (let i = 0; i < neighbors.length; i++) {\n\t\t\tlet neighbor = nodes[neighbors[i]];\n\n\t\t\t// Valid next spot?\n\t\t\tif (!closedSet.includes(neighbors[i])) {\n\t\t\t\tlet tempG = current.g + this.heuristic(neighbor, current);\n\n\t\t\t\t// Is this a better path than before?\n\t\t\t\tlet newPath = false;\n\t\t\t\tif (openSet.includes(neighbors[i])) {\n\t\t\t\t\tif (tempG < neighbor.g) {\n\t\t\t\t\t\tneighbor.g = tempG;\n\t\t\t\t\t\tnewPath = true;\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tneighbor.g = tempG;\n\t\t\t\t\tnewPath = true;\n\t\t\t\t\topenSet.push(neighbors[i]);\n\t\t\t\t}\n\n\t\t\t\t// Yes, it's a better path\n\t\t\t\tif (newPath) {\n\t\t\t\t\tneighbor.h = this.heuristic(neighbor, nodes[endNode]);\n\t\t\t\t\tneighbor.f = neighbor.g + neighbor.h;\n\t\t\t\t\tneighbor.previous = currentKey;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tpath = [];\n\tlet temp = current;\n\tpath.push(temp);\n\twhile (temp.previous) {\n\t\tpath.push(nodes[temp.previous]);\n\t\ttemp = nodes[temp.previous];\n\t}\n\n\t// Add the startNode for drawing the solved path.\n\tpath.push(nodes[startNode]);\n\n\treturn path;\n}\n\nSolver.prototype.walkMaze = function(nodes) {\n\tthis.solved = false;\n\n\tif (!nodes.length) {\n\t\treturn;\n\t}\n\n\tlet startNode = 0;\n\tlet endNode = nodes.length - 1;\n\tif ((false !== this.start) && (false !== this.finish)) {\n\t\tstartNode = this.start;\n\t\tendNode = this.finish;\n\t}\n\n\tlet max = 0;\n\tlet i = 0;\n\tlet node = false;\n\tlet from = false;\n\tconst multi_nodes = [];\n\tconst opposite = { 'n': 's', 's': 'n', 'w': 'e', 'e': 'w' };\n\n\twhile (this.solved === false) {\n\t\tmax++\n\t\tif (this.maxSolve && (this.maxSolve < max)) {\n\t\t\talert('Solving maze took too long. Please try again or use smaller maze dimensions');\n\t\t\tbreak\n\t\t}\n\n\t\tif (!node) {\n\t\t\ti = startNode;\n\t\t\tnode = nodes[i];\n\t\t}\n\n\t\tif (i === endNode) {\n\t\t\t// Found the end node.\n\t\t\tthis.solved = true;\n\t\t\tbreak\n\t\t}\n\n\t\tnode['count'] = 4 - (Object.keys(node['nswe'])\n\t\t\t\t.map(key => !node['nswe'][key] ? 0 : 1)\n\t\t\t\t.reduce((a, b) => a + b, 0));\n\n\t\tif (node.count > 2) {\n\t\t\tif (-1 === multi_nodes.indexOf(i)) {\n\t\t\t\tmulti_nodes.push(i);\n\t\t\t}\n\t\t}\n\n\t\tif (false !== from) {\n\t\t\tnode['nswe'][from] = 1;\n\t\t\tnode.count--;\n\t\t\tnodes[i] = node;\n\t\t}\n\n\t\tif (0 === node.count) {\n\t\t\tfrom = false;\n\n\t\t\tif (!multi_nodes.length) {\n\t\t\t\t// Jump back to start.\n\t\t\t\ti = startNode;\n\t\t\t\tnode = nodes[startNode];\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Jump back to multiple directions node\n\t\t\ti = multi_nodes.pop();\n\t\t\tnode = nodes[i];\n\n\t\t\tif (node.count > 1) {\n\t\t\t\t// Add multi node back if more than one option left\n\t\t\t\tmulti_nodes.push(i);\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\tlet directions = Object.keys(node['nswe']).filter(key => !node['nswe'][key] ? true : false);\n\t\tlet direction = directions[Math.floor(Math.random() * directions.length)];\n\n\t\tif (node.count >= 1) {\n\t\t\tnode.count--;\n\t\t\tfrom = opposite[direction];\n\t\t\tnode['nswe'][direction] = 1;\n\t\t\tnode['previous'] = direction;\n\t\t\tnodes[i] = node;\n\t\t}\n\n\t\tif (node['connected'].hasOwnProperty(direction)) {\n\t\t\ti = node['connected'][direction];\n\t\t\tnode = nodes[i];\n\t\t} else {\n\t\t\t// Error: Node is not connected to direction\n\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn nodes;\n}\n\nSolver.prototype.drawAstarSolve = function() {\n\tconst nodes = this.path;\n\tconst wallSize = this.maze.wallSize;\n\n\tconst canvas = document.getElementById('maze');\n\tif (!canvas || !nodes.length || !this.solved) {\n\t\treturn;\n\t}\n\n\tconst canvas_width = ((this.maze.width * 2) + 1) * wallSize;\n\tconst canvas_height = ((this.maze.height * 2) + 1) * wallSize;\n\n\tif (!((canvas.width === canvas_width) && (canvas.height === canvas_height))) {\n\t\t// Error: Not the expected canvas size.\n\t\treturn;\n\t}\n\n\tconst ctx = canvas.getContext('2d');\n\tctx.fillStyle = this.maze.solveColor;\n\n\tlet startNode = 0;\n\tlet endNode = nodes.length - 1;\n\tlet finished = false\n\tlet node = false;\n\n\tconst hasGates = (false !== this.start) && (false !== this.finish);\n\tif (hasGates) {\n\t\tstartNode = this.start;\n\t\tendNode = this.finish;\n\t\tconst gateEntry = getEntryNode(this.maze.entryNodes, 'start', true);\n\n\t\tctx.fillRect((gateEntry.x * wallSize), (gateEntry.y * wallSize), wallSize, wallSize);\n\t}\n\n\tfor (let i = nodes.length - 1; i >= 0; i--) {\n\t\tif (!(0 <= (i - 1))) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tlet previousX = nodes[i - 1].x;\n\t\tlet previousY = nodes[i - 1].y;\n\n\t\tlet start;\n\t\tlet to_x;\n\t\tif (nodes[i].y === previousY) {\n\t\t\tlet start = nodes[i].x\n\t\t\tlet to_x = ((previousX - start) * wallSize) + wallSize;\n\n\t\t\tif (nodes[i].x > previousX) {\n\t\t\t\tstart = previousX\n\t\t\t\tto_x = ((nodes[i].x - previousX) * wallSize) + wallSize;\n\t\t\t}\n\n\t\t\tctx.fillRect((start * wallSize), (nodes[i].y * wallSize), to_x, wallSize);\n\t\t}\n\n\t\tif (nodes[i].x === previousX) {\n\t\t\tlet start = nodes[i].y;\n\t\t\tlet to_y = ((previousY - start) * wallSize) + wallSize;\n\n\t\t\tif (nodes[i].y > previousY) {\n\t\t\t\tstart = previousY;\n\t\t\t\tto_y = ((nodes[i].y - previousY) * wallSize) + wallSize;\n\t\t\t}\n\n\t\t\tctx.fillRect((nodes[i].x * wallSize), (start * wallSize), wallSize, to_y);\n\t\t}\n\t}\n\n\tif (hasGates) {\n\t\tconst gateExit = getEntryNode(this.maze.entryNodes, 'end', true);\n\t\tctx.fillRect((gateExit.x * wallSize), (gateExit.y * wallSize), wallSize, wallSize);\n\t}\n}\n\nSolver.prototype.draw = function() {\n\tconst nodes = this.path;\n\tconst wallSize = this.maze.wallSize;\n\n\tconst canvas = document.getElementById('maze');\n\tif (!canvas || !nodes.length || !this.solved) {\n\t\treturn;\n\t}\n\n\tconst canvas_width = ((this.maze.width * 2) + 1) * wallSize;\n\tconst canvas_height = ((this.maze.height * 2) + 1) * wallSize;\n\n\tif (!((canvas.width === canvas_width) && (canvas.height === canvas_height))) {\n\t\t// Error: Not the expected canvas size.\n\t\treturn;\n\t}\n\n\tconst ctx = canvas.getContext('2d');\n\tctx.fillStyle = this.maze.solveColor;\n\n\tlet max = 0;\n\tlet i;\n\tlet startNode = 0;\n\tlet endNode = nodes.length - 1;\n\tlet finished = false\n\tlet node = false;\n\n\tconst hasGates = (false !== this.start) && (false !== this.finish);\n\tif (hasGates) {\n\t\tstartNode = this.start;\n\t\tendNode = this.finish;\n\t\tconst gateEntry = getEntryNode(this.maze.entryNodes, 'start', true);\n\n\t\tctx.fillRect((gateEntry.x * wallSize), (gateEntry.y * wallSize), wallSize, wallSize);\n\t}\n\n\twhile (finished === false) {\n\t\tmax++\n\t\tif (this.maxSolve && (this.maxSolve < max)) {\n\t\t\talert('Solving maze took too long. Please try again or use smaller maze dimensions');\n\t\t\tbreak\n\t\t}\n\n\t\tif (!node) {\n\t\t\tnode = nodes[startNode];\n\t\t}\n\n\t\tif (i === endNode) {\n\t\t\tfinished = true;\n\t\t\tbreak\n\t\t}\n\n\t\tif (node.previous === \"undefined\" || node.connected === \"undefined\") {\n\t\t\t// Error: Last step or connected nodes doesn't exist.\n\t\t\tbreak;\n\t\t}\n\n\t\tif (!node.connected.hasOwnProperty(node.previous)) {\n\t\t\t// Error: Connected direction doesnt exist.\n\t\t\tbreak;\n\t\t}\n\n\t\ti = node.connected[node.previous];\n\t\tlet connected_node = nodes[i];\n\n\t\tif (-1 !== ['w', 'e'].indexOf(node.previous)) {\n\t\t\tlet start = node.x\n\t\t\tlet to_x = ((connected_node.x - start) * wallSize) + wallSize;\n\n\t\t\tif ('w' === node.previous) {\n\t\t\t\tstart = connected_node.x\n\t\t\t\tto_x = ((node.x - connected_node.x) * wallSize) + wallSize;\n\t\t\t}\n\n\t\t\tctx.fillRect((start * wallSize), (node.y * wallSize), to_x, wallSize);\n\t\t}\n\n\t\tif (-1 !== ['n', 's'].indexOf(node.previous)) {\n\t\t\tlet start = node.y;\n\t\t\tlet to_y = ((connected_node.y - start) * wallSize) + wallSize;\n\n\t\t\tif ('n' === node.previous) {\n\t\t\t\tstart = connected_node.y\n\t\t\t\tto_y = ((node.y - connected_node.y) * wallSize) + wallSize;\n\t\t\t}\n\n\t\t\tctx.fillRect((node.x * wallSize), (start * wallSize), wallSize, to_y);\n\t\t}\n\n\t\tnode = nodes[i];\n\t}\n\n\tif (hasGates) {\n\t\tconst gateExit = getEntryNode(this.maze.entryNodes, 'end', true);\n\t\tctx.fillRect((gateExit.x * wallSize), (gateExit.y * wallSize), wallSize, wallSize);\n\t}\n}"
  },
  {
    "path": "src/utils.js",
    "content": "function isValidHex(hex) {\n\treturn /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/i.test(hex.trim());\n}\n\nfunction replaceAt(str, index, replacement) {\n\t// Replace a character at index in a string\n\tif (index > str.length - 1) {\n\t\treturn str;\n\t}\n\treturn str.substr(0, index) + replacement + str.substr(index + 1);\n}\n\nfunction stringVal(str, index) {\n\t// Get the number value at a specific index in a string (0 or 1)\n\treturn parseInt(str.charAt(index), 10);\n}\n\nfunction getInputIntVal(id, defaultValue) {\n\tconst el = document.getElementById(id);\n\tif (el) {\n\t\tlet el_value = parseInt(el.value, 10);\n\t\tel_value = (0 < el_value) ? el_value : defaultValue;\n\t\tel.value = el_value;\n\t\treturn el_value;\n\t}\n\n\tel.value = defaultValue;\n\treturn defaultValue;\n}\n\nfunction removeFromArray(arr, element) {\n\tconst index = arr.indexOf(element);\n\tif (-1 !== index) {\n\t\tarr.splice(index, 1);\n\t}\n}\n\n/**\n * Randomize array element order in-place.\n * Using Durstenfeld shuffle algorithm.\n */\nfunction shuffleArray(array) {\n\tfor (let i = array.length - 1; i > 0; i--) {\n\t\tlet j = Math.floor(Math.random() * (i + 1));\n\t\tlet temp = array[i];\n\t\tarray[i] = array[j];\n\t\tarray[j] = temp;\n\t}\n}"
  }
]