import pygame from pygame.locals import * import pickle import os import random from os import path pygame.init() #define colours BG = (144, 201, 120) RED = (255, 0, 0) WHITE = (255, 255, 255) GREEN = (0, 255, 0) BLACK = (0, 0, 0) PINK = (235, 65, 54) BLUE = (0, 0, 225) #load images bg_img = pygame.image.load('img/background.png') bg_game_img = pygame.image.load('img/sky.png') start_img = pygame.image.load('img/start_btn.png') exit_img = pygame.image.load('img/exit_btn.png') slime_img = pygame.image.load('img/slime.png') coin_img = pygame.image.load('img/coin.png') screen_width = 900 screen_height = 900 #Scale the images scaled_start_img = pygame.transform.scale(start_img, (start_img.get_width() * 2.5, start_img.get_height() * 2.5)) scaled_exit_img = pygame.transform.scale(exit_img, (exit_img.get_width() * 2.5, exit_img.get_height() * 2.5)) scaled_bg_game_img = pygame.transform.scale(bg_img, (screen_width, screen_height )) clock = pygame.time.Clock() fps = 60 screen = pygame.display.set_mode((screen_width, screen_height)) pygame.display.set_caption('Platformer') #define game variables TILE_SIZE = 50 MAX_LEVELS = 3 main_menu = True game_over = 0 score = 0 level = 1 required_coins = 4 initial_score = score game_score = 0 #Initialize the font using the font path font = pygame.font.Font('ARCADECLASSIC.TTF', 125) font_score = pygame.font.Font('ARCADECLASSIC.TTF', 30) #define player action variables moving_left = False moving_right = False shoot = False grenade = False grenade_thrown = False # Define the number of required coins for each level required_coins_dict = { 1: 3, 2: 4, 3: 5, } # High score file HIGH_SCORE_FILE = "high_score.txt" def load_high_score(): """Load the high score from a file, if it exists. Return 0 if there is an error or the file doesn't exist.""" if os.path.exists(HIGH_SCORE_FILE): with open(HIGH_SCORE_FILE, 'r') as file: try: return int(file.read().strip()) except ValueError: return 0 return 0 def save_high_score(high_score): """Save the current high score to a file.""" with open(HIGH_SCORE_FILE, 'w') as file: file.write(str(high_score)) coin_group = pygame.sprite.Group() exit_group = pygame.sprite.Group() def draw_text(text, font, text_col, x, y): img = font.render(text, True, text_col) screen.blit(img, (x, y)) def draw_bg_menu(): scaled_bg_img = pygame.transform.scale(bg_img, (1000, 1000)) screen.blit(scaled_bg_img, (0, 0)) def draw_bg_game(): scaled_bg_game_img = pygame.transform.scale(bg_game_img, (1000, 1000)) screen.blit(scaled_bg_game_img, (0, 0)) #function to reset level def reset_level(level): global score, required_coins required_coins = required_coins_dict.get(level, 4) # Default to 4 if level is not in dictionary # Store the initial score at the start of the level initial_score = score player.reset(100, screen_height - 130) slime_group.empty() platform_group.empty() coin_group.empty() spike_group.empty() exit_group.empty() #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) world = World(world_data) #create dummy coin for showing the score score_coin = Coin(TILE_SIZE // 2 - 17, TILE_SIZE // 2 - 13) coin_group.add(score_coin) return world def draw_end_screen(player_score, high_score): # Clear the screen draw_bg_menu() # Display text for end screen end_text = font_score.render("Congratulations! You completed the game", True, BLUE) screen.blit(end_text, (150, 200)) # Display player's score player_score_text = font_score.render(f"Your Score: {game_score}", True, WHITE) screen.blit(player_score_text, (335, 375)) # Display high score high_score_text = font_score.render(f"High Score: {high_score}", True, WHITE) screen.blit(high_score_text, (335, 300)) # Display additional instructions or options if needed # Flip the display to update the screen pygame.display.flip() # Coin class class Coin(pygame.sprite.Sprite): def __init__(self, x=None, y=None): super().__init__() self.image = pygame.transform.scale(coin_img,(TILE_SIZE // 2 , TILE_SIZE // 2 )) self.rect = self.image.get_rect() if x is not None and y is not None: self.rect.x = x self.rect.y = y def update(self): pass class Button(): def __init__(self, x, y, image): self.image = image self.rect = self.image.get_rect() self.rect.x = x self.rect.y = y self.clicked = False def draw(self): action = False #get mouse position pos = pygame.mouse.get_pos() #check mouseover and clicked conditions if self.rect.collidepoint(pos): if pygame.mouse.get_pressed()[0] == 1 and self.clicked == False: action = True self.clicked = True if pygame.mouse.get_pressed()[0] == 0: self.clicked = False #draw button screen.blit(self.image, self.rect) return action class Player(): def __init__(self, x, y): self.reset(x, y) def update(self, game_over): dx = 0 dy = 0 walk_cooldown = 5 col_thresh = 20 if game_over == 0: #get keypresses key = pygame.key.get_pressed() if key[pygame.K_SPACE] and self.jumped == False and self.in_air == False: self.vel_y = -16 self.jumped = True if key[pygame.K_SPACE] == False: self.jumped = False if key[pygame.K_a]: dx -= 5 self.counter += 1 self.direction = -1 if key[pygame.K_d]: dx += 5 self.counter += 1 self.direction = 1 if key[pygame.K_a] == False and key[pygame.K_d] == False: self.counter = 0 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] #handle animation 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] #add gravity self.vel_y += 1 if self.vel_y > 10: self.vel_y = 10 dy += self.vel_y #check for collision self.in_air = True for tile in world.tile_list: #check for collision in x direction if tile[1].colliderect(self.rect.x + dx, self.rect.y, self.width, self.height): dx = 0 #check for collision in y direction if tile[1].colliderect(self.rect.x, self.rect.y + dy, self.width, self.height): #check if below the ground i.e. jumping if self.vel_y < 0: dy = tile[1].bottom - self.rect.top self.vel_y = 0 #check if above the ground i.e. falling elif self.vel_y >= 0: dy = tile[1].top - self.rect.bottom self.vel_y = 0 self.in_air = False # Prevent the player from going off the screen if self.rect.left + dx < 0: dx = -self.rect.left if self.rect.right + dx > screen_width: dx = screen_width - self.rect.right #check for collision with enemies if pygame.sprite.spritecollide(self, slime_group, False): game_over = -1 #check for collision with lava if pygame.sprite.spritecollide(self, spike_group, False): game_over = -1 #check for collision with exit if pygame.sprite.spritecollide(self, exit_group, False) and score >= required_coins: game_over = 1 #check for collision with platforms for platform in platform_group: #collision in the x direction if platform.rect.colliderect(self.rect.x + dx, self.rect.y, self.width, self.height): dx = 0 #collision in the y direction if platform.rect.colliderect(self.rect.x, self.rect.y + dy, self.width, self.height): #check if below platform if abs((self.rect.top + dy) - platform.rect.bottom) < col_thresh: self.vel_y = 0 dy = platform.rect.bottom - self.rect.top #check if above platform elif abs((self.rect.bottom + dy) - platform.rect.top) < col_thresh: self.rect.bottom = platform.rect.top - 1 self.in_air = False dy = 0 #move sideways with the platform if platform.move_x != 0: self.rect.x += platform.move_direction #update player coordinates self.rect.x += dx self.rect.y += dy elif game_over == -1: self.image = self.dead_image draw_text('GAME OVER!', font, BLUE, 165, 200) #draw player onto screen screen.blit(self.image, self.rect) return game_over def reset(self, x, y): self.images_right = [] self.images_left = [] self.index = 0 self.counter = 0 for num in range(0, 4): img_right = pygame.image.load(f'img/Run/{num}.png') img_right = pygame.transform.scale(img_right, (30, 70)) img_left = pygame.transform.flip(img_right, True, False) self.images_right.append(img_right) self.images_left.append(img_left) self.dead_image = pygame.image.load('img/Death/0.png') #Scale the death image self.dead_image = pygame.transform.scale(self.dead_image, (40, 80)) 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.vel_y = 0 self.jumped = False self.direction = 0 self.in_air = True class World(): def __init__(self, data): self.tile_list = [] #load images grass_img = pygame.image.load('img/grass.png') row_count = 0 for row in data: col_count = 0 for tile in row: if tile == 1: img = pygame.transform.scale(grass_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: slime = Enemy(col_count * TILE_SIZE, row_count * TILE_SIZE + 15) slime_group.add(slime) if tile == 3: spike = Spike(col_count * TILE_SIZE, row_count * TILE_SIZE + (TILE_SIZE // 2)) spike_group.add(spike) if tile == 4: coin = Coin(col_count * TILE_SIZE + (TILE_SIZE // 2), row_count * TILE_SIZE + (TILE_SIZE // 2)) coin_group.add(coin) if tile == 5: exit = Exit(col_count * TILE_SIZE, row_count * TILE_SIZE - (TILE_SIZE // 2)) exit_group.add(exit) if tile == 6: platform = Platform(col_count * TILE_SIZE, row_count * TILE_SIZE, 0, 1) platform_group.add(platform) if tile == 7: platform = Platform(col_count * TILE_SIZE, row_count * TILE_SIZE, 1, 0) platform_group.add(platform) col_count += 1 row_count += 1 def draw(self): for tile in self.tile_list: screen.blit(tile[0], tile[1]) class Enemy(pygame.sprite.Sprite): def __init__(self, x, y): pygame.sprite.Sprite.__init__(self) self.image = pygame.transform.scale(slime_img, (TILE_SIZE , int(TILE_SIZE * 0.70))) self.rect = self.image.get_rect() self.rect.x = x self.rect.y = y self.move_direction = 1 self.move_counter = 0 def update(self): self.rect.x += self.move_direction self.move_counter += 1 if abs(self.move_counter) > 50: self.move_direction *= -1 self.move_counter *= -1 class Platform(pygame.sprite.Sprite): def __init__(self, x, y, move_x, move_y): pygame.sprite.Sprite.__init__(self) img = pygame.image.load('img/grass.png') self.image = pygame.transform.scale(img, (TILE_SIZE, TILE_SIZE // 2)) self.rect = self.image.get_rect() self.rect.x = x self.rect.y = y self.move_counter = 0 self.move_direction = 1 self.move_x = move_x self.move_y = move_y def update(self): self.rect.x += self.move_direction * self.move_x self.rect.y += self.move_direction * self.move_y self.move_counter += 1 if abs(self.move_counter) > 50: self.move_direction *= -1 self.move_counter *= -1 class Spike(pygame.sprite.Sprite): def __init__(self, x, y): pygame.sprite.Sprite.__init__(self) img = pygame.image.load('img/spikes.png') self.image = pygame.transform.scale(img, (TILE_SIZE, TILE_SIZE // 2)) self.rect = self.image.get_rect() self.rect.x = x self.rect.y = y class Exit(pygame.sprite.Sprite): def __init__(self, x, y): pygame.sprite.Sprite.__init__(self) img = pygame.image.load('img/exit.png') self.image = pygame.transform.scale(img, (TILE_SIZE, int(TILE_SIZE * 1.5))) self.rect = self.image.get_rect() self.rect.x = x self.rect.y = y # Sprite groups all_sprites = pygame.sprite.Group() coins = pygame.sprite.Group() #create buttons restart_button = Button(335, screen_height // 2, scaled_start_img) start_button = Button(150, screen_height / 2, scaled_start_img) exit_button = Button(550, screen_height / 2, scaled_exit_img) # Create dummy coin for showing the score score_coin = Coin() score_coin.rect.x = TILE_SIZE // 2 score_coin.rect.y = TILE_SIZE // 2 all_sprites.add(score_coin) # Add to all_sprites group instead of coin_group player = Player(100, screen_height - 130) slime_group = pygame.sprite.Group() platform_group = pygame.sprite.Group() spike_group = pygame.sprite.Group() coin_group = pygame.sprite.Group() exit_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) world = World(world_data) run = True while run: clock.tick(fps) high_score = load_high_score() # Load the high score from the file at the start # Update all sprites all_sprites.update() draw_bg_game() # Check for coin collection coins_collected = pygame.sprite.spritecollide(player, coin_group, True) if coins_collected: game_score += len(coins_collected) # Increase the score by the number of coins collected score += len(coins_collected) # Increase the score by the number of coins collected if game_score > high_score: # Check if the current score is higher than the high score high_score = game_score # Update the high score save_high_score(high_score) # Save the new high score to the file print(f"Collected {len(coins_collected)} coins. Score: {score}. High Score: {high_score}") if main_menu == True: draw_bg_menu() if exit_button.draw(): run = False if start_button.draw(): main_menu = False world = reset_level(level) else: world.draw() if game_over == 0: slime_group.update() platform_group.update() #update score #check if a coin has been collected if pygame.sprite.spritecollide(player, coin_group, True): score += 1 game_score += 1 draw_text('X ' + str(score), font_score, WHITE, TILE_SIZE - 10, 10) slime_group.draw(screen) platform_group.draw(screen) spike_group.draw(screen) coin_group.draw(screen) exit_group.draw(screen) game_over = player.update(game_over) #if player has died if game_over == -1: if restart_button.draw(): world_data = [] world = reset_level(level) game_over = 0 score = initial_score game_score = initial_score #if player has completed the level if game_over == 1: #reset game and go to next level level += 1 if level <= MAX_LEVELS: #reset level world_data = [] world = reset_level(level) score = initial_score game_over = 0 else: draw_text('YOU WIN!', font, BLUE, 185, 200) draw_end_screen(score, high_score) if restart_button.draw(): level = 1 #reset level world_data = [] world = reset_level(level) game_over = 0 for event in pygame.event.get(): if event.type == pygame.QUIT: run = False pygame.display.update() pygame.quit()