diff --git a/tutorial4-code/__pycache__/utils.cpython-311.pyc b/tutorial4-code/__pycache__/utils.cpython-311.pyc new file mode 100644 index 0000000..cb1f351 Binary files /dev/null and b/tutorial4-code/__pycache__/utils.cpython-311.pyc differ diff --git a/tutorial4-code/imgs/finish.png b/tutorial4-code/imgs/finish.png new file mode 100644 index 0000000..9f78c56 Binary files /dev/null and b/tutorial4-code/imgs/finish.png differ diff --git a/tutorial4-code/imgs/grass.jpg b/tutorial4-code/imgs/grass.jpg new file mode 100644 index 0000000..dbacd68 Binary files /dev/null and b/tutorial4-code/imgs/grass.jpg differ diff --git a/tutorial4-code/imgs/green-car.png b/tutorial4-code/imgs/green-car.png new file mode 100644 index 0000000..053ec99 Binary files /dev/null and b/tutorial4-code/imgs/green-car.png differ diff --git a/tutorial4-code/imgs/grey-car.png b/tutorial4-code/imgs/grey-car.png new file mode 100644 index 0000000..2886358 Binary files /dev/null and b/tutorial4-code/imgs/grey-car.png differ diff --git a/tutorial4-code/imgs/purple-car.png b/tutorial4-code/imgs/purple-car.png new file mode 100644 index 0000000..376dbc9 Binary files /dev/null and b/tutorial4-code/imgs/purple-car.png differ diff --git a/tutorial4-code/imgs/red-car.png b/tutorial4-code/imgs/red-car.png new file mode 100644 index 0000000..4944182 Binary files /dev/null and b/tutorial4-code/imgs/red-car.png differ diff --git a/tutorial4-code/imgs/track-border.png b/tutorial4-code/imgs/track-border.png new file mode 100644 index 0000000..a5d9a46 Binary files /dev/null and b/tutorial4-code/imgs/track-border.png differ diff --git a/tutorial4-code/imgs/track.png b/tutorial4-code/imgs/track.png new file mode 100644 index 0000000..bd6954d Binary files /dev/null and b/tutorial4-code/imgs/track.png differ diff --git a/tutorial4-code/imgs/white-car.png b/tutorial4-code/imgs/white-car.png new file mode 100644 index 0000000..35ecb1b Binary files /dev/null and b/tutorial4-code/imgs/white-car.png differ diff --git a/tutorial4-code/main.py b/tutorial4-code/main.py new file mode 100644 index 0000000..7ad862a --- /dev/null +++ b/tutorial4-code/main.py @@ -0,0 +1,290 @@ +import pygame +import time +import math +from utils import scale_image, blit_rotate_center, blit_text_center +pygame.font.init() + +GRASS = scale_image(pygame.image.load("imgs/grass.jpg"), 2.5) +TRACK = scale_image(pygame.image.load("imgs/track.png"), 0.9) + +TRACK_BORDER = scale_image(pygame.image.load("imgs/track-border.png"), 0.9) +TRACK_BORDER_MASK = pygame.mask.from_surface(TRACK_BORDER) + +FINISH = pygame.image.load("imgs/finish.png") +FINISH_MASK = pygame.mask.from_surface(FINISH) +FINISH_POSITION = (130, 250) + +RED_CAR = scale_image(pygame.image.load("imgs/red-car.png"), 0.55) +GREEN_CAR = scale_image(pygame.image.load("imgs/green-car.png"), 0.55) + +WIDTH, HEIGHT = TRACK.get_width(), TRACK.get_height() +WIN = pygame.display.set_mode((WIDTH, HEIGHT)) +pygame.display.set_caption("Racing Game!") + +MAIN_FONT = pygame.font.SysFont("comicsans", 44) + +FPS = 60 +PATH = [(175, 119), (110, 70), (56, 133), (70, 481), (318, 731), (404, 680), (418, 521), (507, 475), (600, 551), (613, 715), (736, 713), + (734, 399), (611, 357), (409, 343), (433, 257), (697, 258), (738, 123), (581, 71), (303, 78), (275, 377), (176, 388), (178, 260)] + + +class GameInfo: + LEVELS = 10 + + def __init__(self, level=1): + self.level = level + self.started = False + self.level_start_time = 0 + + def next_level(self): + self.level += 1 + self.started = False + + def reset(self): + self.level = 1 + self.started = False + self.level_start_time = 0 + + def game_finished(self): + return self.level > self.LEVELS + + def start_level(self): + self.started = True + self.level_start_time = time.time() + + def get_level_time(self): + if not self.started: + return 0 + return round(time.time() - self.level_start_time) + + +class AbstractCar: + def __init__(self, max_vel, rotation_vel): + self.img = self.IMG + self.max_vel = max_vel + self.vel = 0 + self.rotation_vel = rotation_vel + self.angle = 0 + self.x, self.y = self.START_POS + self.acceleration = 0.1 + + def rotate(self, left=False, right=False): + if left: + self.angle += self.rotation_vel + elif right: + self.angle -= self.rotation_vel + + def draw(self, win): + blit_rotate_center(win, self.img, (self.x, self.y), self.angle) + + def move_forward(self): + self.vel = min(self.vel + self.acceleration, self.max_vel) + self.move() + + def move_backward(self): + self.vel = max(self.vel - self.acceleration, -self.max_vel/2) + self.move() + + def move(self): + radians = math.radians(self.angle) + vertical = math.cos(radians) * self.vel + horizontal = math.sin(radians) * self.vel + + self.y -= vertical + self.x -= horizontal + + def collide(self, mask, x=0, y=0): + car_mask = pygame.mask.from_surface(self.img) + offset = (int(self.x - x), int(self.y - y)) + poi = mask.overlap(car_mask, offset) + return poi + + def reset(self): + self.x, self.y = self.START_POS + self.angle = 0 + self.vel = 0 + + +class PlayerCar(AbstractCar): + IMG = RED_CAR + START_POS = (180, 200) + + def reduce_speed(self): + self.vel = max(self.vel - self.acceleration / 2, 0) + self.move() + + def bounce(self): + self.vel = -self.vel + self.move() + + +class ComputerCar(AbstractCar): + IMG = GREEN_CAR + START_POS = (150, 200) + + def __init__(self, max_vel, rotation_vel, path=[]): + super().__init__(max_vel, rotation_vel) + self.path = path + self.current_point = 0 + self.vel = max_vel + + def draw_points(self, win): + for point in self.path: + pygame.draw.circle(win, (255, 0, 0), point, 5) + + def draw(self, win): + super().draw(win) + # self.draw_points(win) + + def calculate_angle(self): + 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.rotation_vel, abs(difference_in_angle)) + else: + self.angle += min(self.rotation_vel, abs(difference_in_angle)) + + def update_path_point(self): + target = self.path[self.current_point] + rect = pygame.Rect( + self.x, self.y, self.img.get_width(), self.img.get_height()) + if rect.collidepoint(*target): + self.current_point += 1 + + def move(self): + if self.current_point >= len(self.path): + return + + self.calculate_angle() + self.update_path_point() + super().move() + + def next_level(self, level): + self.reset() + self.vel = self.max_vel + (level - 1) * 0.2 + self.current_point = 0 + + +def draw(win, images, player_car, computer_car, game_info): + for img, pos in images: + win.blit(img, pos) + + level_text = MAIN_FONT.render( + f"Level {game_info.level}", 1, (255, 255, 255)) + win.blit(level_text, (10, HEIGHT - level_text.get_height() - 70)) + + time_text = MAIN_FONT.render( + f"Time: {game_info.get_level_time()}s", 1, (255, 255, 255)) + win.blit(time_text, (10, HEIGHT - time_text.get_height() - 40)) + + vel_text = MAIN_FONT.render( + f"Vel: {round(player_car.vel, 1)}px/s", 1, (255, 255, 255)) + win.blit(vel_text, (10, HEIGHT - vel_text.get_height() - 10)) + + player_car.draw(win) + computer_car.draw(win) + pygame.display.update() + + +def move_player(player_car): + keys = pygame.key.get_pressed() + moved = False + + if keys[pygame.K_a]: + player_car.rotate(left=True) + if keys[pygame.K_d]: + player_car.rotate(right=True) + if keys[pygame.K_w]: + moved = True + player_car.move_forward() + if keys[pygame.K_s]: + moved = True + player_car.move_backward() + + if not moved: + player_car.reduce_speed() + + +def handle_collision(player_car, computer_car, game_info): + if player_car.collide(TRACK_BORDER_MASK) != None: + player_car.bounce() + + computer_finish_poi_collide = computer_car.collide( + FINISH_MASK, *FINISH_POSITION) + if computer_finish_poi_collide != None: + blit_text_center(WIN, MAIN_FONT, "You lost!") + pygame.display.update() + pygame.time.wait(5000) + game_info.reset() + player_car.reset() + computer_car.reset() + + player_finish_poi_collide = player_car.collide( + FINISH_MASK, *FINISH_POSITION) + if player_finish_poi_collide != None: + if player_finish_poi_collide[1] == 0: + player_car.bounce() + else: + game_info.next_level() + player_car.reset() + computer_car.next_level(game_info.level) + + +run = True +clock = pygame.time.Clock() +images = [(GRASS, (0, 0)), (TRACK, (0, 0)), + (FINISH, FINISH_POSITION), (TRACK_BORDER, (0, 0))] +player_car = PlayerCar(4, 4) +computer_car = ComputerCar(2, 4, PATH) +game_info = GameInfo() + +while run: + clock.tick(FPS) + + draw(WIN, images, player_car, computer_car, game_info) + + while not game_info.started: + blit_text_center( + WIN, MAIN_FONT, f"Press any key to start level {game_info.level}!") + pygame.display.update() + for event in pygame.event.get(): + if event.type == pygame.QUIT: + pygame.quit() + break + + if event.type == pygame.KEYDOWN: + game_info.start_level() + + for event in pygame.event.get(): + if event.type == pygame.QUIT: + run = False + break + + move_player(player_car) + computer_car.move() + + handle_collision(player_car, computer_car, game_info) + + if game_info.game_finished(): + blit_text_center(WIN, MAIN_FONT, "You won the game!") + pygame.time.wait(5000) + game_info.reset() + player_car.reset() + computer_car.reset() + + +pygame.quit() diff --git a/tutorial4-code/utils.py b/tutorial4-code/utils.py new file mode 100644 index 0000000..2276f33 --- /dev/null +++ b/tutorial4-code/utils.py @@ -0,0 +1,19 @@ +import pygame + + +def scale_image(img, factor): + size = round(img.get_width() * factor), round(img.get_height() * factor) + return pygame.transform.scale(img, size) + + +def blit_rotate_center(win, image, top_left, angle): + rotated_image = pygame.transform.rotate(image, angle) + new_rect = rotated_image.get_rect( + center=image.get_rect(topleft=top_left).center) + win.blit(rotated_image, new_rect.topleft) + + +def blit_text_center(win, font, text): + render = font.render(text, 1, (200, 200, 200)) + win.blit(render, (win.get_width()/2 - render.get_width() / + 2, win.get_height()/2 - render.get_height()/2))