Ruby
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Methods and Blocks
-
Section 4: Object-Oriented Ruby
-
Section 5: Metaprogramming
-
Section 6: Working with Data and Files
-
Section 7: Ruby Frameworks Overview
-
Section 8: Ecosystem and Testing
-
Section 9: Practical Projects
-
Section 10: Interview Practice
-
Section 11: Data Structures and Algorithms in Ruby
-
Section 12: More Practice Exercises
-
Section 13: Enumerable and Functional Style
-
Section 14: More OOP Practice
-
Section 15: More Testing
-
Section 16: Enumerable and Comparable Modules In Depth
-
Section 17: Ruby Standard Library: Core Utilities
-
Section 18: Ruby Standard Library: Data and Security
-
Section 19: Ruby Standard Library: CLI and Text
-
Section 20: Ruby Networking
-
Section 21: Ruby on Rails Deep Dive
-
Section 22: Ruby Metaprogramming Deep Dive
-
Section 23: Ruby Design Patterns
-
Section 24: Ruby Concurrency
-
Section 25: Ruby Testing Deep Dive
-
Section 26: Ruby Gems and Packaging
-
Section 27: Ruby Performance
-
Section 28: More Practice Exercises
-
Section 29: More Interview Practice
-
Section 30: Rails API Development
-
Section 31: Rails Authentication and Authorization
-
Section 32: Rails Testing Deep Dive
-
Section 33: Rails Performance
-
Section 34: Rails Deployment
-
Section 35: Sinatra and Lightweight Ruby Web Apps
-
Section 36: More Ruby Language Deep Dive
-
Section 37: Ruby 3.x Modern Features
-
Section 38: More Data Structures in Ruby
-
Section 39: More Practical Projects
-
Section 40: Ruby Ecosystem Tools
-
Section 41: More Practice and Review
-
Section 42: Final Practice and Mastery
-
Section 43: Ruby Interview Deep Dive
-
Section 44: Ruby Background Processing Deep Dive
-
Section 45: Ruby GraphQL
-
Section 46: Ruby Object Model Deep Dive
-
Section 47: Ruby Hanami Framework Overview
-
Section 48: Ruby gRPC and Protocol Buffers
-
Section 49: Ruby Data Processing
-
Section 50: Ruby Search Integration
-
Section 51: Ruby File Upload and Media
-
Section 52: Ruby Email and Notifications
-
Section 53: Ruby Admin Panels
-
Section 54: Ruby Feature Flags and Experimentation
-
Section 55: Ruby Monitoring and Observability
-
Section 56: Ruby Docker and Deployment Deep Dive
-
Section 57: Ruby Security Deep Dive
-
Section 58: More Advanced Metaprogramming
-
Section 59: More Final Projects
45: Practice Exercise: Building a Text Adventure Engine
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
Roomclass that stores a description and a Hash of exits. - A
Playerclass that tracks thecurrent_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
whileloop 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.
There are no comments for now.