Skip to Content
Course content

45: Practice Exercise: Building a Text Adventure Engine

Click on the "Edit" button in the top corner of the screen to edit your slide content.

A few years ago, I mentored a developer who decided to build a small text-based RPG for a game jam. He started with a simple if/else structure: if the player is in the "Forest," and they type "north," move them to the "Cave." It worked fine for three rooms. But by the time he hit twelve rooms, his main loop was a 2,000-line nightmare of nested conditionals. He spent more time chasing syntax errors in his giant case statement than actually designing the game. He had built a script, not an engine.

That's the trap we're avoiding today. In Ruby, we don't want to hardcode the story; we want to build a system where the story is just data. By treating rooms, items, and players as objects, we can create a world that expands without making our code more complex.

Mapping the World with Object Relations

Instead of checking strings to see where the player is, we should create a Room class. The secret sauce here is using a Hash to handle navigation. If every room has an exits hash where the keys are directions (like "north" or "down") and the values are references to other Room objects, movement becomes a simple lookup rather than a logic puzzle.

class Room
  attr_accessor :description, :exits

  def initialize(description)
    @description = description
    @exits = {}
  end

  def add_exit(direction, room)
    @exits[direction] = room
  end
end

Now, when the player wants to move, you aren't writing if room == "Kitchen". You're simply asking the current room if it has an exit in that direction and, if so, updating the player's current location to that object. I've found that this "graph" structure makes it incredibly easy to add new areas to your game without ever touching the movement logic again.

Connecting the Player to the Environment

The Player class should be lean. Its primary job is to keep track of the state—specifically, where they are and what they're carrying. I usually suggest keeping the "action" logic inside a game controller or the main loop, but the player object should hold the reference to the current room.

class Player
  attr_accessor :current_room

  def initialize(starting_room)
    @current_room = starting_room
  end
end

By separating the Player from the Room, you can easily implement features like inventory systems or locked doors later on. For example, you could add a has_key? check to the movement logic before allowing the current_room to change. It keeps your concerns separated, which is the hallmark of a maintainable engine.

The Command Loop and Input Parsing

The heartbeat of your engine is the game loop. You need a way to take raw user input, clean it up, and translate it into an action. I've always preferred using gets.chomp.downcase.split. This takes a command like "Go North," turns it into ["go", "north"], and allows you to handle the verb and the noun separately.

The loop should generally follow a strict rhythm: describe the current location, ask for input, process the movement or action, and repeat. If the player types a direction that doesn't exist in the current room's exits hash, you just tell them "You can't go that way" and loop back. It's a simple cycle, but when implemented correctly, it feels like a living world to the user.




📋 Practical Task

Exercise: Implementing the Alchemist's Forgotten Laboratory

Your goal is to build a functioning text adventure engine that allows a player to navigate through a small map. You must implement the following:

  • A Room class that stores a description and a Hash of exits.
  • A Player class that tracks the current_room.
  • A map consisting of at least four rooms: The Entrance, The Brewing Station, The Library, and The Secret Vault.
  • Connect these rooms logically (e.g., The Entrance leads north to the Brewing Station, the Brewing Station leads east to the Library, etc.).
  • A while loop that:
    • Prints the description of the current room.
    • Accepts a direction (e.g., "north", "south", "east", "west") from the user.
    • Moves the player if the exit exists, or prints an error message if it doesn't.
    • Provides a "quit" command to break the loop.

Bonus Challenge: Add a "look" command that reprints the room description and a list of available exits without moving the player.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.