commit
04b2318351
@ -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 for an example mini-adventure. |
@ -0,0 +1,6 @@ |
||||
To Do |
||||
--------- |
||||
|
||||
* Inventory protocol (stubs are present) |
||||
* Room movement protocol (stubs are present) |
||||
* Some sort of pluggable I/O system - provide callables for sending output, etc. |
@ -0,0 +1,3 @@ |
||||
"""PYOO: A python MOO-like verb based text adventure engine.""" |
||||
|
||||
from .pyoo import Pyoo, Thing, Container, Place, Player, PyooError, PyooVerbNotFound, prepositions, verb |
@ -0,0 +1,257 @@ |
||||
import string |
||||
import functools |
||||
import itertools |
||||
import fnmatch |
||||
|
||||
prepositions = ('with/using', |
||||
'at/to', |
||||
'in front of', |
||||
'in/inside/into', |
||||
'on top of/on/onto/upon', |
||||
'out of/from inside/from', |
||||
'over', |
||||
'through', |
||||
'under/underneath/beneath', |
||||
'behind', |
||||
'beside', |
||||
'for/about', |
||||
'is', |
||||
'as', |
||||
'off/off of') |
||||
|
||||
normalized_preps = tuple([x.split('/') for x in prepositions]) |
||||
|
||||
class PyooError(Exception): |
||||
pass |
||||
|
||||
class PyooVerbNotFound(PyooError): |
||||
pass |
||||
|
||||
# this simple decorator adds verb metadata to a method or function |
||||
# verbname is a comma-separated list of verb names with possible woldcard |
||||
# dobjspec is '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 is 'this' or 'that' or 'none' or 'any' |
||||
# prepspec is one of prepositions strings |
||||
# verb prototypes are: (verbname, dobjstr, prepstr, iobjstr, dobj, iobj, argstr) |
||||
def verb(verbname, dobjspec, prepspec, iobjspec): |
||||
def verb_decorate(verbfunc): |
||||
names = [x.strip() for x in verbname.split(',')] |
||||
verbfunc.name = names[0] |
||||
verbfunc.names = tuple(names) |
||||
ps = prepspec |
||||
if isinstance(ps, basestring): |
||||
if ps.find('/') > 0: |
||||
ps = ps.split('/') |
||||
else: |
||||
for p in normalized_preps: |
||||
if ps in p: |
||||
ps = p |
||||
break |
||||
verbfunc.callspec = (dobjspec, ps, iobjspec) |
||||
verbfunc.is_verb = True |
||||
return verbfunc |
||||
return verb_decorate |
||||
|
||||
class Thing(object): |
||||
def __init__(self, thingname, description="A nondescript object."): |
||||
names = [x.strip() for x in thingname.split(',')] |
||||
self.name = names[0] |
||||
self.names = tuple(names) |
||||
self.description = description |
||||
self.location = None |
||||
|
||||
def verbs(self): |
||||
"""Return a list of bound methods which denote themselves as verbs.""" |
||||
verbs = list() |
||||
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): |
||||
"""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() |
||||
for verb in self.verbs(): |
||||
vvars = [verb.names,] |
||||
if verb.callspec[0] == 'this': |
||||
vvars.append(self.names) |
||||
elif verb.callspec[0] in ('that','any'): |
||||
vvars.append(('*',)) |
||||
|
||||
if verb.callspec[1] != 'none': |
||||
vvars.append(verb.callspec[1]) |
||||
|
||||
if verb.callspec[2] == 'this': |
||||
vvars.append(self.names) |
||||
elif verb.callspec[2] in ('that','any'): |
||||
vvars.append(('*',)) |
||||
|
||||
for combo in itertools.product(*vvars): |
||||
globstr = ' '.join(combo) |
||||
verbglobs.append((globstr, tuple(combo), verb, self)) |
||||
return verbglobs |
||||
|
||||
def __repr__(self): |
||||
return "<Thing '%s' object at 0x%x>" % (self.name, self.__hash__()) |
||||
|
||||
class Container(Thing): |
||||
def __init__(self, names, description=''): |
||||
Thing.__init__(self, names, description) |
||||
self.contents = list() |
||||
|
||||
def __repr__(self): |
||||
return "<Container '%s' object at 0x%x>" % (self.name, self.__hash__()) |
||||
|
||||
class Place(Container): |
||||
def __init__(self, names, description=''): |
||||
Container.__init__(self, names, description) |
||||
self.ways = list() |
||||
|
||||
@verb('go,n,w,s,e,u,d,in,out','none','none','none') |
||||
def go(self, verbname, dobjstr, prepstr, iobjstr, dobj, iobj, argstr): |
||||
pass |
||||
|
||||
def __repr__(self): |
||||
return "<Place '%s' object at 0x%x>" % (self.name, self.__hash__()) |
||||
|
||||
|
||||
class Player(Thing): |
||||
def __init__(self): |
||||
Thing.__init__(self, 'player') |
||||
self.inventory = list() |
||||
|
||||
def __repr__(self): |
||||
return "<Player '%s' object at 0x%x>" % (self.name, self.__hash__()) |
||||
|
||||
class Pyoo(object): |
||||
def __init__(self, defaultcontents=[], player=None, rooms=[], room=None): |
||||
# the "working" contents |
||||
self.contents = list() |
||||
|
||||
# name lookup caches |
||||
self.namecache = list() |
||||
self.commandcache = list() |
||||
|
||||
# the "player" object |
||||
self.player = player |
||||
if self.player: |
||||
player.interpreter = self |
||||
|
||||
# the rooms that the player can move to |
||||
self.rooms = rooms |
||||
for rm in rooms: |
||||
rm.interpreter = self |
||||
self.room = room |
||||
|
||||
# the items and objets that are always with the player |
||||
self.defaultcontents = defaultcontents |
||||
for cont in self.defaultcontents: |
||||
cont.interpreter = self |
||||
|
||||
self.update() |
||||
|
||||
def update(self): |
||||
self.contents = list(self.defaultcontents) |
||||
if self.room: |
||||
self.contents.append(self.room) |
||||
try: |
||||
self.contents.extend(self.room.contents) |
||||
except: |
||||
pass |
||||
if self.player: |
||||
self.contents.append(self.player) |
||||
try: |
||||
self.contents.extend(self.player.inventory) |
||||
except: |
||||
pass |
||||
|
||||
self.update_namecache() |
||||
self.update_commandcache() |
||||
|
||||
def update_namecache(self): |
||||
self.namecache = list() |
||||
for obj in self.contents: |
||||
for name in obj.names: |
||||
self.namecache.append((name, obj)) |
||||
|
||||
def update_commandcache(self): |
||||
self.commandcache = list() |
||||
for obj in self.contents: |
||||
for verbglob in obj.verb_globs(): |
||||
self.commandcache.append(verbglob) |
||||
|
||||
def get_matches(self, command): |
||||
res = [x for x in self.commandcache if fnmatch.fnmatch(command, x[0])] |
||||
# sort by ambiguity (percentage of *) |
||||
res.sort(lambda a,b: cmp(a[0].count('*') / float(len(a[0])), b[0].count('*') / float(len(b[0])))) |
||||
return res |
||||
|
||||
def get_namematches(self, name): |
||||
return [x for x in self.namecache if fnmatch.fnmatch(name, x[0])] |
||||
|
||||
def handle_move(self, newroom): |
||||
pass |
||||
|
||||
def handle_get(self, thing): |
||||
pass |
||||
|
||||
def interpret(self, command): |
||||
cmdmatches = self.get_matches(command) |
||||
if not cmdmatches: |
||||
raise PyooVerbNotFound() |
||||
cmd = cmdmatches[0] |
||||
glob = cmd[0] |
||||
comps = cmd[1] |
||||
verb = cmd[2] |
||||
this = cmd[3] |
||||
|
||||
# FIXME make this better to supprot mulitple word objstrs and prepstr |
||||
|
||||
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 |
||||
|
||||
dobj = None |
||||
if verb.callspec[0] == 'this': |
||||
dobj = this |
||||
elif verb.callspec[0] == 'that': |
||||
# lookup object |
||||
m = self.get_namematches(dobjstr) |
||||
if m: |
||||
dobj = m[0][1] |
||||
else: |
||||
# this is probably an error |
||||
dobj = None |
||||
|
||||
iobj = None |
||||
if verb.callspec[2] == 'this': |
||||
iobj = this |
||||
elif verb.callspec[2] == 'that': |
||||
# lookp object |
||||
m = self.get_namematches(iobjstr) |
||||
if m: |
||||
iobj = m[0][1] |
||||
else: |
||||
# this is probably an error |
||||
iobj = None |
||||
|
||||
return verb(verbstr, dobjstr, prepstr, iobjstr, dobj, iobj, argstr) |
@ -0,0 +1,75 @@ |
||||
import pyoo |
||||
from pyoo import Thing, verb, Pyoo, Container, Place, Player, PyooVerbNotFound |
||||
|
||||
class Hammer(Thing): |
||||
def __init__(self): |
||||
Thing.__init__(self, 'hammer','a heavy ball-peen hammer.') |
||||
|
||||
@verb('hit','that','with','this') |
||||
def hit(self, verbname, dobjstr, prepstr, iobjstr, dobj, iobj, argstr): |
||||
try: |
||||
dobj.handle_hit(self) |
||||
except: |
||||
pass |
||||
|
||||
@verb('drop','this','none','none') |
||||
def drop(self, verbname, dobjstr, prepstr, iobjstr, dobj, iobj, argstr): |
||||
print verbname, dobjstr, prepstr, iobjstr, dobj, iobj |
||||
|
||||
@verb('get','this','none','none') |
||||
def get(self, verbname, dobjstr, prepstr, iobjstr, dobj, iobj, argstr): |
||||
print verbname, dobjstr, prepstr, iobjstr, dobj, iobj |
||||
|
||||
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." |
||||
|
||||
class HammerTime(Place): |
||||
def __init__(self): |
||||
Place.__init__(self, "HAMMERTIME") |
||||
self.contents.append(Hammer()) |
||||
self.contents.append(Nail()) |
||||
|
||||
@verb('look,l','none','none','none') |
||||
def look(self, verbname, dobjstr, prepstr, iobjstr, dobj, iobj, argstr): |
||||
if self.contents[1].depth > 0: |
||||
print "You see a nail sticking out ", self.contents[1].depth, "cm." |
||||
else: |
||||
print "You see a nail fully hammered in." |
||||
print "You see a hammer." |
||||
|
||||
@verb('look,l', 'that','none','none') |
||||
def look_at(self, verbname, dobjstr, prepstr, iobjstr, dobj, iobj, argstr, stateobject=None): |
||||
if dobj: |
||||
print "%s: %s" % (dobj.name, dobj.description) |
||||
else: |
||||
print "That doesn't appear to be here." |
||||
|
||||
hammertime = HammerTime() |
||||
game = Pyoo([], Player(), [hammertime,], hammertime) |
||||
run = True |
||||
|
||||
if __name__ == '__main__': |
||||
while (run): |
||||
cmd = '' |
||||
try: |
||||
cmd = raw_input('>') |
||||
except EOFError: |
||||
run = False |
||||
if cmd.startswith('quit'): |
||||
run = False |
||||
else: |
||||
try: |
||||
game.interpret(cmd) |
||||
except PyooVerbNotFound: |
||||
print "I don't understand that." |
||||
|
||||
print "Bye!" |
Loading…
Reference in new issue