3

Hi everyone and thank you in advance for your help. I've just discovered pygame (a library for python) and I wanted to play a bit with it but I'm facing a problem. I tried to use threads in my code but the program keeps crashing each time I launch it.

I have isolated the problem and I know that it is thread_1 that causes the crash because when I comment it out everything works again. I have tried to change the code of the function of thread_1, but it still crashes. I'm pretty sure it's not the content of the function animateTitle that causes the crash but the way I'm using threads.

import pygame

from pygame.locals import *

from threading import Thread


def encadre(screen):  
    pygame.draw.line(screen, (250, 250, 250), (230, 140), (520, 140), 3)
    pygame.draw.line(screen, (250, 250, 250), (230, 190), (520, 190), 3)
    pygame.draw.line(screen, (250, 250, 250), (230, 140), (230, 190), 3)
    pygame.draw.line(screen, (250, 250, 250), (520, 140), (520, 190), 3)


def initRoad(screen):  
    pygame.draw.line(screen, (250, 250, 250), (30, 0), (30, 500))
    pygame.draw.line(screen, (250, 250, 250), (100, 0), (100, 500))
    pygame.draw.line(screen, (250, 250, 250), (650, 0), (650, 500))
    pygame.draw.line(screen, (250, 250, 250), (720, 0), (720, 500))
    drawLines(screen)


def drawLines(screen): 
    i = 0
    while i <= 49:
        pygame.draw.line(screen, (250, 250, 250), (65, i * 10), (65, (i + 1) * 10))
        pygame.draw.line(screen, (250, 250, 250), (685, i * 10), (685, (i + 1) * 10))
        i = i + 3


def initText(screen, text1):  
    text1pos = text1.get_rect()
    text1pos.x = 235
    text1pos.y = 150
    screen.blit(text1, text1pos)

    font1 = pygame.font.Font(None, 30)
    text1 = font1.render("PLAY", 1, (10, 10, 10))
    text1pos = text1.get_rect()
    text1pos.x = 210
    text1pos.y = 310
    screen.blit(text1, text1pos)

    font1 = pygame.font.Font(None, 30)
    text1 = font1.render("QUIT", 1, (10, 10, 10))
    text1pos = text1.get_rect()
    text1pos.x = 490
    text1pos.y = 310
    screen.blit(text1, text1pos)


def animateRoad(screen):  # not done
    pygame.draw.line(screen, (130, 130, 130), (65, 0), (65, 500))
    pygame.draw.line(screen, (130, 130, 130), (685, 0), (685, 500))


def animateTitle(screen, text1): 
    text1pos = text1.get_rect()
    while True:
        pygame.draw.rect(screen, (130, 130, 130), (235, 150, 283, 35))
        pygame.display.flip()
        pygame.time.wait(500)
        text1pos.x = 235
        text1pos.y = 150
        screen.blit(text1, text1pos)
        pygame.display.flip()
        pygame.time.wait(1000)


def loop(surface1, surface2):
    while True:
        for event in pygame.event.get():

            if event.type == QUIT:
                return

            if event.type == pygame.MOUSEBUTTONDOWN:
                if surface1.topleft[0] <= pygame.mouse.get_pos()[0] <= surface1.topright[0]:
                    if surface1.topleft[1] <= pygame.mouse.get_pos()[1] <= surface1.bottomleft[1]:
                    print('play')

                if surface2.topleft[0] <= pygame.mouse.get_pos()[0] <= surface2.topright[0]:
                    if surface2.topleft[1] <= pygame.mouse.get_pos()[1] <= surface2.bottomleft[1]:
                    return

    pygame.display.flip()
    pygame.time.wait(10)


def main():
    pygame.init()
    screen = pygame.display.set_mode((750, 500))
    pygame.display.set_caption('Infinite circle run')

    background = pygame.Surface(screen.get_size())  
    background = background.convert()
    background.fill((130, 130, 130))
    screen.blit(background, (0, 0))

    encadre(screen)
    initRoad(screen)

    surface1 = pygame.Rect(193, 290, 85, 50) 
    button1 = pygame.draw.rect(screen, (0, 0, 240), surface1)
    surface2 = pygame.Rect(472, 290, 85, 50)  
    button2 = pygame.draw.rect(screen, (240, 0, 0), surface2)

    font1 = pygame.font.Font(None, 50)
    text1 = font1.render("Infinite circle run", 1, (0, 240, 0))
    initText(screen, text1)  

    pygame.display.flip()

    thread_1 = Thread(target=animateTitle(screen, text1), daemon=True)
    thread_2 = Thread(target=loop(surface1, surface2))
    # thread_3 = Thread(target=animateRoad(screen))

    thread_1.start()
    thread_2.start()
    # thread_3.start()


if __name__ == '__main__':
    main()
Ted Klein Bergman
  • 9,146
  • 4
  • 29
  • 50
Riushda
  • 33
  • 5
  • Might need to initialize the thread like so. `thread_1 = Thread(target=animateTitle, args=(screen, text1, ), daemon=True)` – Torin M. Jan 31 '19 at 22:21
  • I tried but the program still crash with the classic windows error 'not responding'. – Riushda Jan 31 '19 at 22:29

1 Answers1

3

Threading causes many problems and a general rule of thumb is to avoid them if they aren't necessary. They make your program non-deterministic, harder to debug, harder to test, harder to maintain and slower (most of the time). In your program, there are no reasons to use threads. Instead, you should do things sequentially. Pygame will implicitly create threads whenever necessary (for example when dealing with pygame.mixer)

Why it doesn't work, is because pygame expects all event handling to happen in the thread that set the video mode (pygame is using SDL2, hence that link). You cannot handle them in another thread, and as you don't handle them (correctly), the operating system will think your program has crashed.


I made an example to show one way you could do the animation. The concept is that you tell pygame that after a certain amount of time, you want an event to be posted. When that event appears in your event loop, you do something.

In your case, you tell pygame to post DRAW_TEXT_EVENT after 500 ms. When this event appears in your event loop, you first tell pygame to don't post DRAW_TEXT_EVENT anymore, but to post CLEAR_TEXT_EVENT after 1000ms. Then you draw the text.

After 1000ms, the CLEAR_TEXT_EVENT will appear in your event loop. Now you do basically the same thing, but disable CLEAR_TEXT_EVENT and tell pygame to post DRAW_TEXT_EVENT after 500ms.

I haven't changed much in your code. I added 2 definitions for the events at the top. I've removed your functions loop and animateTitle, and put them into the game loop. Lastly, I've added the game loop in the main function.

import pygame
from pygame.locals import *

# Events that we're going to post.
DRAW_TEXT_EVENT  = pygame.USEREVENT + 1
CLEAR_TEXT_EVENT = pygame.USEREVENT + 2


def encadre(screen):
    pygame.draw.line(screen, (250, 250, 250), (230, 140), (520, 140), 3)
    pygame.draw.line(screen, (250, 250, 250), (230, 190), (520, 190), 3)
    pygame.draw.line(screen, (250, 250, 250), (230, 140), (230, 190), 3)
    pygame.draw.line(screen, (250, 250, 250), (520, 140), (520, 190), 3)


def initRoad(screen):
    pygame.draw.line(screen, (250, 250, 250), (30, 0), (30, 500))
    pygame.draw.line(screen, (250, 250, 250), (100, 0), (100, 500))
    pygame.draw.line(screen, (250, 250, 250), (650, 0), (650, 500))
    pygame.draw.line(screen, (250, 250, 250), (720, 0), (720, 500))
    drawLines(screen)


def drawLines(screen):
    i = 0
    while i <= 49:
        pygame.draw.line(screen, (250, 250, 250), (65, i * 10), (65, (i + 1) * 10))
        pygame.draw.line(screen, (250, 250, 250), (685, i * 10), (685, (i + 1) * 10))
        i = i + 3


def initText(screen, text1):
    text1pos = text1.get_rect()
    text1pos.x = 235
    text1pos.y = 150
    screen.blit(text1, text1pos)

    font1 = pygame.font.Font(None, 30)
    text1 = font1.render("PLAY", 1, (10, 10, 10))
    text1pos = text1.get_rect()
    text1pos.x = 210
    text1pos.y = 310
    screen.blit(text1, text1pos)

    font1 = pygame.font.Font(None, 30)
    text1 = font1.render("QUIT", 1, (10, 10, 10))
    text1pos = text1.get_rect()
    text1pos.x = 490
    text1pos.y = 310
    screen.blit(text1, text1pos)


def animateRoad(screen):  # not done
    pygame.draw.line(screen, (130, 130, 130), (65, 0), (65, 500))
    pygame.draw.line(screen, (130, 130, 130), (685, 0), (685, 500))



def main():
    pygame.init()
    screen = pygame.display.set_mode((750, 500))
    pygame.display.set_caption('Infinite circle run')

    background = pygame.Surface(screen.get_size())
    background = background.convert()
    background.fill((130, 130, 130))
    screen.blit(background, (0, 0))

    encadre(screen)
    initRoad(screen)

    surface1 = pygame.Rect(193, 290, 85, 50)
    button1 = pygame.draw.rect(screen, (0, 0, 240), surface1)
    surface2 = pygame.Rect(472, 290, 85, 50)
    button2 = pygame.draw.rect(screen, (240, 0, 0), surface2)

    font1 = pygame.font.Font(None, 50)
    text1 = font1.render("Infinite circle run", 1, (0, 240, 0))
    initText(screen, text1)

    pygame.display.flip()

    pygame.time.set_timer(DRAW_TEXT_EVENT, 500)

    text1pos = text1.get_rect()

    # GAME LOOP
    while True:

        for event in pygame.event.get():
            if event.type == QUIT:
                return
            elif event.type == pygame.MOUSEBUTTONDOWN:
                if surface1.topleft[0] <= pygame.mouse.get_pos()[0] <= surface1.topright[0]:
                    if surface1.topleft[1] <= pygame.mouse.get_pos()[1] <= surface1.bottomleft[1]:
                        print('play')
                elif surface2.topleft[0] <= pygame.mouse.get_pos()[0] <= surface2.topright[0]:
                    if surface2.topleft[1] <= pygame.mouse.get_pos()[1] <= surface2.bottomleft[1]:
                        return

            elif event.type == DRAW_TEXT_EVENT:
                pygame.draw.rect(screen, (130, 130, 130), (235, 150, 283, 35))
                pygame.time.set_timer(DRAW_TEXT_EVENT,  0)     # Disable the event.
                pygame.time.set_timer(CLEAR_TEXT_EVENT, 1000)  # Post event after 1000ms.
            elif event.type == CLEAR_TEXT_EVENT:
                text1pos.x = 235
                text1pos.y = 150
                screen.blit(text1, text1pos)
                pygame.time.set_timer(CLEAR_TEXT_EVENT, 0)    # Disable the event.
                pygame.time.set_timer(DRAW_TEXT_EVENT,  500)  # Post event after 500ms.

        # Only call once each frame!
        pygame.display.flip()


if __name__ == '__main__':
    main()
Ted Klein Bergman
  • 9,146
  • 4
  • 29
  • 50
  • Yes you are right i don't like to use thread too but in this case i didn't know how to do because i needed that the animation of the title run at the same time than the mouse listener – Riushda Jan 31 '19 at 23:38
  • @DariushMohandes Just draw them after each other. Nothing you draw will be visible until you call `pygame.display.flip()`, so they will be displayed at the same time. – Ted Klein Bergman Jan 31 '19 at 23:41
  • @DariushMohandes And make sure to only call `pygame.display.flip()` once each frame, not after every draw or blit. – Ted Klein Bergman Jan 31 '19 at 23:42
  • @DariushMohandes You've got yourself into some trouble by using `pygame.time.wait()`. You can't do animations this way. Instead, you can call `pygame.time.set_timer()` to call a function after a certain amount of time ([example](https://stackoverflow.com/a/50237012/6486738)) or calculate the time yourself in the main loop. – Ted Klein Bergman Jan 31 '19 at 23:49
  • In my case I tried with the function animatedTitle to make the illusion that the title was flashing so i needed to use pygam.display.flip() to achieve that – Riushda Jan 31 '19 at 23:50
  • But I will try to manage everything in one loop without thread as you said – Riushda Jan 31 '19 at 23:51
  • So pygame.time.set_timer() is the proper way to do animation without thread ? – Riushda Jan 31 '19 at 23:54
  • @DariushMohandes It depends. In your case, it seems easiest to use, as you want things to change state every half/full second. In other kinds of animations, you'd probably use something like [this](https://stackoverflow.com/a/42013186/6486738). – Ted Klein Bergman Feb 01 '19 at 00:02
  • @DariushMohandes I'll mock up an example on how you could do it in 10 minutes or so. – Ted Klein Bergman Feb 01 '19 at 00:04
  • Okay thank you because I don't see how to manage the fact that if i want to close the window of my program I'll have to wait the end of the animation of the loop – Riushda Feb 01 '19 at 00:10
  • @DariushMohandes I've added an example. Hopefully, it's clear what happens. – Ted Klein Bergman Feb 01 '19 at 00:23