diff --git a/README.md b/README.md
index d454dca..6908aaa 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,4 @@
-# maze
\ No newline at end of file
+# maze
+
+ - [Preview](https://vpolets.github.io/maze/)
+ - [PullRequest](https://github.com/vpolets/maze/pull/1/files)
\ No newline at end of file
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..ce879eb
--- /dev/null
+++ b/index.html
@@ -0,0 +1,32 @@
+
+
+
+ Maze
+
+
+
+
+
+
+
+
+
+
diff --git a/index.js b/index.js
new file mode 100644
index 0000000..cc58188
--- /dev/null
+++ b/index.js
@@ -0,0 +1,457 @@
+'use strict';
+
+const maze = document.querySelector('#maze');
+const mazeFrame = document.querySelector('#maze-frame');
+
+function mazeSize(height, width) {
+ this.height = height;
+ this.width = width;
+}
+
+function point(i, j) {
+ this.i = i;
+ this.j = j;
+}
+const renderTable = array => {
+ const table = document.createElement('table');
+ const tbody = document.createElement('tbody');
+
+ for (let i = 0; i < array.length; i++) {
+ const tr = document.createElement('tr');
+ for (let j = 0; j < array[i].length; j++) {
+ const td = document.createElement('td');
+ td.setAttribute('value', array[i][j]);
+ if (array[i][j] !== -2) {
+ array[i][j] = -1;
+ }
+ tr.append(td);
+ }
+ tbody.append(tr);
+ }
+
+ table.append(tbody);
+ return table;
+};
+
+const setStartPoint = (cell, table) => {
+ if (table.querySelector('.start-point')) {
+ table.querySelector('.start-point').classList.remove('start-point');
+ }
+ table.rows[cell.i].cells[cell.j].classList.add('start-point');
+};
+
+const setFinishPoint = (cell, table) => {
+ if (table.querySelector('.finish-point')) {
+ table.querySelector('.finish-point').classList.remove('finish-point');
+ }
+ table.rows[cell.i].cells[cell.j].classList.add('finish-point');
+};
+
+const randomInteger = (min, max) => {
+ return Math.round(min - 0.5 + Math.random() * (max - min + 1));
+};
+
+const makeArrayGrid = size => {
+ const width = size.width % 2 === 0 ? +size.width + 1 : size.width;
+ const height = size.height % 2 === 0 ? +size.height + 1 : size.height;
+ const arrayGrid = [];
+ for (let i = 0; i < height; i++) {
+ arrayGrid[i] = [];
+ for (let j = 0; j < width; j++) {
+ if (
+ i > 0 &&
+ i < height - 1 &&
+ j > 0 &&
+ j < width - 1 &&
+ i % 2 === 1 &&
+ j % 2 === 1
+ ) {
+ arrayGrid[i][j] = -1;
+ } else {
+ arrayGrid[i][j] = -2;
+ }
+ }
+ }
+ return arrayGrid;
+};
+const isUnvisited = array => {
+ return array.some(rows => rows.some(el => el === -1));
+};
+const getUnvisitedCells = array => {
+ const unvisited = [];
+
+ array.forEach((row, i) =>
+ row.forEach((cell, j) => {
+ if (cell === -1) {
+ unvisited.push(new point(i, j));
+ }
+ })
+ );
+
+ return unvisited;
+};
+
+const unvisitedNeighbors = (cell, array, radius, value) => {
+ const unvisited = [];
+
+ if (array[cell.i - radius]) {
+ if (array[cell.i - radius][cell.j]) {
+ if (
+ array[cell.i - radius][cell.j] === -1 ||
+ array[cell.i - radius][cell.j] - value > 1
+ ) {
+ unvisited.push(new point(cell.i - radius, cell.j));
+ }
+ }
+ }
+
+ if (array[cell.i][cell.j + radius]) {
+ if (
+ array[cell.i][cell.j + radius] === -1 ||
+ array[cell.i][cell.j + radius] - value > 1
+ ) {
+ unvisited.push(new point(cell.i, cell.j + radius));
+ }
+ }
+
+ if (array[cell.i + radius]) {
+ if (array[cell.i + radius][cell.j]) {
+ if (
+ array[cell.i + radius][cell.j] === -1 ||
+ array[cell.i + radius][cell.j] - value > 1
+ ) {
+ unvisited.push(new point(cell.i + radius, cell.j));
+ }
+ }
+ }
+
+ if (array[cell.i][cell.j - radius]) {
+ if (
+ array[cell.i][cell.j - radius] === -1 ||
+ array[cell.i][cell.j - radius] - value > 1
+ ) {
+ unvisited.push(new point(cell.i, cell.j - radius));
+ }
+ }
+
+ return unvisited;
+};
+
+const removeWall = (curr, sel, array) => {
+ let wall;
+ if (curr.i === sel.i) {
+ if (curr.j > sel.j) {
+ wall = new point(curr.i, curr.j - 1);
+ } else {
+ wall = new point(curr.i, curr.j + 1);
+ }
+ } else if (curr.i > sel.i) {
+ wall = new point(curr.i - 1, curr.j);
+ } else {
+ wall = new point(curr.i + 1, curr.j);
+ }
+ array[wall.i][wall.j] = 2;
+};
+
+const buildMazeArray = size => {
+ const maze = [...makeArrayGrid(size)];
+ const stack = [];
+
+ const startPoint = new point(1, 1);
+ let currentPoint = startPoint;
+
+ while (isUnvisited(maze)) {
+ const unvisited = unvisitedNeighbors(currentPoint, maze, 2);
+ if (unvisited.length > 0) {
+ stack.push(currentPoint);
+ const selected = unvisited[randomInteger(0, unvisited.length - 1)];
+ removeWall(currentPoint, selected, maze);
+ currentPoint = selected;
+ maze[currentPoint.i][currentPoint.j] = 2;
+ } else if (stack.length > 0) {
+ currentPoint = stack.pop();
+ } else {
+ const unvisitedCells = getUnvisitedCells(maze);
+ currentPoint =
+ unvisitedCells[randomInteger(0, unvisitedCells.length - 1)];
+ maze[currentPoint.i][currentPoint.j] = 2;
+ }
+ }
+
+ return maze;
+};
+
+const findExit = (mazeArray, start, finish) => {
+ const array = mazeArray.map(function(arr) {
+ return arr.slice();
+ });
+ const stack = [start];
+
+ let entranceFound = false;
+ let currentPoint = start;
+ array[currentPoint.i][currentPoint.j] = 0;
+ let step = array[currentPoint.i][currentPoint.j];
+ do {
+ step++;
+ const unvisited = unvisitedNeighbors(currentPoint, array, 1, step);
+ if (unvisited.length > 0) {
+ const selected = unvisited[0];
+ if (finish.i === selected.i && finish.j === selected.j) {
+ entranceFound = true;
+ array[selected.i][selected.j] = step;
+ currentPoint = stack.pop();
+ step = array[currentPoint.i][currentPoint.j];
+ } else {
+ stack.push(currentPoint);
+ currentPoint = selected;
+ array[currentPoint.i][currentPoint.j] = step;
+ }
+ } else if (stack.length > 0) {
+ currentPoint = stack.pop();
+ step = array[currentPoint.i][currentPoint.j];
+ } else if (finish.i === start.i && finish.j === start.j) {
+ alert('Remove start or finish poit to another cell.');
+ entranceFound = !entranceFound;
+ } else {
+ alert("There isn't an exit!");
+ entranceFound = !entranceFound;
+ }
+ } while (!entranceFound || stack.length);
+
+ return array;
+};
+
+const renderWay = (array, table, callback) => {
+ let params = [];
+ const max = Math.max(...[].concat(...array));
+
+ for (let i = 0; i <= max; i++) {
+ params.push([]);
+ }
+
+ array.forEach((row, i) =>
+ row.forEach((cell, j) => {
+ if (cell !== -2 && cell !== -1) {
+ params[cell].push(new point(i, j));
+ }
+ })
+ );
+
+ const func = (array, table, counter) => {
+ array.forEach(
+ cell => (table.rows[cell.i].cells[cell.j].textContent = counter)
+ );
+ };
+
+ const stepsTimeout = (params, counter) => {
+ if (!params.length) {
+ callback();
+ return;
+ }
+ const currentParams = params.shift();
+
+ func(currentParams, table, counter++);
+ setTimeout(() => {
+ stepsTimeout(params, counter);
+ }, 30);
+ };
+
+ let counter = 0;
+ stepsTimeout(params, counter);
+};
+
+const findSmallestNeighbor = (cell, array) => {
+ let smallest;
+
+ if (array[cell.i - 1]) {
+ if (array[cell.i - 1][cell.j]) {
+ if (
+ array[cell.i - 1][cell.j] > -1 &&
+ array[cell.i - 1][cell.j] - array[cell.i][cell.j] === -1
+ ) {
+ smallest = new point(cell.i - 1, cell.j);
+ }
+ }
+ }
+
+ if (array[cell.i][cell.j + 1]) {
+ if (
+ array[cell.i][cell.j + 1] > -1 &&
+ array[cell.i][cell.j + 1] - array[cell.i][cell.j] === -1
+ ) {
+ smallest = new point(cell.i, cell.j + 1);
+ }
+ }
+
+ if (array[cell.i + 1]) {
+ if (array[cell.i + 1][cell.j]) {
+ if (
+ array[cell.i + 1][cell.j] > -1 &&
+ array[cell.i + 1][cell.j] - array[cell.i][cell.j] === -1
+ ) {
+ smallest = new point(cell.i + 1, cell.j);
+ }
+ }
+ }
+
+ if (array[cell.i][cell.j - 1]) {
+ if (
+ array[cell.i][cell.j - 1] > -1 &&
+ array[cell.i][cell.j - 1] - array[cell.i][cell.j] === -1
+ ) {
+ smallest = new point(cell.i, cell.j - 1);
+ }
+ }
+
+ return smallest;
+};
+
+const showShortestWay = (array, table, finish, start) => {
+ const shortWay = [];
+ let current = finish;
+
+ while (array[current.i][current.j] !== 1) {
+ current = findSmallestNeighbor(current, array);
+ shortWay.push(current);
+ }
+
+ const func = cell => {
+ table.rows[cell.i].cells[cell.j].classList.add('short');
+ };
+
+ const stepsTimeout = (params, counter) => {
+ if (!params.length) return;
+ const currentParams = params.pop();
+
+ func(currentParams);
+ setTimeout(() => {
+ stepsTimeout(params);
+ }, 30);
+ };
+
+ stepsTimeout(shortWay);
+};
+
+const renderControllButtons = frame => {
+ const block = document.createElement('div');
+ block.classList.add('controll-buttons');
+
+ const setStartButton = document.createElement('button');
+ const setFinishtButton = document.createElement('button');
+ const setWallButton = document.createElement('button');
+ const findPathButton = document.createElement('button');
+
+ setStartButton.id = 'set-start';
+ setFinishtButton.id = 'set-finish';
+ setWallButton.id = 'set-wall';
+ findPathButton.id = 'find-path';
+
+ setStartButton.textContent = 'Set start';
+ setFinishtButton.textContent = 'Set finish';
+ setWallButton.textContent = 'Set wall';
+ findPathButton.textContent = 'Find shortest path';
+
+ block.append(setStartButton, setFinishtButton, setWallButton, findPathButton);
+ frame.append(block);
+};
+
+//Maze Controlls
+
+document.getElementById('generate-maze').addEventListener('click', event => {
+ const height = document.getElementById('maze-height').value;
+ const width = document.getElementById('maze-width').value;
+ const size = new mazeSize(height, width);
+ const mazeArray = buildMazeArray(size);
+
+ let startPoint = new point(1, 1);
+ let finishPoint = new point(
+ mazeArray.length - 2,
+ mazeArray[mazeArray.length - 2].length - 2
+ );
+
+ if (maze.querySelector('table')) {
+ maze.querySelector('table').remove();
+ mazeFrame.querySelector('.controll-buttons').remove();
+ }
+
+ maze.append(renderTable(mazeArray));
+ renderControllButtons(document.getElementById('maze-frame'));
+ let grid = maze.querySelector('tbody');
+ setStartPoint(startPoint, grid);
+ setFinishPoint(finishPoint, grid);
+ let currentAction = 3;
+
+ mazeFrame
+ .querySelector('.controll-buttons')
+ .addEventListener('click', event => {
+ /*Legend to currentAction
+ ** 1 - Set Start
+ ** 2 - Set Finish
+ ** 3 - Set Wall
+ */
+
+ const target = event.target;
+ if (target.id === 'set-start') {
+ currentAction = 1;
+ }
+
+ if (target.id === 'set-finish') {
+ currentAction = 2;
+ }
+
+ if (target.id === 'set-wall') {
+ currentAction = 3;
+ }
+
+ if (target.id === 'find-path') {
+ maze.querySelector('table').remove();
+ maze.prepend(renderTable(mazeArray));
+ grid = maze.querySelector('tbody');
+ setStartPoint(startPoint, grid);
+ setFinishPoint(finishPoint, grid);
+ let ways = findExit(mazeArray, startPoint, finishPoint);
+ renderWay(ways, grid, () => {
+ showShortestWay(ways, grid, finishPoint, startPoint);
+ });
+ }
+ return;
+ });
+
+ document.addEventListener('click', event => {
+ if (event.target.tagName !== 'TD') {
+ return;
+ }
+ const target = event.target;
+ const coordX = target.cellIndex;
+ const coordY = target.parentNode.sectionRowIndex;
+
+ if (currentAction === 1) {
+ startPoint = new point(coordY, coordX);
+ setStartPoint(startPoint, maze.querySelector('tbody'));
+ }
+
+ if (currentAction === 2) {
+ finishPoint = new point(coordY, coordX);
+ setFinishPoint(finishPoint, maze.querySelector('tbody'));
+ mazeArray[finishPoint.i][finishPoint.j] = -1;
+ }
+
+ if (currentAction === 3) {
+ if (
+ +maze
+ .querySelector('tbody')
+ .rows[coordY].cells[coordX].getAttribute('value') === -2
+ ) {
+ maze
+ .querySelector('tbody')
+ .rows[coordY].cells[coordX].setAttribute('value', '0');
+ mazeArray[coordY][coordX] = -1;
+ } else {
+ maze
+ .querySelector('tbody')
+ .rows[coordY].cells[coordX].setAttribute('value', '-2');
+ mazeArray[coordY][coordX] = -2;
+ }
+ }
+ });
+});
diff --git a/style.css b/style.css
new file mode 100644
index 0000000..6e165cd
--- /dev/null
+++ b/style.css
@@ -0,0 +1,61 @@
+#maze-frame {
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ text-align: center;
+ }
+
+ #controlls {
+ padding: 10px 70px 10px;
+ margin-bottom: 10px;
+ border: 2px solid rgb(156, 156, 156);
+ }
+
+ #maze-frame .controll-input {
+ width: 40px;
+ }
+
+ #maze table {
+ border-collapse: collapse;
+ width: auto;
+ font-family: Lora, serif;
+ font-size: 9px;
+ }
+
+ #maze {
+ }
+
+ #maze td {
+ width: 20px;
+ height: 20px;
+ background: #fff;
+ border: 1px solid #000;
+ cursor: pointer;
+ }
+
+ #maze table tbody td[value='-2'] {
+ background: #000;
+ }
+
+ #maze table tbody td.start-point {
+ background: #2e7d32;
+ color: #2e7d32;
+ }
+
+ #maze table tbody td.finish-point {
+ background: #e53935;
+ color: #e53935;
+ }
+ #maze table td.short {
+ background: #ffe600;
+ }
+
+ #maze-frame .controll-buttons {
+ margin-top: 25px;
+ width: 380px;
+ display: flex;
+ justify-content: space-between;
+ }
+
\ No newline at end of file