-
Notifications
You must be signed in to change notification settings - Fork 0
Tutorial 2: Car
Important
This tutorial starts at the workshop-tutorial-1 branch and the complete solution can be found at workshop-tutorial-2 branch.
This tutorial focuses on the implementation of the Car class and also its subclass PlayerCar.
Note
This tutorial will implement/update the following modules:
There are some functions to be implemented to setup the car.
The first thing to do is initialize the fields of the Car class in the Car.__init__.
As we can see, the function already has a super().__init__(pos, rot) call to initialize the superclass Transformable. The fields should be easy to initialize, just initialize everything to zero except the ones provided by the parameters.
Solution Code
def __init__(
self,
pos: npt.NDArray[np.float32] = vec(0, 0),
rot: float = 0,
color: pygame.Color = pygame.Color(0, 0, 0),
out_of_track_color: pygame.Color = pygame.Color(255, 0, 0),
):
"""
Initialize the car.
:param pos: The position of the car
:param rot: The rotation of the car
:param color: The color of the car
:param out_of_track_color: The border color of the car when it is out
of track
"""
super().__init__(pos, rot)
#vvv NEW vvv#
self.color = color
self.out_of_track_color = out_of_track_color
self.speed = 0.0
self.acceleration = 0.0
self.angular_speed = 0.0
self.angular_acceleration = 0.0
self.out_of_track = False
self.progress = 0
self.total_progress = 0
#^^^ NEW ^^^#While the Car._get_input does not actually need to be implemented as it is expected to be implemented by subclasses, it is better to understand what it does.
It is to let it's subclasses to define how the car should move, we will call this method from Car.update and then handle the car movement from there.
This is nice because subclasses of Car does not need to care how is the movement achieved, they just need to know where should the car move next. It is like how drivers only need to know how to steer and accelerate the car without needing to know how the car engine actually works.
Take a look at the Car.Input class, we can see it contains 2 fields forward and turn. It is also annotated with a @dataclass, which helps us create a simple __init__ with all the fields as the parameter.
As we can see from the parameter of Car.reset_state, it is supplied the track. We are going to use this to initialize our starting position and rotation, which are fields inherited from Transformable.
Since this function is also called during each iteration, we would also need to reset speed, acceleration, etc.
One thing that is different from __init__ is the Car.total_progress field because we didn't have the track information in __init__. Now with the track information, we are going to set it to the total length of the track by using the number of line segments in track.polyline.
def reset_state(self, track: Track):
"""
Reset the state of the car.
:param track: The track
:return: None
"""
#vvv NEW vvv#
# Set the position to the first point of the track
#
# Set the rotation to the start direction of the track with
# `get_start_dir`
self.pos = track.curve.pts[0].pos.astype(np.float32)
self.rot = np.atan2(_____) - math.pi
# Reset everything else
self.speed = 0.0
self.acceleration = 0.0
self.angular_speed = 0.0
self.angular_acceleration = 0.0
self.out_of_track = False
self.progress = 0
self.total_progress = len(_____)
#^^^ NEW ^^^#Solution Code
def reset_state(self, track: Track):
"""
Reset the state of the car.
:param track: The track
:return: None
"""
#vvv NEW vvv#
# Set the position to the first point of the track
#
# Set the rotation to the start direction of the track with
# `get_start_dir`
self.pos = track.curve.pts[0].pos.astype(np.float32)
self.rot = np.atan2(*track.get_start_dir()) - math.pi
# Reset everything else
self.speed = 0.0
self.acceleration = 0.0
self.angular_speed = 0.0
self.angular_acceleration = 0.0
self.out_of_track = False
self.progress = 0
self.total_progress = len(track.polyline)
#^^^ NEW ^^^#To implement the logics of a car, we are going to define how is the car updated and drawn on the screen each frame.
In the Car.update function, we are going to do several things - linear movement, angular movement, out of track handling, and progress updating.
For linear movement, we are going to call the _get_input method to get the input for the car from subclasses. We are going to use the Input.forward field for setting the acceleration of the car. If the acceleration is not zero, we are going to update the speed of the car. If the acceleration is zero, we are going to apply deceleration to the car.
def update(self, dt: float, track: Track):
"""
Update the car.
:param dt: The delta time
:param track: The track
:return: None
"""
#vvv NEW vvv#
# Get the input for the car from `_get_input`
#
# The `_get_input` method will be implemented in the subclasses
input_data = self._get_input()
# Linear acceleration
self.acceleration = input_data.forward * _____ * dt
if self.acceleration != 0:
# If there is acceleration, update the speed
self.speed += _____ * dt
self.speed = clamp(self.speed, -self.MAX_SPEED, self.MAX_SPEED)
else:
# If there is no acceleration, apply deceleration
self.acceleration = (
self.speed / self.MAX_SPEED * self.DECELERATION * dt
)
dspeed = self.acceleration * dt
if abs(dspeed) > abs(self.speed):
self.speed = 0
else:
self.speed -= dspeed
#^^^ NEW ^^^#Solution Code
def update(self, dt: float, track: Track):
"""
Update the car.
:param dt: The delta time
:param track: The track
:return: None
"""
#vvv NEW vvv#
# Get the input for the car from `_get_input`
#
# The `_get_input` method will be implemented in the subclasses
input_data = self._get_input()
# Linear acceleration
self.acceleration = input_data.forward * self.ACCELERATION * dt
if self.acceleration != 0:
# If there is acceleration, update the speed
self.speed += self.acceleration * dt
self.speed = clamp(self.speed, -self.MAX_SPEED, self.MAX_SPEED)
else:
# If there is no acceleration, apply deceleration
self.acceleration = (
self.speed / self.MAX_SPEED * self.DECELERATION * dt
)
dspeed = self.acceleration * dt
if abs(dspeed) > abs(self.speed):
self.speed = 0
else:
self.speed -= dspeed
#^^^ NEW ^^^#Since we were using clamp, we should import it.
import math
from dataclasses import dataclass
from typing import List
import numpy as np
import numpy.typing as npt
import pygame
from engine.entity.camera import Camera
from engine.entity.track import Track
from engine.entity.transformable import Transformable
from engine.utils import clamp, vec # UPDATEDFor angular movement, it is similar to linear movement. We are going to use the Input.turn field instead.
def update(self, dt: float, track: Track):
"""
Update the car.
:param dt: The delta time
:param track: The track
:return: None
"""
# ...
#vvv NEW vvv#
# Angular movement
self.angular_acceleration = (
input_data.turn * _____ * dt
)
if self.angular_acceleration != 0:
# If there is angular acceleration, update the angular speed
self.angular_speed += _____ * dt
self.angular_speed = clamp(
self.angular_speed,
-self.MAX_ANGULAR_SPEED,
self.MAX_ANGULAR_SPEED,
)
else:
# If there is no angular acceleration, apply angular deceleration
self.angular_acceleration = (
self.angular_speed
/ self.MAX_ANGULAR_SPEED
* self.ANGULAR_DECELERATION
* dt
)
dangular_speed = self.angular_acceleration * dt
if abs(dangular_speed) > abs(self.angular_speed):
self.angular_speed = 0
else:
self.angular_speed -= dangular_speed
#^^^ NEW ^^^#Solution Code
def update(self, dt: float, track: Track):
"""
Update the car.
:param dt: The delta time
:param track: The track
:return: None
"""
# ...
#vvv NEW vvv#
# Angular movement
self.angular_acceleration = (
input_data.turn * self.ANGULAR_ACCELERATION * dt
)
if self.angular_acceleration != 0:
# If there is angular acceleration, update the angular speed
self.angular_speed += self.angular_acceleration * dt
self.angular_speed = clamp(
self.angular_speed,
-self.MAX_ANGULAR_SPEED,
self.MAX_ANGULAR_SPEED,
)
else:
# If there is no angular acceleration, apply angular deceleration
self.angular_acceleration = (
self.angular_speed
/ self.MAX_ANGULAR_SPEED
* self.ANGULAR_DECELERATION
* dt
)
dangular_speed = self.angular_acceleration * dt
if abs(dangular_speed) > abs(self.angular_speed):
self.angular_speed = 0
else:
self.angular_speed -= dangular_speed
#^^^ NEW ^^^#Now we are going to do out of track handling. We are going to use the Track.shapely_polygon's Polygon.contains to check if the car is within the track's polygon. If the car is out of track, we are going to limit the speed of the car to a penalty speed.
def update(self, dt: float, track: Track):
"""
Update the car.
:param dt: The delta time
:param track: The track
:return: None
"""
# ...
#vvv NEW vvv#
# Out of track handling by checking with `shapely_polygon` of the track
self.out_of_track = not track.shapely_polygon.contains(
shapely.points(self.pos)
)
if self.out_of_track:
# If the car is out of track, limit the speed the penalty speed
self.speed = clamp(
self.speed,
_____ * self.OUT_OF_TRACK_PENALTY,
_____ * self.OUT_OF_TRACK_PENALTY,
)
#^^^ NEW ^^^#Solution Code
def update(self, dt: float, track: Track):
"""
Update the car.
:param dt: The delta time
:param track: The track
:return: None
"""
# ...
#vvv NEW vvv#
# Out of track handling by checking with `shapely_polygon` of the track
self.out_of_track = not track.shapely_polygon.contains(
shapely.points(self.pos)
)
if self.out_of_track:
# If the car is out of track, limit the speed the penalty speed
self.speed = clamp(
self.speed,
-self.MAX_SPEED * self.OUT_OF_TRACK_PENALTY,
self.MAX_SPEED * self.OUT_OF_TRACK_PENALTY,
)
#^^^ NEW ^^^#Since we are using shapely, we should also import it at the top of the file.
import math
from dataclasses import dataclass
from typing import List
import numpy as np
import numpy.typing as npt
import pygame
import shapely # NEW
from engine.entity.camera import Camera
from engine.entity.track import Track
from engine.entity.transformable import Transformable
from engine.utils import clamp, vecFinally, we are going to update the position and rotation of the car. We are going to use the Transformable.translate_forward function and the Transformable.rotate function.
def update(self, dt: float, track: Track):
"""
Update the car.
:param dt: The delta time
:param track: The track
:return: None
"""
# ...
#vvv NEW vvv#
# Update the position and rotation using Transformable methods
#
# `translate_forward` updates the position based on the speed
#
# `rotate` updates the rotation based on the angular speed
self.translate_forward(_____ * dt)
self.rotate(_____ * dt)
#^^^ NEW ^^^#Solution Code
def update(self, dt: float, track: Track):
"""
Update the car.
:param dt: The delta time
:param track: The track
:return: None
"""
# ...
#vvv NEW vvv#
# Update the position and rotation using Transformable methods
#
# `translate_forward` updates the position based on the speed
#
# `rotate` updates the rotation based on the angular speed
self.translate_forward(self.speed * dt)
self.rotate(self.angular_speed * dt)
#^^^ NEW ^^^#Lastly, while the players do not need to know their progress, AI cars will need it to know how well they are doing on the track. We are going to update the progress by checking if the car is close to the next point in the track by using a simple distance check.
def update(self, dt: float, track: Track):
"""
Update the car.
:param dt: The delta time
:param track: The track
:return: None
"""
# ...
#vvv NEW vvv#
# Update the progress
#
# If the car is close to the next point in the track by using a simple
# distance check, update the progress
if self.progress + 1 < len(track.polyline):
check_point = track.polyline[self.progress + 1]
if (
np.linalg.norm(_____ - _____)
< track.width + self.HEIGHT
):
self.progress += 1
#^^^ NEW ^^^#Solution Code
def update(self, dt: float, track: Track):
"""
Update the car.
:param dt: The delta time
:param track: The track
:return: None
"""
# ...
#vvv NEW vvv#
# Update the progress
#
# If the car is close to the next point in the track by using a simple
# distance check, update the progress
if self.progress + 1 < len(track.polyline):
check_point = track.polyline[self.progress + 1]
if (
np.linalg.norm(check_point - self.pos)
< track.width + self.HEIGHT
):
self.progress += 1
#^^^ NEW ^^^#Now we are going to implement the Car.draw function to draw the car on the screen.
First, we should implement the Car.get_corners function so that later in the draw function we can use this to find out where the corners of the car body are.
def get_corners(self) -> List[npt.NDArray[np.float32]]:
"""
Get the transformed corners of the car.
:return: The transformed corners of the car
"""
#vvv NEW vvv#
def rotate_angle(
x: float, y: float, rad: float
) -> npt.NDArray[np.float32]:
# Rotate a local point around the center of the car
#
# Then return the global position of this rotated point
pos = vec(x, y)
return self.pos + np.dot(rot_mat(rad), pos)
# Rotate every corner of the car
return [
rotate_angle(_____, _____, self.rot),
rotate_angle(_____, _____, self.rot),
rotate_angle(_____, _____, self.rot),
rotate_angle(_____, _____, self.rot),
]
#^^^ NEW ^^^#Solution Code
def get_corners(self) -> List[npt.NDArray[np.float32]]:
"""
Get the transformed corners of the car.
:return: The transformed corners of the car
"""
#vvv NEW vvv#
def rotate_angle(
x: float, y: float, rad: float
) -> npt.NDArray[np.float32]:
# Rotate a local point around the center of the car
#
# Then return the global position of this rotated point
pos = vec(x, y)
return self.pos + np.dot(rot_mat(rad), pos)
# Rotate every corner of the car
return [
rotate_angle(-self.WIDTH / 2, -self.HEIGHT / 2, self.rot),
rotate_angle(self.WIDTH / 2, -self.HEIGHT / 2, self.rot),
rotate_angle(self.WIDTH / 2, self.HEIGHT / 2, self.rot),
rotate_angle(-self.WIDTH / 2, self.HEIGHT / 2, self.rot),
]
#^^^ NEW ^^^#We used rot_mat to get the rotation matrix, so let's import it.
import math
from dataclasses import dataclass
from typing import List
import numpy as np
import numpy.typing as npt
import pygame
import shapely
from engine.entity.camera import Camera
from engine.entity.track import Track
from engine.entity.transformable import Transformable
from engine.utils import clamp, rot_mat, vec # UPDATEDThen we are just going to do 2 things - draw the car body as a rectangle and draw a red outline if the car is out of track. To draw the car body, we are going to use the Camera.get_coord function to get the position of the car's corners from the camera's point of view, where the corners are from the get_corners function that we just wrote.
def draw(self, screen: pygame.Surface, camera: Camera):
"""
Draw the car.
:param screen: The screen to draw on
:param camera: The camera
:return: None
"""
#vvv NEW vvv#
# Get the global position of the corners and then draw the polygon
polygon = [camera._____(corners) for corners in self._____()]
pygame.draw.polygon(screen, self.color, polygon)
if self.out_of_track:
# Draw the outline of the car with the out of track color
pygame.draw.polygon(screen, self.out_of_track_color, polygon, 2)
#^^^ NEW ^^^#Solution Code
def draw(self, screen: pygame.Surface, camera: Camera):
"""
Draw the car.
:param screen: The screen to draw on
:param camera: The camera
:return: None
"""
#vvv NEW vvv#
# Get the global position of the corners and then draw the polygon
polygon = [camera.get_coord(corners) for corners in self.get_corners()]
pygame.draw.polygon(screen, self.color, polygon)
if self.out_of_track:
# Draw the outline of the car with the out of track color
pygame.draw.polygon(screen, self.out_of_track_color, polygon, 2)
#^^^ NEW ^^^#Now we can implement the PlayerCar class. It is very simple since we only need to implement the _get_input function, the rest are already handled by the Car class. Just map the w, s, a, and d keys to the forward and turn fields, as well as clamp the values to the range [-1, 1].
import pygame # NEW
from engine.entity.car import Car
from engine.utils import clamp # NEW
class PlayerCar(Car):
"""
A class representing the player car.
"""
def _get_input(self) -> Car.Input:
#vvv NEW vvv#
# Get the forward input from player
#
# Add 1 if the `w` key is pressed, subtract 1 if the `s` key is pressed
forward = 0
if pygame.key.get_pressed()[pygame.K_w]:
forward += 1
if pygame.key.get_pressed()[pygame.K_s]:
forward -= 1
# Get the turn input from player
#
# Add 1 if the `d` key is pressed, subtract 1 if the `a` key is pressed
turn = 0
if pygame.key.get_pressed()[pygame.K_a]:
turn -= 1
if pygame.key.get_pressed()[pygame.K_d]:
turn += 1
# Limit the forward and turn values to the range [-1, 1]
= clamp(forward, -1.0, 1.0)
turn = clamp(turn, -1.0, 1.0)
return Car.Input(_____, _____)
#^^^ NEW ^^^#Solution Code
import pygame # NEW
from engine.entity.car import Car
from engine.utils import clamp # NEW
class PlayerCar(Car):
"""
A class representing the player car.
"""
def _get_input(self) -> Car.Input:
#vvv NEW vvv#
# Get the forward input from player
#
# Add 1 if the `w` key is pressed, subtract 1 if the `s` key is pressed
forward = 0
if pygame.key.get_pressed()[pygame.K_w]:
forward += 1
if pygame.key.get_pressed()[pygame.K_s]:
forward -= 1
# Get the turn input from player
#
# Add 1 if the `d` key is pressed, subtract 1 if the `a` key is pressed
turn = 0
if pygame.key.get_pressed()[pygame.K_a]:
turn -= 1
if pygame.key.get_pressed()[pygame.K_d]:
turn += 1
# Limit the forward and turn values to the range [-1, 1]
forward = clamp(forward, -1.0, 1.0)
turn = clamp(turn, -1.0, 1.0)
return Car.Input(forward, turn)
#^^^ NEW ^^^#Next we are going to add the cars into game.main and train.main.
Let's first add the cars into the training mode. While the training mode does not have the player car, we can add the AICar class to represent the AI cars.
Note
The AICar class is not implemented yet, it currently does not do anything, i.e. _get_input returns Car.Input(0, 0). We will implement it in the next tutorial.
First, we are going to use the load_nn function to load the sensor rotations, weights, hidden layer sizes, activation function, and color of the AI cars. We don't really need to use these yet because the AI cars don't do anything, but we need them to initialize the AI cars. Then initialize the AI cars into a list using the loaded data, the args.ai_count argument, and the args.init_mutate_noise.
# Create a camera object
camera = Camera(screen)
#vvv NEW vvv#
# Load the neural network arguments using `load_nn`
sensor_rots, weights, hidden_layer_sizes, activation, color = load_nn(args)
# Create a list of AI cars
#
# `args.ai_count` is the number of AI cars
#
# Get the activation function using `activation_funcs[activation]`
#
# Take the i-th element of `weights` if it is not None, otherwise None
#
# Supply `init_mutate_noise` with `args.init_mutate_noise`
ai_cars = [
AICar(
sensor_rots=np.array(sensor_rots, dtype=np.float32),
activation=activation_funcs[activation],
weights=weights[i % len(weights)] if weights else None,
init_mutate_noise=args.init_mutate_noise,
hidden_layer_sizes=hidden_layer_sizes,
color=pygame.Color(*color),
)
for i in range(args.ai_count)
]
# Reset the state of each car by calling `reset_state` with the track
for car in ai_cars:
car._____(track)
#^^^ NEW ^^^#
# Main loop forever while `running` is True
running = True
fixed_dt = 0.032
skip_frame_counter = 0Solution Code
# Create a camera object
camera = Camera(screen)
#vvv NEW vvv#
# Load the neural network arguments using `load_nn`
sensor_rots, weights, hidden_layer_sizes, activation, color = load_nn(args)
# Create a list of AI cars
#
# `args.ai_count` is the number of AI cars
#
# Get the activation function using `activation_funcs[activation]`
#
# Take the i-th element of `weights` if it is not None, otherwise None
#
# Supply `init_mutate_noise` with `args.init_mutate_noise`
ai_cars = [
AICar(
sensor_rots=np.array(sensor_rots, dtype=np.float32),
activation=activation_funcs[activation],
weights=weights[i % len(weights)] if weights else None,
init_mutate_noise=args.init_mutate_noise,
hidden_layer_sizes=hidden_layer_sizes,
color=pygame.Color(*color),
)
for i in range(args.ai_count)
]
# Reset the state of each car by calling `reset_state` with the track
for car in ai_cars:
car.reset_state(track)
#^^^ NEW ^^^#
# Main loop forever while `running` is True
running = True
fixed_dt = 0.032
skip_frame_counter = 0Since we had to use numpy, we should import it.
import argparse
import math
import random
from pathlib import Path
from typing import List, Optional, Tuple
import numpy as np # NEW
import pygame
from engine.activations import activation_funcs
from engine.entity.ai_car import AICar
from engine.entity.camera import Camera
from engine.entity.player_car import PlayerCar
from engine.entity.track import TrackNext, let's also add a shortcut to save the neural networks, even though we have not yet implemented the AI cars, but it is good to have it ready.
# Handle events from `pygame.event.get()`
for event in pygame.event.get():
if event.type == pygame.QUIT:
# If it is a quit event, we stop the loop
running = False
elif event.type == pygame.KEYDOWN:
if (
pygame.key.get_mods() & pygame.KMOD_CTRL
and event.key == pygame.K_q
):
# If control + q is pressed, we quit the program
running = False
#vvv NEW vvv#
if (
pygame.key.get_mods() & pygame.KMOD_CTRL
and event.key == pygame.K_s
):
# If control + s is pressed, we save the neural network
save_nn(_____, _____)
#^^^ NEW ^^^#Solution Code
# Handle events from `pygame.event.get()`
for event in pygame.event.get():
if event.type == pygame.QUIT:
# If it is a quit event, we stop the loop
running = False
elif event.type == pygame.KEYDOWN:
if (
pygame.key.get_mods() & pygame.KMOD_CTRL
and event.key == pygame.K_q
):
# If control + q is pressed, we quit the program
running = False
#vvv NEW vvv#
if (
pygame.key.get_mods() & pygame.KMOD_CTRL
and event.key == pygame.K_s
):
# If control + s is pressed, we save the neural network
save_nn(args, ai_cars)
#^^^ NEW ^^^#Let's also make the camera follow the first AI car instead of the player car that we put there temporarily before. We are going to sort the AI cars based on their progress in the next tutorial so that it will actually follow the first AI car, but again it is nice to have it ready.
Solution Code
# Update the camera to follow the first AI car, i.e. the most fit car # UPDATED
camera.update(fixed_dt, ai_cars[0]) # UPDATEDNow, we can also remove the player car import.
import argparse
import math
import random
from pathlib import Path
from typing import List, Optional, Tuple
import numpy as np
import pygame
from engine.activations import activation_funcs
from engine.entity.ai_car import AICar
from engine.entity.camera import Camera
# from engine.entity.player_car import PlayerCar # REMOVED
from engine.entity.track import TrackLastly, we can draw all the cars after the track is drawn.
# Draw the track and the cars on the screen
track.draw(screen, camera, 5)
#vvv NEW vvv#
for car in ai_cars:
car._____(_____, _____)
#^^^ NEW ^^^#Solution Code
# Draw the track and the cars on the screen
track.draw(screen, camera, 5)
#vvv NEW vvv#
for car in ai_cars:
car.draw(screen, camera)
#^^^ NEW ^^^#Now, try it out with python main.py train -t demo0 demo1 demo2 -n my-nn -s -90 -45 0 45 90 -z 4 4 2 -f leaky_relu -q 3 -a 10 -c 3. You should see a black rectangle in the center of the screen, which are all the cars stacked together.
Now, let's implement the gameplay mode, where we will actually have a controllable player car.
First, we also need to initialize the cars, but this time we are going to initialize the player car, and only initialize the AI cars if the args.nn argument is provided. To initialize the player car, we should also provide the color of the car using the args.color argument.
# Setup the track by loading it
#
# `args.track` is a string representing the name of the track to play in
track = Track.load(args.track)
#vvv NEW vvv#
# Create a player car object
#
# `args.color` is a tuple[int, int, int] representing the color of the
# player car
player_car = PlayerCar(color=pygame.Color(*args.color))
# If `args.follow_ai` is False, reset the state of the player car by
# calling `reset_state` with the track
if not args.follow_ai:
player_car._____(_____)
# Create a list of AI cars
ai_cars = []
# If `args.nn` is provided, load the neural network arguments using
# `load_nn`
if args.nn:
sensor_rots, weights, activation, color = load_nn(args)
# Create a list of AI cars
#
# `args.ai_count` is the number of AI cars
#
# Get the activation function using `activation_funcs[activation]`
#
# Take the i-th element of `weights` if it is not None, otherwise None
#
# Supply `init_mutate_noise` with `args.init_mutate_noise`
ai_cars = [
AICar(
np.array(sensor_rots, dtype=np.float32),
weights=weights[i % len(weights)],
init_mutate_noise=args.init_mutate_noise,
activation=activation_funcs[activation],
color=pygame.Color(*color),
)
for i in range(args.ai_count)
]
# Reset the state of each AI car by calling `reset_state` with the
# track
for car in ai_cars:
car.reset_state(track)
#^^^ NEW ^^^#
# Create a camera object
#
# If `args.follow_ai` is True, follow the first AI car, otherwise follow
# the player car
camera = Camera(screen, _____) # UPDATEDSolution Code
# Setup the track by loading it
#
# `args.track` is a string representing the name of the track to play in
track = Track.load(args.track)
#vvv NEW vvv#
# Create a player car object
#
# `args.color` is a tuple[int, int, int] representing the color of the
# player car
player_car = PlayerCar(color=pygame.Color(*args.color))
# If `args.follow_ai` is False, reset the state of the player car by
# calling `reset_state` with the track
if not args.follow_ai:
player_car.reset_state(track)
# Create a list of AI cars
ai_cars = []
# If `args.nn` is provided, load the neural network arguments using
# `load_nn`
if args.nn:
sensor_rots, weights, activation, color = load_nn(args)
# Create a list of AI cars
#
# `args.ai_count` is the number of AI cars
#
# Get the activation function using `activation_funcs[activation]`
#
# Take the i-th element of `weights` if it is not None, otherwise None
#
# Supply `init_mutate_noise` with `args.init_mutate_noise`
ai_cars = [
AICar(
np.array(sensor_rots, dtype=np.float32),
weights=weights[i % len(weights)],
init_mutate_noise=args.init_mutate_noise,
activation=activation_funcs[activation],
color=pygame.Color(*color),
)
for i in range(args.ai_count)
]
# Reset the state of each AI car by calling `reset_state` with the
# track
for car in ai_cars:
car.reset_state(track)
#^^^ NEW ^^^#
# Create a camera object
#
# If `args.follow_ai` is True, follow the first AI car, otherwise follow
# the player car
camera = Camera(screen, player_car) # UPDATEDRemember to import all the necessary modules.
import argparse
from pathlib import Path
from typing import List, Tuple
import numpy as np # NEW
import pygame
from engine.activations import activation_funcs # NEW
from engine.entity.ai_car import AICar # NEW
from engine.entity.camera import Camera
from engine.entity.player_car import PlayerCar
from engine.entity.track import TrackNext we are going to call the update logics in the main loop for the player car so the player can control the car's input. Also note that args.follow_ai argument should be taken into account.
if (
pygame.key.get_mods() & pygame.KMOD_CTRL
and event.key == pygame.K_q
):
# If control + q is pressed, we quit the program
running = False
#vvv NEW vvv#
# Update the player car if `args.follow_ai` is False
if not args.follow_ai:
player_car.update(_____, _____)
#^^^ NEW ^^^#
# Update the camera to follow the first AI car if `args.follow_ai` is
# True, otherwise follow the player car
camera.update(fixed_dt, _____) # UPDATEDSolution Code
if (
pygame.key.get_mods() & pygame.KMOD_CTRL
and event.key == pygame.K_q
):
# If control + q is pressed, we quit the program
running = False
#vvv NEW vvv#
# Update the player car if `args.follow_ai` is False
if not args.follow_ai:
player_car.update(fixed_dt, track)
#^^^ NEW ^^^#
# Update the camera to follow the first AI car if `args.follow_ai` is
# True, otherwise follow the player car
camera.update(fixed_dt, args.follow_ai and ai_cars[0]) # UPDATEDLastly, draw all the cars on screen just like with the training mode.
# Draw the track and the cars on the screen
track.draw(screen, camera, 5)
#vvv NEW vvv#
# Draw the player car if `args.follow_ai` is False
if not args.follow_ai:
player_car._____(_____, _____)
# Draw each AI car on the screen
for car in ai_cars:
car._____(_____, _____)
#^^^ NEW ^^^#
# Update the display with `update`
pygame.display.update()Solution Code
# Draw the track and the cars on the screen
track.draw(screen, camera, 5)
#vvv NEW vvv#
# Draw the player car if `args.follow_ai` is False
if not args.follow_ai:
player_car.draw(screen, camera)
# Draw each AI car on the screen
for car in ai_cars:
car.draw(screen, camera)
#^^^ NEW ^^^#
# Update the display with `update`
pygame.display.update()Right now, you should be able to play the game with python main.py game -t demo0 -n demo -a 5.
However, one handy feature is the ability to restart the game. So let's add a restart function, and call it when the player pressed Ctrl + R.
#
# If `args.follow_ai` is True, follow the first AI car, otherwise follow
# the player car
camera = Camera(screen, player_car)
#vvv NEW vvv#
# Define the restart function
def restart():
# If `args.follow_ai` is False, reset the state of the player car by
# calling `reset_state` with the track
if not args.follow_ai:
player_car._____(_____)
#^^^ NEW ^^^#
# Main loop forever while `running` is True
running = True
fixed_dt = 0.032Solution Code
#
# If `args.follow_ai` is True, follow the first AI car, otherwise follow
# the player car
camera = Camera(screen, player_car)
#vvv NEW vvv#
# Define the restart function
def restart():
# If `args.follow_ai` is False, reset the state of the player car by
# calling `reset_state` with the track
if not args.follow_ai:
player_car.reset_state(track)
#^^^ NEW ^^^#
# Main loop forever while `running` is True
running = True
fixed_dt = 0.032Then when the keys are pressed, we call the restart function.
Solution Code
# Handle events from `pygame.event.get()`
for event in pygame.event.get():
if event.type == pygame.QUIT:
# If it is a quit event, we stop the loop
running = False
elif event.type == pygame.KEYDOWN:
#vvv NEW vvv#
if (
pygame.key.get_mods() & pygame.KMOD_CTRL
and event.key == pygame.K_r
):
# If control + r is pressed, we restart the program
restart()
#^^^ NEW ^^^#
if (
pygame.key.get_mods() & pygame.KMOD_CTRL
and event.key == pygame.K_q
):
# If control + q is pressed, we quit the program
running = FalseNow you should be able to restart the game with Ctrl + R. Try playing it with python main.py game -t demo0 -n demo -a 5!