import pygame
import random
import math
import os
import sys
def resource_path(relative_path):
try:
base_path = sys._MEIPASS
except Exception:
base_path = os.path.abspath(".")
return os.path.join(base_path, relative_path)
class MazeRunnerGame:
def __init__(self):
pygame.init()
self.unit_size = 40
self.width, self.height = 19 * self.unit_size, 15 * self.unit_size
self.window = pygame.display.set_mode((self.width, self.height))
pygame.display.set_caption("Maze Runner")
self.image_folder = 'images'
self.player_img = self.load_image('bae.png')
self.enemy_img = self.load_random_enemy_image()
self.maze = self.generate_maze()
self.enemies = []
self.player_pos = [self.unit_size, self.unit_size]
self.collectibles = []
self.score = 0
self.goal_pos = self.select_goal_position()
self.enemy_delay = 200
self.enemy_timer = 0
self.enemy_cooldown_time = 1000
self.lives = 3
self.run = True
self.can_pass_walls = False
self.pass_walls_timer = 0
self.spawn_collectibles(5) # Spawn initial collectibles
self.spawn_enemies() # Ensure enemies are spawned
self.attack_mode = False
self.attack_mode_timer = 0
self.red_square_pos = None # This will be set by spawn_red_square
self.spawn_red_square()
self.check_red_square_collision()
self.draw_red_square()
pygame.font.init() # Initialize the font module
self.font = pygame.font.SysFont("arial", 24) # Choose an appropriate font and size
def load_image(self, filename):
filepath = resource_path(os.path.join(self.image_folder, filename))
img = pygame.image.load(filepath)
return pygame.transform.scale(img, (self.unit_size, self.unit_size))
def load_random_enemy_image(self):
enemy_images = ['huh.png', 'tower.png', 'chikaa.png', 'profv2.png']
chosen_image = random.choice(enemy_images)
return self.load_image(chosen_image)
def generate_maze(self):
maze_width, maze_height = self.width // self.unit_size, self.height // self.unit_size
maze = [['#' for _ in range(maze_width)] for _ in range(maze_height)]
def dfs(x, y):
directions = [(1, 0), (-1, 0), (0, 1), (0, -1)]
random.shuffle(directions)
for dx, dy in directions:
nx, ny = x + 2 * dx, y + 2 * dy
if 0 <= nx < maze_width and 0 <= ny < maze_height and maze[ny][nx] == '#':
maze[y + dy][x + dx] = ' '
maze[ny][nx] = ' '
dfs(nx, ny)
start_x, start_y = random.randrange(1, maze_width, 2), random.randrange(1, maze_height, 2)
dfs(start_x, start_y)
return maze
def spawn_red_square(self):
maze_width, maze_height = self.width // self.unit_size, self.height // self.unit_size
valid_spawn = False
while not valid_spawn:
x = random.randint(1, maze_width - 2) * self.unit_size
y = random.randint(1, maze_height - 2) * self.unit_size
if self.maze[y // self.unit_size][x // self.unit_size] == ' ':
self.red_square_pos = (x, y)
valid_spawn = True
def check_red_square_collision(self):
if self.red_square_pos and math.dist(self.player_pos, self.red_square_pos) < self.unit_size:
self.attack_mode = True
self.attack_mode_timer = 2000 # around 9 seconds of attack mode
self.red_square_pos = None # Remove the current red square
# Respawn the red square at a new location
self.spawn_red_square()
self.spawn_enemies() # Ensure enemies are spawned after reaching the goal if needed
def attack_enemies(self):
if not self.attack_mode:
return # Exit if not in attack mode
for enemy in self.enemies[:]:
if math.dist(self.player_pos, enemy['position']) < self.unit_size:
self.enemies.remove(enemy) # Remove the enemy on collision
self.score += 100 # Increase the score by 100 for each enemy killed
def draw_red_square(self):
if self.red_square_pos:
pygame.draw.rect(self.window, (255, 0, 0), (*self.red_square_pos, self.unit_size, self.unit_size))
def select_goal_position(self):
maze_width, maze_height = self.width // self.unit_size, self.height // self.unit_size
valid_positions = [(x, y) for x in range(1, maze_width - 1) for y in range(1, maze_height - 1) if self.maze[y][x] == ' ']
goal_pos = random.choice(valid_positions)
return goal_pos[0] * self.unit_size, goal_pos[1] * self.unit_size
def move_entity(self, entity, direction):
x, y = entity
next_x, next_y = x, y
# Determine the next position based on the direction
if direction == "LEFT":
next_x -= self.unit_size
elif direction == "RIGHT":
next_x += self.unit_size
elif direction == "UP":
next_y -= self.unit_size
elif direction == "DOWN":
next_y += self.unit_size
# Check if the entity is the player and if they can pass through walls
is_player = entity == self.player_pos
can_move_through_walls = is_player and self.can_pass_walls and self.pass_walls_timer > 0
# Allow moving through walls only if it's the player with the ability activated
if can_move_through_walls or (0 <= next_x < self.width and 0 <= next_y < self.height and self.maze[next_y // self.unit_size][next_x // self.unit_size] != '#'):
x, y = next_x, next_y
return [x, y]
def spawn_enemies(self):
maze_width, maze_height = self.width // self.unit_size, self.height // self.unit_size
for _ in range(3): # Adjust the number as needed
valid_spawn = False
while not valid_spawn:
x = random.randint(0, maze_width - 1) * self.unit_size
y = random.randint(0, maze_height - 1) * self.unit_size
if self.maze[y // self.unit_size][x // self.unit_size] == ' ':
self.enemies.append({
'position': [x, y],
'cooldown_timer': 0,
'movement_cooldown': 100 # Movement cooldown in milliseconds or game loops
})
valid_spawn = True
def spawn_collectibles(self, count):
while len(self.collectibles) < count:
maze_width, maze_height = self.width // self.unit_size, self.height // self.unit_size
valid_positions = [(x, y) for x in range(maze_width) for y in range(maze_height) if self.maze[y][x] == ' ']
collectible_pos = random.choice(valid_positions)
self.collectibles.append((collectible_pos[0] * self.unit_size, collectible_pos[1] * self.unit_size))
def check_collectibles_collision(self):
for collectible in self.collectibles[:]:
if math.dist(self.player_pos, collectible) < self.unit_size:
self.collectibles.remove(collectible)
self.score += 10
if len(self.collectibles) < 5:
self.spawn_collectibles(5) # Maintain a minimum of 5 collectibles
def draw_lives(self):
font = pygame.font.SysFont(None, 24)
lives_text = font.render(f'Lives: {self.lives}', True, (255, 255, 255))
self.window.blit(lives_text, (10, 10))
def draw_score(self):
font = pygame.font.SysFont(None, 24)
score_text = font.render(f'Score: {self.score}', True, (255, 255, 0))
self.window.blit(score_text, (self.width - 120, 10))
def draw_collectibles(self):
for collectible in self.collectibles:
pygame.draw.rect(self.window, (255, 255, 0), (*collectible, self.unit_size, self.unit_size))
def check_collision(self):
if self.attack_mode:
# Checks if player got the attack power up, if so the player takes no damage
return
for enemy in self.enemies:
if (self.player_pos[0] < enemy['position'][0] + self.unit_size and
self.player_pos[0] + self.unit_size > enemy['position'][0] and
self.player_pos[1] < enemy['position'][1] + self.unit_size and
self.player_pos[1] + self.unit_size > enemy['position'][1]):
if enemy['cooldown_timer'] == 0:
self.lives -= 1
enemy['cooldown_timer'] = self.enemy_cooldown_time
def get_chase_direction(self, enemy_position):
player_x, player_y = self.player_pos
enemy_x, enemy_y = enemy_position
if abs(player_x - enemy_x) > abs(player_y - enemy_y):
return "LEFT" if player_x < enemy_x else "RIGHT"
else:
return "UP" if player_y < enemy_y else "DOWN"
def update_enemy_positions(self):
for enemy in self.enemies:
# Decrease the cooldown timer if it's above 0
if enemy['movement_cooldown'] > 0:
enemy['movement_cooldown'] -= 1
continue # Skip moving this enemy if it's still cooling down
# Reset the cooldown timer after the enemy moves
enemy['movement_cooldown'] = 100 # Reset to its original value or another value for variable speeds
# logic to decide movement (random or chasing)
if random.random() < 0.5: # 50% chance to chase
chase_direction = self.get_chase_direction(enemy['position'])
enemy['position'] = self.move_entity(enemy['position'], chase_direction)
else:
direction = random.choice(["LEFT", "RIGHT", "UP", "DOWN"])
enemy['position'] = self.move_entity(enemy['position'], direction)
def update_attack_mode(self):
if self.attack_mode and self.attack_mode_timer > 0:
self.attack_mode_timer -= 1
elif self.attack_mode_timer <= 0:
self.attack_mode = False
def game_loop(self):
while self.run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.run = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_a: # Change from K_LEFT to K_a
self.player_pos = self.move_entity(self.player_pos, "LEFT")
elif event.key == pygame.K_d: # Change from K_RIGHT to K_d
self.player_pos = self.move_entity(self.player_pos, "RIGHT")
elif event.key == pygame.K_w: # Change from K_UP to K_w
self.player_pos = self.move_entity(self.player_pos, "UP")
elif event.key == pygame.K_s: # Change from K_DOWN to K_s
self.player_pos = self.move_entity(self.player_pos, "DOWN")
self.enemy_timer += 1
if self.enemy_timer >= self.enemy_delay:
self.enemy_timer = 0
for enemy in self.enemies:
direction = random.choice(["LEFT", "RIGHT", "UP", "DOWN"])
enemy['position'] = self.move_entity(enemy['position'], direction)
for enemy in self.enemies:
if enemy['cooldown_timer'] > 0:
enemy['cooldown_timer'] -= 1
self.update_enemy_positions()
self.check_collision()
self.check_collectibles_collision()
if math.dist(self.player_pos, self.goal_pos) < self.unit_size:
self.can_pass_walls = True
self.pass_walls_timer = 1500 # around 5 seconds
self.goal_pos = self.select_goal_position()
self.spawn_enemies() # Ensure enemies are spawned after reaching the goal if needed
if self.can_pass_walls:
self.pass_walls_timer -= 1
if self.pass_walls_timer <= 0:
self.can_pass_walls = False
if self.can_pass_walls:
pass_walls_text = self.font.render("Wall Pass Active", True, (0, 255, 0))
self.window.blit(pass_walls_text, (10, self.height - 40))
if self.attack_mode:
attack_mode_text = self.font.render("Attack Mode Active", True, (255, 0, 0))
self.window.blit(attack_mode_text, (10, self.height - 70))
pygame.display.update()
self.update_attack_mode()
self.window.fill((255, 255, 255))
for i in range(len(self.maze)):
for j in range(len(self.maze[i])):
if self.maze[i][j] == '#':
pygame.draw.rect(self.window, (0, 0, 0), (j * self.unit_size, i * self.unit_size, self.unit_size, self.unit_size))
self.window.blit(self.player_img, self.player_pos)
for enemy in self.enemies:
self.window.blit(self.enemy_img, enemy['position'])
pygame.draw.rect(self.window, (0, 255, 0), (*self.goal_pos, self.unit_size, self.unit_size)) # Draw goal
self.draw_lives()
self.draw_score()
self.draw_collectibles()
self.check_red_square_collision() # Check collision with the red square
self.attack_enemies() # Check if player attacks enemies
# Rendering section before display update
self.draw_red_square() # Draw the red square
pygame.display.update()
if self.lives <= 0:
print("Game over! You ran out of lives.")
self.run = False
if __name__ == "__main__":
game = MazeRunnerGame()
game.game_loop()
pygame.quit()