""" ------------------------------------------------- Project: ORB QUEST Standard: 91896 (2.7) School: Tauranga Boys' College Author: Thomas Brighouse Date: March 2025 Python: ------------------------------------------------- """ import pygame import os import sys pygame.mixer.pre_init(44100, -16, 2, 512) pygame.init() # Screen dimensions - made smaller for your request (smaller than 1280x960) SCREEN_WIDTH = 960 SCREEN_HEIGHT = 720 screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) pygame.display.set_caption("ORB QUEST") clock = pygame.time.Clock() FPS = 60 # Colors WHITE = (255, 255, 255) BLACK = (0, 0, 0) # Load sounds orb_sound = pygame.mixer.Sound('collect.mp3') die_sound = pygame.mixer.Sound('lobotomy-sound-effect.mp3') minotaur_die_sound = pygame.mixer.Sound('die.mp3') pygame.mixer.music.load('music.mp3') pygame.mixer.music.play(-1) def load_animation(prefix, count): return [pygame.transform.scale(pygame.image.load(f'img/{prefix}{i}.png'), (64, 64)) for i in range(count)] walk_right = load_animation('glad_walk', 3) walk_left = [pygame.transform.flip(img, True, False) for img in walk_right] jump_right = load_animation('glad_jump', 4) jump_left = [pygame.transform.flip(img, True, False) for img in jump_right] attack_right = load_animation('glad_attack', 5) attack_left = [pygame.transform.flip(img, True, False) for img in attack_right] idle_right = pygame.transform.scale(pygame.image.load('img/glad_idle.png'), (64, 64)) idle_left = pygame.transform.flip(idle_right, True, False) orb_img = pygame.image.load('img/orb.png') block_img = pygame.image.load('img/tile.png') door_img = pygame.transform.scale(pygame.image.load('img/door.gif'), (48, 72)) bg_img = pygame.transform.scale(pygame.image.load('img/bg.png'), (SCREEN_WIDTH, SCREEN_HEIGHT)) title_img = pygame.transform.scale(pygame.image.load('img/title_page.png'), (SCREEN_WIDTH, SCREEN_HEIGHT)) minotaur_idle = pygame.transform.scale(pygame.image.load('img/minotaur_idle.png'), (64, 64)) minotaur_walk = [pygame.transform.scale(pygame.image.load(f'img/minotaur_walk{i}.png'), (64, 64)) for i in range(8)] tiles = pygame.sprite.Group() orbs = pygame.sprite.Group() doors = pygame.sprite.Group() enemies = pygame.sprite.Group() font_small = pygame.font.SysFont(None, 24) font_large = pygame.font.SysFont(None, 72) font_medium = pygame.font.SysFont(None, 36) def draw_text(text, size, x, y): font = pygame.font.SysFont(None, size) surf = font.render(text, True, WHITE) screen.blit(surf, (x, y)) def draw_controls(): lines = [ "CONTROLS:", "Move: WASD", "Jump: W", "Attack: Q" ] x = SCREEN_WIDTH - 170 y = 20 for line in lines: text_surf = font_small.render(line, True, WHITE) screen.blit(text_surf, (x, y)) y += 25 class Tile(pygame.sprite.Sprite): def __init__(self, x, y): super().__init__() self.image = pygame.transform.scale(block_img, (64, 64)) self.rect = self.image.get_rect(topleft=(x, y)) # Smaller collision rect to allow easier jump-through self.collision_rect = pygame.Rect(self.rect.x, self.rect.y + 16, 64, 48) tiles.add(self) class Orb(pygame.sprite.Sprite): def __init__(self, x, y): super().__init__() self.image = orb_img self.rect = self.image.get_rect(center=(x + 32, y + 32)) orbs.add(self) class Door(pygame.sprite.Sprite): def __init__(self, x, y): super().__init__() self.image = door_img self.rect = self.image.get_rect(midbottom=(x + 32, y + 64)) doors.add(self) class Minotaur(pygame.sprite.Sprite): def __init__(self, x, y): super().__init__() self.walk_index = 0 self.image = minotaur_idle self.rect = self.image.get_rect(topleft=(x, y)) self.direction = 1 self.alive = True enemies.add(self) def update(self): if self.alive: self.walk_index = (self.walk_index + 1) % (len(minotaur_walk) * 8) self.image = minotaur_walk[self.walk_index // 8] else: self.image.set_alpha(0) class Player(pygame.sprite.Sprite): def __init__(self): super().__init__() self.direction = 1 self.speed = 5 self.jump_speed = -20 self.vel_x = 0 self.vel_y = 0 self.on_ground = False self.state = 'idle' self.attacking = False self.attack_index = 0 self.attack_cooldown = 0 self.walk_index = 0 self.jump_index = 0 self.attack_speed = 3 self.image = idle_right self.rect = self.image.get_rect(topleft=(100, 550)) # lowered start due to smaller screen self.orbs_collected = 0 def update(self, tiles, enemies): keys = pygame.key.get_pressed() self.vel_x = 0 if not self.attacking: if keys[pygame.K_d]: self.vel_x = self.speed self.direction = 1 if self.on_ground: self.state = 'walk' elif keys[pygame.K_a]: self.vel_x = -self.speed self.direction = -1 if self.on_ground: self.state = 'walk' else: if self.on_ground: self.state = 'idle' if keys[pygame.K_w] and self.on_ground: self.vel_y = self.jump_speed self.on_ground = False self.state = 'jump' if keys[pygame.K_q] and not self.attacking and self.attack_cooldown <= 0: self.attacking = True self.attack_index = 0 self.state = 'attack' self.attack_cooldown = 20 if self.attack_cooldown > 0: self.attack_cooldown -= 1 self.vel_y += 1 if self.vel_y > 15: self.vel_y = 15 self.rect.x += self.vel_x self.check_collision(tiles, 'x') self.rect.y += self.vel_y self.check_collision(tiles, 'y') self.animate() if self.attacking and self.attack_index == 2: attack_rect = self.rect.copy() if self.direction == 1: attack_rect.x += 40 else: attack_rect.x -= 40 for enemy in enemies: if enemy.alive and attack_rect.colliderect(enemy.rect): minotaur_die_sound.play() enemy.alive = False enemies.remove(enemy) collected = pygame.sprite.spritecollide(self, orbs, True) if collected: orb_sound.play() self.orbs_collected += len(collected) # Removed spikes collision entirely for enemy in enemies: if enemy.alive and self.rect.colliderect(enemy.rect): if not self.attacking: die_sound.play() game_over_screen() def check_collision(self, tiles, direction): if direction == 'x': hits = [tile for tile in tiles if self.rect.colliderect(tile.collision_rect)] for tile in hits: if self.vel_x > 0: self.rect.right = tile.collision_rect.left elif self.vel_x < 0: self.rect.left = tile.collision_rect.right elif direction == 'y': hits = [tile for tile in tiles if self.rect.colliderect(tile.collision_rect)] for tile in hits: if self.vel_y > 0: self.rect.bottom = tile.collision_rect.top self.vel_y = 0 self.on_ground = True elif self.vel_y < 0: self.rect.top = tile.collision_rect.bottom self.vel_y = 0 def animate(self): if self.state == 'walk': self.walk_index = (self.walk_index + 1) % (len(walk_right) * 6) frame = self.walk_index // 6 self.image = walk_right[frame] if self.direction == 1 else walk_left[frame] elif self.state == 'jump': self.jump_index = (self.jump_index + 1) % (len(jump_right) * 6) frame = self.jump_index // 6 self.image = jump_right[frame] if self.direction == 1 else jump_left[frame] elif self.state == 'attack': self.attack_index += 1 frame = min(self.attack_index // self.attack_speed, len(attack_right) - 1) self.image = attack_right[frame] if self.direction == 1 else attack_left[frame] if self.attack_index >= len(attack_right) * self.attack_speed: self.attacking = False self.attack_index = 0 self.state = 'idle' elif self.state == 'idle': self.image = idle_right if self.direction == 1 else idle_left def game_over_screen(): ghost = pygame.Surface((64, 64), pygame.SRCALPHA) pygame.draw.ellipse(ghost, (255, 255, 255, 160), (0, 0, 64, 64)) y = SCREEN_HEIGHT // 2 while True: screen.blit(bg_img, (0, 0)) screen.blit(ghost, (SCREEN_WIDTH // 2 - 32, y)) text = font_large.render("YOU DIED", True, WHITE) restart = font_large.render("R - Restart", True, WHITE) leave = font_large.render("ESC - Quit", True, WHITE) screen.blit(text, (SCREEN_WIDTH // 2 - text.get_width() // 2, 100)) screen.blit(restart, (SCREEN_WIDTH // 2 - restart.get_width() // 2, 300)) screen.blit(leave, (SCREEN_WIDTH // 2 - leave.get_width() // 2, 400)) pygame.display.update() y -= 1 for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() sys.exit() if event.type == pygame.KEYDOWN: if event.key == pygame.K_r: main() elif event.key == pygame.K_ESCAPE: pygame.quit() sys.exit() def load_level(level_map): tiles.empty() orbs.empty() doors.empty() enemies.empty() x = y = 0 for row in level_map: for col in row: if col == 'B': Tile(x, y) elif col == 'O': Orb(x, y) elif col == 'D': Door(x, y) elif col == 'M': Minotaur(x, y) # spikes ('S') ignored and removed x += 64 y += 64 x = 0 levels = [ [ ' ', ' ', ' ', ' ', ' ', ' O ', ' B B ', ' BBB BBB BBBBB ', ' ', ' BBBB B D ', 'BBBBBBBBBBBBBBBBBBBBBBBBBBBB' ], [ ' ', ' ', ' ', ' ', ' O M ', ' B B B BBB ', ' ', ' BBBB B ', ' B ', ' B B D ', 'BBBBBBBBBBBBBBBBBBBBBBBBBBBB' ], [ ' ', ' ', ' ', ' ', ' BBBBB ', ' M O B ', ' BBBBB ', ' O ', ' BBBB M D ', ' BBBBBBBBBBBBBBBBBBBBBBBBBB', 'BBBBBBBBBBBBBBBBBBBBBBBBBBBB' ], # 4th "level" is just a placeholder for the game complete screen [] ] def game_complete_screen(): while True: screen.fill(BLACK) complete_text = font_large.render("GAME COMPLETE", True, WHITE) screen.blit(complete_text, (SCREEN_WIDTH // 2 - complete_text.get_width() // 2, SCREEN_HEIGHT // 2 - complete_text.get_height() // 2)) pygame.display.update() for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() sys.exit() if event.type == pygame.KEYDOWN: if event.key == pygame.K_r: main() # restart game elif event.key == pygame.K_ESCAPE: pygame.quit() sys.exit() def main(): player = Player() current_level = 0 load_level(levels[current_level]) total_orbs = len(orbs) running = True while running: clock.tick(FPS) for event in pygame.event.get(): if event.type == pygame.QUIT: running = False player.update(tiles, enemies) enemies.update() # Door level change for door in doors: if player.rect.colliderect(door.rect) and player.orbs_collected >= total_orbs: current_level += 1 if current_level >= len(levels): # This won't happen now due to 4th empty level running = False elif current_level == 3: # Show GAME COMPLETE screen game_complete_screen() running = False else: load_level(levels[current_level]) player.rect.topleft = (100, 550) player.orbs_collected = 0 total_orbs = len(orbs) screen.blit(bg_img, (0, 0)) tiles.draw(screen) orbs.draw(screen) doors.draw(screen) enemies.draw(screen) screen.blit(player.image, player.rect) draw_text(f"Orbs: {player.orbs_collected}/{total_orbs}", 36, 20, 20) draw_controls() pygame.display.update() pygame.quit() sys.exit() if __name__ == '__main__': main()