]> Untitled Git - axy/ft/pacman.git/commitdiff
More game impl stuff yey
author= <=>
Tue, 29 Sep 2026 13:01:16 +0000 (15:01 +0200)
committer= <=>
Tue, 29 Sep 2026 13:01:16 +0000 (15:01 +0200)
src/pacman/ecs/graphics.py
src/pacman/game/__init__.py

index 2c4dc99e46f2c9e667369fdd775a2ce2a40a7034..1eb2bf8c7fad4452c61bd1012a72983ea141d831 100644 (file)
@@ -6,7 +6,7 @@ import pygame
 
 from pacman.ecs.assets import Asset
 from pacman.ecs.coord import CoordAbs2D, PropagateTransform
-from pacman.ecs.schedule import StartupSchedule, UpdateSchedule
+from pacman.ecs.schedule import StartupSchedule, SystemSet, UpdateSchedule
 from pacman.ecs.update import PostUpdate, PreUpdate
 from pacman.ecs.world import (
     Component,
@@ -92,9 +92,14 @@ def render_sprite2d(
     pygame.display.flip()
 
 
+GraphicsSystems = SystemSet("graphics-systems")
+
+
 def graphics_plugins(world: World) -> None:
     world.with_systems(StartupSchedule, init_window).with_systems(
         UpdateSchedule,
-        Systems(render_sprite2d).in_set(PostUpdate).after(PropagateTransform),
+        Systems(render_sprite2d)
+        .in_set(PostUpdate, GraphicsSystems)
+        .after(PropagateTransform),
         Systems(poll_events, close_on_close).chain().in_set(PreUpdate),
     )
index 48a75f718c4667ccad56c9daa9d9ae45ed7552e2..4b5af6df2902109b862c3152b0ed843f05af78e7 100644 (file)
@@ -1,21 +1,48 @@
 import math
 from collections.abc import Callable
+from dataclasses import dataclass
 from pathlib import Path
+from random import random
 
 import pygame
 from mazegenerator import MazeGenerator
-from pygame import Vector2, Vector3
+from pygame import K_a, K_d, K_s, K_w, Vector2, Vector3
 
 from pacman.ecs import minimal_plugins
 from pacman.ecs.assets import Assets
 from pacman.ecs.coord import Coord2D, CoordAbs2D
-from pacman.ecs.graphics import ShouldRender, Sprite2D, graphics_plugins
+from pacman.ecs.graphics import (
+    GraphicsSystems,
+    ShouldRender,
+    Sprite2D,
+    WindowEvents,
+    graphics_plugins,
+)
 from pacman.ecs.hierarchy import ChildOf
 from pacman.ecs.schedule import StartupSchedule, UpdateSchedule
-from pacman.ecs.world import Component, Entity, Query, World
+from pacman.ecs.update import PostUpdate, PreUpdate
+from pacman.ecs.world import Component, Entity, Not, Query, Systems, World
+from pacman.utils.panic import panic
 
 
 class Maze(Component):
+    def __init__(self, x: int, y: int) -> None:
+        self._cells: dict[tuple[int, int], Entity] = {}
+        self._x = x
+        self._y = y
+
+
+@dataclass
+class Cell(Component):
+    n: int
+    pass
+
+
+class PacGum(Component):
+    pass
+
+
+class SuperPacGum(Component):
     pass
 
 
@@ -23,45 +50,240 @@ def create_maze(
     world: World, assets: Assets[Sprite2D]
 ) -> Callable[[MazeGenerator], Entity]:
     def inner(maze: MazeGenerator) -> Entity:
+        root_maze = Maze(len(maze.maze[0]), len(maze.maze))
         root = world.spawn(
-            tuple[Maze, Coord2D, CoordAbs2D], (Maze(), Coord2D(), CoordAbs2D())
+            tuple[Maze, Coord2D, CoordAbs2D],
+            (root_maze, Coord2D(), CoordAbs2D()),
         )
-        cell_paths = [
-            [f"./asset/walls/{cell}.png" for cell in line]
-            for line in maze.maze
-        ]
         cell_dims = Vector3(64, 64, 0)
-        cells = [
-            (
+        cells = {
+            (x, y): (
                 ChildOf(root),
                 Coord2D(cell_dims.elementwise() * Vector3(x, y, 0)),
-                CoordAbs2D(),
-                assets.load(cell),
+                Cell(cell),
                 ShouldRender(),
+                CoordAbs2D(),
+                assets.load(f"./asset/walls/{cell}.png"),
             )
-            for y, line in enumerate(cell_paths)
+            for y, line in enumerate(maze.maze)
             for x, cell in enumerate(line)
-        ]
-        world.spawn_many(
-            tuple[ChildOf, Coord2D, CoordAbs2D, Sprite2D, ShouldRender], cells
+        }
+        spawned_cells = world.spawn_many(
+            tuple[ChildOf, Coord2D, Cell, ShouldRender, CoordAbs2D, Sprite2D],
+            list(cells.values()),
         )
+        root_maze._cells = dict(zip(cells.keys(), spawned_cells, strict=True))
         return root
 
     return inner
 
 
+def spawn_superpacgums(
+    world: World, mazes: Query[Maze], assets: Assets[Sprite2D]
+) -> None:
+    pacgums = []
+    for maze in mazes:
+        for pos in [
+            (0, 0),
+            (0, maze._y - 1),
+            (maze._x - 1, 0),
+            (maze._x - 1, maze._y - 1),
+        ]:
+            cell = maze._cells[pos]
+            pacgums.append(
+                (
+                    ChildOf(cell),
+                    Coord2D(0, 0, -1),
+                    CoordAbs2D(),
+                    assets.load("./asset/misc/superpacgum.png"),
+                    ShouldRender(),
+                    SuperPacGum(),
+                )
+            )
+    world.spawn_many(
+        tuple[
+            ChildOf, Coord2D, CoordAbs2D, Sprite2D, ShouldRender, SuperPacGum
+        ],
+        pacgums,
+    )
+
+
+def spawn_pacgums(
+    world: World,
+    cells: Query[tuple[Entity, Cell]],
+    superpacgums: Query[ChildOf, SuperPacGum],
+    assets: Assets[Sprite2D],
+) -> None:
+    ignore = {e.parent for e in superpacgums}
+    pacgums = []
+    for ent, cell in cells:
+        if ent in ignore:
+            continue
+        if cell.n == 15:
+            continue
+        if random() > 0.7:
+            continue
+        pacgums.append(
+            (
+                ChildOf(ent),
+                Coord2D(0, 0, -1),
+                CoordAbs2D(),
+                assets.load("./asset/misc/pacgum.png"),
+                ShouldRender(),
+                PacGum(),
+            )
+        )
+    world.spawn_many(
+        tuple[ChildOf, Coord2D, CoordAbs2D, Sprite2D, ShouldRender, PacGum],
+        pacgums,
+    )
+
+
 def setup_maze(world: World) -> None:
     world.add_system(create_maze)(MazeGenerator())
 
 
-def move_maze(mazes: Query[Coord2D, Maze]) -> None:
-    tick = pygame.time.get_ticks()
-    for coord in mazes:
-        n = 400
-        coord.y = n + n * math.sin(tick / 100)
+class PlayerControlled(Component):
+    def __init__(self) -> None:
+        self._dir: int = 0
+
+    @property
+    def direction(self) -> Vector2:
+        match self._dir:
+            case 0:
+                return Vector2(0, -1)
+            case 1:
+                return Vector2(1, 0)
+            case 2:
+                return Vector2(0, 1)
+            case 3:
+                return Vector2(-1, 0)
+            case _:
+                raise Exception()
+
+
+def update_player_controlled(
+    entities: Query[PlayerControlled], events: WindowEvents
+) -> None:
+    vec = None
+    for key in events.keys():
+        if key == K_w:
+            vec = 0
+        if key == K_d:
+            vec = 1
+        if key == K_s:
+            vec = 2
+        if key == K_a:
+            vec = 3
+    if vec is not None:
+        for ctl in entities:
+            ctl._dir = vec
+
+
+def move_player_controlled(
+    entities: Query[tuple[Coord2D, PlayerControlled]],
+) -> None:
+    for pos, ctl in entities:
+        direction = ctl.direction * 4
+        pos += Vector3(direction.x, direction.y, 0)
+
+
+@dataclass
+class AnimationSet(Component):
+    anims: list[list[Sprite2D]]
+    ticks_per_frame: int
+    idx: int = 0
+    frame: int = 0
+    curr_tick: int = 0
+
+    @property
+    def curr_frame(self) -> Sprite2D:
+        return self.anims[self.idx][self.frame]
+
+    def tick(self) -> None:
+        self.curr_tick += 1
+        self.frame += self.curr_tick // self.ticks_per_frame
+        self.curr_tick %= self.ticks_per_frame
+        self.frame %= len(self.anims[self.idx])
+
+
+def insert_animation_sprites(
+    world: World, query: Query[tuple[Entity, AnimationSet], Not[Sprite2D]]
+) -> None:
+    inserts = []
+    for ent, anim in query:
+        inserts.append((ent, anim.curr_frame))
+    world.insert_many(Sprite2D, inserts)
+
+
+def update_animations(
+    query: Query[tuple[Sprite2D, AnimationSet], ShouldRender],
+) -> None:
+    for sprite, anim in query:
+        sprite.surface = anim.curr_frame.surface
+        anim.tick()
+
+
+class Pacman(Component):
+    pass
+
+
+def spawn_pacman(world: World, assets: Assets[Sprite2D]) -> None:
+    world.spawn(
+        tuple[
+            Coord2D,
+            CoordAbs2D,
+            AnimationSet,
+            Pacman,
+            PlayerControlled,
+            ShouldRender,
+        ],
+        (
+            Coord2D(0, 0, -10),
+            CoordAbs2D(),
+            AnimationSet(
+                [
+                    [
+                        assets.load(
+                            f"./asset/player/player_{direction}{frame}.png"
+                        )
+                        for frame in "ABCB"
+                    ]
+                    for direction in "URDL"
+                ],
+                4,
+            ),
+            Pacman(),
+            PlayerControlled(),
+            ShouldRender(),
+        ),
+    )
+
+
+def update_pacman_direction(
+    query: Query[tuple[AnimationSet, PlayerControlled], Pacman],
+) -> None:
+    for anims, ctl in query:
+        anims.idx = ctl._dir
 
 
 def run_game() -> None:
     World().with_plugins(graphics_plugins, minimal_plugins).with_systems(
-        StartupSchedule, setup_maze
+        StartupSchedule,
+        Systems(
+            setup_maze, spawn_superpacgums, spawn_pacgums, spawn_pacman
+        ).chain(),
+    ).with_systems(
+        UpdateSchedule,
+        Systems(update_player_controlled, move_player_controlled)
+        .chain()
+        .in_set(PreUpdate),
+        Systems(
+            insert_animation_sprites,
+            update_pacman_direction,
+            update_animations,
+        )
+        .chain()
+        .before(GraphicsSystems)
+        .in_set(PostUpdate),
     ).run_main()