Merge pull request 'Added maze loading. /JL' (#15) from 0.2.3 into main

Reviewed-on: #15
This commit is contained in:
2025-04-17 21:43:49 +02:00
8 changed files with 158 additions and 37 deletions

View File

@@ -1,9 +1,5 @@
from enum import Enum, auto from enum import Enum, auto
class PillType(Enum):
NORMAL = 1
POWER = 2
class PlayerDirection(Enum): class PlayerDirection(Enum):
DirectionRight = 0 DirectionRight = 0
DirectionLeft = 180 DirectionLeft = 180
@@ -29,6 +25,8 @@ class Colors(Enum):
WHITE = (255, 255, 255) WHITE = (255, 255, 255)
RED = (255, 0, 0) RED = (255, 0, 0)
# add others as needed # add others as needed
def __getitem__(self):
return self.value
class GhostColor(Enum): class GhostColor(Enum):
BLINKY = (255, 0, 0) BLINKY = (255, 0, 0)

View File

@@ -1,5 +1,6 @@
from .enums import GhostColor, GhostMode, GhostBehavior from .enums import GhostColor, GhostMode, GhostBehavior
from .behaviors import path_toward # required if you want a fallback from .ghost_behaviors import path_toward # required if you want a fallback
import pygame
class Ghost(pygame.sprite.Sprite): class Ghost(pygame.sprite.Sprite):
def __init__(self, name, color_enum, behavior_enum, position, speed): def __init__(self, name, color_enum, behavior_enum, position, speed):

View File

@@ -8,7 +8,7 @@ class ActorPacman:
self.direction = PlayerDirection.DirectionRight self.direction = PlayerDirection.DirectionRight
self.speed = 3 self.speed = 3
self.x, self.y = center self.x, self.y = center
self.radius = 100 self.radius = 10
self.mouth_angle_deg = 45 self.mouth_angle_deg = 45
self.min_mouth_deg = 5 self.min_mouth_deg = 5
self.max_mouth_deg = 45 self.max_mouth_deg = 45
@@ -42,7 +42,7 @@ class ActorPacman:
def draw(self): def draw(self):
mouth_angle = math.radians(self.mouth_angle_deg) mouth_angle = math.radians(self.mouth_angle_deg)
center = (self.x, self.y) center = (self.x, self.y)
rotation_offset = math.radians(self.direction, 0) rotation_offset = math.radians(self.direction.value)
start_angle = mouth_angle / 2 + rotation_offset start_angle = mouth_angle / 2 + rotation_offset
end_angle = 2 * math.pi - mouth_angle / 2 + rotation_offset end_angle = 2 * math.pi - mouth_angle / 2 + rotation_offset
points = [center] points = [center]
@@ -52,7 +52,7 @@ class ActorPacman:
y = center[1] + self.radius * math.sin(angle) y = center[1] + self.radius * math.sin(angle)
points.append((x, y)) points.append((x, y))
angle += math.radians(1) angle += math.radians(1)
pygame.draw.polygon(self.screen, Colors.Yellow, points) pygame.draw.polygon(self.screen, Colors.Yellow.value, points)
def move_right(self): def move_right(self):
pass pass

View File

@@ -23,7 +23,7 @@ class PivotWall:
(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, points) pygame.draw.polygon(self.screen, Colors.Cyan.value, points)
def rotate(self, delta_angle): def rotate(self, delta_angle):
self.angle = (self.angle + delta_angle) % 360 self.angle = (self.angle + delta_angle) % 360
@@ -42,15 +42,15 @@ class GhostHome:
r = self.rect r = self.rect
t = self.wall_thickness t = self.wall_thickness
pygame.draw.rect(self.screen, Colors.Magenta, (r.left, r.top + t, t, r.height -t)) pygame.draw.rect(self.screen, Colors.Magenta.value, (r.left, r.top + t, t, r.height -t))
pygame.draw.rect(self.screen, Colors.Magenta, (r.right - t, 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, (r.left, r.bottom - t, r.width, t)) pygame.draw.rect(self.screen, Colors.Magenta.value, (r.left, r.bottom - t, r.width, t))
gap = 40 gap = 40
gap_x1 = r.centerx - gap // 2 gap_x1 = r.centerx - gap // 2
gap_x2 = r.centerx + gap // 2 gap_x2 = r.centerx + gap // 2
pygame.draw.rect(self.screen, Colors.Magenta, (r.left, r.top, gap_x1 - r.left, t)) pygame.draw.rect(self.screen, Colors.Magenta.value, (r.left, r.top, gap_x1 - r.left, t))
pygame.draw.rect(self.screen, Colors.Magenta, (gap_x2, r.top, r.right - gap_x2, t)) pygame.draw.rect(self.screen, Colors.Magenta.value, (gap_x2, r.top, r.right - gap_x2, t))
class Labyrinth: class Labyrinth:
def __init__(self, screen, width, height, wall_thickness=20): def __init__(self, screen, width, height, wall_thickness=20):
@@ -72,15 +72,15 @@ class Labyrinth:
mid_x = self.width // 2 mid_x = self.width // 2
mid_y = self.height // 2 mid_y = self.height // 2
pygame.draw.rect(self.screen, Colors.Blue, (0, 0, mid_x - 50, w)) pygame.draw.rect(self.screen, Colors.Blue.value, (0, 0, mid_x - 50, w))
pygame.draw.rect(self.screen, Colors.Blue, (mid_x + 50, self.width - (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, (0, self.height - w, 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, (mid_x + 50, self.height - w, self.width - (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, (0, 0, w, mid_y -50)) pygame.draw.rect(self.screen, Colors.Blue.value, (0, 0, w, mid_y -50))
pygame.draw.rect(self.screen, Colors.Blue, (0, mid_y + 50, w, self.height - (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, (self.width - w, 0, w, 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, (self.width - w, mid_y + 50, w, self.height - (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: for pivot in self.pivot_walls:
pivot.draw() pivot.draw()

85
maze.py Normal file
View File

@@ -0,0 +1,85 @@
import pygame
TILE_SIZE = 32
# Color definitions
COLOR_BG = (0, 0, 0)
COLOR_WALL = (0, 0, 255)
COLOR_DOT = (255, 255, 255)
COLOR_POWER = (255, 255, 255)
class Maze:
def __init__(self, file_path):
self.file_path = file_path
self.layout = []
self.dots = set()
self.power_pellets = set()
self.load_maze()
def load_maze(self):
with open(self.file_path, "r") as f:
self.layout = [line.strip() for line in f.readlines()]
self.rows = len(self.layout)
self.cols = len(self.layout[0])
self.width = self.cols * TILE_SIZE
self.height = self.rows * TILE_SIZE
# Track collectible positions
self.dots.clear()
self.power_pellets.clear()
for y, row in enumerate(self.layout):
for x, char in enumerate(row):
if char == ".":
self.dots.add((x, y))
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)
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)
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)
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)
pygame.draw.circle(screen, COLOR_POWER, center, 5)
def tile_at(self, x, y):
"""Return the tile character at a specific (x, y) tile coordinate."""
if 0 <= y < self.rows and 0 <= x < self.cols:
return self.layout[y][x]
return " " # Treat out-of-bounds as path
def is_wall(self, x, y):
return self.tile_at(x, y) == "W"
def collect_dot(self, x, y):
"""Mark a dot as collected. Return True if there was a dot or power pellet."""
pos = (x, y)
if pos in self.dots:
self.dots.remove(pos)
return "dot"
elif pos in self.power_pellets:
self.power_pellets.remove(pos)
return "power"
return None
def reset_collectibles(self):
"""Reloads only dot/pellet positions from the layout."""
self.load_maze()
def pixel_to_tile(self, px, py):
"""Convert pixel coordinates to tile (grid) coordinates."""
return px // TILE_SIZE, py // TILE_SIZE
def tile_to_pixel(self, tx, ty):
"""Convert tile (grid) coordinates to top-left pixel position."""
return tx * TILE_SIZE, ty * TILE_SIZE

29
maze/pacman_maze.txt Normal file
View File

@@ -0,0 +1,29 @@
WWWWWWWWWWWWWWWWWWWWWWWWWWWWW
W.............W.............W
W.WWWWW.WWWWW.W.WWWWW.WWWWW.W
W*W W.W W.W.W W.W W*W
W.WWWWW.WWWWW.W.WWWWW.WWWWW.W
W...........................W
W.WWWWW.W.WWWWWWWWW.W.WWWWW.W
W.WWWWW.W.WWWWWWWWW.W.WWWWW.W
W.......W.....W.....W.......W
WWWWWWW.WWWWW.W.WWWWW.WWWWWWW
W W.WWWWW.W.WWWWW.W W
W W...............W W
WWWWWWW.W.WWW---WWW.W.WWWWWWW
........W.W W.W........
WWWWWWW.W.W W.W.WWWWWWW
W W.W.W W.W.W W
W W.W.WWWWWWWWW.W.W W
WWWWWWW.......P.......WWWWWWW
W.......WWWWWWWWWWWWW.......W
W.WWWWW.W W...W W.WWWWW.W
W*WWWWW.WWWWW.W.WWWWW.WWWWW*W
W...WW........W........WW...W
WWW.WW.WW.WWW...WWW.WW.WW.WWW
WWW.WW.WW.WWWWWWWWW.WW.WW.WWW
W......WW.....W.....WW......W
W.WWWWWWWWWWW.W.WWWWWWWWWWW.W
W.WWWWWWWWWWW.W.WWWWWWWWWWW.W
W...........................W
WWWWWWWWWWWWWWWWWWWWWWWWWWWWW

34
pman.py
View File

@@ -1,11 +1,13 @@
import pygame import pygame
from actors.enums import Colors, PlayerDirection from actors.enums import Colors, PlayerDirection, PillType
from actors.pacman import ActorPacman from actors.pacman import ActorPacman
from labyrinth import Labyrinth from labyrinth import Labyrinth
from actors.ghosts 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 scoreboard import Scoreboard from scoreboard import Scoreboard
from maze import Maze
__version__ = "0.2.1" __version__ = "0.2.3"
def spawn_ghosts(center_position): def spawn_ghosts(center_position):
@@ -37,7 +39,7 @@ def place_pills(maze, spacing=16):
for x in range(width): for x in range(width):
if maze[y][x] == 0: # assuming 0 is path if maze[y][x] == 0: # assuming 0 is path
if x % spacing == 0 and y % spacing == 0: if x % spacing == 0 and y % spacing == 0:
pills.add(NormalPill(x * spacing + spacing // 2, y * spacing + spacing // 2)) pills.add(PillType.PillTypeRegular(x * spacing + spacing // 2, y * spacing + spacing // 2))
# Power pills in 4 corners # Power pills in 4 corners
corners = [ corners = [
@@ -47,26 +49,29 @@ def place_pills(maze, spacing=16):
(height - 2, width - 2) (height - 2, width - 2)
] ]
for cy, cx in corners: for cy, cx in corners:
pills.add(PowerPill(cx * spacing + spacing // 2, cy * spacing + spacing // 2)) pills.add(PillType.PillTypePower(cx * spacing + spacing // 2, cy * spacing + spacing // 2))
return pills return pills
def main() -> None: def main() -> None:
pygame.init() pygame.init()
screen_width, screen_height = 800, 800 screen_width, screen_height = (32*29), (32*30)
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() scoreboard = Scoreboard()
labyrinth = Labyrinth(screen, width=screen_width, height=screen_height) #labyrinth = Labyrinth(screen, width=screen_width, height=screen_height)
player = ActorPacman(screen, center=(200, 200)) maze = Maze("maze/pacman_maze.txt")
ghost_home_center = (maze_width // 2, maze_height // 2)
player = ActorPacman(screen, center=maze.pacman_start)
ghost_mode_controller = GhostModeController()
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()
running = True running = True
while running: while running:
screen.fill(Colors.Black) screen.fill(Colors.Black.value)
for event in pygame.event.get(): for event in pygame.event.get():
match event.type: match event.type:
@@ -83,7 +88,8 @@ def main() -> None:
case pygame.K_DOWN: case pygame.K_DOWN:
player.direction = PlayerDirection.DirectionDown player.direction = PlayerDirection.DirectionDown
case pygame.JOYHATMOTION: case pygame.JOYHATMOTION:
hat_x, hat_y =event.value print("Hat has been pressed.")
hat_x, hat_y = event.value
if hat_x == 1: if hat_x == 1:
player.direction = PlayerDirection.DirectionRight player.direction = PlayerDirection.DirectionRight
elif hat_x == -1: elif hat_x == -1:
@@ -93,7 +99,9 @@ def main() -> None:
elif hat_y == -1: elif hat_y == -1:
player.direction = PlayerDirection.DirectionDown player.direction = PlayerDirection.DirectionDown
labyrinth.draw() #labyrinth.draw()
# In your main game loop:
maze.draw(screen)
player.animate() player.animate()
player.draw() player.draw()
ghost_mode_controller.update() ghost_mode_controller.update()
@@ -101,7 +109,7 @@ def main() -> None:
for ghost in ghosts: for ghost in ghosts:
ghost.set_mode(current_mode) ghost.set_mode(current_mode)
ghost.update(maze, pacman) #ghost.update(maze, pacman)
ghosts.draw(screen) ghosts.draw(screen)
scoreboard.draw(screen) scoreboard.draw(screen)

View File

@@ -1,6 +1,6 @@
import pygame import pygame
import os import os
from enums import Color from actors.enums import Colors
class Scoreboard: class Scoreboard:
def __init__(self, font_size=24, highscore_file="highscore.txt"): def __init__(self, font_size=24, highscore_file="highscore.txt"):
@@ -9,7 +9,7 @@ class Scoreboard:
self.highscore = self.load_highscore() self.highscore = self.load_highscore()
self.font = pygame.font.Font(None, font_size) self.font = pygame.font.Font(None, font_size)
self.color = Color.WHITE.value self.color = Colors.WHITE.value
self.score_pos = (10, 10) self.score_pos = (10, 10)
self.highscore_pos = (10, 40) self.highscore_pos = (10, 40)