Search code examples
pythonpython-3.xpygamepygame-surfacepygame-clock

CREATING A REACTION TIME TEST GAME IN PYGAME


I am trying to create a reaction time test game in python using pygame for gui. Since, I am fairly new to the technology, I am stuck at a portion of the code as how to register further keypresses and then record time accordingly.

This is my code till now:

import pygame
from datetime import datetime
import time
import random
pygame.init()

screen = pygame.display.set_mode((640, 480))
pygame.display.set_caption("Reaction Time Test")

font = pygame.font.SysFont(None, 30)

text = font.render("PRESS ANY KEY TO START TEST", 0, (255,255,255))

w = font.render("PRESS ANY KEY",0,(0,255,0))
count = 0
screen.blit(text, (150,240))
running = True
while running:
    pygame.display.flip()
    
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
            pygame.quit()
        
        if count <= 5 and event.type == pygame.KEYDOWN:
             
                screen.fill(pygame.Color("black"))
                pygame.display.flip()
                wait_time = random.randint(1,4)
                time.sleep(wait_time)
                reaction_start = datetime.now()
                print(reaction_start)
                screen.blit(w,(225,200))
                count = count + 1
                
                if event.type == pygame.KEYDOWN:
                    reaction_end = datetime.now()
                    print(reaction_end)
                    t = reaction_end - reaction_start
                    print(t)
                    f = font.render("REACTION TIME: "+ str(t),0,(255,255,255))
                    screen.blit(f,(225,300))

             
        if count > 5:
            screen.fill(pygame.Color("black"))
            pygame.display.flip()
            s = font.render("AVERAGE REACTION TIME IS: ",0,(255,255,255))
            screen.blit(s,(150,200))
            pygame.display.flip()
                

The part where I am stuck at it is this code snippet

if count <= 5 and event.type == pygame.KEYDOWN:
             
                screen.fill(pygame.Color("black"))
                pygame.display.flip()
                wait_time = random.randint(1,4)
                time.sleep(wait_time)
                reaction_start = datetime.now()
                print(reaction_start)
                screen.blit(w,(225,200))
                count = count + 1
                
                if event.type == pygame.KEYDOWN:
                    reaction_end = datetime.now()
                    print(reaction_end)
                    t = reaction_end - reaction_start
                    print(t)
                    f = font.render("REACTION TIME: "+ str(t),0,(255,255,255))
                    screen.blit(f,(225,300))

It would register the reaction_start and reaction_end almost simulataneously and would not wait for the key press.

This currently prints both the statements "PRESS ANY KEY" and "REACTION TIME:" together, but when I had put the statements of screen.fill(pygame.Color("black") and pygame.display.flip() before the screen.blit(f), it would only show REACTION TIME: and not "PRESS ANY KEY"


Solution

  • In pygame the system time can be obtained by calling pygame.time.get_ticks(), which returns the number of milliseconds since pygame.init() was called.

    Do not try to delay or to wait in the application loop. Add a game_state variabel, that which can have the states "start", "wait" and "wait_for_reaction".

    Get the current time at the begin of the application loop:

    while running:
        current_time = pygame.time.get_ticks()
    

    Set the start time when a key is pressed:

    if event.type == pygame.KEYDOWN:
        if game_state == "start":
            game_state = "wait"
            start_time = current_time + random.randint(1000, 4000)
    

    Chang the game_state form "wait" to "wait_for_reaction" when the current time is greater or equal the start time:

    if game_state == "wait":
        if current_time >= start_time:
            game_state = "wait_for_reaction" 
    

    Compute the reaction time and restart the process, when a key is hit again:

    if event.type == pygame.KEYDOWN:
        # [...]
        
        if game_state == "wait_for_reaction": 
            game_state = "wait" 
            reaction_time = (current_time - start_time) / 1000
            start_time = current_time + random.randint(1000, 4000)
            count += 1
            average_time = (average_time * (count-1) + reaction_time) / count
            r_surf = font.render(f"REACTION TIME: {reaction_time:.03f}",0,(255,255,255))
            ar_surf = font.render(f"AVERAGE REACTION TIME IS: {average_time:.03f}",0,(255,255,255))
    

    Redraw the scene in every frame, dependent on the state of the game:

    while running:
        # [...]
    
        screen.fill(pygame.Color("black"))
        
        center = screen.get_rect().center
        if game_state == "start":
            screen.blit(text, text.get_rect(center = center))
        if game_state == "wait_for_reaction":
            screen.blit(w, w.get_rect(center = center))
        if r_surf:
            screen.blit(r_surf, r_surf.get_rect(center = (center[0], 350)))
        if ar_surf:
            screen.blit(ar_surf, ar_surf.get_rect(center = (center[0], 400)))
    
        pygame.display.flip()
    

    Complete example:

    import pygame
    import random
    pygame.init()
    
    screen = pygame.display.set_mode((640, 480))
    pygame.display.set_caption("Reaction Time Test")
    
    font = pygame.font.SysFont(None, 30)
    
    text = font.render("PRESS ANY KEY TO START TEST", 0, (255,255,255))
    w = font.render("PRESS ANY KEY",0,(0,255,0))
    r_surf = None
    ar_surf = None
    
    game_state = "start"
    start_time = 0
    average_time = 0
    
    count = 0
    running = True
    while running:
        current_time = pygame.time.get_ticks()
        
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
                pygame.quit()
            if event.type == pygame.KEYDOWN:
                if game_state == "start":
                    game_state = "wait" 
                    start_time = current_time + random.randint(1000, 4000)
                if game_state == "wait_for_reaction": 
                    game_state = "wait" 
                    reaction_time = (current_time - start_time) / 1000
                    start_time = current_time + random.randint(1000, 4000)
                    count += 1
                    average_time = (average_time * (count-1) + reaction_time) / count
                    r_surf = font.render(f"REACTION TIME: {reaction_time:.03f}",0,(255,255,255))
                    ar_surf = font.render(f"AVERAGE REACTION TIME IS: {average_time:.03f}",0,(255,255,255))
    
        if game_state == "wait":
            if current_time >= start_time:
                game_state = "wait_for_reaction"        
    
        screen.fill(pygame.Color("black"))
        
        center = screen.get_rect().center
        if game_state == "start":
            screen.blit(text, text.get_rect(center = center))
        if game_state == "wait_for_reaction":
            screen.blit(w, w.get_rect(center = center))
        if r_surf:
            screen.blit(r_surf, r_surf.get_rect(center = (center[0], 350)))
        if ar_surf:
            screen.blit(ar_surf, ar_surf.get_rect(center = (center[0], 400)))
    
        pygame.display.flip()