Python Text-Based Game Tutorial

Python Text-Based Game Tutorial: From Code to Adventure

Introduction: The Art of Interactive Storytelling

Creating a text-based game in Python is one of the most rewarding projects for a beginner programmer. There’s something magical about building a world from nothing but lines of code, where players can explore, make decisions, and experience stories that respond to their choices. In this tutorial, we’ll walk through creating a complete text-based adventure game, but more importantly, we’ll explore how to make it feel alive.

When I wrote my first text-based game at nineteen, I remember the thrill of showing it to my younger cousin. She spent an hour exploring my medieval fantasy world, and every time she discovered a new path or found a hidden item, her eyes lit up. That moment taught me that programming isn’t just about syntax—it’s about creating experiences. So let’s build something that feels like an adventure, not just a series of print() statements.

Chapter 1: Setting Up Your Adventure

The Humble Beginning

Every great adventure starts with a single step, and in Python, that step is creating your main game file. Let’s call it adventure.py. Before we dive into complex systems, let’s think about what makes a text-based game engaging:

  1. A compelling setting that players can visualize
  2. Meaningful choices that affect the outcome
  3. Discoverable secrets that reward exploration
  4. A clear sense of progress and achievement

Open your favorite code editor and let’s begin with the skeleton of our game:

"""
The Enchanted Forest - A Text Adventure
A tale of mystery, magic, and choice
"""

def main():
    print("Welcome to the Enchanted Forest...")
    print("=" * 50)

    # We'll build our game here

if __name__ == "__main__":
    main()

This simple structure gives us a foundation. The if __name__ == "__main__": line might seem mysterious now, but it’s Python’s way of saying “run this code if you’re executing this file directly.” It’s a best practice that keeps your code clean and reusable.

Chapter 2: Building the World

Creating Your First Room

In a text-based game, the world is made of interconnected spaces—rooms, locations, or scenes. Let’s think about how to represent a room in Python. We could use a class, but for simplicity, we’ll start with a dictionary:

def create_rooms():
    """Create all the rooms in our game world"""

    rooms = {
        'forest_clearing': {
            'name': 'Forest Clearing',
            'description': 'Sunlight filters through ancient oak trees, dappling the mossy ground with golden spots. A worn stone path leads north deeper into the forest, while to the east you hear the gentle babble of a stream.',
            'exits': {'north': 'dark_woods', 'east': 'stream_bank'},
            'items': ['old_key']
        },
        'dark_woods': {
            'name': 'Dark Woods',
            'description': 'The trees grow thicker here, their branches intertwining overhead to block out most of the light. Strange symbols are carved into the bark of a massive oak to your left. The path continues north, or you could examine the markings more closely.',
            'exits': {'south': 'forest_clearing', 'north': 'ancient_ruin'},
            'items': ['strange_amulet']
        },
        'stream_bank': {
            'name': 'Stream Bank',
            'description': 'Clear water rushes over smooth stones. A small wooden bridge crosses the stream to the south, and you notice something glittering in the shallows.',
            'exits': {'west': 'forest_clearing', 'south': 'bridge_crossing'},
            'items': ['shiny_coin']
        },
        'ancient_ruin': {
            'name': 'Ancient Ruin',
            'description': 'Crumbling stone pillars rise from the earth, covered in ivy and moss. In the center, a stone pedestal holds a glowing crystal. The air hums with ancient magic.',
            'exits': {'south': 'dark_woods', 'east': 'hidden_cave'},
            'items': ['crystal']
        },
        'bridge_crossing': {
            'name': 'Bridge Crossing',
            'description': 'The wooden bridge creaks beneath your feet. Below, the stream flows into a dark pool. You see a mysterious door carved into the cliff face on the far side.',
            'exits': {'north': 'stream_bank', 'east': 'doorway'},
            'items': []
        },
        'hidden_cave': {
            'name': 'Hidden Cave',
            'description': 'The cave entrance is barely visible behind a curtain of hanging vines. Inside, the walls glitter with mineral deposits, and you can see a treasure chest in the back.',
            'exits': {'west': 'ancient_ruin'},
            'items': ['treasure_chest', 'gold_coins']
        },
        'doorway': {
            'name': 'Mysterious Doorway',
            'description': 'The stone door is covered in intricate carvings depicting a forest spirit. There\'s a keyhole shaped like an ancient key, and the door pulses with a faint green light.',
            'exits': {'west': 'bridge_crossing'},
            'items': []
        }
    }

    return rooms

Making Rooms Feel Alive

But wait—rooms shouldn’t just be data. They should have personality. Let’s add some atmosphere with dynamic descriptions that change based on what the player has done:

def describe_room(room_id, player, rooms):
    """Create a vivid description of the room"""
    room = rooms[room_id]

    # Build the description
    description = f"\n=== {room['name']} ===\n"
    description += f"\n{room['description']}\n"

    # Add any items present in the room
    if room['items'] and not room_id in player['visited_items']:
        if len(room['items']) == 1:
            description += f"\nYou notice a {room['items'][0].replace('_', ' ')} here."
        else:
            items_list = ', '.join(item.replace('_', ' ') for item in room['items'])
            description += f"\nYou see: {items_list}"

        # Mark that we've mentioned these items
        if 'visited_items' not in player:
            player['visited_items'] = []
        player['visited_items'].append(room_id)

    # Add exit information
    exits = room['exits'].keys()
    if exits:
        exit_directions = ', '.join(exits)
        description += f"\n\nExits: {exit_directions}"

    # Add special atmosphere for certain rooms
    if room_id == 'forest_clearing':
        description += "\n\nThe forest hums with life around you."
    elif room_id == 'dark_woods':
        if player.get('has_torch', False):
            description += "\n\nYour torch flickers against the oppressive darkness."
        else:
            description += "\n\nThe darkness presses in around you."
    elif room_id == 'ancient_ruin':
        if player.get('has_crystal', False):
            description += "\n\nThe crystal you took glows warmly in your pack."

    print(description)

Chapter 3: The Player’s Journey

Creating the Player Character

Now we need to track our hero. A dictionary makes a perfect character sheet:

def create_player():
    """Initialize the player's character"""
    print("\nBefore you begin your journey, tell me about yourself...")

    name = input("What is your name, adventurer? ").strip()
    while not name:
        name = input("Please, tell me your name: ").strip()

    print(f"\nWelcome, {name}. Your adventure awaits...")

    return {
        'name': name,
        'current_room': 'forest_clearing',
        'inventory': [],
        'health': 10,
        'max_health': 10,
        'has_torch': False,
        'has_crystal': False,
        'quest_completed': False,
        'visited_items': [],
        'moves': 0,
        'score': 0
    }

The Core Loop: Living in the World

The heart of any text-based game is the input loop. This is where the magic happens—where player decisions create the story:

def game_loop(player, rooms):
    """The main game loop"""
    game_running = True

    # Initial greeting
    describe_room(player['current_room'], player, rooms)

    while game_running:
        # Get player input
        command = input("\n> ").strip().lower()

        # Split command into action and target
        if not command:
            continue

        parts = command.split(' ', 1)
        action = parts[0]
        target = parts[1] if len(parts) > 1 else ''

        # Process commands
        if action in ['quit', 'exit']:
            print("\nFarewell, brave adventurer. Until we meet again.")
            game_running = False

        elif action in ['go', 'move', 'walk']:
            handle_movement(player, rooms, target)

        elif action in ['look', 'examine']:
            handle_look(player, rooms, target)

        elif action in ['take', 'get', 'pick']:
            handle_take(player, rooms, target)

        elif action in ['inventory', 'i']:
            show_inventory(player)

        elif action in ['help', '?']:
            show_help()

        elif action in ['use']:
            handle_use(player, rooms, target)

        elif action in ['talk', 'speak']:
            handle_talk(player, rooms, target)

        elif action in ['status']:
            show_status(player)

        else:
            print("I don't understand that command. Type 'help' for a list of commands.")

Chapter 4: Making Decisions Meaningful

The Art of Choice

A text-based game lives and dies by its choices. Let’s create some meaningful moments. Here’s how to handle player actions with style:

def handle_movement(player, rooms, direction):
    """Move the player to a new room if possible"""
    current_room = player['current_room']
    room = rooms[current_room]

    if direction in room['exits']:
        player['current_room'] = room['exits'][direction]
        player['moves'] += 1

        # Check for special events
        check_special_events(player, rooms)

        # Describe the new room
        describe_room(player['current_room'], player, rooms)
    else:
        print(f"\nYou cannot go {direction}. The path is blocked or doesn't exist.")

def check_special_events(player, rooms):
    """Trigger special events based on the player's location"""
    room_id = player['current_room']

    if room_id == 'doorway':
        if 'old_key' in player['inventory']:
            print("\nAs you approach the mysterious doorway, the old key in your pocket begins to glow warmly.")
            print("Do you want to use the key to open the door?")
            choice = input("> ").strip().lower()
            if choice in ['yes', 'y', 'open']:
                player['quest_completed'] = True
                player['score'] += 50
                print("\nThe key slides perfectly into the lock. The door swings open to reveal a beautiful garden filled with glowing flowers.")
                print("You have completed the main quest! The forest spirit appears before you, smiling.")
                print(f"Congratulations, {player['name']}! You have brought light back to the Enchanted Forest.")
                print(f"Score: {player['score']}")
                # Set a special victory flag
                rooms['doorway']['description'] += "\n\nThe door now stands open, revealing the garden beyond."
        else:
            print("\nThe doorway seems to require a key. Perhaps there's one somewhere in the forest.")

    elif room_id == 'ancient_ruin':
        if not player.get('has_crystal', False):
            print("\nThe crystal on the pedestal pulses with light. It seems to be calling to you.")
            print("Would you like to take the crystal?")
            choice = input("> ").strip().lower()
            if choice in ['yes', 'y', 'take']:
                rooms['ancient_ruin']['items'].remove('crystal')
                player['inventory'].append('crystal')
                player['has_crystal'] = True
                player['score'] += 20
                print("\nYou take the crystal. It hums with power and warmth in your hands.")
                print("The air around you shimmers slightly. You feel more connected to the forest.")

Combat and Challenge

Every adventure needs some danger. Let’s add a simple encounter system:

def handle_combat(player, enemy_name, enemy_health):
    """Handle a combat encounter"""
    print(f"\nA {enemy_name} appears before you!")
    print(f"Health: {enemy_health}")

    while enemy_health > 0:
        print(f"\nYour health: {player['health']}/{player['max_health']}")
        print("What do you do?")
        action = input("> ").strip().lower()

        if action in ['attack', 'fight', 'hit']:
            # Player attacks
            damage = random.randint(1, 4)
            enemy_health -= damage
            print(f"\nYou strike the {enemy_name} for {damage} damage!")

            if enemy_health > 0:
                # Enemy attacks back
                enemy_damage = random.randint(1, 3)
                player['health'] -= enemy_damage
                print(f"The {enemy_name} attacks you for {enemy_damage} damage!")

                if player['health'] <= 0:
                    print("\nYou have been defeated! The darkness claims you...")
                    return False
        elif action in ['flee', 'run', 'escape']:
            if random.random() > 0.3:
                print("\nYou manage to escape!")
                return True
            else:
                print("\nYou try to flee, but the enemy blocks your path!")
                enemy_damage = random.randint(1, 2)
                player['health'] -= enemy_damage
                print(f"The {enemy_name} attacks you for {enemy_damage} damage!")
        else:
            print("\nYou hesitate, unsure what to do.")
            enemy_damage = random.randint(1, 2)
            player['health'] -= enemy_damage
            print(f"The {enemy_name} takes advantage and hits you for {enemy_damage} damage!")

    print(f"\nYou have defeated the {enemy_name}!")
    player['score'] += 15
    return True

Chapter 5: The Art of Storytelling

Writing Compelling Descriptions

The words you choose make all the difference. Let’s look at how to write descriptions that immerse players:

def generate_ambient_text(room_id, player):
    """Generate ambient descriptions that add atmosphere"""
    ambient_texts = {
        'forest_clearing': [
            "A gentle breeze rustles the leaves above you.",
            "Birds sing a cheerful melody in the canopy.",
            "A squirrel chatters at you from a nearby branch.",
            "The sunlight shifts, creating dancing patterns on the ground."
        ],
        'dark_woods': [
            "The shadows seem to move at the edge of your vision.",
            "An owl hoots mournfully somewhere in the darkness.",
            "The wind whispers through the trees, carrying strange sounds.",
            "You feel like you're being watched."
        ],
        'ancient_ruin': [
            "The ancient stones hum with residual magic.",
            "Moss-covered runes seem to shift when you're not looking directly at them.",
            "A cool breeze carries the scent of old magic and earth.",
            "The silence here is heavy, like the world is holding its breath."
        ],
        'stream_bank': [
            "The water babbles gently over smooth stones.",
            "A dragonfly flits lazily past your shoulder.",
            "The stream seems to sing a quiet, soothing song.",
            "Small fish dart through the clear water."
        ],
        'hidden_cave': [
            "Water drips rhythmically from the cave ceiling.",
            "The mineral deposits on the walls sparkle in the dim light.",
            "You feel a draft from deeper within the cave.",
            "The air is cool and damp, smelling of earth and stone."
        ]
    }

    if room_id in ambient_texts and random.random() < 0.3:  # 30% chance of ambient text
        print(random.choice(ambient_texts[room_id]))

Chapter 6: Puzzles and Secrets

Creating Engaging Challenges

Puzzles add depth to your game. Here’s a riddle system that creates memorable moments:

def handle_riddle(player):
    """Present a riddle to the player"""
    riddles = [
        {
            'question': "I have cities, but no houses. I have mountains, but no trees. I have water, but no fish. What am I?",
            'answer': 'map',
            'hint': "Think about what can contain all these things without actually having them...",
            'reward': 10,
            'item_reward': 'treasure_map'
        },
        {
            'question': "What has keys but can't open locks, space but no room, and you can enter but can't go inside?",
            'answer': 'keyboard',
            'hint': "Think about something you might be using right now...",
            'reward': 10,
            'item_reward': None
        },
        {
            'question': "I'm tall when I'm young, short when I'm old. What am I?",
            'answer': 'candle',
            'hint': "Think about things that change with age...",
            'reward': 10,
            'item_reward': 'candle'
        }
    ]

    riddle = random.choice(riddles)
    attempts = 0

    print(f"\nA mysterious voice speaks:\n'{riddle['question']}'")

    while attempts < 3:
        answer = input("\nYour answer: ").strip().lower()
        attempts += 1

        if answer == riddle['answer']:
            print("\nThe voice chuckles warmly. 'You are wise indeed. Take this gift.'")
            player['score'] += riddle['reward']

            if riddle['item_reward']:
                player['inventory'].append(riddle['item_reward'])
                print(f"You receive: {riddle['item_reward'].replace('_', ' ')}")

            return True
        else:
            if attempts < 3:
                print(f"\n'Not quite. {riddle['hint']}'")
            else:
                print("\nThe voice sighs. 'Perhaps another time then.'")

    return False

Chapter 7: NPCs and Dialogue

Bringing Characters to Life

Non-player characters make your world feel populated. Let’s create an NPC system:

def create_npcs():
    """Create the NPCs in our game world"""
    return {
        'forest_spirit': {
            'name': 'Forest Spirit',
            'room': 'forest_clearing',
            'dialogue': [
                "Welcome, traveler. The forest has been waiting for you.",
                "The darkness grows in the ancient ruins. You must find the crystal to restore balance.",
                "Be careful in the dark woods. The shadows there have grown restless.",
                "When you find the doorway, remember: the key is where it's been waiting for you.",
                "The forest thanks you for your courage."
            ],
            'quest_dialogue': [
                "I see you carry the crystal. The forest feels your kindness.",
                "The doorway awaits. You have everything you need."
            ],
            'given_gift': False
        },
        'old_man': {
            'name': 'Old Man',
            'room': 'bridge_crossing',
            'dialogue': [
                "Ah, a traveler! I haven't seen one in years.",
                "The bridge has been here longer than I can remember.",
                "Watch your step crossing the stream - the stones are slippery.",
                "If you're looking for treasure, there's a cave hidden behind the waterfall.",
                "The forest spirit watches over this place. Show respect, and you'll be rewarded."
            ],
            'given_gift': False
        }
    }

def handle_talk(player, npcs, npc_name):
    """Talk to an NPC"""
    current_room = player['current_room']
    npc_found = None

    for npc_id, npc_data in npcs.items():
        if npc_data['room'] == current_room and npc_data['name'].lower() == npc_name:
            npc_found = npc_data
            break

    if npc_found:
        if player['quest_completed'] and npc_id == 'forest_spirit':
            dialogue = random.choice(npc_found['quest_dialogue'])
        else:
            dialogue = random.choice(npc_found['dialogue'])

        print(f"\n{npc_found['name']} says: '{dialogue}'")

        # Special interactions
        if npc_id == 'forest_spirit' and not npc_found['given_gift']:
            print("\nThe forest spirit grants you a blessing!")
            player['max_health'] += 5
            player['health'] = player['max_health']
            player['score'] += 10
            npc_found['given_gift'] = True
            print("You feel a surge of vitality as the forest's energy flows through you.")
    else:
        print(f"\nThere's no one here by that name. Are you sure you're in the right place?")

Chapter 8: Advanced Features

Saving and Loading

Let’s allow players to save their progress:

import json
import os

def save_game(player, rooms, filename='savegame.json'):
    """Save the game state to a file"""
    save_data = {
        'player': player,
        'rooms': rooms,
        'timestamp': datetime.now().isoformat()
    }

    try:
        with open(filename, 'w') as f:
            json.dump(save_data, f, indent=2)
        print("\nGame saved successfully!")
        print(f"Saved at: {save_data['timestamp']}")
        return True
    except Exception as e:
        print(f"\nError saving game: {e}")
        return False

def load_game(filename='savegame.json'):
    """Load a saved game state"""
    if not os.path.exists(filename):
        print("\nNo save file found. Starting a new game...")
        return None, None

    try:
        with open(filename, 'r') as f:
            save_data = json.load(f)

        print(f"\nGame loaded successfully!")
        print(f"Last saved: {save_data['timestamp']}")
        print(f"Welcome back, {save_data['player']['name']}!")

        return save_data['player'], save_data['rooms']
    except Exception as e:
        print(f"\nError loading game: {e}")
        print("Starting a new game instead...")
        return None, None

The Complete Game Loop

Here’s how everything comes together:

import random
from datetime import datetime

def main():
    print("=" * 60)
    print("THE ENCHANTED FOREST")
    print("A Text Adventure by [Your Name]")
    print("=" * 60)
    print("\nIn a world where magic sleeps beneath the surface...")
    print("...you are called to restore balance to the Enchanted Forest.")
    print("Type 'help' at any time for a list of commands.\n")

    # Try to load saved game
    player, rooms = load_game()

    if player is None or rooms is None:
        rooms = create_rooms()
        player = create_player()

    # Create NPCs
    npcs = create_npcs()

    # Main game loop
    game_loop(player, rooms, npcs)

    # Offer to save before quitting
    print("\nDo you want to save your progress before you go?")
    if input("> ").strip().lower() in ['yes', 'y']:
        save_game(player, rooms)

    print("\nThank you for playing 'The Enchanted Forest'!")
    print(f"Final Score: {player['score']}")
    print(f"Total Moves: {player['moves']}")

    # Rate the player's adventure
    if player['quest_completed']:
        print("\n★ You completed the main quest! The forest is saved! ★")
        if player['score'] > 100:
            print("You are a legendary hero!")
        elif player['score'] > 50:
            print("You are a true adventurer!")
        else:
            print("You did well, young one.")
    else:
        print("\nThe forest still needs your help. Perhaps you'll return one day...")

if __name__ == "__main__":
    main()

Chapter 9: Polish and Refinement

Making Your Game Shine

Now that we have a working game, let’s add those touches that make it special:

  1. Color and Formatting: Use ANSI color codes to add visual flair:
class Colors:
    HEADER = '\033[95m'
    BLUE = '\033[94m'
    GREEN = '\033[92m'
    YELLOW = '\033[93m'
    RED = '\033[91m'
    ENDC = '\033[0m'
    BOLD = '\033[1m'

def colored_print(text, color=Colors.ENDC):
    print(f"{color}{text}{Colors.ENDC}")

# Use it like this:
colored_print("You feel a surge of energy!", Colors.GREEN)
  1. Sound Effects: Use the playsound library or system beeps for feedback:
import sys

def play_sound(type):
    """Simple sound feedback using system beeps"""
    if type == 'success':
        print('\a', end='', file=sys.stderr)
    elif type == 'error':
        print('\a\a', end='', file=sys.stderr)
    elif type == 'item':
        print('\a', end='', file=sys.stderr)
  1. Animation: Create text animations for dramatic moments:
import time

def typewriter(text, delay=0.03):
    """Display text with a typewriter effect"""
    for char in text:
        print(char, end='', flush=True)
        time.sleep(delay)
    print()

def dramatic_reveal(text):
    """Display text character by character with a dramatic pause"""
    for char in text:
        print(char, end='', flush=True)
        if char in '.!?':
            time.sleep(0.5)
        else:
            time.sleep(0.05)
    print()

Chapter 10: Beyond the Basics

Expanding Your Game World

The game we’ve built is just the beginning. Here are ideas for expansion:

  1. Multiple Endings: Your choices should lead to different conclusions
  2. Side Quests: Add optional content that rewards exploration
  3. Item Crafting: Combine items to create new tools
  4. Character Classes: Let players choose different starting abilities
  5. Day/Night Cycle: Change the world based on time
  6. Weather System: Affect gameplay with rain, fog, or storms
  7. Persistent NPCs: Characters that remember your interactions
  8. Minigames: Add puzzles, gambling, or skill challenges

Sample Expansion: The Dark Woods Ambush

def dark_woods_encounter(player, rooms):
    """A special encounter in the dark woods"""
    if player['current_room'] == 'dark_woods' and not player.get('woods_cleared', False):
        # Random chance of encounter
        if random.random() < 0.2:
            print("\nSuddenly, the shadows around you coalesce!")
            print("A shadow wolf lunges from the darkness!")

            # Combat
            success = handle_combat(player, 'Shadow Wolf', 6)

            if success:
                player['woods_cleared'] = True
                print("\nThe shadow dissolves, leaving behind a glowing pearl.")
                player['inventory'].append('shadow_pearl')
                player['score'] += 25
                print("You feel the darkness in the woods lift slightly.")
            else:
                # Player lost combat
                print("\nYou awaken in the forest clearing, shaken but alive...")
                print("The shadow wolf has dragged you away from the dark woods.")
                player['current_room'] = 'forest_clearing'
                player['health'] = 3  # Left for dead, but alive

Conclusion: The Journey Continues

Congratulations! You’ve created a text-based adventure game that’s more than just code—it’s a world waiting to be explored. What started as simple print() statements has become a living, breathing experience where players can lose themselves in adventure.

Remember, the games that resonate most aren’t necessarily the most complex. They’re the ones that make players feel something—excitement, wonder, fear, or joy. Your job isn’t just to program a game; it’s to create an experience that players will remember.

As you continue your programming journey, always keep the player in mind. Test your game with friends and watch how they play. You’ll quickly discover what works and what doesn’t. The most valuable feedback often comes from seeing someone else try to play your game.

What You’ve Learned

Throughout this tutorial, you’ve built:

  • A multi-room game world with dynamic descriptions
  • A player system with inventory, health, and progression
  • Meaningful choices with consequences
  • NPCs with dialogue and interactions
  • Combat encounters and puzzles
  • Save/load functionality
  • Polish features like color and animation

But more importantly, you’ve learned how to think about game design, how to structure code for flexibility, and how to create experiences through code. These skills will serve you well in any programming project, not just games.

Final Thoughts

The text-based adventure is one of the oldest forms of video games, yet it remains one of the most powerful. Without fancy graphics or complex physics, you’re forced to rely on the most important elements: imagination, storytelling, and meaningful choices.

Your game is unique because you created it. It reflects your style, your ideas, and your creativity. Take pride in that. Whether you choose to expand this game or move on to new projects, the skills you’ve developed here will stay with you.

So what’s next? Will you add more rooms, create new puzzles, or perhaps build a completely different game? The code is yours. The world is yours. Go make something wonderful.

Leave a Comment

Scroll to Top