@@ -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)
|
@@ -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
38
actors/ghost_behaviors.py
Normal 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
|
40
actors/ghost_mode_controller.py
Normal file
40
actors/ghost_mode_controller.py
Normal 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
33
pman.py
@@ -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)
|
||||
|
||||
|
Reference in New Issue
Block a user