Tower of Hanoi – HTML, CSS, JS

Share
tower of hanoi copilot
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Animated Tower of Hanoi (4 Disks Auto-Start)</title>
    <style>
        * {
            box-sizing: border-box;
            margin: 0;
            padding: 0;
        }

        body {
            display: flex;
            flex-direction: column;
            align-items: center;
            font-family: sans-serif;
            padding: 20px;
        }

        h1 {
            margin-bottom: 20px;
        }

        #stage {
            position: relative;
            width: 600px;
            height: 300px;
            background: #f5f5f5;
            border: 2px solid #333;
        }

        .peg {
            position: absolute;
            bottom: 0;
            width: 8px;
            height: 180px;
            background: #333;
        }

        #peg0 { left: 100px; }
        #peg1 { left: 300px; }
        #peg2 { left: 500px; }

        .disk {
            position: absolute;
            height: 20px;
            border-radius: 4px;
            transition: top 0.4s ease, left 0.4s ease;
        }
    </style>
</head>
<body>
    <h1>Tower of Hanoi — 4 Disks</h1>
    <div id="stage">
        <div class="peg" id="peg0"></div>
        <div class="peg" id="peg1"></div>
        <div class="peg" id="peg2"></div>
    </div>

    <script>
        const stage = document.getElementById('stage');

        function setup(n) {
            stage.querySelectorAll('.disk').forEach(d => d.remove());

            const pegs = [[], [], []];
            const pegXs = [100, 300, 500];

            for (let i = n; i > 0; i--) {
                const disk = document.createElement('div');
                disk.className = 'disk';
                disk.style.width = (i * 20 + 40) + 'px';
                disk.style.background = `hsl(${i * 30}, 70%, 50%)`;
                stage.appendChild(disk);
                pegs[0].push(disk);
            }

            pegs.forEach((stack, pegIndex) => {
                stack.forEach((disk, level) => {
                    disk.dataset.peg = pegIndex;
                    disk.dataset.level = level;
                    disk.style.left = pegXs[pegIndex] - disk.offsetWidth / 2 + 'px';
                    disk.style.top = stage.clientHeight - 20 * (level + 1) + 'px';
                });
            });

            return { pegs, pegXs };
        }

        function gen(n, from, to, aux, moves) {
            if (n > 1) gen(n - 1, from, aux, to, moves);
            moves.push({ disk: n, from, to });
            if (n > 1) gen(n - 1, aux, to, from, moves);
        }

        function animate(moves, pegs, pegXs) {
            let i = 0;

            function step() {
                if (i >= moves.length) return;

                const { disk, from, to } = moves[i++];
                const d = pegs[from].pop();
                pegs[to].push(d);

                const level = pegs[to].length - 1;
                d.style.left = pegXs[to] - d.offsetWidth / 2 + 'px';
                d.style.top = stage.clientHeight - 20 * (level + 1) + 'px';

                setTimeout(step, 500);
            }

            step();
        }

        // Auto-start with 4 disks
        window.addEventListener('load', () => {
            const n = 4;
            const { pegs, pegXs } = setup(n);
            const moves = [];
            gen(n, 0, 2, 1, moves);
            setTimeout(() => animate(moves, pegs, pegXs), 200);
        });
    </script>
</body>
</html>