# adventureEngine.py # Copyright 2005-2006, Paul McGuire # # Updated 2012 - latest pyparsing API # Updated 2023 - using PEP8 API names # import pyparsing as pp import random import string def a_or_an(item): if item.desc.startswith(tuple("aeiou")): return "an " + item.desc else: return "a " + item.desc def enumerate_items(items_list): if not items_list: return "nothing" *all_but_last, last = items_list out = [] if all_but_last: out.append(", ".join(a_or_an(item) for item in all_but_last)) if len(all_but_last) > 1: out[-1] += ',' out.append("and") out.append(a_or_an(last)) return " ".join(out) def enumerate_doors(doors_list): if not doors_list: return "" *all_but_last, last = doors_list out = [] if all_but_last: out.append(", ".join(all_but_last)) if len(all_but_last) > 1: out[-1] += ',' out.append("and") out.append(last) return " ".join(out) class Room: 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 add_item(self, it): self.inv.append(it) def remove_item(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 {} {} here.".format(is_form, enumerate_items(visibleItems))) else: print("You see %s." % (enumerate_items(visibleItems))) class Exit(Room): def __init__(self): super().__init__("") def enter(self, player): player.gameOver = True class Item: 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().__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 open_item(self, player): if not self.isOpened: self.isOpened = not self.isOpened if self.contents is not None: for item in self.contents: player.room.add_item(item) self.contents = [] self.desc = "open " + self.desc def close_item(self, player): if self.isOpened: self.isOpened = not self.isOpened if self.desc.startswith("open "): self.desc = self.desc[5:] class Command: "Base class for commands" def __init__(self, verb, verbProg): self.verb = verb self.verbProg = verbProg @staticmethod def help_description(): return "" def _do_command(self, player): pass def __call__(self, player): print(self.verbProg.capitalize() + "...") self._do_command(player) class MoveCommand(Command): def __init__(self, quals): super().__init__("MOVE", "moving") self.direction = quals.direction[0] @staticmethod def help_description(): 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 _do_command(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().__init__("TAKE", "taking") self.subject = quals.item @staticmethod def help_description(): return "TAKE or PICKUP or PICK UP - pick up an object (but some are deadly)" def _do_command(self, player): rm = player.room subj = Item.items[self.subject] if subj in rm.inv and subj.isVisible: if subj.isTakeable: rm.remove_item(subj) player.take(subj) else: print(subj.cantTakeMessage) else: print("There is no %s here." % subj) class DropCommand(Command): def __init__(self, quals): super().__init__("DROP", "dropping") self.subject = quals.item @staticmethod def help_description(): return "DROP or LEAVE - drop an object (but fragile items may break)" def _do_command(self, player): rm = player.room subj = Item.items[self.subject] if subj in player.inv: rm.add_item(subj) player.drop(subj) else: print("You don't have %s." % (a_or_an(subj))) class InventoryCommand(Command): def __init__(self, quals): super().__init__("INV", "taking inventory") @staticmethod def help_description(): return "INVENTORY or INV or I - lists what items you have" def _do_command(self, player): print("You have %s." % enumerate_items(player.inv)) class LookCommand(Command): def __init__(self, quals): super().__init__("LOOK", "looking") @staticmethod def help_description(): return "LOOK or L - describes the current room and any objects in it" def _do_command(self, player): player.room.describe() class ExamineCommand(Command): def __init__(self, quals): super().__init__("EXAMINE", "examining") self.subject = Item.items[quals.item] @staticmethod def help_description(): return "EXAMINE or EX or X - look closely at an object" def _do_command(self, player): msg = random.choice( [ "It's {}.", "It's just {}.", "It's a beautiful {1}.", "It's a rare and beautiful {1}.", "It's a rare {1}.", "Just {}, nothing special...", "{0}, just {0}." ] ) print(msg.format(a_or_an(self.subject), self.subject).capitalize()) class DoorsCommand(Command): def __init__(self, quals): super().__init__("DOORS", "looking for doors") @staticmethod def help_description(): return "DOORS - display what doors are visible from this room" def _do_command(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 ] reply += enumerate_doors(doorNames) reply += "." print(reply) class UseCommand(Command): def __init__(self, quals): super().__init__("USE", "using") self.subject = Item.items[quals.usedObj] if quals.targetObj: self.target = Item.items[quals.targetObj] else: self.target = None @staticmethod def help_description(): return "USE or U - use an object, optionally IN or ON another object" def _do_command(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().__init__("OPEN", "opening") self.subject = Item.items[quals.item] @staticmethod def help_description(): return "OPEN or O - open an object" def _do_command(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.open_item(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().__init__("CLOSE", "closing") self.subject = Item.items[quals.item] @staticmethod def help_description(): return "CLOSE or CL - close an object" def _do_command(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.close_item(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().__init__("QUIT", "quitting") @staticmethod def help_description(): return "QUIT or Q - ends the game" def _do_command(self, player): print("Ok....") player.gameOver = True class HelpCommand(Command): def __init__(self, quals): super().__init__("HELP", "helping") @staticmethod def help_description(): return "HELP or H or ? - displays this help message" def _do_command(self, player): print("Enter any of the following commands (not case sensitive):") for cmd in [ InventoryCommand, DropCommand, TakeCommand, UseCommand, OpenCommand, CloseCommand, MoveCommand, LookCommand, ExamineCommand, DoorsCommand, QuitCommand, HelpCommand, ]: print(" - %s" % cmd.help_description()) print() class AppParseException(pp.ParseException): pass class Parser: def __init__(self): self.bnf = self.make_bnf() def make_bnf(self): invVerb = pp.one_of("INV INVENTORY I", caseless=True) dropVerb = pp.one_of("DROP LEAVE", caseless=True) takeVerb = pp.one_of("TAKE PICKUP", caseless=True) | ( pp.CaselessLiteral("PICK") + pp.CaselessLiteral("UP") ) moveVerb = pp.one_of("MOVE GO", caseless=True) | pp.Empty() useVerb = pp.one_of("USE U", caseless=True) openVerb = pp.one_of("OPEN O", caseless=True) closeVerb = pp.one_of("CLOSE CL", caseless=True) quitVerb = pp.one_of("QUIT Q", caseless=True) lookVerb = pp.one_of("LOOK L", caseless=True) doorsVerb = pp.CaselessLiteral("DOORS") helpVerb = pp.one_of("H HELP ?", caseless=True) itemRef = pp.OneOrMore(pp.Word(pp.alphas)).set_parse_action(self.validate_item_name).setName("item_ref") nDir = pp.one_of("N NORTH", caseless=True).set_parse_action(pp.replace_with("N")) sDir = pp.one_of("S SOUTH", caseless=True).set_parse_action(pp.replace_with("S")) eDir = pp.one_of("E EAST", caseless=True).set_parse_action(pp.replace_with("E")) wDir = pp.one_of("W WEST", caseless=True).set_parse_action(pp.replace_with("W")) moveDirection = nDir | sDir | eDir | wDir invCommand = invVerb dropCommand = dropVerb + itemRef("item") takeCommand = takeVerb + itemRef("item") useCommand = ( useVerb + itemRef("usedObj") + pp.Opt(pp.one_of("IN ON", caseless=True)) + pp.Opt(itemRef, default=None)("targetObj") ) openCommand = openVerb + itemRef("item") closeCommand = closeVerb + itemRef("item") moveCommand = (moveVerb | "") + moveDirection("direction") quitCommand = quitVerb lookCommand = lookVerb examineCommand = pp.one_of("EXAMINE EX X", caseless=True) + itemRef("item") doorsCommand = doorsVerb.setName("DOORS") helpCommand = helpVerb # attach command classes to expressions invCommand.set_parse_action(InventoryCommand) dropCommand.set_parse_action(DropCommand) takeCommand.set_parse_action(TakeCommand) useCommand.set_parse_action(UseCommand) openCommand.set_parse_action(OpenCommand) closeCommand.set_parse_action(CloseCommand) moveCommand.set_parse_action(MoveCommand) quitCommand.set_parse_action(QuitCommand) lookCommand.set_parse_action(LookCommand) examineCommand.set_parse_action(ExamineCommand) doorsCommand.set_parse_action(DoorsCommand) helpCommand.set_parse_action(HelpCommand) # define parser using all command expressions parser = pp.ungroup( invCommand | useCommand | openCommand | closeCommand | dropCommand | takeCommand | moveCommand | lookCommand | examineCommand | doorsCommand | helpCommand | quitCommand )("command") return parser def validate_item_name(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 parse_cmd(self, cmdstr): try: ret = self.bnf.parse_string(cmdstr) return ret except AppParseException as pe: print(pe.msg) except pp.ParseException as pe: print( random.choice( [ "Sorry, I don't understand that.", "Huh?", "Excuse me?", "???", "What?", ] ) ) class Player: 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.add_item(Item.items[i]) def playGame(p, startRoom): # create parser parser = Parser() p.moveTo(startRoom) while not p.gameOver: cmdstr = input(">> ") cmd = parser.parse_cmd(cmdstr) if cmd is not None: cmd.command(p) print() print("You ended the game with:") for i in p.inv: print(" -", a_or_an(i)) # ==================== # start game definition roomMap = """ d-Z | f-c-e . | q