Search code examples
pythonpython-3.xpygamepygame-clock

How to show text for 5 seconds then disappear and display buttons?


I am trying to make it so when you get an answer correct in my trivia game, it will get rid of the big question you see and say "well done" for 5 seconds and then go back to the main menu where there are 4 randomly selected questions. The questions are loaded in from a quizfile.csv and contains:

What colour is elon musk's hair?,brown
What is most popular sport?,football
What was Queen first called?,Smile
What's superior Apple Or Windows?,Windows

They are random as they are just placeholder and for testing purposes only. The place where I want the pause/display for 5 seconds function is on line 115 of the first file.

I have tried time.sleep(5) and this just freezes the program and doesn't display it for 5 seconds, much like for less than a second. Would I need to use pygame.event.set_timer() and if so, how?

import csv
import sys
import random
import pygame
import textwrap
import time
import pygame_textinput
textinput = pygame_textinput.TextInput()


pygame.init()
clock = pygame.time.Clock()
FPS=60
SCREENSIZE = SCREENWIDTH, SCREENHEIGHT = 1080, 720
screen = pygame.display.set_mode(SCREENSIZE)

white = (255, 255, 255)
black = (0, 0, 0)
red = (255, 0, 0)
yellow = (255, 255, 0)
green = (0, 255, 0)

questions = {}

def wrap_text(message, wraplimit): #keep text on screen
    return textwrap.fill(message, wraplimit)

def text_objects(text,font): #render text
    textSurf = font.render(text, True, black)
    return textSurf, textSurf.get_rect()


def question(text):
    xx=0
    text = wrap_text(text,20) #wrap text
    largeText = pygame.font.Font('freesansbold.ttf',100)
    for part in text.split('\n'): #for each line from the wrapped text
        TextSurf, TextRect = text_objects(part, largeText)
        TextRect.center = ((SCREENWIDTH/2)),(SCREENHEIGHT/2+xx)
        screen.blit(TextSurf, TextRect)
        xx+=75 #change height of text so doesnt overlap



class Button:
    def __init__(self, question, answer,positionx,positiony): #setup all vars
        self.question = question
        self.answer = answer
        self.positionx = positionx
        self.positiony = positiony

    def button(self):
        ltr = len(self.question)
        w= 12.5*ltr #make width of button big enough for text
        button = pygame.Rect(self.positionx,self.positiony,w,50) #make button
        largeText = pygame.font.Font('freesansbold.ttf',20)
        TextSurf, TextRect = text_objects(self.question, largeText)
        TextRect.center = ((self.positionx+(w/2)),(self.positiony+25)) #button text
        return button, TextSurf, TextRect
    def question(self):
        question(self.question) #display question
    def giveQuestionAnswer(self):
        return self.question,self.answer #give question and answer

with open("quizfile.csv") as f: #load in questions
    reader = csv.reader(f)
    quiz_qas = list(reader) 

z=0 #for positioning
t=0 #for loop
quiz = random.sample(quiz_qas, 4) #randomly select 4 questions
for q, a in quiz: #for every question and answer in the file
    questions[q] = a #define the dictionary of questions and answers
    for x, y in questions.items(): #for every answer and question in the dictionary, link them
        if t==0: #the sweet spots for getting a different question every time
            b = Button(x,y,200,200) #make button object
            z+=50

        elif t==5:
            b1 = Button(x,y,600,200)
            z+=50

        elif t==7:
            b2 = Button(x,y,600,400)
            z+=50
        elif t==9:
            b3 = Button(x,y,200,400)
            z+=50
        t+=1

b2on = False #for handling displaying the question
b3on = False
b4on = False
b5on = False
correct=False
gameState = "running"  # controls which state the games is in
# game loop #################### runs 60 times a second!
while gameState != "exit":  # game loop - note:  everything in the mainloop is indented one tab

    screen.fill(white)
    events = pygame.event.get()
    if b2on:
        q,a = b.giveQuestionAnswer() #get question and answer
        question(q) #display answer
        # Feed it with events every frame
        # Blit its surface onto the screen
        screen.blit(textinput.get_surface(), (10, 10))
        if textinput.update(events): #if hit enter
            if textinput.get_text() == a:
                b2on = False
                correct=True

    if correct:
        question("well done")
        #PAUSE SCREEN HERE AND DISPLAY WELL DONE FOR 5 SECONDS
        correct=False

    elif b3on:
        q,a = b1.giveQuestionAnswer()
        question(q)

        textinput.update(events)
        screen.blit(textinput.get_surface(), (10, 10))
        if textinput.update(events): #if hit enter
            if textinput.get_text() == a:
                b3on = False
                correct=True

    elif b4on:
        q,a = b2.giveQuestionAnswer()
        question(q)

        textinput.update(events)
        screen.blit(textinput.get_surface(), (10, 10))
        if textinput.update(events): #if hit enter
            if textinput.get_text() == a:
                b4on = False
                correct=True

    elif b5on:
        q,a = b3.giveQuestionAnswer()
        question(q)

        textinput.update(events)
        screen.blit(textinput.get_surface(), (10, 10))
        if textinput.update(events): #if hit enter
            if textinput.get_text() == a:
                b5on = False
                correct=True

    elif b2on==False and b3on==False and b4on==False and b5on==False:
        B2,TextSurf,TextRect = b.button() #draw buttons
        pygame.draw.rect(screen, [255, 0, 0], B2)
        screen.blit(TextSurf, TextRect)

        B3,TextSurf,TextRect = b1.button()
        pygame.draw.rect(screen, [255, 0, 0], B3)
        screen.blit(TextSurf, TextRect)

        B4,TextSurf,TextRect = b2.button()
        pygame.draw.rect(screen, [255, 0, 0], B4)
        screen.blit(TextSurf, TextRect)

        B5,TextSurf,TextRect = b3.button()
        pygame.draw.rect(screen, [255, 0, 0], B5)
        screen.blit(TextSurf, TextRect)


    for event in pygame.event.get():  # get user interaction events
        if event.type == pygame.QUIT:  # tests if window's X (close) has been clicked
            gameState = "exit"  # causes exit of game loop
        if event.type == pygame.MOUSEBUTTONDOWN:
            mouse_pos = event.pos  # gets mouse position

            if B2.collidepoint(mouse_pos): #if click on button
                b2on = True #display question
            if B3.collidepoint(mouse_pos):
                b3on = True
            if B4.collidepoint(mouse_pos):
                b4on = True
            if B5.collidepoint(mouse_pos):
                b5on = True


    pygame.display.update()            
    pygame.display.flip()  # transfers build screen to human visable screen
    clock.tick(FPS)  # limits game to frame per second, FPS value

# out of game loop ###############
print("The game has closed") 
pygame.quit() 
sys.exit()  

and here is the pygame_textinput.py file I am using to create that blinking input effect and function.

"""
Copyright 2017, Silas Gyger, [email protected], All rights reserved.

Borrowed from https://github.com/Nearoo/pygame-text-input under the MIT license.
"""

import os.path

import pygame
import pygame.locals as pl

pygame.font.init()


class TextInput:
    """
    This class lets the user input a piece of text, e.g. a name or a message.
    This class let's the user input a short, one-lines piece of text at a blinking cursor
    that can be moved using the arrow-keys. Delete, home and end work as well.
    """
    def __init__(
            self,
            initial_string="",
            font_family="",
            font_size=35,
            antialias=True,
            text_color=(0, 0, 0),
            cursor_color=(0, 0, 1),
            repeat_keys_initial_ms=400,
            repeat_keys_interval_ms=35):
        """
        :param initial_string: Initial text to be displayed
        :param font_family: name or list of names for font (see pygame.font.match_font for precise format)
        :param font_size:  Size of font in pixels
        :param antialias: Determines if antialias is applied to font (uses more processing power)
        :param text_color: Color of text (duh)
        :param cursor_color: Color of cursor
        :param repeat_keys_initial_ms: Time in ms before keys are repeated when held
        :param repeat_keys_interval_ms: Interval between key press repetition when helpd
        """

        # Text related vars:
        self.antialias = antialias
        self.text_color = text_color
        self.font_size = font_size
        self.input_string = initial_string  # Inputted text

        if not os.path.isfile(font_family):
            font_family = pygame.font.match_font(font_family)

        self.font_object = pygame.font.Font(font_family, font_size)

        # Text-surface will be created during the first update call:
        self.surface = pygame.Surface((1, 1))
        self.surface.set_alpha(0)

        # Vars to make keydowns repeat after user pressed a key for some time:
        self.keyrepeat_counters = {}  # {event.key: (counter_int, event.unicode)} (look for "***")
        self.keyrepeat_intial_interval_ms = repeat_keys_initial_ms
        self.keyrepeat_interval_ms = repeat_keys_interval_ms

        # Things cursor:
        self.cursor_surface = pygame.Surface((int(self.font_size/20+1), self.font_size))
        self.cursor_surface.fill(cursor_color)
        self.cursor_position = len(initial_string)  # Inside text
        self.cursor_visible = True  # Switches every self.cursor_switch_ms ms
        self.cursor_switch_ms = 500  # /|\
        self.cursor_ms_counter = 0

        self.clock = pygame.time.Clock()

    def update(self, events):
        for event in events:
            if event.type == pygame.KEYDOWN:
                self.cursor_visible = True  # So the user sees where he writes

                # If none exist, create counter for that key:
                if event.key not in self.keyrepeat_counters:
                    self.keyrepeat_counters[event.key] = [0, event.unicode]

                if event.key == pl.K_BACKSPACE:
                    self.input_string = (
                        self.input_string[:max(self.cursor_position - 1, 0)]
                        + self.input_string[self.cursor_position:]
                    )

                    # Subtract one from cursor_pos, but do not go below zero:
                    self.cursor_position = max(self.cursor_position - 1, 0)
                elif event.key == pl.K_DELETE:
                    self.input_string = (
                        self.input_string[:self.cursor_position]
                        + self.input_string[self.cursor_position + 1:]
                    )

                elif event.key == pl.K_RETURN:
                    return True

                elif event.key == pl.K_RIGHT:
                    # Add one to cursor_pos, but do not exceed len(input_string)
                    self.cursor_position = min(self.cursor_position + 1, len(self.input_string))

                elif event.key == pl.K_LEFT:
                    # Subtract one from cursor_pos, but do not go below zero:
                    self.cursor_position = max(self.cursor_position - 1, 0)

                elif event.key == pl.K_END:
                    self.cursor_position = len(self.input_string)

                elif event.key == pl.K_HOME:
                    self.cursor_position = 0

                else:
                    # If no special key is pressed, add unicode of key to input_string
                    self.input_string = (
                        self.input_string[:self.cursor_position]
                        + event.unicode
                        + self.input_string[self.cursor_position:]
                    )
                    self.cursor_position += len(event.unicode)  # Some are empty, e.g. K_UP

            elif event.type == pl.KEYUP:
                # *** Because KEYUP doesn't include event.unicode, this dict is stored in such a weird way
                if event.key in self.keyrepeat_counters:
                    del self.keyrepeat_counters[event.key]

        # Update key counters:
        for key in self.keyrepeat_counters:
            self.keyrepeat_counters[key][0] += self.clock.get_time()  # Update clock

            # Generate new key events if enough time has passed:
            if self.keyrepeat_counters[key][0] >= self.keyrepeat_intial_interval_ms:
                self.keyrepeat_counters[key][0] = (
                    self.keyrepeat_intial_interval_ms
                    - self.keyrepeat_interval_ms
                )

                event_key, event_unicode = key, self.keyrepeat_counters[key][1]
                pygame.event.post(pygame.event.Event(pl.KEYDOWN, key=event_key, unicode=event_unicode))

        # Re-render text surface:
        self.surface = self.font_object.render(self.input_string, self.antialias, self.text_color)

        # Update self.cursor_visible
        self.cursor_ms_counter += self.clock.get_time()
        if self.cursor_ms_counter >= self.cursor_switch_ms:
            self.cursor_ms_counter %= self.cursor_switch_ms
            self.cursor_visible = not self.cursor_visible

        if self.cursor_visible:
            cursor_y_pos = self.font_object.size(self.input_string[:self.cursor_position])[0]
            # Without this, the cursor is invisible when self.cursor_position > 0:
            if self.cursor_position > 0:
                cursor_y_pos -= self.cursor_surface.get_width()
            self.surface.blit(self.cursor_surface, (cursor_y_pos, 0))

        self.clock.tick()
        return False

    def get_surface(self):
        return self.surface

    def get_text(self):
        return self.input_string

    def get_cursor_position(self):
        return self.cursor_position

    def set_text_color(self, color):
        self.text_color = color

    def set_cursor_color(self, color):
        self.cursor_surface.fill(color)

    def clear_text(self):
        self.input_string = ""
        self.cursor_position = 0

Any help appreciated!


Solution

  • You've to use a timer event. See pygame.event.

    Create a user event and a paused state.

    pausetimerevent = pygame.USEREVENT + 1
    paused = False
    

    If the game is paused then display "well done". When the answer is correct, then set paused = True and start the timer. See pygame.time.set_timer:

    while gameState != "exit": 
    
        if not paused and b2on:
            # [...]
    
    
        if paused:
            question("well done")
    
        elif correct:
            correct = False
            paused = True
            pygame.time.set_timer(pausetimerevent, 5000) # 5000 milliseconds = 5 socond 
    

    After the the time has elapsed, the event occurs. Reset paused and reset the timer by passing 0 to the time argument:

    while gameState != "exit": 
    
        # [...]
    
        for event in pygame.event.get():  # get user interaction events
            if event.type == pygame.QUIT:  # tests if window's X (close) has been clicked
                gameState = "exit"  # causes exit of game loop
            if event.type == pygame.MOUSEBUTTONDOWN:
                mouse_pos = event.pos  # gets mouse position
    
                # [...]
    
            if event.type == pausetimerevent:
                pygame.time.set_timer(pausetimerevent, 0) # stop timer
                paused = False