diff --git a/assets/cherry.png b/assets/cherry.png new file mode 100644 index 0000000..cb902c4 Binary files /dev/null and b/assets/cherry.png differ diff --git a/hud.py b/hud.py new file mode 100644 index 0000000..ea6239a --- /dev/null +++ b/hud.py @@ -0,0 +1,85 @@ +import pygame +import os +import math +from actors.enums import Colors +from actors.pacman import ActorPacman + +class HUD: + def __init__(self, screen_width, screen_height, font_size=24, highscore_file="highscore.txt", initial_lives=3, cherry_image=None): + self.score = 0 + self.lives = initial_lives + self.p_man = [] + self.highscore_file = highscore_file + self.highscore = self.load_highscore() + + self.font = pygame.font.Font(None, font_size) + self.color = Colors.WHITE.value + self.screen_width = screen_width + self.screen_height = screen_height + + self.padding = 10 + self.life_radius = 10 + self.life_spacing = 30 + + self.cherry_image = pygame.transform.scale(cherry_image, (24, 24)) if cherry_image else None + + def load_highscore(self): + if os.path.exists(self.highscore_file): + with open(self.highscore_file, 'r') as file: + try: + return int(file.read()) + except ValueError: + return 0 + return 0 + + def save_highscore(self): + with open(self.highscore_file, 'w') as file: + file.write(str(self.highscore)) + + def add_points(self, points): + self.score += points + if self.score > self.highscore: + self.highscore = self.score + self.save_highscore() + + def lose_life(self): + self.lives = max(0, self.lives - 1) + + def reset(self, reset_score=True, reset_lives=True): + if reset_score: + self.score = 0 + if reset_lives: + self.lives = 3 + + def draw_pacman_icon(self, screen, center): + start_angle = math.radians(30) + end_angle = math.radians(330) + rect = pygame.Rect(0, 0, self.life_radius * 2, self.life_radius * 2) + rect.center = center + # Mouth: draw arc first, then overlay circle to mask + pygame.draw.arc(screen, (255, 255, 0), rect, start_angle, end_angle, self.life_radius) + pygame.draw.circle(screen, (255, 255, 0), center, self.life_radius) + + def draw(self, screen): + # Score (top-left) + score_text = self.font.render(f"Score: {self.score}", True, self.color) + screen.blit(score_text, (self.padding, self.padding)) + + # Highscore (top-center) + highscore_text = self.font.render(f"High Score: {self.highscore}", True, self.color) + highscore_rect = highscore_text.get_rect(midtop=(self.screen_width // 2, self.padding)) + screen.blit(highscore_text, highscore_rect) + + # Lives (bottom-left) + for i in range(self.lives): + x = self.padding + i * self.life_spacing + y = self.screen_height - self.padding - self.life_radius + self.p_man.append(ActorPacman(screen, (x + self.life_radius, y))) + self.p_man[i].draw() + #self.draw_pacman_icon(screen, (x + self.life_radius, y)) + + # Level indicator (bottom-right) + if self.cherry_image: + cherry_x = self.screen_width - self.padding - self.cherry_image.get_width() + cherry_y = self.screen_height - self.padding - self.cherry_image.get_height() + screen.blit(self.cherry_image, (cherry_x, cherry_y)) diff --git a/labyrinth.py b/labyrinth.py deleted file mode 100644 index 0251e4c..0000000 --- a/labyrinth.py +++ /dev/null @@ -1,92 +0,0 @@ -from actors.enums import Colors -import pygame -import math - -class PivotWall: - def __init__(self, screen, center, length=40, thickness=8, angle=0): - self.screen = screen - self.center = center - self.length = length - self.thickness = thickness - self.angle = angle - - def draw(self): - x, y = self.center - angle_rad = math.radians(self.angle) - dx = math.cos(angle_rad) * self.length / 2 - dy = math.sin(angle_rad) * self.length / 2 - - points = [ - (x - dx - dy * self.thickness / self.length, y - dy + dx * self.thickness / self.length), - (x - dx + dy * self.thickness / self.length, y - dy - dx * self.thickness / self.length), - (x + dx + dy * self.thickness / self.length, y + dy - dx * self.thickness / self.length), - (x + dx - dy * self.thickness / self.length, y + dy + dx * self.thickness / self.length), - ] - - pygame.draw.polygon(self.screen, Colors.Cyan.value, points) - - def rotate(self, delta_angle): - self.angle = (self.angle + delta_angle) % 360 - -class GhostHome: - def __init__(self, screen, center, width=100, height=60, wall_thickness=8): - self.screen = screen - self.center = center - self.width = width - self.height = height - self.wall_thickness = wall_thickness - cx, cy = self.center - self.rect = pygame.Rect(cx - width // 2, cy - height // 2, width, height) - - def draw(self): - r = self.rect - t = self.wall_thickness - - pygame.draw.rect(self.screen, Colors.Magenta.value, (r.left, r.top + t, t, r.height -t)) - pygame.draw.rect(self.screen, Colors.Magenta.value, (r.right - t, r.top + t, t, r.height -t)) - pygame.draw.rect(self.screen, Colors.Magenta.value, (r.left, r.bottom - t, r.width, t)) - - gap = 40 - gap_x1 = r.centerx - gap // 2 - gap_x2 = r.centerx + gap // 2 - pygame.draw.rect(self.screen, Colors.Magenta.value, (r.left, r.top, gap_x1 - r.left, t)) - pygame.draw.rect(self.screen, Colors.Magenta.value, (gap_x2, r.top, r.right - gap_x2, t)) - -class Labyrinth: - def __init__(self, screen, width, height, wall_thickness=20): - self.screen = screen - self.width = width - self.height = height - self.wall_thickness = wall_thickness - offset = 50 - self.pivot_walls = [ - PivotWall(self.screen, (offset, offset)), - PivotWall(self.screen, (width - offset, offset)), - PivotWall(self.screen, (offset - height, offset)), - PivotWall(self.screen, (width - offset, height - offset)) - ] - self.ghost_home = GhostHome(self.screen, center=(width // 2, height // 2)) - - def draw(self): - w = self.wall_thickness - mid_x = self.width // 2 - mid_y = self.height // 2 - - pygame.draw.rect(self.screen, Colors.Blue.value, (0, 0, mid_x - 50, w)) - pygame.draw.rect(self.screen, Colors.Blue.value, (mid_x + 50, 0, self.width - (mid_x + 50), w)) - pygame.draw.rect(self.screen, Colors.Blue.value, (0, self.height - w, mid_x - 50, w)) - pygame.draw.rect(self.screen, Colors.Blue.value, (mid_x + 50, self.height - w, self.width - (mid_x + 50), w)) - - pygame.draw.rect(self.screen, Colors.Blue.value, (0, 0, w, mid_y -50)) - pygame.draw.rect(self.screen, Colors.Blue.value, (0, mid_y + 50, w, self.height - (mid_y + 50))) - pygame.draw.rect(self.screen, Colors.Blue.value, (self.width - w, 0, w, mid_y - 50)) - pygame.draw.rect(self.screen, Colors.Blue.value, (self.width - w, mid_y + 50, w, self.height - (mid_y + 50))) - - for pivot in self.pivot_walls: - pivot.draw() - - self.ghost_home.draw() - - def rotate_all_pivots(self, angle): - for pivot in self.pivot_walls: - pivot.rotate(angle) \ No newline at end of file diff --git a/maze.py b/maze.py index 76fa5d3..a5abd8c 100644 --- a/maze.py +++ b/maze.py @@ -35,21 +35,21 @@ class Maze: elif char == "*": self.power_pellets.add((x, y)) elif char == "P": - self.pacman_start = (x * TILE_SIZE + TILE_SIZE // 2, y * TILE_SIZE + TILE_SIZE // 2) + self.pacman_start = (x * TILE_SIZE + TILE_SIZE // 2, y * TILE_SIZE + TILE_SIZE // 2 + 32) def draw(self, screen): for y, row in enumerate(self.layout): for x, char in enumerate(row): - rect = pygame.Rect(x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE, TILE_SIZE) + rect = pygame.Rect(x * TILE_SIZE, y * TILE_SIZE + 32, TILE_SIZE, TILE_SIZE) if char == "W": pygame.draw.rect(screen, COLOR_WALL, rect) # Draw dots for (x, y) in self.dots: - center = (x * TILE_SIZE + TILE_SIZE // 2, y * TILE_SIZE + TILE_SIZE // 2) + center = (x * TILE_SIZE + TILE_SIZE // 2, y * TILE_SIZE + TILE_SIZE // 2 + 32) pygame.draw.circle(screen, COLOR_DOT, center, 2) # Draw power pellets for (x, y) in self.power_pellets: - center = (x * TILE_SIZE + TILE_SIZE // 2, y * TILE_SIZE + TILE_SIZE // 2) + center = (x * TILE_SIZE + TILE_SIZE // 2, y * TILE_SIZE + TILE_SIZE // 2 + 32) pygame.draw.circle(screen, COLOR_POWER, center, 5) def tile_at(self, x, y): diff --git a/pman.py b/pman.py index 734dc3d..1c00794 100644 --- a/pman.py +++ b/pman.py @@ -1,13 +1,12 @@ import pygame from actors.enums import Colors, PlayerDirection, PillType from actors.pacman import ActorPacman -from labyrinth import Labyrinth from actors.ghost import Blinky, Pinky, Inky, Clyde # adjust import path as needed from actors.ghost_mode_controller import GhostModeController -from scoreboard import Scoreboard +from hud import HUD from maze import Maze -__version__ = "0.2.3" +__version__ = "0.2.4" def spawn_ghosts(center_position): @@ -29,43 +28,19 @@ def spawn_ghosts(center_position): return pygame.sprite.Group(blinky, pinky, inky, clyde) -def place_pills(maze, spacing=16): - pills = pygame.sprite.Group() - height = len(maze) - width = len(maze[0]) - - # Normal pills - for y in range(height): - for x in range(width): - if maze[y][x] == 0: # assuming 0 is path - if x % spacing == 0 and y % spacing == 0: - pills.add(PillType.PillTypeRegular(x * spacing + spacing // 2, y * spacing + spacing // 2)) - - # Power pills in 4 corners - corners = [ - (1, 1), - (1, width - 2), - (height - 2, 1), - (height - 2, width - 2) - ] - for cy, cx in corners: - pills.add(PillType.PillTypePower(cx * spacing + spacing // 2, cy * spacing + spacing // 2)) - - return pills - def main() -> None: pygame.init() - screen_width, screen_height = (32*29), (32*30) + screen_width, screen_height = (32*29), (32*30)+32 screen = pygame.display.set_mode((screen_width, screen_height)) pygame.display.set_caption("Pac-Man " + __version__) - scoreboard = Scoreboard() - #labyrinth = Labyrinth(screen, width=screen_width, height=screen_height) + cherry_img = pygame.image.load("assets/cherry.png").convert_alpha() + hud = HUD(screen_width, screen_height, cherry_image=cherry_img) maze = Maze("maze/pacman_maze.txt") player = ActorPacman(screen, center=maze.pacman_start) ghost_mode_controller = GhostModeController() - ghost_home_center = (screen_width // 2, screen_height // 2) + ghost_home_center = (screen_width // 2, (screen_height) // 2) ghosts = spawn_ghosts(ghost_home_center) clock = pygame.time.Clock() @@ -99,8 +74,8 @@ def main() -> None: elif hat_y == -1: player.direction = PlayerDirection.DirectionDown - #labyrinth.draw() # In your main game loop: + hud.draw(screen) maze.draw(screen) player.animate() player.draw() @@ -112,7 +87,6 @@ def main() -> None: #ghost.update(maze, pacman) ghosts.draw(screen) - scoreboard.draw(screen) pygame.display.flip() clock.tick(60) diff --git a/scoreboard.py b/scoreboard.py deleted file mode 100644 index 798c0f4..0000000 --- a/scoreboard.py +++ /dev/null @@ -1,42 +0,0 @@ -import pygame -import os -from actors.enums import Colors - -class Scoreboard: - def __init__(self, font_size=24, highscore_file="highscore.txt"): - self.score = 0 - self.highscore_file = highscore_file - self.highscore = self.load_highscore() - - self.font = pygame.font.Font(None, font_size) - self.color = Colors.WHITE.value - self.score_pos = (10, 10) - self.highscore_pos = (10, 40) - - def load_highscore(self): - if os.path.exists(self.highscore_file): - with open(self.highscore_file, 'r') as file: - try: - return int(file.read()) - except ValueError: - return 0 - return 0 - - def save_highscore(self): - with open(self.highscore_file, 'w') as file: - file.write(str(self.highscore)) - - def add_points(self, points): - self.score += points - if self.score > self.highscore: - self.highscore = self.score - self.save_highscore() - - def reset(self): - self.score = 0 - - def draw(self, screen): - score_text = self.font.render(f"Score: {self.score}", True, self.color) - highscore_text = self.font.render(f"High Score: {self.highscore}", True, self.color) - screen.blit(score_text, self.score_pos) - screen.blit(highscore_text, self.highscore_pos) \ No newline at end of file