Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 67

Implementing Core Game Command Handlers

Recommended resources

Welcome back to the capstone project. In our last lesson, you built the player's "senses" by implementing the go, look around, and inventory commands. You saw how our data-driven architecture, particularly the getDistance function, provides an elegant way to query the game world and perceive the state of objects relative to the player.

This lesson marks a crucial transition from perception to interaction. You will implement the take and drop commands, which for the first time will actively change the game state by manipulating the object graph. To support more expressive commands like look at sword, we will also overhaul our simple parser, replacing it with a more powerful and flexible pattern-matching system. This upgrade is a significant step towards the feel of a professional text adventure.

The Limits of a Simple Parser

Our current parser, likely based on a simple strtok call, has served its purpose but has two major weaknesses we need to address: its destructive nature and its syntactic simplicity.

First, functions like strtok modify the string they are parsing. This is often undesirable, as you might need the original input for logging, debugging, or re-parsing. A common professional practice is to work on a copy.

More C string parsing with strtok_r, strsep (and strdup)

This video from Jacob Sorber's channel on C programming discusses the practical issues with standard C string parsing functions.

Please watch the section from this timestamp. The key takeaway is that these parsing functions are "destructive." The presenter introduces the strdup() function, which creates a heap-allocated duplicate of a string, as a clean solution to this problem. We should adopt this practice in our own code.

Second, a "verb-noun" parser is too restrictive. It can't distinguish between look (which should be interpreted as look around) and look at sword. To handle more natural language, we need a more sophisticated approach. Richard Bartle's classic text on adventure games outlines the kind of grammatical structures a good parser should handle.

Compute!'s Guide to Adventure Games - Richard A. Bartle

This guide provides the theoretical foundation for what we are building.

First, read the section on parsing. This will familiarize you with concepts like direct and indirect objects, adjectives, and prepositions in the context of game commands. Next, read the section on Action Routines. This explains how the parsed command (tokens) is handed off to functions that execute the logic, check preconditions (e.g., "is the object here?"), and produce output. This is exactly the model we will be implementing.


A More Powerful Parser: Pattern Matching

To overcome these limitations, we will replace our current parser with a pattern-matching engine. Instead of just splitting words, this parser will attempt to match the user's input against a predefined list of command patterns, such as look at A or put A in B.

This is a significant architectural upgrade, and the guide "How to Program a Text Adventure in C" provides a complete and brilliant implementation that we will adopt.

13. The parser

This resource details a full implementation of a pattern-based parser in C. We will be using this as the blueprint for our new parser.

First, read the introductory section. It explains the core concept: a grammar made of patterns (e.g., go A) where uppercase letters are nonterminal symbols that match any object tag. It also introduces the idea of a global params array to store the captured nouns. Next, study the code in the section that shows the implementation of parseAndExecute. Pay close attention to the COMMAND struct, which pairs a pattern string with a function pointer. The main loop iterates through an array of these commands, trying to find a match. Finally, read through the implementation of the matching logic itself in match.c. This is the heart of the engine. Trace how it handles spaces, literal words (terminals), and captures object tags into the params array (nonterminals).


Your Implementation Task

Your task is to refactor your project to use this new parser and then implement the take and drop commands.

1. Refactor Your Parser

First, replace your existing parseAndExecute function with the new pattern-matching system.

  • Create match.h and match.c: Copy the code for these two files directly from the "The parser" resource you just studied.

  • Refactor game.c (or create parsexec.c):

    • Define the COMMAND struct.
    • Create the static const COMMAND commands[] array.
    • Populate it with patterns for the commands you already have: quit, look, look around, go A, and inventory. Map them to your existing execute... functions. Note that you may need to adjust your function signatures, which we'll cover next.
    • Implement the new parseAndExecute function that iterates through the commands array and calls matchCommand.
  • Modify execute Functions: The new parser passes nouns via the global const char *params[26]; array, not as function arguments. You must refactor your existing functions (executeGo, executeLook) to get their noun from params[0] instead of a const char *noun parameter.

    For example, your executeGo function will change from void executeGo(const char *noun) to bool executeGo(void) and will now find its destination noun in params[0]. The htpataic resource provides clear examples of this refactoring. Look at its executeGo implementation in the location.c section for a guide.

2. Enhance the look Command

With the new parser, we can now easily implement look at <object>.

  • Add patterns: Add "look at A", "look A", and "examine A" to your commands array, all pointing to your executeLook function.
  • Update OBJECT struct: Add a new field for detailed descriptions, e.g., const char *details;. Populate this for a few objects in your world.c.
  • Update executeLook: Modify the function to handle both cases:
    • If the command was look or look around, params[0] will be an empty string. In this case, perform the original action of describing the room and listing its contents.
    • If the command matched look at A, params[0] will contain the tag of object A. Find this object, check if it's visible (i.e., distHere or distHeld), and if so, print its details string.

3. Implement take and drop

Now for the main event: interaction. We'll create executeGet and executeDrop.

  • Add patterns: Add "get A" and "take A" patterns pointing to an executeGet function. Add a "drop A" pattern pointing to an executeDrop function.
  • Implement executeGet(void):
    1. Use a helper function (like getVisible from the resource) to find the object matching the tag in params[0]. This function should only find objects that are distHere.
    2. Perform checks: Is the object valid? Is it actually distHere? Is it something that can be taken? (You might add a takeable flag to your OBJECT struct later, but for now, assume anything is).
    3. If all checks pass, modify the game state: obj->location = player;.
    4. Print a confirmation message, e.g., "You pick up the shiny brass lamp."
  • Implement executeDrop(void):
    1. Use a helper function to find the object matching params[0], but this time it must be in the player's possession (distHeld).
    2. Perform checks: Does the player actually have this item?
    3. If so, modify the game state: obj->location = player->location;. This drops the item into the current room.
    4. Print a confirmation message, e.g., "You drop the shiny brass lamp."

The inventory.c file in the "The parser" resource provides excellent, complete examples for executeGet and executeDrop. It is highly recommended that you model your implementation on them.


Conclusion

This lesson is a major leap forward for your capstone project. By implementing a sophisticated pattern-matching parser, you've laid the groundwork for a much richer command vocabulary. More importantly, with take and drop, your game is no longer a static world to be observed, but a dynamic environment that the player can directly influence.

Key Takeaways:

  • A pattern-matching parser is more flexible and powerful than simple string splitting, enabling more natural commands.
  • Using function pointers in a COMMAND struct provides a clean, data-driven way to link command patterns to their logic.
  • The take and drop commands are the first actions that modify the object graph by changing location pointers.
  • The distance system remains essential for validating actions. You can only take an object that is distHere, and only drop an object that is distHeld.

In our next lesson, we will address a critical feature for any real game: persistence. You will learn how to save the entire game state—including the current locations of all objects—to a file, and how to load it back, so the player's progress isn't lost when the program closes.