Unable to close pygame window when try again button clicked

Viewed 40

I am making a spaceship game where you control a spaceship and fire bullets to destroy enemy spaceships. When you click the try again button when you lose, you should be able to both replay and close the game. However, even though I was able to play multiply times, I wasn't able to close the window. I think this has something to do with the if statement that checks if the try again button is clicked. Or maybe it has something to do with something else. How can I fix it so I can close the window at all times?

This is my current code:

import pygame
from pygame.locals import *
from random import randint, choice
from tools import *

pygame.init()
pygame.font.init()

SCREEN_X = 800
SCREEN_Y = 500
CENTER_POS = (400, 225)

screen = pygame.display.set_mode((SCREEN_X, SCREEN_Y))


class Spaceship(pygame.sprite.Sprite):
    """A spaceship object. Used for the player."""

    def __init__(self, s, x, y):
        pygame.sprite.Sprite.__init__(self)

        self.screen = s
        self.x, self.y = x, y
        self.image = pygame.image.load("spaceship.png")
        self.image = pygame.transform.scale(self.image, (175, 175))
        self.rect = self.image.get_rect()
        self.rect.center = (self.x, self.y)

    def update(self):
        """Updates the spaceship rect"""
        self.rect.center = (self.x, self.y)


class Bullet(pygame.sprite.Sprite):
    """A bullet object. Appears when the player clicks."""

    def __init__(self, s, x, y):
        pygame.sprite.Sprite.__init__(self)

        self.screen = s
        self.x, self.y = x, y
        self.image = pygame.image.load("bullet.png")
        self.image = pygame.transform.scale(self.image, (100, 100))
        self.rect = self.image.get_rect()
        self.rect.center = (self.x, self.y)

    def update(self):
        """Let's the bullet move upward."""

        self.y -= 5
        self.rect.center = (self.x, self.y)
        if self.y < 0:
            self.kill()


class Enemy(pygame.sprite.Sprite):
    """An enemy object. The player's job is to destroy enemies."""

    def __init__(self, s, x, y, t):
        pygame.sprite.Sprite.__init__(self)

        self.type = t
        self.screen, self.x, self.y = s, x, y
        self.image = pygame.image.load(get_enemy_image()[self.type])

        # There is an if statement because the
        # N1 Galaxy Fighter and M7 Comet Glider need different sizes

        if self.type == "N1 Galaxy Fighter":
            self.image = pygame.transform.scale(self.image, (235, 215))
        elif self.type == "M7 Comet Glider":
            self.image = pygame.transform.scale(self.image, (155, 215))
        self.rect = self.image.get_rect()
        self.rect = self.image.get_rect()
        self.rect.center = (self.x, self.y)
        self.score_given = get_enemy_given_score()[self.type]

    def update(self):
        if self.y < 0:
            self.kill()
        self.y += 3
        self.rect.center = (self.x, self.y)


class GameOverBackground(pygame.sprite.Sprite):
    """The game over background object."""

    def __init__(self, s, x, y, size=(100, 100)):
        pygame.sprite.Sprite.__init__(self)

        self.screen, self.x, self.y = s, x, y
        self.size = size
        self.image = pygame.image.load("Game_Over.jpg")
        self.image = pygame.transform.scale(self.image, self.size)
        self.rect = self.image.get_rect()

    def blitme(self):
        """Blits the game over image on the screen"""
        self.screen.blit(self.image, self.rect)


class Coin(pygame.sprite.Sprite):
    """A coin object."""

    def __init__(self, pos=(0, 0), size=(100, 100)):
        pygame.sprite.Sprite.__init__(self)

        self.x, self.y = pos[0], pos[1]
        self.size = size
        self.image = pygame.image.load("coin.png")
        self.image = pygame.transform.scale(self.image, self.size)
        self.rect = self.image.get_rect()
        self.rect.center = (self.x, self.y)

    def update(self):
        """Updates the coin rect"""
        self.rect.center = (self.x, self.y)


class StartButton(pygame.sprite.Sprite):
    def __init__(self, s, x, y, size=None):
        pygame.sprite.Sprite.__init__(self)

        self.screen = s
        self.x = x
        self.y = y
        self.image = pygame.image.load("start_button.png")

        if size is not None:
            self.image = pygame.transform.scale(self.image, size)

        self.rect = self.image.get_rect()
        self.rect.center = (self.x, self.y)

    def blitme(self):
        self.screen.blit(self.image, self.rect)


class TryAgainButton(pygame.sprite.Sprite):
    def __init__(self, s, x, y, size=None):
        pygame.sprite.Sprite.__init__(self)

        self.screen = s
        self.x = x
        self.y = y
        self.image = pygame.image.load("try_again.png")

        if size is not None:
            self.image = pygame.transform.scale(self.image, size)

        self.rect = self.image.get_rect()
        self.rect.center = (self.x, self.y)

    def blitme(self):
        self.screen.blit(self.image, self.rect)


def main():
    bg = GameOverBackground(screen, 0, 0, size=(800, 500))

    spaceship = Spaceship(screen, 400, 400)

    start_button = StartButton(screen, CENTER_POS[0], CENTER_POS[1], size=(300, 195))
    button_rect = start_button.image.get_rect(topleft=(start_button.x, start_button.y))
    game_started = False

    try_again_button = TryAgainButton(screen, CENTER_POS[0], CENTER_POS[1]+215, size=(300, 195))
    button_rect_2 = try_again_button.image.get_rect(topleft=(try_again_button.x, try_again_button.y))
    try_again = False

    bullets = pygame.sprite.Group()
    enemies = pygame.sprite.Group()
    coins = pygame.sprite.Group()

    clock = pygame.time.Clock()

    enemy_interval = 2000
    enemy_event = pygame.USEREVENT + 1
    pygame.time.set_timer(enemy_event, enemy_interval)

    coin_interval = 3500
    coin_event = pygame.USEREVENT + 1
    pygame.time.set_timer(coin_event, coin_interval)

    score = 0
    lives = 3

    with open("high_score.txt", "r") as file:
        highscore = file.read()

    font = pygame.font.SysFont("Arial", 30)
    score_text_surface = font.render("Score: {:,}".format(score), True, (0, 0, 0))
    lives_text_surface = font.render("HP: %s" % lives, True, (0, 0, 0))
    high_score_text_surface = font.render("High Score: %s" % highscore, True, (0, 0, 0))

    spaceship_collided = False

    running = True
    while running:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False

            if event.type == MOUSEBUTTONDOWN:
                if not game_started:
                    if button_rect.collidepoint(event.pos):
                        game_started = True

                if game_started is True and button_rect_2.collidepoint(event.pos):
                    try_again = True

            if game_started and event.type == MOUSEBUTTONDOWN:
                bullet = Bullet(screen, spaceship.x, spaceship.y - 20)
                bullets.add(bullet)

            if game_started and event.type == enemy_event and not lives <= 0:
                enemy = Enemy(screen, randint(-100, 725), 0, choice(["N1 Galaxy Fighter", "M7 Comet Glider"]))
                enemies.add(enemy)

            if game_started and event.type == coin_event and not lives <= 0:
                if len(coins) < 100:
                    coins.add(Coin((randint(-125, 750), randint(-200, 400))))

        screen.fill((255, 255, 255))  # DO NOT DRAW ANYTHING IN FRONT OF THIS LINE, I'M WARNING YOU

        if not game_started:
            start_button.blitme()

        if try_again:
            main()

        if game_started:
            bullets.update()

            key = pygame.key.get_pressed()
            amount = 5

            if key[pygame.K_a]:
                spaceship.x -= amount
            elif key[pygame.K_d]:
                spaceship.x += amount
            elif key[pygame.K_w]:
                spaceship.y -= amount
            elif key[pygame.K_s]:
                spaceship.y += amount

            spaceship.update()

            if not lives <= 0:
                screen.blit(spaceship.image, spaceship.rect)

            if not lives <= 0:
                bullets.draw(screen)
                enemies.draw(screen)

                coins.update()
                coins.draw(screen)

                for i in enemies:
                    i.update()
                    if pygame.sprite.spritecollide(i, bullets, True):
                        score += i.score_given
                        i.kill()

            if spaceship_collided and lives <= 0:
                bg.blitme()
                if score > int(highscore):
                    with open("high_score.txt", "w") as file:
                        file.write(str(score))

            if score >= 99999:
                score = 99999

            if not lives <= 0:
                score_text_surface = font.render("Score: {:,}".format(score), True, (0, 0, 0))
                screen.blit(score_text_surface, (590, 0))

                lives_text_surface = font.render("HP: %s" % lives, True, (0, 0, 0))
                screen.blit(lives_text_surface, (260, 0))

                high_score_text_surface = font.render("High Score: %s" % highscore, True, (0, 0, 0))
                screen.blit(high_score_text_surface, (360, 0))

            if pygame.sprite.spritecollide(spaceship, enemies, dokill=True):
                lives -= 1
                spaceship_collided = True

            if pygame.sprite.spritecollide(spaceship, coins, dokill=True):
                score += 10

        if lives <= 0:
            try_again_button.blitme()

        pygame.display.update()
        clock.tick(60)


if __name__ == "__main__":
    main()
1 Answers

You can't do it like that. Never run the main application loop recursively. The issue is

if try_again:
   main()

What you are actually doing is starting a new game within the game in progress. If you want to close the game, you will need to close all games that you have started before. Hence, you have to press close several times.

Instead of calling main recursively, you either have to reset all game states or call main in a loop.
In your particular situation, the later solution is easier to implement. Remove the recursive call but set running = False, return try_again from main and call main while it returns True:

def main():
    # [...]

    try_again = False
    running = True
    while running:
        # [...]

        if try_again:
            # main() <---- DELETE
            running = False

        # [...]

    return try_again
if __name__ == "__main__":

    keep_running = True
    while keep_running: 
        keep_running = main()
Related