# adventureEngine.py # Copyright 2005-2006, Paul McGuire # # Updated 2012 - latest pyparsing API # from pyparsing import * import random import string def aOrAn( item ): if item.desc[0] in "aeiou": return "an " + item.desc else: return "a " + item.desc def enumerateItems(l): if len(l) == 0: return "nothing" out = [] if len(l) > 1: out.append(', '.join(aOrAn(item) for item in l[:-1])) out.append('and') out.append(aOrAn(l[-1])) return " ".join(out) def enumerateDoors(l): if len(l) == 0: return "" out = [] if len(l) > 1: out.append(', '.join(l[:-1])) out.append("and") out.append(l[-1]) 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 random.random() > 0.5: if len(visibleItems) > 1: is_form = "are" else: is_form = "is" print("There %s %s here." % (is_form, enumerateItems(visibleItems))) else: print("You see %s." % (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 self.cantTakeMessage = "You can't take that!" 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 if contents is not None: if isinstance(contents, Item): self.contents = [contents,] else: self.contents = contents else: self.contents = [] def openItem(self, player): if not self.isOpened: self.isOpened = not self.isOpened if self.contents is not None: for item in self.contents: player.room.addItem( item ) self.contents = [] self.desc = "open " + self.desc def closeItem(self, player): if self.isOpened: self.isOpened = not self.isOpened if self.desc.startswith("open "): self.desc = self.desc[5:] 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(subj.cantTakeMessage) 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." % (aOrAn(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 quals.targetObj: 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: if not self.subject.isOpened: self.subject.openItem( player ) else: print("It's already open.") else: print("You can't open that.") else: print("There is no %s here to open." % self.subject) class CloseCommand(Command): def __init__(self, quals): super(CloseCommand,self).__init__("CLOSE", "closing") self.subject = Item.items[quals.item] @staticmethod def helpDescription(): return "CLOSE or CL - close an object" def _doCommand(self, player): rm = player.room availItems = rm.inv+player.inv if self.subject in availItems: if self.subject.isOpenable: if self.subject.isOpened: self.subject.closeItem( player ) else: print("You can't close that, it's not open.") else: print("You can't close that.") else: print("There is no %s here to close." % 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, CloseCommand, 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 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) closeVerb = oneOf("CLOSE CL", 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("item") takeCommand = takeVerb + itemRef("item") useCommand = useVerb + itemRef("usedObj") + \ Optional(oneOf("IN ON",caseless=True)) + \ Optional(itemRef,default=None)("targetObj") openCommand = openVerb + itemRef("item") closeCommand = closeVerb + itemRef("item") moveCommand = moveVerb + moveDirection("direction") quitCommand = quitVerb lookCommand = lookVerb doorsCommand = doorsVerb helpCommand = helpVerb # attach command classes to expressions invCommand.setParseAction(InventoryCommand) dropCommand.setParseAction(DropCommand) takeCommand.setParseAction(TakeCommand) useCommand.setParseAction(UseCommand) openCommand.setParseAction(OpenCommand) closeCommand.setParseAction(CloseCommand) moveCommand.setParseAction(MoveCommand) quitCommand.setParseAction(QuitCommand) lookCommand.setParseAction(LookCommand) doorsCommand.setParseAction(DoorsCommand) helpCommand.setParseAction(HelpCommand) # define parser using all command expressions return ( invCommand | useCommand | openCommand | closeCommand | dropCommand | takeCommand | moveCommand | lookCommand | doorsCommand | helpCommand | quitCommand )("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 as pe: print(pe.msg) except ParseException as 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 c in string.ascii_letters: 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 c in string.ascii_letters: 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,str): r = rooms[r] r.addItem( Item.items[i] ) def playGame(p,startRoom): # create parser parser = Parser() p.moveTo( startRoom ) while not p.gameOver: cmdstr = 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)) #==================== # start game definition roomMap = """ d-Z | f-c-e . | q