Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions src/algorithms/Recursion/towerOfHanoi.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
export function towerOfHanoiVisualizerSteps(N) {
const steps = [];
const moves = [];
const rods = {
A: Array.from({ length: N }, (_, i) => N - i),
B: [],
C: [],
};

function cloneRods() {
return {
A: [...rods.A],
B: [...rods.B],
C: [...rods.C],
};
}

function move(n, from, to, aux) {
if (n === 1) {
const disk = rods[from].pop();
rods[to].push(disk);
steps.push({
type: "move",
disk,
from,
to,
rods: cloneRods(),
message: `Move disk ${disk} from ${from} → ${to}`,
});
moves.push({ disk, from, to });
return;
}

steps.push({
type: "recursive-call",
message: `Move ${n - 1} disks from ${from} → ${aux} using ${to} as auxiliary.`,
rods: cloneRods(),
});

move(n - 1, from, aux, to);

const disk = rods[from].pop();
rods[to].push(disk);
steps.push({
type: "move",
disk,
from,
to,
rods: cloneRods(),
message: `Move disk ${disk} from ${from} → ${to}`,
});
moves.push({ disk, from, to });

steps.push({
type: "recursive-call",
message: `Move ${n - 1} disks from ${aux} → ${to} using ${from} as auxiliary.`,
rods: cloneRods(),
});

move(n - 1, aux, to, from);
}

move(N, "A", "C", "B");

return {
steps,
moves,
moveCount: moves.length,
};
}
199 changes: 199 additions & 0 deletions src/components/Recursion/towerOfHanoiVisualizer.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
import React, { useState, useEffect, useRef, useCallback } from "react";
import { towerOfHanoiVisualizerSteps } from "../../algorithms/Recursion/towerOfHanoi";

const DEFAULT_N = 3;
const MIN_N = 2;
const MAX_N = 6;
const DEFAULT_SPEED = 700;

function Rods({ rods }) {
const rodNames = ["A", "B", "C"];

return (
<div className="flex justify-center gap-16 mt-10">
{rodNames.map((rod) => (
<div key={rod} className="flex flex-col items-center">
<div className="h-60 w-2 bg-gray-600 relative mb-2 rounded"></div>
<div className="w-32 h-2 bg-gray-700 mb-1 rounded"></div>
<div className="flex flex-col-reverse items-center justify-end h-48 w-32">
{rods[rod].map((disk, i) => (
<div
key={i}
className="h-5 rounded text-center font-semibold shadow-sm text-gray-900"
style={{
width: `${20 + disk * 15}px`,
backgroundColor: `hsl(${disk * 60}, 70%, 55%)`,
}}
>
{disk}
</div>
))}
</div>
<span className="text-gray-300 font-semibold mt-2">{rod}</span>
</div>
))}
</div>
);
}

function Legend({ color, text }) {
return (
<div className="flex items-center gap-2 mb-1">
<div
className="w-5 h-5 rounded border border-gray-600"
style={{ backgroundColor: color }}
></div>
<span className="text-gray-300 text-sm">{text}</span>
</div>
);
}

export default function TowerOfHanoiVisualizer() {
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 [moveCount, setMoveCount] = useState(0);
const timerRef = useRef(null);

const runSolver = useCallback(() => {
const result = towerOfHanoiVisualizerSteps(N);
setSteps(result.steps);
setMoveCount(result.moveCount);
setCurrentStepIdx(0);
setIsPlaying(false);
}, [N]);

useEffect(() => {
runSolver();
}, [runSolver]);

useEffect(() => {
if (isPlaying && currentStepIdx < steps.length - 1) {
timerRef.current = setTimeout(() => {
setCurrentStepIdx((i) => i + 1);
}, speed);
} else {
clearTimeout(timerRef.current);
setIsPlaying(false);
}
return () => clearTimeout(timerRef.current);
}, [isPlaying, currentStepIdx, steps.length, speed]);

const step = steps[currentStepIdx] || {};
const rods = step.rods || { A: [], B: [], C: [] };

return (
<div className="p-6 min-h-screen bg-gray-900 text-gray-100 font-sans">
<div className="max-w-6xl mx-auto">
<h1 className="text-4xl font-bold mb-6 text-center text-yellow-400">
🏰 Tower of Hanoi Visualizer
</h1>

{/* Controls */}
<div className="flex flex-wrap gap-6 mb-8 p-6 rounded-lg bg-gray-800 border border-gray-700 justify-center items-center">
<div>
<label className="text-gray-300 text-sm">Disks (N):</label>
<input
type="number"
min={MIN_N}
max={MAX_N}
value={N}
onChange={(e) =>
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"
/>
</div>

<div>
<label className="text-gray-300 text-sm">Speed (ms):</label>
<input
type="number"
min={200}
max={2000}
value={speed}
onChange={(e) =>
setSpeed(Math.max(200, 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"
/>
</div>

<button
onClick={runSolver}
className="bg-yellow-600 hover:bg-yellow-700 text-white font-medium px-6 py-2 rounded-md"
>
Restart
</button>

<button
onClick={() => setIsPlaying((p) => !p)}
className={`px-5 py-2 rounded-md font-medium ${isPlaying ? "bg-red-600" : "bg-green-600"
} text-white`}
>
{isPlaying ? "Pause" : "Play"}
</button>

<button
onClick={() => setCurrentStepIdx((i) => Math.max(0, i - 1))}
disabled={currentStepIdx === 0}
className={`px-4 py-2 rounded-md font-medium ${currentStepIdx > 0
? "bg-gray-700 hover:bg-gray-600 text-white"
: "bg-gray-800 text-gray-500"
}`}
>
Prev
</button>

<button
onClick={() =>
setCurrentStepIdx((i) => Math.min(steps.length - 1, i + 1))
}
disabled={currentStepIdx === steps.length - 1}
className={`px-4 py-2 rounded-md font-medium ${currentStepIdx < steps.length - 1
? "bg-gray-700 hover:bg-gray-600 text-white"
: "bg-gray-800 text-gray-500"
}`}
>
Next
</button>
</div>

{/* Visualization */}
<div className="flex flex-col lg:flex-row gap-10 justify-center items-start">
<div className="flex flex-col items-center w-full">
<Rods rods={rods} />
<div className="mt-6 text-center text-gray-300 text-sm">
Step {currentStepIdx + 1}/{steps.length} • Total Moves:{" "}
<b className="text-green-400">{moveCount}</b>
</div>
</div>

{/* Info Panel */}
<div className="flex-1 flex flex-col gap-5 max-w-md">
<div className="bg-gray-800 p-4 rounded-lg border border-gray-700">
<h3 className="text-yellow-400 font-semibold mb-2 text-lg">
Step Explanation
</h3>
<p className="text-gray-200 text-base leading-relaxed">
{step.message || "Solving Tower of Hanoi..."}
</p>
</div>

<div className="bg-gray-800 border border-gray-700 rounded-lg p-4 shadow-sm">
<h3 className="text-yellow-400 font-semibold mb-3 text-lg">
Color Meanings
</h3>
<Legend color="hsl(60, 70%, 55%)" text="Smallest Disk" />
<Legend color="hsl(120, 70%, 55%)" text="Medium Disk" />
<Legend color="hsl(180, 70%, 55%)" text="Larger Disk" />
<Legend color="hsl(240, 70%, 55%)" text="Largest Disk" />
</div>
</div>
</div>
</div>
</div>
);
}
10 changes: 9 additions & 1 deletion src/pages/Recursion/RecursionPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { X, Menu } from "lucide-react";
import MazeSolver from "./MazeSolver";
import NQueens from "./NQueens";
import Sudoku from "./sudokuSolver";
import TowerOfHanoi from "./towerOfHanoi";
export default function RecursionPage() {
const [selectedAlgo, setSelectedAlgo] = useState("");
const [sidebarOpen, setSidebarOpen] = useState(true);
Expand All @@ -22,11 +23,17 @@ export default function RecursionPage() {
</div>
);
case "SudokuSolver":
return(
return (
<div className="w-full p-4 overflow-auto">
<Sudoku />
</div>
);
case "TowerofHanoi":
return (
<div className="w-full p-4 overflow-auto">
<TowerOfHanoi />
</div>
);

default:
return (
Expand Down Expand Up @@ -71,6 +78,7 @@ export default function RecursionPage() {
<option value="MazeSolver">Maze Solver</option>
<option value="NQueens">NQueens</option>
<option value="SudokuSolver">Sudoku Solver</option>
<option value="TowerofHanoi">Tower of Hanoi</option>
</select>

<button
Expand Down
6 changes: 6 additions & 0 deletions src/pages/Recursion/towerOfHanoi.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import React from "react";
import TowerOfHanoiVisualizer from "../../components/recursion/TowerOfHanoiVisualizer";

export default function TowerOfHanoi() {
return <TowerOfHanoiVisualizer />;
}
Loading