2

I am trying create multiple buttons with each button consists of 2 rectangles in Pygame. However, the program would stuck for a second in a certain step probably due to a nested loop. Specifically, the "while loop" marked by the code fence in the main loop only works correctly only when being executed for the first time. If it is executed after the first time, the program would stop responding for a second. Is there anyway to correct that? I am currently testing it on on window 10 64 bit , Python 3.7.4, PyGame 1.9.6.

import pygame
import time
import random

pygame.init()

# Screen for displaying everything
display_width = 1200
display_height = 700
game_display = pygame.display.set_mode((display_width,display_height),pygame.RESIZABLE)
pygame.display.set_caption('Currently testing')

# Color for later use
red = (200,0,0)
green = (0,200,0)
bright_red = (255,0,0)
bright_green = (0,255,0)
black= (0,0,0)
white= (225,225,225)
grey = (166,166,166)
font_color= black
background_color= white

def create_button(x,y,w,h,name,callback):
    # lower part of the button
    button_upper= pygame.Rect(x, y, w, h)
    # upper part of the button
    button_lower= pygame.Rect(x, y+ h, w, h)
    # define the area of the button for later interaction
    interactable= pygame.Rect(x, y, w, 2*h)
    button_info= {'button_lower':button_lower,
                  'button_upper':button_upper, 
                  'button_name':name,
                  'interaction':interactable,
                  'button function':callback,
                  'color_upper':red, 'color_lower':green}
    return button_info

def draw_button(button_info):
    # Drawing lower part of the button
    pygame.draw.rect(game_display, 
                    button_info['color_lower'],
                    button_info['button_lower'])
    # Drawing upper part
    pygame.draw.rect(game_display, 
                    button_info['color_upper'],
                    button_info['button_upper'])

# Text object for later use
def text_object(text,font):
    textSurface= font.render(text,True,font_color)
    return textSurface, textSurface.get_rect()
def central_text(text,size,pos):
    largeText = pygame.font.Font('freesansbold.ttf',size)
    textSurface, textRect = text_object(text,largeText)
    textRect.center = (pos)
    game_display.blit(textSurface,textRect)

def main():

    # The function of the button, temporarily
    def print_name():
        nonlocal button
        nonlocal done
        game_display.fill(background_color)
        central_text('Button'+' '+button['button_name']+' '+'clicked',80,(display_width/2,display_height/2))
        pygame.display.update()
        time.sleep(3)         
        done= True
    # function of non-interactable block
    def do_nothing():
        pass

    # Actually create those button
    button_1= create_button(display_width*0.3,display_height*0.5,40,40,'1',print_name)
    button_2= create_button(display_width*0.35,display_height*0.5,50,50,'2',print_name)
    button_3= create_button(display_width*0.4,display_height*0.5,30,30,'3',print_name)
    button_4= create_button(display_width*0.45,display_height*0.5,20,20,'4',print_name)
    button_5= create_button(display_width*0.5,display_height*0.5,40,30,'5',print_name)
    button_6= create_button(display_width*0.55,display_height*0.5,50,40,'6',print_name)
    button_7= create_button(display_width*0.6,display_height*0.5,50,30,'7',print_name)
    button_8= create_button(display_width*0.65,display_height*0.5,50,50,'8',print_name)
    button_9= create_button(display_width*0.7,display_height*0.5,30,40,'9',print_name)
    button_10= create_button(display_width*0.75,display_height*0.5,60,70,'10',print_name)
    # Create non-interactable rectangles
    block_1= create_button(display_width*0.75,display_height*0.8,40,40,'10',do_nothing)
    block_2= create_button(display_width*0.7,display_height*0.8,40,40,'9',do_nothing)
    block_3= create_button(display_width*0.7,display_height*0.8,40,40,'8',do_nothing)
    # Select and store those button in different list, with a non-interactable 
    # rectangles in each list
    list_1=[button_1, button_2, button_3, button_4, button_5, 
            button_6, button_7, button_8, button_9, button_10]
    list_2=[button_1, button_2, button_3, button_4, button_5, 
            button_6, button_7, block_3, button_9, button_10]
    list_3=[button_1, button_2, button_3, button_4, button_5, 
            button_6, button_7, button_8, block_2, button_10]
    list_4=[button_1, button_2, button_3, button_4, button_5, 
            button_6, button_7, button_8, button_9, block_1]

    # Attempt to control how many times and in what sequence those
    # button list would be used
    index= [1,1,2,2,2,3,3,4,4]
    random.shuffle(index)
    text=' ' # a text that would accompany all the buttons
    for i in range (len(index)):
        done = False
        if index[i] == 1:
            button_list= list_1
            text= 'text 1'
        elif index[i] == 2:
            button_list= list_2
            text= 'text 2'
        elif index[i] == 3:
            button_list= list_3
            text= 'text 3'
        else:
            button_list= list_4
            text= 'text 4'
        # display message 1
        game_display.fill(background_color)
        central_text('test_text 1',80,(display_width/2,display_height/2))
        pygame.display.update()
        time.sleep(2)
        # display message 2
        game_display.fill(background_color)
        central_text('test_text 2',80,(display_width/2,display_height/2))
        pygame.display.update()       
        time.sleep(3)
       '''This is where the program would stuck for a second'''
        game_display.fill(background_color)
        central_text(text,30,(display_width/2,display_height*0.2))
        while not done:
            for event in pygame.event.get():
                if event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_ESCAPE:
                        pygame.display.quit()
                        pygame.quit()
                        quit()     
                # block that would be executed when left mouse button is pressed
                elif event.type == pygame.MOUSEBUTTONDOWN:
                    if event.button == 1:
                        for button in button_list:
                            if button['interaction'].collidepoint(event.pos):
                                button['button function']()
                elif event.type == pygame.MOUSEMOTION:
                    # When the mouse gets moved, change the color of the
                    # buttons if they collide with the mouse.
                    for button in button_list:
                        if not button['button function']== do_nothing:
                            if button['interaction'].collidepoint(event.pos):
                                button['color_upper']= red
                                button['color_lower']= green
                            else:
                                button['color_upper']= bright_red
                                button['color_lower']= bright_green
            for button in button_list:
                # Turn non-interactable blocks into grey
                if button['button function']== do_nothing:
                    button['color_upper']= grey
                    button['color_lower']= grey                    
                draw_button(button)
            pygame.display.update()
        '''the block above would some times stucks the program'''
main()
  • code works correctly on Linux Mint 19, Python 3.7.4, PyGame 1.9.6 – furas Sep 08 '19 at 03:21
  • @furas Have you encountered any delay when running it? I just figured the button screen would only work when being displayed for the first time. After that the program would stuck for a second before the the button screen being displayed. Now I am not sure what cause it. I am currently testing it on on window 10 64 bit , Python 3.7.4, PyGame 1.9.6. – Spenserark Bottest Sep 08 '19 at 06:13
  • I don't see any delay. If something doesn't work then you can use `print()` to see which part of code is executed and what values you have in variables. maybe you will see when it stops and what values makes problem. OR learn to use debugger. – furas Sep 08 '19 at 12:06

1 Answers1

0

You need to handle the events by either by either pygame.event.pump() or pygame.event.get(), when you do delays and display updates. When you use pygame then you should use pygame.time.delay() or pygame.time.wait().

I recommend to change the procedure. Use 1 main loop and 1 event loop. Implement the game procedure in this loop. Instead of

for i in range (len(index)):

   # [...]

   while not done:
       for event in pygame.event.get():
   
           # [...]

   # [...]

change the procedure to

done = False
run = True
i = 0
while run:

    if done:
        done = False
        if i < len(index)-1:
            i += 1
        else:
            run = False

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False

        # [...]

    # [...]

To implement the different state of the game, I recommend to use a game state. e.g.:

from enum import Enum
class GameState(Enum):
    START = 0
    TEXT1 = 1
    TEXT2 = 2
    RUN = 3
    BUTTON = 4

state = GameState.START

Use a timer event (pygame.time.set_timer()) to switch the states of the game. e.g.:

clicked_button = None
my_event_id = pygame.USEREVENT + 1
while run:

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False

        # timer event
        elif event.type == my_event_id:
             if state == GameState.TEXT1:
                pygame.time.set_timer(my_event_id, 3000)
                state = GameState.TEXT2
            elif state == GameState.TEXT2:
                pygame.time.set_timer(my_event_id, 0)
                state = GameState.RUN
            else:
                pygame.time.set_timer(my_event_id, 0)
                state = GameState.START
                done = True

        if state == GameState.RUN:
            if event.type == pygame.MOUSEBUTTONDOWN:
                if event.button == 1:
                    for button in button_list:
                        if button['interaction'].collidepoint(event.pos):
                            pygame.time.set_timer(my_event_id, 3000)
                            state = GameState.BUTTON
                            clicked_button = button
            # [...]

Draw the scene dependent on the game state:

while run:

    # [...]
    
    game_display.fill(background_color)

    if state == GameState.START:
        pygame.time.set_timer(my_event_id, 2000)
        state = GameState.TEXT1
    elif state == GameState.TEXT1:
        # display message 1
        central_text('test_text 1',80,(display_width/2,display_height/2))
    elif state == GameState.TEXT2:
        # display message 2
        central_text('test_text 2',80,(display_width/2,display_height/2))
    elif state == GameState.RUN:
        central_text(text,30,(display_width/2,display_height*0.2))
        # [...]
    else:
        clicked_button['button function']()
            
    pygame.display.update()

The game loop and the main game procedure may look like this:

from enum import Enum
class GameState(Enum):
    START = 0
    TEXT1 = 1
    TEXT2 = 2
    RUN = 3
    BUTTON = 4

def main():

    # The function of the button, temporarily
    def print_name():
        nonlocal clicked_button
        central_text('Button'+' '+clicked_button['button_name']+' '+'clicked',80,(display_width/2,display_height/2))        
    # function of non-interactable block
    def do_nothing():
        pass

    # Actually create those button
    button_1= create_button(display_width*0.3,display_height*0.5,40,40,'1',print_name)
    button_2= create_button(display_width*0.35,display_height*0.5,50,50,'2',print_name)
    button_3= create_button(display_width*0.4,display_height*0.5,30,30,'3',print_name)
    button_4= create_button(display_width*0.45,display_height*0.5,20,20,'4',print_name)
    button_5= create_button(display_width*0.5,display_height*0.5,40,30,'5',print_name)
    button_6= create_button(display_width*0.55,display_height*0.5,50,40,'6',print_name)
    button_7= create_button(display_width*0.6,display_height*0.5,50,30,'7',print_name)
    button_8= create_button(display_width*0.65,display_height*0.5,50,50,'8',print_name)
    button_9= create_button(display_width*0.7,display_height*0.5,30,40,'9',print_name)
    button_10= create_button(display_width*0.75,display_height*0.5,60,70,'10',print_name)
    # Create non-interactable rectangles
    block_1= create_button(display_width*0.75,display_height*0.8,40,40,'10',do_nothing)
    block_2= create_button(display_width*0.7,display_height*0.8,40,40,'9',do_nothing)
    block_3= create_button(display_width*0.7,display_height*0.8,40,40,'8',do_nothing)
    # Select and store those button in different list, with a non-interactable 
    # rectangles in each list
    list_1=[button_1, button_2, button_3, button_4, button_5, 
            button_6, button_7, button_8, button_9, button_10]
    list_2=[button_1, button_2, button_3, button_4, button_5, 
            button_6, button_7, block_3, button_9, button_10]
    list_3=[button_1, button_2, button_3, button_4, button_5, 
            button_6, button_7, button_8, block_2, button_10]
    list_4=[button_1, button_2, button_3, button_4, button_5, 
            button_6, button_7, button_8, button_9, block_1]

    # Attempt to control how many times and in what sequence those
    # button list would be used
    index= [1,1,2,2,2,3,3,4,4]
    random.shuffle(index)
    text=' ' # a text that would accompany all the buttons

 
    my_event_id = pygame.USEREVENT + 1
    done = False
    run = True
    state = GameState.START
    i = 0
    clicked_button = None
    while run:

        if done:
            done = False
            if i < len(index)-1:
                i += 1
            else:
                run = False

        if index[i] == 1:
            button_list= list_1
            text= 'text 1'
        elif index[i] == 2:
            button_list= list_2
            text= 'text 2'
        elif index[i] == 3:
            button_list= list_3
            text= 'text 3'
        else:
            button_list= list_4
            text= 'text 4' 

        # event loop
       
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                run = False
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_ESCAPE:
                    run = False  
            elif event.type == my_event_id:
                if state == GameState.TEXT1:
                    pygame.time.set_timer(my_event_id, 3000)
                    state = GameState.TEXT2
                elif state == GameState.TEXT2:
                    pygame.time.set_timer(my_event_id, 0)
                    state = GameState.RUN
                else:
                    pygame.time.set_timer(my_event_id, 0)
                    state = GameState.START
                    done = True
                    
            if state == GameState.RUN:
                # block that would be executed when left mouse button is pressed
                if event.type == pygame.MOUSEBUTTONDOWN:
                    if event.button == 1:
                        for button in button_list:
                            if button['interaction'].collidepoint(event.pos):
                                pygame.time.set_timer(my_event_id, 3000)
                                state = GameState.BUTTON
                                clicked_button = button

                elif event.type == pygame.MOUSEMOTION:
                    # When the mouse gets moved, change the color of the
                    # buttons if they collide with the mouse.
                    for button in button_list:
                        if not button['button function']== do_nothing:
                            if button['interaction'].collidepoint(event.pos):
                                button['color_upper']= red
                                button['color_lower']= green
                            else:
                                button['color_upper']= bright_red
                                button['color_lower']= bright_green

        # draw

        game_display.fill(background_color)

        if state == GameState.START:
            pygame.time.set_timer(my_event_id, 2000)
            state = GameState.TEXT1
        elif state == GameState.TEXT1:
            # display message 1
            central_text('test_text 1',80,(display_width/2,display_height/2))
        elif state == GameState.TEXT2:
            # display message 2
            central_text('test_text 2',80,(display_width/2,display_height/2))
        elif state == GameState.RUN:
            central_text(text,30,(display_width/2,display_height*0.2))
            for button in button_list:
                # Turn non-interactable blocks into grey
                if button['button function']== do_nothing:
                    button['color_upper']= grey
                    button['color_lower']= grey                    
                draw_button(button)
        else:
            clicked_button['button function']()
            
        pygame.display.update()
Rabbid76
  • 202,892
  • 27
  • 131
  • 174