From 1bd4199182a684864caba713ec255d31779f5614 Mon Sep 17 00:00:00 2001 From: Thomas Rimelen Date: Thu, 8 Oct 2026 14:35:25 +0200 Subject: [PATCH] Fix library + despawn maze and childrens --- src/pacman/ecs/hierarchy.py | 20 +++++++++++ src/pacman/ecs/world.py | 67 +++++++++++++++++++++++-------------- src/pacman/game/__init__.py | 43 ++++++++++++++++++------ 3 files changed, 95 insertions(+), 35 deletions(-) diff --git a/src/pacman/ecs/hierarchy.py b/src/pacman/ecs/hierarchy.py index a50e97c..116f53a 100644 --- a/src/pacman/ecs/hierarchy.py +++ b/src/pacman/ecs/hierarchy.py @@ -153,3 +153,23 @@ class Hierarchy[Items, Filters = tuple[()]](FromWorld): if children is None: continue queue.extend((child, e) for e in children.children) + + def despawn_with_children(self, root: Entity) -> Callable[[World], None]: + queue = [root] + entities = set() + while len(queue) != 0: + nxt = queue.pop() + if nxt in entities: + continue + entities.add(nxt) + if (contents := self._nodes.get(nxt)) is None: + continue + _, children = contents + if children is None: + continue + queue.extend(children.children) + + def inner(world: World) -> None: + world.despawn_many(entities) + + return inner diff --git a/src/pacman/ecs/world.py b/src/pacman/ecs/world.py index 28322e7..34722c9 100644 --- a/src/pacman/ecs/world.py +++ b/src/pacman/ecs/world.py @@ -132,7 +132,7 @@ class Archetype: def add_components[T: Components]( self, ty: type[T], components: list[tuple["Entity", T]] - ) -> None: + ) -> Callable[[], None]: dst, comps_ty = self._compute_add(ty) comps = [components_iter(e) for _, e in components] positions = [self._positions[e] for e, _ in components] @@ -155,19 +155,22 @@ class Archetype: dst._extend(cb) self._discard(positions) - for k, lst in moved_comps: - hooks = self._world._components_cache[k][1] - hooks._remove( - zip((dst._entities[i] for i in itr), lst, strict=True) - ) - for k in comps_ty: - lst = dst._components[k] - hooks = self._world._components_cache[k][1] - hooks._insert((dst._entities[i], lst[i]) for i in itr) + def res() -> None: + for k, lst in moved_comps: + hooks = self._world._components_cache[k][1] + hooks._remove( + zip((dst._entities[i] for i in itr), lst, strict=True) + ) + for k in comps_ty: + lst = dst._components[k] + hooks = self._world._components_cache[k][1] + hooks._insert((dst._entities[i], lst[i]) for i in itr) + + return res def remove_components[T: Components]( - self, ty: type[T], entities: Iterable["Entity"] - ) -> None: + self, ty: type[T], entities: list["Entity"] + ) -> Callable[[], None]: dst, comps_ty = self._compute_remove(ty) positions = [self._positions[e] for e in entities] @@ -183,11 +186,16 @@ class Archetype: ] dst._extend(cb) self._discard(positions) - for k, comp in zip(comps_ty, removed_comps, strict=True): - hooks = self._world._components_cache[k][1] - hooks._remove(zip(removed_ents, comp, strict=True)) - def remove(self, entities: list["Entity"]) -> None: + def res() -> None: + for k, comp in zip(comps_ty, removed_comps, strict=True): + hooks = self._world._components_cache[k][1] + hooks._remove(zip(removed_ents, comp, strict=True)) + + return res + + def remove(self, entities: list["Entity"]) -> Callable[[], None]: + entities = [e for e in entities if e in self._positions] positions = [self._positions[e] for e in entities] comps = [ ( @@ -199,8 +207,12 @@ class Archetype: self._discard(positions) for ent in entities: del self._world._entities[ent] - for hook, comp in comps: - hook._remove(zip(entities, comp, strict=True)) + + def res() -> None: + for hook, comp in comps: + hook._remove(zip(entities, comp, strict=True)) + + return res def _extend( self, @@ -767,7 +779,7 @@ class World(FromWorld): self._base_archetype.add_components( cast(type[Components], ty), list(zip(ents, cast(list[Components], comps), strict=True)), - ) + )() return ents def despawn(self, ent: Entity) -> None: @@ -780,8 +792,9 @@ class World(FromWorld): if arch not in archs: archs[arch] = [] archs[arch].append(ent) - for arch, lst in archs.items(): - arch.remove(lst) + cbs = [arch.remove(lst) for arch, lst in archs.items()] + for cb in cbs: + cb() def insert[T: Components](self, ty: type[T], ent: Entity, comp: T) -> None: self.insert_many(ty, [(ent, comp)]) @@ -796,8 +809,9 @@ class World(FromWorld): archs[arch] = [] archs[arch].append((ent, comp)) - for arch, lst in archs.items(): - arch.add_components(ty, lst) + cbs = [arch.add_components(ty, lst) for arch, lst in archs.items()] + for cb in cbs: + cb() def remove[T: Components](self, ty: type[T], ent: Entity) -> None: self.remove_many(ty, [ent]) @@ -807,13 +821,16 @@ class World(FromWorld): ) -> None: archs: dict[Archetype, list[Entity]] = {} for ent in comps: + if ent not in self._entities: + continue arch = self._entities[ent] if arch not in archs: archs[arch] = [] archs[arch].append(ent) - for arch, lst in archs.items(): - arch.remove_components(ty, lst) + cbs = [arch.remove_components(ty, lst) for arch, lst in archs.items()] + for cb in cbs: + cb() def _sys_extractors[T]( self, system: System[T] diff --git a/src/pacman/game/__init__.py b/src/pacman/game/__init__.py index a3e96a0..7362e19 100644 --- a/src/pacman/game/__init__.py +++ b/src/pacman/game/__init__.py @@ -20,10 +20,18 @@ from pacman.ecs.graphics import ( WindowEvents, graphics_plugins, ) -from pacman.ecs.hierarchy import ChildOf +from pacman.ecs.hierarchy import ChildOf, Hierarchy from pacman.ecs.schedule import StartupSchedule, UpdateSchedule from pacman.ecs.update import PostUpdate, PreUpdate, Update -from pacman.ecs.world import Component, Entity, Query, Resource, Systems, World +from pacman.ecs.world import ( + CommandQueue, + Component, + Entity, + Query, + Resource, + Systems, + World, +) _DIR_OFFSETS = [(0, -1), (1, 0), (0, 1), (-1, 0)] @@ -253,7 +261,7 @@ def spawn_pacgums( def setup_maze(world: World) -> None: - world.add_system(create_maze)(MazeGenerator((14, 14))) + world.add_system(create_maze)(MazeGenerator((5, 5))) def move_in_motion( @@ -322,10 +330,11 @@ def move_to_target( def spawn_pacman( - world: World, assets: Assets[Sprite2D], maze_q: Query[Maze] + world: World, assets: Assets[Sprite2D], maze_q: Query[tuple[Maze, Entity]] ) -> None: - if (maze := maze_q.single()) is None: + if (maze_t := maze_q.single()) is None: return + maze, maze_ent = maze_t world.spawn( tuple[ Coord2D, @@ -336,6 +345,7 @@ def spawn_pacman( InMotion, MoveSpeed, TilePos, + ChildOf, ], ( Coord2D(3 * 64, 3 * 64, -10), @@ -357,15 +367,17 @@ def spawn_pacman( InMotion(maze._cells[(3, 3)], maze._cells[(3, 3)], 1), MoveSpeed(3.0), TilePos(maze._cells[(3, 3)]), + ChildOf(maze_ent), ), ) def spawn_ghosts( - world: World, assets: Assets[Sprite2D], maze_q: Query[Maze] + world: World, assets: Assets[Sprite2D], maze_q: Query[tuple[Maze, Entity]] ) -> None: - if (maze := maze_q.single()) is None: + if (maze_t := maze_q.single()) is None: return + maze, maze_ent = maze_t world.spawn_many( tuple[ Coord2D, @@ -378,6 +390,7 @@ def spawn_ghosts( Ghost, TargetTile, GhostStatus, + ChildOf, ], [ ( @@ -403,6 +416,7 @@ def spawn_ghosts( Ghost(variant, pos, maze._cells[pos]), TargetTile(pos), GhostStatus(), + ChildOf(maze_ent), ) for variant, pos in [ (GhostVariant.A, (0, 0)), @@ -487,8 +501,10 @@ def collect_superpacgum( def collide_ghost( pacmans: Query[CoordAbs2D, Pacman], ghosts: Query[ - tuple[CoordAbs2D, GhostStatus, Ghost, InMotion, Coord2D], Ghost + tuple[CoordAbs2D, GhostStatus, Ghost, InMotion, Coord2D], + Ghost, ], + score: Score, ) -> None: for pacman_pos in pacmans: for ghost_pos, status, ghost, motion, pos in ghosts: @@ -501,7 +517,9 @@ def collide_ghost( case GhostState.wander | GhostState.chase: # TODO: kill pacman print("Pacman hit") + case GhostState.flee: + score.score += 100 # FIXME status.state = GhostState.dead status.tick = 0 motion.src = ghost.home_entity @@ -618,8 +636,13 @@ def update_inputs(inputs: Inputs, events: WindowEvents) -> None: del inputs.inputs[key] -def victory() -> None: - return +def victory( + hierarchy: Hierarchy[tuple[()]], + mazes: Query[Entity, Maze], + queue: CommandQueue, +) -> None: + for maze in mazes: + queue.queue(hierarchy.despawn_with_children(maze)) def run_game() -> None: -- 2.53.0