# adventureEngine.py
# Copyright 2005-2006, Paul McGuire
#
from pyparsing import *
import random
def aOrAn( item ):
if item.desc[0] in "aeiou":
return "an"
else:
return "a"
def enumerateItems(l):
if len(l) == 0: return "nothing"
out = []
for item in l:
if len(l)>1 and item == l[-1]:
out.append("and")
out.append( aOrAn( item ) )
if item == l[-1]:
out.append(item.desc)
else:
if len(l)>2:
out.append(item.desc+",")
else:
out.append(item.desc)
return " ".join(out)
def enumerateDoors(l):
if len(l) == 0: return ""
out = []
for item in l:
if len(l)>1 and item == l[-1]:
out.append("and")
if item == l[-1]:
out.append(item)
else:
if len(l)>2:
out.append(item+",")
else:
out.append(item)
return " ".join(out)
class Room(object):
def __init__(self, desc):
self.desc = desc
self.inv = []
self.gameOver = False
self.doors = [None,None,None,None]
def __getattr__(self,attr):
return \
{
"n":self.doors[0],
"s":self.doors[1],
"e":self.doors[2],
"w":self.doors[3],
}[attr]
def enter(self,player):
if self.gameOver:
player.gameOver = True
def addItem(self, it):
self.inv.append(it)
def removeItem(self,it):
self.inv.remove(it)
def describe(self):
print self.desc
visibleItems = [ it for it in self.inv if it.isVisible ]
if len(visibleItems) > 1:
print "There are %s here." % enumerateItems( visibleItems )
else:
print "There is %s here." % enumerateItems( visibleItems )
class Exit(Room):
def __init__(self):
super(Exit,self).__init__("")
def enter(self,player):
player.gameOver = True
class Item(object):
items = {}
def __init__(self, desc):
self.desc = desc
self.isDeadly = False
self.isFragile = False
self.isBroken = False
self.isTakeable = True
self.isVisible = True
self.isOpenable = False
self.useAction = None
self.usableConditionTest = None
Item.items[desc] = self
def __str__(self):
return self.desc
def breakItem(self):
if not self.isBroken:
print "<Crash!>"
self.desc = "broken " + self.desc
self.isBroken = True
def isUsable(self, player, target):
if self.usableConditionTest:
return self.usableConditionTest( player, target )
else:
return False
def useItem(self, player, target):
if self.useAction:
self.useAction(player, self, target)
class OpenableItem(Item):
def __init__(self, desc, contents = None):
super(OpenableItem,self).__init__(desc)
self.isOpenable = True
self.isOpened = False
self.contents = contents
def openItem(self, player):
if not self.isOpened:
self.isOpened = True
self.isOpenable = False
if self.contents is not None:
player.room.addItem( self.contents )
self.desc = "open " + self.desc
class Command(object):
"Base class for commands"
def __init__(self, verb, verbProg):
self.verb = verb
self.verbProg = verbProg
@staticmethod
def helpDescription():
return ""
def _doCommand(self, player):
pass
def __call__(self, player ):
print self.verbProg.capitalize()+"..."
self._doCommand(player)
class MoveCommand(Command):
def __init__(self, quals):
super(MoveCommand,self).__init__("MOVE", "moving")
self.direction = quals["direction"][0]
@staticmethod
def helpDescription():
return """MOVE or GO - go NORTH, SOUTH, EAST, or WEST
(can abbreviate as 'GO N' and 'GO W', or even just 'E' and 'S')"""
def _doCommand(self, player):
rm = player.room
nextRoom = rm.doors[
{
"N":0,
"S":1,
"E":2,
"W":3,
}[self.direction]
]
if nextRoom:
player.moveTo( nextRoom )
else:
print "Can't go that way."
class TakeCommand(Command):
def __init__(self, quals):
super(TakeCommand,self).__init__("TAKE", "taking")
self.subject = quals["item"]
@staticmethod
def helpDescription():
return "TAKE or PICKUP or PICK UP - pick up an object (but some are deadly)"
def _doCommand(self, player):
rm = player.room
subj = Item.items[self.subject]
if subj in rm.inv and subj.isVisible:
if subj.isTakeable:
rm.removeItem(subj)
player.take(subj)
else:
print "You can't take that!"
else:
print "There is no %s here." % subj
class DropCommand(Command):
def __init__(self, quals):
super(DropCommand,self).__init__("DROP", "dropping")
self.subject = quals["item"]
@staticmethod
def helpDescription():
return "DROP or LEAVE - drop an object (but fragile items may break)"
def _doCommand(self, player):
rm = player.room
subj = Item.items[self.subject]
if subj in player.inv:
rm.addItem(subj)
player.drop(subj)
else:
print "You don't have %s %s." % (aOrAn(subj), subj)
class InventoryCommand(Command):
def __init__(self, quals):
super(InventoryCommand,self).__init__("INV", "taking inventory")
@staticmethod
def helpDescription():
return "INVENTORY or INV or I - lists what items you have"
def _doCommand(self, player):
print "You have %s." % enumerateItems( player.inv )
class LookCommand(Command):
def __init__(self, quals):
super(LookCommand,self).__init__("LOOK", "looking")
@staticmethod
def helpDescription():
return "LOOK or L - describes the current room and any objects in it"
def _doCommand(self, player):
player.room.describe()
class DoorsCommand(Command):
def __init__(self, quals):
super(DoorsCommand,self).__init__("DOORS", "looking for doors")
@staticmethod
def helpDescription():
return "DOORS - display what doors are visible from this room"
def _doCommand(self, player):
rm = player.room
numDoors = sum([1 for r in rm.doors if r is not None])
if numDoors == 0:
reply = "There are no doors in any direction."
else:
if numDoors == 1:
reply = "There is a door to the "
else:
reply = "There are doors to the "
doorNames = [ {0:"north", 1:"south", 2:"east", 3:"west"}[i]
for i,d in enumerate(rm.doors) if d is not None ]
#~ print doorNames
reply += enumerateDoors( doorNames )
reply += "."
print reply
class UseCommand(Command):
def __init__(self, quals):
super(UseCommand,self).__init__("USE", "using")
self.subject = Item.items[ quals["usedObj"] ]
if "targetObj" in quals.keys():
self.target = Item.items[ quals["targetObj"] ]
else:
self.target = None
@staticmethod
def helpDescription():
return "USE or U - use an object, optionally IN or ON another object"
def _doCommand(self, player):
rm = player.room
availItems = rm.inv+player.inv
if self.subject in availItems:
if self.subject.isUsable( player, self.target ):
self.subject.useItem( player, self.target )
else:
print "You can't use that here."
else:
print "There is no %s here to use." % self.subject
class OpenCommand(Command):
def __init__(self, quals):
super(OpenCommand,self).__init__("OPEN", "opening")
self.subject = Item.items[ quals["item"] ]
@staticmethod
def helpDescription():
return "OPEN or O - open an object"
def _doCommand(self, player):
rm = player.room
availItems = rm.inv+player.inv
if self.subject in availItems:
if self.subject.isOpenable:
self.subject.openItem( player )
else:
print "You can't use that here."
else:
print "There is no %s here to use." % self.subject
class QuitCommand(Command):
def __init__(self, quals):
super(QuitCommand,self).__init__("QUIT", "quitting")
@staticmethod
def helpDescription():
return "QUIT or Q - ends the game"
def _doCommand(self, player):
print "Ok...."
player.gameOver = True
class HelpCommand(Command):
def __init__(self, quals):
super(HelpCommand,self).__init__("HELP", "helping")
@staticmethod
def helpDescription():
return "HELP or H or ? - displays this help message"
def _doCommand(self, player):
print "Enter any of the following commands (not case sensitive):"
for cmd in [
InventoryCommand,
DropCommand,
TakeCommand,
UseCommand,
OpenCommand,
MoveCommand,
LookCommand,
DoorsCommand,
QuitCommand,
HelpCommand,
]:
print " - %s" % cmd.helpDescription()
print
class AppParseException(ParseException):
pass
class Parser(object):
def __init__(self):
self.bnf = self.makeBNF()
def makeCommandParseAction( self, cls ):
def cmdParseAction(s,l,tokens):
return cls(tokens)
return cmdParseAction
def makeBNF(self):
invVerb = oneOf("INV INVENTORY I", caseless=True)
dropVerb = oneOf("DROP LEAVE", caseless=True)
takeVerb = oneOf("TAKE PICKUP", caseless=True) | \
(CaselessLiteral("PICK") + CaselessLiteral("UP") )
moveVerb = oneOf("MOVE GO", caseless=True) | empty
useVerb = oneOf("USE U", caseless=True)
openVerb = oneOf("OPEN O", caseless=True)
quitVerb = oneOf("QUIT Q", caseless=True)
lookVerb = oneOf("LOOK L", caseless=True)
doorsVerb = CaselessLiteral("DOORS")
helpVerb = oneOf("H HELP ?",caseless=True)
itemRef = OneOrMore(Word(alphas)).setParseAction( self.validateItemName )
nDir = oneOf("N NORTH",caseless=True).setParseAction(replaceWith("N"))
sDir = oneOf("S SOUTH",caseless=True).setParseAction(replaceWith("S"))
eDir = oneOf("E EAST",caseless=True).setParseAction(replaceWith("E"))
wDir = oneOf("W WEST",caseless=True).setParseAction(replaceWith("W"))
moveDirection = nDir | sDir | eDir | wDir
invCommand = invVerb
dropCommand = dropVerb + itemRef.setResultsName("item")
takeCommand = takeVerb + itemRef.setResultsName("item")
useCommand = useVerb + itemRef.setResultsName("usedObj") + \
Optional(oneOf("IN ON",caseless=True)) + \
Optional(itemRef,default=None).setResultsName("targetObj")
openCommand = openVerb + itemRef.setResultsName("item")
moveCommand = moveVerb + moveDirection.setResultsName("direction")
quitCommand = quitVerb
lookCommand = lookVerb
doorsCommand = doorsVerb
helpCommand = helpVerb
invCommand.setParseAction(
self.makeCommandParseAction( InventoryCommand ) )
dropCommand.setParseAction(
self.makeCommandParseAction( DropCommand ) )
takeCommand.setParseAction(
self.makeCommandParseAction( TakeCommand ) )
useCommand.setParseAction(
self.makeCommandParseAction( UseCommand ) )
openCommand.setParseAction(
self.makeCommandParseAction( OpenCommand ) )
moveCommand.setParseAction(
self.makeCommandParseAction( MoveCommand ) )
quitCommand.setParseAction(
self.makeCommandParseAction( QuitCommand ) )
lookCommand.setParseAction(
self.makeCommandParseAction( LookCommand ) )
doorsCommand.setParseAction(
self.makeCommandParseAction( DoorsCommand ) )
helpCommand.setParseAction(
self.makeCommandParseAction( HelpCommand ) )
return ( invCommand |
useCommand |
openCommand |
dropCommand |
takeCommand |
moveCommand |
lookCommand |
doorsCommand |
helpCommand |
quitCommand ).setResultsName("command") + LineEnd()
def validateItemName(self,s,l,t):
iname = " ".join(t)
if iname not in Item.items:
raise AppParseException(s,l,"No such item '%s'." % iname)
return iname
def parseCmd(self, cmdstr):
try:
ret = self.bnf.parseString(cmdstr)
return ret
except AppParseException, pe:
print pe.msg
except ParseException, pe:
print random.choice([ "Sorry, I don't understand that.",
"Huh?",
"Excuse me?",
"???",
"What?" ] )
class Player(object):
def __init__(self, name):
self.name = name
self.gameOver = False
self.inv = []
def moveTo(self, rm):
self.room = rm
rm.enter(self)
if self.gameOver:
if rm.desc:
rm.describe()
print "Game over!"
else:
rm.describe()
def take(self,it):
if it.isDeadly:
print "Aaaagh!...., the %s killed me!" % it
self.gameOver = True
else:
self.inv.append(it)
def drop(self,it):
self.inv.remove(it)
if it.isFragile:
it.breakItem()
def createRooms( rm ):
"""
create rooms, using multiline string showing map layout
string contains symbols for the following:
A-Z, a-z indicate rooms, and rooms will be stored in a dictionary by
reference letter
-, | symbols indicate connection between rooms
<, >, ^, . symbols indicate one-way connection between rooms
"""
# start with empty dictionary of rooms
ret = {}
# look for room symbols, and initialize dictionary
# - exit room is always marked 'Z'
for c in rm:
if "A" <= c <= "Z" or "a" <= c <= "z":
if c != "Z":
ret[c] = Room(c)
else:
ret[c] = Exit()
# scan through input string looking for connections between rooms
rows = rm.split("\n")
for row,line in enumerate(rows):
for col,c in enumerate(line):
if "A" <= c <= "Z" or "a" <= c <= "z":
room = ret[c]
n = None
s = None
e = None
w = None
# look in neighboring cells for connection symbols (must take
# care to guard that neighboring cells exist before testing
# contents)
if col > 0 and line[col-1] in "<-":
other = line[col-2]
w = ret[other]
if col < len(line)-1 and line[col+1] in "->":
other = line[col+2]
e = ret[other]
if row > 1 and col < len(rows[row-1]) and rows[row-1][col] in '|^':
other = rows[row-2][col]
n = ret[other]
if row < len(rows)-1 and col < len(rows[row+1]) and rows[row+1][col] in '|.':
other = rows[row+2][col]
s = ret[other]
# set connections to neighboring rooms
room.doors=[n,s,e,w]
return ret
# put items in rooms
def putItemInRoom(i,r):
if isinstance(r,basestring):
r = rooms[r]
r.addItem( Item.items[i] )
def playGame(p,startRoom):
# create parser
parser = Parser()
p.moveTo( startRoom )
while not p.gameOver:
cmdstr = raw_input(">> ")
cmd = parser.parseCmd(cmdstr)
if cmd is not None:
cmd.command( p )
print
print "You ended the game with:"
for i in p.inv:
print " -", aOrAn(i), i
#====================
# start game definition
roomMap = """
d-Z
|
f-c-e
. |
q<b
|
A
"""
rooms = createRooms( roomMap )
rooms["A"].desc = "You are standing at the front door."
rooms["b"].desc = "You are in a garden."
rooms["c"].desc = "You are in a kitchen."
rooms["d"].desc = "You are on the back porch."
rooms["e"].desc = "You are in a library."
rooms["f"].desc = "You are on the patio."
rooms["q"].desc = "You are sinking in quicksand. You're dead..."
rooms["q"].gameOver = True
# define global variables for referencing rooms
frontPorch = rooms["A"]
garden = rooms["b"]
kitchen = rooms["c"]
backPorch = rooms["d"]
library = rooms["e"]
patio = rooms["f"]
# create items
itemNames = """sword.diamond.apple.flower.coin.shovel.book.mirror.telescope.gold bar""".split(".")
for itemName in itemNames:
Item( itemName )
Item.items["apple"].isDeadly = True
Item.items["mirror"].isFragile = True
Item.items["coin"].isVisible = False
Item.items["shovel"].usableConditionTest = ( lambda p,t: p.room is garden )
def useShovel(p,subj,target):
coin = Item.items["coin"]
if not coin.isVisible and coin in p.room.inv:
coin.isVisible = True
Item.items["shovel"].useAction = useShovel
Item.items["telescope"].isTakeable = False
def useTelescope(p,subj,target):
print "You don't see anything."
Item.items["telescope"].useAction = useTelescope
OpenableItem("treasure chest", Item.items["gold bar"])
putItemInRoom("shovel", frontPorch)
putItemInRoom("coin", garden)
putItemInRoom("flower", garden)
putItemInRoom("apple", library)
putItemInRoom("mirror", library)
putItemInRoom("telescope", library)
putItemInRoom("book", kitchen)
putItemInRoom("diamond", backPorch)
putItemInRoom("treasure chest", patio)
# create player
plyr = Player("Bob")
plyr.take( Item.items["sword"] )
# start game
playGame( plyr, frontPorch )
|