Skip to content

Commit 37f10e7

Browse files
Merge pull request #73 from LONECODER1/feature/towerOfHanoi
Tower of Hanoi added
2 parents 5d198a3 + 1527faa commit 37f10e7

File tree

4 files changed

+284
-1
lines changed

4 files changed

+284
-1
lines changed
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
export function towerOfHanoiVisualizerSteps(N) {
2+
const steps = [];
3+
const moves = [];
4+
const rods = {
5+
A: Array.from({ length: N }, (_, i) => N - i),
6+
B: [],
7+
C: [],
8+
};
9+
10+
function cloneRods() {
11+
return {
12+
A: [...rods.A],
13+
B: [...rods.B],
14+
C: [...rods.C],
15+
};
16+
}
17+
18+
function move(n, from, to, aux) {
19+
if (n === 1) {
20+
const disk = rods[from].pop();
21+
rods[to].push(disk);
22+
steps.push({
23+
type: "move",
24+
disk,
25+
from,
26+
to,
27+
rods: cloneRods(),
28+
message: `Move disk ${disk} from ${from}${to}`,
29+
});
30+
moves.push({ disk, from, to });
31+
return;
32+
}
33+
34+
steps.push({
35+
type: "recursive-call",
36+
message: `Move ${n - 1} disks from ${from}${aux} using ${to} as auxiliary.`,
37+
rods: cloneRods(),
38+
});
39+
40+
move(n - 1, from, aux, to);
41+
42+
const disk = rods[from].pop();
43+
rods[to].push(disk);
44+
steps.push({
45+
type: "move",
46+
disk,
47+
from,
48+
to,
49+
rods: cloneRods(),
50+
message: `Move disk ${disk} from ${from}${to}`,
51+
});
52+
moves.push({ disk, from, to });
53+
54+
steps.push({
55+
type: "recursive-call",
56+
message: `Move ${n - 1} disks from ${aux}${to} using ${from} as auxiliary.`,
57+
rods: cloneRods(),
58+
});
59+
60+
move(n - 1, aux, to, from);
61+
}
62+
63+
move(N, "A", "C", "B");
64+
65+
return {
66+
steps,
67+
moves,
68+
moveCount: moves.length,
69+
};
70+
}
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
import React, { useState, useEffect, useRef, useCallback } from "react";
2+
import { towerOfHanoiVisualizerSteps } from "../../algorithms/Recursion/towerOfHanoi";
3+
4+
const DEFAULT_N = 3;
5+
const MIN_N = 2;
6+
const MAX_N = 6;
7+
const DEFAULT_SPEED = 700;
8+
9+
function Rods({ rods }) {
10+
const rodNames = ["A", "B", "C"];
11+
12+
return (
13+
<div className="flex justify-center gap-16 mt-10">
14+
{rodNames.map((rod) => (
15+
<div key={rod} className="flex flex-col items-center">
16+
<div className="h-60 w-2 bg-gray-600 relative mb-2 rounded"></div>
17+
<div className="w-32 h-2 bg-gray-700 mb-1 rounded"></div>
18+
<div className="flex flex-col-reverse items-center justify-end h-48 w-32">
19+
{rods[rod].map((disk, i) => (
20+
<div
21+
key={i}
22+
className="h-5 rounded text-center font-semibold shadow-sm text-gray-900"
23+
style={{
24+
width: `${20 + disk * 15}px`,
25+
backgroundColor: `hsl(${disk * 60}, 70%, 55%)`,
26+
}}
27+
>
28+
{disk}
29+
</div>
30+
))}
31+
</div>
32+
<span className="text-gray-300 font-semibold mt-2">{rod}</span>
33+
</div>
34+
))}
35+
</div>
36+
);
37+
}
38+
39+
function Legend({ color, text }) {
40+
return (
41+
<div className="flex items-center gap-2 mb-1">
42+
<div
43+
className="w-5 h-5 rounded border border-gray-600"
44+
style={{ backgroundColor: color }}
45+
></div>
46+
<span className="text-gray-300 text-sm">{text}</span>
47+
</div>
48+
);
49+
}
50+
51+
export default function TowerOfHanoiVisualizer() {
52+
const [N, setN] = useState(DEFAULT_N);
53+
const [speed, setSpeed] = useState(DEFAULT_SPEED);
54+
const [steps, setSteps] = useState([]);
55+
const [currentStepIdx, setCurrentStepIdx] = useState(0);
56+
const [isPlaying, setIsPlaying] = useState(false);
57+
const [moveCount, setMoveCount] = useState(0);
58+
const timerRef = useRef(null);
59+
60+
const runSolver = useCallback(() => {
61+
const result = towerOfHanoiVisualizerSteps(N);
62+
setSteps(result.steps);
63+
setMoveCount(result.moveCount);
64+
setCurrentStepIdx(0);
65+
setIsPlaying(false);
66+
}, [N]);
67+
68+
useEffect(() => {
69+
runSolver();
70+
}, [runSolver]);
71+
72+
useEffect(() => {
73+
if (isPlaying && currentStepIdx < steps.length - 1) {
74+
timerRef.current = setTimeout(() => {
75+
setCurrentStepIdx((i) => i + 1);
76+
}, speed);
77+
} else {
78+
clearTimeout(timerRef.current);
79+
setIsPlaying(false);
80+
}
81+
return () => clearTimeout(timerRef.current);
82+
}, [isPlaying, currentStepIdx, steps.length, speed]);
83+
84+
const step = steps[currentStepIdx] || {};
85+
const rods = step.rods || { A: [], B: [], C: [] };
86+
87+
return (
88+
<div className="p-6 min-h-screen bg-gray-900 text-gray-100 font-sans">
89+
<div className="max-w-6xl mx-auto">
90+
<h1 className="text-4xl font-bold mb-6 text-center text-yellow-400">
91+
🏰 Tower of Hanoi Visualizer
92+
</h1>
93+
94+
{/* Controls */}
95+
<div className="flex flex-wrap gap-6 mb-8 p-6 rounded-lg bg-gray-800 border border-gray-700 justify-center items-center">
96+
<div>
97+
<label className="text-gray-300 text-sm">Disks (N):</label>
98+
<input
99+
type="number"
100+
min={MIN_N}
101+
max={MAX_N}
102+
value={N}
103+
onChange={(e) =>
104+
setN(Math.max(MIN_N, Math.min(MAX_N, Number(e.target.value))))
105+
}
106+
className="w-20 mt-1 p-2 rounded-md bg-gray-700 text-white border border-gray-600"
107+
/>
108+
</div>
109+
110+
<div>
111+
<label className="text-gray-300 text-sm">Speed (ms):</label>
112+
<input
113+
type="number"
114+
min={200}
115+
max={2000}
116+
value={speed}
117+
onChange={(e) =>
118+
setSpeed(Math.max(200, Math.min(2000, Number(e.target.value))))
119+
}
120+
className="w-20 mt-1 p-2 rounded-md bg-gray-700 text-white border border-gray-600"
121+
/>
122+
</div>
123+
124+
<button
125+
onClick={runSolver}
126+
className="bg-yellow-600 hover:bg-yellow-700 text-white font-medium px-6 py-2 rounded-md"
127+
>
128+
Restart
129+
</button>
130+
131+
<button
132+
onClick={() => setIsPlaying((p) => !p)}
133+
className={`px-5 py-2 rounded-md font-medium ${isPlaying ? "bg-red-600" : "bg-green-600"
134+
} text-white`}
135+
>
136+
{isPlaying ? "Pause" : "Play"}
137+
</button>
138+
139+
<button
140+
onClick={() => setCurrentStepIdx((i) => Math.max(0, i - 1))}
141+
disabled={currentStepIdx === 0}
142+
className={`px-4 py-2 rounded-md font-medium ${currentStepIdx > 0
143+
? "bg-gray-700 hover:bg-gray-600 text-white"
144+
: "bg-gray-800 text-gray-500"
145+
}`}
146+
>
147+
Prev
148+
</button>
149+
150+
<button
151+
onClick={() =>
152+
setCurrentStepIdx((i) => Math.min(steps.length - 1, i + 1))
153+
}
154+
disabled={currentStepIdx === steps.length - 1}
155+
className={`px-4 py-2 rounded-md font-medium ${currentStepIdx < steps.length - 1
156+
? "bg-gray-700 hover:bg-gray-600 text-white"
157+
: "bg-gray-800 text-gray-500"
158+
}`}
159+
>
160+
Next
161+
</button>
162+
</div>
163+
164+
{/* Visualization */}
165+
<div className="flex flex-col lg:flex-row gap-10 justify-center items-start">
166+
<div className="flex flex-col items-center w-full">
167+
<Rods rods={rods} />
168+
<div className="mt-6 text-center text-gray-300 text-sm">
169+
Step {currentStepIdx + 1}/{steps.length} • Total Moves:{" "}
170+
<b className="text-green-400">{moveCount}</b>
171+
</div>
172+
</div>
173+
174+
{/* Info Panel */}
175+
<div className="flex-1 flex flex-col gap-5 max-w-md">
176+
<div className="bg-gray-800 p-4 rounded-lg border border-gray-700">
177+
<h3 className="text-yellow-400 font-semibold mb-2 text-lg">
178+
Step Explanation
179+
</h3>
180+
<p className="text-gray-200 text-base leading-relaxed">
181+
{step.message || "Solving Tower of Hanoi..."}
182+
</p>
183+
</div>
184+
185+
<div className="bg-gray-800 border border-gray-700 rounded-lg p-4 shadow-sm">
186+
<h3 className="text-yellow-400 font-semibold mb-3 text-lg">
187+
Color Meanings
188+
</h3>
189+
<Legend color="hsl(60, 70%, 55%)" text="Smallest Disk" />
190+
<Legend color="hsl(120, 70%, 55%)" text="Medium Disk" />
191+
<Legend color="hsl(180, 70%, 55%)" text="Larger Disk" />
192+
<Legend color="hsl(240, 70%, 55%)" text="Largest Disk" />
193+
</div>
194+
</div>
195+
</div>
196+
</div>
197+
</div>
198+
);
199+
}

src/pages/Recursion/RecursionPage.jsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { X, Menu } from "lucide-react";
33
import MazeSolver from "./MazeSolver";
44
import NQueens from "./NQueens";
55
import Sudoku from "./sudokuSolver";
6+
import TowerOfHanoi from "./towerOfHanoi";
67
export default function RecursionPage() {
78
const [selectedAlgo, setSelectedAlgo] = useState("");
89
const [sidebarOpen, setSidebarOpen] = useState(true);
@@ -22,11 +23,17 @@ export default function RecursionPage() {
2223
</div>
2324
);
2425
case "SudokuSolver":
25-
return(
26+
return (
2627
<div className="w-full p-4 overflow-auto">
2728
<Sudoku />
2829
</div>
2930
);
31+
case "TowerofHanoi":
32+
return (
33+
<div className="w-full p-4 overflow-auto">
34+
<TowerOfHanoi />
35+
</div>
36+
);
3037

3138
default:
3239
return (
@@ -71,6 +78,7 @@ export default function RecursionPage() {
7178
<option value="MazeSolver">Maze Solver</option>
7279
<option value="NQueens">NQueens</option>
7380
<option value="SudokuSolver">Sudoku Solver</option>
81+
<option value="TowerofHanoi">Tower of Hanoi</option>
7482
</select>
7583

7684
<button
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import React from "react";
2+
import TowerOfHanoiVisualizer from "../../components/recursion/TowerOfHanoiVisualizer";
3+
4+
export default function TowerOfHanoi() {
5+
return <TowerOfHanoiVisualizer />;
6+
}

0 commit comments

Comments
 (0)