diff --git a/package-lock.json b/package-lock.json index 210d3cd..c952e7e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -74,6 +74,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -2453,6 +2454,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz", "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==", "devOptional": true, + "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -2462,6 +2464,7 @@ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.2.tgz", "integrity": "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==", "devOptional": true, + "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -2497,6 +2500,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2649,6 +2653,7 @@ "url": "https://github.com/sponsors/ai" } ], + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.19", "caniuse-lite": "^1.0.30001751", @@ -2812,7 +2817,8 @@ "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "peer": true }, "node_modules/d3-color": { "version": "3.1.0", @@ -2871,6 +2877,7 @@ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "license": "ISC", + "peer": true, "engines": { "node": ">=12" } @@ -3088,6 +3095,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.38.0.tgz", "integrity": "sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw==", "dev": true, + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -4054,6 +4062,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "peer": true, "engines": { "node": ">=12" }, @@ -4079,6 +4088,7 @@ "url": "https://github.com/sponsors/ai" } ], + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -4116,6 +4126,7 @@ "version": "19.2.0", "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -4124,6 +4135,7 @@ "version": "19.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -4152,6 +4164,7 @@ "version": "9.2.0", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", + "peer": true, "dependencies": { "@types/use-sync-external-store": "^0.0.6", "use-sync-external-store": "^1.4.0" @@ -4302,7 +4315,8 @@ "node_modules/redux": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", - "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==" + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", + "peer": true }, "node_modules/redux-thunk": { "version": "3.1.0", @@ -4661,6 +4675,7 @@ "version": "7.1.12", "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.12.tgz", "integrity": "sha512-ZWyE8YXEXqJrrSLvYgrRP7p62OziLW7xI5HYGWFzOvupfAlrLvURSzv/FyGyy0eidogEM3ujU+kUG1zuHgb6Ug==", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", diff --git a/src/App.jsx b/src/App.jsx index 29babb2..71a3bf5 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -7,6 +7,7 @@ import Homepage from "./pages/Homepage.jsx"; import DSPage from "./pages/dataStructure/datastructurePage.jsx" import DynamicProgrammingPage from "./pages/dynamic-programming/DyanmicProgrammingPage.jsx"; import Searchingpage from "./pages/searching/searchingPage"; +import RecursionPage from "./pages/Recursion/RecursionPage"; function App() { return ( @@ -18,6 +19,7 @@ function App() { }/> } /> } /> + }/> ); diff --git a/src/algorithms/Recursion/mazeSolver.js b/src/algorithms/Recursion/mazeSolver.js new file mode 100644 index 0000000..08702c5 --- /dev/null +++ b/src/algorithms/Recursion/mazeSolver.js @@ -0,0 +1,81 @@ +export function mazeSolverSteps(grid, start, end, allowDiagonal = false) { + const rows = grid.length; + const cols = grid[0].length; + + const steps = []; + const visited = Array.from({ length: rows }, () => Array(cols).fill(false)); + const parent = Array.from({ length: rows }, () => + Array(cols).fill(null) + ); + + const directions = allowDiagonal ? [[0, 1], [1, 0], [0, -1], [-1, 0],[1, 1], [1, -1], [-1, 1], [-1, -1],] + : [[0, 1], [1, 0], [0, -1], [-1, 0]]; + + const queue = []; + queue.push(start); + visited[start[0]][start[1]] = true; + + let solutionFound = false; + + while (queue.length > 0){ + const [r, c] = queue.shift(); + //exploring cell + steps.push({ type: "explore", row: r, col: c, visited: visited.map(v => [...v]), + grid: cloneGrid(grid), + message: `Exploring cell (${r}, ${c}).`, + }); + + if (r === end[0] && c === end[1]) { + solutionFound = true; + steps.push({ type: "found",row: r,col: c,visited: visited.map(v => [...v]), + grid: cloneGrid(grid), + message: `Reached end (${r}, ${c}) — shortest path found!`, + }); + break; + } + + for (const [dr, dc] of directions) { + const nr = r + dr; + const nc = c + dc; + + if(nr>=0 &&nc >= 0 && nr [...v]), + grid: cloneGrid(grid), + message: `Adding cell (${nr}, ${nc}) to queue.`, + }); + } + } + } + //reconstruct shortest path + const path = []; + if (solutionFound) { + let node = end; + while (node) { + path.unshift(node); + node = parent[node[0]][node[1]]; + } + + for (const [r, c] of path) { + steps.push({type: "path_cell",row: r,col: c,visited: visited.map(v => [...v]), + grid: cloneGrid(grid), + path: [...path], + message: `Cell (${r}, ${c}) is part of the shortest path.`, + }); + } + } else { + steps.push({ type: "no_path", row: null,col: null,visited: visited.map(v => [...v]), + grid: cloneGrid(grid), + message: "No path possible between Start and End points.", + }); + } + + return {steps,solutionFound,path}; +} + +function cloneGrid(grid) { + return grid.map(row => [...row]); +} \ No newline at end of file diff --git a/src/components/Recursion/MazeSolverVisualizer.jsx b/src/components/Recursion/MazeSolverVisualizer.jsx new file mode 100644 index 0000000..f787fd7 --- /dev/null +++ b/src/components/Recursion/MazeSolverVisualizer.jsx @@ -0,0 +1,345 @@ +import React, { useState, useEffect, useRef } from "react"; +import { mazeSolverSteps } from "../../algorithms/Recursion/mazeSolver"; + +const CELL = {EMPTY: 0, WALL: 1, START: 2, END: 3}; +const defaultSize = 10; + +function makeGrid(rows, cols) { + return Array.from({ length: rows }, () => Array(cols).fill(CELL.EMPTY)); +} +function randomMaze(rows, cols, wallProbability = 0.3) { + const grid = makeGrid(rows, cols); + for (let i = 0; i < rows; i++) { + for (let j = 0; j < cols; j++) { + if (Math.random() < wallProbability) grid[i][j] = CELL.WALL; + } + } + return grid; +} +function getCellClass(cell, row, col, state) { + if (cell === CELL.START) + return "bg-yellow-400 text-black font-bold border border-yellow-600"; + if (cell === CELL.END) + return "bg-yellow-400 text-black font-bold border border-yellow-600"; + if (cell === CELL.WALL) + return "bg-black border border-gray-700"; + + if (state.type === "path_cell" && state.path?.some(([r, c]) => r === row && c === col)) + return "bg-green-500 border border-green-700 animate-pulse"; + + if (state.row === row && state.col === col) { + if (state.type === "explore") + return "bg-blue-500 border border-blue-700 animate-pulse"; + if (state.type === "dead_end") + return "bg-red-600 border border-red-800"; + if (state.type === "backtrack") + return "bg-red-800 border border-red-900 animate-pulse"; + if (state.type === "found") + return "bg-green-600 border border-green-800 animate-pulse"; + } + + if (state.backtracked?.[row]?.[col]) + return "bg-red-800 border border-red-900"; + + if (state.visited?.[row]?.[col]) + return "bg-blue-900 border border-blue-700"; + + return "bg-gray-300 border border-gray-400"; +} + +export default function MazeSolverVisualizer() { + const [rows, setRows] = useState(defaultSize); + const [cols, setCols] = useState(defaultSize); + const [grid, setGrid] = useState(makeGrid(defaultSize, defaultSize)); + const [start, setStart] = useState([0, 0]); + const [end, setEnd] = useState([defaultSize - 1, defaultSize - 1]); + const [allowDiagonal, setAllowDiagonal] = useState(false); + const [speed, setSpeed] = useState(250); + + const [steps, setSteps] = useState([]); + const [stepIdx, setStepIdx] = useState(0); + const [running, setRunning] = useState(false); + const [solutionFound, setSolutionFound] = useState(null); + const [mode, setMode] = useState("manual"); + + const timerRef = useRef(null); + + //handle grid cell click + function handleCellClick(r, c) { + if (running) return; + if ((r === start[0] && c === start[1]) || (r === end[0] && c === end[1])) return; + + setGrid(g => + g.map((rowArr, i) => + rowArr.map((cell, j) => + i === r && j === c ? (cell === CELL.WALL ? CELL.EMPTY : CELL.WALL) : cell + ) + ) + ); + } + + //handle right click + function handleCellRightClick(r, c, e) { + e.preventDefault(); + if (running) return; + + if (r === end[0] && c === end[1]) return; + if (r === start[0] && c === start[1]) return; + + setGrid(g => + g.map((rowArr, i) => + rowArr.map((cell, j) => { + if (i === start[0] && j === start[1]) return CELL.EMPTY; + if (i === end[0] && j === end[1]) return CELL.EMPTY; + if (i === r && j === c) { + if (!start || (start[0] !== r || start[1] !== c)) { + setStart([r, c]); + return CELL.START; + } else { + setEnd([r, c]); + return CELL.END; + } + } + return cell; + }) + ) + ); + } + + //Change grid size + function handleGridSizeChange(size) { + const newGrid = mode === "random" ? randomMaze(size, size) : makeGrid(size, size); + setRows(size); + setCols(size); + setGrid(newGrid); + setStart([0, 0]); + setEnd([size - 1, size - 1]); + resetState(); + } + + //random maze + function generateRandomMaze() { + const newGrid = randomMaze(rows, cols, 0.3); + newGrid[start[0]][start[1]] = CELL.START; + newGrid[end[0]][end[1]] = CELL.END; + setGrid(newGrid); + setMode("random"); + } + + // Manual maze + function createManualGrid() { + const newGrid = makeGrid(rows, cols); + newGrid[start[0]][start[1]] = CELL.START; + newGrid[end[0]][end[1]] = CELL.END; + setGrid(newGrid); + setMode("manual"); + } + // Start + function startVisualization() { + if (running) return; + const g = grid.map(row => [...row]); + g[start[0]][start[1]] = CELL.START; + g[end[0]][end[1]] = CELL.END; + + const result = mazeSolverSteps(g, start, end, allowDiagonal); + setSteps(result.steps); + setStepIdx(0); + setRunning(true); + setSolutionFound(result.solutionFound); + } + //Animation + useEffect(() => { + if (running && steps.length > 0 && stepIdx < steps.length - 1) { + timerRef.current = setTimeout(() => { + setStepIdx(idx => idx + 1); + }, speed); + } else { + clearTimeout(timerRef.current); + setRunning(false); + } + return () => clearTimeout(timerRef.current); + }, [running, stepIdx, steps, speed]); + + function handlePauseResume() { + if (!steps.length) return; + setRunning(r => !r); + } + + function resetState() { + clearTimeout(timerRef.current); + setSteps([]); + setStepIdx(0); + setRunning(false); + setSolutionFound(null); + } + + function handleReset() { + setGrid(makeGrid(rows, cols)); + setStart([0, 0]); + setEnd([rows - 1, cols - 1]); + resetState(); + } + + const currentStep = steps[stepIdx] || {}; + + return ( +
+
+

+ 🌀 Maze Solver Visualizer +

+ {/* Controls */} +
+
+ + + +
+ + handleGridSizeChange(Number(e.target.value))} + className="border border-gray-600 bg-gray-700 text-white p-2 rounded w-16" + /> +
+ +
+ + setSpeed(Number(e.target.value))} + className="accent-indigo-500" + /> + {speed}ms +
+ + +
+ +
+ + + +
+
+{/* Visualization*/} +
+
+
+ {grid.map((rowArr, i) => + rowArr.map((cell, j) => ( +
handleCellClick(i, j)} + onContextMenu={e => handleCellRightClick(i, j, e)} + > + {cell === CELL.START && "S"} + {cell === CELL.END && "E"} +
+ )) + )} +
+
+ +
+ {steps.length > 0 ? ( + <> +

# STEPS :

+

+ Step {stepIdx + 1} / {steps.length} +

+

{currentStep.message}

+ + {solutionFound === false && stepIdx === steps.length - 1 && ( +
+ No Path Found Between Start and End! +
+ )} + + ) : ( +
+ Start the visualization to see step-by-step progress here. +
+ )} +
+

# Instructions

+
    +
  • Left-click on a cell to toggle a wall (black cell).
  • +
  • Right-click on a cell to move the Start (S) or End (E) point.
  • +
+
+

# Meaning of cell colors

+
+ + + + + + + +
+
+
+
+
+
+ ); +} + +function Legend({ color, text }) { + return ( +
+
+ {text} +
+ ); +} diff --git a/src/pages/Recursion/MazeSolver.jsx b/src/pages/Recursion/MazeSolver.jsx new file mode 100644 index 0000000..8910919 --- /dev/null +++ b/src/pages/Recursion/MazeSolver.jsx @@ -0,0 +1,5 @@ +import React from "react"; +import MazeSolverVisualizer from "../../components/Recursion/MazeSolverVisualizer"; +export default function MazeSolver() { + return ; +} \ No newline at end of file diff --git a/src/pages/Recursion/RecursionPage.jsx b/src/pages/Recursion/RecursionPage.jsx new file mode 100644 index 0000000..22ae46e --- /dev/null +++ b/src/pages/Recursion/RecursionPage.jsx @@ -0,0 +1,87 @@ +import React, { useState } from "react"; +import { X, Menu } from "lucide-react"; +import MazeSolver from "./MazeSolver"; + +export default function RecursionPage() { + const [selectedAlgo, setSelectedAlgo] = useState(""); + const [sidebarOpen, setSidebarOpen] = useState(true); + + const renderAlgorithm = () => { + switch (selectedAlgo) { + case "MazeSolver": + return ( +
+ +
+ ); + default: + return ( +
+

+ Recursion & Backtracking Visualizer +

+

+ Select an algorithm from the left panel to start visualization. +

+
+ ); + } + }; + + return ( +
+ {/* Sidebar */} +
+
+

+ Recursion Panel +

+ +
+ + + + + + + + ← Back to Home + +
+ + {!sidebarOpen && ( + + )} + +
{renderAlgorithm()}
+
+ ); +}