羽毛球比赛记分软件

功能:支持单打/双打比赛,自动计分,局数管理,换边,暂停等

软件采用Python Tkinter开发,界面包含比分显示、选手名称设置、发球权标识等模块。

特色功能包括:

遵循羽毛球21分制规则,支持20平后净胜2分获胜;提供3局2胜制比赛管理;支持全屏显示比赛结果;可导出比赛数据为CSV文件;提供快捷键操作(空格/回车键计分)

软件适用于正式比赛和日常训练,能有效提升记分效率和观赛体验。

# -*- coding: utf-8 -*-

"""

羽毛球比赛记分软件

功能:支持单打/双打比赛,自动计分,局数管理,换边,暂停等

"""

import tkinter as tk

from tkinter import ttk, messagebox, filedialog

import json

import os

import csv

from datetime import datetime

class BadmintonScoreApp:

def __init__(self, root):

self.root = root

self.root.title("🏸 羽毛球比赛记分器")

self.root.geometry("900x700")

self.root.minsize(800, 600)

# 比赛状态

self.match_type = tk.StringVar(value="singles") # singles/doubles

self.current_server = tk.StringVar(value="left") # left/right

self.left_score = tk.IntVar(value=0)

self.right_score = tk.IntVar(value=0)

self.left_games = tk.IntVar(value=0)

self.right_games = tk.IntVar(value=0)

self.is_paused = tk.BooleanVar(value=False)

self.is_match_over = tk.BooleanVar(value=False)

# 选手名称

self.left_player_name = tk.StringVar(value="左侧选手")

self.right_player_name = tk.StringVar(value="右侧选手")

# 自定义全屏标题

self.custom_title = tk.StringVar(value="🏸 羽毛球联谊赛 🏸")

self.target_games = tk.IntVar(value=2) # 3局2胜

self.pause_reason = tk.StringVar(value="")

# 比赛历史

self.match_history = []

self.setup_ui()

self.setup_styles()

# 绑定快捷键

self.root.bind('', self.handle_space_key)

self.root.bind('', self.handle_enter_key)

self.root.bind('

', lambda e: self.toggle_pause())

self.root.bind('

', lambda e: self.toggle_pause())

self.root.bind('', lambda e: self.reset_match())

self.root.bind('', lambda e: self.reset_match())

# 确保根窗口获取焦点

self.root.bind('', self.focus_root)

def focus_root(self, event=None):

"""确保根窗口获取焦点"""

# 检查焦点是否在输入框或文本框等可编辑控件上,如果是则不阻止

focus_widget = self.root.focus_get()

if focus_widget is not None:

# 允许 Entry、Text、Combobox 等可编辑控件正常接收键盘事件

widget_class = focus_widget.winfo_class()

if widget_class in ['Entry', 'Text', 'Combobox']:

return None # 允许事件继续传播

self.root.focus_set()

return 'break' # 阻止事件继续传播

def handle_space_key(self, event=None):

"""处理空格键 - 左侧得分"""

if not self.is_paused.get() and not self.is_match_over.get():

self.add_point("left")

return 'break' # 阻止事件传播,防止按钮捕获

def handle_enter_key(self, event=None):

"""处理回车键 - 右侧得分"""

if not self.is_paused.get() and not self.is_match_over.get():

self.add_point("right")

return 'break' # 阻止事件传播,防止按钮捕获

def setup_styles(self):

"""设置界面样式"""

style = ttk.Style()

style.theme_use('clam')

# 按钮样式

style.configure('TButton', font=('Microsoft YaHei', 10))

style.configure('Large.TButton', font=('Microsoft YaHei', 14, 'bold'), padding=10)

style.configure('Danger.TButton', font=('Microsoft YaHei', 10), foreground='red')

# 标签样式

style.configure('Title.TLabel', font=('Microsoft YaHei', 24, 'bold'))

style.configure('Score.TLabel', font=('Microsoft YaHei', 72, 'bold'))

style.configure('Name.TLabel', font=('Microsoft YaHei', 14))

# 全屏窗口样式

style.configure('Dark.TFrame', background='#2C3E50')

def setup_ui(self):

"""设置用户界面"""

# 主框架

main_frame = ttk.Frame(self.root, padding="20")

main_frame.pack(fill=tk.BOTH, expand=True)

# 顶部标题和设置区域

top_frame = ttk.Frame(main_frame)

top_frame.pack(fill=tk.X, pady=(0, 20))

# 标题

title_label = ttk.Label(top_frame, text="🏸 羽毛球师生联谊赛@zhoujunyacs 🏸", style='Title.TLabel')

title_label.pack(side=tk.LEFT)

# 设置区域

settings_frame = ttk.Frame(top_frame)

settings_frame.pack(side=tk.RIGHT)

# 比赛类型选择

ttk.Label(settings_frame, text="比赛类型:").grid(row=0, column=0, padx=5)

ttk.Radiobutton(settings_frame, text="单打", variable=self.match_type,

value="singles", command=self.update_player_labels).grid(row=0, column=1, padx=5)

ttk.Radiobutton(settings_frame, text="双打", variable=self.match_type,

value="doubles", command=self.update_player_labels).grid(row=0, column=2, padx=5)

# 比赛项目选择

ttk.Label(settings_frame, text="比赛项目:").grid(row=0, column=3, padx=5)

self.match_project = tk.StringVar(value="第一男双")

project_combo = ttk.Combobox(settings_frame, textvariable=self.match_project,

values=["第一男双", "第二男双", "第三男双", "第一混双", "第二混双", "第三混双",

"女双", "女单", "男单"], width=10, state="readonly")

project_combo.grid(row=0, column=4, padx=5)

# 绑定比赛项目变更事件,实时更新全屏显示

project_combo.bind("<>", self.on_project_changed)

# 局数设置

ttk.Label(settings_frame, text="目标局数:").grid(row=0, column=5, padx=5)

self.target_games = tk.IntVar(value=3) # 默认3局2胜

games_entry = ttk.Entry(settings_frame, textvariable=self.target_games, width=5)

games_entry.grid(row=0, column=6, padx=5)

ttk.Label(settings_frame, text="局").grid(row=0, column=7, padx=2)

# 自定义全屏标题(第二行)

title_frame = ttk.Frame(settings_frame)

title_frame.grid(row=1, column=0, columnspan=8, pady=(10, 0))

ttk.Label(title_frame, text="全屏标题:").pack(side=tk.LEFT)

tk.Entry(title_frame, textvariable=self.custom_title, width=30, font=('Microsoft YaHei', 10)).pack(side=tk.LEFT, padx=5)

ttk.Label(title_frame, text="(全屏时显示的自定义标题)", font=('Microsoft YaHei', 9),

foreground='gray').pack(side=tk.LEFT)

# 中间比赛区域

match_frame = ttk.Frame(main_frame)

match_frame.pack(fill=tk.BOTH, expand=True, pady=20)

# 左侧选手区域

left_frame = ttk.LabelFrame(match_frame, text="左侧", padding="10")

left_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=(0, 10))

# 左侧选手名称输入

left_name_frame = ttk.Frame(left_frame)

left_name_frame.pack(fill=tk.X, pady=(0, 10))

ttk.Label(left_name_frame, text="选手名称:").pack(side=tk.LEFT)

tk.Entry(left_name_frame, textvariable=self.left_player_name, width=15, font=('Microsoft YaHei', 10)).pack(side=tk.LEFT, padx=5)

# 左侧局数

games_frame = ttk.Frame(left_frame)

games_frame.pack(fill=tk.X, pady=5)

ttk.Label(games_frame, text="已胜局数:", font=('Microsoft YaHei', 12)).pack(side=tk.LEFT)

# 左侧局数减按钮

tk.Button(games_frame, text="-", font=('Microsoft YaHei', 12, 'bold'),

width=2, command=lambda: self.modify_games("left", -1)).pack(side=tk.LEFT, padx=2)

left_games_label = ttk.Label(games_frame, textvariable=self.left_games, font=('Microsoft YaHei', 24, 'bold'),

foreground='green')

left_games_label.pack(side=tk.LEFT, padx=5)

# 左侧局数加按钮

tk.Button(games_frame, text="+", font=('Microsoft YaHei', 12, 'bold'),

width=2, command=lambda: self.modify_games("left", 1)).pack(side=tk.LEFT, padx=2)

# 左侧比分显示

self.left_score_label = ttk.Label(left_frame, textvariable=self.left_score,

style='Score.TLabel', foreground='blue')

self.left_score_label.pack(pady=10)

# 左侧发球标记

self.left_server_label = ttk.Label(left_frame, text="🏸 发球权", font=('Microsoft YaHei', 14),

foreground='orange' if self.current_server.get() == "left" else 'gray')

self.left_server_label.pack(pady=5)

# 左侧加分按钮

btn_frame = ttk.Frame(left_frame)

btn_frame.pack(fill=tk.X, pady=10)

add_btn = ttk.Button(btn_frame, text="+1 分 (空格)", command=lambda: self.add_point("left"),

style='Large.TButton')

add_btn.pack(side=tk.LEFT, expand=True, fill=tk.X, padx=2)

# 右侧选手区域

right_frame = ttk.LabelFrame(match_frame, text="右侧", padding="10")

right_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=(10, 0))

# 右侧选手名称输入

right_name_frame = ttk.Frame(right_frame)

right_name_frame.pack(fill=tk.X, pady=(0, 10))

ttk.Label(right_name_frame, text="选手名称:").pack(side=tk.LEFT)

tk.Entry(right_name_frame, textvariable=self.right_player_name, width=15, font=('Microsoft YaHei', 10)).pack(side=tk.LEFT, padx=5)

# 右侧局数

games_frame = ttk.Frame(right_frame)

games_frame.pack(fill=tk.X, pady=5)

ttk.Label(games_frame, text="已胜局数:", font=('Microsoft YaHei', 12)).pack(side=tk.LEFT)

# 右侧局数减按钮

tk.Button(games_frame, text="-", font=('Microsoft YaHei', 12, 'bold'),

width=2, command=lambda: self.modify_games("right", -1)).pack(side=tk.LEFT, padx=2)

right_games_label = ttk.Label(games_frame, textvariable=self.right_games, font=('Microsoft YaHei', 24, 'bold'),

foreground='green')

right_games_label.pack(side=tk.LEFT, padx=5)

# 右侧局数加按钮

tk.Button(games_frame, text="+", font=('Microsoft YaHei', 12, 'bold'),

width=2, command=lambda: self.modify_games("right", 1)).pack(side=tk.LEFT, padx=2)

# 右侧比分显示

self.right_score_label = ttk.Label(right_frame, textvariable=self.right_score,

style='Score.TLabel', foreground='red')

self.right_score_label.pack(pady=10)

# 右侧发球标记

self.right_server_label = ttk.Label(right_frame, text="🏸 发球权", font=('Microsoft YaHei', 14),

foreground='orange' if self.current_server.get() == "right" else 'gray')

self.right_server_label.pack(pady=5)

# 右侧加分按钮

btn_frame = ttk.Frame(right_frame)

btn_frame.pack(fill=tk.X, pady=10)

add_btn = ttk.Button(btn_frame, text="+1 分 (回车)", command=lambda: self.add_point("right"),

style='Large.TButton')

add_btn.pack(side=tk.LEFT, expand=True, fill=tk.X, padx=2)

# 底部控制区域

control_frame = ttk.Frame(main_frame)

control_frame.pack(fill=tk.X, pady=15)

# 左边按钮组

left_buttons = ttk.Frame(control_frame)

left_buttons.pack(side=tk.LEFT, padx=5)

# 创建按钮时设置 takefocus=0 防止捕获键盘焦点

change_sides_btn = ttk.Button(left_buttons, text="🔄 换边", command=self.change_sides,

width=10, takefocus=0)

change_sides_btn.pack(side=tk.LEFT, padx=2)

ttk.Button(left_buttons, text="⏸ 暂停 (P)", command=self.toggle_pause,

width=12, takefocus=0).pack(side=tk.LEFT, padx=2)

# 暂停显示

self.pause_label = ttk.Label(control_frame, text="", font=('Microsoft YaHei', 12, 'bold'),

foreground='red')

self.pause_label.pack(side=tk.LEFT, padx=10)

# 中间按钮组 - 使用pack的expand和fill使按钮随窗口大小调整

center_buttons = ttk.Frame(control_frame)

center_buttons.pack(side=tk.LEFT, expand=True, fill=tk.X, padx=10)

# 全屏结果显示按钮

self.fullscreen_btn = ttk.Button(center_buttons, text="📺 全屏显示结果",

command=self.show_fullscreen_result,

width=15, takefocus=0)

self.fullscreen_btn.pack(side=tk.LEFT, expand=True, fill=tk.X, padx=2)

# 导出按钮

export_btn = ttk.Button(center_buttons, text="📊 导出结果",

command=self.export_match_data,

width=12, takefocus=0)

export_btn.pack(side=tk.LEFT, expand=True, fill=tk.X, padx=2)

# 右边按钮组

right_buttons = ttk.Frame(control_frame)

right_buttons.pack(side=tk.RIGHT, padx=5)

ttk.Button(right_buttons, text="↩ 撤销", command=self.undo_point,

width=8, takefocus=0).pack(side=tk.RIGHT, padx=2)

ttk.Button(right_buttons, text="🔁 重置 (R)", command=self.reset_match,

width=10, takefocus=0).pack(side=tk.RIGHT, padx=2)

# 状态栏

status_frame = ttk.Frame(main_frame)

status_frame.pack(fill=tk.X, pady=(10, 0))

self.status_label = ttk.Label(status_frame, text="准备开始比赛", font=('Microsoft YaHei', 10))

self.status_label.pack(side=tk.LEFT)

# 快捷键提示

hint_label = ttk.Label(status_frame, text="快捷键: 空格=左侧得分 | 回车=右侧得分 | P=暂停 | R=重置",

font=('Microsoft YaHei', 9), foreground='gray')

hint_label.pack(side=tk.RIGHT)

# 绑定窗口关闭事件

self.root.protocol("WM_DELETE_WINDOW", self.on_close)

def update_player_labels(self):

"""更新选手标签"""

if self.match_type.get() == "singles":

self.left_player_name.set("单打选手A")

self.right_player_name.set("单打选手B")

else:

self.left_player_name.set("双打组合A")

self.right_player_name.set("双打组合B")

def modify_games(self, side, delta):

"""手动修改局数"""

if side == "left":

new_value = max(0, self.left_games.get() + delta)

self.left_games.set(new_value)

else:

new_value = max(0, self.right_games.get() + delta)

self.right_games.set(new_value)

self.status_label.config(text=f"已调整局数: 左侧 {self.left_games.get()} - 右侧 {self.right_games.get()}")

def on_project_changed(self, event=None):

"""比赛项目变更时重置比分和局数"""

# 重置当前局比分

self.left_score.set(0)

self.right_score.set(0)

self.current_server.set("left")

self.update_server_display()

# 重置局数计数

self.left_games.set(0)

self.right_games.set(0)

# 重置暂停和比赛结束状态

self.is_paused.set(False)

self.is_match_over.set(False)

self.pause_label.config(text="")

# 提示用户

self.status_label.config(text=f"比赛项目已变更为: {self.match_project.get()},已重置比分和局数")

def add_point(self, side):

"""加分"""

if self.is_paused.get() or self.is_match_over.get():

return

# 记录用于撤销

self.last_action = {

'side': side,

'left_score': self.left_score.get(),

'right_score': self.right_score.get(),

'server': self.current_server.get()

}

if side == "left":

new_score = self.left_score.get() + 1

self.left_score.set(new_score)

# 羽毛球规则:得分方获得发球权

self.current_server.set("left")

self.status_label.config(text=f"左侧选手得分!当前比分: {self.left_score.get()}-{self.right_score.get()}")

else:

new_score = self.right_score.get() + 1

self.right_score.set(new_score)

# 羽毛球规则:得分方获得发球权

self.current_server.set("right")

self.status_label.config(text=f"右侧选手得分!当前比分: {self.left_score.get()}-{self.right_score.get()}")

self.update_server_display()

self.check_game_winner()

self.check_match_winner()

def update_server_display(self):

"""更新发球权显示"""

if self.current_server.get() == "left":

self.left_server_label.config(foreground='orange')

self.right_server_label.config(foreground='gray')

else:

self.left_server_label.config(foreground='gray')

self.right_server_label.config(foreground='orange')

def check_game_winner(self):

"""检查单局获胜者"""

left = self.left_score.get()

right = self.right_score.get()

winner = None

# 羽毛球规则:先得21分者获胜,但如果比分到20平,则需要净胜2分才获胜

if left >= 21 or right >= 21:

if left >= 20 and right >= 20:

# 20平后,任何一方净胜2分即获胜(无上限)

if left - right >= 2:

winner = "left"

elif right - left >= 2:

winner = "right"

else:

# 未到20平,正常21分获胜

if left >= 21 and left - right >= 1:

winner = "left"

elif right >= 21 and right - left >= 1:

winner = "right"

if winner:

self.root.bell()

if winner == "left":

self.left_games.set(self.left_games.get() + 1)

winner_name = self.left_player_name.get()

else:

self.right_games.set(self.right_games.get() + 1)

winner_name = self.right_player_name.get()

messagebox.showinfo("🎉 本局结束", f"{winner_name} 获胜!\n比分: {left}-{right}")

# 记录到历史

self.match_history.append({

'time': datetime.now().strftime("%Y-%m-%d %H:%M:%S"),

'left': self.left_player_name.get(),

'right': self.right_player_name.get(),

'score': f"{left}-{right}",

'winner': winner_name

})

# 重置比分

self.left_score.set(0)

self.right_score.set(0)

self.current_server.set("left")

self.update_server_display()

self.status_label.config(text=f"局数: {self.left_games.get()}-{self.right_games.get()}")

def check_match_winner(self):

"""检查整场比赛获胜者"""

target = self.target_games.get()

if self.left_games.get() >= target:

self.is_match_over.set(True)

self.root.bell()

winner = self.left_player_name.get()

messagebox.showinfo("🏆 比赛结束", f"恭喜 {winner} 赢得比赛!")

self.status_label.config(text=f"比赛结束!{winner} 获胜")

# 自动保存比赛结果到txt文件

self.save_match_result(winner)

elif self.right_games.get() >= target:

self.is_match_over.set(True)

self.root.bell()

winner = self.right_player_name.get()

messagebox.showinfo("🏆 比赛结束", f"恭喜 {winner} 赢得比赛!")

self.status_label.config(text=f"比赛结束!{winner} 获胜")

# 自动保存比赛结果到txt文件

self.save_match_result(winner)

def save_match_result(self, winner):

"""保存比赛结果到txt文件"""

try:

# 获取当前时间戳作为文件名

timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")

file_name = f"比赛结果_{self.match_project.get()}_{timestamp}.txt"

file_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), file_name)

with open(file_path, 'w', encoding='utf-8') as f:

f.write("=" * 50 + "\n")

f.write("🏸 羽毛球比赛结果\n")

f.write("=" * 50 + "\n\n")

f.write(f"比赛项目: {self.match_project.get()}\n")

f.write(f"比赛时间: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n")

f.write("-" * 50 + "\n")

f.write("对阵信息\n")

f.write("-" * 50 + "\n")

f.write(f"左侧选手: {self.left_player_name.get()}\n")

f.write(f"右侧选手: {self.right_player_name.get()}\n\n")

f.write("-" * 50 + "\n")

f.write("比赛结果\n")

f.write("-" * 50 + "\n")

f.write(f"左侧最终比分: {self.left_score.get()}\n")

f.write(f"右侧最终比分: {self.right_score.get()}\n")

f.write(f"左侧获胜局数: {self.left_games.get()}\n")

f.write(f"右侧获胜局数: {self.right_games.get()}\n")

f.write(f"总局数: {self.target_games.get()}\n\n")

f.write("=" * 50 + "\n")

f.write(f"🏆 获胜者: {winner}\n")

f.write("=" * 50 + "\n\n")

f.write("自动生成 by 羽毛球记分器\n")

self.status_label.config(text=f"比赛结果已保存到: {file_name}")

except Exception as e:

self.status_label.config(text=f"保存结果失败: {str(e)}")

def undo_point(self):

"""撤销上一步操作"""

if hasattr(self, 'last_action') and self.last_action:

action = self.last_action

# 恢复分数

self.left_score.set(action['left_score'])

self.right_score.set(action['right_score'])

# 恢复发球权

if action['side'] == "left":

self.current_server.set("right" if self.current_server.get() == "left" else "left")

else:

self.current_server.set("left" if self.current_server.get() == "right" else "right")

self.update_server_display()

self.status_label.config(text="已撤销上一步操作")

self.last_action = None

def toggle_pause(self):

"""暂停/继续"""

self.is_paused.set(not self.is_paused.get())

if self.is_paused.get():

self.pause_label.config(text="⏸ 已暂停")

self.status_label.config(text="比赛暂停中...")

else:

self.pause_label.config(text="")

self.status_label.config(text="比赛继续")

def change_sides(self):

"""换边"""

# 交换选手名称

left_name = self.left_player_name.get()

right_name = self.right_player_name.get()

self.left_player_name.set(right_name)

self.right_player_name.set(left_name)

# 交换比分

left_score = self.left_score.get()

right_score = self.right_score.get()

self.left_score.set(right_score)

self.right_score.set(left_score)

# 交换局数

left_games = self.left_games.get()

right_games = self.right_games.get()

self.left_games.set(right_games)

self.right_games.set(left_games)

# 切换发球权

self.current_server.set("right" if self.current_server.get() == "left" else "left")

self.update_server_display()

self.status_label.config(text="已换边")

def reset_match(self):

"""重置比赛"""

if messagebox.askyesno("确认", "确定要重置比赛吗?所有比分和局数将被清除!"):

self.left_score.set(0)

self.right_score.set(0)

self.left_games.set(0)

self.right_games.set(0)

self.current_server.set("left")

self.is_paused.set(False)

self.is_match_over.set(False)

self.pause_label.config(text="")

self.last_action = None

self.update_server_display()

self.status_label.config(text="比赛已重置,准备开始新比赛")

def on_close(self):

"""窗口关闭事件"""

if messagebox.askyesno("退出", "确定要退出记分器吗?"):

# 保存比赛历史

if self.match_history:

try:

history_file = os.path.join(os.path.dirname(os.path.abspath(__file__)),

"match_history.json")

with open(history_file, 'w', encoding='utf-8') as f:

json.dump(self.match_history, f, ensure_ascii=False, indent=2)

except:

pass

self.root.destroy()

def export_match_data(self):

"""导出比赛数据"""

try:

# 判断当前比赛的获胜者

winner = None

if self.left_games.get() > self.right_games.get():

winner = self.left_player_name.get()

elif self.right_games.get() > self.left_games.get():

winner = self.right_player_name.get()

else:

winner = "平局"

# 弹出保存对话框

file_path = filedialog.asksaveasfilename(

defaultextension=".csv",

filetypes=[("CSV文件", "*.csv"), ("所有文件", "*.*")],

initialfile=f"比赛结果_{self.match_project.get()}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv",

title="保存比赛结果"

)

if file_path:

with open(file_path, 'w', encoding='utf-8-sig') as f:

# 写入CSV头部

f.write("比赛项目,比赛时间,左侧选手,右侧选手,左侧得分,右侧得分,左侧局数,右侧局数,总局数,获胜者\n")

# 写入比赛数据

f.write(f"{self.match_project.get()},{datetime.now().strftime('%Y-%m-%d %H:%M:%S')},")

f.write(f"{self.left_player_name.get()},{self.right_player_name.get()},")

f.write(f"{self.left_score.get()},{self.right_score.get()},")

f.write(f"{self.left_games.get()},{self.right_games.get()},")

f.write(f"{self.target_games.get()},{winner}\n")

# 写入详细记录

f.write("\n\n比赛记录\n")

f.write("时间,左侧选手,右侧选手,比分,获胜者\n")

for record in self.match_history:

f.write(f"{record['time']},{record['left']},{record['right']},{record['score']},{record['winner']}\n")

messagebox.showinfo("导出成功", f"比赛结果已成功导出到:\n{file_path}")

self.status_label.config(text=f"已导出比赛结果到: {os.path.basename(file_path)}")

except Exception as e:

messagebox.showerror("导出失败", f"导出比赛结果时出错:\n{str(e)}")

self.status_label.config(text=f"导出失败: {str(e)}")

def show_fullscreen_result(self):

"""全屏显示比赛结果(自适应所有屏幕尺寸)"""

# 如果已经存在全屏窗口,先关闭

if hasattr(self, 'fullscreen_window') and self.fullscreen_window and self.fullscreen_window.winfo_exists():

self.fullscreen_window.destroy()

# 创建全屏窗口(作为独立窗口,不阻塞主窗口)

self.fullscreen_window = tk.Toplevel(self.root)

self.fullscreen_window.title("🏆 比赛结果")

self.fullscreen_window.attributes('-fullscreen', True)

# 获取屏幕尺寸

screen_width = self.root.winfo_screenwidth()

screen_height = self.root.winfo_screenheight()

# 根据屏幕尺寸动态计算字体大小和间距

# 基础缩放比例(以1920x1080为基准)

scale_x = screen_width / 1920

scale_y = screen_height / 1080

scale = min(scale_x, scale_y) # 保持宽高比

# 动态计算字体大小(确保在不同分辨率下都清晰可见)

title_font_size = max(int(48 * scale), 24) # 标题字体

project_font_size = max(int(24 * scale), 14) # 项目名称字体

rules_font_size = max(int(14 * scale), 10) # 规则提示字体

player_font_size = max(int(36 * scale), 20) # 选手名字字体

vs_font_size = max(int(32 * scale), 18) # VS字体

score_font_size = max(int(120 * scale), 60) # 比分字体

games_font_size = max(int(24 * scale), 14) # 局数字体

server_font_size = max(int(20 * scale), 12) # 发球权字体

hint_font_size = max(int(14 * scale), 10) # 快捷键提示字体

# 动态计算间距

padding_x = max(int(50 * scale), 20)

padding_y = max(int(30 * scale), 15)

title_padding_y = max(int(20 * scale), 10)

section_spacing = max(int(30 * scale), 15)

# 背景颜色

self.fullscreen_window.configure(background='#2C3E50')

# 主框架

main_frame = ttk.Frame(self.fullscreen_window, style='Dark.TFrame')

main_frame.pack(fill=tk.BOTH, expand=True, padx=padding_x, pady=padding_y)

# 最上面添加:自定义全屏标题

event_title = tk.Label(main_frame, text=self.custom_title.get(),

font=('Microsoft YaHei', title_font_size, 'bold'),

background='#2C3E50', foreground='#F1C40F')

event_title.pack(pady=(title_padding_y, section_spacing))

# 保存引用以便实时更新

self._event_title_label = event_title

# 绑定标题变更事件

self.custom_title.trace_add('write', lambda *args: event_title.config(text=self.custom_title.get()))

# 比赛项目名称(保存引用以便实时更新)

self._project_label = tk.Label(main_frame, text=f"📋 {self.match_project.get()}",

font=('Microsoft YaHei', project_font_size),

background='#2C3E50', foreground='#BDC3C7')

self._project_label.pack(pady=(0, section_spacing))

# 比赛规则提示

rules_label = tk.Label(main_frame,

text="📖 比赛规则: 3局2胜制 | 每局21分 | 20平后净胜2分获胜",

font=('Microsoft YaHei', rules_font_size),

background='#2C3E50', foreground='#95A5A6')

rules_label.pack(pady=(0, section_spacing))

# 选手信息框架

players_frame = ttk.Frame(main_frame, style='Dark.TFrame')

players_frame.pack(fill=tk.X, pady=section_spacing)

# 左侧选手(保存引用)

self._left_player_label = tk.Label(players_frame, text=self.left_player_name.get(),

font=('Microsoft YaHei', player_font_size, 'bold'),

background='#2C3E50', foreground='#3498DB')

self._left_player_label.pack(side=tk.LEFT, expand=True)

# VS

vs_label = tk.Label(players_frame, text="VS",

font=('Microsoft YaHei', vs_font_size, 'bold'),

background='#2C3E50', foreground='#E74C3C')

vs_label.pack(side=tk.LEFT, padx=max(int(40 * scale), 15))

# 右侧选手(保存引用)

self._right_player_label = tk.Label(players_frame, text=self.right_player_name.get(),

font=('Microsoft YaHei', player_font_size, 'bold'),

background='#2C3E50', foreground='#E74C3C')

self._right_player_label.pack(side=tk.LEFT, expand=True)

# 比分框架

score_frame = ttk.Frame(main_frame, style='Dark.TFrame')

score_frame.pack(fill=tk.X, pady=section_spacing)

# 左侧比分(保存引用)

self._left_score_label = tk.Label(score_frame, text=str(self.left_score.get()),

font=('Microsoft YaHei', score_font_size, 'bold'),

background='#2C3E50', foreground='#3498DB')

self._left_score_label.pack(side=tk.LEFT, expand=True)

# 比分分隔符

score_sep = tk.Label(score_frame, text="-",

font=('Microsoft YaHei', int(score_font_size * 0.8), 'bold'),

background='#2C3E50', foreground='#FFFFFF')

score_sep.pack(side=tk.LEFT, padx=max(int(30 * scale), 10))

# 右侧比分(保存引用)

self._right_score_label = tk.Label(score_frame, text=str(self.right_score.get()),

font=('Microsoft YaHei', score_font_size, 'bold'),

background='#2C3E50', foreground='#E74C3C')

self._right_score_label.pack(side=tk.LEFT, expand=True)

# 局数信息(保存引用)

self._games_label = tk.Label(main_frame,

text=f"局数: {self.left_games.get()} - {self.right_games.get()} (共{self.target_games.get()}局)",

font=('Microsoft YaHei', games_font_size),

background='#2C3E50', foreground='#BDC3C7')

self._games_label.pack(pady=section_spacing)

# 发球权提示(保存引用)

if self.current_server.get() == "left":

self._server_label = tk.Label(main_frame,

text=f"🔵 {self.left_player_name.get()} 发球",

font=('Microsoft YaHei', server_font_size),

background='#2C3E50', foreground='#3498DB')

else:

self._server_label = tk.Label(main_frame,

text=f"🔴 {self.right_player_name.get()} 发球",

font=('Microsoft YaHei', server_font_size),

background='#2C3E50', foreground='#E74C3C')

self._server_label.pack(pady=section_spacing)

# 快捷键提示(底部显示)

hint_frame = ttk.Frame(main_frame, style='Dark.TFrame')

hint_frame.pack(side=tk.BOTTOM, pady=(max(int(30 * scale), 15), max(int(20 * scale), 10)))

tk.Label(hint_frame, text="空格=左侧得分 | 回车=右侧得分 | R=重置 | ESC=退出全屏",

font=('Microsoft YaHei', hint_font_size),

background='#2C3E50', foreground='#7F8C8D').pack()

# 退出全屏函数

def exit_fullscreen(event=None):

self.fullscreen_window.destroy()

self.fullscreen_window = None

# 绑定快捷键

self.fullscreen_window.bind('', exit_fullscreen)

self.fullscreen_window.bind('', lambda e: self.add_point('left'))

self.fullscreen_window.bind('', lambda e: self.add_point('right'))

self.fullscreen_window.bind('r', lambda e: self.reset_match())

self.fullscreen_window.bind('R', lambda e: self.reset_match())

# 启动实时更新(200毫秒间隔)

self.update_fullscreen()

# 确保全屏窗口获取焦点

self.fullscreen_window.focus_set()

def update_fullscreen(self):

"""实时更新全屏显示"""

# 检查全屏窗口是否还存在

if not hasattr(self, 'fullscreen_window') or not self.fullscreen_window or not self.fullscreen_window.winfo_exists():

return

try:

# 更新比赛项目

if hasattr(self, '_project_label') and self._project_label.winfo_exists():

self._project_label.config(text=f"📋 {self.match_project.get()}")

# 更新左侧选手名称

if hasattr(self, '_left_player_label') and self._left_player_label.winfo_exists():

self._left_player_label.config(text=self.left_player_name.get())

# 更新右侧选手名称

if hasattr(self, '_right_player_label') and self._right_player_label.winfo_exists():

self._right_player_label.config(text=self.right_player_name.get())

# 更新比分

if hasattr(self, '_left_score_label') and self._left_score_label.winfo_exists():

self._left_score_label.config(text=str(self.left_score.get()))

if hasattr(self, '_right_score_label') and self._right_score_label.winfo_exists():

self._right_score_label.config(text=str(self.right_score.get()))

# 更新局数

if hasattr(self, '_games_label') and self._games_label.winfo_exists():

self._games_label.config(text=f"局数: {self.left_games.get()} - {self.right_games.get()} (共{self.target_games.get()}局)")

# 更新发球权

if hasattr(self, '_server_label') and self._server_label.winfo_exists():

if self.current_server.get() == "left":

self._server_label.config(text=f"� {self.left_player_name.get()} 发球")

self._server_label.config(foreground='#3498DB')

else:

self._server_label.config(text=f"🔴 {self.right_player_name.get()} 发球")

self._server_label.config(foreground='#E74C3C')

# 继续定时更新(200毫秒)

self.fullscreen_window.after(200, self.update_fullscreen)

except Exception as e:

# 如果出错,停止更新

print(f"更新全屏显示时出错: {e}")

def main():

root = tk.Tk()

app = BadmintonScoreApp(root)

root.mainloop()

if __name__ == "__main__":

main()

2026-01-04 02:22:03