From 931211e604b70201c440f80d57df04dc9aacc1e5 Mon Sep 17 00:00:00 2001 From: Lerking Date: Tue, 15 Apr 2025 21:27:26 +0200 Subject: [PATCH 01/12] Update pman.py --- pman.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pman.py b/pman.py index 210573b..cd0e8c7 100644 --- a/pman.py +++ b/pman.py @@ -3,7 +3,7 @@ from actors.enums import Colors, PlayerDirection from actors.pacman import ActorPacman from labyrinth import Labyrinth -__version__ = "0.1.2" +__version__ = "0.2.0" def main() -> None: pygame.init() -- 2.39.5 From 411db322039e2fe8303a835559f095651051a2d9 Mon Sep 17 00:00:00 2001 From: Lerking Date: Tue, 15 Apr 2025 21:29:09 +0200 Subject: [PATCH 02/12] Update actors/ghost.py --- actors/ghost.py | 39 +++++++++++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/actors/ghost.py b/actors/ghost.py index 7775768..0bda1bc 100644 --- a/actors/ghost.py +++ b/actors/ghost.py @@ -1,5 +1,36 @@ -from .enums import GhostDirection +import pygame +from enums import GhostColor, GhostMode -class ActorGhost: - def __init__(self): - pass \ No newline at end of file +class ActorGhost(pygame.sprite.Sprite): + def __init__(self, name, color_enum, position, speed): + super().__init__() + self.name = name + self.color = color_enum.value + 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): + 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 + else: + self.change_direction(maze) + + def change_direction(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 \ No newline at end of file -- 2.39.5 From 058316febd0e6cd1bd5f4d8b71033867826fe890 Mon Sep 17 00:00:00 2001 From: Lerking Date: Tue, 15 Apr 2025 21:30:24 +0200 Subject: [PATCH 03/12] Update actors/enums.py --- actors/enums.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/actors/enums.py b/actors/enums.py index 45f4e39..80d5945 100644 --- a/actors/enums.py +++ b/actors/enums.py @@ -1,4 +1,4 @@ -from enum import Enum +from enum import Enum, auto class PlayerDirection(Enum): DirectionRight = 0 @@ -21,4 +21,15 @@ class Colors(Enum): Black = (0, 0, 0) Blue = (0, 0, 255) Cyan = (0, 255, 255) - Magenta = (255, 0, 255) \ No newline at end of file + 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() \ No newline at end of file -- 2.39.5 From af49d834f63482e8e60cce127e6a46b2eac30f6a Mon Sep 17 00:00:00 2001 From: Lerking Date: Tue, 15 Apr 2025 21:33:12 +0200 Subject: [PATCH 04/12] Update actors/ghost.py --- actors/ghost.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/actors/ghost.py b/actors/ghost.py index 0bda1bc..bfe9ae5 100644 --- a/actors/ghost.py +++ b/actors/ghost.py @@ -33,4 +33,20 @@ class ActorGhost(pygame.sprite.Sprite): break def set_mode(self, mode: GhostMode): - self.mode = mode \ No newline at end of file + self.mode = mode + +class Blinky(Ghost): + def __init__(self, position): + super().__init__("Blinky", GhostColor.BLINKY, position, speed=2) + +class Pinky(Ghost): + def __init__(self, position): + super().__init__("Pinky", GhostColor.PINKY, position, speed=2) + +class Inky(Ghost): + def __init__(self, position): + super().__init__("Inky", GhostColor.INKY, position, speed=2) + +class Clyde(Ghost): + def __init__(self, position): + super().__init__("Clyde", GhostColor.CLYDE, position, speed=2) \ No newline at end of file -- 2.39.5 From 2aafdcc18c06c095fad2cd3fea9252882e557ab8 Mon Sep 17 00:00:00 2001 From: Lerking Date: Tue, 15 Apr 2025 21:37:23 +0200 Subject: [PATCH 05/12] Update actors/enums.py --- actors/enums.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/actors/enums.py b/actors/enums.py index 80d5945..4b86c41 100644 --- a/actors/enums.py +++ b/actors/enums.py @@ -32,4 +32,19 @@ class GhostColor(Enum): class GhostMode(Enum): SCATTER = auto() CHASE = auto() - FRIGHTENED = auto() \ No newline at end of file + 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) \ No newline at end of file -- 2.39.5 From f32dbaf67a705b47477d55cda58b891896a402fa Mon Sep 17 00:00:00 2001 From: Lerking Date: Tue, 15 Apr 2025 21:38:00 +0200 Subject: [PATCH 06/12] Add actors/Behaviors.py --- actors/Behaviors.py | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 actors/Behaviors.py diff --git a/actors/Behaviors.py b/actors/Behaviors.py new file mode 100644 index 0000000..87da6ce --- /dev/null +++ b/actors/Behaviors.py @@ -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 \ No newline at end of file -- 2.39.5 From c7b1285879914708ee2b682c7151373b91ff8456 Mon Sep 17 00:00:00 2001 From: Lerking Date: Tue, 15 Apr 2025 21:38:25 +0200 Subject: [PATCH 07/12] Update actors/behaviors.py --- actors/{Behaviors.py => behaviors.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename actors/{Behaviors.py => behaviors.py} (100%) diff --git a/actors/Behaviors.py b/actors/behaviors.py similarity index 100% rename from actors/Behaviors.py rename to actors/behaviors.py -- 2.39.5 From 679a1e31fa095acd6758494179f3aa05bded7c55 Mon Sep 17 00:00:00 2001 From: Lerking Date: Tue, 15 Apr 2025 21:40:20 +0200 Subject: [PATCH 08/12] Update actors/ghost.py --- actors/ghost.py | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/actors/ghost.py b/actors/ghost.py index bfe9ae5..a157d20 100644 --- a/actors/ghost.py +++ b/actors/ghost.py @@ -1,11 +1,12 @@ -import pygame -from enums import GhostColor, GhostMode +from enums import GhostColor, GhostMode, GhostBehavior +from behaviors import path_toward # required if you want a fallback -class ActorGhost(pygame.sprite.Sprite): - def __init__(self, name, color_enum, position, speed): +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) @@ -14,14 +15,16 @@ class ActorGhost(pygame.sprite.Sprite): self.mode = GhostMode.SCATTER self.home_position = position - def update(self, 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 update(self, maze, pacman): + if self.mode == GhostMode.FRIGHTENED: + self.change_direction_randomly(maze) else: - self.change_direction(maze) + 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(self, maze): + 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)] @@ -37,16 +40,16 @@ class ActorGhost(pygame.sprite.Sprite): class Blinky(Ghost): def __init__(self, position): - super().__init__("Blinky", GhostColor.BLINKY, position, speed=2) + super().__init__("Blinky", GhostColor.BLINKY, GhostBehavior.BLINKY, position, speed=2) class Pinky(Ghost): def __init__(self, position): - super().__init__("Pinky", GhostColor.PINKY, position, speed=2) + super().__init__("Pinky", GhostColor.PINKY, GhostBehavior.PINKY, position, speed=2) class Inky(Ghost): def __init__(self, position): - super().__init__("Inky", GhostColor.INKY, position, speed=2) + super().__init__("Inky", GhostColor.INKY, GhostBehavior.INKY, position, speed=2) class Clyde(Ghost): def __init__(self, position): - super().__init__("Clyde", GhostColor.CLYDE, position, speed=2) \ No newline at end of file + super().__init__("Clyde", GhostColor.CLYDE, GhostBehavior.CLYDE, position, speed=2) \ No newline at end of file -- 2.39.5 From ce742ae7076f0df62832c2e9f037713d55a80cd8 Mon Sep 17 00:00:00 2001 From: Lerking Date: Tue, 15 Apr 2025 21:44:06 +0200 Subject: [PATCH 09/12] Add actors/ghost_mode_controller.py --- actors/ghost_mode_controller.py | 40 +++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 actors/ghost_mode_controller.py diff --git a/actors/ghost_mode_controller.py b/actors/ghost_mode_controller.py new file mode 100644 index 0000000..634d232 --- /dev/null +++ b/actors/ghost_mode_controller.py @@ -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() \ No newline at end of file -- 2.39.5 From 444d6c508c8365e4aaafd02eee82d9a4d371a377 Mon Sep 17 00:00:00 2001 From: Lerking Date: Tue, 15 Apr 2025 21:44:33 +0200 Subject: [PATCH 10/12] Update actors/ghost_behaviors.py --- actors/{behaviors.py => ghost_behaviors.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename actors/{behaviors.py => ghost_behaviors.py} (100%) diff --git a/actors/behaviors.py b/actors/ghost_behaviors.py similarity index 100% rename from actors/behaviors.py rename to actors/ghost_behaviors.py -- 2.39.5 From 57f8b19bde0173076ee9b90c6c436b95abc5bcca Mon Sep 17 00:00:00 2001 From: Lerking Date: Tue, 15 Apr 2025 21:51:26 +0200 Subject: [PATCH 11/12] Update actors/ghost.py --- actors/ghost.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/actors/ghost.py b/actors/ghost.py index a157d20..3c45261 100644 --- a/actors/ghost.py +++ b/actors/ghost.py @@ -37,6 +37,10 @@ class Ghost(pygame.sprite.Sprite): 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): -- 2.39.5 From 641c3fb158a7dae2f72cbba31baa0a331d065719 Mon Sep 17 00:00:00 2001 From: Lerking Date: Tue, 15 Apr 2025 21:59:22 +0200 Subject: [PATCH 12/12] Update pman.py --- pman.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/pman.py b/pman.py index cd0e8c7..c8e2cfb 100644 --- a/pman.py +++ b/pman.py @@ -2,9 +2,30 @@ 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.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() screen_width, screen_height = 800, 800 @@ -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) -- 2.39.5