Python反应时间游戏计时错误排查求助(Tkinter+多线程)
Reaction Time Game计时异常问题排查与修复
问题现象
游戏完成3轮后,统计GUI显示的三次用时呈现固定递增0.01秒的异常,例如:
- 用时1:1.15秒
- 用时2:1.16秒
- 用时3:1.17秒
错误原因
- 全局变量共享冲突:所有计时线程复用同一个全局
time_used变量,新线程启动时重置该变量,但旧线程可能仍在运行(全局keep_counting被新启动操作设为True),导致多个线程同时修改time_used,数值被错误累加。 - 计时方式精度不足:依赖
sleep(0.01)累加计时的方式受线程调度影响大,误差会被不断累积。 - 线程控制不严谨:未确保前一轮的计时线程完全停止就启动新线程,多线程并发操作数据列表引发数据混乱。
修复方案
- 替换计时方式:使用
time.time()记录按钮出现和点击的时间戳,直接计算时间差,精度更高且无累加误差。 - 移除全局共享变量:将计时相关变量改为局部变量,避免多线程冲突。
- 简化线程逻辑:不再使用额外的计时线程,通过时间戳直接计算用时,消除线程同步问题。
- 增加文件容错:处理数据文件不存在的情况,自动创建新文件并添加表头。
修正后完整代码
from tkinter import * import time import openpyxl import datetime import random def run_game(): root = Tk() root.title("Reaction Time Game") root.iconbitmap("C:/Users/Axelr/PycharmProjects/PC01/main/Self built/Reaction Time Game/icon.ico") root.geometry("1400x1000") root.configure(bg="orange") # 处理数据文件不存在的情况 try: workbook = openpyxl.load_workbook(filename="time_data.xlsx") except FileNotFoundError: workbook = openpyxl.Workbook() sheet = workbook.active sheet.append(["Time 1", "Time 2", "Time 3", "Average", "Datetime"]) sheet = workbook.active time_used_list = [] times_clicked = 0 current_round_data = None def show_results(): root.destroy() root2 = Tk() root2.title("Reaction Time Game Completed") root2.iconbitmap("C:/Users/Axelr/PycharmProjects/PC01/main/Self built/Reaction Time Game/icon.ico") root2.geometry("700x570") root2.configure(bg="lightblue") def save_data(): current_datetime = datetime.datetime.now().strftime("%Y-%m-%d | %H:%M") place = sheet.max_row + 1 sheet[f"A{place}"] = round(time_used_list[0], 2) sheet[f"B{place}"] = round(time_used_list[1], 2) sheet[f"C{place}"] = round(time_used_list[2], 2) average = sum(time_used_list) / len(time_used_list) sheet[f"D{place}"] = round(average, 2) sheet[f"E{place}"] = current_datetime workbook.save(filename="time_data.xlsx") print("Saved time data") def restart_game(): root2.destroy() run_game() print("list of times:") for t in time_used_list: print(t) # 结果UI组件 title_label = Label(root2, text="Results", font=("Arial", 20, "bold"), bg="orange") title_label.pack(padx=20, pady=20) time1_label = Label(root2, text=f"Time used 1: {round(time_used_list[0], 2)} seconds", font=("Arial", 20), bg="orange") time1_label.pack(padx=20, pady=20) time2_label = Label(root2, text=f"Time used 2: {round(time_used_list[1], 2)} seconds", font=("Arial", 20), bg="orange") time2_label.pack(padx=20, pady=20) time3_label = Label(root2, text=f"Time used 3: {round(time_used_list[2], 2)} seconds", font=("Arial", 20), bg="orange") time3_label.pack(padx=20, pady=20) average_time = sum(time_used_list) / len(time_used_list) average_label = Label(root2, text=f"Average Time Used: {round(average_time, 2)} seconds", font=("Arial", 20), bg="orange") average_label.pack(padx=20, pady=20) save_btn = Button(root2, text="Save", font=("Arial", 20), command=save_data, bg="orange") save_btn.pack(padx=20, pady=20) play_btn = Button(root2, text="Play Again", font=("Arial", 20), bg="orange", command=restart_game) play_btn.pack(padx=20, pady=20) root2.mainloop() def on_button_click(): nonlocal times_clicked, current_round_data # 计算当前回合用时 click_time = time.time() time_used = click_time - current_round_data["start_time"] time_used_list.append(time_used) current_round_data["btn"].destroy() times_clicked += 1 if times_clicked >= 3: show_results() else: start_round() def start_round(): nonlocal current_round_data # 生成按钮随机属性 color_list = ["red", "green", "yellow", "cyan", "white", "blue", "magenta"] random_color = random.choice(color_list) random_x = random.randint(100, 600) random_y = random.randint(100, 600) random_width = random.randint(5, 15) font_list = ["Helvetica", "Garamond", "Frutiger", "Bodoni", "Times", "Futura"] random_font = random.choice(font_list) random_font_size = random.randint(10, 18) text_list = ["!", "?", "/", "+", "=", "<", ">", "%", "&", "(", ")", "-", "|", ";", ":", "[", "]", "{", "}", "^", "@", "#"] random_text = random.choice(text_list) random_time = random.randint(3, 5) # 打印调试信息 print(f"Random position: ({random_x}, {random_y})") print(f"Random size: {random_width}") print(f"Random font: {random_font} {random_font_size}") print(f"Random text: {random_text}") print(f"Wait time: {random_time}s") def create_button(): nonlocal current_round_data btn = Button(root, text=random_text, font=(random_font, random_font_size), bg=random_color, width=random_width, command=on_button_click) btn.place(x=random_x, y=random_y) # 记录按钮出现的时间戳和按钮对象 current_round_data = {"start_time": time.time(), "btn": btn} # 延迟指定时间后创建按钮 root.after(random_time * 1000, create_button) # 启动第一回合 start_round() root.mainloop() run_game()
关键修复点说明
- 计时逻辑优化:用
time.time()获取精确时间戳,直接计算按钮出现到点击的时间差,完全避免了线程累加的误差和冲突。 - 移除全局变量冲突:用局部变量和
nonlocal替代全局变量,每个回合的计时数据独立存储,不会被其他操作干扰。 - 简化线程逻辑:不再使用额外的计时线程,消除了线程同步的复杂度和潜在问题。
- 增加文件容错:自动处理数据文件不存在的场景,避免程序崩溃。
内容的提问来源于stack exchange,提问作者axelrothing
相关产品推荐
相关产品推荐

