import tkinter as tk
from tkinter import ttk, messagebox
import copy
from collections import deque
import time
class WaterSortGUI:
def __init__(self, root):
self.root = root
self.root.title("Water Sort Puzzle Solver")
self.root.geometry("900x700")
self.root.resizable(True, True)
# 确保中文正常显示
self.style = ttk.Style()
self.style.configure("TLabel", font=("SimHei", 10))
self.style.configure("TButton", font=("SimHei", 10))
# 状态变量
self.initial_bottles = []
self.max_capacity = 4
self.solution = None
self.current_step = 0
self.auto_play = False
self.play_speed = 1.0 # 播放速度因子
# 创建界面组件
self.create_widgets()
def create_widgets(self):
# 顶部控制栏
control_frame = ttk.Frame(self.root)
control_frame.pack(fill=tk.X, padx=10, pady=10)
ttk.Label(control_frame, text="瓶子数量:").pack(side=tk.LEFT, padx=5)
self.num_bottles_var = tk.StringVar(value="4")
ttk.Entry(control_frame, textvariable=self.num_bottles_var, width=5).pack(side=tk.LEFT, padx=5)
ttk.Label(control_frame, text="最大容量:").pack(side=tk.LEFT, padx=5)
self.capacity_var = tk.StringVar(value="4")
ttk.Entry(control_frame, textvariable=self.capacity_var, width=5).pack(side=tk.LEFT, padx=5)
ttk.Button(control_frame, text="输入初始状态", command=self.input_initial_state).pack(side=tk.LEFT, padx=10)
ttk.Button(control_frame, text="求解", command=self.solve_puzzle).pack(side=tk.LEFT, padx=10)
self.auto_play_var = tk.BooleanVar(value=False)
ttk.Checkbutton(control_frame, text="自动播放", variable=self.auto_play_var).pack(side=tk.LEFT, padx=10)
ttk.Label(control_frame, text="播放速度:").pack(side=tk.LEFT, padx=5)
self.speed_scale = ttk.Scale(control_frame, from_=0.2, to=2.0, orient=tk.HORIZONTAL,
variable=self.play_speed, length=100)
self.speed_scale.pack(side=tk.LEFT, padx=5)
ttk.Button(control_frame, text="上一步", command=self.prev_step).pack(side=tk.LEFT, padx=10)
ttk.Button(control_frame, text="下一步", command=self.next_step).pack(side=tk.LEFT, padx=10)
# 状态显示区
self.status_frame = ttk.Frame(self.root)
self.status_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
ttk.Label(self.status_frame, text="状态:").pack(anchor=tk.W)
self.status_text = tk.Text(self.status_frame, height=5, wrap=tk.WORD)
self.status_text.pack(fill=tk.X, padx=5, pady=5)
self.status_text.config(state=tk.DISABLED)
# 瓶子可视化区
self.bottles_frame = ttk.Frame(self.root)
self.bottles_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# 解决方案区
solution_frame = ttk.Frame(self.root)
solution_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
ttk.Label(solution_frame, text="解决方案:").pack(anchor=tk.W)
self.solution_text = tk.Text(solution_frame, height=10, wrap=tk.WORD)
self.solution_text.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
self.solution_text.config(state=tk.DISABLED)
def input_initial_state(self):
"""获取用户输入的初始状态"""
try:
num_bottles = int(self.num_bottles_var.get())
self.max_capacity = int(self.capacity_var.get())
if num_bottles <= 0 or self.max_capacity <= 0:
messagebox.showerror("错误", "瓶子数量和容量必须为正整数")
return
input_window = tk.Toplevel(self.root)
input_window.title("输入初始状态")
input_window.geometry(f"{400 + num_bottles * 100}x300")
bottles_frame = ttk.Frame(input_window)
bottles_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
entry_widgets = []
for i in range(num_bottles):
ttk.Label(bottles_frame, text=f"瓶子 {i+1}:").grid(row=i, column=0, padx=5, pady=5, sticky=tk.W)
entry = ttk.Entry(bottles_frame, width=20)
entry.grid(row=i, column=1, padx=5, pady=5)
entry_widgets.append(entry)
def confirm_input():
self.initial_bottles = []
for i in range(num_bottles):
colors = entry_widgets[i].get().strip().split()
if len(colors) > self.max_capacity:
messagebox.showerror("错误", f"瓶子 {i+1} 的颜色数量超过容量")
return
self.initial_bottles.append(colors)
self.update_bottles_display()
self.status_text.config(state=tk.NORMAL)
self.status_text.delete(1.0, tk.END)
self.status_text.insert(tk.END, f"初始状态: {self.initial_bottles}")
self.status_text.config(state=tk.DISABLED)
input_window.destroy()
ttk.Button(input_window, text="确认", command=confirm_input).pack(pady=10)
except ValueError:
messagebox.showerror("错误", "请输入有效的数字")
def update_bottles_display(self):
"""更新瓶子可视化显示"""
# 清除现有瓶子
for widget in self.bottles_frame.winfo_children():
widget.destroy()
if not self.initial_bottles:
ttk.Label(self.bottles_frame, text="请先输入初始状态").pack()
return
# 计算瓶子宽度和间距
num_bottles = len(self.initial_bottles)
max_width = self.root.winfo_width() - 100
bottle_width = max(80, max_width // num_bottles - 20)
for i, bottle in enumerate(self.initial_bottles):
bottle_frame = ttk.Frame(self.bottles_frame, width=bottle_width, height=300)
bottle_frame.pack(side=tk.LEFT, padx=10, pady=10, fill=tk.BOTH)
# 绘制瓶子轮廓
bottle_canvas = tk.Canvas(bottle_frame, bg="white", width=bottle_width, height=300)
bottle_canvas.pack(fill=tk.BOTH, expand=True)
# 瓶子主体
bottle_body = bottle_canvas.create_rectangle(
10, 50, bottle_width-10, 250,
outline="black", width=2
)
# 瓶口
bottle_neck = bottle_canvas.create_rectangle(
bottle_width//2 - 10, 30, bottle_width//2 + 10, 50,
outline="black", width=2, fill="gray"
)
# 绘制颜色层
color_height = 200 / self.max_capacity # 每层颜色高度
for j, color in enumerate(bottle):
if j >= self.max_capacity:
break
y = 250 - (j + 1) * color_height
color = self.get_color_code(color)
bottle_canvas.create_rectangle(
15, y, bottle_width-15, 250 - j * color_height,
fill=color, outline=""
)
# 瓶子编号
bottle_canvas.create_text(
bottle_width//2, 270, text=f"瓶子 {i+1}"
)
def get_color_code(self, color):
"""将颜色字符串转换为RGB颜色码"""
color_map = {
'R': 'red', 'G': 'green', 'B': 'blue',
'Y': 'yellow', 'O': 'orange', 'P': 'purple',
'W': 'white', 'Bk': 'black', 'C': 'cyan'
}
return color_map.get(color, 'gray')
def solve_puzzle(self):
"""求解问题"""
if not self.initial_bottles:
messagebox.showinfo("提示", "请先输入初始状态")
return
try:
self.status_text.config(state=tk.NORMAL)
self.status_text.delete(1.0, tk.END)
self.status_text.insert(tk.END, "正在求解...")
self.status_text.config(state=tk.DISABLED)
self.root.update()
# 调用原有算法求解
self.solution = solve_water_sort(self.initial_bottles, self.max_capacity)
if not self.solution:
self.status_text.config(state=tk.NORMAL)
self.status_text.delete(1.0, tk.END)
self.status_text.insert(tk.END, "无解")
self.status_text.config(state=tk.DISABLED)
messagebox.showinfo("结果", "无解")
return
# 显示解决方案
self.solution_text.config(state=tk.NORMAL)
self.solution_text.delete(1.0, tk.END)
self.solution_text.insert(tk.END, f"共需要 {len(self.solution)-1} 步\n\n")
for i, state in enumerate(self.solution):
self.solution_text.insert(tk.END, f"步骤 {i}:\n")
for j, bottle in enumerate(state):
self.solution_text.insert(tk.END, f"瓶子 {j+1}: {bottle}\n")
self.solution_text.insert(tk.END, "\n")
self.solution_text.config(state=tk.DISABLED)
self.status_text.config(state=tk.NORMAL)
self.status_text.delete(1.0, tk.END)
self.status_text.insert(tk.END, f"已找到解决方案,共 {len(self.solution)-1} 步")
self.status_text.config(state=tk.DISABLED)
self.current_step = 0
self.update_bottles_display_from_solution()
if self.auto_play_var.get():
self.auto_play_solution()
except Exception as e:
messagebox.showerror("错误", f"求解过程中发生错误: {str(e)}")
self.status_text.config(state=tk.NORMAL)
self.status_text.delete(1.0, tk.END)
self.status_text.insert(tk.END, f"错误: {str(e)}")
self.status_text.config(state=tk.DISABLED)
def update_bottles_display_from_solution(self):
"""根据解决方案更新瓶子显示"""
if not self.solution or self.current_step >= len(self.solution):
return
state = self.solution[self.current_step]
self.initial_bottles = [copy.deepcopy(b) for b in state]
self.update_bottles_display()
self.status_text.config(state=tk.NORMAL)
self.status_text.delete(1.0, tk.END)
self.status_text.insert(tk.END, f"当前步骤: {self.current_step}/{len(self.solution)-1}")
self.status_text.config(state=tk.DISABLED)
def prev_step(self):
"""上一步"""
if not self.solution or self.current_step <= 0:
return
self.current_step -= 1
self.update_bottles_display_from_solution()
def next_step(self):
"""下一步"""
if not self.solution or self.current_step >= len(self.solution) - 1:
return
self.current_step += 1
self.update_bottles_display_from_solution()
if self.auto_play_var.get() and self.current_step < len(self.solution) - 1:
self.root.after(int(500 * self.play_speed), self.next_step)
def auto_play_solution(self):
"""自动播放解决方案"""
if not self.auto_play_var.get() or self.current_step >= len(self.solution) - 1:
return
self.current_step += 1
self.update_bottles_display_from_solution()
if self.current_step < len(self.solution) - 1:
self.root.after(int(500 * self.play_speed), self.auto_play_solution)
def is_goal_state(bottles):
"""检查是否达到目标状态:每个瓶子只有一种颜色或为空"""
for bottle in bottles:
if len(set(bottle)) > 1:
return False
return True
def is_valid_pour(source, target, max_capacity=4):
"""检查是否可以从source瓶倒入target瓶"""
if not source: # 源瓶为空
return False
if not target: # 目标瓶为空 - 可以倒入
return True
if len(target) >= max_capacity: # 目标瓶已满
return False
return source[-1] == target[-1] # 颜色相同才能倒入
def pour_water(source_bottle, target_bottle, max_capacity=4):
"""执行倒水操作,返回新的源瓶和目标瓶状态"""
source = copy.deepcopy(source_bottle)
target = copy.deepcopy(target_bottle)
while is_valid_pour(source, target, max_capacity) and len(target) < max_capacity:
target.append(source.pop())
return source, target
def bottle_hash(bottles):
"""为瓶子状态创建唯一哈希值"""
return tuple(tuple(bottle) for bottle in sorted(bottles))
def solve_water_sort(initial_bottles, max_capacity=4):
"""解决颜色分类倒水问题的主函数,支持自定义瓶子最大容量"""
queue = deque()
visited = set()
parent = {}
initial_state = [list(bottle) for bottle in initial_bottles]
queue.append(initial_state)
visited.add(bottle_hash(initial_state))
parent[bottle_hash(initial_state)] = None
while queue:
current_state = queue.popleft()
if is_goal_state(current_state):
path = []
state = current_state
while state is not None:
path.append(state)
state = parent.get(bottle_hash(state), None)
return path[::-1] # 反转路径,从初始到目标
for i in range(len(current_state)):
for j in range(len(current_state)):
if i == j:
continue
if is_valid_pour(current_state[i], current_state[j], max_capacity):
new_state = copy.deepcopy(current_state)
new_source, new_target = pour_water(new_state[i], new_state[j], max_capacity)
new_state[i] = new_source
new_state[j] = new_target
state_hash = bottle_hash(new_state)
if state_hash not in visited:
visited.add(state_hash)
parent[state_hash] = current_state
queue.append(new_state)
return None
# 主程序入口
if __name__ == "__main__":
root = tk.Tk()
app = WaterSortGUI(root)
root.mainloop()给上述代码画一个软件流程图
最新发布