Spinning car bug in my car racing game

I don’t know if this is the right place to ask for help for something like this and if it isn’t, i would be happy with other suggestions and resources. I just finished building my car racing game and came across a bug i don’t know how to fix. After the computer car beats the player car by crossing the finish line and resets the game, the computer car no longer follows it’s intended path. I just spin in one position and doesn’t move after that. How can i make it go to it’s intended path?

Repl link:

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

# 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)

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!')

main_font = pygame.font.SysFont('comicsans', 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:
    # maximum levels in the game
    levels = 10

    def __init__(self, level=1):
        # 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):
        """This method is used to advance to the next level of the game.
           It increments each level by 1. It sets the current state of the game
           as not started to indicate the next level of the game will begin soon """
        # increase level by 1
        self.level += 1
        self.started = False

    def reset_level(self):
        """This method resets the level of the game back to level 1.
           It sets the games state as not started
           as well as resets the games timer."""
        self.level = 1
        self.started = False
        self.level_start_time = 0

    def game_finished(self):
        """This method checks if the game is completed.
           This occurs when the game exceeds level 10.
           If the game level is not completed,
            the function will return false. Else it will be true"""
        return self.level > self.levels

    def start_level(self):
        """This method starts the current level of the game
           as well as start a timer used to calculate
           how long each level took to complete. """
        # Start the game
        self.started = True
        # Start the games timer
        self.level_start_time = time.time()

    def get_level_time(self):
        """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."""
    # initialize class variable red car
    car_image = red_car
    # position class variable
    start_position = (0, 0)

    def __init__(self, max_velocity, 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 depending on if it moves left or right from its initial position.
           Angle will either increase or decrease depending on if the car moves left or right"""
        if left:
            self.angle += self.delta_angle
        if right:
            self.angle -= self.delta_angle

    def draw_car(self, win):
        """Call the blit_rotation_center function from the utils module """
        blit_rotation_center(win, self.img, (self.x, self.y), self.angle)

    def move_forward(self):
        """Allows Player car to accelerate when the 'w' key is pressed.
        The car will increase its velocity everytime the 'w' key is pressed until it reaches it's maximum velocity"""
        # Maximum velocity after car has accelerated at rest
        self.velocity = min(self.velocity + self.acceleration, self.max_velocity)

    def move_backwards(self):
        """Allows Player car to accelerate backwards when the 's' key is pressed.
        The car will increase its velocity everytime the 's' key is pressed until it reaches it's maximum velocity"""
        # Maximum velocity after car has accelerated at rest
        self.velocity = max(self.velocity - self.acceleration, -self.max_velocity/2)

    def move(self):
        """This function is responsible for making player and/or computer car rotate in the proper direction.
           It also Ensures the car moves in the correct direction based on its angle and its position in the window.
        # 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):
        """This function is used to check if the player or computer car comes in contact with specified surface.
           The function returns None if no collision occurred or returns a tuple containing the offset"""
        # 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):
        """This method resets the position of both cars after colliding with the finish line."""
        self.x, self.y = self.start_position
        self.angle = 0
        self.velocity = 0

class PlayerCar(AbstractCar):
    """Player car class inheriting attributes from Abstract car class."""
    # initialize class attribute for the player car
    car_image = red_car
    # starting position of player car
    start_position = (180, 200)

    def reduce_speed(self):
        """This function slows the car down when the 'w' key is not being pressed """
        self.velocity = max(self.velocity - self.acceleration / 2, 0)
        # allows car to turn and move during deceleration

    def bounce(self):
        """This is functions is triggered when the player car comes into contact with the track_border_mask.
            The player car will bounce back in the opposite direction with the same speed"""
        # change the direction of the player car velocity
        self.velocity = -self.velocity

class ComputerCar(AbstractCar):
    car_image = green_car
    start_position = (150, 200)

    def __init__(self, max_velocity, delta_angle, path=[]):
        super().__init__(max_velocity, delta_angle)  # inherent all attributes defined in AbstractCar class
        self.path = path   # list containing the coordinates the computer car will take
        self.current_point = 0  # The index of the targets contained in the path variable
        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):
        # self.draw_points(win)

    def calculate_angle(self):
        """This function ensures the car is moving to the intended target(assigned coordinate) on the screen.
           It does ths by calculating the change in angle between the Computer car and the target.
           This points the car in the proper direction thus allowing it to rotate properly when moving on the road """
        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
            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))
            self.angle += min(self.delta_angle, abs(difference_in_angle))

    def update_path_point(self):
        """This function allows the Computer car to move to the next target in its path on the window"""
        # 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):
        if self.current_point >= len(self.path):


    def comp_next_level(self, level):
        """This method increases the speed of the computer car after the completion of each level.
           It also resets the position of the computer car after each level."""
        delta_velocity_of_car = 0.2
        self.velocity = self.max_velocity + (level - 1) * delta_velocity_of_car
        self.current_point = 0

def draw(win, images, user_car, comp_car, games_information):
    """This function draws images and updates them on the window for all images created main.py"""
    for img, pos in images:
        win.blit(img, pos)

    # initialize the games level
    level_text = main_font.render(
        f'Level {games_information.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: {games_information.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
    # show all images you draw on the screen

def move_player(users_car):
    """Contains all the keys accessible to the player car"""
    # 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
    # Check if 'd' key is pressed
    if keys[pygame.K_d]:
        # rotate the car right
    # check if 'w' key is pressed
    if keys[pygame.K_w]:
        # car is moving
        moved = True
        # move car forward
    # check if 's' key is pressed
    if keys[pygame.K_s]:
        # car is moving
        moved = True
        # move car backwards
    # The car is not moving
    if not moved:

def handle_collision(user_car, comp_car, game_info):
    """This function is responsible for handling all the collisions by both the player and the computer car.
       This includes collision for the player car between the finish line,the track border and
       for the computer car the collision between the finish line and its targets"""

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

    comp_finish_poi_collide = comp_car.collide(
        finish_mask, *finish_position)
    # Check is 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!')
        # Slow the window down
        # Reset the game
        # Reset the position of the cars if true

    user_finish_poi_collide = user_car.collide(finish_mask, *finish_position)
    # Check is 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:
            # increase the level of the computer car
            # reset the position of the cars

# 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(3.8, 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
    # 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}!')
        # 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:

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

    # Draw the computer and its path coordinates on the 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

    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')

# print(computer_car.path)

it seems like you just forgot to call comp_car.comp_next_level(game_info.level) near line 466, when the computer car reaches the finish line. (You do correctly call it for when the player car reaches the finish line.)

Calling it will fix it, but there are certain improvements that can be made to your code.
My main suggestion is to create a large Game class that has the following functionality:

  • Holds all game info, including both cars.
  • Has methods for:
    • resetting the level
    • the game loop
    • waiting for key press to start the game
    • winning the game
    • all of the other functions, draw, handle_collision

Another suggestion I would make is to make the player and computer car have the exact same ability, the only difference being AI vs player input, both using the same general controls.

1 Like

Can you elaborate on your last suggestion?

I also have a few questions about my code:

Why is my player car able to inherit all the attributes of the Abstract car even though it doesn’t have the super().__init__() defined in the class?

would my docstrings in my code follow python standards? Do they need more detail or do they have to be short and brief?

So, the AI car looks kind of wonky, it seems to accelerate instantly. But the main point is that instead of having the AI or player input control the car directly, have a “middleman” of some sort, same for both player and AI, that controls the car directly. The AI and player just influence the middleman. (You would now be able to switch out different AI and player input for any car because there is one, shared controls.) This also ensures that the AI can’t do anything that the player cannot do.

The player car inherits the __init__ method from the abstract class, if that’s what you are asking. Even though the player car class does not define an __init__ method, the player car class actually has the abstract car __init__ method. (Also, a user-defined class may have any attribute it likes. There is nothing special about __init__ except being called when creating an instance.)

The docstrings are up to your preference, though I would say duplicating the method docstrings in the class docstring might be excessive. It’s also not necessary to tell the obvious with docstrings.
A possible format is to have a one sentence summary as the first part of a docstring, then a more detailed description in the body.


if the player car does automatically inherit attributes from abstract car, then why do we have the super().__init__() statement defined in the computer car class? I thought child classes can’t inherit parent class attributes without the super.__init__() statement but will by default inherit only methods.

Think about it this way:

class A:
  def my_method(self):
    self.bar = 2
class B(A):
  def my_method(self):
    self.foo = 5
class C(A):

Class A is the base class, it has a my_method. Think of it like __init__, as there’s really no difference here.
Class B also defines a method with the same name. It overrides A’s method, so A’s method is not accessible from class B. In class B, you choose whether or not to call the parent class’s method with super(), and also how.
Class C does not define the method, and so C will have A’s method.

In your case, the __init__ method of Abstract car is inherited by Player car. If you use Player’s __init__, it behaves the exact same as if it were instead Abstract. The Computer car overrides Abstract’s __init__ method, but also chooses to call it.

What about “inheriting” attributes? There’s really no such thing, because you can put any attribute on your instance at any time. The __init__ method is really just a convenient spot and time to initialize (set) some attributes, but it does not control which attributes the class has. So when you do super().__init__(...), you are just calling a function to set some attributes on self, nothing special.

a = A()
a.kjfaksjdfhaksldhf = 2

Understanding classes more deeply requires you to look under the hood to see what is really going on. You can experiment in the Shell with the python interpreter.

1 Like