双色球号码生成器

利用python和deepseek生成了一段代码,用于产生单式或复式、单注或多注的双色球号码。

1. 可以选择单式或者复式
2. 可以一次生成多注
3. 可以选择屏蔽已产生的号码中的部分或者全部重新产生一组号码
4. 产生的号码会自动与历史数据(excel)比对,如果存在则重新产生一组号码
5. 支持导出已产生的号码到excel文档
6. 可以计算费用,目前处于注释状态,可以搜索cost_label和"费用"

运行效果如下图:

代码如下:

# -*- coding: utf-8 -*-
"""
Created on Mon Oct 13 08:50:52 2025

@author: oldhen

本代码用于产生双色球号码
1. 可以选择单式或者复式
2. 可以一次生成多注
3. 可以选择屏蔽已产生的号码中的部分或者全部重新产生一组号码
4. 产生的号码会自动与历史数据(excel)比对,如果存在则重新产生一组号码
5. 支持导出已产生的号码到excel文档
6. 可以计算费用,目前处于注释状态,可以搜索cost_label和"费用"
"""

import tkinter as tk
from tkinter import ttk, messagebox
import random
import re
#import requests
#from bs4 import BeautifulSoup
import threading
from datetime import datetime
import pandas as pd
import os

class DoubleColorBallGenerator:
    def __init__(self, root):
        self.root = root
        self.root.title("双色球号码产生器")
        self.root.geometry("700x620")
        self.root.resizable(False, False)
        
        # 存储历史数据
        self.history_data = []
        # 存储已生成的号码用于排除
        self.generated_reds = set()
        self.generated_blues = set()
        # 标记是否已经生成过号码
        self.has_generated = False
        # 存储当前生成的蓝球号码
        self.current_blue_balls = []
        # 存储当前生成的所有注号码
        self.current_bets = []
        
        # 加载历史数据
        self.load_history_data()
        
        self.create_widgets()
    
    def get_current_year_suffix(self):
        """获取当前年份的后两位,并组合成期号格式"""
        current_year = datetime.now().year
        year_suffix = str(current_year)[-2:]  # 获取年份后两位
        issue_number = f"{year_suffix}152"  # 组合成期号,如24152
        return issue_number
    
    def save_to_excel(self):
        """将当前生成的号码保存到Excel文件"""
        try:
            if not self.current_bets:
                messagebox.showwarning("提示", "没有号码可保存")
                return
                
            # 准备数据
            data = []
            for i, (red_balls, blue_ball) in enumerate(self.current_bets):
                row = {
                    '注号': i+1,
                    '红球1': red_balls[0],
                    '红球2': red_balls[1],
                    '红球3': red_balls[2],
                    '红球4': red_balls[3],
                    '红球5': red_balls[4],
                    '红球6': red_balls[5],
                    '蓝球': blue_ball,
                    '号码组合': f"{' '.join(map(str, red_balls))} + {blue_ball}"
                }
                data.append(row)
            
            # 创建DataFrame
            df = pd.DataFrame(data)
            
            # 保存到Excel
            filename = "双色球生成号码.xlsx"
            df.to_excel(filename, index=False, engine='openpyxl')
            messagebox.showinfo("成功", f"号码已成功保存到 {filename}")
            print(f"号码已保存到 {filename},共 {len(data)} 注")
            
        except Exception as e:
            messagebox.showerror("错误", f"保存Excel文件失败: {e}")
    
    def load_history_data(self):
        """从Excel文件加载历史开奖数据"""
        def load_from_excel():
            try:
                filename = "双色球历史数据.xlsx"
                if not os.path.exists(filename):
                    print(f"Excel文件 {filename} 不存在")
                    return
                
                # 读取Excel文件
                df = pd.read_excel(filename, engine='openpyxl')
                print(f"成功读取Excel文件,共 {len(df)} 条记录")
                
                # 处理每一行数据
                for index, row in df.iterrows():
                    try:
                        # 提取红球号码
                        red_balls = []
                        for i in range(1, 7):  # 红球1到红球6
                            col_name = f'红球{i}'
                            if col_name in row:
                                ball_value = row[col_name]
                                if pd.notna(ball_value):
                                    red_balls.append(int(ball_value))
                        
                        # 提取蓝球号码
                        blue_ball = None
                        if '蓝球' in row and pd.notna(row['蓝球']):
                            blue_ball = int(row['蓝球'])
                        
                        # 验证数据完整性
                        if len(red_balls) == 6 and blue_ball is not None and 1 <= blue_ball <= 16:
                            self.history_data.append((red_balls, blue_ball))
                            
                    except (ValueError, TypeError) as e:
                        print(f"解析第{index+1}行数据时出错: {e}")
                        continue
                
                #print(f"成功加载 {len(self.history_data)} 期历史数据")
                
            except Exception as e:
                print(f"从Excel文件加载历史数据失败: {e}")
        
        # 在新线程中加载数据
        thread = threading.Thread(target=load_from_excel)
        thread.daemon = True
        thread.start()
    
    def create_circle_ball(self, parent, number, color, diameter=50):
        """创建圆形球体"""
        canvas = tk.Canvas(parent, width=diameter, height=diameter, 
                          highlightthickness=0, bg=parent.cget('bg'))
        
        # 绘制圆形
        canvas.create_oval(2, 2, diameter-2, diameter-2, 
                          fill=color, outline="black", width=1.2)
        
        # 添加数字
        canvas.create_text(diameter/2, diameter/2, text=str(number), 
                          font=("Arial", 12, "bold"), fill="white")
        
        return canvas
    
    def create_widgets(self):
        # 设置统一的背景色
        self.root.configure(bg='#f0f0f0')
        
        # 第一区:结果显示区 - 初始设置较小高度
        result_frame = ttk.LabelFrame(self.root, text="双色球号码", padding=10)
        result_frame.pack(fill="x", padx=10, pady=5)  # 注意:这里使用fill="x"而不是fill="both"
        
        # 创建带滚动条的结果区域
        self.canvas = tk.Canvas(result_frame, bg='#f0f0f0', highlightthickness=0, height=150)  # 初始高度设为150
        scrollbar = ttk.Scrollbar(result_frame, orient="vertical", command=self.canvas.yview)
        self.scrollable_frame = ttk.Frame(self.canvas)
        
        self.scrollable_frame.bind(
            "<Configure>",
            lambda e: self.canvas.configure(scrollregion=self.canvas.bbox("all"))
        )
        
        # 确保scrollable_frame填满整个Canvas宽度
        self.canvas_frame = self.canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw")
        
        # 绑定Canvas大小变化事件,确保scrollable_frame宽度跟随Canvas变化
        def configure_canvas(event):
            self.canvas.itemconfig(self.canvas_frame, width=event.width)
        
        self.canvas.bind('<Configure>', configure_canvas)
        self.canvas.configure(yscrollcommand=scrollbar.set)
        
        self.canvas.pack(side="left", fill="both", expand=True)
        scrollbar.pack(side="right", fill="y")
        
        # 初始提示文字
        self.prompt_label = tk.Label(
            self.scrollable_frame, 
            text="请点击\"随机生成\"按钮", 
            font=("Arial", 20, "bold"),
            fg="gray",
            bg='#f0f0f0'
        )
        self.prompt_label.pack(pady=20)
        
        # 号码显示区域容器
        self.bets_display_frame = tk.Frame(self.scrollable_frame, bg='#f0f0f0')
        
        # 导出按钮容器
        self.export_button_frame = tk.Frame(self.scrollable_frame, bg='#f0f0f0')
        
        # 第二区:单式/复式选择区
        choice_frame = ttk.LabelFrame(self.root, text="投注方式", padding=10)
        choice_frame.pack(fill="x", padx=10, pady=5)
        
        self.bet_type = tk.StringVar(value="single")
        
        single_radio = ttk.Radiobutton(choice_frame, text="单式", 
                                     variable=self.bet_type, value="single",
                                     command=self.on_bet_type_change)
        single_radio.pack(side="left", padx=10)
        
        compound_radio = ttk.Radiobutton(choice_frame, text="复式", 
                                       variable=self.bet_type, value="compound",
                                       command=self.on_bet_type_change)
        compound_radio.pack(side="left", padx=10)
        
        ttk.Label(choice_frame, text="蓝球个数:").pack(side="left", padx=(20, 5))
        self.blue_count_var = tk.StringVar(value="1")
        self.blue_count_entry = ttk.Entry(choice_frame, textvariable=self.blue_count_var, 
                                        width=5, state="disabled")
        self.blue_count_entry.pack(side="left", padx=5)
        
        # 添加注数输入框
        ttk.Label(choice_frame, text="注数:").pack(side="left", padx=(20, 5))
        self.bet_count_var = tk.StringVar(value="1")
        self.bet_count_entry = ttk.Entry(choice_frame, textvariable=self.bet_count_var, width=5)
        self.bet_count_entry.pack(side="left", padx=5)
        
        # 第三区:用户个性区
        custom_frame = ttk.LabelFrame(self.root, text="必选号码", padding=10)
        custom_frame.pack(fill="x", padx=10, pady=5)
        
        # 红球输入
        red_frame = tk.Frame(custom_frame)
        red_frame.pack(fill="x", pady=5)
        ttk.Label(red_frame, text="红球必选(1-33,逗号分隔):").pack(side="left")
        self.red_custom_var = tk.StringVar()
        self.red_custom_entry = ttk.Entry(red_frame, textvariable=self.red_custom_var, width=30)
        self.red_custom_entry.pack(side="left", padx=10)
        self.red_custom_entry.bind("<KeyRelease>", self.on_red_input_change)
        self.red_custom_entry.bind("<FocusOut>", self.validate_red_input)
        
        # 蓝球输入
        blue_frame = tk.Frame(custom_frame)
        blue_frame.pack(fill="x", pady=5)
        ttk.Label(blue_frame, text="蓝球必选(1-12,逗号分隔):").pack(side="left")
        self.blue_custom_var = tk.StringVar()
        self.blue_custom_entry = ttk.Entry(blue_frame, textvariable=self.blue_custom_var, width=30)
        self.blue_custom_entry.pack(side="left", padx=10)
        self.blue_custom_entry.bind("<KeyRelease>", self.on_blue_input_change)
        self.blue_custom_entry.bind("<FocusOut>", self.validate_blue_input)
        
        # 初始的生成按钮
        button_frame = tk.Frame(self.root, bg='#f0f0f0')
        button_frame.pack(pady=10)
        
        self.generate_button = ttk.Button(button_frame, text="随机生成", command=self.on_generate_button_click)
        self.generate_button.pack()
        
        # 第四区:排除生成区(初始隐藏)
        self.exclude_frame = ttk.LabelFrame(self.root, text="排除生成", padding=10)
        # 初始时不显示,等待第一次生成号码后再显示
        
        self.exclude_option = tk.StringVar(value="none")
        
        ttk.Radiobutton(self.exclude_frame, text="不排除", 
                       variable=self.exclude_option, value="none").pack(anchor="w")
        ttk.Radiobutton(self.exclude_frame, text="排除已产生的红球", 
                       variable=self.exclude_option, value="red").pack(anchor="w")
        ttk.Radiobutton(self.exclude_frame, text="排除已产生的蓝球", 
                       variable=self.exclude_option, value="blue").pack(anchor="w")
        ttk.Radiobutton(self.exclude_frame, text="全部排除", 
                       variable=self.exclude_option, value="all").pack(anchor="w")
        
        # 状态栏
        self.status_var = tk.StringVar(value="就绪")
        status_bar = ttk.Label(self.root, textvariable=self.status_var, relief="sunken")
        status_bar.pack(fill="x", side="bottom", padx=10, pady=5)
    
    def on_generate_button_click(self):
        """生成按钮点击事件处理"""
        if not self.has_generated:
            # 第一次点击,执行随机生成
            self.generate_numbers(is_regen=False)
            # 显示排除生成区域并更新按钮位置
            self.show_exclude_frame()
        else:
            # 后续点击,执行再次生成
            self.generate_numbers(is_regen=True)
    
    def show_exclude_frame(self):
        """显示排除生成区域并更新按钮位置"""
        if not self.has_generated:
            # 先隐藏初始按钮
            self.generate_button.master.pack_forget()
            
            # 显示排除生成区域
            self.exclude_frame.pack(fill="x", padx=10, pady=5)
            
            # 在排除生成区域下方添加新的生成按钮
            new_button_frame = tk.Frame(self.root, bg='#f0f0f0')
            new_button_frame.pack(pady=10)
            
            self.generate_button = ttk.Button(new_button_frame, text="再次生成", command=self.on_generate_button_click)
            self.generate_button.pack()
            
            self.has_generated = True
    
    def on_bet_type_change(self):
        """切换单式/复式时的回调"""
        if self.bet_type.get() == "compound":
            self.blue_count_entry.config(state="normal")
        else:
            self.blue_count_entry.config(state="disabled")
    
    def on_red_input_change(self, event=None):
        """红球输入变化时的处理"""
        # 允许数字和逗号,但不立即修改输入,只做验证
        text = self.red_custom_var.get()
        
        # 检查是否有非法字符(除了数字和逗号之外的字符)
        if text and not re.match(r'^[\d,\s]*$', text):
            # 移除非法字符,但保留逗号和空格
            cleaned = re.sub(r'[^\d,\s]', '', text)
            # 移除多余的空格
            cleaned = re.sub(r'\s+', '', cleaned)
            self.red_custom_var.set(cleaned)
    
    def on_blue_input_change(self, event=None):
        """蓝球输入变化时的处理"""
        # 允许数字和逗号,但不立即修改输入,只做验证
        text = self.blue_custom_var.get()
        
        # 检查是否有非法字符(除了数字和逗号之外的字符)
        if text and not re.match(r'^[\d,\s]*$', text):
            # 移除非法字符,但保留逗号和空格
            cleaned = re.sub(r'[^\d,\s]', '', text)
            # 移除多余的空格
            cleaned = re.sub(r'\s+', '', cleaned)
            self.blue_custom_var.set(cleaned)
    
    def validate_red_input(self, event=None):
        """验证红球输入"""
        text = self.red_custom_var.get().strip()
        if text:
            # 清理输入,移除所有空格
            cleaned = re.sub(r'\s+', '', text)
            
            # 分割数字并去重
            numbers = []
            for num_str in cleaned.split(','):
                if num_str.strip():
                    try:
                        num = int(num_str.strip())
                        if 1 <= num <= 33 and num not in numbers:
                            numbers.append(num)
                    except ValueError:
                        continue
            
            # 限制最多6个红球
            if len(numbers) > 6:
                numbers = numbers[:6]
                messagebox.showwarning("提示", "红球最多只能选择6个号码")
            
            # 更新输入框
            self.red_custom_var.set(','.join(map(str, numbers)))
    
    def validate_blue_input(self, event=None):
        """验证蓝球输入"""
        text = self.blue_custom_var.get().strip()
        if text:
            # 清理输入,移除所有空格
            cleaned = re.sub(r'\s+', '', text)
            
            # 分割数字并去重
            numbers = []
            for num_str in cleaned.split(','):
                if num_str.strip():
                    try:
                        num = int(num_str.strip())
                        if 1 <= num <= 12 and num not in numbers:
                            numbers.append(num)
                    except ValueError:
                        continue
            
            # 限制最多12个蓝球
            if len(numbers) > 12:
                numbers = numbers[:12]
                messagebox.showwarning("提示", "蓝球最多只能选择12个号码")
            
            # 更新输入框
            self.blue_custom_var.set(','.join(map(str, numbers)))
    
    def parse_numbers(self, text):
        """解析逗号分隔的数字字符串"""
        if not text:
            return []
        numbers = []
        for num_str in text.split(','):
            if num_str.strip():
                try:
                    num = int(num_str.strip())
                    numbers.append(num)
                except ValueError:
                    continue
        return numbers
    
    def generate_numbers(self, is_regen=False):
        """生成双色球号码"""
        try:
            # 获取注数
            try:
                bet_count = int(self.bet_count_var.get())
                if bet_count < 1 or bet_count > 20:
                    messagebox.showerror("错误", "注数必须在1-20范围内!")
                    return
            except ValueError:
                messagebox.showerror("错误", "请输入有效的注数!")
                return
            
            # 存储当前生成的所有注
            temp_bets = []
            
            # 生成指定注数的号码
            for bet_index in range(bet_count):
                # 获取必选号码
                custom_red = list(set(self.parse_numbers(self.red_custom_var.get())))
                custom_blue = list(set(self.parse_numbers(self.blue_custom_var.get())))
                
                # 验证必选号码
                if len(custom_red) > 6:
                    messagebox.showerror("错误", "红球必选号码不能超过6个!")
                    return
                
                if any(num < 1 or num > 33 for num in custom_red):
                    messagebox.showerror("错误", "红球号码必须在1-33范围内!")
                    return
                
                if any(num < 1 or num > 12 for num in custom_blue):
                    messagebox.showerror("错误", "蓝球号码必须在1-12范围内!")
                    return
                
                # 获取排除选项
                exclude_red = self.exclude_option.get() in ["red", "all"] and is_regen
                exclude_blue = self.exclude_option.get() in ["blue", "all"] and is_regen
                
                # 生成红球
                available_red = [i for i in range(1, 34) if i not in custom_red]
                
                # 排除已生成的红球
                if exclude_red:
                    available_red = [num for num in available_red if num not in self.generated_reds]
                
                need_red_count = 6 - len(custom_red)
                
                if need_red_count > len(available_red):
                    messagebox.showerror("错误", "可用的红球号码不足!")
                    return  # 直接返回,不修改当前显示的号码
                
                selected_red = custom_red + random.sample(available_red, need_red_count)
                selected_red.sort()
                
                # 生成蓝球
                if self.bet_type.get() == "single":
                    # 单式:1个蓝球
                    if custom_blue:
                        selected_blue = custom_blue[0]
                    else:
                        available_blue = [i for i in range(1, 13)]
                        # 排除已生成的蓝球
                        if exclude_blue:
                            available_blue = [num for num in available_blue if num not in self.generated_blues]
                        
                        if not available_blue:
                            messagebox.showerror("错误", "可用的蓝球号码不足!")
                            return  # 直接返回,不修改当前显示的号码
                        
                        selected_blue = random.choice(available_blue)
                    blue_balls = [selected_blue]
                else:
                    # 复式:多个蓝球
                    try:
                        blue_count = int(self.blue_count_var.get())
                        if blue_count < 1 or blue_count > 12:
                            messagebox.showerror("错误", "蓝球个数必须在1-12范围内!")
                            return
                    except ValueError:
                        messagebox.showerror("错误", "请输入有效的蓝球个数!")
                        return
                    
                    available_blue = [i for i in range(1, 13) if i not in custom_blue]
                    
                    # 排除已生成的蓝球
                    if exclude_blue:
                        available_blue = [num for num in available_blue if num not in self.generated_blues]
                    
                    need_blue_count = blue_count - len(custom_blue)
                    
                    if need_blue_count > len(available_blue):
                        messagebox.showerror("错误", "可用的蓝球号码不足!")
                        return  # 直接返回,不修改当前显示的号码
                    
                    selected_blue_list = custom_blue + random.sample(available_blue, need_blue_count)
                    selected_blue_list.sort()
                    blue_balls = selected_blue_list
                
                # 存储当前注的号码
                temp_bets.append((selected_red.copy(), blue_balls[0] if self.bet_type.get() == "single" else blue_balls.copy()))
                
                # 记录已生成的号码
                if not is_regen:
                    self.generated_reds.clear()
                    self.generated_blues.clear()
                
                self.generated_reds.update(selected_red)
                self.generated_blues.update(blue_balls)
                
                # 检查是否中过大奖
                self.check_prize_history(selected_red, blue_balls)
            
            # 如果所有注都成功生成,才更新显示
            self.current_bets = temp_bets
            
            # 清空之前的显示
            self.prompt_label.pack_forget()
            self.bets_display_frame.pack_forget()
            self.export_button_frame.pack_forget()
            
            # 清空显示区域
            for widget in self.bets_display_frame.winfo_children():
                widget.destroy()
            for widget in self.export_button_frame.winfo_children():
                widget.destroy()
            
            # 显示新生成的号码
            for bet_index, (selected_red, blue_balls) in enumerate(self.current_bets):
                # 创建一行显示区域
                bet_row_frame = tk.Frame(self.bets_display_frame, bg='#f0f0f0')
                bet_row_frame.pack(fill="x", pady=2)
                
                # 添加注号标签
                bet_label = tk.Label(bet_row_frame, text=f"第{bet_index+1}注:", font=("Arial", 10), bg='#f0f0f0')
                bet_label.pack(side="left", padx=(0, 10))
                
                # 创建红球显示区域
                red_balls_frame = tk.Frame(bet_row_frame, bg='#f0f0f0')
                red_balls_frame.pack(side="left")
                
                # 显示红球
                for i in range(6):
                    canvas = self.create_circle_ball(red_balls_frame, selected_red[i], "red", diameter=30)
                    canvas.pack(side="left", padx=1)
                
                # 添加分隔符
                separator = tk.Label(bet_row_frame, text="+", font=("Arial", 10), bg='#f0f0f0')
                separator.pack(side="left", padx=5)
                
                # 蓝球显示区域
                blue_balls_frame = tk.Frame(bet_row_frame, bg='#f0f0f0')
                blue_balls_frame.pack(side="left")
                
                # 显示蓝球
                if isinstance(blue_balls, list):
                    for blue_ball in blue_balls:
                        canvas = self.create_circle_ball(blue_balls_frame, blue_ball, "blue", diameter=30)
                        canvas.pack(side="left", padx=1)
                else:
                    canvas = self.create_circle_ball(blue_balls_frame, blue_balls, "blue", diameter=30)
                    canvas.pack(side="left", padx=1)
            
            # 显示号码区域和导出按钮
            self.bets_display_frame.pack(fill="x", padx=10, pady=10)
            
            # 添加导出按钮
            export_button = ttk.Button(self.export_button_frame, text="导出号码", command=self.save_to_excel)
            export_button.pack(pady=10)
            self.export_button_frame.pack(fill="x")
            
            # 动态调整窗口大小
            #self.adjust_window_size(bet_count)
            
            # 确保内容填满Canvas区域
            self.canvas.update_idletasks()
            self.canvas.configure(scrollregion=self.canvas.bbox("all"))
            
            self.status_var.set(f"成功生成 {bet_count} 注号码!")
            
        except Exception as e:
            messagebox.showerror("错误", f"生成号码时出现错误: {str(e)}")
            self.status_var.set("生成失败")
    
    # def adjust_window_size(self, bet_count):
    #     """根据注数调整窗口大小"""
    #     # 注数大于7时调整窗口大小为700x900,否则保持700x600
    #     if bet_count > 7:
    #         self.root.geometry("700x900")
    #     else:
    #         self.root.geometry("700x600")
    
    def check_prize_history(self, red_balls, blue_balls):
        """检查是否在历史数据中已经中过奖"""
        # 对于复式投注,检查所有蓝球组合
        for blue_ball in blue_balls:
            for history_red, history_blue in self.history_data:
                if (set(red_balls) == set(history_red) and blue_ball == history_blue):
                    result = messagebox.askyesno(
                        "提示", 
                        f"这个号码组合(红球: {', '.join(map(str, red_balls))}, 蓝球: {blue_ball})在历史开奖中已经中过大奖!\n是否重新生成新的号码?"
                    )
                    if result:
                        self.generate_numbers(is_regen=True)
                    return

def main():
    root = tk.Tk()
    app = DoubleColorBallGenerator(root)
    root.mainloop()

if __name__ == "__main__":
    main()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值