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): class PlayerDirection(Enum):
DirectionRight = 0 DirectionRight = 0
@@ -21,4 +21,30 @@ class Colors(Enum):
Black = (0, 0, 0) Black = (0, 0, 0)
Blue = (0, 0, 255) Blue = (0, 0, 255)
Cyan = (0, 255, 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: class Ghost(pygame.sprite.Sprite):
def __init__(self): def __init__(self, name, color_enum, behavior_enum, position, speed):
pass 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.enums import Colors, PlayerDirection
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
__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: def main() -> None:
pygame.init() pygame.init()
@@ -13,6 +34,8 @@ def main() -> None:
labyrinth = Labyrinth(screen, width=screen_width, height=screen_height) labyrinth = Labyrinth(screen, width=screen_width, height=screen_height)
player = ActorPacman(screen, center=(200, 200)) 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() clock = pygame.time.Clock()
running = True running = True
@@ -47,6 +70,14 @@ def main() -> None:
labyrinth.draw() labyrinth.draw()
player.animate() player.animate()
player.draw() 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() pygame.display.flip()
clock.tick(60) clock.tick(60)