# 8.移动方向 传统的文本冒险使用指南针方向进行导航。 例如,我们在第 6 章中绘制的地图上,玩家可能想向东移动,从田野移动到洞穴。我们可以通过给连接Cave的通道标上“east”来实现这一点。但是,我们首先需要解决两个问题。 1. 我们可能仍然想把这段通道称为“entrance”和“east”。但现在,一个对象只能有一个标签。 2. 在更大的地图上,具有更多的位置和道路,标签“east”将被定义多次。到目前为止,标签在我们的游戏中是全球独一无二的,没有重复项。 思考题:你能否想出解决办法? 这些问题同样适用于其他对象,而不仅仅是通道。 在我们的冒险中,我们有一枚银币和一枚金币。一方面,如果不接受在只有一枚硬币存在的地点得到硬币,那将是愚蠢的。 另一方面,在两种硬币都在同一位置存在的情况下,玩家应该可以有两个拾取选择。 这立即将我们带到了解析器的第三个问题: 1. 一个标签只能是一个单词;“ sliver coin”这是俩单词,他不接受啊 所有三个问题都将在本章中解决,从问题#1 开始。它通过为每个对象提供一个标签列表来解决,而不仅仅是一个标签。 # object.h ```c typedef struct object { const char *description; const char **tags; struct object *location; struct object *destination; } OBJECT; extern OBJECT objs[]; #define field (objs + 0) #define cave (objs + 1) #define silver (objs + 2) #define gold (objs + 3) #define guard (objs + 4) #define player (objs + 5) #define intoCave (objs + 6) #define exitCave (objs + 7) #define wallField (objs + 8) #define wallCave (objs + 9) #define endOfObjs (objs + 10) ``` # object.c ```c #include #include "object.h" static const char *tags0[] = {"field", NULL}; static const char *tags1[] = {"cave", NULL}; static const char *tags2[] = {"silver", "coin", "silver coin", NULL}; static const char *tags3[] = {"gold", "coin", "gold coin", NULL}; static const char *tags4[] = {"guard", "burly guard", NULL}; static const char *tags5[] = {"yourself", NULL}; static const char *tags6[] = {"east", "entrance", NULL}; static const char *tags7[] = {"west", "exit", NULL}; static const char *tags8[] = {"west", "north", "south", "forest", NULL}; static const char *tags9[] = {"east", "north", "south", "rock", NULL}; //我们不固定标签长度,在结束的时候用NULL来标记 OBJECT objs[] = { {"an open field" , tags0, NULL , NULL }, {"a little cave" , tags1, NULL , NULL }, {"a silver coin" , tags2, field, NULL }, {"a gold coin" , tags3, cave , NULL }, {"a burly guard" , tags4, field, NULL }, {"yourself" , tags5, field, NULL }, {"a cave entrance to the east", tags6, field, cave }, {"an exit to the west" , tags7, cave , field }, {"dense forest all around" , tags8, field, NULL }, {"solid rock all around" , tags9, cave , NULL } //我们用NULL来阻绝进入一个你不知道的地方 }; ``` 当然,要让这个改动生效,我们还需要调整noun.c中的objectHasTag函数。 同时,我们将让函数 getVisiblegetPossession 告知玩家他必须更具体的选择你到底是银币还是金币 ,而不是随机选择任何一个对象。 # noun.h ```c extern OBJECT *getVisible(const char *intention, const char *noun); extern OBJECT *getPossession(OBJECT *from, const char *verb, const char *noun); ``` # noun.c ```c #include #include #include #include "object.h" #include "misc.h" static bool objectHasTag(OBJECT *obj, const char *noun) { if (noun != NULL && *noun != '\0') { const char **tag; for (tag = obj->tags; *tag != NULL; tag++) { if (strcmp(*tag, noun) == 0) return true; }//扫描对象的tag列表 } return false; } static OBJECT ambiguousNoun;//我们需要这玩意的地址帮助我们把它当成一个返回值 static OBJECT *getObject(const char *noun, OBJECT *from, DISTANCE maxDistance) { OBJECT *obj, *res = NULL; for (obj = objs; obj < endOfObjs; obj++) { if (objectHasTag(obj, noun) && getDistance(from, obj) <= maxDistance) { res = res == NULL ? obj : &ambiguousNoun;//标签不明确的解决方案哦 } } return res; } OBJECT *getVisible(const char *intention, const char *noun) { OBJECT *obj = getObject(noun, player, distOverthere); if (obj == NULL) { if (getObject(noun, player, distNotHere) == NULL) { printf("I don't understand %s.\n", intention); } else { printf("You don't see any %s here.\n", noun); } } else if (obj == &ambiguousNoun)//模糊匹配 { printf("Please be specific about which %s you mean.\n", noun); obj = NULL; } return obj; } OBJECT *getPossession(OBJECT *from, const char *verb, const char *noun) { OBJECT *obj = NULL; if (from == NULL) { printf("I don't understand who you want to %s.\n", verb); } else if ((obj = getObject(noun, from, distHeldContained)) == NULL) { if (getObject(noun, player, distNotHere) == NULL) { printf("I don't understand what you want to %s.\n", verb); } else if (from == player) { printf("You are not holding any %s.\n", noun); } else { printf("There appears to be no %s you can get from %s.\n", noun, from->description); } } else if (obj == &ambiguousNoun)//模糊匹配 { printf("Please be specific about which %s you want to %s.\n", noun, verb); obj = NULL; } else if (obj == from) { printf("You should not be doing that to %s.\n", obj->description); obj = NULL; } return obj; } ``` 问题 #3 可以通过从函数parseAndExecute中删除一个 [空格](http://en.wikipedia.org/wiki/Space_(punctuation))字符来解决(下面的第 10 行)。这个解决方案远非完美('silver' 和 'coin' 之间的双空格是大咩的),但直到我们在第 13 章中让自己成为一个更好的解析器之前。 # parsexec.c ```c #include #include #include #include "location.h" #include "inventory.h" bool parseAndExecute(char *input) { char *verb = strtok(input, " \n"); char *noun = strtok(NULL, "\n"); if (verb != NULL) { if (strcmp(verb, "quit") == 0) { return false; } else if (strcmp(verb, "look") == 0) { executeLook(noun); } else if (strcmp(verb, "go") == 0) { executeGo(noun); } else if (strcmp(verb, "get") == 0) { executeGet(noun); } else if (strcmp(verb, "drop") == 0) { executeDrop(noun); } else if (strcmp(verb, "give") == 0) { executeGive(noun); } else if (strcmp(verb, "ask") == 0) { executeAsk(noun); } else if (strcmp(verb, "inventory") == 0) { executeInventory(); } else { printf("I don't know how to '%s'.\n", verb); } } return true; } ``` 模块main.cinventory.clocation.cmove.cmisc.c保持不变 现在对象数组 ( object.c ) 开始在多个维度上增长(特别是在引入多个标签的情况下),我们需要一种使其更易于维护的方法。 猜猜看该怎么办?