汉洛塔帝国结构思维

一、项目简介

汉洛塔(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. 核心代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
<!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. 响应式设计:确保在各种设备上都能良好显示

八、结语

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

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

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