Added HUD with lives and level indicators. /JL

This commit is contained in:
2025-04-17 22:29:00 +02:00
parent 21870930ef
commit ffa7ba0db3
6 changed files with 96 additions and 171 deletions

BIN
assets/cherry.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

85
hud.py Normal file
View File

@@ -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))

View File

@@ -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)

View File

@@ -35,21 +35,21 @@ class Maze:
elif char == "*": elif char == "*":
self.power_pellets.add((x, y)) self.power_pellets.add((x, y))
elif char == "P": 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): def draw(self, screen):
for y, row in enumerate(self.layout): for y, row in enumerate(self.layout):
for x, char in enumerate(row): 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": if char == "W":
pygame.draw.rect(screen, COLOR_WALL, rect) pygame.draw.rect(screen, COLOR_WALL, rect)
# Draw dots # Draw dots
for (x, y) in self.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) pygame.draw.circle(screen, COLOR_DOT, center, 2)
# Draw power pellets # Draw power pellets
for (x, y) in self.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) pygame.draw.circle(screen, COLOR_POWER, center, 5)
def tile_at(self, x, y): def tile_at(self, x, y):

40
pman.py
View File

@@ -1,13 +1,12 @@
import pygame import pygame
from actors.enums import Colors, PlayerDirection, PillType from actors.enums import Colors, PlayerDirection, PillType
from actors.pacman import ActorPacman 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 import Blinky, Pinky, Inky, Clyde # adjust import path as needed
from actors.ghost_mode_controller import GhostModeController from actors.ghost_mode_controller import GhostModeController
from scoreboard import Scoreboard from hud import HUD
from maze import Maze from maze import Maze
__version__ = "0.2.3" __version__ = "0.2.4"
def spawn_ghosts(center_position): def spawn_ghosts(center_position):
@@ -29,43 +28,19 @@ def spawn_ghosts(center_position):
return pygame.sprite.Group(blinky, pinky, inky, clyde) 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: def main() -> None:
pygame.init() 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)) screen = pygame.display.set_mode((screen_width, screen_height))
pygame.display.set_caption("Pac-Man " + __version__) pygame.display.set_caption("Pac-Man " + __version__)
scoreboard = Scoreboard() cherry_img = pygame.image.load("assets/cherry.png").convert_alpha()
#labyrinth = Labyrinth(screen, width=screen_width, height=screen_height) hud = HUD(screen_width, screen_height, cherry_image=cherry_img)
maze = Maze("maze/pacman_maze.txt") maze = Maze("maze/pacman_maze.txt")
player = ActorPacman(screen, center=maze.pacman_start) player = ActorPacman(screen, center=maze.pacman_start)
ghost_mode_controller = GhostModeController() 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) ghosts = spawn_ghosts(ghost_home_center)
clock = pygame.time.Clock() clock = pygame.time.Clock()
@@ -99,8 +74,8 @@ def main() -> None:
elif hat_y == -1: elif hat_y == -1:
player.direction = PlayerDirection.DirectionDown player.direction = PlayerDirection.DirectionDown
#labyrinth.draw()
# In your main game loop: # In your main game loop:
hud.draw(screen)
maze.draw(screen) maze.draw(screen)
player.animate() player.animate()
player.draw() player.draw()
@@ -112,7 +87,6 @@ def main() -> None:
#ghost.update(maze, pacman) #ghost.update(maze, pacman)
ghosts.draw(screen) ghosts.draw(screen)
scoreboard.draw(screen)
pygame.display.flip() pygame.display.flip()
clock.tick(60) clock.tick(60)

View File

@@ -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)