""" ----------------------------------------- Project: Escape Drake Standard: 91896 (2.7) School: Tauranga Boys' Collage Auther: Hunter Moss Date: May 2024 Python: 3.11.2 ----------------------------------------- """ import pygame import os import pickle from os import path pygame.init() # Screen Size screen_width = 1000 screen_height = 1000 screen = pygame.display.set_mode((screen_width, screen_height)) pygame.display.set_caption('Escape Drake') # Load images background_img = pygame.image.load('Images/Background.png') # Colours Red = (255, 0, 0) Black = (0, 0, 0) # Constants tile_size = 50 # Game variables level = 3 # Load player images function def load_images(folder, prefix, count): return [pygame.transform.scale(pygame.image.load(os.path.join(folder, f"{prefix} ({i}).png")), (tile_size, tile_size)) for i in range(1, count + 1)] # Player class class Player(pygame.sprite.Sprite): def __init__(self): super().__init__() self.images = { 'idle': load_images('Images/Player', 'idle', 16), 'run': load_images('Images/Player', 'run', 20), 'jump': load_images('Images/Player', 'jump', 30), 'dead': load_images('Images/Player', 'dead', 30) } self.image = self.images['idle'][0] self.rect = self.image.get_rect(topleft=(50, screen_height - 50)) self.vel = pygame.math.Vector2(0, 0) self.speed = 5 self.gravity = 0.5 self.jump_power = -10 self.on_ground = False self.jumping = False self.facing_right = True self.frame = 0 self.idle_animation_speed = 0.2 self.animation_speed = 0.6 self.alive = True def update(self, tiles, door, drakes): if self.alive: keys = pygame.key.get_pressed() # Horizontal movement if keys[pygame.K_LEFT]: self.vel.x = -self.speed self.facing_right = False elif keys[pygame.K_RIGHT]: self.vel.x = self.speed self.facing_right = True else: self.vel.x = 0 # Jumping if (keys[pygame.K_SPACE] or keys[pygame.K_UP]) and self.on_ground: self.vel.y = self.jump_power self.on_ground = False self.jumping = True # Apply gravity self.vel.y += self.gravity # Horizontal collision self.rect.x += self.vel.x self.check_collision(tiles, 'x') # Vertical collision self.rect.y += self.vel.y self.check_collision(tiles, 'y') # Screen collision self.rect.clamp_ip(pygame.Rect(0, 0, screen_width, screen_height)) # Check door collision if door and self.rect.colliderect(door.rect): door.open = True if level < 3: increase_level() else: end_game() # Check collision with DRAKES for drake in drakes: if self.rect.colliderect(drake.rect): self.alive = False # Animation handling if self.jumping: self.image = self.images['jump'][0] elif self.vel.x != 0: self.frame = (self.frame + self.animation_speed) % len(self.images['run']) self.image = self.images['run'][int(self.frame)] else: self.frame = (self.frame + self.idle_animation_speed) % len(self.images['idle']) self.image = self.images['idle'][int(self.frame)] # Flip sprite if not self.facing_right: self.image = pygame.transform.flip(self.image, True, False) # Reset jumping state if on ground if self.rect.bottom >= screen_height: self.on_ground = True self.jumping = False self.vel.y = 0 else: # Play death animation self.frame = (self.frame + self.animation_speed) % len(self.images['dead']) self.image = self.images['dead'][int(self.frame)] # Show retry screen after death animation finishes if self.frame >= len(self.images['dead']) - 1: show_retry_screen() def check_collision(self, tiles, direction): for tile in tiles: if self.rect.colliderect(tile.rect): if direction == 'x': if self.vel.x > 0: # Moving right self.rect.right = tile.rect.left elif self.vel.x < 0: # Moving left self.rect.left = tile.rect.right elif direction == 'y': if self.vel.y > 0: # Moving down self.rect.bottom = tile.rect.top self.vel.y = 0 self.on_ground = True self.jumping = False elif self.vel.y < 0: # Moving up self.rect.top = tile.rect.bottom self.vel.y = 0 # Block Classes class Block(pygame.sprite.Sprite): def __init__(self, x, y, image, scale_to_tile=True): super().__init__() if scale_to_tile: self.image = pygame.transform.scale(image, (tile_size, tile_size)) else: self.image = image self.rect = self.image.get_rect(topleft=(x, y)) class Brick0(Block): def __init__(self, x, y): image = pygame.image.load('Images/Level/brick_dark0.png') super().__init__(x, y, image) class Brick1(Block): def __init__(self, x, y): image = pygame.image.load('Images/Level/brick_dark1.png') super().__init__(x, y, image) class Brick2(Block): def __init__(self, x, y): image = pygame.image.load('Images/Level/brick_dark2.png') super().__init__(x, y, image) class Brick3(Block): def __init__(self, x, y): image = pygame.image.load('Images/Level/brick_dark3.png') super().__init__(x, y, image) class Cobbweb(Block): def __init__(self, x, y): image = pygame.image.load('Images/Level/Cobbwebs.png') super().__init__(x, y, image) class Grill(Block): def __init__(self, x, y): image = pygame.image.load('Images/Level/Grill.png') super().__init__(x, y, image) class Skulls(Block): def __init__(self, x, y): image = pygame.image.load('Images/Level/skulls.png') super().__init__(x, y, image) class Platform(Block): def __init__(self, x, y): image = pygame.image.load('Images/Level/platform.png') original_width, original_height = image.get_size() resized_image = pygame.transform.scale(image, (tile_size, original_height + 5)) adjusted_y = y + tile_size - original_height # Position at the bottom of the tile super().__init__(x, adjusted_y, resized_image, scale_to_tile=False) class Trog(Block): def __init__(self, x, y): image = pygame.image.load('Images/dngn_altar_trog.png') super().__init__(x, y, image) class Key(Block): def __init__(self, x, y): image = pygame.image.load('Images/Key.png') resized_image = pygame.transform.scale(image, (50, 20)) # Resize key image to 50x20 pixels # Calculate adjusted_y to center the key vertically within the grid adjusted_y = y + (tile_size - resized_image.get_height()) // 2 super().__init__(x, adjusted_y, resized_image, scale_to_tile=False) class Drake(Block): def __init__(self, x, y): image = pygame.image.load('Images/DRAKE.png') image = pygame.transform.scale(image, (tile_size, tile_size)) super().__init__(x, y, image) class Door(Block): def __init__(self, x, y): self.closed_image = pygame.image.load('Images/dngn_closed_door.png') self.open_image = pygame.image.load('Images/dngn_open_door.png') super().__init__(x, y, self.closed_image) self.open = False def update(self): if self.open: self.image = self.open_image # World class class World: def __init__(self, data): self.tile_list = [] self.floor_group = pygame.sprite.Group() self.door = None self.drakes = [] # List to hold multiple drakes self.load_level(data) def load_level(self, data): block_types = { 'brick0': Brick0, 'brick1': Brick1, 'brick2': Brick2, 'brick3': Brick3, 'cobbweb': Cobbweb, 'grill': Grill, 'skulls': Skulls, 'platform': Platform, 'trog': Trog, 'key': Key, 'drake': Drake, 'door': Door } for block in data: x, y, block_type = block if block_type in block_types: block_class = block_types[block_type] block_instance = block_class(x, y) if block_type == 'door': self.door = block_instance elif block_type == 'drake': self.drakes.append(block_instance) else: self.tile_list.append(block_instance) self.floor_group.add(block_instance) def draw(self): for tile in self.tile_list: screen.blit(tile.image, tile.rect) if self.door: screen.blit(self.door.image, self.door.rect) for drake in self.drakes: screen.blit(drake.image, drake.rect) def get_tiles(self): return self.floor_group # Functions to handle level changes and game end def increase_level(): global level level += 1 if path.exists(f'level_data/level{level}_data'): with open(f'level_data/level{level}_data', 'rb') as pickle_in: world_data = pickle.load(pickle_in) return World(world_data) else: end_game() # End of game def end_game(): print("Congratulations! You have completed the game.") pygame.quit() exit() # Retry screen function def show_retry_screen(): font = pygame.font.Font(None, 74) retry_text = font.render('You failed to Escape Drake!', True, Red) play_again_text = font.render('Escape again?', True, (0, 255, 0)) retry_rect = retry_text.get_rect(center=(screen_width // 2, screen_height // 2 - 50)) play_again_rect = play_again_text.get_rect(center=(screen_width // 2, screen_height // 2 + 50)) while True: screen.fill(Black) screen.blit(retry_text, retry_rect) screen.blit(play_again_text, play_again_rect) for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() exit() if event.type == pygame.MOUSEBUTTONDOWN and play_again_rect.collidepoint(event.pos): reset_level() return pygame.display.update() def reset_level(): global world global player global level level = 1 if path.exists(f'level_data/level{level}_data'): with open(f'level_data/level{level}_data', 'rb') as pickle_in: world_data = pickle.load(pickle_in) world = World(world_data) else: world = World([]) # Default to an empty world if level data is not found player.rect.topleft = (50, screen_height - 50) # Reset player position player.alive = True # Load level data and create world instance if path.exists(f'level_data/level{level}_data'): with open(f'level_data/level{level}_data', 'rb') as pickle_in: world_data = pickle.load(pickle_in) world = World(world_data) else: world = World([]) # Default to an empty world if level data is not found # Main game loop def main(): global world global player global level run = True clock = pygame.time.Clock() player = Player() player_group = pygame.sprite.Group() player_group.add(player) while run: screen.blit(background_img, (0, 0)) # Event handling for event in pygame.event.get(): if event.type == pygame.QUIT: run = False player.update(world.get_tiles(), world.door, world.drakes) if world.door: world.door.update() world.draw() player_group.draw(screen) # Display current level font = pygame.font.Font(None, 30) level_text = font.render(f'Level: {level}', True, (255, 255, 255)) screen.blit(level_text, (10, 10)) pygame.display.update() clock.tick(60) pygame.quit() if __name__ == "__main__": main()