]> Untitled Git - axy/ft/pacman.git/commitdiff
Ghost status as enum and respawn mechanics
author= <=>
Thu, 8 Oct 2026 10:51:32 +0000 (12:51 +0200)
committer= <=>
Thu, 8 Oct 2026 10:51:32 +0000 (12:51 +0200)
src/pacman/game/__init__.py

index 82ffc14548fcd93e537f90b42d1d4ec898ace766..a3e96a071b9ef3d0d07a0c9fab593cc7c6979a5b 100644 (file)
@@ -95,19 +95,26 @@ class Pacman(Component):
     pass
 
 
+class GhostState(Enum):
+    chase = auto()
+    wander = auto()
+    flee = auto()
+    dead = auto()
+    respawning = auto()
+
+
 @dataclass
 class GhostStatus(Component):
     tick: int = 0
-    cycle_chase: int = 60 * 10
     cycle_wander: int = 60 * 10
-    chase: bool = False
-    flee: bool = False
+    state: GhostState = GhostState.wander
 
 
 @dataclass
 class Ghost(Component):
     variant: GhostVariant
     home: tuple[int, int]
+    home_entity: Entity
 
 
 @dataclass
@@ -283,6 +290,8 @@ def move_to_target(
     if (maze := maze_q.single()) is None:
         return
     for target, motion, pos, status in entities:
+        if status.state in (GhostState.dead, GhostState.respawning):
+            continue
         cell, cell_pos = cells[motion.pos]
         if cell_pos.xy.distance_squared_to(pos.xy) > 0.01:
             continue
@@ -302,7 +311,7 @@ def move_to_target(
                 )
             ),
         )
-        if status.flee:
+        if status.state is GhostState.flee:
             lst = candidates[:-1]
             shuffle(lst)
             candidates = lst + [candidates[-1]]
@@ -383,14 +392,15 @@ def spawn_ghosts(
                             )
                         ]
                         for direction in "URDL"
-                    ] + [[assets.load("./asset/enemy/ghost_blue.png")]],
+                    ]
+                    + [[assets.load("./asset/enemy/ghost_blue.png")]],
                     6,
                 ),
                 ShouldRender(),
                 InMotion(maze._cells[pos], maze._cells[pos], 1),
                 MoveSpeed(2.5),
                 TilePos(maze._cells[pos]),
-                Ghost(variant, pos),
+                Ghost(variant, pos, maze._cells[pos]),
                 TargetTile(pos),
                 GhostStatus(),
             )
@@ -441,7 +451,7 @@ def collect_pacgum(
         pacgum
         for coord, pacgum in pacgums
         for pos in pacmans
-        if pos._vec.xy.distance_squared_to(coord._vec.xy) < 100
+        if pos._vec.xy.distance_squared_to(coord._vec.xy) <= 256
     ]
     score.score += len(collided)
     world.despawn_many(collided)
@@ -458,29 +468,47 @@ def collect_superpacgum(
         pacgum
         for pacgum_pos, pacgum in pacgums
         for pacman_pos in pacmans
-        if pacman_pos._vec.xy.distance_squared_to(pacgum_pos._vec.xy) < 100
+        if pacman_pos._vec.xy.distance_squared_to(pacgum_pos._vec.xy) <= 256
     ]
     score.score += len(collided * 10)
-    if len(collided) > 0:
-        for ghost, status in ghosts:
+    if len(collided) == 0:
+        return
+    for ghost, status in ghosts:
+        if status.state in (GhostState.wander, GhostState.chase):
             tmp = ghost.src
             ghost.src = ghost.pos
             ghost.pos = tmp
-            status.flee = True
-            status.tick = 60 * 8
+            status.state = GhostState.flee
+        if status.state is GhostState.flee:
+            status.tick = 0
     world.despawn_many(collided)
 
 
 def collide_ghost(
-    pacmans: Query[CoordAbs2D, Pacman], ghosts: Query[CoordAbs2D, Ghost]
+    pacmans: Query[CoordAbs2D, Pacman],
+    ghosts: Query[
+        tuple[CoordAbs2D, GhostStatus, Ghost, InMotion, Coord2D], Ghost
+    ],
 ) -> None:
     for pacman_pos in pacmans:
-        for ghost_pos in ghosts:
+        for ghost_pos, status, ghost, motion, pos in ghosts:
             if (
                 pacman_pos._vec.xy.distance_squared_to(ghost_pos._vec.xy)
-                < 3600
+                > 3600
             ):
-                pass
+                continue
+            match status.state:
+                case GhostState.wander | GhostState.chase:
+                    # TODO: kill pacman
+                    print("Pacman hit")
+                case GhostState.flee:
+                    status.state = GhostState.dead
+                    status.tick = 0
+                    motion.src = ghost.home_entity
+                    motion.pos = ghost.home_entity
+                    pos.xy = Vector2(*ghost.home) * 64
+                case _:
+                    pass
 
 
 def level_is_finished(pacgums: Query[SuperPacGum | PacGum]) -> bool:
@@ -502,44 +530,59 @@ def update_ghost_targets(
         if ghost.variant == GhostVariant.A
     )
     for ghost, pos, target, status in ghosts:
-        if not status.chase:
-            target.pos = ghost.home
-            continue
-        target.pos = ghost.variant.compute_target_tile(
-            cells[player_pos.pos].grid_pos,
-            player_motion.direction,
-            cells[pos.pos].grid_pos,
-            ghost,
-            cells[blinky_pos].grid_pos,
-        )
+        match status.state:
+            case GhostState.wander:
+                target.pos = ghost.home
+            case GhostState.chase:
+                target.pos = ghost.variant.compute_target_tile(
+                    cells[player_pos.pos].grid_pos,
+                    player_motion.direction,
+                    cells[pos.pos].grid_pos,
+                    ghost,
+                    cells[blinky_pos].grid_pos,
+                )
+            case _:
+                pass
 
 
 def update_chase_status(status_q: Query[GhostStatus]) -> None:
     for status in status_q:
-        if status.flee:
-            if status.tick == 0:
-                status.flee = False
-                continue
-            status.tick -= 1
-        else:
-            status.tick += 1
-            status.chase = status.tick > status.cycle_wander
-            if status.tick < (status.cycle_wander + status.cycle_chase):
-                continue
-
+        target_time: int
+        next_state: GhostState
+        cycle_wander_next: int = status.cycle_wander
+        match status.state:
+            case GhostState.flee:
+                target_time, next_state = (8 * 60, GhostState.wander)
+            case GhostState.chase:
+                if cycle_wander_next > 60 * 4:
+                    cycle_wander_next -= 120
+                target_time, next_state = (
+                    60 * 10,
+                    GhostState.wander,
+                )
+            case GhostState.wander:
+                target_time, next_state = (
+                    status.cycle_wander,
+                    GhostState.chase,
+                )
+            case GhostState.dead:
+                target_time, next_state = (5 * 60, GhostState.respawning)
+            case GhostState.respawning:
+                target_time, next_state = (3 * 60, GhostState.wander)
+        status.tick += 1
+        if target_time <= status.tick:
+            status.state = next_state
             status.tick = 0
-            if status.cycle_wander > 60 * 4:
-                status.cycle_wander -= 60
-            if status.cycle_chase > 60 * 7:
-                status.cycle_chase -= 60
+            status.cycle_wander = cycle_wander_next
 
 
 def update_ghost_speed(ghost: Query[tuple[MoveSpeed, GhostStatus]]) -> None:
     for speed, status in ghost:
-        speed.pixels_per_frame = 2.5
-        if status.flee:
-            speed.pixels_per_frame = 1.5
-
+        match status.state:
+            case GhostState.chase | GhostState.wander:
+                speed.pixels_per_frame = 2.5
+            case GhostState.flee:
+                speed.pixels_per_frame = 1.5
 
 
 def update_pacman_anim(
@@ -553,7 +596,13 @@ def update_ghost_anim(
     ghost_q: Query[tuple[InMotion, AnimationSet, GhostStatus], Ghost],
 ) -> None:
     for motion, anim, status in ghost_q:
-        anim.idx = motion.direction if not status.flee else 4
+        match status.state:
+            case GhostState.chase | GhostState.wander:
+                anim.idx = motion.direction
+            case GhostState.flee:
+                anim.idx = 4
+            case _:
+                pass
 
 
 def setup_resources(world: World) -> None:
@@ -601,8 +650,12 @@ def run_game() -> None:
         Systems(update_pacman_anim, update_ghost_anim)
         .before(GraphicsSystems)
         .in_set(PostUpdate),
-        Systems(collect_pacgum, collect_superpacgum, collide_ghost).after(
-            PropagateTransform
-        ),
+        Systems(
+            collect_pacgum,
+            collect_superpacgum,
+            Systems(collide_ghost).before(update_ghost_anim),
+        )
+        .after(PropagateTransform)
+        .before(GraphicsSystems),
         Systems(victory).run_if(level_is_finished),
     ).run_main()