Contents
14. Multiple nouns
|
How to program a text adventure in C
by Ruud Helderman
<r.helderman@hccnet.nl>
Licensed under
MIT License
14. Multiple nouns
Time to prove the new parser is really capable of
interpreting more complex commands.
So far, our parser only accepted simple verb-noun commands.
Let’s try and parse some more complex commands like:
- get coin from box
- put coin in box
- ask coin from guard
- give coin to guard
Adding patterns to parsexec.c seems straightforward:
- get A from B
- put A in B
- ask A from B
- give A to B
But as explained in the previous chapter, similar commands
(like ‘get A from B’ and the already existing ‘get A’)
must be placed in the correct order.
If ‘get A’ would appear first,
then it would consume any command starting with ‘get’,
including all those that were supposed to be matched by the new pattern.
In short, ‘get A from B’ must come before ‘get A’.
Some commands (for example put) do not have a single-noun variant.
Nevertheless, we will add a pattern with a single nonterminal
(‘put A’).
We need this to correctly capture a misspelled noun.
The full pattern ‘put A in B’
will not match a command like ‘put koin in box’.
As explained in the previous chapter,
only a nonterminal at the end of a pattern
is able to capture a misspelled or otherwise unrecognized noun.
The same applies to commands with more than two nouns.
With n nouns,
you need n patterns to handle all possible mismatches.
An example with three parameters:
- “paint A on B with C”
- “paint A on B”
- “paint A”
Again, the order of the patterns is vital:
if “paint A” would be on top (meaning it will be tried first),
then it would consume every paint command,
including those that were meant to be caught by 1 and 2.
For now, we do not need patterns with more than two nonterminals.
Sample output |
Welcome to Little Cave Adventure.
You are in an open field.
You see:
a silver coin
a burly guard
a cave entrance to the east
dense forest all around
--> get coin
You pick up a silver coin.
--> give coin to guard
You give a silver coin to a burly guard.
--> go cave
You walk into the cave.
You are in a little cave.
You see:
an exit to the west
solid rock all around
a closed door to the south
a tiny key
--> get key
You pick up a tiny key.
--> go south
The door is closed.
--> open door
You open a closed door to the south.
--> go south
You walk through the door into a backroom.
You are in a backroom.
You see:
solid rock all around
an open door to the north
a wooden box
--> unlock box
You unlock a wooden box.
--> open box
You open a wooden box.
--> examine box
The box is open.
You see:
a gold coin
--> get gold from box
You get a gold coin from a wooden box.
--> inventory
You have:
a gold coin
a tiny key
--> put gold in box
You put a gold coin in a wooden box.
--> examine box
The box is open.
You see:
a gold coin
--> quit
Bye!
|
parsexec.h |
- extern bool parseAndExecute(const char *input);
|
parsexec.c |
- #include <ctype.h>
- #include <stdbool.h>
- #include <stdio.h>
- #include "object.h"
- #include "misc.h"
- #include "match.h"
- #include "location.h"
- #include "inventory.h"
- #include "inventory2.h"
- #include "openclose.h"
- typedef struct
- {
- const char *pattern;
- bool (*function)(void);
- } COMMAND;
- static bool executeQuit(void)
- {
- return false;
- }
- static bool executeNoMatch(void)
- {
- const char *src = *params;
- int len;
- for (len = 0; src[len] != '\0' && !isspace(src[len]); len++);
- if (len > 0) printf("I don't know how to '%.*s'.\n", len, src);
- return true;
- }
- bool parseAndExecute(const char *input)
- {
- static const COMMAND commands[] =
- {
- { "quit" , executeQuit },
- { "look" , executeLookAround },
- { "look around" , executeLookAround },
- { "look at A" , executeLook },
- { "look A" , executeLook },
- { "examine A" , executeLook },
- { "go to A" , executeGo },
- { "go A" , executeGo },
- { "get A from B" , executeGetFrom },
- { "get A" , executeGet },
- { "put A in B" , executePutIn },
- { "drop A in B" , executePutIn },
- { "drop A" , executeDrop },
- { "ask A from B" , executeAskFrom },
- { "ask A" , executeAsk },
- { "give A to B" , executeGiveTo },
- { "give A" , executeGive },
- { "inventory" , executeInventory },
- { "open A" , executeOpen },
- { "close A" , executeClose },
- { "lock A" , executeLock },
- { "unlock A" , executeUnlock },
- { "A" , executeNoMatch }
- };
- const COMMAND *cmd;
- for (cmd = commands; !matchCommand(input, cmd->pattern); cmd++);
- return (*cmd->function)();
- }
|
In a new module inventory2.c, we implement the new multi-noun commands
get, drop/put, ask and give.
Now we can finally put the gold coin back in the box!
inventory2.h |
- extern bool executeGetFrom(void);
- extern bool executePutIn(void);
- extern bool executeAskFrom(void);
- extern bool executeGiveTo(void);
|
inventory2.c |
- #include <stdbool.h>
- #include <stdio.h>
- #include "object.h"
- #include "match.h"
- #include "noun.h"
- #include "move.h"
- #include "reach.h"
- bool executeGetFrom(void)
- {
- OBJECT *from = reachableObject("where to get that from", params[1]);
- if (from != NULL && getVisible("what you want to get", params[0]) != NULL)
- {
- if (from->health > 0)
- {
- printf("You should ask %s nicely.\n", from->description);
- }
- else
- {
- moveObject(getPossession(from, "get", params[0]), player);
- }
- }
- return true;
- }
- bool executePutIn(void)
- {
- OBJECT *obj = getPossession(player, "put", params[0]);
- if (obj != NULL)
- {
- OBJECT *to = reachableObject("where to put that in", params[1]);
- if (to != NULL)
- {
- if (to->health > 0)
- {
- printf("You should offer that nicely to %s.\n", to->description);
- }
- else
- {
- moveObject(obj, to);
- }
- }
- }
- return true;
- }
- bool executeAskFrom(void)
- {
- OBJECT *from = reachableObject("who to ask that", params[1]);
- if (from != NULL)
- {
- if (from->health > 0)
- {
- if (getVisible("what you want to ask", params[0]) != NULL)
- {
- moveObject(getPossession(from, "ask", params[0]), player);
- }
- }
- else
- {
- printf("There is no response from %s.\n", from->description);
- }
- }
- return true;
- }
- bool executeGiveTo(void)
- {
- OBJECT *obj = getPossession(player, "give", params[0]);
- if (obj != NULL)
- {
- OBJECT *to = reachableObject("who to give that to", params[1]);
- if (to != NULL)
- {
- if (to->health > 0)
- {
- moveObject(obj, to);
- }
- else
- {
- printf("No eagerness is shown by %s.\n", to->description);
- }
- }
- }
- return true;
- }
|
You may notice that commands like ‘put coin in forest’
and ‘put coin in box’ (when the box is closed)
get the following strange reply:
“That is way too heavy.”
That is because most objects
(including the closed box, and scenary like the forest)
have zero capacity to hold other objects.
Rightfully so, but the message is quite inappropriate.
To avoid that, we will introduce a separate message
for objects that have zero capacity:
“It doesn’t seem to fit in.”
However, that particular message is totally misleading
when it comes as a response to ‘put key in box’.
So we will make a special exception for that particular combination of objects.
move.c |
- #include <stdbool.h>
- #include <stdio.h>
- #include "object.h"
- #include "misc.h"
- static int weightOfContents(OBJECT *container)
- {
- int sum = 0;
- OBJECT *obj;
- for (obj = objs; obj < endOfObjs; obj++)
- {
- if (isHolding(container, obj)) sum += obj->weight;
- }
- return sum;
- }
- static void describeMove(OBJECT *obj, OBJECT *to)
- {
- if (to == player->location)
- {
- printf("You drop %s.\n", obj->description);
- }
- else if (to != player)
- {
- printf(to->health > 0 ? "You give %s to %s.\n" : "You put %s in %s.\n",
- obj->description, to->description);
- }
- else if (obj->location == player->location)
- {
- printf("You pick up %s.\n", obj->description);
- }
- else
- {
- printf("You get %s from %s.\n",
- obj->description, obj->location->description);
- }
- }
- void moveObject(OBJECT *obj, OBJECT *to)
- {
- if (obj == NULL)
- {
- // already handled by getVisible or getPossession
- }
- else if (to == NULL)
- {
- printf("There is nobody here to give that to.\n");
- }
- else if (to->capacity == 0)
- {
- printf(obj == keyForBox && (to == closedBox || to == lockedBox) ?
- "The key seems to fit the lock.\n" :
- "It doesn't seem to fit in.\n");
- }
- else if (obj->weight > to->capacity)
- {
- printf("That is way too heavy.\n");
- }
- else if (obj->weight + weightOfContents(to) > to->capacity)
- {
- printf("That would become too heavy.\n");
- }
- else
- {
- describeMove(obj, to);
- obj->location = to;
- }
- }
|
I guess the more complex the commands we accept,
the more effort we need to put into making convincing replies.
Next chapter: 15. Light and dark