From 24b00a5c77e9d1d6c4c24d30f52fd8c9f2ce081c Mon Sep 17 00:00:00 2001 From: bePrivate Date: Wed, 19 Jul 2023 16:10:48 +0200 Subject: [PATCH] initial commit --- README.md | 20 +++++ time_squared | 211 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 231 insertions(+) create mode 100644 README.md create mode 100755 time_squared diff --git a/README.md b/README.md new file mode 100644 index 0000000..313d803 --- /dev/null +++ b/README.md @@ -0,0 +1,20 @@ +# Time^2 + +Time squared is a psychological test which measures +a subjet's attention span and concentration. + +The goal is to click all 25 numbers in the grid in +ascending order as quickly as possible. + +After finishing, the subject can see how they performed. + +## Installation + +_Prerequisites: python3, tkinter, matplotlib +Refer to their respective installation guides if you require help._ + +`git clone https://gitlab.com/bePrivate/time_squared` + +`cd time_squared` + +To start, type `python3 time_squared`. diff --git a/time_squared b/time_squared new file mode 100755 index 0000000..8616c7c --- /dev/null +++ b/time_squared @@ -0,0 +1,211 @@ +#!/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()