Source code for ppb.gomlib

"""
The Game Object Model.
"""
from collections import defaultdict, deque
from collections.abc import Collection
from typing import Hashable
from typing import Iterable
from typing import Iterator
from typing import Type

from ppb.errors import BadChildException
from ppb.errors import NotMyChildError


[docs] class Children(Collection): """ A container for game objects. Supports tagging. """ def __init__(self): self._all = set() self._kinds = defaultdict(set) self._tags = defaultdict(set) def __contains__(self, item: 'GameObject') -> bool: return item in self._all def __iter__(self) -> Iterator['GameObject']: return (x for x in list(self._all)) def __len__(self) -> int: return len(self._all)
[docs] def add(self, child: 'GameObject', tags: Iterable[Hashable] = ()) -> 'GameObject': """ Add a child. :param child: Any Game Object. The item to be added. :param tags: An iterable of Hashable objects. (Probably strings.) Values that can be used to retrieve a group containing the child. Examples: :: children.add(MyObject()) children.add(MyObject(), tags=("red", "blue") """ # Ugh, this is copied in EngineChildren if isinstance(child, type): raise BadChildException(child) 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(child) for kind in type(child).mro(): self._kinds[kind].add(child) for tag in tags: self._tags[tag].add(child) return child
[docs] def remove(self, child: 'GameObject') -> 'GameObject': """ Remove the given object from the container. :param child: A hashable contained by container. Example: :: container.remove(myObject) """ # Ugh, this is copied in EngineChildren try: self._all.remove(child) except KeyError as exc: raise NotMyChildError() from exc for kind in type(child).mro(): self._kinds[kind].remove(child) for s in self._tags.values(): s.discard(child) return child
[docs] def get(self, *, kind: Type = None, tag: 'GameObject' = None, **_) -> Iterator: """ Iterate over the 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. (Probably strings.) 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: :: children.get(type=MyObject) children.get(tag="red") children.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))
[docs] def walk(self): """ Iterate over the children and their children. """ for child in self._all: yield child if hasattr(child, 'children') and hasattr(child.children.walk): yield from child.children.walk()
[docs] def tags(self): """ Generates all of the tags currently in the collections """ yield from self._tags
[docs] def kinds(self): """ Generates all types of the children (including super types) """ yield from self._kinds
[docs] class GameObject: """ A generic parent class for game objects. Handles: * Property-based init (``Sprite(position=pos, image=img)``) * Children management """ #: The children of this object children: Children def __init__(self, **props): super().__init__() self.children = Children() for k, v in props.items(): setattr(self, k, v) def __iter__(self) -> Iterator: """ Shorthand for :meth:`Children.__iter__()` """ yield from self.children
[docs] def add(self, child: 'GameObject', tags: Iterable = ()) -> None: """ Shorthand for :meth:`Children.add()` """ return self.children.add(child, tags)
[docs] def get(self, *, kind: Type = None, tag: Hashable = None, **kwargs) -> Iterator: """ Shorthand for :meth:`Children.get()` """ return self.children.get(kind=kind, tag=tag, **kwargs)
[docs] def remove(self, child: 'GameObject') -> 'GameObject': """ Shorthand for :meth:`Children.remove()` """ return self.children.remove(child)
[docs] def walk(root) -> Iterable[GameObject]: """ Conducts a walk of the GOM tree from the root. Includes the root. Is non-recursive. """ q = deque([root]) while q: cur = q.popleft() yield cur if hasattr(cur, 'children'): q.extend(cur.children)