Full Code of keesiemeijer/maze-generator for AI

master 29da89fd5b43 cached
11 files
49.0 KB
15.1k tokens
27 symbols
1 requests
Download .txt
Repository: keesiemeijer/maze-generator
Branch: master
Commit: 29da89fd5b43
Files: 11
Total size: 49.0 KB

Directory structure:
gitextract_6ojt6g7z/

├── LICENSE
├── README.md
├── index.html
└── src/
    ├── app.js
    ├── color-picker.js
    ├── entries.js
    ├── globals.js
    ├── human-colours-en-gb.js
    ├── maze.js
    ├── solver.js
    └── utils.js

================================================
FILE CONTENTS
================================================

================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2017 Kees Meijer

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# maze-generator
Create mazes using the [recursive backtracking algorithm](https://en.wikipedia.org/wiki/Maze_generation_algorithm#Recursive_backtracker).

Check out the [maze generator demo](https://keesiemeijer.github.io/maze-generator/).

**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.

![maze](https://user-images.githubusercontent.com/1436618/106612888-e1d90600-6569-11eb-87cf-2477b2578598.png)

### LICENSE

MIT


================================================
FILE: index.html
================================================
<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8"/>
		<title>Maze generator</title>
		<style type="text/css">
			body {
				font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;
				margin: 0 1em 1em;
			}
			p label, .color-picker label {
				width: 10em;
				display: inline-block;
			}
			p.desc, #download {
				font-size: smaller;
			}
			p.desc {
				border-top: 1px solid #afafaf;
				padding-top: 1em;

			}
			.hide {
				display: none;
			}
			.controls {
				padding-bottom: 1em;
			}
			canvas {
				/* if canvas overflows body */
				margin-right: 1em;
			}
			.color-picker {
				margin: 16px 0;
				display:flex;
				position: relative;
			}
			.color-sample {
				width: 1em;
				border: 1px solid #000;
				margin: 0 .5em;
			}
			.color-picker {
				margin-left: 5px;
			}
			.palette {
				margin: 1.5em 0 0 10em;
				width: 270px;
				background: #efefef;
				border: 1px solid #bcbcbc;
				border-radius: 1px;
				display: none;
				padding: 1px;
				position: absolute;
				z-index: 1000;
			}
			.palette div {
				width: 18px;
				height: 18px;
				margin: 3px;
				cursor:pointer;
				display: inline-block;
			}
			.palette div:focus {
				outline-width: 2px;
				outline-style: dashed;
			}
			.screen-reader-text {
				border: 0;
				clip: rect(1px, 1px, 1px, 1px);
				-webkit-clip-path: inset(50%);
				clip-path: inset(50%);
				height: 1px;
				margin: -1px;
				overflow: hidden;
				padding: 0;
				position: absolute;
				width: 1px;
				word-wrap: normal !important; /* many screen reader and browser combinations announce broken words as they would appear visually */
			}

			noscript p {
				border: 1px solid red;
				padding: 1em;
			}

			#generate {
				border-top: 1px solid #afafaf;
				padding-top: 1em;
			}

			button {
				font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;
				padding:5px 1em;
				margin-right: .5em;
				font-size: .9em;
				background:#3377ff;
				color: #fff;
				border:0 none;
				cursor:pointer;
				-webkit-border-radius: 3px;
				border-radius: 5px;
			}
			button:hover {
				background:#0055ff;
			}
		</style>
	</head>
	<body>
		<h1>Maze Generator</h1>
		<p>
			Create, 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>
		<p>
			The <a href="https://en.wikipedia.org/wiki/Maze_generation_algorithm#Recursive_backtracker">recursive backtracking algorithm</a> is used to create the mazes.
			For more information check out the <a href="https://github.com/keesiemeijer/maze-generator">Github Repository</a>.
		</p>
		<hr>
		<div class="controls">
			<p>
				<label for="wall-size">Wall thickness:</label>
				<input id="wall-size" type="number" name="" value="10" min="1">
			</p>

			<p>
				<label for="width">Columns:</label>
				<input id="width" type="number" name="width" value="20" min="3" max="200">
			</p>

			<p>
				<label for="height">Rows:</label>
				<input id="height" type="number" name="height" value="20" min="3" max="200">
			</p>
			<p>
				<label for="entry">Maze entries:</label>
				<select id="entry" name="entry">
					<option value="">none</option>
					<option value="diagonal" selected="selected">diagonal</option>
					<option value="horizontal">left and right</option>
					<option value="vertical">top and bottom</option>
				</select>
			</p>
			<p>
				<label for="bias">Bias:</label>
				<select id="bias" name="bias">
					<option value="">none</option>
					<option value="horizontal">horizontal</option>
					<option value="vertical">vertical</option>
				</select>
			</p>
			<p class='desc'>
				The maze can be solved in multiple ways if you remove maze walls. (maximum <span>300</span> walls)<br/>
				The <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.
			</p>
			<p style='padding-bottom: 0; margin-bottom: 0;'>
				<label for="remove_walls">Remove maze walls:</label>
				<input id="remove_walls" type="number" name="remove_walls" value="0" min="0" max="200">
			</p>
			<p class='desc'>
				Click the colors below to select a color from a color pallete.
			</p>
			<div class="color-picker" data-default="#ffffff">
				<label for="backgroundColor">Background Color</label> 
				<input type="text" class="" id="backgroundColor" name="backgroundColor" value="#ffffff"><div class="color-sample" style="background-color:#ffffff"></div>
			</div>
			<div class="color-picker" data-default="#000000">
				<label for="color">Maze Color</label> 
				<input type="text" class="" id="color" name="color" value="#000000"><div class="color-sample" style="background-color:#000000"></div>
			</div>
			<div class="color-picker" data-default="#cc3737">
				<label for="solveColor">Solve Color</label> 
				<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>
			</div>
			<div id="generate">
				<button id="create-maze" onclick="initMaze();" class='hide'>Generate Maze</button>
				<button id="solve" onclick="initSolve();" class='hide'>Solve Maze</button>
				<a href="" class="hide" id="download">download maze</a>
			</div>
		</div>
		<noscript>
			<p>Sorry... this site requires JavaScript to generate a maze. Please enable it in your browser</p>
		</noscript>
		<script type="text/javascript">
			const createMazeButton = document.getElementById('create-maze');
			createMazeButton.classList.toggle("hide")
		</script>
		<canvas id="maze"></canvas>
		<script type="text/javascript" src="src/globals.js"></script>
		<script type="text/javascript" src="src/utils.js"></script>
		<script type="text/javascript" src="src/entries.js"></script>
		<script type="text/javascript" src="src/maze.js"></script>
		<script type="text/javascript" src="src/solver.js"></script>
		<script type="text/javascript" src="src/app.js"></script>
		<script type="text/javascript" src="src/human-colours-en-gb.js"></script>
		<script type="text/javascript" src="src/color-picker.js"></script>
	</body>
</html>

================================================
FILE: src/app.js
================================================
// Global variables
let mazeNodes = {};

// Check if globals are defined
if (typeof maxMaze === 'undefined') {
    maxMaze = 0;
}

if (typeof maxSolve === 'undefined') {
    maxSolve = 0;
}

if (typeof maxCanvas === 'undefined') {
    maxCanvas = 0;
}

if (typeof maxCanvasDimension === 'undefined') {
    maxCanvasDimension = 0;
}

if (typeof maxWallsRemove === 'undefined') {
    maxWallsRemove = 300;
}

// Update remove max walls html
const removeMaxWallsText = document.querySelector('.desc span');
if (removeMaxWallsText) {
    removeMaxWallsText.innerHTML = maxWallsRemove;
}

const removeWallsInput = document.getElementById('remove_walls');
if (removeWallsInput) {
    removeWallsInput.max = maxWallsRemove;
}

const download = document.getElementById("download");
download.addEventListener("click", downloadImage, false);
download.setAttribute('download', 'maze.png');

function initMaze() {
    download.setAttribute('download', 'maze.png');
    download.innerHTML = 'download maze';

    const settings = {
        width: getInputIntVal('width', 20),
        height: getInputIntVal('height', 20),
        wallSize: getInputIntVal('wall-size', 10),
        removeWalls: getInputIntVal('remove_walls', 0),
        entryType: '',
        bias: '',
        color: '#000000',
        backgroundColor: '#FFFFFF',
        solveColor: '#cc3737',

        // restrictions
        maxMaze: maxMaze,
        maxCanvas: maxCanvas,
        maxCanvasDimension: maxCanvasDimension,
        maxSolve: maxSolve,
        maxWallsRemove: maxWallsRemove,
    }

    const colors = ['color', 'backgroundColor', 'solveColor'];
    for (let i = 0; i < colors.length; i++) {
        const colorInput = document.getElementById(colors[i]);
        settings[colors[i]] = colorInput.value
        if (!isValidHex(settings[colors[i]])) {
            let defaultColor = colorInput.parentNode.dataset.default;
            colorInput.value = defaultColor;
            settings[colors[i]] = defaultColor;
        }

        const colorSample = colorInput.parentNode.querySelector('.color-sample');
        colorSample.style = 'background-color: ' + settings[colors[i]] + ';';
    }

    if (settings['removeWalls'] > maxWallsRemove) {
        settings['removeWalls'] = maxWallsRemove;
        if (removeWallsInput) {
            removeWallsInput.value = maxWallsRemove;
        }
    }

    const entry = document.getElementById('entry');
    if (entry) {
        settings['entryType'] = entry.options[entry.selectedIndex].value;
    }

    const bias = document.getElementById('bias');
    if (bias) {
        settings['bias'] = bias.options[bias.selectedIndex].value;
    }

    const maze = new Maze(settings);
    maze.generate();
    maze.draw();

    if (download && download.classList.contains('hide')) {
        download.classList.toggle("hide");
    }

    const solveButton = document.getElementById("solve");
    if (solveButton && solveButton.classList.contains('hide')) {
        solveButton.classList.toggle("hide");
    }

    mazeNodes = {}
    if (maze.matrix.length) {
        mazeNodes = maze;
    }

    location.href = "#";
    location.href = "#generate";
}

function downloadImage(e) {
    const image = document.getElementById('maze').toDataURL("image/png");
    image.replace("image/png", "image/octet-stream");
    download.setAttribute("href", image);
}

function initSolve() {
    const solveButton = document.getElementById("solve");
    if (solveButton) {
        solveButton.classList.toggle("hide");
    }

    download.setAttribute('download', 'maze-solved.png');
    download.innerHTML = 'download solved maze';

    if ((typeof mazeNodes.matrix === 'undefined') || !mazeNodes.matrix.length) {
        return;
    }

    const solver = new Solver(mazeNodes);
    solver.solve();
    if (mazeNodes.wallsRemoved) {
        solver.drawAstarSolve();
    } else {
        solver.draw();
    }

    mazeNodes = {}
}

================================================
FILE: src/color-picker.js
================================================
(function() {

	// Amount of colors in a row
	const row = 11;

	// Classes
	const colorPickerClass = 'color-picker';
	const colorSampleClass = 'color-sample';
	const paletteClass = 'palette';
	const screenReaderClass = 'screen-reader-text';

	// Todo: Use object with color description for accessibility
	var hexColors = [
		'#000000',
		'#191919',
		'#323232',
		'#4b4b4b',
		'#646464',
		'#7d7d7d',
		'#969696',
		'#afafaf',
		'#c8c8c8',
		'#e1e1e1',
		'#ffffff',
		'#820000',
		'#9b0000',
		'#b40000',
		'#cd0000',
		'#e60000',
		'#ff0000',
		'#ff1919',
		'#ff3232',
		'#ff4b4b',
		'#ff6464',
		'#ff7d7d',
		'#823400',
		'#9b3e00',
		'#b44800',
		'#cd5200',
		'#e65c00',
		'#ff6600',
		'#ff7519',
		'#ff8532',
		'#ff944b',
		'#ffa364',
		'#ffb27d',
		'#828200',
		'#9b9b00',
		'#b4b400',
		'#cdcd00',
		'#e6e600',
		'#ffff00',
		'#ffff19',
		'#ffff32',
		'#ffff4b',
		'#ffff64',
		'#ffff7d',
		'#003300',
		'#004d00',
		'#008000',
		'#00b300',
		'#00cc00',
		'#00e600',
		'#1aff1a',
		'#4dff4d',
		'#66ff66',
		'#80ff80',
		'#b3ffb3',
		'#001a4d',
		'#002b80',
		'#003cb3',
		'#004de6',
		'#0000ff',
		'#0055ff',
		'#3377ff',
		'#4d88ff',
		'#6699ff',
		'#80b3ff',
		'#b3d1ff',
		'#003333',
		'#004d4d',
		'#006666',
		'#009999',
		'#00cccc',
		'#00ffff',
		'#1affff',
		'#33ffff',
		'#4dffff',
		'#80ffff',
		'#b3ffff',
		'#4d004d',
		'#602060',
		'#660066',
		'#993399',
		'#ac39ac',
		'#bf40bf',
		'#c653c6',
		'#cc66cc',
		'#d279d2',
		'#d98cd9',
		'#df9fdf',
		'#660029',
		'#800033',
		'#b30047',
		'#cc0052',
		'#e6005c',
		'#ff0066',
		'#ff1a75',
		'#ff3385',
		'#ff4d94',
		'#ff66a3',
		'#ff99c2',
	];

	const colorPickers = document.querySelectorAll('.' + colorPickerClass);
	const palette = '<div class="' + paletteClass + '" style="display: none;"></div>';
	let paletteHasFocus = false;
	let desc = "Use a hex color code or use the tab key to select a color.";
	desc += ' Use the arrow keys to scroll through all colors. Use the space or return key to select the color.';
	desc += ' Use the escape key to close the palette.'

	for (let i = 0; i < colorPickers.length; i++) {
		// Create aria describedby element for the color input
		var describedby = document.createElement("p");
		describedby.style.display = 'none';
		describedby.id = 'desc-' + i;
		describedby.innerHTML = desc;

		// Insert describedby description
		colorPickers[i].insertAdjacentElement('afterbegin', describedby)

		const colorInput = colorPickers[i].querySelector('input');

		// Show color palette on input focus
		colorInput.addEventListener("focus", showColorPalette, false);

		// Check if tab key is used to focus a color in the palette
		colorInput.addEventListener("keydown", inputTabPressed, false);

		// Update color sample after key up
		colorInput.addEventListener("keyup", updateColorSample, false);

		// Add describedby attribute
		colorInput.setAttribute("aria-describedby", 'desc-' + i);

		// Insert color palette
		colorPickers[i].insertAdjacentHTML('beforeend', palette);

		// Get inserted palette
		const colorPalette = colorPickers[i].querySelector('.' + paletteClass);

		for (let j = 0; j < hexColors.length; j++) {
			var colorDiv = document.createElement("div");
			var colorDivText = document.createElement("span");
			colorDivText.className = screenReaderClass;
			colorDivText.innerHTML = hexColors[j];
			colorDiv.appendChild(colorDivText);

			// Make color divs tabbable.
			colorDiv.tabIndex = 0;
			colorDiv.style = 'background-color: ' + hexColors[j] + ';';
			colorDiv.setAttribute("role", "button");
			colorDiv.setAttribute('data-index', j + 1);
			colorPalette.appendChild(colorDiv);


			// Get RGB color from background
			let rgbColor = colorDiv.style.backgroundColor;

			// Get human readable colorname
			let colorLabel = getHumanReadableColor(rgbColor, hexColors[j]);
			if (colorLabel.length) {
				colorDiv.setAttribute("aria-label", colorLabel);
			}

			// Check if a color is the new focused element
			colorDiv.addEventListener("blur", colorBlur);

			// Navigate colors in palette
			colorDiv.addEventListener("keyup", colorNavigation, false);
		}

		colorPalette.onmouseenter = function() {
			paletteHasFocus = true;
		}
		colorPalette.onmouseleave = function() {
			paletteHasFocus = false;
		}

		// Close palette if palette or color is clicked
		colorPalette.addEventListener("click", paletteClick, false);

		// Hide colorpalette if paletteHasFocus is false
		colorInput.addEventListener("focusout", hideColorPalette);
	}

	let colorSample = document.querySelectorAll('.' + colorSampleClass)
	for (let i = 0; i < colorSample.length; i++) {
		// Set initial color same as default (should already be set in HTML)
		let defaultColor = colorSample[i].parentNode.dataset.default;
		colorSample[i].style = 'background-color: ' + defaultColor + ';';

		// Get RGB color from background
		let rgbColor = colorSample[i].style.backgroundColor;

		// Add span for human readable text
		let colorSampleText = document.createElement("span");
		colorSampleText.className = screenReaderClass;
		colorSample[i].appendChild(colorSampleText);

		// Update human readable text
		updateColorSampleText(colorSample[i], defaultColor);

		// Display palette if sample is clicked
		colorSample[i].addEventListener("click", showColorPalette, false);
	}

	function colorBlur(e) {
		// Check what element has focus
		if (e.relatedTarget === null) {
			// No element has focus
			this.parentNode.style.display = 'none';
			paletteHasFocus = false;

		} else {
			if (paletteClass !== e.relatedTarget.parentNode.className) {
				// No element in the palette has focus
				this.parentNode.style.display = 'none';
				paletteHasFocus = false;
			}
		}
	}

	function showColorPalette(e) {
		this.parentNode.querySelector('input').focus();

		let palette = this.parentNode.querySelector('.' + paletteClass);
		palette.style.display = 'block';
	}

	function hideColorPalette(e) {
		let colorPalette = this.parentNode.querySelector('.' + paletteClass);
		if (paletteHasFocus === false) {
			colorPalette.style.display = 'none';
		}
	}

	function paletteClick(e) {
		if (paletteClass !== e.target.className) {
			// Get the clicked color
			let hexColor = rgbToHex(e.target.style.backgroundColor);

			this.parentNode.querySelector('input').value = hexColor;
			let colorSample = this.parentNode.querySelector('.' + colorSampleClass);

			colorSample.style = 'background-color: ' + hexColor + ';';
			updateColorSampleText(colorSample, hexColor)

		}
		// Hide palette
		this.style.display = 'none';
		paletteHasFocus = false;
	}

	function colorNavigation(e) {
		// Select color with space or enter
		if (13 === e.which || 32 === e.which) {
			let hexColor = rgbToHex(this.style.backgroundColor);

			this.parentNode.parentNode.querySelector('input').value = hexColor;
			let colorSample = this.parentNode.parentNode.querySelector('.' + colorSampleClass);
			colorSample.style = 'background-color: ' + hexColor + ';';
			updateColorSampleText(colorSample, hexColor)

			this.parentNode.style.display = 'none';
			paletteHasFocus = false;
			return;
		}

		if(27 === e.which) {
			this.parentNode.style.display = 'none';
			paletteHasFocus = false;
			return;
		}

		let index = 0;

		// Palette navigation with arrow keys
		if (40 === e.which) {
			// down arrow
			index = parseInt(this.dataset.index, 10) + row;
		} else if (38 === e.which) {
			// up arrow
			index = parseInt(this.dataset.index, 10) - row;
		} else if (37 === e.which) {
			// left arrow
			index = parseInt(this.dataset.index, 10) - 1;
		} else if (39 === e.which) {
			// right arrow
			index = parseInt(this.dataset.index, 10) + 1;
		} else {
			// Not a navigation key
			return;
		}

		if (0 >= index || hexColors.length < index) {
			return;
		}

		let next = this.parentNode.querySelector('[data-index="' + index + '"]');
		if (next) {
			next.focus();
		}
	}

	function inputTabPressed(e) {
		// Tab key to go to the first color in the palette
		if (9 === e.which) {
			// Palette has focus if a color has focus
			paletteHasFocus = true;
		}
	}

	function updateColorSample(e) {
		// Update colorsample if it's a valid color
		if (isValidHex(this.value)) {
			let colorSample = this.parentNode.querySelector('.' + colorSampleClass);
			colorSample.style = 'background-color: ' + this.value + ';';
			updateColorSampleText(colorSample, this.value);
		}
	}

	function updateColorSampleText(el, hexColor) {
		let span = el.querySelector('span');
		let rgbColor = el.style.backgroundColor;

		let readableColor = getHumanReadableColor(rgbColor, hexColor);
		if (readableColor.length) {
			span.innerHTML = readableColor;
		}
	}

	function getHumanReadableColor(rgbColor, hexColor) {
		if (typeof HumanColours === "undefined") {
			return hexColor;
		}

		let arr = rgbColor.replace('rgb', '').replace('(', '').replace(')', '').split(',');
		let hslColor = rgbToHsl(arr[0], arr[1], arr[2]);
		hslColor = 'hsl(' + hslColor.join(',') + ')';
		let readable = new HumanColours(hslColor);

		return 'Color ' + readable.hueName() + ', ' + readable.saturationName() + ', ' + readable.lightnessName();
	}

	function isValidHex(hex) {
		return /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/i.test(hex.trim());
	}

	function componentToHex(c) {
		var hex = c.toString(16);
		return hex.length == 1 ? "0" + hex : hex;
	}

	function rgbToHex(color) {
		arr = color.replace('rgb', '').replace('(', '').replace(')', '').split(',');
		return "#" + componentToHex(Number(arr[0])) + componentToHex(Number(arr[1])) + componentToHex(Number(arr[2]));
	}

	function rgbToHsl(r, g, b) {
		r /= 255, g /= 255, b /= 255;

		var max = Math.max(r, g, b),
			min = Math.min(r, g, b);
		var h, s, l = (max + min) / 2;

		if (max == min) {
			h = s = 0; // achromatic
		} else {
			var d = max - min;
			s = l > 0.5 ? d / (2 - max - min) : d / (max + min);

			switch (max) {
				case r:
					h = (g - b) / d + (g < b ? 6 : 0);
					break;
				case g:
					h = (b - r) / d + 2;
					break;
				case b:
					h = (r - g) / d + 4;
					break;
			}

			h /= 6;
		}

		return [Math.floor(h * 360), Math.floor(s * 100), Math.floor(l * 100)];
	}
})();

================================================
FILE: src/entries.js
================================================
function getEntryNode( entries, type, gate = false ) {
	if ( !hasEntries( entries ) ) {
		return false;
	}

	if( 'start' === type ) {
		return gate ? entries.start.gate : {'x': entries.start.x, 'y': entries.start.y};
	}

	if( 'end' === type ) {
		return gate ? entries.end.gate : {'x': entries.end.x, 'y': entries.end.y};
	}

	return false;
}

function hasEntries( entries ) {
	if ( entries.hasOwnProperty( 'start' ) && entries.hasOwnProperty( 'end' ) ) {
		return true;
	}

	return false;
}

================================================
FILE: src/globals.js
================================================
// Maze Restrictions.
//
// The higher the values the less restricted.
// Set values to 0 for no restriction.
//
// Be aware that the larger the maze, the more memory is consumed.
// With recursive backtracking the whole maze is stored in memory.

const maxMaze = 75000;
const maxSolve = 30000;
const maxCanvas = 16080100;
const maxCanvasDimension = 32760;

// Maximum walls you can remove
const maxWallsRemove = 300;


================================================
FILE: src/human-colours-en-gb.js
================================================
(function(global) {

  var regex = /hsl\((.*)\)/, //Match hsl values
      h, //Hue
      s, //Saturation
      l, //Lightness
      hue,
      sat,
      light;

  function HumanColours(hsl){
    this.HSL = hsl;
    this.values = this.HSL.replace(regex, '$1').split(',');
  }
  
  HumanColours.prototype = {
    getHSL: function() {
      return this.HSL;
    },
    
    getHue: function() {
      return this.values[0];
    },
    
    getSaturation: function() {
      return this.values[1].replace('%', '');
    },
  
    getLightness: function() {
      return this.values[2].replace('%', '');
    },
    
    hueName: function() {
      h = this.getHue();
  
      if ( h < 15 ) { hue = 'red'; }
      if ( h === 15 ) { hue = 'reddish'; }
      if ( h > 15 ) { hue = 'orange'; }
      if ( h > 45 ) { hue = 'yellow'; }
      if ( h > 70 ) { hue = 'lime'; }
      if ( h > 79 ) { hue = 'green'; }
      if ( h > 163 ) { hue = 'cyan'; }
      if ( h > 193 ) { hue = 'blue'; }
      if ( h > 240 ) { hue = 'indigo'; }
      if ( h > 260 ) { hue = 'violet'; }
      if ( h > 270 ) { hue = 'purple'; }
      if ( h > 291 ) { hue = 'magenta'; }
      if ( h > 327 ) { hue = 'rose'; }
      if ( h > 344 ) { hue = 'red'; }
  
      return hue;
    },
    
    saturationName: function() {
      s = this.getSaturation();
  
      if( s < 4) { sat =  'grey'; }
      if( s > 3) { sat =  'almost grey'; }
      if( s > 10) { sat =  'very unsaturated'; }
      if( s > 30) { sat =  'unsaturated'; }
      if( s > 46) { sat =  'rather unsaturated'; }
      if( s > 60) { sat =  'saturated'; }
      if( s > 80) { sat =  'rather saturated'; }
      if( s > 90) { sat =  'very saturated'; }
  
      return sat;
    },
    
    lightnessName: function() {
      l = this.getLightness();
  
      if( l < 10 ) { light = 'almost black'; }
      if( l > 9 ) { light = 'very dark'; }
      if( l > 22 ) { light = 'dark'; }
      if( l > 30 ) { light = 'normal?'; }
      if( l > 60 ) { light = 'light'; }
      if( l > 80 ) { light = 'very light'; }
      if( l > 94 ) { light = 'almost white'; }
  
      return light;
    }
  };

  global.HumanColours = HumanColours;

}(this));


================================================
FILE: src/maze.js
================================================
function Maze(args) {
	const defaults = {
		width: 20,
		height: 20,
		wallSize: 10,
		entryType: '',
		bias: '',
		color: '#000000',
		backgroundColor: '#FFFFFF',
		solveColor: '#cc3737',
		removeWalls: 0,

		// Maximum 300 walls can be removed
		maxWallsRemove: 300,

		// No restrictions
		maxMaze: 0,
		maxCanvas: 0,
		maxCanvasDimension: 0,
		maxSolve: 0,
	}

	const settings = Object.assign({}, defaults, args);

	this.matrix = [];
	this.wallsRemoved = 0;
	this.width = parseInt(settings['width'], 10);
	this.height = parseInt(settings['height'], 10);
	this.wallSize = parseInt(settings['wallSize'], 10);
	this.removeWalls = parseInt(settings['removeWalls'], 10);
	this.entryNodes = this.getEntryNodes(settings['entryType']);
	this.bias = settings['bias'];
	this.color = settings['color'];
	this.backgroundColor = settings['backgroundColor'];
	this.solveColor = settings['solveColor'];
	this.maxMaze = parseInt(settings['maxMaze'], 10);
	this.maxCanvas = parseInt(settings['maxCanvas'], 10);
	this.maxCanvasDimension = parseInt(settings['maxCanvasDimension'], 10);
	this.maxSolve = parseInt(settings['maxSolve'], 10);
	this.maxWallsRemove = parseInt(settings['maxWallsRemove'], 10);
}

Maze.prototype.generate = function() {
	if (!this.isValidSize()) {
		this.matrix = [];
		alert('Please use smaller maze dimensions');
		return;
	}

	let nodes = this.generateNodes();
	nodes = this.parseMaze(nodes);
	this.getMatrix(nodes);
	this.removeMazeWalls();
}

Maze.prototype.isValidSize = function() {
	const max = this.maxCanvasDimension;
	const canvas_width = ((this.width * 2) + 1) * this.wallSize;
	const canvas_height = ((this.height * 2) + 1) * this.wallSize;

	// Max dimension Firefox and Chrome
	if (max && ((max <= canvas_width) || (max <= canvas_height))) {
		return false;
	}

	// Max area (200 columns) * (200 rows) with wall size 10px
	if (this.maxCanvas && (this.maxCanvas <= (canvas_width * canvas_height))) {
		return false;
	}

	return true;
}

Maze.prototype.generateNodes = function() {
	const count = this.width * this.height;
	let nodes = [];

	for (let i = 0; i < count; i++) {
		// visited, nswe
		nodes[i] = "01111";
	}

	return nodes;
}

Maze.prototype.parseMaze = function(nodes) {

	const mazeSize = nodes.length;
	const positionIndex = { 'n': 1, 's': 2, 'w': 3, 'e': 4, };
	const oppositeIndex = { 'n': 2, 's': 1, 'w': 4, 'e': 3 };

	if (!mazeSize) {
		return;
	}

	let max = 0;
	let moveNodes = [];
	let visited = 0;
	let position = parseInt(Math.floor(Math.random() * nodes.length), 10);

	let biasCount = 0;
	let biasFactor = 3;
	if (this.bias) {
		if (('horizontal' === this.bias)) {
			biasFactor = (1 <= (this.width / 100)) ? Math.floor(this.width / 100) + 2 : 3;
		} else if ('vertical' === this.bias) {
			biasFactor = (1 <= (this.height / 100)) ? Math.floor(this.height / 100) + 2 : 3;
		}
	}

	// Set start node visited.
	nodes[position] = replaceAt(nodes[position], 0, 1);

	while (visited < (mazeSize - 1)) {
		biasCount++;

		max++;
		if (this.maxMaze && (this.maxMaze < max)) {
			alert('Please use smaller maze dimensions');
			move_nodes = [];
			this.matrix = [];
			return [];
		}

		let next = this.getNeighbours(position);
		let directions = Object.keys(next).filter(function(key) {
			return (-1 !== next[key]) && !stringVal(this[next[key]], 0);
		}, nodes);

		if (this.bias && (biasCount !== biasFactor)) {
			directions = this.biasDirections(directions);
		} else {
			biasCount = 0;
		}

		if (directions.length) {
			++visited;

			if (1 < directions.length) {
				moveNodes.push(position);
			}

			let direction = directions[Math.floor(Math.random() * directions.length)];

			// Update current position
			nodes[position] = replaceAt(nodes[position], positionIndex[direction], 0);
			// Set new position
			position = next[direction];

			// Update next position
			nodes[position] = replaceAt(nodes[position], oppositeIndex[direction], 0);
			nodes[position] = replaceAt(nodes[position], 0, 1);
		} else {
			if (!moveNodes.length) {
				break;
			}

			position = moveNodes.pop();
		}
	}

	return nodes;
}


Maze.prototype.getMatrix = function(nodes) {
	const mazeSize = this.width * this.height;

	// Add the complete maze in a matrix
	// where 1 is a wall and 0 is a corridor.

	let row1 = '';
	let row2 = '';

	if (nodes.length !== mazeSize) {
		return;
	}

	for (let i = 0; i < mazeSize; i++) {
		row1 += !row1.length ? '1' : '';
		row2 += !row2.length ? '1' : '';

		if (stringVal(nodes[i], 1)) {
			row1 += '11';
			if (stringVal(nodes[i], 4)) {
				row2 += '01';
			} else {
				row2 += '00';
			}
		} else {
			let hasAbove = nodes.hasOwnProperty(i - this.width);
			let above = hasAbove && stringVal(nodes[i - this.width], 4);
			let hasNext = nodes.hasOwnProperty(i + 1);
			let next = hasNext && stringVal(nodes[i + 1], 1);

			if (stringVal(nodes[i], 4)) {
				row1 += '01';
				row2 += '01';
			} else if (next || above) {
				row1 += '01';
				row2 += '00';
			} else {
				row1 += '00';
				row2 += '00';
			}
		}

		if (0 === ((i + 1) % this.width)) {
			this.matrix.push(row1);
			this.matrix.push(row2);
			row1 = '';
			row2 = '';
		}
	}

	// Add closing row
	this.matrix.push('1'.repeat((this.width * 2) + 1));
}

Maze.prototype.getEntryNodes = function(access) {
	const y = ((this.height * 2) + 1) - 2;
	const x = ((this.width * 2) + 1) - 2;

	let entryNodes = {};

	if ('diagonal' === access) {
		entryNodes.start = { 'x': 1, 'y': 1, 'gate': { 'x': 0, 'y': 1 } };
		entryNodes.end = { 'x': x, 'y': y, 'gate': { 'x': x + 1, 'y': y } };
	}

	if ('horizontal' === access || 'vertical' === access) {
		let xy = ('horizontal' === access) ? y : x;
		xy = ((xy - 1) / 2);
		let even = (xy % 2 === 0);
		xy = even ? xy + 1 : xy;

		let start_x = ('horizontal' === access) ? 1 : xy;
		let start_y = ('horizontal' === access) ? xy : 1;
		let end_x = ('horizontal' === access) ? x : (even ? start_x : start_x + 2);
		let end_y = ('horizontal' === access) ? (even ? start_y : start_y + 2) : y;
		let startgate = ('horizontal' === access) ? { 'x': 0, 'y': start_y } : { 'x': start_x, 'y': 0 };
		let endgate = ('horizontal' === access) ? { 'x': x + 1, 'y': end_y } : { 'x': end_x, 'y': y + 1 };

		entryNodes.start = { 'x': start_x, 'y': start_y, 'gate': startgate };
		entryNodes.end = { 'x': end_x, 'y': end_y, 'gate': endgate };
	}

	return entryNodes;
}

Maze.prototype.biasDirections = function(directions) {

	const horizontal = (-1 !== directions.indexOf('w')) || (-1 !== directions.indexOf('e'));
	const vertical = (-1 !== directions.indexOf('n')) || (-1 !== directions.indexOf('s'));

	if (('horizontal' === this.bias) && horizontal) {
		directions = directions.filter(function(key) {
			return (('w' === key) || ('e' === key))
		});
	} else if (('vertical' === this.bias) && vertical) {
		directions = directions.filter(function(key) {
			return (('n' === key) || ('s' === key))
		});
	}

	return directions;
}

Maze.prototype.getNeighbours = function(pos) {
	return {
		'n': (0 <= (pos - this.width)) ? pos - this.width : -1,
		's': ((this.width * this.height) > (pos + this.width)) ? pos + this.width : -1,
		'w': ((0 < pos) && (0 !== (pos % this.width))) ? pos - 1 : -1,
		'e': (0 !== ((pos + 1) % this.width)) ? pos + 1 : -1,
	};
}

Maze.prototype.removeWall = function(row, index) {
	// Remove wall if possible.
	const evenRow = (row % 2 === 0);
	const evenIndex = (index % 2 === 0);
	const wall = stringVal(this.matrix[row], index);

	if (!wall) {
		return false;
	}

	if (!evenRow && evenIndex) {
		// Uneven row and even column
		const hasTop = (row - 2 > 0) && (1 === stringVal(this.matrix[row - 2], index));
		const hasBottom = (row + 2 < this.matrix.length) && (1 === stringVal(this.matrix[row + 2], index));

		if (hasTop && hasBottom) {
			this.matrix[row] = replaceAt(this.matrix[row], index, '0');
			return true;
		} else if (!hasTop && hasBottom) {
			const left = 1 === stringVal(this.matrix[row - 1], index - 1);
			const right = 1 === stringVal(this.matrix[row - 1], index + 1);
			if (left || right) {
				this.matrix[row] = replaceAt(this.matrix[row], index, '0');
				return true;
			}
		} else if (!hasBottom && hasTop) {
			const left = 1 === stringVal(this.matrix[row + 1], index - 1);
			const right = 1 === stringVal(this.matrix[row + 1], index + 1);
			if (left || right) {
				this.matrix[row] = replaceAt(this.matrix[row], index, '0');
				return true;
			}
		}

	} else if (evenRow && !evenIndex) {
		// Even row and uneven column
		const hasLeft = 1 === stringVal(this.matrix[row], index - 2);
		const hasRight = 1 === stringVal(this.matrix[row], index + 2);

		if (hasLeft && hasRight) {
			this.matrix[row] = replaceAt(this.matrix[row], index, '0');
			return true;
		} else if (!hasLeft && hasRight) {
			const top = 1 === stringVal(this.matrix[row - 1], index - 1);
			const bottom = 1 === stringVal(this.matrix[row + 1], index - 1);
			if (top || bottom) {
				this.matrix[row] = replaceAt(this.matrix[row], index, '0');
				return true;
			}
		} else if (!hasRight && hasLeft) {
			const top = 1 === stringVal(this.matrix[row - 1], index + 1);
			const bottom = 1 === stringVal(this.matrix[row + 1], index + 1);
			if (top || bottom) {
				this.matrix[row] = replaceAt(this.matrix[row], index, '0');
				return true;
			}
		}
	}

	return false;
}

Maze.prototype.removeMazeWalls = function() {
	if (!this.removeWalls || !this.matrix.length) {
		return;
	}

	const min = 1;
	const max = this.matrix.length - 1;
	const maxTries = this.maxWallsRemove;
	let tries = 0;

	while (tries < maxTries) {
		tries++;

		// Did we reached the goal
		if (this.wallsRemoved >= this.removeWalls) {
			break;
		}

		// Get random row from matrix
		let y = Math.floor(Math.random() * (max - min + 1)) + min;
		y = (y === max) ? y - 1 : y;

		let walls = [];
		let row = this.matrix[y];

		// Get walls from random row
		for (let i = 0; i < row.length; i++) {
			if (i === 0 || i === row.length - 1) {
				continue;
			}

			const wall = stringVal(row, i);
			if (wall) {
				walls.push(i);
			}
		}

		// Shuffle walls randomly
		shuffleArray(walls);

		// Try breaking a wall for this row.
		for (let i = 0; i < walls.length; i++) {
			if (this.removeWall(y, walls[i])) {

				// Wall can be broken
				this.wallsRemoved++;
				break;
			}
		}
	}
}

Maze.prototype.draw = function() {
	const canvas = document.getElementById('maze');
	if (!canvas || !this.matrix.length) {
		return;
	}

	if (!this.isValidSize()) {
		this.matrix = [];
		alert('Please use smaller maze dimensions');
		return;
	}

	canvas.width = ((this.width * 2) + 1) * this.wallSize;;
	canvas.height = ((this.height * 2) + 1) * this.wallSize;

	const ctx = canvas.getContext('2d');
	ctx.clearRect(0, 0, canvas.width, canvas.height);

	// Add background
	ctx.fillStyle = this.backgroundColor;
	ctx.fillRect(0, 0, canvas.width, canvas.height);

	// Set maze collor
	ctx.fillStyle = this.color;

	const row_count = this.matrix.length;
	const gateEntry = getEntryNode(this.entryNodes, 'start', true);
	const gateExit = getEntryNode(this.entryNodes, 'end', true);

	for (let i = 0; i < row_count; i++) {
		let row_length = this.matrix[i].length;
		for (let j = 0; j < row_length; j++) {
			if (gateEntry && gateExit) {
				if ((j === gateEntry.x) && (i === gateEntry.y)) {
					continue;
				}
				if ((j === gateExit.x) && (i === gateExit.y)) {
					continue;
				}
			}
			let pixel = parseInt(this.matrix[i].charAt(j), 10);
			if (pixel) {
				ctx.fillRect((j * this.wallSize), (i * this.wallSize), this.wallSize, this.wallSize);
			}
		}
	}
}

================================================
FILE: src/solver.js
================================================
function Solver(maze) {
	this.maze = maze;
	this.maxSolve = maze.maxSolve;
	this.start = false;
	this.finish = false;
	this.solved = false;
	this.path = false;
}

Solver.prototype.solve = function() {
	const startPosition = getEntryNode(this.maze.entryNodes, 'start');
	const endPosition = getEntryNode(this.maze.entryNodes, 'end');

	// Get nodes (from the maze matrix) that have connections to other nodes.
	const nodes = this.getMazeSolveNodes(startPosition, endPosition);

	// Get the connections for every solve node.
	const connected = this.connectMazeSolveNodes(nodes);

	if (this.maze.wallsRemoved) {
		this.path = this.walkMazeAstar(connected);
	} else {
		this.path = this.walkMaze(connected);
	}
}

Solver.prototype.getMazeSolveNodes = function(start, end) {
	const matrix = this.maze.matrix;
	const nodes = [];

	// Property used (by both solvers) to find and draw the path to the exit
	const previous = undefined;

	const rowCount = matrix.length;
	for (let y = 0; y < rowCount; y++) {

		if (y === 0 || y === (rowCount - 1) || (0 === (y % 2))) {
			// First and last rows are walls only.
			// Even rows don't have any connections
			continue;
		}

		let rowLength = matrix[y].length;
		for (let x = 0; x < rowLength; x++) {
			if (stringVal(matrix[y], x)) {
				// Walls don't have connections.
				continue;
			}

			const nswe = {
				'n': (0 < y) && stringVal(matrix[y - 1], x),
				's': (rowCount > y) && stringVal(matrix[y + 1], x),
				'w': (0 < x) && stringVal(matrix[y], (x - 1)),
				'e': (rowLength > x) && stringVal(matrix[y], (x + 1))
			}

			if (start && end) {
				if ((x === start.x) && (y === start.y)) {
					this.start = nodes.length;
					nodes.push({ x, y, nswe, previous });
					continue;
				}

				if ((x === end.x) && (y === end.y)) {
					this.finish = nodes.length;
					nodes.push({ x, y, nswe, previous });
					continue;
				}
			}

			// Walls left or right
			if (nswe['w'] || nswe['e']) {
				// left or right direction possible
				if (!nswe['w'] || !nswe['e']) {
					nodes.push({ x, y, nswe, previous });
					continue;

				} else {
					// Up or down direction possible.
					if ((!nswe['n'] && nswe['s']) || (nswe['n'] && !nswe['s'])) {
						nodes.push({ x, y, nswe, previous });
						continue;
					}
				}
			} else {
				// All directions possible
				if (!nswe['n'] && !nswe['s'] && !nswe['w'] && !nswe['e']) {
					nodes.push({ x, y, nswe, previous });
					continue;
				} else {
					// Up or down direction possible.
					if ((!nswe['n'] && nswe['s']) || (nswe['n'] && !nswe['s'])) {
						nodes.push({ x, y, nswe, previous });
						continue;
					}
				}
			}
		} // x loop
	} // y loop

	return nodes;
}

Solver.prototype.connectMazeSolveNodes = function(nodes) {
	// Connect nodes to their neighbours.
	const y_nodes = {};
	const nodes_length = nodes.length;

	for (let i = 0; i < nodes_length; i++) {
		nodes[i]['connected'] = {};
		let x = nodes[i]['x'];
		let y = nodes[i]['y'];

		if (!nodes[i]['nswe']['w']) {
			nodes[i]['connected']['w'] = i - 1;
		}

		if (!nodes[i]['nswe']['e']) {
			nodes[i]['connected']['e'] = i + 1;
		}

		if (!nodes[i]['nswe']['n'] && y_nodes.hasOwnProperty(x)) {
			nodes[i]['connected']['n'] = y_nodes[x];

			if (nodes.hasOwnProperty(y_nodes[x])) {
				nodes[y_nodes[x]]['connected']['s'] = i;
				delete y_nodes[x];
			}
		}

		if (!nodes[i]['nswe']['s']) {
			y_nodes[x] = i;
		}

		if (this.maze.wallsRemoved) {
			// Not needed for A star solve
			delete nodes[i]['nswe'];
		}
	}

	return nodes;
}

Solver.prototype.heuristic = function(a, b) {
	return Math.abs(a.x - b.x) + Math.abs(a.y - b.y);
}

Solver.prototype.walkMazeAstar = function(nodes) {
	this.solved = false;

	if (!nodes.length) {
		return;
	}

	let openSet = [];
	let closedSet = [];

	let startNode = 0;
	let endNode = nodes.length - 1;
	if ((false !== this.start) && (false !== this.finish)) {
		startNode = this.start;
		endNode = this.finish;
	}

	// Add defaults to all nodes before we walk the maze.
	nodes.forEach( e => {
		e['f'] = 0;
		e['g'] = 0;
		e['h'] = 0;
	});

	openSet.push(startNode);

	let max = 0;

	while (openSet.length > 0) {
		max++
		if (this.maxSolve && (this.maxSolve < max)) {
			alert('Solving maze took too long. Please try again or use smaller maze dimensions');
			break
		}

		// Best next option
		let winner = 0;
		for (let i = 0; i < openSet.length; i++) {
			if (nodes[openSet[i]].f < nodes[openSet[winner]].f) {
				winner = i;
			}
		}

		var current = nodes[openSet[winner]];
		let currentKey = openSet[winner]

		// Did I finish?
		if (current === nodes[endNode]) {
			this.solved = true;
			break;
		}

		removeFromArray(openSet, openSet[winner]);
		closedSet.push(currentKey);

		let neighbors = [];
		for (key in current.connected) {
			if (current.connected.hasOwnProperty(key)) {
				neighbors.push(current.connected[key]);
			}
		}

		for (let i = 0; i < neighbors.length; i++) {
			let neighbor = nodes[neighbors[i]];

			// Valid next spot?
			if (!closedSet.includes(neighbors[i])) {
				let tempG = current.g + this.heuristic(neighbor, current);

				// Is this a better path than before?
				let newPath = false;
				if (openSet.includes(neighbors[i])) {
					if (tempG < neighbor.g) {
						neighbor.g = tempG;
						newPath = true;
					}
				} else {
					neighbor.g = tempG;
					newPath = true;
					openSet.push(neighbors[i]);
				}

				// Yes, it's a better path
				if (newPath) {
					neighbor.h = this.heuristic(neighbor, nodes[endNode]);
					neighbor.f = neighbor.g + neighbor.h;
					neighbor.previous = currentKey;
				}
			}
		}
	}

	path = [];
	let temp = current;
	path.push(temp);
	while (temp.previous) {
		path.push(nodes[temp.previous]);
		temp = nodes[temp.previous];
	}

	// Add the startNode for drawing the solved path.
	path.push(nodes[startNode]);

	return path;
}

Solver.prototype.walkMaze = function(nodes) {
	this.solved = false;

	if (!nodes.length) {
		return;
	}

	let startNode = 0;
	let endNode = nodes.length - 1;
	if ((false !== this.start) && (false !== this.finish)) {
		startNode = this.start;
		endNode = this.finish;
	}

	let max = 0;
	let i = 0;
	let node = false;
	let from = false;
	const multi_nodes = [];
	const opposite = { 'n': 's', 's': 'n', 'w': 'e', 'e': 'w' };

	while (this.solved === false) {
		max++
		if (this.maxSolve && (this.maxSolve < max)) {
			alert('Solving maze took too long. Please try again or use smaller maze dimensions');
			break
		}

		if (!node) {
			i = startNode;
			node = nodes[i];
		}

		if (i === endNode) {
			// Found the end node.
			this.solved = true;
			break
		}

		node['count'] = 4 - (Object.keys(node['nswe'])
				.map(key => !node['nswe'][key] ? 0 : 1)
				.reduce((a, b) => a + b, 0));

		if (node.count > 2) {
			if (-1 === multi_nodes.indexOf(i)) {
				multi_nodes.push(i);
			}
		}

		if (false !== from) {
			node['nswe'][from] = 1;
			node.count--;
			nodes[i] = node;
		}

		if (0 === node.count) {
			from = false;

			if (!multi_nodes.length) {
				// Jump back to start.
				i = startNode;
				node = nodes[startNode];
				continue;
			}

			// Jump back to multiple directions node
			i = multi_nodes.pop();
			node = nodes[i];

			if (node.count > 1) {
				// Add multi node back if more than one option left
				multi_nodes.push(i);
			}
			continue;
		}

		let directions = Object.keys(node['nswe']).filter(key => !node['nswe'][key] ? true : false);
		let direction = directions[Math.floor(Math.random() * directions.length)];

		if (node.count >= 1) {
			node.count--;
			from = opposite[direction];
			node['nswe'][direction] = 1;
			node['previous'] = direction;
			nodes[i] = node;
		}

		if (node['connected'].hasOwnProperty(direction)) {
			i = node['connected'][direction];
			node = nodes[i];
		} else {
			// Error: Node is not connected to direction
			break;
		}
	}

	return nodes;
}

Solver.prototype.drawAstarSolve = function() {
	const nodes = this.path;
	const wallSize = this.maze.wallSize;

	const canvas = document.getElementById('maze');
	if (!canvas || !nodes.length || !this.solved) {
		return;
	}

	const canvas_width = ((this.maze.width * 2) + 1) * wallSize;
	const canvas_height = ((this.maze.height * 2) + 1) * wallSize;

	if (!((canvas.width === canvas_width) && (canvas.height === canvas_height))) {
		// Error: Not the expected canvas size.
		return;
	}

	const ctx = canvas.getContext('2d');
	ctx.fillStyle = this.maze.solveColor;

	let startNode = 0;
	let endNode = nodes.length - 1;
	let finished = false
	let node = false;

	const hasGates = (false !== this.start) && (false !== this.finish);
	if (hasGates) {
		startNode = this.start;
		endNode = this.finish;
		const gateEntry = getEntryNode(this.maze.entryNodes, 'start', true);

		ctx.fillRect((gateEntry.x * wallSize), (gateEntry.y * wallSize), wallSize, wallSize);
	}

	for (let i = nodes.length - 1; i >= 0; i--) {
		if (!(0 <= (i - 1))) {
			continue;
		}

		let previousX = nodes[i - 1].x;
		let previousY = nodes[i - 1].y;

		let start;
		let to_x;
		if (nodes[i].y === previousY) {
			let start = nodes[i].x
			let to_x = ((previousX - start) * wallSize) + wallSize;

			if (nodes[i].x > previousX) {
				start = previousX
				to_x = ((nodes[i].x - previousX) * wallSize) + wallSize;
			}

			ctx.fillRect((start * wallSize), (nodes[i].y * wallSize), to_x, wallSize);
		}

		if (nodes[i].x === previousX) {
			let start = nodes[i].y;
			let to_y = ((previousY - start) * wallSize) + wallSize;

			if (nodes[i].y > previousY) {
				start = previousY;
				to_y = ((nodes[i].y - previousY) * wallSize) + wallSize;
			}

			ctx.fillRect((nodes[i].x * wallSize), (start * wallSize), wallSize, to_y);
		}
	}

	if (hasGates) {
		const gateExit = getEntryNode(this.maze.entryNodes, 'end', true);
		ctx.fillRect((gateExit.x * wallSize), (gateExit.y * wallSize), wallSize, wallSize);
	}
}

Solver.prototype.draw = function() {
	const nodes = this.path;
	const wallSize = this.maze.wallSize;

	const canvas = document.getElementById('maze');
	if (!canvas || !nodes.length || !this.solved) {
		return;
	}

	const canvas_width = ((this.maze.width * 2) + 1) * wallSize;
	const canvas_height = ((this.maze.height * 2) + 1) * wallSize;

	if (!((canvas.width === canvas_width) && (canvas.height === canvas_height))) {
		// Error: Not the expected canvas size.
		return;
	}

	const ctx = canvas.getContext('2d');
	ctx.fillStyle = this.maze.solveColor;

	let max = 0;
	let i;
	let startNode = 0;
	let endNode = nodes.length - 1;
	let finished = false
	let node = false;

	const hasGates = (false !== this.start) && (false !== this.finish);
	if (hasGates) {
		startNode = this.start;
		endNode = this.finish;
		const gateEntry = getEntryNode(this.maze.entryNodes, 'start', true);

		ctx.fillRect((gateEntry.x * wallSize), (gateEntry.y * wallSize), wallSize, wallSize);
	}

	while (finished === false) {
		max++
		if (this.maxSolve && (this.maxSolve < max)) {
			alert('Solving maze took too long. Please try again or use smaller maze dimensions');
			break
		}

		if (!node) {
			node = nodes[startNode];
		}

		if (i === endNode) {
			finished = true;
			break
		}

		if (node.previous === "undefined" || node.connected === "undefined") {
			// Error: Last step or connected nodes doesn't exist.
			break;
		}

		if (!node.connected.hasOwnProperty(node.previous)) {
			// Error: Connected direction doesnt exist.
			break;
		}

		i = node.connected[node.previous];
		let connected_node = nodes[i];

		if (-1 !== ['w', 'e'].indexOf(node.previous)) {
			let start = node.x
			let to_x = ((connected_node.x - start) * wallSize) + wallSize;

			if ('w' === node.previous) {
				start = connected_node.x
				to_x = ((node.x - connected_node.x) * wallSize) + wallSize;
			}

			ctx.fillRect((start * wallSize), (node.y * wallSize), to_x, wallSize);
		}

		if (-1 !== ['n', 's'].indexOf(node.previous)) {
			let start = node.y;
			let to_y = ((connected_node.y - start) * wallSize) + wallSize;

			if ('n' === node.previous) {
				start = connected_node.y
				to_y = ((node.y - connected_node.y) * wallSize) + wallSize;
			}

			ctx.fillRect((node.x * wallSize), (start * wallSize), wallSize, to_y);
		}

		node = nodes[i];
	}

	if (hasGates) {
		const gateExit = getEntryNode(this.maze.entryNodes, 'end', true);
		ctx.fillRect((gateExit.x * wallSize), (gateExit.y * wallSize), wallSize, wallSize);
	}
}

================================================
FILE: src/utils.js
================================================
function isValidHex(hex) {
	return /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/i.test(hex.trim());
}

function replaceAt(str, index, replacement) {
	// Replace a character at index in a string
	if (index > str.length - 1) {
		return str;
	}
	return str.substr(0, index) + replacement + str.substr(index + 1);
}

function stringVal(str, index) {
	// Get the number value at a specific index in a string (0 or 1)
	return parseInt(str.charAt(index), 10);
}

function getInputIntVal(id, defaultValue) {
	const el = document.getElementById(id);
	if (el) {
		let el_value = parseInt(el.value, 10);
		el_value = (0 < el_value) ? el_value : defaultValue;
		el.value = el_value;
		return el_value;
	}

	el.value = defaultValue;
	return defaultValue;
}

function removeFromArray(arr, element) {
	const index = arr.indexOf(element);
	if (-1 !== index) {
		arr.splice(index, 1);
	}
}

/**
 * Randomize array element order in-place.
 * Using Durstenfeld shuffle algorithm.
 */
function shuffleArray(array) {
	for (let i = array.length - 1; i > 0; i--) {
		let j = Math.floor(Math.random() * (i + 1));
		let temp = array[i];
		array[i] = array[j];
		array[j] = temp;
	}
}
Download .txt
gitextract_6ojt6g7z/

├── LICENSE
├── README.md
├── index.html
└── src/
    ├── app.js
    ├── color-picker.js
    ├── entries.js
    ├── globals.js
    ├── human-colours-en-gb.js
    ├── maze.js
    ├── solver.js
    └── utils.js
Download .txt
SYMBOL INDEX (27 symbols across 7 files)

FILE: src/app.js
  function initMaze (line 40) | function initMaze() {
  function downloadImage (line 116) | function downloadImage(e) {
  function initSolve (line 122) | function initSolve() {

FILE: src/color-picker.js
  function colorBlur (line 218) | function colorBlur(e) {
  function showColorPalette (line 234) | function showColorPalette(e) {
  function hideColorPalette (line 241) | function hideColorPalette(e) {
  function paletteClick (line 248) | function paletteClick(e) {
  function colorNavigation (line 265) | function colorNavigation(e) {
  function inputTabPressed (line 316) | function inputTabPressed(e) {
  function updateColorSample (line 324) | function updateColorSample(e) {
  function updateColorSampleText (line 333) | function updateColorSampleText(el, hexColor) {
  function getHumanReadableColor (line 343) | function getHumanReadableColor(rgbColor, hexColor) {
  function isValidHex (line 356) | function isValidHex(hex) {
  function componentToHex (line 360) | function componentToHex(c) {
  function rgbToHex (line 365) | function rgbToHex(color) {
  function rgbToHsl (line 370) | function rgbToHsl(r, g, b) {

FILE: src/entries.js
  function getEntryNode (line 1) | function getEntryNode( entries, type, gate = false ) {
  function hasEntries (line 17) | function hasEntries( entries ) {

FILE: src/human-colours-en-gb.js
  function HumanColours (line 11) | function HumanColours(hsl){

FILE: src/maze.js
  function Maze (line 1) | function Maze(args) {

FILE: src/solver.js
  function Solver (line 1) | function Solver(maze) {

FILE: src/utils.js
  function isValidHex (line 1) | function isValidHex(hex) {
  function replaceAt (line 5) | function replaceAt(str, index, replacement) {
  function stringVal (line 13) | function stringVal(str, index) {
  function getInputIntVal (line 18) | function getInputIntVal(id, defaultValue) {
  function removeFromArray (line 31) | function removeFromArray(arr, element) {
  function shuffleArray (line 42) | function shuffleArray(array) {
Condensed preview — 11 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (56K chars).
[
  {
    "path": "LICENSE",
    "chars": 1068,
    "preview": "MIT License\n\nCopyright (c) 2017 Kees Meijer\n\nPermission is hereby granted, free of charge, to any person obtaining a cop"
  },
  {
    "path": "README.md",
    "chars": 713,
    "preview": "# maze-generator\nCreate mazes using the [recursive backtracking algorithm](https://en.wikipedia.org/wiki/Maze_generation"
  },
  {
    "path": "index.html",
    "chars": 6306,
    "preview": "<!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\tbo"
  },
  {
    "path": "src/app.js",
    "chars": 3922,
    "preview": "// Global variables\nlet mazeNodes = {};\n\n// Check if globals are defined\nif (typeof maxMaze === 'undefined') {\n    maxMa"
  },
  {
    "path": "src/color-picker.js",
    "chars": 10095,
    "preview": "(function() {\n\n\t// Amount of colors in a row\n\tconst row = 11;\n\n\t// Classes\n\tconst colorPickerClass = 'color-picker';\n\tco"
  },
  {
    "path": "src/entries.js",
    "chars": 491,
    "preview": "function getEntryNode( entries, type, gate = false ) {\n\tif ( !hasEntries( entries ) ) {\n\t\treturn false;\n\t}\n\n\tif( 'start'"
  },
  {
    "path": "src/globals.js",
    "chars": 418,
    "preview": "// Maze Restrictions.\n//\n// The higher the values the less restricted.\n// Set values to 0 for no restriction.\n//\n// Be a"
  },
  {
    "path": "src/human-colours-en-gb.js",
    "chars": 2171,
    "preview": "(function(global) {\n\n  var regex = /hsl\\((.*)\\)/, //Match hsl values\n      h, //Hue\n      s, //Saturation\n      l, //Lig"
  },
  {
    "path": "src/maze.js",
    "chars": 11509,
    "preview": "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\tcolo"
  },
  {
    "path": "src/solver.js",
    "chars": 12302,
    "preview": "function Solver(maze) {\n\tthis.maze = maze;\n\tthis.maxSolve = maze.maxSolve;\n\tthis.start = false;\n\tthis.finish = false;\n\tt"
  },
  {
    "path": "src/utils.js",
    "chars": 1149,
    "preview": "function isValidHex(hex) {\n\treturn /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/i.test(hex.trim());\n}\n\nfunction replaceAt(str, in"
  }
]

About this extraction

This page contains the full source code of the keesiemeijer/maze-generator GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 11 files (49.0 KB), approximately 15.1k tokens, and a symbol index with 27 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!