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.
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.handmatch.c: Copy the code for these two files directly from the "The parser" resource you just studied. -
Refactor
game.c(or createparsexec.c):- Define the
COMMANDstruct. - Create the
static const COMMAND commands[]array. - Populate it with patterns for the commands you already have:
quit,look,look around,go A, andinventory. Map them to your existingexecute...functions. Note that you may need to adjust your function signatures, which we'll cover next. - Implement the new
parseAndExecutefunction that iterates through thecommandsarray and callsmatchCommand.
- Define the
-
Modify
executeFunctions: The new parser passes nouns via the globalconst char *params[26];array, not as function arguments. You must refactor your existing functions (executeGo,executeLook) to get their noun fromparams[0]instead of aconst char *nounparameter.For example, your
executeGofunction will change fromvoid executeGo(const char *noun)tobool executeGo(void)and will now find its destination noun inparams[0]. Thehtpataicresource provides clear examples of this refactoring. Look at itsexecuteGoimplementation in thelocation.csection 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 yourcommandsarray, all pointing to yourexecuteLookfunction. - Update
OBJECTstruct: Add a new field for detailed descriptions, e.g.,const char *details;. Populate this for a few objects in yourworld.c. - Update
executeLook: Modify the function to handle both cases:- If the command was
lookorlook 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 objectA. Find this object, check if it's visible (i.e.,distHereordistHeld), and if so, print itsdetailsstring.
- If the command was
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 anexecuteGetfunction. Add a"drop A"pattern pointing to anexecuteDropfunction. - Implement
executeGet(void):- Use a helper function (like
getVisiblefrom the resource) to find the object matching the tag inparams[0]. This function should only find objects that aredistHere. - Perform checks: Is the object valid? Is it actually
distHere? Is it something that can be taken? (You might add atakeableflag to yourOBJECTstruct later, but for now, assume anything is). - If all checks pass, modify the game state:
obj->location = player;. - Print a confirmation message, e.g., "You pick up the shiny brass lamp."
- Use a helper function (like
- Implement
executeDrop(void):- Use a helper function to find the object matching
params[0], but this time it must be in the player's possession (distHeld). - Perform checks: Does the player actually have this item?
- If so, modify the game state:
obj->location = player->location;. This drops the item into the current room. - Print a confirmation message, e.g., "You drop the shiny brass lamp."
- Use a helper function to find the object matching
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
COMMANDstruct provides a clean, data-driven way to link command patterns to their logic. - The
takeanddropcommands are the first actions that modify the object graph by changinglocationpointers. - The
distancesystem remains essential for validating actions. You can onlytakean object that isdistHere, and onlydropan object that isdistHeld.
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.