#!/bin/python3 import tkinter as tk import os as os import argparse as ap import time as ti import random as ra import matplotlib.pyplot as pl from matplotlib.gridspec import GridSpec class App(tk.Tk): def __init__(self): super().__init__() # get cmd-line arguments self.args = self.get_args() # properties self.resizable(False, False) self.title("Time^2") self.configure(bg="black") self.measure_missclick = False self.correct_number = 1 # the number that needs to be clicked next, aka the correct one # width, height of canvas and window self.w = 1000 self.h = 1000 self.screen_w = self.winfo_screenwidth() self.screen_h = self.winfo_screenheight() self.center_x = self.screen_w//2 - self.w//2 self.center_y = self.screen_h//2 - self.h//2 self.geometry(f"{self.w}x{self.h}+{self.center_x}+{self.center_y}") # start screen (init phase 1) self.btn_start = tk.Button(self, text="Start test", font=("Courier", 50)) self.btn_start.config(command=lambda btn=self.btn_start: self.init_phase_2(btn)) self.btn_start.pack(pady=450) def init_phase_2(self, btn): # buttons self.taken = [] self.num_gone = 0 self.area = self.draw() btn.destroy() self.get_random() # data needed for evaluation self.data = { "time_clicks": [], "time_missclicks": [], "time_test": 0, "num_clicks": 0, "num_missclicks": 0 } self.start = ti.time() self.start2 = ti.time() self.start_entire_test = ti.time() self.end = ti.time() self.end_entire_test = ti.time() def get_args(self): p = ap.ArgumentParser(prog="Time^2") p.add_argument("-d", "--debug", action="store_true", help="print debug messages") return p.parse_args() # debug print def dp(self, payload: str): if self.args.debug: print(f"D: [{payload}]") # drawing buttons on the screen def draw(self) -> list[list]: area = [] for i in range(5): temp = [] for j in range(5): button = tk.Button(self, width=50, height=5, font=("Courier", 40), text="", bg="#ffffff", fg="#000000") button.config(command=lambda button=button, x=j*200, y=i*200, w=self.w//5, h=self.h//5: [self.button_callback(button, x, y, w, h)], activebackground="lightgray", activeforeground="black") button.place(x=j*200, y=i*200, width=self.w//5, height=self.h//5) self.dp(f"Creating a button => x: {j*200}, \ty: {i*200},\tw: {self.w//5},\th: {self.h//5}") temp.append(button) area.append(temp) return area # function for buttons to work def button_callback(self, instance, x, y, w, h): self.dp(f"Button clicked => {instance['text']}") # only if the number is correct if instance["text"] == str(self.correct_number): self.data["num_clicks"] += 1 self.end = ti.time() instance.destroy() self.num_gone += 1 self.dp(f"Correct number was => {self.correct_number}") self.correct_number += 1 if self.measure_missclick: self.measure_missclick = False self.data["time_missclicks"].append(self.end - self.start) for i in range(5): for j in range(5): try: if self.area[i][j]["bg"] == "#ff0000": self.area[i][j].config(bg="#ffffff", fg="#000000", activebackground="lightgray", activeforeground="#000000") self.area[i][j].place() except: pass self.data["time_clicks"].append(self.end - self.start2) self.start = ti.time() self.start2 = ti.time() else: if instance["bg"] != "#ff0000": self.measure_missclick = True self.start = ti.time() self.data["num_clicks"] += 1 self.data["num_missclicks"] += 1 self.dp(f"Number of missclicks => {self.data['num_missclicks']}") instance.config(bg="#ff0000", fg="#ffffff", activebackground="darkred", activeforeground="#ffffff") instance.place(x=x, y=y, width=w, height=h) # check for test end if self.num_gone == 25: self.destroy() self.end_entire_test = ti.time() self.data["time_test"] = self.end_entire_test - self.start_entire_test self.show_data() self.dp("Data => ") for key in self.data: if type(self.data[key]) == float or type(self.data[key]) == int: self.dp(" "+key+" => "+ "\n "+str(self.data[key])) if type(self.data[key]) == list: self.dp(" "+key+" => "+ "".join(["\n "+str(i) for i in self.data[key]])) # returns a random number that can be used as a text in a new button def get_random(self) -> str: for i in range(5): for j in range(5): r = ra.randint(1, 25) while r in self.taken: r = ra.randint(1, 25) self.taken.append(r) self.area[i][j]["text"] = str(r) def show_data(self): if len(self.data["time_clicks"]) != 0: time_click_avg = sum(self.data["time_clicks"])/len(self.data["time_clicks"]) else: time_click_avg = 0 if self.data["time_clicks"] != []: time_click_longest = max(self.data["time_clicks"]) time_click_shortest = min(self.data["time_clicks"]) else: time_click_longest = 0 time_click_shortest = 0 if len(self.data["time_missclicks"]) != 0: time_missclick_avg = sum(self.data["time_missclicks"])/len(self.data["time_missclicks"]) else: time_missclick_avg = 0 if self.data["time_missclicks"] != []: time_missclick_longest = max(self.data["time_missclicks"]) time_missclick_shortest = min(self.data["time_missclicks"]) else: time_missclick_longest = 0 time_missclick_shortest = 0 pl.rcParams["toolbar"] = "None" ax1 = pl.subplot(2, 1, 1) ax2 = pl.subplot(2, 1, 2) ax1.set_title("Time between clicks on correct numbers", loc="left") ax1.plot(self.data["time_clicks"], marker="o") ax2.set_title("Time between missclicks and corrections", loc="left") ax2.plot(self.data["time_missclicks"], "o") pl.subplots_adjust(hspace=1) tb = pl.table( cellText=[ [round(time_click_avg, 2), round(time_missclick_avg, 2), ""], [round(time_click_longest, 2), round(time_missclick_longest, 2), ""], [round(time_click_shortest, 2), round(time_missclick_shortest, 2), ""], ["", self.data["num_missclicks"], ""], ["", "", round(self.data["time_test"], 2)]], rowLabels= ["Average time", "Longest time", "Shortest time", "Count", "Time taken"], colLabels= ["Between clicks", "Between missclicks and corrections", "To complete the test"], colColours= ["lightgray", "lightgray", "lightgray"], rowColours= ["lightgray", "lightgray", "lightgray", "lightgray", "lightgray"], loc= "top" ) tb.set_fontsize(120) pl.show() if __name__ == "__main__": app = App() app.mainloop()