You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python反应时间游戏计时错误排查求助(Tkinter+多线程)

Reaction Time Game计时异常问题排查与修复

问题现象

游戏完成3轮后,统计GUI显示的三次用时呈现固定递增0.01秒的异常,例如:

  • 用时1:1.15秒
  • 用时2:1.16秒
  • 用时3:1.17秒

错误原因

  1. 全局变量共享冲突:所有计时线程复用同一个全局time_used变量,新线程启动时重置该变量,但旧线程可能仍在运行(全局keep_counting被新启动操作设为True),导致多个线程同时修改time_used,数值被错误累加。
  2. 计时方式精度不足:依赖sleep(0.01)累加计时的方式受线程调度影响大,误差会被不断累积。
  3. 线程控制不严谨:未确保前一轮的计时线程完全停止就启动新线程,多线程并发操作数据列表引发数据混乱。

修复方案

  1. 替换计时方式:使用time.time()记录按钮出现和点击的时间戳,直接计算时间差,精度更高且无累加误差。
  2. 移除全局共享变量:将计时相关变量改为局部变量,避免多线程冲突。
  3. 简化线程逻辑:不再使用额外的计时线程,通过时间戳直接计算用时,消除线程同步问题。
  4. 增加文件容错:处理数据文件不存在的情况,自动创建新文件并添加表头。

修正后完整代码

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 12:10:20