26 Week 11 Project: Text Adventure Game
Make sure you’ve completed: - All previous projects - Chapter 12: Interactive Systems - Understanding of GUI event handling and state management
You should be ready to: - Design complex interactive systems - Manage application state across time - Create engaging user experiences - Handle dynamic content generation
26.1 Project Overview
This project pushes interactive systems to their limits. You’ll create a text-based adventure game with a graphical interface, featuring dynamic storytelling, inventory management, character progression, and branching narratives.
This is where programming becomes storytelling - your code creates worlds!
26.2 The Problem to Solve
People love interactive stories with meaningful choices! Your text adventure should: - Present an engaging narrative with multiple paths - Respond dynamically to player choices - Manage complex game state (inventory, character stats, story progress) - Provide an immersive interface with visuals and audio cues - Save and load game progress - Create replayable experiences with different outcomes
26.3 Architect Your Solution First
Before writing any code or consulting AI, design your adventure game:
1. Story and Game Design
Plan your adventure: - Setting: Medieval fantasy? Space exploration? Modern mystery? - Main Quest: What’s the player trying to achieve? - Key Characters: Who will the player meet? - Major Locations: What places will they explore? - Choice Consequences: How do decisions affect the story?
2. Interface Design
Sketch your game window:
┌───────────────────────────────────────────────────────────────┐
│ 🗡️ ADVENTURE GAME - The Crystal Caves │
├───────────────────────────────────────────────────────────────┤
│ STORY DISPLAY │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ You stand at the entrance to the mysterious Crystal │ │
│ │ Caves. Ancient runes glow faintly on the stone │ │
│ │ archway. A cold wind whispers from within... │ │
│ │ │ │
│ │ To your left, you notice a rusted sword partially │ │
│ │ buried in the ground. To your right, a narrow path │ │
│ │ leads around the cave entrance. │ │
│ └─────────────────────────────────────────────────────────┘ │
├───────────────────────────────────────────────────────────────┤
│ CHOICES │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Enter the caves │ │ Examine sword │ │ Take side path │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
├───────────────────────────────────────────────────────────────┤
│ CHARACTER | INVENTORY | GAME INFO │
│ Health: ❤️❤️❤️❤️❤️ | 🗡️ Rusty Sword | Location: Cave Entrance │
│ Magic: ⭐⭐⭐ | 🧪 Health Potion| Choices Made: 3 │
│ Level: 1 | 💰 15 gold coins| Story Branch: A │
└───────────────────────────────────────────────────────────────┘
3. Game State Architecture
Plan your data structures:
# Game state structure
game_state = {
'player': {
'name': 'Hero',
'health': 100,
'magic': 50,
'level': 1,
'experience': 0,
'location': 'cave_entrance'
},
'inventory': [
{'item': 'rusty_sword', 'type': 'weapon', 'damage': 5},
{'item': 'health_potion', 'type': 'consumable', 'healing': 25}
],
'story': {
'current_scene': 'cave_entrance_01',
'choices_made': ['examined_runes', 'talked_to_wizard'],
'flags': {'has_sword': True, 'wizard_friendly': True},
'branch': 'heroic_path'
},
'game_progress': {
'scenes_visited': 15,
'items_found': 3,
'battles_won': 2,
'save_time': '2024-03-15 16:30:00'
}
}26.4 Implementation Strategy
Phase 1: Core Game Engine
- Scene management system
- Choice handling and consequences
- Basic state tracking
- Simple navigation
Phase 2: Player Systems
- Character stats (health, magic, level)
- Inventory management
- Experience and leveling
- Combat system (if applicable)
Phase 3: Rich Interface
- Formatted story display
- Dynamic choice buttons
- Character/inventory panels
- Progress tracking
Phase 4: Advanced Features
- Save/load game functionality
- Multiple story branches
- Random events
- Achievement system
26.5 AI Partnership Guidelines
Effective Prompts for This Project
✅ Good Learning Prompts:
"I'm building a text adventure game. I need a Scene class that stores
story text, available choices, and consequences. Show me a simple
structure with methods for displaying and handling choices."
"My adventure game needs to track player inventory. Show me how to
add/remove items and display them in a tkinter Listbox with item
descriptions on selection."
"I want to save game state to JSON and reload it later. Show me how
to serialize my game state dictionary and restore it safely."
❌ Avoid These Prompts: - “Create a full RPG with graphics and multiplayer” - “Build an AI that generates infinite storylines” - “Add 3D graphics and voice acting”
AI Learning Progression
Architecture Phase: Game structure
"I need to manage game scenes with story text and player choices. What's a good design pattern for this? Show me a simple example."State Management: Complex data tracking
"My adventure game tracks player stats, inventory, and story progress. How do I organize this data and update it efficiently?"Interface Integration: GUI and game logic
"How do I update tkinter widgets when game state changes? Show me a pattern for keeping GUI in sync with game data."
26.6 Requirements Specification
Functional Requirements
Your text adventure must:
- Story System
- Present narrative text engagingly
- Offer meaningful player choices
- Handle branching storylines
- Support multiple endings
- Character Management
- Track player stats (health, magic, level)
- Manage inventory system
- Handle character progression
- Support item usage
- Game Flow
- Navigate between scenes smoothly
- Remember player choices and consequences
- Provide save/load functionality
- Show game progress and statistics
- User Interface
- Display story text clearly
- Present choices as clickable options
- Show character status and inventory
- Provide game controls (save, load, quit)
Learning Requirements
Your implementation should: - [ ] Use classes to organize game components - [ ] Manage complex application state - [ ] Create dynamic GUI updates - [ ] Handle user input and choices - [ ] Demonstrate file I/O for save games
26.7 Sample Interaction
Here’s how your text adventure might work:
🗡️ THE CRYSTAL CAVES ADVENTURE
═══════════════════════════════════════════════════════════════
[STORY PANEL]
═══════════════════════════════════════════════════════════════
You stand before the legendary Crystal Caves, where ancient magic
is said to still flow through the crystalline walls. The entrance
is carved with mystical runes that pulse with a faint blue light.
A weathered sign reads: "Those who enter with pure hearts may find
what they seek. Those who enter with greed will find only danger."
As you approach, you notice three paths:
═══════════════════════════════════════════════════════════════
[CHOICES]
┌─────────────────────────────────────┐
│ 🚪 Enter through the main entrance │ [BOLD APPROACH]
├─────────────────────────────────────┤
│ 🌿 Follow the narrow side path │ [CAUTIOUS APPROACH]
├─────────────────────────────────────┤
│ 📚 Study the runes more carefully │ [SCHOLARLY APPROACH]
├─────────────────────────────────────┤
│ 🎒 Check your equipment first │ [PREPARED APPROACH]
└─────────────────────────────────────┘
═══════════════════════════════════════════════════════════════
PLAYER STATUS: INVENTORY:
❤️ Health: 100/100 🗡️ Iron Sword (Damage: 10)
⭐ Magic: 30/50 🧪 Health Potion x2
🏆 Level: 2 (XP: 250/500) 🔑 Mysterious Key
🧭 Location: Cave Entrance 💰 45 Gold Pieces
📜 Ancient Map Fragment
GAME PROGRESS: ACHIEVEMENTS:
⏱️ Time Played: 45 minutes ✅ First Steps (Enter the cave)
📍 Scenes Visited: 8 ✅ Collector (Find 5 items)
🎯 Main Quest: Find the Crystal ⬜ Warrior (Win 3 battles)
📊 Completion: 15% ⬜ Scholar (Solve 3 puzzles)
═══════════════════════════════════════════════════════════════
[GAME CONTROLS]
💾 Save Game 📁 Load Game ⚙️ Settings ❌ Quit
26.8 Development Approach
Step 1: Scene Management System
Create the core game structure:
class Scene:
def __init__(self, scene_id, title, description, choices=None):
self.scene_id = scene_id
self.title = title
self.description = description
self.choices = choices or []
self.visited = False
self.items = []
self.characters = []
def add_choice(self, text, consequence, condition=None):
"""Add a choice with optional condition"""
choice = {
'text': text,
'consequence': consequence,
'condition': condition,
'available': True
}
self.choices.append(choice)
def get_available_choices(self, game_state):
"""Get choices available based on current game state"""
available = []
for choice in self.choices:
if choice['condition'] is None or choice['condition'](game_state):
available.append(choice)
return available
class StoryEngine:
def __init__(self):
self.scenes = {}
self.current_scene = None
self.create_story()
def create_story(self):
"""Create all game scenes and connections"""
# Cave entrance
entrance = Scene(
'cave_entrance',
'The Crystal Caves Entrance',
"""You stand before the legendary Crystal Caves. Ancient runes
glow with mystical energy on the stone archway. A sign warns
of dangers within, but also speaks of great treasures for the
worthy."""
)
entrance.add_choice(
"Enter the caves boldly",
{'next_scene': 'main_tunnel', 'player_change': {'courage': +1}}
)
entrance.add_choice(
"Study the runes first",
{'next_scene': 'rune_study', 'player_change': {'wisdom': +1}}
)
entrance.add_choice(
"Look for another entrance",
{'next_scene': 'side_path', 'player_change': {'caution': +1}}
)
self.scenes['cave_entrance'] = entrance
# Add more scenes...
self.create_main_tunnel()
self.create_rune_study()
self.create_side_path()
def get_scene(self, scene_id):
"""Get a scene by ID"""
return self.scenes.get(scene_id)
def process_choice(self, choice, game_state):
"""Process a player's choice and update game state"""
consequence = choice['consequence']
# Change scene
if 'next_scene' in consequence:
self.current_scene = consequence['next_scene']
# Update player stats
if 'player_change' in consequence:
for stat, change in consequence['player_change'].items():
if stat in game_state['player']:
game_state['player'][stat] = game_state['player'].get(stat, 0) + change
# Add items
if 'add_item' in consequence:
game_state['inventory'].append(consequence['add_item'])
# Set story flags
if 'set_flag' in consequence:
for flag, value in consequence['set_flag'].items():
game_state['story']['flags'][flag] = value
return game_stateStep 2: Player Management
Handle character stats and inventory:
class Player:
def __init__(self, name="Hero"):
self.name = name
self.health = 100
self.max_health = 100
self.magic = 50
self.max_magic = 50
self.level = 1
self.experience = 0
self.stats = {
'courage': 0,
'wisdom': 0,
'caution': 0
}
def take_damage(self, amount):
"""Reduce health by amount"""
self.health = max(0, self.health - amount)
return self.health <= 0 # Return True if player died
def heal(self, amount):
"""Restore health"""
self.health = min(self.max_health, self.health + amount)
def use_magic(self, amount):
"""Use magic if available"""
if self.magic >= amount:
self.magic -= amount
return True
return False
def gain_experience(self, amount):
"""Add experience and check for level up"""
self.experience += amount
if self.experience >= self.level * 100:
self.level_up()
def level_up(self):
"""Level up the player"""
self.level += 1
self.experience = 0
self.max_health += 20
self.max_magic += 10
self.health = self.max_health # Full heal on level up
self.magic = self.max_magic
return True
class Inventory:
def __init__(self):
self.items = []
self.max_capacity = 20
def add_item(self, item):
"""Add item to inventory if space available"""
if len(self.items) < self.max_capacity:
self.items.append(item)
return True
return False
def remove_item(self, item_name):
"""Remove item from inventory"""
for i, item in enumerate(self.items):
if item.get('name') == item_name:
return self.items.pop(i)
return None
def has_item(self, item_name):
"""Check if inventory contains item"""
return any(item.get('name') == item_name for item in self.items)
def get_items_by_type(self, item_type):
"""Get all items of a specific type"""
return [item for item in self.items if item.get('type') == item_type]
def use_item(self, item_name, player):
"""Use an item and apply its effects"""
item = self.remove_item(item_name)
if item and item.get('type') == 'consumable':
if 'healing' in item:
player.heal(item['healing'])
return f"Used {item['name']} and restored {item['healing']} health!"
elif 'magic_restore' in item:
player.magic = min(player.max_magic, player.magic + item['magic_restore'])
return f"Used {item['name']} and restored {item['magic_restore']} magic!"
return "Item cannot be used."Step 3: GUI Integration
Connect the game engine to the interface:
import tkinter as tk
from tkinter import scrolledtext, messagebox
import json
class AdventureGameGUI:
def __init__(self, root):
self.root = root
self.root.title("🗡️ The Crystal Caves Adventure")
self.root.geometry("900x700")
# Initialize game components
self.story_engine = StoryEngine()
self.player = Player()
self.inventory = Inventory()
self.game_state = self.create_initial_state()
self.create_interface()
self.start_game()
def create_interface(self):
# Main title
title_frame = tk.Frame(self.root, bg='darkblue', height=50)
title_frame.pack(fill='x')
title_frame.pack_propagate(False)
title_label = tk.Label(title_frame, text="🗡️ THE CRYSTAL CAVES ADVENTURE",
font=('Arial', 16, 'bold'), fg='white', bg='darkblue')
title_label.pack(expand=True)
# Story display area
story_frame = tk.Frame(self.root)
story_frame.pack(fill='both', expand=True, padx=10, pady=5)
tk.Label(story_frame, text="STORY", font=('Arial', 12, 'bold')).pack(anchor='w')
self.story_text = scrolledtext.ScrolledText(
story_frame, height=15, wrap=tk.WORD,
font=('Arial', 11), bg='lightyellow'
)
self.story_text.pack(fill='both', expand=True)
# Choices frame
choices_frame = tk.Frame(self.root)
choices_frame.pack(fill='x', padx=10, pady=5)
tk.Label(choices_frame, text="CHOICES", font=('Arial', 12, 'bold')).pack(anchor='w')
self.choices_frame = tk.Frame(choices_frame)
self.choices_frame.pack(fill='x')
# Status panel
status_frame = tk.Frame(self.root, bg='lightgray', height=100)
status_frame.pack(fill='x', padx=10, pady=5)
status_frame.pack_propagate(False)
# Split status into three columns
player_frame = tk.Frame(status_frame, bg='lightgray')
player_frame.pack(side='left', fill='both', expand=True)
inventory_frame = tk.Frame(status_frame, bg='lightgray')
inventory_frame.pack(side='left', fill='both', expand=True)
progress_frame = tk.Frame(status_frame, bg='lightgray')
progress_frame.pack(side='left', fill='both', expand=True)
# Player status
tk.Label(player_frame, text="PLAYER STATUS", font=('Arial', 10, 'bold'),
bg='lightgray').pack()
self.player_status = tk.Label(player_frame, text="", justify='left',
bg='lightgray', font=('Arial', 9))
self.player_status.pack()
# Inventory
tk.Label(inventory_frame, text="INVENTORY", font=('Arial', 10, 'bold'),
bg='lightgray').pack()
self.inventory_status = tk.Label(inventory_frame, text="", justify='left',
bg='lightgray', font=('Arial', 9))
self.inventory_status.pack()
# Progress
tk.Label(progress_frame, text="PROGRESS", font=('Arial', 10, 'bold'),
bg='lightgray').pack()
self.progress_status = tk.Label(progress_frame, text="", justify='left',
bg='lightgray', font=('Arial', 9))
self.progress_status.pack()
# Control buttons
control_frame = tk.Frame(self.root)
control_frame.pack(fill='x', padx=10, pady=5)
tk.Button(control_frame, text="💾 Save Game",
command=self.save_game).pack(side='left', padx=5)
tk.Button(control_frame, text="📁 Load Game",
command=self.load_game).pack(side='left', padx=5)
tk.Button(control_frame, text="🎒 Use Item",
command=self.show_inventory_dialog).pack(side='left', padx=5)
tk.Button(control_frame, text="❌ Quit",
command=self.quit_game).pack(side='right', padx=5)
def start_game(self):
"""Start the adventure"""
self.story_engine.current_scene = 'cave_entrance'
self.display_current_scene()
def display_current_scene(self):
"""Display the current scene and update interface"""
scene = self.story_engine.get_scene(self.story_engine.current_scene)
if not scene:
return
# Mark scene as visited
scene.visited = True
# Clear and update story text
self.story_text.delete(1.0, tk.END)
self.story_text.insert(tk.END, f"{scene.title}\n\n")
self.story_text.insert(tk.END, scene.description)
# Clear previous choices
for widget in self.choices_frame.winfo_children():
widget.destroy()
# Display available choices
available_choices = scene.get_available_choices(self.game_state)
for i, choice in enumerate(available_choices):
btn = tk.Button(
self.choices_frame,
text=f"{i+1}. {choice['text']}",
command=lambda c=choice: self.make_choice(c),
width=40, height=2, wraplength=300
)
btn.pack(pady=2, fill='x')
# Update status displays
self.update_status_displays()
def make_choice(self, choice):
"""Process a player choice"""
# Update game state based on choice
self.game_state = self.story_engine.process_choice(choice, self.game_state)
# Add choice to history
self.game_state['story']['choices_made'].append(choice['text'])
# Display the scene
self.display_current_scene()
# Check for special events
self.check_random_events()
def update_status_displays(self):
"""Update all status displays"""
# Player status
player_text = f"""❤️ Health: {self.player.health}/{self.player.max_health}
⭐ Magic: {self.player.magic}/{self.player.max_magic}
🏆 Level: {self.player.level} (XP: {self.player.experience})
🧭 Location: {self.story_engine.current_scene.replace('_', ' ').title()}"""
self.player_status.config(text=player_text)
# Inventory
if self.inventory.items:
inventory_text = "\n".join([f"• {item.get('name', 'Unknown')}"
for item in self.inventory.items[:5]])
if len(self.inventory.items) > 5:
inventory_text += f"\n... and {len(self.inventory.items) - 5} more"
else:
inventory_text = "Empty"
self.inventory_status.config(text=inventory_text)
# Progress
progress_text = f"""⏱️ Scenes Visited: {len([s for s in self.story_engine.scenes.values() if s.visited])}
🎯 Choices Made: {len(self.game_state['story']['choices_made'])}
📊 Items Found: {len(self.inventory.items)}"""
self.progress_status.config(text=progress_text)
def save_game(self):
"""Save current game state"""
save_data = {
'player': {
'name': self.player.name,
'health': self.player.health,
'max_health': self.player.max_health,
'magic': self.player.magic,
'max_magic': self.player.max_magic,
'level': self.player.level,
'experience': self.player.experience,
'stats': self.player.stats
},
'inventory': self.inventory.items,
'current_scene': self.story_engine.current_scene,
'game_state': self.game_state
}
try:
with open('adventure_save.json', 'w') as f:
json.dump(save_data, f, indent=2)
messagebox.showinfo("Save Game", "Game saved successfully!")
except Exception as e:
messagebox.showerror("Save Error", f"Could not save game: {e}")
def load_game(self):
"""Load saved game state"""
try:
with open('adventure_save.json', 'r') as f:
save_data = json.load(f)
# Restore player
player_data = save_data['player']
self.player.name = player_data['name']
self.player.health = player_data['health']
self.player.max_health = player_data['max_health']
self.player.magic = player_data['magic']
self.player.max_magic = player_data['max_magic']
self.player.level = player_data['level']
self.player.experience = player_data['experience']
self.player.stats = player_data['stats']
# Restore inventory
self.inventory.items = save_data['inventory']
# Restore scene
self.story_engine.current_scene = save_data['current_scene']
# Restore game state
self.game_state = save_data['game_state']
self.display_current_scene()
messagebox.showinfo("Load Game", "Game loaded successfully!")
except FileNotFoundError:
messagebox.showerror("Load Error", "No saved game found!")
except Exception as e:
messagebox.showerror("Load Error", f"Could not load game: {e}")
# Run the game
if __name__ == "__main__":
root = tk.Tk()
game = AdventureGameGUI(root)
root.mainloop()26.9 Advanced Features
Random Events System
import random
class RandomEvents:
def __init__(self):
self.events = [
{
'name': 'treasure_find',
'chance': 0.1,
'description': 'You discover a hidden treasure!',
'consequence': {'add_item': {'name': 'Gold Coins', 'value': 50}}
},
{
'name': 'magic_surge',
'chance': 0.05,
'description': 'A wave of magic energy flows through you!',
'consequence': {'player_change': {'magic': +20}}
}
]
def check_for_event(self, game_state):
"""Check if a random event occurs"""
for event in self.events:
if random.random() < event['chance']:
return event
return NoneAchievement System
class AchievementManager:
def __init__(self):
self.achievements = {
'first_choice': {'name': 'Decision Maker', 'description': 'Made your first choice'},
'item_collector': {'name': 'Collector', 'description': 'Found 5 items'},
'explorer': {'name': 'Explorer', 'description': 'Visited 10 scenes'},
'level_up': {'name': 'Growing Strong', 'description': 'Reached level 2'}
}
self.unlocked = set()
def check_achievements(self, game_state):
"""Check for newly unlocked achievements"""
newly_unlocked = []
# Check various conditions
if len(game_state['story']['choices_made']) >= 1 and 'first_choice' not in self.unlocked:
self.unlocked.add('first_choice')
newly_unlocked.append('first_choice')
# Add more achievement checks...
return newly_unlocked26.10 Common Pitfalls and Solutions
Pitfall 1: Overly Complex Story Branching
Problem: Too many story paths become unmanageable Solution: Use flags and conditions to merge paths intelligently
Pitfall 2: No Save/Load Validation
Problem: Corrupted save files crash the game Solution: Validate save data and provide fallbacks
Pitfall 3: Static Choices
Problem: Same choices available regardless of player state Solution: Use conditions to make choices dynamic
Pitfall 4: Poor State Management
Problem: Game state becomes inconsistent Solution: Centralize state updates through clear methods
26.11 Testing Your Adventure
Test Scenarios
- Complete Playthroughs: Multiple paths to different endings
- Save/Load: Save at various points and reload
- Edge Cases: Player at 0 health, full inventory
- Choice Validation: Conditional choices appear/disappear correctly
- State Persistence: All progress carries between sessions
26.12 Reflection Questions
After completing the project:
- Interactive Design: What made choices feel meaningful vs arbitrary?
- State Complexity: How did you manage all the interconnected data?
- Player Engagement: What kept players invested in the story?
- Technical Challenges: Which systems were hardest to implement?
26.13 Next Week Preview
Outstanding work! Next week, you’ll create the capstone project - a Todo GUI application that demonstrates everything you’ve learned about software architecture. You’ll design a complete application from scratch using all your skills!
Your text adventure proves you can create engaging, interactive experiences with complex state management - the foundation of game development and interactive applications! 🗡️