Source code for ppb.scenes

from collections import defaultdict
from collections.abc import Collection
from numbers import Number
from typing import Callable
from typing import Hashable
from typing import Iterable
from typing import Iterator
from typing import Sequence
from typing import Type

from ppb.camera import Camera


class GameObjectCollection(Collection):
    """A container for game objects."""

    def __init__(self):
        self.all = set()
        self.kinds = defaultdict(set)
        self.tags = defaultdict(set)

    def __contains__(self, item: Hashable) -> bool:
        return item in self.all

    def __iter__(self) -> Iterator[Hashable]:
        return (x for x in list(self.all))

    def __len__(self) -> int:
        return len(self.all)

    def add(self, game_object: Hashable, tags: Iterable[Hashable] = ()) -> None:
        """
        Add a game_object to the container.

        :param game_object: Any Hashable object. The item to be added.
        :param tags: An iterable of Hashable objects. Values that can be used to
              retrieve a group containing the game_object.

        Examples: ::

            container.add(MyObject())

            container.add(MyObject(), tags=("red", "blue")
        """
        if isinstance(tags, (str, bytes)):
            raise TypeError("You passed a string instead of an iterable, this probably isn't what you intended.\n\nTry making it a tuple.")
        self.all.add(game_object)

        for kind in type(game_object).mro():
            self.kinds[kind].add(game_object)
        for tag in tags:
            self.tags[tag].add(game_object)

    def get(self, *, kind: Type = None, tag: Hashable = None, **_) -> Iterator:
        """
        Get an iterator of objects by kind or tag.

        :param kind: Any type. Pass to get a subset of contained items with the given
              type.
        :param tag: Any Hashable object. Pass to get a subset of contained items with
             the given tag.

        Pass both kind and tag to get objects that are both that type and that
        tag.

        Examples: ::

            container.get(type=MyObject)

            container.get(tag="red")

            container.get(type=MyObject, tag="red")
        """
        if kind is None and tag is None:
            raise TypeError("get() takes at least one keyword-only argument. 'kind' or 'tag'.")
        kinds = self.all
        tags = self.all
        if kind is not None:
            kinds = self.kinds[kind]
        if tag is not None:
            tags = self.tags[tag]
        return (x for x in kinds.intersection(tags))

    def remove(self, game_object: Hashable) -> None:
        """
        Remove the given object from the container.

        :param game_object: A hashable contained by container.

        Example: ::

            container.remove(myObject)
        """
        self.all.remove(game_object)
        for kind in type(game_object).mro():
            self.kinds[kind].remove(game_object)
        for s in self.tags.values():
            s.discard(game_object)


[docs]class BaseScene: # Background color, in RGB, each channel is 0-255 background_color: Sequence[int] = (0, 0, 100) container_class: Type = GameObjectCollection camera_class = Camera def __init__(self, *, set_up: Callable = None, **kwargs): super().__init__() for k, v in kwargs.items(): setattr(self, k, v) self.game_objects = self.container_class() if set_up is not None: set_up(self) def __contains__(self, item: Hashable) -> bool: return item in self.game_objects def __iter__(self) -> Iterator: return (x for x in self.game_objects) @property def kinds(self): return self.game_objects.kinds @property def tags(self): return self.game_objects.tags @property def main_camera(self) -> Camera: try: camera = next(self.game_objects.get(tag="main_camera")) except StopIteration: camera = None return camera @main_camera.setter def main_camera(self, value: Camera): for camera in self.game_objects.get(tag="main_camera"): self.game_objects.remove(camera) self.game_objects.add(value, tags=["main_camera"])
[docs] def add(self, game_object: Hashable, tags: Iterable=())-> None: """ Add a game_object to the scene. :param game_object: Any GameObject object. The item to be added. :param tags: An iterable of Hashable objects. Values that can be used to retrieve a group containing the game_object. Examples: :: scene.add(MyGameObject()) scene.add(MyGameObject(), tags=("red", "blue") """ self.game_objects.add(game_object, tags)
[docs] def get(self, *, kind: Type=None, tag: Hashable=None, **kwargs) -> Iterator: """ Get an iterator of GameObjects by kind or tag. :param kind: Any type. Pass to get a subset of contained GameObjects with the given type. :param tag: Any Hashable object. Pass to get a subset of contained GameObjects with the given tag. Pass both kind and tag to get objects that are both that type and that tag. Examples: :: scene.get(type=MyGameObject) scene.get(tag="red") scene.get(type=MyGameObject, tag="red") """ return self.game_objects.get(kind=kind, tag=tag, **kwargs)
[docs] def remove(self, game_object: Hashable) -> None: """ Remove the given object from the scene. :param game_object: A game object. Example: :: scene.remove(my_game_object) """ self.game_objects.remove(game_object)
[docs] def sprite_layers(self) -> Iterator: """ Return an iterator of the contained Sprites in ascending layer order. Sprites are part of a layer if they have a layer attribute equal to that layer value. Sprites without a layer attribute are considered layer 0. This function exists primarily to assist the Renderer subsystem, but will be left public for other creative uses. """ return sorted(self, key=lambda s: getattr(s, "layer", 0))