################################################################################## # MIT License # # # # Copyright (c) 2021 Brendan P. Westley # # # # Permission is hereby granted, free of charge, to any person obtaining a copy # # of this software and associated documentation files (the "Software"), to deal # # in the Software without restriction, including without limitation the rights # # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # # copies of the Software, and to permit persons to whom the Software is # # furnished to do so, subject to the following conditions: # # # # The above copyright notice and this permission notice shall be included in all # # copies or substantial portions of the Software. # # # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # # SOFTWARE. # ################################################################################## __author__ = "Brendan P. Westley" __version__ = "1" __copyright__ = "Copyright 2021, Brendan P. Westley" __license__ = "MIT" from os import name from time import sleep try: import curses import attr import cattr except ModuleNotFoundError: import sys from os import system print("Installing missing dependencies...") system("py -m pip install windows-curses") system("py -m pip install attrs") system("py -m pip install cattrs") import curses import attr import cattr import curses.textpad from enum import Enum from typing import Callable, Optional, Tuple, Union ########################## BEGIN STRUCTURE DEFINITION ########################## class Direction(Enum): north = 1 n = 1 northeast = 2 ne = 1 east = 3 e = 3 southeast = 4 se = 4 south = 5 s = 5 southwest = 6 sw = 6 west = 7 w = 7 northwest = 8 nw = 8 @attr.s(auto_attribs=True) class Player: world: 'World' inventory: dict[str, 'Item'] name: str aliases: list[str] currentRoom: 'Room' longDescription: str def describe(self) -> str: return "\n".join([item.shortDescription for item in self.inventory.values()]) def examine(self) -> str: return self.longDescription def searchInventory(self, name: str) -> Optional['Item']: for item in self.inventory.values(): if name == item.name.lower(): return item if name in item.aliases: return item return None @attr.s(auto_attribs=True) class World: doors: list['Door'] = attr.ib(factory=list) rooms: dict[str, 'Room'] = attr.ib(factory=dict) player: Optional[Player] = None def findObject(self, room: 'Room', name: str) -> Union[None, Player, 'Door', 'Item']: if self.player is None: raise Exception( "World is not fully instantiated. self.player is not Player.") # Check if name refers to the player if name == self.player.name.lower() or name in self.player.aliases: return self.player # Search for a door by direction for direction in Direction: if name == direction.name: if direction in room.doors: return room.doors[direction] # Search for a door by aliases for door in room.doors.values(): if name in door.aliases: return door # Search for an item in the player's inventory: if self.player is not None: item = self.player.searchInventory(name) if item is not None: return item return None @attr.s(auto_attribs=True) class Item: world: World name: str shortDescription: str longDescription: str aliases: list[str] = attr.ib(factory=list) movable: bool = True quantity: int = attr.ib(default=1) action: Optional[Callable] = attr.ib(default=None) def describe(self) -> str: return ("" if self.quantity == 1 else f"{self.quantity} ") + self.shortDescription def examine(self) -> str: return self.longDescription @attr.s(auto_attribs=True) class Door: world: World sideARoomName: str sideAWall: Direction sideBRoomName: str sideBWall: Direction shortDescription: str longDescription: str aliases: list[str] = attr.ib(factory=list) open: bool = attr.ib(True) locked: bool = attr.ib(False) keys: list[str] = attr.ib(factory=list) def describe(self, name: str) -> str: if name == self.sideARoomName: return f"On the {self.sideAWall.name} wall is {'a locked' if self.locked else 'an unlocked'} and {'open' if self.open else 'closed'} {self.shortDescription}." elif name == self.sideBRoomName: return f"On the {self.sideBWall.name} wall is {'a locked' if self.locked else 'an unlocked'} and {'open' if self.open else 'closed'} {self.shortDescription}." else: raise LookupError(f"{repr(self)} is not connected to room {name}!") def examine(self) -> str: return self.longDescription def otherSide(self, name: str) -> 'Room': if name == self.sideARoomName: return self.sideBRoom elif name == self.sideBRoomName: return self.sideARoom else: raise LookupError(f"{repr(self)} is not connected to room {name}!") @property def sideARoom(self): return self.world.rooms[self.sideARoomName] @property def sideBRoom(self): return self.world.rooms[self.sideBRoomName] @attr.s(auto_attribs=True) class Room: world: World name: str description: str doors: dict[Direction, Door] = attr.ib(factory=dict) items: dict[str, Item] = attr.ib(factory=dict) def describe(self, world: 'World') -> str: # room name and description string: str = f"{self.name}\n{self.description}" # items in the room if len(self.items) > 0: string += "\n\nYou see:\n" + \ "\n".join( [item.shortDescription for item in self.items.values()]) # doors in the room if len(self.doors) > 0: string += "\n\n" + \ "\n".join([door.describe(self.name) for door in self.doors.values()]) else: string += "\n\nYou can detect no doors." return string def examine(self, world: 'World') -> str: return self.describe(world) def searchInventory(self, name: str) -> Optional[Item]: for item in self.items.values(): if name == item.name.lower(): return item if name in item.aliases: return item return None def findDoor(self, name: str) -> Optional[Door]: for direction in Direction: if name == direction.name: if direction in self.doors: return self.doors[direction] for door in self.doors.values(): if name in door.aliases: return door return None ########################### END STRUCTURE DEFINITION ########################### def main(stdscr) -> None: HELPTEXT = """Command Help: examine or e : examine an item, door (by alias or direction), or yourself inventory of i: list your inventory drop or d : drop an item onto the floor get or g : get an item from the room use or u : use an item in your inventory go : go through a door open or o: open a door close : close a door unlock with : unlock a door using an item quit or exit: exit the game or : go through a door or : use an item help or h: bring up this help text""" ############################ BEGIN WORLD DEFINITION ############################ world = World() world.doors = [ # 0 Door( world, sideARoomName="Cell", sideAWall=Direction.north, sideBRoomName="Cell Corridor", sideBWall=Direction.south, shortDescription="steel door", longDescription="The door is cold and depressing.", aliases=["door", "steel door"], open=False ), # 1 Door( world, sideARoomName="Cell Corridor", sideAWall=Direction.east, sideBRoomName="Security Checkpoint", sideBWall=Direction.west, shortDescription="steel door", longDescription="The door is cold and depressing.", aliases=["steel door"], ), # 2 Door( world, sideARoomName="Cell Corridor", sideAWall=Direction.west, sideBRoomName="West Cell Corridor", sideBWall=Direction.east, shortDescription="broken steel door", longDescription="The door has been thrown off its hinges by some terrible force.", aliases=["broken door", "broken steel door"], ), # 3 Door( world, sideARoomName="West Cell Corridor", sideAWall=Direction.north, sideBRoomName="Operation Room", sideBWall=Direction.south, shortDescription="stone door", longDescription="A sliding stone door that moves with ease despite its weight.", aliases=["door", "stone door", "sliding door", "sliding stone door"] ), # 4 Door( world, sideARoomName="Security Checkpoint", sideAWall=Direction.west, sideBRoomName="Lobby", sideBWall=Direction.east, shortDescription="large blast door", longDescription="The massive metal and concrete door fills most of the wall.", aliases=["security", "blast door", "large blast door", "door"], open=False, locked=True, keys=["Level 2 Keycard", "Level 3 Keycard", "Level 4 Keycard", "Level 5 Keycard"] ), # 5 Door( world, sideARoomName="Lobby", sideAWall=Direction.south, sideBRoomName="Red Room", sideBWall=Direction.north, shortDescription="red door", longDescription="The door is constructed out of wood and is painted a bright red.", aliases=["red door", "red"], open=False, locked=True, keys=["Level 3 Keycard", "Level 4 Keycard", "Level 5 Keycard"] ), # 6 Door( world, sideARoomName="Lobby", sideAWall=Direction.southwest, sideBRoomName="Yellow Room", sideBWall=Direction.northeast, shortDescription="yellow door", longDescription="The door is constructed out of wood and is painted a bright yellow.", aliases=["yellow door", "yellow"], open=False, locked=True, keys=["Level 2 Keycard", "Level 3 Keycard", "Level 4 Keycard", "Level 5 Keycard"] ), # 7 Door( world, sideARoomName="Lobby", sideAWall=Direction.west, sideBRoomName="Green Room", sideBWall=Direction.east, shortDescription="green door", longDescription="The door is constructed out of wood and is painted a bright green.", aliases=["green door", "green"], open=False, locked=True, keys=["Level 2 Keycard", "Level 3 Keycard", "Level 4 Keycard", "Level 5 Keycard"] ), # 8 Door( world, sideARoomName="Lobby", sideAWall=Direction.northwest, sideBRoomName="Blue Room", sideBWall=Direction.southeast, shortDescription="blue door", longDescription="The door is constructed out of wood and is painted a bright blue.", aliases=["blue door", "blue"], open=False, locked=True, keys=["Level 3 Keycard", "Level 4 Keycard", "Level 5 Keycard"] ), # 9 Door( world, sideARoomName="Lobby", sideAWall=Direction.north, sideBRoomName="Black Room", sideBWall=Direction.south, shortDescription="black door", longDescription="The door is constructed out of wood and is painted a dark black.", aliases=["black door", "black"], open=False, locked=True, keys=["Level 4 Keycard", "Level 5 Keycard"] ), # 10 Door( world, sideARoomName="Black Room", sideAWall=Direction.north, sideBRoomName="Maze", sideBWall=Direction.south, shortDescription="black room tunnel", longDescription="The maze starts here...", aliases=[], open=True, locked=True, keys=["Level 5 Keycard"] ), # 11 Door( world, sideARoomName="Maze", sideAWall=Direction.east, sideBRoomName="Maze ", sideBWall=Direction.south, shortDescription="tunnel", longDescription="A twisty tunnel lies beyond.", aliases=[], open=True, locked=True, keys=["Level 5 Keycard"] ), # 12 Door( world, sideARoomName="Maze", sideAWall=Direction.north, sideBRoomName="Maze ", sideBWall=Direction.south, shortDescription="tunnel", longDescription="A twisty tunnel lies beyond.", aliases=[], open=True, locked=True, keys=["Level 5 Keycard"] ), # 13 Door( world, sideARoomName="Maze ", sideAWall=Direction.north, sideBRoomName="Maze", sideBWall=Direction.west, shortDescription="tunnel", longDescription="A twisty tunnel lies beyond.", aliases=[], open=True, locked=True, keys=["Level 5 Keycard"] ), # 14 Door( world, sideARoomName="Maze ", sideAWall=Direction.north, sideBRoomName="Maze ", sideBWall=Direction.east, shortDescription="tunnel", longDescription="A twisty tunnel lies beyond.", aliases=[], open=True, locked=True, keys=["Level 5 Keycard"] ), # 15 Door( world, sideARoomName="Maze ", sideAWall=Direction.south, sideBRoomName="Maze ", sideBWall=Direction.west, shortDescription="tunnel", longDescription="A twisty tunnel lies beyond.", aliases=[], open=True, locked=True, keys=["Level 5 Keycard"] ), # 16 Door( world, sideARoomName="Maze ", sideAWall=Direction.north, sideBRoomName="Maze ", sideBWall=Direction.south, shortDescription="tunnel", longDescription="A twisty tunnel lies beyond.", aliases=[], open=True, locked=True, keys=["Level 5 Keycard"] ), # 17 Door( world, sideARoomName="Maze ", sideAWall=Direction.west, sideBRoomName="Maze ", sideBWall=Direction.east, shortDescription="tunnel", longDescription="A twisty tunnel lies beyond.", aliases=[], open=True, locked=True, keys=["Level 5 Keycard"] ), # 18 Door( world, sideARoomName="Maze ", sideAWall=Direction.west, sideBRoomName="Maze ", sideBWall=Direction.north, shortDescription="tunnel", longDescription="A twisty tunnel lies beyond.", aliases=[], open=True, locked=True, keys=["Level 5 Keycard"] ), # 19 Door( world, sideARoomName="Maze ", sideAWall=Direction.east, sideBRoomName="Maze ", sideBWall=Direction.south, shortDescription="tunnel", longDescription="A twisty tunnel lies beyond.", aliases=[], open=True, locked=True, keys=["Level 5 Keycard"] ), # 20 Door( world, sideARoomName="Maze ", sideAWall=Direction.west, sideBRoomName="Maze ", sideBWall=Direction.east, shortDescription="tunnel", longDescription="A twisty tunnel lies beyond.", aliases=[], open=True, locked=True, keys=["Level 5 Keycard"] ), # 21 Door( world, sideARoomName="Maze ", sideAWall=Direction.north, sideBRoomName="Maze ", sideBWall=Direction.west, shortDescription="tunnel", longDescription="A twisty tunnel lies beyond.", aliases=[], open=True, locked=True, keys=["Level 5 Keycard"] ), # 22 Door( world, sideARoomName="Maze ", sideAWall=Direction.south, sideBRoomName="Elevator Lobby", sideBWall=Direction.north, shortDescription="tunnel", longDescription="A twisty tunnel lies beyond.", aliases=[], open=True, locked=True, keys=["Level 5 Keycard"] ), # 23 Door( world, sideARoomName="Maze ", sideAWall=Direction.west, sideBRoomName="Maze ", sideBWall=Direction.east, shortDescription="tunnel", longDescription="A twisty tunnel lies beyond.", aliases=[], open=True, locked=True, keys=["Level 5 Keycard"] ), # 24 Door( world, sideARoomName="Maze ", sideAWall=Direction.north, sideBRoomName="Maze ", sideBWall=Direction.south, shortDescription="tunnel", longDescription="A twisty tunnel lies beyond.", aliases=[], open=True, locked=True, keys=["Level 5 Keycard"] ) ] world.rooms = { "Cell": Room( world, name="Cell", description="You find yourself in a plain white room.", doors={Direction.north: world.doors[0]} ), "Cell Corridor": Room( world, name="Cell Corridor", description="A long dark concrete corridor stretches to the east and west.", doors={ Direction.south: world.doors[0], Direction.east: world.doors[1], Direction.west: world.doors[2]} ), "Security Checkpoint": Room( world, name="Security Checkpoint", description="""A guard's desk sits behind a window on the north wall. The glass is intact despite being pitted with bullet craters.""", doors={Direction.west: world.doors[1]} ), "West Cell Corridor": Room( world, name="West Cell Corridor", description="You stumble around in the dim light.", doors={Direction.east: world.doors[2], Direction.north: world.doors[3]} ), "Operation Room": Room( world, name="Operation Room", description="""A large rectangular stone table dominates the center of the room. Strange inscriptions cover the walls and ceiling.""", doors={Direction.south: world.doors[3]}, items={ "Level 2 Keycard": Item( world, name="Level 2 Keycard", shortDescription="level 2 keycard", longDescription="A thin light blue sheet of plastic with the label \"Level 2\".", aliases=["level 2 keycard", "level 2", "2"] ), "Stone Table": Item( world, name="Stone Table", shortDescription="stone table", longDescription="""The stone table appearers to be solid. The table lacks any markings except a deep channel around the edge of the top surface.""", aliases=["table", "stone"], movable=False ) } ), "Lobby": Room( world, name="Lobby", description="The large round room features a deep hole in the center surrounded with a yellow guardrail.", doors={ Direction.south: world.doors[5], Direction.southwest: world.doors[6], Direction.west: world.doors[7], Direction.northwest: world.doors[8], Direction.north: world.doors[9] }, items={ "hole": Item( world, name="Hole", shortDescription="deep hole", longDescription="You cannot see the bottom of the hole. A bright blue point can be seen far below.", aliases=["deep", "deep hole"], movable=False ) } ), "Red Room": Room( world, name="Red Room", description="""A table and four chairs sit in the middle of the room. A closed book sits on the table.""", doors={Direction.north: world.doors[5]}, items={ "Book": Item( world, name="Book", shortDescription="book", longDescription="""The book resists opening as if it does not want to be read. You pry open the cover to reveal a single page with the text \"NENWWWS\". The book snaps shut.""", aliases=["book", "closed book"], movable=False ), "Level 4 Keycard": Item( world, name="Level 4 Keycard", shortDescription="level 4 keycard", longDescription="A thin dark blue sheet of plastic with the label \"Level 4\".", aliases=["level 4 keycard", "level 4", "4"] ) } ), "Yellow Room": Room( world, name="Yellow Room", description="The concrete walls, crudely painted dark yellow, appear to be dripping.", doors={ Direction.northeast: world.doors[6] } ), "Green Room": Room( world, name="Green Room", description="The walls are covered in soft moss and the room smells like pine trees.", doors={ Direction.east: world.doors[7] }, items={ "Clump of Moss": Item( world, name="Clump of Moss", shortDescription="clump of moss", longDescription="The moss is soft and pretty, the only greenery in this place.", aliases=["moss", "clump", "clump of moss"] ), "Level 3 Keycard": Item( world, name="Level 3 Keycard", shortDescription="level 2 keycard", longDescription="A thin blue sheet of plastic with the label \"Level 3\".", aliases=["level 3 keycard", "level 3", "3"] ) } ), "Blue Room": Room( world, name="Blue Room", description="Beyond the threshold lays a vast ocean stretching in all directions.", doors={ Direction.southeast: world.doors[8] } ), "Black Room": Room( world, name="Black Room", description="""The void lies ahead, but should you enter? Not all passages are straight and some may lead back to the same room!""", doors={ Direction.south: world.doors[9], Direction.north: world.doors[10] }, items={ "Level 5 Keycard": Item( world, name="Level 5 Keycard", shortDescription="level 5 keycard", longDescription="""The black plastic rectangle offers infinate opportunities... If you want to open locked doors that is. Can allow anything to be closed.""", aliases=["level 5 keycard", "level 5", "5"] ) } ), "Maze": Room( world, name="Maze", description="You fumble around in the dark.", doors={ Direction.south: world.doors[10], Direction.east: world.doors[11], Direction.north: world.doors[12], Direction.west: world.doors[13] } ), "Maze ": Room( world, name="Maze ", description="You fumble around in the dark.", doors={ Direction.south: world.doors[11], Direction.north: world.doors[14] } ), "Maze ": Room( world, name="Maze ", description="You fumble around in the dark.", doors={ Direction.south: world.doors[12], Direction.north: world.doors[13] } ), "Maze ": Room( world, name="Maze ", description="You fumble around in the dark.", doors={ Direction.east: world.doors[14], Direction.south: world.doors[15], Direction.west: world.doors[17] } ), "Maze ": Room( world, name="Maze ", description="You fumble around in the dark.", doors={ Direction.west: world.doors[15], Direction.north: world.doors[16], Direction.south: world.doors[16] } ), "Maze ": Room( world, name="Maze ", description="You fumble around in the dark.", doors={ Direction.east: world.doors[15], Direction.south: world.doors[15], Direction.west: world.doors[17] } ), "Maze ": Room( world, name="Maze ", description="You fumble around in the dark.", doors={ Direction.north: world.doors[18], Direction.east: world.doors[19], Direction.south: world.doors[18], Direction.west: world.doors[20] } ), "Maze ": Room( world, name="Maze ", description="You fumble around in the dark.", doors={ Direction.east: world.doors[20], Direction.north: world.doors[21], Direction.south: world.doors[22], Direction.west: world.doors[23] } ), "Maze": Room( world, name="Maze", description="You fumble around in the dark.", doors={ Direction.west: world.doors[21], Direction.east: world.doors[23], Direction.north: world.doors[24], Direction.south: world.doors[24] } ), "Elevator Lobby": Room( world, name="Maze", description="You fumble around in the dark.", doors={ Direction.: world.doors[], Direction.: world.doors[], Direction.: world.doors[] } ) } world.player = Player( world, inventory={}, name="Player", aliases=["me", "myself", "player"], currentRoom=world.rooms["Cell"], longDescription="You are clothed in long and heavy dark grey fabric that you find surprisingly soft and smooth." ) ############################# END WORLD DEFINITION ############################# curses.curs_set(False) curses.echo() HEIGHT, WIDTH = stdscr.getmaxyx() if HEIGHT < 30 or WIDTH < 100: stdscr.clear() stdscr.addstr( 0, 0, f"TERMINAL MUST BE 30 LINES 100 COLUMNS OR LARGER!\nCURRENTLY {HEIGHT} LINES {WIDTH} COLUMNS.") stdscr.refresh() sleep(5) return running: bool = True info: str = HELPTEXT while running: # Redraw screen description: str = world.player.currentRoom.describe(world) stdscr.clear() stdscr.addstr(0, 0, description) stdscr.addstr(description.count("\n") + 2, 0, info) stdscr.addstr(HEIGHT - 1, 0, ">") stdscr.refresh() # Get command verb, _, noun = stdscr.getstr( HEIGHT - 1, 1, WIDTH - 1).decode("UTF-8").lower().partition(" ") ############################ BEGIN COMMAND PARSING ############################# # Parse command if verb == "": info = "Enter a command. Try help or h." elif verb == "help" or verb == "h": info = HELPTEXT elif verb == "examine" or verb == "e": if noun == "": info = """Usage: examine Object can be a door, direction where there is a door, item in your inventory, item in the room, or \"yourself\". Example: examine silver door, examine north, examine brass key, examine table, examine yourself""" else: # Examine a door, direction, or item _object = world.findObject(world.player.currentRoom, noun) if _object is not None: info = _object.examine() else: info = f"Cannot find a door, direction, or item {noun} to examine." elif verb == "inventory" or verb == "i": if noun != "": info = "Usage: inventory (this command is used by itself)" else: # List inventory info = world.player.describe() elif verb == "drop" or verb == "d": if noun == "": info = """Usage: drop Examples: drop flashlight, drop light""" else: # Drop item from inventory item = world.player.searchInventory(noun) if item is None: info = f"You do not have {noun} in your inventory." else: world.player.currentRoom.items[item.name] = item del world.player.inventory[item.name] info = f"You dropped {item.shortDescription} on the floor." elif verb == "get" or verb == "g": if noun == "": info = """Usage: get Examples: get brass key, get key""" else: # Get an item from the room item = world.player.currentRoom.searchInventory(noun) if item is None: info = f"There is no {noun} in this room." elif item.movable: world.player.inventory[item.name] = item del world.player.currentRoom.items[item.name] info = f"You picked up {item.shortDescription}." else: info = f"{item.shortDescription} cannot be moved." elif verb == "use" or verb == "u": if noun == "": info = """Usage: use Examples: use flashlight, use light""" else: # Use an item in your inventory item = world.player.searchInventory(noun) if item is None: info = f"You do not have {noun} in your inventory." else: if item.action is None: info = f"{item.shortDescription} cannot be used." else: info = "" item.action() elif verb == "go": if noun == "": info = """Usage: go Examples: go north, go silver door""" else: # Go through a door door = world.player.currentRoom.findDoor(noun) if door is None: info = f"You cannot go {noun}." else: if door.open: world.player.currentRoom = door.otherSide( world.player.currentRoom.name) info = "" else: info = f"{door.shortDescription} is closed." elif verb == "open" or verb == "o": if noun == "": info = """Usage: open Examples: open north, open silver door""" else: # Open a door door = world.player.currentRoom.findDoor(noun) if door is None: info = f"There is no door {noun}." else: if door.open: info = f"{door.shortDescription} is already open." elif door.locked: info = f"{door.shortDescription} is locked." else: door.open = True info = f"{door.shortDescription} opens." elif verb == "close": if noun == "": info = """Usage: close Examples: close north, close silver door""" else: # Close a door door = world.player.currentRoom.findDoor(noun) if door is None: info = f"There is no door {noun}." else: if not door.open: info = f"{door.shortDescription} is already closed." elif door.locked: info = f"{door.shortDescription} is locked." else: door.open = False info = f"{door.shortDescription} closes." elif verb == "unlock": if not noun.startswith("with ") and len(noun) > 5: info = """Usage: unlock with Examples: unlock north with brass key, unlock silver door with orange card""" else: # Unlock a door door = world.player.currentRoom.findDoor(noun) if door is None: info = f"There is no door {noun}." else: if door.locked: item = world.player.searchInventory(noun[5:]) if item is None: info = f"You do not have {noun[5:]} in your inventory." elif item.name in door.keys: door.locked = False info = f"{door.shortDescription} unlocks." else: info = f"{door.shortDescription} can only be opened with {', '.join(door.keys)}." else: info = f"{door.shortDescription} is already unlocked." elif verb == "quit" or verb == "exit": if noun != "": info = "Type \"quit\" or \"exit\" by itself to do so." else: # Exit the game running = False else: if noun != "": info = f"""{verb} not a recognized verb. The following nouns can be used independently as shortcuts: or : go through a door (equivalent to: go ) Examples: north, black door or : use an item (equivalent to: use Examples: flashlight, light""" else: _object = world.findObject(world.player.currentRoom, verb) if isinstance(_object, Door): if _object.open: world.player.currentRoom = _object.otherSide( world.player.currentRoom.name) info = "" else: info = "That door is closed." elif isinstance(_object, Item): if _object.action is None: info = f"{_object.shortDescription} cannot be used." else: _object.action() else: info = f"I don't understand the verb, direction, door, or item \"{verb}\". Try help or h." ############################# END COMMAND PARSING ############################## curses.wrapper(main)