Text Adventure Game Engine¶
Difficulty: Advanced
Time: 90-120 minutes
Learning Focus: Object-oriented programming, game design, file I/O, AI interaction
Module: chat
Overview¶
Create a text adventure game engine that allows students to build interactive stories with rooms, items, and characters. The engine supports saving/loading games and provides AI-powered hints to guide players.
Instructions¶
from hands_on_ai.chat import get_response
import json
import os
class Room:
"""A location in the game world with description and connections to other rooms."""
def __init__(self, name, description, exits=None, items=None):
self.name = name
self.description = description
self.exits = exits or {} # Dictionary mapping direction -> room name
self.items = items or [] # List of item names
def add_exit(self, direction, room_name):
"""Add an exit from this room."""
self.exits[direction] = room_name
def add_item(self, item):
"""Add an item to this room."""
self.items.append(item)
def remove_item(self, item):
"""Remove an item from this room."""
if item in self.items:
self.items.remove(item)
return True
return False
def get_details(self):
"""Get a formatted description of the room including exits and items."""
details = f"{self.name}\n"
details += f"{'-' * len(self.name)}\n"
details += f"{self.description}\n"
if self.exits:
details += "\nExits:"
for direction, room in self.exits.items():
details += f" {direction}"
if self.items:
details += "\n\nYou can see:"
for item in self.items:
details += f"\n- {item}"
return details
def to_dict(self):
"""Convert room to dictionary for saving."""
return {
"name": self.name,
"description": self.description,
"exits": self.exits,
"items": self.items
}
@classmethod
def from_dict(cls, data):
"""Create room from dictionary data."""
return cls(
data["name"],
data["description"],
data.get("exits", {}),
data.get("items", [])
)
class Player:
"""The player character with inventory and current location."""
def __init__(self, name, current_room="Starting Room"):
self.name = name
self.current_room = current_room
self.inventory = []
self.game_flags = {} # For tracking game state, quests, etc.
def move(self, direction, world):
"""Try to move in a direction. Return success/failure message."""
current_room = world.get_room(self.current_room)
if direction in current_room.exits:
self.current_room = current_room.exits[direction]
return f"You move {direction}."
else:
return f"You can't go {direction} from here."
def take(self, item_name, world):
"""Try to take an item from the current room."""
current_room = world.get_room(self.current_room)
for item in current_room.items:
if item.lower() == item_name.lower():
current_room.remove_item(item)
self.inventory.append(item)
return f"You take the {item}."
return f"There is no {item_name} here."
def drop(self, item_name, world):
"""Try to drop an item from inventory into the current room."""
current_room = world.get_room(self.current_room)
for item in self.inventory:
if item.lower() == item_name.lower():
self.inventory.remove(item)
current_room.add_item(item)
return f"You drop the {item}."
return f"You don't have a {item_name}."
def check_inventory(self):
"""Check what items the player is carrying."""
if not self.inventory:
return "Your inventory is empty."
result = "You are carrying:"
for item in self.inventory:
result += f"\n- {item}"
return result
def to_dict(self):
"""Convert player to dictionary for saving."""
return {
"name": self.name,
"current_room": self.current_room,
"inventory": self.inventory,
"game_flags": self.game_flags
}
@classmethod
def from_dict(cls, data):
"""Create player from dictionary data."""
player = cls(data["name"], data["current_room"])
player.inventory = data.get("inventory", [])
player.game_flags = data.get("game_flags", {})
return player
class World:
"""The game world containing all rooms and game state."""
def __init__(self, title="Adventure Game"):
self.title = title
self.rooms = {} # Dictionary mapping room name -> Room object
def add_room(self, room):
"""Add a room to the world."""
self.rooms[room.name] = room
def get_room(self, room_name):
"""Get a room by name."""
return self.rooms.get(room_name)
def to_dict(self):
"""Convert world to dictionary for saving."""
return {
"title": self.title,
"rooms": {name: room.to_dict() for name, room in self.rooms.items()}
}
@classmethod
def from_dict(cls, data):
"""Create world from dictionary data."""
world = cls(data["title"])
for name, room_data in data["rooms"].items():
world.add_room(Room.from_dict(room_data))
return world
class GameEngine:
"""Main game engine for running the adventure."""
def __init__(self, player, world):
self.player = player
self.world = world
self.running = False
self.commands = {
"go": self.cmd_go,
"look": self.cmd_look,
"take": self.cmd_take,
"drop": self.cmd_drop,
"inventory": self.cmd_inventory,
"help": self.cmd_help,
"quit": self.cmd_quit
}
self.save_dir = "game_saves"
os.makedirs(self.save_dir, exist_ok=True)
def cmd_go(self, args):
"""Handle movement command."""
if not args:
return "Go where? Try 'go north', 'go south', etc."
direction = args[0].lower()
return self.player.move(direction, self.world)
def cmd_look(self, args):
"""Look around the current room."""
current_room = self.world.get_room(self.player.current_room)
return current_room.get_details()
def cmd_take(self, args):
"""Take an item from the room."""
if not args:
return "Take what? Try 'take [item name]'."
item_name = " ".join(args)
return self.player.take(item_name, self.world)
def cmd_drop(self, args):
"""Drop an item from inventory."""
if not args:
return "Drop what? Try 'drop [item name]'."
item_name = " ".join(args)
return self.player.drop(item_name, self.world)
def cmd_inventory(self, args):
"""Check inventory."""
return self.player.check_inventory()
def cmd_help(self, args):
"""Show help information."""
help_text = "Available commands:\n"
help_text += "- go [direction]: Move in a direction (north, south, east, west)\n"
help_text += "- look: Examine your surroundings\n"
help_text += "- take [item]: Take an item from the room\n"
help_text += "- drop [item]: Drop an item from your inventory\n"
help_text += "- inventory: Check what you're carrying\n"
help_text += "- help: Show this help text\n"
help_text += "- quit: Exit the game\n"
help_text += "- hint: Get a helpful hint (AI-powered)\n"
help_text += "- save [name]: Save your progress\n"
return help_text
def cmd_quit(self, args):
"""Quit the game."""
self.running = False
return "Thanks for playing!"
def save_game(self, filename):
"""Save the current game state."""
game_data = {
"world": self.world.to_dict(),
"player": self.player.to_dict()
}
filepath = os.path.join(self.save_dir, f"{filename}.json")
with open(filepath, 'w') as f:
json.dump(game_data, f, indent=2)
return f"Game saved as '{filename}'"
@classmethod
def load_game(cls, filename):
"""Load a game from a save file."""
filepath = os.path.join("game_saves", f"{filename}.json")
with open(filepath, 'r') as f:
game_data = json.load(f)
world = World.from_dict(game_data["world"])
player = Player.from_dict(game_data["player"])
return cls(player, world)
def process_input(self, user_input):
"""Process user input and return the result."""
words = user_input.lower().split()
if not words:
return "Please enter a command."
command = words[0]
args = words[1:] if len(words) > 1 else []
if command in self.commands:
return self.commands[command](args)
else:
return f"I don't understand '{command}'. Try 'help' for a list of commands."
def run(self):
"""Run the main game loop."""
self.running = True
print(f"Welcome to {self.world.title}!")
print(f"You are {self.player.name}, an adventurer seeking fortune and glory.")
print("Type 'help' for a list of commands.")
# Show the initial room
current_room = self.world.get_room(self.player.current_room)
print("\n" + current_room.get_details())
while self.running:
user_input = input("\n> ").strip()
# Special case for save command
if user_input.startswith("save "):
save_name = user_input[5:].strip()
if save_name:
print(self.save_game(save_name))
else:
print("Please specify a save name: 'save [name]'")
continue
# Special case for AI hint
if user_input.lower() == "hint":
try:
current_room = self.world.get_room(self.player.current_room)
inventory_str = ", ".join(self.player.inventory) if self.player.inventory else "nothing"
hint_prompt = f"""
In this text adventure game:
- The player is in: {current_room.name}
- Room description: {current_room.description}
- Available exits: {', '.join(current_room.exits.keys()) if current_room.exits else 'none'}
- Items in room: {', '.join(current_room.items) if current_room.items else 'none'}
- Player is carrying: {inventory_str}
Based on this situation, provide a gentle hint about what the player might try next.
Keep it vague enough to not spoil puzzles but helpful enough to guide them.
"""
print("Thinking of a hint...")
hint = get_response(hint_prompt)
print(f"\nHint: {hint}")
except Exception as e:
print(f"Sorry, I couldn't come up with a hint right now: {e}")
continue
result = self.process_input(user_input)
print(result)
def create_default_world():
"""Create a simple default world for demonstration."""
world = World("The Forgotten Caverns")
# Create rooms
entrance = Room(
"Cave Entrance",
"You stand at the entrance to a mysterious cave. Sunlight filters in from above, casting eerie shadows on the walls."
)
main_passage = Room(
"Main Passage",
"A narrow passage stretches deeper into the cave. Water drips from the ceiling, creating small puddles on the ground."
)
chamber = Room(
"Crystal Chamber",
"This large chamber is filled with glowing crystals of various colors, illuminating the space with an otherworldly light."
)
side_tunnel = Room(
"Side Tunnel",
"A tight tunnel branches off from the main passage. The air feels stale here."
)
underground_pool = Room(
"Underground Pool",
"A still, dark pool of water fills most of this chamber. The surface reflects the subtle glow from the ceiling."
)
# Connect rooms
entrance.add_exit("north", "Main Passage")
main_passage.add_exit("south", "Cave Entrance")
main_passage.add_exit("north", "Crystal Chamber")
main_passage.add_exit("east", "Side Tunnel")
chamber.add_exit("south", "Main Passage")
chamber.add_exit("west", "Underground Pool")
side_tunnel.add_exit("west", "Main Passage")
underground_pool.add_exit("east", "Crystal Chamber")
# Add items
entrance.add_item("torch")
entrance.add_item("rope")
main_passage.add_item("rusty key")
chamber.add_item("glowing crystal")
underground_pool.add_item("ancient coin")
# Add rooms to world
world.add_room(entrance)
world.add_room(main_passage)
world.add_room(chamber)
world.add_room(side_tunnel)
world.add_room(underground_pool)
return world
def play_adventure_game():
"""Start a new adventure game or load a saved one."""
print("=== Text Adventure Game Engine ===")
print("1. Start new game")
print("2. Load saved game")
choice = input("\nSelect an option: ")
if choice == "1":
player_name = input("\nWhat is your name, adventurer? ")
player = Player(player_name, "Cave Entrance")
world = create_default_world()
game = GameEngine(player, world)
game.run()
elif choice == "2":
# Check for save files
save_dir = "game_saves"
if not os.path.exists(save_dir) or not os.listdir(save_dir):
print("No save files found. Starting a new game...")
player_name = input("\nWhat is your name, adventurer? ")
player = Player(player_name, "Cave Entrance")
world = create_default_world()
game = GameEngine(player, world)
else:
# List save files
save_files = [f[:-5] for f in os.listdir(save_dir) if f.endswith(".json")]
print("\nAvailable save files:")
for i, save in enumerate(save_files, 1):
print(f"{i}. {save}")
save_idx = int(input("\nSelect a save file (number): ")) - 1
if 0 <= save_idx < len(save_files):
try:
game = GameEngine.load_game(save_files[save_idx])
print(f"Loaded save: {save_files[save_idx]}")
except Exception as e:
print(f"Error loading save: {e}")
return
else:
print("Invalid selection. Starting a new game...")
player_name = input("\nWhat is your name, adventurer? ")
player = Player(player_name, "Cave Entrance")
world = create_default_world()
game = GameEngine(player, world)
game.run()
else:
print("Invalid choice. Exiting.")
# Run the game
if __name__ == "__main__":
play_adventure_game()
Extension Ideas¶
- Add more room types with special properties (e.g., dark rooms that require a light source)
- Implement NPCs (non-player characters) that the player can talk to
- Add simple puzzles that require specific items to solve
- Create a quest system with objectives and rewards
- Design a combat system for encounters with enemies
- Build a web-based interface using a framework like Flask