Merge pull request '0.2.0' (#12) from 0.2.0 into main

Reviewed-on: #12
This commit is contained in:
2025-04-15 21:59:59 +02:00
5 changed files with 196 additions and 7 deletions

View File

@@ -1,4 +1,4 @@
from enum import Enum
from enum import Enum, auto
class PlayerDirection(Enum):
DirectionRight = 0
@@ -21,4 +21,30 @@ class Colors(Enum):
Black = (0, 0, 0)
Blue = (0, 0, 255)
Cyan = (0, 255, 255)
Magenta = (255, 0, 255)
Magenta = (255, 0, 255)
class GhostColor(Enum):
BLINKY = (255, 0, 0)
PINKY = (255, 184, 255)
INKY = (0, 255, 255)
CLYDE = (255, 184, 82)
class GhostMode(Enum):
SCATTER = auto()
CHASE = auto()
FRIGHTENED = auto()
class GhostBehavior(Enum):
BLINKY = "blinky_behavior"
PINKY = "pinky_behavior"
INKY = "inky_behavior"
CLYDE = "clyde_behavior"
def decide_direction(self, ghost, pacman, maze):
strategy = {
GhostBehavior.BLINKY: blinky_behavior,
GhostBehavior.PINKY: pinky_behavior,
GhostBehavior.INKY: inky_behavior,
GhostBehavior.CLYDE: clyde_behavior,
}[self]
return strategy(ghost, pacman, maze)

View File

@@ -1,5 +1,59 @@
from .enums import GhostDirection
from enums import GhostColor, GhostMode, GhostBehavior
from behaviors import path_toward # required if you want a fallback
class ActorGhost:
def __init__(self):
pass
class Ghost(pygame.sprite.Sprite):
def __init__(self, name, color_enum, behavior_enum, position, speed):
super().__init__()
self.name = name
self.color = color_enum.value
self.behavior = behavior_enum
self.image = pygame.Surface((16, 16))
self.image.fill(self.color)
self.rect = self.image.get_rect(center=position)
self.speed = speed
self.direction = pygame.Vector2(1, 0)
self.mode = GhostMode.SCATTER
self.home_position = position
def update(self, maze, pacman):
if self.mode == GhostMode.FRIGHTENED:
self.change_direction_randomly(maze)
else:
self.direction = self.behavior.decide_direction(self, pacman, maze)
new_pos = self.rect.move(self.direction.x * self.speed, self.direction.y * self.speed)
if not maze.is_wall(new_pos.center):
self.rect = new_pos
def change_direction_randomly(self, maze):
import random
directions = [pygame.Vector2(1, 0), pygame.Vector2(-1, 0),
pygame.Vector2(0, 1), pygame.Vector2(0, -1)]
random.shuffle(directions)
for d in directions:
test_pos = self.rect.move(d.x * self.speed, d.y * self.speed)
if not maze.is_wall(test_pos.center):
self.direction = d
break
def set_mode(self, mode: GhostMode):
self.mode = mode
if mode == GhostMode.FRIGHTENED:
self.image.fill((33, 33, 255)) # dark blue
else:
self.image.fill(self.color)
class Blinky(Ghost):
def __init__(self, position):
super().__init__("Blinky", GhostColor.BLINKY, GhostBehavior.BLINKY, position, speed=2)
class Pinky(Ghost):
def __init__(self, position):
super().__init__("Pinky", GhostColor.PINKY, GhostBehavior.PINKY, position, speed=2)
class Inky(Ghost):
def __init__(self, position):
super().__init__("Inky", GhostColor.INKY, GhostBehavior.INKY, position, speed=2)
class Clyde(Ghost):
def __init__(self, position):
super().__init__("Clyde", GhostColor.CLYDE, GhostBehavior.CLYDE, position, speed=2)

38
actors/ghost_behaviors.py Normal file
View File

@@ -0,0 +1,38 @@
import pygame
import random
def blinky_behavior(ghost, pacman, maze):
# Simple chase: move toward Pac-Man's position
return path_toward(ghost, pacman.rect.center, maze)
def pinky_behavior(ghost, pacman, maze):
# Aim 4 tiles ahead of Pac-Man
offset = pacman.direction * 64
target = (pacman.rect.centerx + offset.x, pacman.rect.centery + offset.y)
return path_toward(ghost, target, maze)
def inky_behavior(ghost, pacman, maze):
# Placeholder for now: same as Pinky
return pinky_behavior(ghost, pacman, maze)
def clyde_behavior(ghost, pacman, maze):
# If close to Pac-Man, scatter; otherwise chase
distance = pygame.Vector2(pacman.rect.center).distance_to(ghost.rect.center)
if distance < 100:
return path_toward(ghost, ghost.home_position, maze)
return path_toward(ghost, pacman.rect.center, maze)
def path_toward(ghost, target_pos, maze):
# Placeholder logic: pick a direction that reduces distance to target
directions = [pygame.Vector2(1, 0), pygame.Vector2(-1, 0),
pygame.Vector2(0, 1), pygame.Vector2(0, -1)]
best = ghost.direction
min_dist = float("inf")
for d in directions:
test_pos = ghost.rect.move(d.x * ghost.speed, d.y * ghost.speed)
if not maze.is_wall(test_pos.center):
dist = pygame.Vector2(target_pos).distance_to(test_pos.center)
if dist < min_dist:
min_dist = dist
best = d
return best

View File

@@ -0,0 +1,40 @@
import time
from enums import GhostMode
class GhostModeController:
def __init__(self):
self.timers = [
(GhostMode.SCATTER, 7),
(GhostMode.CHASE, 20),
(GhostMode.SCATTER, 7),
(GhostMode.CHASE, 20),
(GhostMode.SCATTER, 5),
(GhostMode.CHASE, 9999) # stay in chase mode eventually
]
self.current_index = 0
self.last_switch_time = time.time()
self.mode = self.timers[self.current_index][0]
self.frightened_until = 0
def update(self):
now = time.time()
if self.mode == GhostMode.FRIGHTENED:
if now > self.frightened_until:
self.resume_cycle()
return
mode, duration = self.timers[self.current_index]
if now - self.last_switch_time >= duration:
self.current_index = min(self.current_index + 1, len(self.timers) - 1)
self.mode = self.timers[self.current_index][0]
self.last_switch_time = now
def trigger_frightened(self, duration=6):
self.mode = GhostMode.FRIGHTENED
self.frightened_until = time.time() + duration
def resume_cycle(self):
# Return to normal cycle after frightened ends
self.mode = self.timers[self.current_index][0]
self.last_switch_time = time.time()

33
pman.py
View File

@@ -2,8 +2,29 @@ import pygame
from actors.enums import Colors, PlayerDirection
from actors.pacman import ActorPacman
from labyrinth import Labyrinth
from actors.ghosts import Blinky, Pinky, Inky, Clyde # adjust import path as needed
__version__ = "0.1.2"
__version__ = "0.2.0"
def spawn_ghosts(center_position):
"""
Spawns up to 4 ghosts at the center of the maze ("ghost house").
Args:
center_position (tuple): (x, y) coordinates for spawn point.
Returns:
pygame.sprite.Group: A group containing all ghost instances.
"""
offset = 20 # spread out a little bit inside ghost home
blinky = Blinky((center_position[0] - offset, center_position[1] - offset))
pinky = Pinky((center_position[0] + offset, center_position[1] - offset))
inky = Inky((center_position[0] - offset, center_position[1] + offset))
clyde = Clyde((center_position[0] + offset, center_position[1] + offset))
return pygame.sprite.Group(blinky, pinky, inky, clyde)
def main() -> None:
pygame.init()
@@ -13,6 +34,8 @@ def main() -> None:
labyrinth = Labyrinth(screen, width=screen_width, height=screen_height)
player = ActorPacman(screen, center=(200, 200))
ghost_home_center = (maze_width // 2, maze_height // 2)
ghosts = spawn_ghosts(ghost_home_center)
clock = pygame.time.Clock()
running = True
@@ -47,6 +70,14 @@ def main() -> None:
labyrinth.draw()
player.animate()
player.draw()
ghost_mode_controller.update()
current_mode = ghost_mode_controller.mode
for ghost in ghosts:
ghost.set_mode(current_mode)
ghost.update(maze, pacman)
ghosts.draw(screen)
pygame.display.flip()
clock.tick(60)