Initial checkin.

main
Cas Rusnov 9 years ago
commit 04b2318351
  1. 61
      .gitignore
  2. 6
      README.md
  3. 6
      TODO.md
  4. 3
      pyoo/__init__.py
  5. 257
      pyoo/pyoo.py
  6. 75
      testverb.py

61
.gitignore vendored

@ -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…
Cancel
Save