diff --git a/src/algorithms/Recursion/NQueens.js b/src/algorithms/Recursion/NQueens.js new file mode 100644 index 0000000..2889781 --- /dev/null +++ b/src/algorithms/Recursion/NQueens.js @@ -0,0 +1,101 @@ +export function nQueensVisualizerSteps(N) { + const steps = []; + const board = Array.from({ length: N }, () => Array(N).fill(0)); + const solutions = []; + const cols = Array(N).fill(false); + const diag1 = Array(2 * N).fill(false); + const diag2 = Array(2 * N).fill(false); + const stack = []; + + function cloneBoard(b) { + return b.map(r => [...r]); + } + + function solve(row) { + if (row === N) { + steps.push({ + type: "solution", + board: cloneBoard(board), + message: `Found valid solution!`, + stack: [...stack], + safe: true, + solutionCount: solutions.length + 1 + }); + solutions.push(cloneBoard(board)); + return; + } + + for (let col = 0; col < N; col++) { + steps.push({ + type: "try", + board: cloneBoard(board), + row, + col, + message: `Trying to place Queen at (${row}, ${col})`, + safe: null, + stack: [...stack] + }); + + if (!cols[col] && !diag1[row - col + N] && !diag2[row + col]) { + steps.push({ + type: "check", + board: cloneBoard(board), + row, + col, + safe: true, + message: `Position (${row}, ${col}) is safe.`, + stack: [...stack] + }); + + board[row][col] = 1; + cols[col] = diag1[row - col + N] = diag2[row + col] = true; + stack.push({ row, col }); + + steps.push({ + type: "place", + board: cloneBoard(board), + row, + col, + message: `Placed Queen at (${row}, ${col}). Moving to next row.`, + safe: true, + stack: [...stack] + }); + + solve(row + 1); + + board[row][col] = 0; + cols[col] = diag1[row - col + N] = diag2[row + col] = false; + stack.pop(); + + steps.push({ + type: "remove", + board: cloneBoard(board), + row, + col, + message: `Backtracking: Removed Queen from (${row}, ${col}).`, + safe: false, + stack: [...stack] + }); + } else { + steps.push({ + type: "check", + board: cloneBoard(board), + row, + col, + safe: false, + message: `Conflict at (${row}, ${col}). Cannot place Queen here.`, + stack: [...stack] + }); + } + } + } + + solve(0); + + return { + steps, + solutions, + solutionCount: solutions.length, + solvable: solutions.length > 0, + }; +} diff --git a/src/components/Recursion/NQueensVisualizer.jsx b/src/components/Recursion/NQueensVisualizer.jsx new file mode 100644 index 0000000..81982b1 --- /dev/null +++ b/src/components/Recursion/NQueensVisualizer.jsx @@ -0,0 +1,277 @@ +import React, { useState, useEffect, useRef } from "react"; +import { nQueensVisualizerSteps } from "../../algorithms/Recursion/NQueens"; + +const DEFAULT_N = 5; +const MIN_N = 4; +const MAX_N = 10; +const DEFAULT_SPEED = 500; + +function Chessboard({ board, step, N }) { + if ( + !board || + board.length !== N || + board.some((row) => !row || row.length !== N) + ) { + return ( +
+ Loading chessboard... +
+ ); + } + const BOARD_SIZE = 440; + const cellSize = BOARD_SIZE / N; + + return ( +
+ {Array.from({ length: N }).map((_, row) => + Array.from({ length: N }).map((_, col) => { + const cell = board[row][col]; + const isQueen = cell === 1; + + let color = (row + col) % 2 === 0 ? "bg-gray-100" : "bg-gray-400"; + + if (step) { + if (step.type === "try" && step.row === row && step.col === col) + color = "bg-blue-300"; + else if (step.type === "check" && step.row === row && step.col === col) + color = step.safe === false ? "bg-yellow-300" : "bg-green-300"; + else if (step.type === "place" && step.row === row && step.col === col) + color = "bg-green-500"; + else if (step.type === "remove" && step.row === row && step.col === col) + color = "bg-gray-500"; + else if (step.type === "solution" && board[row][col] === 1) + color = "bg-lime-400"; + } + + return ( +
+ {isQueen && ( + + ♛ + + )} +
+ ); + }) + )} +
+ ); +} + +function StackTrace({ stack }) { + return ( +
+

+ Recursion Stack +

+ {stack.length === 0 ? ( +
Stack empty
+ ) : ( +
+ {stack.map((pos, idx) => ( + + Row-{pos.row}, Col-{pos.col} + + ))} +
+ )} +
+ ); +} + +function Legend({ color, text, symbol }) { + return ( +
+
+ {symbol} +
+ {text} +
+ ); +} + +export default function NQueensVisualizer() { + const [N, setN] = useState(DEFAULT_N); + const [speed, setSpeed] = useState(DEFAULT_SPEED); + const [steps, setSteps] = useState([]); + const [currentStepIdx, setCurrentStepIdx] = useState(0); + const [isPlaying, setIsPlaying] = useState(false); + const [solutionCount, setSolutionCount] = useState(0); + const [solvable, setSolvable] = useState(null); + const timerRef = useRef(null); + + const runSolver = () => { + const result = nQueensVisualizerSteps(N); + setSteps(result.steps); + setSolutionCount(result.solutionCount); + setSolvable(result.solvable); + setCurrentStepIdx(0); + setIsPlaying(false); + }; + + useEffect(() => { + setSteps([]); + setCurrentStepIdx(0); + setSolvable(null); + setSolutionCount(0); + runSolver(); + }, [N]); + + useEffect(() => { + if (isPlaying && currentStepIdx < steps.length - 1) { + timerRef.current = setTimeout(() => { + setCurrentStepIdx((idx) => idx + 1); + }, speed); + } else { + clearTimeout(timerRef.current); + setIsPlaying(false); + } + return () => clearTimeout(timerRef.current); + }, [isPlaying, currentStepIdx, steps, speed]); + + const step = steps[currentStepIdx] || {}; + const board = step.board || Array.from({ length: N }, () => Array(N).fill(0)); + + return ( +
+
+

+ ♛ N-Queens Visualizer +

+ + {/* Controls */} +
+
+ + + setN(Math.max(MIN_N, Math.min(MAX_N, Number(e.target.value)))) + } + className="w-20 mt-1 p-2 rounded-md bg-gray-700 text-white border border-gray-600" + /> +
+ +
+ + + setSpeed(Math.max(100, Math.min(2000, Number(e.target.value)))) + } + className="w-20 mt-1 p-2 rounded-md bg-gray-700 text-white border border-gray-600" + /> +
+ + + + + +
+ + {/* Main Layout*/} +
+ {/*Chessboard*/} +
+ +
+ Step {currentStepIdx + 1}/{steps.length} • Solutions Found:{" "} + {solutionCount} + {solvable === false && ( +

+ No solution for N={N} +

+ )} +
+
+ +
+
+

+ Step Explanation +

+

+ {step.message || "Start solving N-Queens..."} +

+
+ + + +
+

+ Color Meanings +

+
+ + + + + + +
+
+
+
+
+
+ ); +} diff --git a/src/pages/Recursion/NQueens.jsx b/src/pages/Recursion/NQueens.jsx new file mode 100644 index 0000000..aff8d53 --- /dev/null +++ b/src/pages/Recursion/NQueens.jsx @@ -0,0 +1,6 @@ +import React from "react"; +import NQueensVisualizer from "../../components/recursion/NQueensVisualizer"; + +export default function NQueens() { + return ; +} \ No newline at end of file diff --git a/src/pages/Recursion/RecursionPage.jsx b/src/pages/Recursion/RecursionPage.jsx index 22ae46e..3e2bbc8 100644 --- a/src/pages/Recursion/RecursionPage.jsx +++ b/src/pages/Recursion/RecursionPage.jsx @@ -1,7 +1,7 @@ import React, { useState } from "react"; import { X, Menu } from "lucide-react"; import MazeSolver from "./MazeSolver"; - +import NQueens from "./NQueens"; export default function RecursionPage() { const [selectedAlgo, setSelectedAlgo] = useState(""); const [sidebarOpen, setSidebarOpen] = useState(true); @@ -14,6 +14,12 @@ export default function RecursionPage() { ); + case "NQueens": + return ( +
+ +
+ ); default: return (
@@ -55,6 +61,7 @@ export default function RecursionPage() { > +