Merge pull request 'Added maze loading. /JL' (#15) from 0.2.3 into main
Reviewed-on: #15
This commit is contained in:
@@ -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)
|
||||||
|
@@ -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):
|
||||||
|
@@ -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
|
||||||
|
28
labyrinth.py
28
labyrinth.py
@@ -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
85
maze.py
Normal 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
29
maze/pacman_maze.txt
Normal 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
34
pman.py
@@ -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)
|
||||||
|
@@ -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)
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user