Python实现汉诺塔演示程序

Python实现汉诺塔演示程序

汉诺塔问题

一个板子上有三根柱子以及一些大小各不相同的圆盘。我们分别把这三根柱子叫做起始柱A、辅助柱B及目标柱C,汉诺塔移动圆盘的规则如下:

把起始柱A上所有的圆盘都移动到C柱,且在移动过程中始终保持圆盘从小到大排列,即大盘在下、小盘在上。

换句话说,用户在移动圆盘时遵循以下规则:

(1)每次只能移动一个圆盘。

(2)每次移动只能将柱子顶部的圆盘移动到另一个柱子的顶部。

(3)圆盘只能放在更大的圆盘上面或空柱子上。

汉诺塔问题是用递归方法求解的一个典型问题,在此给出算法的模拟演示,使学生更直观的了解递归算法的思想。

这里给出Python实现,用鼠标拖动圆盘。

先给出运行效果示意图:

源码如下:

import tkinter as tk
from tkinter import ttk, messagebox

class HanoiGame(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("汉诺塔游戏")
        self.geometry("600x550")
        self.canvas = tk.Canvas(self, width=600, height=400, bg="#f0f0f0")
        self.canvas.pack()

        # 初始化游戏变量
        self.numDisks = 3  # 圆盘数量
        self.towers = [[], [], []]  # 代表三个塔的列表
        self.draggingDisk = None  # 当前正在拖动的圆盘
        self.draggingDiskSize = 0  # 拖动的圆盘的大小
        self.init_controls()  # 初始化控制面板
        self.init_game()  # 初始化游戏

    def init_controls(self):
        control_frame = tk.Frame(self) # 创建控制面板框架
        control_frame.pack(fill=tk.X, pady=5)

        tk.Label(control_frame, text="选择圆盘数量:").pack(side=tk.LEFT, padx=5)
        self.numDisksVar = tk.IntVar(value=3)
        numDisksCombo = ttk.Combobox(control_frame, textvariable=self.numDisksVar, values=[1, 2, 3, 4, 5, 6, 7, 8], width=3)
        numDisksCombo.pack(side=tk.LEFT, padx=5)

        tk.Button(control_frame, text="开始游戏", command=self.init_game).pack(side=tk.LEFT, padx=5)
        tk.Button(control_frame, text="提示", command=self.show_hints).pack(side=tk.LEFT, padx=5)

        #用一个新的Frame用于包含文本框和滚动条。这样做是为了确保它们能够一起正确地布局
        hints_frame = tk.Frame(self)  
        hints_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
        self.hintsBox = tk.Text(hints_frame, height=5, state='disabled') # 初始化为禁用状
        self.hintsBox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        scrollbar = tk.Scrollbar(hints_frame, command=self.hintsBox.yview)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)

        self.hintsBox.config(yscrollcommand=scrollbar.set)

    def init_game(self):
        """初始化或重置游戏状态"""
        self.numDisks = self.numDisksVar.get()  # 获取用户选择的圆盘数量
        self.towers = [[i for i in range(self.numDisks, 0, -1)], [], []]  # 重置塔
        self.draw_game()  # 绘制游戏界面
        self.clear_hints()  # 清空提示信息

    def clear_hints(self):
        self.hintsBox.config(state='normal')  # 允许编辑文本框以清空内容
        self.hintsBox.delete(1.0, tk.END)  # 清空文本框
        self.hintsBox.config(state='disabled')  # 再次禁用文本框以防止编辑

    def draw_game(self):
        self.canvas.delete("all")
        for i, label in enumerate(["A", "B", "C"]):
            x = 100 + i * 200
            self.canvas.create_rectangle(x - 10, 300, x + 10, 150, fill="gray")
            self.canvas.create_text(x, 140, text=label, font=("Arial", 16))
        for i, tower in enumerate(self.towers):
            for j, disk in enumerate(tower):
                self.draw_disk(i, j, disk)
        self.canvas.tag_bind("disk", "<ButtonPress-1>", self.start_drag)
        self.canvas.tag_bind("disk", "<B1-Motion>", self.drag)
        self.canvas.tag_bind("disk", "<ButtonRelease-1>", self.stop_drag)

    def draw_disk(self, tower_index, disk_index, disk_size):
        x = 100 + tower_index * 200
        y = 300 - disk_index * 20
        self.canvas.create_rectangle(x - disk_size * 10, y, x + disk_size * 10, y - 20, fill="blue", tags=("disk", f"{tower_index}-{disk_index}", f"size{disk_size}"))

    def start_drag(self, event):
        closest_disk = self.canvas.find_closest(event.x, event.y)[0]
        tags = self.canvas.gettags(closest_disk)
        for tag in tags:
            if tag.startswith("size"):
                self.draggingDiskSize = int(tag.replace("size", ""))
            if "-" in tag:
                tower_index, disk_index = map(int, tag.split("-"))
                # 检查是否是塔顶的圆盘
                if disk_index == len(self.towers[tower_index]) - 1:
                    self.draggingDisk = closest_disk
                else:
                    self.draggingDisk = None
                    return  # 如果不是塔顶的圆盘,则不允许拖动

    def drag(self, event):
        if self.draggingDisk:
            self.canvas.coords(self.draggingDisk, event.x - self.draggingDiskSize * 10, event.y - 10, event.x + self.draggingDiskSize * 10, event.y + 10)

    def stop_drag(self, event):
        if self.draggingDisk:
            tower_index = min(max((event.x - 100) // 200, 0), 2)
            disk_tag = self.canvas.gettags(self.draggingDisk)[1]
            source_tower, disk_index = map(int, disk_tag.split("-"))
            disk_size = self.towers[source_tower][disk_index]
            if not self.towers[tower_index] or disk_size < self.towers[tower_index][-1]:
                self.towers[tower_index].append(self.towers[source_tower].pop(disk_index))
                self.draw_game()
                self.check_win()
            else:
                self.draw_game()  # 重新绘制以修正位置
            self.draggingDisk = None

    def check_win(self):
        # 检查是否所有圆盘都移动到了最右边的柱子上
        if len(self.towers[2]) == self.numDisks:
            messagebox.showinfo("成功", "恭喜你成功完成了游戏!")

    def show_hints(self):
        """显示解决游戏的步骤提示"""
        steps = []
        self.generate_hanoi_steps(self.numDisks, 'A', 'C', 'B', steps)
        self.hintsBox.config(state='normal')
        self.hintsBox.delete(1.0, tk.END)
        for i, step in enumerate(steps, start=1):  # start=1 表示序号从1开始
            self.hintsBox.insert(tk.END, f"{i}. {step}\n")  # 在每条提示信息前加入序号
        self.hintsBox.config(state='disabled')

    def generate_hanoi_steps(self, n, source, target, auxiliary, steps):
        if n > 0:
            self.generate_hanoi_steps(n - 1, source, auxiliary, target, steps)
            steps.append(f"{source} → {target}")
            self.generate_hanoi_steps(n - 1, auxiliary, target, source, steps)

if __name__ == "__main__":
    app = HanoiGame()
    app.mainloop()

附录

汉诺塔递归算法文字版

# 参数:n 层数,a 起点 , b 辅助(中转) c 目标
def  move(n,a,b,c):
  if n==1:                    #当只有一个圆盘时只需从最左端移向最右端
    print(a,'-->',c)          #面板可视化,可以通过列举图标表现移动的步骤形式
  else:
    move(n-1,a,c,b)      #将前n-1个盘子从a移动到b上
    move(1,a,b,c)        #将最底下的最后一个盘子从a移动到c上
    move(n-1,b,a,c)      #将b上的n-1个盘子移动到c上

n=int(input("请输入汉诺塔的层数并回车:\n"))
move(n,'A','B','C')

  • 8
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

学习&实践爱好者

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值