Seite 1 von 1
Reaktionszeittest misst etwas, aber keine Reaktionszeiten
Verfasst: Freitag 13. Juli 2018, 22:54
von Atalanttore
Hallo
Bei meinem aktuellen Übungsprojekt geht es um die Reaktion des Benutzers. Aus einer Liste wird jede Sekunde zufällig eine Farbe für den Bildschirmhintergrund ausgewählt und geändert. Sobald der Bildschirmhintergrund rot wird, soll die Startzeit gespeichert und die Reaktionszeit bis der Benutzer die Leertaste drückt in einer Liste gespeichert werden.
Mehrere Sachen funktionieren leider nicht:
- In die Liste wird immer etwas geschrieben, egal ob Tastendruck oder nicht.
- Der in der Liste geschriebene Wert ist viel zu niedrig für eine Reaktionszeit.
Der fehlerhafte Code sieht so aus:
Code: Alles auswählen
import tkinter as tk
from random import choice
import time
switch_time = 1000
reaction_times = []
class Application:
def __init__(self, app_win):
self.app_win = app_win
self.width = app_win.winfo_screenwidth()
self.height = app_win.winfo_screenheight()
self.colors = ["green", "red", "blue", "yellow", "black"]
self.current_color = "red"
self.color = ""
self.canvas = tk.Canvas(app_win, width=self.width, height=self.height,
highlightthickness=0)
self.canvas.pack()
self.change_bg_color(self.current_color)
def change_bg_color(self, current_color):
global reaction_times
self.color = choice(self.colors)
while self.color == current_color:
self.color = choice(self.colors)
if self.color == "red":
self.start_time = time.monotonic()
print("Startzeit:", self.start_time)
if self.key_pressed() == True:
print("Rot und Taste gedrückt bei Stoppzeit:", time.monotonic())
reaction_time = time.monotonic() - self.start_time
reaction_times.append(reaction_time)
print(reaction_times)
self.canvas.config(bg = self.color)
self.app_win.after(switch_time, self.change_bg_color, self.color)
def key_pressed(self):
return True
def main():
app_win = tk.Tk()
app_win.attributes("-fullscreen", True)
app_win.bind("<Escape>", lambda e: app_win.quit())
app = Application(app_win)
app_win.bind("<space>", lambda e: app.key_pressed())
app_win.mainloop()
if __name__ == '__main__':
main()
Gruß
Atalanttore
Re: Reaktionszeittest misst etwas, aber keine Reaktionszeiten
Verfasst: Freitag 13. Juli 2018, 23:35
von __blackjack__
@Atalanttore: Da `key_pressed()` immer `True` liefert wird ganz offensichtlich die Laufzeit der drei Zeilen Code zwischen dem ermitteln der Startzeit und dem ermitteln von `reaction_time` gemessen.
Ein Druck auf die Leertaste bewirkt letztlich gar nichts, denn da wird ja nur ``return True`` ausgeführt, wobei dieses `True` an den Aufrufer, also die GUI-Hauptschleife zurückgegeben wird, die das ignoriert, weil sie keine Rückgabewerte von so einer Ereignisbehandlung erwartet.
In dieser Behandlung der Leertaste müsste die Prüfung erfolgen ob die Farbe gerade rot ist und dann die Ermittlung der Zeit. Die Startzeit würde ich einfach bei jedem Farbwechsel setzen.
Ich vermute ich habe schon mal was zu ``global`` gesagt‽ Falls nicht: Vergiss das es ``global`` gibt! Insbesondere wenn Du eh schon objektorientiert programmierst gibt es dafür so gar keine Ausrede.
Re: Reaktionszeittest misst etwas, aber keine Reaktionszeiten
Verfasst: Samstag 14. Juli 2018, 09:52
von Sirius3
@Atalanttore: Dir fehlt noch das Verständnis, wie Ereignisgesteuerte Programmierung funktioniert. Die Ereignisse sind bei Dir der Timer und der Tastendruck. Die Ereignisbehandlung ist dann Farbe wechseln, bzw. Zeitdauer speichern. Du machst beim einen Ereignis beides und beim anderen nichts.
Außer Konstanten und Definitionen sollte nichts auf oberster Ebene stehen, Konstanten schreibt man komplett groß: SWITCH_TIME. reaction_times sollte ein Attribut einer Applicationinstanz sein.
self.current_color sollte eine lokale Variable sein; was ist der semantische Unterschied zu self.color?
self.start_time wird erst in change_bg_color eingeführt, sollte es aber schon in __init__.
Re: Reaktionszeittest misst etwas, aber keine Reaktionszeiten
Verfasst: Samstag 14. Juli 2018, 18:59
von Atalanttore
Danke für die Erklärungen. Damit macht der Reaktionszeittest jetzt was er soll und ich habe die Verarbeitung der Tastendrücke sogar noch ausgebaut.
Was haltet ihr von dem Code?
Code: Alles auswählen
import tkinter as tk
from random import choice
import time
from statistics import mean
SWITCH_TIME = 1000
DESIGNATED_COLOR = "red"
class Application:
def __init__(self, app_win):
self.app_win = app_win
self.width = app_win.winfo_screenwidth()
self.height = app_win.winfo_screenheight()
self.colors = ["green", "red", "blue", "yellow", "black"]
self.current_color = DESIGNATED_COLOR
self.color = ""
self.reaction_times = []
self.false_positives = 0
self.color_missed = 0
self.key_too_often_pressed = 0
self.key_pressed_in_time = True
self.color_locked = False
self.canvas = tk.Canvas(app_win, width=self.width, height=self.height,
highlightthickness=0)
self.canvas.pack()
self.change_bg_color(self.current_color)
def change_bg_color(self, current_color, color_locked = False):
self.color_locked = color_locked
self.color = choice(self.colors)
# Farbauswahl nochmal ausführen, wenn zufällig
# ausgewählte Farbe gleich letzter Farbe
while self.color == current_color:
self.color = choice(self.colors)
self.canvas.config(bg=self.color)
self.start_time = time.monotonic() # Ereignis Timer
self.app_win.after(SWITCH_TIME, self.check_for_key_press)
def check_for_key_press(self):
if self.color_locked == False and self.color == DESIGNATED_COLOR:
self.color_missed += 1
self.change_bg_color(self.color)
def key_pressed(self):
if self.color == DESIGNATED_COLOR and self.color_locked == True:
self.key_too_often_pressed += 1
elif self.color == DESIGNATED_COLOR:
reaction_time = time.monotonic() - self.start_time
self.reaction_times.append(reaction_time)
self.color_locked = True
elif self.color != DESIGNATED_COLOR:
self.false_positives += 1
def end_app(self):
if len(self.reaction_times) >= 1:
# mean() auf Liste ohne Einträge führt zu einem Fehler
print("Gemittelte Reaktionszeit:", mean(self.reaction_times))
print("Reaktionszeit pro richtiger Farbe:", self.reaction_times)
print("Bei richtiger Farbe zu oft gedrückt:", self.key_too_often_pressed)
print("Bei richtiger Farbe nicht gedrückt:", self.color_missed)
print("Bei falscher Farbe gedrückt:", self.false_positives)
self.app_win.quit()
def main():
app_win = tk.Tk()
app_win.attributes("-fullscreen", True)
app_win.bind("<Escape>", lambda e: app.end_app())
app = Application(app_win)
app_win.bind("<space>", lambda e: app.key_pressed()) # Ereignis Tastendruck
app_win.mainloop()
if __name__ == '__main__':
main()
Gruß
Atalanttore
Re: Reaktionszeittest misst etwas, aber keine Reaktionszeiten
Verfasst: Samstag 14. Juli 2018, 19:56
von __blackjack__
Ich denke es gibt zu viele Attribute. `width` und `height` beispielsweise. `key_pressed_in_time` wird nie benutzt. `current_color` wird in der `__init__()` gesetzt und in der gleichen Methode wird einmal lesend darauf zugegriffen und sonst nirgends‽
`change_bg_color()` bekommt unnötigerweise `current_color` übergeben, wo die Method doch ganz einfach `self.color` selbst an einen lokalen Namen binden könnte.
Das optionale `color_locked`-Argument wird nie bei einem Aufruf verwendet.
Die Zufallsfarbe würde ich ohne `choice()` an zwei Stellen aufrufen zu müssen ermitteln. In einer ”Endlosschleife” einfach so oft `choice()` aufrufen bis man eine neue, andere Farbe gefunden hat, und die Schleife dann mit ``break`` verlassen.
Vergleiche mit literalen `True`- und `False`-Werten ergeben nur wieder einen Wahrheitswert. Den hatte man ja schon. Also kann man den auch gleich direkt verwenden, oder mit ``not`` negieren wenn man auf das Gegenteil testen möchte.
Die Tests in `key_pressed()` sind IMHO ungünstig aufgeteilt. Statt drei Zweige auf oberster Ebene zu haben, würde ich das wohl eine Ebene tiefer verschachteln.
``if len(self.reaction_times) >= 1:`` würde man kürzer als ``if self.reaction_times:`` schreiben.
Re: Reaktionszeittest misst etwas, aber keine Reaktionszeiten
Verfasst: Samstag 14. Juli 2018, 21:08
von Atalanttore
Vielen Dank für die Analyse. Sehr hilfreich zum Lernen.
__blackjack__ hat geschrieben: Samstag 14. Juli 2018, 19:56
`change_bg_color()` bekommt unnötigerweise `current_color` übergeben, wo die Method doch ganz einfach `self.color` selbst an einen lokalen Namen binden könnte.
Ich habe vor einiger Zeit gelesen/gehört, dass eine Methode alle nötigen Werte als Argumente übergeben bekommen soll und nicht auf Variablen außerhalb der Methode zugreifen sollte. Bei meinen bisherigen Übungsprojekten habe ich versucht mich einigermaßen daran zu halten.
__blackjack__ hat geschrieben: Samstag 14. Juli 2018, 19:56
``if len(self.reaction_times) >= 1:`` würde man kürzer als ``if self.reaction_times:`` schreiben.
Diese Schreibweise kannte ich bisher noch nicht.
Der Code sieht nach den Optimierungen jetzt so aus:
Code: Alles auswählen
import tkinter as tk
from random import choice
import time
from statistics import mean
SWITCH_TIME = 1000
DESIGNATED_COLOR = "red"
class Application:
def __init__(self, app_win):
self.app_win = app_win
self.colors = ["green", "red", "blue", "yellow", "black"]
self.color = None
self.reaction_times = []
self.false_positives = 0
self.color_missed = 0
self.key_too_often_pressed = 0
self.color_locked = False
self.canvas = tk.Canvas(app_win, width=self.app_win.winfo_screenwidth(), height=self.app_win.winfo_screenheight(),
highlightthickness=0)
self.canvas.pack()
self.change_bg_color(DESIGNATED_COLOR)
def change_bg_color(self, color_locked = False):
self.color_locked = color_locked
current_color = self.color
while True:
if self.color == current_color:
self.color = choice(self.colors)
else:
break
self.canvas.config(bg=self.color)
self.start_time = time.monotonic() # Ereignis Timer
self.app_win.after(SWITCH_TIME, self.check_for_key_press)
def check_for_key_press(self):
if not self.color_locked and self.color == DESIGNATED_COLOR:
self.color_missed += 1
self.change_bg_color()
def key_pressed(self):
if self.color == DESIGNATED_COLOR:
if self.color_locked:
self.key_too_often_pressed += 1
else:
reaction_time = time.monotonic() - self.start_time
self.reaction_times.append(reaction_time)
self.color_locked = True
else:
self.false_positives += 1
def end_app(self):
if self.reaction_times:
# mean() auf Liste ohne Einträge führt zu einem Fehler
print("Gemittelte Reaktionszeit:", mean(self.reaction_times))
print("Reaktionszeit pro richtiger Farbe:", self.reaction_times)
print("Bei richtiger Farbe zu oft gedrückt:", self.key_too_often_pressed)
print("Bei richtiger Farbe nicht gedrückt:", self.color_missed)
print("Bei falscher Farbe gedrückt:", self.false_positives)
self.app_win.quit()
def main():
app_win = tk.Tk()
app_win.attributes("-fullscreen", True)
app_win.bind("<Escape>", lambda e: app.end_app())
app = Application(app_win)
app_win.bind("<space>", lambda e: app.key_pressed()) # Ereignis Tastendruck
app_win.mainloop()
if __name__ == '__main__':
main()
Gruß
Atalanttore