From: = <=> Date: Mon, 31 Aug 2026 15:20:05 +0000 (+0200) Subject: Wiggling yippee X-Git-Url: https://git.uwuaxy.net/sitemap.xml?a=commitdiff_plain;h=db89f2b65fa474a5baeb1694b980a4a2b2309511;p=axy%2Fft%2Fpacman.git Wiggling yippee --- diff --git a/pyproject.toml b/pyproject.toml index f34688c..c1a0c92 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,7 +28,7 @@ build-backend = "uv_build" [tool.ruff] line-length = 79 -lint.select = ["E", "F", "UP", "B", "SIM", "I", "ARG", "N", "D"] +lint.select = ["E", "F", "UP", "B", "SIM", "I", "ARG", "N", "D", "ANN", "PYI"] [tool.ruff.lint.pydocstyle] convention = "google" @@ -36,10 +36,6 @@ convention = "google" [tool.uv.sources] mazegenerator = { path = "mazegenerator-2.0.2-py3-none-any.whl" } -#[[tool.mypy.overrides]] -#module = ["fire"] -#follow_untyped_imports = true - [tool.ty.rules] all = "error" possibly-missing-import = "ignore" diff --git a/src/pacman/ecs/__init__.py b/src/pacman/ecs/__init__.py index 44eb0ad..b57e604 100644 --- a/src/pacman/ecs/__init__.py +++ b/src/pacman/ecs/__init__.py @@ -1,5 +1,6 @@ from collections.abc import Callable, Generator, Iterable from dataclasses import dataclass +from pathlib import Path from typing import Any, cast, get_args, overload import pygame.sprite @@ -69,33 +70,40 @@ class World: args = get_args(ty) if len(args) == 0: return - for entity in self._entities: - try: - yield cast( - tuple[*T], - tuple( - entity if e is Entity else self._components[e][entity] - for e in args - ), - ) - except KeyError: + sets: list[dict[Entity, Any]] = sorted( + ( + self._entities if arg is Entity else self._components[arg] + for arg in args + if arg is Entity or arg in self._components + ), + key=len, + reverse=True, + ) + if len(sets) == 0: + return + for entity in sets.pop(): + if any(entity not in e for e in sets): continue + yield cast( + tuple[*T], + tuple( + entity if e is Entity else self._components[e][entity] + for e in args + ), + ) @overload def __getitem__[T](self, arg: Resource[T]) -> T: ... - @overload def __getitem__[*T]( self, arg: type[tuple[*T]] ) -> Generator[tuple[*T]]: ... - @overload def __getitem__(self, arg: Entity) -> EntityThunk: ... - def __getitem__( self, - arg, - ): + arg: Any, + ) -> Any: if isinstance(arg, Entity): return self.entity(arg) if isinstance(arg, Resource): @@ -104,32 +112,30 @@ class World: @overload def __setitem__[T](self, key: Resource[T], val: T) -> None: ... - @overload def __setitem__[*T](self, key: Entity, val: tuple[*T]) -> None: ... - - def __setitem__(self, key, val): - if isinstance(key, Entity): + def __setitem__(self, key: Any, val: Any) -> None: + if isinstance(key, Entity) and isinstance(val, tuple): if key in self: del self[key] self._entities[key] = set() for component in val: self[key][type(component)] = component - else: + elif isinstance(key, Resource) and isinstance(val, key.storage): self._resources[key.storage] = val + else: + raise TypeError() @overload def __delitem__[T](self, key: Resource[T]) -> None: ... - @overload def __delitem__(self, key: Entity) -> None: ... - - def __delitem__(self, key): + def __delitem__[T](self, key: Entity | Resource[T]) -> None: if isinstance(key, Entity): components = self._entities[key] - del self._entities[entity] + del self._entities[key] for component in components: - del self._components[component][entity] + del self._components[component][key] else: del self._resources[key.storage] @@ -138,7 +144,7 @@ class World: @overload def __contains__(self, item: Entity) -> bool: ... - def __contains__(self, item): + def __contains__[T](self, item: Entity | Resource[T]) -> bool: if isinstance(item, Resource): return item.storage in self._resources return item in self._entities @@ -212,6 +218,7 @@ def minimal_plugins(world: World) -> None: @dataclass class ScreenCoord2D: vec: Vector2 + depth: float class ShouldRender: @@ -221,86 +228,77 @@ class ShouldRender: @dataclass class Window: surface: pygame.Surface - - -def init_window(world: World) -> None: - world[Resource(Window)] = Window(pygame.display.set_mode()) + clock: pygame.time.Clock @dataclass -class Sprite2D: - surface: pygame.Surface +class WindowEvents: + events: list[pygame.event.Event] -def render_sprite2d(world: World) -> None: - window = world.res_s(Window) - sprites = [ - (sprite.surface, coord.vec) - for coord, sprite, _ in world.query( - tuple[ScreenCoord2D, Sprite2D, ShouldRender] - ) - ] - window.surface.blits(sprites) +def init_window(world: World) -> None: + pygame.init() + world[Resource(Window)] = Window( + pygame.display.set_mode(), pygame.time.Clock() + ) + world[Resource(WindowEvents)] = WindowEvents([]) @dataclass -class ShapeColor: - color: pygame.Color +class Sprite2D: + surface: pygame.Surface -@dataclass -class Circle: - radius: float +def poll_events(world: World) -> None: + world.res_s(WindowEvents).events = pygame.event.get() -def render_circle2d(world: World) -> None: - window = world.res_s(Window) - for circle, coord, color, _ in world.query( - tuple[Circle, ScreenCoord2D, ShapeColor, ShouldRender] +def close_on_close(world: World) -> None: + if pygame.QUIT in ( + event.type for event in world.res_s(WindowEvents).events ): - pygame.draw.circle( - window.surface, color.color, coord.vec, circle.radius - ) - - -@dataclass -class Rectangle: - dims: Vector2 + world[Resource(WorldShouldExit)] = WorldShouldExit() -def render_rectangle2d(world: World) -> None: +def render_sprite2d(world: World) -> None: window = world.res_s(Window) - for rect, coord, color, _ in world.query( - tuple[Rectangle, ScreenCoord2D, ShapeColor, ShouldRender] - ): - pygame.draw.rect(window.surface, color.color, (coord.vec, rect.dims)) - - -world = World() -a = Entity() -b = Entity() -c = Entity() -world[a] = (3, "hello") -world[b] = (2,) -world[c] = ("hai",) -world[b][tuple[str]] = ("wow",) -del world[a][str] -for entity, component in world[tuple[Entity, int]]: - print(entity) - print(component) -for string, integer in world[tuple[str, int]]: - print(string) - print(integer) -for string, integer in world[tuple[tuple[str], int]]: - print(string) - print(integer) - -world[Resource(int)] = 15155 -world[Resource(str)] = "hello ecs" -print(world[Resource(int)]) -del world[Resource(int)] -print(Resource(int) in world) -print(world[Resource(str)]) -world[Resource(int)] = 15 -print(Resource(int) in world) -print(world[Resource(int)]) + sprites_raw = sorted( + world[tuple[ScreenCoord2D, Sprite2D, ShouldRender]], + key=lambda e: e[0].depth, + reverse=True, + ) + sprites = [(sprite.surface, coord.vec) for coord, sprite, _ in sprites_raw] + window.surface.fill((0, 0, 0)) + window.surface.blits(sprites) + window.clock.tick(60) + pygame.display.flip() + + +def graphics_plugins(world: World) -> None: + world.with_systems(StartupSchedule, init_window).with_systems( + UpdateSchedule, poll_events, close_on_close, render_sprite2d + ) + + +def move_sprites(world: World) -> None: + import math + + time = pygame.time.get_ticks() / 100 + for (coord,) in world[tuple[ScreenCoord2D]]: + pos = max(time - coord.depth * 0.3, 0.0) + coord.vec.y = 500.0 + math.sin(pos) * 30.0 + + +world = ( + World() + .with_plugins(minimal_plugins, graphics_plugins) + .with_systems(UpdateSchedule, move_sprites) +) +assets = list(Path("./asset").rglob("*.png")) +for i, asset in enumerate(assets): + world[Entity()] = ( + ScreenCoord2D(Vector2(30.0 * i, 500.0), 1.0 * i), + Sprite2D(pygame.image.load(asset)), + ShouldRender(), + ) +world.run_main()