Text not showing after player car wins game

Question:
After The player Car completes all levels of the game, the text ‘You win’ is not being displayed on the screen. I also keep getting an exception that says pygame.error: Library not initialized every time i win the game by completing all the levels. This exception occurs only when i close the pygame window.

https://replit.com/@jonathanessombe/Car-Racing-game#main.py

#main.py
import pygame
import time
import math
# import the module from our Race Car game directory
from utils import scale_image, blit_rotation_center, blit_text_center

# initialize pygame font module
pygame.font.init()

# poi = point of intersection

# Initializing images and scale the images using scale_image()
grass = scale_image(pygame.image.load('grass.jpg'), 2.5)
track = scale_image(pygame.image.load('track.png'), 0.85)

# Initialize the calling mask
track_border = scale_image(pygame.image.load('track-border.png'), 0.85)
track_border_mask = pygame.mask.from_surface(track_border)

finish = pygame.image.load('finish.png')
finish_mask = pygame.mask.from_surface(finish)
finish_position = (130, 250)
# initialize player and computer car images
red_car = scale_image(pygame.image.load('red-car.png'), 0.55)
green_car = scale_image(pygame.image.load('green-car.png'), 0.55)
# width and height from the track image we initialized
width, height = track.get_width(), track.get_height()

# Initialize and interactive window
window = pygame.display.set_mode((width, height))
# Title of the window created
pygame.display.set_caption('Racing Game!')
# initialize font object
main_font = pygame.font.SysFont('comics-ans', 30)
# frames per seconds
fps = 60
# Computer cars path on the window
computers_path = [(166, 104), (105, 67), (51, 129), (61, 455), (316, 691), (382, 636),
                  (396, 484), (483, 452), (568, 520), (581, 673), (693, 663), (693, 367),
                  (607, 336), (388, 321), (408, 238), (658, 241), (698, 102), (560, 71),
                  (288, 71), (260, 356), (170, 367), (178, 260)]


class GameInfo:
    """
    A class to increase levels, resets levels and provide game information.

    Attributes:
        levels (int): maximum levels in the game
        level (int): current level in the game
        started (bool): state of the game
        level_start_time (int): game timer

    Methods:
        __init__(self, level=1):
            initialize the GameInfo class with an instance and a given level.


        next_level(self):
            Increment the games level by 1.


        reset_level(self):
            Reset the games level back to 1 and set the state as not started.


        game_finished(self) -> bool:
            Checks if the game is completed.This occurs when the game exceeds level 10.
            Returns:
                bool: False if game is not completed(not reached level 10), else True.


            start_level(self):
                Starts the current level of the game and a timer in each level.


            get_level_time(self) -> int:
                Calculates the time elapsed since the level started.
                Returns:
                    int: Time since the level started. returns 0 if level not started.
        """
    # maximum levels in the game
    levels = 1

    def __init__(self, level=1):
        """initialize the GameInfo class with an instance and a given level."""
        # current game level
        self.level = level
        # State of the game(not started)
        self.started = False
        # initialize games timer
        self.level_start_time = 0

    def next_level(self):
        """Increment the games level by 1 and set game state as not started."""
        # increase level by 1
        self.level += 1
        self.started = False

    def reset_level(self):
        """Reset the games level back to 1 and set the state as not started."""
        self.level = 1
        self.started = False
        self.level_start_time = 0

    def game_finished(self):
        """
        Checks if the game is completed.This occurs when the game exceeds level 10.

        Returns:
            bool: False if game is not completed(not reached level 10), else True.
        """
        return self.level > self.levels

    def start_level(self):
        """Starts the current level of the game and a timer in each level."""
        # Start the game
        self.started = True
        # Start the games timer
        self.level_start_time = time.time()

    def get_level_time(self):
        """
        Calculates the time elapsed since the level started.

        Returns:
            int: Time since the level started. returns 0 if level not started.

        This method calculates the time difference between
        when the game started to when it ended. If the timer has not started,
        the function will return a time of 0.
        """
        if not self.started:
            return 0
        return round(time.time() - self.level_start_time)


class AbstractCar:
    """
    This parent class contains attributes that will be used by both cars.

    Attributes:
        image (Pygame.surface): image of player or computer car.
        max_velocity (int): maximum velocity of car.
        velocity (int): current velocity of car.
        delta_angle (int): changing speed of the angle.
        angle (int): direction car is facing.
        x (int): x coordinate of car.
        y (int): y coordinate of car.
        acceleration (int): change in velocity.

    Methods:
        __init__(self, level=1):
            initialize this class with an instance, max_velocity and delta_angle.


        rotate(self, left=False, right=False):
            Change the angle of the car based on user input.


        draw_car(self, win):
            Draw both cars on the window.


        move_forward(self):
            Change velocity of the car and move in the forward direction.


        move_backwards(self):
            Change velocity of the car and move in the backward direction.


        move(self):
            Allow the car to move in correct direction based in user input.


        collide(self, mask, x=0, y=0):
            Check for car collision with the track border.
            Returns:
                tuple: (x,y) position of where car collision occurred, else None.

        reset_cars(self):
            Reset position of all cars in the window.
        """

    # initialize class variable red car
    car_image = red_car
    # position class variable
    start_position = (0, 0)

    def __init__(self, max_velocity, delta_angle):
        """initialize this class with an instance, max_velocity and delta_angle."""
        # image of the car
        self.img = self.car_image
        self.max_velocity = max_velocity
        # initial velocity
        self.velocity = 0
        # change in angle of the player car when changing directions
        self.delta_angle = delta_angle
        # initial angle
        self.angle = 0
        # initial x and y coordinates of the car
        self.x, self.y = self.start_position
        # increase velocity by this value
        self.acceleration = 0.1

    def rotate(self, left=False, right=False):
        """
        Change the angle of the car based on user input.

        Parameters:
            left (bool, optional): rotate car left if True.
            right (bool, optional): rotate car right if True.

        If left is True, increase angle which will cause car to move counterclockwise.
        If Right is True, decrease angle which will cause car to move clockwise.
        """
        if left:
            self.angle += self.delta_angle
        if right:
            self.angle -= self.delta_angle

    def draw_car(self, win):
        """
        Draw both cars on the window properly while handling collisions.

        Parameters:
            win (pygame.Surface): The game window display.
        """
        blit_rotation_center(win, self.img, (self.x, self.y), self.angle)

    def move_forward(self):
        """Increase velocity of the car and move in the forward direction."""
        # Maximum velocity after car has accelerated at rest
        self.velocity = min(self.velocity + self.acceleration, self.max_velocity)
        self.move()

    def move_backwards(self):
        """Change velocity of the car and move in the backward direction."""
        # Maximum velocity after car has accelerated at rest
        self.velocity = max(self.velocity - self.acceleration, -self.max_velocity / 2)
        self.move()

    def move(self):
        """
        Allow the car to move in correct direction based in user input.

        This function is responsible for making car rotate in the proper direction.
        Also ensures car moves in the correct direction based on it's angle and position.
        """
        # initial angle in radians
        rads = math.radians(self.angle)
        # change in velocity in the vertical direction
        vertical = math.cos(rads) * self.velocity
        # change in velocity in the horizontal direction
        horizontal = math.sin(rads) * self.velocity

        # Changes coordinates of the car which ensures the car is moving in the correct direction
        self.y -= vertical
        self.x -= horizontal

    def collide(self, mask, x=0, y=0):
        """
        Check for car collision with the track border.

            Parameters:
                mask: object being collided on.
                x: x coordinate of the calling mask(track_border).
                y: y coordinate of the calling mask(track_border).

            Returns:
                tuple: (x,y) position of where car collision occurred, else None.
        """
        # initialize car mask
        car_mask = pygame.mask.from_surface(self.img)
        # Change in x and y between calling mask and mask being called
        offset = (int(self.x - x), int(self.y - y))
        # check if both masks are overlapping each other
        point_of_intersection = mask.overlap(car_mask, offset)
        # return tuple containing the position(∆x and ∆y) of the collision or None if no collision
        return point_of_intersection

    def reset_cars(self):
        """Reset position of all cars in the window."""
        self.x, self.y = self.start_position
        self.angle = 0
        self.velocity = 0


class PlayerCar(AbstractCar):
    """
    A class that contains all functionalities of the player car.

    Attributes:
        car_image (Pygame.surface): car image for player car
        start_position (int): current position of player car


    Methods:
        reduce_speed(self):
            Slow down the player car when not moving forward.


        bounce(self):
            Bounce player car backward after colliding with track border.
        """
    # initialize class attribute for the player car
    car_image = red_car
    # starting position of player car
    start_position = (180, 200)

    def reduce_speed(self):
        """Slow down the player car when not moving forward."""
        self.velocity = max(self.velocity - self.acceleration / 2, 0)
        # allows car to turn and move during deceleration
        self.move()

    def bounce(self):
        """Bounce player car backward after colliding with track border."""
        # change the direction of the player car velocity
        self.velocity = -self.velocity
        self.move()


class ComputerCar(AbstractCar):
    """
    A class that contains all functionalities of the computer car.

    Attributes:
        car_image (Pygame.surface): car image for computer car.
        start_position (int): starting position of computer car.
        path (list): contains targets computer car follows.
        current_point (int): computer car's current target.
        velocity (int): maximum velocity of the computer car.

    Methods:
        __init__(self, max_velocity, delta_angle, path=[]):
            initialize computer car object

        comp_draw(self, win):
            draw computer car on the window.


        calculate_angle(self):
            Ensure computer car is moving to the intended target.


        update_path_point(self):
            Move computer car to the next target.


        comp_move(self) -> None:
            Allow computer car to move on the track.
            Returns:
                None: if computer car reaches the finish line.


        comp_next_level(self, level):
            Increases the velocity of computer car after each level until level 10.
        """
    car_image = green_car
    start_position = (150, 200)

    def __init__(self, max_velocity, delta_angle, path=[]):
        # inherent all attributes in AbstractCar class
        super().__init__(max_velocity, delta_angle)
        self.path = path  # list containing the coordinates the computer car will take
        self.current_point = 0  # The current position of the car in its path
        self.velocity = max_velocity

    # def draw_points(self, win):
    #     red = (255, 0, 0)
    #     for point in self.path:
    #         pygame.draw.circle(win, red, point, 5)

    def comp_draw(self, win):
        """
        draw computer car on the window.

        Parameters:
            win: interactive window object.
        """
        super().draw_car(win)
        # self.draw_points(win)

    def calculate_angle(self):
        """Ensure computer car is moving to the intended target."""
        target_x, target_y = self.path[self.current_point]
        x_diff = target_x - self.x
        y_diff = target_y - self.y

        if y_diff == 0:
            desired_radian_angle = math.pi / 2
        else:
            desired_radian_angle = math.atan(x_diff / y_diff)

        if target_y > self.y:
            desired_radian_angle += math.pi

        difference_in_angle = self.angle - math.degrees(desired_radian_angle)
        if difference_in_angle >= 180:
            difference_in_angle -= 360

        if difference_in_angle > 0:
            self.angle -= min(self.delta_angle, abs(difference_in_angle))
        else:
            self.angle += min(self.delta_angle, abs(difference_in_angle))

    def update_path_point(self):
        """Move computer car to the next target."""
        # Check if the car collides with the target
        target = self.path[self.current_point]
        # create a rectangle object for the car
        rect = pygame.Rect(self.x, self.y, self.img.get_width(), self.img.get_height())
        # Check if the rectangle around the car collides with a target
        if rect.collidepoint(*target):
            # Allow the computer car to move to the next target/ coordinate in the path variable
            self.current_point += 1

    def comp_move(self):
        """
        Allow computer car to move on the track.

        Returns:
            None: if computer car reaches the finish line.
        """
        if self.current_point >= len(self.path):
            return

        self.calculate_angle()
        self.update_path_point()
        super().move()

    def comp_next_level(self, level):
        """
        Increases the velocity of computer car after each level until level 10.

        Parameters:
            level: current level in the game
        """
        # velocity increment after each level
        delta_velocity_of_car = 0.2
        self.reset_cars()
        self.velocity = self.max_velocity + (level - 1) * delta_velocity_of_car
        self.current_point = 0


def draw(win, images, user_car, comp_car, current_game):
    """
    This function draws images and updates them on the pygame window.

    Parameters:
        win: interactive window object.
        images: image displayed in interactive window
        user_car: instance of PlayerCar class.
        comp_car: instance of ComputerCar class.
        current_game: instance of GameInfo class.
    """
    for img, pos in images:
        win.blit(img, pos)

    # initialize the games level
    level_text = main_font.render(
        f'Level {current_game.level}', 1, (255, 255, 255))
    # draw the games level on the window
    win.blit(level_text, (10, height - level_text.get_height() - 70))
    # initialize the timer
    time_text = main_font.render(
        f'Time: {current_game.get_level_time()}s', 1, (255, 255, 255))
    # draw the timer on the window
    win.blit(time_text, (10, height - time_text.get_height() - 40))
    # initialize the velocity
    velocity_text = main_font.render(f'Vel: {user_car.velocity:.1f}px/s', 1, (255, 255, 255))
    # draw the timer on the window
    win.blit(velocity_text, (10, height - velocity_text.get_height() - 10))

    # draw the cars into the window
    user_car.draw_car(win)
    comp_car.comp_draw(win)
    # show all images you draw on the screen
    pygame.display.update()


def move_player(users_car):
    """
    Contains all the keys accessible to the player car.

    Parameters:
        users_car: instance of PlayerCar class.
    """
    # List containing state off all keys where all elements are True or False
    keys = pygame.key.get_pressed()
    # State of the car. It is either idle or moving
    moved = False

    # Check if 'a' key is pressed
    if keys[pygame.K_a]:
        # rotate the car left
        users_car.rotate(left=True)
    # Check if 'd' key is pressed
    if keys[pygame.K_d]:
        # rotate the car right
        users_car.rotate(right=True)
    # check if 'w' key is pressed
    if keys[pygame.K_w]:
        # car is moving
        moved = True
        # move car forward
        users_car.move_forward()
    # check if 's' key is pressed
    if keys[pygame.K_s]:
        # car is moving
        moved = True
        # move car backwards
        users_car.move_backwards()
    # The car is not moving
    if not moved:
        users_car.reduce_speed()


def handle_collision(user_car, comp_car, current_game):
    """
    Handles collisions by both cars with finish line and track the border.

    Parameters:
        user_car: instance of PlayerCar class.
        comp_car: instance of ComputerCar class.
        current_game: instance of GameInfo class.

    This function handles all the collisions that occur in the game.
    This includes collisions when either car collides with the finish line and
    when player car collides with the track border.
    """

    # check if player car is touching track_border_mask
    if user_car.collide(track_border_mask) is not None:
        user_car.bounce()

    comp_finish_poi_collide = comp_car.collide(
        finish_mask, *finish_position)
    # Check if the computer car collides with the finish line
    if comp_finish_poi_collide is not None:
        # draw the 'You lost' text on the screen
        blit_text_center(window, main_font, 'You lost!')
        pygame.display.update()
        # Slow the window down
        pygame.time.wait(5000)
        # Reset the game
        current_game.reset_level()
        # Reset the position of the cars if true
        user_car.reset_cars()
        comp_car.comp_next_level(current_game.level)


    user_finish_poi_collide = user_car.collide(finish_mask, *finish_position)
    # Check if the player collides with the finish line
    if user_finish_poi_collide is not None:
        # Check if player car collides with finish line when y coordinate is 0
        if user_finish_poi_collide[1] == 0:
            user_car.bounce()
        else:
            # increase the level of the computer car
            game_info.next_level()
            # reset the position of the cars
            user_car.reset_cars()
            comp_car.comp_next_level(current_game.level)


# keep the program running unless it is closed
run = True
# initialize clock object
clock = pygame.time.Clock()
# images and their position in the window(order matters)
images_list = [(grass, (0, 0)), (track, (0, 0)),
               (finish, finish_position), (track_border, (0, 0))]
# initialize player and computer car object
player_car = PlayerCar(4, 4)
computer_car = ComputerCar(1, 4, computers_path)
# initialize GameInfo() object
game_info = GameInfo()

# keep window running unless closed
while run:

    # Regulate from rate to 60 frames per seconds for all users
    clock.tick(fps)
    # draw images on screen
    draw(window, images_list, player_car, computer_car, game_info)

    # show game text when the level has not started
    while not game_info.started:
        # Draw new level game text on screen
        blit_text_center(window, main_font, f'Press any key to start level {game_info.level}!')
        pygame.display.update()
        # check to see if the ser either closes the window or presses a key
        for event in pygame.event.get():
            # close the window if the user exits the window
            if event.type == pygame.QUIT:
                pygame.quit()

            # if user presses any key down, start the level
            if event.type == pygame.KEYDOWN:
                game_info.start_level()

    # Draw the computer and its path coordinates on the window
    computer_car.comp_draw(window)

    # close window if the user exits the window
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False

        # if event.type == pygame.MOUSEBUTTONDOWN:
        #     mouse_pos = pygame.mouse.get_pos()
        #     computer_car.path.append(mouse_pos)

    # Move the player and computer car
    move_player(player_car)
    computer_car.comp_move()

    handle_collision(player_car, computer_car, game_info)

    # if player car beats computer car
    if game_info.game_finished():
        blit_text_center(window, main_font, 'You win')
        pygame.time.wait(5000)
        game_info.reset_level()
        player_car.reset_cars()
        computer_car.comp_next_level(game_info.level)

# print(computer_car.path)
pygame.quit()


#utils.py

# Utility functions for main.py file

import pygame

pygame.font.init()


def scale_image(img, factor):
    """
    Change the size of the images initialized from main.py with a scale factor.

    Parameters:
        img (pygame.Surface): image on window.
        factor (int): constant used to change image size.

    Returns:
        pygame.Surface: same image with a new size
    """
    # tuple containing values of length and width rounded to nearest integer
    size = round(img.get_width() * factor), round(img.get_height() * factor)
    # update the size of the image
    return pygame.transform.scale(img, size)


def blit_rotation_center(win, image, top_left, angle):
    """
    Draw both cars on the window properly while handling collisions.

    Args:
         win (pygame.Surface): The game window display
         image (pygame.Surface): computer and/or player car image
         top_left (tuple): coordinates of the car image
         angle (int): current angle of the car

    This function ensures the car image rotates from the center and not the top left corner.
    This allows a realistic representation of the car when turning in the game.
    """
    # rotates image around top left hand corner(current origin) - Not what we want
    rotated_image = pygame.transform.rotate(image, angle)
    # rotate image around the center of the imaginary rectangle it's in - What we want
    new_rectangle = rotated_image.get_rect(
        center=image.get_rect(topleft=top_left).center)
    # draw rotated image in screen
    win.blit(rotated_image, new_rectangle.topleft)


def blit_text_center(win, font, text):
    """
    This functions draws the text that will be displayed on the screen when the level starts.

    Args:
        win: interactive window object.
        font (pygame.font.Font): font object displayed on screen.
        text (str): desired string to be displayed.
    """
    color = (200, 200, 200)
    # initialize
    render = font.render(text, 1, color)
    win.blit(render, (win.get_width() / 2 - render.get_width() / 2,
                      win.get_height() / 2 - render.get_height() / 2 - 25))



First, the reason that the text is not displaying is because you don’t update the screen.
When all levels are completed, the ‘You win’ text is correctly blitted to the window. However, changes to the window are not visible until after a call to pygame.display.update() or pygame.display.flip(). The application waits for 5 seconds without ever updating the screen to show the text.

Second, the reason why you get the pygame.error is because you call pygame.quit() too early in your code when the user closes the pygame window. When you call pygame.quit(), you can no longer use any pygame functions. So, only call pygame.quit() once you are sure that no more pygame code is being run, or don’t call it at all. (I’d recommend creating a function for the game loop, so quitting could just be returning from the function with return.)

1 Like

I tried your suggestion in my code. Every time i press any key at the start however, it closes the window.

import pygame
import time
import math
# import the module from our Race Car game directory
from utils import scale_image, blit_rotation_center, blit_text_center

# initialize pygame font module
pygame.font.init()

# poi = point of intersection

# Initializing images and scale the images using scale_image()
grass = scale_image(pygame.image.load('grass.jpg'), 2.5)
track = scale_image(pygame.image.load('track.png'), 0.85)

# Initialize the calling mask
track_border = scale_image(pygame.image.load('track-border.png'), 0.85)
track_border_mask = pygame.mask.from_surface(track_border)

finish = pygame.image.load('finish.png')
finish_mask = pygame.mask.from_surface(finish)
finish_position = (130, 250)
# initialize player and computer car images
red_car = scale_image(pygame.image.load('red-car.png'), 0.55)
green_car = scale_image(pygame.image.load('green-car.png'), 0.55)
# width and height from the track image we initialized
width, height = track.get_width(), track.get_height()

# Initialize and interactive window
window = pygame.display.set_mode((width, height))
# Title of the window created
pygame.display.set_caption('Racing Game!')
# initialize font object
main_font = pygame.font.SysFont('comics-ans', 30)
# frames per seconds
fps = 60
# Computer cars path on the window
computers_path = [(166, 104), (105, 67), (51, 129), (61, 455), (316, 691), (382, 636),
                  (396, 484), (483, 452), (568, 520), (581, 673), (693, 663), (693, 367),
                  (607, 336), (388, 321), (408, 238), (658, 241), (698, 102), (560, 71),
                  (288, 71), (260, 356), (170, 367), (178, 260)]


class GameInfo:
    """
    A class to increase levels, resets levels and provide game information.

    Attributes:
        levels (int): maximum levels in the game
        level (int): current level in the game
        started (bool): state of the game
        level_start_time (int): game timer

    Methods:
        __init__(self, level=1):
            initialize the GameInfo class with an instance and a given level.


        next_level(self):
            Increment the games level by 1.


        reset_level(self):
            Reset the games level back to 1 and set the state as not started.


        game_finished(self) -> bool:
            Checks if the game is completed.This occurs when the game exceeds level 10.
            Returns:
                bool: False if game is not completed(not reached level 10), else True.


            start_level(self):
                Starts the current level of the game and a timer in each level.


            get_level_time(self) -> int:
                Calculates the time elapsed since the level started.
                Returns:
                    int: Time since the level started. returns 0 if level not started.
        """
    # maximum levels in the game
    levels = 1

    def __init__(self, level=1):
        """initialize the GameInfo class with an instance and a given level."""
        # current game level
        self.level = level
        # State of the game(not started)
        self.started = False
        # initialize games timer
        self.level_start_time = 0

    def next_level(self):
        """Increment the games level by 1 and set game state as not started."""
        # increase level by 1
        self.level += 1
        self.started = False

    def reset_level(self):
        """Reset the games level back to 1 and set the state as not started."""
        self.level = 1
        self.started = False
        self.level_start_time = 0

    def game_finished(self):
        """
        Checks if the game is completed.This occurs when the game exceeds level 10.

        Returns:
            bool: False if game is not completed(not reached level 10), else True.
        """
        return self.level > self.levels

    def start_level(self):
        """Starts the current level of the game and a timer in each level."""
        # Start the game
        self.started = True
        # Start the games timer
        self.level_start_time = time.time()

    def get_level_time(self):
        """
        Calculates the time elapsed since the level started.

        Returns:
            int: Time since the level started. returns 0 if level not started.

        This method calculates the time difference between
        when the game started to when it ended. If the timer has not started,
        the function will return a time of 0.
        """
        if not self.started:
            return 0
        return round(time.time() - self.level_start_time)


class AbstractCar:
    """
    This parent class contains attributes that will be used by both cars.

    Attributes:
        image (Pygame.surface): image of player or computer car.
        max_velocity (int): maximum velocity of car.
        velocity (int): current velocity of car.
        delta_angle (int): changing speed of the angle.
        angle (int): direction car is facing.
        x (int): x coordinate of car.
        y (int): y coordinate of car.
        acceleration (int): change in velocity.

    Methods:
        __init__(self, level=1):
            initialize this class with an instance, max_velocity and delta_angle.


        rotate(self, left=False, right=False):
            Change the angle of the car based on user input.


        draw_car(self, win):
            Draw both cars on the window.


        move_forward(self):
            Change velocity of the car and move in the forward direction.


        move_backwards(self):
            Change velocity of the car and move in the backward direction.


        move(self):
            Allow the car to move in correct direction based in user input.


        collide(self, mask, x=0, y=0):
            Check for car collision with the track border.
            Returns:
                tuple: (x,y) position of where car collision occurred, else None.

        reset_cars(self):
            Reset position of all cars in the window.
        """

    # initialize class variable red car
    car_image = red_car
    # position class variable
    start_position = (0, 0)

    def __init__(self, max_velocity, delta_angle):
        """initialize this class with an instance, max_velocity and delta_angle."""
        # image of the car
        self.img = self.car_image
        self.max_velocity = max_velocity
        # initial velocity
        self.velocity = 0
        # change in angle of the player car when changing directions
        self.delta_angle = delta_angle
        # initial angle
        self.angle = 0
        # initial x and y coordinates of the car
        self.x, self.y = self.start_position
        # increase velocity by this value
        self.acceleration = 0.1

    def rotate(self, left=False, right=False):
        """
        Change the angle of the car based on user input.

        Parameters:
            left (bool, optional): rotate car left if True.
            right (bool, optional): rotate car right if True.

        If left is True, increase angle which will cause car to move counterclockwise.
        If Right is True, decrease angle which will cause car to move clockwise.
        """
        if left:
            self.angle += self.delta_angle
        if right:
            self.angle -= self.delta_angle

    def draw_car(self, win):
        """
        Draw both cars on the window properly while handling collisions.

        Parameters:
            win (pygame.Surface): The game window display.
        """
        blit_rotation_center(win, self.img, (self.x, self.y), self.angle)

    def move_forward(self):
        """Increase velocity of the car and move in the forward direction."""
        # Maximum velocity after car has accelerated at rest
        self.velocity = min(self.velocity + self.acceleration, self.max_velocity)
        self.move()

    def move_backwards(self):
        """Change velocity of the car and move in the backward direction."""
        # Maximum velocity after car has accelerated at rest
        self.velocity = max(self.velocity - self.acceleration, -self.max_velocity / 2)
        self.move()

    def move(self):
        """
        Allow the car to move in correct direction based in user input.

        This function is responsible for making car rotate in the proper direction.
        Also ensures car moves in the correct direction based on it's angle and position.
        """
        # initial angle in radians
        rads = math.radians(self.angle)
        # change in velocity in the vertical direction
        vertical = math.cos(rads) * self.velocity
        # change in velocity in the horizontal direction
        horizontal = math.sin(rads) * self.velocity

        # Changes coordinates of the car which ensures the car is moving in the correct direction
        self.y -= vertical
        self.x -= horizontal

    def collide(self, mask, x=0, y=0):
        """
        Check for car collision with the track border.

            Parameters:
                mask: object being collided on.
                x: x coordinate of the calling mask(track_border).
                y: y coordinate of the calling mask(track_border).

            Returns:
                tuple: (x,y) position of where car collision occurred, else None.
        """
        # initialize car mask
        car_mask = pygame.mask.from_surface(self.img)
        # Change in x and y between calling mask and mask being called
        offset = (int(self.x - x), int(self.y - y))
        # check if both masks are overlapping each other
        point_of_intersection = mask.overlap(car_mask, offset)
        # return tuple containing the position(∆x and ∆y) of the collision or None if no collision
        return point_of_intersection

    def reset_cars(self):
        """Reset position of all cars in the window."""
        self.x, self.y = self.start_position
        self.angle = 0
        self.velocity = 0


class PlayerCar(AbstractCar):
    """
    A class that contains all functionalities of the player car.

    Attributes:
        car_image (Pygame.surface): car image for player car
        start_position (int): current position of player car


    Methods:
        reduce_speed(self):
            Slow down the player car when not moving forward.


        bounce(self):
            Bounce player car backward after colliding with track border.
        """
    # initialize class attribute for the player car
    car_image = red_car
    # starting position of player car
    start_position = (180, 200)

    def reduce_speed(self):
        """Slow down the player car when not moving forward."""
        self.velocity = max(self.velocity - self.acceleration / 2, 0)
        # allows car to turn and move during deceleration
        self.move()

    def bounce(self):
        """Bounce player car backward after colliding with track border."""
        # change the direction of the player car velocity
        self.velocity = -self.velocity
        self.move()


class ComputerCar(AbstractCar):
    """
    A class that contains all functionalities of the computer car.

    Attributes:
        car_image (Pygame.surface): car image for computer car.
        start_position (int): starting position of computer car.
        path (list): contains targets computer car follows.
        current_point (int): computer car's current target.
        velocity (int): maximum velocity of the computer car.

    Methods:
        __init__(self, max_velocity, delta_angle, path=[]):
            initialize computer car object

        comp_draw(self, win):
            draw computer car on the window.


        calculate_angle(self):
            Ensure computer car is moving to the intended target.


        update_path_point(self):
            Move computer car to the next target.


        comp_move(self) -> None:
            Allow computer car to move on the track.
            Returns:
                None: if computer car reaches the finish line.


        comp_next_level(self, level):
            Increases the velocity of computer car after each level until level 10.
        """
    car_image = green_car
    start_position = (150, 200)

    def __init__(self, max_velocity, delta_angle, path=[]):
        # inherent all attributes in AbstractCar class
        super().__init__(max_velocity, delta_angle)
        self.path = path  # list containing the coordinates the computer car will take
        self.current_point = 0  # The current position of the car in its path
        self.velocity = max_velocity

    # def draw_points(self, win):
    #     red = (255, 0, 0)
    #     for point in self.path:
    #         pygame.draw.circle(win, red, point, 5)

    def comp_draw(self, win):
        """
        draw computer car on the window.

        Parameters:
            win: interactive window object.
        """
        super().draw_car(win)
        # self.draw_points(win)

    def calculate_angle(self):
        """Ensure computer car is moving to the intended target."""
        target_x, target_y = self.path[self.current_point]
        x_diff = target_x - self.x
        y_diff = target_y - self.y

        if y_diff == 0:
            desired_radian_angle = math.pi / 2
        else:
            desired_radian_angle = math.atan(x_diff / y_diff)

        if target_y > self.y:
            desired_radian_angle += math.pi

        difference_in_angle = self.angle - math.degrees(desired_radian_angle)
        if difference_in_angle >= 180:
            difference_in_angle -= 360

        if difference_in_angle > 0:
            self.angle -= min(self.delta_angle, abs(difference_in_angle))
        else:
            self.angle += min(self.delta_angle, abs(difference_in_angle))

    def update_path_point(self):
        """Move computer car to the next target."""
        # Check if the car collides with the target
        target = self.path[self.current_point]
        # create a rectangle object for the car
        rect = pygame.Rect(self.x, self.y, self.img.get_width(), self.img.get_height())
        # Check if the rectangle around the car collides with a target
        if rect.collidepoint(*target):
            # Allow the computer car to move to the next target/ coordinate in the path variable
            self.current_point += 1

    def comp_move(self):
        """
        Allow computer car to move on the track.

        Returns:
            None: if computer car reaches the finish line.
        """
        if self.current_point >= len(self.path):
            return

        self.calculate_angle()
        self.update_path_point()
        super().move()

    def comp_next_level(self, level):
        """
        Increases the velocity of computer car after each level until level 10.

        Parameters:
            level: current level in the game
        """
        # velocity increment after each level
        delta_velocity_of_car = 0.2
        self.reset_cars()
        self.velocity = self.max_velocity + (level - 1) * delta_velocity_of_car
        self.current_point = 0


def draw(win, images, user_car, comp_car, current_game):
    """
    This function draws images and updates them on the pygame window.

    Parameters:
        win: interactive window object.
        images: image displayed in interactive window
        user_car: instance of PlayerCar class.
        comp_car: instance of ComputerCar class.
        current_game: instance of GameInfo class.
    """
    for img, pos in images:
        win.blit(img, pos)

    # initialize the games level
    level_text = main_font.render(
        f'Level {current_game.level}', 1, (255, 255, 255))
    # draw the games level on the window
    win.blit(level_text, (10, height - level_text.get_height() - 70))
    # initialize the timer
    time_text = main_font.render(
        f'Time: {current_game.get_level_time()}s', 1, (255, 255, 255))
    # draw the timer on the window
    win.blit(time_text, (10, height - time_text.get_height() - 40))
    # initialize the velocity
    velocity_text = main_font.render(f'Vel: {user_car.velocity:.1f}px/s', 1, (255, 255, 255))
    # draw the timer on the window
    win.blit(velocity_text, (10, height - velocity_text.get_height() - 10))

    # draw the cars into the window
    user_car.draw_car(win)
    comp_car.comp_draw(win)
    # show all images you draw on the screen
    pygame.display.update()


def move_player(users_car):
    """
    Contains all the keys accessible to the player car.

    Parameters:
        users_car: instance of PlayerCar class.
    """
    # List containing state off all keys where all elements are True or False
    keys = pygame.key.get_pressed()
    # State of the car. It is either idle or moving
    moved = False

    # Check if 'a' key is pressed
    if keys[pygame.K_a]:
        # rotate the car left
        users_car.rotate(left=True)
    # Check if 'd' key is pressed
    if keys[pygame.K_d]:
        # rotate the car right
        users_car.rotate(right=True)
    # check if 'w' key is pressed
    if keys[pygame.K_w]:
        # car is moving
        moved = True
        # move car forward
        users_car.move_forward()
    # check if 's' key is pressed
    if keys[pygame.K_s]:
        # car is moving
        moved = True
        # move car backwards
        users_car.move_backwards()
    # The car is not moving
    if not moved:
        users_car.reduce_speed()


def handle_collision(user_car, comp_car, current_game):
    """
    Handles collisions by both cars with finish line and track the border.

    Parameters:
        user_car: instance of PlayerCar class.
        comp_car: instance of ComputerCar class.
        current_game: instance of GameInfo class.

    This function handles all the collisions that occur in the game.
    This includes collisions when either car collides with the finish line and
    when player car collides with the track border.
    """

    # check if player car is touching track_border_mask
    if user_car.collide(track_border_mask) is not None:
        user_car.bounce()

    comp_finish_poi_collide = comp_car.collide(
        finish_mask, *finish_position)
    # Check if the computer car collides with the finish line
    if comp_finish_poi_collide is not None:
        # draw the 'You lost' text on the screen
        blit_text_center(window, main_font, 'You lost!')
        pygame.display.update()
        # Slow the window down
        pygame.time.wait(5000)
        # Reset the game
        current_game.reset_level()
        # Reset the position of the cars if true
        user_car.reset_cars()
        comp_car.comp_next_level(current_game.level)

    user_finish_poi_collide = user_car.collide(finish_mask, *finish_position)
    # Check if the player collides with the finish line
    if user_finish_poi_collide is not None:
        # Check if player car collides with finish line when y coordinate is 0
        if user_finish_poi_collide[1] == 0:
            user_car.bounce()
        else:
            # increase the level of the computer car
            game_info.next_level()
            # reset the position of the cars
            user_car.reset_cars()
            comp_car.comp_next_level(current_game.level)

        # if player car beats computer car
        if game_info.game_finished():
            blit_text_center(window, main_font, 'You win!')
            pygame.display.update()
            pygame.time.wait(5000)
            game_info.reset_level()
            player_car.reset_cars()
            computer_car.comp_next_level(game_info.level)


def game_loop(win, current_font, current_game):
    # show game text when the level has not started
    while not current_game.started:
        # Draw new level game text on screen
        blit_text_center(win, current_font, f'Press any key to start level {current_game.level}!')
        pygame.display.update()
        # check to see if the ser either
        # closes the window or presses a key
        for game_event in pygame.event.get():
            # close the window if the user exits the window
            if game_event.type == pygame.QUIT:
                return False
            # if user presses any key down, start the level
            if game_event.type == pygame.KEYDOWN:
                return True


# keep the program running unless it is closed
run = True
# initialize clock object
clock = pygame.time.Clock()
# images and their position in the window(order matters)
images_list = [(grass, (0, 0)), (track, (0, 0)),
               (finish, finish_position), (track_border, (0, 0))]
# initialize player and computer car object
player_car = PlayerCar(4, 4)
computer_car = ComputerCar(1, 4, computers_path)
# initialize GameInfo() object
game_info = GameInfo()

# keep window running unless closed
while run:

    # Regulate frame rate to 60 frames per seconds for all users
    clock.tick(fps)
    # draw images on screen
    draw(window, images_list, player_car, computer_car, game_info)

    current_loop = game_loop(window, main_font, game_info)

    if current_loop:
        game_info.start_level()
        # Draw the computer and its path coordinates on the window
        # computer_car.comp_draw(window)

        # Move the player and computer car
        move_player(player_car)
        computer_car.comp_move()

        handle_collision(player_car, computer_car, game_info)
        # close window if the user exits the window
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                run = False
                break

            # if event.type == pygame.MOUSEBUTTONDOWN:
            #     mouse_pos = pygame.mouse.get_pos()
            #     computer_car.path.append(mouse_pos)

    if not current_loop:
        run = False
        break

# print(computer_car.path)
pygame.quit()

Sorry if I was unclear, I meant something more like this:

# images and their position in the window(order matters)
images_list = [(grass, (0, 0)), (track, (0, 0)),
               (finish, finish_position), (track_border, (0, 0))]

# initialize player and computer car object
player_car = PlayerCar(4, 4)
computer_car = ComputerCar(1, 4, computers_path)

# initialize GameInfo() object
game_info = GameInfo()

# initialize clock object
clock = pygame.time.Clock()

def game_loop():
    # keep window running until return
    while True:
        # Regulate from rate to 60 frames per seconds for all users
        clock.tick(fps)
        # draw images on screen
        draw(window, images_list, player_car, computer_car, game_info)
    
        # show game text when the level has not started
        while not game_info.started:
            # Draw new level game text on screen
            blit_text_center(window, main_font, f'Press any key to start level {game_info.level}!')
            pygame.display.update()
            # check to see if the ser either closes the window or presses a key
            for event in pygame.event.get():
                # close the window if the user exits the window
                if event.type == pygame.QUIT:
                    return
    
                # if user presses any key down, start the level
                if event.type == pygame.KEYDOWN:
                    game_info.start_level()
    
        # Draw the computer and its path coordinates on the window
        computer_car.comp_draw(window)
    
        # close window if the user exits the window
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                return
    
            # if event.type == pygame.MOUSEBUTTONDOWN:
            #     mouse_pos = pygame.mouse.get_pos()
            #     computer_car.path.append(mouse_pos)
    
        # Move the player and computer car
        move_player(player_car)
        computer_car.comp_move()
    
        handle_collision(player_car, computer_car, game_info)
    
        # if player car beats computer car
        if game_info.game_finished():
            blit_text_center(window, main_font, 'You win')
            pygame.time.wait(5000)
            game_info.reset_level()
            player_car.reset_cars()
            computer_car.comp_next_level(game_info.level)
    
    # print(computer_car.path)


game_loop()
pygame.quit()

Very little changed, just wrapping it in a function, and replacing run = False with return. You can also perhaps create another loop function for the ‘Press any key to start level’ loop.
You could also modify this so that the entire game is a class, and some of the functions are methods.

2 Likes
#main.py
import pygame
import time
import math
# import the module from our Race Car game directory
from utils import scale_image, blit_rotation_center, blit_text_center

# initialize pygame font module
pygame.font.init()

# poi = point of intersection

# Initializing images and scale the images using scale_image()
grass = scale_image(pygame.image.load('grass.jpg'), 2.5)
track = scale_image(pygame.image.load('track.png'), 0.85)

# Initialize the calling mask
track_border = scale_image(pygame.image.load('track-border.png'), 0.85)
track_border_mask = pygame.mask.from_surface(track_border)

finish = pygame.image.load('finish.png')
finish_mask = pygame.mask.from_surface(finish)
finish_position = (130, 250)
# initialize player and computer car images
red_car = scale_image(pygame.image.load('red-car.png'), 0.55)
green_car = scale_image(pygame.image.load('green-car.png'), 0.55)
# width and height from the track image we initialized
width, height = track.get_width(), track.get_height()

# Initialize and interactive window
window = pygame.display.set_mode((width, height))
# Title of the window created
pygame.display.set_caption('Racing Game!')
# initialize font object
main_font = pygame.font.SysFont('comics-ans', 30)
# frames per seconds
fps = 60
# Computer cars path on the window
computers_path = [(166, 104), (105, 67), (51, 129), (61, 455), (316, 691),
                  (382, 636), (396, 484), (483, 452), (568, 520), (581, 673),
                  (693, 663), (693, 367), (607, 336), (388, 321), (408, 238), (658, 241),
                  (698, 102), (560, 71), (288, 71), (260, 356), (170, 367), (178, 260)]


class GameInfo:
    """
    A class to increase levels, resets levels and provides the game's information.

    Attributes:
        levels (int): maximum levels in the game
        level (int): current level in the game
        started (bool): state of the game
        level_start_time (int): game timer

    Methods:
        __init__(self, level=1):
            initialize the GameInfo class with an instance and a given level.


        next_level(self):
            Increment the games level by 1.


        reset_level(self):
            Reset the games level back to 1 and set the state as not started.


        game_finished(self) -> bool:
            Checks if the game is completed.This occurs when the game exceeds level 10.
            Returns:
                bool: False if game is not completed(not reached level 10), else True.


            start_level(self):
                Starts the current level of the game and a timer in each level.


            get_level_time(self) -> int:
                Calculates the time elapsed since the level started.
                Returns:
                    int: Time since the level started. returns 0 if level not started.
        """
    # maximum levels in the game
    levels = 5

    def __init__(self, level=1):
        """initialize the GameInfo class with an instance and a given level."""
        # current game level
        self.level = level
        # State of the game(not started)
        self.started = False
        # initialize games timer
        self.level_start_time = 0

    def next_level(self):
        """Increment the games level by 1 and set game state as not started."""
        # increase level by 1
        self.level += 1
        self.started = False

    def reset_level(self):
        """Reset the games level back to 1 and set the state as not started."""
        self.level = 1
        self.started = False
        self.level_start_time = 0

    def game_finished(self):
        """
        Checks if the game is completed.This occurs when the game exceeds level 10.

        Returns:
            bool: False if game is not completed(not reached level 10), else True.
        """
        return self.level > self.levels

    def start_level(self):
        """Starts the current level of the game and a timer in each level."""
        # Start the game
        self.started = True
        # Start the games timer
        self.level_start_time = time.time()

    def get_level_time(self):
        """
        Calculates the time elapsed since the level started.

        Returns:
            int: Time since the level started. returns 0 if level not started.

        This method calculates the time difference between
        when the game started to when it ended. If the timer has not started,
        the function will return a time of 0.
        """
        if not self.started:
            return 0
        return round(time.time() - self.level_start_time)


class AbstractCar:
    """
    This parent class contains attributes that will be used by both cars.

    Attributes:
        image (Pygame.surface): image of player or computer car.
        max_velocity (int): maximum velocity of car.
        velocity (int): current velocity of car.
        delta_angle (int): changing speed of the angle.
        angle (int): direction car is facing.
        x (int): x coordinate of car.
        y (int): y coordinate of car.
        acceleration (int): change in velocity.

    Methods:
        __init__(self, level=1):
            initialize this class with an instance, max_velocity and delta_angle.


        rotate(self, left=False, right=False):
            Change the angle of the car based on user input.


        draw_car(self, win):
            Draw both cars on the window.


        move_forward(self):
            Change velocity of the car and move in the forward direction.


        move_backwards(self):
            Change velocity of the car and move in the backward direction.


        move(self):
            Allow the car to move in correct direction based in user input.


        collide(self, mask, x=0, y=0):
            Check for car collision with the track border.
            Returns:
                tuple: (x,y) position of where car collision occurred, else None.

        reset_cars(self):
            Reset position of all cars in the window.
        """

    # initialize class variable red car
    car_image = red_car
    # position class variable
    start_position = (0, 0)

    def __init__(self, max_velocity, delta_angle):
        """initialize this class with an instance, max_velocity and delta_angle."""
        # image of the car
        self.img = self.car_image
        self.max_velocity = max_velocity
        # initial velocity
        self.velocity = 0
        # change in angle of the player car when changing directions
        self.delta_angle = delta_angle
        # initial angle
        self.angle = 0
        # initial x and y coordinates of the car
        self.x, self.y = self.start_position
        # increase velocity by this value
        self.acceleration = 0.1

    def rotate(self, left=False, right=False):
        """
        Change the angle of the car based on user input.

        Parameters:
            left (bool, optional): rotate car left if True.
            right (bool, optional): rotate car right if True.

        If left is True, increase angle which will cause car to move counterclockwise.
        If Right is True, decrease angle which will cause car to move clockwise.
        """
        if left:
            self.angle += self.delta_angle
        if right:
            self.angle -= self.delta_angle

    def draw_car(self, win):
        """
        Draw both cars on the window properly while handling collisions.

        Parameters:
            win (pygame.Surface): The game window display.
        """
        blit_rotation_center(win, self.img, (self.x, self.y), self.angle)

    def move_forward(self):
        """Increase velocity of the car and move in the forward direction."""
        # Maximum velocity after car has accelerated at rest
        self.velocity = min(self.velocity + self.acceleration, self.max_velocity)
        self.move()

    def move_backwards(self):
        """Change velocity of the car and move in the backward direction."""
        # Maximum velocity after car has accelerated at rest
        self.velocity = max(self.velocity - self.acceleration, -self.max_velocity / 2)
        self.move()

    def move(self):
        """
        Allow the car to move in correct direction based in user input.

        This function is responsible for making car rotate in the proper direction.
        Also ensures car moves in the correct direction based on it's angle and position.
        """
        # initial angle in radians
        rads = math.radians(self.angle)
        # change in velocity in the vertical direction
        vertical = math.cos(rads) * self.velocity
        # change in velocity in the horizontal direction
        horizontal = math.sin(rads) * self.velocity

        # Changes coordinates of the car which ensures the car is moving in the correct direction
        self.y -= vertical
        self.x -= horizontal

    def collide(self, mask, x=0, y=0):
        """
        Check for car collision with the track border.

            Parameters:
                mask: object being collided on.
                x: x coordinate of the calling mask(track_border).
                y: y coordinate of the calling mask(track_border).

            Returns:
                tuple: (x,y) position of where car collision occurred, else None.
        """
        # initialize car mask
        car_mask = pygame.mask.from_surface(self.img)
        # Change in x and y between calling mask and mask being called
        offset = (int(self.x - x), int(self.y - y))
        # check if both masks are overlapping each other
        point_of_intersection = mask.overlap(car_mask, offset)
        # return tuple containing the position(∆x and ∆y) of the collision or None if no collision
        return point_of_intersection

    def reset_cars(self):
        """Reset position of all cars in the window."""
        self.x, self.y = self.start_position
        self.angle = 0
        self.velocity = 0


class PlayerCar(AbstractCar):
    """
    A class that contains all functionalities of the player car.

    Attributes:
        car_image (Pygame.surface): car image for player car
        start_position (int): current position of player car


    Methods:
        reduce_speed(self):
            Slow down the player car when not moving forward.


        bounce(self):
            Bounce player car backward after colliding with track border.
        """
    # initialize class attribute for the player car
    car_image = red_car
    # starting position of player car
    start_position = (180, 200)

    def reduce_speed(self):
        """Slow down the player car when not moving forward."""
        self.velocity = max(self.velocity - self.acceleration / 2, 0)
        # allows car to turn and move during deceleration
        self.move()

    def bounce(self):
        """Bounce player car backward after colliding with track border."""
        # change the direction of the player car velocity
        self.velocity = -self.velocity
        self.move()


class ComputerCar(AbstractCar):
    """
    A class that contains all functionalities of the computer car.

    Attributes:
        car_image (Pygame.surface): car image for computer car.
        start_position (int): starting position of computer car.
        path (list): contains targets computer car follows.
        current_point (int): computer car's current target.
        velocity (int): maximum velocity of the computer car.

    Methods:
        __init__(self, max_velocity, delta_angle, path=[]):
            initialize computer car object

        comp_draw(self, win):
            draw computer car on the window.


        calculate_angle(self):
            Ensure computer car is moving to the intended target.


        update_path_point(self):
            Move computer car to the next target.


        comp_move(self) -> None:
            Allow computer car to move on the track.
            Returns:
                None: if computer car reaches the finish line.


        comp_next_level(self, level):
            Increases the velocity of computer car after each level until level 10.
        """
    car_image = green_car
    start_position = (150, 200)

    def __init__(self, max_velocity, delta_angle, path=[]):
        # inherent all attributes in AbstractCar class
        super().__init__(max_velocity, delta_angle)
        self.path = path  # list containing the coordinates the computer car will take
        self.current_point = 0  # The current position of the car in its path
        self.velocity = max_velocity
    
    # def draw_points(self, win):
    #     red = (255, 0, 0)
    #     for point in self.path:
    #         pygame.draw.circle(win, red, point, 5)

    def comp_draw(self, win):
        """
        draw computer car on the window.

        Parameters:
            win: interactive window object.
        """
        super().draw_car(win)
        # self.draw_points(win)

    def calculate_angle(self):
        """Ensure computer car is moving to the intended target."""
        target_x, target_y = self.path[self.current_point]
        x_diff = target_x - self.x
        y_diff = target_y - self.y

        if y_diff == 0:
            desired_radian_angle = math.pi / 2
        else:
            desired_radian_angle = math.atan(x_diff / y_diff)

        if target_y > self.y:
            desired_radian_angle += math.pi

        difference_in_angle = self.angle - math.degrees(desired_radian_angle)
        if difference_in_angle >= 180:
            difference_in_angle -= 360

        if difference_in_angle > 0:
            self.angle -= min(self.delta_angle, abs(difference_in_angle))
        else:
            self.angle += min(self.delta_angle, abs(difference_in_angle))

    def update_path_point(self):
        """Move computer car to the next target."""
        # Check if the car collides with the target
        target = self.path[self.current_point]
        # create a rectangle object for the car
        rect = pygame.Rect(self.x, self.y, self.img.get_width(), self.img.get_height())
        # Check if the rectangle around the car collides with a target
        if rect.collidepoint(*target):
            # Allow the computer car to move to the next target/ coordinate in the path variable
            self.current_point += 1

    def comp_move(self):
        """
        Allow computer car to move on the track.

        Returns:
            None: if computer car reaches the finish line.
        """
        if self.current_point >= len(self.path):
            return

        self.calculate_angle()
        self.update_path_point()
        super().move()

    def comp_next_level(self, level):
        """
        Increases the velocity of computer car after each level until level 10.

        Parameters:
            level: current level in the game
        """
        # velocity increment after each level
        delta_velocity_of_car = 3 / 8
        self.reset_cars()
        self.velocity = self.max_velocity + (level - 1) * delta_velocity_of_car
        self.current_point = 0


def draw(win, images, user_car, comp_car, current_game):
    """
    This function draws images and updates them on the pygame window.

    Parameters:
        win: interactive window object.
        images: image displayed in interactive window
        user_car: instance of PlayerCar class.
        comp_car: instance of ComputerCar class.
        current_game: instance of GameInfo class.
    """
    for img, pos in images:
        win.blit(img, pos)

    # initialize the games level
    level_text = main_font.render(
        f'Level {current_game.level}', 1, (255, 255, 255))
    # draw the games level on the window
    win.blit(level_text, (10, height - level_text.get_height() - 70))
    # initialize the timer
    time_text = main_font.render(
        f'Time: {current_game.get_level_time()}s', 1, (255, 255, 255))
    # draw the timer on the window
    win.blit(time_text, (10, height - time_text.get_height() - 40))
    # initialize the velocity
    velocity_text = main_font.render(f'Vel: {user_car.velocity:.1f}px/s', 1, (255, 255, 255))
    # draw the timer on the window
    win.blit(velocity_text, (10, height - velocity_text.get_height() - 10))

    # draw the cars into the window
    user_car.draw_car(win)
    comp_car.comp_draw(win)
    # show all images you draw on the screen
    pygame.display.update()


def move_player(users_car):
    """
    Contains all the keys accessible to the player car.

    Parameters:
        users_car: instance of PlayerCar class.
    """
    # List containing state off all keys where all elements are True or False
    keys = pygame.key.get_pressed()
    # State of the car. It is either idle or moving
    moved = False

    # Check if 'a' key is pressed
    if keys[pygame.K_a]:
        # rotate the car left
        users_car.rotate(left=True)
    # Check if 'd' key is pressed
    if keys[pygame.K_d]:
        # rotate the car right
        users_car.rotate(right=True)
    # check if 'w' key is pressed
    if keys[pygame.K_w]:
        # car is moving
        moved = True
        # move car forward
        users_car.move_forward()
    # check if 's' key is pressed
    if keys[pygame.K_s]:
        # car is moving
        moved = True
        # move car backwards
        users_car.move_backwards()
    # The car is not moving
    if not moved:
        users_car.reduce_speed()


def handle_collision(user_car, comp_car, current_game):
    """
    Handles collisions by both cars with finish line and track the border.

    Parameters:
        user_car: instance of PlayerCar class.
        comp_car: instance of ComputerCar class.
        current_game: instance of GameInfo class.

    This function handles all the collisions that occur in the game.
    This includes collisions when either car collides with the finish line and
    when player car collides with the track border.
    """

    # check if player car is touching track_border_mask
    if user_car.collide(track_border_mask) is not None:
        user_car.bounce()

    comp_finish_poi_collide = comp_car.collide(
        finish_mask, *finish_position)
    # Check if the computer car collides with the finish line
    if comp_finish_poi_collide is not None:
        # draw the 'You lost' text on the screen
        blit_text_center(window, main_font, 'You lost!')
        pygame.display.update()
        # Slow the window down
        pygame.time.wait(5000)
        # Reset the game
        current_game.reset_level()
        # Reset the position of the cars if true
        user_car.reset_cars()
        comp_car.comp_next_level(current_game.level)

    user_finish_poi_collide = user_car.collide(finish_mask, *finish_position)
    # Check if the player collides with the finish line
    if user_finish_poi_collide is not None:
        # Check if player car collides with finish line when y coordinate is 0
        if user_finish_poi_collide[1] == 0:
            user_car.bounce()
        else:
            # increase the level of the computer car
            game_info.next_level()
            # reset the position of the cars
            user_car.reset_cars()
            comp_car.comp_next_level(current_game.level)

        # if player car beats computer car
        if game_info.game_finished():
            blit_text_center(window, main_font, 'You win!')
            pygame.display.update()
            pygame.time.wait(5000)
            game_info.reset_level()
            player_car.reset_cars()
            computer_car.comp_next_level(game_info.level)


# initialize clock object
clock = pygame.time.Clock()
# images and their position in the window(order matters)
images_list = [(grass, (0, 0)), (track, (0, 0)),
               (finish, finish_position), (track_border, (0, 0))]
# initialize player and computer car object
player_car = PlayerCar(4, 4)
computer_car = ComputerCar(2, 4, computers_path)
# initialize GameInfo() object
game_info = GameInfo()


def game_loop():
    """
    This function controls the flow of the game and it's mechanics.

    This function handles all user input events,
    updates the game window, collisions and keeps window running in a loop.
    """
    # keep the program running unless it is closed
    run = True

    # keep window running unless closed
    while run:

        # Regulate frame rate to 60 frames per seconds for all users
        clock.tick(fps)
        # draw images on screen
        draw(window, images_list, player_car, computer_car, game_info)

        # show game text when the level has not started
        while not game_info.started:
            # Draw new level game text on screen
            blit_text_center(window, main_font, f'Press any key to start level {game_info.level}!')
            pygame.display.update()
            # check to see if the ser either closes the window or presses a key
            for event in pygame.event.get():
                # close the window if the user exits the window
                if event.type == pygame.QUIT:
                    return

                # if user presses any key down, start the level
                if event.type == pygame.KEYDOWN:
                    game_info.start_level()

        # close window if the user exits the window
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                run = False

            # if event.type == pygame.MOUSEBUTTONDOWN:
            #     mouse_pos = pygame.mouse.get_pos()
            #     computer_car.path.append(mouse_pos)

        # Move the player and computer car
        move_player(player_car)
        computer_car.comp_move()

        handle_collision(player_car, computer_car, game_info)
    # print(computer_car.path)


game_loop()
pygame.quit()

#utils.py

# Utility functions for main.py file

import pygame

pygame.font.init()


def scale_image(img, factor):
    """
    Change the size of the images initialized from main.py with a scale factor.

    Parameters:
        img (pygame.Surface): image on window.
        factor (float): constant used to change image size.

    Returns:
        pygame.Surface: same image with a new size
    """
    # tuple containing values of length and width rounded to nearest integer
    size = round(img.get_width() * factor), round(img.get_height() * factor)
    # update the size of the image
    return pygame.transform.scale(img, size)


def blit_rotation_center(win, image, top_left, angle):
    """
    Rotate car images from center and not top left corner.

    Args:
         win (pygame.Surface): The game window display
         image (pygame.Surface): computer and/or player car image
         top_left (tuple): coordinates of the car image
         angle (int): current angle of the car

    This function ensures the car image rotates from the center and not the top left corner.
    This allows a realistic representation of the car when turning in the game.
    """
    # rotates image around top left hand corner(current origin) - Not what we want
    rotated_image = pygame.transform.rotate(image, angle)
    # rotate image around the center of the imaginary rectangle it's in - What we want
    new_rectangle = rotated_image.get_rect(
        center=image.get_rect(topleft=top_left).center)
    # draw rotated image in screen
    win.blit(rotated_image, new_rectangle.topleft)


def blit_text_center(win, font, text):
    """
    This functions draws the texts that will be displayed on the screen.

    Args:
        win: interactive window object.
        font (pygame.font.Font): font object displayed on screen.
        text (str): desired string to be displayed.
    """
    color = (200, 200, 200)
    # initialize text object
    render = font.render(text, 1, color)
    # draw text object on the window
    win.blit(render, (win.get_width() / 2 - render.get_width() / 2,
                      win.get_height() / 2 - render.get_height() / 2 - 25))


2 Likes

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.