汉洛塔结构思维

汉洛塔帝国结构思维

一、项目简介

汉洛塔(Tower of Hanoi)是一个经典的递归问题,而我设计的「汉洛塔帝国结构思维」则是将这个经典算法与帝国结构的概念相结合,创造出一个可视化的、具有层次感的结构模型。

项目地址:https://5d587990.pinme.dev/

二、设计理念

1. 汉洛塔的核心思想

汉洛塔问题的核心在于递归思维:将n个盘子从A柱移动到C柱,需要先将n-1个盘子从A柱移动到B柱,然后将最大的盘子从A柱移动到C柱,最后将n-1个盘子从B柱移动到C柱。

2. 帝国结构的层次概念

帝国结构通常具有明确的层级关系,从中央到地方,从核心到边缘,形成一个有序的体系。我将汉洛塔的层级结构与帝国的层级结构进行类比,创造出一种新的思维模型。

三、实现方案

1. 技术栈

  • HTML5 + CSS3
  • JavaScript
  • 响应式设计

2. 核心代码

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
    <title>汉诺塔 - 经典递归教学演示 | 慢速动画 清晰演示</title>
    <style>
        * {
            box-sizing: border-box;
            user-select: none;
        }

        body {
            font-family: 'Segoe UI', 'Poppins', 'Roboto', 'Microsoft YaHei', sans-serif;
            background: linear-gradient(145deg, #1a2a3a 0%, #0f1a24 100%);
            min-height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
            padding: 20px;
            margin: 0;
        }

        /* 主卡片容器 */
        .hanoi-container {
            max-width: 1100px;
            width: 100%;
            background: rgba(255, 255, 255, 0.1);
            backdrop-filter: blur(2px);
            border-radius: 48px;
            box-shadow: 0 25px 45px rgba(0, 0, 0, 0.3), inset 0 1px 1px rgba(255, 255, 255, 0.2);
            padding: 20px 25px 30px;
            transition: all 0.3s ease;
        }

        /* 标题区 */
        .title-section {
            text-align: center;
            margin-bottom: 20px;
        }
        h1 {
            margin: 0;
            font-size: 2.2rem;
            font-weight: 700;
            background: linear-gradient(135deg, #FFE6B0, #FFB347);
            background-clip: text;
            -webkit-background-clip: text;
            color: transparent;
            text-shadow: 0 2px 4px rgba(0,0,0,0.2);
            letter-spacing: 1px;
        }
        .sub {
            color: #c0e0ff;
            font-size: 0.9rem;
            border-top: 1px solid rgba(255,215,150,0.4);
            display: inline-block;
            padding-top: 6px;
            margin-top: 6px;
            font-weight: 300;
        }

        /* canvas 区域 */
        .canvas-area {
            background: #2c3e2f1a;
            border-radius: 40px;
            padding: 15px;
            box-shadow: inset 0 0 8px #00000020, 0 12px 20px rgba(0,0,0,0.3);
            margin-bottom: 20px;
        }
        canvas {
            display: block;
            margin: 0 auto;
            background: #fef3e2;
            background-image: radial-gradient(circle at 25% 40%, rgba(210,180,140,0.1) 2%, transparent 2.5%);
            background-size: 28px 28px;
            border-radius: 32px;
            box-shadow: 0 8px 18px rgba(0, 0, 0, 0.3);
            cursor: pointer;
            width: 100%;
            height: auto;
        }

        /* 控制面板 */
        .controls {
            background: #2e3b2cd9;
            backdrop-filter: blur(8px);
            border-radius: 60px;
            padding: 15px 20px;
            margin: 15px 0 10px;
            display: flex;
            flex-wrap: wrap;
            justify-content: center;
            gap: 15px;
            align-items: center;
            border: 1px solid rgba(255,215,150,0.5);
        }
        .ctrl-group {
            background: #1f2a1b;
            padding: 5px 15px;
            border-radius: 40px;
            display: flex;
            align-items: center;
            gap: 12px;
            box-shadow: inset 0 1px 2px #00000030, 0 2px 5px rgba(0,0,0,0.2);
        }
        .ctrl-group label {
            font-weight: 600;
            color: #FFE2A4;
            letter-spacing: 1px;
        }
        select, button {
            background: #f9e2b7;
            border: none;
            padding: 8px 18px;
            border-radius: 40px;
            font-weight: bold;
            font-size: 1rem;
            cursor: pointer;
            transition: all 0.2s ease;
            font-family: inherit;
            box-shadow: 0 2px 5px black;
        }
        select {
            background: #f5d98f;
        }
        button {
            background: #ffcd7e;
            color: #2c2b26;
        }
        button:hover {
            background: #ffbc5e;
            transform: scale(0.97);
            box-shadow: 0 1px 2px black;
        }
        button:active {
            transform: scale(0.95);
        }
        .stat {
            background: #00000066;
            padding: 6px 16px;
            border-radius: 32px;
            color: #FFE3A4;
            font-weight: bold;
            font-size: 1.2rem;
            font-family: monospace;
            letter-spacing: 1px;
        }
        .message-area {
            background: #1f2620cc;
            border-radius: 28px;
            padding: 8px 20px;
            text-align: center;
            color: #f7d98c;
            font-weight: 500;
            margin-top: 15px;
            font-size: 0.9rem;
            backdrop-filter: blur(4px);
            border-left: 6px solid #ffb347;
        }
        .footer-note {
            font-size: 0.75rem;
            text-align: center;
            margin-top: 20px;
            color: #bbaa7a;
        }
        @media (max-width: 680px) {
            .controls { border-radius: 30px; gap: 10px; }
            .ctrl-group { padding: 3px 12px; gap: 8px; }
            button, select { padding: 5px 12px; font-size: 0.8rem; }
            .stat { font-size: 1rem; }
            h1 { font-size: 1.7rem; }
        }
        .selected-indicator {
            font-weight: bold;
            color: #ffdd99;
            background: #00000055;
            border-radius: 20px;
            padding: 0 12px;
        }
    </style>
</head>
<body>
<div class="hanoi-container">
    <div class="title-section">
        <h1>🗼 汉诺塔 · 递归思维工坊</h1>
        <div class="sub">经典递归 | 手动探索 | 慢速自动演示 | 步骤清晰可见</div>
    </div>

    <div class="canvas-area">
        <canvas id="hanoiCanvas" width="900" height="500" style="width:100%; height:auto; max-width:900px; aspect-ratio:900/500"></canvas>
    </div>

    <div class="controls">
        <div class="ctrl-group">
            <label>📀 盘子数量</label>
            <select id="diskCountSelect">
                <option value="3">3 盘 (简单)</option>
                <option value="4" selected>4 盘 (推荐)</option>
                <option value="5">5 盘 (挑战)</option>
                <option value="6">6 盘 (大师)</option>
                <option value="7">7 盘 (递归之魂)</option>
                <option value="8">8 盘 (极限演示)</option>
            </select>
        </div>
        <div class="ctrl-group">
            <button id="resetBtn">🔄 重置游戏</button>
            <button id="autoSolveBtn">✨ 自动求解 (慢速动画)</button>
        </div>
        <div class="stat">
            🧮 移动次数: <span id="moveCount">0</span>
        </div>
        <div class="selected-indicator" id="selectedTowerMsg">
            ⚡ 未选中柱子
        </div>
    </div>

    <div class="message-area" id="messageBox">
        💡 点击柱子选择源柱 → 再点击目标柱移动盘子。合法移动遵循"小盘不能压大盘"。自动求解已调慢速度,便于观察递归过程。
    </div>
    <div class="footer-note">
        📐 汉诺塔规则:一次移动一个盘子,任何时候大盘不能在小盘之上。目标:将所有盘子从A移到C。 最小步数 = 2ⁿ-1
    </div>
</div>

<script>
    (function(){
        // ---------- DOM 元素 ----------
        const canvas = document.getElementById('hanoiCanvas');
        const ctx = canvas.getContext('2d');
        const diskCountSelect = document.getElementById('diskCountSelect');
        const resetBtn = document.getElementById('resetBtn');
        const autoSolveBtn = document.getElementById('autoSolveBtn');
        const moveCountSpan = document.getElementById('moveCount');
        const messageBox = document.getElementById('messageBox');
        const selectedTowerMsg = document.getElementById('selectedTowerMsg');

        // ---------- 全局参数 ----------
        let diskCount = 4;          // 当前盘子数量 (默认4)
        let towers = [[], [], []];  // 三个柱子, 每个存储盘子大小(1最小, diskCount最大)
        let moveCount = 0;
        let selectedTower = null;    // 当前选中的柱子索引 0,1,2 或 null
        let autoTimer = null;         // 自动求解定时器
        let solvingActive = false;    // 是否正在自动求解中
        let solvingSteps = [];         // 存储自动求解步骤 {from, to}
        let stepIndex = 0;             // 当前执行到第几步

        // 自动求解动画间隔 (毫秒) ------ 调慢至 1.2 秒,让每一步清晰可见
        const AUTO_STEP_INTERVAL = 1200;

        // ---------- canvas 几何配置 ----------
        const width = 900, height = 500;
        canvas.width = width;
        canvas.height = height;
        // 柱子X坐标 (三个)
        const pegX = [200, 450, 700];
        const baseY = 420;          // 柱子底部Y坐标
        const pegTopY = 120;         // 柱子顶部Y坐标
        const diskHeight = 28;        // 每个盘子的高度(px)
        const diskGap = 2;            // 盘子间隙
        // 最大/最小盘子宽度
        const maxDiskWidth = 180;
        const minDiskWidth = 50;

        // ---------- 辅助函数: 显示消息 (3秒后自动恢复普通提示) ----------
        let msgTimeout = null;
        function setMessage(msg, isError = false) {
            if(msgTimeout) clearTimeout(msgTimeout);
            messageBox.innerHTML = isError ? `⚠️ ${msg}` : `💡 ${msg}`;
            if(!isError) {
                msgTimeout = setTimeout(() => {
                    if(messageBox.innerHTML.includes(msg))
                        messageBox.innerHTML = "💡 点击柱子选择源柱 → 再点击目标柱移动盘子。合法移动遵循"小盘不能压大盘"。自动求解已调慢速度。";
                }, 3200);
            } else {
                msgTimeout = setTimeout(() => {
                    if(messageBox.innerHTML.includes(msg))
                        messageBox.innerHTML = "💡 点击柱子选择源柱 → 再点击目标柱移动盘子。合法移动遵循"小盘不能压大盘"。自动求解已调慢速度。";
                }, 2500);
            }
        }

        // 更新移动次数显示
        function updateMoveDisplay() {
            moveCountSpan.innerText = moveCount;
        }

        // 更新选中提示UI
        function updateSelectedUI() {
            if(selectedTower !== null && !solvingActive) {
                const towerNames = ['A柱', 'B柱', 'C柱'];
                selectedTowerMsg.innerHTML = `🎯 已选中: ${towerNames[selectedTower]}  (再次点击取消)`;
            } else {
                selectedTowerMsg.innerHTML = `⚡ 未选中柱子`;
            }
        }

        // 检查胜利 (所有盘子在C柱,索引2)
        function checkVictory() {
            if(towers[2].length === diskCount) {
                if(!solvingActive) {
                    setMessage(`🎉 恭喜!在 ${moveCount} 步内完成汉诺塔! 最优步数: ${Math.pow(2, diskCount)-1} 步 🎉`);
                } else {
                    setMessage(`✨ 自动演示完成!总步数: ${moveCount} (最优解) ✨`);
                }
                return true;
            }
            return false;
        }

        // ---------- 核心移动逻辑 ----------
        // 尝试从 from 移动盘子到 to, 合法则执行并返回true, 否则false
        function tryMove(from, to) {
            if(solvingActive) {
                setMessage("自动求解进行中, 请按重置或等待完成", false);
                return false;
            }
            if(from === to) {
                setMessage("❓ 源柱子和目标柱子相同,取消选中", false);
                return false;
            }
            const fromPeg = towers[from];
            const toPeg = towers[to];
            if(fromPeg.length === 0) {
                setMessage(`❌ 源柱子上没有盘子`, true);
                return false;
            }
            const topFrom = fromPeg[fromPeg.length-1];
            if(toPeg.length > 0) {
                const topTo = toPeg[toPeg.length-1];
                if(topFrom > topTo) {
                    setMessage(`🚫 非法移动: 大盘不能压在小盘上`, true);
                    return false;
                }
            }
            // 执行移动
            const movedDisk = fromPeg.pop();
            toPeg.push(movedDisk);
            moveCount++;
            updateMoveDisplay();
            draw();  // 重绘
            // 胜利检测
            const isWin = checkVictory();
            if(isWin && !solvingActive) {
                draw(); // 重新绘制高亮胜利效果
            }
            return true;
        }

        // 重置游戏 (清除选中, 停止自动求解)
        function resetGame(keepDiskCount = true) {
            // 停止自动求解任务
            stopAutoSolve();
            // 重置状态
            selectedTower = null;
            solvingActive = false;
            moveCount = 0;
            updateMoveDisplay();
            // 初始化塔盘
            initTowers(diskCount);
            draw();
            setMessage("🔄 游戏已重置,可以开始移动或自动求解", false);
            updateSelectedUI();
            // 清除胜利残留消息
        }

        // 初始化柱子数据 (盘子全部在A柱)
        function initTowers(count) {
            // 盘子大小: 1最小, count最大 (底层最大)
            const newTowers = [[], [], []];
            for(let i = count; i >= 1; i--) {
                newTowers[0].push(i);   // 栈底是最大盘子(数值大), 栈顶最小盘子
            }
            towers = newTowers;
        }

        // 根据盘子数量改变时调用 (完全重置)
        function changeDiskCount(newCount) {
            if(solvingActive) stopAutoSolve();
            diskCount = newCount;
            selectedTower = null;
            moveCount = 0;
            updateMoveDisplay();
            initTowers(diskCount);
            draw();
            setMessage(`盘子数量调整为 ${diskCount} 个,最优解需要 ${Math.pow(2, diskCount)-1} 步`, false);
            updateSelectedUI();
        }

        // ---------- 递归生成最优步骤 (从src到dst借助aux) ----------
        function generateHanoiSteps(n, src, dst, aux, stepsArr) {
            if(n === 1) {
                stepsArr.push({from: src, to: dst});
                return;
            }
            generateHanoiSteps(n-1, src, aux, dst, stepsArr);
            stepsArr.push({from: src, to: dst});
            generateHanoiSteps(n-1, aux, dst, src, stepsArr);
        }

        // 自动求解:重置游戏 -> 生成步骤序列 -> 逐步移动 (慢速)
        function startAutoSolve() {
            if(solvingActive) {
                setMessage("已有自动求解进行中,请先重置", true);
                return;
            }
            // 停止之前的任何残留定时器
            if(autoTimer) clearInterval(autoTimer);
            // 重置游戏到干净状态 (停止旧动画, 清状态)
            stopAutoSolve();      // 确保标记清理
            // 重置游戏(数据初始化,移动次数归零,清除选中)
            selectedTower = null;
            solvingActive = false;  // 临时关闭避免干扰重置
            moveCount = 0;
            updateMoveDisplay();
            initTowers(diskCount);
            draw();
            updateSelectedUI();

            // 生成最优步骤序列 (从A(0) 到 C(2) 借助 B(1))
            const steps = [];
            generateHanoiSteps(diskCount, 0, 2, 1, steps);
            solvingSteps = steps;
            stepIndex = 0;

            if(steps.length === 0) {
                setMessage("无需移动,已完成", false);
                return;
            }

            solvingActive = true;
            setMessage(`✨ 自动求解开始,共 ${steps.length} 步,每步间隔 ${AUTO_STEP_INTERVAL/1000} 秒 ✨`, false);
            // 开始定时器执行移动 (慢速)
            autoTimer = setInterval(() => {
                if(!solvingActive) {
                    // 如果标志被重置,清除定时器
                    if(autoTimer) clearInterval(autoTimer);
                    autoTimer = null;
                    return;
                }
                if(stepIndex >= solvingSteps.length) {
                    // 自动求解完成
                    clearInterval(autoTimer);
                    autoTimer = null;
                    solvingActive = false;
                    setMessage(`🎉 自动求解完成!总步数: ${moveCount},最优解法演示结束`, false);
                    checkVictory();   // 显示胜利消息
                    updateSelectedUI();
                    draw();
                    return;
                }
                const step = solvingSteps[stepIndex];
                const success = executeAutoMove(step.from, step.to);
                if(success) {
                    stepIndex++;
                } else {
                    // 理论上不会非法,但若出现异常停止
                    clearInterval(autoTimer);
                    autoTimer = null;
                    solvingActive = false;
                    setMessage(`自动求解出错,已停止`, true);
                    updateSelectedUI();
                    draw();
                }
            }, AUTO_STEP_INTERVAL);
        }

        // 自动移动专用 (不检查选中,且忽略手动限制)
        function executeAutoMove(from, to) {
            if(from === to) return true;
            const fromPeg = towers[from];
            const toPeg = towers[to];
            if(fromPeg.length === 0) return false;
            const topFrom = fromPeg[fromPeg.length-1];
            if(toPeg.length > 0) {
                const topTo = toPeg[toPeg.length-1];
                if(topFrom > topTo) return false;
            }
            // 移动盘子
            const moved = fromPeg.pop();
            toPeg.push(moved);
            moveCount++;
            updateMoveDisplay();
            draw();
            checkVictory();
            return true;
        }

        // 停止自动求解 (清定时器, 重置标志)
        function stopAutoSolve() {
            if(autoTimer) {
                clearInterval(autoTimer);
                autoTimer = null;
            }
            solvingActive = false;
            solvingSteps = [];
            stepIndex = 0;
        }

        // ---------- canvas 绘图模块 (精美盘子+柱子) ----------
        function draw() {
            if(!ctx) return;
            ctx.clearRect(0, 0, width, height);
            
            // 背景木纹风格
            ctx.fillStyle = "#fef0db";
            ctx.fillRect(0, 0, width, height);
            // 装饰网格线
            ctx.beginPath();
            ctx.strokeStyle = "#e9cf9e";
            ctx.lineWidth = 1;
            for(let i = 0; i < width; i+= 40){
                ctx.beginPath();
                ctx.moveTo(i, 0);
                ctx.lineTo(i, height);
                ctx.stroke();
            }
            
            // 绘制底座平台
            ctx.fillStyle = "#b87c4f";
            ctx.shadowBlur = 0;
            ctx.fillRect(0, baseY+5, width, 12);
            ctx.fillStyle = "#8b5a2b";
            ctx.fillRect(0, baseY+8, width, 8);
            ctx.fillStyle = "#dba562";
            ctx.fillRect(0, baseY+2, width, 6);
            
            // 绘制柱子 (木纹)
            for(let i=0; i<pegX.length; i++) {
                const x = pegX[i];
                ctx.beginPath();
                ctx.moveTo(x-10, baseY);
                ctx.lineTo(x+10, baseY);
                ctx.lineTo(x+8, pegTopY-8);
                ctx.lineTo(x-8, pegTopY-8);
                ctx.fillStyle = "#c68944";
                ctx.fill();
                ctx.fillStyle = "#a56b2f";
                ctx.fillRect(x-4, pegTopY-8, 8, baseY - pegTopY+10);
                // 柱顶装饰
                ctx.beginPath();
                ctx.arc(x, pegTopY-12, 10, 0, Math.PI*2);
                ctx.fillStyle = "#e5b56a";
                ctx.fill();
                ctx.fillStyle = "#ca9622";
                ctx.beginPath();
                ctx.arc(x, pegTopY-12, 6, 0, Math.PI*2);
                ctx.fill();
            }
            
            // 高亮选中的柱子 (外发光)
            if(selectedTower !== null && !solvingActive) {
                const idx = selectedTower;
                const x = pegX[idx];
                ctx.save();
                ctx.shadowBlur = 18;
                ctx.shadowColor = "#ffc285";
                ctx.beginPath();
                ctx.rect(x-20, pegTopY-20, 40, baseY - pegTopY + 30);
                ctx.fillStyle = "rgba(255,210,120,0.2)";
                ctx.fill();
                ctx.restore();
                // 绘制金色光圈
                ctx.beginPath();
                ctx.arc(x, baseY-15, 28, 0, Math.PI*2);
                ctx.strokeStyle = "#ffae5a";
                ctx.lineWidth = 3;
                ctx.stroke();
            }
            
            // 绘制盘子: 对每个柱子从底向上绘制 (栈底在数组头部)
            for(let p=0; p<towers.length; p++) {
                const stack = towers[p];
                const pegXpos = pegX[p];
                const count = stack.length;
                for(let i=0; i<count; i++) {
                    const diskSize = stack[i];  // 1最小, diskCount最大
                    // 宽度比例映射
                    const widthRatio = (diskSize - 1) / (diskCount - 1 || 1);
                    const diskW = minDiskWidth + widthRatio * (maxDiskWidth - minDiskWidth);
                    const yPos = baseY - (i+1) * (diskHeight + diskGap) + 3;
                    const xPos = pegXpos - diskW/2;
                    // 渐变色填充
                    const gradient = ctx.createLinearGradient(xPos, yPos, xPos+diskW, yPos+diskHeight);
                    gradient.addColorStop(0, `hsl(${35 + diskSize * 12}, 70%, 58%)`);
                    gradient.addColorStop(1, `hsl(${25 + diskSize * 10}, 80%, 48%)`);
                    ctx.fillStyle = gradient;
                    ctx.shadowBlur = 3;
                    ctx.shadowColor = "rgba(0,0,0,0.3)";
                    ctx.beginPath();
                    ctx.roundRect(xPos, yPos, diskW, diskHeight-2, 12);
                    ctx.fill();
                    ctx.fillStyle = "#fff3cf";
                    ctx.font = "bold 16px 'Segoe UI'";
                    ctx.shadowBlur = 0;
                    ctx.fillText(`${diskSize}`, xPos+diskW/2-7, yPos+diskHeight-9);
                }
            }
            // 柱标字母
            ctx.font = "bold 22system-ui, 'Segoe UI'";
            ctx.fillStyle = "#5d3a1a";
            ctx.shadowBlur = 0;
            ctx.fillText("A", pegX[0]-12, baseY+28);
            ctx.fillText("B", pegX[1]-12, baseY+28);
            ctx.fillText("C", pegX[2]-12, baseY+28);
            ctx.fillStyle = "#f7d48b";
            ctx.font = "bold 20px monospace";
            ctx.fillText("⦿", pegX[0]-18, baseY+25);
            ctx.fillText("⦿", pegX[1]-18, baseY+25);
            ctx.fillText("⦿", pegX[2]-18, baseY+25);
        }
        
        // roundRect辅助
        if (!CanvasRenderingContext2D.prototype.roundRect) {
            CanvasRenderingContext2D.prototype.roundRect = function(x, y, w, h, r) {
                if (w < 2 * r) r = w / 2;
                if (h < 2 * r) r = h / 2;
                this.moveTo(x+r, y);
                this.lineTo(x+w-r, y);
                this.quadraticCurveTo(x+w, y, x+w, y+r);
                this.lineTo(x+w, y+h-r);
                this.quadraticCurveTo(x+w, y+h, x+w-r, y+h);
                this.lineTo(x+r, y+h);
                this.quadraticCurveTo(x, y+h, x, y+h-r);
                this.lineTo(x, y+r);
                this.quadraticCurveTo(x, y, x+r, y);
                return this;
            };
        }
        
        // ---------- 点击 canvas 交互 (获取柱子索引) ----------
        function handleCanvasClick(e) {
            if(solvingActive) {
                setMessage("自动求解中,请先点击【重置】或等待完成", true);
                return;
            }
            const rect = canvas.getBoundingClientRect();
            const scaleX = canvas.width / rect.width;   // canvas物理像素比
            const scaleY = canvas.height / rect.height;
            let mouseX = (e.clientX - rect.left) * scaleX;
            let mouseY = (e.clientY - rect.top) * scaleY;
            // 检测点击哪个柱子区域 (横坐标范围)
            let clickedPeg = null;
            for(let i=0; i<pegX.length; i++) {
                const xCenter = pegX[i];
                if(mouseX >= xCenter-65 && mouseX <= xCenter+65 && mouseY >= 70 && mouseY <= baseY+40) {
                    clickedPeg = i;
                    break;
                }
            }
            if(clickedPeg === null) {
                // 点空白取消选中
                if(selectedTower !== null) {
                    selectedTower = null;
                    setMessage("已取消选中", false);
                    updateSelectedUI();
                    draw();
                }
                return;
            }
            // 交互逻辑: 无选中 -> 选中柱子 (必须有盘子)
            if(selectedTower === null) {
                if(towers[clickedPeg].length === 0) {
                    setMessage(`❌ ${['A','B','C'][clickedPeg]}柱没有盘子,不能选中`, true);
                    return;
                }
                selectedTower = clickedPeg;
                setMessage(`✅ 已选中 ${['A','B','C'][clickedPeg]}柱,点击目标柱子移动`, false);
                updateSelectedUI();
                draw();
                return;
            } 
            // 已有选中 -> 尝试移动
            const source = selectedTower;
            const target = clickedPeg;
            const success = tryMove(source, target);
            if(success) {
                // 移动成功, 清除选中
                selectedTower = null;
                updateSelectedUI();
                draw();
                // 胜利时额外效果
                if(towers[2].length === diskCount) setMessage(`🎉 胜利! 总步数:${moveCount}`, false);
            } else {
                // 移动非法, 不清除选中,保留选中并提示
                draw();
            }
        }
        
        // ---------- 事件绑定与初始化 ----------
        function bindEvents() {
            canvas.addEventListener('click', handleCanvasClick);
            resetBtn.addEventListener('click', () => {
                if(solvingActive) stopAutoSolve();
                resetGame();
                selectedTower = null;
                updateSelectedUI();
                draw();
            });
            autoSolveBtn.addEventListener('click', () => {
                if(solvingActive) {
                    setMessage("已有自动演示进行中,请按重置再试", true);
                    return;
                }
                startAutoSolve();
            });
            diskCountSelect.addEventListener('change', (e) => {
                const newVal = parseInt(e.target.value, 10);
                if(!isNaN(newVal) && newVal !== diskCount) {
                    if(solvingActive) stopAutoSolve();
                    changeDiskCount(newVal);
                    selectedTower = null;
                    updateSelectedUI();
                }
            });
        }
        
        // 初始化
        function init() {
            diskCount = 4;
            diskCountSelect.value = "4";
            initTowers(4);
            moveCount = 0;
            selectedTower = null;
            solvingActive = false;
            updateMoveDisplay();
            draw();
            bindEvents();
            updateSelectedUI();
            setMessage("🎓 欢迎!点击柱子开始移动,或点击【自动求解】观看慢速递归演示 (每步1.2秒)", false);
        }
        
        init();
    })();
</script>
</body>
</html>

四、结构解析

1. 层级结构

汉洛塔的结构体现了一种清晰的层级关系:

  • 核心层:最大的盘子,相当于帝国的中央核心
  • 中间层:中等大小的盘子,相当于帝国的中层管理
  • 边缘层:最小的盘子,相当于帝国的基层单位

2. 移动规则

汉洛塔的移动规则也反映了帝国管理的智慧:

  • 每次只能移动一个盘子(管理决策的单一性)
  • 大盘子不能放在小盘子上面(层级的不可跨越性)
  • 通过递归的方式解决问题(分而治之的管理策略)

五、思维启发

1. 递归思维

汉洛塔问题的解决过程展示了递归思维的强大之处。在帝国管理中,我们也可以采用类似的思维方式:将复杂的问题分解为更小的子问题,然后逐个解决。

2. 层级管理

汉洛塔的层级结构告诉我们,一个良好的组织结构应该是层次分明的,每个层级都有其明确的职责和位置。

3. 秩序与规则

汉洛塔的移动规则强调了秩序的重要性。在任何组织中,明确的规则和秩序都是其正常运行的保障。

六、项目价值

  1. 教育价值:通过可视化的方式展示了汉洛塔问题的解决过程,有助于理解递归算法
  2. 思维启发:将算法与帝国结构相结合,提供了一种新的思维视角
  3. 艺术价值:精美的视觉设计和流畅的动画效果,使抽象的算法变得生动有趣

七、未来展望

  1. 增加交互性:允许用户自定义盘子数量和移动速度
  2. 添加更多主题:除了帝国结构,还可以探索其他领域的类比
  3. 优化算法:实现更高效的汉洛塔算法可视化
  4. 响应式设计:确保在各种设备上都能良好显示

八、结语

汉洛塔帝国结构思维是一个将经典算法与现实世界结构相结合的尝试。通过这个项目,我们不仅可以更直观地理解汉洛塔问题,还可以从中获得关于组织管理的启发。

正如汉洛塔的盘子需要按照一定的规则有序移动一样,一个成功的帝国(或任何组织)也需要清晰的层级结构和明确的运行规则。递归思维不仅是解决算法问题的有效方法,也是处理复杂管理问题的重要工具。

希望这个项目能够为你带来新的思考视角和启发!

相关推荐
木子n11 小时前
第2篇:坐标变换与数学基础:FOC算法的核心数学工具
算法·电机控制·foc
阿Y加油吧2 小时前
两道经典 DP 题:零钱兑换 & 单词拆分(完全背包 + 字符串 DP)
算法
疯狂打码的少年2 小时前
有序线性表删除一个元素:顺序存储 vs 单链表,平均要移动多少个元素?
数据结构·算法·链表
y = xⁿ2 小时前
20天速通LeetCode day07:前缀和
数据结构·算法·leetcode
载数而行5203 小时前
算法集训1:模拟,枚举,错误分析,前缀和,差分
算法
hehelm3 小时前
vector模拟实现
前端·javascript·算法
Tina学编程4 小时前
[HOT 100]今日一练------划分字母区间
算法·hot 100
RTC老炮4 小时前
RaptorQ前向纠错算法架构分析
网络·算法·架构·webrtc
故事和你914 小时前
洛谷-数据结构1-1-线性表2
开发语言·数据结构·算法·动态规划·图论