Skip to main content
Example code, link to Inform 7 Interactive Fiction
Source Link
AJNeufeld
  • 35.3k
  • 5
  • 41
  • 103

Example

Something to get you started. The lantern is just a Thing. The rug is actually a Rug (a special Thing), which has an action defined in the frog.py game file.

Notice the adventure game framework can be reused for many different games.

This is a "better" way that you're previous approach, but I wouldn't say it is a good way, yet. The framework has a lot of detail to flush out, and reworked to include better visibility and touchability. Items should optionally have a .has_light attribute. A room .has_light if it itself has that attribute set to True, or if an item within it has that attribute, unless the item is in a closed container (unless that container is transparent).

If you continue down this road, eventually you'll re-invent the Inform 7 interactive fiction framework. Good luck.

adventure.py

COMMANDS = { 'go', 'move', 'use', 'examine', 'open', 'close', 'inventory' }

DIRECTIONS = set()
REVERSE_DIRECTION = {}

for fwd, rev in (('n', 's'), ('e', 'w'), ('u', 'd')):
    DIRECTIONS.add(fwd)
    DIRECTIONS.add(rev)
    REVERSE_DIRECTION[fwd] = rev
    REVERSE_DIRECTION[rev] = fwd

class CantSee(Exception):
    pass

class Thing:
    def __init__(self, short_description, **kwargs):
        self.short_description = short_description
        self.long_description = None
        self.concealed = False
        self.scenery = False
        self.fixed = False
        self.openable = False

        for key, value in kwargs.items():
            if not key in self.__dict__:
                raise ValueError("Unrecognized argument: "+key)
            self.__dict__[key] = value

    def description(self):
        return self.short_description

    def examine(self):
        if self.long_description:
            print(self.long_description)
        else:
            print("There is nothing special about it")

    def move(self):
        if self.fixed:
            print("You can't move it")
        else:
            print("You move it a bit.")


class Container(Thing):
    def __init__(self, short_description, **kwargs):
        self.contents = {}
        self.openable = True
        self.open = False
        self.transparent = False
        
        super().__init__(short_description, **kwargs)

    def containing():
        if self.contents:
            return ", ".join(item.description() for item in self.contents())
        return "nothing"
                        

    def description(self):
        text = self.short_description
        if self.openable:
            if self.open:
                text += " (which is closed)"
            else:
                text += " (which is open)"

        if self.open or self.transparent:
            if self.contents:
                text += "(containing " + self.containing() + ")"
                
        return description
            
        
class Door(Thing):
    def __init__(self, short_description, **kwargs):

        self.lockable = False
        self.locked = False
        self.key = None
        self.connects = {}

        super().__init__(short_description, **kwargs)

        self.fixed = True
        self.closed = True

class Room(Thing):
    
    def __init__(self, name, **kwargs):
        self.exits = {}
        self.visited = False
        self.contents = set()
        
        super().__init__(name, **kwargs)

    def exit_to(self, direction, destination, door=None):
        reverse = REVERSE_DIRECTION[direction]

        if door:
            door.connects[direction] = destination
            door.connects[reverse] = self
            self.exits[direction] = door
            destination.exits[reverse] = door
        else:
            self.exits[direction] = destination
            destination.exits[reverse] = self

    def enter(self):
        print("Location:", self.short_description)
        if not self.visited:
            self.describe()
            self.visited = True

    def visible_things(self):
        return [item for item in self.contents if not item.concealed]

    def describe(self):
        if self.long_description:
            print(self.long_description)
            print()

        items = [item for item in self.visible_things()  if not item.scenery]
        
        for item in items:
            if item.concealed or item.scenery:
                continue

        if items:
            print("You see:")
            for item in items:
                print("   ", item.description())

class Player(Container):
    def __init__(self):
        super().__init__("yourself")
        self.long_description = "As good looking as ever."

        self.openable = False
        self.location = None
        self.alive = True

    def inventory(self):
        if self.contents:
            print("You are carring:")
            for item in self.contents:
                print("   ", item.description)
        else:
            print("You have nothing.")

    def go(self, direction):
        destination = self.location.exits.get(direction, None)
        if isinstance(destination, Door):
            door = destination
            destination = door.connects[direction]
            if door.concealed:
                destination = None
            elif door.closed:
                if door.locked:
                    print("You'd need to unlock the door first")
                    return
                print("First opening the", door.short_description)

        if destination:
            self.location = destination
            destination.enter()
        else:
            print("You can't go that way")

class Game:
    def __init__(self, protagonist):
        self.player = protagonist
        self.game_over = False
        self.turns = 0

    def welcome(self):
        print("A text adventure game.")

    def help(self):
        print("Examine everything.")

    def get_action(self):
        while True:
            command = input("\n> ").lower().split()

            if command:
                if len(command) == 1:
                    if command[0] in DIRECTIONS:
                        command.insert(0, 'go')
                    if command[0] == 'i':
                        command[0] = 'inventory'

                if command == ['inventory']:
                    self.player.inventory()

                elif command == ['help']:
                    self.help()

                elif command[0] == 'go':
                    if len(command) == 2 and command[1] in DIRECTIONS:
                        return command
                    else:
                        print("I'm sorry; go where?")
                    
                elif command[0] in COMMANDS:
                    return command

                else:
                    print("I don't understand")

    def go(self, direction):
        self.player.go(direction)

    def item(self, thing):
        items = self.player.location.visible_things()
        for item in items:
            if thing in item.short_description:
                return item
        raise CantSee(thing)
                
    def move(self, thing):
        item = self.item(thing)
        item.move()        

    def perform_action(self, command):
        if command[0] == 'go' and len(command) == 2:
            self.go(command[1])
        elif command[0] == 'move' and len(command) == 2:
            self.move(command[1])
        else:
            print("Command not implemented")


    def play(self):
        self.welcome()

        self.player.location.enter()

        while not self.game_over:
            command = self.get_action()
            try:
                self.perform_action(command)
                self.turns += 1
            except CantSee as thing:
                print("You don't see a", thing)

        if not self.player.alive:
            print("You have died.")
        else:
            print("Game over.")

frog.py

from adventure import Thing, Room, Door, Player, Game

burrow = Room("Your Burrow")
basement = Room("Your Basement")
front_yard = Room("Your Front Yard")

front_door = Door("Front Door")
trap_door = Door("Trap Door", concealed=True)
burrow.exit_to('n', front_yard, front_door)
burrow.exit_to('d', basement, trap_door)

class Rug(Thing):
    def move(self):
        if trap_door.concealed:
            print("Moving the rug reveals a trap door to the basement.")
            trap_door.concealed = False
        else:
            super().move()

rug = Rug("a rug", fixed=True)
burrow.contents.add(rug)
lantern = Thing("a lantern")
burrow.contents.add(lantern)


player = Player()
player.location = burrow

class FrogGame(Game):
    def welcome(self):
        print("""\
You are in a burrow, but not just any burrow.  The burrow you reside in is in
fact the estate of Von Frogerick III, who just so happens to be your great
great grandfather.  The immense and fascinating history of your lineage matters
not though, for you are hungry.  You should find a fly to eat.
""")

game = FrogGame(player)

if __name__ == '__main__':

    game.play()

Example

Something to get you started. The lantern is just a Thing. The rug is actually a Rug (a special Thing), which has an action defined in the frog.py game file.

Notice the adventure game framework can be reused for many different games.

This is a "better" way that you're previous approach, but I wouldn't say it is a good way, yet. The framework has a lot of detail to flush out, and reworked to include better visibility and touchability. Items should optionally have a .has_light attribute. A room .has_light if it itself has that attribute set to True, or if an item within it has that attribute, unless the item is in a closed container (unless that container is transparent).

If you continue down this road, eventually you'll re-invent the Inform 7 interactive fiction framework. Good luck.

adventure.py

COMMANDS = { 'go', 'move', 'use', 'examine', 'open', 'close', 'inventory' }

DIRECTIONS = set()
REVERSE_DIRECTION = {}

for fwd, rev in (('n', 's'), ('e', 'w'), ('u', 'd')):
    DIRECTIONS.add(fwd)
    DIRECTIONS.add(rev)
    REVERSE_DIRECTION[fwd] = rev
    REVERSE_DIRECTION[rev] = fwd

class CantSee(Exception):
    pass

class Thing:
    def __init__(self, short_description, **kwargs):
        self.short_description = short_description
        self.long_description = None
        self.concealed = False
        self.scenery = False
        self.fixed = False
        self.openable = False

        for key, value in kwargs.items():
            if not key in self.__dict__:
                raise ValueError("Unrecognized argument: "+key)
            self.__dict__[key] = value

    def description(self):
        return self.short_description

    def examine(self):
        if self.long_description:
            print(self.long_description)
        else:
            print("There is nothing special about it")

    def move(self):
        if self.fixed:
            print("You can't move it")
        else:
            print("You move it a bit.")


class Container(Thing):
    def __init__(self, short_description, **kwargs):
        self.contents = {}
        self.openable = True
        self.open = False
        self.transparent = False
        
        super().__init__(short_description, **kwargs)

    def containing():
        if self.contents:
            return ", ".join(item.description() for item in self.contents())
        return "nothing"
                        

    def description(self):
        text = self.short_description
        if self.openable:
            if self.open:
                text += " (which is closed)"
            else:
                text += " (which is open)"

        if self.open or self.transparent:
            if self.contents:
                text += "(containing " + self.containing() + ")"
                
        return description
            
        
class Door(Thing):
    def __init__(self, short_description, **kwargs):

        self.lockable = False
        self.locked = False
        self.key = None
        self.connects = {}

        super().__init__(short_description, **kwargs)

        self.fixed = True
        self.closed = True

class Room(Thing):
    
    def __init__(self, name, **kwargs):
        self.exits = {}
        self.visited = False
        self.contents = set()
        
        super().__init__(name, **kwargs)

    def exit_to(self, direction, destination, door=None):
        reverse = REVERSE_DIRECTION[direction]

        if door:
            door.connects[direction] = destination
            door.connects[reverse] = self
            self.exits[direction] = door
            destination.exits[reverse] = door
        else:
            self.exits[direction] = destination
            destination.exits[reverse] = self

    def enter(self):
        print("Location:", self.short_description)
        if not self.visited:
            self.describe()
            self.visited = True

    def visible_things(self):
        return [item for item in self.contents if not item.concealed]

    def describe(self):
        if self.long_description:
            print(self.long_description)
            print()

        items = [item for item in self.visible_things()  if not item.scenery]
        
        for item in items:
            if item.concealed or item.scenery:
                continue

        if items:
            print("You see:")
            for item in items:
                print("   ", item.description())

class Player(Container):
    def __init__(self):
        super().__init__("yourself")
        self.long_description = "As good looking as ever."

        self.openable = False
        self.location = None
        self.alive = True

    def inventory(self):
        if self.contents:
            print("You are carring:")
            for item in self.contents:
                print("   ", item.description)
        else:
            print("You have nothing.")

    def go(self, direction):
        destination = self.location.exits.get(direction, None)
        if isinstance(destination, Door):
            door = destination
            destination = door.connects[direction]
            if door.concealed:
                destination = None
            elif door.closed:
                if door.locked:
                    print("You'd need to unlock the door first")
                    return
                print("First opening the", door.short_description)

        if destination:
            self.location = destination
            destination.enter()
        else:
            print("You can't go that way")

class Game:
    def __init__(self, protagonist):
        self.player = protagonist
        self.game_over = False
        self.turns = 0

    def welcome(self):
        print("A text adventure game.")

    def help(self):
        print("Examine everything.")

    def get_action(self):
        while True:
            command = input("\n> ").lower().split()

            if command:
                if len(command) == 1:
                    if command[0] in DIRECTIONS:
                        command.insert(0, 'go')
                    if command[0] == 'i':
                        command[0] = 'inventory'

                if command == ['inventory']:
                    self.player.inventory()

                elif command == ['help']:
                    self.help()

                elif command[0] == 'go':
                    if len(command) == 2 and command[1] in DIRECTIONS:
                        return command
                    else:
                        print("I'm sorry; go where?")
                    
                elif command[0] in COMMANDS:
                    return command

                else:
                    print("I don't understand")

    def go(self, direction):
        self.player.go(direction)

    def item(self, thing):
        items = self.player.location.visible_things()
        for item in items:
            if thing in item.short_description:
                return item
        raise CantSee(thing)
                
    def move(self, thing):
        item = self.item(thing)
        item.move()        

    def perform_action(self, command):
        if command[0] == 'go' and len(command) == 2:
            self.go(command[1])
        elif command[0] == 'move' and len(command) == 2:
            self.move(command[1])
        else:
            print("Command not implemented")


    def play(self):
        self.welcome()

        self.player.location.enter()

        while not self.game_over:
            command = self.get_action()
            try:
                self.perform_action(command)
                self.turns += 1
            except CantSee as thing:
                print("You don't see a", thing)

        if not self.player.alive:
            print("You have died.")
        else:
            print("Game over.")

frog.py

from adventure import Thing, Room, Door, Player, Game

burrow = Room("Your Burrow")
basement = Room("Your Basement")
front_yard = Room("Your Front Yard")

front_door = Door("Front Door")
trap_door = Door("Trap Door", concealed=True)
burrow.exit_to('n', front_yard, front_door)
burrow.exit_to('d', basement, trap_door)

class Rug(Thing):
    def move(self):
        if trap_door.concealed:
            print("Moving the rug reveals a trap door to the basement.")
            trap_door.concealed = False
        else:
            super().move()

rug = Rug("a rug", fixed=True)
burrow.contents.add(rug)
lantern = Thing("a lantern")
burrow.contents.add(lantern)


player = Player()
player.location = burrow

class FrogGame(Game):
    def welcome(self):
        print("""\
You are in a burrow, but not just any burrow.  The burrow you reside in is in
fact the estate of Von Frogerick III, who just so happens to be your great
great grandfather.  The immense and fascinating history of your lineage matters
not though, for you are hungry.  You should find a fly to eat.
""")

game = FrogGame(player)

if __name__ == '__main__':

    game.play()
Source Link
AJNeufeld
  • 35.3k
  • 5
  • 41
  • 103

An interesting beginning to your game. I'd be interested to seeing how it turns out.


Text adventure games are usually data-driven. This means you would have a game engine (program) that would read a game description file (data), and execute the same instructions for every action in every location in the game, producing different outcomes based on the game data.

What you have written is not data driven. You have a function for handling actions in the burrow(), another for the burrow_basement(), and another for front_of_house(). The code is these functions look very similar, and very repetitive, which is why we can write it once, and change the outcome based on the data.

A problem with the way you've written the game is recursion. If you start in the burrow, and you can go down into the basement or north to the front of the house. Each of those actions calls load_room(location) which never returns, and calls its own function for handling that new room location. For instance, if you go down to the basement, load_room("burrow_basement") is called, and it calls burrow_basement(), which in response to an "up" action would call load_room("burrow"), which would call burrow(). We keep getting deeper and deeper into the call stack. If the player keeps exploring, eventually they will run into Python's stack limit. You could fix this by increasing the stack size, but that is just a kludge; the correct solution is to get rid of the recursion.


Object model

Places

You should start by defining some things. Like rooms. A room is location the player can be in. It should have a short name ("Kitchen", "Burrow's Basement"), and a longer description. The player may or may not have been in the room before. It will have connections (exits) to other locations. There may be items in the room.

class Room:
    def __init__(self, name, description):
        self.name = name
        self.description = description
        self.visited = False
        self.exits = { }
        self.contents = []

Things

Beyond rooms, the world is full of objects, or things. Things will have a name, and perhaps a description. Some things can be carried (photos, pamphlets, jars, ...) where as other things must remain where they are (stoves, sinks). Some things can be worn, like a coat or a hat. It might be hidden:

class Thing:
    def __init__(self, name, description):
        self.name = name
        self.description = description
        self.fixed = False
        self.moved = False
        self.wearable = False
        self.concealed = False

Containers

Some things can be placed inside other things. Fish sticks may be found in an ice box. The ice box is usually closed, but occasionally open. Some containers may be locked, like a cabinet. Some containers may be see through, like a china cabinet. You can climb into some containers, like a wardrobe or a car, but most you cannot.

class Container(Thing):
    def __init__(self, name, description):
        super().__init__(name, description)
        self.contents = []
        self.open = False
        self.locked = False
        self.key = None
        self.transparent = False
        self.enterable = False

Is a room a container? Rooms have names, descriptions, and contents. And they are enterable.

Supporters

Some things can be placed on other things. You can put a jar on a table. You can put a key on a hook. So perhaps some containers should be considered a supporter, instead. You can even enter some supporters, such as climbing into a bed.

Animate

Some things can move around, and have behaviours. You can talk to some of them, such as a shopkeeper, and they will talk back. Other actors, might not talk back, such as a cat.

A shopkeeper (an animate thing) might be wearing pants with pockets (a container) which may contain a pocket watch (openable).

Is the player an animate container??? They can hold things (inventory), move around (animate), take things (container), hold things, wear things.

Doors

A door sits between two rooms. A door could be open or closed. If it is closed, it may or may not be locked.

class Door(Thing):
    def __init__(self, name, description, key=None)
        self.name = name
        self.description = description
        self.open = False
        self.lockable = key is not None
        self.locked = True if key else False
        self.key = key
        self.connects = []


living_room = Room("Living Room", "A place for the family to hang out")
dining_room = Room("Dining Room", "Many meals were had here")
basement = Room("Basement", "A dark and dingy hole in the ground")

trap_door = Door("Trap door", "A piece of wood, with a basic hinge and a ring handle")
trap_door.concealed = True
trap_door.connects = [living_room, basement]

dining_room.exits["south"] = living_room
living_room.exits["north"] = dining_room
living_room.exits["down"] = trap_door
basement.exits["up"] = trap_door

Here, we have two exits from the living room, north directly to the dining room (no door), and down to a trap_door. The trap_door is concealed, so if the player is in the living room, and the try to go "down", initially they should get a "you can't go that way". Moving the rug should reveal the trap door (marking it not concealed), allowing travel through the door to the other location it connects to. Maybe:

rug = Thing("A rug", "A thick heavy rug, passed down through the ages")
rug.fixed = True
def rug_move():
    print("You pull back the corner of the rug, revealing a trap door")
    rug.description = "One corner of the rug has been lifted to reveal a trap door"
    trap_door.concealed = False
rug.on_move = run_move

Now if your game logic allows you to type "lift rug" or "move rug", you could parse the word "rug", find the object with that name in the location.contents, and call that object's .on_move() function, which tells you what happened, changes the rug's description, and removes the concealed attribute from the trap door.