Commit c09bbeb
Changed files (4)
004.Maze-Runner
004.Maze-Runner/index.html
@@ -0,0 +1,69 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>004.Maze-Runner</title>
+ <link rel="stylesheet" href="./style.css">
+ <link rel="stylesheet" href="../assets/common.css">
+ <link rel="shortcut icon" href="../assets/favicon.png" type="image/png">
+</head>
+
+<body>
+ <!-- Modal Section -->
+ <input class="modal-state" id="modal" type="checkbox" />
+ <div class="modal">
+ <label class="modal__bg" for="modal"></label>
+ <div class="modal__inner" style="width: 30%;">
+ <label class="modal__close" for="modal"></label>
+ <h2>004.Maze-Runner</h2>
+ <p class="modal__p">
+ <ul>
+ <li>A simple maze game which uses the concept of
+ <span style="color:var(--lightGold)">
+ backtracking (DFS)
+ </span>
+ to generate the maze.
+ </li>
+ <li>
+ Initially the starting cell is marked as visited.
+ </li>
+ <li>
+ If the current cell has any neighbours which have not been visited, then randomly selected neighbour
+ is
+ marked as visited and made as the current cell.
+ </li>
+ <li>
+ If the current cell has no unvisited neighbours,then the current cell is backtracked to the previous
+ cell.
+ </li>
+ <li>
+ The process continues until all cells have been visited.
+ Refer to
+ <a href="https://en.wikipedia.org/wiki/Maze_generation_algorithm#Randomized_depth-first_search:~:text=branch%20before%20backtracking.-,Recursive%20implementation,-%5Bedit%5D"
+ target="_blank" style="color:var(--lightGold)">
+ Maze Generation Algorithm
+ </a>
+ </li>
+ </ul>
+ </p>
+ </div>
+ </div>
+
+ <!-- Main Content -->
+ <h1>Maze Runner</h1>
+ <p id="tooltip"></p>
+ <div id="maze">
+
+
+ </div>
+ <!-- Footer -->
+ <div class="footer">
+ <label for="modal">(i)</label>
+ <a class="arrow" href="../"></a>
+ </div>
+</body>
+<script src="./script.js"></script>
+
+</html>
\ No newline at end of file
004.Maze-Runner/script.js
@@ -0,0 +1,160 @@
+let cells;
+let maze_created = false;
+let won = false;
+
+const rows = 15;
+const cols = 15;
+const visited = new Set();
+const tooltip = document.getElementById("tooltip");
+
+
+function getRandomUnVisitedNeighbour(current) {
+ const neighbors = [];
+
+ const row = Math.floor(current / cols);
+ const col = current % cols;
+
+ if (row > 0) {
+ neighbors.push(current - cols);
+ }
+
+ if (row < rows - 1) {
+ neighbors.push(current + cols);
+ }
+
+ if (col > 0) {
+ neighbors.push(current - 1);
+ }
+
+ if (col < cols - 1) {
+ neighbors.push(current + 1);
+ }
+
+ const unvisited = neighbors.filter(neighbour => !visited.has(neighbour));
+ if (unvisited.length === 0) {
+ return null;
+ }
+ const randomNeighbour = unvisited[Math.floor(Math.random() * unvisited.length)];
+ return randomNeighbour;
+}
+
+function removeWall(current, next) {
+ /*
+Explanation of the following code:
+If the direction is 1, it means that the random neighbour is to the right of the current cell
+If the direction is -1, it means that the random neighbour is to the left of the current cell
+If the direction is cols, it means that the random neighbour is below the current cell
+If the direction is -cols, it means that the random neighbour is above the current cell
+*/
+ const direction = next - current;
+ if (direction === -cols) {
+ cells[current].style.borderTop = "none";
+ cells[next].style.borderBottom = "none";
+ }
+ if (direction === cols) {
+ cells[current].style.borderBottom = "none";
+ cells[next].style.borderTop = "none";
+ }
+ if (direction === -1) {
+ cells[current].style.borderLeft = "none";
+ cells[next].style.borderRight = "none";
+ }
+ if (direction === 1) {
+ cells[current].style.borderRight = "none";
+ cells[next].style.borderLeft = "none";
+ }
+}
+
+async function sleep(ms) {
+ return new Promise(resolve => setTimeout(resolve, ms));
+}
+
+async function dfs(maze,start) {
+ const stack = [start];
+ while (stack.length > 0){
+ const current = stack.pop();
+ maze.children[current].classList.add("current-gen");
+ visited.add(current);
+ const next = getRandomUnVisitedNeighbour(current);
+ if (next) {
+ removeWall(current, next);
+ stack.push(current);
+ stack.push(next);
+ }
+ await sleep(10);
+ maze.children[current].classList.remove("current-gen");
+ }
+ maze.children[start].classList.add("current");
+ maze_created = true;
+ tooltip.innerHTML = "Use W A S D or arrow keys to move";
+}
+
+
+function movement(maze,movement){
+ const current = document.querySelector(".current");
+ const index = Array.from(maze.children).indexOf(current);
+ if(movement === "UP" && current.style.borderTop === "none"){
+ maze.children[index].classList.remove("current");
+ maze.children[index-cols].classList.add("current");
+ }
+ if(movement === "DOWN" && current.style.borderBottom === "none"){
+ maze.children[index].classList.remove("current");
+ maze.children[index+cols].classList.add("current");
+ }
+ if(movement === "LEFT" && current.style.borderLeft === "none"){
+ maze.children[index].classList.remove("current");
+ maze.children[index-1].classList.add("current");
+ }
+ if(movement === "RIGHT" && current.style.borderRight === "none"){
+ maze.children[index].classList.remove("current");
+ maze.children[index+1].classList.add("current");
+ }
+ if(document.querySelector(".current").classList.contains("final")){
+ document.querySelector(".current").classList.add("won");
+ won = true;
+ tooltip.innerHTML = "You won! Press F5 to play again";
+ }
+}
+
+
+document.addEventListener("DOMContentLoaded", async function () {
+ tooltip.innerHTML = "Generating maze..."
+ const maze = document.querySelector("#maze")
+ for (let i = 0; i < rows; i++) {
+ for (let j = 0; j < cols; j++) {
+ const cell_element = document.createElement("div");
+ cell_element.classList.add("cell");
+ maze.appendChild(cell_element);
+ }
+ }
+
+ const initial_position = 0;
+ const final_position = rows * cols - 1;
+
+ cells = document.querySelectorAll(".cell");
+ await dfs(maze,initial_position);
+ cells[final_position].classList.add("final");
+
+ document.addEventListener("keydown", function (event) {
+ if(!maze_created || won){
+ return;
+ }
+ if(event.key ==="w" || event.key === "W" || event.key === "ArrowUp"){
+ movement(maze,"UP");
+ }
+ if(event.key ==="s" || event.key === "S" || event.key === "ArrowDown"){
+ movement(maze,"DOWN");
+ }
+ if(event.key ==="a" || event.key === "A" || event.key === "ArrowLeft"){
+ movement(maze,"LEFT");
+ }
+ if(event.key ==="d" || event.key === "D" || event.key === "ArrowRight"){
+ movement(maze,"RIGHT");
+ }
+ // f5 key
+ if(event.key === "F5"){
+ window.location.reload();
+ }
+ });
+
+});
\ No newline at end of file
004.Maze-Runner/style.css
@@ -0,0 +1,55 @@
+body{
+ gap:20px;
+}
+
+#maze {
+ margin-top: 20px;
+ border: 1px solid white;
+ position: relative;
+ width: 300px;
+ height: 300px;
+ grid-template-columns: repeat(15,1fr);
+ grid-template-rows: repeat(15,1fr);
+ display: grid;
+ background-color: black;
+}
+.cell {
+ width: 20px;
+ height: 20px;
+ border: 1px solid white;
+}
+
+.final::before{
+ content: "๐";
+ font-size: 13px;
+ text-align: center;
+ position: relative;
+ top: -3px;
+}
+
+.current-gen{
+ background-color: var(--gold);
+}
+
+.current{
+ color: var(--gold);
+}
+.current::before{
+ content: "โ";
+ font-size: 18px;
+ position: relative;
+ top: -3px;
+
+}
+
+.won::before{
+ content: "๐";
+ font-size: 13px;
+ text-align: center;
+ position: relative;
+ top: -3px;
+}
+
+#tooltip{
+ color: var(--gold);
+}
\ No newline at end of file
index.html
@@ -16,6 +16,7 @@
<a href="./001.Vision/" class="opacity-75 hover:opacity-100 hover:text-yellow-400">001.Vision</a>
<a href="./002.TOTP/" class="opacity-75 hover:opacity-100 hover:text-yellow-400" >002.TOTP</a>
<a href="./003.3n+1/" class="opacity-75 hover:opacity-100 hover:text-yellow-400" >003.3n+1</a>
+ <a href="./004.Maze-Runner/" class="opacity-75 hover:opacity-100 hover:text-yellow-400" >004.Maze-Runner</a>
</div>
</body>
</html>
\ No newline at end of file