Initial chekin post-discontinuity.
This commit is contained in:
commit
eacebb0106
|
@ -0,0 +1,61 @@
|
|||
*.py[cod]
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Packages
|
||||
*.egg
|
||||
*.egg-info
|
||||
dist
|
||||
build
|
||||
eggs
|
||||
parts
|
||||
bin
|
||||
var
|
||||
sdist
|
||||
develop-eggs
|
||||
.installed.cfg
|
||||
lib
|
||||
lib64
|
||||
__pycache__
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
.coverage
|
||||
.tox
|
||||
nosetests.xml
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
|
||||
# Mr Developer
|
||||
.mr.developer.cfg
|
||||
.project
|
||||
.pydevproject
|
||||
|
||||
# Emacs git ignore
|
||||
# -*- mode: gitignore; -*-
|
||||
*~
|
||||
\#*\#
|
||||
/.emacs.desktop
|
||||
/.emacs.desktop.lock
|
||||
*.elc
|
||||
auto-save-list
|
||||
tramp
|
||||
.\#*
|
||||
|
||||
# Org-mode
|
||||
.org-id-locations
|
||||
*_archive
|
||||
|
||||
# flymake-mode
|
||||
*_flymake.*
|
||||
|
||||
# eshell files
|
||||
/eshell/history
|
||||
/eshell/lastdir
|
||||
|
||||
# elpa packages
|
||||
/elpa/
|
|
@ -0,0 +1,6 @@
|
|||
PYOO
|
||||
=====
|
||||
|
||||
A Python text-adventure engine inspired by MOO server.
|
||||
|
||||
See testverb.py and testrooms.py for an example mini-adventures.
|
|
@ -0,0 +1,25 @@
|
|||
Boost Software License - Version 1.0 - August 17th, 2003
|
||||
|
||||
Copyright (c) 2014, 2019, 2020, Cas Rusnov
|
||||
|
||||
Permission is hereby granted, free of charge, to any person or organization
|
||||
obtaining a copy of the software and accompanying documentation covered by
|
||||
this license (the "Software") to use, reproduce, display, distribute,
|
||||
execute, and transmit the Software, and to prepare derivative works of the
|
||||
Software, and to permit third-parties to whom the Software is furnished to
|
||||
do so, all subject to the following:
|
||||
|
||||
The copyright notices in the Software and this entire statement, including
|
||||
the above license grant, this restriction and the following disclaimer,
|
||||
must be included in all copies of the Software, in whole or in part, and
|
||||
all derivative works of the Software, unless such copies or derivative
|
||||
works are solely in the form of machine-executable object code generated by
|
||||
a source language processor.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
|
||||
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
|
||||
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,122 @@
|
|||
"""
|
||||
Base data and types for supporting Pyoo operations.
|
||||
"""
|
||||
|
||||
from collections import namedtuple
|
||||
from typing import Callable, List, Tuple, Type, Sequence, Union, Iterable, Protocol
|
||||
|
||||
|
||||
PREPOSITIONS = (
|
||||
"with/using",
|
||||
"at/to",
|
||||
# 'in front of',
|
||||
"in/inside/into",
|
||||
# 'on top of/on/onto/upon',
|
||||
"on/onto/upon",
|
||||
# 'out of/from inside/from',
|
||||
"from",
|
||||
"over",
|
||||
"through",
|
||||
"under/underneath/beneath",
|
||||
"behind",
|
||||
"beside",
|
||||
"for/about",
|
||||
"is",
|
||||
"as",
|
||||
"off",
|
||||
)
|
||||
"""`tuple[str]`: A list of preposition strings (equivilancies are / separated)"""
|
||||
|
||||
NORMALIZED_PREPS = tuple([x.split("/") for x in PREPOSITIONS])
|
||||
"""A list of prepositions with equiviliants split."""
|
||||
|
||||
|
||||
class PyooError(Exception):
|
||||
"""Generic Pyoo error."""
|
||||
|
||||
|
||||
class PyooVerbNotFound(PyooError):
|
||||
"""Pyoo Error which indicates a verb was not found."""
|
||||
|
||||
|
||||
class PyooObjectNotFound(PyooError):
|
||||
"""Pyoo Error which indicates an object was not found."""
|
||||
|
||||
|
||||
VerbCallFrame = namedtuple("VerbCallFrame", "environment,player,verbname,dobj,dobjstr,prepstr,iobj,iobjstr,argstr")
|
||||
"""Namedtuple which contains the call frame (all arguments) for a verb call."""
|
||||
|
||||
|
||||
class Verb:
|
||||
"""This is a wrapper for a callable to give it the properties required for verbs."""
|
||||
def __init__(self,
|
||||
function: Callable,
|
||||
names: List[str],
|
||||
callspec: Tuple[str, List[str], str]):
|
||||
self.function = function
|
||||
self.name = names[0]
|
||||
self.names = names
|
||||
self.callspec = callspec
|
||||
|
||||
def __call__(self, *args, **kwargs) -> None:
|
||||
return self.function(*args, **kwargs)
|
||||
|
||||
def __get__(self, obj, objtype: Type):
|
||||
class VerbFunction:
|
||||
def __init__(self, func: Callable, obj, typ: Type, verb: "Verb"):
|
||||
self._func = func
|
||||
self._obj = obj
|
||||
self._typ = typ
|
||||
self.is_verb = True
|
||||
self.name = verb.name
|
||||
self.names = verb.names
|
||||
self.callspec = verb.callspec
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
return self._func.__get__(self._obj, self._typ)(*args, **kwargs)
|
||||
|
||||
return VerbFunction(self.function, obj, objtype, self)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<Verb {self.name} {self.callspec}>"
|
||||
|
||||
|
||||
def make_verb(verbname: str, dobjspec: str, prepspec: str, iobjspec: str) -> Callable[..., Verb]:
|
||||
"""This simple decorator adds verb metadata to a method or function.
|
||||
|
||||
Arguments:
|
||||
verbname (str): List of verb names, comma separated, with wildcards.
|
||||
dobjspec (str): 'this' or 'that' or 'none' or 'any'
|
||||
(this = the object which defines the verb, that = an object in the soup, any = any string,
|
||||
none = blank)
|
||||
iobjspec (str): 'this' or 'that' or 'none' or 'any' (as above)
|
||||
prepspec (str): one of prepositions strings (as in the PREPOSITIONS list)
|
||||
"""
|
||||
def verb_decorate(verbfunc: Callable[[VerbCallFrame, ], None]) -> Verb:
|
||||
names = [x.strip() for x in verbname.split(",")]
|
||||
ps: List[str] = []
|
||||
if prepspec.find("/") > 0:
|
||||
ps = prepspec.split("/")
|
||||
else:
|
||||
for prep in NORMALIZED_PREPS:
|
||||
if prepspec in prep:
|
||||
ps = prep
|
||||
break
|
||||
ps = [prepspec]
|
||||
return Verb(verbfunc, names, (dobjspec, ps, iobjspec))
|
||||
|
||||
return verb_decorate
|
||||
|
||||
|
||||
#
|
||||
# type things
|
||||
#
|
||||
|
||||
|
||||
class InterpreterProtocol(Protocol):
|
||||
"""Defines minimal protocol for interpreter objects."""
|
||||
def update(self) -> None:
|
||||
...
|
||||
|
||||
|
||||
SingleMultiString = Union[Sequence[str], str, Iterable[str]]
|
|
@ -0,0 +1,139 @@
|
|||
"""This module defines the basic interpreter system."""
|
||||
|
||||
import fnmatch
|
||||
|
||||
from typing import Optional, List, Set, Tuple, cast
|
||||
|
||||
from .things import Thing, Container, Place, Player
|
||||
from .base import VerbCallFrame, PyooVerbNotFound, PyooObjectNotFound
|
||||
|
||||
|
||||
class Interpreter:
|
||||
"""Base interpreter class.
|
||||
|
||||
Manages a collection of objects, and invoking verbs on them.
|
||||
"""
|
||||
def __init__(self, contents: Optional[List[Thing]] = None):
|
||||
"""Initialize an interpreter.
|
||||
|
||||
Arguments:
|
||||
contents (optional list of things): The initial contents to populate the interpreter with.
|
||||
|
||||
"""
|
||||
if contents is None:
|
||||
contents = []
|
||||
self.contents: Set[Thing] = set(contents)
|
||||
for cont in self.contents:
|
||||
cont.interpreter = self
|
||||
self.content_cache: List[Tuple[str, Thing]] = []
|
||||
self.update()
|
||||
|
||||
def add_player(self, player_object: Player) -> None:
|
||||
"""Add a player to the interpreter."""
|
||||
self.contents.add(player_object)
|
||||
player_object.interpreter = self
|
||||
self.update()
|
||||
|
||||
def remove_player(self, player_object: Player) -> None:
|
||||
"""Remove a player from the interpreter."""
|
||||
self.contents.remove(player_object)
|
||||
self.update()
|
||||
|
||||
def update(self) -> None:
|
||||
"""Do any updates required when objects are added or removed."""
|
||||
self.update_caches()
|
||||
|
||||
def update_caches(self) -> None:
|
||||
"""Update the caches for objects contained in the interpreter, as well as updating all of the caches
|
||||
on contained containers.
|
||||
"""
|
||||
self.content_cache = []
|
||||
for obj in self.contents:
|
||||
for name in obj.names:
|
||||
self.content_cache.append((name, obj))
|
||||
try:
|
||||
cast(Container, obj).update_caches()
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
def handle_move(self, newroom: Place, player: Player) -> None:
|
||||
"""Move a player to a new contained room."""
|
||||
if player.location:
|
||||
player.location.handle_exit(player)
|
||||
newroom.handle_enter(player)
|
||||
|
||||
def lookup_global_object(self, objstr: str) -> List[Tuple[str, Thing]]:
|
||||
"""Find an object within the soup by objstr."""
|
||||
return [x for x in self.content_cache if fnmatch.fnmatch(objstr, x[0])]
|
||||
|
||||
def lookup_object(self, player: Player, objstr: str) -> Optional[Thing]:
|
||||
"""Find an object relative to a player
|
||||
|
||||
This first checks the player's inventory, and then the container which contains the
|
||||
player.
|
||||
"""
|
||||
m: Optional[List[Tuple[str, Thing]]] = None
|
||||
try:
|
||||
m = player.get_name_matches(objstr)
|
||||
except PyooObjectNotFound:
|
||||
m = None
|
||||
if not m and player.location:
|
||||
m = player.location.get_name_matches(objstr)
|
||||
if m:
|
||||
return m[0][1]
|
||||
else:
|
||||
return None
|
||||
|
||||
def interpret(self, command: str, player: Player) -> None:
|
||||
"""Interpret a player's command by splitting it into components and finding matching verbs."""
|
||||
# FIXME make this better to support mulitple word objstrs and prepstr
|
||||
if not command:
|
||||
return
|
||||
|
||||
cmd_comps = command.split()
|
||||
verbstr = cmd_comps[0]
|
||||
dobjstr = ""
|
||||
prepstr = ""
|
||||
iobjstr = ""
|
||||
argstr = ""
|
||||
|
||||
try:
|
||||
argstr = " ".join(cmd_comps[1:])
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
try:
|
||||
dobjstr = cmd_comps[1]
|
||||
prepstr = cmd_comps[2]
|
||||
iobjstr = cmd_comps[3]
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
try:
|
||||
cmdmatches = player.get_command_matches(command)
|
||||
except PyooVerbNotFound:
|
||||
if player.location:
|
||||
cmdmatches = player.location.get_command_matches(command)
|
||||
else:
|
||||
raise PyooVerbNotFound
|
||||
cmd = cmdmatches[0]
|
||||
# glob = cmd[0]
|
||||
# comps = cmd[1]
|
||||
verb = cmd[2]
|
||||
this = cmd[3]
|
||||
|
||||
dobj = None
|
||||
if verb.callspec[0] == "this":
|
||||
dobj = this
|
||||
elif verb.callspec[0] == "that":
|
||||
# lookup object
|
||||
dobj = self.lookup_object(player, dobjstr)
|
||||
|
||||
iobj = None
|
||||
if verb.callspec[2] == "this":
|
||||
iobj = this
|
||||
elif verb.callspec[2] == "that":
|
||||
# lookp object
|
||||
iobj = self.lookup_object(player, iobjstr)
|
||||
|
||||
return verb(VerbCallFrame(self, player, verbstr, dobj, dobjstr, prepstr, iobj, iobjstr, argstr))
|
|
@ -0,0 +1,87 @@
|
|||
"""PlaceLoader is a system to load simple plain text data to build a room-based space."""
|
||||
|
||||
from enum import Enum
|
||||
from typing import Dict, List, TextIO, Type
|
||||
|
||||
from .things import Place
|
||||
from .interpret import Interpreter
|
||||
|
||||
|
||||
class PlaceLoaderStates(Enum):
|
||||
START = 0
|
||||
DESC = 1
|
||||
EXITS = 2
|
||||
|
||||
|
||||
class PlaceLoader:
|
||||
"""
|
||||
A factory which loads a simple data format containing a description of rooms, and how
|
||||
they are connected, and produces room objects for each one.
|
||||
|
||||
The format is a plain text file containing a series of room definitions:
|
||||
|
||||
ROOMNAME
|
||||
DESCRIPTION (multiple lines)
|
||||
.
|
||||
direction,alias ROOMNAME2
|
||||
.
|
||||
ROOMNAME2 ...
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, in_file: TextIO, baseplace: Type[Place] = Place):
|
||||
"""Initialize the factory by loading a description file.
|
||||
|
||||
Arguments:
|
||||
in_file: A file to load the places from.
|
||||
baseplace: A type to initialize the rooms from.
|
||||
|
||||
"""
|
||||
self.base = baseplace
|
||||
self.places: Dict[str, Place] = {} # indexed by name
|
||||
|
||||
if in_file:
|
||||
self.process_file(in_file)
|
||||
|
||||
def process_file(self, in_file: TextIO) -> None:
|
||||
"""Load any room information from the passed in file.
|
||||
|
||||
Arguments:
|
||||
in_file: The file to load the rooms from.
|
||||
|
||||
"""
|
||||
state = PlaceLoaderStates.START
|
||||
desc: List[str] = []
|
||||
temp_exits: Dict[Place, List[str]] = {}
|
||||
for line in in_file:
|
||||
line = line.strip()
|
||||
if state == PlaceLoaderStates.START:
|
||||
rm = self.base(line)
|
||||
temp_exits[rm] = []
|
||||
state = PlaceLoaderStates.DESC
|
||||
desc = []
|
||||
elif state == PlaceLoaderStates.DESC:
|
||||
if line == ".":
|
||||
state = PlaceLoaderStates.EXITS
|
||||
rm.description = desc
|
||||
else:
|
||||
desc.append(line)
|
||||
elif state == PlaceLoaderStates.EXITS:
|
||||
if line == ".":
|
||||
state = PlaceLoaderStates.START
|
||||
self.places[rm.name] = rm
|
||||
else:
|
||||
temp_exits[rm].append(line)
|
||||
|
||||
# assemble the exits
|
||||
for place in list(self.places.values()):
|
||||
for ext in temp_exits[place]:
|
||||
names, destination = ext.split(" ", 1)
|
||||
for nm in names.split(","):
|
||||
place.ways[nm] = self.places[destination]
|
||||
place.update_go()
|
||||
|
||||
|
||||
def interpreter_from_placeloader(placeloader: PlaceLoader) -> Interpreter:
|
||||
"""Return an interpreter intialized with the contents of a placeloader."""
|
||||
return Interpreter(list(placeloader.places.values()))
|
|
@ -0,0 +1,241 @@
|
|||
"""Base (Pyoo) classes which implement basic protocols."""
|
||||
|
||||
import fnmatch
|
||||
import itertools
|
||||
|
||||
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
|
||||
|
||||
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[str, ...] = tuple(names)
|
||||
self.description = description
|
||||
self.location: Optional["Container"] = None
|
||||
self.interpreter: Optional[InterpreterProtocol] = None
|
||||
|
||||
def tell(self, message: SingleMultiString) -> None:
|
||||
print("<tell stub>", self, message)
|
||||
|
||||
def verbs(self) -> List[Verb]:
|
||||
"""Return a list of bound methods which denote themselves as verbs."""
|
||||
verbs: List[Verb] = []
|
||||
for item in dir(self):
|
||||
try:
|
||||
v = self.__getattribute__(item)
|
||||
if v.is_verb:
|
||||
verbs.append(v)
|
||||
except AttributeError:
|
||||
continue
|
||||
return verbs
|
||||
|
||||
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[Tuple[str, Tuple, Verb, "Thing"]] = []
|
||||
for vrb in self.verbs():
|
||||
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(tuple(vrb.callspec[1]))
|
||||
|
||||
if vrb.callspec[2] == "this":
|
||||
vvars.append(self.names)
|
||||
elif vrb.callspec[2] in ("that", "any"):
|
||||
vvars.append(("*",))
|
||||
|
||||
for combo in itertools.product(*vvars):
|
||||
globstr = " ".join(combo)
|
||||
verbglobs.append((globstr, tuple(combo), vrb, self))
|
||||
|
||||
return verbglobs
|
||||
|
||||
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: "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) -> str:
|
||||
return "<Thing '%s' object at 0x%x>" % (self.name, self.__hash__())
|
||||
|
||||
|
||||
class Container(Thing):
|
||||
"""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[Thing] = set()
|
||||
self.name_cache: List[Tuple[str, Thing]] = []
|
||||
self.command_cache: List[Tuple[str, Tuple, Verb, Thing]] = []
|
||||
|
||||
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:
|
||||
for name in obj.names:
|
||||
self.name_cache.append((name, obj))
|
||||
for verbglob in obj.verb_globs():
|
||||
if verbglob[0][0] == "#":
|
||||
continue
|
||||
self.command_cache.append(verbglob)
|
||||
for verbglob in self.verb_globs():
|
||||
if verbglob[0][0] == "#":
|
||||
continue
|
||||
self.command_cache.append(verbglob)
|
||||
|
||||
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])))
|
||||
if (len(res) < 1):
|
||||
raise PyooVerbNotFound
|
||||
return res
|
||||
|
||||
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: 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: 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:
|
||||
cast("Container", newobj).update_caches()
|
||||
except AttributeError:
|
||||
pass
|
||||
self.update_caches()
|
||||
|
||||
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: SingleMultiString) -> None:
|
||||
"""Handle telling all content objects."""
|
||||
self.handle_tell(msg, self.contents)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "<Container '%s' object at 0x%x>" % (self.name, self.__hash__())
|
||||
|
||||
|
||||
class Place(Container):
|
||||
"""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[str, Container] = dict()
|
||||
|
||||
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: 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: VerbCallFrame) -> None:
|
||||
self.do_go(verb_callframe.dobjstr, 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.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: Thing) -> None:
|
||||
super().handle_enter(newobj)
|
||||
self.handle_tell("{} arrives".format(newobj.name), self.contents - {newobj})
|
||||
|
||||
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:
|
||||
if direction not in self.go.names:
|
||||
self.go.names.append(direction)
|
||||
if self.interpreter:
|
||||
self.interpreter.update()
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "<Place '%s' object at 0x%x>" % (self.name, self.__hash__())
|
||||
|
||||
|
||||
class Player(Container):
|
||||
def __init__(self, names: str, description: str = ""):
|
||||
super().__init__(names, description)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "<Player '%s' object at 0x%x>" % (self.name, self.__hash__())
|
|
@ -0,0 +1,40 @@
|
|||
Porch
|
||||
This is a small, fairly nondescript porch covered on two sides with a neck-high fence. It is made of old boards nailed down instead of screwed.
|
||||
|
||||
To the north is the front door, slightly ajar.
|
||||
.
|
||||
n,north Play Room
|
||||
.
|
||||
Play Room
|
||||
This is a medium-sized room with light wood parcade floor. There are toys of every description strewn about willy-nilly, two large baskets of stuffed animals and various tables adorned with books and drawing supplies. Large south-facing windows stream bright winter sunlight, warming the room.
|
||||
|
||||
To the south is the front door. To the east the room empties into the living room, to the north it continues into the hallway.
|
||||
.
|
||||
s,south Porch
|
||||
n,north Hallway
|
||||
e,east Living Room
|
||||
.
|
||||
Hallway
|
||||
This is a narrow wood-floored hallway, echoing with every sound.
|
||||
|
||||
Several doors connect to the hallway, all closed. It also contiues to the south into the play room, and east into the kitchen.
|
||||
.
|
||||
s,south Play Room
|
||||
e,east Kitchen
|
||||
.
|
||||
Kitchen
|
||||
This is a small linolium-floored kitchen, with red counters, a double stainless steel sink filled with dishes and a noisy refridgerator. It is illuminated from above by recessed flourescent lights, although only one bulb seems to be working.
|
||||
|
||||
The room contiues to the south into the living room, and to the west into the hallway.
|
||||
.
|
||||
s,south Living Room
|
||||
w,west Hallway
|
||||
.
|
||||
Living Room
|
||||
This small intimate livingroom with light wood parkade flooring is dominated by a large wampa rug on the floor and a TV opposite a three seat red couch. A window to the south has the shades draw for enhanced viewing.
|
||||
|
||||
The room continues to the west past the couch into the play room, and to the north into the kitchen.
|
||||
.
|
||||
w,west Play Room
|
||||
n,north Kitchen
|
||||
.
|
|
@ -0,0 +1,21 @@
|
|||
"""Module setup."""
|
||||
|
||||
import os.path
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
PACKAGE_NAME = "pyoo"
|
||||
VERSION = "0.0.3"
|
||||
|
||||
srcpath = os.path.dirname(__file__)
|
||||
|
||||
if __name__ == "__main__":
|
||||
setup(
|
||||
name=PACKAGE_NAME,
|
||||
version=VERSION,
|
||||
packages=find_packages(),
|
||||
python_requires=">=3.6.3",
|
||||
# scripts=["scripts/adventure.py"]
|
||||
description="A simple LambdaMOO-like command interpreter for Python",
|
||||
long_description=open(os.path.join(srcpath, "README.md"), "r").read(),
|
||||
long_description_content_type="text/markdown",
|
||||
)
|
|
@ -0,0 +1,49 @@
|
|||
from pyoo.things import Place, Player
|
||||
from pyoo.placeloader import interpreter_from_placeloader, PlaceLoader
|
||||
from pyoo.interpret import PyooVerbNotFound
|
||||
from pyoo.base import make_verb
|
||||
|
||||
|
||||
class DescriptivePlace(Place):
|
||||
def handle_enter(self, player):
|
||||
super().handle_enter(player)
|
||||
self.do_look()
|
||||
|
||||
@make_verb("look,l", "none", "none", "none")
|
||||
def look(self, verb_callframe):
|
||||
self.do_look()
|
||||
|
||||
def do_look(self):
|
||||
print(self.name)
|
||||
if isinstance(self.description, str):
|
||||
print(self.description)
|
||||
else:
|
||||
for line in self.description:
|
||||
print(line)
|
||||
|
||||
|
||||
loader = PlaceLoader(open("roomtest.txt", "r"), DescriptivePlace)
|
||||
player = Player("player")
|
||||
game = interpreter_from_placeloader(loader)
|
||||
porch = game.lookup_global_object("Porch")[0][1]
|
||||
run = True
|
||||
game.update()
|
||||
game.handle_move(porch, player)
|
||||
|
||||
# REPL
|
||||
if __name__ == "__main__":
|
||||
while run:
|
||||
cmd = ""
|
||||
try:
|
||||
cmd = input(">")
|
||||
except EOFError:
|
||||
run = False
|
||||
if cmd.startswith("quit"):
|
||||
run = False
|
||||
else:
|
||||
try:
|
||||
game.interpret(cmd, player)
|
||||
except PyooVerbNotFound:
|
||||
print("I don't understand that.")
|
||||
|
||||
print("Bye!")
|
|
@ -0,0 +1,91 @@
|
|||
from pyoo.interpret import Interpreter
|
||||
from pyoo.things import Thing, Place, Player
|
||||
from pyoo.base import make_verb, PyooVerbNotFound
|
||||
|
||||
|
||||
class Hammer(Thing):
|
||||
def __init__(self):
|
||||
Thing.__init__(self, "hammer", "a heavy ball-peen hammer.")
|
||||
|
||||
@make_verb("hit", "that", "with", "this")
|
||||
def hit(self, verb_callframe):
|
||||
try:
|
||||
verb_callframe.dobj.handle_hit(self)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
@make_verb("drop", "this", "none", "none")
|
||||
def drop(self, verb_callframe):
|
||||
print(verb_callframe)
|
||||
|
||||
@make_verb("get", "this", "none", "none")
|
||||
def get(self, verb_callframe):
|
||||
print(verb_callframe)
|
||||
|
||||
|
||||
class Nail(Thing):
|
||||
def __init__(self):
|
||||
Thing.__init__(self, "nail", "a nine inch nail.")
|
||||
self.depth = 1
|
||||
|
||||
def handle_hit(self, hitter):
|
||||
if self.depth > 0:
|
||||
print("bang! the nail is hammered.")
|
||||
self.depth -= 1
|
||||
else:
|
||||
print("ping! the nail won't go deeper.")
|
||||
|
||||
def contents_desc_hook(self):
|
||||
if self.depth > 0:
|
||||
return "You see a nail sticking out "+str(self.depth)+"cm."
|
||||
else:
|
||||
return "You see a nail fully hammered in."
|
||||
|
||||
|
||||
class HammerTime(Place):
|
||||
def __init__(self):
|
||||
Place.__init__(self, "HAMMERTIME")
|
||||
self.handle_enter(Hammer())
|
||||
self.handle_enter(Nail())
|
||||
|
||||
@make_verb("look,l", "none", "none", "none")
|
||||
def look(self, verb_callframe):
|
||||
for cont in self.contents:
|
||||
try:
|
||||
print(cont.contents_desc_hook())
|
||||
except AttributeError:
|
||||
continue
|
||||
print("You see a hammer.")
|
||||
|
||||
@make_verb("look,l", "that", "none", "none")
|
||||
def look_at(self, verb_callframe):
|
||||
if verb_callframe.dobj:
|
||||
dobj = verb_callframe.dobj
|
||||
print("%s: %s" % (dobj.name, dobj.description))
|
||||
else:
|
||||
print("That doesn't appear to be here.")
|
||||
|
||||
|
||||
hammertime = HammerTime()
|
||||
game = Interpreter([hammertime])
|
||||
player = Player("player")
|
||||
game.add_player(player)
|
||||
game.handle_move(hammertime, player)
|
||||
game.update()
|
||||
run = True
|
||||
if __name__ == "__main__":
|
||||
while run:
|
||||
cmd = ""
|
||||
try:
|
||||
cmd = input(">")
|
||||
except EOFError:
|
||||
run = False
|
||||
if cmd.startswith("quit"):
|
||||
run = False
|
||||
else:
|
||||
try:
|
||||
game.interpret(cmd, player)
|
||||
except PyooVerbNotFound:
|
||||
print("I don't understand that.")
|
||||
|
||||
print("Bye!")
|
Loading…
Reference in New Issue