|
|
|
@ -1,23 +1,36 @@ |
|
|
|
|
"""Base (Pyoo) classes which implement basic protocols.""" |
|
|
|
|
|
|
|
|
|
import fnmatch |
|
|
|
|
import itertools |
|
|
|
|
|
|
|
|
|
from .base import make_verb, PyooVerbNotFound, PyooObjectNotFound |
|
|
|
|
from typing import cast, List, Optional, Tuple, Set, Dict |
|
|
|
|
|
|
|
|
|
from .base import make_verb, PyooVerbNotFound, InterpreterProtocol, SingleMultiString, Verb, VerbCallFrame |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Thing: |
|
|
|
|
"""The base of all Pyoo objects.""" |
|
|
|
|
|
|
|
|
|
def __init__(self, name: str, description: SingleMultiString = "A nondescript object."): |
|
|
|
|
"""Create a Thing |
|
|
|
|
|
|
|
|
|
class Thing(object): |
|
|
|
|
def __init__(self, thingname, description="A nondescript object."): |
|
|
|
|
names = [x.strip() for x in thingname.split(",")] |
|
|
|
|
Arguments: |
|
|
|
|
name (str): A string of comma-separated names for this object. |
|
|
|
|
description (str): A string that describes this object. |
|
|
|
|
""" |
|
|
|
|
names = [x.strip() for x in name.split(",")] |
|
|
|
|
self.name = names[0] |
|
|
|
|
self.names = tuple(names) |
|
|
|
|
self.names: Tuple[str, ...] = tuple(names) |
|
|
|
|
self.description = description |
|
|
|
|
self.location = None |
|
|
|
|
self.interpreter = None |
|
|
|
|
self.location: Optional["Container"] = None |
|
|
|
|
self.interpreter: Optional[InterpreterProtocol] = None |
|
|
|
|
|
|
|
|
|
def tell(self, message): |
|
|
|
|
def tell(self, message: SingleMultiString) -> None: |
|
|
|
|
print("<tell stub>", self, message) |
|
|
|
|
|
|
|
|
|
def verbs(self): |
|
|
|
|
def verbs(self) -> List[Verb]: |
|
|
|
|
"""Return a list of bound methods which denote themselves as verbs.""" |
|
|
|
|
verbs = list() |
|
|
|
|
verbs: List[Verb] = [] |
|
|
|
|
for item in dir(self): |
|
|
|
|
try: |
|
|
|
|
v = self.__getattribute__(item) |
|
|
|
@ -27,20 +40,20 @@ class Thing(object): |
|
|
|
|
continue |
|
|
|
|
return verbs |
|
|
|
|
|
|
|
|
|
def verb_globs(self): |
|
|
|
|
def verb_globs(self) -> List[Tuple[str, Tuple, Verb, "Thing"]]: |
|
|
|
|
"""Return a list of (globstr, bound method) where commands matching globstr should call method (given that |
|
|
|
|
'that' matches an object in the soup). |
|
|
|
|
""" |
|
|
|
|
verbglobs = list() |
|
|
|
|
verbglobs: List[Tuple[str, Tuple, Verb, "Thing"]] = [] |
|
|
|
|
for vrb in self.verbs(): |
|
|
|
|
vvars = [vrb.names] |
|
|
|
|
vvars: List[Tuple] = [tuple(vrb.names)] |
|
|
|
|
if vrb.callspec[0] == "this": |
|
|
|
|
vvars.append(self.names) |
|
|
|
|
elif vrb.callspec[0] in ("that", "any"): |
|
|
|
|
vvars.append(("*",)) |
|
|
|
|
|
|
|
|
|
if vrb.callspec[1] != "none": |
|
|
|
|
vvars.append(vrb.callspec[1]) |
|
|
|
|
if vrb.callspec[1] != ["none"]: |
|
|
|
|
vvars.append(tuple(vrb.callspec[1])) |
|
|
|
|
|
|
|
|
|
if vrb.callspec[2] == "this": |
|
|
|
|
vvars.append(self.names) |
|
|
|
@ -50,25 +63,45 @@ class Thing(object): |
|
|
|
|
for combo in itertools.product(*vvars): |
|
|
|
|
globstr = " ".join(combo) |
|
|
|
|
verbglobs.append((globstr, tuple(combo), vrb, self)) |
|
|
|
|
|
|
|
|
|
return verbglobs |
|
|
|
|
|
|
|
|
|
def handle_move(self, newlocation): |
|
|
|
|
def handle_move(self, newlocation: "Container") -> None: |
|
|
|
|
"""Handle moving this object to a new location. |
|
|
|
|
|
|
|
|
|
Acts as a way for children to hook this occurance too. |
|
|
|
|
""" |
|
|
|
|
self.location = newlocation |
|
|
|
|
|
|
|
|
|
def handle_remove(self, oldlocation): |
|
|
|
|
def handle_remove(self, oldlocation: "Container") -> None: |
|
|
|
|
"""Handle removing this object from a container. |
|
|
|
|
|
|
|
|
|
Acts as a way for children to hook this occurance too. |
|
|
|
|
""" |
|
|
|
|
self.location = None |
|
|
|
|
|
|
|
|
|
def __repr__(self): |
|
|
|
|
def __repr__(self) -> str: |
|
|
|
|
return "<Thing '%s' object at 0x%x>" % (self.name, self.__hash__()) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Container(Thing): |
|
|
|
|
def __init__(self, names, description=""): |
|
|
|
|
"""A Pyoo object which contains other Pyoo objects.""" |
|
|
|
|
|
|
|
|
|
def __init__(self, names: str, description: SingleMultiString = ""): |
|
|
|
|
"""Create a Container. |
|
|
|
|
|
|
|
|
|
Arguments: |
|
|
|
|
names (str): A comma-separated list of names. |
|
|
|
|
description (str): A description for this object. |
|
|
|
|
|
|
|
|
|
""" |
|
|
|
|
super().__init__(names, description) |
|
|
|
|
self.contents = set() |
|
|
|
|
self.name_cache = [] |
|
|
|
|
self.command_cache = [] |
|
|
|
|
self.contents: Set[Thing] = set() |
|
|
|
|
self.name_cache: List[Tuple[str, Thing]] = [] |
|
|
|
|
self.command_cache: List[Tuple[str, Tuple, Verb, Thing]] = [] |
|
|
|
|
|
|
|
|
|
def update_caches(self): |
|
|
|
|
def update_caches(self) -> None: |
|
|
|
|
"""Update the internal cache of contained object's verbs and names.""" |
|
|
|
|
self.name_cache = [] |
|
|
|
|
self.command_cache = [] |
|
|
|
|
for obj in self.contents: |
|
|
|
@ -80,10 +113,19 @@ class Container(Thing): |
|
|
|
|
self.command_cache.append(verbglob) |
|
|
|
|
for verbglob in self.verb_globs(): |
|
|
|
|
if verbglob[0][0] == "#": |
|
|
|
|
continue |
|
|
|
|
continue |
|
|
|
|
self.command_cache.append(verbglob) |
|
|
|
|
|
|
|
|
|
def get_command_matches(self, command_spec): |
|
|
|
|
def get_command_matches(self, command_spec: str) -> List[Tuple[str, Tuple, Verb, Thing]]: |
|
|
|
|
"""Return a list of commands which match a command_spec ordered by specificity. |
|
|
|
|
|
|
|
|
|
Arguments: |
|
|
|
|
command_spec (str): A command |
|
|
|
|
|
|
|
|
|
Returns: |
|
|
|
|
array[tuple]: A list of command specifiers. |
|
|
|
|
|
|
|
|
|
""" |
|
|
|
|
res = [x for x in self.command_cache if fnmatch.fnmatch(command_spec, x[0])] |
|
|
|
|
# sort by ambiguity (percentage of *) |
|
|
|
|
res.sort(key=lambda a: a[0].count("*") / float(len(a[0]))) |
|
|
|
@ -91,62 +133,94 @@ class Container(Thing): |
|
|
|
|
raise PyooVerbNotFound |
|
|
|
|
return res |
|
|
|
|
|
|
|
|
|
def get_name_matches(self, name): |
|
|
|
|
def get_name_matches(self, name: str) -> List[Tuple[str, Thing]]: |
|
|
|
|
"""Return a list of objects which match a name spec. |
|
|
|
|
|
|
|
|
|
Arguments: |
|
|
|
|
name (str): A name of an object |
|
|
|
|
|
|
|
|
|
Returns: |
|
|
|
|
array[tuple]: A list of name,thing tuples. |
|
|
|
|
|
|
|
|
|
""" |
|
|
|
|
return [x for x in self.name_cache if fnmatch.fnmatch(name, x[0])] |
|
|
|
|
|
|
|
|
|
def handle_exit(self, oldobj): |
|
|
|
|
def handle_exit(self, oldobj: Thing) -> None: |
|
|
|
|
"""Handle an object leaving the container. |
|
|
|
|
|
|
|
|
|
This allows children to hook this occurence also. |
|
|
|
|
|
|
|
|
|
Arguments: |
|
|
|
|
oldobj (`pyoo.things.Thing`): The object which is exiting. |
|
|
|
|
|
|
|
|
|
""" |
|
|
|
|
self.contents.remove(oldobj) |
|
|
|
|
oldobj.handle_remove(self) |
|
|
|
|
|
|
|
|
|
def handle_enter(self, newobj): |
|
|
|
|
def handle_enter(self, newobj: Thing) -> None: |
|
|
|
|
"""Handle an object entering the container. |
|
|
|
|
|
|
|
|
|
This allows children to hook this occurence also. |
|
|
|
|
|
|
|
|
|
Arguments: |
|
|
|
|
newobj (`pyoo.things.Thing`): The object which is entering. |
|
|
|
|
|
|
|
|
|
""" |
|
|
|
|
self.contents.add(newobj) |
|
|
|
|
newobj.handle_move(self) |
|
|
|
|
try: |
|
|
|
|
newobj.update_caches() |
|
|
|
|
cast("Container", newobj).update_caches() |
|
|
|
|
except AttributeError: |
|
|
|
|
pass |
|
|
|
|
self.update_caches() |
|
|
|
|
|
|
|
|
|
def handle_tell(self, msg, who): |
|
|
|
|
def handle_tell(self, msg: SingleMultiString, who: Set[Thing]) -> None: |
|
|
|
|
"""Handle processing a tell to a list of objects. |
|
|
|
|
|
|
|
|
|
""" |
|
|
|
|
for obj in who: |
|
|
|
|
obj.tell(msg) |
|
|
|
|
|
|
|
|
|
def tell(self, msg): |
|
|
|
|
def tell(self, msg: SingleMultiString) -> None: |
|
|
|
|
"""Handle telling all content objects.""" |
|
|
|
|
self.handle_tell(msg, self.contents) |
|
|
|
|
|
|
|
|
|
def __repr__(self): |
|
|
|
|
def __repr__(self) -> str: |
|
|
|
|
return "<Container '%s' object at 0x%x>" % (self.name, self.__hash__()) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Place(Container): |
|
|
|
|
def __init__(self, names, description=""): |
|
|
|
|
"""A specialized container which models a place.""" |
|
|
|
|
|
|
|
|
|
def __init__(self, names: str, description: str = "") -> None: |
|
|
|
|
"""Create a place.""" |
|
|
|
|
super().__init__(names, description) |
|
|
|
|
self.ways = dict() |
|
|
|
|
self.ways: Dict[str, Container] = dict() |
|
|
|
|
|
|
|
|
|
def tell_only(self, message, verb_callframe): |
|
|
|
|
def tell_only(self, message: SingleMultiString, verb_callframe: VerbCallFrame) -> None: |
|
|
|
|
self.handle_tell(message, self.contents - {verb_callframe.player}) |
|
|
|
|
|
|
|
|
|
# this verb expects to be annotated from update_go. We never want it ot be at the top of a match list by its deault |
|
|
|
|
# name either |
|
|
|
|
@make_verb("#go", "none", "none", "none") |
|
|
|
|
def go(self, verb_callframe): |
|
|
|
|
def go(self, verb_callframe: VerbCallFrame) -> None: |
|
|
|
|
self.do_go(verb_callframe.verbname, verb_callframe) |
|
|
|
|
|
|
|
|
|
@make_verb("go,move,walk,run", "any", "none", "none") |
|
|
|
|
def go_dir(self, verb_callframe): |
|
|
|
|
def go_dir(self, verb_callframe: VerbCallFrame) -> None: |
|
|
|
|
self.do_go(verb_callframe.dobjstr, verb_callframe) |
|
|
|
|
|
|
|
|
|
def do_go(self, direction, verb_callframe): |
|
|
|
|
def do_go(self, direction: str, verb_callframe: VerbCallFrame) -> None: |
|
|
|
|
if direction in self.ways: |
|
|
|
|
self.tell_only("{} moves {}".format(verb_callframe.player, direction), verb_callframe) |
|
|
|
|
self.tell_only("{} moves {}".format(verb_callframe.player.name, direction), verb_callframe) |
|
|
|
|
verb_callframe.player.tell("You move {}".format(direction)) |
|
|
|
|
verb_callframe.environment.handle_move(self.ways[direction], verb_callframe.player) |
|
|
|
|
|
|
|
|
|
def handle_enter(self, newobj): |
|
|
|
|
def handle_enter(self, newobj: Thing) -> None: |
|
|
|
|
super().handle_enter(newobj) |
|
|
|
|
self.tell("{} arrives".format(newobj.name)) |
|
|
|
|
self.handle_tell("{} arrives".format(newobj.name), self.contents - {newobj}) |
|
|
|
|
|
|
|
|
|
def update_go(self): |
|
|
|
|
def update_go(self) -> None: |
|
|
|
|
# note does no actually remove items from the go verb in case the descender is overloading. |
|
|
|
|
# also note, the interpreter needs to have update() called after this is called. |
|
|
|
|
for direction in self.ways: |
|
|
|
@ -155,13 +229,13 @@ class Place(Container): |
|
|
|
|
if self.interpreter: |
|
|
|
|
self.interpreter.update() |
|
|
|
|
|
|
|
|
|
def __repr__(self): |
|
|
|
|
def __repr__(self) -> str: |
|
|
|
|
return "<Place '%s' object at 0x%x>" % (self.name, self.__hash__()) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Player(Container): |
|
|
|
|
def __init__(self, names, description=""): |
|
|
|
|
def __init__(self, names: str, description: str = ""): |
|
|
|
|
super().__init__(names, description) |
|
|
|
|
|
|
|
|
|
def __repr__(self): |
|
|
|
|
def __repr__(self) -> str: |
|
|
|
|
return "<Player '%s' object at 0x%x>" % (self.name, self.__hash__()) |
|
|
|
|