# 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 "" 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