1

I have been trying to make a reaction timer for a project to test reaction times. It uses 'perf_counter' to record the times before and after an input to test how long it takes to press the enter key. The issue is that the enter key can be spammed which makes it seem if they have a reaction time of 0.000001 seconds. I have made a class which disables the keyboard and enables it when I want. Even in that case, people are able to sneak in extra enter presses between the disables and enables. I have attached the code below. Any ideas how to prevent enter spamming?

import time, random, msvcrt
from math import log10, floor
def round_sig(x, sig=5):
    return round(x, sig-int(floor(log10(abs(x))))-1)

class keyboardDisable():

    def start(self):
        self.on = True

    def stop(self):
        self.on = False

    def __call__(self): 
        while self.on:
            msvcrt.getwch()

    def __init__(self):
        self.on = False
        import msvcrt






disable = keyboardDisable()


disable.start() 


print('When I say __GO__ you hit ENTER! This will happen 3 times. Got it?')
time.sleep(2)
print('Ready')
time.sleep(1)
print('Steady')
time.sleep(random.randint(2,5))
print('#####__GO__######')


disable.stop()




tic = time.perf_counter()
a = input()
toc = time.perf_counter()
if msvcrt.kbhit():
    disable.start()
timeSpent = toc-tic
print('Your first time was '+str(timeSpent) + ' seconds')
time.sleep(1)
print('The next one is coming up.')
time.sleep(1)
print('Ready')
time.sleep(1)
print('Steady')

time.sleep(random.randint(2,5))
 
print('#####__GO__######')
disable.stop()

tic2 = time.perf_counter()
b = input()
toc2 = time.perf_counter()
if msvcrt.kbhit():
    disable.start()     
timeSpent2 = toc2-tic2
print('Your second time was '+str(timeSpent2) + ' seconds')
time.sleep(1)
     
print('The last one is coming up.')
time.sleep(1)
print('Ready')
time.sleep(1)
print('Steady')

time.sleep(random.randint(2,5))

print('#####__GO__######')
disable.stop()
tic3 = time.perf_counter()
c = input()
toc3 = time.perf_counter()
    
timeSpent3 = toc3-tic3
print('Your last time was '+str(timeSpent3) + ' seconds')
     
average = (timeSpent + timeSpent2 + timeSpent3)/3
numAverage = round_sig(average)
     
print('Your average time is '+str(numAverage) + ' seconds')
AwesomeHJB
  • 11
  • 4
  • You never call `disable()`, so that `while self.on:` thing never gets executed anyway... – AKX Oct 12 '20 at 14:32
  • I'm confused what you mean? – AwesomeHJB Oct 12 '20 at 14:37
  • You're looking for a technical solution to a human problem. Disabling the keyboard still probably won't do what you want even if you get that working correctly. If a person spams the ENTER key they'll still sneak in more key-presses per second than if they waited properly, and some of those will appear in the window where you've explicitly enabled they keyboard, so it'll show a low reaction time. As a _partial_ fix, what if instead you recorded all ENTER presses and invalidated the test if too many happened outside the allotted window? – Hans Musgrave Oct 12 '20 at 14:38
  • You should check if the key gets pressed early, so you are able to react to that and make this try invalid. Using `input()` is not that easy in this case. You would need an additional thread, or maybe better, use a different input method. – Wups Oct 12 '20 at 14:45

2 Answers2

0

The keyboard-disabling code never really runs.

Here's a simplification of your program that uses a function to capture one reaction time and calls it thrice.

The clear_keyboard_buffer() function (that should consume all outstanding keystrokes) was borrowed from https://stackoverflow.com/a/2521054/51685 .

import time, random, msvcrt, math


def round_sig(x, sig=5):
    return round(x, sig - int(math.floor(math.log10(abs(x)))) - 1)


def clear_keyboard_buffer():
    while msvcrt.kbhit():
        msvcrt.getwch()


def get_reaction_time():
    print("Ready")
    time.sleep(1)
    print("Steady")
    time.sleep(random.randint(2, 5))
    print("#####__GO__######")
    clear_keyboard_buffer()
    tic = time.perf_counter()
    a = input()
    toc = time.perf_counter()
    return toc - tic


print("When I say __GO__ you hit ENTER! This will happen 3 times. Got it?")
time1 = get_reaction_time()
print(f"Your first time was {time1} seconds")
time.sleep(1)
print("The next one is coming up.")
time2 = get_reaction_time()
print(f"Your first time was {time2} seconds")
time.sleep(1)
print("The last one is coming up.")
time3 = get_reaction_time()
print(f"Your first time was {time3} seconds")
average = (time1 + time2 + time3) / 3
print(f"Your average time is {round_sig(average)} seconds")
AKX
  • 152,115
  • 15
  • 115
  • 172
  • I tried this code but it still had the same issue. If you spam enter, it records it for later times – AwesomeHJB Oct 12 '20 at 14:44
  • If you spam enter before it says go for the first time, it does not register those enters for the first one. When you press enter after the first 'go' and it tells you the reaction time, the 2 more times it does it after that has not cleared the keyboard buffer and registers a super early press – AwesomeHJB Oct 12 '20 at 14:54
  • AKX you're my only hope – AwesomeHJB Oct 12 '20 at 14:55
  • Thanks for your help though – AwesomeHJB Oct 12 '20 at 15:02
0

This solution uses a Thread to start the timer, while the main thread waits for input all the time. That way, it is possible to catch early key presses:

from threading import Thread
import random
import time

def start():
    global started
    started = None
    time.sleep(random.randint(2,5))
    print("#### GO ####")
    started = time.time()

t = Thread(target=start)

print("ready...")

# start the thread and directly wait for input:
t.start()
input()

end = time.time()

if not started:
    print("Fail")
else:
    print(end-started)

t.join()
Wups
  • 2,489
  • 1
  • 6
  • 17