【亲测有效】【源码】抽奖程序4.0 抽奖过程随机展示动画 一次抽奖多人正确展示结果

在这里插入图片描述

"""
★★★★★ 增强版抽奖系统 ★★★★★

【功能清单】
✅ 核心功能:
   - 批量导入TXT/CSV文件(支持多编码)
   - 动态抽奖动画(2秒随机闪烁+聚焦效果)
   - 多人中奖记录合并显示(逗号分隔)
   - 中奖权重设置(CSV第二列)
   - 历史记录导出(单条记录包含所有中奖者)

✅ 交互增强:
   - 深色/浅色双主题切换
   - MP3音效反馈(需ding.mp3文件)
   - 树形表格展示历史记录
   - 实时人数统计显示

✅ 高级设置:
   - 排除已中奖者模式
   - 中奖人数调节(1-10人)
   - 时间格式自定义(3种预设)
   - 音效开关控制

✅ 异常处理:
   - 智能编码回退机制
   - 文件错误精确定位
   - 操作防呆设计
"""

import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import random
import datetime
import csv
import threading
from playsound import playsound  # pip install playsound

class LotteryApp:
    def __init__(self, root):
        self.root = root
        self.root.title("高级抽奖系统")  # 设置窗口标题
        self.root.geometry("800x600")   # 初始化窗口尺寸
        
        # === 数据存储 ===
        self.participants = []     # 参与者名单(列表)
        self.history = []          # 历史记录(元组列表)
        self.weights = []          # 权重列表(浮点数)
        self.current_candidates = []  # 动画候选名单(临时数据)
        self.is_animating = False  # 动画状态标记(布尔值)
        
        # === 界面变量 ===
        self.theme_mode = tk.StringVar(value="light")  # 主题模式控制变量
        self.exclude_winners = tk.BooleanVar()        # 排除中奖者复选框状态
        self.num_winners = tk.IntVar(value=1)         # 中奖人数设置值
        self.time_format = tk.StringVar(value="%Y-%m-%d %H:%M:%S")  # 时间格式
        self.audio_enabled = tk.BooleanVar(value=True)  # 音效开关状态
        
        # === 界面构建 ===
        self.create_widgets()     # 创建所有界面组件
        self.apply_theme("light") # 应用默认主题

    def create_widgets(self):
        """构建GUI界面组件"""
        # ---- 控制面板 ----
        control_frame = ttk.Frame(self.root)
        control_frame.pack(pady=10, fill=tk.X)  # 顶部控制栏
        
        # 批量导入按钮
        ttk.Button(control_frame, text="批量导入", 
                 command=self.batch_import).pack(side=tk.LEFT, padx=5)
        # 导出历史按钮
        ttk.Button(control_frame, text="导出历史", 
                 command=self.export_history).pack(side=tk.LEFT, padx=5)
        # 主题切换复选框
        ttk.Checkbutton(control_frame, text="深色模式", 
                      variable=self.theme_mode,
                      command=self.toggle_theme).pack(side=tk.RIGHT, padx=5)
        # 音效开关
        ttk.Checkbutton(control_frame, text="启用音效", 
                      variable=self.audio_enabled).pack(side=tk.RIGHT, padx=5)

        # ---- 抽奖设置面板 ----
        settings_frame = ttk.LabelFrame(self.root, text="抽奖设置")
        settings_frame.pack(pady=10, fill=tk.X)  # 中部设置区域
        
        # 排除中奖者选项
        ttk.Checkbutton(settings_frame, text="排除已中奖者", 
                      variable=self.exclude_winners).grid(row=0, column=0, padx=5)
        # 中奖人数设置
        ttk.Label(settings_frame, text="中奖人数:").grid(row=0, column=1, padx=5)
        ttk.Spinbox(settings_frame, from_=1, to=10, 
                  textvariable=self.num_winners, width=5).grid(row=0, column=2, padx=5)
        # 时间格式选择
        ttk.Label(settings_frame, text="时间格式:").grid(row=0, column=3, padx=5)
        ttk.Combobox(settings_frame, textvariable=self.time_format,
                   values=["%Y-%m-%d", "%H:%M:%S", "%Y-%m-%d %H:%M:%S"]
                   ).grid(row=0, column=4, padx=5)

        # ---- 抽奖主按钮 ----
        self.lottery_btn = ttk.Button(self.root, text="开始抽奖", 
                                    command=self.start_lottery_process)
        self.lottery_btn.pack(pady=20)  # 中央抽奖按钮
        
        # ---- 结果展示 ----
        self.result_label = ttk.Label(self.root, text="等待抽奖...", 
                                    font=("Arial", 16), foreground="red")
        self.result_label.pack()  # 动态结果展示区域

        # ---- 历史记录表格 ----
        history_frame = ttk.LabelFrame(self.root, text="历史记录")
        history_frame.pack(expand=True, fill=tk.BOTH, padx=10, pady=10)  # 底部扩展区域
        
        # 使用Treeview组件显示历史
        self.history_tree = ttk.Treeview(history_frame, columns=("time", "winner"), show="headings")
        self.history_tree.heading("time", text="时间")      # 时间列标题
        self.history_tree.heading("winner", text="中奖者")  # 中奖者列标题
        self.history_tree.pack(expand=True, fill=tk.BOTH)  # 自适应填充

    # ========== 核心功能方法 ==========
    def batch_import(self):
        """批量导入名单文件(支持TXT/CSV)"""
        file_paths = filedialog.askopenfilenames(
            filetypes=[("Text Files", "*.txt"), ("CSV Files", "*.csv")])
        if not file_paths:
            return  # 用户取消选择

        self.show_loading(True)  # 显示加载提示
        total_import = 0
        
        for path in file_paths:
            try:
                encoding = self.detect_encoding(path)  # 自动检测编码
                with open(path, 'r', encoding=encoding) as f:
                    if path.endswith(".csv"):
                        # 处理带权重的CSV文件
                        reader = csv.reader(f)
                        for row in reader:
                            if len(row) >= 2:  # 至少包含名字和权重两列
                                self.participants.append(row[0].strip())
                                self.weights.append(float(row[1]))
                    else:
                        # 处理普通TXT文件
                        self.participants.extend(
                            line.strip() for line in f if line.strip())
                total_import += 1
            except Exception as e:
                messagebox.showerror("错误", f"文件 {path.split('/')[-1]} 导入失败:{str(e)}")
        
        self.show_loading(False)
        messagebox.showinfo("导入完成", 
                          f"成功导入 {total_import}/{len(file_paths)} 个文件\n当前人数:{len(self.participants)}")

    def start_lottery_process(self):
        """启动抽奖流程(入口方法)"""
        if not self.participants:
            messagebox.showwarning("警告", "请先导入抽奖名单!")
            return
        
        self.lottery_btn.config(state=tk.DISABLED)  # 禁用按钮防止重复点击
        self.result_label.config(text="抽奖进行中...")  # 状态提示
        threading.Thread(target=self.run_animation_sequence).start()  # 启动动画线程

    def run_animation_sequence(self):
        """运行动画效果(包含两个阶段)"""
        self.is_animating = True
        self.current_candidates = self.participants.copy()  # 初始化候选名单
        
        # === 第一阶段:快速闪烁(1.5秒) ===
        for _ in range(30):  # 30次 × 50ms = 1500ms
            random.shuffle(self.current_candidates)  # 随机打乱顺序
            display_text = " ✨ ".join(self.current_candidates[:5])  # 显示前5个带特效符号
            self.update_result_label(display_text)  # 更新显示
            threading.Event().wait(0.05)  # 50ms间隔
        
        # === 第二阶段:减速聚焦(0.5秒) ===
        candidates = self.current_candidates.copy()
        for i in range(10):  # 10次,间隔逐渐增加
            keep_num = max(1, len(candidates) - 2)  # 每次减少2个候选
            candidates = random.sample(candidates, keep_num)  # 随机保留部分
            display_text = " → ".join(candidates)  # 用箭头连接
            self.update_result_label(display_text)
            threading.Event().wait(0.05 + i*0.02)  # 间隔从50ms增加到250ms
        
        # === 最终抽奖 ===
        self.is_animating = False
        self.draw_lottery()  # 执行实际抽奖
        self.lottery_btn.config(state=tk.NORMAL)  # 恢复按钮状态

    def draw_lottery(self):
        """执行核心抽奖逻辑"""
        try:  # 尝试播放音效
            if self.audio_enabled.get():
                threading.Thread(target=playsound, args=('ding.mp3',)).start()
        except Exception as e:
            print(f"音效播放失败: {e}")

        # 确定实际中奖人数(不超过剩余人数)
        num = min(self.num_winners.get(), len(self.participants))
        winners = random.choices(
            population=self.participants,
            weights=self.weights if self.weights else None,  # 应用权重
            k=num
        )

        # 排除已中奖者模式处理
        if self.exclude_winners.get():
            self.participants = [p for p in self.participants if p not in winners]

        # === 记录抽奖结果 ===
        time_str = datetime.datetime.now().strftime(self.time_format.get())
        if winners:
            # 合并多人中奖记录(关键修改点)
            winner_str = ", ".join(winners)  # 用逗号分隔多个中奖者
            record = (time_str, winner_str)  # 单条记录包含所有中奖者
            self.history.append(record)  # 添加到历史数据
            self.history_tree.insert("", 0, values=record)  # 插入到表格顶部
        
        # 显示抽奖结果
        result_text = " 🎉 ".join(winners)  # 用彩带符号分隔
        self.result_label.config(text=f"中奖者:{result_text}")

    # ========== 工具方法 ==========
    def detect_encoding(self, file_path):
        """自动检测文件编码"""
        encodings = ['utf-8', 'gbk', 'gb18030', 'big5']  # 常见中文编码
        for enc in encodings:
            try:
                with open(file_path, 'r', encoding=enc) as f:
                    f.read(1024)  # 尝试读取前1KB内容
                return enc  # 成功则返回编码
            except:
                continue
        return 'latin-1'  # 最终回退编码

    def export_history(self):
        """导出历史记录到文件"""
        path = filedialog.asksaveasfilename(
            defaultextension=".csv",
            filetypes=[("CSV Files", "*.csv"), ("Text Files", "*.txt")]
        )
        if not path:
            return  # 用户取消保存

        try:
            with open(path, 'w', newline='', encoding='utf-8') as f:
                writer = csv.writer(f)
                writer.writerow(["时间", "中奖者"])  # 写入标题行
                # 倒序写入保持最新记录在前(与界面显示一致)
                writer.writerows(reversed(self.history))  
            messagebox.showinfo("成功", "历史记录已导出!")
        except Exception as e:
            messagebox.showerror("错误", f"导出失败:{str(e)}")

    def update_result_label(self, text):
        """线程安全更新结果标签"""
        self.root.after(0, lambda: self.result_label.config(text=text))

    def show_loading(self, show):
        """显示/隐藏加载提示"""
        if show:
            self.loading = ttk.Label(self.root, text="正在加载...")
            self.loading.pack(pady=10)
        else:
            if hasattr(self, 'loading'):
                self.loading.destroy()

    def toggle_theme(self):
        """切换主题模式"""
        theme = "dark" if self.theme_mode.get() == "dark" else "light"
        self.apply_theme(theme)

    def apply_theme(self, theme):
        """应用主题样式到界面"""
        colors = {
            "light": {"bg": "#FFFFFF", "fg": "#333333"},  # 浅色主题
            "dark": {"bg": "#2D2D2D", "fg": "#E6E6E6"}    # 深色主题
        }
        style = ttk.Style()
        style.theme_use("clam")  # 使用现代主题
        style.configure(".", 
                      background=colors[theme]["bg"],
                      foreground=colors[theme]["fg"])  # 全局样式
        self.root.config(bg=colors[theme]["bg"])  # 主窗口背景色

# ========== 程序入口 ==========
if __name__ == "__main__":
    root = tk.Tk()          # 创建主窗口
    app = LotteryApp(root)  # 实例化应用
    root.mainloop()         # 启动事件循环

关键优化说明:

  1. 多人记录合并
# 原代码(每人单独记录)
for winner in winners:
    record = (time_str, winner)
    self.history.append(record)

# 新代码(合并多人记录)
winner_str = ", ".join(winners)  # 用逗号连接多个名字
record = (time_str, winner_str)  # 单条记录包含所有中奖者
self.history.append(record)
  1. 导出格式优化
时间,中奖者
"2023-07-21 10:00:00","张三, 李四, 王五"
"2023-07-21 10:05:00","赵六"
  1. 界面显示改进
# 历史记录表格显示效果
+---------------------+-------------------+
|        时间         |      中奖者      |
+---------------------+-------------------+
| 2023-07-21 10:00:00 | 张三, 李四, 王五 |
| 2023-07-21 09:59:00 |       赵六       |
+---------------------+-------------------+

运行准备:

  1. 安装依赖:pip install playsound
  2. 准备音效文件:在程序目录添加ding.mp3
  3. 测试文件示例(带权重的CSV):
    张三,1.5
    李四,2.0
    王五,1.0
    

操作指南:

  1. 点击批量导入选择多个文件
  2. 设置中奖人数和其他选项
  3. 点击开始抽奖观看动态动画
  4. 使用导出历史保存CSV文件
  5. 通过深色模式切换界面主题
posted @   爱上编程技术  阅读(7)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
点击右上角即可分享
微信分享提示