""" -------------------------------------------- Project: Puzzle Game Standard: 91892 (AS 2.7) V6 School: Tauranga Boys' College Author: Tyler Bunning Date: May 2024| -------------------------------------------- """ import pygame import pickle from os import path # Initialize pygame pygame.init() # Set up the clock and frame rate clock = pygame.time.Clock() fps = 60 # Variables for screen dimensions, tile size, level, and game state screen_width = 1000 screen_height = 1000 tile_size = 50 level = 1 run = True constant = 0 # Create the game screen screen = pygame.display.set_mode((screen_width, screen_height)) pygame.display.set_caption('Puzzle Game') # Load background image bg_img = pygame.image.load('sprites/bg.jpg') def draw_grid(): # Function to draw a grid on the screen for line in range(0, 20): pygame.draw.line(screen, (255, 255, 255), (0, line * tile_size), (screen_width, line * tile_size)) pygame.draw.line(screen, (255, 255, 255), (line * tile_size, 0), (line * tile_size, screen_height)) class Player(): def __init__(self, x, y): # Initialize player with different direction images self.images_right = [] self.images_left = [] self.images_up = [] self.images_down = [] self.index = 0 self.counter = 0 # Load right and left facing images for num in range(1, 5): img_right = pygame.image.load(f'sprites/right{num}.png') img_right = pygame.transform.scale(img_right, (40, 40)) img_left = pygame.transform.flip(img_right, True, False) self.images_right.append(img_right) self.images_left.append(img_left) # Load up facing images for num in range(1, 5): img_up = pygame.image.load(f'sprites/up{num}.png') img_up = pygame.transform.scale(img_up, (40, 40)) self.images_up.append(img_up) # Load down facing images for num in range(1, 5): img_down = pygame.image.load(f'sprites/down{num}.png') img_down = pygame.transform.scale(img_down, (40, 40)) self.images_down.append(img_down) # Set initial player image and position self.image = self.images_right[self.index] self.rect = self.image.get_rect() self.rect.x = x self.rect.y = y self.width = self.image.get_width() self.height = self.image.get_height() self.direction = 0 self.coin_counter = 0 def update(self): # Update player movement and animation dx = 0 dy = 0 walk_cooldown = 10 key = pygame.key.get_pressed() # Handle horizontal and vertical movement if key[pygame.K_a]: dx -= 2 self.direction = -1 self.counter += 1 if key[pygame.K_d]: dx += 2 self.direction = 1 self.counter += 1 if key[pygame.K_w]: dy -= 2 self.direction = 2 self.counter += 1 if key[pygame.K_s]: dy += 2 self.direction = -2 self.counter += 1 if key[pygame.K_a] and key[pygame.K_w]: self.direction = -1 if key[pygame.K_a] and key[pygame.K_s]: self.direction = -1 if key[pygame.K_d] and key[pygame.K_w]: self.direction = 1 if key[pygame.K_d] and key[pygame.K_s]: self.direction = 1 # Reset animation if no movement if not key[pygame.K_a] and not key[pygame.K_d] and not key[pygame.K_w] and not key[pygame.K_s]: self.counter = 0 self.index = 0 # Set player image based on direction if self.direction == 1: self.image = self.images_right[self.index] if self.direction == -1: self.image = self.images_left[self.index] if self.direction == 2: self.image = self.images_up[self.index] if self.direction == -2: self.image = self.images_down[self.index] # Update animation frame if self.counter > walk_cooldown: self.counter = 0 self.index += 1 if self.index >= len(self.images_right): self.index = 0 if self.direction == 1: self.image = self.images_right[self.index] if self.direction == -1: self.image = self.images_left[self.index] if self.direction == 2: self.image = self.images_up[self.index] if self.direction == -2: self.image = self.images_down[self.index] # Handle collision with tiles for tile in world.tile_list: if tile[1].colliderect(self.rect.x + dx, self.rect.y, self.width, self.height): dx = 0 if tile[1].colliderect(self.rect.x, self.rect.y + dy, self.width, self.height): if dy < 0: dy = tile[1].bottom - self.rect.top if dy > 0: dy = tile[1].top - self.rect.bottom # Handle collision with crates for crate in crate_group: # Create temporary rectangles for collision detection player_rect_next_x = self.rect.copy() player_rect_next_x.x += dx player_rect_next_y = self.rect.copy() player_rect_next_y.y += dy if crate.rect.colliderect(player_rect_next_x): # Check if crate can be pushed and handle chain reaction if dx > 0: if crate.push(2, 0): crate.rect.x += 2 else: dx = 0 elif dx < 0: if crate.push(-2, 0): crate.rect.x -= 2 else: dx = 0 if crate.rect.colliderect(player_rect_next_y): # Check if crate can be pushed and handle chain reaction if dy > 0: if crate.push(0, 2): crate.rect.y += 2 else: dy = 0 elif dy < 0: if crate.push(0, -2): crate.rect.y -= 2 else: dy = 0 # Check collision with coins coins_collected = pygame.sprite.spritecollide(self, coin_group, True) for coin in coins_collected: self.coin_counter += 1 # Check collision with portal for portal in portal_group: if portal.visible == True: if self.rect.colliderect(portal.rect): print('shit') # Check collision with button for button in button_group: if self.rect.colliderect(button.rect): button.activate() # Update player position self.rect.x += dx self.rect.y += dy # Draw player on the screen screen.blit(self.image, self.rect) self.draw_coin_counter() def draw_coin_counter(self): font = pygame.font.Font(None, 36) text_surface = font.render(f'Coins: {self.coin_counter}', True, (255, 255, 255)) screen.blit(text_surface, (screen_width - 150, 20)) class Crate(pygame.sprite.Sprite): def __init__(self, x, y): pygame.sprite.Sprite.__init__(self) img = pygame.image.load('sprites/crate1.jpg') self.image = pygame.transform.scale(img, (tile_size, tile_size)) self.rect = self.image.get_rect() self.rect.x = x self.rect.y = y def update(self): screen.blit(self.image, self.rect) for button in button_group: if self.rect.colliderect(button.rect): button.activate() def check_collision(self, tiles, x, y): for tile in tiles: if tile[1].colliderect(pygame.Rect(x, y, self.rect.width, self.rect.height)): return True return False def check_crate_collision(self, x, y): for crate in crate_group: if crate != self and crate.rect.colliderect(pygame.Rect(x, y, self.rect.width, self.rect.height)): return crate return None def push(self, dx, dy): crates_to_push = [(self, dx, dy)] visited = set() while crates_to_push: crate, dx, dy = crates_to_push.pop(0) next_x = crate.rect.x + dx next_y = crate.rect.y + dy if (crate, dx, dy) in visited: continue if crate.check_collision(world.tile_list, next_x, next_y): return False next_crate = crate.check_crate_collision(next_x, next_y) if next_crate: crates_to_push.append((next_crate, dx, dy)) else: crate.rect.x = next_x crate.rect.y = next_y visited.add((crate, dx, dy)) return True class Coin(pygame.sprite.Sprite): def __init__(self, x, y): pygame.sprite.Sprite.__init__(self) # Load all 6 images for animation self.animation_images = [] for i in range(1, 7): img = pygame.image.load(f'sprites/coin{i}.png') img = pygame.transform.scale(img, (tile_size // 1.5, tile_size // 1.5)) self.animation_images.append(img) self.current_frame = 0 self.image = self.animation_images[self.current_frame] self.rect = self.image.get_rect() self.rect.center = (x, y) self.animation_delay = 11 self.frame_count = 0 def update(self): # Update animation frame self.frame_count += 1 if self.frame_count >= self.animation_delay: self.frame_count = 0 self.current_frame = (self.current_frame + 1) % len(self.animation_images) self.image = self.animation_images[self.current_frame] class Button(pygame.sprite.Sprite): def __init__(self, x, y, player, portal): pygame.sprite.Sprite.__init__(self) self.image_off = pygame.image.load('sprites/button1.png') self.image_on = pygame.image.load('sprites/button2.png') self.image_off = pygame.transform.scale(self.image_off, (tile_size * 2, int(tile_size * 2))) self.image_on = pygame.transform.scale(self.image_on, (tile_size * 2, int(tile_size * 2))) self.image = self.image_off self.rect = self.image.get_rect() self.rect.x = x self.rect.y = y self.active = False self.crate_group = crate_group self.player = player self.portal = portal def activate(self): if not self.active: self.active = True self.image = self.image_on self.portal.set_visible(True) # Show the portal def deactivate(self): if self.active: self.active = False self.image = self.image_off self.portal.set_visible(False) # Hide the portal def update(self): crate_collision = any(self.rect.colliderect(crate.rect) for crate in self.crate_group) player_collision = self.rect.colliderect(self.player.rect) if crate_collision or player_collision: self.activate() else: self.deactivate() class Portal(pygame.sprite.Sprite): def __init__(self, x, y): pygame.sprite.Sprite.__init__(self) self.animation_images = [] for i in range(1, 5): img = pygame.image.load(f'sprites/p{i}.png') img = pygame.transform.scale(img, (tile_size * 2, tile_size * 2)) self.animation_images.append(img) self.current_frame = 0 self.image = self.animation_images[self.current_frame] self.rect = self.image.get_rect() self.rect.center = (x, y) self.animation_delay = 15 self.frame_count = 0 self.visible = False # Start with the portal invisible def update(self): if not self.visible: return # Skip update if not visible self.frame_count += 1 if self.frame_count >= self.animation_delay: self.frame_count = 0 self.current_frame = (self.current_frame + 1) % len(self.animation_images) self.image = self.animation_images[self.current_frame] def set_visible(self, visible): self.visible = visible if not visible: portal_group.remove(portal) # Remove the portal from the sprite group to disable collisions else: portal_group.add(self) # Add the portal to the sprite group to enable collisions def draw(self, surface): for button in button_group: if button.active: surface.blit(self.image, self.rect) # Update the World class to use these updated Button and Portal classes class World(): def __init__(self, data, player): self.tile_list = [] block_img = pygame.image.load('sprites/block.jpg') row_count = 0 # First pass: create static objects and collect portal positions portal_positions = {} for row in data: col_count = 0 for tile in row: if tile == 1: img = pygame.transform.scale(block_img, (tile_size, tile_size)) img_rect = img.get_rect() img_rect.x = col_count * tile_size img_rect.y = row_count * tile_size tile = (img, img_rect) self.tile_list.append(tile) if tile == 2: crate = Crate(col_count * tile_size, row_count * tile_size) crate_group.add(crate) if tile == 3: coin = Coin(col_count * tile_size, row_count * tile_size) coin_group.add(coin) if tile == 4: portal_positions[(col_count, row_count)] = (col_count * tile_size, row_count * tile_size) col_count += 1 row_count += 1 # Create portals after collecting their positions for (col, row), (x, y) in portal_positions.items(): portal = Portal(x, y) portal_group.add(portal) # Second pass: create buttons row_count = 0 for row in data: col_count = 0 for tile in row: if tile == 5: # Find the nearest portal portal = self.find_nearest_portal(col_count, row_count, portal_positions) button = Button(col_count * tile_size, row_count * tile_size, player, portal) button_group.add(button) col_count += 1 row_count += 1 def find_nearest_portal(self, col, row, portal_positions): nearest_portal = None min_distance = float('inf') for (p_col, p_row), (x, y) in portal_positions.items(): distance = abs(col - p_col) + abs(row - p_row) if distance < min_distance: nearest_portal = portal_group.sprites()[list(portal_positions.keys()).index((p_col, p_row))] min_distance = distance return nearest_portal def draw(self): for tile in self.tile_list: screen.blit(tile[0], tile[1]) # Initialize sprite groups crate_group = pygame.sprite.Group() coin_group = pygame.sprite.Group() portal_group = pygame.sprite.Group() button_group = pygame.sprite.Group() # Load in level data and create world if path.exists(f'level{level}_data'): pickle_in = open(f'level{level}_data', 'rb') world_data = pickle.load(pickle_in) # Create player object player = Player(100, screen_height - 90) world = World(world_data, player) # Main game loop while run: clock.tick(fps) screen.blit(bg_img, (0, 0)) world.draw() button_group.update() button_group.draw(screen) crate_group.update() crate_group.draw(screen) coin_group.update() coin_group.draw(screen) for portal in portal_group: if portal.visible: portal.update() portal.draw(screen) player.update() for event in pygame.event.get(): if event.type == pygame.QUIT: run = False pygame.display.update() pygame.quit()