Python for Humans · Draft v0.1 · Oct 28, 2026 · Project · First recurring-project draft.

Text Adventure: Meet the Castle's Characters

Optional project · After Classes

You should already know
Save Your Adventure and Classes, including init, self, attributes, and methods.
You'll build
Two characters with different gifts, using one small class.
You'll be able to
Create separate instances, call their methods, and save ordinary game data.
You need
This page for the runnable examples; your local Python setup for files that survive closing the browser.

Our castle has places and possessions. What about somebody to talk to?

Let's wake the dragon just enough for a conversation, and invite a ghost into the hall. Both can offer a gift, but they have different names and belongings.

This optional project takes about 25–40 minutes. We'll use one small class. The rooms can stay in dictionaries; they already do that job well.

Start with the Saved Adventure

Here's the complete previous version, including saving and loading. Run it once to recall the route to the crown. Nothing from another page needs to be running:

def show_room(room, descriptions, exits):
    print(descriptions[room])
    print('You can go to:')
    for destination in exits[room]:
        print(destination)


def save_game(room, inventory, filename):
    with open(filename, 'w', encoding='utf-8') as saved:
        saved.write(room + '\n')
        for item in inventory:
            saved.write(item + '\n')


def load_game(filename, rooms):
    with open(filename, encoding='utf-8') as saved:
        lines = saved.read().splitlines()
    if len(lines) == 0:
        raise ValueError('The save file is empty.')
    if lines[0] in rooms:
        room = lines[0]
    else:
        raise ValueError('The saved room is unknown.')
    inventory = []
    position = 1
    while position < len(lines):
        item = lines[position]
        if item in ['torch', 'crown']:
            inventory.append(item)
        else:
            raise ValueError('The saved item is unknown.')
        position = position + 1
    return {'room': room, 'inventory': inventory}


descriptions = {
    'hall': 'Two passages lead away from the castle hall.',
    'dragon': 'A sleeping dragon guards a torch. Borrow it quietly!',
    'treasury': 'Gold glitters in the torchlight. A crown waits here.'
}
exits = {
    'hall': ['dragon', 'treasury'],
    'dragon': ['hall'],
    'treasury': ['hall']
}
items = {'dragon': 'torch', 'treasury': 'crown'}

room = 'hall'
inventory = []
print('Welcome back to the Mysterious Castle!')

while True:
    show_room(room, descriptions, exits)
    print('Your bag:', inventory)
    command = input('Where next? (room name, take, save, load, quit) ').lower()
    if command == 'quit':
        break
    elif command == 'save':
        save_game(room, inventory, 'castle_save.txt')
        print('Adventure saved.')
    elif command == 'load':
        try:
            progress = load_game('castle_save.txt', descriptions)
            room = progress['room']
            inventory = progress['inventory']
            print('Adventure loaded.')
        except FileNotFoundError:
            print('No saved adventure yet. Use save first.')
        except ValueError:
            print('That save file is not a valid castle adventure.')
    elif command == 'take':
        if room in items:
            item = items[room]
            if item in inventory:
                print('You already have it.')
            else:
                inventory.append(item)
                print('You take the', item)
                if item == 'crown':
                    print('You found the crown. You win!')
                    break
        else:
            print('There is nothing to take here.')
    elif command in exits[room]:
        if command == 'treasury':
            if 'torch' in inventory:
                room = command
            else:
                print('Too dark! Find a torch first.')
        else:
            room = command
    else:
        print("You can't do that here.")

print('Thanks for exploring!')

Give a Character Its Own Data

We could keep each name and gift in a dictionary and pass it to a conversation function. A class keeps those values together with the behavior that uses them.

Use __init__(self, name, gift) to store the supplied values as self.name and self.gift. Then add talk(self, inventory). It should offer the gift if the bag doesn't contain it, or acknowledge the earlier gift if it does.

The caller supplies the bag. Python supplies self, the instance receiving the method call.

Try your own class, then compare with this independent example:

class Character:
    def __init__(self, name, gift):
        self.name = name
        self.gift = gift

    def talk(self, inventory):
        if self.gift in inventory:
            print(f'{self.name} says: Enjoy your {self.gift}!')
        else:
            print(f'{self.name} gives you a {self.gift}.')
            inventory.append(self.gift)
dragon = Character('Drowsy Dragon', 'torch')
ghost = Character('Polite Ghost', 'map')
bag = []
dragon.talk(bag)
ghost.talk(bag)
dragon.talk(bag)
print(bag)

The two instances have their own attribute values. Calling the dragon's method doesn't change the ghost's name or gift. Both calls receive the same bag list, so the player keeps both presents.

Try changing only the ghost's gift. Then call each character twice. Does either add a duplicate?

Put Characters in Rooms

Make a dictionary mapping room identifiers to Character instances. Dictionary values can be objects just as they can be lists or numbers.

Add a talk command. If the current room appears in the character dictionary, look up that character and call its talk(inventory) method. Otherwise, print a response and keep exploring.

Remove the torch from the old items dictionary. The dragon now gives it through conversation; the crown is still something we take from a room.

Keep the Save Format Small

The player carries the gifts, so inventory tells us whether a gift has already been given. We don't need a second copy of that fact inside each character.

Keep saving the room and inventory as before, and add 'map' to the loader's allowed items. Character names and gifts are recreated from the program each run. An older save containing a torch still works: the dragon sees it in the loaded bag.

If you later give a character changing health or mood, that new state will need a place in your save format too.

Visit the New Castle

Here's the complete result, including the earlier movement and save features:

class Character:
    def __init__(self, name, gift):
        self.name = name
        self.gift = gift

    def talk(self, inventory):
        if self.gift in inventory:
            print(f'{self.name} says: Enjoy your {self.gift}!')
        else:
            print(f'{self.name} gives you a {self.gift}.')
            inventory.append(self.gift)


def show_room(room, descriptions, exits):
    print(descriptions[room])
    print('You can go to:')
    for destination in exits[room]:
        print(destination)


def save_game(room, inventory, filename):
    with open(filename, 'w', encoding='utf-8') as saved:
        saved.write(room + '\n')
        for item in inventory:
            saved.write(item + '\n')


def load_game(filename, rooms):
    with open(filename, encoding='utf-8') as saved:
        lines = saved.read().splitlines()
    if len(lines) == 0:
        raise ValueError('The save file is empty.')
    if lines[0] in rooms:
        room = lines[0]
    else:
        raise ValueError('The saved room is unknown.')
    inventory = []
    position = 1
    while position < len(lines):
        item = lines[position]
        if item in ['torch', 'crown', 'map']:
            inventory.append(item)
        else:
            raise ValueError('The saved item is unknown.')
        position = position + 1
    return {'room': room, 'inventory': inventory}


descriptions = {
    'hall': 'Two passages lead away from the castle hall.',
    'dragon': 'A sleepy dragon opens one eye. Perhaps it wants to talk.',
    'treasury': 'Gold glitters in the torchlight. A crown waits here.'
}
exits = {
    'hall': ['dragon', 'treasury'],
    'dragon': ['hall'],
    'treasury': ['hall']
}
items = {'treasury': 'crown'}
characters = {
    'dragon': Character('Drowsy Dragon', 'torch'),
    'hall': Character('Polite Ghost', 'map')
}

room = 'hall'
inventory = []
print('Welcome back to the Mysterious Castle!')

while True:
    show_room(room, descriptions, exits)
    print('Your bag:', inventory)
    question = 'Where next? (room name, take, talk, save, load, quit) '
    command = input(question).lower()
    if command == 'quit':
        break
    elif command == 'save':
        save_game(room, inventory, 'castle_save.txt')
        print('Adventure saved.')
    elif command == 'load':
        try:
            progress = load_game('castle_save.txt', descriptions)
            room = progress['room']
            inventory = progress['inventory']
            print('Adventure loaded.')
        except FileNotFoundError:
            print('No saved adventure yet. Use save first.')
        except ValueError:
            print('That save file is not a valid castle adventure.')
    elif command == 'talk':
        if room in characters:
            characters[room].talk(inventory)
        else:
            print('Nobody answers. The crown is a poor conversationalist.')
    elif command == 'take':
        if room in items:
            item = items[room]
            if item in inventory:
                print('You already have it.')
            else:
                inventory.append(item)
                print('You take the', item)
                if item == 'crown':
                    print('You found the crown. You win!')
                    break
        else:
            print('There is nothing to take here.')
    elif command in exits[room]:
        if command == 'treasury':
            if 'torch' in inventory:
                room = command
            else:
                print('Too dark! Find a torch first.')
        else:
            room = command
    else:
        print("You can't do that here.")

print('Thanks for exploring!')

Try talk twice in the hall: one map. Then go dragon, talk, save, hall, load, talk: one torch, even after loading. Finish with hall, treasury, take.

Browser saves remain temporary. To keep progress across sessions, run the program locally as in Save Your Adventure. The code recreates its characters each run; the file restores the player's room and bag.

Add Your Own Voice

Choose one bonus:

You could model rooms and items with classes later. First ask what behavior they would gain. A class earns its place when keeping data and behavior together makes the program easier to understand.

Explain which values belong to an individual character and which belong to the player. Which of them change during this game?