There are many possible reasons for expanding the ‘object’ structure.
When we introduced objects in chapter 4, they had only three attributes. In chapter 6, we added a fourth. This is more or less the absolute minimum. To put more detail into our adventure, we need some more attributes. Here are a few examples.
We define seven new attributes in object.txt:
object.txt | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 | #include <stdio.h> #include "object.h" typedef struct object { const char *description; const char **tags; struct object *location; struct object *destination; struct object *prospect; const char *details; const char *contents; const char *textGo; int weight; int capacity; int health; } OBJECT; extern OBJECT objs[]; - field description "an open field" tags "field" details "The field is a nice and quiet place under a clear blue sky.\n" - cave description "a little cave" tags "cave" details "The cave is just a cold, damp, rocky chamber.\n" - silver description "a silver coin" tags "silver", "coin", "silver coin" location field details "The coin has an eagle on the obverse.\n" weight 1 - gold description "a gold coin" tags "gold", "coin", "gold coin" location cave details "The shiny coin seems to be a rare and priceless artefact.\n" weight 1 - guard description "a burly guard" tags "guard", "burly guard" location field details "The guard is a really big fellow.\n" contents "He has" health 100 capacity 20 - player description "yourself" tags "yourself" location field details "You would need a mirror to look at yourself.\n" contents "You have" health 100 capacity 20 - intoCave description "a cave entrance to the east" tags "east", "entrance" location field prospect cave details "The entrance is just a narrow opening in a small outcrop.\n" textGo "The guard stops you from walking into the cave.\n" - exitCave description "a way out to the west" tags "west", "out" location cave destination field details "Sunlight pours in through an opening in the cave's wall.\n" textGo "You walk out of the cave.\n" - wallField description "dense forest all around" tags "west", "north", "south", "forest" location field details "The field is surrounded by trees and undergrowth.\n" textGo "Dense forest is blocking the way.\n" - wallCave description "solid rock all around" tags "east", "north", "south", "rock" location cave details "Carved in stone is a secret password 'abccb'.\n" textGo "Solid rock is blocking the way.\n" |
Explanation:
New attributes also require an adjustment in the code generator.
object.awk | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | BEGIN { count = 0; obj = ""; if (pass == "c2") { print "\nOBJECT objs[] = {"; } } /^- / { outputRecord(","); obj = $2; prop["description"] = "NULL"; prop["tags"] = ""; prop["location"] = "NULL"; prop["destination"] = "NULL"; prop["prospect"] = ""; prop["details"] = "\"You see nothing special.\\n\""; prop["contents"] = "\"You see\""; prop["textGo"] = "\"You can't get any closer than this.\\n\""; prop["weight"] = "99"; prop["capacity"] = "9999"; prop["health"] = "0"; } obj && /^[ \t]+[a-z]/ { name = $1; $1 = ""; if (name in prop) { prop[name] = $0; } else if (pass == "c2") { print "#error \"" FILENAME " line " NR ": unknown attribute '" name "'\""; } } !obj && pass == (/^#include/ ? "c1" : "h") { print; } END { outputRecord("\n};"); if (pass == "h") { print "\n#define endOfObjs\t(objs + " count ")"; } } function outputRecord(separator) { if (obj) { if (pass == "h") { print "#define " obj "\t(objs + " count ")"; } else if (pass == "c1") { print "static const char *tags" count "[] = {" prop["tags"] ", NULL};"; } else if (pass == "c2") { print "\t{\t/* " count " = " obj " */"; print "\t\t" prop["description"] ","; print "\t\ttags" count ","; print "\t\t" prop["location"] ","; print "\t\t" prop["destination"] ","; print "\t\t" prop[prop["prospect"] ? "prospect" : "destination"] ","; print "\t\t" prop["details"] ","; print "\t\t" prop["contents"] ","; print "\t\t" prop["textGo"] ","; print "\t\t" prop["weight"] ","; print "\t\t" prop["capacity"] ","; print "\t\t" prop["health"]; print "\t}" separator; delete prop; } count++; } } |
Now we're all set to start using the new attributes! details is used in a newly recognized command look <object>, and textGo replaces the fixed text “OK” in our implementation of command go.
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 --> look guard The guard is a really big fellow. --> get guard That is way to heavy. --> get coin OK. --> inventory You have: a silver coin --> give coin OK. --> look guard The guard is a really big fellow. He has: a silver coin --> go cave The guard stops you from walking into the cave. --> quit Bye! |
location.c | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 | #include <stdio.h> #include <string.h> #include "object.h" #include "misc.h" void executeLook(const char *noun) { if (noun != NULL && strcmp(noun, "around") == 0) { printf("You are in %s.\n", player->location->description); listObjectsAtLocation(player->location); } else { OBJECT *obj = parseObject(noun); DISTANCE distance = distanceTo(obj); if (distance >= distUnknownObject) { printf("I don't understand what you want to see.\n"); } else if (distance == distNotHere) { printf("You don't see any %s here.\n", noun); } else if (distance == distOverthere) { printf("Too far away, move closer please.\n"); } else if (distance == distHereContained) { printf("Hard to see, try to get it first.\n"); } else { printf("%s", obj->details); listObjectsAtLocation(obj); } } } static void movePlayer(OBJECT *passage) { printf("%s", passage->textGo); if (passage->destination != NULL) { player->location = passage->destination; printf("\n"); executeLook("around"); } } void executeGo(const char *noun) { OBJECT *obj = parseObject(noun); DISTANCE distance = distanceTo(obj); if (distance >= distUnknownObject) { printf("I don't understand where you want to go.\n"); } else if (distance == distLocation) { printf("You are already there.\n"); } else if (distance == distOverthere) { movePlayer(getPassageTo(obj)); } else if (distance == distHere) { movePlayer(obj); } else if (distance < distNotHere) { printf("You can't get any closer than this.\n"); } else { printf("You don't see any %s here.\n", noun); } } |
Attributes weight and capacity together become a possible reason for not being able to move certain objects around.
inventory.c | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 | #include <stdio.h> #include "object.h" #include "misc.h" static void moveObject(const char *noun, OBJECT *from, OBJECT *to) { OBJECT *obj = parseObject(noun); if (obj == NULL) { printf("I don't understand what item you mean.\n"); } else if (from != obj->location) { switch (distanceTo(obj)) { case distPlayer: printf("You should not be doing that to yourself.\n"); break; case distHeld: printf("You already have %s.\n", obj->description); break; case distLocation: case distOverthere: printf("That's not an item.\n"); break; case distHere: if (from == player) { printf("You have no %s.\n", noun); } else { printf("Sorry, %s has no %s.\n", from->description, noun); } break; case distHeldContained: case distHereContained: printf("Sorry, %s is holding it.\n", obj->location->description); break; default: printf("You don't see any %s here.\n", noun); } } else if (to == NULL) { printf("There is nobody here to give that to.\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 { obj->location = to; printf("OK.\n"); } } void executeGet(const char *noun) { moveObject(noun, player->location, player); } void executeDrop(const char *noun) { moveObject(noun, player, player->location); } void executeGive(const char *noun) { moveObject(noun, player, personHere()); } void executeAsk(const char *noun) { moveObject(noun, personHere(), player); } void executeInventory(void) { if (listObjectsAtLocation(player) == 0) { printf("You are empty-handed.\n"); } } |
The weight check makes use of a new function weightOfContents; it will be implemented in misc.c. In the same module, we also make modifications to some of the existing functions, to support the last few attributes.
Attribute health will be used to tell persons apart from ‘dead’ objects. And contents replaces the fixed text “You see”. The original text was already a bit odd when listing the player's inventory, but now that function listObjectsAtLocation is used to display contents of any possible object (see function executeLook above), we really need something more flexible.
By replacing attribute destination by prospect in function getPassageTo, we are improving responses to all commands (not just go and look) applied to a location that is seen lying on the other end of a ‘passage with a twist.’
misc.h | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | typedef enum { distPlayer, distHeld, distHeldContained, distLocation, distHere, distHereContained, distOverthere, distNotHere, distUnknownObject, distNoObjectSpecified } DISTANCE; extern OBJECT *getPassageTo(OBJECT *targetLocation); extern DISTANCE distanceTo(OBJECT *obj); extern OBJECT *parseObject(const char *noun); extern OBJECT *personHere(void); extern int listObjectsAtLocation(OBJECT *location); extern int weightOfContents(OBJECT *container); |
misc.c | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 | #include <stdio.h> #include <string.h> #include "object.h" #include "misc.h" OBJECT *getPassageTo(OBJECT *targetLocation) { OBJECT *obj; for (obj = objs; obj < endOfObjs; obj++) { if (obj->location == player->location && obj->prospect == targetLocation) { return obj; } } return NULL; } DISTANCE distanceTo(OBJECT *obj) { return obj == NULL ? distUnknownObject : obj == player ? distPlayer : obj == player->location ? distLocation : obj->location == player ? distHeld : obj->location == player->location ? distHere : getPassageTo(obj) != NULL ? distOverthere : obj->location == NULL ? distNotHere : obj->location->location == player ? distHeldContained : obj->location->location == player->location ? distHereContained : distNotHere; } static int nounIsInTags(const char *noun, const char **tags) { while (*tags != NULL) { if (strcmp(noun, *tags++) == 0) return 1; } return 0; } OBJECT *parseObject(const char *noun) { OBJECT *obj, *found = NULL; for (obj = objs; obj < endOfObjs; obj++) { if (noun != NULL && nounIsInTags(noun, obj->tags) && distanceTo(obj) < distanceTo(found)) { found = obj; } } return found; } OBJECT *personHere(void) { OBJECT *obj; for (obj = objs; obj < endOfObjs; obj++) { if (distanceTo(obj) == distHere && obj->health > 0) { return obj; } } return NULL; } int listObjectsAtLocation(OBJECT *location) { int count = 0; OBJECT *obj; for (obj = objs; obj < endOfObjs; obj++) { if (obj != player && obj->location == location) { if (count++ == 0) { printf("%s:\n", location->contents); } printf("%s\n", obj->description); } } return count; } int weightOfContents(OBJECT *container) { int sum = 0; OBJECT *obj; for (obj = objs; obj < endOfObjs; obj++) { if (obj->location == container) sum += obj->weight; } return sum; } |
To make the whole picture complete, it would be nice to expand the generated map from the previous chapter with dashed lines for the ‘apparent’ passages (leading towards a prospect).
map.awk | |
1 2 3 4 5 6 7 8 9 10 11 12 | BEGIN { print "digraph map {"; } /^- / { outputEdge(); location = destination = prospect = ""; } $1 == "location" { location = $2; } $1 == "destination" { destination = $2; } $1 == "prospect" { prospect = $2; } END { outputEdge(); print "}"; } function outputEdge() { if (location && destination) print "\t" location " -> " destination; if (location && prospect) print "\t" location " -> " prospect " [style=dashed]"; } |
Notes:
Next chapter: 11. Conditions