1

The issue is the game crashes everytime after the winner text is displayed (if either player reaches a score of 10 or greater) and fails to re-call the main() function when I stated that it should as you can see from my code below. So when it displays the winner text it should pause the game for 5 sec then break out of the while loop and recall main() but it crashes right after the winner text is displayed (see screenshot below). The logic for the winner text is near the top in my main() function.

Crash after winner text is displayed

Not sure what the root cause is since I called pygame.init() at the top of my program. Any help would be greatly appreciated! The full code is below... if someone can run it and let me know what the root cause of it crashing is that would be great. Thanks in advance!

import pygame

pygame.init()

WIDTH, HEIGHT = 750, 500
WINDOW = pygame.display.set_mode((WIDTH, HEIGHT))   

pygame.display.set_caption("PONG")  

WINNER_FONT = pygame.font.SysFont('comicsans', 100)

BLUE = (0, 0, 255)          
RED = (255, 0, 0)           
YELLOW = (255, 255, 0)     
WHITE = (255, 255, 255)     
BLACK = (0, 0, 0)           

class Paddle(pygame.sprite.Sprite):     
    def __init__(self):
        pygame.sprite.Sprite.__init__(self)    
        self.image = pygame.Surface([10, 75])
        self.image.fill(WHITE)
        self.rect = self.image.get_rect()   
        self.paddle_speed = 4          
        self.points = 0

class Ball(pygame.sprite.Sprite):
    def __init__(self):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.Surface([10, 10])
        self.image.fill(YELLOW)
        self.rect = self.image.get_rect()
        self.speed = 2
        self.dx = 1    
        self.dy = 1

paddle1 = Paddle()
paddle1.image.fill(BLUE)
paddle1.rect.x = 25      
paddle1.rect.y = 225     

paddle2 = Paddle()
paddle2.image.fill(RED)
paddle2.rect.x = 715
paddle2.rect.y = 225

ball = Ball()
ball.rect.x = 375
ball.rect.y = 250

all_sprites = pygame.sprite.Group() 
all_sprites.add(paddle1, paddle2, ball)


def redraw():
    WINDOW.fill(BLACK)
    font = pygame.font.SysFont("Consolas", 35)
    text = font.render("---[PONG]---", 1, YELLOW)
    textRect = text.get_rect()      
    textRect.center = (WIDTH//2, 25)  
    WINDOW.blit(text, textRect)     

    #Player 1 score
    p1_score = font.render(str([paddle1.points]), 1, WHITE)
    p1Rect = p1_score.get_rect()    
    p1Rect.center = (50, 50)        
    WINDOW.blit(p1_score, p1Rect)   
    
    #Player 2 score
    p2_score = font.render(str([paddle2.points]), 1, WHITE)
    p2Rect = p2_score.get_rect()
    p2Rect.center = (700, 50)      
    WINDOW.blit(p2_score, p2Rect)
    
    all_sprites.draw(WINDOW)    
    pygame.display.update()     


def draw_winner(text): 
    draw_text = WINNER_FONT.render(text, 1, WHITE)
    WINDOW.blit(draw_text, (WIDTH//2 - draw_text.get_width()/2, HEIGHT//2 - draw_text.get_height()/2))
    pygame.display.update()
    pygame.time.delay(5000)


def main():
    run = True

    while run:
        pygame.time.delay(10)   
       
        for event in pygame.event.get():    
            if event.type == pygame.QUIT:  
                run = False
                pygame.quit()

        winner_text = ""
        if paddle1.points >= 10:
            winner_text = "Blue Wins!"

        if paddle2.points >= 10:
            winner_text = "Red Wins!"

        if winner_text != "":
            draw_winner(winner_text)
            break
            
        # Paddle movement        
        key = pygame.key.get_pressed()  
        if key[pygame.K_w]:
            paddle1.rect.y -= paddle1.paddle_speed  
        if key[pygame.K_s]:
            paddle1.rect.y += paddle1.paddle_speed  
        if key[pygame.K_UP]:
            paddle2.rect.y -= paddle2.paddle_speed
        if key[pygame.K_DOWN]:
            paddle2.rect.y += paddle2.paddle_speed

        # Ensures paddles never move off the screen
        if paddle1.rect.y < 0:
            paddle1.rect.y = 0
        
        if paddle1.rect.y > 425:
            paddle1.rect.y = 425

        if paddle2.rect.y < 0:
            paddle2.rect.y = 0
        
        if paddle2.rect.y > 425:
            paddle2.rect.y = 425

        # Ball movement
        ball.rect.x += ball.speed * ball.dx     
        ball.rect.y += ball.speed * ball.dy     

        # Setting up collision detection with the walls by changing ball's direction
        if ball.rect.y > 490:   
            ball.dy = -1        

        if ball.rect.x > 740:   
            ball.rect.x, ball.rect.y = 375, 250     
            paddle1.points += 1

        if ball.rect.y < 10:    
            ball.dy = 1         

        if ball.rect.x < 10:    
            ball.rect.x, ball.rect.y = 375, 250
            paddle2.points += 1

        # Setting up collision detection with the paddles
        if paddle1.rect.colliderect(ball.rect):
            ball.dx = 1     

        if paddle2.rect.colliderect(ball.rect):
            ball.dx = -1

        redraw()    
        
    main()

if __name__ == "__main__":
    main()
Rabbid76
  • 202,892
  • 27
  • 131
  • 174
L393nd
  • 25
  • 5
  • I am not sure whether that is a problem or not as the text gets blitted onto the screen, but if you are using Python 3.x your rectangle in draw_winner() gets two floats instead of all 4 integers because of the true division. That's what I see at a first glance. I am not sure I want to play your game though as there is no possibility of both players collecting same number of points. That's not fair! :D – Dalen Mar 28 '21 at 04:02
  • Sorry! You pass position to WINDOW.blit() as tuple of two floats instead of ints. I'm sleepy, don't know what rectangle I was talking about before. :D Just forget that part. – Dalen Mar 28 '21 at 04:11
  • "So when it displays the winner text it should pause the game for 5 sec then break out of the while loop and recall main()" It does so. Calling `main()` again, however, does not reset anything, and I don't understand why you would expect it to. In particular, I don't understand why you expect `winner_text` to change. The result is infinite recursion. You *should not be using a recursive call anyway* - use another while loop. – Karl Knechtel Mar 28 '21 at 09:23
  • Try to explain, in plain English words, the complete set of steps you expect are needed in order to "reset" the game. (Hint: what steps did you need to take initially, in order to make it possible to start the main game loop?) Now, try wrapping that logic up in a function. Does it become clear when and where to use that? – Karl Knechtel Mar 28 '21 at 09:25

2 Answers2

1

Never invoke the application loop recursively and never "wait" in the application loop. See How to wait some time in pygame? and Python : What is the better way to make multiple loops in pygame?.

Create a init function that initializes all game states:

def init():
    global paddle1, paddle2, ball, all_sprites

    paddle1 = Paddle()
    paddle1.image.fill(BLUE)
    paddle1.rect.x = 25      
    paddle1.rect.y = 225     

    paddle2 = Paddle()
    paddle2.image.fill(RED)
    paddle2.rect.x = 715
    paddle2.rect.y = 225

    ball = Ball()
    ball.rect.x = 375
    ball.rect.y = 250

    all_sprites = pygame.sprite.Group() 
    all_sprites.add(paddle1, paddle2, ball)

Call the init function when the game needs to be restarted:

def main():
    run = True
    while run:
        # [...]

        if winner_text != "":
            draw_winner(winner_text)
            init()

        # [...]

    # main() <--- DELETE

Add a game_over state and use pygame.time.get_ticks() to get the number of milliseconds since pygame.init() was called. When the game ends, set the status game_over and calculate the time at which the game must be restarted. When the time comes, reset game_over and call init. Draw the scene depending on game_over:

def main():
    init()
  
    winner_text = ""
    restart_time = 0
    game_over = False

    clock = pygame.time.Clock()
    run = True
    while run:
        clock.tick(100)
        current_time = pygame.time.get_ticks()
       
        # [...]

        if not game_over:
            if paddle1.points >= 10:
                winner_text = "Blue Wins!"
                restart_time = current_time + 5000
                game_over = True

            if paddle2.points >= 10:
                winner_text = "Red Wins!"
                restart_time = current_time + 5000
                game_over = True

        if game_over:
            draw_winner(winner_text)
            if current_time > restart_time:
                init()
                game_over = False
            else: 
                continue

        # [...]

Use pygame.time.Clock to control the frames per second and thus the game speed.

The method tick() of a pygame.time.Clock object, delays the game in that way, that every iteration of the loop consumes the same period of time. See pygame.time.Clock.tick():

This method should be called once per frame.

That means that the loop:

clock = pygame.time.Clock()
run = True
while run:
   clock.tick(100)

runs 100 times per second.


Complete example:

import pygame

pygame.init()

WIDTH, HEIGHT = 750, 500
WINDOW = pygame.display.set_mode((WIDTH, HEIGHT))   

pygame.display.set_caption("PONG")  

WINNER_FONT = pygame.font.SysFont('comicsans', 100)

BLUE = (0, 0, 255)          
RED = (255, 0, 0)           
YELLOW = (255, 255, 0)     
WHITE = (255, 255, 255)     
BLACK = (0, 0, 0)           

class Paddle(pygame.sprite.Sprite):     
    def __init__(self):
        pygame.sprite.Sprite.__init__(self)    
        self.image = pygame.Surface([10, 75])
        self.image.fill(WHITE)
        self.rect = self.image.get_rect()   
        self.paddle_speed = 4          
        self.points = 0

class Ball(pygame.sprite.Sprite):
    def __init__(self):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.Surface([10, 10])
        self.image.fill(YELLOW)
        self.rect = self.image.get_rect()
        self.speed = 2
        self.dx = 1    
        self.dy = 1

def init():
    global paddle1, paddle2, ball, all_sprites

    paddle1 = Paddle()
    paddle1.image.fill(BLUE)
    paddle1.rect.x = 25      
    paddle1.rect.y = 225     

    paddle2 = Paddle()
    paddle2.image.fill(RED)
    paddle2.rect.x = 715
    paddle2.rect.y = 225

    ball = Ball()
    ball.rect.x = 375
    ball.rect.y = 250

    all_sprites = pygame.sprite.Group() 
    all_sprites.add(paddle1, paddle2, ball)

def redraw():
    WINDOW.fill(BLACK)
    font = pygame.font.SysFont("Consolas", 35)
    text = font.render("---[PONG]---", 1, YELLOW)
    textRect = text.get_rect()      
    textRect.center = (WIDTH//2, 25)  
    WINDOW.blit(text, textRect)     

    #Player 1 score
    p1_score = font.render(str([paddle1.points]), 1, WHITE)
    p1Rect = p1_score.get_rect()    
    p1Rect.center = (50, 50)        
    WINDOW.blit(p1_score, p1Rect)   
    
    #Player 2 score
    p2_score = font.render(str([paddle2.points]), 1, WHITE)
    p2Rect = p2_score.get_rect()
    p2Rect.center = (700, 50)      
    WINDOW.blit(p2_score, p2Rect)
    
    all_sprites.draw(WINDOW)    
    pygame.display.update()     


def draw_winner(text): 
    draw_text = WINNER_FONT.render(text, 1, WHITE)
    WINDOW.blit(draw_text, (WIDTH//2 - draw_text.get_width()/2, HEIGHT//2 - draw_text.get_height()/2))
    pygame.display.update()


def main():
    init()
  
    winner_text = ""
    restart_time = 0
    game_over = False

    clock = pygame.time.Clock()
    run = True
    while run:
        clock.tick(100)
        current_time = pygame.time.get_ticks()
       
        for event in pygame.event.get():    
            if event.type == pygame.QUIT:  
                run = False
                pygame.quit()

        if not game_over:
            if paddle1.points >= 10:
                winner_text = "Blue Wins!"
                restart_time = current_time + 5000
                game_over = True

            if paddle2.points >= 10:
                winner_text = "Red Wins!"
                restart_time = current_time + 5000
                game_over = True

        if game_over:
            draw_winner(winner_text)
            if current_time > restart_time:
                init()
                game_over = False
            else: 
                continue
            
        # Paddle movement        
        key = pygame.key.get_pressed()  
        if key[pygame.K_w]:
            paddle1.rect.y -= paddle1.paddle_speed  
        if key[pygame.K_s]:
            paddle1.rect.y += paddle1.paddle_speed  
        if key[pygame.K_UP]:
            paddle2.rect.y -= paddle2.paddle_speed
        if key[pygame.K_DOWN]:
            paddle2.rect.y += paddle2.paddle_speed

        # Ensures paddles never move off the screen
        if paddle1.rect.y < 0:
            paddle1.rect.y = 0
        
        if paddle1.rect.y > 425:
            paddle1.rect.y = 425

        if paddle2.rect.y < 0:
            paddle2.rect.y = 0
        
        if paddle2.rect.y > 425:
            paddle2.rect.y = 425

        # Ball movement
        ball.rect.x += ball.speed * ball.dx     
        ball.rect.y += ball.speed * ball.dy     

        # Setting up collision detection with the walls by changing ball's direction
        if ball.rect.y > 490:   
            ball.dy = -1        

        if ball.rect.x > 740:   
            ball.rect.x, ball.rect.y = 375, 250     
            paddle1.points += 1

        if ball.rect.y < 10:    
            ball.dy = 1         

        if ball.rect.x < 10:    
            ball.rect.x, ball.rect.y = 375, 250
            paddle2.points += 1

        # Setting up collision detection with the paddles
        if paddle1.rect.colliderect(ball.rect):
            ball.dx = 1     

        if paddle2.rect.colliderect(ball.rect):
            ball.dx = -1

        redraw()    

if __name__ == "__main__":
    main()
Rabbid76
  • 202,892
  • 27
  • 131
  • 174
  • Thanks for the help! I used a quick fix to avoid a recursive call: I embedded "paddle1.points = 0" and "paddle2.points = 0" in the "if winner_text !=" " " after the "draw_winner(winner_text)". – L393nd Apr 02 '21 at 20:22
1

First let's have a deep look at your main() function:

def main():
    run = True

    while run:
        #[...]some part of code here

        if winner_text != "":
            draw_winner(winner_text)
            break

        #[...]some part of code here
    main()

Now whenever someone wins either paddle1.points = 10 or paddle2.points = 10

if paddle1.points >= 10:
    winner_text = "Blue Wins!"

if paddle2.points >= 10:
    winner_text = "Red Wins!"

if winner_text != "":
    draw_winner(winner_text)
    break

Now as soon as someone wins this part of your code is executed:

if winner_text != "":
    draw_winner(winner_text)
    break

So, you are just displaying the text like "Blue wins" or "Red wins" and then you are breaking out of the while loop and again calling the main() function, but you're not resetting the paddle1.points and paddle2.points to 0 each.

So your main() function should look something like:

def main():
    run = True
    paddle1.points = 0
    paddle2.points = 0
    while run:
        #[...]while loop here

Here's the full working code:

import pygame,sys

pygame.init()

WIDTH, HEIGHT = 750, 500
WINDOW = pygame.display.set_mode((WIDTH, HEIGHT))   

pygame.display.set_caption("PONG")  

WINNER_FONT = pygame.font.SysFont('comicsans', 100)

BLUE = (0, 0, 255)          
RED = (255, 0, 0)           
YELLOW = (255, 255, 0)     
WHITE = (255, 255, 255)     
BLACK = (0, 0, 0)           

class Paddle(pygame.sprite.Sprite):     
    def __init__(self):
        pygame.sprite.Sprite.__init__(self)    
        self.image = pygame.Surface([10, 75])
        self.image.fill(WHITE)
        self.rect = self.image.get_rect()   
        self.paddle_speed = 4          
        self.points = 0

class Ball(pygame.sprite.Sprite):
    def __init__(self):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.Surface([10, 10])
        self.image.fill(YELLOW)
        self.rect = self.image.get_rect()
        self.speed = 2
        self.dx = 1    
        self.dy = 1

paddle1 = Paddle()
paddle1.image.fill(BLUE)
paddle1.rect.x = 25      
paddle1.rect.y = 225     

paddle2 = Paddle()
paddle2.image.fill(RED)
paddle2.rect.x = 715
paddle2.rect.y = 225

ball = Ball()
ball.rect.x = 375
ball.rect.y = 250

all_sprites = pygame.sprite.Group() 
all_sprites.add(paddle1, paddle2, ball)


def redraw():
    WINDOW.fill(BLACK)
    font = pygame.font.SysFont("Consolas", 35)
    text = font.render("---[PONG]---", 1, YELLOW)
    textRect = text.get_rect()      
    textRect.center = (WIDTH//2, 25)  
    WINDOW.blit(text, textRect)     

    #Player 1 score
    p1_score = font.render(str([paddle1.points]), 1, WHITE)
    p1Rect = p1_score.get_rect()    
    p1Rect.center = (50, 50)        
    WINDOW.blit(p1_score, p1Rect)   
    
    #Player 2 score
    p2_score = font.render(str([paddle2.points]), 1, WHITE)
    p2Rect = p2_score.get_rect()
    p2Rect.center = (700, 50)      
    WINDOW.blit(p2_score, p2Rect)
    
    all_sprites.draw(WINDOW)    
    pygame.display.update()     


def draw_winner(text):
    draw_text = WINNER_FONT.render(text, 1, WHITE)
    WINDOW.blit(draw_text, (WIDTH//2 - draw_text.get_width()/2, HEIGHT//2 - draw_text.get_height()/2))
    pygame.display.update()
    pygame.time.delay(5000)


def main():
    run = True
    paddle1.points = 0
    paddle2.points = 0
    while run:
        pygame.time.delay(10)   
       
        for event in pygame.event.get():    
            if event.type == pygame.QUIT:  
                pygame.quit()
                sys.exit()

        winner_text = ""
        if paddle1.points >= 1:
            winner_text = "Blue Wins!"

        if paddle2.points >= 1:
            winner_text = "Red Wins!"

        if winner_text != "":
            draw_winner(winner_text)
            break
            
        # Paddle movement        
        key = pygame.key.get_pressed()  
        if key[pygame.K_w]:
            paddle1.rect.y -= paddle1.paddle_speed  
        if key[pygame.K_s]:
            paddle1.rect.y += paddle1.paddle_speed  
        if key[pygame.K_UP]:
            paddle2.rect.y -= paddle2.paddle_speed
        if key[pygame.K_DOWN]:
            paddle2.rect.y += paddle2.paddle_speed

        # Ensures paddles never move off the screen
        if paddle1.rect.y < 0:
            paddle1.rect.y = 0
        
        if paddle1.rect.y > 425:
            paddle1.rect.y = 425

        if paddle2.rect.y < 0:
            paddle2.rect.y = 0
        
        if paddle2.rect.y > 425:
            paddle2.rect.y = 425

        # Ball movement
        ball.rect.x += ball.speed * ball.dx     
        ball.rect.y += ball.speed * ball.dy     

        # Setting up collision detection with the walls by changing ball's direction
        if ball.rect.y > 490:   
            ball.dy = -1        

        if ball.rect.x > 740:   
            ball.rect.x, ball.rect.y = 375, 250     
            paddle1.points += 1

        if ball.rect.y < 10:    
            ball.dy = 1         

        if ball.rect.x < 10:    
            ball.rect.x, ball.rect.y = 375, 250
            paddle2.points += 1

        # Setting up collision detection with the paddles
        if paddle1.rect.colliderect(ball.rect):
            ball.dx = 1     

        if paddle2.rect.colliderect(ball.rect):
            ball.dx = -1

        redraw()    
    main()

if __name__ == "__main__":
    main()

Although this works but I'll suggest you to go with what @Rabbid76 has said as invoking the application loop recursively is never considered a good practice

CopyrightC
  • 857
  • 2
  • 7
  • 15
  • Not only the points need to be reset, but the winner text etc. There is a whole entire set of technique that should be learned here, really. – Karl Knechtel Mar 28 '21 at 09:24
  • Thanks for the tip, can't believe I missed resetting the points, I must of been half asleep when I asked this stupid question. This is how I fixed it to avoid using a recursive call. I embedded "paddle1.points = 0" and "paddle2.points = 0" in the "if winner_text !=" " " after the "draw_winner(winner_text)". – L393nd Apr 02 '21 at 20:12